Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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 flag_optimizations

import (
"fmt"
"log"
"os"
"path"
"strconv"
"strings"
"testing"

"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"
"golang.org/x/sys/unix"
)

type MaxRequestSizeReadSuite struct {
suite.Suite
flags []string
}

func (s *MaxRequestSizeReadSuite) SetupSuite() {
mustMountGCSFuseAndSetupTestDir(s.flags, testEnv.ctx, testEnv.storageClient)
}

func (s *MaxRequestSizeReadSuite) TearDownSuite() {
tearDownOptimizationTest(s.T())
}

func (s *MaxRequestSizeReadSuite) TestKernelReaderLargeRead16MiB() {
// 1. Check if the host kernel / FUSE connection supports max_pages >= 4096 (16 MiB / 4 KiB per page = 4096 pages).
if data, err := os.ReadFile("/proc/sys/fs/fuse/max_pages_limit"); err == nil {
if limit, err := strconv.Atoi(strings.TrimSpace(string(data))); err == nil && limit < 4096 {
s.T().Skipf("Skipping TestKernelReaderLargeRead16MiB: /proc/sys/fs/fuse/max_pages_limit (%d) is less than 4096 pages (16 MiB)", limit)
}
}
var stat unix.Stat_t
if err := unix.Stat(setup.MntDir(), &stat); err == nil {
devMinor := unix.Minor(stat.Dev)
maxPagesPath := fmt.Sprintf("/sys/fs/fuse/connections/%d/max_pages", devMinor)
if data, err := os.ReadFile(maxPagesPath); err == nil {
if limit, err := strconv.Atoi(strings.TrimSpace(string(data))); err == nil && limit < 4096 {
s.T().Skipf("Skipping TestKernelReaderLargeRead16MiB: connection max_pages (%d) is less than 4096 pages (16 MiB)", limit)
}
}
}

// 2. Create 16 MiB test file in GCS.
testName := strings.ReplaceAll(s.T().Name(), "/", "_")
fileName := path.Join(testEnv.testDirPath, testName+"_16MiB.txt")
operations.CreateFileOfSize(16*1024*1024, fileName, s.T())
s.T().Cleanup(func() {
_ = os.Remove(fileName)
})

// 3. Truncate log file to record only the read operation.
err := os.Truncate(setup.LogFile(), 0)
require.NoError(s.T(), err, "Failed to truncate log file")

// 4. Perform a 16 MiB read.
content, err := os.ReadFile(fileName)
require.NoError(s.T(), err, "Failed to read 16 MiB file")
require.Len(s.T(), content, 16*1024*1024)

// 5. Verify in gcsfuse trace log that exactly 1 read request of 16 MiB (16777216 bytes) occurred.
logContent, err := os.ReadFile(setup.LogFile())
require.NoError(s.T(), err, "Failed to read log file after read")

lines := strings.Split(string(logContent), "\n")
var readRequestCount int
for _, line := range lines {
if strings.Contains(line, "<- ReadFile") {
readRequestCount++
assert.Contains(s.T(), line, "16777216", "Expected read request to be 16 MiB (16777216 bytes), but got line: %s", line)
Comment thread
abhishek10004 marked this conversation as resolved.
Outdated
}
}
assert.Equal(s.T(), 1, readRequestCount, "Expected exactly 1 read request of 16 MiB when kernel reader and fuse max pages support are enabled, got %d", readRequestCount)
}

