This commit is contained in:
Marius Pana 2018-02-23 15:28:53 +02:00
commit be74f307e0
15 changed files with 656 additions and 39 deletions

View File

@ -6,6 +6,18 @@ Known issues:
## not yet released ## not yet released
## 5.8.0
- [TRITON-124] add node-triton support for bhyve. This adds a `triton instance
create --brand=bhyve ...` option that can be used for zvol images that support
it. Note that bhyve support is alpha in TritonDC -- most datacenters won't yet
support this option.
## 5.7.0
- [TRITON-116] node-triton image sharing. Adds `triton image share` and
`triton image unshare` commands.
## 5.6.1 ## 5.6.1
- [PUBAPI-1470] volume objects should expose their creation timestamp in a - [PUBAPI-1470] volume objects should expose their creation timestamp in a

View File

@ -5,7 +5,7 @@
*/ */
/* /*
* Copyright 2017 Joyent, Inc. * Copyright (c) 2018, Joyent, Inc.
* *
* Client library for the SmartDataCenter Cloud API (cloudapi). * Client library for the SmartDataCenter Cloud API (cloudapi).
* http://apidocs.joyent.com/cloudapi/ * http://apidocs.joyent.com/cloudapi/
@ -767,6 +767,33 @@ CloudApi.prototype.exportImage = function exportImage(opts, cb) {
}); });
}; };
/**
* Update an image.
* <http://apidocs.joyent.com/cloudapi/#UpdateImage>
*
* @param {Object} opts
* - {UUID} id Required. The id of the image to update.
* - {Object} fields Required. The fields to update in the image.
* @param {Function} cb of the form `function (err, body, res)`
*/
CloudApi.prototype.updateImage = function shareImage(opts, cb) {
assert.uuid(opts.id, 'id');
assert.object(opts.fields, 'fields');
assert.func(cb, 'cb');
this._request({
method: 'POST',
path: format('/%s/images/%s?action=update', this.account, opts.id),
data: opts.fields
}, function (err, req, res, body) {
if (err) {
cb(err, null, res);
return;
}
cb(null, body, res);
});
};
/** /**
* Wait for an image to go one of a set of specfic states. * Wait for an image to go one of a set of specfic states.
* *

View File

@ -5,7 +5,7 @@
*/ */
/* /*
* Copyright 2016 Joyent, Inc. * Copyright 2018 Joyent, Inc.
* *
* `triton fwrule instances ...` * `triton fwrule instances ...`
*/ */
@ -111,6 +111,7 @@ function do_instances(subcmd, opts, args, cb) {
common.uuidToShortId(inst.image); common.uuidToShortId(inst.image);
inst.shortid = inst.id.split('-', 1)[0]; inst.shortid = inst.id.split('-', 1)[0];
var flags = []; var flags = [];
if (inst.brand === 'bhyve') flags.push('B');
if (inst.docker) flags.push('D'); if (inst.docker) flags.push('D');
if (inst.firewall_enabled) flags.push('F'); if (inst.firewall_enabled) flags.push('F');
if (inst.brand === 'kvm') flags.push('K'); if (inst.brand === 'kvm') flags.push('K');
@ -159,6 +160,7 @@ do_instances.help = [
'for convenience):', 'for convenience):',
' shortid* A short ID prefix.', ' shortid* A short ID prefix.',
' flags* Single letter flags summarizing some fields:', ' flags* Single letter flags summarizing some fields:',
' "B" the brand is "bhyve"',
' "D" docker instance', ' "D" docker instance',
' "F" firewall is enabled', ' "F" firewall is enabled',
' "K" the brand is "kvm"', ' "K" the brand is "kvm"',

115
lib/do_image/do_share.js Normal file
View File

@ -0,0 +1,115 @@
/*
* 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.
*
* `triton image share ...`
*/
var format = require('util').format;
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
// ---- the command
function do_share(subcmd, opts, args, cb) {
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
} else if (args.length !== 2) {
cb(new errors.UsageError(
'incorrect number of args: expect 2, got ' + args.length));
return;
}
var log = this.top.log;
var tritonapi = this.top.tritonapi;
vasync.pipeline({arg: {cli: this.top}, funcs: [
common.cliSetupTritonApi,
function shareImage(ctx, next) {
log.trace({dryRun: opts.dry_run, account: ctx.account},
'image share account');
if (opts.dry_run) {
next();
return;
}
tritonapi.shareImage({
image: args[0],
account: args[1]
}, function (err, img) {
if (err) {
next(new errors.TritonError(err, 'error sharing image'));
return;
}
log.trace({img: img}, 'image share result');
if (opts.json) {
console.log(JSON.stringify(img));
} else {
console.log('Shared image %s with account %s',
args[0], args[1]);
}
next();
});
}
]}, function (err) {
cb(err);
});
}
do_share.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
group: 'Other options'
},
{
names: ['dry-run'],
type: 'bool',
help: 'Go through the motions without actually sharing.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON stream output.'
}
];
do_share.synopses = [
'{{name}} {{cmd}} [OPTIONS] IMAGE ACCOUNT'
];
do_share.help = [
/* BEGIN JSSTYLED */
'Share an image with another account.',
'',
'{{usage}}',
'',
'{{options}}',
'Where "IMAGE" is an image id (a full UUID), an image name (selects the',
'latest, by "published_at", image with that name), an image "name@version"',
'(selects latest match by "published_at"), or an image short ID (ID prefix).',
'',
'Where "ACCOUNT" is the full account UUID.',
'',
'Note: Only images that are owned by the account can be shared.'
/* END JSSTYLED */
].join('\n');
do_share.completionArgtypes = ['tritonimage', 'none'];
module.exports = do_share;

