libcouchbase 1.0.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +11 -8
  3. data/ext/libcouchbase/CMakeLists.txt +1 -1
  4. data/ext/libcouchbase/README.markdown +38 -6
  5. data/ext/libcouchbase/RELEASE_NOTES.markdown +151 -0
  6. data/ext/libcouchbase/cmake/Modules/GenerateConfigDotH.cmake +2 -2
  7. data/ext/libcouchbase/cmake/Modules/GetVersionInfo.cmake +3 -3
  8. data/ext/libcouchbase/cmake/source_files.cmake +1 -0
  9. data/ext/libcouchbase/contrib/cJSON/cJSON.c +686 -288
  10. data/ext/libcouchbase/contrib/cJSON/cJSON.h +0 -0
  11. data/ext/libcouchbase/contrib/cbsasl/src/hash.c +17 -17
  12. data/ext/libcouchbase/contrib/cliopts/cliopts.c +76 -0
  13. data/ext/libcouchbase/contrib/cliopts/cliopts.h +66 -15
  14. data/ext/libcouchbase/contrib/genhash/genhash.c +1 -2
  15. data/ext/libcouchbase/contrib/lcb-jsoncpp/lcb-jsoncpp.cpp +4 -3
  16. data/ext/libcouchbase/example/instancepool/main.cc +12 -2
  17. data/ext/libcouchbase/example/libeventdirect/main.c +99 -25
  18. data/ext/libcouchbase/example/minimal/minimal.c +7 -5
  19. data/ext/libcouchbase/example/observe/durability.c +102 -0
  20. data/ext/libcouchbase/example/observe/observe.c +19 -6
  21. data/ext/libcouchbase/example/subdoc/subdoc-xattrs.c +1 -2
  22. data/ext/libcouchbase/include/libcouchbase/cntl-private.h +6 -8
  23. data/ext/libcouchbase/include/libcouchbase/cntl.h +84 -64
  24. data/ext/libcouchbase/include/libcouchbase/couchbase.h +295 -78
  25. data/ext/libcouchbase/include/libcouchbase/deprecated.h +2 -2
  26. data/ext/libcouchbase/include/libcouchbase/error.h +1 -1
  27. data/ext/libcouchbase/include/libcouchbase/iops.h +9 -9
  28. data/ext/libcouchbase/include/libcouchbase/ixmgmt.h +2 -2
  29. data/ext/libcouchbase/include/libcouchbase/n1ql.h +69 -7
  30. data/ext/libcouchbase/include/libcouchbase/vbucket.h +17 -0
  31. data/ext/libcouchbase/include/libcouchbase/views.h +3 -3
  32. data/ext/libcouchbase/include/memcached/protocol_binary.h +62 -1
  33. data/ext/libcouchbase/packaging/deb/control +1 -1
  34. data/ext/libcouchbase/packaging/rpm/libcouchbase.spec.in +37 -36
  35. data/ext/libcouchbase/src/bootstrap.cc +22 -8
  36. data/ext/libcouchbase/src/bucketconfig/bc_cccp.cc +1 -1
  37. data/ext/libcouchbase/src/bucketconfig/bc_http.cc +0 -1
  38. data/ext/libcouchbase/src/bucketconfig/confmon.cc +13 -8
  39. data/ext/libcouchbase/src/callbacks.c +2 -0
  40. data/ext/libcouchbase/src/cntl.cc +28 -17
  41. data/ext/libcouchbase/src/dns-srv.cc +1 -2
  42. data/ext/libcouchbase/src/dump.cc +4 -0
  43. data/ext/libcouchbase/src/errmap.h +89 -16
  44. data/ext/libcouchbase/src/handler.cc +28 -11
  45. data/ext/libcouchbase/src/http/http-priv.h +4 -1
  46. data/ext/libcouchbase/src/http/http.cc +3 -0
  47. data/ext/libcouchbase/src/instance.cc +1 -1
  48. data/ext/libcouchbase/src/internal.h +1 -0
  49. data/ext/libcouchbase/src/lcbio/connect.cc +2 -3
  50. data/ext/libcouchbase/src/lcbio/manager.cc +2 -2
  51. data/ext/libcouchbase/src/lcbio/ssl.h +10 -0
  52. data/ext/libcouchbase/src/mc/mcreq.c +8 -0
  53. data/ext/libcouchbase/src/mcserver/mcserver.cc +14 -1
  54. data/ext/libcouchbase/src/n1ql/ixmgmt.cc +0 -3
  55. data/ext/libcouchbase/src/n1ql/n1ql.cc +22 -29
  56. data/ext/libcouchbase/src/n1ql/params.cc +46 -1
  57. data/ext/libcouchbase/src/newconfig.cc +4 -4
  58. data/ext/libcouchbase/src/operations/durability-seqno.cc +4 -0
  59. data/ext/libcouchbase/src/operations/durability.cc +3 -0
  60. data/ext/libcouchbase/src/operations/ping.cc +315 -0
  61. data/ext/libcouchbase/src/operations/stats.cc +10 -0
  62. data/ext/libcouchbase/src/operations/subdoc.cc +13 -1
  63. data/ext/libcouchbase/src/retrychk.cc +1 -0
  64. data/ext/libcouchbase/src/settings.c +2 -0
  65. data/ext/libcouchbase/src/settings.h +13 -7
  66. data/ext/libcouchbase/src/ssl/ssl_c.c +28 -2
  67. data/ext/libcouchbase/src/ssl/ssl_common.c +3 -0
  68. data/ext/libcouchbase/src/ssl/ssl_e.c +15 -1
  69. data/ext/libcouchbase/src/ssl/ssl_iot_common.h +3 -1
  70. data/ext/libcouchbase/src/timings.c +0 -1
  71. data/ext/libcouchbase/src/vbucket/vbucket.c +49 -1
  72. data/ext/libcouchbase/tests/iotests/mock-environment.cc +58 -40
  73. data/ext/libcouchbase/tests/iotests/mock-environment.h +23 -4
  74. data/ext/libcouchbase/tests/iotests/mock-unit-test.h +8 -8
  75. data/ext/libcouchbase/tests/iotests/t_behavior.cc +5 -5
  76. data/ext/libcouchbase/tests/iotests/t_durability.cc +50 -0
  77. data/ext/libcouchbase/tests/iotests/t_eerrs.cc +4 -2
  78. data/ext/libcouchbase/tests/iotests/t_errmap.cc +6 -3
  79. data/ext/libcouchbase/tests/iotests/t_lock.cc +5 -6
  80. data/ext/libcouchbase/tests/iotests/t_misc.cc +44 -0
  81. data/ext/libcouchbase/tests/iotests/t_serverops.cc +1 -0
  82. data/ext/libcouchbase/tests/iotests/t_subdoc.cc +28 -0
  83. data/ext/libcouchbase/tests/iotests/t_views.cc +22 -10
  84. data/ext/libcouchbase/tools/CMakeLists.txt +21 -1
  85. data/ext/libcouchbase/tools/cbc-handlers.h +23 -3
  86. data/ext/libcouchbase/tools/cbc-n1qlback.cc +1 -1
  87. data/ext/libcouchbase/tools/cbc-pillowfight.cc +126 -26
  88. data/ext/libcouchbase/tools/cbc-proxy.cc +403 -0
  89. data/ext/libcouchbase/tools/cbc-subdoc.cc +826 -0
  90. data/ext/libcouchbase/tools/cbc.cc +149 -37
  91. data/ext/libcouchbase/tools/common/options.h +5 -2
  92. data/ext/libcouchbase/tools/linenoise/linenoise.c +15 -15
  93. data/lib/libcouchbase.rb +4 -0
  94. data/lib/libcouchbase/bucket.rb +51 -0
  95. data/lib/libcouchbase/connection.rb +100 -13
  96. data/lib/libcouchbase/ext/libcouchbase.rb +40 -0
  97. data/lib/libcouchbase/ext/libcouchbase/cmdsubdoc.rb +13 -1
  98. data/lib/libcouchbase/ext/libcouchbase/enums.rb +2 -1
  99. data/lib/libcouchbase/ext/libcouchbase/sdspec.rb +5 -0
  100. data/lib/libcouchbase/subdoc_request.rb +129 -0
  101. data/lib/libcouchbase/version.rb +1 -1
  102. data/spec/bucket_spec.rb +15 -1
  103. data/spec/connection_spec.rb +1 -1
  104. data/spec/subdoc_spec.rb +192 -0
  105. metadata +13 -4
  106. data/ext/libcouchbase/.travis.yml +0 -19
