Skip to content

Commit

Permalink
NC | NSFS | Add stat to account_id_cache
Browse files Browse the repository at this point in the history
Signed-off-by: shirady <57721533+shirady@users.noreply.github.com>
  • Loading branch information
shirady committed Jan 1, 2025
1 parent 97084c7 commit 06639d0
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 9 deletions.
9 changes: 8 additions & 1 deletion config.js
Original file line number Diff line number Diff line change
Expand Up @@ -635,16 +635,24 @@ config.REMOTE_NOOAA_NAMESPACE = `remote-${config.KUBE_APP_LABEL}`;
///////////////////////////////
config.INLINE_MAX_SIZE = 4096;

///////////////////////////////
// CACHE (ACCOUNT, BUCKET) //
///////////////////////////////

// Object SDK bucket cache expiration time
config.OBJECT_SDK_BUCKET_CACHE_EXPIRY_MS = 60000;
// Object SDK account cache expiration time
config.OBJECT_SDK_ACCOUNT_CACHE_EXPIRY_MS = Number(process.env.ACCOUNTS_CACHE_EXPIRY) || 10 * 60 * 1000; // TODO: Decide on a time that we want to invalidate
// Accountspace_fs account id cache expiration time
config.ACCOUNTS_ID_CACHE_EXPIRY = 3 * 60 * 1000; // TODO: Decide on a time that we want to invalidate


// Object SDK bucket_namespace_cache allow stat of the config file
config.NC_ENABLE_BUCKET_NS_CACHE_STAT_VALIDATION = true;
// Object SDK account_cache allow stat of the config file
config.NC_ENABLE_ACCOUNT_CACHE_STAT_VALIDATION = true;
// accountspace_fs allow stat of the config file
config.NC_ENABLE_ACCOUNT_ID_CACHE_STAT_VALIDATION = true;

//////////////////////////////
// OPERATOR RELATED //
Expand Down Expand Up @@ -932,7 +940,6 @@ config.NC_DISABLE_HEALTH_ACCESS_CHECK = false;
config.NC_DISABLE_POSIX_MODE_ACCESS_CHECK = true;
config.NC_DISABLE_SCHEMA_CHECK = false;

config.ACCOUNTS_ID_CACHE_EXPIRY = 3 * 60 * 1000;
////////// GPFS //////////
config.GPFS_DOWN_DELAY = 1000;

Expand Down
54 changes: 47 additions & 7 deletions src/sdk/accountspace_fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,62 @@ const dummy_service_name = 's3';

const account_id_cache = new LRUCache({
name: 'AccountIDCache',
// TODO: Decide on a time that we want to invalidate
expiry_ms: Number(config.ACCOUNTS_ID_CACHE_EXPIRY),
expiry_ms: config.ACCOUNTS_ID_CACHE_EXPIRY,
make_key: ({ _id }) => _id,
/**
* Accounts are added to the cache based on id, Default value for show_secrets and decrypt_secret_key will be true,
* and show_secrets and decrypt_secret_key `false` only when we load cache from the health script,
* health script doesn't need to fetch or decrypt the secret.
* @param {{ _id: string,
* show_secrets?: boolean,
* decrypt_secret_key?: boolean,
* config_fs: import('./config_fs').ConfigFS }} params
* @param {{
* _id: string,
* show_secrets?: boolean,
* decrypt_secret_key?: boolean,
* config_fs: ConfigFS
* }} params
*/
load: async ({ _id, show_secrets = true, decrypt_secret_key = true, config_fs}) =>
config_fs.get_identity_by_id(_id, CONFIG_TYPES.ACCOUNT, { show_secrets: show_secrets, decrypt_secret_key: decrypt_secret_key }),
config_fs.get_identity_by_id_and_stat_file(_id, CONFIG_TYPES.ACCOUNT,
{ show_secrets: show_secrets, decrypt_secret_key: decrypt_secret_key }),
validate: (params, data) => _validate_account_id(params, data),
});

