diff --git a/solution/deps.yaml b/solution/deps.yaml index 886643fea..86edae8de 100644 --- a/solution/deps.yaml +++ b/solution/deps.yaml @@ -6,7 +6,7 @@ backbeat: dashboard: backbeat/backbeat-dashboards image: backbeat policy: backbeat/backbeat-policies - tag: 9.5.0-preview.1 + tag: 9.5.0-preview.3 envsubst: BACKBEAT_TAG busybox: image: busybox @@ -16,7 +16,7 @@ cloudserver: sourceRegistry: ghcr.io/scality dashboard: cloudserver/cloudserver-dashboards image: cloudserver - tag: 9.4.0-preview.2 + tag: 9.4.0-preview.4 envsubst: CLOUDSERVER_TAG drctl: sourceRegistry: ghcr.io/scality @@ -80,7 +80,7 @@ mongodb-connector: pensieve-api: sourceRegistry: ghcr.io/scality image: pensieve-api - tag: 1.10.1 + tag: 1.11.0-preview.1 envsubst: PENSIEVE_API_TAG rclone: sourceRegistry: rclone diff --git a/tests/functional/ctst/features/lifecycleExpiration.feature b/tests/functional/ctst/features/lifecycleExpiration.feature new file mode 100644 index 000000000..e5cd7f3f3 --- /dev/null +++ b/tests/functional/ctst/features/lifecycleExpiration.feature @@ -0,0 +1,21 @@ +Feature: Lifecycle expiration + + @2.16.0 + @PreMerge + @Expiration + @Lifecycle + Scenario: Days=0 expiration deletes all objects in a non-versioned bucket + Given a "Non versioned" bucket + And 5 objects "expire-obj" of size 100 bytes + When i set a lifecycle expiration of 0 days + Then the bucket should contain 0 objects within 120 seconds + + @2.16.0 + @PreMerge + @Expiration + @Lifecycle + Scenario: Days=0 expiration deletes all versions and delete markers in a versioned bucket + Given a "Versioned" bucket + And 5 objects "expire-ver-obj" of size 100 bytes + When i set a lifecycle expiration of 0 days including noncurrent versions + Then the bucket should contain 0 objects within 180 seconds diff --git a/tests/functional/ctst/steps/lifecycleExpiration.ts b/tests/functional/ctst/steps/lifecycleExpiration.ts new file mode 100644 index 000000000..0c12418c6 --- /dev/null +++ b/tests/functional/ctst/steps/lifecycleExpiration.ts @@ -0,0 +1,38 @@ +import { Then, When } from '@cucumber/cucumber'; +import { S3, Utils } from 'cli-testing'; +import { ListObjectVersionsOutput } from '@aws-sdk/client-s3'; +import assert from 'assert'; +import Zenko from 'world/Zenko'; +import { safeJsonParse } from 'common/utils'; +import { addExpirationWorkflow } from './utils/utils'; + +When('i set a lifecycle expiration of {int} days', async function (this: Zenko, days: number) { + await addExpirationWorkflow.call(this, days); +}); + +When('i set a lifecycle expiration of {int} days including noncurrent versions', + async function (this: Zenko, days: number) { + await addExpirationWorkflow.call(this, days, true); + }); + +Then('the bucket should contain {int} objects within {int} seconds', { timeout: 6 * 60 * 1000 }, + async function (this: Zenko, expectedCount: number, seconds: number) { + const bucketName = this.getSaved('bucketName'); + const deadline = Date.now() + seconds * 1000; + let count = -1; + do { + const res = await S3.listObjectVersions({ bucket: bucketName, maxItems: '1000' }); + const parsed = safeJsonParse(res.stdout || '{}'); + assert.ok(parsed.ok, `Failed to list versions in bucket ${bucketName}: ${parsed.error}`); + const versions = parsed.result?.Versions ?? []; + const deleteMarkers = parsed.result?.DeleteMarkers ?? []; + count = versions.length + deleteMarkers.length; + if (count === expectedCount) { + return; + } + await Utils.sleep(2000); + } while (Date.now() < deadline); + assert.fail( + `Bucket ${bucketName} has ${count} versions/delete markers, ` + + `expected ${expectedCount} after ${seconds}s`); + }); diff --git a/tests/functional/ctst/steps/utils/utils.ts b/tests/functional/ctst/steps/utils/utils.ts index d19a73013..c2700a5f1 100644 --- a/tests/functional/ctst/steps/utils/utils.ts +++ b/tests/functional/ctst/steps/utils/utils.ts @@ -378,35 +378,52 @@ async function emptyVersionedBucket(world: Zenko) { })); } -async function addTransitionWorkflow(this: Zenko, location: string, enabled = true) { - let conditionOk = false; - this.resetCommand(); - this.addCommandParameter({ bucket: this.getSaved('bucketName') }); - const enabledStr = enabled ? 'Enabled' : 'Disabled'; - const lifecycleConfiguration = JSON.stringify({ - Rules: [ - { - Status: enabledStr, - Prefix: '', - Transitions: [ - { - Days: 0, - StorageClass: location, - }, - ], - }, - ], - }); - this.addCommandParameter({ - lifecycleConfiguration, +async function putBucketLifecycleConfigurationWithRetry(world: Zenko, rules: Record[]) { + world.resetCommand(); + world.addCommandParameter({ bucket: world.getSaved('bucketName') }); + world.addCommandParameter({ + lifecycleConfiguration: JSON.stringify({ Rules: rules }), }); - const commandParameters = this.getCommandParameters(); + const commandParameters = world.getCommandParameters(); + let conditionOk = false; while (!conditionOk) { const res = await S3.putBucketLifecycleConfiguration(commandParameters); conditionOk = res.err === null; - // Wait for the transition to be accepted because the deployment of the location's pods can take some time - await Utils.sleep(5000); + // Wait for the configuration to be accepted because the deployment of the location's pods can take some time + await Utils.sleep(5000); + } +} + +async function addTransitionWorkflow(this: Zenko, location: string, enabled = true) { + const enabledStr = enabled ? 'Enabled' : 'Disabled'; + await putBucketLifecycleConfigurationWithRetry(this, [ + { + Status: enabledStr, + Prefix: '', + Transitions: [ + { + Days: 0, + StorageClass: location, + }, + ], + }, + ]); +} + +async function addExpirationWorkflow(this: Zenko, days: number, includeNoncurrentVersions = false) { + const rule: Record = { + Status: 'Enabled', + Prefix: '', + Expiration: { + Days: days, + }, + }; + if (includeNoncurrentVersions) { + rule.NoncurrentVersionExpiration = { + NoncurrentDays: days, + }; } + await putBucketLifecycleConfigurationWithRetry(this, [rule]); } async function getReplicationLocationConfig(world: Zenko, location: string): Promise<{ @@ -601,6 +618,7 @@ export { getObjectNameWithBackendFlakiness, restoreObject, addTransitionWorkflow, + addExpirationWorkflow, getReplicationLocationConfig, putBucketReplication, };