Print this page
OS-2573 metadata agent support for PUT, DELETE and V2 protocol
Split |
Close |
Expand all |
Collapse all |
--- old/src/vm/lib/metadata/agent.js
+++ new/src/vm/lib/metadata/agent.js
1 +// vim: set ts=4 sts=4 sw=4 et:
1 2 var VM = require('/usr/vm/node_modules/VM');
2 3 var ZWatch = require('./zwatch');
3 4 var common = require('./common');
5 +var crc32 = require('./crc32');
4 6 var async = require('/usr/node/node_modules/async');
5 7 var execFile = require('child_process').execFile;
6 8 var fs = require('fs');
7 9 var net = require('net');
8 10 var path = require('path');
9 11 var util = require('util');
10 12 var zsock = require('/usr/node/node_modules/zsock');
11 13 var zutil = require('/usr/node/node_modules/zutil');
12 14
13 15 var sdc_fields = [
14 16 'alias',
15 17 'billing_id',
16 18 'brand',
17 19 'cpu_cap',
18 20 'cpu_shares',
19 21 'create_timestamp',
20 22 'server_uuid',
21 23 'image_uuid',
22 24 'datacenter_name',
23 25 'do_not_inventory',
24 26 'dns_domain',
25 27 'force_metadata_socket',
26 28 'fs_allowed',
27 29 'hostname',
28 30 'limit_priv',
29 31 'last_modified',
30 32 'max_physical_memory',
31 33 'max_locked_memory',
32 34 'max_lwps',
33 35 'max_swap',
34 36 'nics',
35 37 'owner_uuid',
36 38 'package_name',
37 39 'package_version',
38 40 'quota',
39 41 'ram',
40 42 'resolvers',
41 43 'routes',
42 44 'state',
43 45 'tmpfs',
44 46 'uuid',
45 47 'vcpus',
46 48 'vnc_port',
47 49 'zfs_io_priority',
48 50 'zonepath',
49 51 'zonename'
50 52 ];
51 53
52 54 var MetadataAgent = module.exports = function (options) {
53 55 this.log = options.log;
54 56 this.zlog = {};
55 57 this.zones = {};
56 58 this.zoneConnections = {};
57 59 };
58 60
59 61 MetadataAgent.prototype.createZoneLog = function (type, zonename) {
60 62 var self = this;
61 63 self.zlog[zonename] = self.log.child({brand: type, 'zonename': zonename});
62 64 return (self.zlog[zonename]);
63 65 };
64 66
65 67 MetadataAgent.prototype.updateZone = function (zonename, callback) {
66 68 var self = this;
67 69 var log = self.log;
68 70
69 71 function shouldLoad(cb) {
70 72 if (!self.zones.hasOwnProperty(zonename)) {
71 73 // don't have a cache, load this guy
72 74 log.info('no cache for: ' + zonename + ', loading');
73 75 cb(true);
74 76 return;
75 77 }
76 78
77 79 // we do have a cached version, we'll reload only if timestamp changed.
78 80 fs.stat('/etc/zones/' + zonename + '.xml', function (err, stats) {
79 81 var old_mtime;
80 82
81 83 if (err) {
82 84 // fail open when we can't stat
83 85 log.error({err: err}, 'cannot fs.stat() ' + zonename);
84 86 cb(true);
85 87 return;
86 88 }
87 89
88 90 old_mtime = (new Date(self.zones[zonename].last_modified));
89 91 if (stats.mtime.getTime() > old_mtime.getTime()) {
90 92 log.info('last_modified was updated, reloading: ' + zonename);
91 93 cb(true);
92 94 return;
93 95 }
94 96
95 97 log.debug('using cache for: ' + zonename);
96 98 cb(false);
97 99 });
98 100 }
99 101
100 102 shouldLoad(function (load) {
101 103 if (load) {
102 104 VM.lookup({ zonename: zonename }, { fields: sdc_fields },
103 105 function (error, machines) {
104 106 self.zones[zonename] = machines[0];
105 107 callback();
106 108 return;
107 109 }
108 110 );
109 111 } else {
110 112 // no need to reload since there's no change, use existing data
111 113 callback();
112 114 return;
113 115 }
114 116 });
115 117 };
116 118
117 119 MetadataAgent.prototype.createServersOnExistingZones = function () {
118 120 var self = this;
119 121
120 122 VM.lookup({}, { fields: sdc_fields }, function (error, zones) {
121 123 async.forEach(zones, function (zone, cb) {
122 124 if (zone.zonename === 'global') {
123 125 cb();
124 126 return;
125 127 }
126 128
127 129 self.zones[zone.zonename] = zone;
128 130
129 131 if (zone.state !== 'running') {
130 132 self.log.debug('skipping zone ' + zone.zonename + ' which has '
131 133 + 'non-running state: ' + zone.state);
132 134 cb();
133 135 return;
134 136 }
135 137
136 138 if (error) {
137 139 throw error;
138 140 }
139 141
140 142 if (!self.zlog[zone.zonename]) {
141 143 // create a logger specific to this VM
142 144 self.createZoneLog(zone.brand, zone.zonename);
143 145 }
144 146
145 147 if (zone.brand === 'kvm') {
146 148 self.startKVMSocketServer(zone.zonename, function (err) {
147 149 cb();
148 150 });
149 151 } else {
150 152 self.startZoneSocketServer(zone.zonename, function (err) {
151 153 cb();
152 154 });
153 155 }
154 156 }, function (err) {
155 157 self.log.info('Created zone metadata sockets on ' + zones.length
156 158 + ' zones');
157 159 });
158 160 });
159 161 };
160 162
161 163 MetadataAgent.prototype.startDeletedZonesPurger = function () {
162 164 var cmd = '/usr/sbin/zoneadm';
163 165 var self = this;
164 166
165 167 // Every 5 minutes we check to see whether zones we've got in self.zones
166 168 // were deleted. If they are, we delete the record from the cache.
167 169 setInterval(function () {
168 170 execFile(cmd, ['list', '-c'], function (err, stdout, stderr) {
169 171 var zones = {};
170 172 if (err) {
171 173 self.log.error({err: err}, 'unable to get list of zones');
172 174 return;
173 175 }
174 176
175 177 // each output line is a zonename, so we turn this into an object
176 178 // that looks like:
177 179 //
178 180 // {
179 181 // zonename: true,
180 182 // zonename: true
181 183 // ...
182 184 // }
183 185 //
184 186 // so we can then loop through all the cached zonenames and remove
185 187 // those that don't exist on the system any longer.
186 188 stdout.split(/\n/).forEach(function (z) {
187 189 zones[z] = true;
188 190 });
189 191 Object.keys(self.zones).forEach(function (z) {
190 192 if (!zones.hasOwnProperty(z)) {
191 193 self.log.info(z + ' no longer exists, purging from cache');
192 194 delete self.zones[z];
193 195 if (self.zlog.hasOwnProperty(z)) {
194 196 delete self.zlog[z];
195 197 }
196 198 }
197 199 });
198 200 });
199 201 }, (5 * 60 * 1000));
200 202
201 203 self.log.info('Setup interval to purge deleted zones.');
202 204 };
203 205
204 206 MetadataAgent.prototype.start = function () {
205 207 var self = this;
206 208 var zwatch = this.zwatch = new ZWatch();
207 209 self.createServersOnExistingZones();
208 210 self.startDeletedZonesPurger();
209 211
210 212 zwatch.on('zone_transition', function (msg) {
211 213 if (msg.cmd === 'start') {
212 214 self.updateZone(msg.zonename, function (error) {
213 215 if (error) {
214 216 self.log.error({err: error}, 'Error updating attributes: '
215 217 + error.message);
216 218 return;
217 219 }
218 220 if (!self.zlog[msg.zonename]) {
219 221 // create a logger specific to this VM
220 222 self.createZoneLog(self.zones[msg.zonename].brand,
221 223 msg.zonename);
222 224 }
223 225 if (self.zones[msg.zonename].brand === 'kvm') {
224 226 self.startKVMSocketServer(msg.zonename);
225 227 } else {
226 228 self.startZoneSocketServer(msg.zonename);
227 229 }
228 230 });
229 231 } else if (msg.cmd === 'stop') {
230 232 if (self.zoneConnections[msg.zonename]) {
231 233 self.zoneConnections[msg.zonename].end();
232 234 }
233 235 }
234 236 });
235 237
236 238 zwatch.start(self.log);
237 239 };
238 240
239 241 MetadataAgent.prototype.stop = function () {
240 242 this.zwatch.stop();
241 243 };
242 244
243 245 MetadataAgent.prototype.startKVMSocketServer = function (zonename, callback) {
244 246 var self = this;
245 247 var vmobj = self.zones[zonename];
246 248 var zlog = self.zlog[zonename];
247 249 var sockpath = path.join(vmobj.zonepath, '/root/tmp/vm.ttyb');
248 250
249 251 zlog.info('Starting socket server');
250 252
251 253 async.waterfall([
252 254 function (cb) {
253 255 common.retryUntil(2000, 120000,
254 256 function (c) {
255 257 fs.exists(sockpath, function (exists) {
256 258 zlog.debug(sockpath + ' exists: ' + exists);
257 259 setTimeout(function () {
258 260 c(null, exists);
259 261 }, 1000);
260 262 });
261 263 }, function (error) {
262 264 if (error) {
263 265 zlog.error({err: error}, 'Timed out waiting for '
264 266 + 'metadata socket');
265 267 } else {
266 268 zlog.debug('returning from startKVMSocketServer w/o '
267 269 + 'error');
268 270 }
269 271 cb(error);
270 272 }
271 273 );
272 274 }
273 275 ], function (error) {
274 276 var zopts = { zone: zonename, sockpath: sockpath };
275 277 self.createKVMServer(zopts, function () {
276 278 if (callback) {
277 279 callback();
278 280 }
279 281 });
280 282 });
281 283 };
282 284
283 285 function rtrim(str, chars) {
284 286 chars = chars || '\\s';
285 287 str = str || '';
286 288 return str.replace(new RegExp('[' + chars + ']+$', 'g'), '');
287 289 }
288 290
289 291 MetadataAgent.prototype.createKVMServer = function (zopts, callback) {
290 292 var self = this;
291 293 var zlog = self.zlog[zopts.zone];
292 294 var kvmstream = new net.Stream();
293 295
294 296 self.zoneConnections[zopts.zone] = {
295 297 conn: new net.Stream(),
296 298 done: false,
297 299 end: function () {
298 300 if (this.done) {
299 301 return;
300 302 }
301 303 this.done = true;
302 304 zlog.info('Closing kvm stream for ' + zopts.zone);
303 305 kvmstream.end();
304 306 }
305 307 };
306 308
307 309 var buffer = '';
308 310 var handler = self.makeMetadataHandler(zopts.zone, kvmstream);
309 311
310 312 kvmstream.on('data', function (data) {
311 313 var chunk, chunks;
312 314 buffer += data.toString();
313 315 chunks = buffer.split('\n');
314 316 while (chunks.length > 1) {
315 317 chunk = chunks.shift();
316 318 handler(chunk);
317 319 }
318 320 buffer = chunks.pop();
319 321 });
320 322
321 323 kvmstream.on('error', function (e) {
322 324 zlog.error({err: e}, 'KVM Socket error: ' + e.message);
323 325 });
324 326
325 327 kvmstream.connect(zopts.sockpath);
326 328 callback();
327 329 };
328 330
329 331 MetadataAgent.prototype.startZoneSocketServer =
330 332 function (zonename, callback) {
331 333 var self = this;
332 334 var zlog = self.zlog[zonename];
333 335 var zonePath = self.zones[zonename].zonepath;
334 336 var localpath = '/.zonecontrol';
335 337 var zonecontrolpath = path.join(zonePath, 'root', localpath);
336 338
337 339 zlog.info('Starting socket server');
338 340
339 341 function ensureZonecontrolExists(cb) {
340 342 fs.exists(zonecontrolpath, function (exists) {
341 343 if (exists) {
342 344 cb();
343 345 return;
344 346 } else {
345 347 fs.mkdir(zonecontrolpath, parseInt('700', 8), function (error) {
346 348 cb(error);
347 349 });
348 350 }
349 351 });
350 352 }
351 353
352 354 ensureZonecontrolExists(function (err) {
353 355 var sockpath = path.join(localpath, 'metadata.sock');
354 356 var zopts = {
355 357 zone: zonename,
356 358 path: sockpath
357 359 };
358 360
359 361 if (err) {
360 362 callback({err: err}, 'unable to create ' + zonecontrolpath);
361 363 return;
362 364 }
363 365
364 366 self.createZoneSocket(zopts, function (createErr) {
365 367 if (createErr) {
366 368 // We call callback here, but don't include the error because
367 369 // this is running in async.forEach and we don't want to fail
368 370 // the others and there's nothing we can do to recover anyway.
369 371 if (callback) {
370 372 callback();
371 373 }
372 374 return;
373 375 }
374 376
375 377 zlog.info('Zone socket created.');
376 378
377 379 if (callback) {
378 380 callback();
379 381 }
380 382 });
381 383 });
382 384 };
383 385
384 386 MetadataAgent.prototype.createZoneSocket =
385 387 function (zopts, callback, waitSecs) {
386 388 var self = this;
387 389 var zlog = self.zlog[zopts.zone];
388 390 waitSecs = waitSecs || 1;
389 391
390 392 zsock.createZoneSocket(zopts, function (error, fd) {
391 393 if (error) {
392 394 // If we get errors trying to create the zone socket, wait and then
393 395 // keep retrying.
394 396 waitSecs = waitSecs * 2;
395 397 zlog.error(
396 398 { err: error },
397 399 'createZoneSocket error, %s seconds before next attempt',
398 400 waitSecs);
399 401 setTimeout(function () {
400 402 self.createZoneSocket(zopts, function () {}, waitSecs);
401 403 }, waitSecs * 1000);
402 404 return;
403 405 }
404 406
405 407 var server = net.createServer(function (socket) {
406 408 var handler = self.makeMetadataHandler(zopts.zone, socket);
407 409 var buffer = '';
408 410 socket.on('data', function (data) {
409 411 var chunk, chunks;
410 412 buffer += data.toString();
411 413 chunks = buffer.split('\n');
412 414 while (chunks.length > 1) {
413 415 chunk = chunks.shift();
414 416 handler(chunk);
415 417 }
416 418 buffer = chunks.pop();
417 419 });
418 420
419 421 socket.on('error', function (err) {
420 422 zlog.error({err: err}, 'ZSocket error: ' + err.message);
421 423 zlog.info('Attempting to recover; closing and recreating zone '
422 424 + 'socket and server.');
423 425 try {
424 426 server.close();
425 427 } catch (e) {
426 428 zlog.error({err: e}, 'Caught exception closing server: '
427 429 + e.message);
428 430 }
429 431
430 432 socket.end();
431 433 self.createZoneSocket(zopts);
432 434 });
433 435 });
434 436
435 437 self.zoneConnections[zopts.zone] = {
436 438 conn: server,
437 439 done: false,
438 440 end: function () {
439 441 if (this.done) {
440 442 return;
441 443 }
442 444 this.done = true;
443 445 zlog.info('Closing server');
444 446 server.close();
445 447 }
446 448 };
447 449
448 450 server.on('error', function (e) {
449 451 zlog.error({err: e}, 'Zone socket error: ' + e.message);
450 452 if (e.code !== 'EINTR') {
451 453 throw e;
452 454 }
453 455 });
454 456 var Pipe = process.binding('pipe_wrap').Pipe;
455 457 var p = new Pipe(true);
456 458 p.open(fd);
457 459 p.readable = p.writable = true;
458 460 server._handle = p;
459 461
460 462 server.listen();
461 463 });
462 464
463 465 if (callback) {
464 466 callback();
465 467 }
466 468 };
467 469
468 470 MetadataAgent.prototype.makeMetadataHandler = function (zone, socket) {
469 471 var self = this;
470 472 var zlog = self.zlog[zone];
471 473 var write = function (str) {
472 474 if (socket.writable) {
473 475 socket.write(str);
474 476 } else {
475 477 zlog.error('Socket for ' + zone + ' closed before we could write '
↓ open down ↓ |
462 lines elided |
↑ open up ↑ |
476 478 + 'anything.');
477 479 }
478 480 };
479 481
480 482 return function (data) {
481 483 var cmd;
482 484 var parts;
483 485 var val;
484 486 var vmobj;
485 487 var want;
488 + var reqid;
489 + var req_is_v2 = false;
486 490
487 491 parts = rtrim(data.toString()).replace(/\n$/, '')
488 492 .match(/^([^\s]+)\s?(.*)/);
489 493
490 494 if (!parts) {
491 495 write('invalid command\n');
492 496 return;
493 497 }
494 498
495 499 cmd = parts[1];
496 500 want = parts[2];
497 501
498 - if (cmd === 'GET' && !want) {
502 + if ((cmd === 'NEGOTIATE' || cmd === 'GET') && !want) {
499 503 write('invalid command\n');
500 504 return;
501 505 }
502 506
503 507 vmobj = self.zones[zone];
504 508
509 + // Unbox V2 protocol frames:
510 + if (cmd === 'V2') {
511 + if (!parse_v2_request(want))
512 + return;
513 + }
514 +
505 515 if (cmd === 'GET') {
506 516 zlog.info('Serving ' + want);
507 517 if (want.slice(0, 4) === 'sdc:') {
508 518 want = want.slice(4);
509 519
510 520 // NOTE: sdc:nics, sdc:resolvers and sdc:routes are not a
511 521 // committed interface, do not rely on it. At this point it
512 522 // should only be used by mdata-fetch, if you add a consumer
513 523 // that depends on it, please add a note about that here
514 524 // otherwise expect it will be removed on you sometime.
515 525 if (want === 'nics' && vmobj.hasOwnProperty('nics')) {
516 526 val = JSON.stringify(vmobj.nics);
517 527 returnit(null, val);
518 528 return;
519 529 } else if (want === 'resolvers'
520 530 && vmobj.hasOwnProperty('resolvers')) {
521 531
522 532 // resolvers and routes are special because we might reload
523 533 // metadata trying to get the new ones w/o zone reboot. To
524 534 // ensure these are fresh we always run updateZone which
525 535 // reloads the data if stale.
526 536 self.updateZone(zone, function () {
527 537 // See NOTE above about nics, same applies to resolvers.
528 538 // It's here solely for the use of mdata-fetch.
529 539 val = JSON.stringify(self.zones[zone].resolvers);
530 540 returnit(null, val);
531 541 return;
532 542 });
533 543 } else if (want === 'routes'
534 544 && vmobj.hasOwnProperty('routes')) {
535 545
536 546 var vmRoutes = [];
537 547
538 548 self.updateZone(zone, function () {
539 549
540 550 vmobj = self.zones[zone];
541 551
542 552 // The notes above about resolvers also to routes. It's
543 553 // here solely for the use of mdata-fetch, and we need
544 554 // to do the updateZone here so that we have latest
545 555 // data.
546 556 for (var r in vmobj.routes) {
547 557 var route = { linklocal: false, dst: r };
548 558 var nicIdx = vmobj.routes[r].match(/nics\[(\d+)\]/);
549 559 if (!nicIdx) {
550 560 // Non link-local route: we have all the
551 561 // information we need already
552 562 route.gateway = vmobj.routes[r];
553 563 vmRoutes.push(route);
554 564 continue;
555 565 }
556 566 nicIdx = Number(nicIdx[1]);
557 567
558 568 // Link-local route: we need the IP of the local nic
559 569 if (!vmobj.hasOwnProperty('nics')
560 570 || !vmobj.nics[nicIdx]
561 571 || !vmobj.nics[nicIdx].hasOwnProperty('ip')
562 572 || vmobj.nics[nicIdx].ip === 'dhcp') {
563 573
564 574 continue;
565 575 }
566 576
567 577 route.gateway = vmobj.nics[nicIdx].ip;
568 578 route.linklocal = true;
569 579 vmRoutes.push(route);
570 580 }
571 581
572 582 returnit(null, JSON.stringify(vmRoutes));
573 583 return;
574 584 });
575 585 } else if (want === 'operator-script') {
576 586 addMetadata(function (err) {
577 587 if (err) {
578 588 returnit(new Error('Unable to load metadata: '
579 589 + err.message));
580 590 return;
581 591 }
582 592
583 593 returnit(null,
584 594 vmobj.internal_metadata['operator-script']);
585 595 return;
586 596 });
587 597 } else {
588 598 addTags(function (err) {
589 599 if (!err) {
590 600 val = VM.flatten(vmobj, want);
591 601 }
592 602 returnit(err, val);
593 603 return;
594 604 });
595 605 }
596 606 } else {
597 607 // not sdc:, so key will come from *_mdata
598 608 addMetadata(function (err) {
599 609 var which_mdata = 'customer_metadata';
600 610
601 611 if (err) {
602 612 returnit(new Error('Unable to load metadata: '
603 613 + err.message));
604 614 return;
605 615 }
606 616
607 617 if (want.match(/_pw$/)) {
608 618 which_mdata = 'internal_metadata';
609 619 }
610 620
↓ open down ↓ |
96 lines elided |
↑ open up ↑ |
611 621 if (vmobj.hasOwnProperty(which_mdata)) {
612 622 returnit(null, vmobj[which_mdata][want]);
613 623 return;
614 624 } else {
615 625 returnit(new Error('Zone did not contain '
616 626 + which_mdata));
617 627 return;
618 628 }
619 629 });
620 630 }
631 + } else if (cmd === 'NEGOTIATE') {
632 + if (want === 'V2') {
633 + write('V2_OK\n');
634 + } else {
635 + write('FAILURE\n');
636 + }
637 + return;
638 + } else if (req_is_v2 && cmd === 'DELETE') {
639 + want = want.trim();
640 +
641 + if (!want) {
642 + returnit(new Error('Invalid DELETE Request'));
643 + return;
644 + }
645 +
646 + setMetadata(want, null, function (err) {
647 + if (err) {
648 + returnit(err);
649 + } else {
650 + returnit(null, 'OK');
651 + }
652 + });
653 + } else if (req_is_v2 && cmd === 'PUT') {
654 + var key;
655 + var value;
656 + var terms;
657 +
658 + terms = want.trim().split(' ');
659 +
660 + if (terms.length !== 2) {
661 + returnit(new Error('Invalid PUT Request'));
662 + return;
663 + }
664 +
665 + // PUT requests have two space-separated BASE64-encoded
666 + // arguments: the Key and then the Value.
667 + key = (new Buffer(terms[0], 'base64')).toString().trim();
668 + value = (new Buffer(terms[1], 'base64')).toString();
669 +
670 + if (!key) {
671 + returnit(new Error('Invalid PUT Request'));
672 + return;
673 + }
674 +
675 + if (key.slice(0, 4) === 'sdc:') {
676 + returnit(new Error('Cannot update the "sdc" Namespace.'));
677 + return;
678 + }
679 +
680 + zlog.info('PUT of key "' + key + '"');
681 + setMetadata(key, value, function (err) {
682 + if (err) {
683 + zlog.error(err, 'could not set metadata (key "' + key
684 + + '")');
685 + returnit(err);
686 + } else {
687 + returnit(null, 'OK');
688 + }
689 + });
690 +
691 + return;
621 692 } else if (cmd === 'KEYS') {
622 693 addMetadata(function (err) {
623 694 var ckeys = [];
624 695 var ikeys = [];
625 696
626 697 if (err) {
627 698 returnit(new Error('Unable to load metadata: '
628 699 + err.message));
629 700 return;
630 701 }
631 702
632 703 // *_pw$ keys come from internal_metadata, everything else comes
633 704 // from customer_metadata
634 705 ckeys = Object.keys(vmobj.customer_metadata)
635 706 .filter(function (k) {
636 707
637 708 return (!k.match(/_pw$/));
638 709 });
639 710 ikeys = Object.keys(vmobj.internal_metadata)
640 711 .filter(function (k) {
641 712
642 713 return (k.match(/_pw$/));
643 714 });
644 715
645 716 returnit(null, ckeys.concat(ikeys).join('\n'));
646 717 return;
647 718 });
648 719 } else {
649 720 zlog.error('Unknown command ' + cmd);
650 721 returnit(new Error('Unknown command ' + cmd));
651 722 return;
652 723 }
653 724
654 725 function addTags(cb) {
655 726 var filename;
656 727
657 728 filename = vmobj.zonepath + '/config/tags.json';
658 729 fs.readFile(filename, function (err, file_data) {
659 730
660 731 if (err && err.code === 'ENOENT') {
661 732 vmobj.tags = {};
662 733 cb();
663 734 return;
664 735 }
665 736
666 737 if (err) {
667 738 zlog.error({err: err}, 'failed to load tags.json: '
668 739 + err.message);
669 740 cb(err);
670 741 return;
671 742 }
672 743
673 744 try {
674 745 vmobj.tags = JSON.parse(file_data.toString());
675 746 cb();
676 747 } catch (e) {
677 748 zlog.error({err: e}, 'unable to tags.json for ' + zone
678 749 + ': ' + e.message);
679 750 cb(e);
680 751 }
681 752
682 753 return;
683 754 });
684 755 }
685 756
686 757 function addMetadata(cb) {
687 758 var filename;
688 759
689 760 // If we got here, our answer comes from metadata.
690 761 // XXX In the future, if the require overhead here ends up being
691 762 // larger than a stat would be, we might want to cache these and
692 763 // reload when mtime changes.
693 764
694 765 filename = vmobj.zonepath + '/config/metadata.json';
695 766
696 767 fs.readFile(filename, function (err, file_data) {
697 768 var json = {};
698 769 var mdata_types = [ 'customer_metadata', 'internal_metadata' ];
699 770
700 771 // start w/ both empty, if we fail partway through there will
701 772 // just be no metadata instead of wrong metadata.
702 773 vmobj.customer_metadata = {};
703 774 vmobj.internal_metadata = {};
704 775
705 776 if (err && err.code === 'ENOENT') {
706 777 cb();
707 778 return;
708 779 }
709 780
710 781 if (err) {
711 782 zlog.error({err: err}, 'failed to load mdata.json: '
712 783 + err.message);
713 784 cb(err);
714 785 return;
715 786 }
716 787
717 788 try {
718 789 json = JSON.parse(file_data.toString());
719 790 mdata_types.forEach(function (mdata) {
720 791 if (json.hasOwnProperty(mdata)) {
721 792 vmobj[mdata] = json[mdata];
722 793 }
723 794 });
724 795 cb();
↓ open down ↓ |
94 lines elided |
↑ open up ↑ |
725 796 } catch (e) {
726 797 zlog.error({err: e}, 'unable to load metadata.json for '
727 798 + zone + ': ' + e.message);
728 799 cb(e);
729 800 }
730 801
731 802 return;
732 803 });
733 804 }
734 805
806 + function setMetadata(_key, _value, cb) {
807 + var payload = {};
808 + var which = 'customer_metadata';
809 +
810 + // Some keys come from "internal_metadata":
811 + if (_key.match(/_pw$/) || _key === 'operator-script') {
812 + which = 'internal_metadata';
813 + }
814 +
815 + // Construct payload for VM.update()
816 + if (_value) {
817 + payload['set_' + which] = {};
818 + payload['set_' + which][_key] = _value;
819 + } else {
820 + payload['remove_' + which] = [ _key ];
821 + }
822 +
823 + zlog.trace({ payload: payload }, 'calling VM.update()');
824 + VM.update(vmobj.uuid, payload, zlog, cb);
825 + }
826 +
827 + function parse_v2_request(inframe) {
828 + var m;
829 + var m2;
830 + var newcrc32;
831 + var framecrc32;
832 + var clen;
833 +
834 + m = inframe.match(
835 + /\s*([0-9]+)\s+([0-9a-f]+)\s+([0-9a-f]+)\s+(.*)/);
836 + if (!m) {
837 + zlog.error('V2 frame did not parse: ', inframe);
838 + return (false);
839 + }
840 +
841 + clen = Number(m[1]);
842 + if (!(clen > 0) || clen !== (m[3] + ' ' + m[4]).length) {
843 + zlog.error('V2 invalid clen: ' + m[1]);
844 + return (false);
845 + }
846 +
847 + newcrc32 = crc32.crc32_calc(m[3] + ' ' + m[4]);
848 + framecrc32 = m[2];
849 + if (framecrc32 !== newcrc32) {
850 + zlog.error('V2 crc mismatch (ours ' + newcrc32
851 + + ' theirs ' + framecrc32 + '): ' + want);
852 + return (false);
853 + }
854 +
855 + reqid = m[3];
856 +
857 + m2 = m[4].match(/\s*(\S+)\s*(.*)/);
858 + if (!m2) {
859 + zlog.error('V2 invalid body: ' + m[4]);
860 + return (false);
861 + }
862 +
863 + cmd = m2[1];
864 + want = (new Buffer(m2[2], 'base64')).toString('utf8');
865 +
866 + req_is_v2 = true;
867 +
868 + return (true);
869 + }
870 +
871 +
872 + function format_v2_response(code, body) {
873 + var resp;
874 + var fullresp;
875 +
876 + resp = reqid + ' ' + code;
877 + if (body)
878 + resp += ' ' + (new Buffer(body).toString('base64'));
879 +
880 + fullresp = 'V2 ' + resp.length + ' ' + crc32.crc32_calc(
881 + resp) + ' ' + resp + '\n';
882 +
883 + zlog.trace({ response: fullresp }, 'formatted V2 response');
884 +
885 + return (fullresp);
886 + }
887 +
735 888 function returnit(error, retval) {
736 889 var towrite;
737 890
738 891 if (error) {
739 892 zlog.error(error.message);
740 - write('FAILURE\n');
893 + if (req_is_v2)
894 + write(format_v2_response('FAILURE', error.message));
895 + else
896 + write('FAILURE\n');
741 897 return;
742 898 }
743 899
744 900 // String value
745 901 if (common.isString(retval)) {
746 - towrite = retval.replace(/^\./mg, '..');
747 - write('SUCCESS\n' + towrite + '\n.\n');
902 + if (req_is_v2) {
903 + write(format_v2_response('SUCCESS', retval));
904 + } else {
905 + towrite = retval.replace(/^\./mg, '..');
906 + write('SUCCESS\n' + towrite + '\n.\n');
907 + }
748 908 return;
749 909 } else if (!isNaN(retval)) {
750 - towrite = retval.toString().replace(/^\./mg, '..');
751 - write('SUCCESS\n' + towrite + '\n.\n');
910 + if (req_is_v2) {
911 + write(format_v2_response('SUCCESS', retval.toString()));
912 + } else {
913 + towrite = retval.toString().replace(/^\./mg, '..');
914 + write('SUCCESS\n' + towrite + '\n.\n');
915 + }
752 916 return;
753 917 } else if (retval) {
754 918 // Non-string value
755 - write('FAILURE\n');
919 + if (req_is_v2)
920 + write(format_v2_response('FAILURE'));
921 + else
922 + write('FAILURE\n');
756 923 return;
757 924 } else {
758 925 // Nothing to return
759 - write('NOTFOUND\n');
926 + if (req_is_v2)
927 + write(format_v2_response('NOTFOUND'));
928 + else
929 + write('NOTFOUND\n');
760 930 return;
761 931 }
762 932 }
763 933 };
764 934 };
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX