Print this page
OS-2573 metadata agent support for PUT, DELETE and V2 protocol

   1 var VM  = require('/usr/vm/node_modules/VM');
   2 var ZWatch = require('./zwatch');
   3 var common = require('./common');

   4 var async = require('/usr/node/node_modules/async');
   5 var execFile = require('child_process').execFile;
   6 var fs = require('fs');
   7 var net = require('net');
   8 var path = require('path');
   9 var util = require('util');
  10 var zsock = require('/usr/node/node_modules/zsock');
  11 var zutil = require('/usr/node/node_modules/zutil');
  12 
  13 var sdc_fields = [
  14     'alias',
  15     'billing_id',
  16     'brand',
  17     'cpu_cap',
  18     'cpu_shares',
  19     'create_timestamp',
  20     'server_uuid',
  21     'image_uuid',
  22     'datacenter_name',
  23     'do_not_inventory',


 466 };
 467 
 468 MetadataAgent.prototype.makeMetadataHandler = function (zone, socket) {
 469     var self = this;
 470     var zlog = self.zlog[zone];
 471     var write = function (str) {
 472         if (socket.writable) {
 473             socket.write(str);
 474         } else {
 475             zlog.error('Socket for ' + zone + ' closed before we could write '
 476                 + 'anything.');
 477         }
 478     };
 479 
 480     return function (data) {
 481         var cmd;
 482         var parts;
 483         var val;
 484         var vmobj;
 485         var want;


 486 
 487         parts = rtrim(data.toString()).replace(/\n$/, '')
 488             .match(/^([^\s]+)\s?(.*)/);
 489 
 490         if (!parts) {
 491             write('invalid command\n');
 492             return;
 493         }
 494 
 495         cmd = parts[1];
 496         want = parts[2];
 497 
 498         if (cmd === 'GET' && !want) {
 499             write('invalid command\n');
 500             return;
 501         }
 502 
 503         vmobj = self.zones[zone];
 504 






 505         if (cmd === 'GET') {
 506             zlog.info('Serving ' + want);
 507             if (want.slice(0, 4) === 'sdc:') {
 508                 want = want.slice(4);
 509 
 510                 // NOTE: sdc:nics, sdc:resolvers and sdc:routes are not a
 511                 // committed interface, do not rely on it. At this point it
 512                 // should only be used by mdata-fetch, if you add a consumer
 513                 // that depends on it, please add a note about that here
 514                 // otherwise expect it will be removed on you sometime.
 515                 if (want === 'nics' && vmobj.hasOwnProperty('nics')) {
 516                     val = JSON.stringify(vmobj.nics);
 517                     returnit(null, val);
 518                     return;
 519                 } else if (want === 'resolvers'
 520                     && vmobj.hasOwnProperty('resolvers')) {
 521 
 522                     // resolvers and routes are special because we might reload
 523                     // metadata trying to get the new ones w/o zone reboot. To
 524                     // ensure these are fresh we always run updateZone which


 601                     if (err) {
 602                         returnit(new Error('Unable to load metadata: '
 603                             + err.message));
 604                         return;
 605                     }
 606 
 607                     if (want.match(/_pw$/)) {
 608                         which_mdata = 'internal_metadata';
 609                     }
 610 
 611                     if (vmobj.hasOwnProperty(which_mdata)) {
 612                         returnit(null, vmobj[which_mdata][want]);
 613                         return;
 614                     } else {
 615                         returnit(new Error('Zone did not contain '
 616                             + which_mdata));
 617                         return;
 618                     }
 619                 });
 620             }





























































 621         } else if (cmd === 'KEYS') {
 622             addMetadata(function (err) {
 623                 var ckeys = [];
 624                 var ikeys = [];
 625 
 626                 if (err) {
 627                     returnit(new Error('Unable to load metadata: '
 628                         + err.message));
 629                     return;
 630                 }
 631 
 632                 // *_pw$ keys come from internal_metadata, everything else comes
 633                 // from customer_metadata
 634                 ckeys = Object.keys(vmobj.customer_metadata)
 635                     .filter(function (k) {
 636 
 637                     return (!k.match(/_pw$/));
 638                 });
 639                 ikeys = Object.keys(vmobj.internal_metadata)
 640                     .filter(function (k) {


 715                 }
 716 
 717                 try {
 718                     json = JSON.parse(file_data.toString());
 719                     mdata_types.forEach(function (mdata) {
 720                         if (json.hasOwnProperty(mdata)) {
 721                             vmobj[mdata] = json[mdata];
 722                         }
 723                     });
 724                     cb();
 725                 } catch (e) {
 726                     zlog.error({err: e}, 'unable to load metadata.json for '
 727                         + zone + ': ' + e.message);
 728                     cb(e);
 729                 }
 730 
 731                 return;
 732             });
 733         }
 734 


















































































 735         function returnit(error, retval) {
 736             var towrite;
 737 
 738             if (error) {
 739                 zlog.error(error.message);



 740                 write('FAILURE\n');
 741                 return;
 742             }
 743 
 744             // String value
 745             if (common.isString(retval)) {



 746                 towrite = retval.replace(/^\./mg, '..');
 747                 write('SUCCESS\n' + towrite + '\n.\n');

 748                 return;
 749             } else if (!isNaN(retval)) {



 750                 towrite = retval.toString().replace(/^\./mg, '..');
 751                 write('SUCCESS\n' + towrite + '\n.\n');

 752                 return;
 753             } else if (retval) {
 754                 // Non-string value



 755                 write('FAILURE\n');
 756                 return;
 757             } else {
 758                 // Nothing to return



 759                 write('NOTFOUND\n');
 760                 return;
 761             }
 762         }
 763     };
 764 };
   1 // vim: set ts=4 sts=4 sw=4 et:
   2 var VM  = require('/usr/vm/node_modules/VM');
   3 var ZWatch = require('./zwatch');
   4 var common = require('./common');
   5 var crc32 = require('./crc32');
   6 var async = require('/usr/node/node_modules/async');
   7 var execFile = require('child_process').execFile;
   8 var fs = require('fs');
   9 var net = require('net');
  10 var path = require('path');
  11 var util = require('util');
  12 var zsock = require('/usr/node/node_modules/zsock');
  13 var zutil = require('/usr/node/node_modules/zutil');
  14 
  15 var sdc_fields = [
  16     'alias',
  17     'billing_id',
  18     'brand',
  19     'cpu_cap',
  20     'cpu_shares',
  21     'create_timestamp',
  22     'server_uuid',
  23     'image_uuid',
  24     'datacenter_name',
  25     'do_not_inventory',


 468 };
 469 
 470 MetadataAgent.prototype.makeMetadataHandler = function (zone, socket) {
 471     var self = this;
 472     var zlog = self.zlog[zone];
 473     var write = function (str) {
 474         if (socket.writable) {
 475             socket.write(str);
 476         } else {
 477             zlog.error('Socket for ' + zone + ' closed before we could write '
 478                 + 'anything.');
 479         }
 480     };
 481 
 482     return function (data) {
 483         var cmd;
 484         var parts;
 485         var val;
 486         var vmobj;
 487         var want;
 488         var reqid;
 489         var req_is_v2 = false;
 490 
 491         parts = rtrim(data.toString()).replace(/\n$/, '')
 492             .match(/^([^\s]+)\s?(.*)/);
 493 
 494         if (!parts) {
 495             write('invalid command\n');
 496             return;
 497         }
 498 
 499         cmd = parts[1];
 500         want = parts[2];
 501 
 502         if ((cmd === 'NEGOTIATE' || cmd === 'GET') && !want) {
 503             write('invalid command\n');
 504             return;
 505         }
 506 
 507         vmobj = self.zones[zone];
 508 
 509         // Unbox V2 protocol frames:
 510         if (cmd === 'V2') {
 511             if (!parse_v2_request(want))
 512                 return;
 513         }
 514 
 515         if (cmd === 'GET') {
 516             zlog.info('Serving ' + want);
 517             if (want.slice(0, 4) === 'sdc:') {
 518                 want = want.slice(4);
 519 
 520                 // NOTE: sdc:nics, sdc:resolvers and sdc:routes are not a
 521                 // committed interface, do not rely on it. At this point it
 522                 // should only be used by mdata-fetch, if you add a consumer
 523                 // that depends on it, please add a note about that here
 524                 // otherwise expect it will be removed on you sometime.
 525                 if (want === 'nics' && vmobj.hasOwnProperty('nics')) {
 526                     val = JSON.stringify(vmobj.nics);
 527                     returnit(null, val);
 528                     return;
 529                 } else if (want === 'resolvers'
 530                     && vmobj.hasOwnProperty('resolvers')) {
 531 
 532                     // resolvers and routes are special because we might reload
 533                     // metadata trying to get the new ones w/o zone reboot. To
 534                     // ensure these are fresh we always run updateZone which


 611                     if (err) {
 612                         returnit(new Error('Unable to load metadata: '
 613                             + err.message));
 614                         return;
 615                     }
 616 
 617                     if (want.match(/_pw$/)) {
 618                         which_mdata = 'internal_metadata';
 619                     }
 620 
 621                     if (vmobj.hasOwnProperty(which_mdata)) {
 622                         returnit(null, vmobj[which_mdata][want]);
 623                         return;
 624                     } else {
 625                         returnit(new Error('Zone did not contain '
 626                             + which_mdata));
 627                         return;
 628                     }
 629                 });
 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;
 692         } else if (cmd === 'KEYS') {
 693             addMetadata(function (err) {
 694                 var ckeys = [];
 695                 var ikeys = [];
 696 
 697                 if (err) {
 698                     returnit(new Error('Unable to load metadata: '
 699                         + err.message));
 700                     return;
 701                 }
 702 
 703                 // *_pw$ keys come from internal_metadata, everything else comes
 704                 // from customer_metadata
 705                 ckeys = Object.keys(vmobj.customer_metadata)
 706                     .filter(function (k) {
 707 
 708                     return (!k.match(/_pw$/));
 709                 });
 710                 ikeys = Object.keys(vmobj.internal_metadata)
 711                     .filter(function (k) {


 786                 }
 787 
 788                 try {
 789                     json = JSON.parse(file_data.toString());
 790                     mdata_types.forEach(function (mdata) {
 791                         if (json.hasOwnProperty(mdata)) {
 792                             vmobj[mdata] = json[mdata];
 793                         }
 794                     });
 795                     cb();
 796                 } catch (e) {
 797                     zlog.error({err: e}, 'unable to load metadata.json for '
 798                         + zone + ': ' + e.message);
 799                     cb(e);
 800                 }
 801 
 802                 return;
 803             });
 804         }
 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 
 888         function returnit(error, retval) {
 889             var towrite;
 890 
 891             if (error) {
 892                 zlog.error(error.message);
 893                 if (req_is_v2)
 894                     write(format_v2_response('FAILURE', error.message));
 895                 else
 896                     write('FAILURE\n');
 897                 return;
 898             }
 899 
 900             // String value
 901             if (common.isString(retval)) {
 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                 }
 908                 return;
 909             } else if (!isNaN(retval)) {
 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                 }
 916                 return;
 917             } else if (retval) {
 918                 // Non-string value
 919                 if (req_is_v2)
 920                     write(format_v2_response('FAILURE'));
 921                 else
 922                     write('FAILURE\n');
 923                 return;
 924             } else {
 925                 // Nothing to return
 926                 if (req_is_v2)
 927                     write(format_v2_response('NOTFOUND'));
 928                 else
 929                     write('NOTFOUND\n');
 930                 return;
 931             }
 932         }
 933     };
 934 };