115
lib/do_image/do_unshare.js Normal file
View File

@ -0,0 +1,115 @@
/*
* 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.
*
* `triton image unshare ...`
*/
var format = require('util').format;
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
// ---- the command
function do_unshare(subcmd, opts, args, cb) {
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
} else if (args.length !== 2) {
cb(new errors.UsageError(
'incorrect number of args: expect 2, got ' + args.length));
return;
}
var log = this.top.log;
var tritonapi = this.top.tritonapi;
vasync.pipeline({arg: {cli: this.top}, funcs: [
common.cliSetupTritonApi,
function unshareImage(ctx, next) {
log.trace({dryRun: opts.dry_run, account: ctx.account},
'image unshare account');
if (opts.dry_run) {
next();
return;
}
tritonapi.unshareImage({
image: args[0],
account: args[1]
}, function (err, img) {
if (err) {
next(new errors.TritonError(err, 'error unsharing image'));
return;
}
log.trace({img: img}, 'image unshare result');
if (opts.json) {
console.log(JSON.stringify(img));
} else {
console.log('Unshared image %s with account %s',
args[0], args[1]);
}
next();
});
}
]}, function (err) {
cb(err);
});
}
do_unshare.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
group: 'Other options'
},
{
names: ['dry-run'],
type: 'bool',
help: 'Go through the motions without actually sharing.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON stream output.'
}
];
do_unshare.synopses = [
'{{name}} {{cmd}} [OPTIONS] IMAGE ACCOUNT'
];
do_unshare.help = [
/* BEGIN JSSTYLED */
'Unshare an image with another account.',
'',
'{{usage}}',
'',
'{{options}}',
'Where "IMAGE" is an image id (a full UUID), an image name (selects the',
'latest, by "published_at", image with that name), an image "name@version"',
'(selects latest match by "published_at"), or an image short ID (ID prefix).',
'',
'Where "ACCOUNT" is the full account UUID.',
'',
'Note: Only images that are owned by the account can be unshared.'
/* END JSSTYLED */
].join('\n');
do_unshare.completionArgtypes = ['tritonimage', 'none'];
module.exports = do_unshare;

View File

@ -5,7 +5,7 @@
*/ */
/* /*
* Copyright 2017 Joyent, Inc. * Copyright (c) 2018, Joyent, Inc.
* *
* `triton image ...` * `triton image ...`
*/ */
@ -36,6 +36,8 @@ function ImageCLI(top) {
'create', 'create',
'delete', 'delete',
'export', 'export',
'share',
'unshare',
'wait' 'wait'
] ]
}); });
@ -52,6 +54,8 @@ ImageCLI.prototype.do_get = require('./do_get');
ImageCLI.prototype.do_create = require('./do_create'); ImageCLI.prototype.do_create = require('./do_create');
ImageCLI.prototype.do_delete = require('./do_delete'); ImageCLI.prototype.do_delete = require('./do_delete');
ImageCLI.prototype.do_export = require('./do_export'); ImageCLI.prototype.do_export = require('./do_export');
ImageCLI.prototype.do_share = require('./do_share');
ImageCLI.prototype.do_unshare = require('./do_unshare');
ImageCLI.prototype.do_wait = require('./do_wait'); ImageCLI.prototype.do_wait = require('./do_wait');

