TRITON-401 Add support for fabric vlans and networks to node-triton

Reviewed by: Trent Mick <trent.mick@joyent.com>
Reviewed by: Julien Gilli <julien.gilli@joyent.com>
Reviewed by: Pedro P. Candel <pedro@joyent.com>
Approved by: Pedro P. Candel <pedro@joyent.com>
This commit is contained in:
Marsell Kukuljevic 2018-06-05 06:32:58 +02:00
parent f100c4dbb5
commit 0bc11c1e33
26 changed files with 2586 additions and 81 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@
/test/*.json
/npm-debug.log
/triton-*.tgz
*.swp

View File

@ -6,6 +6,8 @@ Known issues:
## not yet released
- [TRITON-401] Add `triton network` and `triton vlan` commands, for
creating/changing/removing network fabrics and VLANs.
- [TRITON-524] Add `triton inst get --credentials ...` option to match
`triton inst list --credentials ...` for including generated credentials
in instance metadata.

View File

@ -210,6 +210,7 @@ function CLI() {
'package',
'network',
'fwrule',
'vlan',
{ group: 'Other Commands' },
'info',
'account',
@ -705,6 +706,9 @@ CLI.prototype.do_package = require('./do_package');
CLI.prototype.do_networks = require('./do_networks');
CLI.prototype.do_network = require('./do_network');
// VLANs
CLI.prototype.do_vlan = require('./do_vlan');
// Hidden commands
CLI.prototype.do_cloudapi = require('./do_cloudapi');
CLI.prototype.do_badger = require('./do_badger');

View File

@ -357,6 +357,48 @@ CloudApi.prototype.ping = function ping(opts, cb) {
};
// ---- config
/**
* Get config object for the current user.
*
* @param {Object} opts
* @param {Function} cb of the form `function (err, config, res)`
*/
CloudApi.prototype.getConfig = function getConfig(opts, cb) {
assert.object(opts, 'opts');
assert.func(cb, 'cb');
var endpoint = this._path(format('/%s/config', this.account));
this._request(endpoint, function (err, req, res, body) {
cb(err, body, res);
});
};
/**
* Set config object for the current user.
*
* @param {Object} opts
* - {String} default_network: network fabric docker containers are
* provisioned on. Optional.
* @param {Function} cb of the form `function (err, config, res)`
*/
CloudApi.prototype.updateConfig = function updateConfig(opts, cb) {
assert.object(opts, 'opts');
assert.optionalUuid(opts.default_network, 'opts.default_network');
assert.func(cb, 'cb');
this._request({
method: 'PUT',
path: format('/%s/config', this.account),
data: opts
}, function (err, req, res, body) {
cb(err, body, res);
});
};
// ---- networks
/**
@ -458,6 +500,228 @@ CloudApi.prototype.UPDATE_NETWORK_IP_FIELDS = {
reserved: 'boolean'
};
// --- Fabric VLANs
/**
* Creates a network on a fabric (specifically: a fabric VLAN).
*
* @param {Object} options object containing:
* - {Integer} vlan_id (required) VLAN's id, between 0-4095.
* - {String} name (required) A name to identify the network.
* - {String} subnet (required) CIDR description of the network.
* - {String} provision_start_ip (required) First assignable IP addr.
* - {String} provision_end_ip (required) Last assignable IP addr.
* - {String} gateway (optional) Gateway IP address.
* - {String} resolvers (optional) Static routes for hosts on network.
* - {String} description (optional)
* - {Boolean} internet_nat (optional) Whether to provision an Internet
* NAT on the gateway address (default: true).
* @param {Function} callback of the form f(err, vlan, res).
*/
CloudApi.prototype.createFabricNetwork =
function createFabricNetwork(opts, cb) {
assert.object(opts, 'opts');
assert.number(opts.vlan_id, 'opts.vlan_id');
assert.string(opts.name, 'opts.name');
assert.string(opts.subnet, 'opts.subnet');
assert.string(opts.provision_start_ip, 'opts.provision_start_ip');
assert.string(opts.provision_end_ip, 'opts.provision_end_ip');
assert.optionalString(opts.gateway, 'opts.gateway');
assert.optionalString(opts.resolvers, 'opts.resolvers');
assert.optionalString(opts.routes, 'opts.routes');
assert.optionalBool(opts.internet_nat, 'opts.internet_nat');
var data = common.objCopy(opts);
var vlanId = data.vlan_id;
delete data.vlan_id;
this._request({
method: 'POST',
path: format('/%s/fabrics/default/vlans/%d/networks', this.account,
vlanId),
data: data
}, function reqCb(err, req, res, body) {
cb(err, body, res);
});
};
/**
* Lists all networks on a VLAN.
*
* Returns an array of objects.
*
* @param {Object} options object containing:
* - {Integer} vlan_id (required) VLAN's id, between 0-4095.
* @param {Function} callback of the form f(err, networks, res).
*/
CloudApi.prototype.listFabricNetworks =
function listFabricNetworks(opts, cb) {
assert.object(opts, 'opts');
assert.number(opts.vlan_id, 'opts.vlan_id');
assert.func(cb, 'cb');
var endpoint = format('/%s/fabrics/default/vlans/%d/networks',
this.account, opts.vlan_id);
this._passThrough(endpoint, opts, cb);
};
/**
* Remove a fabric network
*
* @param {Object} opts (object)
* - {String} id: The network id. Required.
* - {Integer} vlan_id: The VLAN id. Required.
* @param {Function} cb of the form `function (err, res)`
*/
CloudApi.prototype.deleteFabricNetwork =
function deleteFabricNetwork(opts, cb) {
assert.object(opts, 'opts');
assert.uuid(opts.id, 'opts.id');
assert.number(opts.vlan_id, 'opts.vlan_id');
assert.func(cb, 'cb');
this._request({
method: 'DELETE',
path: format('/%s/fabrics/default/vlans/%d/networks/%s', this.account,
opts.vlan_id, opts.id)
}, function (err, req, res) {
cb(err, res);
});
};
/**
* Creates a VLAN on a fabric.
*
* @param {Object} options object containing:
* - {Integer} vlan_id (required) VLAN's id, between 0-4095.
* - {String} name (required) A name to identify the VLAN.
* - {String} description (optional)
* @param {Function} callback of the form f(err, vlan, res).
*/
CloudApi.prototype.createFabricVlan =
function createFabricVlan(opts, cb) {
assert.object(opts, 'opts');
assert.number(opts.vlan_id, 'opts.vlan_id');
assert.string(opts.name, 'opts.name');
assert.optionalString(opts.description, 'opts.description');
var data = {
vlan_id: opts.vlan_id
};
Object.keys(this.UPDATE_VLAN_FIELDS).forEach(function (attr) {
if (opts[attr] !== undefined)
data[attr] = opts[attr];
});
this._request({
method: 'POST',
path: format('/%s/fabrics/default/vlans', this.account),
data: data
}, function reqCb(err, req, res, body) {
cb(err, body, res);
});
};
/**
* Lists all the VLANs.
*
* Returns an array of objects.
*
* @param opts {Object} Options
* @param {Function} callback of the form f(err, vlans, res).
*/
CloudApi.prototype.listFabricVlans =
function listFabricVlans(opts, cb) {
assert.object(opts, 'opts');
assert.func(cb, 'cb');
var endpoint = format('/%s/fabrics/default/vlans', this.account);
this._passThrough(endpoint, opts, cb);
};
/**
* Retrieves a VLAN.
*
* @param {Integer} id: The VLAN id.
* @param {Function} callback of the form `function (err, vlan, res)`
*/
CloudApi.prototype.getFabricVlan =
function getFabricVlan(opts, cb) {
assert.object(opts, 'opts');
assert.number(opts.vlan_id, 'opts.vlan_id');
assert.func(cb, 'cb');
var endpoint = format('/%s/fabrics/default/vlans/%d', this.account,
opts.vlan_id);
this._request(endpoint, function (err, req, res, body) {
cb(err, body, res);
});
};
// <updatable account field> -> <expected typeof>
CloudApi.prototype.UPDATE_VLAN_FIELDS = {
name: 'string',
description: 'string'
};
/**
* Updates a VLAN.
*
* @param {Object} opts object containing:
* - {Integer} id: The VLAN id. Required.
* - {String} name: The VLAN name. Optional.
* - {String} description: Description of the VLAN. Optional.
* @param {Function} callback of the form `function (err, vlan, res)`
*/
CloudApi.prototype.updateFabricVlan =
function updateFabricVlan(opts, cb) {
assert.object(opts, 'opts');
assert.number(opts.vlan_id, 'opts.vlan_id');
assert.optionalString(opts.rule, 'opts.name');
assert.optionalString(opts.description, 'opts.description');
assert.func(cb, 'cb');
var data = {};
Object.keys(this.UPDATE_VLAN_FIELDS).forEach(function (attr) {
if (opts[attr] !== undefined)
data[attr] = opts[attr];
});
var vlanId = opts.vlan_id;
this._request({
method: 'POST',
path: format('/%s/fabrics/default/vlans/%d', this.account, vlanId),
data: data
}, function onReq(err, req, res, body) {
cb(err, body, res);
});
};
/**
* Remove a VLAN.
*
* @param {Object} opts (object)
* - {Integer} vlan_id: The vlan id. Required.
* @param {Function} cb of the form `function (err, res)`
*/
CloudApi.prototype.deleteFabricVlan =
function deleteFabricVlan(opts, cb) {
assert.object(opts, 'opts');
assert.number(opts.vlan_id, 'opts.vlan_id');
assert.func(cb, 'cb');
this._request({
method: 'DELETE',
path: format('/%s/fabrics/default/vlans/%d', this.account, opts.vlan_id)
}, function onReq(err, req, res) {
cb(err, res);
});
};
// ---- datacenters
/**

187
lib/do_network/do_create.js Normal file
View File

@ -0,0 +1,187 @@
/*
* 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 2018 Joyent, Inc.
*
* `triton network create ...`
*/
var assert = require('assert-plus');
var format = require('util').format;
var jsprim = require('jsprim');
var common = require('../common');
var errors = require('../errors');
var OPTIONAL_OPTS = ['description', 'gateway', 'resolvers', 'routes'];
function do_create(subcmd, opts, args, cb) {
assert.optionalString(opts.name, 'opts.name');
assert.optionalString(opts.subnet, 'opts.subnet');
assert.optionalString(opts.start_ip, 'opts.start_ip');
assert.optionalString(opts.end_ip, 'opts.end_ip');
assert.optionalString(opts.description, 'opts.description');
assert.optionalString(opts.gateway, 'opts.gateway');
assert.optionalString(opts.resolvers, 'opts.resolvers');
assert.optionalString(opts.routes, 'opts.routes');
assert.optionalBool(opts.no_nat, 'opts.no_nat');
assert.optionalBool(opts.json, 'opts.json');
assert.optionalBool(opts.help, 'opts.help');
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length === 0) {
cb(new errors.UsageError('missing VLAN argument'));
return;
} else if (args.length > 1) {
cb(new errors.UsageError('incorrect number of arguments'));
return;
}
var vlanId = jsprim.parseInteger(args[0], { allowSign: false });
if (typeof (vlanId) !== 'number') {
cb(new errors.UsageError('VLAN must be an integer'));
return;
}
var createOpts = {
vlan_id: vlanId,
name: opts.name,
subnet: opts.subnet,
provision_start_ip: opts.start_ip,
provision_end_ip: opts.end_ip
};
if (opts.no_nat) {
createOpts.internet_nat = false;
}
OPTIONAL_OPTS.forEach(function (attr) {
if (opts[attr]) {
createOpts[attr] = opts[attr];
}
});
var cli = this.top;
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
var cloudapi = cli.tritonapi.cloudapi;
cloudapi.createFabricNetwork(createOpts, function onCreate(err, net) {
if (err) {
cb(err);
return;
}
if (opts.json) {
console.log(JSON.stringify(net));
} else {
console.log('Created network %s (%s)', net.name, net.id);
}
cb();
});
});
}
do_create.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON stream output.'
},
{
names: ['name', 'n'],
type: 'string',
helpArg: 'NAME',
help: 'Name of the NETWORK.'
},
{
names: ['description', 'D'],
type: 'string',
helpArg: 'DESC',
help: 'Description of the NETWORK.'
},
{
names: ['subnet'],
type: 'string',
helpArg: 'SUBNET',
help: 'A CIDR string describing the NETWORK.'
},
{
names: ['start_ip'],
type: 'string',
helpArg: 'START_IP',
help: 'First assignable IP address on NETWORK.'
},
{
names: ['end_ip'],
type: 'string',
helpArg: 'END_IP',
help: 'Last assignable IP address on NETWORK.'
},
{
names: ['gateway'],
type: 'string',
helpArg: 'GATEWAY',
help: 'Gateway IP address.'
},
{
names: ['resolvers'],
type: 'string',
helpArg: 'RESOLVERS',
help: 'Resolver IP addresses.'
},
{
names: ['routes'],
type: 'string',
helpArg: 'ROUTES',
help: 'Static routes for hosts on NETWORK.'
},
{
names: ['no_nat'],
type: 'bool',
helpArg: 'NO_NAT',
help: 'Disable creation of an Internet NAT zone on GATEWAY.'
}
];
do_create.synopses = ['{{name}} {{cmd}} [OPTIONS] VLAN'];
do_create.help = [
'Create a network on a VLAN.',
'',
'{{usage}}',
'',
'{{options}}',
'',
'Example:',
' triton network create -n accounting --subnet=192.168.0.0/24',
' --start_ip=192.168.0.1 --end_ip=192.168.0.254 2'
].join('\n');
do_create.helpOpts = {
helpCol: 25
};
module.exports = do_create;

