diff --git a/solution/deps.yaml b/solution/deps.yaml index 886643fea..7f14075d4 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.7 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..08a37aec3 --- /dev/null +++ b/tests/functional/ctst/features/lifecycleExpiration.feature @@ -0,0 +1,17 @@ +Feature: Lifecycle expiration + + @2.16.0 + @PreMerge + @Expiration + @Lifecycle + Scenario Outline: Days=0 expiration empties a "" bucket + Given a "" bucket + And 5 objects "expire-obj" of size 100 bytes + When i set a lifecycle expiration of 0 days for the "" + Then the bucket should contain 0 objects within 180 seconds + + Examples: + | versioningConfiguration | scope | + | Non versioned | current version | + | Versioned | current and noncurrent versions | + | Suspended | current and noncurrent versions | diff --git a/tests/functional/ctst/steps/lifecycleExpiration.ts b/tests/functional/ctst/steps/lifecycleExpiration.ts new file mode 100644 index 000000000..09f8c8249 --- /dev/null +++ b/tests/functional/ctst/steps/lifecycleExpiration.ts @@ -0,0 +1,39 @@ +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 for the {string}', + async function (this: Zenko, days: number, scope: string) { + const includeNoncurrent: Record = { + 'current version': false, + 'current and noncurrent versions': true, + }; + assert(scope in includeNoncurrent, `Unknown expiration scope "${scope}"`); + await addExpirationWorkflow.call(this, days, includeNoncurrent[scope]); + }); + +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, }; diff --git a/tests/functional/mocha/utils/request.js b/tests/functional/mocha/utils/request.js index 2987a3573..8eab23fa8 100644 --- a/tests/functional/mocha/utils/request.js +++ b/tests/functional/mocha/utils/request.js @@ -64,6 +64,7 @@ function makeUpdateRequest(path, cb, userCredentials, body, mode = 'POST') { ...defaultOptions, method: mode || 'POST', path, + body, }; options = aws4.sign(options, userCredentials || credentials); diff --git a/tests/functional/package.json b/tests/functional/package.json index 5baaf520a..8cdbf325e 100644 --- a/tests/functional/package.json +++ b/tests/functional/package.json @@ -20,7 +20,7 @@ "async": "2.1.2", "aws4": "^1.11.0", "aws4-axios": "^3.3.8", - "cli-testing": "git+https://github.com/scality/cli-testing.git#v1.3.0", + "cli-testing": "git+https://github.com/scality/cli-testing.git#v1.3.1", "js-yaml": "^4.2.0", "mocha": "^11.7.5", "mocha-junit-reporter": "^2.2.1", diff --git a/tests/functional/yarn.lock b/tests/functional/yarn.lock index 81f1cb10e..67ce62de6 100644 --- a/tests/functional/yarn.lock +++ b/tests/functional/yarn.lock @@ -2511,9 +2511,9 @@ cli-table3@0.6.5: optionalDependencies: "@colors/colors" "1.5.0" -"cli-testing@git+https://github.com/scality/cli-testing.git#v1.3.0": - version "1.3.0" - resolved "git+https://github.com/scality/cli-testing.git#a4516ed463c766440e16d5bc52ff9c6b9222edce" +"cli-testing@git+https://github.com/scality/cli-testing.git#v1.3.1": + version "1.3.1" + resolved "git+https://github.com/scality/cli-testing.git#a53395ca89d16c7b0995249f5c84002678896377" dependencies: "@aws-crypto/sha256-universal" "^5.2.0" "@aws-sdk/client-iam" "^3.879.0"