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