Skip to content

Commit

Permalink
CLDSRV-397 Introduce the time-progression-factor flag
Browse files Browse the repository at this point in the history
The "time-progression-factor" variable serves as a testing-specific feature that accelerates the progression of time within a system.
By reducing the significance of each day, it enables the swift execution of specific actions, such as expiration, transition, and object locking, which are typically associated with longer timeframes.

This capability allows for efficient testing and evaluation of outcomes, optimizing the observation of processes that would normally take days or even years.
It's important to note that this variable is intended exclusively for testing purposes and is not employed in live production environments, where real-time progression is crucial for accurate results.
  • Loading branch information
nicolas2bert committed Jun 8, 2023
1 parent 399a2a5 commit c8150c6
Show file tree
Hide file tree
Showing 7 changed files with 292 additions and 28 deletions.
44 changes: 41 additions & 3 deletions lib/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ const crypto = require('crypto');
const { v4: uuidv4 } = require('uuid');
const cronParser = require('cron-parser');
const joi = require('@hapi/joi');

const { isValidBucketName } = require('arsenal').s3routes.routesUtils;
const validateAuthConfig = require('arsenal').auth.inMemory.validateAuthConfig;
const { s3routes, auth: arsenalAuth, s3middleware } = require('arsenal');
const { isValidBucketName } = s3routes.routesUtils;
const validateAuthConfig = arsenalAuth.inMemory.validateAuthConfig;
const { buildAuthDataAccount } = require('./auth/in_memory/builder');
const validExternalBackends = require('../constants').externalBackends;
const { azureAccountNameRegex, base64Regex,
allowedUtapiEventFilterFields, allowedUtapiEventFilterStates,
} = require('../constants');
const { utapiVersion } = require('utapi');
const { scaleMsPerDay } = s3middleware.objectUtils;


// config paths
Expand Down Expand Up @@ -1619,6 +1620,43 @@ class Config extends EventEmitter {

// Version of the configuration we're running under
this.overlayVersion = config.overlayVersion || 0;

this._setTimeOptions();
}

_setTimeOptions() {
// NOTE: EXPIRE_ONE_DAY_EARLIER and TRANSITION_ONE_DAY_EARLIER are deprecated in favor of
// TIME_PROGRESSION_FACTOR which decreases the weight attributed to a day in order to among other things
// expedite the lifecycle of objects.

// moves lifecycle expiration deadlines 1 day earlier, mostly for testing
const expireOneDayEarlier = process.env.EXPIRE_ONE_DAY_EARLIER === 'true';
// moves lifecycle transition deadlines 1 day earlier, mostly for testing
const transitionOneDayEarlier = process.env.TRANSITION_ONE_DAY_EARLIER === 'true';
// decreases the weight attributed to a day in order to expedite the lifecycle of objects.
const timeProgressionFactor = Number.parseInt(process.env.TIME_PROGRESSION_FACTOR, 10) || 1;

const isIncompatible = (expireOneDayEarlier || transitionOneDayEarlier) && (timeProgressionFactor > 1);
assert(!isIncompatible, 'The environment variables "EXPIRE_ONE_DAY_EARLIER" or ' +
'"TRANSITION_ONE_DAY_EARLIER" are not compatible with the "TIME_PROGRESSION_FACTOR" variable.');

// The scaledMsPerDay value is initially set to the number of milliseconds per day
// (24 * 60 * 60 * 1000) as the default value.
// However, during testing, if the timeProgressionFactor is defined and greater than 1,
// the scaledMsPerDay value is decreased. This adjustment allows for simulating actions occurring
// earlier in time.
const scaledMsPerDay = scaleMsPerDay(timeProgressionFactor);

this.timeOptions = {
expireOneDayEarlier,
transitionOneDayEarlier,
timeProgressionFactor,
scaledMsPerDay,
};
}

getTimeOptions() {
return this.timeOptions;
}