View File

@ -5,7 +5,7 @@
*/ */
/* /*
* Copyright 2017 Joyent, Inc. * Copyright 2018 Joyent, Inc.
* *
* `triton instance create ...` * `triton instance create ...`
*/ */
@ -376,6 +376,9 @@ function do_create(subcmd, opts, args, cb) {
function (net) { return net.id; }) function (net) { return net.id; })
}; };
if (opts.brand) {
createOpts.brand = opts.brand;
}
if (ctx.volMounts) { if (ctx.volMounts) {
createOpts.volumes = ctx.volMounts; createOpts.volumes = ctx.volMounts;
} }
@ -492,6 +495,13 @@ do_create.options = [
{ {
group: 'Create options' group: 'Create options'
}, },
{
names: ['brand'],
helpArg: 'BRAND',
type: 'string',
help: 'Override the default brand for this instance. Most users will ' +
'not need this option.'
},
{ {
names: ['name', 'n'], names: ['name', 'n'],
helpArg: 'NAME', helpArg: 'NAME',

View File

@ -5,7 +5,7 @@
*/ */
/* /*
* Copyright (c) 2017, Joyent, Inc. * Copyright (c) 2018, Joyent, Inc.
* *
* `triton instance list ...` * `triton instance list ...`
*/ */
@ -150,6 +150,7 @@ function do_list(subcmd, opts, args, callback) {
common.uuidToShortId(inst.image); common.uuidToShortId(inst.image);
inst.shortid = inst.id.split('-', 1)[0]; inst.shortid = inst.id.split('-', 1)[0];
var flags = []; var flags = [];
if (inst.brand === 'bhyve') flags.push('B');
if (inst.docker) flags.push('D'); if (inst.docker) flags.push('D');
if (inst.firewall_enabled) flags.push('F'); if (inst.firewall_enabled) flags.push('F');
if (inst.brand === 'kvm') flags.push('K'); if (inst.brand === 'kvm') flags.push('K');
@ -208,6 +209,7 @@ do_list.help = [
'for convenience):', 'for convenience):',
' shortid* A short ID prefix.', ' shortid* A short ID prefix.',
' flags* Single letter flags summarizing some fields:', ' flags* Single letter flags summarizing some fields:',
' "B" the brand is "bhyve"',
' "D" docker instance', ' "D" docker instance',
' "F" firewall is enabled', ' "F" firewall is enabled',
' "K" the brand is "kvm"', ' "K" the brand is "kvm"',

View File

@ -5,7 +5,7 @@
*/ */
/* /*
* Copyright 2016 Joyent, Inc. * Copyright 2018 Joyent, Inc.
* *
* `triton snapshot create ...` * `triton snapshot create ...`
*/ */
@ -133,7 +133,7 @@ do_create.help = [
'{{usage}}', '{{usage}}',
'', '',
'{{options}}', '{{options}}',
'Snapshot do not work for instances of type "kvm".' 'Snapshots do not work for instances of type "bhyve" or "kvm".'
].join('\n'); ].join('\n');
do_create.completionArgtypes = ['tritoninstance', 'none']; do_create.completionArgtypes = ['tritoninstance', 'none'];

View File

@ -5,7 +5,7 @@
*/ */
/* /*
* Copyright 2017 Joyent, Inc. * Copyright (c) 2018, Joyent, Inc.
*/ */
/* BEGIN JSSTYLED */ /* BEGIN JSSTYLED */
@ -233,10 +233,10 @@ function _stepPkgId(arg, next) {
/** /**
* A function appropriate for `vasync.pipeline` funcs that takes a `arg.image` * A function appropriate for `vasync.pipeline` funcs that takes a `arg.image`
* image name, shortid, or uuid, and determines the image id (setting it * image name, shortid, or uuid, and determines the image object (setting it
* as arg.imgId). * as arg.img).
*/ */
function _stepImgId(arg, next) { function _stepImg(arg, next) {
assert.object(arg.client, 'arg.client'); assert.object(arg.client, 'arg.client');
assert.string(arg.image, 'arg.image'); assert.string(arg.image, 'arg.image');
@ -244,7 +244,7 @@ function _stepImgId(arg, next) {
if (err) { if (err) {
next(err); next(err);
} else { } else {
arg.imgId = img.id; arg.img = img;
next(); next();
} }
}); });
@ -745,10 +745,10 @@ TritonApi.prototype.exportImage = function exportImage(opts, cb)
}; };
vasync.pipeline({arg: arg, funcs: [ vasync.pipeline({arg: arg, funcs: [
_stepImgId, _stepImg,
function cloudApiExportImage(ctx, next) { function cloudApiExportImage(ctx, next) {
self.cloudapi.exportImage({ self.cloudapi.exportImage({
id: ctx.imgId, manta_path: opts.manta_path }, id: ctx.img.id, manta_path: opts.manta_path },
function (err, exportInfo_, res_) { function (err, exportInfo_, res_) {
if (err) { if (err) {
next(err); next(err);
@ -769,6 +769,101 @@ TritonApi.prototype.exportImage = function exportImage(opts, cb)
}); });
}; };
/**
* Share an image with another account.
*
* @param {Object} opts
* - {String} image The image UUID, name, or short ID. Required.
* - {String} account The account UUID. Required.
* @param {Function} cb `function (err, img)`
* On failure `err` is an error instance, else it is null.
* On success: `img` is an image object.
*/
TritonApi.prototype.shareImage = function shareImage(opts, cb)
{
var self = this;
assert.object(opts, 'opts');
assert.string(opts.image, 'opts.image');
assert.string(opts.account, 'opts.account');
assert.func(cb, 'cb');
var arg = {
image: opts.image,
client: self
};
var res;
vasync.pipeline({arg: arg, funcs: [
_stepImg,
function validateAcl(ctx, next) {
ctx.acl = ctx.img.acl && ctx.img.acl.slice() || [];
if (ctx.acl.indexOf(opts.account) === -1) {
ctx.acl.push(opts.account);
}
next();
},
function cloudApiShareImage(ctx, next) {
self.cloudapi.updateImage({id: ctx.img.id, fields: {acl: ctx.acl}},
function _updateImageCb(err, img) {
res = img;
next(err);
});
}
]}, function (err) {
cb(err, res);
});
};
/**
* Unshare an image with another account.
*
* @param {Object} opts
* - {String} image The image UUID, name, or short ID. Required.
* - {String} account The account UUID. Required.
* @param {Function} cb `function (err, img)`
* On failure `err` is an error instance, else it is null.
* On success: `img` is an image object.
*/
TritonApi.prototype.unshareImage = function unshareImage(opts, cb)
{
var self = this;
assert.object(opts, 'opts');
assert.string(opts.image, 'opts.image');
assert.string(opts.account, 'opts.account');
assert.func(cb, 'cb');
var arg = {
image: opts.image,
client: self
};
var res;
vasync.pipeline({arg: arg, funcs: [
_stepImg,
function validateAcl(ctx, next) {
assert.object(ctx.img, 'img');
ctx.acl = ctx.img.acl && ctx.img.acl.slice() || [];
var aclIdx = ctx.acl.indexOf(opts.account);
if (aclIdx === -1) {
cb(new errors.TritonError(format('image is not shared with %s',
opts.account)));
return;
}
ctx.acl.splice(aclIdx, 1);
next();
},
function cloudApiUnshareImage(ctx, next) {
self.cloudapi.updateImage({id: ctx.img.id, fields: {acl: ctx.acl}},
function _updateImageCb(err, img) {
res = img;
next(err);
});
}
]}, function (err) {
cb(err, res);
});
};
/** /**
* Get an active package by ID, exact name, or short ID, in that order. * Get an active package by ID, exact name, or short ID, in that order.
* *

View File

@ -1,7 +1,7 @@
{ {
"name": "spearhead", "name": "spearhead",
"description": "Spearhead Cloud CLI and client (https://spearhead.cloud)", "description": "Spearhead Cloud CLI and client (https://spearhead.cloud)",
"version": "5.6.4", "version": "5.8.0",
"author": "Spearhead Systems (spearhead.systems)", "author": "Spearhead Systems (spearhead.systems)",
"homepage": "https://code.spearhead.cloud/Spearhead/node-spearhead", "homepage": "https://code.spearhead.cloud/Spearhead/node-spearhead",
"dependencies": { "dependencies": {
@ -33,7 +33,8 @@
}, },
"devDependencies": { "devDependencies": {
"tape": "4.2.0", "tape": "4.2.0",
"tap-summary": "3.0.2" "tap-summary": "3.0.2",
"uuid": "3.2.1"
}, },
"main": "./lib", "main": "./lib",
"scripts": { "scripts": {

View File

@ -26,6 +26,11 @@
// to true. // to true.
"skipAffinityTests": false, "skipAffinityTests": false,
// Optional. Set to 'true' to skip testing of bhyve things. Some DCs might
// not support bhyve (no packages or images available, and/or no CNs with
// bhyve compatible hardware).
"skipBhyveTests": false,
// Optional. Set to 'true' to skip testing of KVM things. Some DCs might // Optional. Set to 'true' to skip testing of KVM things. Some DCs might
// not support KVM (no KVM packages or images available). // not support KVM (no KVM packages or images available).
"skipKvmTests": false, "skipKvmTests": false,
@ -36,6 +41,12 @@
"resizePackage": "<package name>", "resizePackage": "<package name>",
"image": "<image uuid, name or name@version>" "image": "<image uuid, name or name@version>"
// The params used for test *bhyve* provisions. By default the tests use:
// the smallest RAM package with "kvm" in the name, the latest
// ubuntu-certified image.
"bhyvePackage": "<package name or uuid>",
"bhyveImage": "<image uuid, name or name@version>",
// The params used for test *KVM* provisions. By default the tests use: // The params used for test *KVM* provisions. By default the tests use:
// the smallest RAM package with "kvm" in the name, the latest // the smallest RAM package with "kvm" in the name, the latest
// ubuntu-certified image. // ubuntu-certified image.

View File

@ -5,7 +5,7 @@
*/ */
/* /*
* Copyright 2016, Joyent, Inc. * Copyright (c) 2018, Joyent, Inc.
*/ */
/* /*
@ -15,6 +15,7 @@
var format = require('util').format; var format = require('util').format;
var os = require('os'); var os = require('os');
var test = require('tape'); var test = require('tape');
var uuid = require('uuid');
var vasync = require('vasync'); var vasync = require('vasync');
var common = require('../../lib/common'); var common = require('../../lib/common');
@ -37,7 +38,7 @@ var testOpts = {
// --- Tests // --- Tests
test('triton image ...', testOpts, function (tt) { test('spearhead image ...', testOpts, function (tt) {
var imgNameVer = IMAGE_DATA.name + '@' + IMAGE_DATA.version; var imgNameVer = IMAGE_DATA.name + '@' + IMAGE_DATA.version;
var originInst; var originInst;
var img; var img;
@ -48,7 +49,7 @@ test('triton image ...', testOpts, function (tt) {
tt.comment(format('- %s: %j', key, value)); tt.comment(format('- %s: %j', key, value));
}); });
// TODO: `triton rm -f` would be helpful for this // TODO: `spearhead rm -f` would be helpful for this
tt.test(' setup: rm existing origin inst ' + ORIGIN_ALIAS, function (t) { tt.test(' setup: rm existing origin inst ' + ORIGIN_ALIAS, function (t) {
h.triton(['inst', 'get', '-j', ORIGIN_ALIAS], h.triton(['inst', 'get', '-j', ORIGIN_ALIAS],
function (err, stdout, stderr) { function (err, stdout, stderr) {
@ -199,6 +200,62 @@ test('triton image ...', testOpts, function (tt) {
} }
}); });
tt.test(' triton image share ...', function (t) {
var dummyUuid = uuid.v4();
var argv = ['image', 'share', img.id, dummyUuid];
h.safeTriton(t, argv, function (err) {
if (err) {
t.end();
return;
}
argv = ['image', 'get', '-j', img.id];
h.safeTriton(t, argv, function (err2, stdout2) {
t.ifErr(err2, 'image get response');
if (err2) {
t.end();
return;
}
var result = JSON.parse(stdout2);
t.ok(result, 'image share result');
t.ok(result.acl, 'image share result.acl');
if (result.acl && Array.isArray(result.acl)) {
t.notEqual(result.acl.indexOf(dummyUuid), -1,
'image share result.acl contains uuid');
} else {
t.fail('image share result does not contain acl array');
}
unshareImage();
});
});
function unshareImage() {
argv = ['image', 'unshare', img.id, dummyUuid];
h.safeTriton(t, argv, function (err) {
if (err) {
t.end();
return;
}
argv = ['image', 'get', '-j', img.id];
h.safeTriton(t, argv, function (err2, stdout2) {
t.ifErr(err2, 'image get response');
if (err2) {
t.end();
return;
}
var result = JSON.parse(stdout2);
t.ok(result, 'image unshare result');
if (result.acl && Array.isArray(result.acl)) {
t.equal(result.acl.indexOf(dummyUuid), -1,
'image unshare result.acl should not contain uuid');
} else {
t.equal(result.acl, undefined, 'image has no acl');
}
t.end();
});
});
}
});
// TODO: Once have `triton ssh ...` working in test suite without hangs, // TODO: Once have `triton ssh ...` working in test suite without hangs,
// then want to check that the created VM has the markerFile. // then want to check that the created VM has the markerFile.

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 2018, Joyent, Inc.
*/
/*
* Test creating a bhyve VM.
*/
var os = require('os');
var format = require('util').format;
var test = require('tape');
var h = require('./helpers');
// --- globals
var INST_ALIAS = 'nodetritontest-instance-create-bhyve-' +
os.hostname();
var testOpts = {
skip: !h.CONFIG.allowWriteActions || h.CONFIG.skipBhyveTests
};
// --- Tests
test('triton image ...', testOpts, function (tt) {
var imgId;
var inst;
var pkgId;
tt.comment('Test config:');
Object.keys(h.CONFIG).forEach(function (key) {
var value = h.CONFIG[key];
tt.comment(format('- %s: %j', key, value));
});
// TODO: `triton rm -f` would be helpful for this
tt.test(' setup: rm existing inst ' + INST_ALIAS, function (t) {
h.deleteTestInst(t, INST_ALIAS, function onDel() {
t.end();
});
});
tt.test(' setup: find image', function (t) {
h.getTestBhyveImg(t, function (err, _imgId) {
t.ifError(err, 'getTestImg' + (err ? ': ' + err : ''));
imgId = _imgId;
t.end();
});
});
tt.test(' setup: find test package', function (t) {
h.getTestBhyvePkg(t, function (err, _pkgId) {
t.ifError(err, 'getTestPkg' + (err ? ': ' + err : ''));
pkgId = _pkgId;
t.end();
});
});
tt.test(' setup: triton create ... -n ' + INST_ALIAS, function (t) {
var argv = ['create', '-wj', '--brand=bhyve', '-n', INST_ALIAS,
imgId, pkgId];
h.safeTriton(t, argv, function (err, stdout) {
var lines = h.jsonStreamParse(stdout);
inst = lines[1];
t.ok(inst.id, 'inst.id: ' + inst.id);
t.equal(lines[1].state, 'running', 'inst is running');
t.end();
});
});
// TODO: Once have `triton ssh ...` working in test suite without hangs,
// then want to check that the created VM works.
// Remove instance. Add a test 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: spearhead rm', {timeout: 10 * 60 * 1000}, function (t) {
h.safeTriton(t, ['rm', '-w', inst.id], function () {
t.end();
});
});
});

View File

@ -5,7 +5,7 @@
*/ */
/* /*
* Copyright 2017 Joyent, Inc. * Copyright 2018 Joyent, Inc.
*/ */
/* /*
@ -26,8 +26,8 @@ var testcommon = require('../lib/testcommon');
var CONFIG; var CONFIG;
var configPath = process.env.TRITON_TEST_CONFIG var configPath = process.env.TSC_TEST_CONFIG
? path.resolve(process.cwd(), process.env.TRITON_TEST_CONFIG) ? path.resolve(process.cwd(), process.env.SC_TEST_CONFIG)
: path.resolve(__dirname, '..', 'config.json'); : path.resolve(__dirname, '..', 'config.json');
try { try {
CONFIG = require(configPath); CONFIG = require(configPath);
@ -44,7 +44,7 @@ try {
'CONFIG.profile.insecure'); 'CONFIG.profile.insecure');
} else if (CONFIG.profileName) { } else if (CONFIG.profileName) {
CONFIG.profile = mod_triton.loadProfile({ CONFIG.profile = mod_triton.loadProfile({
configDir: path.join(process.env.HOME, '.triton'), configDir: path.join(process.env.HOME, '.spearhead'),
name: CONFIG.profileName name: CONFIG.profileName
}); });
} else { } else {
@ -55,11 +55,11 @@ try {
'test/config.json#allowWriteActions'); 'test/config.json#allowWriteActions');
} catch (e) { } catch (e) {
error('* * *'); error('* * *');
error('node-triton integration tests require a config file. By default'); error('node-spearhead integration tests require a config file. By default');
error('it looks for "test/config.json". Or you can set the'); error('it looks for "test/config.json". Or you can set the');
error('TRITON_TEST_CONFIG envvar. E.g.:'); error('SC_TEST_CONFIG envvar. E.g.:');
error(''); error('');
error(' TRITON_TEST_CONFIG=test/coal.json make test'); error(' SC_TEST_CONFIG=test/coal.json make test');
error(''); error('');
error('See "test/config.json.sample" for a starting point for a config.'); error('See "test/config.json.sample" for a starting point for a config.');
error(''); error('');
@ -75,7 +75,7 @@ if (CONFIG.profile.insecure === undefined)
if (CONFIG.allowWriteActions === undefined) if (CONFIG.allowWriteActions === undefined)
CONFIG.allowWriteActions = false; CONFIG.allowWriteActions = false;
var TRITON = [process.execPath, path.resolve(__dirname, '../../bin/triton')]; var TRITON = [process.execPath, path.resolve(__dirname, '../../bin/spearhead')];
var UA = 'node-triton-test'; var UA = 'node-triton-test';
var LOG = require('../lib/log'); var LOG = require('../lib/log');
@ -83,10 +83,10 @@ var LOG = require('../lib/log');
/* /*
* Call the `triton` CLI with the given args. * Call the `spearhead` CLI with the given args.
* *
* @param args {String|Array} Required. CLI arguments to `triton ...` (without * @param args {String|Array} Required. CLI arguments to `spearhead ...` (without
* the "triton"). This can be an array of args, or a string. * the "spearhead"). This can be an array of args, or a string.
* @param opts {Object} Optional. * @param opts {Object} Optional.
* - opts.cwd {String} cwd option to exec. * - opts.cwd {String} cwd option to exec.
* @param cb {Function} * @param cb {Function}
@ -111,11 +111,11 @@ function triton(args, opts, cb) {
PATH: process.env.PATH, PATH: process.env.PATH,
HOME: process.env.HOME, HOME: process.env.HOME,
SSH_AUTH_SOCK: process.env.SSH_AUTH_SOCK, SSH_AUTH_SOCK: process.env.SSH_AUTH_SOCK,
TRITON_PROFILE: 'env', SC_PROFILE: 'env',
TRITON_URL: CONFIG.profile.url, SC_URL: CONFIG.profile.url,
TRITON_ACCOUNT: CONFIG.profile.account, SC_ACCOUNT: CONFIG.profile.account,
TRITON_KEY_ID: CONFIG.profile.keyId, SC_KEY_ID: CONFIG.profile.keyId,
TRITON_TLS_INSECURE: CONFIG.profile.insecure SC_TLS_INSECURE: CONFIG.profile.insecure
}, },
cwd: opts.cwd cwd: opts.cwd
}, },
@ -126,12 +126,12 @@ function triton(args, opts, cb) {
/* /*
* `triton ...` wrapper that: * `spearhead ...` wrapper that:
* - tests non-error exit * - tests non-error exit
* - tests stderr is empty * - tests stderr is empty
* *
* @param {Tape} t - tape test object * @param {Tape} t - tape test object
* @param {Object|Array} opts - options object, or just the `triton` args * @param {Object|Array} opts - options object, or just the `spearhead` args
* @param {Function} cb - `function (err, stdout)` * @param {Function} cb - `function (err, stdout)`
* Note that `err` will already have been tested to be falsey via * Note that `err` will already have been tested to be falsey via
* `t.error(err, ...)`, so it may be fine for the calling test case * `t.error(err, ...)`, so it may be fine for the calling test case
@ -149,7 +149,7 @@ function safeTriton(t, opts, cb) {
// t.comment(f('running: triton %s', opts.args.join(' '))); // t.comment(f('running: triton %s', opts.args.join(' ')));
triton(opts.args, function (err, stdout, stderr) { triton(opts.args, function (err, stdout, stderr) {
t.error(err, f('ran "triton %s", err=%s', opts.args.join(' '), err)); t.error(err, f('ran "spearhead %s", err=%s', opts.args.join(' '), err));
t.equal(stderr, '', 'empty stderr'); t.equal(stderr, '', 'empty stderr');
if (opts.json) { if (opts.json) {
try { try {
@ -211,6 +211,46 @@ function getTestImg(t, cb) {
} }
/*
* Find and return an image that can be used for test *bhyve* provisions.
*
* @param {Tape} t - tape test object
* @param {Function} cb - `function (err, imgId)`
* where `imgId` is an image identifier (an image name, shortid, or id).
*/
function getTestBhyveImg(t, cb) {
if (CONFIG.bhyveImage) {
assert.string(CONFIG.bhyvePackage, 'CONFIG.bhyvePackage');
t.ok(CONFIG.bhyveImage, 'bhyveImage from config: ' + CONFIG.bhyveImage);
cb(null, CONFIG.bhyveImage);
return;
}
var candidateImageNames = {
'ubuntu-certified-16.04': true
};
safeTriton(t, ['img', 'ls', '-j'], function (err, stdout) {
var imgId;
var imgs = jsonStreamParse(stdout);
// Newest images first.
tabula.sortArrayOfObjects(imgs, ['-published_at']);
var imgRepr;
for (var i = 0; i < imgs.length; i++) {
var img = imgs[i];
if (candidateImageNames[img.name]) {
imgId = img.id;
imgRepr = f('%s@%s', img.name, img.version);
break;
}
}
t.ok(imgId,
f('latest bhyve image (using subset of supported names): %s (%s)',
imgId, imgRepr));
cb(err, imgId);
});
}
/* /*
* Find and return an image that can be used for test *KVM* provisions. * Find and return an image that can be used for test *KVM* provisions.
* *
@ -281,6 +321,38 @@ function getTestPkg(t, cb) {
}); });
} }
/*
* Find and return an package that can be used for *bhyve* test provisions.
*
* @param {Tape} t - tape test object
* @param {Function} cb - `function (err, pkgId)`
* where `pkgId` is an package identifier (a name, shortid, or id).
*/
function getTestBhyvePkg(t, cb) {
if (CONFIG.bhyvePackage) {
assert.string(CONFIG.bhyvePackage, 'CONFIG.bhyvePackage');
t.ok(CONFIG.bhyvePackage, 'bhyvePackage from config: ' +
CONFIG.bhyvePackage);
cb(null, CONFIG.bhyvePackage);
return;
}
// bhyve uses the same packages as kvm
safeTriton(t, ['pkg', 'ls', '-j'], function (err, stdout) {
var pkgs = jsonStreamParse(stdout);
// Filter on those with 'kvm' in the name.
pkgs = pkgs.filter(function (pkg) {
return pkg.name.indexOf('kvm') !== -1;
});
// Smallest RAM first.
tabula.sortArrayOfObjects(pkgs, ['memory']);
var pkgId = pkgs[0].id;
t.ok(pkgId, f('smallest (RAM) available kvm package: %s (%s)',
pkgId, pkgs[0].name));
cb(null, pkgId);
});
}
/* /*
* Find and return an package that can be used for *KVM* test provisions. * Find and return an package that can be used for *KVM* test provisions.
* *
@ -361,7 +433,7 @@ function createClient(cb) {
mod_triton.createClient({ mod_triton.createClient({
log: LOG, log: LOG,
profile: CONFIG.profile, profile: CONFIG.profile,
configDir: '~/.triton' // piggy-back on Triton CLI config dir configDir: '~/.spearhead' // piggy-back on Spearhead CLI config dir
}, cb); }, cb);
} }
@ -511,8 +583,10 @@ module.exports = {
deleteTestImg: deleteTestImg, deleteTestImg: deleteTestImg,
getTestImg: getTestImg, getTestImg: getTestImg,
getTestBhyveImg: getTestBhyveImg,
getTestKvmImg: getTestKvmImg, getTestKvmImg: getTestKvmImg,
getTestPkg: getTestPkg, getTestPkg: getTestPkg,
getTestBhyvePkg: getTestBhyvePkg,
getTestKvmPkg: getTestKvmPkg, getTestKvmPkg: getTestKvmPkg,
getResizeTestPkg: getResizeTestPkg, getResizeTestPkg: getResizeTestPkg,