UNPKG

zcage

Version:

Zone administration API for Illumos

1,482 lines (1,324 loc) 114 kB
/* zone managing functions for zcage illumos zone manager * Copyright (c) 2018, Carlos Neira cneirabustos@gmail.com * * This file is part of Zcage. * * zcage is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * zcage is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with zcage. If not, see <http://www.gnu.org/licenses/>. */ const ip = require('ipaddr.js'); const chalk = require('chalk'); const bytes = require('bytes'); const uuidv4 = require('uuid/v4'); var fs = require('fs'), xml2js = require('xml2js'); const util = require('util'); const VError = require('verror'); const dockerNames = require('docker-names'); var columnify = require('columnify'); const { spawnSync, spawn, fork } = require('child_process'); const zfs = require('./imgadm'); var Ajv = require('ajv'); var validator = require('validator'); const path = require('path'); var debug = { enabled: false }; const ubuntu_distros = ['xenial', 'disco', 'bionic']; function mapToObj(inputMap) { let obj = {}; inputMap.forEach(function(value, key) { obj[key] = value; }); return obj; } function ZonesString() { var zoneadm = spawnSync('zoneadm', ['list', '-cp']); if (!zoneadm.stdout) { console.log( "You must be root or use an account with Primary Administrator Role to use zcage" ); return process.exit(); } var zones = zoneadm.stdout.toString().split('\n'); zones.pop(); return zones; } function Zonedata(zonestring) { var values = zonestring.toString().split(':'); var keys = ["zoneid", "zonename", "state", "zone-path", "uuid", "brand", "ip-type" ]; var m = new Map(); values.pop(); for (var i = 0, len = values.length; i < len; i++) { m.set(keys[i], values[i]); } return mapToObj(m); } function list() { var zones = ZonesString(); var zoneobjs = []; for (var i = 0, len = zones.length; i < len; i++) { zoneobjs.push(Zonedata(zones[i])); } return zoneobjs; } function listzones(listOptions, out) { var zones = ZonesString(); var zoneobjs = []; var type; for (var i = 0, len = zones.length; i < len; i++) { z = Zonedata(zones[i]); if (listOptions && ("state" in listOptions)) { if (z.state != "configured" && z.state == "installed") z.state = "stopped"; if (listOptions && listOptions.state == z.state && z.zonename != "global") zoneobjs.push(z); } else { if (z.state == "incomplete") continue; if ("configured" != z.state) { switch (z.state) { case "installed": z.state = "stopped"; break; } if (z.zonename != "global") zoneobjs.push(z); } } } if (!out) { var data = []; console.log( "UUID\t\t\t\t TYPE STATE RAM \tALIAS\t\t\t CREATED" ); for (i = 0, len = zoneobjs.length; i < len; i++) { if (zoneobjs[i].zonename != "global") { getzonedata(zoneobjs[i].zonename, function(zdata, z) { if (zdata.name == 'incomplete') return; let ram; let ipaddr = "unknown"; ram = zdata.ram; let created; if ("CreatedAt" in zdata) created = { CreatedAt: zdata['CreatedAt'] }; else created = { CreatedAt: "" }; switch (zdata.brand) { case "lx": type = "LX"; break; case "bhyve": type = "BHYVE"; break; case "kvm": type = "KVM"; break; case "dpkg": type = "DPKG"; break; default: type = "OS"; break; } data = [{ UUID: z.uuid, TYPE: type, STATE: z.state, RAM: ram, NAME: z.zonename, CREATED: created.CreatedAt }]; var columns = columnify(data, { showHeaders: false, Align: 'right', config: { TYPE: { minWidth: 6 }, STATE: { minWidth: 8 }, RAM: { minWidth: 10 }, NAME: { minWidth: 21 }, } }); console.log(columns); }, zoneobjs[i]); } } } return zoneobjs; } function getdata(zonename, uuid) { var zones = list(); var zone = zones.filter(zone => (zone.zonename == zonename || zone.uuid == uuid)); if (zone == undefined || zone.length == 0) return null; return zone[0]; } function destroy(zonename, dzvol, callback) { if (!isAbletoexec()) { console.log( "You must be root or use an account with Primary Administrator Role to Activate zcage (pfexec zcage activate)" ); return null; } var zoption = '-z'; var z = ''; if (validator.isUUID(zonename)) { z = getdata(zonename, zonename); zoption = '-u'; } else { z = getdata(zonename); } var zdestroy; if (z == null) { console.log(chalk `Destroying zone: ${zonename} [{red.bold ERR}] `); console.log(`${zonename} does not exists`); if (callback) { callback(404, { message: "No such container: " + zonename }); } return null; } if (z.state == "running") { var zhalt = spawnSync('pfexec', ['zoneadm', zoption, zonename, 'halt' ]); var errorText = zhalt.stderr.toString().trim(); if (errorText) { console.log(chalk `Halting zone: ${zonename} [{red.bold ERR}] `); console.log(errorText); if (callback) { callback(500, { message: errorText }); } return zhalt.status; } } zdestroy = spawnSync('pfexec', ['zoneadm', zoption, zonename, 'uninstall', '-F' ]); var errorText = zdestroy.stderr.toString().trim(); if (errorText) { console.log(chalk `Uninstalling zone: ${zonename} [{red.bold ERR}] `); console.log(errorText); if (callback) { callback(500, { message: errorText }); } } if (dzvol == true && (z.brand == 'bhyve' || z.brand == 'kvm')) { getzonedata(z.zonename, function(zdata) { var obj = zdata.vm_data; var zvol = obj.bootdisk; if (zvol) { let zvol_destroy = spawnSync('pfexec', ['zfs', 'destroy', zvol ]); var errorText = zvol_destroy.stderr.toString().trim(); if (errorText) { console.log(errorText); if (callback) { callback(500, { message: errorText }); } return zvol_destroy.status; } } }); } zdestroy = spawnSync('pfexec', ['zonecfg', '-z', z.zonename, 'delete', '-F' ]); var errorText = zdestroy.stderr.toString().trim(); if (errorText) { console.log(chalk `Deleting zone: ${zonename} [{red.bold ERR}] `); console.log(errorText); if (callback) { callback(500, { message: errorText }); } } console.log(chalk `${zonename} destroyed [{green.bold OK}] `); if (callback) { callback(204, { message: "Container Id: " + zonename + " destroyed" }); } return zdestroy.status; } function getinfo(zonename) { var zonecfg = spawnSync('zonecfg', ['-z', zonename, 'info']); var errorText = zonecfg.stderr.toString().trim(); if (errorText) console.log("info " + errorText); return zonecfg.stdout.toString(); } function exec(zonename, cmd, out) { var zlogin = spawnSync('pfexec', ['/usr/sbin/zlogin', zonename, cmd]); var errorText = zlogin.stderr.toString().trim(); if (errorText) console.log("error executing " + cmd + " error: " + errorText); if (out != undefined) out[cmd] = zlogin.stdout.toString(); return zlogin.status; } function fixup_dataset(zonename, args, out) { var fixup = spawnSync('pfexec', args, { shell: true }); var errorText = fixup.stderr.toString().trim(); if (errorText) console.log("error executing pfexec error: " + errorText); return fixup; } function start(zonename, opts, callback, quiet) { if (!isAbletoexec()) { console.log( "You must be root or use an account with Primary Administrator Role to Activate execute this action" ); return null; } var zoption = '-z'; var zone; zonename += ''; if (validator.isUUID(zonename)) { zone = getdata(zonename, zonename); zoption = '-u'; } else { zone = getdata(zonename); } if (zone == null) { console.log(chalk `{red.bold Error : alias does not exists}`); if (callback) { callback(404, { message: "No such container: " + zonename }); } return null; } if (zone.state == "running") { if (callback) { callback(304, { message: "Container already started." }); } console.log(chalk `{blue.bold vm uuid ${zone.uuid} is already running}`); return null; } if (vnicused(zone.zonename)) { console.log(chalk `zone: ${zonename} start [{red.bold ERR}] `); console.log("vnic is being used by other container"); if (callback) { callback(500, { message: "vnic is being used by other container" }); } return null; } if ((zone.brand == 'bhyve' || zone.brand == 'kvm') && (opts != undefined && opts != null)) { var script = "add fs; set dir=" + "\"" + opts + "\"" + ";set special=" + "\"" + opts + "\"" + "; set type=lofs;add options ro;" + "add options nodevices;end;"; var r = spawnSync('pfexec', ['zonecfg', zoption, zonename, 'add attr; set type=string;set name=cdrom;set value=' + opts + ";end;" + script ]); if (debug.enabled) console.log(r.stderr.toString(), r.stdout.toString()); } var start = spawnSync('pfexec', ['zoneadm', zoption, zonename, 'boot']); var errorText = start.stderr.toString().trim(); if (errorText && zone.brand != 'dpkg') { console.log(chalk `${zonename} start [{red.bold ERR}] `); if (callback) { callback(500, { message: errorText }); } } else { if (!quiet) console.log(chalk `${zonename} started [{green.bold OK}] `); if (callback) { callback(204, { message: "Container Id:" + zone.uuid + "started" }); } } if (zone.brand == 'bhyve' || zone.brand == 'kvm') { isvnc_enabled(zone, zone.brand); } getzonedata(zonename, function(zdata) { if (zdata.Cmd) { var logs = zdata.Cmd.replace(',', " ") + ' 2> ' + '/stderr.log' + ' 1> ' + '/stdout.log'; var Cmd = ['zlogin', zdata.name, logs]; var execmd = spawnSync('pfexec', Cmd); if (debug.enabled) { console.log("executing command - stdout: " + execmd.stdout.toString() + " stderr: " + execmd.stderr.toString()); } } }); if (validator.isUUID(zonename)) { zone = getdata(zonename, zonename); zoption = '-u'; } else { zone = getdata(zonename); } zone_status = 'off'; while (zone_status != 'running') { if (validator.isUUID(zonename)) { zone = getdata(zonename, zonename); zoption = '-u'; } else { zone = getdata(zonename); } zone_status = zone.state; } if (zone.brand == 'lx') { // Execute any entrypoint a docker image may have. run_container_config(zonename, 'container-config'); } if (debug.enabled) console.log("starting zone", start.stderr.toString(), start.stdout.toString()); return start.status; } function rctl(zname, rctlOptions, callback) { if (!isAbletoexec()) { console.log( "You must be root or use an account with Primary Administrator Role to Activate execute this action" ); return null; } var rctlobj = {}; var z; if (validator.isUUID(zname)) { z = getdata(zname, zname); } else { z = getdata(zname); } if (z == null) { console.log(chalk `{red.bold Error : Alias does not exists}`); return null; } delete rctlOptions.zonename; rctlobj.rctl = addrctl(rctlOptions, z.zonename); update_rctl(z.zonename, rctlobj.rctl, function(err, zname, script, callback) { Asynczonecfgexec(err, zname, script, callback); }, callback); } function Asynczonecfgexec(err, zname, script, callback) { if (err) { console.log("error on creating script", err); if (callback) callback(400, { message: "Something wrong happened" }); return; } var zonecfg = spawnSync('pfexec', ['zonecfg', '-z', zname, script]); if (zonecfg.err) { console.log("error on zonecfg executing script", zonecfg.stderr.toString()); if (callback) callback(400, { message: "Something wrong happened" }); return; } console.log(zonecfg.stderr.toString()); console.log(zonecfg.stdout.toString()); if (callback) { callback(200, { Warnings: [] }); } return zonecfg.status; } function halt(zonename, callback, quiet) { if (!isAbletoexec()) { console.log( "You must be root or use an account with Primary Administrator Role to Activate execute this action" ); return null; } var zoption = '-z'; var z = ''; zonename += ''; if (validator.isUUID(zonename)) { z = getdata(zonename, zonename); zoption = '-u'; } else { z = getdata(zonename); } if (z == null) { if (callback) { callback(404, { message: "No such container: " + z.uuid }); } console.log("Error zone does not exists"); return null; } if (z != null && (z.brand == 'bhyve' || z.brand == 'kvm')) { spawnSync('pfexec', ['zonecfg', zoption, zonename, 'remove attr name=cdrom; remove fs;' ]); } var stop = spawnSync('pfexec', ['zoneadm', zoption, zonename, 'halt']); var errorText = stop.stderr.toString().trim(); if (errorText && callback) { callback(500, { message: errorText }); console.log("info ", stop.stdout.toString()); return; } else { if (callback) { callback(204, { message: "Container Id:" + z.uuid + "stopped" }); return null; } if (!quiet) console.log(chalk `${zonename} stopped [{green.bold OK}] `); } return stop.stdout.toString(); } function reboot(zonename, callback) { if (!isAbletoexec()) { console.log( "You must be root or use an account with Primary Administrator Role to Activate execute this action" ); return null; } var zoption = '-z'; var z; var reboot; if (validator.isUUID(zonename)) { z = getdata(zonename, zonename); } else { z = getdata(zonename); } if (z == null) { console.log("Error zone does not exists"); if (callback) { callback(404, { message: "No such container: " + zonename }); } return; } if (z.state == "running") { if (z.brand == 'bhyve' || z.brand == 'kvm') { var path = z['zone-path'] + '/root/tmp'; var vncpid = spawnSync('pgrep', ['-f', path]); var pid = vncpid.stdout.toString(); if (vncpid.error != null) { console.log(`Error restarting vnc`); } vncpid = spawnSync('pfexec', ['kill', '-9', pid]); if (vncpid.error != null) { console.log(`Error killing vnc pid : {pid}`); } } reboot = spawnSync('pfexec', ['zoneadm', zoption, zonename, 'reboot' ]); var errorText = reboot.stderr.toString().trim(); if (errorText) { console.log("info ", errorText); if (callback) { callback(500, { message: errorText }); } } else { console.log(chalk `zone: ${zonename} rebooted [{green.bold OK}] `); if (callback) { callback(204, { message: "Container Id:" + z.uuid + " Restarted" }); return reboot.status; } } return reboot.status; } else { console.log("Error vm is not running"); if (callback) { callback(204, { message: "Container Id:" + z.uuid + "Already running" }); return reboot.status; } } } function install(zonename, brand, uuid, zone_spec) { var iz; if (brand == 'lx' || brand == 'illumos') { var img; if (brand != 'illumos' && zone_spec.docker) { var tags = zone_spec.docker; if (tags.length != 2) { console.log("docker tag setup incorrectly: should be for example library/alpine latest " + zone_spec.docker); return -1; } debug.enabled && console.log("docker tags installing ", tags); img = zfs.docker_pull(tags[0], tags[1]); if (debug.enabled) { console.log("docker image pulled: " + img); } //DILOS: must move omnios brands(5) to dilos. iz = spawnSync('pfexec', ['zoneadm', '-z', zonename, 'install', '-t', img ], { shell: true }); // At this point the zone should be in stated configured/installed zone_status = ''; while (zone_status != "installed") { if (validator.isUUID(zonename)) { z = getdata(zonename, zonename); } else { z = getdata(zonename); } zone_status = z.state } } else { let option = '-s'; if (validator.isUUID(uuid)) { img = zfs.ZCAGE.IMAGES + '/' + uuid + '.zss.gz'; } else { img = zfs.ZCAGE.IMAGES + '/' + uuid; option = '-t'; } iz = spawnSync('pfexec', ['zoneadm', '-z', zonename, 'install', option, img ], { shell: true }); } var errorText = iz.stderr.toString().trim(); if (errorText) { console.log("Error installing " + errorText); return iz.status; } } else { if (zone_spec && zone_spec['with-image'] && brand != 'kvm' && brand != 'bhyve') { if (debug.enabled) console.log("installing ", zonename); let option = '-s'; if (validator.isUUID(uuid)) { img = zfs.ZCAGE.IMAGES + '/' + uuid + '.zss.gz'; } else { img = zfs.ZCAGE.IMAGES + '/' + uuid; option = '-t'; } if (debug.enabled) console.log("installing img", img); iz = spawnSync('pfexec', ['zoneadm', '-z', zonename, 'install', option, img ], { shell: true }); var errorText = iz.stderr.toString().trim(); if (errorText) { console.log("Error installing " + errorText); return iz.status; } } else { if (debug.enabled) console.log("installing ", zonename); if (brand == "dpkg") console.log("Installation of dpkg zone will take a couple of minutes...", zonename); iz = spawnSync('pfexec', ['zoneadm', '-z', zonename, 'install']); var errorText = iz.stderr.toString().trim(); if (errorText) { console.log("Error installing ", iz.stderr.toString(), iz.stdout.toString()); return iz.status; } } } if (debug.enabled) console.log("zoneadm: install returned ", iz.stderr.toString(), iz.stdout.toString()); if ((brand == 'bhyve' || brand == 'kvm') && zone_spec['with-image']) { img = zfs.ZCAGE.IMAGES + '/' + uuid; if (zone_spec['with-image'].toUpperCase().indexOf("CLOUD") > -1) { zone_spec['uses-cloud-init'] = true; /* Ubuntu does not use eth0 */ zone_spec.cloud_nic = 'eth0'; ubuntu_distros.forEach(function(d) { if (zone_spec['with-image'].includes(d)) { zone_spec.cloud_nic = 'enp0s6'; } }) } if (debug.enabled) { console.log("vm setting image", JSON.stringify(zone_spec, null, 4)); console.log("vm image used for zvol creation: " + img); console.log("vm zvol size = " + zone_spec.rctl.quota); console.log(" is cloud init = " + zone_spec['uses-cloud-init']); } iz = spawnSync('pfexec', ['bhyve-zvol.sh', img, zone_spec.rctl.quota, zone_spec.vm_metadata['disk'] ]); var errorText = iz.stderr.toString().trim(); if (debug.enabled) console.log("bhyve-zvol stdout:" + iz.stdout.toString() + "stderr: " + iz.stderr.toString()); if (errorText) { console.log("Converting to raw " + errorText); return iz.status; } if (zone_spec['uses-cloud-init'] == true) { if (!zone_spec.udata) { console.log("This seems to be a cloud-init image," + " but --udata was not provided\nNo setup being done"); } else { iz = spawnSync('pfexec', ['cloud-init.sh', JSON.stringify(zone_spec, null, 4)]); if (debug.enabled) console.log("cloud-init stdout:" + iz.stdout.toString() + "stderr: " + iz.stderr.toString()); var errorText = iz.stderr.toString().trim(); if (errorText) { console.log("Setting up cloud-init data " + errorText); return iz.status; } } } } return iz.status; } function uninstall(zonename) { var uz = spawnSync('pfexec', ['zoneadm', '-z', zonename, 'uninstall']); if (uz.error != null) console.log("Error uninstalling ", uz.stderr.toString()); return uz.status; } function create(zonename, zone_spec) { var script = spec2script(zone_spec); if (debug.enabled) { console.log("zone_spec:", JSON.stringify(zone_spec, null, 4)); console.log("spec2script:", script); } var status = -1; if (script != null) { var zonecfg = spawnSync('pfexec', ['zonecfg', '-z', zonename, script]); if (debug.enabled) console.log("configuring zone", zonecfg.stdout.toString(), zonecfg.stderr.toString()); if (zonecfg.err != null) { console.log("Error creating", zonecfg.stderr.toString()); return null; } if (zone_spec.brand == 'lx' || zone_spec.brand == 'bhyve' || zone_spec.brand == 'kvm' || zone_spec.brand == 'illumos') { status = install(zonename, zone_spec.brand, zone_spec['with-image'], zone_spec); if (status != 0) { console.log("Error: Installing zone", zonename); return null; } } else { status = install(zonename, zone_spec.brand); if (status != 0) { console.log("Error: Installing zone", zonename); return null; } } if (zone_spec.brand != 'lx' && zone_spec.brand != 'bhyve' && zone_spec.brand != 'kvm') { status = setupzone(zonename, zone_spec); if (status != 0) { console.log("Zone postsetup failed= ", status); return status; } halt(zonename, null, true); } if ("quota" in zone_spec.rctl) { var cmd = "quota=" + zone_spec.quota + " "; var ds = zfs.GetPool() + zone_spec.zonepath; if (debug.enabled) console.log("Setting quota=%s on dataset %s", zone_spec.rctl.quota, ds); var quota = spawnSync('pfexec', ['zfs', 'set', 'quota=' + zone_spec.rctl.quota, ds ]); if (debug.enabled) console.log("setting zfs quota", quota.stderr.toString()); } } return status; } function setupzone(zonename, zone_spec) { var status = 0; var zone_status = ' '; switch (zone_spec.brand) { case "sparse": case "pkgsrc": status = start(zonename, null, null, false); if (status != 0) { console.log("Error: Starting zone", zonename); } while (zone_status != "running") { if (validator.isUUID(zonename)) { z = getdata(zonename, zonename); } else { z = getdata(zonename); } zone_status = z.state } /* zone must be up to take commands */ var setup = genpostscript(zone_spec); status = exec(zonename, "svcs svc:/milestone/multi-user | grep online"); while (status != 0) { status = exec(zonename, "svcs svc:/milestone/multi-user | grep online"); } exec(zonename, setup); break; case "illumos": zone_status = "installing"; while (zone_status != "installed") { if (validator.isUUID(zonename)) { z = getdata(zonename, zonename); } else { z = getdata(zonename); } zone_status = z.state } postshellcmd = ['rm', '-rf', z['zone-path'] + "/root/ccs"]; out = fixup_dataset(zonename, postshellcmd); if (out.status != 0) return process.exit(); postshellcmd = ['rm', '-rf', z['zone-path'] + "/root/config"]; fixup_dataset(zonename, postshellcmd); postshellcmd = ['rm', '-rf', z['zone-path'] + "/root/cores"]; fixup_dataset(zonename, postshellcmd); postshellcmd = ['rm', '-rf', z['zone-path'] + "/root/dev"]; fixup_dataset(zonename, postshellcmd); postshellcmd = ['rm', '-rf', z['zone-path'] + "/root/local"]; fixup_dataset(zonename, postshellcmd); postshellcmd = ['rm', '-rf', z['zone-path'] + "/root/site"]; fixup_dataset(zonename, postshellcmd); postshellcmd = ['rm', '-rf', z['zone-path'] + "/root/lastbooted"]; fixup_dataset(zonename, postshellcmd); postshellcmd = ['mv', z['zone-path'] + "/root/root", z['zone-path'] + "/root/old"]; fixup_dataset(zonename, postshellcmd); postshellcmd = ['mv', z['zone-path'] + "/root/old/*", z['zone-path'] + "/root/"]; fixup_dataset(zonename, postshellcmd); postshellcmd = ['rm', '-rf', z['zone-path'] + "/root/old"]; fixup_dataset(zonename, postshellcmd); postshellcmd = ['chmod', '755', z['zone-path'] + "/*"]; fixup_dataset(zonename, postshellcmd); status = start(zonename, null, null, false); if (status != 0) { console.log("Error: Starting zone", zonename); } while (zone_status != "running") { if (validator.isUUID(zonename)) { z = getdata(zonename, zonename); } else { z = getdata(zonename); } zone_status = z.state } var setup = genpostscript(zone_spec); /* ipadm must be online to create interfaces */ status = exec(zonename, "svcs svc:/network/ip-interface-management:default | grep online", out); while (status != 0) { status = exec(zonename, "svcs svc:/network/ip-interface-management:default | grep online", out); } exec(zonename, setup); break; default: break; } return status; } function create_zone_spec(resources) { var spec = { zonepath: "", brand: "", 'ip-type': "exclusive", 'dns-domain': "", resolvers: ["8.8.8.8", "8.8.8.4"], autoboot: false }; if (resources != null && ("net", "brand" in resources)) { Object.keys(resources).forEach(function(key) { spec[key] = resources[key]; }); } if (spec.brand === "bhyve" || spec.brand === "kvm") { var vm_metadata = { acpi: 'on', netif: 'virtio-net-viona', type: 'generic', vcpus: "1", ram: "2G" }; if (spec.brand === "bhyve") { vm_metadata.bootrom = "BHYVE"; vm_metadata.hostbridge = "intel"; vm_metadata.vnc = "unix=/tmp/" + uuidv4() + ".vnc"; } if (spec.brand === "kvm") { vm_metadata.vnc = "on"; if (debug.enabled) { console.log("zone spec ram: " + spec.ram + "vm_metadata :" + vm_metadata.ram); } if (resources.vm_metadata.ram.includes('gb')) { resources.vm_metadata.ram = resources.vm_metadata.ram.replace('gb', 'G'); } else if (resources.vm_metadata.ram.includes('mb')) { resources.vm_metadata.ram = resources.vm_metadata.ram.replace('mb', 'M'); } if (debug.enabled) { console.log("kvm changing gb|mb to G|M current ram: " + spec.ram + "vm_metadata :" + vm_metadata.ram); } } spec.vm_metadata = vm_metadata; Object.keys(resources.vm_metadata).forEach(function(key) { if (resources.vm_metadata[key] != undefined) { spec.vm_metadata[key] = resources.vm_metadata[key]; } }); } if (debug.enabled) { console.log("spec", JSON.stringify(spec, null, 4)); console.log("VM", JSON.stringify(spec.vm_metadata, null, 4)); } return spec; } function spec2script(spec) { var script = ""; script = "create;"; if (spec.brand == 'lx') { script += "add attr;set name=kernel-version;set type=string;set value=3.16.0;end;"; } if (spec.brand == 'illumos') { script += "add fs; set dir=" + "/usr" + ";set special=" + "/usr" + "; set type=lofs;add options ro;end;" script += "add fs; set dir=" + "/sbin" + ";set special=" + "/sbin" + "; set type=lofs;add options ro;end;" script += "add fs; set dir=" + "/lib" + ";set special=" + "/lib" + "; set type=lofs;add options ro;end;" } Object.keys(spec).forEach(function(key) { switch (key) { case "net": spec.net.forEach(function(e) { script += " add net ;"; if (e.address == 'dhcp') { script += "set physical=" + e.physical + ";"; if (spec.brand == 'lx') { script += "add property (name=ip,value=\"dhcp\");"; script += "add property (name=primary,value=\"true\");"; script += "add property (name=ips,value=\"dhcp\");"; script += "add property (name=primary,value=\"true\");"; } script += " end;"; } else { script += "set physical=" + e.physical + ";"; var prefix = ip.IPv4.parse(e.netmask.toString()) .prefixLengthFromSubnetMask(); var addr = e.address + "/" + prefix + ";"; script += "set allowed-address=" + addr + ";"; script += "set defrouter=" + e.gateway + ";"; script += " end;"; if (spec.brand == 'illumos') { postshellcmd = "\"ipadm create-if " + e.physical + ";"; prefix = ip.IPv4.parse(e.netmask).prefixLengthFromSubnetMask() postshellcmd += "ipadm create-addr -t -T static -a local=" + e.address + "/" + prefix + " " + e.physical + "/v4 " + ";"; postshellcmd += " route add default " + e.gateway + "\""; script += "add attr;set name=ENTRYPOINT; set type=string; set value=" + postshellcmd + ";end;"; } } }); break; case "resolvers": script += "add attr;set name=resolvers;set type=string;set value="; for (var i = 0, len = spec.resolvers.length; i < len; i++) { if (i + 1 >= len) script += spec.resolvers[i] + ";"; else script += spec.resolvers[i] + ","; } script += "end;"; break; case "dns-domain": break; case "rctl": if ("dedicated-cpu" in spec.rctl) { script += "add dedicated-cpu;set ncpus=" + spec.rctl["dedicated-cpu"] + ";"; if ("importance" in spec.rctl) { script += "set importance=" + spec.rctl.importance + ";"; } script += "end; "; } Object.keys(spec.rctl).forEach(function(key) { if (key == 'quota') { script += "add attr; set name=quota;set type=string;" + " set value=" + spec.rctl.quota + ";end;"; } else if (key == "max-physical-memory" || key == "max-locked-memory" || key == "max-swap" || key == "max-lwps" || key == "max-shm-ids" || key == "max-msg-ids" || key == "max-shm-memory") { script += "add rctl;"; script += "set name=zone." + key + ";" + "add value (priv=privileged,limit=" + spec.rctl[key] + ",action=deny);"; script += "end; "; } else if (key == "cpu-shares") { script += "set cpu-shares=" + spec.rctl["cpu-shares"] + ";" } }); break; case "vm_metadata": Object.keys(spec.vm_metadata).forEach(function(key) { switch (key) { case 'vnc': script += "add attr; set type=string;set name=" + key + "; set value=" + "\"" + spec.vm_metadata[key] + "\"" + " ;end;"; script += "add attr; set type=string;" + "set name=vnc-port;" + "set value=" + (Math.floor(Math.random() * (5999 - 5900) + 5900)).toString() + ";end;;"; break; case 'ram': case 'bootrom': case 'console': case 'hostbridge': case 'cdrom': case 'vcpus': case 'bootorder': case 'bootdisk': script += "add attr; set type=string;set name=" + key + "; set value=" + "\"" + spec.vm_metadata[key] + "\"" + " ;end;"; break; case 'disk': script += "add device; set match=" + "/dev/zvol/rdsk/" + spec.vm_metadata.bootdisk + " ; end;"; break; case 'fs': script += "add fs; set dir=" + "\"" + spec.vm_metadata[key] + "\"" + ";set special=" + "\"" + spec.vm_metadata[key] + "\"" + "; set type=lofs;add options ro;" + "add options nodevices;end;"; break; case 'device': script += "add device; set match=" + spec.vm_metadata[key] + ";end;"; break; } }); break; default: if (key != 'with-image' && key != 'alias' && key != 'debug' && key != 'docker' && key != 'udata' && key != 'cpu' && key != 'image' && key != "Cmd") script += " set " + key + "=" + spec[key] + ";"; break; } }); script += ";verify; commit;"; if (debug.enabled) console.log(arguments.callee.name, script.split(';')); return script; } /* * This is needed to setup networking in sparse zones * # zoneadm -z omni boot * # zlogin omni * # ipadm create-if omni0 * # ipadm create-addr -T static -a local=x.x.x.x/y omni0/v4 * # echo x.x.x.x > /etc/defaultrouter * # echo nameserver 80.80.80.80 > /etc/resolv.conf * # cp /etc/nsswitch.{dns,conf} * # svcadm restart routing-setup * For illumos branded zones, we need to : * - Add default route : route add default <gatewayip> * - ipadm create-if <physical nic> * - ipadm create-addr -t -T static -a local=1.2.3.4/24 tt0/v4 */ function genpostscript(zone_spec) { var postshellcmd = ""; switch (zone_spec.brand) { case "sparse": case "pkgsrc": case "lipkg": case "ipkg": case "bhyve": case "kvm": case "illumos": postshellcmd += " sleep 1 ;"; var resolvers = zone_spec.resolvers.toString().split(","); for (var i = 0, len = resolvers.length; i < len; i++) { postshellcmd += " echo nameserver " + resolvers[i] + " >> /etc/resolv.conf && "; } postshellcmd += "echo " + zone_spec.alias + " > /etc/nodename ; "; postshellcmd += " sleep 3 ;"; // Setup dhcp on interface if requested if (zone_spec.net) zone_spec.net.forEach(function(net) { if (net.address == "dhcp") { postshellcmd += " ipadm create-if " + net.physical + ";"; postshellcmd += " ipadm create-addr -T dhcp " + net.physical + "/v4 " + ";"; } if (zone_spec.brand == "illumos" && net.address != "dhcp") { postshellcmd += " ipadm create-if " + net.physical + ";"; prefix = ip.IPv4.parse(net.netmask).prefixLengthFromSubnetMask() postshellcmd += " ipadm create-addr -t -T static -a local=" + net.address + "/" + prefix + " " + net.physical + "/v4 " + ";"; postshellcmd += " route add default " + net.gateway + ";"; } }); postshellcmd += "sleep 1; "; postshellcmd += " cp /etc/nsswitch.{dns,conf} && svcadm restart routing-setup "; break; default: postshellcmd = null; } if (debug.enabled) console.log("postshellcmd :" + postshellcmd); return postshellcmd; } /* * Parses --ipaddr="vnic0|192.168.1.1/24|gateway,.." * to a network json tag */ function ipaddrcmd2netobject(ipaddr) { if (debug.enabled) console.log(ipaddr); var networks = ipaddr; var net = []; for (var i = 0, l = networks.length; i < l; i++) { var o = {}; addrnic = networks[i].split("|"); if (addrnic.length < 2) { console.log( 'not enough parameters in --net=vnic|vm-ip|vm-gateway-ip' ); return null; } o.physical = addrnic[0]; var address = addrnic[1].split("/"); o.address = address[0]; try { if (o.address != "dhcp") { if (ip.parse(o.address).kind() == "ipv4") { o.netmask = ip.IPv4.subnetMaskFromPrefixLength(address[1]).toString(); } else { o.netmask = ip.IPv6.subnetMaskFromPrefixLength(address[1]).toString(); } } } catch (ex) { console.log("zone definition is invalid", ex.message); return null; } if (o.address != "dhcp") o.gateway = addrnic[2]; net.push(o); } if (debug.enabled) console.log("network is ", net); return net; } function build(createOptions) { if (createOptions.debug == true) debug.enabled = true; if (debug.enabled) console.log("CreateOptions ", JSON.stringify(createOptions, null, 4)); if (!isAbletoexec()) { console.log( "You must have Primary Administrator Privileges to create zones"); return null; } var zname; if ('docker' in createOptions) createOptions.brand = 'lx'; if ("net" in createOptions || "docker" in createOptions) { if ('net' in createOptions) { if (!vnic_exists(createOptions)) { return null; } createOptions.net = ipaddrcmd2netobject(createOptions.net); if (createOptions.net === null) { return null; } } if ("alias" in createOptions) { zname = createOptions.alias; delete createOptions.alias; } else { zname = dockerNames.getRandomName(true); } createOptions.zonepath = zfs.ZCAGE.VMS + '/' + zname; createOptions.alias = zname; if ("ram" in createOptions) { createOptions.rctl = addrctl(createOptions); if (createOptions.rctl == null) { return null; } } if (createOptions['with-image'] && createOptions.disk) { console.log("--with-image and --disk are mutually exclusive"); return null; } if (createOptions.brand === "bhyve" || createOptions.brand === "kvm") { if (createOptions['with-image']) createOptions.disk = zfs.GetPool() + '/' + zname; createOptions.vm_metadata = { ram: createOptions.ram, vcpus: createOptions.cpu, disk: createOptions.disk, bootdisk: createOptions.disk, cdrom: createOptions.cdrom, hostbridge: createOptions.hostbridge, fs: createOptions.cdrom, }; if (debug.enabled) { console.log("vm data", JSON.stringify(createOptions.vm_metadata, null, 4)); } delete createOptions.cpu; delete createOptions.cdrom; delete createOptions.disk; delete createOptions.bootdisk; } delete createOptions.ram; delete createOptions.quota; var zone = getdata(zname); if (zone != null) { console.log("Error : alias already exists"); return null; } if (!createOptions.docker && createOptions['with-image'] && (!fs.existsSync(zfs.ZCAGE.IMAGES + '/' + createOptions['with-image'] + '.zss.gz') && !fs.existsSync(zfs.ZCAGE.IMAGES + '/' + createOptions['with-image']))) { console.log( "There is no image , first execute: pfexec zcage pull --image <uuid> or zcage fetch <url>." ); return null; } if (createOptions.brand != 'lx' && createOptions.docker) { console.log("--docker option is only available for lx branded zones"); return null; } if ((createOptions.brand == 'lx' || createOptions.brand == 'illumos') && !("with-image" in createOptions) && ! createOptions.docker) { console.log("--with-image option is required for a lx container"); return null; } if (createOptions.udata && (createOptions.brand != 'bhyve' && createOptions.brand != 'kvm' || !fs.existsSync(createOptions.udata))) { console.log("--udata is only valid for a bhyve or kvm brand"); console.log("--udata needs to be a json file with the following format\n" + ' { "userid ": " joe ", " pubkey ": " ssh-rsa key"}'); return null; } if (debug.enabled) console.log("createOptions: " + JSON.stringify(createOptions, null, 4)); var z = create_zone_spec(createOptions); if (validate_zonespec(z) === null) return null; if (create(zname, z) === 0) { addmeta(zname, false); if (debug.enabled) { console.log('Setting container config'); } if ('docker' in createOptions) { var tags = createOptions.docker; zfs.docker_get_config(tags[0], tags[1], zname, addtr_from_docker_img); } console.log(chalk `${zname} created [{green.bold OK}] `); } else { console.log("Failed creating zone: ", zname); return -1; } if ((createOptions.brand == 'bhyve' || createOptions.brand == 'kvm') && z['uses-cloud-init'] == true) { if (debug.enabled) console.log("Using cloud-init config" + z.zonepath + '/root/config.iso'); start(zname, z.zonepath