TRITON-58 node-triton should support nic operations

Reviewed by: Marsell Kukuljevic <marsell@joyent.com>
Approved by: Marsell Kukuljevic <marsell@joyent.com>
This commit is contained in:
Mike Zeller 2018-03-05 22:04:36 +00:00
parent 39635cd0a2
commit bf64899685
12 changed files with 1427 additions and 4 deletions

View File

@ -154,6 +154,12 @@ function CloudApi(options) {
this.client = new SaferJsonClient(options);
}
// <Network Object Key> -> <expected typeof>
CloudApi.prototype.NETWORK_OBJECT_FIELDS = {
ipv4_uuid: 'string',
ipv4_ips: 'string'
};
CloudApi.prototype.close = function close(callback) {
this.log.trace({host: this.client.url && this.client.url.host},
@ -1150,7 +1156,7 @@ CloudApi.prototype.createMachine = function createMachine(options, callback) {
assert.optionalString(options.name, 'options.name');
assert.uuid(options.image, 'options.image');
assert.uuid(options.package, 'options.package');
assert.optionalArrayOfUuid(options.networks, 'options.networks');
assert.optionalArray(options.networks, 'options.networks');
// TODO: assert the other fields
assert.func(callback, 'callback');
@ -1536,6 +1542,150 @@ function deleteMachineSnapshot(opts, cb) {
};
// --- NICs
/**
* Adds a NIC on a network to an instance.
*
* @param {Object} options object containing:
* - {String} id (required) the instance id.
* - {String|Object} (required) network uuid or network object.
* @param {Function} callback of the form f(err, nic, res).
*/
CloudApi.prototype.addNic =
function addNic(opts, cb) {
assert.object(opts, 'opts');
assert.uuid(opts.id, 'opts.id');
assert.ok(opts.network, 'opts.network');
var data = {
network: opts.network
};
this._request({
method: 'POST',
path: format('/%s/machines/%s/nics', this.account, opts.id),
data: data
}, function (err, req, res, body) {
cb(err, body, res);
});
};
/**
* Lists all NICs on an instance.
*
* Returns an array of objects.
*
* @param opts {Object} Options
* - {String} id (required) the instance id.
* @param {Function} callback of the form f(err, nics, res).
*/
CloudApi.prototype.listNics =
function listNics(opts, cb) {
assert.object(opts, 'opts');
assert.uuid(opts.id, 'opts.id');
assert.func(cb, 'cb');
var endpoint = format('/%s/machines/%s/nics', this.account, opts.id);
this._passThrough(endpoint, opts, cb);
};
/**
* Retrieves a NIC on an instance.
*
* @param {Object} options object containing:
* - {UUID} id: The instance id. Required.
* - {String} mac: The NIC's MAC. Required.
* @param {Function} callback of the form `function (err, nic, res)`
*/
CloudApi.prototype.getNic =
function getNic(opts, cb) {
assert.object(opts, 'opts');
assert.uuid(opts.id, 'opts.id');
assert.string(opts.mac, 'opts.mac');
assert.func(cb, 'cb');
var mac = opts.mac.replace(/:/g, '');
var endpoint = format('/%s/machines/%s/nics/%s', this.account, opts.id,
mac);
this._request(endpoint, function (err, req, res, body) {
cb(err, body, res);
});
};
/**
* Remove a NIC off an instance.
*
* @param {Object} opts (object)
* - {UUID} id: The instance id. Required.
* - {String} mac: The NIC's MAC. Required.
* @param {Function} cb of the form `function (err, res)`
*/
CloudApi.prototype.removeNic =
function removeNic(opts, cb) {
assert.object(opts, 'opts');
assert.uuid(opts.id, 'opts.id');
assert.string(opts.mac, 'opts.mac');
assert.func(cb, 'cb');
var mac = opts.mac.replace(/:/g, '');
this._request({
method: 'DELETE',
path: format('/%s/machines/%s/nics/%s', this.account, opts.id, mac)
}, function (err, req, res) {
cb(err, res);
});
};
/**
* Wait for a machine's nic to go one of a set of specfic states.
*
* @param {Object} options
* - {String} id {required} machine id
* - {String} mac {required} mac for new nic
* - {Array of String} states - desired state
* - {Number} interval (optional) - time in ms to poll
* @param {Function} callback of the form f(err, nic, res).
*/
CloudApi.prototype.waitForNicStates =
function waitForNicStates(opts, cb) {
var self = this;
assert.object(opts, 'opts');
assert.uuid(opts.id, 'opts.id');
assert.string(opts.mac, 'opts.mac');
assert.arrayOfString(opts.states, 'opts.states');
assert.optionalNumber(opts.interval, 'opts.interval');
assert.func(cb, 'cb');
var interval = opts.interval || 1000;
assert.ok(interval > 0, 'interval must be a positive number');
poll();
function poll() {
self.getNic({
id: opts.id,
mac: opts.mac
}, function onPoll(err, nic, res) {
if (err) {
cb(err, null, res);
return;
}
if (opts.states.indexOf(nic.state) !== -1) {
cb(null, nic, res);
return;
}
setTimeout(poll, interval);
});
}
};
// --- firewall rules
/**

View File

@ -5,7 +5,7 @@
*/
/*
* Copyright (c) 2017, Joyent, Inc.
* Copyright (c) 2018, Joyent, Inc.
*/
var assert = require('assert-plus');
@ -24,7 +24,8 @@ var wordwrap = require('wordwrap');
var errors = require('./errors'),
InternalError = errors.InternalError;
var NETWORK_OBJECT_FIELDS =
require('./cloudapi2').CloudApi.prototype.NETWORK_OBJECT_FIELDS;
// ---- support stuff
@ -1412,6 +1413,55 @@ function ipv4ToLong(ip) {
return l;
}
/*
* Parse the input from the `--nics <nic>` CLI argument.
*
* @param a {Array} The array of strings formatted as key=value
* ex: ['ipv4_uuid=1234', 'ipv4_ips=1.2.3.4|5.6.7.8']
* @return {Object} A network object. From the example above:
* {
* "ipv4_uuid": 1234,
* "ipv4_ips": [
* "1.2.3.4",
* "5.6.7.8"
* ]
* }
* Note: "1234" is used as the UUID for this example, but would actually cause
* `parseNicsCLI` to throw as it is not a valid UUID.
*/
function parseNicsCLI(nic) {
assert.arrayOfString(nic);
var obj = objFromKeyValueArgs(nic, {
disableDotted: true,
typeHintFromKey: NETWORK_OBJECT_FIELDS,
validKeys: Object.keys(NETWORK_OBJECT_FIELDS)
});
if (!obj.ipv4_uuid) {
throw new errors.UsageError(
'ipv4_uuid must be specified in network object');
}
if (obj.ipv4_ips) {
obj.ipv4_ips = obj.ipv4_ips.split('|');
}
assert.uuid(obj.ipv4_uuid, 'obj.ipv4_uuid');
assert.optionalArrayOfString(obj.ipv4_ips, 'obj.ipv4_ips');
/*
* Only 1 IP address may be specified at this time. In the future, this
* limitation should be removed.
*/
if (obj.ipv4_ips && obj.ipv4_ips.length !== 1) {
throw new errors.UsageError('only 1 ipv4_ip may be specified');
}
return obj;
}
//---- exports
module.exports = {
@ -1451,6 +1501,7 @@ module.exports = {
monotonicTimeDiffMs: monotonicTimeDiffMs,
readStdin: readStdin,
validateObject: validateObject,
ipv4ToLong: ipv4ToLong
ipv4ToLong: ipv4ToLong,
parseNicsCLI: parseNicsCLI
};
// vim: set softtabstop=4 shiftwidth=4:

View File

@ -0,0 +1,211 @@
/*
* 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 instance nic create ...`
*/
var assert = require('assert-plus');
var common = require('../../common');
var errors = require('../../errors');
function do_create(subcmd, opts, args, cb) {
assert.optionalBool(opts.wait, 'opts.wait');
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 < 2) {
cb(new errors.UsageError('missing INST and NETWORK or INST and' +
' NICOPT=VALUE arguments'));
return;
}
var cli = this.top;
var netObj;
var netObjArgs = [];
var regularArgs = [];
var createOpts = {};
args.forEach(function forEachArg(arg) {
if (arg.indexOf('=') !== -1) {
netObjArgs.push(arg);
return;
}
regularArgs.push(arg);
});
if (netObjArgs.length > 0) {
if (regularArgs.length > 1) {
cb(new errors.UsageError('cannot specify INST and NETWORK when'
+ ' passing in ipv4 arguments'));
return;
}
if (regularArgs.length !== 1) {
cb(new errors.UsageError('missing INST argument'));
return;
}
try {
netObj = common.parseNicsCLI(netObjArgs);
} catch (err) {
cb(err);
return;
}
}
if (netObj) {
assert.array(regularArgs, 'regularArgs');
assert.equal(regularArgs.length, 1, 'instance uuid');
createOpts.id = regularArgs[0];
createOpts.network = netObj;
} else {
assert.array(args, 'args');
assert.equal(args.length, 2, 'INST and NETWORK');
createOpts.id = args[0];
createOpts.network = args[1];
}
function wait(instId, mac, next) {
assert.string(instId, 'instId');
assert.string(mac, 'mac');
assert.func(next, 'next');
var waiter = cli.tritonapi.waitForNicStates.bind(cli.tritonapi);
/*
* We request state running|stopped because net-agent is doing work to
* keep a NICs state in sync with the VMs state. If a user adds a NIC
* to a stopped instance the final state of the NIC should also be
* stopped.
*/
waiter({
id: instId,
mac: mac,
states: ['running', 'stopped']
}, next);
}
// same signature as wait(), but is a nop
function waitNop(instId, mac, next) {
assert.string(instId, 'instId');
assert.string(mac, 'mac');
assert.func(next, 'next');
next();
}
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
cli.tritonapi.addNic(createOpts, function onAddNic(err, nic) {
if (err) {
cb(err);
return;
}
// If a NIC exists on the network already we will receive a 302
if (!nic) {
var errMsg = 'Instance already has a NIC on that network';
cb(new errors.TritonError(errMsg));
return;
}
// either wait or invoke a nop stub
var func = opts.wait ? wait : waitNop;
if (opts.wait && !opts.json) {
console.log('Creating NIC %s', nic.mac);
}
func(createOpts.id, nic.mac, function onWait(err2, createdNic) {
if (err2) {
cb(err2);
return;
}
var nicInfo = createdNic || nic;
if (opts.json) {
console.log(JSON.stringify(nicInfo));
} else {
console.log('Created NIC %s', nic.mac);
}
cb();
});
});
});
}
do_create.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON stream output.'
},
{
names: ['wait', 'w'],
type: 'bool',
help: 'Wait for the creation to complete.'
}
];
do_create.synopses = [
'{{name}} {{cmd}} [OPTIONS] INST NETWORK',
'{{name}} {{cmd}} [OPTIONS] INST NICOPT=VALUE [NICOPT=VALUE ...]'
];
do_create.help = [
'Create a NIC.',
'',
'{{usage}}',
'',
'{{options}}',
'INST is an instance id (full UUID), name, or short id,',
'and NETWORK is a network id (full UUID), name, or short id.',
'',
'NICOPTs are NIC options. The following NIC options are supported:',
'ipv4_uuid=<full network uuid> (required),' +
' and ipv4_ips=<a single IP string>.',
'',
'Be aware that adding NICs to an instance will cause that instance to',
'reboot.',
'',
'Example:',
' triton instance nic create --wait 22b75576 ca8aefb9',
' triton instance nic create 22b75576' +
' ipv4_uuid=651446a8-dab0-439e-a2c4-2c841ab07c51' +
' ipv4_ips=192.168.128.13'
].join('\n');
do_create.helpOpts = {
helpCol: 25
};
do_create.completionArgtypes = ['tritoninstance', 'tritonnic', 'none'];
module.exports = do_create;

