Print this page
OS-2547 VM.js should protect writes to configuration files with a lockfile
*** 65,74 ****
--- 65,75 ----
var assert = require('assert');
var async = require('/usr/node/node_modules/async');
var bunyan = require('/usr/node/node_modules/bunyan');
var cp = require('child_process');
var dladm = require('/usr/vm/node_modules/dladm');
+ var lock = require('/usr/vm/node_modules/locker').lock;
var EventEmitter = require('events').EventEmitter;
var exec = cp.exec;
var execFile = cp.execFile;
var expat = require('/usr/node/node_modules/node-expat');
var fs = require('fs');
*** 4223,4232 ****
--- 4224,4260 ----
callback();
}
});
}
+ function writeAndRename(log, name, destfile, file_data, callback)
+ {
+ var tempfile = destfile + '.new';
+
+ log.debug('writing ' + name + ' to ' + tempfile);
+
+ fs.writeFile(tempfile, file_data, function (err) {
+ if (err) {
+ callback(err);
+ return;
+ }
+
+ log.debug('wrote ' + name + ' to ' + tempfile);
+ log.debug('renaming from ' + tempfile + ' to ' + destfile);
+
+ fs.rename(tempfile, destfile, function (_err) {
+ if (_err) {
+ callback(_err);
+ return;
+ }
+
+ log.debug('renamed from ' + tempfile + ' to ' + destfile);
+ callback();
+ });
+ });
+ }
+
// writes a Zone's metadata JSON to /zones/<uuid>/config/metadata.json
// and /zones/<uuid>/config/tags.json.
function updateMetadata(vmobj, payload, log, callback)
{
var cmdata = {};
*** 4312,4340 ****
tags[key] = payload.set_tags[key];
}
}
mdata = {'customer_metadata': cmdata, 'internal_metadata': imdata};
! fs.writeFile(mdata_filename, JSON.stringify(mdata, null, 2),
! function (err) {
! if (err) {
! callback(err);
! } else {
! log.debug('wrote metadata to ' + mdata_filename);
! fs.writeFile(tags_filename, JSON.stringify(tags, null, 2),
! function (e) {
! if (e) {
! callback(e);
! } else {
! log.debug('wrote tags to' + tags_filename);
! callback();
! }
! }
! );
! }
}
! );
}
function saveMetadata(payload, log, callback)
{
var protovm = {};
--- 4340,4360 ----
tags[key] = payload.set_tags[key];
}
}
mdata = {'customer_metadata': cmdata, 'internal_metadata': imdata};
!
! async.series([
! function (next) {
! writeAndRename(log, 'metadata', mdata_filename,
! JSON.stringify(mdata, null, 2), next);
! },
! function (next) {
! writeAndRename(log, 'tags', tags_filename,
! JSON.stringify(tags, null, 2), next);
}
! ], callback);
}
function saveMetadata(payload, log, callback)
{
var protovm = {};
*** 12003,12012 ****
--- 12023,12034 ----
exports.update = function (uuid, payload, options, callback)
{
var log;
var new_vmobj;
var vmobj;
+ var unlock;
+ var lockpath;
// options parameter is optional
if (arguments.length === 3) {
callback = arguments[2];
options = {};
*** 12022,12031 ****
--- 12044,12066 ----
log.info('Updating VM ' + uuid + ' with initial payload:\n'
+ JSON.stringify(payload, null, 2));
async.series([
function (cb) {
+ lockpath = '/var/run/vm.' + uuid + '.config.lockfile';
+ log.debug('acquiring lock on ' + lockpath);
+ lock(lockpath, function (err, _unlock) {
+ log.debug('acquired lock on ' + lockpath);
+ if (err) {
+ cb(err);
+ return;
+ }
+ unlock = _unlock;
+ cb();
+ });
+ },
+ function (cb) {
// for update we currently always load the whole vmobj since the
// update functions may need to look at bits from the existing VM.
VM.load(uuid, {log: log}, function (err, obj) {
if (err) {
cb(err);
*** 12159,12169 ****
--- 12194,12219 ----
applyUpdates(vmobj, new_vmobj, payload, log, function () {
cb();
});
}
], function (e) {
+ // If we were able to hold the lockfile, and thus have an unlock
+ // callback, we must call it before returning, whether or not
+ // there was an error.
+ if (unlock) {
+ log.debug('releasing lock on ' + lockpath);
+ unlock(function (unlock_err) {
+ if (unlock_err) {
+ log.error(err, 'unlock error! (path ' + lockpath + ')');
+ } else {
+ log.debug('released lock on ' + lockpath);
+ }
+ callback(e);
+ });
+ } else {
callback(e);
+ }
});
};
function kill(uuid, log, callback)
{