View File

@ -0,0 +1,85 @@
/*
* 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 network delete ...`
*/
var assert = require('assert-plus');
var format = require('util').format;
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
function do_delete(subcmd, opts, args, cb) {
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length < 1) {
cb(new errors.UsageError('missing NETWORK argument(s)'));
return;
}
var cli = this.top;
var networks = args;
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
vasync.forEachParallel({
inputs: networks,
func: function deleteOne(id, next) {
cli.tritonapi.deleteFabricNetwork({ id: id },
function onDelete(err) {
if (err) {
next(err);
return;
}
console.log('Deleted network %s', id);
next();
});
}
}, cb);
});
}
do_delete.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
}
];
do_delete.synopses = ['{{name}} {{cmd}} NETWORK [NETWORK ...]'];
do_delete.help = [
'Remove a fabric network.',
'',
'{{usage}}',
'',
'{{options}}',
'Where NETWORK is a network id (full UUID), name, or short id.'
].join('\n');
do_delete.aliases = ['rm'];
do_delete.completionArgtypes = ['tritonnetwork'];
module.exports = do_delete;

View File

@ -0,0 +1,78 @@
/*
* 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 network get-default ...`
*/
var assert = require('assert-plus');
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
function do_get_default(subcmd, opts, args, cb) {
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length > 0) {
cb(new errors.UsageError('incorrect number of arguments'));
return;
}
var cli = this.top;
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
cli.tritonapi.cloudapi.getConfig({}, function getConf(err, conf) {
if (err) {
cb(err);
return;
}
var defaultNetwork = conf.default_network;
if (!defaultNetwork) {
cb('default network not found');
return;
}
cli.handlerFromSubcmd('network').dispatch({
subcmd: 'get',
opts: opts,
args: [defaultNetwork]
}, cb);
});
});
}
do_get_default.options = require('./do_get').options;
do_get_default.synopses = ['{{name}} {{cmd}}'];
do_get_default.help = [
'Get default network.',
'',
'{{usage}}',
'',
'{{options}}'
].join('\n');
do_get_default.completionArgtypes = ['tritonnetwork'];
module.exports = do_get_default;

View File

@ -66,14 +66,23 @@ function do_list(subcmd, opts, args, callback) {
common.cliSetupTritonApi,
function searchNetworks(arg, next) {
self.top.tritonapi.cloudapi.listNetworks(function (err, networks) {
// since this command is also used by do_vlan/do_networks.js
if (opts.vlan_id) {
self.top.tritonapi.listFabricNetworks({
vlan_id: opts.vlan_id
}, listedNetworks);
} else {
self.top.tritonapi.cloudapi.listNetworks({}, listedNetworks);
}
function listedNetworks(err, networks) {
if (err) {
next(err);
return;
}
arg.networks = networks;
next();
});
}
},
function filterNetworks(arg, next) {

View File

@ -0,0 +1,92 @@
/*
* 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 network set-default ...`
*/
var assert = require('assert-plus');
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
function do_set_default(subcmd, opts, args, cb) {
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length === 0) {
cb(new errors.UsageError('missing NETWORK argument'));
return;
} else if (args.length > 1) {
cb(new errors.UsageError('incorrect number of arguments'));
return;
}
var cli = this.top;
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
cli.tritonapi.getNetwork(args[0], function onNetwork(err, net) {
if (err) {
cb(err);
return;
}
var params = {
default_network: net.id
};
var cloudapi = cli.tritonapi.cloudapi;
cloudapi.updateConfig(params, function onUpdate(err2) {
if (err2) {
cb(err2);
return;
}
console.log('Set network %s (%s) as default.', net.name,
net.id);
cb();
});
});
});
}
do_set_default.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
}
];
do_set_default.synopses = ['{{name}} {{cmd}} NETWORK'];
do_set_default.help = [
'Set default network.',
'',
'{{usage}}',
'',
'{{options}}',
'Where NETWORK is a network id (full UUID), name, or short id.'
].join('\n');
do_set_default.completionArgtypes = ['tritonnetwork'];
module.exports = do_set_default;

