From 4b20a7c843836ee7dd6521049a6dbf7fb471d253 Mon Sep 17 00:00:00 2001 From: Marsell Kukuljevic Date: Thu, 4 Sep 2014 01:23:15 +1000 Subject: [PATCH] PUBAPI-988 - add support for list/add/remove of nics on a VM. --- bin/sdc-nics | 152 +++++++++++++++++++++++++++++++++++++ lib/cloudapi.js | 189 +++++++++++++++++++++++++++++++++++++++++++++++ test/api.test.js | 94 ++++++++++++++++++++++- 3 files changed, 434 insertions(+), 1 deletion(-) create mode 100755 bin/sdc-nics diff --git a/bin/sdc-nics b/bin/sdc-nics new file mode 100755 index 0000000..6dee1bf --- /dev/null +++ b/bin/sdc-nics @@ -0,0 +1,152 @@ +#!/usr/bin/env node +// -*- mode: js -*- +// vim: set filetype=javascript : +// Copyright 2014 Joyent, Inc. All rights reserved. + +var util = require('util'); +var shared = require('../lib/shared'), + commonCb = shared.commonCb; +var cmdln = require('cmdln'), + Cmdln = cmdln.Cmdln; + + +var generalOptions = [ + { + names: ['help', 'h', '?'], + type: 'bool', + help: 'Show this help.' + } +]; + + + +function SDCNic() { + Cmdln.call(this, { + name: 'sdc-nics', + desc: 'SmartDC machine NIC operations', + options: shared.DEFAULT_OPTIONS, + helpOpts: { + includeEnv: true + } + }); +} +util.inherits(SDCNic, Cmdln); + + + +SDCNic.prototype.init = function (opts, args, callback) { + shared.checkRequiredOptions.apply(this, arguments); + Cmdln.prototype.init.apply(this, arguments); + return false; +}; + + + +SDCNic.prototype.do_list = function (subcmd, opts, args, callback) { + if (opts.help) { + return this.do_help('help', {}, [subcmd], callback); + } + + if (args.length !== 1) { + return callback(new Error('machine_id must be specified')); + } + + return this.cloudapi.listNics(args[0], commonCb); +}; + +SDCNic.prototype.do_list.options = generalOptions; +SDCNic.prototype.do_list.help = ( + 'List your machine\'s NICs.\n' + + '\n' + + 'Usage:\n' + + ' {{name}} list [OPTIONS] machine_id\n' + + '\n' + + '{{options}}' +); + + + +SDCNic.prototype.do_get = function (subcmd, opts, args, callback) { + if (opts.help) { + return this.do_help('help', {}, [subcmd], callback); + } + + if (args.length !== 2) { + return callback(new Error('nic_mac and machine_id must be specified')); + } + + return this.cloudapi.getNic(args[1], args[0], commonCb); + +}; + +SDCNic.prototype.do_get.options = generalOptions; +SDCNic.prototype.do_get.help = ( + 'Get a machine\'s NIC by MAC.\n' + + '\n' + + 'Usage:\n' + + ' {{name}} get [OPTIONS] nic_mac machine_id\n' + + '\n' + + '{{options}}' +); + + + +SDCNic.prototype.do_create = function (subcmd, opts, args, callback) { + var self = this; + if (opts.help) { + return this.do_help('help', {}, [subcmd], callback); + } + + if (args.length !== 2) { + var msg = 'network_id and machine_id must be specified'; + return callback(new Error(msg)); + } + + var params = { + network: args[0], + machine: args[1] + }; + + return self.cloudapi.createNic(params, commonCb); +}; + +SDCNic.prototype.do_create.options = generalOptions; +SDCNic.prototype.do_create.help = ( + 'Create a new NIC on one of your machines, and reboot the machine.\n' + + '\n' + + 'Usage:\n' + + ' {{name}} create [OPTIONS] network_id machine_id\n' + + '\n' + + '{{options}}' +); + + + +SDCNic.prototype.do_delete = function (subcmd, opts, args, callback) { + if (opts.help) { + return this.do_help('help', {}, [subcmd], callback); + } + + if (args.length !== 2) { + return callback(new Error('nic_mac and machine_id must be specified')); + } + + return this.cloudapi.deleteNic(args[1], args[0], commonCb); + +}; + +SDCNic.prototype.do_delete.options = generalOptions; +SDCNic.prototype.do_delete.help = ( + 'Removes a NIC from a machine, and reboots machine.\n' + + '\n' + + 'Usage:\n' + + ' {{name}} delete [OPTIONS] nic_mac machine_id\n' + + '\n' + + '{{options}}' +); + + + +if (require.main === module) { + cmdln.main(SDCNic); +} diff --git a/lib/cloudapi.js b/lib/cloudapi.js index b63d61e..09ac36d 100644 --- a/lib/cloudapi.js +++ b/lib/cloudapi.js @@ -46,6 +46,8 @@ var DATACENTERS = ROOT + '/datacenters'; var MACHINES = ROOT + '/machines'; var MACHINE = MACHINES + '/%s'; var METADATA = MACHINE + '/metadata'; +var NICS = MACHINE + '/nics'; +var NIC = NICS + '/%s'; var METADATA_KEY = MACHINE + '/metadata/%s'; var SNAPSHOTS = MACHINE + '/snapshots'; var SNAPSHOT = SNAPSHOTS + '/%s'; @@ -4071,6 +4073,193 @@ CloudAPI.prototype.setRoleTags = setRoleTags; CloudAPI.prototype.SetRoleTags = setRoleTags; +// --- NIC-related functions + +/** + * Retrieves a NIC on one of an account's machines. + * + * Returns an object. + * + * @param {String} account (optional) the login name of the account. + * @param {String} machine is the UUID of the machine. + * @param {String} mac is the MAC address of the NIC. + * @param {Function} callback of the form f(err, nic). + * @param {Boolean} noCache optional flag to force skipping the cache. + * @throws {TypeError} on bad input. + */ +CloudAPI.prototype.getNic = +function getNic(account, machine, mac, callback, noCache) { + var self = this; + + if (typeof (mac) === 'function') { + callback = mac; + mac = machine; + machine = account; + account = this.account; + } + + if (typeof (callback) !== 'function') { + throw new TypeError('callback (function) required'); + } + + if (typeof (account) === 'object') { + account = account.login; + } + + var path = sprintf(NIC, account, machine, mac.replace(/:/g, '')); + + return self._request(path, null, function (req) { + return self._get(req, callback, noCache); + }); + +}; +CloudAPI.prototype.GetNic = CloudAPI.prototype.getNic; + + +/** + * Creates a NIC on a machine. Note that this reboots the machine as part of + * the process. + * + * Returns a JS object (the created nic). + * + * @param {String} account (optional) the login name of the account. + * @param {Object} options object containing: + * - {String} network UUID of network to attach NIC to. + * - {Object} machine to add NIC to. + * @param {Function} callback of the form f(err, nic). + * @throws {TypeError} on bad input. + */ +CloudAPI.prototype.createNic = +function createNic(account, options, callback) { + var self = this; + + if (typeof (options) === 'function') { + callback = options; + options = account; + account = this.account; + } + + if (typeof (callback) !== 'function') { + throw new TypeError('callback (function) required'); + } + + if (typeof (options) !== 'object') { + throw new TypeError('options (object) required'); + } + + if (typeof (account) === 'object') { + account = account.login; + } + + var machine = options.machine; + var network = options.network; + + if (typeof (network) !== 'string') { + throw new TypeError('network (string) required in options'); + } + + if (typeof (machine) !== 'string') { + throw new TypeError('machine (string) required in options'); + } + + var path = sprintf(NICS, account, machine); + + return self._request(path, options, function (req) { + return self._post(req, callback); + }); +}; +CloudAPI.prototype.CreateNic = CloudAPI.prototype.createNic; + + +/** + * Lists all NICs on a given machine. + * + * Returns an array of objects. + * + * @param {String} account (optional) the login name of the account. + * @param {String} machine the UUID of the machine. + * @param {Function} callback of the form f(err, macs). + * @param {Boolean} noCache optional flag to force skipping the cache. + * @throws {TypeError} on bad input. + */ +CloudAPI.prototype.listNics = +function listNics(account, machine, callback, noCache) { + var self = this; + + if (typeof (machine) === 'function') { + noCache = callback; + callback = machine; + machine = account; + account = this.account; + } + + if (typeof (callback) !== 'function') { + throw new TypeError('callback (function) required'); + } + + if (typeof (machine) !== 'string') { + throw new TypeError('machine (string) required'); + } + + if (typeof (account) === 'object') { + account = account.login; + } + + var path = sprintf(NICS, account, machine); + + self._request(path, null, function (req) { + return self._get(req, callback, noCache); + }); +}; +CloudAPI.prototype.ListNics = CloudAPI.prototype.listNics; + + +/** + * Removes a NIC from a machine. Note that this reboots the machine as part of + * the process. + * + * @param {String} account (optional) the login name of the account. + * @param {String} machine is the UUID of the machine. + * @param {String} mac is the MAC address of the NIC. + * @param {Function} callback of the form f(err). + * @throws {TypeError} on bad input. + */ +CloudAPI.prototype.deleteNic = +function deleteNic(account, machine, mac, callback) { + var self = this; + + if (typeof (mac) === 'function') { + callback = mac; + mac = machine; + machine = account; + account = this.account; + } + + if (typeof (callback) !== 'function') { + throw new TypeError('callback (function) required'); + } + + if (typeof (mac) !== 'string') { + throw new TypeError('mac (string) required'); + } + + if (typeof (machine) !== 'string') { + throw new TypeError('machine (string) required'); + } + + if (typeof (account) === 'object') { + account = account.login; + } + + var path = sprintf(NIC, account, machine, mac.replace(/:/g, '')); + + return self._request(path, null, function (req) { + return self._del(req, callback); + }); +}; +CloudAPI.prototype.DeleteNic = CloudAPI.prototype.deleteNic; + + // --- Private Functions diff --git a/test/api.test.js b/test/api.test.js index 0fece8a..f9349b1 100644 --- a/test/api.test.js +++ b/test/api.test.js @@ -10,7 +10,7 @@ var exec = require('child_process').exec; var smartdc = require('../lib'); var sdc; -var PACKAGE, DATASET, IMAGE, MACHINE; +var PACKAGE, DATASET, IMAGE, MACHINE, NETWORK, NIC; var TAG_KEY = 'smartdc_role'; var TAG_VAL = 'unitTest'; @@ -230,6 +230,36 @@ test('get images', function (t) { }); +test('list networks', function (t) { + sdc.listNetworks(function (err, networks) { + t.ifError(err); + t.ok(Array.isArray(networks)); + + NETWORK = networks[0]; + t.ok(NETWORK); + t.ok(NETWORK.id); + t.ok(NETWORK.name); + t.ok(typeof (NETWORK.public) === 'boolean'); + + t.end(); + }); +}); + + +test('get network', function (t) { + sdc.getNetwork(NETWORK.id, function (err, network) { + t.ifError(err); + + t.ok(network); + t.ok(network.id); + t.ok(network.name); + t.ok(typeof (network.public) === 'boolean'); + + t.end(); + }); +}); + + // Datacenters: test('list datacenters', function (t) { sdc.listDatacenters(function (err, datacenters) { @@ -555,6 +585,68 @@ test('delete machine tags', function (t) { +test('list machine nics', function (t) { + sdc.listNics(MACHINE.id, function (err, nics) { + t.ifError(err); + + t.ok(Array.isArray(nics)); + + NIC = nics[0]; + t.ok(NIC); + t.ok(NIC.mac); + t.ok(NIC.ip); + t.ok(NIC.netmask); + t.ok(NIC.gateway); + t.ok(NIC.state); + t.ok(typeof (NIC.primary) === 'boolean'); + + t.end(); + }); +}); + + +test('get machine nic', function (t) { + sdc.getNic(MACHINE.id, NIC.mac, function (err, nic) { + t.ifError(err); + + t.ok(typeof (nic) === 'object'); + t.ok(nic.mac); + t.ok(nic.ip); + t.ok(nic.netmask); + t.ok(nic.gateway); + t.ok(nic.state); + t.ok(typeof (nic.primary) === 'boolean'); + + t.end(); + }); +}); + + +test('remove machine nic', function (t) { + sdc.deleteNic(MACHINE.id, NIC.mac, function (err) { + t.ifError(err); + + waitForAction(MACHINE.id, 'remove_nics', NOW, function (err2) { + t.ifError(err2); + t.end(); + }); + }); +}); + + + +test('add machine nic', function (t) { + sdc.createNic({ machine: MACHINE.id, network: NETWORK.id }, function (err) { + t.ifError(err); + + waitForAction(MACHINE.id, 'add_nics', NOW, function (err2) { + t.ifError(err2); + t.end(); + }); + }); +}); + + // Note: Big chance for this test to be waiting for too long for a // simple rename operation. Or maybe not. test('rename machine', {