_getAuthData() {
Expand Down
18 changes: 10 additions & 8 deletions lib/api/apiUtils/object/expirationHeaders.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,25 @@ const {
LifecycleDateTime,
LifecycleUtils,
} = require('arsenal').s3middleware.lifecycleHelpers;
const { config } = require('../../../Config');

// moves lifecycle transition deadlines 1 day earlier, mostly for testing
const transitionOneDayEarlier = process.env.TRANSITION_ONE_DAY_EARLIER === 'true';
// moves lifecycle expiration deadlines 1 day earlier, mostly for testing
const expireOneDayEarlier = process.env.EXPIRE_ONE_DAY_EARLIER === 'true';
const {
expireOneDayEarlier,
transitionOneDayEarlier,
timeProgressionFactor,
scaledMsPerDay,
} = config.getTimeOptions();

const lifecycleDateTime = new LifecycleDateTime({
transitionOneDayEarlier,
expireOneDayEarlier,
timeProgressionFactor,
});

const lifecycleUtils = new LifecycleUtils(supportedLifecycleRules, lifecycleDateTime);

const oneDay = 24 * 60 * 60 * 1000; // Milliseconds in a day.
const lifecycleUtils = new LifecycleUtils(supportedLifecycleRules, lifecycleDateTime, timeProgressionFactor);

function calculateDate(objDate, expDays, datetime) {
return new Date(datetime.getTimestamp(objDate) + expDays * oneDay);
return new Date(datetime.getTimestamp(objDate) + (expDays * scaledMsPerDay));
}

function formatExpirationHeader(date, id) {
Expand Down
4 changes: 3 additions & 1 deletion lib/api/apiUtils/object/objectLockHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const moment = require('moment');
const { config } = require('../../../Config');
const vault = require('../../../auth/vault');

const { scaledMsPerDay } = config.getTimeOptions();
/**
* Calculates retain until date for the locked object version
* @param {object} retention - includes days or years retention period
Expand All @@ -19,8 +20,9 @@ function calculateRetainUntilDate(retention) {
const date = moment();
// Calculate the number of days to retain the lock on the object
const retainUntilDays = days || years * 365;
const retainUntilDaysInMs = retainUntilDays * scaledMsPerDay;
const retainUntilDate
= date.add(retainUntilDays, 'days');
= date.add(retainUntilDaysInMs, 'ms');
return retainUntilDate.toISOString();
}
/**
Expand Down
4 changes: 2 additions & 2 deletions lib/api/apiUtils/object/versioning.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const async = require('async');
const metadata = require('../../../metadata/wrapper');
const { config } = require('../../../Config');

const oneDay = 24 * 60 * 60 * 1000;
const { scaledMsPerDay } = config.getTimeOptions();

const versionIdUtils = versioning.VersionID;
// Use Arsenal function to generate a version ID used internally by metadata
Expand Down Expand Up @@ -461,7 +461,7 @@ function overwritingVersioning(objMD, metadataStoreParams) {
restoreRequestedAt: objMD.archive?.restoreRequestedAt,
restoreRequestedDays: objMD.archive?.restoreRequestedDays,
restoreCompletedAt: new Date(now),
restoreWillExpireAt: new Date(now + (days * oneDay)),
restoreWillExpireAt: new Date(now + (days * scaledMsPerDay)),
};

/* eslint-enable no-param-reassign */
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"dependencies": {
"@azure/storage-blob": "^12.12.0",
"@hapi/joi": "^17.1.0",
"arsenal": "git+https://github.com/scality/arsenal#8.1.101",
"arsenal": "git+https://github.com/scality/arsenal#8.1.103",
"async": "~2.5.0",
"aws-sdk": "2.905.0",
"bucketclient": "scality/bucketclient#8.1.9",
Expand Down
98 changes: 97 additions & 1 deletion tests/unit/Config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
const assert = require('assert');
const { azureArchiveLocationConstraintAssert } = require('../../lib/Config');
const {
azureArchiveLocationConstraintAssert,
ConfigObject: ConfigObjectForTest,
} = require('../../lib/Config');

describe('Config', () => {
const envToRestore = [];
Expand Down Expand Up @@ -254,6 +257,99 @@ describe('Config', () => {
});
});

describe('time options', () => {
it('should getTimeOptions', () => {
const config = new ConfigObjectForTest();
const expectedOptions = {
expireOneDayEarlier: false,
transitionOneDayEarlier: false,
timeProgressionFactor: 1,
scaledMsPerDay: 86400000,
};
const timeOptions = config.getTimeOptions();
assert.deepStrictEqual(timeOptions, expectedOptions);
});

it('should getTimeOptions with TIME_PROGRESSION_FACTOR', () => {
setEnv('TIME_PROGRESSION_FACTOR', 2);

const config = new ConfigObjectForTest();
const expectedOptions = {
expireOneDayEarlier: false,
transitionOneDayEarlier: false,
timeProgressionFactor: 2,
scaledMsPerDay: 43200000,
};
const timeOptions = config.getTimeOptions();
assert.deepStrictEqual(timeOptions, expectedOptions);
});

it('should getTimeOptions with EXPIRE_ONE_DAY_EARLIER', () => {
setEnv('EXPIRE_ONE_DAY_EARLIER', true);

const config = new ConfigObjectForTest();
const expectedOptions = {
expireOneDayEarlier: true,
transitionOneDayEarlier: false,
timeProgressionFactor: 1,
scaledMsPerDay: 86400000,
};
const timeOptions = config.getTimeOptions();
assert.deepStrictEqual(timeOptions, expectedOptions);
});

it('should getTimeOptions with TRANSITION_ONE_DAY_EARLIER', () => {
setEnv('TRANSITION_ONE_DAY_EARLIER', true);

const config = new ConfigObjectForTest();
const expectedOptions = {
expireOneDayEarlier: false,
transitionOneDayEarlier: true,
timeProgressionFactor: 1,
scaledMsPerDay: 86400000,
};
const timeOptions = config.getTimeOptions();
assert.deepStrictEqual(timeOptions, expectedOptions);
});

it('should getTimeOptions with both EXPIRE_ONE_DAY_EARLIER and TRANSITION_ONE_DAY_EARLIER', () => {
setEnv('EXPIRE_ONE_DAY_EARLIER', true);
setEnv('TRANSITION_ONE_DAY_EARLIER', true);

const config = new ConfigObjectForTest();
const expectedOptions = {
expireOneDayEarlier: true,
transitionOneDayEarlier: true,
timeProgressionFactor: 1,
scaledMsPerDay: 86400000,
};
const timeOptions = config.getTimeOptions();
assert.deepStrictEqual(timeOptions, expectedOptions);
});

it('should throw error if EXPIRE_ONE_DAY_EARLIER and TIME_PROGRESSION_FACTOR', () => {
setEnv('EXPIRE_ONE_DAY_EARLIER', true);
setEnv('TIME_PROGRESSION_FACTOR', 2);

assert.throws(() => new ConfigObjectForTest());
});

it('should throw error if TRANSITION_ONE_DAY_EARLIER and TIME_PROGRESSION_FACTOR', () => {
setEnv('TRANSITION_ONE_DAY_EARLIER', true);
setEnv('TIME_PROGRESSION_FACTOR', 2);

assert.throws(() => new ConfigObjectForTest());
});

it('should throw error if both EXPIRE/TRANSITION_ONE_DAY_EARLIER and TIME_PROGRESSION_FACTOR', () => {
setEnv('EXPIRE_ONE_DAY_EARLIER', true);
setEnv('TRANSITION_ONE_DAY_EARLIER', true);
setEnv('TIME_PROGRESSION_FACTOR', 2);

assert.throws(() => new ConfigObjectForTest());
});
});

describe('utapi option setup', () => {
let oldConfig;

Expand Down
Loading

0 comments on commit c8150c6

Please sign in to comment.