Skip to content

Commit 440434d

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

File tree

5 files changed

+212
-31
lines changed

5 files changed

+212
-31
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 & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@ 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');
18-
const os_utils = require('../../../util/os_utils');
1918
const { ManageCLIResponse } = require('../../../manage_nsfs/manage_nsfs_cli_responses');
2019
const { ManageCLIError } = require('../../../manage_nsfs/manage_nsfs_cli_errors');
2120
const buffer_utils = require('../../../util/buffer_utils');
@@ -2187,19 +2186,6 @@ async function create_object(object_sdk, bucket, key, size, is_old, tagging) {
21872186
return res;
21882187
}
21892188

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

0 commit comments

Comments
 (0)