@@ -203,6 +203,23 @@ protected:
203
203
  void run();
204
204
  };
205
205
 
206
+ class PingHandler : public Handler {
207
+ public:
208
+ HANDLER_DESCRIPTION("Reach all services on every node and measure response time")
209
+ HANDLER_USAGE("[OPTIONS ...]")
210
+ PingHandler() : Handler("ping"), o_details("details") {
211
+ o_details.description("Render extra details about status of the services");
212
+ }
213
+ protected:
214
+ void run();
215
+ void addOptions() {
216
+ Handler::addOptions();
217
+ parser.addOption(o_details);
218
+ }
219
+ private:
220
+ cliopts::BoolOption o_details;
221
+ };
222
+
206
223
  class McFlushHandler : public Handler {
207
224
  public:
208
225
  HANDLER_DESCRIPTION("Flush a memcached bucket")
@@ -280,9 +297,9 @@ private:
280
297
 
281
298
  class N1qlHandler : public Handler {
282
299
  public:
283
- N1qlHandler() : Handler("view"), o_args("qarg"), o_opts("qopt"),
284
- o_prepare("prepare") {}
285
- HANDLER_DESCRIPTION("Execute a N1QL Query")
300
+ N1qlHandler() : Handler("query"), o_args("qarg"), o_opts("qopt"),
301
+ o_prepare("prepare"), o_analytics("analytics") {}
302
+ HANDLER_DESCRIPTION("Execute a N1QL/Analytics Query")
286
303
  HANDLER_USAGE("QUERY [--qarg PARAM1=VALUE1 --qopt PARAM2=VALUE2]")
287
304
 
288
305
  protected:
@@ -299,15 +316,18 @@ protected:
299
316
  o_opts.abbrev('Q');
300
317
 
301
318
  o_prepare.description("Prepare query before issuing");
319
+ o_analytics.description("Perform query to analytics service");
302
320
 
303
321
  parser.addOption(o_args);
304
322
  parser.addOption(o_opts);
305
323
  parser.addOption(o_prepare);
324
+ parser.addOption(o_analytics);
306
325
  }
307
326
  private:
308
327
  cliopts::ListOption o_args;
309
328
  cliopts::ListOption o_opts;
310
329
  cliopts::BoolOption o_prepare;
330
+ cliopts::BoolOption o_analytics;
311
331
  };
