joyent/node-triton#195 test *kvm* image creation
This commit is contained in:
parent
a8f4b57ba3
commit
33ff58c3d3
@ -22,9 +22,19 @@
|
|||||||
// to true.
|
// to true.
|
||||||
"skipAffinityTests": false,
|
"skipAffinityTests": false,
|
||||||
|
|
||||||
|
// Optional. Set to 'true' to skip testing of KVM things. Some DCs might
|
||||||
|
// not support KVM (no KVM packages or images available).
|
||||||
|
"skipKvmTests": false,
|
||||||
|
|
||||||
// The params used for test provisions. By default the tests use:
|
// The params used for test provisions. By default the tests use:
|
||||||
// the smallest RAM package, the latest base* image.
|
// the smallest RAM package, the latest base* image.
|
||||||
"package": "<package name or uuid>",
|
"package": "<package name or uuid>",
|
||||||
"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 *KVM* provisions. By default the tests use:
|
||||||
|
// the smallest RAM package with "kvm" in the name, the latest
|
||||||
|
// ubuntu-certified image.
|
||||||
|
"kvmPackage": "<package name or uuid>",
|
||||||
|
"kvmImage": "<image uuid, name or name@version>"
|
||||||
}
|
}
|
||||||
|
169
test/integration/cli-image-create-kvm.test.js
Normal file
169
test/integration/cli-image-create-kvm.test.js
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
/*
|
||||||
|
* 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 2016, Joyent, Inc.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test image commands.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var format = require('util').format;
|
||||||
|
var os = require('os');
|
||||||
|
var test = require('tape');
|
||||||
|
var vasync = require('vasync');
|
||||||
|
|
||||||
|
var common = require('../../lib/common');
|
||||||
|
var h = require('./helpers');
|
||||||
|
|
||||||
|
|
||||||
|
// --- globals
|
||||||
|
|
||||||
|
var _RESOURCE_NAME_PREFIX = 'nodetritontest-image-create-kvm-' + os.hostname();
|
||||||
|
var ORIGIN_INST_ALIAS = _RESOURCE_NAME_PREFIX + '-origin';
|
||||||
|
var IMAGE_DATA = {
|
||||||
|
name: _RESOURCE_NAME_PREFIX + '-image',
|
||||||
|
version: '1.0.0'
|
||||||
|
};
|
||||||
|
var DERIVED_INST_ALIAS = _RESOURCE_NAME_PREFIX + '-derived';
|
||||||
|
delete _RESOURCE_NAME_PREFIX;
|
||||||
|
|
||||||
|
var testOpts = {
|
||||||
|
skip: !h.CONFIG.allowWriteActions || h.CONFIG.skipKvmTests
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// --- Tests
|
||||||
|
|
||||||
|
test('triton image ...', testOpts, function (tt) {
|
||||||
|
var imgNameVer = IMAGE_DATA.name + '@' + IMAGE_DATA.version;
|
||||||
|
var originInst;
|
||||||
|
var img;
|
||||||
|
|
||||||
|
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 origin inst ' + ORIGIN_INST_ALIAS,
|
||||||
|
function (t) {
|
||||||
|
h.deleteTestInst(t, ORIGIN_INST_ALIAS, function onDel() {
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: `triton rm -f` would be helpful for this
|
||||||
|
tt.test(' setup: rm existing derived inst ' + DERIVED_INST_ALIAS,
|
||||||
|
function (t) {
|
||||||
|
h.deleteTestInst(t, DERIVED_INST_ALIAS, function onDel() {
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tt.test(' setup: rm existing img ' + imgNameVer, function (t) {
|
||||||
|
h.deleteTestImg(t, imgNameVer, function onDel() {
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var originImgNameOrId;
|
||||||
|
tt.test(' setup: find origin image', function (t) {
|
||||||
|
h.getTestKvmImg(t, function (err, imgId) {
|
||||||
|
t.ifError(err, 'getTestImg' + (err ? ': ' + err : ''));
|
||||||
|
originImgNameOrId = imgId;
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var pkgId;
|
||||||
|
tt.test(' setup: find test package', function (t) {
|
||||||
|
h.getTestKvmPkg(t, function (err, pkgId_) {
|
||||||
|
t.ifError(err, 'getTestPkg' + (err ? ': ' + err : ''));
|
||||||
|
pkgId = pkgId_;
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var markerFile = '/nodetritontest-was-here.txt';
|
||||||
|
tt.test(' setup: triton create ... -n ' + ORIGIN_INST_ALIAS, function (t) {
|
||||||
|
var argv = ['create', '-wj', '-n', ORIGIN_INST_ALIAS,
|
||||||
|
'-m', 'user-script=touch ' + markerFile,
|
||||||
|
originImgNameOrId, pkgId];
|
||||||
|
h.safeTriton(t, argv, function (err, stdout) {
|
||||||
|
var lines = h.jsonStreamParse(stdout);
|
||||||
|
originInst = lines[1];
|
||||||
|
t.ok(originInst.id, 'originInst.id: ' + originInst.id);
|
||||||
|
t.equal(lines[1].state, 'running', 'originInst is running');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: I'd like to use this 'triton ssh INST touch $markerFile' to
|
||||||
|
// tweak the image. However, that current hangs when run via
|
||||||
|
// tape (don't know why yet). Instead we'll use a user-script to
|
||||||
|
// change the origin as our image change.
|
||||||
|
//
|
||||||
|
//tt.test(' setup: add marker to origin', function (t) {
|
||||||
|
// var argv = ['ssh', originInst.id,
|
||||||
|
// '-o', 'StrictHostKeyChecking=no',
|
||||||
|
// '-o', 'UserKnownHostsFile=/dev/null',
|
||||||
|
// 'touch', markerFile];
|
||||||
|
// h.safeTriton(t, argv, function (err, stdout) {
|
||||||
|
// t.ifError(err, 'adding origin marker file, err=' + err);
|
||||||
|
// t.end();
|
||||||
|
// });
|
||||||
|
//});
|
||||||
|
|
||||||
|
tt.test(' triton image create ...', function (t) {
|
||||||
|
var argv = ['image', 'create', '-j', '-w', '-t', 'foo=bar',
|
||||||
|
originInst.id, IMAGE_DATA.name, IMAGE_DATA.version];
|
||||||
|
h.safeTriton(t, argv, function (err, stdout) {
|
||||||
|
var lines = h.jsonStreamParse(stdout);
|
||||||
|
img = lines[1];
|
||||||
|
t.ok(img, 'created image, id=' + img.id);
|
||||||
|
t.equal(img.name, IMAGE_DATA.name, 'img.name');
|
||||||
|
t.equal(img.version, IMAGE_DATA.version, 'img.version');
|
||||||
|
t.equal(img.public, false, 'img.public is false');
|
||||||
|
t.equal(img.state, 'active', 'img.state is active');
|
||||||
|
t.equal(img.origin, originInst.image, 'img.origin');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var derivedInst;
|
||||||
|
tt.test(' triton create ... -n ' + DERIVED_INST_ALIAS, function (t) {
|
||||||
|
var argv = ['create', '-wj', '-n', DERIVED_INST_ALIAS, img.id, pkgId];
|
||||||
|
h.safeTriton(t, argv, function (err, stdout) {
|
||||||
|
var lines = h.jsonStreamParse(stdout);
|
||||||
|
derivedInst = lines[1];
|
||||||
|
t.ok(derivedInst.id, 'derivedInst.id: ' + derivedInst.id);
|
||||||
|
t.equal(lines[1].state, 'running', 'derivedInst is running');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Once have `triton ssh ...` working in test suite without hangs,
|
||||||
|
// then want to check that the created VM has the markerFile.
|
||||||
|
|
||||||
|
// Remove instances. 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: triton rm', {timeout: 10 * 60 * 1000}, function (t) {
|
||||||
|
h.safeTriton(t, ['rm', '-w', originInst.id, derivedInst.id],
|
||||||
|
function () {
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tt.test(' cleanup: triton image rm', function (t) {
|
||||||
|
h.safeTriton(t, ['image', 'rm', '-f', img.id], function () {
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2015, Joyent, Inc.
|
* Copyright 2017 Joyent, Inc.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -132,6 +132,9 @@ function triton(args, opts, cb) {
|
|||||||
* @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 `triton` 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
|
||||||
|
* `t.error(err, ...)`, so it may be fine for the calling test case
|
||||||
|
* to ignore `err`.
|
||||||
*/
|
*/
|
||||||
function safeTriton(t, opts, cb) {
|
function safeTriton(t, opts, cb) {
|
||||||
assert.object(t, 't');
|
assert.object(t, 't');
|
||||||
@ -160,6 +163,7 @@ function safeTriton(t, opts, cb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Find and return an image that can be used for test provisions. We look
|
* Find and return an image that can be used for test provisions. We look
|
||||||
* for an available base or minimal image.
|
* for an available base or minimal image.
|
||||||
@ -206,6 +210,47 @@ function getTestImg(t, cb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find and return an image that can be used for test *KVM* 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 getTestKvmImg(t, cb) {
|
||||||
|
if (CONFIG.kvmImage) {
|
||||||
|
assert.string(CONFIG.kvmPackage, 'CONFIG.kvmPackage');
|
||||||
|
t.ok(CONFIG.kvmImage, 'kvmImage from config: ' + CONFIG.kvmImage);
|
||||||
|
cb(null, CONFIG.kvmImage);
|
||||||
|
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 KVM image (using subset of supported names): %s (%s)',
|
||||||
|
imgId, imgRepr));
|
||||||
|
cb(err, imgId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Find and return an package that can be used for test provisions.
|
* Find and return an package that can be used for test provisions.
|
||||||
*
|
*
|
||||||
@ -222,6 +267,10 @@ function getTestPkg(t, cb) {
|
|||||||
|
|
||||||
safeTriton(t, ['pkg', 'ls', '-j'], function (err, stdout) {
|
safeTriton(t, ['pkg', 'ls', '-j'], function (err, stdout) {
|
||||||
var pkgs = jsonStreamParse(stdout);
|
var pkgs = jsonStreamParse(stdout);
|
||||||
|
// Filter out those with 'kvm' in the name.
|
||||||
|
pkgs = pkgs.filter(function (pkg) {
|
||||||
|
return pkg.name.indexOf('kvm') == -1;
|
||||||
|
});
|
||||||
// Smallest RAM first.
|
// Smallest RAM first.
|
||||||
tabula.sortArrayOfObjects(pkgs, ['memory']);
|
tabula.sortArrayOfObjects(pkgs, ['memory']);
|
||||||
var pkgId = pkgs[0].id;
|
var pkgId = pkgs[0].id;
|
||||||
@ -231,6 +280,36 @@ function getTestPkg(t, cb) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find and return an package that can be used for *KVM* 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 getTestKvmPkg(t, cb) {
|
||||||
|
if (CONFIG.kvmPackage) {
|
||||||
|
assert.string(CONFIG.kvmPackage, 'CONFIG.kvmPackage');
|
||||||
|
t.ok(CONFIG.kvmPackage, 'kvmPackage from config: ' + CONFIG.kvmPackage);
|
||||||
|
cb(null, CONFIG.kvmPackage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 second smallest package name that can be used for
|
* Find and return second smallest package name that can be used for
|
||||||
* test provisions.
|
* test provisions.
|
||||||
@ -323,26 +402,76 @@ function createTestInst(t, name, cb) {
|
|||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Remove test instance, if exists.
|
* Delete the given test instance (by name or id). It is not an error for the
|
||||||
|
* instance to not exist. I.e. this is somewhat like `rm -f FILE`.
|
||||||
|
*
|
||||||
|
* Once we've validated that the inst exists, it *is* an error if the delete
|
||||||
|
* fails. This function checks that with `t.ifErr`.
|
||||||
|
*
|
||||||
|
* @param {Tape} t - Tape test object on which to assert details.
|
||||||
|
* @param {String} instNameOrId - The instance name or id to delete.
|
||||||
|
* @param {Function} cb - `function ()`. A deletion error is NOT returned
|
||||||
|
* currently, because it is checked via `t.ifErr`.
|
||||||
*/
|
*/
|
||||||
function deleteTestInst(t, name, cb) {
|
function deleteTestInst(t, instNameOrId, cb) {
|
||||||
triton(['inst', 'get', '-j', name], function (err, stdout, stderr) {
|
assert.object(t, 't');
|
||||||
|
assert.string(instNameOrId, 'instNameOrId');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
|
triton(['inst', 'get', '-j', instNameOrId],
|
||||||
|
function onInstGet(err, stdout, _) {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (err.code === 3) { // `triton` code for ResourceNotFound
|
if (err.code === 3) { // `triton` code for ResourceNotFound
|
||||||
t.ok(true, 'no pre-existing alias in the way');
|
t.ok(true, 'no existing inst ' + instNameOrId);
|
||||||
|
cb();
|
||||||
} else {
|
} else {
|
||||||
t.ifErr(err);
|
t.ifErr(err, err);
|
||||||
|
cb();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
return cb();
|
var instToRm = JSON.parse(stdout);
|
||||||
|
safeTriton(t, ['inst', 'rm', '-w', instToRm.id], function onRm() {
|
||||||
|
t.ok(true, 'deleted inst ' + instToRm.id);
|
||||||
|
cb();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var oldInst = JSON.parse(stdout);
|
/*
|
||||||
|
* Delete the given test image (by name or id). It is not an error for the
|
||||||
|
* image to not exist. I.e. this is somewhat like `rm -f FILE`.
|
||||||
|
*
|
||||||
|
* Once we've validated that the image exists, it *is* an error if the delete
|
||||||
|
* fails. This function checks that with `t.ifErr`.
|
||||||
|
*
|
||||||
|
* @param {Tape} t - Tape test object on which to assert details.
|
||||||
|
* @param {String} imgNameOrId - The image name or id to delete.
|
||||||
|
* @param {Function} cb - `function ()`. A deletion error is NOT returned
|
||||||
|
* currently, because it is checked via `t.ifErr`.
|
||||||
|
*/
|
||||||
|
function deleteTestImg(t, imgNameOrId, cb) {
|
||||||
|
assert.object(t, 't');
|
||||||
|
assert.string(imgNameOrId, 'imgNameOrId');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
safeTriton(t, ['delete', '-w', oldInst.id], function (dErr) {
|
triton(['img', 'get', '-j', imgNameOrId],
|
||||||
t.ifError(dErr, 'deleted old inst ' + oldInst.id);
|
function onImgGet(err, stdout, _) {
|
||||||
cb();
|
if (err) {
|
||||||
});
|
if (err.code === 3) { // `triton` code for ResourceNotFound
|
||||||
|
t.ok(true, 'no existing img ' + imgNameOrId);
|
||||||
|
cb();
|
||||||
|
} else {
|
||||||
|
t.ifErr(err, err);
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var imgToRm = JSON.parse(stdout);
|
||||||
|
safeTriton(t, ['img', 'rm', '-w', imgToRm.id], function onRm() {
|
||||||
|
t.ok(true, 'deleted img ' + imgToRm.id);
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,12 +495,18 @@ module.exports = {
|
|||||||
CONFIG: CONFIG,
|
CONFIG: CONFIG,
|
||||||
triton: triton,
|
triton: triton,
|
||||||
safeTriton: safeTriton,
|
safeTriton: safeTriton,
|
||||||
|
|
||||||
createClient: createClient,
|
createClient: createClient,
|
||||||
createTestInst: createTestInst,
|
createTestInst: createTestInst,
|
||||||
deleteTestInst: deleteTestInst,
|
deleteTestInst: deleteTestInst,
|
||||||
|
deleteTestImg: deleteTestImg,
|
||||||
|
|
||||||
getTestImg: getTestImg,
|
getTestImg: getTestImg,
|
||||||
|
getTestKvmImg: getTestKvmImg,
|
||||||
getTestPkg: getTestPkg,
|
getTestPkg: getTestPkg,
|
||||||
|
getTestKvmPkg: getTestKvmPkg,
|
||||||
getResizeTestPkg: getResizeTestPkg,
|
getResizeTestPkg: getResizeTestPkg,
|
||||||
|
|
||||||
jsonStreamParse: jsonStreamParse,
|
jsonStreamParse: jsonStreamParse,
|
||||||
printConfig: printConfig,
|
printConfig: printConfig,
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user