/** _validate_account_id is an additional layer (to expiry_ms)
* to checks the stat of the config file
* NOTE: this function was copied from object_sdk file (with needed changes)
* @param {object} data
* @param {object} params
* @returns Promise<{boolean>}
*/
async function _validate_account_id(params, data) {
if (config.NC_ENABLE_ACCOUNT_ID_CACHE_STAT_VALIDATION) {
const same_stat = await check_same_stat_account(params.access_keys[0].access_key, params.stat, data.config_fs);
if (!same_stat) { // config file of account was changed
return false;
}
}
return true;
}

/**
* check_same_stat_account will return true the config file was not changed
* in case we had any issue (for example error during stat) the returned value will be undefined
* NOTE: this function was copied from object_sdk file (with needed changes - passing the argument of config_fs)
* @param {Symbol|string} access_key
* @param {nb.NativeFSStats} account_stat
* @param {ConfigFS} config_fs
* @returns Promise<{boolean|undefined>}
*/
async function check_same_stat_account(access_key, account_stat, config_fs) {
if (!config_fs) return;
try {
const current_stat = await config_fs.stat_account_config_file(access_key);
if (current_stat) {
return current_stat.ino === account_stat.ino && current_stat.mtimeNsBigint === account_stat.mtimeNsBigint;
}
} catch (err) {
dbg.warn('check_same_stat_account: current_stat got an error', err, 'ignoring...');
}
}

/**
* @param {Object} requested_account
Expand Down
47 changes: 47 additions & 0 deletions src/sdk/config_fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,53 @@ class ConfigFS {
return identity;
}

/**
* get_identity_by_id_and_stat_file returns the full account/user data and stat the file:
* 1. try by identity path
* 2. if not found - try by account name with new accounts path
* 3. if not found - try by account name with old accounts path
* @param {string} id
* @param {string} [type]
* @param {{show_secrets?: boolean, decrypt_secret_key?: boolean, silent_if_missing?: boolean}} [options]
* @returns {Promise<Object>}
*/
async get_identity_by_id_and_stat_file(id, type, options = {}) {
const identity_path = this.get_identity_path_by_id(id);
const identity = await this.get_identity_by_id(id, type, options);
try {
const stat_by_identity_path = await nb_native().fs.stat(this.fs_context, identity_path);
identity.stat = stat_by_identity_path;
} catch (err_stat_by_identity_path) {
if (err_stat_by_identity_path.code === 'ENOENT') {
dbg.warn('get_identity_by_id_and_stat_file: could not stat by identity ID will try to stat by account name');
const account_name = identity.name;
const account_name_new_path = this.get_account_path_by_name(account_name);
try {
const stat_by_account_name = await nb_native().fs.stat(this.fs_context, account_name_new_path);
identity.stat = stat_by_account_name;
} catch (err_stat_by_account_name_new_path) {
if (err_stat_by_identity_path.code === 'ENOENT') {
dbg.warn('get_identity_by_id_and_stat_file: could not stat by by account name (new accounts path)');
const account_name_old_path = this._get_old_account_path_by_name(account_name);
try {
const stat_by_account_name_old = await nb_native().fs.stat(this.fs_context, account_name_old_path);
identity.stat = stat_by_account_name_old;
} catch (err_stat_by_account_name_old_path) {
dbg.warn('get_identity_by_id_and_stat_file: could not stat by by account name (old accounts path)');
// eslint-disable-next-line max-depth
if (!options.silent_if_missing) {
const error_to_throw = new Error(`Could not stat identity by id ${id} or by account name ${account_name}`);
error_to_throw.code = 'ENOENT';
throw error_to_throw;
}
}
}
}
}
}
return identity; // this identity object should have also a stat property
}