func TestKernelReaderLargeReadSuite(t *testing.T) {
if setup.IsDynamicMount(testEnv.mountDir, testEnv.rootDir) {
t.Skip("Skipping test for dynamic mounting")
}
flagsSet := setup.BuildFlagSets(testEnv.cfg, testEnv.bucketType, t.Name())
for _, flags := range flagsSet {
t.Run(strings.Join(flags, "_"), func(t *testing.T) {
log.Printf("Running tests with flags: %s", flags)
s := &MaxRequestSizeReadSuite{
flags: flags,
}
suite.Run(t, s)
})
}
}
18 changes: 18 additions & 0 deletions tools/integration_tests/test_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,15 @@ write_large_files:
- mounted_directory: "${MOUNTED_DIR}"
test_bucket: "${BUCKET_NAME}"
configs:
- run: TestWriteMaxRequestSize16MiB
flags:
- "--enable-kernel-reader=true,--fuse-max-request-size-kb=16384,--log-severity=trace,--client-protocol=http1"
- "--enable-kernel-reader=true,--fuse-max-request-size-kb=16384,--log-severity=trace,--client-protocol=grpc"
compatible:
flat: true
hns: true
zonal: false
run_on_gke: true
- flags:
- "--enable-streaming-writes=false,--client-protocol=http1"
- "--enable-streaming-writes=false,--client-protocol=grpc"
Expand Down Expand Up @@ -1355,6 +1364,15 @@ flag_optimizations:
- mounted_directory: "${MOUNTED_DIR}"
test_bucket: "${BUCKET_NAME}"
configs:
- run: TestKernelReaderLargeReadSuite
flags:
- "--enable-kernel-reader=true,--fuse-max-request-size-kb=16384,--log-severity=trace,--client-protocol=http1"
Comment thread
abhishek10004 marked this conversation as resolved.
- "--enable-kernel-reader=true,--fuse-max-request-size-kb=16384,--log-severity=trace,--client-protocol=grpc"
compatible:
flat: true
hns: true
zonal: false
run_on_gke: true
- run: TestMountFails
flags:
- "--profile=unknown-profile,--client-protocol=http1"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT 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 (
"os"
"path"
"strings"
"syscall"
"testing"

"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"
)

const (
writeFileStartMsg = "<- WriteFile"
sixteenMiB = 16 * 1024 * 1024
Comment thread
abhishek10004 marked this conversation as resolved.
Outdated
oneMiBInBytes = "1048576"
)

func TestWriteMaxRequestSize16MiB(t *testing.T) {
logContent, err := os.ReadFile(setup.LogFile())
require.NoError(t, err, "Failed to read log file")
if !strings.Contains(string(logContent), "--fuse-max-request-size-kb=16384") {
t.Skip("Skipping TestWriteMaxRequestSize16MiB: --fuse-max-request-size-kb=16384 is not set in the mount config")
}

// Setup test directory and target file.
writeDir := setup.SetupTestDirectory("dirForMaxRequestSizeWrite")
filePath := path.Join(writeDir, "16MiB_write_test_"+setup.GenerateRandomString(5)+".txt")

// Truncate the log file right before writing so only our write operations are logged.
err = os.Truncate(setup.LogFile(), 0)
require.NoError(t, err, "Failed to truncate log file")

// Generate 16 MiB of random data.
data, err := operations.GenerateRandomData(sixteenMiB)
require.NoError(t, err, "Failed to generate random data")

// Open file with O_DIRECT so writes go straight to FUSE without kernel page-cache buffering.
f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC|syscall.O_DIRECT, setup.FilePermission_0600)
require.NoError(t, err, "Failed to open file for direct write")

// Write 16 MiB in a single buffer call.
n, err := f.Write(data)
require.NoError(t, err, "Failed to write 16 MiB to file")
require.Equal(t, sixteenMiB, n)
Comment thread
abhishek10004 marked this conversation as resolved.

err = f.Close()
require.NoError(t, err, "Failed to close file")

// Read log content after write.
logContent, err = os.ReadFile(setup.LogFile())
require.NoError(t, err, "Failed to read log file after write")

lines := strings.Split(string(logContent), "\n")
var writeRequestCount int
for _, line := range lines {
if strings.Contains(line, writeFileStartMsg) {
writeRequestCount++
assert.Contains(t, line, oneMiBInBytes, "Expected write request to be 1 MiB (%s bytes), but got line: %s", oneMiBInBytes, line)
}
}
assert.Equal(t, 16, writeRequestCount, "Expected exactly 16 write requests of 1 MiB each for 16 MiB write, got %d", writeRequestCount)
}
Loading