View File

@ -0,0 +1,126 @@
/*
* 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 instance nic delete ...`
*/
var assert = require('assert-plus');
var vasync = require('vasync');
var common = require('../../common');
var errors = require('../../errors');
function do_delete(subcmd, opts, args, cb) {
assert.object(opts, 'opts');
assert.optionalBool(opts.force, 'opts.force');
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length < 2) {
cb(new errors.UsageError('missing INST and MAC argument(s)'));
return;
} else if (args.length > 2) {
cb(new errors.UsageError('incorrect number of arguments'));
return;
}
var inst = args[0];
var mac = args[1];
var cli = this.top;
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
confirm({mac: mac, force: opts.force}, function onConfirm(confirmErr) {
if (confirmErr) {
console.error('Aborting');
cb();
return;
}
cli.tritonapi.removeNic({
id: inst,
mac: mac
}, function onRemove(err) {
if (err) {
cb(err);
return;
}
console.log('Deleted NIC %s', mac);
cb();
});
});
});
}
// Request confirmation before deleting, unless --force flag given.
// If user declines, terminate early.
function confirm(opts, cb) {
assert.object(opts, 'opts');
assert.func(cb, 'cb');
if (opts.force) {
cb();
return;
}
common.promptYesNo({
msg: 'Delete NIC "' + opts.mac + '"? [y/n] '
}, function (answer) {
if (answer !== 'y') {
cb(new Error('Aborted NIC deletion'));
} else {
cb();
}
});
}
do_delete.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['force', 'f'],
type: 'bool',
help: 'Force removal.'
}
];
do_delete.synopses = ['{{name}} {{cmd}} INST MAC'];
do_delete.help = [
'Remove a NIC from an instance.',
'',
'{{usage}}',
'',
'{{options}}',
'Where INST is an instance id (full UUID), name, or short id.',
'',
'Be aware that removing NICs from an instance will cause that instance to',
'reboot.'
].join('\n');
do_delete.aliases = ['rm'];
do_delete.completionArgtypes = ['tritoninstance', 'none'];
module.exports = do_delete;

