Skip to content

Commit eb025b6

Browse files
committed
Add CTST coverage for Days=0 lifecycle expiration
Add an end-to-end test using the S3 PutBucketLifecycleConfiguration API: a Scenario Outline over non-versioned, versioned and suspended buckets that sets a whole-bucket Days=0 expiration (current version, or current and noncurrent versions) and asserts the bucket empties. Factor the lifecycle-config PUT-with-retry logic shared with the transition workflow helper into putBucketLifecycleConfigurationWithRetry, and add an addExpirationWorkflow helper. Days=0 makes objects immediately eligible, so the test needs no time-progression or one-day-earlier tricks. Issue: ZENKO-5314
1 parent 3ddd376 commit eb025b6

3 files changed

Lines changed: 98 additions & 24 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Feature: Lifecycle expiration
2+
3+
@2.16.0
4+
@PreMerge
5+
@Expiration
6+
@Lifecycle
7+
Scenario Outline: Days=0 expiration empties a "<versioningConfiguration>" bucket
8+
Given a "<versioningConfiguration>" bucket
9+
And 5 objects "expire-obj" of size 100 bytes
10+
When i set a lifecycle expiration of 0 days for the "<scope>"
11+
Then the bucket should contain 0 objects within 180 seconds
12+
13+
Examples:
14+
| versioningConfiguration | scope |
15+
| Non versioned | current version |
16+
| Versioned | current and noncurrent versions |
17+
| Suspended | current and noncurrent versions |
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { Then, When } from '@cucumber/cucumber';
2+
import { S3, Utils } from 'cli-testing';
3+
import { ListObjectVersionsOutput } from '@aws-sdk/client-s3';
4+
import assert from 'assert';
5+
import Zenko from 'world/Zenko';
6+
import { safeJsonParse } from 'common/utils';
7+
import { addExpirationWorkflow } from './utils/utils';
8+
9+
When('i set a lifecycle expiration of {int} days for the {string}',
10+
async function (this: Zenko, days: number, scope: string) {
11+
const includeNoncurrent: Record<string, boolean> = {
12+
'current version': false,
13+
'current and noncurrent versions': true,
14+
};
15+
assert(scope in includeNoncurrent, `Unknown expiration scope "${scope}"`);
16+
await addExpirationWorkflow.call(this, days, includeNoncurrent[scope]);
17+
});
18+
19+
Then('the bucket should contain {int} objects within {int} seconds', { timeout: 6 * 60 * 1000 },
20+
async function (this: Zenko, expectedCount: number, seconds: number) {
21+
const bucketName = this.getSaved<string>('bucketName');
22+
const deadline = Date.now() + seconds * 1000;
23+
let count = -1;
24+
do {
25+
const res = await S3.listObjectVersions({ bucket: bucketName, maxItems: '1000' });
26+
const parsed = safeJsonParse<ListObjectVersionsOutput>(res.stdout || '{}');
27+
assert.ok(parsed.ok, `Failed to list versions in bucket ${bucketName}: ${parsed.error}`);
28+
const versions = parsed.result?.Versions ?? [];
29+
const deleteMarkers = parsed.result?.DeleteMarkers ?? [];
30+
count = versions.length + deleteMarkers.length;
31+
if (count === expectedCount) {
32+
return;
33+
}
34+
await Utils.sleep(2000);
35+
} while (Date.now() < deadline);
36+
assert.fail(
37+
`Bucket ${bucketName} has ${count} versions/delete markers, ` +
38+
`expected ${expectedCount} after ${seconds}s`);
39+
});

tests/functional/ctst/steps/utils/utils.ts

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -378,35 +378,52 @@ async function emptyVersionedBucket(world: Zenko) {
378378
}));
379379
}
380380

381-
async function addTransitionWorkflow(this: Zenko, location: string, enabled = true) {
382-
let conditionOk = false;
383-
this.resetCommand();
384-
this.addCommandParameter({ bucket: this.getSaved<string>('bucketName') });
385-
const enabledStr = enabled ? 'Enabled' : 'Disabled';
386-
const lifecycleConfiguration = JSON.stringify({
387-
Rules: [
388-
{
389-
Status: enabledStr,
390-
Prefix: '',
391-
Transitions: [
392-
{
393-
Days: 0,
394-
StorageClass: location,
395-
},
396-
],
397-
},
398-
],
399-
});
400-
this.addCommandParameter({
401-
lifecycleConfiguration,
381+
async function putBucketLifecycleConfigurationWithRetry(world: Zenko, rules: Record<string, unknown>[]) {
382+
world.resetCommand();
383+
world.addCommandParameter({ bucket: world.getSaved<string>('bucketName') });
384+
world.addCommandParameter({
385+
lifecycleConfiguration: JSON.stringify({ Rules: rules }),
402386
});
403-
const commandParameters = this.getCommandParameters();
387+
const commandParameters = world.getCommandParameters();
388+
let conditionOk = false;
404389
while (!conditionOk) {
405390
const res = await S3.putBucketLifecycleConfiguration(commandParameters);
406391
conditionOk = res.err === null;
407-
// Wait for the transition to be accepted because the deployment of the location's pods can take some time
408-
await Utils.sleep(5000);
392+
// Wait for the configuration to be accepted because the deployment of the location's pods can take some time
393+
await Utils.sleep(5000);
394+
}
395+
}
396+
397+
async function addTransitionWorkflow(this: Zenko, location: string, enabled = true) {
398+
const enabledStr = enabled ? 'Enabled' : 'Disabled';
399+
await putBucketLifecycleConfigurationWithRetry(this, [
400+
{
401+
Status: enabledStr,
402+
Prefix: '',
403+
Transitions: [
404+
{
405+
Days: 0,
406+
StorageClass: location,
407+
},
408+
],
409+
},
410+
]);
411+
}
412+
413+
async function addExpirationWorkflow(this: Zenko, days: number, includeNoncurrentVersions = false) {
414+
const rule: Record<string, unknown> = {
415+
Status: 'Enabled',
416+
Prefix: '',
417+
Expiration: {
418+
Days: days,
419+
},
420+
};
421+
if (includeNoncurrentVersions) {
422+
rule.NoncurrentVersionExpiration = {
423+
NoncurrentDays: days,
424+
};
409425
}
426+
await putBucketLifecycleConfigurationWithRetry(this, [rule]);
410427
}
411428

412429
async function getReplicationLocationConfig(world: Zenko, location: string): Promise<{
@@ -601,6 +618,7 @@ export {
601618
getObjectNameWithBackendFlakiness,
602619
restoreObject,
603620
addTransitionWorkflow,
621+
addExpirationWorkflow,
604622
getReplicationLocationConfig,
605623
putBucketReplication,
606624
};

0 commit comments

Comments
 (0)