Skip to content

Commit 28bf98f

Browse files
authored
bucket notifications - fixes for CompleteMultipartUpload etag, size. Also statusCode check (#8917)
Signed-off-by: Amit Prinz Setter <alphaprinz@gmail.com>
1 parent ed84e68 commit 28bf98f

File tree

5 files changed

+74
-14
lines changed

5 files changed

+74
-14
lines changed

src/endpoint/s3/ops/s3_post_object_uploadId.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ async function post_object_uploadId(req, res) {
4343
res.setHeader('x-amz-version-id', reply.version_id);
4444
}
4545

46+
res.size_for_notif = reply.size;
47+
4648
return {
4749
CompleteMultipartUploadResult: {
4850
Bucket: req.params.bucket,

src/endpoint/s3/s3_bucket_logging.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const { Buffer } = require('node:buffer');
88
const config = require('../../../config');
99
const {compose_notification_req, check_notif_relevant, check_free_space_if_needed} = require('../../util/notifications_util');
1010

11-
async function send_bucket_op_logs(req, res) {
11+
async function send_bucket_op_logs(req, res, reply) {
1212
if (req.params && req.params.bucket &&
1313
!(req.op_name === 'put_bucket' ||
1414
req.op_name === 'delete_bucket' ||
@@ -36,8 +36,10 @@ async function send_bucket_op_logs(req, res) {
3636

3737
if (req.notification_logger && bucket_info.notifications) {
3838
for (const notif_conf of bucket_info.notifications) {
39-
if (check_notif_relevant(notif_conf, req)) {
40-
const notif = compose_notification_req(req, res, bucket_info, notif_conf);
39+
//write the notification log only if request is successful (ie res.statusCode < 300)
40+
//and event is actually relevant to the notification conf
41+
if (res.statusCode < 300 && check_notif_relevant(notif_conf, req)) {
42+
const notif = compose_notification_req(req, res, bucket_info, notif_conf, reply);
4143
dbg.log1("logging notif ", notif_conf, ", notif = ", notif);
4244
writes_aggregate.push({
4345
file: req.notification_logger,

src/endpoint/s3/s3_rest.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ async function handle_request(req, res) {
176176
http_utils.send_reply(req, res, reply, options);
177177
collect_bucket_usage(op, req, res);
178178
try {
179-
await s3_logging.send_bucket_op_logs(req, res); // logging again with result
179+
await s3_logging.send_bucket_op_logs(req, res, reply); // logging again with result
180180
} catch (err) {
181181
dbg.error(`Could not log bucket operation (after operation ${req.op_name}):`, err);
182182
}

src/test/unit_tests/test_notifications.js

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ let http_server = null;
5252
let server_done = false;
5353
let expected_bucket;
5454
let expected_event_name;
55+
let expected_key;
56+
let expected_eTag;
5557

5658
// eslint-disable-next-line max-lines-per-function
5759
mocha.describe('notifications', function() {
@@ -91,9 +93,10 @@ mocha.describe('notifications', function() {
9193
if (notif !== "test notification") {
9294
assert.strictEqual(notif.Records[0].s3.bucket.name, expected_bucket, 'wrong bucket name in notification');
9395
assert.strictEqual(notif.Records[0].eventName, expected_event_name, 'wrong event name in notification');
94-
96+
assert.strictEqual(notif.Records[0].s3.object.key, expected_key, 'wrong key in notification');
97+
const expected_eTag_trimmed = expected_eTag && expected_eTag.substring(1, expected_eTag.length - 1);
98+
assert.strictEqual(notif.Records[0].s3.object.eTag, expected_eTag_trimmed, 'wrong eTag in notification');
9599
}
96-
97100
res.writeHead(200, {'Content-Type': 'text/plain'});
98101
res.end();
99102
server_done = true;
@@ -120,13 +123,18 @@ mocha.describe('notifications', function() {
120123
});
121124

122125
mocha.it('simple notif put', async () => {
123-
await s3.putObject({
126+
const res = await s3.putObject({
124127
Bucket: bucket,
125128
Key: 'f1',
126129
Body: 'this is the body',
127130
});
128131

129-
await notify_await_result({bucket_name: bucket, event_name: 'ObjectCreated:Put'});
132+
await notify_await_result({
133+
bucket_name: bucket,
134+
event_name: 'ObjectCreated:Put',
135+
key: "f1",
136+
etag: res.ETag
137+
});
130138
});
131139

132140

@@ -136,7 +144,12 @@ mocha.describe('notifications', function() {
136144
Key: 'f1',
137145
});
138146

139-
await notify_await_result({bucket_name: bucket, event_name: 'ObjectRemoved:Delete'});
147+
await notify_await_result({
148+
bucket_name: bucket,
149+
event_name: 'ObjectRemoved:Delete',
150+
key: "f1",
151+
etag: undefined
152+
});
140153
});
141154

142155

@@ -155,13 +168,18 @@ mocha.describe('notifications', function() {
155168

156169
assert.strictEqual(set.$metadata.httpStatusCode, 200);
157170

158-
await s3.putObject({
171+
const res = await s3.putObject({
159172
Bucket: bucket,
160173
Key: 'f2',
161174
Body: 'this is the body',
162175
});
163176

164-
await notify_await_result({bucket_name: bucket, event_name: 'ObjectCreated:Put'});
177+
await notify_await_result({
178+
bucket_name: bucket,
179+
event_name: 'ObjectCreated:Put',
180+
key: "f2",
181+
etag: res.ETag
182+
});
165183

166184
await s3.deleteObject({
167185
Bucket: bucket,
@@ -171,16 +189,53 @@ mocha.describe('notifications', function() {
171189
//there shouldn't be a notification for the delete, wait 2 seconds to validate this
172190
await notify_await_result({timeout: 2000});
173191
});
192+
193+
mocha.it('multipart', async () => {
194+
const res_create = await s3.createMultipartUpload({
195+
Bucket: bucket,
196+
Key: 'mp1'
197+
});
198+
199+
const res_upload = await s3.uploadPart({
200+
Bucket: bucket,
201+
Key: 'mp1',
202+
UploadId: res_create.UploadId,
203+
PartNumber: 1,
204+
Body: 'this is the body'
205+
});
206+
207+
const res_complete = await s3.completeMultipartUpload({
208+
Bucket: bucket,
209+
Key: 'mp1',
210+
UploadId: res_create.UploadId,
211+
MultipartUpload: {
212+
Parts: [{
213+
ETag: res_upload.ETag,
214+
PartNumber: 1
215+
}]
216+
}
217+
});
218+
219+
await notify_await_result({
220+
bucket_name: bucket,
221+
event_name: 'ObjectCreated:CompleteMultipartUpload',
222+
key: "mp1",
223+
etag: res_complete.ETag
224+
});
225+
});
226+
174227
});
175228

176229
});
177230

178231
const step_wait = 100;
179-
async function notify_await_result({bucket_name, event_name, timeout}) {
232+
async function notify_await_result({bucket_name, event_name, etag, key, timeout = undefined}) {
180233

181234
//remember expected result here so server could compare it to actual result later
182235
expected_bucket = bucket_name;
183236
expected_event_name = event_name;
237+
expected_eTag = etag;
238+
expected_key = key;
184239
server_done = false;
185240

186241
//busy-sync wait for server

src/util/notifications_util.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -418,8 +418,9 @@ function compose_notification_base(notif_conf, bucket, req) {
418418
}
419419

420420
//see https://docs.aws.amazon.com/AmazonS3/latest/userguide/notification-content-structure.html
421-
function compose_notification_req(req, res, bucket, notif_conf) {
422-
let eTag = res.getHeader('ETag');
421+
function compose_notification_req(req, res, bucket, notif_conf, reply) {
422+
//most s3 ops put etag in header. CompleteMultipartUploadResult is an exception.
423+
let eTag = res.getHeader('ETag') || reply?.CompleteMultipartUploadResult?.ETag;
423424
//eslint-disable-next-line
424425
if (eTag && eTag.startsWith('\"') && eTag.endsWith('\"')) {
425426
eTag = eTag.substring(1, eTag.length - 1);

0 commit comments

Comments
 (0)