joyent/node-triton#226 add new triton volume sizes
subcommand
Reviewed by: Trent Mick <trentm@gmail.com> Approved by: Trent Mick <trentm@gmail.com>
This commit is contained in:
parent
057a784dc3
commit
8438f446cc
@ -7,7 +7,7 @@ Known issues:
|
||||
|
||||
## not yet released
|
||||
|
||||
(nothing yet)
|
||||
- [joyent/node-triton#226] added new `triton volume sizes` subcommand.
|
||||
|
||||
## 5.3.1
|
||||
|
||||
|
@ -2326,6 +2326,17 @@ CloudApi.prototype.listVolumes = function listVolumes(options, cb) {
|
||||
this._passThrough(endpoint, options, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* List the account's volume sizes.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Function} callback - called like `function (err, volumeSizes)`
|
||||
*/
|
||||
CloudApi.prototype.listVolumeSizes = function listVolumeSizes(options, cb) {
|
||||
var endpoint = format('/%s/volumesizes', this.account);
|
||||
this._passThrough(endpoint, options, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a volume for the account.
|
||||
*
|
||||
|
@ -63,6 +63,25 @@ function do_create(subcmd, opts, args, cb) {
|
||||
|
||||
self.top.tritonapi.createVolume(createVolumeParams,
|
||||
function onRes(volCreateErr, volume) {
|
||||
/*
|
||||
* VolumeSizeNotAvailable errors include additional
|
||||
* information in their message
|
||||
* about available volume sizes using units that are
|
||||
* different than the units node-triton users have to use
|
||||
* when specifying volume sizes on the command line
|
||||
* (mebibytes vs gibibytes).
|
||||
* As a result, we override this type of error to provide a
|
||||
* simpler message that is less confusing, and users can use
|
||||
* the "triton volume sizes" command to find out which
|
||||
* sizes are available.
|
||||
*/
|
||||
if (volCreateErr &&
|
||||
volCreateErr.name === 'VolumeSizeNotAvailableError') {
|
||||
next(new Error('volume size not available, use ' +
|
||||
'triton volume sizes command for available sizes'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!volCreateErr && !opts.json) {
|
||||
console.log('Creating volume %s (%s)', volume.name,
|
||||
volume.id);
|
||||
@ -144,11 +163,11 @@ do_create.options = [
|
||||
names: ['size', 's'],
|
||||
type: 'string',
|
||||
helpArg: 'SIZE',
|
||||
help: 'The size of the volume to create, in the form ' +
|
||||
'`<integer><unit>`, e.g. `20G`. <integer> must be > 0. Supported ' +
|
||||
'units are `G` or `g` for gibibytes and `M` or `m` for mebibytes.' +
|
||||
' If a size is not specified, the newly created volume will have ' +
|
||||
'a default size corresponding to the smallest size available.',
|
||||
help: 'The size of the volume to create, in gibibytes, in the form ' +
|
||||
'`<integer>G`, e.g. `20G`. <integer> must be > 0. If a size is ' +
|
||||
'not specified, the newly created volume will have a default ' +
|
||||
'size corresponding to the smallest size available. Available ' +
|
||||
'volume sizes can be listed via the "volume sizes" sub-command.',
|
||||
completionType: 'tritonvolumesize'
|
||||
},
|
||||
{
|
||||
|
90
lib/do_volume/do_sizes.js
Normal file
90
lib/do_volume/do_sizes.js
Normal file
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2017 Joyent, Inc.
|
||||
*
|
||||
* `triton volume sizes ...`
|
||||
*/
|
||||
|
||||
var assert = require('assert-plus');
|
||||
var format = require('util').format;
|
||||
var jsprim = require('jsprim');
|
||||
var tabula = require('tabula');
|
||||
var VError = require('verror');
|
||||
|
||||
var common = require('../common');
|
||||
var errors = require('../errors');
|
||||
|
||||
var COLUMNS = ['type', {name: 'SIZE', lookup: 'sizeHuman', align: 'right'}];
|
||||
var MIBS_IN_GIB = 1024;
|
||||
|
||||
// sort default with -s
|
||||
var sortDefault = 'size';
|
||||
|
||||
function do_sizes(subcmd, opts, args, callback) {
|
||||
var self = this;
|
||||
|
||||
if (opts.help) {
|
||||
this.do_help('help', {}, [subcmd], callback);
|
||||
return;
|
||||
}
|
||||
|
||||
var sort = opts.s.split(',');
|
||||
|
||||
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||
if (setupErr) {
|
||||
callback(setupErr);
|
||||
}
|
||||
|
||||
self.top.tritonapi.cloudapi.listVolumeSizes(
|
||||
function onRes(listVolSizesErr, volumeSizes, res) {
|
||||
if (listVolSizesErr) {
|
||||
return callback(listVolSizesErr);
|
||||
}
|
||||
|
||||
if (opts.json) {
|
||||
common.jsonStream(volumeSizes);
|
||||
} else {
|
||||
volumeSizes =
|
||||
volumeSizes.map(function renderVolSize(volumeSize) {
|
||||
volumeSize.sizeHuman =
|
||||
volumeSize.size / MIBS_IN_GIB + 'G';
|
||||
return volumeSize;
|
||||
});
|
||||
|
||||
tabula(volumeSizes, {
|
||||
skipHeader: opts.H,
|
||||
columns: COLUMNS,
|
||||
sort: sort
|
||||
});
|
||||
}
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
do_sizes.options = [
|
||||
{
|
||||
names: ['help', 'h'],
|
||||
type: 'bool',
|
||||
help: 'Show this help.'
|
||||
}
|
||||
].concat(common.getCliTableOptions({
|
||||
sortDefault: sortDefault
|
||||
}));
|
||||
|
||||
do_sizes.synopses = ['{{name}} {{cmd}} [OPTIONS]'];
|
||||
|
||||
do_sizes.help = [
|
||||
'List volume sizes.',
|
||||
'',
|
||||
'{{usage}}',
|
||||
'',
|
||||
'{{options}}'
|
||||
].join('\n');
|
||||
|
||||
module.exports = do_sizes;
|
@ -30,7 +30,8 @@ function VolumeCLI(top) {
|
||||
'list',
|
||||
'get',
|
||||
'create',
|
||||
'delete'
|
||||
'delete',
|
||||
'sizes'
|
||||
]
|
||||
});
|
||||
}
|
||||
@ -45,6 +46,7 @@ VolumeCLI.prototype.do_list = require('./do_list');
|
||||
VolumeCLI.prototype.do_get = require('./do_get');
|
||||
VolumeCLI.prototype.do_create = require('./do_create');
|
||||
VolumeCLI.prototype.do_delete = require('./do_delete');
|
||||
VolumeCLI.prototype.do_sizes = require('./do_sizes');
|
||||
|
||||
VolumeCLI.aliases = ['vol'];
|
||||
|
||||
|
@ -19,48 +19,30 @@ function throwInvalidSize(size) {
|
||||
/*
|
||||
* Returns the number of MiBs (Mebibytes) represented by the string "size". That
|
||||
* string has the following format: <integer><unit>. The integer must be > 0.
|
||||
* Unit format suffixes are 'G' or 'g' for gibibytes and 'M' or 'm' for
|
||||
* mebibytes.
|
||||
* Unit format suffix can only be 'G' for gibibytes.
|
||||
*
|
||||
* Examples:
|
||||
* - the strings '100m' and '100M' represent 100 mebibytes
|
||||
* - the strings '100g' and '100G' represent 100 gibibytes
|
||||
* Example: the strings '100G' represents 100 gibibytes
|
||||
*
|
||||
* If "size" is not a valid size string, an error is thrown.
|
||||
*/
|
||||
function parseVolumeSize(size) {
|
||||
assert.string(size, 'size');
|
||||
|
||||
var baseValue;
|
||||
var MIBS_IN_GB = 1024;
|
||||
|
||||
var MULTIPLIERS_TABLE = {
|
||||
g: MIBS_IN_GB,
|
||||
G: MIBS_IN_GB,
|
||||
m: 1,
|
||||
M: 1
|
||||
};
|
||||
|
||||
var multiplier;
|
||||
var multiplierSymbol;
|
||||
var baseValue;
|
||||
|
||||
var matches = size.match(/^([1-9]\d*)(g|m|G|M)$/);
|
||||
var matches = size.match(/^([1-9]\d*)G$/);
|
||||
if (!matches) {
|
||||
throwInvalidSize(size);
|
||||
}
|
||||
|
||||
multiplierSymbol = matches[2];
|
||||
if (multiplierSymbol) {
|
||||
multiplier = MULTIPLIERS_TABLE[multiplierSymbol];
|
||||
}
|
||||
|
||||
baseValue = Number(matches[1]);
|
||||
|
||||
if (isNaN(baseValue) || multiplier === undefined) {
|
||||
if (isNaN(baseValue)) {
|
||||
throwInvalidSize(size);
|
||||
}
|
||||
|
||||
return baseValue * multiplier;
|
||||
return baseValue * MIBS_IN_GB;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
@ -19,8 +19,10 @@ var vasync = require('vasync');
|
||||
|
||||
var common = require('../../lib/common');
|
||||
var h = require('./helpers');
|
||||
var mod_volumes = require('../../lib/volumes');
|
||||
|
||||
var FABRIC_NETWORKS = [];
|
||||
var MIBS_IN_GIB = 1024;
|
||||
|
||||
var testOpts = {
|
||||
skip: !(h.CONFIG.allowWriteActions && h.CONFIG.allowVolumesTests)
|
||||
@ -35,7 +37,7 @@ test('triton volume create with non-default size...', testOpts, function (tt) {
|
||||
var validVolumeName =
|
||||
h.makeResourceName('node-triton-test-volume-create-non-default-' +
|
||||
'size');
|
||||
var validVolumeSize = '20g';
|
||||
var validVolumeSize = '20G';
|
||||
var validVolumeSizeInMib = 20 * 1024;
|
||||
|
||||
tt.comment('Test config:');
|
||||
@ -107,3 +109,81 @@ test('triton volume create with non-default size...', testOpts, function (tt) {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('triton volume create with unavailable size...', testOpts, function (tt) {
|
||||
var validVolumeName =
|
||||
h.makeResourceName('node-triton-test-volume-create-unavailable-' +
|
||||
'size');
|
||||
|
||||
tt.test(' triton volume create volume with unavailable size',
|
||||
function (t) {
|
||||
|
||||
vasync.pipeline({arg: {}, funcs: [
|
||||
function getVolumeSizes(ctx, next) {
|
||||
h.triton(['volume', 'sizes', '-j'],
|
||||
function onVolSizesListed(volSizesListErr, sizes) {
|
||||
var largestSizeInMib;
|
||||
|
||||
t.notOk(volSizesListErr,
|
||||
'listing volume sizes should not error');
|
||||
if (volSizesListErr) {
|
||||
next(volSizesListErr);
|
||||
return;
|
||||
}
|
||||
|
||||
t.ok(typeof (sizes) === 'string',
|
||||
'sizes should be a string');
|
||||
|
||||
sizes = sizes.trim().split('\n');
|
||||
t.ok(sizes.length > 0,
|
||||
'there should be at least one available ' +
|
||||
'volume size');
|
||||
|
||||
if (sizes.length === 0) {
|
||||
next(new Error('no volume size available'));
|
||||
return;
|
||||
}
|
||||
|
||||
largestSizeInMib =
|
||||
JSON.parse(sizes[sizes.length - 1]).size;
|
||||
|
||||
ctx.unavailableVolumeSize =
|
||||
(largestSizeInMib / MIBS_IN_GIB + 1) + 'G';
|
||||
|
||||
next();
|
||||
});
|
||||
},
|
||||
function createVolWithUnavailableSize(ctx, next) {
|
||||
h.triton([
|
||||
'volume',
|
||||
'create',
|
||||
'--name',
|
||||
validVolumeName,
|
||||
'--size',
|
||||
ctx.unavailableVolumeSize,
|
||||
'-w'
|
||||
].join(' '), function (volCreateErr, stdout, stderr) {
|
||||
var actualErrMsg;
|
||||
var expectedErrMsg = 'volume size not available';
|
||||
|
||||
t.ok(volCreateErr,
|
||||
'volume creation with unavailable size ' +
|
||||
ctx.unavailableVolumeSize +
|
||||
' should error');
|
||||
|
||||
if (volCreateErr) {
|
||||
actualErrMsg = stderr;
|
||||
t.notEqual(actualErrMsg.indexOf(expectedErrMsg), -1,
|
||||
'error message should include: ' +
|
||||
expectedErrMsg + ' and got: ' +
|
||||
actualErrMsg);
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
]}, function onTestDone(err) {
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user