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