1 /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2 /* 3 * Copyright 2011-2018 Couchbase, Inc. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 #ifndef CBC_HANDLERS_H 19 #define CBC_HANDLERS_H 20 #include "config.h" 21 #include "common/options.h" 22 #include "common/histogram.h" 23 24 namespace cbc { 25 #define HANDLER_DESCRIPTION(s) const char* description() const { return s; } 26 #define HANDLER_USAGE(s) const char* usagestr() const { return s; } 27 class Handler { 28 public: 29 Handler(const char *name); 30 virtual ~Handler(); 31 virtual const char * description() const = 0; usagestr()32 virtual const char * usagestr() const { return NULL; } 33 void execute(int argc, char **argv); 34 35 protected: 36 virtual const std::string& getLoneArg(bool required = false); getRequiredArg()37 virtual const std::string& getRequiredArg() { return getLoneArg(true); } 38 virtual void addOptions(); 39 virtual void run(); 40 cliopts::Parser parser; 41 ConnParams params; 42 lcb_t instance; 43 Histogram hg; 44 std::string cmdname; 45 }; 46 47 48 class GetHandler : public Handler { 49 public: 50 GetHandler(const char *name = "get") : Handler(name)51 Handler(name), o_replica("replica"), o_exptime("expiry") {} 52 description()53 const char* description() const { 54 if (isLock()) { 55 return "Lock keys and retrieve them from the cluster"; 56 } else { 57 return "Retrieve items from the cluster"; 58 } 59 } 60 61 protected: 62 void addOptions(); 63 void run(); 64 65 private: 66 cliopts::StringOption o_replica; 67 cliopts::UIntOption o_exptime; isLock()68 bool isLock() const { return cmdname == "lock"; } 69 }; 70 71 class TouchHandler : public Handler { 72 public: 73 TouchHandler(const char *name = "touch") : Handler(name)74 Handler(name), o_exptime("expiry") { 75 o_exptime.abbrev('e').mandatory(true); 76 } 77 HANDLER_DESCRIPTION("Updated expiry times for documents") 78 protected: 79 void addOptions(); 80 void run(); 81 private: 82 cliopts::UIntOption o_exptime; 83 }; 84 85 class SetHandler : public Handler { 86 public: Handler(name)87 SetHandler(const char *name = "create") : Handler(name), 88 o_flags("flags"), o_exp("expiry"), o_add("add"), o_persist("persist-to"), 89 o_replicate("replicate-to"), o_value("value"), o_json("json"), 90 o_mode("mode") { 91 92 o_flags.abbrev('f').description("Flags for item"); 93 o_exp.abbrev('e').description("Expiry for item"); 94 o_add.abbrev('a').description("Fail if item exists").hide(); 95 o_persist.abbrev('p').description("Wait until item is persisted to this number of nodes"); 96 o_replicate.abbrev('r').description("Wait until item is replicated to this number of nodes"); 97 o_value.abbrev('V').description("Value to use. If unspecified, read from standard input"); 98 o_json.abbrev('J').description("Indicate to the server that this item is JSON"); 99 o_mode.abbrev('M').description("Mode to use when storing"); 100 o_mode.argdesc("upsert|insert|replace"); 101 o_mode.setDefault("upsert"); 102 } 103 description()104 const char* description() const { 105 if (hasFileList()) { 106 return "Store files to the server"; 107 } else { 108 return "Store item to the server"; 109 } 110 } 111 usagestr()112 const char* usagestr() const { 113 if (hasFileList()) { 114 return "[OPTIONS...] FILE ..."; 115 } else { 116 return "[OPTIONS...] KEY -V VALUE"; 117 } 118 } 119 hasFileList()120 bool hasFileList() const { return cmdname == "cp"; } 121 122 virtual lcb_storage_t mode(); 123 124 protected: 125 void run(); 126 void addOptions(); 127 void storeItem(const std::string& key, const char *value, size_t nvalue); 128 void storeItem(const std::string& key, FILE *input); 129 130 private: 131 cliopts::UIntOption o_flags; 132 cliopts::UIntOption o_exp; 133 cliopts::BoolOption o_add; 134 cliopts::IntOption o_persist; 135 cliopts::IntOption o_replicate; 136 cliopts::StringOption o_value; 137 cliopts::BoolOption o_json; 138 cliopts::StringOption o_mode; 139 std::map<std::string, lcb_cas_t> items; 140 }; 141 142 class HashHandler : public Handler { 143 public: 144 HANDLER_DESCRIPTION("Get mapping information for keys") 145 HANDLER_USAGE("KEY ... [OPTIONS ...]") HashHandler()146 HashHandler() : Handler("hash") {} 147 protected: 148 void run(); 149 }; 150 151 class ObserveHandler : public Handler { 152 public: ObserveHandler()153 ObserveHandler() : Handler("observe") { } 154 HANDLER_DESCRIPTION("Obtain persistence and replication status for keys") 155 HANDLER_USAGE("KEY ... ") 156 protected: 157 void run(); 158 }; 159 160 class ObserveSeqnoHandler : public Handler { 161 public: ObserveSeqnoHandler()162 ObserveSeqnoHandler() : Handler("observe-seqno") {} 163 164 HANDLER_DESCRIPTION("Request information about a particular vBucket UUID") 165 HANDLER_USAGE("UUID") 166 167 protected: 168 void run(); 169 }; 170 171 class UnlockHandler : public Handler { 172 public: 173 HANDLER_DESCRIPTION("Unlock keys") 174 HANDLER_USAGE("KEY CAS [OPTIONS ...]") UnlockHandler()175 UnlockHandler() : Handler("unlock") {} 176 protected: 177 void run(); 178 }; 179 180 class VersionHandler : public Handler { 181 public: 182 HANDLER_DESCRIPTION("Display information about libcouchbase") VersionHandler()183 VersionHandler() : Handler("version") {} 184 void run(); addOptions()185 void addOptions() {} 186 }; 187 188 class RemoveHandler : public Handler { 189 public: 190 HANDLER_DESCRIPTION("Remove items from the cluster") 191 HANDLER_USAGE("KEY ... [OPTIONS ...]") RemoveHandler()192 RemoveHandler() : Handler("rm") {} 193 protected: 194 void run(); 195 }; 196 197 class StatsHandler : public Handler { 198 public: 199 HANDLER_DESCRIPTION("Retrieve cluster statistics") 200 HANDLER_USAGE("[STATS_KEY ...] [OPTIONS ...]") StatsHandler()201 StatsHandler() : Handler("stats"), o_keystats("keystats") { 202 o_keystats.description("Keys are document IDs. retrieve information about them"); 203 } 204 protected: 205 void run(); addOptions()206 void addOptions() { 207 Handler::addOptions(); 208 parser.addOption(o_keystats); 209 } 210 private: 211 cliopts::BoolOption o_keystats; 212 }; 213 214 class WatchHandler : public Handler { 215 public: 216 HANDLER_DESCRIPTION("Aggregate and display server statistics") 217 HANDLER_USAGE("[KEYS ....] [OPTIONS ...]") WatchHandler()218 WatchHandler() : Handler("watch"), o_interval("interval") { 219 o_interval.abbrev('n').description("Update interval in seconds").setDefault(1); 220 } 221 protected: 222 void run(); addOptions()223 void addOptions() { 224 Handler::addOptions(); 225 parser.addOption(o_interval); 226 } 227 private: 228 cliopts::UIntOption o_interval; 229 }; 230 231 class VerbosityHandler : public Handler { 232 public: 233 HANDLER_DESCRIPTION("Modify the memcached logging level") 234 HANDLER_USAGE("<detail|debug|info|warning> [OPTIONS ...]") VerbosityHandler()235 VerbosityHandler() : Handler("verbosity") {} 236 protected: 237 void run(); 238 }; 239 240 class McVersionHandler : public Handler { 241 public: 242 HANDLER_DESCRIPTION("Query server versions using the memcached command") 243 HANDLER_USAGE("[OPTIONS ...]") McVersionHandler()244 McVersionHandler() : Handler("mcversion") {} 245 protected: 246 void run(); 247 }; 248 249 class KeygenHandler : public Handler { 250 public: 251 HANDLER_DESCRIPTION("Output a list of keys that equally distribute amongst every vbucket") 252 HANDLER_USAGE("[OPTIONS ...]") KeygenHandler()253 KeygenHandler() : Handler("keygen"), o_keys_per_vbucket("keys-per-vbucket") { 254 o_keys_per_vbucket.setDefault(1).description("number of keys to generate per vbucket"); 255 } 256 protected: 257 void run(); addOptions()258 void addOptions() { 259 Handler::addOptions(); 260 parser.addOption(o_keys_per_vbucket); 261 } 262 263 private: 264 cliopts::UIntOption o_keys_per_vbucket; 265 }; 266 267 class PingHandler : public Handler { 268 public: 269 HANDLER_DESCRIPTION("Reach all services on every node and measure response time") 270 HANDLER_USAGE("[OPTIONS ...]") PingHandler()271 PingHandler() : Handler("ping"), o_details("details") { 272 o_details.description("Render extra details about status of the services"); 273 } 274 protected: 275 void run(); addOptions()276 void addOptions() { 277 Handler::addOptions(); 278 parser.addOption(o_details); 279 } 280 private: 281 cliopts::BoolOption o_details; 282 }; 283 284 class McFlushHandler : public Handler { 285 public: 286 HANDLER_DESCRIPTION("Flush a memcached bucket") McFlushHandler()287 McFlushHandler() : Handler("mcflush") {} 288 protected: 289 void run(); 290 }; 291 292 class ArithmeticHandler : public Handler { 293 public: 294 HANDLER_USAGE("KEY ... [OPTIONS ...]") 295 ArithmeticHandler(const char * name)296 ArithmeticHandler(const char *name) : Handler(name), 297 o_initial("initial"), o_delta("delta"), o_expiry("expiry") { 298 299 o_initial.description("Initial value if item does not exist"); 300 o_delta.setDefault(1); 301 o_expiry.abbrev('e').description("Expiration time for key"); 302 } 303 protected: 304 cliopts::ULongLongOption o_initial; 305 cliopts::ULongLongOption o_delta; 306 cliopts::UIntOption o_expiry; 307 void run(); 308 virtual bool shouldInvert() const = 0; addOptions()309 void addOptions() { 310 Handler::addOptions(); 311 parser.addOption(o_initial); 312 parser.addOption(o_delta); 313 parser.addOption(o_expiry); 314 } 315 }; 316 317 class IncrHandler : public ArithmeticHandler { 318 public: 319 HANDLER_DESCRIPTION("Increment a counter") IncrHandler()320 IncrHandler() : ArithmeticHandler("incr") { 321 o_delta.description("Amount to increment by"); 322 } 323 protected: shouldInvert()324 bool shouldInvert() const { return false; } 325 }; 326 327 class DecrHandler : public ArithmeticHandler { 328 public: 329 HANDLER_DESCRIPTION("Decrement a counter") DecrHandler()330 DecrHandler() : ArithmeticHandler("decr") { 331 o_delta.description("Amount to decrement by"); 332 } 333 protected: shouldInvert()334 bool shouldInvert() const { return true; } 335 }; 336 337 class ViewsHandler : public Handler { 338 public: ViewsHandler()339 ViewsHandler() : Handler("view"), 340 o_spatial("spatial"), o_incdocs("with-docs"), o_params("params") {} 341 342 HANDLER_DESCRIPTION("Query a view") 343 HANDLER_USAGE("DESIGN/VIEW") 344 345 protected: 346 void run(); addOptions()347 void addOptions() { 348 Handler::addOptions(); 349 parser.addOption(o_spatial); 350 parser.addOption(o_incdocs); 351 parser.addOption(o_params); 352 } 353 private: 354 cliopts::BoolOption o_spatial; 355 cliopts::BoolOption o_incdocs; 356 cliopts::StringOption o_params; 357 }; 358 359 class N1qlHandler : public Handler { 360 public: N1qlHandler()361 N1qlHandler() : Handler("query"), o_args("qarg"), o_opts("qopt"), 362 o_prepare("prepare"), o_analytics("analytics") {} 363 HANDLER_DESCRIPTION("Execute a N1QL/Analytics Query") 364 HANDLER_USAGE("QUERY [--qarg PARAM1=VALUE1 --qopt PARAM2=VALUE2]") 365 366 protected: 367 void run(); 368 addOptions()369 void addOptions() { 370 Handler::addOptions(); 371 o_args.description( 372 "Specify values for placeholders (can be specified multiple times"); 373 o_args.abbrev('A'); 374 o_args.argdesc("PLACEHOLDER_PARAM=PLACEHOLDER_VALUE"); 375 376 o_opts.description("Additional query options"); 377 o_opts.abbrev('Q'); 378 379 o_prepare.description("Prepare query before issuing"); 380 o_analytics.description("Perform query to analytics service"); 381 382 parser.addOption(o_args); 383 parser.addOption(o_opts); 384 parser.addOption(o_prepare); 385 parser.addOption(o_analytics); 386 } 387 private: 388 cliopts::ListOption o_args; 389 cliopts::ListOption o_opts; 390 cliopts::BoolOption o_prepare; 391 cliopts::BoolOption o_analytics; 392 }; 393 394 class HttpReceiver { 395 public: HttpReceiver()396 HttpReceiver() : statusInvoked(false) {} ~HttpReceiver()397 virtual ~HttpReceiver() {} 398 void maybeInvokeStatus(const lcb_RESPHTTP*); 399 void install(lcb_t); handleStatus(lcb_error_t,int)400 virtual void handleStatus(lcb_error_t, int) {} onDone()401 virtual void onDone() {} onChunk(const char * data,size_t size)402 virtual void onChunk(const char *data, size_t size) { 403 resbuf.append(data, size); 404 } 405 bool statusInvoked; 406 std::string resbuf; 407 std::map<std::string, std::string> headers; 408 }; 409 410 class HttpBaseHandler : public Handler, public HttpReceiver { 411 public: HttpBaseHandler(const char * name)412 HttpBaseHandler(const char *name) : Handler(name) , 413 o_method("method") { 414 415 o_method.setDefault("GET").abbrev('X').description("HTTP Method to use"); 416 } 417 418 protected: 419 void run(); 420 virtual std::string getURI() = 0; 421 virtual const std::string& getBody(); getContentType()422 virtual std::string getContentType() { return ""; } isAdmin()423 virtual bool isAdmin() const { return false; } 424 virtual lcb_http_method_t getMethod(); 425 virtual void handleStatus(lcb_error_t err, int code); addOptions()426 virtual void addOptions() { 427 if (isAdmin()) { 428 params.setAdminMode(); 429 } 430 Handler::addOptions(); 431 parser.addOption(o_method); 432 } 433 cliopts::StringOption o_method; 434 435 private: 436 std::string body_cached; 437 }; 438 439 class AdminHandler : public HttpBaseHandler { 440 public: 441 HANDLER_DESCRIPTION("Invoke an administrative REST API") 442 HANDLER_USAGE("PATH ... [OPTIONS ...]") HttpBaseHandler(name)443 AdminHandler(const char *name = "admin") : HttpBaseHandler(name) {} 444 protected: 445 virtual void run(); 446 virtual std::string getURI(); isAdmin()447 virtual bool isAdmin() const { return true; } 448 449 }; 450 451 class RbacHandler : public AdminHandler { 452 public: 453 HANDLER_USAGE("[OPTIONS ...]") RbacHandler(const char * name)454 RbacHandler(const char *name) : AdminHandler(name), 455 o_raw('r', "raw") 456 { 457 o_raw.description("Do not reformat output from server (display JSON response)"); 458 } 459 460 protected: 461 virtual void run(); 462 virtual void format() = 0; addOptions()463 virtual void addOptions() { 464 AdminHandler::addOptions(); 465 parser.addOption(o_raw); 466 } 467 468 private: 469 cliopts::BoolOption o_raw; 470 }; 471 472 class RoleListHandler : public RbacHandler { 473 public: 474 HANDLER_DESCRIPTION("List roles") RoleListHandler()475 RoleListHandler() : RbacHandler("role-list") 476 { 477 } 478 479 protected: 480 virtual void format(); addOptions()481 virtual void addOptions() { 482 RbacHandler::addOptions(); 483 } getURI()484 std::string getURI() { return "/settings/rbac/roles"; } getBody()485 const std::string& getBody() { static std::string e; return e; } getMethod()486 lcb_http_method_t getMethod() { return LCB_HTTP_METHOD_GET; } 487 }; 488 489 class UserListHandler : public RbacHandler { 490 public: 491 HANDLER_DESCRIPTION("List users") UserListHandler()492 UserListHandler() : RbacHandler("user-list") 493 { 494 } 495 496 protected: 497 virtual void format(); addOptions()498 virtual void addOptions() { 499 RbacHandler::addOptions(); 500 } getURI()501 std::string getURI() { return "/settings/rbac/users"; } getBody()502 const std::string& getBody() { static std::string e; return e; } getMethod()503 lcb_http_method_t getMethod() { return LCB_HTTP_METHOD_GET; } 504 }; 505 506 class UserDeleteHandler : public AdminHandler { 507 public: 508 HANDLER_DESCRIPTION("Delete a user") 509 HANDLER_USAGE("NAME [OPTIONS ...]") UserDeleteHandler()510 UserDeleteHandler() : AdminHandler("user-delete"), 511 o_domain("domain") 512 { 513 o_domain.description("The domain, where user account defined {local,external}").setDefault("local"); 514 } 515 516 protected: addOptions()517 virtual void addOptions() { 518 AdminHandler::addOptions(); 519 parser.addOption(o_domain); 520 } run()521 void run() { 522 name = getRequiredArg(); 523 domain = o_domain.result(); 524 if (domain != "local" && domain != "external") { 525 throw BadArg("Unrecognized domain type"); 526 } 527 AdminHandler::run(); 528 } getURI()529 std::string getURI() { return std::string("/settings/rbac/users/") + domain + "/" + name; } getBody()530 const std::string& getBody() { static std::string e; return e; } getMethod()531 lcb_http_method_t getMethod() { return LCB_HTTP_METHOD_DELETE; } 532 533 private: 534 cliopts::StringOption o_domain; 535 std::string name; 536 std::string domain; 537 }; 538 539 class UserUpsertHandler : public AdminHandler { 540 public: 541 HANDLER_DESCRIPTION("Create or update a user") 542 HANDLER_USAGE("NAME [OPTIONS ...]") UserUpsertHandler()543 UserUpsertHandler() : AdminHandler("user-upsert"), 544 o_domain("domain"), 545 o_full_name("full-name"), 546 o_password("user-password"), 547 o_roles("role") 548 { 549 o_domain.description("The domain, where user account defined {local,external}").setDefault("local"); 550 o_full_name.description("The user's fullname"); 551 o_roles.description("The role associated with user (can be specified multiple times if needed)"); 552 o_password.description("The password for the user"); 553 } 554 555 protected: addOptions()556 virtual void addOptions() { 557 AdminHandler::addOptions(); 558 parser.addOption(o_domain); 559 parser.addOption(o_full_name); 560 parser.addOption(o_roles); 561 parser.addOption(o_password); 562 } 563 virtual void run(); getURI()564 std::string getURI() { return std::string("/settings/rbac/users/") + domain + "/" + name; } getBody()565 const std::string& getBody() { return body; } getContentType()566 std::string getContentType() { return "application/x-www-form-urlencoded"; } getMethod()567 lcb_http_method_t getMethod() { return LCB_HTTP_METHOD_PUT; } 568 569 private: 570 cliopts::StringOption o_domain; 571 cliopts::StringOption o_full_name; 572 cliopts::StringOption o_password; 573 cliopts::ListOption o_roles; 574 std::string name; 575 std::string domain; 576 std::string body; 577 }; 578 579 class BucketCreateHandler : public AdminHandler { 580 public: 581 HANDLER_DESCRIPTION("Create a bucket") 582 HANDLER_USAGE("NAME [OPTIONS ...]") BucketCreateHandler()583 BucketCreateHandler() : AdminHandler("bucket-create"), 584 o_btype("bucket-type"), 585 o_ramquota("ram-quota"), 586 o_bpass("bucket-password"), 587 o_replicas("num-replicas"), 588 o_proxyport("moxi-port"), 589 isMemcached(false) 590 { 591 o_btype.description("Bucket type {couchbase,memcached}").setDefault("couchbase"); 592 o_ramquota.description("RAM Quota for bucket (MB)").setDefault(100); 593 o_bpass.description("Bucket password"); 594 o_replicas.description("Number of replicas for bucket").setDefault(1); 595 o_proxyport.description("[Compatibility] memcached listening port"); 596 597 } 598 599 protected: 600 virtual void run(); addOptions()601 virtual void addOptions() { 602 AdminHandler::addOptions(); 603 parser.addOption(o_btype); 604 parser.addOption(o_ramquota); 605 parser.addOption(o_bpass); 606 parser.addOption(o_replicas); 607 parser.addOption(o_proxyport); 608 } 609 getURI()610 std::string getURI() { return "/pools/default/buckets"; } getBody()611 const std::string& getBody() { return body_s; } getContentType()612 std::string getContentType() { return "application/x-www-form-urlencoded"; } getMethod()613 lcb_http_method_t getMethod() { return LCB_HTTP_METHOD_POST; } 614 615 private: 616 cliopts::StringOption o_btype; 617 cliopts::UIntOption o_ramquota; 618 cliopts::StringOption o_bpass; 619 cliopts::UIntOption o_replicas; 620 cliopts::UIntOption o_proxyport; 621 std::string body_s; 622 bool isMemcached; 623 }; 624 625 class BucketDeleteHandler : public AdminHandler { 626 public: 627 HANDLER_DESCRIPTION("Delete a bucket") 628 HANDLER_USAGE("NAME [OPTIONS ...]") BucketDeleteHandler()629 BucketDeleteHandler() : AdminHandler("bucket-delete") {} 630 631 protected: run()632 void run() { 633 bname = getRequiredArg(); 634 AdminHandler::run(); 635 } getURI()636 std::string getURI() { return std::string("/pools/default/buckets/") + bname; } getMethod()637 lcb_http_method_t getMethod() { return LCB_HTTP_METHOD_DELETE; } getBody()638 const std::string& getBody() { static std::string e; return e; } 639 private: 640 std::string bname; 641 }; 642 643 class BucketFlushHandler : public Handler { 644 public: 645 HANDLER_DESCRIPTION("Flush a bucket") 646 HANDLER_USAGE("[COMMON OPTIONS ...]") BucketFlushHandler()647 BucketFlushHandler() : Handler("bucket-flush") {} 648 protected: 649 void run(); 650 }; 651 652 class ConnstrHandler : public Handler { 653 public: 654 HANDLER_DESCRIPTION("Parse a connection string and provide info on its components") 655 HANDLER_USAGE("CONNSTR") ConnstrHandler()656 ConnstrHandler() : Handler("connstr") {} 657 protected: handleOptions()658 void handleOptions() { } 659 void run(); 660 }; 661 662 class WriteConfigHandler : public Handler { 663 public: 664 HANDLER_DESCRIPTION("Write the configuration file based on arguments passed") WriteConfigHandler()665 WriteConfigHandler() : Handler("write-config") {} 666 protected: 667 void run(); 668 }; 669 670 } 671 #endif 672