diff --git a/lib/sproxyd.js b/lib/sproxyd.js index 62ff1bd..5d6e112 100644 --- a/lib/sproxyd.js +++ b/lib/sproxyd.js @@ -237,7 +237,7 @@ class SproxydClient { // when current endpoint matches the endpoint of the failed request const currentBootstrap = this.getCurrentBootstrap(); - this._handleRequest(method, stream, size, key, log, (err, ret) => { + this._handleRequest(method, stream, size, key, log, (err, ...rest) => { if (err && !err.isExpected) { if (receivedResponse === true) { log.fatal('multiple responses from sproxyd, trying to ' @@ -273,7 +273,7 @@ class SproxydClient { } receivedResponse = true; log.end().debug('request received response'); - return callback(err, ret); + return callback(err, ...rest); }, args, payload); } @@ -315,11 +315,10 @@ class SproxydClient { } return callback(errorProcessed); } - // We return the key log.debug('stored to sproxyd', { statusCode: response.statusCode, }); - return callback(null, newKey); + return callback(null, newKey, response); }); const startPayloadStreaming = () => { @@ -420,11 +419,13 @@ class SproxydClient { */ put(stream, size, params, reqUids, callback, keyScheme) { const log = this.createLogger(reqUids); - this._failover('PUT', stream, size, keyScheme, 0, log, (err, key) => { + this._failover('PUT', stream, size, keyScheme, 0, log, (err, key, response) => { if (err) { return callback(err); } - return callback(null, key); + const version = response && response.headers + ? response.headers['x-scal-version'] : undefined; + return callback(null, key, version); }, params); } @@ -455,6 +456,24 @@ class SproxydClient { }, reqParams); } + /** + * Update the user metadata of an existing object without replacing its content. + * Sends a zero-byte PUT with x-scal-cmd: update-usermd. + * @param {string} keyScheme - sproxyd key + * @param {string} metadata - metadata to set in x-scal-usermd header + * @param {string} reqUids - The serialized request id + * @param {SproxydClient~putCallback} callback - callback + * @returns {undefined} + */ + putUserMetadata(keyScheme, metadata, params, reqUids, callback) { + const reqParams = Object.assign({}, params, { + headers: Object.assign({}, params && params.headers, { + 'x-scal-cmd': 'update-usermd', + }), + }); + this.putEmptyObject(keyScheme, metadata, reqParams, reqUids, callback); + } + /** * This sends a GET request to sproxyd. * @param {String} key - The key associated to the value diff --git a/package.json b/package.json index ed738cf..a5c3f18 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "engines": { "node": ">=20" }, - "version": "9.0.0", + "version": "9.1.0", "description": "sproxyd client", "main": "index.js", "scripts": { diff --git a/tests/unit/sproxyd.js b/tests/unit/sproxyd.js index 068ee24..35750fe 100644 --- a/tests/unit/sproxyd.js +++ b/tests/unit/sproxyd.js @@ -131,16 +131,25 @@ function handler(req, res) { return; } } - server[key] = Buffer.alloc(0); - if (req.headers['x-scal-usermd']) { - md[key] = req.headers['x-scal-usermd']; - } - versions[key] = (versions[key] || 0) + 64; - req.on('data', data => { - server[key] = Buffer.concat([server[key], data]); - }) - .on('end', () => makeResponse(res, 200, 'OK', null, null, + if (req.headers['x-scal-cmd'] === 'update-usermd') { + if (req.headers['x-scal-usermd']) { + md[key] = req.headers['x-scal-usermd']; + } + versions[key] = (versions[key] || 0) + 64; + req.resume().on('end', () => makeResponse(res, 200, 'OK', null, null, versions[key])); + } else { + server[key] = Buffer.alloc(0); + if (req.headers['x-scal-usermd']) { + md[key] = req.headers['x-scal-usermd']; + } + versions[key] = (versions[key] || 0) + 64; + req.on('data', data => { + server[key] = Buffer.concat([server[key], data]); + }) + .on('end', () => makeResponse(res, 200, 'OK', null, null, + versions[key])); + } } else if (req.method === 'GET') { if (!server[key]) { consumeAndMakeResponse(req, res, 404, 'NoSuchPath'); @@ -323,6 +332,45 @@ const clientImmutableWithFailover = new Sproxy({ }); }); + it('should update user metadata via putUserMetadata without altering content', done => { + const newMd = generateMD(); + let testKey; + let initialVersion; + const upStream = new stream.PassThrough(); + upStream.write(upload); + upStream.end(); + async.series([ + next => client.put(upStream, upload.length, parameters, reqUid, + (err, key, version) => { + testKey = key; + initialVersion = version; + next(err); + }), + next => client.putUserMetadata(testKey, newMd, + { query: { version: initialVersion } }, reqUid, next), + next => client.getUserMetadata(testKey, reqUid, (err, data) => { + assert.strictEqual(data, newMd); + next(err); + }), + next => checkKeyContent(client, testKey, upload, reqUid, next), + ], done); + }); + + it('should fail putUserMetadata with wrong version', done => { + const upStream = new stream.PassThrough(); + upStream.write(upload); + upStream.end(); + async.waterfall([ + next => client.put(upStream, upload.length, parameters, reqUid, next), + (key, version, next) => client.putUserMetadata(key, generateMD(), + { query: { version: 0 } }, reqUid, err => { + assert(err, 'expected error for wrong version'); + assert.strictEqual(err.code, 412); + next(); + }), + ], done); + }); + it('Get HEAD should return an error', done => { client.getUserMetadata(generateKey(), reqUid, err => { assert.notStrictEqual(err, null);