joyent/node-triton#157 support resizing of instances
Reviewed by: Trent Mick <trentm@gmail.com> Approved by: Trent Mick <trentm@gmail.com>
This commit is contained in:
parent
3f4eff598c
commit
ec9b6cc5aa
@ -7,6 +7,9 @@ Known issues:
|
||||
|
||||
## not yet released
|
||||
|
||||
- [joyent/node-triton#157] Add `triton instance resize ...` command and
|
||||
`TritonApi.resizeInstance` method.
|
||||
|
||||
- [joyent/node-triton#129] Fix `triton reboot --wait` to properly wait. Before
|
||||
it would often return immediately, before the instance started rebooting.
|
||||
Add `--wait-timeout N` option to `triton reboot`.
|
||||
|
@ -368,7 +368,8 @@ test-integration`). Integration tests require a config file, by default at
|
||||
"profileName": "east3b",
|
||||
"allowWriteActions": true,
|
||||
"image": "minimal-64",
|
||||
"package": "t4-standard-128M"
|
||||
"package": "g4-highcpu-128M",
|
||||
"resizePackage": "g4-highcpu-256M"
|
||||
}
|
||||
|
||||
See "test/config.json.sample" for a description of all config vars. Minimally
|
||||
|
@ -772,6 +772,31 @@ CloudApi.prototype.getMachine = function getMachine(opts, cb) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* resize a machine by id.
|
||||
*
|
||||
* @param {Object} opts
|
||||
* - id {UUID} Required. The machine id.
|
||||
* - {UUID} package. A package id, as returned from listPackages
|
||||
* @param {Function} callback of the form `function (err, body, res)`
|
||||
*/
|
||||
CloudApi.prototype.resizeMachine = function resizeMachine(opts, callback) {
|
||||
assert.uuid(opts.id, 'opts.id');
|
||||
assert.uuid(opts.package, 'opts.package');
|
||||
var data = {
|
||||
action: 'resize',
|
||||
package: opts.package
|
||||
};
|
||||
|
||||
this._request({
|
||||
method: 'POST',
|
||||
path: format('/%s/machines/%s', this.account, opts.id),
|
||||
data: data
|
||||
}, function (err, req, res, body) {
|
||||
callback(err, body, res);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* rename a machine by id.
|
||||
*
|
||||
|
@ -78,8 +78,7 @@ do_rename.help = [
|
||||
'and "NEWNAME" is an instance name.',
|
||||
'',
|
||||
'Changing an instance name is asynchronous.',
|
||||
'Use "--wait" to not return until',
|
||||
'the changes are completed.'
|
||||
'Use "--wait" to not return until the changes are completed.'
|
||||
].join('\n');
|
||||
|
||||
do_rename.completionArgtypes = ['tritoninstance', 'none'];
|
||||
|
87
lib/do_instance/do_resize.js
Normal file
87
lib/do_instance/do_resize.js
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
var common = require('../common');
|
||||
var errors = require('../errors');
|
||||
|
||||
function do_resize(subcmd, opts, args, callback) {
|
||||
if (opts.help) {
|
||||
this.do_help('help', {}, [subcmd], callback);
|
||||
return;
|
||||
} else if (args.length < 1) {
|
||||
callback(new errors.UsageError('missing INST arg'));
|
||||
return;
|
||||
} else if (args.length < 2) {
|
||||
callback(new errors.UsageError('missing PACKAGE arg'));
|
||||
return;
|
||||
}
|
||||
|
||||
var id = args[0];
|
||||
var pkg = args[1];
|
||||
console.log('Resizing instance %s to "%s"', id, pkg);
|
||||
|
||||
var tritonapi = this.top.tritonapi;
|
||||
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||
if (setupErr) {
|
||||
callback(setupErr);
|
||||
}
|
||||
|
||||
tritonapi.resizeInstance({
|
||||
id: id,
|
||||
package: pkg,
|
||||
wait: opts.wait,
|
||||
waitTimeout: opts.wait_timeout * 1000
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
console.log('Resized instance %s to "%s"', id, pkg);
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
do_resize.options = [
|
||||
{
|
||||
names: ['help', 'h'],
|
||||
type: 'bool',
|
||||
help: 'Show this help.'
|
||||
},
|
||||
{
|
||||
names: ['wait', 'w'],
|
||||
type: 'bool',
|
||||
help: 'Block until resizing 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.'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
do_resize.synopses = ['{{name}} resize [OPTIONS] INST PACKAGE'];
|
||||
do_resize.help = [
|
||||
'Resize an instance.',
|
||||
'',
|
||||
'{{usage}}',
|
||||
'',
|
||||
'{{options}}',
|
||||
'Where "INST" is an instance name, id, or short id',
|
||||
'and "PACKAGE" is a package name, id, or short id.',
|
||||
'',
|
||||
'Changing an instance package is asynchronous.',
|
||||
'Use "--wait" to not return until the changes are completed.'
|
||||
].join('\n');
|
||||
|
||||
do_resize.completionArgtypes = ['tritoninstance', 'tritonpackage', 'none'];
|
||||
|
||||
|
||||
module.exports = do_resize;
|
@ -34,6 +34,7 @@ function InstanceCLI(top) {
|
||||
'get',
|
||||
'create',
|
||||
'delete',
|
||||
'resize',
|
||||
'rename',
|
||||
{ group: '' },
|
||||
'start',
|
||||
@ -64,6 +65,7 @@ InstanceCLI.prototype.do_list = require('./do_list');
|
||||
InstanceCLI.prototype.do_get = require('./do_get');
|
||||
InstanceCLI.prototype.do_create = require('./do_create');
|
||||
InstanceCLI.prototype.do_delete = require('./do_delete');
|
||||
InstanceCLI.prototype.do_resize = require('./do_resize');
|
||||
InstanceCLI.prototype.do_rename = require('./do_rename');
|
||||
|
||||
InstanceCLI.prototype.do_start = require('./do_start');
|
||||
|
@ -184,6 +184,27 @@ function _stepInstId(arg, next) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A function appropriate for `vasync.pipeline` funcs that takes a `arg.package`
|
||||
* package name, short id or uuid, and determines the package id (setting it
|
||||
* as `arg.pkgId`). Also sets `arg.pkgName` so that we can use this to test when
|
||||
* the instance has been updated.
|
||||
*/
|
||||
function _stepPkgId(arg, next) {
|
||||
assert.object(arg.client, 'arg.client');
|
||||
assert.string(arg.package, 'arg.package');
|
||||
|
||||
arg.client.getPackage(arg.package, function (err, pkg) {
|
||||
if (err) {
|
||||
next(err);
|
||||
} else {
|
||||
arg.pkgId = pkg.id;
|
||||
arg.pkgName = pkg.name;
|
||||
next();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A function appropriate for `vasync.pipeline` funcs that takes a `arg.id`
|
||||
* fwrule shortid or uuid, and determines the fwrule id (setting it
|
||||
@ -2410,6 +2431,64 @@ TritonApi.prototype.rebootInstance = function rebootInstance(opts, cb) {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Resize a machine by id.
|
||||
*
|
||||
* @param {Object} opts
|
||||
* - {String} id: Required. The instance name, short id, or id (a UUID).
|
||||
* - {String} package: Required. The new package name, shortId,
|
||||
* or id (a UUID).
|
||||
* - {Boolean} wait: Wait (via polling) until the rename is complete.
|
||||
* Warning: A concurrent resize 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.resizeInstance = function resizeInstance(opts, cb) {
|
||||
assert.string(opts.id, 'opts.id');
|
||||
assert.string(opts.package, 'opts.package');
|
||||
assert.optionalBool(opts.wait, 'opts.wait');
|
||||
assert.optionalNumber(opts.waitTimeout, 'opts.waitTimeout');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
var self = this;
|
||||
var res;
|
||||
|
||||
vasync.pipeline(
|
||||
{arg: {client: self, id: opts.id, package: opts.package}, funcs: [
|
||||
_stepInstId,
|
||||
|
||||
_stepPkgId,
|
||||
|
||||
function resizeMachine(arg, next) {
|
||||
self.cloudapi.resizeMachine({id: arg.instId, package: arg.pkgId},
|
||||
function (err, _res) {
|
||||
res = _res;
|
||||
next(err);
|
||||
});
|
||||
},
|
||||
|
||||
function waitForSizeChanges(arg, next) {
|
||||
if (!opts.wait) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
self._waitForInstanceUpdate({
|
||||
id: arg.instId,
|
||||
timeout: opts.waitTimeout,
|
||||
isUpdated: function (machine) {
|
||||
return arg.pkgName === machine.package;
|
||||
}
|
||||
}, next);
|
||||
}
|
||||
]}, function (err) {
|
||||
cb(err, null, res);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* rename a machine by id.
|
||||
*
|
||||
@ -2451,10 +2530,12 @@ TritonApi.prototype.renameInstance = function renameInstance(opts, cb) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
self._waitForInstanceRename({
|
||||
self._waitForInstanceUpdate({
|
||||
id: arg.instId,
|
||||
timeout: opts.waitTimeout,
|
||||
name: opts.name
|
||||
isUpdated: function (machine) {
|
||||
return opts.name === machine.name;
|
||||
}
|
||||
}, next);
|
||||
}
|
||||
]}, function (err) {
|
||||
@ -2466,22 +2547,24 @@ TritonApi.prototype.renameInstance = function renameInstance(opts, cb) {
|
||||
* Shared implementation for any methods to change instance name.
|
||||
*
|
||||
* @param {Object} opts
|
||||
* - {String} id: The instance ID Required.
|
||||
* - {String} name: Required change new name
|
||||
* - {String} id: Required. The instance ID Required.
|
||||
* - {Function} isUpdated: Required. A function which is passed the
|
||||
* machine data, should check if the change has been applied and
|
||||
* return a Boolean.
|
||||
* - {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) {
|
||||
TritonApi.prototype._waitForInstanceUpdate =
|
||||
function _waitForInstanceUpdate(opts, cb) {
|
||||
var self = this;
|
||||
assert.object(opts, 'opts');
|
||||
assert.uuid(opts.id, 'opts.id');
|
||||
assert.func(opts.isUpdated, 'opts.isUpdated');
|
||||
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');
|
||||
|
||||
/*
|
||||
@ -2499,7 +2582,7 @@ function _waitForInstanceRename(opts, cb) {
|
||||
cb(err);
|
||||
return;
|
||||
}
|
||||
if (opts.name === machine.name) {
|
||||
if (opts.isUpdated(machine)) {
|
||||
cb();
|
||||
return;
|
||||
|
||||
|
@ -25,5 +25,6 @@
|
||||
// The params used for test provisions. By default the tests use:
|
||||
// the smallest RAM package, the latest base* image.
|
||||
"package": "<package name or uuid>",
|
||||
"resizePackage": "<package name>",
|
||||
"image": "<image uuid, name or name@version>"
|
||||
}
|
||||
|
@ -68,6 +68,15 @@ test('triton manage workflow', opts, function (tt) {
|
||||
});
|
||||
});
|
||||
|
||||
var resizePkgName;
|
||||
tt.test(' setup: find resize test package', function (t) {
|
||||
h.getResizeTestPkg(t, function (err, pkgName_) {
|
||||
t.ifError(err, 'getResizeTestPkg' + (err ? ': ' + err : ''));
|
||||
resizePkgName = pkgName_;
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
// create a test machine (blocking) and output JSON
|
||||
tt.test(' setup: triton create', function (t) {
|
||||
var argv = [
|
||||
@ -255,6 +264,27 @@ test('triton manage workflow', opts, function (tt) {
|
||||
});
|
||||
});
|
||||
|
||||
// resize the instance
|
||||
tt.test(' triton inst resize', function (t) {
|
||||
var args = ['inst', 'resize', '-w', instance.id, resizePkgName];
|
||||
h.safeTriton(t, args, function (err, stdout) {
|
||||
t.ok(stdout.match(/^Resizing instance/m),
|
||||
'"Resizing instance" in stdout');
|
||||
t.ok(stdout.match(/^Resized instance/m),
|
||||
'"Resized instance" in stdout');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
tt.test(' confirm resized', function (t) {
|
||||
h.safeTriton(t, {json: true, args: ['inst', 'get', '-j',
|
||||
INST_ALIAS]},
|
||||
function (err, inst) {
|
||||
t.equal(inst.package, resizePkgName, 'instance was resized');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
// rename the instance
|
||||
tt.test(' triton inst rename', function (t) {
|
||||
var args = ['inst', 'rename', '-w', instance.id, INST_ALIAS_NEWNAME];
|
||||
|
@ -231,6 +231,33 @@ function getTestPkg(t, cb) {
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Find and return second smallest package name that can be used for
|
||||
* test provisions.
|
||||
*
|
||||
* @param {Tape} t - tape test object
|
||||
* @param {Function} cb - `function (err, {pkgs})`
|
||||
* where `pkgs` is an Array of 2 test packages to use.
|
||||
*/
|
||||
function getResizeTestPkg(t, cb) {
|
||||
if (CONFIG.resizePackage) {
|
||||
t.ok(CONFIG.resizePackage, 'resizePackage from config: ' +
|
||||
CONFIG.resizePackage);
|
||||
cb(null, CONFIG.resizePackage);
|
||||
return;
|
||||
}
|
||||
|
||||
safeTriton(t, ['pkg', 'ls', '-j'], function (err, stdout) {
|
||||
var pkgs = jsonStreamParse(stdout);
|
||||
// Smallest RAM first.
|
||||
tabula.sortArrayOfObjects(pkgs, ['memory']);
|
||||
var pkg = pkgs[1];
|
||||
t.ok(pkg.name, f('second smallest (RAM) available package: %s (%s)',
|
||||
pkg.id, pkg.name));
|
||||
cb(null, pkg.name);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function jsonStreamParse(s) {
|
||||
var results = [];
|
||||
@ -344,6 +371,7 @@ module.exports = {
|
||||
deleteTestInst: deleteTestInst,
|
||||
getTestImg: getTestImg,
|
||||
getTestPkg: getTestPkg,
|
||||
getResizeTestPkg: getResizeTestPkg,
|
||||
jsonStreamParse: jsonStreamParse,
|
||||
printConfig: printConfig,
|
||||
|
||||
|
Reference in New Issue
Block a user