Skip to content

Commit 1746859

Browse files
committed
test(seaweedfs): add quota-clear and metrics fallback regression tests
Four new tests: - testSetBucketQuotaClearExistingPropagates404: a quota clear on a bucket with an existing positive quota must not swallow a 404. - testGetAllBucketsUsageMetricsFloatValueParsed: scientific-notation gauge values are parsed correctly. - testGetAllBucketsUsageUnparseableMetricFallsBackToList: an unparseable sample triggers the S3 fallback instead of reporting zero. - testGetAllBucketsUsageWrongMetricsEndpointFallsBackToList: a metricsUrl pointing at a Prometheus server (HTTP 200, no SeaweedFS series) triggers the S3 fallback.
1 parent 068987f commit 1746859

1 file changed

Lines changed: 118 additions & 0 deletions

File tree

plugins/storage/object/seaweedfs/src/test/java/org/apache/cloudstack/storage/datastore/driver/SeaweedFSObjectStoreDriverImplTest.java

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,34 @@ public void testSetBucketQuotaZeroTolerates404() throws Exception {
379379
driver.setBucketQuota(bucketTO, TEST_STORE_ID, 0);
380380
}
381381

382+
@Test
383+
public void testSetBucketQuotaClearExistingPropagates404() throws Exception {
384+
BucketTO bucketTO = mock(BucketTO.class);
385+
when(bucketTO.getName()).thenReturn(TEST_BUCKET_NAME);
386+
when(bucketTO.getAccountId()).thenReturn(TEST_ACCOUNT_ID);
387+
doReturn(TEST_S3_URL).when(driver).getS3Url(TEST_STORE_ID);
388+
doReturn("access-key").when(driver).getAccessKey(TEST_STORE_ID);
389+
doReturn("secret-key").when(driver).getSecretKey(TEST_STORE_ID);
390+
391+
// The bucket already has a positive quota, so a quota 0 request is a
392+
// clear of an existing quota. A 404 must NOT be tolerated: reporting
393+
// success would lower CloudStack accounting while SeaweedFS keeps the
394+
// old quota and read-only state.
395+
List<BucketVO> buckets = new ArrayList<>();
396+
buckets.add(new BucketVO(TEST_ACCOUNT_ID, TEST_DOMAIN_ID, TEST_STORE_ID, TEST_BUCKET_NAME, 100, false, false, false, null));
397+
when(bucketDao.listByObjectStoreIdAndAccountId(TEST_STORE_ID, TEST_ACCOUNT_ID)).thenReturn(buckets);
398+
399+
HttpClient mockHttpClient = mock(HttpClient.class);
400+
HttpResponse<String> mockResponse = mock(HttpResponse.class);
401+
when(mockResponse.statusCode()).thenReturn(404);
402+
when(mockResponse.body()).thenReturn("not found");
403+
when(mockHttpClient.send(ArgumentMatchers.<HttpRequest>any(),
404+
ArgumentMatchers.<HttpResponse.BodyHandler<String>>any())).thenReturn(mockResponse);
405+
doReturn(mockHttpClient).when(driver).getS3ExtensionHttpClient();
406+
407+
assertThrows(CloudRuntimeException.class, () -> driver.setBucketQuota(bucketTO, TEST_STORE_ID, 0));
408+
}
409+
382410
@Test
383411
public void testSetBucketQuotaRejects3xx() throws Exception {
384412
BucketTO bucketTO = mock(BucketTO.class);
@@ -833,4 +861,94 @@ public void testGetAllBucketsUsageMetricsFailureFallsBackToList() throws Excepti
833861
assertEquals(1, usage.size());
834862
assertEquals(42L, usage.get("b1").longValue());
835863
}
864+
865+
@Test
866+
public void testGetAllBucketsUsageMetricsFloatValueParsed() throws Exception {
867+
doReturn("http://metrics.local:9327").when(driver).getMetricsUrl(TEST_STORE_ID);
868+
869+
List<BucketVO> buckets = new ArrayList<>();
870+
buckets.add(new BucketVO(TEST_ACCOUNT_ID, TEST_DOMAIN_ID, TEST_STORE_ID, "b1", null, false, false, false, null));
871+
when(bucketDao.listByObjectStoreId(TEST_STORE_ID)).thenReturn(buckets);
872+
873+
// Prometheus gauges are floating point and may use scientific notation
874+
String metricsBody = "SeaweedFS_s3_bucket_size_bytes{bucket=\"b1\"} 1.2345678e+07\n";
875+
HttpClient mockHttpClient = mock(HttpClient.class);
876+
HttpResponse<String> mockResponse = mock(HttpResponse.class);
877+
when(mockResponse.statusCode()).thenReturn(200);
878+
when(mockResponse.body()).thenReturn(metricsBody);
879+
when(mockHttpClient.send(ArgumentMatchers.<HttpRequest>any(),
880+
ArgumentMatchers.<HttpResponse.BodyHandler<String>>any()))
881+
.thenReturn(mockResponse);
882+
doReturn(mockHttpClient).when(driver).getS3ExtensionHttpClient();
883+
884+
Map<String, Long> usage = driver.getAllBucketsUsage(TEST_STORE_ID);
885+
assertEquals(12345678L, usage.get("b1").longValue());
886+
verify(s3Client, never()).listObjectsV2(any(ListObjectsV2Request.class));
887+
}
888+
889+
@Test
890+
public void testGetAllBucketsUsageUnparseableMetricFallsBackToList() throws Exception {
891+
doReturn("http://metrics.local:9327").when(driver).getMetricsUrl(TEST_STORE_ID);
892+
doReturn(s3Client).when(driver).getS3ClientByStoreId(TEST_STORE_ID);
893+
894+
List<BucketVO> buckets = new ArrayList<>();
895+
buckets.add(new BucketVO(TEST_ACCOUNT_ID, TEST_DOMAIN_ID, TEST_STORE_ID, "b1", null, false, false, false, null));
896+
when(bucketDao.listByObjectStoreId(TEST_STORE_ID)).thenReturn(buckets);
897+
898+
// An unparseable sample value must be treated as a scrape failure so
899+
// the S3 fallback runs, rather than reporting the bucket as zero.
900+
String metricsBody = "SeaweedFS_s3_bucket_size_bytes{bucket=\"b1\"} not-a-number\n";
901+
HttpClient mockHttpClient = mock(HttpClient.class);
902+
HttpResponse<String> mockResponse = mock(HttpResponse.class);
903+
when(mockResponse.statusCode()).thenReturn(200);
904+
when(mockResponse.body()).thenReturn(metricsBody);
905+
when(mockHttpClient.send(ArgumentMatchers.<HttpRequest>any(),
906+
ArgumentMatchers.<HttpResponse.BodyHandler<String>>any()))
907+
.thenReturn(mockResponse);
908+
doReturn(mockHttpClient).when(driver).getS3ExtensionHttpClient();
909+
910+
ListObjectsV2Result b1Result = mock(ListObjectsV2Result.class);
911+
S3ObjectSummary s1 = new S3ObjectSummary(); s1.setSize(77L);
912+
List<S3ObjectSummary> summaries = new ArrayList<>(); summaries.add(s1);
913+
when(b1Result.getObjectSummaries()).thenReturn(summaries);
914+
when(b1Result.isTruncated()).thenReturn(false);
915+
when(s3Client.listObjectsV2(any(ListObjectsV2Request.class))).thenReturn(b1Result);
916+
917+
Map<String, Long> usage = driver.getAllBucketsUsage(TEST_STORE_ID);
918+
assertEquals(77L, usage.get("b1").longValue());
919+
}
920+
921+
@Test
922+
public void testGetAllBucketsUsageWrongMetricsEndpointFallsBackToList() throws Exception {
923+
doReturn("http://prometheus.local:9090").when(driver).getMetricsUrl(TEST_STORE_ID);
924+
doReturn(s3Client).when(driver).getS3ClientByStoreId(TEST_STORE_ID);
925+
926+
List<BucketVO> buckets = new ArrayList<>();
927+
buckets.add(new BucketVO(TEST_ACCOUNT_ID, TEST_DOMAIN_ID, TEST_STORE_ID, "b1", null, false, false, false, null));
928+
when(bucketDao.listByObjectStoreId(TEST_STORE_ID)).thenReturn(buckets);
929+
930+
// metricsUrl pointing at a Prometheus server returns HTTP 200 with its
931+
// own internal metrics, not the SeaweedFS bucket series. This must be
932+
// detected so the S3 fallback runs instead of reporting zero.
933+
String metricsBody = "prometheus_build_info{version=\"2.0\"} 1\n" +
934+
"go_goroutines 42\n";
935+
HttpClient mockHttpClient = mock(HttpClient.class);
936+
HttpResponse<String> mockResponse = mock(HttpResponse.class);
937+
when(mockResponse.statusCode()).thenReturn(200);
938+
when(mockResponse.body()).thenReturn(metricsBody);
939+
when(mockHttpClient.send(ArgumentMatchers.<HttpRequest>any(),
940+
ArgumentMatchers.<HttpResponse.BodyHandler<String>>any()))
941+
.thenReturn(mockResponse);
942+
doReturn(mockHttpClient).when(driver).getS3ExtensionHttpClient();
943+
944+
ListObjectsV2Result b1Result = mock(ListObjectsV2Result.class);
945+
S3ObjectSummary s1 = new S3ObjectSummary(); s1.setSize(88L);
946+
List<S3ObjectSummary> summaries = new ArrayList<>(); summaries.add(s1);
947+
when(b1Result.getObjectSummaries()).thenReturn(summaries);
948+
when(b1Result.isTruncated()).thenReturn(false);
949+
when(s3Client.listObjectsV2(any(ListObjectsV2Request.class))).thenReturn(b1Result);
950+
951+
Map<String, Long> usage = driver.getAllBucketsUsage(TEST_STORE_ID);
952+
assertEquals(88L, usage.get("b1").longValue());
953+
}
836954
}

0 commit comments

Comments
 (0)