312
332
 
313
333
  class HttpReceiver {
@@ -122,7 +122,7 @@ private:
122
122
 
123
123
  last_update = now;
124
124
 
125
- const char *prefix = use_ansi_codes ? "\x1B[K" : "";
125
+ const char *prefix;
126
126
  const char *final_suffix;
127
127
 
128
128
  // Only use "ticker" style updates if we're a TTY and we have no
@@ -118,6 +118,7 @@ public:
118
118
  o_writeJson("json"),
119
119
  o_templatePairs("template"),
120
120
  o_subdoc("subdoc"),
121
+ o_noop("noop"),
121
122
  o_sdPathCount("pathcount"),
122
123
  o_populateOnly("populate-only"),
123
124
  o_exptime("expiry")
@@ -141,6 +142,7 @@ public:
141
142
  o_templatePairs.description("Values for templates to be inserted into user documents");
142
143
  o_templatePairs.argdesc("FIELD,MIN,MAX[,SEQUENTIAL]").hide();
143
144
  o_subdoc.description("Use subdoc instead of fulldoc operations");
145
+ o_noop.description("Use NOOP instead of document operations").setDefault(0);
144
146
  o_sdPathCount.description("Number of subdoc paths per command").setDefault(1);
145
147
  o_populateOnly.description("Exit after documents have been populated");
146
148
  o_exptime.description("Set TTL for items").abbrev('e');
@@ -256,6 +258,7 @@ public:
256
258
  parser.addOption(o_writeJson);
257
259
  parser.addOption(o_templatePairs);
258
260
  parser.addOption(o_subdoc);
261
+ parser.addOption(o_noop);
259
262
  parser.addOption(o_sdPathCount);
260
263
  parser.addOption(o_populateOnly);
261
264
  parser.addOption(o_exptime);
@@ -278,6 +281,7 @@ public:
278
281
  bool shouldPauseAtEnd() { return o_pauseAtEnd; }
279
282
  bool sequentialAccess() { return o_sequential; }
280
283
  bool isSubdoc() { return o_subdoc; }
284
+ bool isNoop() { return o_noop.result(); }
281
285
  unsigned firstKeyOffset() { return o_startAt; }
282
286
  uint32_t getNumItems() { return o_numItems; }
283
287
  uint32_t getRateLimit() { return o_rateLimit; }
@@ -319,6 +323,7 @@ private:
319
323
  // List of template ranges for value generation
320
324
  ListOption o_templatePairs;
321
325
  BoolOption o_subdoc;
326
+ BoolOption o_noop;
322
327
  UIntOption o_sdPathCount;
323
328
 
324
329
  // Compound option
@@ -394,17 +399,51 @@ struct NextOp {
394
399
  vector<lcb_IOV> m_valuefrags;
395
400
  vector<lcb_SDSPEC> m_specs;
396
401
  // The mode here is for future use with subdoc
397
- enum Mode { STORE, GET, SDSTORE, SDGET };
402
+ enum Mode { STORE, GET, SDSTORE, SDGET, NOOP };
398
403
  Mode m_mode;
399
404
  };
400
405
 
406
+ class OpGenerator {
407
+ public:
408
+ OpGenerator(int id): m_id(id) {}
409
+
410
+ virtual ~OpGenerator() {};
411
+ virtual void setNextOp(NextOp& op) = 0;
412
+ virtual void setValue(NextOp& op) = 0;
413
+ virtual bool inPopulation() const = 0;
414
+ virtual const char *getStageString() const = 0;
415
+
416
+ protected:
417
+ int m_id;
418
+ };
419
+
420
+ class NoopGenerator : public OpGenerator {
421
+ public:
422
+ NoopGenerator(int ix) : OpGenerator(ix) {}
423
+
424
+
425
+ void setNextOp(NextOp& op) {
426
+ op.m_mode = NextOp::NOOP;
427
+ }
428
+
429
+ void setValue(NextOp&) {}
430
+
431
+ bool inPopulation() const {
432
+ return false;
433
+ }
434
+
435
+ const char *getStageString() const {
436
+ return "Run";
437
+ }
438
+ };
439
+
401
440
  /** Stateful, per-thread generator */
402
- class KeyGenerator {
441
+ class KeyGenerator : public OpGenerator {
403
442
  public:
404
443
  KeyGenerator(int ix)
405
- : m_gencount(0), m_force_sequential(false),
444
+ : OpGenerator(ix), m_gencount(0), m_force_sequential(false),
406
445
  m_in_population(config.shouldPopulate)
407
- {
446
+ {
408
447
  srand(config.getRandomSeed());
409
448
 
410
449
  m_genrandom = new SeqGenerator(
@@ -423,7 +462,6 @@ public:
423
462
  m_force_sequential = config.sequentialAccess();
424
463
  }
425
464
 
426
- m_id = ix;
427
465
  m_local_genstate = config.docgen->createState(config.getNumThreads(), ix);
428
466
  if (config.isSubdoc()) {
429
467
  m_mode_read = NextOp::SDGET;
@@ -439,6 +477,10 @@ public:
439
477
  }
440
478
  }
441
479
 
480
+ void setValue(NextOp& op) {
481
+ m_local_genstate->populateIov(op.m_seqno, op.m_valuefrags);
482
+ }
483
+
442
484
  void setNextOp(NextOp& op) {
443
485
  bool store_override = false;
444
486
 
@@ -461,12 +503,12 @@ public:
461
503
  if (store_override) {
462
504
  // Populate
463
505
  op.m_mode = NextOp::STORE;
464
- m_local_genstate->populateIov(op.m_seqno, op.m_valuefrags);
506
+ setValue(op);
465
507
 
466
508
  } else if (shouldStore(op.m_seqno)) {
467
509
  op.m_mode = m_mode_write;
468
510
  if (op.m_mode == NextOp::STORE) {
469
- m_local_genstate->populateIov(op.m_seqno, op.m_valuefrags);
511
+ setValue(op);
470
512
  } else if (op.m_mode == NextOp::SDSTORE) {
471
513
  op.m_specs.resize(config.sdOpsPerCmd);
472
514
  m_sdgenstate->populateMutate(op.m_seqno, op.m_specs);
@@ -485,6 +527,19 @@ public:
485
527
  generateKey(op);
486
528
  }
487
529
 
530
+ bool inPopulation() const {
531
+ return m_in_population;
532
+ }
533
+
534
+ const char *getStageString() const {
535
+ if (m_in_population) {
536
+ return "Populate";
537
+ } else {
538
+ return "Run";
539
+ }
540
+ }
541
+
542
+ private:
488
543
  bool shouldStore(uint32_t seqno) {
489
544
  if (config.setprc == 0) {
490
545
  return false;
@@ -502,15 +557,7 @@ public:
502
557
  op.m_key.assign(config.getKeyPrefix() + buffer);
503
558
  }
504
559
 
505
- const char *getStageString() const {
506
- if (m_in_population) {
507
- return "Populate";
508
- } else {
509
- return "Run";
510
- }
511
- }
512
560
 
513
- private:
514
561
  SeqGenerator *m_genrandom;
515
562
  SeqGenerator *m_gensequence;
516
563
  size_t m_gencount;
@@ -527,18 +574,31 @@ private:
527
574
  class ThreadContext
528
575
  {
529
576
  public:
530
- ThreadContext(lcb_t handle, int ix) : kgen(ix), niter(0), instance(handle) {
577
+ ThreadContext(lcb_t handle, int ix) : niter(0), instance(handle) {
578
+ if (config.isNoop()) {
579
+ gen = new NoopGenerator(ix);
580
+ } else {
581
+ gen = new KeyGenerator(ix);
582
+ }
583
+ }
531
584
 
585
+ ~ThreadContext() {
586
+ delete gen;
587
+ gen = NULL;
588
+ }
589
+
590
+ bool inPopulation() {
591
+ return gen && (gen->inPopulation() || !retryq.empty());
532
592
  }
533
593
 
534
594
  void singleLoop() {
535
595
  bool hasItems = false;
536
- lcb_sched_enter(instance);
537
596
  NextOp opinfo;
538
597
  unsigned exptime = config.getExptime();
539
598
 
599
+ lcb_sched_enter(instance);
540
600
  for (size_t ii = 0; ii < config.opsPerCycle; ++ii) {
541
- kgen.setNextOp(opinfo);
601
+ gen->setNextOp(opinfo);
542
602
 
543
603
  switch (opinfo.m_mode) {
544
604
  case NextOp::STORE: {
@@ -569,6 +629,11 @@ public:
569
629
  error = lcb_subdoc3(instance, this, &sdcmd);
570
630
  break;
571
631
  }
632
+ case NextOp::NOOP: {
633
+ lcb_CMDNOOP ncmd = { 0 };
634
+ error = lcb_noop3(instance, this, &ncmd);
635
+ break;
636
+ }
572
637
  }
573
638
 
574
639
  if (error != LCB_SUCCESS) {
@@ -587,6 +652,25 @@ public:
587
652
  } else {
588
653
  lcb_sched_fail(instance);
589
654
  }
655
+
656
+ while (!retryq.empty()) {
657
+ lcb_sched_enter(instance);
658
+ while (!retryq.empty()) {
659
+ opinfo = retryq.front();
660
+ retryq.pop();
661
+ lcb_CMDSTORE scmd = { 0 };
662
+ scmd.operation = LCB_SET;
663
+ scmd.exptime = exptime;
664
+ LCB_CMD_SET_KEY(&scmd, opinfo.m_key.c_str(), opinfo.m_key.size());
665
+ LCB_CMD_SET_VALUEIOV(&scmd, &opinfo.m_valuefrags[0], opinfo.m_valuefrags.size());
666
+ error = lcb_store3(instance, this, &scmd);
667
+ }
668
+ lcb_sched_leave(instance);
669
+ lcb_wait(instance);
670
+ if (error != LCB_SUCCESS) {
671
+ log("Operation(s) failed: [0x%x] %s", error, lcb_strerror(instance, error));
672
+ }
673
+ }
590
674
  }
591
675
 
592
676
  bool run() {
@@ -594,7 +678,7 @@ public:
594
678
  singleLoop();
595
679
 
596
680
  if (config.isTimings()) {
597
- InstanceCookie::dumpTimings(instance, kgen.getStageString());
681
+ InstanceCookie::dumpTimings(instance, gen->getStageString());
598
682
  }
599
683
  if (config.params.shouldDump()) {
600
684
  lcb_dump(instance, stderr, LCB_DUMP_ALL);
@@ -606,11 +690,18 @@ public:
606
690
  } while (!config.isLoopDone(++niter));
607
691
 
608
692
  if (config.isTimings()) {
609
- InstanceCookie::dumpTimings(instance, kgen.getStageString(), true);
693
+ InstanceCookie::dumpTimings(instance, gen->getStageString(), true);
610
694
  }
611
695
  return true;
612
696
  }
613
697
 
698
+ void retry(NextOp &op) {
699
+ if (op.m_mode == NextOp::STORE) {
700
+ gen->setValue(op);
701
+ }
702
+ retryq.push(op);
703
+ }
704
+
614
705
  #ifndef WIN32
615
706
  pthread_t thr;
616
707
  #endif
@@ -645,31 +736,39 @@ private:
645
736
  previous_time = now;
646
737
  }
647
738
 
648
- KeyGenerator kgen;
739
+ OpGenerator *gen;
649
740
  size_t niter;
650
741
  lcb_error_t error;
651
742
  lcb_t instance;
743
+ std::queue<NextOp> retryq;
652
744
  };
653
745
 
654
- static void operationCallback(lcb_t, int, const lcb_RESPBASE *resp)
746
+ static void operationCallback(lcb_t, int cbtype, const lcb_RESPBASE *resp)
655
747
  {
656
748
  ThreadContext *tc;
657
749
 
658
750
  tc = const_cast<ThreadContext *>(reinterpret_cast<const ThreadContext *>(resp->cookie));
659
- tc->setError(resp->rc);
751
+ if (cbtype == LCB_CALLBACK_STORE && resp->rc != LCB_SUCCESS && tc->inPopulation()) {
752
+ NextOp op;
753
+ op.m_mode = NextOp::STORE;
754
+ op.m_key.assign((char *)resp->key, resp->nkey);
755
+ op.m_seqno = atoi(op.m_key.c_str());
756
+ tc->retry(op);
757
+ } else {
758
+ tc->setError(resp->rc);
759
+ }
660
760
 
661
761
  #ifndef WIN32
662
762
  static volatile unsigned long nops = 1;
663
763
  static time_t start_time = time(NULL);
664
- static int is_tty = isatty(STDOUT_FILENO);
764
+ static int is_tty = isatty(STDERR_FILENO);
665
765
  if (is_tty) {
666
766
  if (++nops % 1000 == 0) {
667
767
  time_t now = time(NULL);
668
768
  time_t nsecs = now - start_time;
669
769
  if (!nsecs) { nsecs = 1; }
670
770
  unsigned long ops_sec = nops / nsecs;
671
- printf("OPS/SEC: %10lu\r", ops_sec);
672
- fflush(stdout);
771
+ fprintf(stderr, "OPS/SEC: %10lu\r", ops_sec);
673
772
  }
674
773
  }
675
774
  #endif
@@ -795,6 +894,7 @@ int main(int argc, char **argv)
795
894
  lcb_install_callback3(instance, LCB_CALLBACK_GET, operationCallback);
796
895
  lcb_install_callback3(instance, LCB_CALLBACK_SDMUTATE, operationCallback);
797
896
  lcb_install_callback3(instance, LCB_CALLBACK_SDLOOKUP, operationCallback);
897
+ lcb_install_callback3(instance, LCB_CALLBACK_NOOP, operationCallback);
798
898
  cp.doCtls(instance);
799
899
 
800
900
  new InstanceCookie(instance);
@@ -0,0 +1,403 @@
1
+ /*
2
+ * Copyright 2017 Couchbase, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ #define LCB_NO_DEPR_CXX_CTORS
18
+
19
+ #include "common/my_inttypes.h"
20
+ #include "config.h"
21
+ #include <sys/types.h>
22
+ #include <libcouchbase/couchbase.h>
23
+ #include <libcouchbase/vbucket.h>
24
+ #include <libcouchbase/api3.h>
25
+ #include <libcouchbase/pktfwd.h>
26
+ #include <memcached/protocol_binary.h>
27
+ #include <iostream>
28
+ #include <iomanip>
29
+ #include <cstdio>
30
+ #include <cerrno>
31
+ #include <sstream>
32
+ #include <signal.h>
33
+ #include "common/options.h"
34
+ #include "common/histogram.h"
35
+
36
+ #include "internal.h"
37
+
38
+ #include <event2/event.h>
39
+ #include <event2/listener.h>
40
+ #include <event2/bufferevent.h>
41
+ #include <event2/buffer.h>
42
+
43
+ using namespace cbc;
44
+ using namespace cliopts;
45
+
46
+ static void die(const char *msg)
47
+ {
48
+ fprintf(stderr, "%s\n", msg);
49
+ exit(EXIT_FAILURE);
50
+ }
51
+
52
+ static void good_or_die(lcb_error_t rc, const char *msg = "")
53
+ {
54
+ if (rc != LCB_SUCCESS) {
55
+ fprintf(stderr, "%s\n0x%02x: %s\n", msg, rc, lcb_strerror(NULL, rc));
56
+ exit(EXIT_FAILURE);
57
+ }
58
+ }
59
+
60
+ static lcb_t instance = NULL;
61
+ static struct event_base *evbase = NULL;
62
+ static Histogram hg;
63
+
64
+ #define LOGARGS(lvl) (instance)->settings, "proxy", LCB_LOG_##lvl, __FILE__, __LINE__
65
+ #define CL_LOGFMT "<%s:%s> (cl=%p,fd=%d) "
66
+ #define CL_LOGID(cl) cl->host, cl->port, (void *)cl, cl->fd
67
+
68
+ class Configuration
69
+ {
70
+ public:
71
+ Configuration() : o_trace("trace"), o_port("port")
72
+ {
73
+ o_trace.abbrev('t').description("Show packet trace on INFO log level");
74
+ o_port.abbrev('p').description("Port for proxy").setDefault(11211);
75
+ }
76
+
77
+ ~Configuration()
78
+ {
79
+ }
80
+
81
+ void addToParser(Parser &parser)
82
+ {
83
+ m_params.addToParser(parser);
84
+ parser.addOption(o_trace);
85
+ parser.addOption(o_port);
86
+ }
87
+
88
+ void processOptions()
89
+ {
90
+ }
91
+
92
+ void fillCropts(lcb_create_st &opts)
93
+ {
94
+ m_params.fillCropts(opts);
95
+ }
96
+ lcb_error_t doCtls()
97
+ {
98
+ return m_params.doCtls(instance);
99
+ }
100
+ bool useTimings()
101
+ {
102
+ return m_params.useTimings();
103
+ }
104
+ bool shouldDump()
105
+ {
106
+ return m_params.shouldDump();
107
+ }
108
+
109
+ bool isTrace()
110
+ {
111
+ return o_trace.result();
112
+ }
113
+
114
+ unsigned port()
115
+ {
116
+ return o_port.result();
117
+ }
118
+
119
+ private:
120
+ ConnParams m_params;
121
+ BoolOption o_trace;
122
+ UIntOption o_port;
123
+ };
124
+
125
+ static Configuration config;
126
+
127
+ static struct evconnlistener *listener = NULL;
128
+
129
+ static void cleanup()
130
+ {
131
+ if (instance) {
132
+ if (config.shouldDump()) {
133
+ lcb_dump(instance, stderr, LCB_DUMP_ALL);
134
+ }
135
+ if (config.useTimings()) {
136
+ hg.write();
137
+ }
138
+ if (instance) {
139
+ lcb_destroy(instance);
140
+ }
141
+ }
142
+ if (listener) {
143
+ evconnlistener_free(listener);
144
+ }
145
+ if (evbase) {
146
+ event_base_free(evbase);
147
+ }
148
+ }
149
+
150
+ struct client {
151
+ int fd;
152
+ struct bufferevent *bev;
153
+ char host[NI_MAXHOST + 1];
154
+ char port[NI_MAXSERV + 1];
155
+ };
156
+
157
+ static void dump_bytes(const struct client *cl, const char *msg, void *ptr, size_t len)
158
+ {
159
+ if (!config.isTrace()) {
160
+ return;
161
+ }
162
+
163
+ int width = 16;
164
+ const unsigned char *buf = (const unsigned char *)ptr;
165
+ size_t full_rows = len / width;
166
+ size_t remainder = len % width;
167
+ std::stringstream ss;
168
+
169
+ ss << msg << ", " << len << " bytes\n"
170
+ " +-------------------------------------------------+\n"
171
+ " | 0 1 2 3 4 5 6 7 8 9 a b c d e f |\n"
172
+ " +--------+-------------------------------------------------+----------------+";
173
+
174
+ unsigned int row = 0;
175
+ while (row < full_rows) {
176
+ int row_start_index = row * width;
177
+ // prefix
178
+ ss << "\n |" << std::setw(8) << std::setfill('0') << std::hex << row_start_index << "|";
179
+ int row_end_index = row_start_index + width;
180
+ // hex
181
+ int i = row_start_index;
182
+ while (i < row_end_index) {
183
+ ss << " " << std::setw(2) << std::setfill('0') << std::hex << (unsigned int)buf[i++];
184
+ }
185
+ ss << " |";
186
+ // ascii
187
+ i = row_start_index;
188
+ while (i < row_end_index) {
189
+ char b = buf[i++];
190
+ if ((b <= 0x1f) || (b >= 0x7f)) {
191
+ ss << '.';
192
+ } else {
193
+ ss << b;
194
+ }
195
+ }
196
+ ss << "|";
197
+ row++;
198
+ }
199
+ if (remainder != 0) {
200
+ int row_start_index = full_rows * width;
201
+ // prefix
202
+ ss << "\n |" << std::setw(8) << std::setfill('0') << std::hex << row_start_index << "|";
203
+ int row_end_index = row_start_index + remainder;
204
+ // hex
205
+ int i = row_start_index;
206
+ while (i < row_end_index) {
207
+ ss << " " << std::setw(2) << std::setfill('0') << std::hex << (unsigned int)buf[i++];
208
+ }
209
+ i = width - remainder;
210
+ while (i > 0) {
211
+ ss << " ";
212
+ i--;
213
+ }
214
+ ss << " |";
215
+ // ascii
216
+ i = row_start_index;
217
+ while (i < row_end_index) {
218
+ char b = buf[i++];
219
+ if ((b <= 0x1f) || (b >= 0x7f)) {
220
+ ss << '.';
221
+ } else {
222
+ ss << b;
223
+ }
224
+ }
225
+ i = width - remainder;
226
+ while (i > 0) {
227
+ ss << " ";
228
+ i--;
229
+ }
230
+ ss << "|";
231
+ }
232
+ ss << "\n +--------+-------------------------------------------------+----------------+";
233
+ lcb_log(LOGARGS(INFO), CL_LOGFMT "%s", CL_LOGID(cl), ss.str().c_str());
234
+ }
235
+
236
+ static void pktfwd_callback(lcb_t, const void *cookie, lcb_error_t err, lcb_PKTFWDRESP *resp)
237
+ {
238
+ good_or_die(err, "Failed to forward a packet");
239
+
240
+ struct client *cl = (struct client *)cookie;
241
+ struct evbuffer *output = bufferevent_get_output(cl->bev);
242
+ for (unsigned ii = 0; ii < resp->nitems; ii++) {
243
+ dump_bytes(cl, "response", resp->iovs[ii].iov_base, resp->iovs[ii].iov_len);
244
+ evbuffer_expand(output, resp->iovs[ii].iov_len);
245
+ evbuffer_add(output, resp->iovs[ii].iov_base, resp->iovs[ii].iov_len);
246
+ }
247
+ }
248
+
249
+ static void conn_readcb(struct bufferevent *bev, void *cookie)
250
+ {
251
+ struct client *cl = (struct client *)cookie;
252
+ struct evbuffer *input;
253
+ size_t len;
254
+
255
+ input = bufferevent_get_input(bev);
256
+ len = evbuffer_get_length(input);
257
+ if (len < 24) {
258
+ lcb_log(LOGARGS(DEBUG), CL_LOGFMT "not enough data for header", CL_LOGID(cl));
259
+ return;
260
+ }
261
+
262
+ protocol_binary_request_header header;
263
+ evbuffer_copyout(input, &header, sizeof(header));
264
+ lcb_U32 bodylen = ntohl(header.request.bodylen);
265
+
266
+ size_t pktlen = sizeof(header) + bodylen;
267
+ len = evbuffer_get_length(input);
268
+ if (len < pktlen) {
269
+ lcb_log(LOGARGS(DEBUG), CL_LOGFMT "not enough data for packet", CL_LOGID(cl));
270
+ return;
271
+ }
272
+ void *pkt = malloc(pktlen);
273
+ evbuffer_remove(input, pkt, pktlen);
274
+
275
+ lcb_sched_enter(instance);
276
+ lcb_CMDPKTFWD cmd = {0};
277
+ cmd.vb.vtype = LCB_KV_COPY;
278
+ cmd.vb.u_buf.contig.bytes = pkt;
279
+ cmd.vb.u_buf.contig.nbytes = pktlen;
280
+ dump_bytes(cl, "request", pkt, pktlen);
281
+ good_or_die(lcb_pktfwd3(instance, cl, &cmd), "Failed to forward packet");
282
+ lcb_sched_leave(instance);
283
+ }
284
+
285
+ static void conn_eventcb(struct bufferevent *bev, short events, void *cookie)
286
+ {
287
+ struct client *cl = (struct client *)cookie;
288
+
289
+ if (events & BEV_EVENT_EOF) {
290
+ lcb_log(LOGARGS(INFO), CL_LOGFMT "connection closed", CL_LOGID(cl));
291
+ bufferevent_free(bev);
292
+ delete cl;
293
+ } else if (events & BEV_EVENT_ERROR) {
294
+ lcb_log(LOGARGS(ERROR), CL_LOGFMT "got an error on the connection: %s\n", CL_LOGID(cl), strerror(errno));
295
+ bufferevent_free(bev);
296
+ delete cl;
297
+ } else {
298
+ lcb_log(LOGARGS(DEBUG), CL_LOGFMT "ignore event 0x%02x", CL_LOGID(cl), events);
299
+ }
300
+ }
301
+
302
+ static void listener_cb(struct evconnlistener *, evutil_socket_t fd, struct sockaddr *addr, int naddr, void *)
303
+ {
304
+ struct bufferevent *bev;
305
+ bev = bufferevent_socket_new(evbase, fd, BEV_OPT_CLOSE_ON_FREE);
306
+
307
+ if (!bev) {
308
+ die("Error constructing bufferevent");
309
+ }
310
+
311
+ struct client *cl = new client();
312
+ cl->fd = fd;
313
+ cl->bev = bev;
314
+ getnameinfo(addr, naddr, cl->host, sizeof(cl->host), cl->port, sizeof(cl->port), NI_NUMERICHOST | NI_NUMERICSERV);
315
+ bufferevent_setcb(bev, conn_readcb, NULL, conn_eventcb, cl);
316
+ bufferevent_enable(bev, EV_READ | EV_WRITE);
317
+ lcb_log(LOGARGS(INFO), CL_LOGFMT "new client connection", CL_LOGID(cl));
318
+ }
319
+
320
+ static void setup_listener()
321
+ {
322
+ struct sockaddr_in sin;
323
+
324
+ memset(&sin, 0, sizeof(sin));
325
+ sin.sin_family = AF_INET;
326
+ sin.sin_port = htons(config.port());
327
+
328
+ listener = evconnlistener_new_bind(evbase, listener_cb, NULL, LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, -1,
329
+ (struct sockaddr *)&sin, sizeof(sin));
330
+ if (!listener) {
331
+ die("Failed to create proxy listener");
332
+ }
333
+ lcb_log(LOGARGS(INFO), "Listening incoming proxy connections on port %d", config.port());
334
+ }
335
+
336
+ static void bootstrap_callback(lcb_t, lcb_error_t err)
337
+ {
338
+ good_or_die(err, "Failed to bootstrap");
339
+ lcb_log(LOGARGS(INFO), "connected to Couchbase Server");
340
+ setup_listener();
341
+ }
342
+
343
+ static int terminating = 0;
344
+ static void sigint_handler(int)
345
+ {
346
+ lcb_log(LOGARGS(INFO), "terminating the server");
347
+ if (!terminating) {
348
+ event_base_loopbreak(evbase);
349
+ terminating = 1;
350
+ }
351
+ }
352
+
353
+ static void real_main(int argc, char **argv)
354
+ {
355
+ Parser parser;
356
+
357
+ config.addToParser(parser);
358
+ parser.parse(argc, argv);
359
+ config.processOptions();
360
+
361
+ lcb_create_st cropts;
362
+ memset(&cropts, 0, sizeof cropts);
363
+ config.fillCropts(cropts);
364
+
365
+ /* bind to external libevent loop */
366
+ evbase = event_base_new();
367
+ struct lcb_create_io_ops_st ciops;
368
+ memset(&ciops, 0, sizeof(ciops));
369
+ ciops.v.v0.type = LCB_IO_OPS_LIBEVENT;
370
+ ciops.v.v0.cookie = evbase;
371
+ good_or_die(lcb_create_io_ops(&cropts.v.v3.io, &ciops), "Failed to create and IO ops strucutre for libevent");
372
+
373
+ good_or_die(lcb_create(&instance, &cropts), "Failed to create connection");
374
+ config.doCtls();
375
+ lcb_set_bootstrap_callback(instance, bootstrap_callback);
376
+ lcb_set_pktfwd_callback(instance, pktfwd_callback);
377
+
378
+ good_or_die(lcb_connect(instance), "Failed to connect to cluster");
379
+ if (config.useTimings()) {
380
+ hg.install(instance, stdout);
381
+ }
382
+ std::atexit(cleanup);
383
+
384
+ /* setup CTRL-C handler */
385
+ struct sigaction action;
386
+ sigemptyset(&action.sa_mask);
387
+ action.sa_handler = sigint_handler;
388
+ action.sa_flags = 0;
389
+ sigaction(SIGINT, &action, NULL);
390
+
391
+ event_base_dispatch(evbase);
392
+ }
393
+
394
+ int main(int argc, char **argv)
395
+ {
396
+ try {
397
+ real_main(argc, argv);
398
+ return 0;
399
+ } catch (std::exception &exc) {
400
+ std::cerr << exc.what() << std::endl;
401
+ exit(EXIT_FAILURE);
402
+ }
403
+ }