diff --git a/tests/ctst/common/common.ts b/tests/ctst/common/common.ts index 78926f1ae..810200bab 100644 --- a/tests/ctst/common/common.ts +++ b/tests/ctst/common/common.ts @@ -7,6 +7,8 @@ import assert from 'assert'; import { Admin, Kafka } from 'kafkajs'; import { createBucketWithConfiguration, + putMpuObject, + copyObject, putObject, runActionAgainstBucket, getObjectNameWithBackendFlakiness, @@ -62,7 +64,7 @@ export async function cleanS3Bucket( } async function addMultipleObjects(this: Zenko, numberObjects: number, - objectName: string, sizeBytes: number, userMD?: string) { + objectName: string, sizeBytes: number, userMD?: string, parts?: number) { let lastResult = null; for (let i = 1; i <= numberObjects; i++) { this.resetCommand(); @@ -74,7 +76,9 @@ async function addMultipleObjects(this: Zenko, numberObjects: number, if (userMD) { this.addToSaved('userMetadata', userMD); } - lastResult = await putObject(this, objectNameFinal); + lastResult = parts === undefined + ? await putObject(this, objectNameFinal) + : await putMpuObject(this, parts, objectNameFinal); } return lastResult; } @@ -147,6 +151,16 @@ Given('{int} objects {string} of size {int} bytes', await addMultipleObjects.call(this, numberObjects, objectName, sizeBytes); }); +Given('{int} mpu objects {string} of size {int} bytes', + async function (this: Zenko, numberObjects: number, objectName: string, sizeBytes: number) { + await addMultipleObjects.call(this, numberObjects, objectName, sizeBytes, undefined, 3); + }); + +Given("{string} is copied to {string}", + async function (this: Zenko, sourceObject: string, destinationObject: string) { + await copyObject(this, sourceObject, destinationObject); + }); + Given('{int} objects {string} of size {int} bytes on {string} site', async function (this: Zenko, numberObjects: number, objectName: string, sizeBytes: number, site: string) { this.resetCommand(); @@ -164,6 +178,11 @@ Given('{int} objects {string} of size {int} bytes with user metadata {string}', await addMultipleObjects.call(this, numberObjects, objectName, sizeBytes, userMD); }); +Given('{int} mpu objects {string} of size {int} bytes with user metadata {string}', + async function (this: Zenko, numberObjects: number, objectName: string, sizeBytes: number, userMD: string) { + await addMultipleObjects.call(this, numberObjects, objectName, sizeBytes, userMD); + }); + Given('a tag on object {string} with key {string} and value {string}', async function (this: Zenko, objectName: string, tagKey: string, tagValue: string) { this.resetCommand(); diff --git a/tests/ctst/features/dmf.feature b/tests/ctst/features/dmf.feature index 175553e87..09b36a5f7 100644 --- a/tests/ctst/features/dmf.feature +++ b/tests/ctst/features/dmf.feature @@ -92,3 +92,44 @@ Feature: DMF | versioningConfiguration | objectCount | objectSize | | Non versioned | 1 | 100 | | Suspended | 1 | 100 | + + @2.7.0 + @PreMerge + @Dmf + @ColdStorage + Scenario Outline: Overwriting of a cold mpu object + Given a "" bucket + And a transition workflow to "e2e-cold" location + And objects "obj" of size bytes + Then object "obj-1" should be "transitioned" and have the storage class "e2e-cold" + And dmf volume should contain objects + Given mpu objects "obj" of size bytes + Then object "obj-1" should be "transitioned" and have the storage class "e2e-cold" + And dmf volume should contain 1 objects + + Examples: + | versioningConfiguration | objectCount | objectSize | + | Non versioned | 1 | 100 | + | Suspended | 1 | 100 | + + @2.7.0 + @PreMerge + @Dmf + @ColdStorage + Scenario Outline: Overwriting of a cold object with copyObject + Given a "" bucket + And a transition workflow to "e2e-cold" location + And 2 objects "obj" of size bytes + Then object "obj-1" should be "transitioned" and have the storage class "e2e-cold" + And object "obj-2" should be "transitioned" and have the storage class "e2e-cold" + And dmf volume should contain 2 objects + When i restore object "obj-1" for 5 days + Then object "obj-1" should be "restored" and have the storage class "e2e-cold" + Given "obj-1" is copied to "obj-2" + Then object "obj-2" should be "transitioned" and have the storage class "e2e-cold" + And dmf volume should contain 2 objects + + Examples: + | versioningConfiguration | objectSize | + | Non versioned | 100 | + | Suspended | 100 | diff --git a/tests/ctst/steps/utils/utils.ts b/tests/ctst/steps/utils/utils.ts index dd1745bc2..327526e8e 100644 --- a/tests/ctst/steps/utils/utils.ts +++ b/tests/ctst/steps/utils/utils.ts @@ -199,6 +199,72 @@ async function createBucketWithConfiguration( } } +async function putMpuObject(world: Zenko, parts: number = 2, objectName?: string, content?: string) { + world.resetCommand(); + let key = objectName; + if (!key) { + key = `${Utils.randomString()}`; + } + world.addToSaved('objectName', key); + world.logger.debug('Adding mpu object', { objectName: key }); + world.addCommandParameter({ key }); + world.addCommandParameter({ bucket: world.getSaved('bucketName') }); + const userMetadata = world.getSaved('userMetadata'); + if (userMetadata) { + world.addCommandParameter({ metadata: JSON.stringify(userMetadata) }); + } + const initiateMPUResult = await S3.createMultipartUpload(world.getCommandParameters()) + assert.ifError(initiateMPUResult.stderr || initiateMPUResult.err); + world.addCommandParameter({ + uploadId: extractPropertyFromResults(initiateMPUResult, 'UploadId') + }); + + await uploadSetup(world, 'UploadPart', content); + world.deleteKeyFromCommand('metadata'); + + let uploadedParts = []; + for (let i = 0; i < parts; i++) { + world.addCommandParameter({partNumber: i+1}); + const uploadPartResult = await S3.uploadPart(world.getCommandParameters()) + assert.ifError(uploadPartResult.stderr || uploadPartResult.err); + + uploadedParts.push({ + ETag: extractPropertyFromResults(uploadPartResult, 'ETag'), + PartNumber: i+1, + }) + } + + world.deleteKeyFromCommand('partNumber'); + world.addCommandParameter({MultipartUpload: JSON.stringify({ Parts: uploadedParts })}); + const result = await S3.completeMultipartUpload(world.getCommandParameters()); + const versionId = extractPropertyFromResults(result, 'VersionId'); + world.saveCreatedObject(key, versionId || ''); + await uploadTeardown(world, 'UploadPart'); + world.setResult(result); + return result; +} + +async function copyObject(world: Zenko, srcObjectName?: string, dstObjectName?: string) { + const bucket = world.getSaved('bucketName'); + const key = dstObjectName || world.getSaved('objectName'); + + world.resetCommand(); + world.addCommandParameter({ copySource: `${bucket}/${srcObjectName || world.getSaved('objectName')}`}); + world.addCommandParameter({ bucket }); + world.addCommandParameter({ key }); + + const userMetadata = world.getSaved('userMetadata'); + if (userMetadata) { + world.addCommandParameter({ metadata: JSON.stringify(userMetadata) }); + } + + const result = await S3.copyObject(world.getCommandParameters()); + const versionId = extractPropertyFromResults(result, 'VersionId'); + world.saveCreatedObject(key, versionId || ''); + world.setResult(result); + return result; +} + async function putObject(world: Zenko, objectName?: string, content?: string) { world.resetCommand(); let finalObjectName = objectName; @@ -394,6 +460,8 @@ export { runActionAgainstBucket, createBucketWithConfiguration, getAuthorizationConfiguration, + putMpuObject, + copyObject, putObject, emptyNonVersionedBucket, emptyVersionedBucket,