View File

@ -0,0 +1,89 @@
/*
* 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 instance nic 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 < 2) {
cb(new errors.UsageError('missing INST and MAC arguments'));
return;
} else if (args.length > 2) {
cb(new errors.UsageError('incorrect number of arguments'));
return;
}
var inst = args[0];
var mac = args[1];
var cli = this.top;
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
cli.tritonapi.getNic({id: inst, mac: mac}, function onNic(err, nic) {
if (err) {
cb(err);
return;
}
if (opts.json) {
console.log(JSON.stringify(nic));
} else {
console.log(JSON.stringify(nic, 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}} INST MAC'];
do_get.help = [
'Show a specific NIC.',
'',
'{{usage}}',
'',
'{{options}}',
'Where INST is an instance id (full UUID), name, or short id.'
].join('\n');
do_get.completionArgtypes = ['tritoninstance', 'none'];
module.exports = do_get;

View File

@ -0,0 +1,154 @@
/*
* 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 instance nic list ...`
*/
var assert = require('assert-plus');
var tabula = require('tabula');
var common = require('../../common');
var errors = require('../../errors');
var VALID_FILTERS = ['ip', 'mac', 'state', 'network', 'primary', 'gateway'];
var COLUMNS_DEFAULT = 'ip,mac,state,network';
var COLUMNS_DEFAULT_LONG = 'ip,mac,state,network,primary,gateway';
var SORT_DEFAULT = 'ip';
function do_list(subcmd, opts, args, cb) {
assert.array(args, 'args');
assert.func(cb, 'cb');
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (args.length < 1) {
cb(new errors.UsageError('missing INST argument'));
return;
}
var inst = args.shift();
try {
var filters = common.objFromKeyValueArgs(args, {
validKeys: VALID_FILTERS,
disableDotted: true
});
} catch (e) {
cb(e);
return;
}
var cli = this.top;
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
if (setupErr) {
cb(setupErr);
return;
}
cli.tritonapi.listNics({id: inst}, function onNics(err, nics) {
if (err) {
cb(err);
return;
}
// do filtering
Object.keys(filters).forEach(function filterByKey(key) {
var val = filters[key];
nics = nics.filter(function filterByNic(nic) {
return nic[key] === val;
});
});
if (opts.json) {
common.jsonStream(nics);
} else {
nics.forEach(function onNic(nic) {
nic.network = nic.network.split('-')[0];
nic.ip = nic.ip + '/' + convertCidrSuffix(nic.netmask);
});
var columns = COLUMNS_DEFAULT;
if (opts.o) {
columns = opts.o;
} else if (opts.long) {
columns = COLUMNS_DEFAULT_LONG;
}
columns = columns.split(',');
var sort = opts.s.split(',');
tabula(nics, {
skipHeader: opts.H,
columns: columns,
sort: sort
});
}
cb();
});
});
}
function convertCidrSuffix(netmask) {
var bitmask = netmask.split('.').map(function (octet) {
return (+octet).toString(2);
}).join('');
var i = 0;
for (i = 0; i < bitmask.length; i++) {
if (bitmask[i] === '0')
break;
}
return i;
}
do_list.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
}
].concat(common.getCliTableOptions({
includeLong: true,
sortDefault: SORT_DEFAULT
}));
do_list.synopses = ['{{name}} {{cmd}} [OPTIONS] [FILTERS]'];
do_list.help = [
'Show all NICs on an instance.',
'',
'{{usage}}',
'',
'{{options}}',
'',
'Where INST is an instance id (full UUID), name, or short id.',
'',
'Filters:',
' FIELD=<string> String filter. Supported fields: ip, mac, state,',
' network, netmask',
'',
'Filters are applied client-side (i.e. done by the triton command itself).'
].join('\n');
do_list.completionArgtypes = ['tritoninstance', 'none'];
do_list.aliases = ['ls'];
module.exports = do_list;

