Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/w/2.7/feature/ZENKO-4789-scuba-a…
Browse files Browse the repository at this point in the history
…nd-quotas' into w/2.8/feature/ZENKO-4789-scuba-and-quotas
  • Loading branch information
williamlardier committed May 14, 2024
2 parents 2ecd2fa + 78523cd commit 87f8380
Show file tree
Hide file tree
Showing 25 changed files with 934 additions and 177 deletions.
5 changes: 5 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@
"contents": "read"
}
},
"scality/scuba": {
"permissions": {
"contents": "read"
}
},
"scality/s3utils": {
"permissions": {
"contents": "read"
Expand Down
2 changes: 2 additions & 0 deletions .github/scripts/end2end/configs/zenko.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ spec:
logLevel: debug
utapi:
enable: false
scuba:
replicas: 1
management:
provider: InCluster
ui:
Expand Down
6 changes: 6 additions & 0 deletions .github/scripts/end2end/configs/zenkoversion.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ spec:
s3utils:
image: "${S3UTILS_DASHBOARD}"
tag: "${S3UTILS_TAG}"
scuba:
image: "${SCUBA_DASHBOARD}"
tag: "${SCUBA_TAG}"
kafkaCleaner:
image: "${KAFKA_CLEANER_DASHBOARD}"
tag: "${KAFKA_CLEANER_TAG}"
Expand Down Expand Up @@ -112,6 +115,9 @@ spec:
vault:
image: "${VAULT_IMAGE}"
tag: "${VAULT_TAG}"
scuba:
image: "${SCUBA_IMAGE}"
tag: "${SCUBA_TAG}"
shell:
image: "${BUSYBOX_IMAGE}"
tag: "${BUSYBOX_TAG}"
Expand Down
9 changes: 9 additions & 0 deletions .github/scripts/end2end/run-e2e-ctst.sh
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ WORLD_PARAMETERS="$(jq -c <<EOF
EOF
)"

# Set up environment variables for testing
kubectl set env deployment end2end-connector-cloudserver SCUBA_HEALTHCHECK_FREQUENCY=100
kubectl rollout status deployment end2end-connector-cloudserver

E2E_IMAGE=$E2E_CTST_IMAGE_NAME:$E2E_IMAGE_TAG
POD_NAME="${ZENKO_NAME}-ctst-tests"

Expand All @@ -117,6 +121,11 @@ docker run \
"${E2E_IMAGE}" /bin/bash \
-c "SUBDOMAIN=${SUBDOMAIN} CONTROL_PLANE_INGRESS_ENDPOINT=${OIDC_ENDPOINT} ACCOUNT=${ZENKO_ACCOUNT_NAME} KEYCLOAK_REALM=${KEYCLOAK_TEST_REALM_NAME} STORAGE_MANAGER=${STORAGE_MANAGER_USER_NAME} STORAGE_ACCOUNT_OWNER=${STORAGE_ACCOUNT_OWNER_USER_NAME} DATA_CONSUMER=${DATA_CONSUMER_USER_NAME} /ctst/bin/seedKeycloak.sh"; [[ $? -eq 1 ]] && exit 1 || echo 'Keycloak Configured!'

# Grant access to Kube API (insecure, only for testing)
kubectl create clusterrolebinding serviceaccounts-cluster-admin \
--clusterrole=cluster-admin \
--group=system:serviceaccounts

# Running end2end ctst tests
# Using overrides as we need to attach a local folder to the pod
kubectl run $POD_NAME \
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
VERSION="2.8.28"
VERSION="2.8.29"

VERSION_SUFFIX=

Expand Down
14 changes: 10 additions & 4 deletions solution/deps.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ cloudserver:
sourceRegistry: ghcr.io/scality
dashboard: cloudserver/cloudserver-dashboards
image: cloudserver
tag: 8.8.20
tag: 8.8.23
envsubst: CLOUDSERVER_TAG
jmx-javaagent:
sourceRegistry: ghcr.io/banzaicloud
Expand Down Expand Up @@ -75,8 +75,14 @@ s3utils:
sourceRegistry: ghcr.io/scality
dashboard: s3utils/s3utils-dashboards
image: s3utils
tag: 1.14.5
tag: 1.14.6
envsubst: S3UTILS_TAG
scuba:
sourceRegistry: ghcr.io/scality
dashboard: scuba/scuba-dashboards
image: scuba
tag: 1.0.0
envsubst: SCUBA_TAG
sorbet:
sourceRegistry: ghcr.io/scality
policy: sorbet/sorbet-policies
Expand All @@ -95,12 +101,12 @@ vault:
dashboard: vault2/vault-dashboards
policy: vault2/vault-policies
image: vault2
tag: 8.8.4
tag: 8.8.5
envsubst: VAULT_TAG
zenko-operator:
sourceRegistry: ghcr.io/scality
image: zenko-operator
tag: 1.5.45
tag: 1.5.46
envsubst: ZENKO_OPERATOR_TAG
zenko-ui:
sourceRegistry: ghcr.io/scality
Expand Down
6 changes: 6 additions & 0 deletions solution/zenkoversion.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ spec:
s3utils:
image: s3utils-dashboards
tag: '${S3UTILS_TAG}'
scuba:
image: scuba-dashboards
tag: '${SCUBA_TAG}'
policies:
backbeat:
image: backbeat-policies
Expand Down Expand Up @@ -81,6 +84,9 @@ spec:
s3utils:
image: s3utils
tag: '${S3UTILS_TAG}'
scuba:
image: scuba
tag: '${SCUBA_TAG}'
sorbet:
image: sorbet
tag: '${SORBET_TAG}'
Expand Down
125 changes: 103 additions & 22 deletions tests/ctst/common/common.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import fs from 'fs';
import { tmpNameSync } from 'tmp';
import { Given, setDefaultTimeout, Then, When } from '@cucumber/cucumber';
import { Constants, S3, Utils } from 'cli-testing';
import Zenko from 'world/Zenko';
import { extractPropertyFromResults, safeJsonParse } from './utils';
import { safeJsonParse } from './utils';
import assert from 'assert';
import { Admin, Kafka } from 'kafkajs';
import { createBucketWithConfiguration, putObject } from 'steps/utils/utils';
import { createBucketWithConfiguration, putObject, runActionAgainstBucket } from 'steps/utils/utils';
import { ActionPermissionsType } from 'steps/bucket-policies/utils';

setDefaultTimeout(Constants.DEFAULT_TIMEOUT);

Expand Down Expand Up @@ -78,36 +77,33 @@ function getObjectNameWithBackendFlakiness(this: Zenko, objectName: string) {
objectNameFinal = `${objectName}.scal-retry-${backendFlakiness}-job-${backendFlakinessRetryNumber}`;
break;
default:
process.stdout.write(`Unknown backend flakyness ${backendFlakiness}\n`);
this.parameters.logger?.debug('Unknown backend flakyness', { backendFlakiness });
return objectName;
}
return objectNameFinal;
}

