diff --git a/src/manage_nsfs/nc_lifecycle.js b/src/manage_nsfs/nc_lifecycle.js index d34d3a8cfa..6ce47327bd 100644 --- a/src/manage_nsfs/nc_lifecycle.js +++ b/src/manage_nsfs/nc_lifecycle.js @@ -145,7 +145,7 @@ class NCLifecycle { } /** - * process_buckets does the following - + * process_buckets does the following - * 1. if it's a GPFS optimization - create candidates files * 2. iterates over buckets and handles their rules * @param {String[]} bucket_names @@ -364,6 +364,7 @@ class NCLifecycle { */ async get_candidates(bucket_json, lifecycle_rule, object_sdk) { const candidates = { abort_mpu_candidates: [], delete_candidates: [] }; + const versions_list = undefined; if (lifecycle_rule.expiration) { candidates.delete_candidates = await this.get_candidates_by_expiration_rule(lifecycle_rule, bucket_json, object_sdk); @@ -373,7 +374,12 @@ class NCLifecycle { } } if (lifecycle_rule.noncurrent_version_expiration) { - const non_current_candidates = await this.get_candidates_by_noncurrent_version_expiration_rule(lifecycle_rule, bucket_json); + const non_current_candidates = await this.get_candidates_by_noncurrent_version_expiration_rule( + lifecycle_rule, + bucket_json, + object_sdk, + {versions_list} + ); candidates.delete_candidates = candidates.delete_candidates.concat(non_current_candidates); } if (lifecycle_rule.abort_incomplete_multipart_upload) { @@ -428,14 +434,42 @@ class NCLifecycle { } } + /** + * load objects list batch and update state for the next cycle + * @param {Object} object_sdk + * @param {Object} lifecycle_rule + * @param {Object} bucket_json + * @param {Object} rule_state + * @returns + */ + async load_objects_list(object_sdk, lifecycle_rule, bucket_json, rule_state) { + const objects_list = await object_sdk.list_objects({ + bucket: bucket_json.name, + prefix: lifecycle_rule.filter?.prefix, + key_marker: rule_state.key_marker, + limit: config.NC_LIFECYCLE_LIST_BATCH_SIZE + }); + if (objects_list.is_truncated) { + rule_state.key_marker = objects_list.next_marker; + rule_state.is_finished = false; + } else { + rule_state.is_finished = true; + } + const bucket_state = this.lifecycle_run_status.buckets_statuses[bucket_json.name].state; + bucket_state.num_processed_objects += objects_list.objects.length; + return objects_list; + } + /** * * @param {*} lifecycle_rule * @param {Object} bucket_json - * @param {nb.ObjectSDK} object_sdk + * @param {nb.ObjectSDK} object_sdk * @returns {Promise} */ async get_candidates_by_expiration_rule_posix(lifecycle_rule, bucket_json, object_sdk) { + const rule_state = this.lifecycle_run_status.buckets_statuses[bucket_json.name].rules_statuses[lifecycle_rule.id].state.expire; + if (rule_state.is_finished) return []; const expiration = this._get_expiration_time(lifecycle_rule.expiration); if (expiration < 0) return []; const filter_func = this._build_lifecycle_filter({filter: lifecycle_rule.filter, expiration}); @@ -443,14 +477,7 @@ class NCLifecycle { const filtered_objects = []; // TODO list_objects does not accept a filter and works in batch sizes of 1000. should handle batching // also should maybe create a helper function or add argument for a filter in list object - const rule_state = this._get_rule_state(bucket_json, lifecycle_rule); - - const objects_list = await object_sdk.list_objects({ - bucket: bucket_json.name, - prefix: lifecycle_rule.filter?.prefix, - key_marker: rule_state.key_marker, - limit: config.NC_LIFECYCLE_LIST_BATCH_SIZE - }); + const objects_list = await this.load_objects_list(object_sdk, lifecycle_rule, bucket_json, rule_state); objects_list.objects.forEach(obj => { const lifecycle_object = this._get_lifecycle_object_info_from_list_object_entry(obj); if (filter_func(lifecycle_object)) { @@ -459,19 +486,11 @@ class NCLifecycle { filtered_objects.push(candidate); } }); - - const bucket_state = this.lifecycle_run_status.buckets_statuses[bucket_json.name].state; - bucket_state.num_processed_objects += objects_list.objects.length; - if (objects_list.is_truncated) { - rule_state.key_marker = objects_list.next_marker; - } else { - rule_state.is_finished = true; - } return filtered_objects; } /** - * get_candidates_by_expiration_rule_gpfs does the following - + * get_candidates_by_expiration_rule_gpfs does the following - * 1. gets the ilm candidates file path * 2. parses and returns the candidates from the files * @param {*} lifecycle_rule @@ -498,6 +517,64 @@ class NCLifecycle { ///////////////////////////////////////////// //////// NON CURRENT VERSION HELPERS //////// ///////////////////////////////////////////// + /** + * load versions list batch and update state for the next cycle + * @param {Object} object_sdk + * @param {Object} lifecycle_rule + * @param {Object} bucket_json + * @param {Object} rule_state + * @returns + */ + async load_versions_list(object_sdk, lifecycle_rule, bucket_json, rule_state) { + const list_versions = await object_sdk.list_object_versions({ + bucket: bucket_json.name, + prefix: lifecycle_rule.filter?.prefix, + limit: config.NC_LIFECYCLE_LIST_BATCH_SIZE, + key_marker: rule_state.key_marker_versioned, + version_id_marker: rule_state.version_id_marker + }); + if (list_versions.is_truncated) { + rule_state.is_finished = false; + rule_state.key_marker_versioned = list_versions.next_marker; + rule_state.version_id_marker = list_versions.next_version_id_marker; + } else { + rule_state.is_finished = true; + } + const bucket_state = this.lifecycle_run_status.buckets_statuses[bucket_json.name].state; + bucket_state.num_processed_objects += list_versions.objects.length; + return list_versions; + } + + /** + * check if object is delete candidate based on newer noncurrent versions rule + * @param {Object} object_info + * @param {Object} newer_noncurrent_state + * @param {Number} num_newer_versions + * @returns + */ + filter_newer_versions(object_info, newer_noncurrent_state, num_newer_versions) { + if (object_info.is_latest) { + newer_noncurrent_state.version_count = 0; //latest + newer_noncurrent_state.current_version = object_info.key; + return false; + } + newer_noncurrent_state.version_count += 1; + if (newer_noncurrent_state.version_count > num_newer_versions) { + return true; + } + return false; + } + + /** + * check if object is delete candidate based on number of noncurrent days rule + * @param {Object} object_info + * @param {Number} num_non_current_days + * @returns + */ + filter_noncurrent_days(object_info, num_non_current_days) { + //TODO implement + return true; + } /** * get_candidates_by_noncurrent_version_expiration_rule processes the noncurrent version expiration rule @@ -508,11 +585,31 @@ class NCLifecycle { * @param {Object} bucket_json * @returns {Promise} */ - async get_candidates_by_noncurrent_version_expiration_rule(lifecycle_rule, bucket_json) { - // TODO - implement - return []; - } + async get_candidates_by_noncurrent_version_expiration_rule(lifecycle_rule, bucket_json, object_sdk, {versions_list}) { + const rule_state = this.lifecycle_run_status.buckets_statuses[bucket_json.name].rules_statuses[lifecycle_rule.id].state.noncurrent; + if (rule_state.is_finished) return []; + + if (!versions_list) { + versions_list = await this.load_versions_list(object_sdk, lifecycle_rule, bucket_json, rule_state); + } + + const filter_func = this._build_lifecycle_filter({filter: lifecycle_rule.filter, expiration: 0}); + const num_newer_versions = lifecycle_rule.noncurrent_version_expiration.newer_noncurrent_versions; + const num_non_current_days = lifecycle_rule.noncurrent_version_expiration.noncurrent_days; + const delete_candidates = []; + + for (const entry of versions_list.objects) { + const lifecycle_info = this._get_lifecycle_object_info_from_list_object_entry(entry); + if ((num_newer_versions === undefined || this.filter_newer_versions(entry, rule_state, num_newer_versions)) && + (num_non_current_days === undefined || this.filter_noncurrent_days(entry, num_non_current_days))) { + if (filter_func(lifecycle_info)) { + delete_candidates.push({key: entry.key, version_id: entry.version_id}); + } + } + } + return delete_candidates; + } //////////////////////////////////// ///////// ABORT MPU HELPERS //////// //////////////////////////////////// @@ -689,6 +786,11 @@ class NCLifecycle { this.init_bucket_status(bucket_name); } } + if (op_times.end_time) { + if (op_name === TIMED_OPS.PROCESS_RULE) { + this.update_rule_status_is_finished(bucket_name, rule_id); + } + } this._update_times_on_status({ op_name, op_times, bucket_name, rule_id }); this._update_error_on_status({ error, bucket_name, rule_id }); if (bucket_name && rule_id) { @@ -839,12 +941,23 @@ class NCLifecycle { */ init_rule_status(bucket_name, rule_id) { this.lifecycle_run_status.buckets_statuses[bucket_name].rules_statuses[rule_id] ??= {}; - this.lifecycle_run_status.buckets_statuses[bucket_name].rules_statuses[rule_id].state ??= {}; - this.lifecycle_run_status.buckets_statuses[bucket_name].rules_statuses[rule_id].rule_process_times ??= {}; + this.lifecycle_run_status.buckets_statuses[bucket_name].rules_statuses[rule_id].state ??= {expire: {}, noncurrent: {}}; + this.lifecycle_run_status.buckets_statuses[bucket_name].rules_statuses[rule_id].rule_process_times = {}; this.lifecycle_run_status.buckets_statuses[bucket_name].rules_statuses[rule_id].rule_stats ??= {}; return this.lifecycle_run_status.buckets_statuses[bucket_name].rules_statuses[rule_id]; } + /** + * updates the rule state if all actions finished + * @param {string} bucket_name + * @param {string} rule_id + */ + update_rule_status_is_finished(bucket_name, rule_id) { + const rule_state = this.lifecycle_run_status.buckets_statuses[bucket_name].rules_statuses[rule_id].state; + rule_state.is_finished = (rule_state.expire.is_finished === undefined || rule_state.expire.is_finished === true) && + (rule_state.noncurrent.is_finished === undefined || rule_state.noncurrent.is_finished === true); + } + /** * * @param {Object[]} buckets @@ -872,9 +985,9 @@ class NCLifecycle { /** * _set_rule_state sets the current rule state on the lifecycle run status - * @param {Object} bucket_json - * @param {*} lifecycle_rule - * @param {{is_finished?: Boolean | Undefined, candidates_file_offset?: number | undefined}} rule_state + * @param {Object} bucket_json + * @param {*} lifecycle_rule + * @param {{is_finished?: Boolean | Undefined, candidates_file_offset?: number | undefined}} rule_state * @returns {Void} */ _set_rule_state(bucket_json, lifecycle_rule, rule_state) { @@ -883,9 +996,9 @@ class NCLifecycle { /** * _get_rule_state gets the current rule state on the lifecycle run status - * @param {Object} bucket_json - * @param {*} lifecycle_rule - * @returns {{is_finished?: Boolean | Undefined, candidates_file_offset?: number | undefined}} rule_state + * @param {Object} bucket_json + * @param {*} lifecycle_rule + * @returns {{is_finished?: Boolean | Undefined, candidates_file_offset?: number | undefined}} rule_state */ _get_rule_state(bucket_json, lifecycle_rule) { return this.lifecycle_run_status.buckets_statuses[bucket_json.name].rules_statuses[lifecycle_rule.id].state; @@ -969,7 +1082,7 @@ class NCLifecycle { } /** - * get_mount_points returns a map of the following format - + * get_mount_points returns a map of the following format - * { mount_point_path1: {}, mount_point_path2: {} } * @returns {Promise} */ @@ -994,8 +1107,8 @@ class NCLifecycle { /** * find_mount_point_by_bucket_path finds the mount point of a given bucket path - * @param {Object} mount_point_to_policy_map - * @param {String} bucket_path + * @param {Object} mount_point_to_policy_map + * @param {String} bucket_path * @returns {String} */ find_mount_point_by_bucket_path(mount_point_to_policy_map, bucket_path) { @@ -1012,14 +1125,14 @@ class NCLifecycle { /** * create_gpfs_candidates_files creates a candidates file per mount point that is used by at least one bucket * 1. creates a map of mount point to buckets - * 2. for each bucket - + * 2. for each bucket - * 2.1. finds the mount point it belongs to * 2.2. convert the bucket's lifecycle policy to a GPFS ILM policy * 2.3. concat the bucket's GPFS ILM policy to the mount point policy file string - * 3. for each mount point - + * 3. for each mount point - * 3.1. writes the ILM policy to a tmp file * 3. creates the candidates file by applying the ILM policy - * @param {String[]} bucket_names + * @param {String[]} bucket_names * @returns {Promise} */ async create_gpfs_candidates_files(bucket_names) { @@ -1050,10 +1163,10 @@ class NCLifecycle { /** * convert_lifecycle_policy_to_gpfs_ilm_policy converts the lifecycle rule to GPFS ILM policy * currently we support expiration (current version) only - * TODO - implement gpfs optimization for non_current_days - + * TODO - implement gpfs optimization for non_current_days - * non current can't be on the same policy, when implementing non current we should split the policies - * @param {*} lifecycle_rule - * @param {Object} bucket_json + * @param {*} lifecycle_rule + * @param {Object} bucket_json * @returns {String} */ convert_lifecycle_policy_to_gpfs_ilm_policy(lifecycle_rule, bucket_json) { @@ -1074,7 +1187,7 @@ class NCLifecycle { /** * _get_gpfs_ilm_policy_base returns policy base definitions and bucket path phrase - * @param {{bucket_rule_id: String, in_bucket_path: String, in_bucket_internal_dir: String}} ilm_policy_helpers + * @param {{bucket_rule_id: String, in_bucket_path: String, in_bucket_internal_dir: String}} ilm_policy_helpers * @returns {String} */ _get_gpfs_ilm_policy_base(ilm_policy_helpers) { @@ -1091,7 +1204,7 @@ class NCLifecycle { /** * convert_expiry_rule_to_gpfs_ilm_policy converts the expiry rule to GPFS ILM policy * expiration rule works on latest version path (not inside .versions or in nested .versions) - * @param {*} lifecycle_rule + * @param {*} lifecycle_rule * @param {{in_versions_dir: String, in_nested_versions_dir: String}} ilm_policy_paths * @returns {String} */ @@ -1107,7 +1220,7 @@ class NCLifecycle { /** * convert_noncurrent_version_to_gpfs_ilm_policy converts the noncurrent version by days to GPFS ILM policy - * @param {*} lifecycle_rule + * @param {*} lifecycle_rule * @param {{in_versions_dir: String, in_nested_versions_dir: String}} ilm_policy_paths * @returns {String} */ @@ -1118,8 +1231,8 @@ class NCLifecycle { /** * convert_filter_to_gpfs_ilm_policy converts the filter to GPFS ILM policy - * @param {*} lifecycle_rule - * @param {Object} bucket_json + * @param {*} lifecycle_rule + * @param {Object} bucket_json * @returns {String} */ convert_filter_to_gpfs_ilm_policy(lifecycle_rule, bucket_json) { @@ -1139,8 +1252,8 @@ class NCLifecycle { /** * get_lifecycle_ilm_candidates_file_name gets the ILM policy file name - * @param {String} bucket_name - * @param {*} lifecycle_rule + * @param {String} bucket_name + * @param {*} lifecycle_rule * @returns {String} */ get_lifecycle_ilm_candidates_file_name(bucket_name, lifecycle_rule) { @@ -1151,8 +1264,8 @@ class NCLifecycle { /** * get_lifecycle_ilm_candidate_file_suffix returns the suffix of a candidates file based on bucket name, rule id and lifecycle run start * TODO - when noncurrent_version is supported, suffix should contain expiration/non_current_version_expiration rule type - * @param {String} bucket_name - * @param {*} lifecycle_rule + * @param {String} bucket_name + * @param {*} lifecycle_rule * @returns {String} */ get_lifecycle_ilm_candidate_file_suffix(bucket_name, lifecycle_rule) { @@ -1164,7 +1277,7 @@ class NCLifecycle { * write_tmp_ilm_policy writes the ILM policy string to a tmp file * TODO - delete the policy on restart and on is_finished of the rule * TODO - should we unlink the policy file if the file already exists? - might be dangerous - * @param {String} mount_point_path + * @param {String} mount_point_path * @param {String} ilm_policy_string * @returns {Promise} */ @@ -1192,7 +1305,7 @@ class NCLifecycle { /** * lifecycle_ilm_policy_path returns ilm policy file path based on bucket name and rule_id - * @param {String} mount_point_path + * @param {String} mount_point_path * @returns {String} */ get_gpfs_ilm_policy_file_path(mount_point_path) { @@ -1203,8 +1316,8 @@ class NCLifecycle { /** * get_gpfs_ilm_candidates_file_path returns ilm policy file path based on bucket name and rule_id - * @param {*} bucket_json - * @param {*} lifecycle_rule + * @param {*} bucket_json + * @param {*} lifecycle_rule * @returns {String} */ get_gpfs_ilm_candidates_file_path(bucket_json, lifecycle_rule) { @@ -1232,17 +1345,17 @@ class NCLifecycle { } /** - * parse_candidates_from_gpfs_ilm_policy does the following - - * 1. reads the candidates file line by line (Note - we set read_file_offset so we will read the file from the line we stopped last iteration)- + * parse_candidates_from_gpfs_ilm_policy does the following - + * 1. reads the candidates file line by line (Note - we set read_file_offset so we will read the file from the line we stopped last iteration)- * 1.1. if number of parsed candidates is above the batch size - break the loop and stop reading the candidates file - * 1.2. else - + * 1.2. else - * 1.2.1. update the new rule state * 1.2.2. parse the key from the candidate line * 1.2.3. push the key to the candidates array - * 2. if candidates file does not exist, we return without error because it's valid that no candidates found + * 2. if candidates file does not exist, we return without error because it's valid that no candidates found * @param {Object} bucket_json * @param {*} lifecycle_rule - * @param {String} rule_candidates_path + * @param {String} rule_candidates_path * @returns {Promise} parsed_candidates_array */ async parse_candidates_from_gpfs_ilm_policy(bucket_json, lifecycle_rule, rule_candidates_path) { @@ -1284,11 +1397,11 @@ class NCLifecycle { /** * _parse_key_from_line parses the object key from a candidate line - * candidate line (when using mmapplypolicy defer) is of the following format - - * example - + * candidate line (when using mmapplypolicy defer) is of the following format - + * example - * 17460 1316236366 0 -- /mnt/gpfs0/account1_new_buckets_path/bucket1_storage/key1.txt * if file is .folder (directory object) we need to return its parent directory - * @param {*} entry + * @param {*} entry */ _parse_key_from_line(entry, bucket_json) { const line_array = entry.path.split(' '); diff --git a/src/test/unit_tests/jest_tests/test_nc_lifecycle_cli.test.js b/src/test/unit_tests/jest_tests/test_nc_lifecycle_cli.test.js index 24b7adb78a..d896b326b1 100644 --- a/src/test/unit_tests/jest_tests/test_nc_lifecycle_cli.test.js +++ b/src/test/unit_tests/jest_tests/test_nc_lifecycle_cli.test.js @@ -10,7 +10,6 @@ const config = require('../../../../config'); const fs_utils = require('../../../util/fs_utils'); const { ConfigFS } = require('../../../sdk/config_fs'); const { TMP_PATH, set_nc_config_dir_in_config, TEST_TIMEOUT, exec_manage_cli, create_system_json } = require('../../system_tests/test_utils'); -const BucketSpaceFS = require('../../../sdk/bucketspace_fs'); const { TYPES, ACTIONS } = require('../../../manage_nsfs/manage_nsfs_constants'); const NamespaceFS = require('../../../sdk/namespace_fs'); const endpoint_stats_collector = require('../../../sdk/endpoint_stats_collector'); @@ -47,12 +46,6 @@ const lifecycle_rule_delete_all = [ }, }]; -function make_dummy_object_sdk(account_json) { - return { - requesting_account: account_json - }; -} - describe('noobaa cli - lifecycle - lock check', () => { const original_lifecycle_run_time = config.NC_LIFECYCLE_RUN_TIME; const original_lifecycle_run_delay = config.NC_LIFECYCLE_RUN_DELAY_LIMIT_MINS; @@ -140,15 +133,12 @@ describe('noobaa cli - lifecycle - lock check', () => { }); describe('noobaa cli - lifecycle', () => { - const bucketspace_fs = new BucketSpaceFS({ config_root }, undefined); const test_bucket_path = `${root_path}/${test_bucket}`; - const test_bucket2 = 'test-bucket2'; - const test_bucket2_path = `${root_path}/${test_bucket2}`; const test_key1 = 'test_key1'; const test_key2 = 'test_key2'; const prefix = 'test/'; const test_prefix_key = `${prefix}/test_key1`; - let dummy_sdk; + let object_sdk; let nsfs; beforeAll(async () => { @@ -158,9 +148,9 @@ describe('noobaa cli - lifecycle', () => { const res = await exec_manage_cli(TYPES.ACCOUNT, ACTIONS.ADD, account_options1); const json_account = JSON.parse(res).response.reply; console.log(json_account); - dummy_sdk = make_dummy_object_sdk(json_account); - await bucketspace_fs.create_bucket({ name: test_bucket }, dummy_sdk); - await bucketspace_fs.create_bucket({ name: test_bucket2 }, dummy_sdk); + object_sdk = new NsfsObjectSDK('', config_fs, json_account, "DISABLED", config_fs.config_root, undefined); + object_sdk.requesting_account = json_account; + await object_sdk.create_bucket({ name: test_bucket }); const bucket_json = await config_fs.get_bucket_by_name(test_bucket, undefined); nsfs = new NamespaceFS({ @@ -176,14 +166,12 @@ describe('noobaa cli - lifecycle', () => { }); afterEach(async () => { - await bucketspace_fs.delete_bucket_lifecycle({ name: test_bucket }); - await bucketspace_fs.delete_bucket_lifecycle({ name: test_bucket2 }); + await object_sdk.delete_bucket_lifecycle({ name: test_bucket }); await fs_utils.create_fresh_path(test_bucket_path); }); afterAll(async () => { await fs_utils.folder_delete(test_bucket_path); - await fs_utils.folder_delete(test_bucket2_path); await fs_utils.folder_delete(root_path); await fs_utils.folder_delete(config_root); }, TEST_TIMEOUT); @@ -199,12 +187,12 @@ describe('noobaa cli - lifecycle', () => { } } ]; - await bucketspace_fs.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); - const res = await nsfs.create_object_upload({ key: test_key1, bucket: test_bucket }, dummy_sdk); - await nsfs.create_object_upload({ key: test_key1, bucket: test_bucket }, dummy_sdk); + await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); + const res = await object_sdk.create_object_upload({ key: test_key1, bucket: test_bucket }); + await object_sdk.create_object_upload({ key: test_key1, bucket: test_bucket }); await update_mpu_mtime(res.obj_id); await exec_manage_cli(TYPES.LIFECYCLE, '', {disable_service_validation: 'true', disable_runtime_validation: 'true', config_root}, undefined, undefined); - const mpu_list = await nsfs.list_uploads({ bucket: test_bucket }, dummy_sdk); + const mpu_list = await object_sdk.list_uploads({ bucket: test_bucket }); expect(mpu_list.objects.length).toBe(1); //removed the mpu that was created 5 days ago }); @@ -219,13 +207,13 @@ describe('noobaa cli - lifecycle', () => { } } ]; - await bucketspace_fs.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); - let res = await nsfs.create_object_upload({ key: test_key1, bucket: test_bucket }, dummy_sdk); + await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); + let res = await object_sdk.create_object_upload({ key: test_key1, bucket: test_bucket }); await update_mpu_mtime(res.obj_id); - res = await nsfs.create_object_upload({ key: test_prefix_key, bucket: test_bucket }, dummy_sdk); + res = await object_sdk.create_object_upload({ key: test_prefix_key, bucket: test_bucket }); await update_mpu_mtime(res.obj_id); await exec_manage_cli(TYPES.LIFECYCLE, '', {disable_service_validation: 'true', disable_runtime_validation: 'true', config_root}, undefined, undefined); - const mpu_list = await nsfs.list_uploads({ bucket: test_bucket }, dummy_sdk); + const mpu_list = await object_sdk.list_uploads({ bucket: test_bucket }); expect(mpu_list.objects.length).toBe(1); //only removed test_prefix_key }); @@ -245,15 +233,14 @@ describe('noobaa cli - lifecycle', () => { } } ]; - await bucketspace_fs.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); - let res = await nsfs.create_object_upload( - {key: test_key1, bucket: test_bucket, tagging: [...tag_set, ...different_tag_set]}, - dummy_sdk); + await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); + let res = await object_sdk.create_object_upload( + {key: test_key1, bucket: test_bucket, tagging: [...tag_set, ...different_tag_set]}); await update_mpu_mtime(res.obj_id); - res = await nsfs.create_object_upload({ key: test_key1, bucket: test_bucket, tagging: different_tag_set}, dummy_sdk); + res = await object_sdk.create_object_upload({ key: test_key1, bucket: test_bucket, tagging: different_tag_set}); await update_mpu_mtime(res.obj_id); await exec_manage_cli(TYPES.LIFECYCLE, '', {disable_service_validation: 'true', disable_runtime_validation: 'true', config_root}, undefined, undefined); - const mpu_list = await nsfs.list_uploads({ bucket: test_bucket }, dummy_sdk); + const mpu_list = await object_sdk.list_uploads({ bucket: test_bucket }); expect(mpu_list.objects.length).toBe(1); }); @@ -270,12 +257,12 @@ describe('noobaa cli - lifecycle', () => { } } ]; - await bucketspace_fs.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); - await create_object_lifecycle(test_bucket, test_key1, 100, true); - await create_object_lifecycle(test_bucket, test_key2, 100, false); + await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); + await create_object(object_sdk, test_bucket, test_key1, 100, true); + await create_object(object_sdk, test_bucket, test_key2, 100, false); await exec_manage_cli(TYPES.LIFECYCLE, '', {disable_service_validation: 'true', disable_runtime_validation: 'true', config_root}, undefined, undefined); - const object_list = await nsfs.list_objects({bucket: test_bucket}, dummy_sdk); + const object_list = await object_sdk.list_objects({bucket: test_bucket}); expect(object_list.objects.length).toBe(1); expect(object_list.objects[0].key).toBe(test_key2); }); @@ -295,13 +282,13 @@ describe('noobaa cli - lifecycle', () => { } } ]; - await bucketspace_fs.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); - await create_object_lifecycle(test_bucket, test_key1, 100, false); - await create_object_lifecycle(test_bucket, test_key2, 100, false); + await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); + await create_object(object_sdk, test_bucket, test_key1, 100, false); + await create_object(object_sdk, test_bucket, test_key2, 100, false); await update_file_mtime(path.join(test_bucket_path, test_key1)); await exec_manage_cli(TYPES.LIFECYCLE, '', {disable_service_validation: 'true', disable_runtime_validation: 'true', config_root}, undefined, undefined); - const object_list = await nsfs.list_objects({bucket: test_bucket}, dummy_sdk); + const object_list = await object_sdk.list_objects({bucket: test_bucket}); expect(object_list.objects.length).toBe(0); //should delete all objects }); @@ -320,13 +307,12 @@ describe('noobaa cli - lifecycle', () => { } } ]; - await bucketspace_fs.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); - await create_object_lifecycle(test_bucket, test_key1, 100, false); - await create_object_lifecycle(test_bucket, test_key2, 100, false); + await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); + await create_object(object_sdk, test_bucket, test_key1, 100, true); + await create_object(object_sdk, test_bucket, test_key2, 100, false); - await update_file_mtime(path.join(test_bucket_path, test_key1)); await exec_manage_cli(TYPES.LIFECYCLE, '', {disable_service_validation: 'true', disable_runtime_validation: 'true', config_root}, undefined, undefined); - const object_list = await nsfs.list_objects({bucket: test_bucket}, dummy_sdk); + const object_list = await object_sdk.list_objects({bucket: test_bucket}); expect(object_list.objects.length).toBe(2); //should not delete any entry }); @@ -345,12 +331,12 @@ describe('noobaa cli - lifecycle', () => { } } ]; - await bucketspace_fs.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); - await create_object_lifecycle(test_bucket, test_key1, 100, true); - await create_object_lifecycle(test_bucket, test_prefix_key, 100, false); + await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); + await create_object(object_sdk, test_bucket, test_key1, 100, true); + await create_object(object_sdk, test_bucket, test_prefix_key, 100, false); await exec_manage_cli(TYPES.LIFECYCLE, '', {disable_service_validation: 'true', disable_runtime_validation: 'true', config_root}, undefined, undefined); - const object_list = await nsfs.list_objects({bucket: test_bucket}, dummy_sdk); + const object_list = await object_sdk.list_objects({bucket: test_bucket}); expect(object_list.objects.length).toBe(1); expect(object_list.objects[0].key).toBe(test_key1); }); @@ -371,13 +357,13 @@ describe('noobaa cli - lifecycle', () => { } } ]; - await bucketspace_fs.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); + await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); const test_key1_tags = [...tag_set, ...different_tag_set]; - await create_object_lifecycle(test_bucket, test_key1, 100, true, test_key1_tags); - await create_object_lifecycle(test_bucket, test_key2, 100, true, different_tag_set); + await create_object(object_sdk, test_bucket, test_key1, 100, true, test_key1_tags); + await create_object(object_sdk, test_bucket, test_key2, 100, true, different_tag_set); await exec_manage_cli(TYPES.LIFECYCLE, '', {disable_service_validation: 'true', disable_runtime_validation: 'true', config_root}, undefined, undefined); - const object_list = await nsfs.list_objects({bucket: test_bucket}, dummy_sdk); + const object_list = await object_sdk.list_objects({bucket: test_bucket}); expect(object_list.objects.length).toBe(1); expect(object_list.objects[0].key).toBe(test_key2); }); @@ -397,39 +383,179 @@ describe('noobaa cli - lifecycle', () => { } } ]; - await bucketspace_fs.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); + await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); - await create_object_lifecycle(test_bucket, test_key1, 100, true); - await create_object_lifecycle(test_bucket, test_key2, 80, true); - await create_object_lifecycle(test_bucket, test_prefix_key, 20, true); + await create_object(object_sdk, test_bucket, test_key1, 100, true); + await create_object(object_sdk, test_bucket, test_key2, 80, true); + await create_object(object_sdk, test_bucket, test_prefix_key, 20, true); await exec_manage_cli(TYPES.LIFECYCLE, '', {disable_service_validation: 'true', disable_runtime_validation: 'true', config_root}, undefined, undefined); - const object_list = await nsfs.list_objects({bucket: test_bucket}, dummy_sdk); + const object_list = await object_sdk.list_objects({bucket: test_bucket}); expect(object_list.objects.length).toBe(2); object_list.objects.forEach(element => { expect(element.key).not.toBe(test_key2); }); }); - //TODO same as create_object. need to change to used object_sdk amd remove this function - // already done in create noncurrent PR - async function create_object_lifecycle(bucket, key, size, is_old, tagging) { - const data = crypto.randomBytes(size); - await nsfs.upload_object({ - bucket, - key, - source_stream: buffer_utils.buffer_to_read_stream(data), - size, - tagging - }, dummy_sdk); - if (is_old) await update_file_mtime(path.join(root_path, bucket, key)); - } - async function update_mpu_mtime(obj_id) { const mpu_path = nsfs._mpu_path({obj_id}); return await update_file_mtime(mpu_path); } +}); + +describe('noobaa cli - lifecycle versioning ENABLE', () => { + const test_bucket_path = `${root_path}/${test_bucket}`; + const test_key1 = 'test_key1'; + const test_key2 = 'test_key2'; + const prefix = 'test/'; + const test_prefix_key = `${prefix}test_key1`; + let object_sdk; + beforeAll(async () => { + await fs_utils.create_fresh_path(config_root, 0o777); + set_nc_config_dir_in_config(config_root); + await fs_utils.create_fresh_path(root_path, 0o777); + const res = await exec_manage_cli(TYPES.ACCOUNT, ACTIONS.ADD, account_options1); + const json_account = JSON.parse(res).response.reply; + console.log(json_account); + object_sdk = new NsfsObjectSDK('', config_fs, json_account, "DISABLED", config_fs.config_root, undefined); + object_sdk.requesting_account = json_account; + await object_sdk.create_bucket({ name: test_bucket }); + await object_sdk.set_bucket_versioning({name: test_bucket, versioning: "ENABLED"}); + }); + + afterEach(async () => { + await object_sdk.delete_bucket_lifecycle({ name: test_bucket }); + await fs_utils.create_fresh_path(test_bucket_path); + }); + + afterAll(async () => { + await fs_utils.folder_delete(test_bucket_path); + await fs_utils.folder_delete(root_path); + await fs_utils.folder_delete(config_root); + }, TEST_TIMEOUT); + + it('lifecycle_cli - noncurrent expiration rule - expire older versions', async () => { + const lifecycle_rule = [ + { + "id": "expiration after 3 days with tags", + "status": "Enabled", + "filter": { + "prefix": "", + }, + "noncurrent_version_expiration": { + "newer_noncurrent_versions": 2 + } + } + ]; + await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); + + const res = await create_object(object_sdk, test_bucket, test_key1, 100, false); + await create_object(object_sdk, test_bucket, test_key1, 100, false); + await create_object(object_sdk, test_bucket, test_key1, 100, false); + await create_object(object_sdk, test_bucket, test_key1, 100, false); + + await exec_manage_cli(TYPES.LIFECYCLE, '', {disable_service_validation: 'true', disable_runtime_validation: 'true', config_root}, undefined, undefined); + const object_list = await object_sdk.list_object_versions({bucket: test_bucket}); + expect(object_list.objects.length).toBe(3); + object_list.objects.forEach(element => { + expect(element.version_id).not.toBe(res.version_id); + }); + }); + + it('lifecycle_cli - noncurrent expiration rule - expire older versions with filter', async () => { + const lifecycle_rule = [ + { + "id": "expiration after 3 days with tags", + "status": "Enabled", + "filter": { + "prefix": prefix, + "object_size_greater_than": 80, + }, + "noncurrent_version_expiration": { + "newer_noncurrent_versions": 1 + } + } + ]; + await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); + + await create_object(object_sdk, test_bucket, test_key1, 100, false); + await create_object(object_sdk, test_bucket, test_key1, 100, false); + await create_object(object_sdk, test_bucket, test_key1, 100, false); + + const res = await create_object(object_sdk, test_bucket, test_prefix_key, 100, false); + await create_object(object_sdk, test_bucket, test_prefix_key, 60, false); + await create_object(object_sdk, test_bucket, test_prefix_key, 100, false); + await create_object(object_sdk, test_bucket, test_prefix_key, 100, false); + + await exec_manage_cli(TYPES.LIFECYCLE, '', {disable_service_validation: 'true', disable_runtime_validation: 'true', config_root}, undefined, undefined); + const object_list = await object_sdk.list_object_versions({bucket: test_bucket}); + expect(object_list.objects.length).toBe(6); + object_list.objects.forEach(element => { + expect(element.version_id).not.toBe(res.version_id); + }); + }); + + it('lifecycle_cli - noncurrent expiration rule - expire older versions only delete markers', async () => { + const lifecycle_rule = [ + { + "id": "expiration after 3 days with tags", + "status": "Enabled", + "filter": { + "prefix": '', + "object_size_less_than": 1, + }, + "noncurrent_version_expiration": { + "newer_noncurrent_versions": 1 + } + } + ]; + await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); + + await object_sdk.delete_object({bucket: test_bucket, key: test_key1}); + await create_object(object_sdk, test_bucket, test_key1, 100, false); + await object_sdk.delete_object({bucket: test_bucket, key: test_key1}); + await create_object(object_sdk, test_bucket, test_key1, 100, false); + await create_object(object_sdk, test_bucket, test_key1, 100, false); + + await exec_manage_cli(TYPES.LIFECYCLE, '', {disable_service_validation: 'true', disable_runtime_validation: 'true', config_root}, undefined, undefined); + const object_list = await object_sdk.list_object_versions({bucket: test_bucket}); + expect(object_list.objects.length).toBe(3); + object_list.objects.forEach(element => { + expect(element.delete_marker).not.toBe(true); + }); + }); + + it('lifecycle_cli - noncurrent expiration rule - expire versioning enabled bucket', async () => { + const date = new Date(); + date.setDate(date.getDate() - 1); // yesterday + const lifecycle_rule = [ + { + "id": "expiration after 3 days", + "status": "Enabled", + "filter": { + "prefix": prefix, + }, + "expiration": { + "date": date.getTime() + } + } + ]; + await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); + + await create_object(object_sdk, test_bucket, test_prefix_key, 100, false); + await create_object(object_sdk, test_bucket, test_prefix_key, 100, false); + await create_object(object_sdk, test_bucket, test_key2, 100, false); + + await exec_manage_cli(TYPES.LIFECYCLE, '', {disable_service_validation: 'true', disable_runtime_validation: 'true', config_root}, undefined, undefined); + const object_list = await object_sdk.list_object_versions({bucket: test_bucket}); + expect(object_list.objects.length).toBe(4); //added delete marker + let has_delete_marker = false; + object_list.objects.forEach(element => { + if (element.delete_marker) has_delete_marker = true; + }); + expect(has_delete_marker).toBe(true); + }); }); @@ -473,7 +599,6 @@ describe('noobaa cli lifecycle - timeout check', () => { }); describe('noobaa cli - lifecycle batching', () => { - const bucketspace_fs = new BucketSpaceFS({ config_root }, undefined); const test_bucket_path = `${root_path}/${test_bucket}`; const test_key1 = 'test_key1'; const test_key2 = 'test_key2'; @@ -501,7 +626,7 @@ describe('noobaa cli - lifecycle batching', () => { }); afterEach(async () => { - await bucketspace_fs.delete_bucket_lifecycle({ name: test_bucket }); + await object_sdk.delete_bucket_lifecycle({ name: test_bucket }); await fs_utils.create_fresh_path(test_bucket_path); fs_utils.folder_delete(tmp_lifecycle_logs_dir_path); await config_fs.delete_config_json_file(); @@ -514,7 +639,7 @@ describe('noobaa cli - lifecycle batching', () => { }); it("lifecycle batching - with lifecycle rule, one batch", async () => { - await bucketspace_fs.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule_delete_all }); + await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule_delete_all }); await create_object(object_sdk, test_bucket, test_key1, 100, true); await create_object(object_sdk, test_bucket, test_key2, 100, true); @@ -541,7 +666,7 @@ describe('noobaa cli - lifecycle batching', () => { "days_after_initiation": 3 } }]; - await bucketspace_fs.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); + await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); await object_sdk.create_object_upload({ key: test_key1, bucket: test_bucket }); await object_sdk.create_object_upload({ key: test_key2, bucket: test_bucket }); @@ -554,7 +679,7 @@ describe('noobaa cli - lifecycle batching', () => { }); it("lifecycle batching - with lifecycle rule, multiple list batches, one bucket batch", async () => { - await bucketspace_fs.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule_delete_all }); + await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule_delete_all }); await create_object(object_sdk, test_bucket, test_key1, 100, true); await create_object(object_sdk, test_bucket, test_key2, 100, true); @@ -569,7 +694,7 @@ describe('noobaa cli - lifecycle batching', () => { }); it("lifecycle batching - with lifecycle rule, multiple list batches, multiple bucket batches", async () => { - await bucketspace_fs.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule_delete_all }); + await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule_delete_all }); await create_object(object_sdk, test_bucket, test_key1, 100, true); await create_object(object_sdk, test_bucket, test_key2, 100, true); @@ -586,7 +711,7 @@ describe('noobaa cli - lifecycle batching', () => { }); it("lifecycle batching - with lifecycle rule, multiple list batches, one bucket batch", async () => { - await bucketspace_fs.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule_delete_all }); + await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule_delete_all }); await create_object(object_sdk, test_bucket, test_key1, 100, true); await create_object(object_sdk, test_bucket, test_key2, 100, true); @@ -609,7 +734,7 @@ describe('noobaa cli - lifecycle batching', () => { }); it("lifecycle batching - continue finished lifecycle should do nothing", async () => { - await bucketspace_fs.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule_delete_all }); + await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule_delete_all }); await exec_manage_cli(TYPES.LIFECYCLE, '', { disable_service_validation: 'true', disable_runtime_validation: 'true', config_root }, true, undefined); await create_object(object_sdk, test_bucket, test_key1, 100, true); @@ -628,9 +753,9 @@ describe('noobaa cli - lifecycle batching', () => { }); it("continue lifecycle batching should finish the run - delete all", async () => { - await bucketspace_fs.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule_delete_all }); + await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule_delete_all }); await config_fs.update_config_json_file(JSON.stringify({ - NC_LIFECYCLE_TIMEOUT_MS: 60, + NC_LIFECYCLE_TIMEOUT_MS: 5, NC_LIFECYCLE_LOGS_DIR: tmp_lifecycle_logs_dir_path, NC_LIFECYCLE_BUCKET_BATCH_SIZE: 5, NC_LIFECYCLE_LIST_BATCH_SIZE: 2 @@ -645,13 +770,20 @@ describe('noobaa cli - lifecycle batching', () => { } catch (e) { //ignore timout error } + await config_fs.update_config_json_file(JSON.stringify({ + NC_LIFECYCLE_TIMEOUT_MS: config.NC_LIFECYCLE_TIMEOUT_MS, + NC_LIFECYCLE_LOGS_DIR: tmp_lifecycle_logs_dir_path, + NC_LIFECYCLE_BUCKET_BATCH_SIZE: 5, + NC_LIFECYCLE_LIST_BATCH_SIZE: 2 + + })); await exec_manage_cli(TYPES.LIFECYCLE, '', {continue: 'true', disable_service_validation: 'true', disable_runtime_validation: 'true', config_root}, undefined, undefined); const object_list2 = await object_sdk.list_objects({bucket: test_bucket}); expect(object_list2.objects.length).toBe(0); }); it("continue lifecycle batching should finish the run - validate new run. don't delete already deleted items", async () => { - await bucketspace_fs.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule_delete_all }); + await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule_delete_all }); await config_fs.update_config_json_file(JSON.stringify({ NC_LIFECYCLE_TIMEOUT_MS: 70, NC_LIFECYCLE_LOGS_DIR: tmp_lifecycle_logs_dir_path, @@ -693,6 +825,72 @@ describe('noobaa cli - lifecycle batching', () => { expect(res_keys).toContain(key); } }); + + it("lifecycle batching - with lifecycle rule, multiple list batches, multiple bucket batches - newer noncurrent versions", async () => { + const lifecycle_rule = [ + { + "id": "expiration after 3 days with tags", + "status": "Enabled", + "filter": { + "prefix": "", + }, + "noncurrent_version_expiration": { + "newer_noncurrent_versions": 2 + } + } + ]; + await object_sdk.set_bucket_versioning({ name: test_bucket, versioning: 'ENABLED' }); + await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); + + const res = await create_object(object_sdk, test_bucket, test_key1, 100, false); + for (let i = 0; i < 10; i++) { + await create_object(object_sdk, test_bucket, test_key1, 100, false); + } + const latest_lifecycle = await exec_manage_cli(TYPES.LIFECYCLE, '', {disable_service_validation: 'true', disable_runtime_validation: 'true', config_root}, undefined, undefined); + const parsed_res_latest_lifecycle = JSON.parse(latest_lifecycle); + expect(parsed_res_latest_lifecycle.response.reply.state.is_finished).toBe(true); + + const object_list = await object_sdk.list_object_versions({bucket: test_bucket}); + expect(object_list.objects.length).toBe(3); + object_list.objects.forEach(element => { + expect(element.version_id).not.toBe(res.version_id); + }); + }); + + it("lifecycle rule, multiple list batches, multiple bucket batches - both expire and noncurrent actions", async () => { + const lifecycle_rule = [ + { + "id": "expiration after 3 days with tags", + "status": "Enabled", + "filter": { + "prefix": "", + }, + "expiration": { + "date": yesterday.getTime() + }, + "noncurrent_version_expiration": { + "newer_noncurrent_versions": 2 + } + } + ]; + await object_sdk.set_bucket_versioning({ name: test_bucket, versioning: 'ENABLED' }); + await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule }); + + const keys = [test_key1, test_key2, "key3", "key4", "key5", "key6", "key7"]; + for (const key of keys) { + await create_object(object_sdk, test_bucket, key, 100, false); + } + for (let i = 0; i < 10; i++) { + await create_object(object_sdk, test_bucket, test_key1, 100, false); + } + const latest_lifecycle = await exec_manage_cli(TYPES.LIFECYCLE, '', {disable_service_validation: 'true', disable_runtime_validation: 'true', config_root}, undefined, undefined); + const parsed_res_latest_lifecycle = JSON.parse(latest_lifecycle); + expect(parsed_res_latest_lifecycle.response.reply.state.is_finished).toBe(true); + + const object_list = await object_sdk.list_object_versions({bucket: test_bucket}); + const expected_length = keys.length * 2 + 1; //all keys + delete marker for each key + 1 noncurrent versions for test_key1 + expect(object_list.objects.length).toBe(expected_length); + }); }); describe('noobaa cli - lifecycle notifications', () => { @@ -836,7 +1034,6 @@ describe('noobaa cli - lifecycle notifications', () => { } }); -//TODO should move outside scope and change also in lifecycle tests. already done in non current lifecycle rule PR async function create_object(sdk, bucket, key, size, is_old, tagging) { const data = crypto.randomBytes(size); const res = await sdk.upload_object({