From 5dab69c002911a44dcde468890249981d7160795 Mon Sep 17 00:00:00 2001 From: Yang Yong Date: Wed, 21 Dec 2016 11:30:24 +0900 Subject: [PATCH] joyent/node-triton#146 triton instance rename --wait --- lib/cloudapi2.js | 4 +- lib/do_instance/do_rename.js | 54 +++++++++--- lib/tritonapi.js | 91 ++++++++++++++++++-- test/integration/cli-manage-workflow.test.js | 22 +++++ 4 files changed, 149 insertions(+), 22 deletions(-) diff --git a/lib/cloudapi2.js b/lib/cloudapi2.js index a347c07..f28624c 100644 --- a/lib/cloudapi2.js +++ b/lib/cloudapi2.js @@ -778,7 +778,7 @@ CloudApi.prototype.getMachine = function getMachine(opts, cb) { * @param {Object} opts * - id {UUID} Required. The machine id. * - {String} name. The machine name - * @param {Function} callback of the form `function (err, res)` + * @param {Function} callback of the form `function (err, body, res)` */ CloudApi.prototype.renameMachine = function renameMachine(opts, callback) { assert.uuid(opts.id, 'opts.id'); @@ -793,7 +793,7 @@ CloudApi.prototype.renameMachine = function renameMachine(opts, callback) { path: format('/%s/machines/%s', this.account, opts.id), data: data }, function (err, req, res, body) { - callback(err, res); + callback(err, body, res); }); }; diff --git a/lib/do_instance/do_rename.js b/lib/do_instance/do_rename.js index a89c306..af24b02 100644 --- a/lib/do_instance/do_rename.js +++ b/lib/do_instance/do_rename.js @@ -7,13 +7,7 @@ var common = require('../common'); var errors = require('../errors'); - -function perror(err) { - console.error('error: %s', err.message); -} - function do_rename(subcmd, opts, args, callback) { - var self = this; if (opts.help) { this.do_help('help', {}, [subcmd], callback); return; @@ -24,14 +18,30 @@ function do_rename(subcmd, opts, args, callback) { callback(new errors.UsageError('missing NEWNAME arg')); return; } - var cOpts = {id: args[0], name: args[1]}; - self.top.tritonapi.renameInstance(cOpts, function (err) { - if (err) { - callback(err); - return; + + var id = args[0]; + var name = args[1]; + console.log('Renaming instance %s to "%s"', id, name); + + var tritonapi = this.top.tritonapi; + common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) { + if (setupErr) { + callback(setupErr); } - console.log('Renamed instance %s to "%s"', cOpts.id, cOpts.name); - callback(); + + tritonapi.renameInstance({ + id: id, + name: name, + wait: opts.wait, + waitTimeout: opts.wait_timeout * 1000 + }, function (err) { + if (err) { + callback(err); + return; + } + console.log('Renamed instance %s to "%s"', id, name); + callback(); + }); }); } @@ -41,6 +51,18 @@ do_rename.options = [ names: ['help', 'h'], type: 'bool', help: 'Show this help.' + }, + { + names: ['wait', 'w'], + type: 'bool', + help: 'Block until renaming instance is complete.' + }, + { + names: ['wait-timeout'], + type: 'positiveInteger', + default: 120, + help: 'The number of seconds to wait before timing out with an error. ' + + 'The default is 120 seconds.' } ]; @@ -53,7 +75,11 @@ do_rename.help = [ '', '{{options}}', 'Where "INST" is an instance name, id, or short id', - 'and "NEWNAME" is an instance name.' + 'and "NEWNAME" is an instance name.', + '', + 'Changing an instance name is asynchronous.', + 'Use "--wait" to not return until', + 'the changes are completed.' ].join('\n'); do_rename.completionArgtypes = ['tritoninstance', 'none']; diff --git a/lib/tritonapi.js b/lib/tritonapi.js index b016a98..9065bc5 100644 --- a/lib/tritonapi.js +++ b/lib/tritonapi.js @@ -817,7 +817,6 @@ TritonApi.prototype.getInstance = function getInstance(opts, cb) { if (inst || instFromList) { return next(); } - self.cloudapi.listMachines({name: opts.id}, function (err, insts) { if (err) { return next(err); @@ -2266,14 +2265,24 @@ TritonApi.prototype.deletePolicy = function deletePolicy(opts, cb) { * rename a machine by id. * * @param {Object} opts - * - id {UUID} or The machine name or shortID Required. - * - {String} name. The machine name - * @param {Function} callback of the form `function (err, res)` + * - {String} id: Required. The instance name, short id, or id (a UUID). + * - {String} name: Required. The new instance name. + * - {Boolean} wait: Wait (via polling) until the rename is complete. + * Warning: A concurrent rename of the same instance can result in this + * polling being unable to notice the change. Use `waitTimeout` to + * put an upper bound. + * - {Number} waitTimeout: The number of milliseconds after which to + * timeout (call `cb` with a timeout error) waiting. Only relevant if + * `opts.wait === true`. Default is Infinity (i.e. it doesn't timeout). + * @param {Function} callback of the form `function (err, _, res)` */ TritonApi.prototype.renameInstance = function renameInstance(opts, cb) { assert.string(opts.id, 'opts.id'); assert.string(opts.name, 'opts.name'); + assert.optionalBool(opts.wait, 'opts.wait'); + assert.optionalNumber(opts.waitTimeout, 'opts.waitTimeout'); assert.func(cb, 'cb'); + var self = this; var res; @@ -2282,16 +2291,86 @@ TritonApi.prototype.renameInstance = function renameInstance(opts, cb) { function renameMachine(arg, next) { self.cloudapi.renameMachine({id: arg.instId, name: opts.name}, - function (err, _res) { + function (err, _, _res) { res = _res; next(err); }); + }, + + function waitForNameChanges(arg, next) { + if (!opts.wait) { + next(); + return; + } + self._waitForInstanceRename({ + id: arg.instId, + timeout: opts.waitTimeout, + name: opts.name + }, next); } ]}, function (err) { - cb(err, res); + cb(err, null, res); }); }; +/** + * Shared implementation for any methods to change instance name. + * + * @param {Object} opts + * - {String} id: The instance ID Required. + * - {String} name: Required change new name + * - {Number} timeout: The number of milliseconds after which to + * timeout (call `cb` with a timeout error) waiting. + * Default is Infinity (i.e. it doesn't timeout). + * @param {Function} cb: `function (err)` + */ + +TritonApi.prototype._waitForInstanceRename = +function _waitForInstanceRename(opts, cb) { + var self = this; + assert.object(opts, 'opts'); + assert.uuid(opts.id, 'opts.id'); + assert.optionalNumber(opts.timeout, 'opts.timeout'); + var timeout = opts.hasOwnProperty('timeout') ? opts.timeout : Infinity; + assert.ok(timeout > 0, 'opts.timeout must be greater than zero'); + assert.string(opts.name, 'opts.name'); + assert.func(cb, 'cb'); + + /* + * Hardcoded 2s poll interval for now. Not yet configurable, being mindful + * of avoiding lots of clients naively swamping a CloudAPI and hitting + * throttling. + */ + var POLL_INTERVAL = 2 * 1000; + + var startTime = Date.now(); + + var poll = function () { + self.cloudapi.getMachine({id: opts.id}, function (err, machine) { + if (err) { + cb(err); + return; + } + if (opts.name === machine.name) { + cb(); + return; + + } else { + var elapsedTime = Date.now() - startTime; + if (elapsedTime > timeout) { + cb(new errors.TimeoutError(format('timeout waiting for ' + + 'instance %s rename (elapsed %ds)', + opts.id, Math.round(elapsedTime / 1000)))); + } else { + setTimeout(poll, POLL_INTERVAL); + } + } + }); + }; + + setImmediate(poll); +}; + //---- exports module.exports = { diff --git a/test/integration/cli-manage-workflow.test.js b/test/integration/cli-manage-workflow.test.js index c08bed0..0d5eed1 100644 --- a/test/integration/cli-manage-workflow.test.js +++ b/test/integration/cli-manage-workflow.test.js @@ -24,6 +24,7 @@ var h = require('./helpers'); // --- globals var INST_ALIAS = f('nodetritontest-managewf-%s', os.hostname()); +var INST_ALIAS_NEWNAME = INST_ALIAS + '-renamed'; var opts = { skip: !h.CONFIG.allowWriteActions @@ -236,6 +237,27 @@ test('triton manage workflow', opts, function (tt) { }); }); + // rename the instance + tt.test(' triton inst rename', function (t) { + var args = ['inst', 'rename', '-w', instance.id, INST_ALIAS_NEWNAME]; + h.safeTriton(t, args, function (err, stdout) { + t.ok(stdout.match(/^Renaming instance/m), + '"Renaming instance" in stdout'); + t.ok(stdout.match(/^Renamed instance/m), + '"Renamed instance" in stdout'); + t.end(); + }); + }); + + tt.test(' confirm renamed', function (t) { + h.safeTriton(t, {json: true, args: ['inst', 'get', '-j', + INST_ALIAS_NEWNAME]}, + function (err, inst) { + t.equal(inst.name, INST_ALIAS_NEWNAME, 'instance was renamed'); + t.end(); + }); + }); + // remove test instance tt.test(' cleanup (triton delete)', function (t) { h.safeTriton(t, ['delete', '-w', instance.id], function () {