async function addMultipleObjects(this: Zenko, numberObjects: number,
objectName: string, sizeBytes: number, userMD?: string) {
let lastResult = null;
for (let i = 1; i <= numberObjects; i++) {
const objectNameFinal = getObjectNameWithBackendFlakiness.call(this, `${objectName}-${i}`);

this.addToSaved('objectName', `${objectNameFinal}` || Utils.randomString());
const objectPath = tmpNameSync({prefix: this.getSaved<string>('objectName')});
fs.writeFileSync(objectPath, Buffer.alloc(sizeBytes, this.getSaved<string>('objectName')));
this.resetCommand();
this.addCommandParameter({ key: this.getSaved<string>('objectName') });
this.addCommandParameter({ bucket: this.getSaved<string>('bucketName') });
this.addCommandParameter({ body: objectPath });
const objectNameFinal = getObjectNameWithBackendFlakiness.call(this, `${objectName}-${i}`) ||
Utils.randomString();
if (sizeBytes > 0) {
this.addToSaved('objectSize', sizeBytes);
}
if (userMD) {
this.addCommandParameter({ metadata: JSON.stringify(userMD) });
}
process.stdout.write(`Adding object ${objectNameFinal}\n`);
this.addToSaved('versionId', extractPropertyFromResults(
await S3.putObject(this.getCommandParameters()), 'VersionId')
);
fs.rmSync(objectPath);
this.addToSaved('objectName', objectNameFinal);
this.parameters.logger?.debug('Adding object', { objectName: objectNameFinal });
lastResult = await putObject(this, objectNameFinal);
const createdObjects = this.getSaved<Map<string, string>>('createdObjects') || new Map<string, string>();
createdObjects.set(this.getSaved<string>('objectName'), this.getSaved<string>('versionId'));
this.addToSaved('createdObjects', createdObjects);
}
return lastResult;
}

