fix(s3): add x-amz-tagging-count header to HEAD object response (#1568)

Co-authored-by: houseme <housemecn@gmail.com>
This commit is contained in:
安正超
2026-01-21 20:45:06 +08:00
committed by GitHub
parent ae50760fcc
commit 9fc1c264b0
4 changed files with 189 additions and 25 deletions
+23 -2
View File
@@ -116,7 +116,7 @@ use rustfs_utils::{
http::{
AMZ_BUCKET_REPLICATION_STATUS, AMZ_CHECKSUM_MODE, AMZ_CHECKSUM_TYPE,
headers::{
AMZ_DECODED_CONTENT_LENGTH, AMZ_OBJECT_TAGGING, AMZ_RESTORE_EXPIRY_DAYS, AMZ_RESTORE_REQUEST_DATE,
AMZ_DECODED_CONTENT_LENGTH, AMZ_OBJECT_TAGGING, AMZ_RESTORE_EXPIRY_DAYS, AMZ_RESTORE_REQUEST_DATE, AMZ_TAG_COUNT,
RESERVED_METADATA_PREFIX, RESERVED_METADATA_PREFIX_LOWER,
},
},
@@ -3040,6 +3040,15 @@ impl S3 for FS {
let content_language = metadata_map.get("content-language").cloned();
let expires = info.expires.map(Timestamp::from);
// Calculate tag count from user_tags already in ObjectInfo
// This avoids an additional API call since user_tags is already populated by get_object_info
let tag_count = if !info.user_tags.is_empty() {
let tag_set = decode_tags(&info.user_tags);
tag_set.len()
} else {
0
};
let output = HeadObjectOutput {
content_length: Some(content_length),
content_type,
@@ -3076,7 +3085,19 @@ impl S3 for FS {
// interact with objects (PUT/POST/DELETE/LIST, etc.) rely on the system-level
// CORS layer instead. In case both are applicable, this bucket-level CORS logic
// takes precedence for these read operations.
let response = wrap_response_with_cors(&bucket, &req.method, &req.headers, output).await;
let mut response = wrap_response_with_cors(&bucket, &req.method, &req.headers, output).await;
// Add x-amz-tagging-count header if object has tags
// Per S3 API spec, this header should be present in HEAD object response when tags exist
if tag_count > 0 {
let header_name = http::HeaderName::from_static(AMZ_TAG_COUNT);
if let Ok(header_value) = tag_count.to_string().parse::<http::HeaderValue>() {
response.headers.insert(header_name, header_value);
} else {
warn!("Failed to parse x-amz-tagging-count header value, skipping");
}
}
let result = Ok(response);
let _ = helper.complete(&result);
+165 -22
View File
@@ -12,11 +12,20 @@ Run the local S3 compatibility test script:
The script will automatically:
1. Build RustFS (if needed)
2. Start the service
3. Wait for it to be ready
4. Run S3 compatibility tests
5. Collect results in `artifacts/s3tests-single/`
1. **Check prerequisites**: Verify port availability (unless in existing mode)
2. **Build/Start RustFS**:
- Build mode (default): Compile with `cargo build --release` (skips if binary is recent < 30 min)
- Binary mode: Use specified or default binary path
- Docker mode: Build Docker image and start container
- Existing mode: Skip startup, connect to running service
3. **Wait for readiness**: Multi-step health check (process/container → port → log → S3 API)
4. **Prepare environment**:
- Generate s3tests configuration from template
- Provision alt user via admin API
- Clone s3-tests repository if missing
- Install missing dependencies (awscurl, tox, gettext)
5. **Run tests**: Execute ceph s3-tests via tox with configured filters
6. **Collect results**: Save logs and test results in `artifacts/s3tests-${TEST_MODE}/`
## Deployment Modes
@@ -35,7 +44,13 @@ DEPLOY_MODE=build ./scripts/s3-tests/run.sh
./scripts/s3-tests/run.sh --no-cache
```
**Note**: In build mode, if the binary exists and was compiled less than 30 minutes ago, compilation will be skipped unless `--no-cache` is specified.
**Behavior**:
- Automatically compiles RustFS binary if it doesn't exist
- If binary exists and was compiled less than **30 minutes** ago, compilation is skipped (unless `--no-cache` is specified)
- Automatically starts the service after compilation
- Automatically clones s3-tests repository if missing
- Automatically installs missing dependencies (awscurl, tox, gettext)
- **Automatic Cleanup**: Process is automatically stopped when script exits
### 2. Binary File Mode
@@ -49,6 +64,13 @@ DEPLOY_MODE=binary ./scripts/s3-tests/run.sh
DEPLOY_MODE=binary RUSTFS_BINARY=./target/release/rustfs ./scripts/s3-tests/run.sh
```
**Behavior**:
- Uses existing binary file (must exist, script will not compile)
- Automatically starts the service using the specified binary
- Automatically clones s3-tests repository if missing
- Automatically installs missing dependencies (awscurl, tox, gettext)
- **Automatic Cleanup**: Process is automatically stopped when script exits
### 3. Docker Mode
Build Docker image and run in container:
@@ -57,6 +79,13 @@ Build Docker image and run in container:
DEPLOY_MODE=docker ./scripts/s3-tests/run.sh
```
**Behavior**:
- Automatically builds Docker image using `Dockerfile.source`
- Creates Docker network (`rustfs-net`) if it doesn't exist
- Automatically clones s3-tests repository if missing
- Automatically installs missing dependencies (awscurl, tox, gettext)
- **Automatic Cleanup**: Container and network are automatically removed when script exits
### 4. Existing Service Mode
Connect to an already running RustFS service:
@@ -68,6 +97,18 @@ DEPLOY_MODE=existing S3_HOST=127.0.0.1 S3_PORT=9000 ./scripts/s3-tests/run.sh
DEPLOY_MODE=existing S3_HOST=192.168.1.100 S3_PORT=9000 ./scripts/s3-tests/run.sh
```
**Behavior**:
- Skips service startup and port availability checks
- Connects directly to the specified service endpoint
- Automatically clones s3-tests repository if missing
- Automatically installs missing dependencies (awscurl, tox, gettext)
- **Note**: The service must already have the alt user (`rustfsalt`) provisioned, or the script will provision it automatically
**Automatic Cleanup**: The script uses trap handlers to automatically clean up resources when it exits (success or failure):
- Stops RustFS process (build/binary mode)
- Stops and removes Docker container (docker mode)
- Removes Docker network (docker mode)
## Configuration Options
### Command Line Options
@@ -102,7 +143,10 @@ DEPLOY_MODE=existing S3_HOST=192.168.1.100 S3_PORT=9000 ./scripts/s3-tests/run.s
- `TEST_MODE`: Test mode (default: `single`)
- `MAXFAIL`: Stop after N failures (default: `1`)
- `XDIST`: Enable parallel execution with N workers (default: `0`, disabled)
- `MARKEXPR`: pytest marker expression for filtering tests (default: exclude unsupported features)
- `MARKEXPR`: pytest marker expression for filtering tests
- Default: `not lifecycle and not versioning and not s3website and not bucket_logging and not encryption`
- Excludes features not yet supported by RustFS to reduce test execution time
- Can be customized to test specific features or remove exclusions
### Configuration Files
@@ -119,19 +163,28 @@ DEPLOY_MODE=existing S3_HOST=192.168.1.100 S3_PORT=9000 ./scripts/s3-tests/run.s
```bash
# Basic usage - compiles and runs automatically
# Skips compilation if binary exists and is less than 30 minutes old
./scripts/s3-tests/run.sh
# Force rebuild (skip cache check)
# Force rebuild (skip cache check, always compile)
./scripts/s3-tests/run.sh --no-cache
# Run all tests, stop after 50 failures
MAXFAIL=50 ./scripts/s3-tests/run.sh
# Enable parallel execution (4 worker processes)
# Automatically installs pytest-xdist if needed
XDIST=4 ./scripts/s3-tests/run.sh
# Use custom data storage location
# Data will be stored in /tmp/test-data/rustfs-single/
DATA_ROOT=/tmp ./scripts/s3-tests/run.sh
# Run specific test markers (e.g., test multipart uploads only)
MARKEXPR="multipart" ./scripts/s3-tests/run.sh
# Remove feature exclusions (test all features, including unsupported ones)
MARKEXPR="" ./scripts/s3-tests/run.sh
```
### Binary File Mode
@@ -163,14 +216,20 @@ DEPLOY_MODE=docker XDIST=4 ./scripts/s3-tests/run.sh
### Existing Service Mode
```bash
# Connect to locally running service
# Connect to locally running service (default: 127.0.0.1:9000)
DEPLOY_MODE=existing ./scripts/s3-tests/run.sh
# Connect to remote service
DEPLOY_MODE=existing S3_HOST=192.168.1.100 S3_PORT=9000 ./scripts/s3-tests/run.sh
# Test specific features
# Test specific features (custom marker expression)
DEPLOY_MODE=existing MARKEXPR="not lifecycle and not versioning" ./scripts/s3-tests/run.sh
# Use custom credentials
DEPLOY_MODE=existing \
S3_ACCESS_KEY=myaccesskey \
S3_SECRET_KEY=mysecretkey \
./scripts/s3-tests/run.sh
```
### Custom Configuration Files
@@ -206,25 +265,109 @@ less artifacts/s3tests-single/rustfs-single/rustfs.log
## Prerequisites
### Required
### Required System Dependencies
- Docker (for docker mode only)
- Python 3 (for running s3-tests)
- `nc` (netcat) or `timeout` command (for port checking)
The following dependencies must be installed manually on your system:
### Auto-installed
#### All Deployment Modes
The script will automatically install the following dependencies if missing:
- **Python 3**: Required for running s3-tests
- Check: `python3 --version`
- Install:
- macOS: Usually pre-installed, or `brew install python3`
- Linux: `apt-get install python3` or `yum install python3`
- `awscurl` (for S3 API calls and user provisioning)
- `tox` (for running s3-tests in isolated environment)
- `gettext-base` (for `envsubst` - config file generation)
- On macOS: `brew install gettext`
- On Linux: `apt-get install gettext-base`
- **Git**: Required for cloning s3-tests repository
- Check: `git --version`
- Install:
- macOS: Usually pre-installed, or `brew install git`
- Linux: `apt-get install git` or `yum install git`
- **Port checking tools**: One of the following for port availability checks
- `nc` (netcat): `apt-get install netcat` or `brew install netcat`
- OR `timeout` command: Usually pre-installed on Linux
- OR bash built-in TCP redirection support
#### Docker Mode Only
- **Docker**: Required only when using `DEPLOY_MODE=docker`
- Check: `docker --version`
- Install: [Docker Installation Guide](https://docs.docker.com/get-docker/)
#### Build Mode Only
- **Rust toolchain**: Required when using `DEPLOY_MODE=build` (default)
- Check: `rustc --version` and `cargo --version`
- Install: [Rust Installation Guide](https://www.rust-lang.org/tools/install)
### Auto-installed Dependencies
The script will automatically install the following dependencies if missing (no manual action required):
- **awscurl**: For S3 API calls and user provisioning
- Installed via: `python3 -m pip install --user --upgrade pip awscurl`
- Location: `$HOME/.local/bin/awscurl`
- **tox**: For running s3-tests in isolated Python environment
- Installed via: `python3 -m pip install --user --upgrade pip tox`
- Location: `$HOME/.local/bin/tox`
- **gettext-base**: For `envsubst` command (config file generation)
- macOS: Automatically installs via `brew install gettext`
- Linux: Automatically installs via `sudo apt-get install gettext-base`
- **Note**: macOS installation may require manual intervention if brew fails
- **s3-tests repository**: Automatically cloned if not present
- Source: `https://github.com/ceph/s3-tests.git`
- Location: `${PROJECT_ROOT}/s3-tests`
**Note**: The script adds `$HOME/.local/bin` to `PATH` automatically, so auto-installed Python tools are accessible.
### Proxy Configuration
The script automatically disables proxy for localhost requests to avoid interference. All proxy environment variables (`http_proxy`, `https_proxy`, etc.) are unset at script startup.
The script automatically disables proxy for localhost requests to avoid interference. All proxy environment variables (`http_proxy`, `https_proxy`, `HTTP_PROXY`, `HTTPS_PROXY`) are unset at script startup. The `NO_PROXY` variable is set to `127.0.0.1,localhost,::1`.
## Cleanup
The test script automatically cleans up processes and containers when it exits. However, if you need to manually clean up:
### Using the Cleanup Script
A dedicated cleanup script is available to clean up test resources:
```bash
# Clean up port 9000 and test data directory
./scripts/s3-tests/cleanup.sh
# Use custom port and host
S3_PORT=9001 S3_HOST=127.0.0.1 ./scripts/s3-tests/cleanup.sh
```
The cleanup script will:
- Kill any process using the specified port (default: 9000)
- Clean test data directory at `target/test-data/rustfs-single/`
- Verify port is released
### Manual Cleanup
If the cleanup script doesn't work or you need more control:
```bash
# Kill process on port 9000
lsof -ti:9000 | xargs kill -9
# Or use netstat/ss
kill -9 $(netstat -tuln | grep :9000 | awk '{print $7}' | cut -d'/' -f1)
# Remove test data
rm -rf target/test-data/rustfs-single/
# Stop Docker container (if using docker mode)
docker rm -f rustfs-single
# Remove Docker network (if using docker mode)
docker network rm rustfs-net
```
## Troubleshooting
+1
View File
@@ -120,6 +120,7 @@ test_object_copy_bucket_not_found
test_object_copy_key_not_found
test_object_copy_not_owned_object_bucket
test_object_head_zero_bytes
test_get_obj_head_tagging
test_object_metadata_replaced_on_put
test_object_put_authenticated
test_object_read_not_exist
-1
View File
@@ -167,7 +167,6 @@ test_ignore_public_acls
# PublicAccessBlock and tag validation tests
test_block_public_policy
test_block_public_policy_with_principal
test_get_obj_head_tagging
test_get_public_block_deny_bucket_policy
test_get_undefined_public_block
test_put_excess_key_tags