Skip to content

Commit ba183be

Browse files
author
Aayush
committed
Added unit tests for nc
Signed-off-by: Aayush <[email protected]>
1 parent 29db382 commit ba183be

File tree

5 files changed

+213
-30
lines changed

5 files changed

+213
-30
lines changed

src/test/system_tests/test_utils.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,47 @@ const run_or_skip_test = cond => {
790790
} else return it.skip;
791791
};
792792

793+
/**
794+
* update_file_mtime updates the mtime of the target path
795+
* Warnings:
796+
* - This operation would change the mtime of the file to 5 days ago - which means that it changes the etag / obj_id of the object
797+
* - Please do not use on versioned objects (version_id will not be changed, but the mtime will be changed) - might cause issues.
798+
* @param {String} target_path
799+
* @returns {Promise<Void>}
800+
*/
801+
async function update_file_mtime(target_path) {
802+
const update_file_mtime_cmp = os_utils.IS_MAC ? `touch -t $(date -v -5d +"%Y%m%d%H%M.%S") ${target_path}` : `touch -d "5 days ago" ${target_path}`;
803+
await os_utils.exec(update_file_mtime_cmp, { return_stdout: true });
804+
}
805+
806+
/**
807+
* validate_expiration_header validates the `x-amz-expiration` header against the object creation time, expected rule ID and expiration days
808+
*
809+
* The header is expected to have the format:
810+
* expiry-date="YYYY-MM-DDTHH:MM:SS.SSSZ", rule-id="RULE_ID"
811+
*
812+
* @param {string} expiration_header - expiration header value
813+
* @param {string|Date} start_time - start/create time (string or Date) of the object
814+
* @param {string} expected_rule_id - expected rule ID to match in the header
815+
* @param {number} delta_days - expected number of days between start_time and expiry-date
816+
* @returns {boolean} true if the header is valid and matches the expected_rule_id and delta_days otherwise false
817+
*/
818+
function validate_expiration_header(expiration_header, start_time, expected_rule_id, delta_days) {
819+
const match = expiration_header.match(/expiry-date="(.+)", rule-id="(.+)"/);
820+
if (!match) return false;
821+
822+
const [, expiry_str, rule_id] = match;
823+
const expiration = new Date(expiry_str);
824+
const start = new Date(start_time);
825+
start.setUTCHours(0, 0, 0, 0); // adjusting to midnight UTC otherwise the tests will fail - fix for ceph-s3 tests
826+
827+
const days_diff = Math.floor((expiration.getTime() - start.getTime()) / (24 * 60 * 60 * 1000));
828+
829+
return days_diff === delta_days && rule_id === expected_rule_id;
830+
}
831+
832+
exports.update_file_mtime = update_file_mtime;
833+
exports.validate_expiration_header = validate_expiration_header;
793834
exports.run_or_skip_test = run_or_skip_test;
794835
exports.blocks_exist_on_cloud = blocks_exist_on_cloud;
795836
exports.create_hosts_pool = create_hosts_pool;

src/test/unit_tests/jest_tests/test_nc_lifecycle_posix_integration.test.js

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const fs = require('fs');
1111
const config = require('../../../../config');
1212
const fs_utils = require('../../../util/fs_utils');
1313
const { ConfigFS } = require('../../../sdk/config_fs');
14-
const { TMP_PATH, set_nc_config_dir_in_config, TEST_TIMEOUT, exec_manage_cli, create_system_json } = require('../../system_tests/test_utils');
14+
const { TMP_PATH, set_nc_config_dir_in_config, TEST_TIMEOUT, exec_manage_cli, create_system_json, update_file_mtime } = require('../../system_tests/test_utils');
1515
const { TYPES, ACTIONS } = require('../../../manage_nsfs/manage_nsfs_constants');
1616
const NamespaceFS = require('../../../sdk/namespace_fs');
1717
const endpoint_stats_collector = require('../../../sdk/endpoint_stats_collector');
@@ -2187,19 +2187,6 @@ async function create_object(object_sdk, bucket, key, size, is_old, tagging) {
21872187
return res;
21882188
}
21892189