View File

@ -0,0 +1,50 @@
/*
* 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 inst nic ...`
*/
var Cmdln = require('cmdln').Cmdln;
var util = require('util');
// ---- CLI class
function NicCLI(top) {
this.top = top.top;
Cmdln.call(this, {
name: top.name + ' nic',
desc: 'List and manage instance NICs.',
helpSubcmds: [
'help',
'list',
'get',
'create',
'delete'
],
helpOpts: {
minHelpCol: 23
}
});
}
util.inherits(NicCLI, Cmdln);
NicCLI.prototype.init = function init(opts, args, cb) {
this.log = this.top.log;
Cmdln.prototype.init.apply(this, arguments);
};
NicCLI.prototype.do_list = require('./do_list');
NicCLI.prototype.do_create = require('./do_create');
NicCLI.prototype.do_get = require('./do_get');
NicCLI.prototype.do_delete = require('./do_delete');
module.exports = NicCLI;

View File

@ -49,6 +49,7 @@ function InstanceCLI(top) {
'ip',
'wait',
'audit',
'nic',
'snapshot',
'tag'
]
@ -81,6 +82,7 @@ InstanceCLI.prototype.do_ssh = require('./do_ssh');
InstanceCLI.prototype.do_ip = require('./do_ip');
InstanceCLI.prototype.do_wait = require('./do_wait');
InstanceCLI.prototype.do_audit = require('./do_audit');
InstanceCLI.prototype.do_nic = require('./do_nic');
InstanceCLI.prototype.do_snapshot = require('./do_snapshot');
InstanceCLI.prototype.do_snapshots = require('./do_snapshots');
InstanceCLI.prototype.do_tag = require('./do_tag');