View File

@ -33,7 +33,11 @@ function NetworkCLI(top) {
'help',
'list',
'get',
'ip'
'ip',
'create',
'delete',
'get-default',
'set-default'
]
});
}
@ -47,6 +51,10 @@ NetworkCLI.prototype.init = function init(opts, args, cb) {
NetworkCLI.prototype.do_list = require('./do_list');
NetworkCLI.prototype.do_get = require('./do_get');
NetworkCLI.prototype.do_ip = require('./do_ip');
NetworkCLI.prototype.do_create = require('./do_create');
NetworkCLI.prototype.do_delete = require('./do_delete');
NetworkCLI.prototype.do_get_default = require('./do_get_default');
NetworkCLI.prototype.do_set_default = require('./do_set_default');
module.exports = NetworkCLI;

123
lib/do_vlan/do_create.js Normal file
View File

@ -0,0 +1,123 @@
/*
* 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 vlan create ...`
*/
var assert = require('assert-plus');
var format = require('util').format;
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
function do_create(subcmd, opts, args, cb) {
assert.optionalString(opts.name, 'opts.name');
assert.optionalString(opts.description, 'opts.description');
assert.optionalBool(opts.json, 'opts.json');
assert.optionalBool(opts.help, 'opts.help');
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length === 0) {
cb(new errors.UsageError('missing VLAN argument'));
return;
} else if (args.length > 1) {
cb(new errors.UsageError('incorrect number of arguments'));
return;
}
var createOpts = {
vlan_id: +args[0]
};
if (opts.name) {
createOpts.name = opts.name;
}
if (opts.description) {
createOpts.description = opts.description;
}
var cli = this.top;
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
var cloudapi = cli.tritonapi.cloudapi;
cloudapi.createFabricVlan(createOpts, function onCreate(err, vlan) {
if (err) {
cb(err);
return;
}
if (opts.json) {
console.log(JSON.stringify(vlan));
} else {
if (vlan.name) {
console.log('Created vlan %s (%d)', vlan.name,
vlan.vlan_id);
} else {
console.log('Created vlan %d', vlan.vlan_id);
}
}
cb();
});
});
}
do_create.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON stream output.'
},
{
names: ['name', 'n'],
type: 'string',
helpArg: 'NAME',
help: 'Name of the VLAN.'
},
{
names: ['description', 'D'],
type: 'string',
helpArg: 'DESC',
help: 'Description of the VLAN.'
}
];
do_create.synopses = ['{{name}} {{cmd}} [OPTIONS] VLAN'];
do_create.help = [
'Create a VLAN.',
'',
'{{usage}}',
'',
'{{options}}',
'Example:',
' triton vlan create -n "dmz" -D "Demilitarized zone" 73'
].join('\n');
do_create.helpOpts = {
helpCol: 25
};
module.exports = do_create;

85
lib/do_vlan/do_delete.js Normal file
View File

@ -0,0 +1,85 @@
/*
* 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 vlan delete ...`
*/
var assert = require('assert-plus');
var format = require('util').format;
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
function do_delete(subcmd, opts, args, cb) {
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length < 1) {
cb(new errors.UsageError('missing VLAN argument(s)'));
return;
}
var cli = this.top;
var vlanIds = args;
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
vasync.forEachParallel({
inputs: vlanIds,
func: function deleteOne(id, next) {
cli.tritonapi.deleteFabricVlan({ vlan_id: id },
function onDelete(err) {
if (err) {
next(err);
return;
}
console.log('Deleted vlan %s', id);
next();
});
}
}, cb);
});
}
do_delete.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
}
];
do_delete.synopses = ['{{name}} {{cmd}} VLAN [VLAN ...]'];
do_delete.help = [
'Remove a VLAN.',
'',
'{{usage}}',
'',
'{{options}}',
'Where VLAN is a VLAN id or name.'
].join('\n');
do_delete.aliases = ['rm'];
do_delete.completionArgtypes = ['tritonvlan'];
module.exports = do_delete;

88
lib/do_vlan/do_get.js Normal file
View File

@ -0,0 +1,88 @@
/*
* 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 vlan get ...`
*/
var assert = require('assert-plus');
var common = require('../common');
var errors = require('../errors');
function do_get(subcmd, opts, args, cb) {
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length === 0) {
cb(new errors.UsageError('missing VLAN argument'));
return;
} else if (args.length > 1) {
cb(new errors.UsageError('incorrect number of arguments'));
return;
}
var id = args[0];
var cli = this.top;
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
cli.tritonapi.getFabricVlan(id, function onGet(err, vlan) {
if (err) {
cb(err);
return;
}
if (opts.json) {
console.log(JSON.stringify(vlan));
} else {
console.log(JSON.stringify(vlan, null, 4));
}
cb();
});
});
}
do_get.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON stream output.'
}
];
do_get.synopses = ['{{name}} {{cmd}} VLAN'];
do_get.help = [
'Show a specific VLAN.',
'',
'{{usage}}',
'',
'{{options}}',
'Where VLAN is a VLAN id or name.'
].join('\n');
do_get.completionArgtypes = ['tritonvlan', 'none'];
module.exports = do_get;

123
lib/do_vlan/do_list.js Normal file
View File

@ -0,0 +1,123 @@
/*
* 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 2018 Joyent, Inc.
*
* `triton vlan list ...`
*/
var assert = require('assert-plus');
var tabula = require('tabula');
var common = require('../common');
var errors = require('../errors');
var COLUMNS_DEFAULT = 'vlan_id,name,description';
var SORT_DEFAULT = 'vlan_id';
var VALID_FILTERS = ['vlan_id', 'name', 'description'];
function do_list(subcmd, opts, args, cb) {
assert.object(opts, 'opts');
assert.array(args, 'args');
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
try {
var filters = common.objFromKeyValueArgs(args, {
validKeys: VALID_FILTERS,
disableDotted: true
});
} catch (e) {
cb(e);
return;
}
if (filters.vlan_id !== undefined) {
filters.vlan_id = +filters.vlan_id;
}
var cli = this.top;
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
var cloudapi = cli.tritonapi.cloudapi;
cloudapi.listFabricVlans({}, function onList(err, vlans) {
if (err) {
cb(err);
return;
}
// do filtering
Object.keys(filters).forEach(function doFilter(key) {
var val = filters[key];
vlans = vlans.filter(function (vlan) {
return vlan[key] === val;
});
});
if (opts.json) {
common.jsonStream(vlans);
} else {
var columns = COLUMNS_DEFAULT;
if (opts.o) {
columns = opts.o;
}
columns = columns.split(',');
var sort = opts.s.split(',');
tabula(vlans, {
skipHeader: opts.H,
columns: columns,
sort: sort
});
}
cb();
});
});
}
do_list.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
}
].concat(common.getCliTableOptions({
sortDefault: SORT_DEFAULT
}));
do_list.synopses = ['{{name}} {{cmd}} [OPTIONS] [FILTERS]'];
do_list.help = [
'List VLANs.',
'',
'{{usage}}',
'',
'Filters:',
' FIELD=<integer> Number filter. Supported fields: vlan_id',
' FIELD=<string> String filter. Supported fields: name, description',
'',
'{{options}}',
'Filters are applied client-side (i.e. done by the triton command itself).'
].join('\n');
do_list.aliases = ['ls'];
module.exports = do_list;

View File

@ -0,0 +1,52 @@
/*
* 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 vlan networks ...`
*/
var errors = require('../errors');
function do_networks(subcmd, opts, args, cb) {
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length === 0) {
cb(new errors.UsageError('missing VLAN argument'));
return;
} else if (args.length > 1) {
cb(new errors.UsageError('incorrect number of arguments'));
return;
}
opts.vlan_id = args[0];
this.top.handlerFromSubcmd('network').dispatch({
subcmd: 'list',
opts: opts,
args: []
}, cb);
}
do_networks.synopses = ['{{name}} {{cmd}} [OPTIONS] VLAN'];
do_networks.help = [
'Show all networks on a VLAN.',
'',
'{{usage}}',
'',
'{{options}}',
'Where VLAN is a VLAN id or name.'
].join('\n');
do_networks.options = require('../do_network/do_list').options;
module.exports = do_networks;

201
lib/do_vlan/do_update.js Normal file
View File

