1 // This file is part of OpenTSDB.
2 // Copyright (C) 2013  The OpenTSDB Authors.
3 //
4 // This program is free software: you can redistribute it and/or modify it
5 // under the terms of the GNU Lesser General Public License as published by
6 // the Free Software Foundation, either version 2.1 of the License, or (at your
7 // option) any later version.  This program is distributed in the hope that it
8 // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9 // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
10 // General Public License for more details.  You should have received a copy
11 // of the GNU Lesser General Public License along with this program.  If not,
12 // see <http://www.gnu.org/licenses/>.
13 package net.opentsdb.tsd;
14 
15 import java.io.ByteArrayOutputStream;
16 import java.io.IOException;
17 import java.util.Arrays;
18 import java.util.HashMap;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Map.Entry;
22 import java.util.TreeMap;
23 
24 import org.hbase.async.Bytes;
25 import org.hbase.async.Bytes.ByteMap;
26 import org.hbase.async.PutRequest;
27 import org.jboss.netty.handler.codec.http.HttpMethod;
28 import org.jboss.netty.handler.codec.http.HttpResponseStatus;
29 
30 import com.stumbleupon.async.Callback;
31 import com.stumbleupon.async.Deferred;
32 
33 import net.opentsdb.core.TSDB;
34 import net.opentsdb.core.Tags;
35 import net.opentsdb.meta.TSMeta;
36 import net.opentsdb.meta.TSUIDQuery;
37 import net.opentsdb.meta.UIDMeta;
38 import net.opentsdb.uid.NoSuchUniqueId;
39 import net.opentsdb.uid.NoSuchUniqueName;
40 import net.opentsdb.uid.UniqueId;
41 import net.opentsdb.uid.UniqueId.UniqueIdType;
42 
43 /**
44  * Handles calls for UID processing including getting UID status, assigning UIDs
45  * and other functions.
46  * @since 2.0
47  */
48 final class UniqueIdRpc implements HttpRpc {
49 
50   @Override
execute(TSDB tsdb, HttpQuery query)51   public void execute(TSDB tsdb, HttpQuery query) throws IOException {
52 
53     // the uri will be /api/vX/uid/? or /api/uid/?
54     final String[] uri = query.explodeAPIPath();
55     final String endpoint = uri.length > 1 ? uri[1] : "";
56 
57     if (endpoint.toLowerCase().equals("assign")) {
58       this.handleAssign(tsdb, query);
59       return;
60     } else if (endpoint.toLowerCase().equals("uidmeta")) {
61       this.handleUIDMeta(tsdb, query);
62       return;
63     } else if (endpoint.toLowerCase().equals("tsmeta")) {
64       this.handleTSMeta(tsdb, query);
65       return;
66     } else if (endpoint.toLowerCase().equals("rename")) {
67       this.handleRename(tsdb, query);
68       return;
69     } else {
70       throw new BadRequestException(HttpResponseStatus.NOT_IMPLEMENTED,
71           "Other UID endpoints have not been implemented yet");
72     }
73   }
74 
75   /**
76    * Assigns UIDs to the given metric, tagk or tagv names if applicable
77    * <p>
78    * This handler supports GET and POST whereby the GET command can
79    * parse query strings with the {@code type} as their parameter and a comma
80    * separated list of values to assign UIDs to.
81    * <p>
82    * Multiple types and names can be provided in one call. Each name will be
83    * processed independently and if there's an error (such as an invalid name or
84    * it is already assigned) the error will be stored in a separate error map
85    * and other UIDs will be processed.
86    * @param tsdb The TSDB from the RPC router
87    * @param query The query for this request
88    */
handleAssign(final TSDB tsdb, final HttpQuery query)89   private void handleAssign(final TSDB tsdb, final HttpQuery query) {
90     // only accept GET And POST
91     if (query.method() != HttpMethod.GET && query.method() != HttpMethod.POST) {
92       throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED,
93           "Method not allowed", "The HTTP method [" + query.method().getName() +
94           "] is not permitted for this endpoint");
95     }
96 
97     final HashMap<String, List<String>> source;
98     if (query.method() == HttpMethod.POST) {
99       source = query.serializer().parseUidAssignV1();
100     } else {
101       source = new HashMap<String, List<String>>(3);
102       // cut down on some repetitive code, split the query string values by
103       // comma and add them to the source hash
104       String[] types = {"metric", "tagk", "tagv"};
105       for (int i = 0; i < types.length; i++) {
106         final String values = query.getQueryStringParam(types[i]);
107         if (values != null && !values.isEmpty()) {
108           final String[] metrics = values.split(",");
109           if (metrics != null && metrics.length > 0) {
110             source.put(types[i], Arrays.asList(metrics));
111           }
112         }
113       }
114     }
115 
116     if (source.size() < 1) {
117       throw new BadRequestException("Missing values to assign UIDs");
118     }
119 
120     final Map<String, TreeMap<String, String>> response =
121       new HashMap<String, TreeMap<String, String>>();
122 
123     int error_count = 0;
124     for (Map.Entry<String, List<String>> entry : source.entrySet()) {
125       final TreeMap<String, String> results =
126         new TreeMap<String, String>();
127       final TreeMap<String, String> errors =
128         new TreeMap<String, String>();
129 
130       for (String name : entry.getValue()) {
131         try {
132           final byte[] uid = tsdb.assignUid(entry.getKey(), name);
133           results.put(name,
134               UniqueId.uidToString(uid));
135         } catch (IllegalArgumentException e) {
136           errors.put(name, e.getMessage());
137           error_count++;
138         }
139       }
140 
141       response.put(entry.getKey(), results);
142       if (errors.size() > 0) {
143         response.put(entry.getKey() + "_errors", errors);
144       }
145     }
146 
147     if (error_count < 1) {
148       query.sendReply(query.serializer().formatUidAssignV1(response));
149     } else {
150       query.sendReply(HttpResponseStatus.BAD_REQUEST,
151           query.serializer().formatUidAssignV1(response));
152     }
153   }
154 
155   /**
156    * Handles CRUD calls to individual UIDMeta data entries
157    * @param tsdb The TSDB from the RPC router
158    * @param query The query for this request
159    */
handleUIDMeta(final TSDB tsdb, final HttpQuery query)160   private void handleUIDMeta(final TSDB tsdb, final HttpQuery query) {
161 
162     final HttpMethod method = query.getAPIMethod();
163     // GET
164     if (method == HttpMethod.GET) {
165 
166       final String uid = query.getRequiredQueryStringParam("uid");
167       final UniqueIdType type = UniqueId.stringToUniqueIdType(
168           query.getRequiredQueryStringParam("type"));
169       try {
170         final UIDMeta meta = UIDMeta.getUIDMeta(tsdb, type, uid)
171         .joinUninterruptibly();
172         query.sendReply(query.serializer().formatUidMetaV1(meta));
173       } catch (NoSuchUniqueId e) {
174         throw new BadRequestException(HttpResponseStatus.NOT_FOUND,
175             "Could not find the requested UID", e);
176       } catch (Exception e) {
177         throw new RuntimeException(e);
178       }
179     // POST
180     } else if (method == HttpMethod.POST || method == HttpMethod.PUT) {
181 
182       final UIDMeta meta;
183       if (query.hasContent()) {
184         meta = query.serializer().parseUidMetaV1();
185       } else {
186         meta = this.parseUIDMetaQS(query);
187       }
188 
189       /**
190        * Storage callback used to determine if the storage call was successful
191        * or not. Also returns the updated object from storage.
192        */
193       class SyncCB implements Callback<Deferred<UIDMeta>, Boolean> {
194 
195         @Override
196         public Deferred<UIDMeta> call(Boolean success) throws Exception {
197           if (!success) {
198             throw new BadRequestException(
199                 HttpResponseStatus.INTERNAL_SERVER_ERROR,
200                 "Failed to save the UIDMeta to storage",
201                 "This may be caused by another process modifying storage data");
202           }
203 
204           return UIDMeta.getUIDMeta(tsdb, meta.getType(), meta.getUID());
205         }
206 
207       }
208 
209       try {
210         final Deferred<UIDMeta> process_meta = meta.syncToStorage(tsdb,
211             method == HttpMethod.PUT).addCallbackDeferring(new SyncCB());
212         final UIDMeta updated_meta = process_meta.joinUninterruptibly();
213         tsdb.indexUIDMeta(updated_meta);
214         query.sendReply(query.serializer().formatUidMetaV1(updated_meta));
215       } catch (IllegalStateException e) {
216         query.sendStatusOnly(HttpResponseStatus.NOT_MODIFIED);
217       } catch (IllegalArgumentException e) {
218         throw new BadRequestException(e);
219       } catch (NoSuchUniqueId e) {
220         throw new BadRequestException(HttpResponseStatus.NOT_FOUND,
221             "Could not find the requested UID", e);
222       } catch (Exception e) {
223         throw new RuntimeException(e);
224       }
225     // DELETE
226     } else if (method == HttpMethod.DELETE) {
227 
228       final UIDMeta meta;
229       if (query.hasContent()) {
230         meta = query.serializer().parseUidMetaV1();
231       } else {
232         meta = this.parseUIDMetaQS(query);
233       }
234       try {
235         meta.delete(tsdb).joinUninterruptibly();
236         tsdb.deleteUIDMeta(meta);
237       } catch (IllegalArgumentException e) {
238         throw new BadRequestException("Unable to delete UIDMeta information", e);
239       } catch (NoSuchUniqueId e) {
240         throw new BadRequestException(HttpResponseStatus.NOT_FOUND,
241             "Could not find the requested UID", e);
242       } catch (Exception e) {
243         throw new RuntimeException(e);
244       }
245       query.sendStatusOnly(HttpResponseStatus.NO_CONTENT);
246 
247     } else {
248       throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED,
249           "Method not allowed", "The HTTP method [" + method.getName() +
250           "] is not permitted for this endpoint");
251     }
252   }
253 
254   /**
255    * Handles CRUD calls to individual TSMeta data entries
256    * @param tsdb The TSDB from the RPC router
257    * @param query The query for this request
258    */
handleTSMeta(final TSDB tsdb, final HttpQuery query)259   private void handleTSMeta(final TSDB tsdb, final HttpQuery query) {
260 
261     final HttpMethod method = query.getAPIMethod();
262     // GET
263     if (method == HttpMethod.GET) {
264 
265       String tsuid = null;
266       if (query.hasQueryStringParam("tsuid")) {
267         tsuid = query.getQueryStringParam("tsuid");
268         try {
269           final TSMeta meta = TSMeta.getTSMeta(tsdb, tsuid).joinUninterruptibly();
270           if (meta != null) {
271             query.sendReply(query.serializer().formatTSMetaV1(meta));
272           } else {
273             throw new BadRequestException(HttpResponseStatus.NOT_FOUND,
274                 "Could not find Timeseries meta data");
275           }
276         } catch (NoSuchUniqueName e) {
277           // this would only happen if someone deleted a UID but left the
278           // the timeseries meta data
279           throw new BadRequestException(HttpResponseStatus.NOT_FOUND,
280               "Unable to find one of the UIDs", e);
281         } catch (BadRequestException e) {
282           throw e;
283         } catch (Exception e) {
284           throw new RuntimeException(e);
285         }
286       } else {
287         String mquery = query.getRequiredQueryStringParam("m");
288         // m is of the following forms:
289         // metric[{tag=value,...}]
290         // where the parts in square brackets `[' .. `]' are optional.
291         final HashMap<String, String> tags = new HashMap<String, String>();
292         String metric = null;
293         try {
294           metric = Tags.parseWithMetric(mquery, tags);
295         } catch (IllegalArgumentException e) {
296           throw new BadRequestException(e);
297         }
298         final TSUIDQuery tsuid_query = new TSUIDQuery(tsdb, metric, tags);
299         try {
300           final List<TSMeta> tsmetas = tsuid_query.getTSMetas()
301               .joinUninterruptibly();
302           query.sendReply(query.serializer().formatTSMetaListV1(tsmetas));
303         } catch (NoSuchUniqueName e) {
304           throw new BadRequestException(HttpResponseStatus.NOT_FOUND,
305               "Unable to find one of the UIDs", e);
306         } catch (BadRequestException e) {
307           throw e;
308         } catch (RuntimeException e) {
309           throw new BadRequestException(e);
310         } catch (Exception e) {
311           throw new RuntimeException(e);
312         }
313       }
314     // POST / PUT
315     } else if (method == HttpMethod.POST || method == HttpMethod.PUT) {
316 
317       final TSMeta meta;
318       if (query.hasContent()) {
319         meta = query.serializer().parseTSMetaV1();
320       } else {
321         meta = this.parseTSMetaQS(query);
322       }
323 
324       /**
325        * Storage callback used to determine if the storage call was successful
326        * or not. Also returns the updated object from storage.
327        */
328       class SyncCB implements Callback<Deferred<TSMeta>, Boolean> {
329 
330         @Override
331         public Deferred<TSMeta> call(Boolean success) throws Exception {
332           if (!success) {
333             throw new BadRequestException(
334                 HttpResponseStatus.INTERNAL_SERVER_ERROR,
335                 "Failed to save the TSMeta to storage",
336                 "This may be caused by another process modifying storage data");
337           }
338 
339           return TSMeta.getTSMeta(tsdb, meta.getTSUID());
340         }
341 
342       }
343 
344       if (meta.getTSUID() == null || meta.getTSUID().isEmpty()) {
345         // we got a JSON object without TSUID. Try to find a timeseries spec of
346         // the form "m": "metric{tagk=tagv,...}"
347         final String metric = query.getRequiredQueryStringParam("m");
348         final boolean create = query.getQueryStringParam("create") != null
349             && query.getQueryStringParam("create").equals("true");
350         final String tsuid = getTSUIDForMetric(metric, tsdb);
351 
352         class WriteCounterIfNotPresentCB implements Callback<Boolean, Boolean> {
353 
354           @Override
355           public Boolean call(Boolean exists) throws Exception {
356             if (!exists && create) {
357               final PutRequest put = new PutRequest(tsdb.metaTable(),
358                   UniqueId.stringToUid(tsuid), TSMeta.FAMILY(),
359                   TSMeta.COUNTER_QUALIFIER(), Bytes.fromLong(0));
360               tsdb.getClient().put(put);
361             }
362 
363             return exists;
364           }
365 
366         }
367 
368         try {
369           // Check whether we have a TSMeta stored already
370           final boolean exists = TSMeta
371               .metaExistsInStorage(tsdb, tsuid)
372               .joinUninterruptibly();
373           // set TSUID
374           meta.setTSUID(tsuid);
375 
376           if (!exists && create) {
377             // Write 0 to counter column if not present
378             TSMeta.counterExistsInStorage(tsdb, UniqueId.stringToUid(tsuid))
379                 .addCallback(new WriteCounterIfNotPresentCB())
380                 .joinUninterruptibly();
381             // set TSUID
382             final Deferred<TSMeta> process_meta = meta.storeNew(tsdb)
383                 .addCallbackDeferring(new SyncCB());
384             final TSMeta updated_meta = process_meta.joinUninterruptibly();
385             tsdb.indexTSMeta(updated_meta);
386             tsdb.processTSMetaThroughTrees(updated_meta);
387             query.sendReply(query.serializer().formatTSMetaV1(updated_meta));
388           } else if (exists) {
389             final Deferred<TSMeta> process_meta = meta.syncToStorage(tsdb,
390                 method == HttpMethod.PUT).addCallbackDeferring(new SyncCB());
391             final TSMeta updated_meta = process_meta.joinUninterruptibly();
392             tsdb.indexTSMeta(updated_meta);
393             query.sendReply(query.serializer().formatTSMetaV1(updated_meta));
394           } else {
395             throw new BadRequestException(
396                 "Could not find TSMeta, specify \"create=true\" to create a new one.");
397           }
398         } catch (IllegalStateException e) {
399           query.sendStatusOnly(HttpResponseStatus.NOT_MODIFIED);
400         } catch (IllegalArgumentException e) {
401           throw new BadRequestException(e);
402         } catch (BadRequestException e) {
403           throw e;
404         } catch (NoSuchUniqueName e) {
405           // this would only happen if someone deleted a UID but left the
406           // the timeseries meta data
407           throw new BadRequestException(HttpResponseStatus.NOT_FOUND,
408               "Unable to find one or more UIDs", e);
409         } catch (Exception e) {
410           throw new RuntimeException(e);
411         }
412       } else {
413         try {
414           final Deferred<TSMeta> process_meta = meta.syncToStorage(tsdb,
415               method == HttpMethod.PUT).addCallbackDeferring(new SyncCB());
416           final TSMeta updated_meta = process_meta.joinUninterruptibly();
417           tsdb.indexTSMeta(updated_meta);
418           query.sendReply(query.serializer().formatTSMetaV1(updated_meta));
419         } catch (IllegalStateException e) {
420           query.sendStatusOnly(HttpResponseStatus.NOT_MODIFIED);
421         } catch (IllegalArgumentException e) {
422           throw new BadRequestException(e);
423         } catch (NoSuchUniqueName e) {
424           // this would only happen if someone deleted a UID but left the
425           // the timeseries meta data
426           throw new BadRequestException(HttpResponseStatus.NOT_FOUND,
427               "Unable to find one or more UIDs", e);
428         } catch (Exception e) {
429           throw new RuntimeException(e);
430         }
431       }
432     // DELETE
433     } else if (method == HttpMethod.DELETE) {
434 
435       final TSMeta meta;
436       if (query.hasContent()) {
437         meta = query.serializer().parseTSMetaV1();
438       } else {
439         meta = this.parseTSMetaQS(query);
440       }
441       try{
442         meta.delete(tsdb);
443         tsdb.deleteTSMeta(meta.getTSUID());
444       } catch (IllegalArgumentException e) {
445         throw new BadRequestException("Unable to delete TSMeta information", e);
446       }
447       query.sendStatusOnly(HttpResponseStatus.NO_CONTENT);
448     } else {
449       throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED,
450           "Method not allowed", "The HTTP method [" + method.getName() +
451           "] is not permitted for this endpoint");
452     }
453   }
454 
455   /**
456    * Used with verb overrides to parse out values from a query string
457    * @param query The query to parse
458    * @return An UIDMeta object with configured values
459    * @throws BadRequestException if a required value was missing or could not
460    * be parsed
461    */
parseUIDMetaQS(final HttpQuery query)462   private UIDMeta parseUIDMetaQS(final HttpQuery query) {
463     final String uid = query.getRequiredQueryStringParam("uid");
464     final String type = query.getRequiredQueryStringParam("type");
465     final UIDMeta meta = new UIDMeta(UniqueId.stringToUniqueIdType(type), uid);
466     final String display_name = query.getQueryStringParam("display_name");
467     if (display_name != null) {
468       meta.setDisplayName(display_name);
469     }
470 
471     final String description = query.getQueryStringParam("description");
472     if (description != null) {
473       meta.setDescription(description);
474     }
475 
476     final String notes = query.getQueryStringParam("notes");
477     if (notes != null) {
478       meta.setNotes(notes);
479     }
480 
481     return meta;
482   }
483 
484   /**
485    * Rename UID to a new name of the given metric, tagk or tagv names
486    * <p>
487    * This handler supports GET and POST whereby the GET command can parse query
488    * strings with the {@code type} and {@code name} as their parameters.
489    * <p>
490    * @param tsdb The TSDB from the RPC router
491    * @param query The query for this request
492    */
handleRename(final TSDB tsdb, final HttpQuery query)493   private void handleRename(final TSDB tsdb, final HttpQuery query) {
494     // only accept GET and POST
495     if (query.method() != HttpMethod.GET && query.method() != HttpMethod.POST) {
496       throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED,
497           "Method not allowed", "The HTTP method[" + query.method().getName() +
498           "] is not permitted for this endpoint");
499     }
500 
501     final HashMap<String, String> source;
502     if (query.method() == HttpMethod.POST) {
503       source = query.serializer().parseUidRenameV1();
504     } else {
505       source = new HashMap<String, String>(3);
506       final String[] types = {"metric", "tagk", "tagv", "name"};
507       for (int i = 0; i < types.length; i++) {
508         final String value = query.getQueryStringParam(types[i]);
509         if (value!= null && !value.isEmpty()) {
510           source.put(types[i], value);
511         }
512       }
513     }
514     String type = null;
515     String oldname = null;
516     String newname = null;
517     for (Map.Entry<String, String> entry : source.entrySet()) {
518       if (entry.getKey().equals("name")) {
519         newname = entry.getValue();
520       } else {
521         type = entry.getKey();
522         oldname = entry.getValue();
523       }
524     }
525 
526     // we need a type/value and new name
527     if (type == null || oldname == null || newname == null) {
528       throw new BadRequestException("Missing necessary values to rename UID");
529     }
530 
531     HashMap<String, String> response = new HashMap<String, String>(2);
532     try {
533       tsdb.renameUid(type, oldname, newname);
534       response.put("result", "true");
535     } catch (IllegalArgumentException e) {
536       response.put("result", "false");
537       response.put("error", e.getMessage());
538     }
539 
540     if (!response.containsKey("error")) {
541       query.sendReply(query.serializer().formatUidRenameV1(response));
542     } else {
543       query.sendReply(HttpResponseStatus.BAD_REQUEST,
544           query.serializer().formatUidRenameV1(response));
545     }
546   }
547 
548   /**
549    * Used with verb overrides to parse out values from a query string
550    * @param query The query to parse
551    * @return An TSMeta object with configured values
552    * @throws BadRequestException if a required value was missing or could not
553    * be parsed
554    */
parseTSMetaQS(final HttpQuery query)555   private TSMeta parseTSMetaQS(final HttpQuery query) {
556     final String tsuid = query.getQueryStringParam("tsuid");
557     final TSMeta meta;
558     if (tsuid != null && !tsuid.isEmpty()) {
559       meta = new TSMeta(tsuid);
560     } else {
561       meta = new TSMeta();
562     }
563 
564     final String display_name = query.getQueryStringParam("display_name");
565     if (display_name != null) {
566       meta.setDisplayName(display_name);
567     }
568 
569     final String description = query.getQueryStringParam("description");
570     if (description != null) {
571       meta.setDescription(description);
572     }
573 
574     final String notes = query.getQueryStringParam("notes");
575     if (notes != null) {
576       meta.setNotes(notes);
577     }
578 
579     final String units = query.getQueryStringParam("units");
580     if (units != null) {
581       meta.setUnits(units);
582     }
583 
584     final String data_type = query.getQueryStringParam("data_type");
585     if (data_type != null) {
586       meta.setDataType(data_type);
587     }
588 
589     final String retention = query.getQueryStringParam("retention");
590     if (retention != null && !retention.isEmpty()) {
591       try {
592         meta.setRetention(Integer.parseInt(retention));
593       } catch (NumberFormatException nfe) {
594         throw new BadRequestException("Unable to parse 'retention' value");
595       }
596     }
597 
598     final String max = query.getQueryStringParam("max");
599     if (max != null && !max.isEmpty()) {
600       try {
601         meta.setMax(Float.parseFloat(max));
602       } catch (NumberFormatException nfe) {
603         throw new BadRequestException("Unable to parse 'max' value");
604       }
605     }
606 
607     final String min = query.getQueryStringParam("min");
608     if (min != null && !min.isEmpty()) {
609       try {
610         meta.setMin(Float.parseFloat(min));
611       } catch (NumberFormatException nfe) {
612         throw new BadRequestException("Unable to parse 'min' value");
613       }
614     }
615 
616     return meta;
617   }
618 
619   /**
620    * Parses a query string "m=metric{tagk1=tagv1,...}" type query and returns
621    * a tsuid.
622    * @param data_query The query we're building
623    * @throws BadRequestException if we are unable to parse the query or it is
624    * missing components
625    * @todo - make this asynchronous
626    */
getTSUIDForMetric(final String query_string, TSDB tsdb)627   private String getTSUIDForMetric(final String query_string, TSDB tsdb) {
628     if (query_string == null || query_string.isEmpty()) {
629       throw new BadRequestException("The query string was empty");
630     }
631 
632     // m is of the following forms:
633     // metric[{tag=value,...}]
634     // where the parts in square brackets `[' .. `]' are optional.
635     final HashMap<String, String> tags = new HashMap<String, String>();
636     String metric = null;
637     try {
638       metric = Tags.parseWithMetric(query_string, tags);
639     } catch (IllegalArgumentException e) {
640       throw new BadRequestException(e);
641     }
642 
643     // sort the UIDs on tagk values
644     final ByteMap<byte[]> tag_uids = new ByteMap<byte[]>();
645     for (final Entry<String, String> pair : tags.entrySet()) {
646       tag_uids.put(tsdb.getUID(UniqueIdType.TAGK, pair.getKey()),
647           tsdb.getUID(UniqueIdType.TAGV, pair.getValue()));
648     }
649 
650     // Byte Buffer to generate TSUID, pre allocated to the size of the TSUID
651     final ByteArrayOutputStream buf = new ByteArrayOutputStream(
652         TSDB.metrics_width() + tag_uids.size() *
653         (TSDB.tagk_width() + TSDB.tagv_width()));
654     try {
655       buf.write(tsdb.getUID(UniqueIdType.METRIC, metric));
656       for (final Entry<byte[], byte[]> uids: tag_uids.entrySet()) {
657         buf.write(uids.getKey());
658         buf.write(uids.getValue());
659       }
660     } catch (IOException e) {
661       throw new BadRequestException(e);
662     }
663     final String tsuid = UniqueId.uidToString(buf.toByteArray());
664 
665     return tsuid;
666   }
667 
668 }
669