View File

@ -2029,6 +2029,230 @@ function deleteAllInstanceTags(opts, cb) {
};
// ---- nics
/**
* Add a NIC on a network to an instance.
*
* @param {Object} opts
* - {String} id: The instance ID, name, or short ID. Required.
* - {Object|String} network: The network object or ID, name, or short ID.
* Required.
* @param {Function} callback `function (err, nic, res)`
*/
TritonApi.prototype.addNic =
function addNic(opts, cb) {
assert.string(opts.id, 'opts.id');
assert.ok(opts.network, 'opts.network');
assert.func(cb, 'cb');
var self = this;
var pipeline = [];
var res;
var nic;
switch (typeof (opts.network)) {
case 'string':
pipeline.push(_stepNetId);
break;
case 'object':
break;
default:
throw new Error('unexpected opts.network: ' + opts.network);
}
pipeline.push(_stepInstId);
pipeline.push(function createNic(arg, next) {
self.cloudapi.addNic({
id: arg.instId,
network: arg.netId || arg.network
}, function onCreateNic(err, _nic, _res) {
res = _res;
res.instId = arg.instId; // gross hack, in case caller needs it
res.netId = arg.netId; // ditto
nic = _nic;
next(err);
});
});
var pipelineArg = {
client: self,
id: opts.id,
network: opts.network
};
vasync.pipeline({
arg: pipelineArg,
funcs: pipeline
}, function (err) {
cb(err, nic, res);
});
};
/**
* List an instance's NICs.
*
* @param {Object} opts
* - {String} id: The instance ID, name, or short ID. Required.
* @param {Function} callback `function (err, nics, res)`
*/
TritonApi.prototype.listNics =
function listNics(opts, cb) {
assert.string(opts.id, 'opts.id');
assert.func(cb, 'cb');
var self = this;
var res;
var nics;
vasync.pipeline({arg: {client: self, id: opts.id}, funcs: [
_stepInstId,
function list(arg, next) {
self.cloudapi.listNics({
id: arg.instId
}, function (err, _nics, _res) {
res = _res;
res.instId = arg.instId; // gross hack, in case caller needs it
nics = _nics;
next(err);
});
}
]}, function (err) {
cb(err, nics, res);
});
};
/**
* Get a NIC belonging to an instance.
*
* @param {Object} opts
* - {String} id: The instance ID, name, or short ID. Required.
* - {String} mac: The NIC's MAC address. Required.
* @param {Function} callback `function (err, nic, res)`
*/
TritonApi.prototype.getNic =
function getNic(opts, cb) {
assert.string(opts.id, 'opts.id');
assert.string(opts.mac, 'opts.mac');
assert.func(cb, 'cb');
var self = this;
var res;
var nic;
vasync.pipeline({arg: {client: self, id: opts.id, mac: opts.mac}, funcs: [
_stepInstId,
function get(arg, next) {
self.cloudapi.getNic({
id: arg.instId,
mac: arg.mac
}, function (err, _nic, _res) {
res = _res;
res.instId = arg.instId; // gross hack, in case caller needs it
nic = _nic;
next(err);
});
}
]}, function (err) {
cb(err, nic, res);
});
};
/**
* Remove a NIC from an instance.
*
* @param {Object} opts
* - {String} id: The instance ID, name, or short ID. Required.
* - {String} mac: The NIC's MAC address. Required.
* @param {Function} callback `function (err, res)`
*
*/
TritonApi.prototype.removeNic =
function removeNic(opts, cb) {
assert.string(opts.id, 'opts.id');
assert.string(opts.mac, 'opts.mac');
assert.func(cb, 'cb');
var self = this;
var res;
vasync.pipeline({arg: {client: self, id: opts.id, mac: opts.mac}, funcs: [
_stepInstId,
function deleteNic(arg, next) {
self.cloudapi.removeNic({
id: arg.instId,
mac: arg.mac
}, function (err, _res) {
res = _res;
res.instId = arg.instId; // gross hack, in case caller needs it
next(err);
});
}
]}, function (err) {
cb(err, res);
});
};
/**
* Wrapper for cloudapi2's waitForNicStates that will first translate
* opts.id into the proper uuid from shortid/name.
*
* @param {Object} options
* - {String} id {required} machine id
* - {String} mac {required} mac for new nic
* - {Array of String} states - desired state
* @param {Function} callback of the form f(err, nic, res).
*/
TritonApi.prototype.waitForNicStates = function waitForNicStates(opts, cb) {
assert.object(opts, 'opts');
assert.string(opts.id, 'opts.id');
assert.string(opts.mac, 'opts.mac');
assert.arrayOfString(opts.states, 'opts.states');
var self = this;
var nic, res;
function waitForNic(arg, next) {
var _opts = {
id: arg.instId,
mac: arg.mac,
states: arg.states
};
self.cloudapi.waitForNicStates(_opts,
function onWaitForNicState(err, _nic, _res) {
res = _res;
nic = _nic;
next(err);
});
}
var pipelineArgs = {
client: self,
id: opts.id,
mac: opts.mac,
states: opts.states
};
vasync.pipeline({
arg: pipelineArgs,
funcs: [
_stepInstId,
waitForNic
]
}, function onWaitForNicPipeline(err) {
cb(err, nic, res);
});
};
// ---- Firewall Rules
/**

View File

@ -0,0 +1,110 @@
/*
* 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 NIC-related APIs as a module.
*/
var h = require('./helpers');
var test = require('tape');
// --- Globals
var CLIENT;
var INST;
var NIC;
// --- Tests
test('TritonApi networks', function (tt) {
tt.test(' setup', function (t) {
h.createClient(function (err, client_) {
t.error(err);
CLIENT = client_;
t.end();
});
});
tt.test(' setup: inst', function (t) {
CLIENT.cloudapi.listMachines({}, function (err, vms) {
if (vms.length === 0)
return t.end();
t.ok(Array.isArray(vms), 'vms array');
INST = vms[0];
t.end();
});
});
tt.test(' TritonApi listNics', function (t) {
if (!INST)
return t.end();
function check(val, valName, next) {
CLIENT.listNics({id: val}, function (err, nics) {
if (h.ifErr(t, err, 'no err ' + valName))
return t.end();
t.ok(Array.isArray(nics), 'nics array');
NIC = nics[0];
next();
});
}
var shortId = INST.id.split('-')[0];
check(INST.id, 'id', function () {
check(INST.name, 'name', function () {
check(shortId, 'shortId', function () {
t.end();
});
});
});
});
tt.test(' TritonApi getNic', function (t) {
if (!NIC)
return t.end();
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();
t.deepEqual(nic, NIC, instValName);
next();
});
}
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 () {
t.end();
});
});
});
});
tt.test(' teardown: client', function (t) {
CLIENT.close();
t.end();
});
});

