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

@@ -1,8 +1,10 @@
+// vim: set ts=4 sts=4 sw=4 et:
 var VM  = require('/usr/vm/node_modules/VM');
 var ZWatch = require('./zwatch');
 var common = require('./common');
+var crc32 = require('./crc32');
 var async = require('/usr/node/node_modules/async');
 var execFile = require('child_process').execFile;
 var fs = require('fs');
 var net = require('net');
 var path = require('path');

@@ -481,10 +483,12 @@
         var cmd;
         var parts;
         var val;
         var vmobj;
         var want;
+        var reqid;
+        var req_is_v2 = false;
 
         parts = rtrim(data.toString()).replace(/\n$/, '')
             .match(/^([^\s]+)\s?(.*)/);
 
         if (!parts) {

@@ -493,17 +497,23 @@
         }
 
         cmd = parts[1];
         want = parts[2];
 
-        if (cmd === 'GET' && !want) {
+        if ((cmd === 'NEGOTIATE' || cmd === 'GET') && !want) {
             write('invalid command\n');
             return;
         }
 
         vmobj = self.zones[zone];
 
+        // Unbox V2 protocol frames:
+        if (cmd === 'V2') {
+            if (!parse_v2_request(want))
+                return;
+        }
+
         if (cmd === 'GET') {
             zlog.info('Serving ' + want);
             if (want.slice(0, 4) === 'sdc:') {
                 want = want.slice(4);
 

@@ -616,10 +626,71 @@
                             + which_mdata));
                         return;
                     }
                 });
             }
+        } else if (cmd === 'NEGOTIATE') {
+            if (want === 'V2') {
+                write('V2_OK\n');
+            } else {
+                write('FAILURE\n');
+            }
+            return;
+        } else if (req_is_v2 && cmd === 'DELETE') {
+            want = want.trim();
+
+            if (!want) {
+                returnit(new Error('Invalid DELETE Request'));
+                return;
+            }
+
+            setMetadata(want, null, function (err) {
+                if (err) {
+                    returnit(err);
+                } else {
+                    returnit(null, 'OK');
+                }
+            });
+        } else if (req_is_v2 && cmd === 'PUT') {
+            var key;
+            var value;
+            var terms;
+
+            terms = want.trim().split(' ');
+
+            if (terms.length !== 2) {
+                returnit(new Error('Invalid PUT Request'));
+                return;
+            }
+
+            // PUT requests have two space-separated BASE64-encoded
+            // arguments: the Key and then the Value.
+            key = (new Buffer(terms[0], 'base64')).toString().trim();
+            value = (new Buffer(terms[1], 'base64')).toString();
+
+            if (!key) {
+                returnit(new Error('Invalid PUT Request'));
+                return;
+            }
+
+            if (key.slice(0, 4) === 'sdc:') {
+                returnit(new Error('Cannot update the "sdc" Namespace.'));
+                return;
+            }
+
+            zlog.info('PUT of key "' + key + '"');
+            setMetadata(key, value, function (err) {
+                if (err) {
+                    zlog.error(err, 'could not set metadata (key "' + key
+                        + '")');
+                    returnit(err);
+                } else {
+                    returnit(null, 'OK');
+                }
+            });
+
+            return;
         } else if (cmd === 'KEYS') {
             addMetadata(function (err) {
                 var ckeys = [];
                 var ikeys = [];
 

@@ -730,34 +801,133 @@
 
                 return;
             });
         }
 
+        function setMetadata(_key, _value, cb) {
+            var payload = {};
+            var which = 'customer_metadata';
+
+            // Some keys come from "internal_metadata":
+            if (_key.match(/_pw$/) || _key === 'operator-script') {
+                which = 'internal_metadata';
+            }
+
+            // Construct payload for VM.update()
+            if (_value) {
+                payload['set_' + which] = {};
+                payload['set_' + which][_key] = _value;
+            } else {
+                payload['remove_' + which] = [ _key ];
+            }
+
+            zlog.trace({ payload: payload }, 'calling VM.update()');
+            VM.update(vmobj.uuid, payload, zlog, cb);
+        }
+
+        function parse_v2_request(inframe) {
+            var m;
+            var m2;
+            var newcrc32;
+            var framecrc32;
+            var clen;
+
+            m = inframe.match(
+                /\s*([0-9]+)\s+([0-9a-f]+)\s+([0-9a-f]+)\s+(.*)/);
+            if (!m) {
+                zlog.error('V2 frame did not parse: ', inframe);
+                return (false);
+            }
+
+            clen = Number(m[1]);
+            if (!(clen > 0) || clen !== (m[3] + ' ' + m[4]).length) {
+                zlog.error('V2 invalid clen: ' + m[1]);
+                return (false);
+            }
+
+            newcrc32 = crc32.crc32_calc(m[3] + ' ' + m[4]);
+            framecrc32 = m[2];
+            if (framecrc32 !== newcrc32) {
+                zlog.error('V2 crc mismatch (ours ' + newcrc32
+                    + ' theirs ' + framecrc32 + '): ' + want);
+                return (false);
+            }
+
+            reqid = m[3];
+
+            m2 = m[4].match(/\s*(\S+)\s*(.*)/);
+            if (!m2) {
+                zlog.error('V2 invalid body: ' + m[4]);
+                return (false);
+            }
+
+            cmd = m2[1];
+            want = (new Buffer(m2[2], 'base64')).toString('utf8');
+
+            req_is_v2 = true;
+
+            return (true);
+        }
+
+
+        function format_v2_response(code, body) {
+            var resp;
+            var fullresp;
+
+            resp = reqid + ' ' + code;
+            if (body)
+                resp += ' ' + (new Buffer(body).toString('base64'));
+
+            fullresp = 'V2 ' + resp.length + ' ' + crc32.crc32_calc(
+                resp) + ' ' + resp + '\n';
+
+            zlog.trace({ response: fullresp }, 'formatted V2 response');
+
+            return (fullresp);
+        }
+
         function returnit(error, retval) {
             var towrite;
 
             if (error) {
                 zlog.error(error.message);
+                if (req_is_v2)
+                    write(format_v2_response('FAILURE', error.message));
+                else
                 write('FAILURE\n');
                 return;
             }
 
             // String value
             if (common.isString(retval)) {
+                if (req_is_v2) {
+                    write(format_v2_response('SUCCESS', retval));
+                } else {
                 towrite = retval.replace(/^\./mg, '..');
                 write('SUCCESS\n' + towrite + '\n.\n');
+                }
                 return;
             } else if (!isNaN(retval)) {
+                if (req_is_v2) {
+                    write(format_v2_response('SUCCESS', retval.toString()));
+                } else {
                 towrite = retval.toString().replace(/^\./mg, '..');
                 write('SUCCESS\n' + towrite + '\n.\n');
+                }
                 return;
             } else if (retval) {
                 // Non-string value
+                if (req_is_v2)
+                    write(format_v2_response('FAILURE'));
+                else
                 write('FAILURE\n');
                 return;
             } else {
                 // Nothing to return
+                if (req_is_v2)
+                    write(format_v2_response('NOTFOUND'));
+                else
                 write('NOTFOUND\n');
                 return;
             }
         }
     };