feat(seaweedfs): add SeaweedFS object storage provider - #14160
Conversation
Add a new object storage provider plugin for SeaweedFS, alongside the
existing MinIO, Ceph RGW, and Cloudian HyperStore providers.
SeaweedFS exposes an S3-compatible API and an AWS IAM-compatible API, so
this provider uses the AWS S3 and IAM Java SDKs — the same approach as
the Cloudian HyperStore provider. No proprietary admin client is needed.
Key features:
- Bucket CRUD, policy, versioning, encryption, ACLs via AmazonS3 SDK
- Per-account IAM user provisioning via AmazonIdentityManagement SDK
- Per-bucket quota via the SeaweedFS S3 ?seaweedfs-quota extension
(PUT /{bucket}?seaweedfs-quota), authenticated via SigV4 and authorized
via the s3:PutBucketQuota IAM permission. This requires SeaweedFS PR
apache#11279.
- Usage reporting via S3 ListObjectsV2 (MVP; Prometheus or SOSAPI
capacity.xml recommended for production scale)
The service credential (accesskey/secretkey on the object store) is
granted only s3:PutBucketQuota and s3:GetBucketQuota via an IAM policy,
so it cannot delete buckets, manage users, or change cluster topology.
The plugin follows the Cloudian HyperStore pattern almost line for line:
same store-details keys (s3Url, iamUrl, accesskey, secretkey), same
IAM-user-with-restricted-policy pattern, same Spring wiring.
SeaweedFS registers its embedded IAM API at POST / on the same S3 endpoint (UnifiedPostHandler in s3api_server.go), not under /iam. The AWS IAM SDK uses the Query protocol and POSTs to the endpoint root, so defaulting iamUrl to <s3Url>/iam would send IAM operations to an unregistered path. Default to s3Url instead; a separate iamUrl is only needed for deployments running a standalone weed iam server. Found by Greptile review on PR apache#11279.
…licy constant Two issues found by CodeRabbit review on PR apache#11279: 1. S3Signer implements legacy S3 Signature Version 2, not SigV4. SeaweedFS expects SigV4. Replace with AWSS3V4Signer which implements AWS Signature Version 4. The seaweedfs-quota query parameter is included in the signed canonical query string. 2. SERVICE_CREDENTIAL_POLICY was a dead constant (never referenced) that claimed the service credential is scoped to only s3:PutBucketQuota/s3:GetBucketQuota. This contradicts the actual implementation, which uses the service credential (admin) for all driver operations: bucket CRUD, IAM user provisioning, and quota. Remove the dead constant and document the actual credential model.
|
Congratulations on your first Pull Request and welcome to the Apache CloudStack community! If you have any issues or are unsure about any anything please check our Contribution Guide (https://github.com/apache/cloudstack/blob/main/CONTRIBUTING.md)
|
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved packaging, lifecycle, IAM isolation, credential handling, and quota-signing issues remain.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds SeaweedFS as a CloudStack object-storage provider using S3/IAM APIs and SeaweedFS quota support.
Changes:
- Implements provider lifecycle, bucket, IAM, quota, and usage operations.
- Adds Maven and Spring module registration.
- Adds provider and driver tests.
File summaries
| File | Description |
|---|---|
plugins/storage/object/seaweedfs/src/test/java/org/apache/cloudstack/storage/datastore/provider/SeaweedFSObjectStoreProviderImplTest.java |
Tests provider registration. |
plugins/storage/object/seaweedfs/src/test/java/org/apache/cloudstack/storage/datastore/driver/SeaweedFSObjectStoreDriverImplTest.java |
Tests driver behavior and quota handling. |
plugins/storage/object/seaweedfs/src/main/resources/META-INF/cloudstack/storage-object-seaweedfs/spring-storage-object-seaweedfs-context.xml |
Registers Spring provider wiring. |
plugins/storage/object/seaweedfs/src/main/resources/META-INF/cloudstack/storage-object-seaweedfs/module.properties |
Defines module metadata. |
plugins/storage/object/seaweedfs/src/main/java/org/apache/cloudstack/storage/datastore/util/SeaweedFSObjectStoreUtil.java |
Builds clients and quota requests. |
plugins/storage/object/seaweedfs/src/main/java/org/apache/cloudstack/storage/datastore/provider/SeaweedFSObjectStoreProviderImpl.java |
Registers the provider. |
plugins/storage/object/seaweedfs/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SeaweedFSObjectStoreLifeCycleImpl.java |
Handles pool lifecycle and validation. |
plugins/storage/object/seaweedfs/src/main/java/org/apache/cloudstack/storage/datastore/driver/SeaweedFSObjectStoreDriverImpl.java |
Implements storage and IAM operations. |
plugins/storage/object/seaweedfs/pom.xml |
Defines module dependencies. |
plugins/pom.xml |
Registers the SeaweedFS module. |
Review details
Suppressed comments (4)
plugins/storage/object/seaweedfs/src/main/java/org/apache/cloudstack/storage/datastore/driver/SeaweedFSObjectStoreDriverImpl.java:430
- On any transient per-bucket S3 failure, this inserts
0.BucketApiServiceImpltreats returned values as authoritative and persists bucket and object-store usage, so a temporary timeout resets usage to zero and under-reports capacity. Propagate the failure for the whole store (or skip the store update) instead of publishing zero.
} catch (AmazonClientException e) {
logger.warn("Failed to get usage for bucket {}: {}", bucket.getName(), e.getMessage());
bucketUsage.put(bucket.getName(), 0L);
plugins/storage/object/seaweedfs/src/main/java/org/apache/cloudstack/storage/datastore/util/SeaweedFSObjectStoreUtil.java:271
HttpClient.newHttpClient()and this request have no connect or request timeout. A stalled SeaweedFS endpoint can block the synchronous bucket create/update API worker indefinitely. Use a shared client with configured connect and request timeouts, preferably using the provider's existing timeout configuration.
java.net.http.HttpClient client = java.net.http.HttpClient.newHttpClient();
java.net.http.HttpResponse<String> response = client.send(reqBuilder.build(),
java.net.http.HttpResponse.BodyHandlers.ofString());
plugins/storage/object/seaweedfs/src/main/java/org/apache/cloudstack/storage/datastore/util/SeaweedFSObjectStoreUtil.java:261
- The request adds
Content-Lengthto the SDK headers at line 243, then copies every header intojava.net.http.HttpRequest.Builder. Java's HttpClient rejectsContent-Lengthas a restricted header, so this loop can throw before the request is sent. Filter transport-managed headers (at leastContent-Length, andHostif present) and let HttpClient generate them while signing the same body.
for (java.util.Map.Entry<String, String> entry : request.getHeaders().entrySet()) {
if (entry.getKey() != null && entry.getValue() != null) {
reqBuilder.header(entry.getKey(), entry.getValue());
}
plugins/storage/object/seaweedfs/src/test/java/org/apache/cloudstack/storage/datastore/driver/SeaweedFSObjectStoreDriverImplTest.java:280
- The common
setUpalready populatesstoreDetailsMapwith the S3 URL and both credentials, so this test never exercises the missing-configuration guard; it passes only because the real HTTP call fails. Clear or override the details map and assert the validation message before any network call.
// No S3 URL/credentials configured — should throw with a clear message
assertThrows(CloudRuntimeException.class, () -> driver.setBucketQuota(bucketTO, TEST_STORE_ID, 10));
- Files reviewed: 10/10 changed files
- Comments generated: 6
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| <module>storage/object/minio</module> | ||
| <module>storage/object/ceph</module> | ||
| <module>storage/object/cloudian</module> | ||
| <module>storage/object/seaweedfs</module> |
| Map<String, Object> objectStoreParameters = new HashMap<String, Object>(); | ||
| objectStoreParameters.put(SeaweedFSObjectStoreUtil.STORE_KEY_NAME, name); | ||
| objectStoreParameters.put(SeaweedFSObjectStoreUtil.STORE_KEY_URL, url); | ||
| objectStoreParameters.put(SeaweedFSObjectStoreUtil.STORE_KEY_PROVIDER_NAME, providerName); |
| " \"Action\": [\n" + | ||
| " \"s3:*\"\n" + | ||
| " ],\n" + | ||
| " \"Resource\": \"*\"\n" + |
| com.amazonaws.DefaultRequest<?> request = new com.amazonaws.DefaultRequest<>("s3"); | ||
| request.setEndpoint(endpointUri); | ||
| request.setHttpMethod(com.amazonaws.http.HttpMethodName.valueOf(method)); | ||
| request.setResourcePath(resourcePath); |
| // Create a new access key for this user | ||
| CreateAccessKeyResult result = iamClient.createAccessKey( | ||
| new CreateAccessKeyRequest().withUserName(userName)); | ||
| AccessKey key = result.getAccessKey(); |
| // Should not throw for 0 — uses static method, can't easily mock, but | ||
| // the test validates the code path doesn't throw before the HTTP call | ||
| // Since we can't mock the static HTTP call, we expect a CloudRuntimeException | ||
| // from the HTTP call failing (no real server). That's acceptable — it proves | ||
| // the code path reaches the S3 extension rather than throwing "not supported". | ||
| assertThrows(CloudRuntimeException.class, () -> driver.setBucketQuota(bucketTO, TEST_STORE_ID, 0)); |
Summary
Adds SeaweedFS as a first-class object storage provider in CloudStack, alongside the existing MinIO, Ceph RGW, and Cloudian HyperStore providers.
SeaweedFS exposes an S3-compatible API and an AWS IAM-compatible API, so this provider uses the AWS S3 and IAM Java SDKs — the same approach as the Cloudian HyperStore provider. No proprietary admin client is needed.
Key features
AmazonS3SDK (AWS SDK v1, same as Ceph/Cloudian)AmazonIdentityManagementSDK (same as Cloudian HyperStore)?seaweedfs-quotaextension (PUT /{bucket}?seaweedfs-quota), authenticated via SigV4 and authorized via thes3:PutBucketQuotaIAM permission. This requires SeaweedFS PR Multiple guest networks deployment issue #11279 (merged).ListObjectsV2(MVP; Prometheus or SOSAPIcapacity.xmlrecommended for production scale)Architecture
The plugin follows the Cloudian HyperStore pattern almost line for line:
s3Url,iamUrl,accesskey,secretkeyiamUrldefaults tos3Url(SeaweedFS registers its IAM API atPOST /on the same S3 endpoint)Quota management
SeaweedFS enforces bucket quota server-side (read-only flag when usage exceeds the limit). The configuration surface is a narrow S3 subresource —
PUT /{bucket}?seaweedfs-quota— authenticated via standard S3 SigV4 and authorized via dedicateds3:PutBucketQuota/s3:GetBucketQuotaIAM permissions. This avoids exposing the broad SeaweedFS admin API to CloudStack. The plugin signs the request withAWSS3V4Signerand sends it viajava.net.http.HttpClient(the AWS S3 SDK doesn't natively support custom subresources).Comparison with MinIO and Ceph
MinioClientAmazonS3AmazonS3AmazonS3MinioAdminClientRgwAdminAmazonIdentityManagementAmazonIdentityManagementMinioAdminClientRgwAdminMinioAdminClientRgwAdminListObjectsV2Files
New module under
plugins/storage/object/seaweedfs/:pom.xml— Maven moduleSeaweedFSObjectStoreProviderImpl.java— Spring provider registrationSeaweedFSObjectStoreLifeCycleImpl.java— Pool add/health-check, URL validationSeaweedFSObjectStoreDriverImpl.java— Bucket + user ops via S3 + IAM SDKSeaweedFSObjectStoreUtil.java— S3 + IAM client builders, constants, SigV4 quota requestSeaweedFS dependency
Requires SeaweedFS with the
?seaweedfs-quotaS3 extension (PR seaweedfs/seaweedfs#11279, merged). Without it, quota operations will fail with 404; all other operations (bucket CRUD, user provisioning, usage) work with any recent SeaweedFS release.Test plan
mvn -pl plugins/storage/object/seaweedfs test(18 tests, 0 failures)addObjectStoragePoolwiths3Url,accesskey,secretkeyweed shells3.bucket.list