Print this page
OS-2547 VM.js should protect writes to configuration files with a lockfile


  50  * stop(uuid, options={[force=true]}, callback)
  51  * sysrq(uuid, req=[nmi|screenshot], options={}, callback)
  52  * update(uuid, properties, callback)
  53  *
  54  * Exported variables:
  55  *
  56  * logname - you can set this to a string [a-zA-Z_] to use as log name
  57  * logger - you can set this to a node-bunyan log stream to capture the logs
  58  * INFO_TYPES - list of supported types for the info command
  59  * SYSRQ_TYPES - list of supported requests for sysrq
  60  */
  61 
  62 // Ensure we're using the platform's node
  63 require('/usr/node/node_modules/platform_node_version').assert();
  64 
  65 var assert = require('assert');
  66 var async = require('/usr/node/node_modules/async');
  67 var bunyan = require('/usr/node/node_modules/bunyan');
  68 var cp = require('child_process');
  69 var dladm = require('/usr/vm/node_modules/dladm');

  70 var EventEmitter = require('events').EventEmitter;
  71 var exec = cp.exec;
  72 var execFile = cp.execFile;
  73 var expat = require('/usr/node/node_modules/node-expat');
  74 var fs = require('fs');
  75 var fw = require('/usr/fw/lib/fw');
  76 var http = require('http');
  77 var net = require('net');
  78 var path = require('path');
  79 var Qmp = require('/usr/vm/node_modules/qmp').Qmp;
  80 var spawn = cp.spawn;
  81 var sprintf = require('/usr/node/node_modules/sprintf').sprintf;
  82 var tty = require('tty');
  83 var util = require('util');
  84 
  85 var log_to_file = false;
  86 
  87 // keep the last 512 messages just in case we end up wanting them.
  88 var ringbuffer = new bunyan.RingBuffer({ limit: 512 });
  89 


4208                 }
4209                 createme.push(d);
4210             }
4211         }
4212     }
4213 
4214     function loggedCreateVolume(volume, cb) {
4215         return createVolume(volume, log, cb);
4216     }
4217 
4218     // create all the volumes we found that we need.
4219     async.forEachSeries(createme, loggedCreateVolume, function (err) {
4220         if (err) {
4221             callback(err);
4222         } else {
4223             callback();
4224         }
4225     });
4226 }
4227 



