View File

@ -0,0 +1,252 @@
/*
* 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 (c) 2018, Joyent, Inc.
*/
/*
* Integration tests for `triton instance nics ...`
*/
var h = require('./helpers');
var f = require('util').format;
var os = require('os');
var test = require('tape');
// --- Globals
var INST_ALIAS = f('nodetritontest-nics-%s', os.hostname());
var NETWORK;
var INST;
var NIC;
var NIC2;
var OPTS = {
skip: !h.CONFIG.allowWriteActions
};
// --- Tests
if (OPTS.skip) {
console.error('** skipping %s tests', __filename);
console.error('** set "allowWriteActions" in test config to enable');
}
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) {
t.ifErr(err);
t.end();
});
});
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();
INST = instId;
t.end();
});
});
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();
NETWORK = JSON.parse(stdout.trim().split('\n')[0]);
t.ok(NETWORK, 'NETWORK');
t.end();
});
});
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();
NIC = JSON.parse(stdout);
t.end();
});
});
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();
var obj = JSON.parse(stdout);
t.equal(obj.mac, NIC.mac, 'nic MAC is correct');
t.equal(obj.ip, NIC.ip, 'nic IP is correct');
t.equal(obj.network, NIC.network, 'nic network is correct');
t.end();
});
});
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();
var nics = stdout.trim().split('\n');
t.ok(nics[0].match(/IP\s+MAC\s+STATE\s+NETWORK/), 'nic list' +
' header correct');
nics.shift();
t.ok(nics.length >= 1, 'triton nic list expected nic num');
var testNics = nics.filter(function (nic) {
return nic.match(NIC.mac);
});
t.equal(testNics.length, 1, 'triton nic list test nic found');
t.end();
});
});
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();
var nics = stdout.trim().split('\n').map(function (line) {
return JSON.parse(line);
});
t.ok(nics.length >= 1, 'triton nic list expected nic num');
var testNics = nics.filter(function (nic) {
return nic.mac === NIC.mac;
});
t.equal(testNics.length, 1, 'triton nic list test nic found');
t.end();
});
});
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();
var nics = stdout.trim().split('\n').map(function (str) {
return JSON.parse(str);
});
t.equal(nics.length, 1);
t.equal(nics[0].ip, NIC.ip);
t.equal(nics[0].network, NIC.network);
t.end();
});
});
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();
var nics = stdout.trim().split('\n').map(function (str) {
return JSON.parse(str);
});
t.equal(nics.length, 1);
t.equal(nics[0].ip, NIC.ip);
t.equal(nics[0].network, NIC.network);
t.end();
});
});
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();
t.ok(stdout.match('Deleted NIC ' + NIC.mac, 'deleted nic'));
t.end();
});
});
tt.test(' triton instance nic create (with NICOPTS)', function (t) {
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();
NIC2 = JSON.parse(stdout);
t.end();
});
});
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();
var obj = JSON.parse(stdout);
t.equal(obj.mac, NIC2.mac, 'nic MAC is correct');
t.equal(obj.ip, NIC2.ip, 'nic IP is correct');
t.equal(obj.network, NIC2.network, 'nic network is correct');
t.end();
});
});
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();
t.ok(stdout.match('Deleted NIC ' + NIC2.mac, 'deleted nic'));
t.end();
});
});
/*
* Use a timeout, because '-w' on delete doesn't have a way to know if the
* attempt failed or if it is just taking a really long time.
*/
tt.test(' cleanup: triton instance rm INST', {timeout: 10 * 60 * 1000},
function (t) {
h.deleteTestInst(t, INST_ALIAS, function () {
t.end();
});
});
});

View File

@ -56,6 +56,10 @@ var subs = [
['instance snapshot list', 'instance snapshot ls', 'instance snapshots'],
['instance snapshot get'],
['instance snapshot delete', 'instance snapshot rm'],
['instance nic create'],
['instance nic list', 'instance nic ls'],
['instance nic get'],
['instance nic delete', 'instance nic rm'],
['ip'],
['ssh'],
['network'],