PageRenderTime 54ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/src/vm/node_modules/vmload/vmload-xml.js

https://gitlab.com/loomis/smartos-live
JavaScript | 436 lines | 277 code | 43 blank | 116 comment | 64 complexity | be9e21b9440ad8feb8bc0f24247feb5e MD5 | raw file
  1. /*
  2. * CDDL HEADER START
  3. *
  4. * The contents of this file are subject to the terms of the
  5. * Common Development and Distribution License, Version 1.0 only
  6. * (the "License"). You may not use this file except in compliance
  7. * with the License.
  8. *
  9. * You can obtain a copy of the license at http://smartos.org/CDDL
  10. *
  11. * See the License for the specific language governing permissions
  12. * and limitations under the License.
  13. *
  14. * When distributing Covered Code, include this CDDL HEADER in each
  15. * file.
  16. *
  17. * If applicable, add the following below this CDDL HEADER, with the
  18. * fields enclosed by brackets "[]" replaced with your own identifying
  19. * information: Portions Copyright [yyyy] [name of copyright owner]
  20. *
  21. * CDDL HEADER END
  22. *
  23. * Copyright (c) 2014, Joyent, Inc. All rights reserved.
  24. *
  25. *
  26. * vmload-xml: loads VM properties from /etc/zones/<zonename>.xml
  27. *
  28. * This is a subcomponent of vmload. The purpose of this piece is to convert
  29. * the values from the /etc/zones/<zonename>.xml file for a given VM into
  30. * a JSON object that serves as a building-block of a VM JSON object. Fields
  31. * are translated to their appropriate data types as defined in proptable.js.
  32. *
  33. */
  34. // Ensure we're using the expected node
  35. // XXX TODO update this when it's been moved.
  36. // require('/usr/vm/node/node_modules/platform_node_version').assert();
  37. var assert = require('assert');
  38. // XXX TODO update this when it's been moved
  39. // var expat = require('/usr/vm/node/node_modules/node-expat');
  40. var expat = require('/usr/node/node_modules/node-expat');
  41. var fs = require('fs');
  42. var props = require('/usr/vm/node_modules/props');
  43. var rtrim = require('utils').rtrim;
  44. // load generated tables of data
  45. var BRAND_OPTIONS = props.BRAND_OPTIONS;
  46. var XML_PROPERTIES = props.XML_PROPERTIES;
  47. /*
  48. * endElement() is called when we get to the </element>
  49. */
  50. function endElement(name, state)
  51. {
  52. // trim stack back above this element
  53. var stack = state.stack;
  54. while (stack.pop() !== name) {
  55. // do nothing, we just want to consume.
  56. continue;
  57. }
  58. }
  59. /*
  60. * This function parses the zone XML (string) from 'data' and adds the VM
  61. * properties to a new object. Upon completion it calls:
  62. *
  63. * callback(null, <object>)
  64. *
  65. * If an error occurs parsing the XML from 'data', this will call:
  66. *
  67. * callback(error)
  68. *
  69. * with an Error object having an error string and one of the following as
  70. * error.code:
  71. *
  72. * 'PARSE_ERROR' - error parsing XML
  73. *
  74. */
  75. function getVmobjXML(data, options, callback)
  76. {
  77. var allowed;
  78. var brand_options;
  79. var err;
  80. var fields;
  81. var log;
  82. var obj = {};
  83. var parser;
  84. var state = {};
  85. assert(options.log, 'no logger passed to getVmobj()');
  86. log = options.log;
  87. parser = new expat.Parser('UTF-8');
  88. state.obj = obj;
  89. parser.on('startElement', function (name, attrs) {
  90. startElement(name, attrs, state, log);
  91. return;
  92. });
  93. parser.on('endElement', function (name) {
  94. endElement(name, state);
  95. return;
  96. });
  97. if (!parser.parse(data)) {
  98. err = new Error(parser.getError());
  99. err.code = 'PARSE_ERROR';
  100. callback(err);
  101. return;
  102. }
  103. /*
  104. * Ensure we know how to handle this brand, if not someone needs to change
  105. * proptable.js and rebuild props.js
  106. */
  107. assert(obj.brand && (typeof (obj.brand) === 'string'), 'VM missing brand: '
  108. + JSON.stringify(obj));
  109. if (BRAND_OPTIONS.hasOwnProperty(obj.brand)) {
  110. brand_options = BRAND_OPTIONS[obj.brand];
  111. } else {
  112. log.warn('VM has unsupported brand: ' + JSON.stringify(obj)
  113. + ' treating as "joyent"');
  114. brand_options = BRAND_OPTIONS['joyent'];
  115. }
  116. assert(brand_options.hasOwnProperty('allowed_properties'),
  117. 'Missing list of allowed properties for brand: ' + obj.brand);
  118. allowed = brand_options.allowed_properties;
  119. // add in fields we want even if they're empty
  120. if (!obj.hasOwnProperty('nics')) {
  121. obj.nics = [];
  122. }
  123. if (!obj.hasOwnProperty('disks') && allowed.hasOwnProperty('disks')) {
  124. obj.disks = [];
  125. }
  126. // If this brand uses 'vm_autoboot' instead of autoboot, swap them now.
  127. if (brand_options.features.use_vm_autoboot) {
  128. obj.autoboot = obj.vm_autoboot;
  129. delete obj.vm_autoboot;
  130. }
  131. // transitions are virtual properties which spill over to other fields.
  132. if (obj.hasOwnProperty('transition')) {
  133. fields = rtrim(obj.transition).split(':');
  134. if (fields.length === 3) {
  135. delete obj.transition;
  136. obj.state = fields[0];
  137. obj.transition_to = fields[1];
  138. obj.transition_expire = Number(fields[2]);
  139. } else {
  140. log.debug('getVmobj() ignoring bad value for '
  141. + 'transition "' + obj.transition + '"');
  142. }
  143. }
  144. // sort the disks + nics + filesystems by index
  145. if (obj.hasOwnProperty('disks')) {
  146. indexSort(obj.disks, 'path', /^.*-disk(\d+)$/);
  147. }
  148. if (obj.hasOwnProperty('nics')) {
  149. indexSort(obj.nics, 'interface', /^net(\d+)$/);
  150. }
  151. if (obj.hasOwnProperty('filesystems')) {
  152. indexSort(obj.filesystems, 'target', /^(.*)$/);
  153. }
  154. callback(null, obj);
  155. }
  156. /*
  157. * This function reads the file 'filename' and passes the data to getVmobjXML().
  158. * The callback() function will be called in the same manner as with getVmobjXML
  159. * with the possibility of any errors resulting from fs.readFile().
  160. *
  161. */
  162. function getVmobjXMLFile(filename, options, callback)
  163. {
  164. var log;
  165. assert(filename, 'no filename passed to getVmobj()');
  166. assert(options.log, 'no logger passed to getVmobj()');
  167. log = options.log;
  168. log.trace('reading zone XML from ' + filename);
  169. fs.readFile(filename, function (error, data) {
  170. if (error) {
  171. if (!error.code) {
  172. error.code = 'READ_FILE';
  173. }
  174. callback(error);
  175. return;
  176. }
  177. getVmobjXML(data.toString(), options, callback);
  178. return;
  179. });
  180. }
  181. /*
  182. * Sort an array of objects by the given field (ascending)
  183. */
  184. function indexSort(obj, field, pattern)
  185. {
  186. obj.sort(function (a, b) {
  187. var avalue = 0;
  188. var bvalue = 0;
  189. var matches;
  190. if (a.hasOwnProperty(field)) {
  191. matches = a[field].match(pattern);
  192. if (matches) {
  193. avalue = Number(matches[1]);
  194. }
  195. }
  196. if (b.hasOwnProperty(field)) {
  197. matches = b[field].match(pattern);
  198. if (matches) {
  199. bvalue = Number(matches[1]);
  200. }
  201. }
  202. return avalue - bvalue;
  203. });
  204. }
  205. /*
  206. * This is called by the parser when we get to the start of an element. We do
  207. * all the work here and just allow endElement to pop us back up the stack
  208. * we're maintaining every time an element starts.
  209. *
  210. * In proptable.js we define those elements we'll parse here using entries like:
  211. *
  212. * alias: {
  213. * zonexml: 'zone.attr.alias',
  214. * loadValueTranslator: 'utils.unbase64'
  215. * }
  216. *
  217. * which means when we get to the part of the XML that looks something like:
  218. *
  219. * <zone>
  220. * ...
  221. * <attr name="alias" type="string" value="STRING"/>
  222. *
  223. * we'll take STRING, run it through the utils.unbase64() function and use the
  224. * result as the value of 'alias' in our new VM object.
  225. *
  226. */
  227. function startElement(name, attrs, state, log)
  228. {
  229. var disk;
  230. var filesystem;
  231. var k;
  232. var nic;
  233. var obj;
  234. var stack;
  235. var transformed;
  236. var value;
  237. var where;
  238. assert(log, 'no logger passed to startElement()');
  239. if (!state.hasOwnProperty('stack')) {
  240. state.stack = [];
  241. }
  242. obj = state.obj;
  243. stack = state.stack;
  244. stack.push(name);
  245. where = stack.join('.');
  246. if (where === 'zone') {
  247. Object.keys(attrs).forEach(function (key) {
  248. k = where + '.' + key;
  249. transformed = transformProperty(k, attrs[key], log);
  250. if (!transformed) {
  251. return;
  252. }
  253. obj[transformed.key] = transformed.value;
  254. });
  255. } else if (where === 'zone.attr') {
  256. k = where + '.' + attrs.name;
  257. transformed = transformProperty(k, attrs.value, log);
  258. if (!transformed) {
  259. return;
  260. }
  261. obj[transformed.key] = transformed.value;
  262. } else if (where === 'zone.dataset') {
  263. /*
  264. * zone.dataset is special in that attrs has only a 'name' and we're
  265. * just making an array of the names, so we don't pass through
  266. * transformProperty() like the others.
  267. */
  268. if (!obj.datasets) {
  269. obj.datasets = [];
  270. }
  271. obj.datasets.push(attrs.name);
  272. } else if (where === 'zone.device') {
  273. disk = {};
  274. Object.keys(attrs).forEach(function (key) {
  275. k = stack.join('.') + '.' + key;
  276. transformed = transformProperty(k, attrs[key], log);
  277. if (!transformed) {
  278. return;
  279. }
  280. disk[transformed.key] = transformed.value;
  281. });
  282. if (!obj.disks) {
  283. obj.disks = [];
  284. }
  285. obj.disks.push(disk);
  286. } else if (where === 'zone.device.net-attr') {
  287. disk = obj.disks[obj.disks.length - 1];
  288. k = where + '.' + attrs.name;
  289. transformed = transformProperty(k, attrs.value, log);
  290. if (!transformed) {
  291. return;
  292. }
  293. disk[transformed.key] = transformed.value;
  294. } else if (where === 'zone.filesystem') {
  295. filesystem = {};
  296. Object.keys(attrs).forEach(function (key) {
  297. k = stack.join('.') + '.' + key;
  298. transformed = transformProperty(k, attrs[key], log);
  299. if (!transformed) {
  300. return;
  301. }
  302. filesystem[transformed.key] = transformed.value;
  303. });
  304. if (!obj.filesystems) {
  305. obj.filesystems = [];
  306. }
  307. obj.filesystems.push(filesystem);
  308. } else if (where === 'zone.filesystem.fsoption') {
  309. /*
  310. * filesystem.fsoptions is special in that attrs has only a 'name' and
  311. * we're just making an array of the names, so we don't pass through
  312. * transformProperty() like the others.
  313. */
  314. filesystem = obj.filesystems[obj.filesystems.length - 1];
  315. if (!filesystem.options) {
  316. filesystem.options = [];
  317. }
  318. filesystem.options.push(attrs.name);
  319. } else if (where === 'zone.network') {
  320. nic = {};
  321. Object.keys(attrs).forEach(function (key) {
  322. k = stack.join('.') + '.' + key;
  323. transformed = transformProperty(k, attrs[key], log);
  324. if (!transformed) {
  325. return;
  326. }
  327. nic[transformed.key] = transformed.value;
  328. });
  329. if (!obj.nics) {
  330. obj.nics = [];
  331. }
  332. obj.nics.push(nic);
  333. } else if (where === 'zone.network.net-attr') {
  334. nic = obj.nics[obj.nics.length - 1];
  335. k = where + '.' + attrs.name;
  336. transformed = transformProperty(k, attrs.value, log);
  337. if (!transformed) {
  338. return;
  339. }
  340. nic[transformed.key] = transformed.value;
  341. } else if (where === 'zone.rctl') {
  342. stack.push(attrs.name);
  343. } else if ((value = where.match(/^(zone.rctl.zone.*.)rctl-value$/))) {
  344. k = value[1] + attrs.priv + '.' + attrs.action;
  345. transformed = transformProperty(k, attrs.limit, log);
  346. if (!transformed) {
  347. return;
  348. }
  349. obj[transformed.key] = transformed.value;
  350. } else {
  351. log.error({where: where, attrs: attrs}, 'unhandled zone XML property');
  352. }
  353. }
  354. function transformProperty(key, value, log)
  355. {
  356. var result = {};
  357. log.trace({
  358. key: {name: key, type: typeof (key)},
  359. value: {name: value, type: typeof (value)}
  360. }, 'before transformProperty()');
  361. if (!XML_PROPERTIES.hasOwnProperty(key)) {
  362. // missing from XML_PROPERTIES, add to proptable.js / properties
  363. log.warn('"' + key + '" not found in XML_PROPERTIES');
  364. return null;
  365. }
  366. if (XML_PROPERTIES[key].ignore) {
  367. log.trace('ignoring "' + key + '" due to "ignore" XML_PROPERTIES');
  368. return null;
  369. }
  370. /*
  371. * If we're missing objname, that means there was an error building props.js
  372. * and you most likely need to fix expander.js. All entries in properties
  373. * in proptable.js should have their keys as the value for 'objname' in
  374. * props.js.
  375. */
  376. assert(XML_PROPERTIES[key].hasOwnProperty('objname'), 'corrupt props.js, '
  377. + ' XML_PROPERTIES missing "objname" for ' + key);
  378. result.key = XML_PROPERTIES[key].objname;
  379. if (XML_PROPERTIES[key].hasOwnProperty('loadValueTranslator')) {
  380. result.value = XML_PROPERTIES[key].loadValueTranslator(value);
  381. } else {
  382. result.value = value;
  383. }
  384. log.trace({
  385. key: {name: result.key, type: typeof (result.key)},
  386. value: {name: result.value, type: typeof (result.value)}
  387. }, 'after transformProperty()');
  388. return result;
  389. }
  390. module.exports = {
  391. getVmobjXML: getVmobjXML,
  392. getVmobjXMLFile: getVmobjXMLFile
  393. };