From 8438f446cc4e5fc129ad06bfc7bc517a0c568eb3 Mon Sep 17 00:00:00 2001 From: Julien Gilli Date: Thu, 17 Aug 2017 17:50:46 -0700 Subject: [PATCH] joyent/node-triton#226 add new `triton volume sizes` subcommand Reviewed by: Trent Mick Approved by: Trent Mick --- CHANGES.md | 2 +- lib/cloudapi2.js | 11 +++ lib/do_volume/do_create.js | 29 ++++++-- lib/do_volume/do_sizes.js | 90 +++++++++++++++++++++++ lib/do_volume/index.js | 4 +- lib/volumes.js | 30 ++------ test/integration/cli-volumes-size.test.js | 82 ++++++++++++++++++++- 7 files changed, 216 insertions(+), 32 deletions(-) create mode 100644 lib/do_volume/do_sizes.js diff --git a/CHANGES.md b/CHANGES.md index 26b0c63..8b00156 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,7 +7,7 @@ Known issues: ## not yet released -(nothing yet) +- [joyent/node-triton#226] added new `triton volume sizes` subcommand. ## 5.3.1 diff --git a/lib/cloudapi2.js b/lib/cloudapi2.js index 6a177b8..78fd4a6 100644 --- a/lib/cloudapi2.js +++ b/lib/cloudapi2.js @@ -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. * diff --git a/lib/do_volume/do_create.js b/lib/do_volume/do_create.js index 9503a69..4346a5b 100644 --- a/lib/do_volume/do_create.js +++ b/lib/do_volume/do_create.js @@ -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 ' + - '``, e.g. `20G`. 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 ' + + '`G`, e.g. `20G`. 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' }, { diff --git a/lib/do_volume/do_sizes.js b/lib/do_volume/do_sizes.js new file mode 100644 index 0000000..6e9513b --- /dev/null +++ b/lib/do_volume/do_sizes.js @@ -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; diff --git a/lib/do_volume/index.js b/lib/do_volume/index.js index 645639f..5ce00ed 100644 --- a/lib/do_volume/index.js +++ b/lib/do_volume/index.js @@ -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']; diff --git a/lib/volumes.js b/lib/volumes.js index d2c27b8..2f5054b 100644 --- a/lib/volumes.js +++ b/lib/volumes.js @@ -19,48 +19,30 @@ function throwInvalidSize(size) { /* * Returns the number of MiBs (Mebibytes) represented by the string "size". That * string has the following format: . 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 = { diff --git a/test/integration/cli-volumes-size.test.js b/test/integration/cli-volumes-size.test.js index 02360f1..20a0194 100644 --- a/test/integration/cli-volumes-size.test.js +++ b/test/integration/cli-volumes-size.test.js @@ -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:'); @@ -106,4 +108,82 @@ test('triton volume create with non-default size...', testOpts, function (tt) { t.end(); }); }); +}); + +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(); + }); + }); }); \ No newline at end of file