Skip to content

Commit b744060

Browse files
committed
NSFS | fix copy_object issues
Signed-off-by: nadav mizrahi <nadav.mizrahi16@gmail.com>
1 parent f393125 commit b744060

File tree

4 files changed

+56
-45
lines changed

4 files changed

+56
-45
lines changed

src/sdk/namespace_fs.js

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ class NamespaceFS {
483483
}
484484

485485
/**
486-
* @param {nb.ObjectSDK} object_sdk
486+
* @param {nb.ObjectSDK} object_sdk
487487
* @returns {nb.NativeFSContext}
488488
*/
489489
prepare_fs_context(object_sdk) {
@@ -1090,7 +1090,9 @@ class NamespaceFS {
10901090
// end the stream
10911091
res.end();
10921092

1093-
await stream_utils.wait_finished(res, { signal: object_sdk.abort_controller.signal });
1093+
// in case of transform streams such as ChunkFS there is also a readable part. since we expect write stream
1094+
// and don't care about the readable part, set readable: false
1095+
await stream_utils.wait_finished(res, { readable: false, signal: object_sdk.abort_controller.signal });
10941096
object_sdk.throw_if_aborted();
10951097

10961098
dbg.log0('NamespaceFS: read_object_stream completed file', file_path, {
@@ -1209,9 +1211,7 @@ class NamespaceFS {
12091211
}
12101212

12111213
if (copy_res) {
1212-
if (copy_res === copy_status_enum.FALLBACK) {
1213-
params.copy_source.nsfs_copy_fallback();
1214-
} else {
1214+
if (copy_res !== copy_status_enum.FALLBACK) {
12151215
// open file after copy link/same inode should use read open mode
12161216
open_mode = config.NSFS_OPEN_READ_MODE;
12171217
if (copy_res === copy_status_enum.SAME_INODE) open_path = file_path;
@@ -1294,10 +1294,8 @@ class NamespaceFS {
12941294
const stat = await target_file.stat(fs_context);
12951295
this._verify_encryption(params.encryption, this._get_encryption_info(stat));
12961296

1297-
// handle xattr
1298-
// assign user xattr on non copy / copy with xattr_copy header provided
12991297
const copy_xattr = params.copy_source && params.xattr_copy;
1300-
let fs_xattr = copy_xattr ? undefined : to_fs_xattr(params.xattr);
1298+
let fs_xattr = to_fs_xattr(params.xattr);
13011299

13021300
// assign noobaa internal xattr - content type, md5, versioning xattr
13031301
if (params.content_type) {
@@ -1339,7 +1337,6 @@ class NamespaceFS {
13391337

13401338
// when object is a dir, xattr are set on the folder itself and the content is in .folder file
13411339
if (is_dir_content) {
1342-
if (params.copy_source) fs_xattr = await this._get_copy_source_xattr(params, fs_context, fs_xattr);
13431340
await this._assign_dir_content_to_xattr(fs_context, fs_xattr, { ...params, size: stat.size }, copy_xattr);
13441341
}
13451342
stat.xattr = { ...stat.xattr, ...fs_xattr };
@@ -1351,12 +1348,11 @@ class NamespaceFS {
13511348
await native_fs_utils._make_path_dirs(file_path, fs_context);
13521349
const copy_xattr = params.copy_source && params.xattr_copy;
13531350

1354-
let fs_xattr = copy_xattr ? {} : to_fs_xattr(params.xattr) || {};
1351+
let fs_xattr = to_fs_xattr(params.xattr) || {};
13551352
if (params.content_type) {
13561353
fs_xattr = fs_xattr || {};
13571354
fs_xattr[XATTR_CONTENT_TYPE] = params.content_type;
13581355
}
1359-
if (params.copy_source) fs_xattr = await this._get_copy_source_xattr(params, fs_context, fs_xattr);
13601356

13611357
await this._assign_dir_content_to_xattr(fs_context, fs_xattr, params, copy_xattr);
13621358
// when .folder exist and it's no upload flow - .folder should be deleted if it exists
@@ -1372,13 +1368,6 @@ class NamespaceFS {
13721368
return upload_info;
13731369
}
13741370

1375-
async _get_copy_source_xattr(params, fs_context, fs_xattr) {
1376-
const is_source_dir = params.copy_source.key.endsWith('/');
1377-
const source_file_md_path = await this._find_version_path(fs_context, params.copy_source, is_source_dir);
1378-
const source_stat = await nb_native().fs.stat(fs_context, source_file_md_path);
1379-
return { ...source_stat.xattr, ...fs_xattr };
1380-
}
1381-
13821371
// move to dest GPFS (wt) / POSIX (w / undefined) - non part upload
13831372
async _move_to_dest(fs_context, source_path, dest_path, target_file, open_mode, key) {
13841373
let retries = config.NSFS_RENAME_RETRIES;
@@ -1511,7 +1500,7 @@ class NamespaceFS {
15111500
// Can be finetuned further on if needed and inserting the Semaphore logic inside
15121501
// Instead of wrapping the whole _upload_stream function (q_buffers lives outside of the data scope of the stream)
15131502
async _upload_stream({ fs_context, params, target_file, object_sdk, offset }) {
1514-
const { source_stream } = params;
1503+
const { source_stream, copy_source } = params;
15151504
try {
15161505
// Not using async iterators with ReadableStreams due to unsettled promises issues on abort/destroy
15171506
const md5_enabled = this._is_force_md5_enabled(object_sdk);
@@ -1526,8 +1515,14 @@ class NamespaceFS {
15261515
large_buf_size: multi_buffer_pool.get_buffers_pool(undefined).buf_size
15271516
});
15281517
chunk_fs.on('error', err1 => dbg.error('namespace_fs._upload_stream: error occured on stream ChunkFS: ', err1));
1529-
await stream_utils.pipeline([source_stream, chunk_fs]);
1530-
await stream_utils.wait_finished(chunk_fs);
1518+
if (copy_source) {
1519+
await this.read_object_stream(copy_source, object_sdk, chunk_fs);
1520+
} else if (params.source_params) {
1521+
await params.source_ns.read_object_stream(params.source_params, object_sdk, chunk_fs);
1522+
} else {
1523+
await stream_utils.pipeline([source_stream, chunk_fs]);
1524+
await stream_utils.wait_finished(chunk_fs);
1525+
}
15311526
return { digest: chunk_fs.digest, total_bytes: chunk_fs.total_bytes };
15321527
} catch (error) {
15331528
dbg.error('_upload_stream had error: ', error);
@@ -1813,6 +1808,7 @@ class NamespaceFS {
18131808
upload_params.params.xattr = create_params_parsed.xattr;
18141809
upload_params.params.storage_class = create_params_parsed.storage_class;
18151810
upload_params.digest = MD5Async && (((await MD5Async.digest()).toString('hex')) + '-' + multiparts.length);
1811+
upload_params.params.content_type = create_params_parsed.content_type;
18161812

18171813
const upload_info = await this._finish_upload(upload_params);
18181814

src/sdk/object_sdk.js

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ class ObjectSDK {
106106
* in order to handle aborting requests gracefully. The `abort_controller` member will
107107
* be used to signal async flows that abort was detected.
108108
* @see {@link https://nodejs.org/docs/latest/api/globals.html#class-abortcontroller}
109-
* @param {import('http').IncomingMessage} req
110-
* @param {import('http').ServerResponse} res
109+
* @param {import('http').IncomingMessage} req
110+
* @param {import('http').ServerResponse} res
111111
*/
112112
setup_abort_controller(req, res) {
113113
res.once('error', err => {
@@ -158,7 +158,7 @@ class ObjectSDK {
158158
}
159159

160160
/**
161-
* @param {string} name
161+
* @param {string} name
162162
* @returns {Promise<nb.Namespace>}
163163
*/
164164
async _get_bucket_namespace(name) {
@@ -268,7 +268,7 @@ class ObjectSDK {
268268
return Boolean(fs_root_path || fs_root_path === '');
269269
}
270270

271-
// validates requests for non nsfs buckets from accounts which are nsfs_only
271+
// validates requests for non nsfs buckets from accounts which are nsfs_only
272272
has_non_nsfs_bucket_access(account, ns) {
273273
dbg.log1('validate_non_nsfs_bucket: ', account, ns?.write_resource?.resource);
274274
if (!account) return false;
@@ -524,7 +524,7 @@ class ObjectSDK {
524524
/**
525525
* Calls the op and report time and error to stats collector.
526526
* on_success can be added to update read/write stats (but on_success shouln't throw)
527-
*
527+
*
528528
* @template T
529529
* @param {{
530530
* op_name: string;
@@ -642,7 +642,9 @@ class ObjectSDK {
642642
params.content_type = source_md.content_type;
643643
}
644644
try {
645-
if (params.xattr) params.xattr = _.omitBy(params.xattr, (val, name) => name.startsWith('noobaa-namespace'));
645+
//omitBy iterates all xattr calling startsWith on them. this can include symbols such as XATTR_SORT_SYMBOL.
646+
//in that case startsWith will not apply
647+
if (params.xattr) params.xattr = _.omitBy(params.xattr, (val, name) => name.startsWith?.('noobaa-namespace'));
646648
} catch (e) {
647649
dbg.log3("Got an error while trying to omitBy param.xattr:", params.xattr, "error:", e);
648650
}
@@ -658,19 +660,14 @@ class ObjectSDK {
658660
params.copy_source.bucket = actual_source_ns.get_bucket(bucket);
659661
params.copy_source.obj_id = source_md.obj_id;
660662
params.copy_source.version_id = source_md.version_id;
661-
if (source_ns instanceof NamespaceFS) {
662-
params.copy_source.nsfs_copy_fallback = () => {
663-
this._populate_nsfs_copy_fallback({ source_params, source_ns, params });
664-
params.copy_source = null;
665-
};
666-
}
667663
} else {
668664
// source cannot be copied directly (different plaforms, accounts, etc.)
669665
// set the source_stream to read from the copy source
670666
// Source params need these for read operations
671667
source_params.object_md = source_md;
672668
source_params.obj_id = source_md.obj_id;
673669
source_params.version_id = source_md.version_id;
670+
source_params.bucket = actual_source_ns.get_bucket(bucket);
674671
// param size is needed when doing an upload. Can be overrided during ranged writes
675672
params.size = source_md.size;
676673

@@ -684,7 +681,13 @@ class ObjectSDK {
684681

685682
// if the source namespace is NSFS then we need to pass the read_object_stream the read_stream
686683
if (source_ns instanceof NamespaceFS) {
687-
this._populate_nsfs_copy_fallback({ source_params, source_ns, params });
684+
if (target_ns instanceof NamespaceFS) {
685+
params.source_ns = actual_source_ns;
686+
params.source_params = source_params;
687+
} else {
688+
//this._populate_nsfs_copy_fallback({ source_params, source_ns, params });
689+
throw new Error('TODO fix _populate_nsfs_copy_fallback');
690+
}
688691
} else {
689692
params.source_stream = await source_ns.read_object_stream(source_params, this);
690693
}
@@ -701,9 +704,9 @@ class ObjectSDK {
701704
}
702705
}
703706

704-
// nsfs copy_object & server side copy consisted of link and a fallback to
707+
// nsfs copy_object & server side copy consisted of link and a fallback to
705708
// read stream and then upload stream
706-
// nsfs copy object when can't server side copy - fallback directly
709+
// nsfs copy object when can't server side copy - fallback directly
707710
_populate_nsfs_copy_fallback({ source_ns, params, source_params }) {
708711
const read_stream = new stream.PassThrough();
709712
source_ns.read_object_stream(source_params, this, read_stream)

src/test/system_tests/ceph_s3_tests/s3-tests-lists/nsfs_s3_tests_pending_list.txt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,11 +153,7 @@ s3tests_boto3/functional/test_s3.py::test_put_object_ifmatch_failed
153153
s3tests_boto3/functional/test_s3.py::test_put_object_ifnonmatch_failed
154154
s3tests_boto3/functional/test_s3.py::test_put_object_ifnonmatch_overwrite_existed_failed
155155
s3tests_boto3/functional/test_s3.py::test_object_raw_authenticated_bucket_gone
156-
s3tests_boto3/functional/test_s3.py::test_object_copy_to_itself_with_metadata
157156
s3tests_boto3/functional/test_s3.py::test_object_copy_canned_acl
158-
s3tests_boto3/functional/test_s3.py::test_object_copy_retaining_metadata
159-
s3tests_boto3/functional/test_s3.py::test_object_copy_replacing_metadata
160-
s3tests_boto3/functional/test_s3.py::test_object_copy_versioning_multipart_upload
161157
s3tests_boto3/functional/test_s3.py::test_multipart_upload_missing_part
162158
s3tests_boto3/functional/test_s3.py::test_multipart_upload_incorrect_etag
163159
s3tests_boto3/functional/test_s3.py::test_atomic_dual_conditional_write_1mb

src/test/unit_tests/test_namespace_fs.js

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1582,12 +1582,14 @@ mocha.describe('namespace_fs copy object', function() {
15821582
assert.deepStrictEqual(xattr, { ...add_user_prefix(read_md_res.xattr), [XATTR_DIR_CONTENT]: `${read_md_res.size}` });
15831583
assert.equal(stream_content_type, read_md_res.content_type);
15841584

1585+
const copy_source = { bucket: upload_bkt, key: key1 };
15851586
await ns_tmp.upload_object({
15861587
bucket: upload_bkt,
15871588
key: key2,
1588-
copy_source: { bucket: upload_bkt, key: key1 },
1589+
copy_source: copy_source,
15891590
size: 100,
1590-
xattr_copy: true
1591+
xattr_copy: true,
1592+
xattr: await _get_source_copy_xattr(copy_source, ns_tmp, dummy_object_sdk)
15911593
}, dummy_object_sdk);
15921594
const file_path2 = ns_tmp_bucket_path + '/' + key2;
15931595
xattr = await get_xattr(file_path2);
@@ -1622,12 +1624,14 @@ mocha.describe('namespace_fs copy object', function() {
16221624
assert.deepStrictEqual(xattr, { ...add_user_prefix(read_md_res.xattr) });
16231625
assert.equal(stream_content_type, read_md_res.content_type);
16241626

1627+
const copy_source = { bucket: upload_bkt, key: src_key };
16251628
await ns_tmp.upload_object({
16261629
bucket: upload_bkt,
16271630
key: dst_key,
1628-
copy_source: { bucket: upload_bkt, key: src_key },
1631+
copy_source: copy_source,
16291632
size: 100,
16301633
xattr_copy: true,
1634+
xattr: await _get_source_copy_xattr(copy_source, ns_tmp, dummy_object_sdk)
16311635
}, dummy_object_sdk);
16321636
const file_path2 = ns_tmp_bucket_path + '/' + dst_key;
16331637
xattr = await get_xattr(file_path2);
@@ -1663,12 +1667,14 @@ mocha.describe('namespace_fs copy object', function() {
16631667
assert.deepStrictEqual(xattr, { ...add_user_prefix(read_md_res.xattr) });
16641668
assert.equal(stream_content_type, read_md_res.content_type);
16651669

1670+
const copy_source = { bucket: upload_bkt, key: src_key };
16661671
await ns_tmp.upload_object({
16671672
bucket: upload_bkt,
16681673
key: dst_key,
1669-
copy_source: { bucket: upload_bkt, key: src_key },
1674+
copy_source: copy_source,
16701675
size: 0,
1671-
xattr_copy: true
1676+
xattr_copy: true,
1677+
xattr: await _get_source_copy_xattr(copy_source, ns_tmp, dummy_object_sdk)
16721678
}, dummy_object_sdk);
16731679
const file_path2 = ns_tmp_bucket_path + '/' + dst_key;
16741680
xattr = await get_xattr(file_path2);
@@ -1694,6 +1700,16 @@ mocha.describe('namespace_fs copy object', function() {
16941700

16951701
});
16961702

1703+
//simulates object_sdk.fix_copy_source_params filtering of source xattr for copy object tests
1704+
async function _get_source_copy_xattr(copy_source, source_ns, object_sdk) {
1705+
const read_md_res = await source_ns.read_object_md({
1706+
bucket: copy_source.bucket,
1707+
key: copy_source.key
1708+
}, object_sdk);
1709+
const res = _.omitBy(read_md_res.xattr, (val, name) => name.startsWith?.('noobaa-namespace'));
1710+
return res;
1711+
}
1712+
16971713
async function list_objects(ns, bucket, delimiter, prefix, dummy_object_sdk) {
16981714
const res = await ns.list_objects({
16991715
bucket: bucket,

0 commit comments

Comments
 (0)