From 245739e5d4c509b769e34e2a99bb4c1ba10d52b9 Mon Sep 17 00:00:00 2001 From: Marat Stepanov Date: Wed, 26 Apr 2017 12:26:34 +0000 Subject: [PATCH 01/11] Initial commit after fork --- README_API.md | 43 ++++ package.json | 21 ++ server.js | 542 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 606 insertions(+) create mode 100644 README_API.md create mode 100644 package.json create mode 100755 server.js diff --git a/README_API.md b/README_API.md new file mode 100644 index 0000000..66508ff --- /dev/null +++ b/README_API.md @@ -0,0 +1,43 @@ +# Raspberry Pi Virtualization Server API + +## Installation + +First, follow the README.md to prepare system + +### Install additional Node.js modules + +We assume that server.js script is located in /home/ubuntu/raspberry_virtualization + +``` +cd /home/ubuntu/raspberry_virtualization +npm install nodemon express body-parser ps-node linux-mountutils wait.for mkdirp + +``` + +## Usage +### How to launch +In order to run script in development mode, move to script folder and run + +``` +sudo npm run dev +``` + +In this mode, server is being automatically relaunched every time you modify and file in folder + +Alternatively, you can just invoke: +``` +sudo node /path/to/script/server.js +``` + +### Working with API + +Server listens on 8000 by default. It accepts POST and DELETE methods at /container path and waits for x-www-form-urlencode json message in the following format: +``` +{ name: 'containername' } +``` +for example: +{ name: 'test1' } + + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..cfd2c99 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "api", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "dev": "nodemon server.js" + }, + "author": "", + "license": "ISC", + "dependencies": { + "body-parser": "^1.17.1", + "express": "^4.15.2", + "mongodb": "^2.2.26", + "mountutil" : "https://github.com/mw-white/node-linux-mountutils.git" + }, + "devDependencies": { + "nodemon": "^1.11.0" + } +} diff --git a/server.js b/server.js new file mode 100755 index 0000000..498a5d2 --- /dev/null +++ b/server.js @@ -0,0 +1,542 @@ +var express = require('express'); +var bodyParser = require('body-parser'); +var app = express(); +var port = 8000; +var fs = require('fs'); +var glob = require('glob'); +var ps = require('ps-node'); +var mountutil = require('linux-mountutils'); +var exec = require('child_process').exec; +var execSync = require('child_process').execSync; +var pt = require('path'); +var statvfs = require('statvfs'); +var mknod = require('mknod'); +var argv = require('minimist')(process.argv.slice(2)); +var fuse = require('fuse-bindings'); +var mkdirp = require('mkdirp'); +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: true })) + + +function folderMirroring (original_folder, mirror_folder, fuse_options) { + if (!pt.isAbsolute(original_folder) || !pt.isAbsolute(mirror_folder)){ + console.log("Please use absolute paths!"); + return; + } + console.log("Mounting folder " + original_folder + " in folder " + mirror_folder); + if(fuse_options != undefined) + console.log("Fuse options: " + fuse_options); + + var getattr_function = function(path, cb){ + console.log('getattr(%s)',path); + fs.lstat(pt.join(original_folder, path), function(err, stats){ + if(err){ + //console.log('error: ', err); + cb(fuse[err.code]); + } + else{ + cb(null,stats); + } + }); + } + + var access_function = function(path, mode, cb){ + console.log('access(%s)', path); + fs.access(pt.join(original_folder, path), mode, function(err){ + if(err){ + //console.log('error: ', err); + cb(fuse[err.code]); + } + else{ + cb(0); + } + }); + } + + var readlink_function = function(path, cb){ + console.log('readlink(%s)', path); + fs.readlink(pt.join(original_folder, path), function(err, linkString){ + if(err){ + //console.log('error: ', err); + cb(fuse[err.code]); + } + else{ + cb(null, linkString); + } + }); + } + + var readdir_function = function(path, cb){ + console.log('readdir(%s)', path); + fs.readdir(pt.join(original_folder, path), function(err, files){ + if(err){ + //console.log('error: ', err); + cb(fuse[err.code]); + } + else{ + console.log('files: ', files); + cb(null,files); + } + }); + } + + var mknod_function = function(path, mode, dev, cb){ + console.log('mknod(%s)', path); + mknod(pt.join(original_folder, path), mode, dev, function (err) { + if(err){ + //console.log('error: ', err); + cb(fuse[err.code]); + } + else{ + cb(0); + } + }); + } + + var mkdir_function = function(path, mode, cb){ + console.log('mkdir(%s)', path); + fs.mkdir(pt.join(original_folder, path), mode, function (err) { + if(err){ + //console.log('error: ', err); + cb(fuse[err.code]); + } + else{ + cb(0); + } + }); + } + + var unlink_function = function(path, cb){ + console.log('unlink(%s)', path); + fs.unlink(pt.join(original_folder, path), function (err) { + if(err){ + //console.log('error: ', err); + cb(fuse[err.code]); + } + else{ + cb(0); + } + }); + } + + var rmdir_function = function(path, cb){ + console.log('rmdir(%s)', path); + fs.rmdir(pt.join(original_folder, path), function (err) { + if(err){ + //console.log('error: ', err); + cb(fuse[err.code]); + } + else{ + cb(0); + } + }); + } + + var symlink_function = function(src, dest, cb){ + console.log('symlink(%s,%s)', src, dest); + fs.symlink(pt.join(original_folder, src), pt.join(original_folder, dest), function (err) { + if(err){ + //console.log('error: ', err); + cb(fuse[err.code]); + } + else{ + cb(0); + } + }); + } + + var rename_function = function(src, dest, cb){ + console.log('rename(%s,%s)', src, dest); + fs.rename(pt.join(original_folder, src), pt.join(original_folder, dest), function (err) { + if(err){ + //console.log('error: ', err); + cb(fuse[err.code]); + } + else{ + cb(0); + } + }); + } + + var link_function = function(src, dest, cb){ + console.log('link(%s,%s)', src, dest); + fs.link(pt.join(original_folder, src), pt.join(original_folder, dest), function (err) { + if(err){ + //console.log('error: ', err); + cb(fuse[err.code]); + } + else{ + cb(0); + } + }); + } + + var chmod_function = function(path, mode, cb){ + console.log('chmod(%s)', path); + fs.chmod(pt.join(original_folder, path), mode, function (err) { + if(err){ + //console.log('error: ', err); + cb(fuse[err.code]); + } + else{ + cb(0); + } + }); + } + + var chown_function = function(path, uid, gid, cb){ + console.log('chown(%s)', path); + fs.chown(pt.join(original_folder, path), uid, gid, function (err) { + if(err){ + //console.log('error: ', err); + cb(fuse[err.code]); + } + else{ + cb(0); + } + }); + } + + var truncate_function = function(path, size, cb){ + console.log('truncate(%s)', path); + fs.truncate(pt.join(original_folder, path), size, function (err) { + if(err){ + //console.log('error: ', err); + cb(fuse[err.code]); + } + else{ + cb(0); + } + }); + } + + var utimens_function = function(path, atime, mtime, cb){ + console.log('utimens(%s)', path); + fs.utimes(pt.join(original_folder, path), atime, mtime, function (err) { + if(err){ + //console.log('error: ', err); + cb(fuse[err.code]); + } + else{ + cb(0); + } + }); + } + + var open_function = function(path, flags, cb){ + console.log('open(%s)', path); + fs.open(pt.join(original_folder, path), flags, function (err, fd) { + if(err){ + //console.log('error: ', err); + cb(fuse[err.code]); + } + else{ + cb(null, fd); + } + }); + } + + var read_function = function(path, fd, buffer, length, position, cb){ + console.log('read(%s)', path); + fs.open(pt.join(original_folder, path), 'r', function (err, int_fd) { + if(err){ + //console.log('error: ', err); + cb(fuse[err.code]); + } + else{ + fs.read(int_fd, buffer, 0, length, position, function(err, bytesRead, int_buffer){ + if(err){ + //console.log('error: ', err); + cb(fuse[err.code]); + } + else{ + buffer.copy(int_buffer); + fs.close(int_fd, function(err){ + if(err){ + //console.log('error:', err); + cb(fuse[err.code]); + } + else{ + cb(bytesRead); + } + }); + } + }); + } + }); + } + + var write_function = function(path, fd, buffer, length, position, cb){ + console.log('write(%s)', path); + fs.open(pt.join(original_folder, path), 'r+', function (err, int_fd) { + if(err){ + //console.log('error: ', err); + cb(fuse[err.code]); + } + else{ + fs.write(int_fd, buffer, 0, length, position, function(err, written, int_buffer){ + if(err){ + //console.log('error: ', err); + cb(fuse[err.code]); + } + else{ + fs.close(int_fd, function(err){ + if(err){ + //console.log('error:', err); + cb(fuse[err.code]); + } + else{ + cb(written); + } + }); + } + }); + } + }); + + } + + var getxattr_function = function(path, name, buffer, length, offset, cb){ + console.log('getxattr_function(%s)', path); + fs.lstat(pt.join(original_folder, path), function(err, stats){ + if(err){ + console.log('error: ', err); + cb(fuse[err.code]); + } + else{ + console.log('stats: ', stats); + cb(null,stats); + } + }); + + } + + var statfs_function = function(path, cb){ + console.log('statvfs(%s)', path); + statvfs(pt.join(original_folder, path), function(err, stats){ + if(err){ + //console.log('error: ', err); + cb(fuse[err.code]); + } + else{ + cb(null,stats); + } + }); + } + + fuse.mount(mirror_folder, { + getattr: getattr_function, + access: access_function, + readlink: readlink_function, + readdir: readdir_function, + mknod: mknod_function, //(not available in fs, using mknod module instead) + mkdir: mkdir_function, + unlink: unlink_function, + rmdir: rmdir_function, + symlink: symlink_function, + rename: rename_function, + link: link_function, + chmod: chmod_function, + chown: chown_function, + truncate: truncate_function, + utimens: utimens_function, + open: open_function, + read: read_function, + write: write_function, + statfs: statfs_function, //(not available in fs, using statvfs module instead) + //setxattr: setxattr_function (not available in fs or other implementations) + getxattr: getxattr_function, //(not available in fs or other implementations, using lstat instead) + //listxattr: (not available in fuse-bindings) + //removexattr: (not available in fuse-bindings) + options: fuse_options + }); +} + + +app.post('/container', function (req, res) { + name = req.body.name; + console.log ("Request body: ", req.body); + console.log(`Trying to launch ${name} container...`); + try { + execSync(`lxc launch ubuntu:16.04 ${name}`); + } catch (e) { + console.log ("An error has occured while launching container: ", e.message); + } finally { + console.log (`Launched ${name} container`); + } + + uidstats = fs.statSync(`/var/lib/lxd/containers/${name}/rootfs/`); + uid = uidstats["uid"]; + console.log ("UID: ", uid); + + + try { + var nowhere = execSync(`lxc exec ${name} -- addgroup gpio`); + } catch (e) { + console.log ("An error has occured while adding gpio group: ", e.message); + } finally { + console.log (`Added gpio group in ${name} container `); + } + try { + var nowhere = execSync(`lxc exec ${name} -- usermod -a -G gpio ubuntu`); + } catch (e) { + console.log ("An error has occured while adding ubuntu user to gpio group: ", e.message); + } finally { + console.log (`Added ubuntu user to gpio group in ${name} container`); + } + + output = (execSync('lxc exec ' + name + ' -- cat /etc/group')).toString(); + gid = parseInt(output.match(/gpio:x:([0-9]+):.*/i)[1]) + parseInt(uid); + console.log ("GID: ", gid); + if (!fs.existsSync(`/gpio_mnt`)){ + fs.mkdirSync(`/gpio_mnt`); + } + + if (!fs.existsSync(`/gpio_mnt/${name}`)){ + fs.mkdirSync(`/gpio_mnt/${name}`); + } + //fs.chmodSync does not perform recursive chmod, therefore using mkdirp library + execSync(`chmod 777 /gpio_mnt/`); + try { + mkdirp.sync(`/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio`); + } catch (e) { + console.log ("Error: ", e.message); + } + try { + mkdirp.sync(`/gpio_mnt/${name}/sys/class/gpio`); + } catch (e) { + console.log ("Error: ", e.message); + } + + + var nowhere = execSync(`sudo chown ${uid}.${gid} -R /gpio_mnt/${name}/sys/`); + try { + var nowhere = execSync(`lxc exec ${name} -- mkdir -p /gpio_mnt/sys/class/gpio`); + } catch (e) { + console.log ("Error: ", e.message); + } + try { + var nowhere = execSync(`lxc exec ${name} -- mkdir -p /gpio_mnt/sys/devices/platform/soc/3f200000.gpio`); + } catch (e) { + console.log ("Error: ", e.message); + } + + try { + var nowhere = execSync(`lxc config device add ${name} gpio disk source=/gpio_mnt/${name}/sys/class/gpio path=/gpio_mnt/sys/class/gpio`); + } catch (e) { + console.log ("Error: ", e.message); + } + try { + var nowhere = execSync(`lxc config device add ${name} devices disk source=/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio path=/gpio_mnt/sys/devices/platform/soc/3f200000.gpio`); + } catch (e) { + console.log ("Error: ", e.message); + } + + folderMirroring (`/sys/devices/platform/soc/3f200000.gpio`, `/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio`, `uid=${uid} gid=${gid} allow_other`); + folderMirroring (`/sys/class/gpio`, `/gpio_mnt/${name}/sys/class/gpio`, `uid=${uid} gid=${gid} allow_other`); + + res.send(`ok`); +}); + +app.delete('/container', function (req, res) { + name = req.body.name; + console.log(name); + +// path1 = `/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio` +// var mounted = mountutil.isMounted(`/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio`,true); +// console.log (mounted.mounted); +// if((mountutil.isMounted(`/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio`,true)).mounted) { +// console.log (`Trying to unmount ${path1}...`); + var mountpoints = [`/gpio_mnt/${req.body.name}/sys/devices/platform/soc/3f200000.gpio`,`/gpio_mnt/${req.body.name}/sys/class/gpio`]; + mountpoints.forEach (function(mountPath,i,mountpoints) { + console.log (mountPath); + fuse.unmount(mountPath, function (err) { + if (err) { + console.log('filesystem at ' + mountPath + ' not unmounted', err) + } else { + console.log('filesystem at ' + mountPath + ' unmounted') + } + }); + }); + +//unmount devices + + + +// try { +// execSync(`umount ${mountpoint}`) +// } catch (e) { +// console.log(`An error has occured during ${mountpoint} mountpoint removal: ${e.error}`); +// } finally { +// console.log(`Successfully unmounted ${mountpoint} mountpoint`); +// } +// /*mountutil.umount(mountpoint, false, { "removeDir": true }, function(result) { +// if (result.error) { +// console.log('Error during ' + mountpoint + 'unmounting:' + result.error); +// } else { +// console.log(`successfully unmounted ${mountpoint}`); +// } +// });*/ +// }); + + //remove lxc container devices + console.log("11"); + try { + execSync(`lxc config device remove ${name} gpio disk`) + } catch (e) { + console.log(`An error has occured during gpio disk removal from ${name}: ${e.error}`); + } finally { + console.log(`gpio disk was removed from ${name}`); + } + + try { + execSync(`lxc config device remove ${name} devices disk`) + } catch (e) { + console.log(`An error has occured during disk removal from ${name}: ${e.error}`); + } finally { + console.log(`disk was removed from ${name}`); + } + //remove gpoi_mnt folder for container + var path = "/gpio_mnt/" + name; + console.log(`removing ${path} folder...`); + + var deleteFolderRecursive = function(path) { + if( fs.existsSync(path) ) { + fs.readdirSync(path).forEach(function(file,index){ + var curPath = path + "/" + file; + if(fs.lstatSync(curPath).isDirectory()) { // recurse + deleteFolderRecursive(curPath); + } else { // delete file + fs.unlinkSync(curPath); + console.log(curPath + " deleted"); + } + }); + fs.rmdirSync(path); + console.log(path + " deleted"); + } + }; + try { + execSync(`rm -rf /gpio_mnt/${name}`); + } catch (e) { + console.log(`An error has occured during /gpio_mnt/${name} folder removal: ${e.error}`); + } finally { + console.log(`/gpio_mnt/${name} folder was removed`); + } + + + //remove container + console.log("Removing container..."); + try { + execSync(`lxc delete --force ${name}`); + } catch (e){ + console.log(`An error has occured during ${name} removal: ${e.message}`); + } finally { + console.log(`${name} was removed`); + } + res.send("ok"); +}); + +app.listen(port, () => { + console.log('We are live on ' + port); +}); + From 3971538e11566e69a15073c6d8e2ca2b2f0181f3 Mon Sep 17 00:00:00 2001 From: Marat Stepanov Date: Wed, 26 Apr 2017 12:32:09 +0000 Subject: [PATCH 02/11] Minor fix --- README_API.md | 2 +- package.json | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/README_API.md b/README_API.md index 66508ff..520540f 100644 --- a/README_API.md +++ b/README_API.md @@ -10,7 +10,7 @@ We assume that server.js script is located in /home/ubuntu/raspberry_virtualizat ``` cd /home/ubuntu/raspberry_virtualization -npm install nodemon express body-parser ps-node linux-mountutils wait.for mkdirp +npm install nodemon express body-parser ps-node linux-mountutils mkdirp ``` diff --git a/package.json b/package.json index cfd2c99..0660a8f 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,6 @@ "dependencies": { "body-parser": "^1.17.1", "express": "^4.15.2", - "mongodb": "^2.2.26", "mountutil" : "https://github.com/mw-white/node-linux-mountutils.git" }, "devDependencies": { From 379c4060d6dafdfa22a4e124f4cb037092dc6740 Mon Sep 17 00:00:00 2001 From: Marat Stepanov Date: Wed, 26 Apr 2017 15:01:39 +0000 Subject: [PATCH 03/11] added a lot of comments --- server.js | 127 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 66 insertions(+), 61 deletions(-) diff --git a/server.js b/server.js index 498a5d2..e6ac056 100755 --- a/server.js +++ b/server.js @@ -1,7 +1,8 @@ +// express is used to handle API routes var express = require('express'); var bodyParser = require('body-parser'); +// via app variable we'll be using express module var app = express(); -var port = 8000; var fs = require('fs'); var glob = require('glob'); var ps = require('ps-node'); @@ -14,10 +15,14 @@ var mknod = require('mknod'); var argv = require('minimist')(process.argv.slice(2)); var fuse = require('fuse-bindings'); var mkdirp = require('mkdirp'); +// using bodyParster.json in order to parse JSON strings app.use(bodyParser.json()); +// using bodyParser.urlenconded - without it express module won't be able to understand x-www-form-urlencoded app.use(bodyParser.urlencoded({ extended: true })) +// specifying port number on which application will be listening +var port = 8000; - +// function is full copy of node-folder-mirroring script, just omitted initial checks of arguments count ant correctness function folderMirroring (original_folder, mirror_folder, fuse_options) { if (!pt.isAbsolute(original_folder) || !pt.isAbsolute(mirror_folder)){ console.log("Please use absolute paths!"); @@ -352,12 +357,23 @@ function folderMirroring (original_folder, mirror_folder, fuse_options) { }); } - +// route invoked by POST method to /container path (i.e. http://server:port/container) +// req variable contains initial request came from client +// res variable answer to be sent to client app.post('/container', function (req, res) { + // req.body is client request's body, which is in JSON format. It's beign automatically parsed by bodyParser.json() + // req.body.name value is taken from parsed JSON and assigned to name variable in order to use it further name = req.body.name; + // All console.log lines are added in debugging purposes console.log ("Request body: ", req.body); console.log(`Trying to launch ${name} container...`); + // using try construction where it is possible in order to make code more reliable try { + // there are few libraries to manage lxc directly from node.js, but they do not allow to configure containers + // therefore we use exesSync, which invokes bash shell. + // execSync is synchronous exec - difference is that execSync waits for command to finish, while exec does not + // if we use exec, all further code will be invoked before container actually starts + // by this line we launch container. Name is taken from client's JSON execSync(`lxc launch ubuntu:16.04 ${name}`); } catch (e) { console.log ("An error has occured while launching container: ", e.message); @@ -365,38 +381,44 @@ app.post('/container', function (req, res) { console.log (`Launched ${name} container`); } + // taking uid of file's owner using fs.statSync method uidstats = fs.statSync(`/var/lib/lxd/containers/${name}/rootfs/`); uid = uidstats["uid"]; console.log ("UID: ", uid); try { - var nowhere = execSync(`lxc exec ${name} -- addgroup gpio`); + // Adding gpio group to container. + execSync(`lxc exec ${name} -- addgroup gpio`); } catch (e) { console.log ("An error has occured while adding gpio group: ", e.message); } finally { console.log (`Added gpio group in ${name} container `); } try { - var nowhere = execSync(`lxc exec ${name} -- usermod -a -G gpio ubuntu`); + // adding ubuntu user to gpio group in container + execSync(`lxc exec ${name} -- usermod -a -G gpio ubuntu`); } catch (e) { console.log ("An error has occured while adding ubuntu user to gpio group: ", e.message); } finally { console.log (`Added ubuntu user to gpio group in ${name} container`); } - + // getting gpio group's ID in containter and suming it with rootfs folder's uid. It will be used further while calling folder mirroring function output = (execSync('lxc exec ' + name + ' -- cat /etc/group')).toString(); gid = parseInt(output.match(/gpio:x:([0-9]+):.*/i)[1]) + parseInt(uid); console.log ("GID: ", gid); - if (!fs.existsSync(`/gpio_mnt`)){ - fs.mkdirSync(`/gpio_mnt`); - } - + // checking if /gpio_mnt/${name} exists and if not - create it using mkdirp.sync + // standard fs.mkdirSync does not fit because there is no way to make folder recursively if (!fs.existsSync(`/gpio_mnt/${name}`)){ - fs.mkdirSync(`/gpio_mnt/${name}`); + try { + mkdirp.sync(`/gpio_mnt/${name}`); + } catch (e) { + console.log ("Error: ", e.message); + } } - //fs.chmodSync does not perform recursive chmod, therefore using mkdirp library - execSync(`chmod 777 /gpio_mnt/`); + // fs.chmodSync does not perform recursive chmod, therefore using execSync + chmod with -R flag + execSync(`chmod 777 -R /gpio_mnt/`); + // creating folders using mkdirp.sync for pins mapping try { mkdirp.sync(`/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio`); } catch (e) { @@ -407,50 +429,56 @@ app.post('/container', function (req, res) { } catch (e) { console.log ("Error: ", e.message); } - - - var nowhere = execSync(`sudo chown ${uid}.${gid} -R /gpio_mnt/${name}/sys/`); + // there is no fs.chown with recursion - using execSync + chown -R + execSync(`chown ${uid}.${gid} -R /gpio_mnt/${name}/sys/`); + // creating folder in container, which will be mapped to parent's appropriate folder try { - var nowhere = execSync(`lxc exec ${name} -- mkdir -p /gpio_mnt/sys/class/gpio`); + execSync(`lxc exec ${name} -- mkdir -p /gpio_mnt/sys/class/gpio`); } catch (e) { console.log ("Error: ", e.message); } + // creating folder in container, which will be mapped to parent's appropriate folder try { - var nowhere = execSync(`lxc exec ${name} -- mkdir -p /gpio_mnt/sys/devices/platform/soc/3f200000.gpio`); + execSync(`lxc exec ${name} -- mkdir -p /gpio_mnt/sys/devices/platform/soc/3f200000.gpio`); } catch (e) { console.log ("Error: ", e.message); } - + // mapping parent's folders to appropriate container's folders try { - var nowhere = execSync(`lxc config device add ${name} gpio disk source=/gpio_mnt/${name}/sys/class/gpio path=/gpio_mnt/sys/class/gpio`); + execSync(`lxc config device add ${name} gpio disk source=/gpio_mnt/${name}/sys/class/gpio path=/gpio_mnt/sys/class/gpio`); } catch (e) { console.log ("Error: ", e.message); } + // mapping parent's folders to appropriate container's folders try { - var nowhere = execSync(`lxc config device add ${name} devices disk source=/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio path=/gpio_mnt/sys/devices/platform/soc/3f200000.gpio`); + execSync(`lxc config device add ${name} devices disk source=/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio path=/gpio_mnt/sys/devices/platform/soc/3f200000.gpio`); } catch (e) { console.log ("Error: ", e.message); } - + // calling functions to reflect changes between parent and container's folders using FUSE folderMirroring (`/sys/devices/platform/soc/3f200000.gpio`, `/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio`, `uid=${uid} gid=${gid} allow_other`); folderMirroring (`/sys/class/gpio`, `/gpio_mnt/${name}/sys/class/gpio`, `uid=${uid} gid=${gid} allow_other`); - + // respond to client. Currenlty no logic, which tracks actual state of all pin mapping processes. Therefore, always answer "ok" res.send(`ok`); }); +// route invoked by DELETE method to /container path (i.e. http://server:port/container) +// req variable contains initial request came from client +// res variable answer to be sent to client app.delete('/container', function (req, res) { + // req.body is client request's body, which is in JSON format. It's beign automatically parsed by bodyParser.json() + // req.body.name value is taken from parsed JSON and assigned to name variable in order to use it further name = req.body.name; + // All console.log lines are added in debugging purposes console.log(name); - -// path1 = `/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio` -// var mounted = mountutil.isMounted(`/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio`,true); -// console.log (mounted.mounted); -// if((mountutil.isMounted(`/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio`,true)).mounted) { -// console.log (`Trying to unmount ${path1}...`); + // creating array with list of mounting points to unmount, in order to simplify code var mountpoints = [`/gpio_mnt/${req.body.name}/sys/devices/platform/soc/3f200000.gpio`,`/gpio_mnt/${req.body.name}/sys/class/gpio`]; + // using forEach() method in order to go through all array elements and unmount them mountpoints.forEach (function(mountPath,i,mountpoints) { console.log (mountPath); + // using fuse.unmount(), which actually removes FUSE mounting fuse.unmount(mountPath, function (err) { + // this is callback function, which handles errors if (err) { console.log('filesystem at ' + mountPath + ' not unmounted', err) } else { @@ -459,28 +487,11 @@ app.delete('/container', function (req, res) { }); }); -//unmount devices - - - -// try { -// execSync(`umount ${mountpoint}`) -// } catch (e) { -// console.log(`An error has occured during ${mountpoint} mountpoint removal: ${e.error}`); -// } finally { -// console.log(`Successfully unmounted ${mountpoint} mountpoint`); -// } -// /*mountutil.umount(mountpoint, false, { "removeDir": true }, function(result) { -// if (result.error) { -// console.log('Error during ' + mountpoint + 'unmounting:' + result.error); -// } else { -// console.log(`successfully unmounted ${mountpoint}`); -// } -// });*/ -// }); - - //remove lxc container devices - console.log("11"); + // there are few libraries to manage lxc directly from node.js, but they do not allow to configure containers + // therefore we use exesSync, which invokes bash shell. + // execSync is synchronous exec - difference is that execSync waits for command to finish, while exec does not + // if we use exec, all further code will be invoked before container actually starts + // by these 2 try constructions we reconfigre container and remove gpio mountings. Name is taken from client's JSON try { execSync(`lxc config device remove ${name} gpio disk`) } catch (e) { @@ -496,7 +507,7 @@ app.delete('/container', function (req, res) { } finally { console.log(`disk was removed from ${name}`); } - //remove gpoi_mnt folder for container + //recursively remove gpoi_mnt folder. There is no method in fs to recursively remove, therefore using deleteFolderRecursice function var path = "/gpio_mnt/" + name; console.log(`removing ${path} folder...`); @@ -515,16 +526,8 @@ app.delete('/container', function (req, res) { console.log(path + " deleted"); } }; - try { - execSync(`rm -rf /gpio_mnt/${name}`); - } catch (e) { - console.log(`An error has occured during /gpio_mnt/${name} folder removal: ${e.error}`); - } finally { - console.log(`/gpio_mnt/${name} folder was removed`); - } - - //remove container + //remove container by bash console.log("Removing container..."); try { execSync(`lxc delete --force ${name}`); @@ -533,9 +536,11 @@ app.delete('/container', function (req, res) { } finally { console.log(`${name} was removed`); } + // respond to client. Currenlty no logic, which tracks actual state of all pin mapping processes. Therefore, always answer "ok" res.send("ok"); }); +// app.listen is used to launch web server for API requests listening app.listen(port, () => { console.log('We are live on ' + port); }); From f1304a65b909edc6bf81a88292650e19e90d01ec Mon Sep 17 00:00:00 2001 From: Marat Stepanov Date: Thu, 27 Apr 2017 06:22:13 +0000 Subject: [PATCH 04/11] changed almost all calls from asynchronous to synchronous with callback nesting --- server.js | 310 +++++++++++++++++++++++++++++------------------------- 1 file changed, 167 insertions(+), 143 deletions(-) diff --git a/server.js b/server.js index e6ac056..c83bee4 100755 --- a/server.js +++ b/server.js @@ -366,100 +366,121 @@ app.post('/container', function (req, res) { name = req.body.name; // All console.log lines are added in debugging purposes console.log ("Request body: ", req.body); - console.log(`Trying to launch ${name} container...`); - // using try construction where it is possible in order to make code more reliable - try { - // there are few libraries to manage lxc directly from node.js, but they do not allow to configure containers - // therefore we use exesSync, which invokes bash shell. - // execSync is synchronous exec - difference is that execSync waits for command to finish, while exec does not - // if we use exec, all further code will be invoked before container actually starts - // by this line we launch container. Name is taken from client's JSON - execSync(`lxc launch ubuntu:16.04 ${name}`); - } catch (e) { - console.log ("An error has occured while launching container: ", e.message); - } finally { - console.log (`Launched ${name} container`); - } + console.log(`Launching ${name} container...`); - // taking uid of file's owner using fs.statSync method - uidstats = fs.statSync(`/var/lib/lxd/containers/${name}/rootfs/`); - uid = uidstats["uid"]; - console.log ("UID: ", uid); + // there are few libraries to manage lxc directly from node.js, but they do not allow to configure containers + // therefore we use exec, which invokes bash shell. + exec (`lxc launch ubuntu:16.04 ${name}`, (error, stdout, stderr) => { + if (error) { + console.error(`An error has occured while launching container: ${error}`); + // do not continue if container failed to launch + return + } else { + console.log (`Launched ${name} container`); + // go on if container launched successfully + // taking uid of file's owner using fs.statSync method + uidstats = fs.statSync(`/var/lib/lxd/containers/${name}/rootfs/`); + uid = uidstats["uid"]; + console.log ("UID: ", uid); - try { - // Adding gpio group to container. - execSync(`lxc exec ${name} -- addgroup gpio`); - } catch (e) { - console.log ("An error has occured while adding gpio group: ", e.message); - } finally { - console.log (`Added gpio group in ${name} container `); - } - try { - // adding ubuntu user to gpio group in container - execSync(`lxc exec ${name} -- usermod -a -G gpio ubuntu`); - } catch (e) { - console.log ("An error has occured while adding ubuntu user to gpio group: ", e.message); - } finally { - console.log (`Added ubuntu user to gpio group in ${name} container`); - } - // getting gpio group's ID in containter and suming it with rootfs folder's uid. It will be used further while calling folder mirroring function - output = (execSync('lxc exec ' + name + ' -- cat /etc/group')).toString(); - gid = parseInt(output.match(/gpio:x:([0-9]+):.*/i)[1]) + parseInt(uid); - console.log ("GID: ", gid); - // checking if /gpio_mnt/${name} exists and if not - create it using mkdirp.sync - // standard fs.mkdirSync does not fit because there is no way to make folder recursively - if (!fs.existsSync(`/gpio_mnt/${name}`)){ - try { - mkdirp.sync(`/gpio_mnt/${name}`); - } catch (e) { - console.log ("Error: ", e.message); + // Adding gpio group to container. + exec(`lxc exec ${name} -- addgroup gpio`, (error, stdout, stderr) => { + if (error) { + console.error(`An error has occured while adding gpio group: ${error}`); + } else { + console.log (`Added gpio group in ${name} container `); + } + // adding ubuntu user to gpio group in container + exec(`lxc exec ${name} -- usermod -a -G gpio ubuntu`, (error, stdout, stderr) => { + if (error) { + console.error(`An error has occured while adding ubuntu user to gpio group: ${error}`); + } else { + console.log (`Added ubuntu user to gpio group in ${name} container `); + } + + // getting gpio group's ID in containter and suming it with rootfs folder's uid. It will be used further while calling folder mirroring function + output = (execSync('lxc exec ' + name + ' -- cat /etc/group')).toString(); + gid = parseInt(output.match(/gpio:x:([0-9]+):.*/i)[1]) + parseInt(uid); + console.log ("GID: ", gid); + // checking if /gpio_mnt/${name} exists and if not - create it using mkdirp.sync + // standard fs.mkdirSync does not fit because there is no way to make folder recursively + if (!fs.existsSync(`/gpio_mnt/${name}`)){ + try { + mkdirp.sync(`/gpio_mnt/${name}`); + } catch (e) { + console.log ("Error: ", e.message); + } + } + // fs.chmod does not perform recursive chmod, therefore using exec + chmod with -R flag + exec(`chmod 777 -R /gpio_mnt/`, (error, stdout, stderr) => { + if (error) { + console.error(`An error has occured while performing chmod 777 -R /gpio_mnt/: ${error}`); + } else { + console.log (`Performed chmod 777 -R /gpio_mnt/ succesfully`); + } + // creating folders using mkdirp.sync for pins mapping + try { + mkdirp.sync(`/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio`); + } catch (e) { + console.log ("Error: ", e.message); + } + try { + mkdirp.sync(`/gpio_mnt/${name}/sys/class/gpio`); + } catch (e) { + console.log ("Error: ", e.message); + } + // there is no fs.chown with recursion - using exec + chown -R + exec(`chown ${uid}.${gid} -R /gpio_mnt/${name}/sys/`, (error, stdout, stderr) => { + if (error) { + console.error(`An error has occured while performing chmod 777 -R /gpio_mnt/: ${error}`); + } else { + console.log (`Performed chmod 777 -R /gpio_mnt/ succesfully`); + } + // creating folder in container, which will be mapped to parent's appropriate folder + exec(`lxc exec ${name} -- mkdir -p /gpio_mnt/sys/class/gpio`,(error, stdout, stderr) => { + if (error) { + console.error(`An error has occured while creating /gpio_mnt/sys/class/gpio folder in ${name} container`); + } else { + console.log (`Created /gpio_mnt/sys/class/gpio folder in ${name} container`); + } + + // creating folder in container, which will be mapped to parent's appropriate folder + exec(`lxc exec ${name} -- mkdir -p /gpio_mnt/sys/devices/platform/soc/3f200000.gpio`, (error, stdout, stderr) => { + if (error) { + console.error(`An error has occured while creating /gpio_mnt/sys/devices/platform/soc/3f200000.gpio folder in ${name} container`); + } else { + console.log (`Created /gpio_mnt/sys/devices/platform/soc/3f200000.gpio folder in ${name} container`); + } + // mapping parent's folders to appropriate container's folders + exec(`lxc config device add ${name} gpio disk source=/gpio_mnt/${name}/sys/class/gpio path=/gpio_mnt/sys/class/gpio`, (error, stdout, stderr) => { + if (error) { + console.error(`An error has occured while mounting /gpio_mnt/sys/class/gpio folder in ${name} container`); + } else { + console.log (`Mounted /gpio_mnt/sys/class/gpio folder in ${name} container`); + } + // mapping parent's folders to appropriate container's folders + exec(`lxc config device add ${name} gpio disk source=/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio path=/gpio_mnt/sys/devices/platform/soc/3f200000.gpio`, (error, stdout, stderr) => { + if (error) { + console.error(`An error has occured while mounting /gpio_mnt/sys/devices/platform/soc/3f200000.gpio folder in ${name} container`); + } else { + console.log (`Mounted /gpio_mnt/sys/devices/platform/soc/3f200000.gpio folder in ${name} container`); + } + // calling functions to reflect changes between parent and container's folders using FUSE + folderMirroring (`/sys/devices/platform/soc/3f200000.gpio`, `/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio`, `uid=${uid} gid=${gid} allow_other`); + folderMirroring (`/sys/class/gpio`, `/gpio_mnt/${name}/sys/class/gpio`, `uid=${uid} gid=${gid} allow_other`); + }); + }); + }); + }); + }); + }); + }); + }); } - } - // fs.chmodSync does not perform recursive chmod, therefore using execSync + chmod with -R flag - execSync(`chmod 777 -R /gpio_mnt/`); - // creating folders using mkdirp.sync for pins mapping - try { - mkdirp.sync(`/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio`); - } catch (e) { - console.log ("Error: ", e.message); - } - try { - mkdirp.sync(`/gpio_mnt/${name}/sys/class/gpio`); - } catch (e) { - console.log ("Error: ", e.message); - } - // there is no fs.chown with recursion - using execSync + chown -R - execSync(`chown ${uid}.${gid} -R /gpio_mnt/${name}/sys/`); - // creating folder in container, which will be mapped to parent's appropriate folder - try { - execSync(`lxc exec ${name} -- mkdir -p /gpio_mnt/sys/class/gpio`); - } catch (e) { - console.log ("Error: ", e.message); - } - // creating folder in container, which will be mapped to parent's appropriate folder - try { - execSync(`lxc exec ${name} -- mkdir -p /gpio_mnt/sys/devices/platform/soc/3f200000.gpio`); - } catch (e) { - console.log ("Error: ", e.message); - } - // mapping parent's folders to appropriate container's folders - try { - execSync(`lxc config device add ${name} gpio disk source=/gpio_mnt/${name}/sys/class/gpio path=/gpio_mnt/sys/class/gpio`); - } catch (e) { - console.log ("Error: ", e.message); - } - // mapping parent's folders to appropriate container's folders - try { - execSync(`lxc config device add ${name} devices disk source=/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio path=/gpio_mnt/sys/devices/platform/soc/3f200000.gpio`); - } catch (e) { - console.log ("Error: ", e.message); - } - // calling functions to reflect changes between parent and container's folders using FUSE - folderMirroring (`/sys/devices/platform/soc/3f200000.gpio`, `/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio`, `uid=${uid} gid=${gid} allow_other`); - folderMirroring (`/sys/class/gpio`, `/gpio_mnt/${name}/sys/class/gpio`, `uid=${uid} gid=${gid} allow_other`); - // respond to client. Currenlty no logic, which tracks actual state of all pin mapping processes. Therefore, always answer "ok" - res.send(`ok`); + }); + // respond to client. Currenlty no logic, which tracks actual state of all pin mapping processes. Therefore, always answer "invoked" + res.send(`invoked`); }); // route invoked by DELETE method to /container path (i.e. http://server:port/container) @@ -480,64 +501,67 @@ app.delete('/container', function (req, res) { fuse.unmount(mountPath, function (err) { // this is callback function, which handles errors if (err) { - console.log('filesystem at ' + mountPath + ' not unmounted', err) + console.error('filesystem at ' + mountPath + ' not unmounted', err) } else { console.log('filesystem at ' + mountPath + ' unmounted') - } - }); - }); + } + // continue in callback, in order to keep order of tasks. Otherwise, some tasks will be performed earlier than other. + // for example, first we should unmount filesystem, and then remove lxc device from container, and not vise versa. - // there are few libraries to manage lxc directly from node.js, but they do not allow to configure containers - // therefore we use exesSync, which invokes bash shell. - // execSync is synchronous exec - difference is that execSync waits for command to finish, while exec does not - // if we use exec, all further code will be invoked before container actually starts - // by these 2 try constructions we reconfigre container and remove gpio mountings. Name is taken from client's JSON - try { - execSync(`lxc config device remove ${name} gpio disk`) - } catch (e) { - console.log(`An error has occured during gpio disk removal from ${name}: ${e.error}`); - } finally { - console.log(`gpio disk was removed from ${name}`); - } - - try { - execSync(`lxc config device remove ${name} devices disk`) - } catch (e) { - console.log(`An error has occured during disk removal from ${name}: ${e.error}`); - } finally { - console.log(`disk was removed from ${name}`); - } - //recursively remove gpoi_mnt folder. There is no method in fs to recursively remove, therefore using deleteFolderRecursice function - var path = "/gpio_mnt/" + name; - console.log(`removing ${path} folder...`); - - var deleteFolderRecursive = function(path) { - if( fs.existsSync(path) ) { - fs.readdirSync(path).forEach(function(file,index){ - var curPath = path + "/" + file; - if(fs.lstatSync(curPath).isDirectory()) { // recurse - deleteFolderRecursive(curPath); - } else { // delete file - fs.unlinkSync(curPath); - console.log(curPath + " deleted"); + // there are few libraries to manage lxc directly from node.js, but they do not allow to configure containers + // therefore we use exes, which invokes bash shell. + // by these 2 try constructions we reconfigre container and remove gpio mountings. Name is taken from client's JSON + exec(`lxc config device remove ${name} gpio disk`,(error, stdout, stderr) => { + if (error) { + console.error(`An error has occured during gpio disk removal from ${name}: ${error}`); + } else { + console.log(`gpio disk was removed from ${name}`); } - }); - fs.rmdirSync(path); - console.log(path + " deleted"); - } - }; + + // continue in callback, in order to keep order of tasks. There are few more iterations + exec(`lxc config device remove ${name} devices disk`,(error, stdout, stderr) => { + if (error) { + console.error(`An error has occured during disk removal from ${name}: ${error}`); + } else { + console.log(`disk was removed from ${name}`); + } + + //recursively remove gpoi_mnt folder. There is no method in fs to recursively remove, therefore using deleteFolderRecursice function + var path = "/gpio_mnt/" + name; + console.log(`removing ${path} folder...`); - //remove container by bash - console.log("Removing container..."); - try { - execSync(`lxc delete --force ${name}`); - } catch (e){ - console.log(`An error has occured during ${name} removal: ${e.message}`); - } finally { - console.log(`${name} was removed`); - } - // respond to client. Currenlty no logic, which tracks actual state of all pin mapping processes. Therefore, always answer "ok" - res.send("ok"); + var deleteFolderRecursive = function(path) { + if( fs.existsSync(path) ) { + fs.readdirSync(path).forEach(function(file,index){ + var curPath = path + "/" + file; + if(fs.lstatSync(curPath).isDirectory()) { // recurse + deleteFolderRecursive(curPath); + } else { // delete file + fs.unlinkSync(curPath); + console.log(curPath + " deleted"); + } + }); + fs.rmdirSync(path); + console.log(path + " deleted"); + } + }; + + //remove container by bash + console.log("Removing container..."); + exec(`lxc delete --force ${name}`,(error, stdout, stderr) => { + if (error) { + console.error(`An error has occured during ${name} removal: ${error}`); + } else { + console.log(`${name} was removed`); + } + }); + }); + }); + }); + }); + // respond to client. Currenlty no logic, which tracks actual state of all pin mapping processes. Therefore, always answer "invoked" + // answer comes immediately after receving API call + res.send("invoked"); }); // app.listen is used to launch web server for API requests listening From bbe36c1961379f8802ac71f4b4e4abe95ef41b32 Mon Sep 17 00:00:00 2001 From: Francesco Longo Date: Thu, 27 Apr 2017 09:38:16 +0200 Subject: [PATCH 05/11] Minor modification --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 874c7e1..ac6e002 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Raspberry Pi Virtualization -An approach to create multiple virtual Raspberry Pis on a single physical one through the use of LXD and FUSE. The virtual Raspberry Pis need to have access to the GPIO pseudo-filesystem. Of course, priviledged LXD containers could directly access the filesystem without the need of FUSE. However, being able to mediate access to GPIO pins is of great interest in Internet of Things and Fog/Edge computing scenarios. +An approach to create multiple virtual Raspberry Pis on a single physical one through the use of LXD and FUSE. The virtual Raspberry Pis need to have access to the GPIO pseudo-filesystem. Of course, priviledged LXD containers could directly access the filesystem without the need of FUSE. However, being able to mediate access to GPIO pins is particularly interesting in Internet of Things and Fog/Edge computing scenarios. ## HowTo This is a first version of Raspberry Pi virtualization: full pass-through of GPIO folder. It has been tested on a Raspberry Pi 2 with Ubuntu 16.04 LTS downloaded from https://wiki.ubuntu.com/ARM/RaspberryPi. From a4af4a7a52ac64d6234a1e8d5f3f2bf591954013 Mon Sep 17 00:00:00 2001 From: Marat Stepanov Date: Thu, 27 Apr 2017 10:32:10 +0000 Subject: [PATCH 06/11] Resolving issues #9 and #10 --- server.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/server.js b/server.js index c83bee4..b948c29 100755 --- a/server.js +++ b/server.js @@ -392,7 +392,8 @@ app.post('/container', function (req, res) { console.log (`Added gpio group in ${name} container `); } // adding ubuntu user to gpio group in container - exec(`lxc exec ${name} -- usermod -a -G gpio ubuntu`, (error, stdout, stderr) => { + // without useradd ubuntu command, it keeps failing with error: ubuntu user does not exist. + exec(`lxc exec ${name} -- useradd ubuntu && usermod -a -G gpio ubuntu`, (error, stdout, stderr) => { if (error) { console.error(`An error has occured while adding ubuntu user to gpio group: ${error}`); } else { @@ -460,9 +461,10 @@ app.post('/container', function (req, res) { console.log (`Mounted /gpio_mnt/sys/class/gpio folder in ${name} container`); } // mapping parent's folders to appropriate container's folders - exec(`lxc config device add ${name} gpio disk source=/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio path=/gpio_mnt/sys/devices/platform/soc/3f200000.gpio`, (error, stdout, stderr) => { + exec(`lxc config device add ${name} devices disk source=/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio path=/gpio_mnt/sys/devices/platform/soc/3f200000.gpio`, (error, stdout, stderr) => { if (error) { console.error(`An error has occured while mounting /gpio_mnt/sys/devices/platform/soc/3f200000.gpio folder in ${name} container`); + console.error(`Error text: ${error}`); } else { console.log (`Mounted /gpio_mnt/sys/devices/platform/soc/3f200000.gpio folder in ${name} container`); } @@ -492,11 +494,17 @@ app.delete('/container', function (req, res) { name = req.body.name; // All console.log lines are added in debugging purposes console.log(name); - // creating array with list of mounting points to unmount, in order to simplify code - var mountpoints = [`/gpio_mnt/${req.body.name}/sys/devices/platform/soc/3f200000.gpio`,`/gpio_mnt/${req.body.name}/sys/class/gpio`]; - // using forEach() method in order to go through all array elements and unmount them - mountpoints.forEach (function(mountPath,i,mountpoints) { - console.log (mountPath); + // using mountPath variable in order unify code with next callback + mountPath = `/gpio_mnt/${req.body.name}/sys/class/gpio`; + // using fuse.unmount(), which actually removes FUSE mounting + fuse.unmount(mountPath, function (err) { + // this is callback function, which handles errors + if (err) { + console.error('filesystem at ' + mountPath + ' not unmounted', err) + } else { + console.log('filesystem at ' + mountPath + ' unmounted') + } + mountPath = `/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio`; // using fuse.unmount(), which actually removes FUSE mounting fuse.unmount(mountPath, function (err) { // this is callback function, which handles errors From 9851fcd78678292ddaade52b8ec4bc37cd1d0519 Mon Sep 17 00:00:00 2001 From: Marat Stepanov Date: Thu, 27 Apr 2017 10:39:44 +0000 Subject: [PATCH 07/11] Resolved issue #9 by other approach (sleep instead of useradd) --- server.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server.js b/server.js index b948c29..b150c6c 100755 --- a/server.js +++ b/server.js @@ -392,8 +392,9 @@ app.post('/container', function (req, res) { console.log (`Added gpio group in ${name} container `); } // adding ubuntu user to gpio group in container - // without useradd ubuntu command, it keeps failing with error: ubuntu user does not exist. - exec(`lxc exec ${name} -- useradd ubuntu && usermod -a -G gpio ubuntu`, (error, stdout, stderr) => { + // without sleep 1, it keeps failing with error: ubuntu user does not exist. + // This is due to the fact that the cloud-init script that creates the ubuntu user has not finished yet when you try to add the ubuntu user to the gpio group. + exec(`lxc exec ${name} -- sleep 1 && usermod -a -G gpio ubuntu`, (error, stdout, stderr) => { if (error) { console.error(`An error has occured while adding ubuntu user to gpio group: ${error}`); } else { From 052832ea03a9d9bcc4da00fe1f4c6f293e90574c Mon Sep 17 00:00:00 2001 From: Marat Stepanov Date: Mon, 1 May 2017 16:47:18 +0000 Subject: [PATCH 08/11] Fixed #12 and minor changes --- server.js | 134 +++++++++++++++++++++++++++--------------------------- 1 file changed, 66 insertions(+), 68 deletions(-) diff --git a/server.js b/server.js index b150c6c..5efdff7 100755 --- a/server.js +++ b/server.js @@ -15,6 +15,8 @@ var mknod = require('mknod'); var argv = require('minimist')(process.argv.slice(2)); var fuse = require('fuse-bindings'); var mkdirp = require('mkdirp'); +var wrench = require('wrench'), + util = require('util'); // using bodyParster.json in order to parse JSON strings app.use(bodyParser.json()); // using bodyParser.urlenconded - without it express module won't be able to understand x-www-form-urlencoded @@ -273,6 +275,9 @@ function folderMirroring (original_folder, mirror_folder, fuse_options) { var write_function = function(path, fd, buffer, length, position, cb){ console.log('write(%s)', path); + if (path == "/export") { + console.log(buffer.toString()); + } fs.open(pt.join(original_folder, path), 'r+', function (err, int_fd) { if(err){ //console.log('error: ', err); @@ -297,7 +302,8 @@ function folderMirroring (original_folder, mirror_folder, fuse_options) { } }); } - }); + }); + } @@ -378,7 +384,6 @@ app.post('/container', function (req, res) { } else { console.log (`Launched ${name} container`); // go on if container launched successfully - // taking uid of file's owner using fs.statSync method uidstats = fs.statSync(`/var/lib/lxd/containers/${name}/rootfs/`); uid = uidstats["uid"]; @@ -388,97 +393,90 @@ app.post('/container', function (req, res) { exec(`lxc exec ${name} -- addgroup gpio`, (error, stdout, stderr) => { if (error) { console.error(`An error has occured while adding gpio group: ${error}`); - } else { - console.log (`Added gpio group in ${name} container `); + } else { console.log (`Added gpio group in ${name} container `); } - // adding ubuntu user to gpio group in container - // without sleep 1, it keeps failing with error: ubuntu user does not exist. - // This is due to the fact that the cloud-init script that creates the ubuntu user has not finished yet when you try to add the ubuntu user to the gpio group. - exec(`lxc exec ${name} -- sleep 1 && usermod -a -G gpio ubuntu`, (error, stdout, stderr) => { - if (error) { - console.error(`An error has occured while adding ubuntu user to gpio group: ${error}`); - } else { - console.log (`Added ubuntu user to gpio group in ${name} container `); - } - - // getting gpio group's ID in containter and suming it with rootfs folder's uid. It will be used further while calling folder mirroring function - output = (execSync('lxc exec ' + name + ' -- cat /etc/group')).toString(); - gid = parseInt(output.match(/gpio:x:([0-9]+):.*/i)[1]) + parseInt(uid); - console.log ("GID: ", gid); - // checking if /gpio_mnt/${name} exists and if not - create it using mkdirp.sync - // standard fs.mkdirSync does not fit because there is no way to make folder recursively - if (!fs.existsSync(`/gpio_mnt/${name}`)){ - try { - mkdirp.sync(`/gpio_mnt/${name}`); - } catch (e) { - console.log ("Error: ", e.message); - } - } - // fs.chmod does not perform recursive chmod, therefore using exec + chmod with -R flag - exec(`chmod 777 -R /gpio_mnt/`, (error, stdout, stderr) => { + setTimeout(function (){ + // without sleeping, it keeps failing with error: ubuntu user does not exist. + // This is due to the fact that the cloud-init script that creates the ubuntu user has not finished yet when you try to add the ubuntu user to the gpio group. + // adding ubuntu user to gpio group in container + exec(`lxc exec ${name} -- usermod -a -G gpio ubuntu`, (error, stdout, stderr) => { if (error) { - console.error(`An error has occured while performing chmod 777 -R /gpio_mnt/: ${error}`); + console.error(`An error has occured while adding ubuntu user to gpio group: ${error}`); } else { - console.log (`Performed chmod 777 -R /gpio_mnt/ succesfully`); - } - // creating folders using mkdirp.sync for pins mapping - try { - mkdirp.sync(`/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio`); - } catch (e) { - console.log ("Error: ", e.message); + console.log (`Added ubuntu user to gpio group in ${name} container `); } - try { - mkdirp.sync(`/gpio_mnt/${name}/sys/class/gpio`); - } catch (e) { - console.log ("Error: ", e.message); + }); + }, 15000); + // getting gpio group's ID in containter and suming it with rootfs folder's uid. It will be used further while calling folder mirroring function + output = (execSync('lxc exec ' + name + ' -- cat /etc/group')).toString(); + gid = parseInt(output.match(/gpio:x:([0-9]+):.*/i)[1]) + parseInt(uid); + console.log ("GID: ", gid); + // checking if /gpio_mnt/${name} exists and if not - create it using mkdirp.sync + // standard fs.mkdirSync does not fit because there is no way to make folder recursively + if (!fs.existsSync(`/gpio_mnt/${name}`)){ + try { + mkdirp.sync(`/gpio_mnt/${name}`); + } catch (e) { + console.log ("Error: ", e.message); + } + } + // fs.chmod does not perform recursive chmod, therefore using exec + chmod with -R flag + exec(`chmod 777 -R /gpio_mnt/`, (error, stdout, stderr) => { + if (error) console.error(`An error has occured while performing chmod 777 -R /gpio_mnt/: ${error}`); + else console.log (`Performed chmod 777 -R /gpio_mnt/ succesfully`); + // creating folders using mkdirp.sync for pins mapping + mkdirp(`/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio`,function (err) { + if (err) { + console.error(`An error has occured while creating /gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio folder: ${err.message}`); + } else { + console.log (`Created /gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio folder successfully`); } - // there is no fs.chown with recursion - using exec + chown -R - exec(`chown ${uid}.${gid} -R /gpio_mnt/${name}/sys/`, (error, stdout, stderr) => { - if (error) { - console.error(`An error has occured while performing chmod 777 -R /gpio_mnt/: ${error}`); + mkdirp(`/gpio_mnt/${name}/sys/class/gpio`, function (err) { + if (err) { + console.error(`An error has occured while creating /gpio_mnt/${name}/sys/class/gpio folder: ${err.message}`); } else { - console.log (`Performed chmod 777 -R /gpio_mnt/ succesfully`); + console.log (`Created /gpio_mnt/${name}/sys/class/gpio folder successfully`); } + // adding permissions to root&gpio + wrench.chownSyncRecursive(`/gpio_mnt/${name}/sys/`, uid, gid); // creating folder in container, which will be mapped to parent's appropriate folder exec(`lxc exec ${name} -- mkdir -p /gpio_mnt/sys/class/gpio`,(error, stdout, stderr) => { if (error) { console.error(`An error has occured while creating /gpio_mnt/sys/class/gpio folder in ${name} container`); - } else { - console.log (`Created /gpio_mnt/sys/class/gpio folder in ${name} container`); + } else { + console.log (`Created /gpio_mnt/sys/class/gpio folder in ${name} container`); } - // creating folder in container, which will be mapped to parent's appropriate folder exec(`lxc exec ${name} -- mkdir -p /gpio_mnt/sys/devices/platform/soc/3f200000.gpio`, (error, stdout, stderr) => { - if (error) { + if (error) { console.error(`An error has occured while creating /gpio_mnt/sys/devices/platform/soc/3f200000.gpio folder in ${name} container`); - } else { - console.log (`Created /gpio_mnt/sys/devices/platform/soc/3f200000.gpio folder in ${name} container`); - } + } else { + console.log (`Created /gpio_mnt/sys/devices/platform/soc/3f200000.gpio folder in ${name} contaier`); + } // mapping parent's folders to appropriate container's folders - exec(`lxc config device add ${name} gpio disk source=/gpio_mnt/${name}/sys/class/gpio path=/gpio_mnt/sys/class/gpio`, (error, stdout, stderr) => { - if (error) { + exec(`lxc config device add ${name} gpio disk source=/gpio_mnt/${name}/sys/class/gpio path=/gpio_mnt/sys/class/gpio`, (error, stdout, stderr) => { + if (error) { console.error(`An error has occured while mounting /gpio_mnt/sys/class/gpio folder in ${name} container`); - } else { + } else { console.log (`Mounted /gpio_mnt/sys/class/gpio folder in ${name} container`); - } + } // mapping parent's folders to appropriate container's folders - exec(`lxc config device add ${name} devices disk source=/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio path=/gpio_mnt/sys/devices/platform/soc/3f200000.gpio`, (error, stdout, stderr) => { - if (error) { - console.error(`An error has occured while mounting /gpio_mnt/sys/devices/platform/soc/3f200000.gpio folder in ${name} container`); - console.error(`Error text: ${error}`); - } else { + exec(`lxc config device add ${name} devices disk source=/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio path=/gpio_mnt/sys/devices/platform/soc/3f200000.gpio`, (error, stdout, stderr) => { + if (error) { + console.error(`An error has occured while mounting /gpio_mnt/sys/devices/platform/soc/3f200000.gpio folder in ${name} container: ${error}`); + } else { console.log (`Mounted /gpio_mnt/sys/devices/platform/soc/3f200000.gpio folder in ${name} container`); - } + } // calling functions to reflect changes between parent and container's folders using FUSE - folderMirroring (`/sys/devices/platform/soc/3f200000.gpio`, `/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio`, `uid=${uid} gid=${gid} allow_other`); - folderMirroring (`/sys/class/gpio`, `/gpio_mnt/${name}/sys/class/gpio`, `uid=${uid} gid=${gid} allow_other`); - }); + folderMirroring (`/sys/devices/platform/soc/3f200000.gpio`, `/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio`, [`uid=${uid}`,`gid=${gid}`,`allow_other`]); + folderMirroring (`/sys/class/gpio`, `/gpio_mnt/${name}/sys/class/gpio`, [`uid=${uid}`,`gid=${gid}`,`allow_other`]); + }); }); - }); + }); }); }); }); - }); + }); }); } }); From 859b77b51b03f0cc627d43ae0e510d551f5f960f Mon Sep 17 00:00:00 2001 From: Marat Stepanov Date: Mon, 1 May 2017 19:55:01 +0000 Subject: [PATCH 09/11] To resolve #13 --- README_API.md | 2 +- server.js | 45 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/README_API.md b/README_API.md index 520540f..25ac46c 100644 --- a/README_API.md +++ b/README_API.md @@ -10,7 +10,7 @@ We assume that server.js script is located in /home/ubuntu/raspberry_virtualizat ``` cd /home/ubuntu/raspberry_virtualization -npm install nodemon express body-parser ps-node linux-mountutils mkdirp +npm install nodemon express body-parser ps-node linux-mountutils mkdirp node-lxd wrench ``` diff --git a/server.js b/server.js index 5efdff7..0dabb7c 100755 --- a/server.js +++ b/server.js @@ -17,6 +17,9 @@ var fuse = require('fuse-bindings'); var mkdirp = require('mkdirp'); var wrench = require('wrench'), util = require('util'); + +var lxd = require("node-lxd"); +var client = lxd(); // using bodyParster.json in order to parse JSON strings app.use(bodyParser.json()); // using bodyParser.urlenconded - without it express module won't be able to understand x-www-form-urlencoded @@ -384,7 +387,7 @@ app.post('/container', function (req, res) { } else { console.log (`Launched ${name} container`); // go on if container launched successfully - // taking uid of file's owner using fs.statSync method + // taking uid of container's root using fs.statSync method uidstats = fs.statSync(`/var/lib/lxd/containers/${name}/rootfs/`); uid = uidstats["uid"]; console.log ("UID: ", uid); @@ -571,6 +574,46 @@ app.delete('/container', function (req, res) { res.send("invoked"); }); +// on start of server, checking for existence of running containers. If they exist, re-mount folders +client.containers(function(err, containers) { + for (var i = 0; i < containers.length; i++) { + if((containers[i]._metadata.status) == 'Running') { + //Remounting + name = containers[i].name(); + console.log (`${name} is running`); + // taking uid of container's root using fs.statSync method + uidstats = fs.statSync(`/var/lib/lxd/containers/${name}/rootfs/`); + uid = uidstats["uid"]; + console.log ("UID: ", uid); + // getting gpio group's ID in containter and suming it with rootfs folder's uid. It will be used further while calling folder mirroring function + output = (execSync('lxc exec ' + name + ' -- cat /etc/group')).toString(); + gid = parseInt(output.match(/gpio:x:([0-9]+):.*/i)[1]) + parseInt(uid); + console.log ("GID: ", gid); + + // using fuse.unmount(), which actually removes FUSE mounting + fuse.unmount(`/gpio_mnt/${name}/sys/class/gpio`, function (err) { + // this is callback function, which handles errors + if (err) { + console.error(`filesystem at /gpio_mnt/${name}/sys/class/gpio not unmounted due to error: ${err}`); + } else { + console.log(`filesystem at /gpio_mnt/${name}/sys/class/gpio has been unmounted`); + } + folderMirroring (`/sys/class/gpio`, `/gpio_mnt/${name}/sys/class/gpio`, [`uid=${uid}`,`gid=${gid}`,`allow_other`]); + }); + // using fuse.unmount(), which actually removes FUSE mounting + fuse.unmount(`/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio`, function (err) { + // this is callback function, which handles errors< + if (err) { + console.error(`filesystem at /gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio not unmounted due to error: ${err}`); + } else { + console.log(`filesystem at /gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio has been unmounted`); + } + folderMirroring (`/sys/devices/platform/soc/3f200000.gpio`, `/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio`, [`uid=${uid}`,`gid=${gid}`,`allow_other`]); + }); + } + } +}); + // app.listen is used to launch web server for API requests listening app.listen(port, () => { console.log('We are live on ' + port); From fb3551f82d4688c09997908575655b391dc82aaf Mon Sep 17 00:00:00 2001 From: Marat Stepanov Date: Tue, 2 May 2017 21:01:11 +0000 Subject: [PATCH 10/11] On a half way to resolve #5 --- server.js | 325 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 220 insertions(+), 105 deletions(-) diff --git a/server.js b/server.js index 0dabb7c..a0d76fc 100755 --- a/server.js +++ b/server.js @@ -6,7 +6,6 @@ var app = express(); var fs = require('fs'); var glob = require('glob'); var ps = require('ps-node'); -var mountutil = require('linux-mountutils'); var exec = require('child_process').exec; var execSync = require('child_process').execSync; var pt = require('path'); @@ -20,12 +19,68 @@ var wrench = require('wrench'), var lxd = require("node-lxd"); var client = lxd(); +var gpio = require("gpio"); // using bodyParster.json in order to parse JSON strings app.use(bodyParser.json()); // using bodyParser.urlenconded - without it express module won't be able to understand x-www-form-urlencoded app.use(bodyParser.urlencoded({ extended: true })) // specifying port number on which application will be listening var port = 8000; +// this function is desired to simplify remounts during server initialization +function folderRemount (folder, name, uid, gid) { + fuse.unmount(`/gpio_mnt/${name}${folder}`, function (err) { + // this is callback function, which handles errors + if (err) { + console.error(`filesystem at /gpio_mnt/${name}${folder} not unmounted due to error: ${err}`); + } else { + console.log(`filesystem at /gpio_mnt/${name}${folder} has been unmounted`); + } + folderMirroring (folder, `/gpio_mnt/${name}${folder}`, [`uid=${uid}`,`gid=${gid}`,`allow_other`]); + }); +} + +// this function will be used later to unmount all fuse mount points of container +function unmountAllFuse (name, callback) { + exec (`mount | grep -E "\/dev\/fuse on \/(gpio_mnt|var\/lib\/lxd\/devices)\/${name}\/"`, (error, stdout, stderr) => { + if (error) { + console.log (`No mounting points found for ${name} container ${error}`); + callback(); + } else { + reg = /(\/gpio_mnt.*|\/var\/lib\/lxd\/devices\/.*)\stype\sfuse/g; + while ((match = reg.exec(stdout.toString())) !== null) { + mountPoint = match[1]; + console.log(`Found mounting point to unmount: ${mountPoint}`); + // using fuse.unmount(), which actually removes FUSE mounting + fuse.unmount(mountPoint, function (err) { + // this is callback function, which handles errors + if (err) { + console.error('filesystem at ' + mountPoint + ' not unmounted', err) + } else { + console.log('filesystem at ' + mountPoint + ' unmounted') + } + }); + } + callback(); + } + }); +} + +// this function will be used later to recursively remove folder +function deleteFolderRecursive (path) { + if( fs.existsSync(path) ) { + fs.readdirSync(path).forEach(function(file,index){ + var curPath = path + "/" + file; + if(fs.lstatSync(curPath).isDirectory()) { // recurse + deleteFolderRecursive(curPath); + } else { // delete file + fs.unlinkSync(curPath); + console.log(curPath + " deleted"); + } + }); + fs.rmdirSync(path); + console.log(path + " deleted"); + } +}; // function is full copy of node-folder-mirroring script, just omitted initial checks of arguments count ant correctness function folderMirroring (original_folder, mirror_folder, fuse_options) { @@ -278,36 +333,148 @@ function folderMirroring (original_folder, mirror_folder, fuse_options) { var write_function = function(path, fd, buffer, length, position, cb){ console.log('write(%s)', path); - if (path == "/export") { - console.log(buffer.toString()); - } - fs.open(pt.join(original_folder, path), 'r+', function (err, int_fd) { - if(err){ - //console.log('error: ', err); - cb(fuse[err.code]); - } - else{ - fs.write(int_fd, buffer, 0, length, position, function(err, written, int_buffer){ - if(err){ - //console.log('error: ', err); - cb(fuse[err.code]); - } - else{ - fs.close(int_fd, function(err){ - if(err){ - //console.log('error:', err); - cb(fuse[err.code]); - } - else{ - cb(written); - } + // getting value that was pushed + inputstring = buffer.toString().trim(); + // trying to convert value to integer + if (parseInt(inputstring)) {inputstring = parseInt(inputstring)} + // checking if export folder is target + if ((original_folder + path) == "/sys/class/gpio/export") { + // return "ok" + // correct return is not implemented yet + cb(2); + console.log("Export string: " + buffer.toString()); + console.log("Original folder: " + original_folder); + //get container name from the path + name = mirror_folder.match(/\/gpio_mnt\/(.*)\/sys\/class\/gpio/i)[1]; + console.log("Mirrored folder: " + mirror_folder); + // checking for input. If it's number from 0 to 100, go on + if (inputstring >=0 && inputstring <= 100) { + // check if pin is already exported + if (fs.existsSync(`/sys/class/gpio/gpio${inputstring}`)){ + console.log (`pin ${inputstring} is exported already`); + } else { + // if its not exported yet, export it + gpio.export(inputstring, { + ready: function() { + // create folder for exported pin + mkdirp(`/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring}`,function (err) { + if (err) { + console.error(`An error has occured while creating /gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring} folder: ${err.message}`); + } else { + console.log (`Created /gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring} folder successfully`); + } + // create folder for exported pin in container + exec(`lxc exec ${name} -- mkdir -p /gpio_mnt/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring}`, (error, stdout, stderr) => { + if (error) { + console.error(`An error has occured while creating /gpio_mnt/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring} folder in ${name} container`); + } else { + console.log (`Created /gpio_mnt/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring} folder in ${name} contaier`); + } + + // create device to mount folder to container + exec(`lxc config device add ${name} pin${inputstring} disk source=/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring} path=/gpio_mnt/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring}`, (error, stdout, stderr) => { + if (error) { + console.error(`An error has occured while mounting /gpio_mnt/sys/devices/platform/soc/3f200000.gpiogpio/gpio${inputstring} folder in ${name} container: ${error}`); + } else { + console.log (`Mounted /gpio_mnt/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring} folder in ${name} container`); + } + // start mirroring using fuse + folderMirroring (`/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring}`, `/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring}`, [`uid=${uid}`,`gid=${gid}`,`allow_other`]); + //cb (null); }); - } + }); + }); + } }); - } - }); - + } + // if user tries to put something wrong to export folder + } else { + console.log('user tried to perform unexpected action'); + } + // if user tries to unexport + } else if ((original_folder + path) == "/sys/class/gpio/unexport") { + // return "ok" + // correct return is not implemented yet + cb(2); + console.log("Unexport string: " + buffer.toString()); + console.log("Original folder: " + original_folder); + //get container name from the path + name = mirror_folder.match(/\/gpio_mnt\/(.*)\/sys\/class\/gpio/i)[1]; + console.log("Mirrored folder: " + mirror_folder); + // checking for input. If it's number from 0 to 100, go on + if (inputstring >=0 && inputstring <= 100) { + // check if pin exported to this container or not + exec(`mount | grep "/dev/fuse on /gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring} type fuse"`, (error, stdout, stderr) => { + if (error) { + // if not exported to this container - do not proceed + console.error(`pin ${inputstring} is not exported to ${name} container yet. Cannot proceed with unexporting`); + } else { + // proceed this unexporting if exported to this container + console.log (`Unexporting pin ${inputstring}...`); + fuse.unmount(`/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring}`, function (err) { + // this is callback function, which handles errors + if (err) { + console.error(`filesystem at /gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring} not unmounted due to error: ${err}`); + } else { + console.log(`filesystem at /gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring} has been unmounted`); + } + // try removing device from container + exec(`lxc config device remove ${name} pin${inputstring}`,(error, stdout, stderr) => { + if (error) { + console.error(`An error has occured while removing pin${inputstring} device in ${name} container: ${error}`); + } else { + console.log (`Removed ${inputstring} device from ${name} container`); + } + // remove folder from container + exec(`lxc exec ${name} -- rm -R /gpio_mnt/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring}`, (error, stdout, stderr) => { + if (error) { + console.error(`An error has occured while removing /gpio_mnt/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring} folder in ${name} container`); + } else { + console.log (`removed /gpio_mnt/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring} folder in ${name} contaier`); + } + // remove folder from physical raspberry + deleteFolderRecursive (`/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring}`); + // unexport pin + gpio.unexport(inputstring, { + ready: function() { + //cb (2); + } + }); + }); + }); + }); + } + }); + } + } else { + fs.open(pt.join(original_folder, path), 'r+', function (err, int_fd) { + if(err){ + //console.log('error: ', err); + cb(fuse[err.code]); + } + else{ + fs.write(int_fd, buffer, 0, length, position, function(err, written, int_buffer){ + if(err){ + //console.log('error: ', err); + cb(fuse[err.code]); + } + else{ + fs.close(int_fd, function(err){ + if(err){ + //console.log('error:', err); + cb(fuse[err.code]); + } + else{ + console.log (written); + cb(written); + } + }); + } + }); + } + }); + } } var getxattr_function = function(path, name, buffer, length, offset, cb){ @@ -423,6 +590,7 @@ app.post('/container', function (req, res) { console.log ("Error: ", e.message); } } + // fs.chmod does not perform recursive chmod, therefore using exec + chmod with -R flag exec(`chmod 777 -R /gpio_mnt/`, (error, stdout, stderr) => { if (error) console.error(`An error has occured while performing chmod 777 -R /gpio_mnt/: ${error}`); @@ -464,16 +632,16 @@ app.post('/container', function (req, res) { console.log (`Mounted /gpio_mnt/sys/class/gpio folder in ${name} container`); } // mapping parent's folders to appropriate container's folders - exec(`lxc config device add ${name} devices disk source=/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio path=/gpio_mnt/sys/devices/platform/soc/3f200000.gpio`, (error, stdout, stderr) => { - if (error) { - console.error(`An error has occured while mounting /gpio_mnt/sys/devices/platform/soc/3f200000.gpio folder in ${name} container: ${error}`); - } else { - console.log (`Mounted /gpio_mnt/sys/devices/platform/soc/3f200000.gpio folder in ${name} container`); - } + //exec(`lxc config device add ${name} devices disk source=/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio path=/gpio_mnt/sys/devices/platform/soc/3f200000.gpio`, (error, stdout, stderr) => { + //if (error) { + // console.error(`An error has occured while mounting /gpio_mnt/sys/devices/platform/soc/3f200000.gpio folder in ${name} container: ${error}`); + //} else { + // console.log (`Mounted /gpio_mnt/sys/devices/platform/soc/3f200000.gpio folder in ${name} container`); + //} // calling functions to reflect changes between parent and container's folders using FUSE - folderMirroring (`/sys/devices/platform/soc/3f200000.gpio`, `/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio`, [`uid=${uid}`,`gid=${gid}`,`allow_other`]); - folderMirroring (`/sys/class/gpio`, `/gpio_mnt/${name}/sys/class/gpio`, [`uid=${uid}`,`gid=${gid}`,`allow_other`]); - }); + //folderMirroring (`/sys/devices/platform/soc/3f200000.gpio`, `/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio`, [`uid=${uid}`,`gid=${gid}`,`allow_other`]); + folderMirroring (`/sys/class/gpio`, `/gpio_mnt/${name}/sys/class/gpio`, [`uid=${uid}`,`gid=${gid}`,`allow_other`]); + //}); }); }); }); @@ -496,26 +664,9 @@ app.delete('/container', function (req, res) { name = req.body.name; // All console.log lines are added in debugging purposes console.log(name); - // using mountPath variable in order unify code with next callback - mountPath = `/gpio_mnt/${req.body.name}/sys/class/gpio`; - // using fuse.unmount(), which actually removes FUSE mounting - fuse.unmount(mountPath, function (err) { - // this is callback function, which handles errors - if (err) { - console.error('filesystem at ' + mountPath + ' not unmounted', err) - } else { - console.log('filesystem at ' + mountPath + ' unmounted') - } - mountPath = `/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio`; - // using fuse.unmount(), which actually removes FUSE mounting - fuse.unmount(mountPath, function (err) { - // this is callback function, which handles errors - if (err) { - console.error('filesystem at ' + mountPath + ' not unmounted', err) - } else { - console.log('filesystem at ' + mountPath + ' unmounted') - } - // continue in callback, in order to keep order of tasks. Otherwise, some tasks will be performed earlier than other. + // get all fuse mount points of this container in order to unmount them + unmountAllFuse (name, function () { + // continue in unnamed callback, in order to keep order of tasks. Otherwise, some tasks will be performed earlier than other. // for example, first we should unmount filesystem, and then remove lxc device from container, and not vise versa. // there are few libraries to manage lxc directly from node.js, but they do not allow to configure containers @@ -528,34 +679,10 @@ app.delete('/container', function (req, res) { console.log(`gpio disk was removed from ${name}`); } - // continue in callback, in order to keep order of tasks. There are few more iterations - exec(`lxc config device remove ${name} devices disk`,(error, stdout, stderr) => { - if (error) { - console.error(`An error has occured during disk removal from ${name}: ${error}`); - } else { - console.log(`disk was removed from ${name}`); - } - //recursively remove gpoi_mnt folder. There is no method in fs to recursively remove, therefore using deleteFolderRecursice function - var path = "/gpio_mnt/" + name; - console.log(`removing ${path} folder...`); - - var deleteFolderRecursive = function(path) { - if( fs.existsSync(path) ) { - fs.readdirSync(path).forEach(function(file,index){ - var curPath = path + "/" + file; - if(fs.lstatSync(curPath).isDirectory()) { // recurse - deleteFolderRecursive(curPath); - } else { // delete file - fs.unlinkSync(curPath); - console.log(curPath + " deleted"); - } - }); - fs.rmdirSync(path); - console.log(path + " deleted"); - } - }; + console.log(`removing /gpio_mnt/${name} folder...`); + deleteFolderRecursive (`/gpio_mnt/${name}`); //remove container by bash console.log("Removing container..."); exec(`lxc delete --force ${name}`,(error, stdout, stderr) => { @@ -565,9 +692,8 @@ app.delete('/container', function (req, res) { console.log(`${name} was removed`); } }); - }); + //}); }); - }); }); // respond to client. Currenlty no logic, which tracks actual state of all pin mapping processes. Therefore, always answer "invoked" // answer comes immediately after receving API call @@ -589,27 +715,16 @@ client.containers(function(err, containers) { output = (execSync('lxc exec ' + name + ' -- cat /etc/group')).toString(); gid = parseInt(output.match(/gpio:x:([0-9]+):.*/i)[1]) + parseInt(uid); console.log ("GID: ", gid); - - // using fuse.unmount(), which actually removes FUSE mounting - fuse.unmount(`/gpio_mnt/${name}/sys/class/gpio`, function (err) { - // this is callback function, which handles errors - if (err) { - console.error(`filesystem at /gpio_mnt/${name}/sys/class/gpio not unmounted due to error: ${err}`); - } else { - console.log(`filesystem at /gpio_mnt/${name}/sys/class/gpio has been unmounted`); + //calling function that was defined earlier + folderRemount(`/sys/class/gpio`,name,uid,gid); + //look for exported pins and remount their folders + if (fs.existsSync(`/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio/gpio`)){ + pinfolders = fs.readdirSync(`/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio/gpio`); + for (var j = 0; i < pinfolders.length; i++) { + console.log (pinfolders[i]); + folderRemount(`/sys/devices/platform/soc/3f200000.gpio/gpio/${pinfolders[i]}`,name,uid,gid); } - folderMirroring (`/sys/class/gpio`, `/gpio_mnt/${name}/sys/class/gpio`, [`uid=${uid}`,`gid=${gid}`,`allow_other`]); - }); - // using fuse.unmount(), which actually removes FUSE mounting - fuse.unmount(`/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio`, function (err) { - // this is callback function, which handles errors< - if (err) { - console.error(`filesystem at /gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio not unmounted due to error: ${err}`); - } else { - console.log(`filesystem at /gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio has been unmounted`); - } - folderMirroring (`/sys/devices/platform/soc/3f200000.gpio`, `/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio`, [`uid=${uid}`,`gid=${gid}`,`allow_other`]); - }); + } } } }); From 48fb8b212ea1ba3ae1865642be1d30694174daad Mon Sep 17 00:00:00 2001 From: Marat Stepanov Date: Wed, 3 May 2017 19:15:53 +0000 Subject: [PATCH 11/11] Fully resolved #5 --- README_API.md | 2 +- server.js | 83 ++++++++++++++++++++++++++++----------------------- 2 files changed, 46 insertions(+), 39 deletions(-) diff --git a/README_API.md b/README_API.md index 25ac46c..40127b5 100644 --- a/README_API.md +++ b/README_API.md @@ -10,7 +10,7 @@ We assume that server.js script is located in /home/ubuntu/raspberry_virtualizat ``` cd /home/ubuntu/raspberry_virtualization -npm install nodemon express body-parser ps-node linux-mountutils mkdirp node-lxd wrench +npm install nodemon express body-parser ps-node linux-mountutils mkdirp node-lxd wrench gpio ``` diff --git a/server.js b/server.js index a0d76fc..08c6b18 100755 --- a/server.js +++ b/server.js @@ -349,48 +349,48 @@ function folderMirroring (original_folder, mirror_folder, fuse_options) { console.log("Mirrored folder: " + mirror_folder); // checking for input. If it's number from 0 to 100, go on if (inputstring >=0 && inputstring <= 100) { - // check if pin is already exported + // check if pin is already exported if not - do it if (fs.existsSync(`/sys/class/gpio/gpio${inputstring}`)){ - console.log (`pin ${inputstring} is exported already`); + console.log (`Pin ${inputstring} is exported already. Skipping export of physical pin`); } else { // if its not exported yet, export it gpio.export(inputstring, { ready: function() { - // create folder for exported pin - mkdirp(`/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring}`,function (err) { - if (err) { - console.error(`An error has occured while creating /gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring} folder: ${err.message}`); - } else { - console.log (`Created /gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring} folder successfully`); - } - // create folder for exported pin in container - exec(`lxc exec ${name} -- mkdir -p /gpio_mnt/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring}`, (error, stdout, stderr) => { - if (error) { - console.error(`An error has occured while creating /gpio_mnt/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring} folder in ${name} container`); - } else { - console.log (`Created /gpio_mnt/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring} folder in ${name} contaier`); - } - - // create device to mount folder to container - exec(`lxc config device add ${name} pin${inputstring} disk source=/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring} path=/gpio_mnt/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring}`, (error, stdout, stderr) => { - if (error) { - console.error(`An error has occured while mounting /gpio_mnt/sys/devices/platform/soc/3f200000.gpiogpio/gpio${inputstring} folder in ${name} container: ${error}`); - } else { - console.log (`Mounted /gpio_mnt/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring} folder in ${name} container`); - } - // start mirroring using fuse - folderMirroring (`/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring}`, `/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring}`, [`uid=${uid}`,`gid=${gid}`,`allow_other`]); - //cb (null); - }); - }); - }); } }); } - // if user tries to put something wrong to export folder - } else { - console.log('user tried to perform unexpected action'); - } + // create folder for pin + mkdirp(`/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring}`,function (err) { + if (err) { + console.error(`An error has occured while creating /gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring} folder: ${err.message}`); + } else { + console.log (`Created /gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring} folder successfully`); + } + // create folder for exported pin in container + exec(`lxc exec ${name} -- mkdir -p /gpio_mnt/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring}`, (error, stdout, stderr) => { + if (error) { + console.error(`An error has occured while creating /gpio_mnt/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring} folder in ${name} container`); + } else { + console.log (`Created /gpio_mnt/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring} folder in ${name} contaier`); + } + + // create device to mount folder to container + exec(`lxc config device add ${name} pin${inputstring} disk source=/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring} path=/gpio_mnt/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring}`, (error, stdout, stderr) => { + if (error) { + console.error(`An error has occured while mounting /gpio_mnt/sys/devices/platform/soc/3f200000.gpiogpio/gpio${inputstring} folder in ${name} container: ${error}`); + } else { + console.log (`Mounted /gpio_mnt/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring} folder in ${name} container`); + } + // start mirroring using fuse + folderMirroring (`/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring}`, `/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring}`, [`uid=${uid}`,`gid=${gid}`,`allow_other`]); + //cb (null); + }); + }); + }); + // if user tries to put something wrong to export folder + } else { + console.log('user tried to perform unexpected action'); + } // if user tries to unexport } else if ((original_folder + path) == "/sys/class/gpio/unexport") { // return "ok" @@ -435,10 +435,17 @@ function folderMirroring (original_folder, mirror_folder, fuse_options) { } // remove folder from physical raspberry deleteFolderRecursive (`/gpio_mnt/${name}/sys/devices/platform/soc/3f200000.gpio/gpio/gpio${inputstring}`); - // unexport pin - gpio.unexport(inputstring, { - ready: function() { - //cb (2); + //find if any container has mounted this pin + glob('/gpio_mnt/*/sys/devices/platform/soc/3f200000.gpio/gpio/gpio'+inputstring, function(err,files){ + //if no containers this this pin, proceed this unexporting it from physical rasp + if(files.length == 0){ + // unexport pin + gpio.unexport(inputstring, { + ready: function() { + console.log(`unexported pin ${inputstring}`) + //cb (2); + } + }); } }); });