/**
* search_accounts_by_id searches old accounts directory and finds an account that its _id matches the given id param
* @param {string} id
Expand Down
3 changes: 2 additions & 1 deletion src/sdk/object_sdk.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const BucketSpaceNB = require('./bucketspace_nb');
const { RpcError } = require('../rpc');

const anonymous_access_key = Symbol('anonymous_access_key');

const bucket_namespace_cache = new LRUCache({
name: 'ObjectSDK-Bucket-Namespace-Cache',
// This is intentional. Cache entry expiration is handled by _validate_bucket_namespace().
Expand Down Expand Up @@ -93,7 +94,7 @@ async function _validate_account(data, params) {
const bs_allow_stat_account = Boolean(bs.check_same_stat_account);
if (bs_allow_stat_account && config.NC_ENABLE_ACCOUNT_CACHE_STAT_VALIDATION) {
const same_stat = await bs.check_same_stat_account(params.access_key, data.stat);
if (!same_stat) { // config file of bucket was changed
if (!same_stat) { // config file of account was changed
return false;
}
}
Expand Down
38 changes: 38 additions & 0 deletions src/test/unit_tests/test_nc_with_a_couple_of_forks.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ mocha.describe('operations with a couple of forks', async function() {

mocha.after(async () => {
fs_utils.folder_delete(`${config_root}`);
fs_utils.folder_delete(`${new_bucket_path_param}`);
});

mocha.it('versioning change with a couple of forks', async function() {
Expand Down Expand Up @@ -147,4 +148,41 @@ mocha.describe('operations with a couple of forks', async function() {
// cleanup
await s3_uid5_after_access_keys_update.deleteBucket({ Bucket: bucket_name2 });
});

mocha.it('head a bucket after account update (change fs_backend)', async function() {
// create additional account
const account_name = 'Oliver';
const account_options_create = { account_name, uid: 6001, gid: 6001, config_root: config_dir_name };
await fs_utils.create_fresh_path(new_bucket_path_param);
await fs.promises.chown(new_bucket_path_param, account_options_create.uid, account_options_create.gid);
await fs.promises.chmod(new_bucket_path_param, 0o700);
const access_details = await generate_nsfs_account(rpc_client, EMAIL, new_bucket_path_param, account_options_create);
// check the account status
const account_options_status = { config_root: config_dir_name, name: account_name};
const res_account_status = await exec_manage_cli(TYPES.ACCOUNT, ACTIONS.STATUS, account_options_status);
assert.equal(JSON.parse(res_account_status).response.code, ManageCLIResponse.AccountStatus.code);
// generate the s3 client
const s3_uid6001 = generate_s3_client(access_details.access_key,
access_details.secret_key, CORETEST_ENDPOINT);
// check the connection for the new account (can be any of the forks)
const res_list_buckets = await s3_uid6001.listBuckets({});
assert.equal(res_list_buckets.$metadata.httpStatusCode, 200);
// create a bucket
const bucket_name3 = 'bucket3';
const res_bucket_create = await s3_uid6001.createBucket({ Bucket: bucket_name3 });
assert.equal(res_bucket_create.$metadata.httpStatusCode, 200);
// head the bucket
const res_head_bucket1 = await s3_uid6001.headBucket({Bucket: bucket_name3});
assert.equal(res_head_bucket1.$metadata.httpStatusCode, 200);
// update the account
const account_options_update = { config_root: config_dir_name, name: account_name, fs_backend: 'GPFS'};
const res_account_update = await exec_manage_cli(TYPES.ACCOUNT, ACTIONS.UPDATE, account_options_update);
assert.equal(JSON.parse(res_account_update).response.code, ManageCLIResponse.AccountUpdated.code);
// head the bucket (again)
const res_head_bucket2 = await s3_uid6001.headBucket({Bucket: bucket_name3});
assert.equal(res_head_bucket2.$metadata.httpStatusCode, 200);

// cleanup
await s3_uid6001.deleteBucket({ Bucket: bucket_name3 });
});
});

0 comments on commit 06639d0

Please sign in to comment.