4228 // writes a Zone's metadata JSON to /zones/<uuid>/config/metadata.json
4229 // and /zones/<uuid>/config/tags.json.
4230 function updateMetadata(vmobj, payload, log, callback)
4231 {
4232     var cmdata = {};
4233     var imdata = {};
4234     var key;
4235     var mdata = {};
4236     var mdata_filename;
4237     var tags = {};
4238     var tags_filename;
4239     var zonepath;
4240 
4241     assert(log, 'no logger passed to updateMetadata()');
4242 
4243     if (vmobj.hasOwnProperty('zonepath')) {
4244         zonepath = vmobj.zonepath;
4245     } else if (vmobj.hasOwnProperty('zpool')
4246         && vmobj.hasOwnProperty('zonename')) {
4247 


4297     // same thing for tags
4298     for (key in vmobj.tags) {
4299         if (vmobj.tags.hasOwnProperty(key)) {
4300             tags[key] = vmobj.tags[key];
4301             if (payload.hasOwnProperty('remove_tags')
4302                 && payload.remove_tags.indexOf(key) !== -1) {
4303 
4304                 // in the remove_* list, don't load it.
4305                 delete tags[key];
4306             }
4307         }
4308     }
4309 
4310     for (key in payload.set_tags) {
4311         if (payload.set_tags.hasOwnProperty(key)) {
4312             tags[key] = payload.set_tags[key];
4313         }
4314     }
4315 
4316     mdata = {'customer_metadata': cmdata, 'internal_metadata': imdata};
4317     fs.writeFile(mdata_filename, JSON.stringify(mdata, null, 2),
4318         function (err) {
4319             if (err) {
4320                 callback(err);
4321             } else {
4322                 log.debug('wrote metadata to ' + mdata_filename);
4323                 fs.writeFile(tags_filename, JSON.stringify(tags, null, 2),
4324                     function (e) {
4325                         if (e) {
4326                             callback(e);
4327                         } else {
4328                             log.debug('wrote tags to' + tags_filename);
4329                             callback();
4330                         }
4331                     }
4332                 );
4333             }
4334         }
4335     );
4336 }
4337 
4338 function saveMetadata(payload, log, callback)
4339 {
4340     var protovm = {};
4341 
4342     assert(log, 'no logger passed to saveMetadata()');
4343 
4344     if (!payload.hasOwnProperty('zonepath')
4345         || !payload.hasOwnProperty('zpool')
4346         || !payload.hasOwnProperty('zonename')) {
4347 
4348         callback(new Error('saveMetadata payload is missing zone '
4349             + 'properties.'));
4350         return;
4351     }
4352 
4353     protovm.zonepath = payload.zonepath;
4354     protovm.zpool = payload.zpool;
4355     protovm.zonename = payload.zonename;


11988                         // the actual updates above already did happen.
11989                         cb();
11990                     }
11991                 );
11992             } else {
11993                 cb();
11994             }
11995         }
11996 
11997     ], function (err, res) {
11998         log.debug('done applying updates to ' + oldobj.uuid);
11999         callback(err);
12000     });
12001 }
12002 
12003 exports.update = function (uuid, payload, options, callback)
12004 {
12005     var log;
12006     var new_vmobj;
12007     var vmobj;


12008 
12009     // options parameter is optional
12010     if (arguments.length === 3) {
12011         callback = arguments[2];
12012         options = {};
12013     }
12014 
12015     ensureLogging(true);
12016     if (options.hasOwnProperty('log')) {
12017         log = options.log;
12018     } else {
12019         log = VM.log.child({action: 'update', vm: uuid});
12020     }
12021 
12022     log.info('Updating VM ' + uuid + ' with initial payload:\n'
12023         + JSON.stringify(payload, null, 2));
12024 
12025     async.series([
12026         function (cb) {













12027             // for update we currently always load the whole vmobj since the
12028             // update functions may need to look at bits from the existing VM.
12029             VM.load(uuid, {log: log}, function (err, obj) {
12030                 if (err) {
12031                     cb(err);
12032                     return;
12033                 }
12034                 vmobj = obj;
12035                 cb();
12036             });
12037         }, function (cb) {
12038             normalizePayload(payload, vmobj, log, function (e) {
12039                 log.debug('Used payload:\n'
12040                     + JSON.stringify(payload, null, 2));
12041                 cb(e);
12042             });
12043         }, function (cb) {
12044             var deletables = [];
12045             var to_remove = [];
12046             var n;


12144         }, function (cb) {
12145             // Update the firewall data
12146             updateFirewallData(payload, vmobj, log, cb);
12147         }, function (cb) {
12148             // Do another full reload (all fields) so we can compare in
12149             // applyUpdates() and decide what's changed that we need to apply.
12150             VM.load(uuid, {log: log}, function (e, newobj) {
12151                 if (e) {
12152                     cb(e);
12153                 } else {
12154                     new_vmobj = newobj;
12155                     cb();
12156                 }
12157             });
12158         }, function (cb) {
12159             applyUpdates(vmobj, new_vmobj, payload, log, function () {
12160                 cb();
12161             });
12162         }
12163     ], function (e) {














12164         callback(e);

12165     });
12166 };
12167 
12168 function kill(uuid, log, callback)
12169 {
12170     var load_fields;
12171     var unset_autoboot = 'set autoboot=false';
12172 
12173     assert(log, 'no logger passed to kill()');
12174 
12175     log.info('Killing VM ' + uuid);
12176 
12177     load_fields = [
12178         'brand',
12179         'state',
12180         'transition_to',
12181         'uuid'
12182     ];
12183 
12184     /* We load here to ensure this vm exists. */




  50  * stop(uuid, options={[force=true]}, callback)
  51  * sysrq(uuid, req=[nmi|screenshot], options={}, callback)
  52  * update(uuid, properties, callback)
  53  *
  54  * Exported variables:
  55  *
  56  * logname - you can set this to a string [a-zA-Z_] to use as log name
  57  * logger - you can set this to a node-bunyan log stream to capture the logs
  58  * INFO_TYPES - list of supported types for the info command
  59  * SYSRQ_TYPES - list of supported requests for sysrq
  60  */
  61 
  62 // Ensure we're using the platform's node
  63 require('/usr/node/node_modules/platform_node_version').assert();
  64 
  65 var assert = require('assert');
  66 var async = require('/usr/node/node_modules/async');
  67 var bunyan = require('/usr/node/node_modules/bunyan');
  68 var cp = require('child_process');
  69 var dladm = require('/usr/vm/node_modules/dladm');
  70 var lock = require('/usr/vm/node_modules/locker').lock;
  71 var EventEmitter = require('events').EventEmitter;
  72 var exec = cp.exec;
  73 var execFile = cp.execFile;
  74 var expat = require('/usr/node/node_modules/node-expat');
  75 var fs = require('fs');
  76 var fw = require('/usr/fw/lib/fw');
  77 var http = require('http');
  78 var net = require('net');
  79 var path = require('path');
  80 var Qmp = require('/usr/vm/node_modules/qmp').Qmp;
  81 var spawn = cp.spawn;
  82 var sprintf = require('/usr/node/node_modules/sprintf').sprintf;
  83 var tty = require('tty');
  84 var util = require('util');
  85 
  86 var log_to_file = false;
  87 
  88 // keep the last 512 messages just in case we end up wanting them.
  89 var ringbuffer = new bunyan.RingBuffer({ limit: 512 });
  90 


4209                 }
4210                 createme.push(d);
4211             }
4212         }
4213     }
4214 
4215     function loggedCreateVolume(volume, cb) {
4216         return createVolume(volume, log, cb);
4217     }
4218 
4219     // create all the volumes we found that we need.
4220     async.forEachSeries(createme, loggedCreateVolume, function (err) {
4221         if (err) {
4222             callback(err);
4223         } else {
4224             callback();
4225         }
4226     });
4227 }
4228 
4229 function writeAndRename(log, name, destfile, file_data, callback)
4230 {
4231     var tempfile = destfile + '.new';
4232 
4233     log.debug('writing ' + name + ' to ' + tempfile);
4234 
4235     fs.writeFile(tempfile, file_data, function (err) {
4236         if (err) {
4237             callback(err);
4238             return;
4239         }
4240 
4241         log.debug('wrote ' + name + ' to ' + tempfile);
4242         log.debug('renaming from ' + tempfile + ' to ' + destfile);
4243 
4244         fs.rename(tempfile, destfile, function (_err) {
4245             if (_err) {
4246                 callback(_err);
4247                 return;
4248             }
4249 
4250             log.debug('renamed from ' + tempfile + ' to ' + destfile);
4251             callback();
4252         });
4253     });
4254 }
4255 
4256 // writes a Zone's metadata JSON to /zones/<uuid>/config/metadata.json
4257 // and /zones/<uuid>/config/tags.json.
4258 function updateMetadata(vmobj, payload, log, callback)
4259 {
4260     var cmdata = {};
4261     var imdata = {};
4262     var key;
4263     var mdata = {};
4264     var mdata_filename;
4265     var tags = {};
4266     var tags_filename;
4267     var zonepath;
4268 
4269     assert(log, 'no logger passed to updateMetadata()');
4270 
4271     if (vmobj.hasOwnProperty('zonepath')) {
4272         zonepath = vmobj.zonepath;
4273     } else if (vmobj.hasOwnProperty('zpool')
4274         && vmobj.hasOwnProperty('zonename')) {
4275 


4325     // same thing for tags
4326     for (key in vmobj.tags) {
4327         if (vmobj.tags.hasOwnProperty(key)) {
4328             tags[key] = vmobj.tags[key];
4329             if (payload.hasOwnProperty('remove_tags')
4330                 && payload.remove_tags.indexOf(key) !== -1) {
4331 
4332                 // in the remove_* list, don't load it.
4333                 delete tags[key];
4334             }
4335         }
4336     }
4337 
4338     for (key in payload.set_tags) {
4339         if (payload.set_tags.hasOwnProperty(key)) {
4340             tags[key] = payload.set_tags[key];
4341         }
4342     }
4343 
4344     mdata = {'customer_metadata': cmdata, 'internal_metadata': imdata};
4345 
4346     async.series([
4347         function (next) {
4348             writeAndRename(log, 'metadata', mdata_filename,
4349                 JSON.stringify(mdata, null, 2), next);
4350         },
4351         function (next) {
4352             writeAndRename(log, 'tags', tags_filename,
4353                 JSON.stringify(tags, null, 2), next);








4354         }
4355     ], callback);
4356 }
4357 
4358 function saveMetadata(payload, log, callback)
4359 {
4360     var protovm = {};
4361 
4362     assert(log, 'no logger passed to saveMetadata()');
4363 
4364     if (!payload.hasOwnProperty('zonepath')
4365         || !payload.hasOwnProperty('zpool')
4366         || !payload.hasOwnProperty('zonename')) {
4367 
4368         callback(new Error('saveMetadata payload is missing zone '
4369             + 'properties.'));
4370         return;
4371     }
4372 
4373     protovm.zonepath = payload.zonepath;
4374     protovm.zpool = payload.zpool;
4375     protovm.zonename = payload.zonename;


12008                         // the actual updates above already did happen.
12009                         cb();
12010                     }
12011                 );
12012             } else {
12013                 cb();
12014             }
12015         }
12016 
12017     ], function (err, res) {
12018         log.debug('done applying updates to ' + oldobj.uuid);
12019         callback(err);
12020     });
12021 }
12022 
12023 exports.update = function (uuid, payload, options, callback)
12024 {
12025     var log;
12026     var new_vmobj;
12027     var vmobj;
12028     var unlock;
12029     var lockpath;
12030 
12031     // options parameter is optional
12032     if (arguments.length === 3) {
12033         callback = arguments[2];
12034         options = {};
12035     }
12036 
12037     ensureLogging(true);
12038     if (options.hasOwnProperty('log')) {
12039         log = options.log;
12040     } else {
12041         log = VM.log.child({action: 'update', vm: uuid});
12042     }
12043 
12044     log.info('Updating VM ' + uuid + ' with initial payload:\n'
12045         + JSON.stringify(payload, null, 2));
12046 
12047     async.series([
12048         function (cb) {
12049             lockpath = '/var/run/vm.' + uuid + '.config.lockfile';
12050             log.debug('acquiring lock on ' + lockpath);
12051             lock(lockpath, function (err, _unlock) {
12052                 log.debug('acquired lock on ' + lockpath);
12053                 if (err) {
12054                     cb(err);
12055                     return;
12056                 }
12057                 unlock = _unlock;
12058                 cb();
12059             });
12060         },
12061         function (cb) {
12062             // for update we currently always load the whole vmobj since the
12063             // update functions may need to look at bits from the existing VM.
12064             VM.load(uuid, {log: log}, function (err, obj) {
12065                 if (err) {
12066                     cb(err);
12067                     return;
12068                 }
12069                 vmobj = obj;
12070                 cb();
12071             });
12072         }, function (cb) {
12073             normalizePayload(payload, vmobj, log, function (e) {
12074                 log.debug('Used payload:\n'
12075                     + JSON.stringify(payload, null, 2));
12076                 cb(e);
12077             });
12078         }, function (cb) {
12079             var deletables = [];
12080             var to_remove = [];
12081             var n;


12179         }, function (cb) {
12180             // Update the firewall data
12181             updateFirewallData(payload, vmobj, log, cb);
12182         }, function (cb) {
12183             // Do another full reload (all fields) so we can compare in
12184             // applyUpdates() and decide what's changed that we need to apply.
12185             VM.load(uuid, {log: log}, function (e, newobj) {
12186                 if (e) {
12187                     cb(e);
12188                 } else {
12189                     new_vmobj = newobj;
12190                     cb();
12191                 }
12192             });
12193         }, function (cb) {
12194             applyUpdates(vmobj, new_vmobj, payload, log, function () {
12195                 cb();
12196             });
12197         }
12198     ], function (e) {
12199         // If we were able to hold the lockfile, and thus have an unlock
12200         // callback, we must call it before returning, whether or not
12201         // there was an error.
12202         if (unlock) {
12203             log.debug('releasing lock on ' + lockpath);
12204             unlock(function (unlock_err) {
12205                 if (unlock_err) {
12206                     log.error(err, 'unlock error! (path ' + lockpath + ')');
12207                 } else {
12208                     log.debug('released lock on ' + lockpath);
12209                 }
12210                 callback(e);
12211             });
12212         } else {
12213             callback(e);
12214         }
12215     });
12216 };
12217 
12218 function kill(uuid, log, callback)
12219 {
12220     var load_fields;
12221     var unset_autoboot = 'set autoboot=false';
12222 
12223     assert(log, 'no logger passed to kill()');
12224 
12225     log.info('Killing VM ' + uuid);
12226 
12227     load_fields = [
12228         'brand',
12229         'state',
12230         'transition_to',
12231         'uuid'
12232     ];
12233 
12234     /* We load here to ensure this vm exists. */