Skip to content

Commit 872cedd

Browse files
committed
Updated the logic for matching rules and some changes
Signed-off-by: Aayush Chouhan <achouhan@redhat.com>
1 parent 43d2c7d commit 872cedd

File tree

4 files changed

+77
-29
lines changed

4 files changed

+77
-29
lines changed

src/endpoint/s3/ops/s3_get_object.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ async function get_object(req, res) {
3939

4040
const object_md = await req.object_sdk.read_object_md(md_params);
4141

42-
http_utils.set_expiration_header(req, res); // setting expiration header for bucket lifecycle
4342
s3_utils.set_response_object_md(res, object_md);
4443
s3_utils.set_encryption_response_headers(req, res, object_md.encryption);
4544
if (object_md.storage_class === s3_utils.STORAGE_CLASS_GLACIER) {
@@ -50,6 +49,7 @@ async function get_object(req, res) {
5049
}
5150
}
5251
http_utils.set_response_headers_from_request(req, res);
52+
await http_utils.set_expiration_header(req, res, object_md); // setting expiration header for bucket lifecycle
5353
const obj_size = object_md.size;
5454
const params = {
5555
object_md,

src/endpoint/s3/ops/s3_head_object.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ async function head_object(req, res) {
2424
if (req.query.get_from_cache !== undefined) {
2525
params.get_from_cache = true;
2626
}
27-
http_utils.set_expiration_header(req, res); // setting expiration header for bucket lifecycle
2827

2928
const object_md = await req.object_sdk.read_object_md(params);
3029

3130
s3_utils.set_response_object_md(res, object_md);
3231
s3_utils.set_encryption_response_headers(req, res, object_md.encryption);
3332
http_utils.set_response_headers_from_request(req, res);
33+
await http_utils.set_expiration_header(req, res, object_md); // setting expiration header for bucket lifecycle
3434
}
3535

3636
module.exports = {

src/endpoint/s3/ops/s3_put_object.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ async function put_object(req, res) {
3232
sha256_b64: req.content_sha256_buf && req.content_sha256_buf.toString('base64'),
3333
};
3434

35-
http_utils.set_expiration_header(req, res); // setting expiration header for bucket lifecycle
36-
3735
dbg.log0('PUT OBJECT', req.params.bucket, req.params.key,
3836
req.headers['x-amz-copy-source'] || '', encryption || '');
3937
//for copy, use correct s3_event_method. otherwise, just use default (req.method)
@@ -82,6 +80,7 @@ async function put_object(req, res) {
8280
};
8381
}
8482
res.setHeader('ETag', `"${reply.etag}"`);
83+
await http_utils.set_expiration_header(req, res); // setting expiration header for bucket lifecycle
8584

8685
if (reply.seq) {
8786
res.seq = reply.seq;

src/util/http_utils.js

+74-25
Original file line numberDiff line numberDiff line change
@@ -671,20 +671,53 @@ function set_amz_headers(req, res) {
671671
*
672672
* @param {Object} req
673673
* @param {http.ServerResponse} res
674+
* @param {Object} object_md
674675
*/
675-
async function set_expiration_header(req, res) {
676-
const rules = req.params.bucket && await req.object_sdk.read_bucket_lifecycle_config_info(req.params.bucket);
677-
const object_md = {
678-
bucket: req.params.bucket,
679-
key: req.params.key,
680-
size: req.headers['x-amz-decoded-content-length'] || req.headers['content-length'] ? parse_content_length(req, {
681-
ErrorClass: S3Error,
682-
error_missing_content_length: S3Error.MissingContentLength
683-
}) : undefined,
684-
tagging: req.body && req.body.Tagging ? s3_utils.parse_body_tagging_xml(req) : undefined,
685-
};
676+
async function set_expiration_header(req, res, object_md) {
677+
const rules = req.params.bucket && await req.object_sdk.get_bucket_lifecycle_configuration_rules({ name: req.params.bucket });
678+
if (!object_md) { // calculating object_md for putObject
679+
object_md = {
680+
bucket: req.params.bucket,
681+
key: req.params.key,
682+
create_time: new Date(Date.UTC(new Date().getUTCFullYear(), new Date().getUTCMonth(), new Date().getUTCDate())).getTime(),
683+
size: req.headers['x-amz-decoded-content-length'] || req.headers['content-length'] ? parse_content_length(req, {
684+
ErrorClass: S3Error,
685+
error_missing_content_length: S3Error.MissingContentLength
686+
}) : undefined,
687+
tagging: req.body && req.body.Tagging ? s3_utils.parse_body_tagging_xml(req) : undefined,
688+
};
689+
}
686690

687-
if (object_md.key && rules?.length > 0) {
691+
const matched_rule = get_lifecycle_rule_for_object(rules, object_md);
692+
if (matched_rule) {
693+
const expiration_header = parse_expiration_header(matched_rule, object_md.create_time);
694+
if (expiration_header) {
695+
dbg.log1('set x_amz_expiration header from applied rule: ', matched_rule);
696+
res.setHeader('x-amz-expiration', expiration_header);
697+
}
698+
}
699+
}
700+
701+
/**
702+
* get_lifecycle_rule_for_object determines the most specific matching lifecycle rule for the given object metadata
703+
*
704+
* priority is based on:
705+
* - longest matching prefix
706+
* - most matching tags
707+
* - narrowest object size range
708+
*
709+
* @param {Array<Object>} rules
710+
* @param {Object} object_md
711+
* @returns {Object|undefined}
712+
*/
713+
function get_lifecycle_rule_for_object(rules, object_md) {
714+
let matched_rule;
715+
let rule_priority = {
716+
prefix_len: -1,
717+
tag_count: -1,
718+
size_span: Infinity,
719+
};
720+
if (object_md?.key && rules?.length > 0) {
688721
for (const rule of rules) {
689722
if (rule?.status !== 'Enabled') continue;
690723

@@ -705,37 +738,53 @@ async function set_expiration_header(req, res) {
705738
if (!matches_all_tags) continue;
706739
}
707740

708-
const expiration_header = parse_expiration_header(rule?.expiration, rule?.id);
709-
if (expiration_header) {
710-
dbg.log1('set x_amz_expiration header from applied rule: ', rule);
711-
res.setHeader('x-amz-expiration', expiration_header);
712-
break; // apply only for first matching rule
741+
const priority = {
742+
prefix_len: (filter?.prefix || '').length,
743+
tag_count: Array.isArray(filter?.tagging) ? filter?.tagging.length : 0,
744+
size_span: (filter?.object_size_less_than ?? Infinity) - (filter?.object_size_greater_than ?? 0)
745+
};
746+
747+
// compare prefix length
748+
const is_more_specific_prefix = priority.prefix_len > rule_priority.prefix_len;
749+
750+
// compare tag count (if prefixes are equal)
751+
const is_more_specific_tags = priority.prefix_len === rule_priority.prefix_len &&
752+
priority.tag_count > rule_priority.tag_count;
753+
754+
// compare size span (if prefixes and tags are equal)
755+
const is_more_specific_size = priority.prefix_len === rule_priority.prefix_len &&
756+
priority.tag_count === rule_priority.tag_count &&
757+
priority.size_span < rule_priority.size_span;
758+
759+
if (is_more_specific_prefix || is_more_specific_tags || is_more_specific_size) {
760+
matched_rule = rule;
761+
rule_priority = priority;
713762
}
714763
}
715764
}
765+
return matched_rule;
716766
}
717767

718768
/**
719769
* parse_expiration_header converts an expiration rule (either with `date` or `days`)
720770
* into an s3 style `x-amz-expiration` header value
721771
*
722-
* @param {Object} expiration - expiration object from lifecycle config
723-
* @param {string} rule_id - id of the lifecycle rule
772+
* @param {Object} rule
773+
* @param {Object} create_time
724774
* @returns {string|undefined}
725775
*
726776
* Example output:
727777
* expiry-date="Thu, 10 Apr 2025 00:00:00 GMT", rule-id="rule_id"
728778
*/
729-
function parse_expiration_header(expiration, rule_id) {
779+
function parse_expiration_header(rule, create_time) {
780+
const expiration = rule.expiration;
781+
const rule_id = rule.rule_id;
782+
730783
if (!expiration || (!expiration.date && !expiration.days)) return undefined;
731784

732785
const expiration_date = expiration.date ?
733786
new Date(expiration.date) :
734-
new Date(Date.UTC(
735-
new Date().getUTCFullYear(),
736-
new Date().getUTCMonth(),
737-
new Date().getUTCDate() + expiration.days
738-
));
787+
new Date(create_time + expiration.days * 24 * 60 * 60 * 1000);
739788

740789
return `expiry-date="${expiration_date.toUTCString()}", rule-id="${rule_id}"`;
741790
}

0 commit comments

Comments
 (0)