@ -0,0 +1,201 @@
/*
* 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 2018 Joyent, Inc.
*
* `triton vlan update ...`
*/
var assert = require('assert-plus');
var format = require('util').format;
var fs = require('fs');
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
var UPDATE_VLAN_FIELDS
= require('../cloudapi2').CloudApi.prototype.UPDATE_VLAN_FIELDS;
function do_update(subcmd, opts, args, cb) {
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
var log = this.log;
var tritonapi = this.top.tritonapi;
if (args.length === 0) {
cb(new errors.UsageError('missing VLAN argument'));
return;
}
var id = args.shift();
vasync.pipeline({arg: {}, funcs: [
function gatherDataArgs(ctx, next) {
if (opts.file) {
next();
return;
}
try {
ctx.data = common.objFromKeyValueArgs(args, {
disableDotted: true,
typeHintFromKey: UPDATE_VLAN_FIELDS
});
} catch (err) {
next(err);
return;
}
next();
},
function gatherDataFile(ctx, next) {
if (!opts.file || opts.file === '-') {
next();
return;
}
var input = fs.readFileSync(opts.file, 'utf8');
try {
ctx.data = JSON.parse(input);
} catch (err) {
next(new errors.TritonError(format(
'invalid JSON for vlan update in "%s": %s',
opts.file, err)));
return;
}
next();
},
function gatherDataStdin(ctx, next) {
if (opts.file !== '-') {
next();
return;
}
var stdin = '';
process.stdin.resume();
process.stdin.on('data', function (chunk) {
stdin += chunk;
});
process.stdin.on('error', console.error);
process.stdin.on('end', function () {
try {
ctx.data = JSON.parse(stdin);
} catch (err) {
log.trace({stdin: stdin},
'invalid VLAN update JSON on stdin');
next(new errors.TritonError(format(
'invalid JSON for VLAN update on stdin: %s',
err)));
return;
}
next();
});
},
function validateIt(ctx, next) {
assert.object(ctx.data, 'ctx.data');
var keys = Object.keys(ctx.data);
if (keys.length === 0) {
console.log('No fields given for VLAN update');
next();
return;
}
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = ctx.data[key];
var type = UPDATE_VLAN_FIELDS[key];
if (!type) {
next(new errors.UsageError(format('unknown or ' +
'unupdateable field: %s (updateable fields are: %s)',
key,
Object.keys(UPDATE_VLAN_FIELDS).sort().join(', '))));
return;
}
if (typeof (value) !== type) {
next(new errors.UsageError(format('field "%s" must be ' +
'of type "%s", but got a value of type "%s"', key,
type, typeof (value))));
return;
}
}
next();
},
function updateAway(ctx, next) {
var data = ctx.data;
data.vlan_id = id;
tritonapi.updateFabricVlan(data, function onUpdate(err) {
if (err) {
next(err);
return;
}
delete data.vlan_id;
console.log('Updated vlan %s (fields: %s)', id,
Object.keys(data).join(', '));
next();
});
}
]}, cb);
}
do_update.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['file', 'f'],
type: 'string',
helpArg: 'JSON-FILE',
help: 'A file holding a JSON file of updates, or "-" to read ' +
'JSON from stdin.'
}
];
do_update.synopses = [
'{{name}} {{cmd}} VLAN [FIELD=VALUE ...]',
'{{name}} {{cmd}} -f JSON-FILE VLAN'
];
do_update.help = [
'Update a VLAN.',
'',
'{{usage}}',
'',
'{{options}}',
'Updateable fields:',
' ' + Object.keys(UPDATE_VLAN_FIELDS).sort().map(function (f) {
return f + ' (' + UPDATE_VLAN_FIELDS[f] + ')';
}).join(', '),
'',
'Where VLAN is a VLAN id or name.'
].join('\n');
do_update.completionArgtypes = ['tritonvlan', 'tritonupdatevlanfield'];
module.exports = do_update;

55
lib/do_vlan/index.js Normal file
View File

@ -0,0 +1,55 @@
/*
* 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 vlan ...`
*/
var Cmdln = require('cmdln').Cmdln;
var util = require('util');
// ---- CLI class
function VlanCLI(top) {
this.top = top;
Cmdln.call(this, {
name: top.name + ' vlan',
desc: 'List and manage Triton fabric VLANs.',
helpSubcmds: [
'help',
'list',
'get',
'create',
'update',
'delete',
{ group: '' },
'networks'
],
helpOpts: {
minHelpCol: 23
}
});
}
util.inherits(VlanCLI, Cmdln);
VlanCLI.prototype.init = function init(opts, args, cb) {
this.log = this.top.log;
Cmdln.prototype.init.apply(this, arguments);
};
VlanCLI.prototype.do_list = require('./do_list');
VlanCLI.prototype.do_create = require('./do_create');
VlanCLI.prototype.do_get = require('./do_get');
VlanCLI.prototype.do_update = require('./do_update');
VlanCLI.prototype.do_delete = require('./do_delete');
VlanCLI.prototype.do_networks = require('./do_networks');
module.exports = VlanCLI;

View File

