Skip to content

Commit be61cd9

Browse files
authored
Merge pull request #8961 from nadavMiz/lifecycle-delete-marker-expiration1
NC | lifecycle | add expire delete marker rule
2 parents 12f9b56 + f216a3b commit be61cd9

File tree

2 files changed

+174
-18
lines changed

2 files changed

+174
-18
lines changed

src/manage_nsfs/nc_lifecycle.js

Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,12 @@ class NCLifecycle {
369369
candidates.delete_candidates = await this.get_candidates_by_expiration_rule(lifecycle_rule, bucket_json,
370370
object_sdk);
371371
if (lifecycle_rule.expiration.days || lifecycle_rule.expiration.expired_object_delete_marker) {
372-
const dm_candidates = await this.get_candidates_by_expiration_delete_marker_rule(lifecycle_rule, bucket_json);
372+
const dm_candidates = await this.get_candidates_by_expiration_delete_marker_rule(
373+
lifecycle_rule,
374+
bucket_json,
375+
object_sdk,
376+
{versions_list}
377+
);
373378
candidates.delete_candidates = candidates.delete_candidates.concat(dm_candidates);
374379
}
375380
}
@@ -439,21 +444,21 @@ class NCLifecycle {
439444
* @param {Object} object_sdk
440445
* @param {Object} lifecycle_rule
441446
* @param {Object} bucket_json
442-
* @param {Object} rule_state
447+
* @param {Object} expire_state
443448
* @returns
444449
*/
445-
async load_objects_list(object_sdk, lifecycle_rule, bucket_json, rule_state) {
450+
async load_objects_list(object_sdk, lifecycle_rule, bucket_json, expire_state) {
446451
const objects_list = await object_sdk.list_objects({
447452
bucket: bucket_json.name,
448453
prefix: lifecycle_rule.filter?.prefix,
449-
key_marker: rule_state.key_marker,
454+
key_marker: expire_state.key_marker,
450455
limit: config.NC_LIFECYCLE_LIST_BATCH_SIZE
451456
});
452457
if (objects_list.is_truncated) {
453-
rule_state.key_marker = objects_list.next_marker;
454-
rule_state.is_finished = false;
458+
expire_state.key_marker = objects_list.next_marker;
459+
expire_state.is_finished = false;
455460
} else {
456-
rule_state.is_finished = true;
461+
expire_state.is_finished = true;
457462
}
458463
const bucket_state = this.lifecycle_run_status.buckets_statuses[bucket_json.name].state;
459464
bucket_state.num_processed_objects += objects_list.objects.length;
@@ -503,15 +508,51 @@ class NCLifecycle {
503508
return parsed_candidates;
504509
}
505510

511+
/**
512+
* check if delete candidate based on expired delete marker rule
513+
* @param {Object} object
514+
* @param {Object} next_object
515+
* @param {Function} filter_func
516+
* @returns
517+
*/
518+
filter_expired_delete_marker(object, next_object, filter_func) {
519+
const lifecycle_info = this._get_lifecycle_object_info_from_list_object_entry(object);
520+
if (!filter_func(lifecycle_info)) return false;
521+
return object.is_latest && object.delete_marker && object.key !== next_object.key;
522+
}
523+
506524
/**
507525
* get_candidates_by_expiration_delete_marker_rule processes the expiration delete marker rule
508526
* @param {*} lifecycle_rule
509527
* @param {Object} bucket_json
510528
* @returns {Promise<Object[]>}
511529
*/
512-
async get_candidates_by_expiration_delete_marker_rule(lifecycle_rule, bucket_json) {
513-
// TODO - implement
514-
return [];
530+
async get_candidates_by_expiration_delete_marker_rule(lifecycle_rule, bucket_json, object_sdk, {versions_list}) {
531+
const rule_state = this.lifecycle_run_status.buckets_statuses[bucket_json.name].rules_statuses[lifecycle_rule.id].state.noncurrent;
532+
if (rule_state.is_finished) return [];
533+
if (!versions_list) {
534+
versions_list = await this.load_versions_list(object_sdk, lifecycle_rule, bucket_json, rule_state);
535+
}
536+
const candidates = [];
537+
const expiration = lifecycle_rule.expiration?.days ? this._get_expiration_time(lifecycle_rule.expiration) : 0;
538+
const filter_func = this._build_lifecycle_filter({filter: lifecycle_rule.filter, expiration});
539+
for (let i = 0; i < versions_list.objects.length - 1; i++) {
540+
if (this.filter_expired_delete_marker(versions_list.objects[i], versions_list.objects[i + 1], filter_func)) {
541+
candidates.push(versions_list.objects[i]);
542+
}
543+
}
544+
const last_item = versions_list.objects.length > 0 && versions_list.objects[versions_list.objects.length - 1];
545+
const lifecycle_info = this._get_lifecycle_object_info_from_list_object_entry(last_item);
546+
if (last_item.is_latest && last_item.delete_marker && filter_func(lifecycle_info)) {
547+
if (rule_state.is_finished) {
548+
candidates.push(last_item);
549+
} else {
550+
//need the next item to decide if we need to delete. start the next cycle from this key latest
551+
rule_state.key_marker_versioned = last_item.key;
552+
rule_state.version_id_marker = undefined;
553+
}
554+
}
555+
return candidates;
515556
}
516557

517558
/////////////////////////////////////////////
@@ -522,23 +563,23 @@ class NCLifecycle {
522563
* @param {Object} object_sdk
523564
* @param {Object} lifecycle_rule
524565
* @param {Object} bucket_json
525-
* @param {Object} rule_state
566+
* @param {Object} noncurrent_state
526567
* @returns
527568
*/
528-
async load_versions_list(object_sdk, lifecycle_rule, bucket_json, rule_state) {
569+
async load_versions_list(object_sdk, lifecycle_rule, bucket_json, noncurrent_state) {
529570
const list_versions = await object_sdk.list_object_versions({
530571
bucket: bucket_json.name,
531572
prefix: lifecycle_rule.filter?.prefix,
532573
limit: config.NC_LIFECYCLE_LIST_BATCH_SIZE,
533-
key_marker: rule_state.key_marker_versioned,
534-
version_id_marker: rule_state.version_id_marker
574+
key_marker: noncurrent_state.key_marker_versioned,
575+
version_id_marker: noncurrent_state.version_id_marker
535576
});
536577
if (list_versions.is_truncated) {
537-
rule_state.is_finished = false;
538-
rule_state.key_marker_versioned = list_versions.next_marker;
539-
rule_state.version_id_marker = list_versions.next_version_id_marker;
578+
noncurrent_state.is_finished = false;
579+
noncurrent_state.key_marker_versioned = list_versions.next_marker;
580+
noncurrent_state.version_id_marker = list_versions.next_version_id_marker;
540581
} else {
541-
rule_state.is_finished = true;
582+
noncurrent_state.is_finished = true;
542583
}
543584
const bucket_state = this.lifecycle_run_status.buckets_statuses[bucket_json.name].state;
544585
bucket_state.num_processed_objects += list_versions.objects.length;

src/test/unit_tests/jest_tests/test_nc_lifecycle_cli.test.js

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ describe('noobaa cli - lifecycle versioning ENABLE', () => {
407407
const test_bucket_path = `${root_path}/${test_bucket}`;
408408
const test_key1 = 'test_key1';
409409
const test_key2 = 'test_key2';
410+
const test_key3 = 'test_key3';
410411
const prefix = 'test/';
411412
const test_prefix_key = `${prefix}test_key1`;
412413
let object_sdk;
@@ -556,6 +557,120 @@ describe('noobaa cli - lifecycle versioning ENABLE', () => {
556557
});
557558
expect(has_delete_marker).toBe(true);
558559
});
560+
561+
it('lifecycle_cli - expiration rule - expire delete marker ', async () => {
562+
const lifecycle_rule = [
563+
{
564+
"id": "expiration after 3 days with tags",
565+
"status": "Enabled",
566+
"filter": {
567+
"prefix": '',
568+
},
569+
"expiration": {
570+
"expired_object_delete_marker": true
571+
}
572+
}
573+
];
574+
await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule });
575+
await object_sdk.delete_object({bucket: test_bucket, key: test_key1});
576+
await object_sdk.delete_object({bucket: test_bucket, key: test_key2});
577+
578+
await create_object(object_sdk, test_bucket, test_prefix_key, 100, false);
579+
await object_sdk.delete_object({bucket: test_bucket, key: test_prefix_key});
580+
581+
await exec_manage_cli(TYPES.LIFECYCLE, '', {disable_service_validation: 'true', disable_runtime_validation: 'true', config_root}, undefined, undefined);
582+
const object_list = await object_sdk.list_object_versions({bucket: test_bucket});
583+
expect(object_list.objects.length).toBe(2);
584+
object_list.objects.forEach(element => {
585+
expect(element.key).toBe(test_prefix_key);
586+
});
587+
});
588+
589+
it('lifecycle_cli - expiration rule - expire delete marker with filter', async () => {
590+
const lifecycle_rule = [
591+
{
592+
"id": "expiration after 3 days with tags",
593+
"status": "Enabled",
594+
"filter": {
595+
"prefix": prefix,
596+
"object_size_less_than": 1
597+
},
598+
"expiration": {
599+
"expired_object_delete_marker": true
600+
}
601+
}
602+
];
603+
await object_sdk.set_bucket_versioning({name: test_bucket, versioning: "ENABLED"});
604+
await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule });
605+
606+
await object_sdk.delete_object({bucket: test_bucket, key: test_key1});
607+
await object_sdk.delete_object({bucket: test_bucket, key: test_prefix_key});
608+
609+
await exec_manage_cli(TYPES.LIFECYCLE, '', {disable_service_validation: 'true', disable_runtime_validation: 'true', config_root}, undefined, undefined);
610+
const object_list = await object_sdk.list_object_versions({bucket: test_bucket});
611+
expect(object_list.objects.length).toBe(1);
612+
expect(object_list.objects[0].key).toBe(test_key1);
613+
});
614+
615+
it('lifecycle_cli - expiration rule - expire delete marker last item', async () => {
616+
const lifecycle_rule = [
617+
{
618+
"id": "expiration after 3 days with tags",
619+
"status": "Enabled",
620+
"filter": {
621+
"prefix": '',
622+
"object_size_less_than": 1
623+
},
624+
"expiration": {
625+
"expired_object_delete_marker": true
626+
}
627+
}
628+
];
629+
await object_sdk.set_bucket_versioning({name: test_bucket, versioning: "ENABLED"});
630+
await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule });
631+
632+
await object_sdk.delete_object({bucket: test_bucket, key: test_key1});
633+
634+
await exec_manage_cli(TYPES.LIFECYCLE, '', {disable_service_validation: 'true', disable_runtime_validation: 'true', config_root}, undefined, undefined);
635+
const object_list = await object_sdk.list_object_versions({bucket: test_bucket});
636+
expect(object_list.objects.length).toBe(0);
637+
});
638+
639+
it('lifecycle_cli - expiration rule - last item in batch is latest delete marker', async () => {
640+
const lifecycle_rule = [
641+
{
642+
"id": "expiration after 3 days with tags",
643+
"status": "Enabled",
644+
"filter": {
645+
"prefix": '',
646+
"object_size_less_than": 1
647+
},
648+
"expiration": {
649+
"expired_object_delete_marker": true
650+
}
651+
}
652+
];
653+
await config_fs.create_config_json_file(JSON.stringify({
654+
NC_LIFECYCLE_LIST_BATCH_SIZE: 3,
655+
NC_LIFECYCLE_BUCKET_BATCH_SIZE: 3,
656+
}));
657+
await object_sdk.set_bucket_versioning({name: test_bucket, versioning: "ENABLED"});
658+
await object_sdk.set_bucket_lifecycle_configuration_rules({ name: test_bucket, rules: lifecycle_rule });
659+
660+
await object_sdk.delete_object({bucket: test_bucket, key: test_key1});
661+
await object_sdk.delete_object({bucket: test_bucket, key: test_key1});
662+
await object_sdk.delete_object({bucket: test_bucket, key: test_key2}); //last in batch should not delete
663+
await object_sdk.delete_object({bucket: test_bucket, key: test_key2});
664+
await object_sdk.delete_object({bucket: test_bucket, key: test_key3}); // last in batch should delete
665+
await object_sdk.delete_object({bucket: test_bucket, key: 'test_key4'});
666+
667+
await exec_manage_cli(TYPES.LIFECYCLE, '', {disable_service_validation: 'true', disable_runtime_validation: 'true', config_root}, undefined, undefined);
668+
const object_list = await object_sdk.list_object_versions({bucket: test_bucket});
669+
expect(object_list.objects.length).toBe(4);
670+
object_list.objects.forEach(element => {
671+
expect([test_key1, test_key2]).toContain(element.key);
672+
});
673+
});
559674
});
560675

561676

0 commit comments

Comments
 (0)