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
|
## 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
|
- [joyent/node-triton#129] Fix `triton reboot --wait` to properly wait. Before
|
||||||
it would often return immediately, before the instance started rebooting.
|
it would often return immediately, before the instance started rebooting.
|
||||||
Add `--wait-timeout N` option to `triton reboot`.
|
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",
|
"profileName": "east3b",
|
||||||
"allowWriteActions": true,
|
"allowWriteActions": true,
|
||||||
"image": "minimal-64",
|
"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
|
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.
|
* rename a machine by id.
|
||||||
*
|
*
|
||||||
|
@ -78,8 +78,7 @@ do_rename.help = [
|
|||||||
'and "NEWNAME" is an instance name.',
|
'and "NEWNAME" is an instance name.',
|
||||||
'',
|
'',
|
||||||
'Changing an instance name is asynchronous.',
|
'Changing an instance name is asynchronous.',
|
||||||
'Use "--wait" to not return until',
|
'Use "--wait" to not return until the changes are completed.'
|
||||||
'the changes are completed.'
|
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
do_rename.completionArgtypes = ['tritoninstance', 'none'];
|
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',
|
'get',
|
||||||
'create',
|
'create',
|
||||||
'delete',
|
'delete',
|
||||||
|
'resize',
|
||||||
'rename',
|
'rename',
|
||||||
{ group: '' },
|
{ group: '' },
|
||||||
'start',
|
'start',
|
||||||
@ -64,6 +65,7 @@ InstanceCLI.prototype.do_list = require('./do_list');
|
|||||||
InstanceCLI.prototype.do_get = require('./do_get');
|
InstanceCLI.prototype.do_get = require('./do_get');
|
||||||
InstanceCLI.prototype.do_create = require('./do_create');
|
InstanceCLI.prototype.do_create = require('./do_create');
|
||||||
InstanceCLI.prototype.do_delete = require('./do_delete');
|
InstanceCLI.prototype.do_delete = require('./do_delete');
|
||||||
|
InstanceCLI.prototype.do_resize = require('./do_resize');
|
||||||
InstanceCLI.prototype.do_rename = require('./do_rename');
|
InstanceCLI.prototype.do_rename = require('./do_rename');
|
||||||
|
|
||||||
InstanceCLI.prototype.do_start = require('./do_start');
|
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`
|
* A function appropriate for `vasync.pipeline` funcs that takes a `arg.id`
|
||||||
* fwrule shortid or uuid, and determines the fwrule id (setting it
|
* 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.
|
* rename a machine by id.
|
||||||
*
|
*
|
||||||
@ -2451,10 +2530,12 @@ TritonApi.prototype.renameInstance = function renameInstance(opts, cb) {
|
|||||||
next();
|
next();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self._waitForInstanceRename({
|
self._waitForInstanceUpdate({
|
||||||
id: arg.instId,
|
id: arg.instId,
|
||||||
timeout: opts.waitTimeout,
|
timeout: opts.waitTimeout,
|
||||||
name: opts.name
|
isUpdated: function (machine) {
|
||||||
|
return opts.name === machine.name;
|
||||||
|
}
|
||||||
}, next);
|
}, next);
|
||||||
}
|
}
|
||||||
]}, function (err) {
|
]}, function (err) {
|
||||||
@ -2466,22 +2547,24 @@ TritonApi.prototype.renameInstance = function renameInstance(opts, cb) {
|
|||||||
* Shared implementation for any methods to change instance name.
|
* Shared implementation for any methods to change instance name.
|
||||||
*
|
*
|
||||||
* @param {Object} opts
|
* @param {Object} opts
|
||||||
* - {String} id: The instance ID Required.
|
* - {String} id: Required. The instance ID Required.
|
||||||
* - {String} name: Required change new name
|
* - {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
|
* - {Number} timeout: The number of milliseconds after which to
|
||||||
* timeout (call `cb` with a timeout error) waiting.
|
* timeout (call `cb` with a timeout error) waiting.
|
||||||
* Default is Infinity (i.e. it doesn't timeout).
|
* Default is Infinity (i.e. it doesn't timeout).
|
||||||
* @param {Function} cb: `function (err)`
|
* @param {Function} cb: `function (err)`
|
||||||
*/
|
*/
|
||||||
TritonApi.prototype._waitForInstanceRename =
|
TritonApi.prototype._waitForInstanceUpdate =
|
||||||
function _waitForInstanceRename(opts, cb) {
|
function _waitForInstanceUpdate(opts, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
assert.object(opts, 'opts');
|
assert.object(opts, 'opts');
|
||||||
assert.uuid(opts.id, 'opts.id');
|
assert.uuid(opts.id, 'opts.id');
|
||||||
|
assert.func(opts.isUpdated, 'opts.isUpdated');
|
||||||
assert.optionalNumber(opts.timeout, 'opts.timeout');
|
assert.optionalNumber(opts.timeout, 'opts.timeout');
|
||||||
var timeout = opts.hasOwnProperty('timeout') ? opts.timeout : Infinity;
|
var timeout = opts.hasOwnProperty('timeout') ? opts.timeout : Infinity;
|
||||||
assert.ok(timeout > 0, 'opts.timeout must be greater than zero');
|
assert.ok(timeout > 0, 'opts.timeout must be greater than zero');
|
||||||
assert.string(opts.name, 'opts.name');
|
|
||||||
assert.func(cb, 'cb');
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -2499,7 +2582,7 @@ function _waitForInstanceRename(opts, cb) {
|
|||||||
cb(err);
|
cb(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (opts.name === machine.name) {
|
if (opts.isUpdated(machine)) {
|
||||||
cb();
|
cb();
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -25,5 +25,6 @@
|
|||||||
// The params used for test provisions. By default the tests use:
|
// The params used for test provisions. By default the tests use:
|
||||||
// the smallest RAM package, the latest base* image.
|
// the smallest RAM package, the latest base* image.
|
||||||
"package": "<package name or uuid>",
|
"package": "<package name or uuid>",
|
||||||
|
"resizePackage": "<package name>",
|
||||||
"image": "<image uuid, name or name@version>"
|
"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
|
// create a test machine (blocking) and output JSON
|
||||||
tt.test(' setup: triton create', function (t) {
|
tt.test(' setup: triton create', function (t) {
|
||||||
var argv = [
|
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
|
// rename the instance
|
||||||
tt.test(' triton inst rename', function (t) {
|
tt.test(' triton inst rename', function (t) {
|
||||||
var args = ['inst', 'rename', '-w', instance.id, INST_ALIAS_NEWNAME];
|
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) {
|
function jsonStreamParse(s) {
|
||||||
var results = [];
|
var results = [];
|
||||||
@ -344,6 +371,7 @@ module.exports = {
|
|||||||
deleteTestInst: deleteTestInst,
|
deleteTestInst: deleteTestInst,
|
||||||
getTestImg: getTestImg,
|
getTestImg: getTestImg,
|
||||||
getTestPkg: getTestPkg,
|
getTestPkg: getTestPkg,
|
||||||
|
getResizeTestPkg: getResizeTestPkg,
|
||||||
jsonStreamParse: jsonStreamParse,
|
jsonStreamParse: jsonStreamParse,
|
||||||
printConfig: printConfig,
|
printConfig: printConfig,
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user