@ -282,7 +282,9 @@ function _stepFwRuleId(arg, next) {
* and determines the network id (setting it as `arg.netId`).
*/
function _stepNetId(arg, next) {
assert.object(arg, 'arg');
assert.object(arg.client, 'arg.client');
assert.func(next, 'next');
var id = arg.network || arg.id;
assert.string(id, 'arg.network || arg.id');
@ -291,7 +293,7 @@ function _stepNetId(arg, next) {
arg.netId = id;
next();
} else {
arg.client.getNetwork(id, function (err, net) {
arg.client.getNetwork(id, function onGet(err, net) {
if (err) {
next(err);
} else {
@ -302,6 +304,98 @@ function _stepNetId(arg, next) {
}
}
/**
* A function appropriate for `vasync.pipeline` funcs that takes a `arg.id` and
* optionally a `arg.vlan_id`, where `arg.id` is a network name, shortid or
* uuid, and `arg.vlan_id` is a VLAN's id or name. Sets the network id as
* `arg.netId` and the VLAN id as `arg.vlanId`.
*/
function _stepFabricNetId(arg, next) {
assert.object(arg, 'arg');
assert.object(arg.client, 'arg.client');
assert.string(arg.id, 'arg.id');
assert.func(next, 'next');
var id = arg.id;
var vlanId = arg.vlan_id;
var vlanIdType = typeof (vlanId);
if (common.isUUID(id) && vlanIdType === 'number') {
arg.netId = id;
arg.vlanId = vlanId;
next();
return;
}
arg.client.getNetwork(id, function onGetNetwork(err, net) {
if (err) {
next(err);
return;
}
if (vlanIdType === 'number') {
assert.equal(net.vlan_id, vlanId, 'VLAN belongs to network');
}
if (vlanIdType === 'number' || vlanIdType === 'undefined') {
arg.netId = net.id;
arg.vlanId = net.vlan_id;
next();
return;
}
// at this point the only type left we support are strings
assert.string(vlanId, 'arg.vlan_id');
arg.client.getFabricVlan(vlanId, function onGetFabric(err2, vlan) {
if (err2) {
next(err2);
return;
}
assert.equal(net.vlan_id, vlan.vlan_id, 'VLAN belongs to network');
arg.netId = net.id;
arg.vlanId = net.vlan_id;
next();
});
});
}
/**
* A function appropriate for `vasync.pipeline` funcs that takes a
* `arg.vlan_id`, where that is either a VLAN's id or name. Sets the
* VLAN id as `arg.vlanId`.
*/
function _stepFabricVlanId(arg, next) {
assert.object(arg, 'arg');
assert.object(arg.client, 'arg.client');
assert.ok(typeof (arg.vlan_id) === 'string' ||
typeof (arg.vlan_id) === 'number', 'arg.vlan_id');
assert.func(next, 'next');
var vlanId = arg.vlan_id;
if (typeof (vlanId) === 'number') {
arg.vlanId = vlanId;
next();
return;
}
arg.client.getFabricVlan(vlanId, function onGet(err, vlan) {
if (err) {
next(err);
return;
}
arg.vlanId = vlan.vlan_id;
next();
});
}
//---- TritonApi class
/**
@ -934,7 +1028,7 @@ TritonApi.prototype.getNetwork = function getNetwork(name, cb) {
assert.func(cb, 'cb');
if (common.isUUID(name)) {
this.cloudapi.getNetwork(name, function (err, net) {
this.cloudapi.getNetwork(name, function onGet(err, net) {
if (err) {
if (err.restCode === 'ResourceNotFound') {
// Wrap with *our* ResourceNotFound for exitStatus=3.
@ -947,7 +1041,7 @@ TritonApi.prototype.getNetwork = function getNetwork(name, cb) {
}
});
} else {
this.cloudapi.listNetworks(function (err, nets) {
this.cloudapi.listNetworks({}, function onList(err, nets) {
if (err) {
return cb(err);
}
@ -984,6 +1078,72 @@ TritonApi.prototype.getNetwork = function getNetwork(name, cb) {
}
};
/**
* List all fabric networks on a VLAN. Takes a network's VLAN ID or name as an
* argument.
*/
TritonApi.prototype.listFabricNetworks =
function listFabricNetworks(opts, cb) {
assert.object(opts, 'opts');
assert.ok(typeof (opts.vlan_id) === 'string' ||
typeof (opts.vlan_id) === 'number', 'opts.vlan_id');
assert.func(cb, 'cb');
var self = this;
var networks;
vasync.pipeline({
arg: {client: self, vlan_id: opts.vlan_id}, funcs: [
_stepFabricVlanId,
function listNetworks(arg, next) {
self.cloudapi.listFabricNetworks({
vlan_id: arg.vlanId
}, function listCb(err, nets) {
if (err) {
next(err);
return;
}
networks = nets;
next();
});
}
]}, function (err) {
cb(err, networks);
});
};
/**
* Delete a fabric network by ID, exact name, or short ID, in that order.
* Can accept a network's VLAN ID or name as an optional argument.
*
* If the name is ambiguous, then this errors out.
*/
TritonApi.prototype.deleteFabricNetwork =
function deleteFabricNetwork(opts, cb) {
assert.object(opts, 'opts');
assert.string(opts.id, 'opts.id');
assert.func(cb, 'cb');
var self = this;
vasync.pipeline({
arg: {client: self, id: opts.id, vlan_id: opts.vlan_id},
funcs: [
_stepFabricNetId,
function deleteNetwork(arg, next) {
self.cloudapi.deleteFabricNetwork({
id: arg.netId, vlan_id: arg.vlanId
}, next);
}
]}, cb);
};
/**
* List a network's IPs.
*
@ -2137,14 +2297,15 @@ function deleteAllInstanceTags(opts, cb) {
*/
TritonApi.prototype.addNic =
function addNic(opts, cb) {
assert.object(opts, 'opts');
assert.string(opts.id, 'opts.id');
assert.ok(opts.network, 'opts.network');
assert.func(cb, 'cb');
var self = this;
var nic;
var pipeline = [];
var res;
var nic;
switch (typeof (opts.network)) {
case 'string':
@ -2198,8 +2359,8 @@ function listNics(opts, cb) {
assert.func(cb, 'cb');
var self = this;
var res;
var nics;
var res;
vasync.pipeline({arg: {client: self, id: opts.id}, funcs: [
_stepInstId,
@ -2207,7 +2368,7 @@ function listNics(opts, cb) {
function list(arg, next) {
self.cloudapi.listNics({
id: arg.instId
}, function (err, _nics, _res) {
}, function onList(err, _nics, _res) {
res = _res;
res.instId = arg.instId; // gross hack, in case caller needs it
nics = _nics;
@ -2245,7 +2406,7 @@ function getNic(opts, cb) {
self.cloudapi.getNic({
id: arg.instId,
mac: arg.mac
}, function (err, _nic, _res) {
}, function onGet(err, _nic, _res) {
res = _res;
res.instId = arg.instId; // gross hack, in case caller needs it
nic = _nic;
@ -2283,7 +2444,7 @@ function removeNic(opts, cb) {
self.cloudapi.removeNic({
id: arg.instId,
mac: arg.mac
}, function (err, _res) {
}, function onRemove(err, _res) {
res = _res;
res.instId = arg.instId; // gross hack, in case caller needs it
next(err);
@ -2635,6 +2796,93 @@ TritonApi.prototype.deleteFirewallRule = function deleteFirewallRule(opts, cb) {
};
// ---- VLANs
/**
* Get a VLAN by ID or exact name, in that order.
*
* If the name is ambiguous, then this errors out.
*/
TritonApi.prototype.getFabricVlan = function getFabricVlan(name, cb) {
assert.ok(typeof (name) === 'string' ||
typeof (name) === 'number', 'name');
assert.func(cb, 'cb');
if (+name >= 0 && +name < 4096) {
this.cloudapi.getFabricVlan({vlan_id: +name}, function on(err, vlan) {
if (err) {
if (err.restCode === 'ResourceNotFound') {
// Wrap with our own ResourceNotFound for exitStatus=3.
err = new errors.ResourceNotFoundError(err,
format('vlan with id %s was not found', name));
}
cb(err);
} else {
cb(null, vlan);
}
});
} else {
this.cloudapi.listFabricVlans({}, function onList(err, vlans) {
if (err) {
return cb(err);
}
var nameMatches = [];
for (var i = 0; i < vlans.length; i++) {
var vlan = vlans[i];
if (vlan.name === name) {
nameMatches.push(vlan);
}
}
if (nameMatches.length === 1) {
cb(null, nameMatches[0]);
} else if (nameMatches.length > 1) {
cb(new errors.TritonError(format(
'vlan name "%s" is ambiguous: matches %d vlans',
name, nameMatches.length)));
} else {
cb(new errors.ResourceNotFoundError(format(
'no vlan with name "%s" was found', name)));
}
});
}
};
/**
* Delete a VLAN by ID or exact name, in that order.
*
* If the name is ambiguous, then this errors out.
*/
TritonApi.prototype.deleteFabricVlan = function deleteFabricVlan(opts, cb) {
assert.object(opts, 'opts');
assert.ok(typeof (opts.vlan_id) === 'string' ||
typeof (opts.vlan_id) === 'number', 'opts.vlan_id');
assert.func(cb, 'cb');
var self = this;
var vlanId = opts.vlan_id;
if (+vlanId >= 0 && +vlanId < 4096) {
deleteVlan(+vlanId);
} else {
self.getFabricVlan(vlanId, function onGet(err, vlan) {
if (err) {
cb(err);
return;
}
deleteVlan(vlan.vlan_id);
});
}
function deleteVlan(id) {
self.cloudapi.deleteFabricVlan({vlan_id: id}, cb);
}
};
// ---- RBAC
/**

View File

@ -5,7 +5,7 @@
*/
/*
* Copyright 2016 Joyent, Inc.
* Copyright 2018 Joyent, Inc.
*/
/*
@ -18,6 +18,8 @@ var test = require('tape');
// --- Globals
var NET_NAME = 'node-triton-testnet967';
var CLIENT;
var NET;
@ -33,11 +35,14 @@ test('TritonApi networks', function (tt) {
});
});
tt.test(' cleanup: rm network ' + NET_NAME + ' if exists', function (t) {
CLIENT.deleteFabricNetwork({id: NET_NAME}, function () {
t.end();
});
});
tt.test(' setup: net', function (t) {
var opts = {
account: CLIENT.profile.account
};
CLIENT.cloudapi.listNetworks(opts, function (err, nets) {
CLIENT.cloudapi.listNetworks({}, function (err, nets) {
if (h.ifErr(t, err))
return t.end();
@ -78,6 +83,61 @@ test('TritonApi networks', function (tt) {
});
tt.test(' TritonApi deleteFabricNetwork', function (t) {
function check(genId, idType, vlanId, cb) {
CLIENT.cloudapi.createFabricNetwork({
name: NET_NAME,
subnet: '192.168.97.0/24',
provision_start_ip: '192.168.97.1',
provision_end_ip: '192.168.97.254',
vlan_id: vlanId
}, function onCreate(err, net) {
if (h.ifErr(t, err, 'Error creating network')) {
t.end();
return;
}
var id = genId(net);
CLIENT.deleteFabricNetwork({id: id}, function onDelete(err2) {
if (h.ifErr(t, err, 'Error deleting net by ' + idType)) {
t.end();
return;
}
CLIENT.cloudapi.getNetwork(net.id, function onGet(err3) {
t.ok(err3, 'Network should be gone');
cb();
});
});
});
}
// get a VLAN, then create and delete a set of fabrics to check it's
// possible to delete by id, shortId and name
CLIENT.cloudapi.listFabricVlans({}, function onList(err, vlans) {
if (vlans.length === 0) {
t.end();
return;
}
function getId(net) { return net.id; }
function getName(net) { return net.name; }
function getShort(net) { return net.id.split('-')[0]; }
var vlanId = +vlans[0].vlan_id;
check(getId, 'id', vlanId, function onId() {
check(getName, 'name', vlanId, function onName() {
check(getShort, 'shortId', vlanId, function onShort() {
t.end();
});
});
});
});
});
tt.test(' teardown: client', function (t) {
CLIENT.close();
t.end();

View File

@ -25,9 +25,9 @@ var NIC;
// --- Tests
test('TritonApi networks', function (tt) {
test('TritonApi nics', function (tt) {
tt.test(' setup', function (t) {
h.createClient(function (err, client_) {
h.createClient(function onCreate(err, client_) {
t.error(err);
CLIENT = client_;
t.end();
@ -36,9 +36,11 @@ test('TritonApi networks', function (tt) {
tt.test(' setup: inst', function (t) {
CLIENT.cloudapi.listMachines({}, function (err, vms) {
if (vms.length === 0)
return t.end();
CLIENT.cloudapi.listMachines({}, function onList(err, vms) {
if (vms.length === 0) {
t.end();
return;
}
t.ok(Array.isArray(vms), 'vms array');
INST = vms[0];
@ -49,13 +51,17 @@ test('TritonApi networks', function (tt) {
tt.test(' TritonApi listNics', function (t) {
if (!INST)
return t.end();
if (!INST) {
t.end();
return;
}
function check(val, valName, next) {
CLIENT.listNics({id: val}, function (err, nics) {
if (h.ifErr(t, err, 'no err ' + valName))
return t.end();
CLIENT.listNics({id: val}, function onList(err, nics) {
if (h.ifErr(t, err, 'no err ' + valName)) {
t.end();
return;
}
t.ok(Array.isArray(nics), 'nics array');
NIC = nics[0];
@ -66,9 +72,9 @@ test('TritonApi networks', function (tt) {
var shortId = INST.id.split('-')[0];
check(INST.id, 'id', function () {
check(INST.name, 'name', function () {
check(shortId, 'shortId', function () {
check(INST.id, 'id', function doId() {
check(INST.name, 'name', function doName() {
check(shortId, 'shortId', function doShort() {
t.end();
});
});
@ -77,13 +83,17 @@ test('TritonApi networks', function (tt) {
tt.test(' TritonApi getNic', function (t) {
if (!NIC)
return t.end();
if (!NIC) {
t.end();
return;
}
function check(inst, mac, instValName, next) {
CLIENT.getNic({id: inst, mac: mac}, function (err, nic) {
if (h.ifErr(t, err, 'no err for ' + instValName))
return t.end();
CLIENT.getNic({id: inst, mac: mac}, function onGet(err, nic) {
if (h.ifErr(t, err, 'no err for ' + instValName)) {
t.end();
return;
}
t.deepEqual(nic, NIC, instValName);
@ -93,9 +103,9 @@ test('TritonApi networks', function (tt) {
var shortId = INST.id.split('-')[0];
check(INST.id, NIC.mac, 'id', function () {
check(INST.name, NIC.mac, 'name', function () {
check(shortId, NIC.mac, 'shortId', function () {
check(INST.id, NIC.mac, 'id', function doId() {
check(INST.name, NIC.mac, 'name', function doName() {
check(shortId, NIC.mac, 'shortId', function doShort() {
t.end();
});
});

View File

@ -0,0 +1,120 @@
/*
* 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 2018 Joyent, Inc.
*/
/*
* Integration tests for using VLAN-related APIs as a module.
*/
var h = require('./helpers');
var test = require('tape');
// --- Globals
var CLIENT;
var VLAN;
// --- Tests
test('TritonApi vlan', function (tt) {
tt.test(' setup', function (t) {
h.createClient(function onCreate(err, client_) {
t.error(err);
CLIENT = client_;
t.end();
});
});
tt.test(' setup: vlan', function (t) {
CLIENT.cloudapi.listFabricVlans({}, function onList(err, vlans) {
if (vlans.length === 0) {
t.end();
return;
}
VLAN = vlans[0];
t.end();
});
});
tt.test(' TritonApi getFabricVlan', function (t) {
if (!VLAN) {
t.end();
return;
}
function check(val, valName, next) {
CLIENT.getFabricVlan(val, function onGet(err, vlan) {
if (h.ifErr(t, err, 'no err')) {
t.end();
return;
}
t.deepEqual(vlan, VLAN, valName);
next();
});
}
check(VLAN.vlan_id, 'vlan_id', function onId() {
check(VLAN.name, 'name', function onName() {
t.end();
});
});
});
tt.test(' TritonApi deleteFabricVlan', function (t) {
function check(genId, idType, cb) {
CLIENT.cloudapi.createFabricVlan({
vlan_id: 3291,
name: 'test3291'
}, function onCreate(err, vlan) {
if (h.ifErr(t, err, 'Error creating VLAN')) {
t.end();
return;
}
var id = genId(vlan);
CLIENT.deleteFabricVlan({vlan_id: id}, function onDel(err2) {
if (h.ifErr(t, err, 'Error deleting VLAN by ' + idType)) {
t.end();
return;
}
CLIENT.cloudapi.getFabricVlan({vlan_id: vlan.vlan_id},
function onGet(err3) {
t.ok(err3, 'VLAN should be gone');
cb();
});
});
});
}
function getVlanId(net) { return net.vlan_id; }
function getName(net) { return net.name; }
check(getVlanId, 'vlan_id', function onId() {
check(getName, 'name', function onName() {
t.end();
});
});
});
tt.test(' teardown: client', function (t) {
CLIENT.close();
t.end();
});
});

View File

@ -5,28 +5,53 @@
*/
/*
* Copyright 2017 Joyent, Inc.
* Copyright 2018 Joyent, Inc.
*/
/*
* Integration tests for `triton network(s)`
*/
var h = require('./helpers');
var f = require('util').format;
var os = require('os');
var test = require('tape');
var h = require('./helpers');
var common = require('../../lib/common');
// --- Globals
var NET_NAME = f('nodetritontest-network-%s', os.hostname());
var networks;
var vlan;
var OPTS = {
skip: !h.CONFIG.allowWriteActions
};
// --- Tests
if (OPTS.skip) {
console.error('** skipping some %s tests', __filename);
console.error('** set "allowWriteActions" in test config to enable');
}
test('triton networks', function (tt) {
tt.test(' setup: find a test VLAN', function (t) {
h.triton('vlan list -j', function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err))
return t.end();
vlan = JSON.parse(stdout.trim().split('\n')[0]);
t.ok(vlan, 'vlan for testing found');
t.end();
});
});
tt.test(' triton network list -h', function (t) {
h.triton('networks -h', function (err, stdout, stderr) {
if (h.ifErr(t, err))
@ -209,3 +234,135 @@ test('triton network get', function (tt) {
});
});
test('triton network create', OPTS, function (tt) {
tt.test(' cleanup: rm network ' + NET_NAME + ' if exists', function (t) {
h.triton('network delete ' + NET_NAME, function (err, stdout) {
t.end();
});
});
tt.test(' triton network create -h', function (t) {
h.triton('network create -h', function (err, stdout, stderr) {
if (h.ifErr(t, err))
return t.end();
t.ok(/Usage:\s+triton network\b/.test(stdout));
t.end();
});
});
tt.test(' triton network help create', function (t) {
h.triton('network help create', function (err, stdout, stderr) {
if (h.ifErr(t, err))
return t.end();
t.ok(/Usage:\s+triton network create\b/.test(stdout));
t.end();
});
});
tt.test(' triton network create', function (t) {
h.triton('network create', function (err, stdout, stderr) {
t.ok(err);
t.ok(/error \(Usage\)/.test(stderr));
t.end();
});
});
tt.test(' triton network create VLAN', function (t) {
h.triton('network create --name=' + NET_NAME +
' --subnet=192.168.97.0/24 --start_ip=192.168.97.1' +
' --end_ip=192.168.97.254 -j ' + vlan.vlan_id,
function (err, stdout) {
if (h.ifErr(t, err))
return t.end();
var network = JSON.parse(stdout.trim().split('\n')[0]);
t.equal(network.name, NET_NAME);
t.equal(network.subnet, '192.168.97.0/24');
t.equal(network.provision_start_ip, '192.168.97.1');
t.equal(network.provision_end_ip, '192.168.97.254');
t.equal(network.vlan_id, vlan.vlan_id);
h.triton('network delete ' + network.id, function (err2) {
h.ifErr(t, err2);
t.end();
});
});
});
});
test('triton network delete', OPTS, function (tt) {
tt.test(' triton network delete -h', function (t) {
h.triton('network delete -h', function (err, stdout, stderr) {
if (h.ifErr(t, err))
return t.end();
t.ok(/Usage:\s+triton network\b/.test(stdout));
t.end();
});
});
tt.test(' triton network help delete', function (t) {
h.triton('network help delete', function (err, stdout, stderr) {
if (h.ifErr(t, err))
return t.end();
t.ok(/Usage:\s+triton network delete\b/.test(stdout));
t.end();
});
});
tt.test(' triton network delete', function (t) {
h.triton('network delete', function (err, stdout, stderr) {
t.ok(err);
t.ok(/error \(Usage\)/.test(stderr));
t.end();
});
});
function deleteNetworkTester(t, deleter) {
h.triton('network create --name=' + NET_NAME +
' --subnet=192.168.97.0/24 --start_ip=192.168.97.1' +
' --end_ip=192.168.97.254 -j ' + vlan.vlan_id,
function (err, stdout) {
if (h.ifErr(t, err, 'create test network'))
return t.end();
var network = JSON.parse(stdout.trim().split('\n')[0]);
deleter(null, network, function (err2) {
if (h.ifErr(t, err2, 'deleting test network'))
return t.end();
h.triton('network get ' + network.id, function (err3) {
t.ok(err3, 'network should be gone');
t.end();
});
});
});
}
tt.test(' triton network delete ID', function (t) {
deleteNetworkTester(t, function (err, network, cb) {
h.triton('network delete ' + network.id, cb);
});
});
tt.test(' triton network delete NAME', function (t) {
deleteNetworkTester(t, function (err, network, cb) {
h.triton('network delete ' + network.name, cb);
});
});
tt.test(' triton network delete SHORTID', function (t) {
deleteNetworkTester(t, function (err, network, cb) {
var shortid = network.id.split('-')[0];
h.triton('network delete ' + shortid, cb);
});
});
});

View File

@ -41,7 +41,7 @@ test('triton instance nics', OPTS, function (tt) {
h.printConfig(tt);
tt.test(' cleanup existing inst with alias ' + INST_ALIAS, function (t) {
h.deleteTestInst(t, INST_ALIAS, function (err) {
h.deleteTestInst(t, INST_ALIAS, function onDelete(err) {
t.ifErr(err);
t.end();
});
@ -49,8 +49,10 @@ test('triton instance nics', OPTS, function (tt) {
tt.test(' setup: triton instance create', function (t) {
h.createTestInst(t, INST_ALIAS, {}, function onInst(err, instId) {
if (h.ifErr(t, err, 'triton instance create'))
return t.end();
if (h.ifErr(t, err, 'triton instance create')) {
t.end();
return;
}
t.ok(instId, 'created instance ' + instId);
INST = instId;
@ -61,8 +63,10 @@ test('triton instance nics', OPTS, function (tt) {
tt.test(' setup: find network for tests', function (t) {
h.triton('network list -j', function onNetworks(err, stdout) {
if (h.ifErr(t, err, 'triton network list'))
return t.end();
if (h.ifErr(t, err, 'triton network list')) {
t.end();
return;
}
NETWORK = JSON.parse(stdout.trim().split('\n')[0]);
t.ok(NETWORK, 'NETWORK');
@ -74,9 +78,11 @@ test('triton instance nics', OPTS, function (tt) {
tt.test(' triton instance nic create', function (t) {
var cmd = 'instance nic create -j -w ' + INST + ' ' + NETWORK.id;
h.triton(cmd, function (err, stdout, stderr) {
if (h.ifErr(t, err, 'triton instance nic create'))
return t.end();
h.triton(cmd, function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err, 'triton instance nic create')) {
t.end();
return;
}
NIC = JSON.parse(stdout);
t.ok(NIC, 'created NIC: ' + stdout.trim());
@ -88,9 +94,11 @@ test('triton instance nics', OPTS, function (tt) {
tt.test(' triton instance nic get', function (t) {
var cmd = 'instance nic get ' + INST + ' ' + NIC.mac;
h.triton(cmd, function (err, stdout, stderr) {
if (h.ifErr(t, err, 'triton instance nic get'))
return t.end();
h.triton(cmd, function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err, 'triton instance nic get')) {
t.end();
return;
}
var obj = JSON.parse(stdout);
t.equal(obj.mac, NIC.mac, 'nic MAC is correct');
@ -104,9 +112,11 @@ test('triton instance nics', OPTS, function (tt) {
tt.test(' triton instance nic list', function (t) {
var cmd = 'instance nic list ' + INST;
h.triton(cmd, function (err, stdout, stderr) {
if (h.ifErr(t, err, 'triton instance nic list'))
return t.end();
h.triton(cmd, function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err, 'triton instance nic list')) {
t.end();
return;
}
var nics = stdout.trim().split('\n');
t.ok(nics[0].match(/IP\s+MAC\s+STATE\s+NETWORK/), 'nic list' +
@ -115,7 +125,7 @@ test('triton instance nics', OPTS, function (tt) {
t.ok(nics.length >= 1, 'triton nic list expected nic num');
var testNics = nics.filter(function (nic) {
var testNics = nics.filter(function doFilter(nic) {
return nic.match(NIC.mac);
});
@ -128,17 +138,19 @@ test('triton instance nics', OPTS, function (tt) {
tt.test(' triton instance nic list -j', function (t) {
var cmd = 'instance nic list -j ' + INST;
h.triton(cmd, function (err, stdout, stderr) {
if (h.ifErr(t, err, 'triton instance nic list'))
return t.end();
h.triton(cmd, function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err, 'triton instance nic list')) {
t.end();
return;
}
var nics = stdout.trim().split('\n').map(function (line) {
var nics = stdout.trim().split('\n').map(function doParse(line) {
return JSON.parse(line);
});
t.ok(nics.length >= 1, 'triton nic list expected nic num');
var testNics = nics.filter(function (nic) {
var testNics = nics.filter(function doFilter(nic) {
return nic.mac === NIC.mac;
});
@ -150,11 +162,13 @@ test('triton instance nics', OPTS, function (tt) {
tt.test(' triton instance nic list mac=<...>', function (t) {
var cmd = 'instance nic list -j ' + INST + ' mac=' + NIC.mac;
h.triton(cmd, function (err, stdout, stderr) {
if (h.ifErr(t, err))
return t.end();
h.triton(cmd, function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err)) {
t.end();
return;
}
var nics = stdout.trim().split('\n').map(function (str) {
var nics = stdout.trim().split('\n').map(function doParse(str) {
return JSON.parse(str);
});
@ -169,11 +183,13 @@ test('triton instance nics', OPTS, function (tt) {
tt.test(' triton nic list mac=<...>', function (t) {
var cmd = 'instance nic list -j ' + INST + ' mac=' + NIC.mac;
h.triton(cmd, function (err, stdout, stderr) {
if (h.ifErr(t, err))
return t.end();
h.triton(cmd, function doTriton(err, stdout, stderr) {
if (h.ifErr(t, err)) {
t.end();
return;
}
var nics = stdout.trim().split('\n').map(function (str) {
var nics = stdout.trim().split('\n').map(function doParse(str) {
return JSON.parse(str);
});
@ -188,9 +204,11 @@ test('triton instance nics', OPTS, function (tt) {
tt.test(' triton instance nic delete', function (t) {
var cmd = 'instance nic delete --force ' + INST + ' ' + NIC.mac;
h.triton(cmd, function (err, stdout, stderr) {
if (h.ifErr(t, err, 'triton instance nic delete'))
return t.end();
h.triton(cmd, function doTriton(err, stdout, stderr) {
if (h.ifErr(t, err, 'triton instance nic delete')) {
t.end();
return;
}
t.ok(stdout.match('Deleted NIC ' + NIC.mac, 'deleted nic'));
@ -202,9 +220,11 @@ test('triton instance nics', OPTS, function (tt) {
var cmd = 'instance nic create -j -w ' + INST + ' ipv4_uuid=' +
NETWORK.id;
h.triton(cmd, function (err, stdout, stderr) {
if (h.ifErr(t, err, 'triton instance nic create'))
return t.end();
h.triton(cmd, function doTriton(err, stdout, stderr) {
if (h.ifErr(t, err, 'triton instance nic create')) {
t.end();
return;
}
NIC2 = JSON.parse(stdout);
@ -215,9 +235,11 @@ test('triton instance nics', OPTS, function (tt) {
tt.test(' triton instance nic with ip get', function (t) {
var cmd = 'instance nic get ' + INST + ' ' + NIC2.mac;
h.triton(cmd, function (err, stdout, stderr) {
if (h.ifErr(t, err, 'triton instance nic get'))
return t.end();
h.triton(cmd, function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err, 'triton instance nic get')) {
t.end();
return;
}
var obj = JSON.parse(stdout);
t.equal(obj.mac, NIC2.mac, 'nic MAC is correct');
@ -231,9 +253,11 @@ test('triton instance nics', OPTS, function (tt) {
tt.test(' triton instance nic with ip delete', function (t) {
var cmd = 'instance nic delete --force ' + INST + ' ' + NIC2.mac;
h.triton(cmd, function (err, stdout, stderr) {
if (h.ifErr(t, err, 'triton instance nic with ip delete'))
return t.end();
h.triton(cmd, function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err, 'triton instance nic with ip delete')) {
t.end();
return;
}
t.ok(stdout.match('Deleted NIC ' + NIC2.mac, 'deleted nic'));

View File

@ -5,7 +5,7 @@
*/
/*
* Copyright (c) 2015, Joyent, Inc.
* Copyright (c) 2018, Joyent, Inc.
*/
/*
@ -65,8 +65,18 @@ var subs = [
['ip'],
['ssh'],
['network'],
['network list', 'networks'],
['network create'],
['network list', 'network ls', 'networks'],
['network get'],
['network get-default'],
['network set-default'],
['network delete', 'network rm'],
['vlan'],
['vlan create'],
['vlan list', 'vlan ls'],
['vlan get'],
['vlan update'],
['vlan delete', 'vlan rm'],
['key'],
['key add'],
['key list', 'key ls', 'keys'],

View File

@ -0,0 +1,418 @@
/*
* 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 2018 Joyent, Inc.
*/
/*
* Integration tests for `triton vlans`
*/
var f = require('util').format;
var os = require('os');
var test = require('tape');
var h = require('./helpers');
var common = require('../../lib/common');
// --- Globals
var VLAN_NAME = f('nodetritontest-vlan-%s', os.hostname());
var VLAN_ID = 3197;
var VLAN;
var OPTS = {
skip: !h.CONFIG.allowWriteActions
};
// --- Tests
if (OPTS.skip) {
console.error('** skipping some %s tests', __filename);
console.error('** set "allowWriteActions" in test config to enable');
}
test('triton vlan list', function (tt) {
tt.test(' triton vlan list -h', function (t) {
h.triton('vlan list -h', function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err)) {
t.end();
return;
}
t.ok(/Usage:\s+triton vlan list/.test(stdout));
t.end();
});
});
tt.test(' triton vlan list', function (t) {
h.triton('vlan list', function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err)) {
t.end();
return;
}
t.ok(/^VLAN_ID\b/.test(stdout));
t.ok(/\bNAME\b/.test(stdout));
t.end();
});
});
tt.test(' triton vlan list -j', function (t) {
h.triton('vlan list -j', function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err)) {
t.end();
return;
}
VLAN = JSON.parse(stdout.trim().split('\n')[0]);
t.end();
});
});
tt.test(' triton vlan list vlan_id=<...>', function (t) {
var cmd = 'vlan list -j vlan_id=' + VLAN.vlan_id;
h.triton(cmd, function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err)) {
t.end();
return;
}
var vlans = stdout.trim().split('\n').map(function onParse(str) {
return JSON.parse(str);
});
t.deepEqual(vlans, [VLAN]);
t.end();
});
});
tt.test(' triton vlan list vlan_id=<...> name=<...> (good)', function (t) {
var cmd = 'vlan list -j vlan_id=' + VLAN.vlan_id + ' name=' + VLAN.name;
h.triton(cmd, function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err)) {
t.end();
return;
}
var vlans = stdout.trim().split('\n').map(function onParse(str) {
return JSON.parse(str);
});
t.deepEqual(vlans, [VLAN]);
t.end();
});
});
tt.test(' triton vlan list vlan_id=<...> name=<...> (bad)', function (t) {
// search for a mismatch, should return nada
var cmd = 'vlan list -j vlan_id=' + VLAN.vlan_id + ' name=' +
VLAN.name + 'a';
h.triton(cmd, function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err)) {
t.end();
return;
}
t.equal(stdout, '');
t.end();
});
});
});
test('triton vlan get', function (tt) {
tt.test(' triton vlan get -h', function (t) {
h.triton('vlan get -h', function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err)) {
t.end();
return;
}
t.ok(/Usage:\s+triton vlan\b/.test(stdout));
t.end();
});
});
tt.test(' triton vlan help get', function (t) {
h.triton('vlan help get', function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err)) {
t.end();
return;
}
t.ok(/Usage:\s+triton vlan get\b/.test(stdout));
t.end();
});
});
tt.test(' triton vlan get', function (t) {
h.triton('vlan get', function onTriton(err, stdout, stderr) {
t.ok(err);
t.ok(/error \(Usage\)/.test(stderr));
t.end();
});
});
tt.test(' triton vlan get ID', function (t) {
h.triton('vlan get ' + VLAN.vlan_id,
function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err)) {
t.end();
return;
}
var vlan = JSON.parse(stdout);
t.equal(vlan.vlan_id, VLAN.vlan_id);
t.end();
});
});
tt.test(' triton vlan get NAME', function (t) {
h.triton('vlan get ' + VLAN.name,
function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err)) {
t.end();
return;
}
var vlan = JSON.parse(stdout);
t.equal(vlan.vlan_id, VLAN.vlan_id);
t.end();
});
});
});
test('triton vlan networks', function (tt) {
tt.test(' triton vlan networks -h', function (t) {
h.triton('vlan networks -h', function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err)) {
t.end();
return;
}
t.ok(/Usage:\s+triton vlan networks/.test(stdout));
t.end();
});
});
tt.test(' triton vlan help networks', function (t) {
h.triton('vlan help networks', function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err)) {
t.end();
return;
}
t.ok(/Usage:\s+triton vlan networks/.test(stdout));
t.end();
});
});
tt.test(' triton vlan networks', function (t) {
h.triton('vlan networks', function onTriton(err, stdout, stderr) {
t.ok(err);
t.ok(/error \(Usage\)/.test(stderr));
t.end();
});
});
tt.test(' triton vlan networks ID', function (t) {
h.triton('vlan networks -j ' + VLAN.vlan_id,
function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err)) {
t.end();
return;
}
var vlan = JSON.parse(stdout);
t.equal(vlan.vlan_id, VLAN.vlan_id);
t.end();
});
});
tt.test(' triton vlan networks NAME', function (t) {
h.triton('vlan networks -j ' + VLAN.name,
function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err)) {
t.end();
return;
}
var vlan = JSON.parse(stdout);
t.equal(vlan.vlan_id, VLAN.vlan_id);
t.end();
});
});
});
test('triton vlan create', OPTS, function (tt) {
tt.test(' cleanup: rm vlan ' + VLAN_NAME + ' if exists', function (t) {
h.triton('vlan delete ' + VLAN_NAME, function onTriton(err, stdout) {
t.end();
});
});
tt.test(' triton vlan create -h', function (t) {
h.triton('vlan create -h', function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err)) {
t.end();
return;
}
t.ok(/Usage:\s+triton vlan\b/.test(stdout));
t.end();
});
});
tt.test(' triton vlan help create', function (t) {
h.triton('vlan help create', function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err)) {
t.end();
return;
}
t.ok(/Usage:\s+triton vlan create\b/.test(stdout));
t.end();
});
});
tt.test(' triton vlan create', function (t) {
h.triton('vlan create', function onTriton(err, stdout, stderr) {
t.ok(err);
t.ok(/error \(Usage\)/.test(stderr));
t.end();
});
});
tt.test(' triton vlan create VLAN', function (t) {
h.triton('vlan create -j --name=' + VLAN_NAME + ' ' + VLAN_ID,
function onTriton(err, stdout) {
if (h.ifErr(t, err)) {
t.end();
return;
}
var vlan = JSON.parse(stdout.trim().split('\n')[0]);
t.equal(vlan.name, VLAN_NAME);
t.equal(vlan.vlan_id, VLAN_ID);
h.triton('vlan delete ' + vlan.vlan_id, function onTriton2(err2) {
h.ifErr(t, err2);
t.end();
});
});
});
});
test('triton vlan delete', OPTS, function (tt) {
tt.test(' triton vlan delete -h', function (t) {
h.triton('vlan delete -h', function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err)) {
t.end();
return;
}
t.ok(/Usage:\s+triton vlan\b/.test(stdout));
t.end();
});
});
tt.test(' triton vlan help delete', function (t) {
h.triton('vlan help delete', function onTriton(err, stdout, stderr) {
if (h.ifErr(t, err)) {
t.end();
return;
}
t.ok(/Usage:\s+triton vlan delete\b/.test(stdout));
t.end();
});
});
tt.test(' triton vlan delete', function (t) {
h.triton('vlan delete', function onTriton(err, stdout, stderr) {
t.ok(err);
t.ok(/error \(Usage\)/.test(stderr));
t.end();
});
});
function deleteNetworkTester(t, deleter) {
h.triton('vlan create -j --name=' + VLAN_NAME + ' ' + VLAN_ID,
function onTriton(err, stdout) {
if (h.ifErr(t, err, 'create test vlan')) {
t.end();
return;
}
var vlan = JSON.parse(stdout.trim().split('\n')[0]);
deleter(null, vlan, function onDelete(err2) {
if (h.ifErr(t, err2, 'deleting test vlan')) {
t.end();
return;
}
h.triton('vlan get ' + vlan.vlan_id, function onTriton2(err3) {
t.ok(err3, 'vlan should be gone');
t.end();
});
});
});
}
tt.test(' triton vlan delete ID', function (t) {
deleteNetworkTester(t, function doDelete(err, vlan, cb) {
h.triton('vlan delete ' + vlan.vlan_id, cb);
});
});
tt.test(' triton vlan delete NAME', function (t) {
deleteNetworkTester(t, function doDelete(err, vlan, cb) {
h.triton('vlan delete ' + vlan.name, cb);
});
});
});

View File

@ -183,6 +183,7 @@ function getTestImg(t, cb) {
var candidateImageNames = {
'base-64-lts': true,
'base-64': true,
'minimal-64-lts': true,
'minimal-64': true,
'base-32-lts': true,
'base-32': true,