2190-
/**
2191-
* update_file_mtime updates the mtime of the target path
2192-
* Warnings:
2193-
* - This operation would change the mtime of the file to 5 days ago - which means that it changes the etag / obj_id of the object
2194-
* - Please do not use on versioned objects (version_id will not be changed, but the mtime will be changed) - might cause issues.
2195-
* @param {String} target_path
2196-
* @returns {Promise<Void>}
2197-
*/
2198-
async function update_file_mtime(target_path) {
2199-
const update_file_mtime_cmp = os_utils.IS_MAC ? `touch -t $(date -v -5d +"%Y%m%d%H%M.%S") ${target_path}` : `touch -d "5 days ago" ${target_path}`;
2200-
await os_utils.exec(update_file_mtime_cmp, { return_stdout: true });
2201-
}
2202-
22032190
/**
22042191
* updates the number of noncurrent days xattr of target path to be 5 days older. use only on noncurrent objects.
22052192
* is use this function on latest object the xattr will be changed when the object turns noncurrent

src/test/unit_tests/nc_index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ require('./test_bucketspace_versioning');
2121
require('./test_nc_bucket_logging');
2222
require('./test_nc_online_upgrade_s3_integrations');
2323
require('./test_public_access_block');
24+
require('./test_nc_bucket_lifecycle');
2425

2526
// running with iam port
2627
require('./test_nc_iam_basic_integration.js'); // please notice that we use a different setup

src/test/unit_tests/test_lifecycle.js

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const MDStore = require('../../server/object_services/md_store').MDStore;
2222
const coretest = require('./coretest');
2323
const lifecycle = require('../../server/bg_services/lifecycle');
2424
const http_utils = require('../../util/http_utils');
25+
const test_utils = require('../../../src/test/system_tests/test_utils');
2526
const commonTests = require('../lifecycle/common');
2627
const seed = crypto.randomBytes(16);
2728
const generator = crypto.createCipheriv('aes-128-gcm', seed, Buffer.alloc(12));
@@ -824,7 +825,7 @@ mocha.describe('lifecycle', () => {
824825
res = await s3.headObject({ Bucket: bucket, Key: key });
825826
assert.ok(res.Expiration, 'expiration header missing in headObject response');
826827

827-
const valid = validate_expiration_header(res.Expiration, start_time, expected_id, expected_days);
828+
const valid = test_utils.validate_expiration_header(res.Expiration, start_time, expected_id, expected_days);
828829
assert.ok(valid, `expected rule ${expected_id} to match`);
829830
};
830831

@@ -845,21 +846,6 @@ mocha.describe('lifecycle', () => {
845846
};
846847
}
847848

848-
function validate_expiration_header(expiration_header, start_time, expected_rule_id, delta_days) {
849-
const match = expiration_header.match(/expiry-date="(.+)", rule-id="(.+)"/);
850-
if (!match) return false;
851-
console.log("match: ", match);
852-
853-
const [, expiry_str, rule_id] = match;
854-
const expiration = new Date(expiry_str);
855-
const start = new Date(start_time);
856-
start.setUTCHours(0, 0, 0, 0); // adjusting to midnight UTC otherwise the tests will fail - fix for ceph-s3 tests
857-
858-
const days_diff = Math.floor((expiration.getTime() - start.getTime()) / (24 * 60 * 60 * 1000));
859-
860-
return days_diff === delta_days && rule_id === expected_rule_id;
861-
}
862-
863849
mocha.it('should select rule with longest prefix', async () => {
864850
const rules = [
865851
generate_rule('short-prefix', 'test1/', [], undefined, undefined, 10),
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/* Copyright (C) 2024 NooBaa */
2+
'use strict';
3+
4+
const path = require('path');
5+
const mocha = require('mocha');
6+
const assert = require('assert');
7+
const fs_utils = require('../../util/fs_utils');
8+
const { TYPES, ACTIONS } = require('../../manage_nsfs/manage_nsfs_constants');
9+
const { TMP_PATH, set_path_permissions_and_owner, invalid_nsfs_root_permissions, generate_s3_client, get_coretest_path,
10+
validate_expiration_header, update_file_mtime, exec_manage_cli } = require('../system_tests/test_utils');
11+
const _ = require('lodash');
12+
13+
const coretest_path = get_coretest_path();
14+
const coretest = require(coretest_path);
15+
const { rpc_client, EMAIL, get_admin_mock_account_details } = coretest;
16+
coretest.setup({});
17+
18+
let s3_admin;
19+
20+
const tmp_fs_root = path.join(TMP_PATH, 'test_nc_bucket_lifecycle/');
21+
22+
/**
23+
* is_nc_coretest returns true when the test runs on NC env
24+
*/
25+
const is_nc_coretest = process.env.NC_CORETEST === 'true';
26+
27+
mocha.describe('nc lifecycle - check expiration header', async function () {
28+
const bucket_path = path.join(tmp_fs_root, 'test-bucket/');
29+
const bucket_name = 'test-bucket';
30+
31+
mocha.before(async function() {
32+
this.timeout(0); // eslint-disable-line no-invalid-this
33+
if (invalid_nsfs_root_permissions()) this.skip(); // eslint-disable-line no-invalid-this
34+
// create paths
35+
await fs_utils.create_fresh_path(tmp_fs_root, 0o777);
36+
await fs_utils.create_fresh_path(bucket_path, 0o770);
37+
await fs_utils.file_must_exist(bucket_path);
38+
39+
// set permissions
40+
if (is_nc_coretest) {
41+
const { uid, gid } = get_admin_mock_account_details();
42+
await set_path_permissions_and_owner(bucket_path, { uid, gid }, 0o700);
43+
}
44+
45+
// create s3_admin client
46+
const admin = (await rpc_client.account.read_account({ email: EMAIL, }));
47+
const admin_keys = admin.access_keys;
48+
s3_admin = generate_s3_client(admin_keys[0].access_key.unwrap(),
49+
admin_keys[0].secret_key.unwrap(),
50+
coretest.get_http_address());
51+
52+
// create test bucket
53+
const cli_bucket_options = {
54+
name: bucket_name,
55+
owner: admin.name,
56+
path: bucket_path,
57+
};
58+
await exec_manage_cli(TYPES.BUCKET, ACTIONS.ADD, cli_bucket_options);
59+
});
60+
61+
mocha.after(async function() {
62+
this.timeout(0); // eslint-disable-line no-invalid-this
63+
fs_utils.folder_delete(tmp_fs_root);
64+
});
65+
66+
const run_expiration_test = async ({ rules, expected_id, expected_days, key, tagging = undefined, size = 1000}) => {
67+
const putLifecycleParams = {
68+
Bucket: bucket_name,
69+
LifecycleConfiguration: { Rules: rules }
70+
};
71+
await s3_admin.putBucketLifecycleConfiguration(putLifecycleParams);
72+
73+
const putObjectParams = {
74+
Bucket: bucket_name,
75+
Key: key,
76+
Body: 'x'.repeat(size) // default 1KB if size not specified
77+
};
78+
if (tagging) {
79+
putObjectParams.Tagging = tagging;
80+
}
81+
const start_time = new Date();
82+
let res = await s3_admin.putObject(putObjectParams);
83+
assert.ok(res.Expiration, 'expiration header missing in putObject response');
84+
85+
// update file mtime to simulate a 5-days old object
86+
await update_file_mtime(path.join(bucket_path, key));
87+
88+
res = await s3_admin.headObject({ Bucket: bucket_name, Key: key });
89+
assert.ok(res.Expiration, 'expiration header missing in headObject response');
90+
91+
const valid = validate_expiration_header(res.Expiration, start_time, expected_id, expected_days - 5);
92+
assert.ok(valid, `expected rule ${expected_id} to match`);
93+
};
94+
95+
function generate_rule(expiration_days, id, prefix, tags, size_gt, size_lt) {
96+
const filters = {};
97+
if (prefix) filters.Prefix = prefix;
98+
if (Array.isArray(tags) && tags.length) filters.Tags = tags;
99+
if (size_gt !== undefined) filters.ObjectSizeGreaterThan = size_gt;
100+
if (size_lt !== undefined) filters.ObjectSizeLessThan = size_lt;
101+
102+
const filter = Object.keys(filters).length > 1 ? { And: filters } : filters;
103+
104+
return {
105+
ID: id,
106+
Status: 'Enabled',
107+
Filter: filter,
108+
Expiration: { Days: expiration_days },
109+
};
110+
}
111+
112+
mocha.it('should select rule with longest prefix', async () => {
113+
const rules = [
114+
generate_rule(10, 'short-prefix', 'lifecycle-test1/', [], undefined, undefined),
115+
generate_rule(17, 'long-prefix', 'lifecycle-test1/logs/', [], undefined, undefined),
116+
];
117+
await run_expiration_test({
118+
rules,
119+
key: 'lifecycle-test1/logs//file.txt',
120+
expected_id: 'long-prefix',
121+
expected_days: 17
122+
});
123+
});
124+
125+
mocha.it('should select rule with more tags when prefix is same', async () => {
126+
const rules = [
127+
generate_rule(5, 'one-tag', 'lifecycle-test2/', [{ Key: 'env', Value: 'prod' }], undefined, undefined),
128+
generate_rule(9, 'two-tags', 'lifecycle-test2/', [
129+
{ Key: 'env', Value: 'prod' },
130+
{ Key: 'team', Value: 'backend' }
131+
], undefined, undefined),
132+
];
133+
await run_expiration_test({
134+
rules,
135+
key: 'lifecycle-test2/file2.txt',
136+
tagging: 'env=prod&team=backend',
137+
expected_id: 'two-tags',
138+
expected_days: 9
139+
});
140+
});
141+
142+
mocha.it('should select rule with narrower size span when prefix and tags are matching', async () => {
143+
const rules = [
144+
generate_rule(4, 'wide-range', 'lifecycle-test3/', [], 100, 10000),
145+
generate_rule(6, 'narrow-range', 'lifecycle-test3/', [], 1000, 5000),
146+
];
147+
await run_expiration_test({
148+
rules,
149+
key: 'lifecycle-test3/file3.txt',
150+
size: 1500,
151+
expected_id: 'narrow-range',
152+
expected_days: 6
153+
});
154+
});
155+
156+
mocha.it('should fallback to first matching rule if all filters are equal', async () => {
157+
const rules = [
158+
generate_rule(7, 'rule-a', 'lifecycle/test4/', [], 0, 10000),
159+
generate_rule(11, 'rule-b', 'lifecycle/test4/', [], 0, 10000),
160+
];
161+
await run_expiration_test({
162+
rules,
163+
key: 'lifecycle/test4/file4.txt',
164+
expected_id: 'rule-a',
165+
expected_days: 7
166+
});
167+
});
168+
});

0 commit comments

Comments
 (0)