async function addUserMetadataToObject(this: Zenko, objectName: string|undefined, userMD: string) {
Expand All @@ -122,19 +118,24 @@ async function addUserMetadataToObject(this: Zenko, objectName: string|undefined
return await S3.copyObject(this.getCommandParameters());
}

async function getTopicsOffsets(topics:string[], kafkaAdmin:Admin) {
async function getTopicsOffsets(topics: string[], kafkaAdmin: Admin) {
const offsets = [];
for (const topic of topics) {
const partitions: ({ high: string; low: string; })[] =
await kafkaAdmin.fetchTopicOffsets(topic);
await kafkaAdmin.fetchTopicOffsets(topic);
offsets.push({ topic, partitions });
}
return offsets;
}

Given('an account', async function (this: Zenko) {
await this.createAccount();
});

Given('a {string} bucket', async function (this: Zenko, versioning: string) {
this.resetCommand();
const preName = this.parameters.AccountName || Constants.ACCOUNT_NAME;
const preName = this.getSaved<string>('accountName') ||
this.parameters.AccountName || Constants.ACCOUNT_NAME;
const bucketName = `${preName}${Constants.BUCKET_NAME_TEST}${Utils.randomString()}`.toLocaleLowerCase();
this.addToSaved('bucketName', bucketName);
this.addCommandParameter({ bucket: bucketName });
Expand Down Expand Up @@ -263,7 +264,8 @@ When('i restore object {string} for {int} days', async function (this: Zenko, ob
this.addCommandParameter({ versionId });
}
this.addCommandParameter({ restoreRequest: `Days=${days}` });
await S3.restoreObject(this.getCommandParameters());
const result = await S3.restoreObject(this.getCommandParameters());
this.setResult(result);
});

// wait for object to transition to a location or get restored from it
Expand Down Expand Up @@ -407,3 +409,82 @@ Given('an object {string} that {string}', async function (this: Zenko, objectNam
await putObject(this, objectName);
}
});

When('the user tries to perform the current S3 action on the bucket {int} times with a {int} ms delay',
async function (this: Zenko, numberOfRuns: number, delay: number) {
this.setAuthMode('test_identity');
const action = {
...this.getSaved<ActionPermissionsType>('currentAction'),
};
if (action.action.includes('Version') && !action.action.includes('Versioning')) {
action.action = action.action.replace('Version', '');
this.addToSaved('currentAction', action);
}
for (let i = 0; i < numberOfRuns; i++) {
// For repeated WRITE actions, we want to change the object name
if (action.action === 'PutObject') {
this.addToSaved('objectName', `objectrepeat-${Utils.randomString()}`);
} else if (action.action === 'CopyObject') {
this.addToSaved('copyObject', `objectrepeatcopy-${Utils.randomString()}`);
}
await runActionAgainstBucket(this, this.getSaved<ActionPermissionsType>('currentAction').action);
if (this.getResult().err) {
// stop at any error, the error will be evaluated in a separated step
return;
}
await Utils.sleep(delay);
}
});

Then('the API should {string} with {string}', function (this: Zenko, result: string, expected: string) {
this.cleanupEntity();
const action = this.getSaved<ActionPermissionsType>('currentAction');
switch (result) {
case 'succeed':
if (action.expectedResultOnAllowTest) {
assert.strictEqual(
this.getResult().err?.includes(action.expectedResultOnAllowTest) ||
this.getResult().stdout?.includes(action.expectedResultOnAllowTest) ||
this.getResult().err === null, true);
} else {
assert.strictEqual(!!this.getResult().err, false);
}
break;
case 'fail':
assert.strictEqual(this.getResult().err?.includes(expected), true);
break;
default:
throw new Error('The API should have a correct expected result defined');
}
});

Then('the operation finished without error', function (this: Zenko) {
this.cleanupEntity();
assert.strictEqual(!!this.getResult().err, false);
});

Given('an upload size of {int} B for the object {string}', async function (
this: Zenko,
size: number,
objectName: string
) {
this.addToSaved('objectSize', size);
if (this.getSaved<boolean>('preExistingObject')) {
if (objectName) {
this.addToSaved('objectName', objectName);
} else {
this.addToSaved('objectName', `object-${Utils.randomString()}`);
}
await putObject(this, this.getSaved<string>('objectName'));
}
});

When('I PUT an object with size {int}', async function (this: Zenko, size: number) {
if (size > 0) {
this.addToSaved('objectSize', size);
}
this.addToSaved('objectName', `object-${Utils.randomString()}`);
const result = await addMultipleObjects.call(
this, 1, this.getSaved<string>('objectName'), size);
this.setResult(result!);
});
57 changes: 57 additions & 0 deletions tests/ctst/common/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import {
Before,
After,
defineParameterType,
Given,
setParallelCanAssign,
parallelCanAssignHelpers,
} from '@cucumber/cucumber';
import Zenko, { EntityType } from '../world/Zenko';
import { Scality } from 'cli-testing';
import { cleanAzureContainer, cleanZenkoLocation } from 'steps/azureArchive';
import { cleanS3Bucket } from './common';

// HTTPS should not cause any error for CTST
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

Expand All @@ -18,6 +23,58 @@ Before(async function (this: Zenko) {
await Zenko.init(this.parameters);
});

After(function (this: Zenko) {
this.resetSaved();
});

After({ tags: '@Quotas' }, async function () {
// Remove any quota at the end of the scenario, in case
// the account gets reused, placed after the global After
// hook to make sure it is executed first.
const world = this as Zenko;
// restore account
await world.createAccount();
await world.setupEntity(EntityType.STORAGE_MANAGER);
world.resumeAssumedRole();
world.addCommandParameter({
bucket: world.getSaved<string>('bucketName'),
});
const resultBucket = await Scality.deleteBucketQuota(
world.parameters,
world.getCliMode(),
world.getCommandParameters());
world.parameters.logger?.debug('DeleteBucketQuota result', {
resultBucket,
parameters: world.getCommandParameters(),
});
const resultAccount = await Scality.deleteAccountQuota(
world.parameters,
world.getCliMode());

world.parameters.logger?.debug('DeleteAccountQuota result', {
resultAccount,
parameters: world.getCommandParameters(),
});
if (resultBucket.err || resultAccount.err) {
throw new Error('Unable to delete quotas');
}
});

After({ tags: '@AzureArchive' }, async function (this: Zenko) {
await cleanS3Bucket(
this,
this.getSaved<string>('bucketName'),
);
await cleanZenkoLocation(
this,
this.getSaved<string>('locationName'),
);
await cleanAzureContainer(
this,
this.getSaved<string>('bucketName'),
);
});

defineParameterType({
name: 'type',
regexp: /(.*)/,
Expand Down
Loading

0 comments on commit 87f8380

Please sign in to comment.