couchbase 3.2.4 → 3.2.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. package/binding.gyp +5 -0
  2. package/deps/lcb/CMakeLists.txt +28 -6
  3. package/deps/lcb/README.markdown +5 -9
  4. package/deps/lcb/RELEASE_NOTES.markdown +80 -5
  5. package/deps/lcb/cmake/Modules/GetVersionInfo.cmake +1 -1
  6. package/deps/lcb/doc/Doxyfile +1 -1
  7. package/deps/lcb/doc/cbc.markdown +10 -0
  8. package/deps/lcb/gyp_config/common/libcouchbase/configuration.h +3 -3
  9. package/deps/lcb/include/libcouchbase/couchbase.h +52 -0
  10. package/deps/lcb/include/libcouchbase/error.h +5 -2
  11. package/deps/lcb/libcouchbase.gyp +7 -1
  12. package/deps/lcb/packaging/rpm/libcouchbase.spec.in +1 -1
  13. package/deps/lcb/plugins/io/iocp/iocp_iops.h +1 -1
  14. package/deps/lcb/plugins/io/iocp/iocp_loop.c +3 -3
  15. package/deps/lcb/plugins/io/iocp/iocp_util.c +2 -2
  16. package/deps/lcb/src/bucketconfig/bc_file.cc +29 -15
  17. package/deps/lcb/src/capi/cmd_counter.hh +12 -0
  18. package/deps/lcb/src/capi/cmd_exists.hh +12 -0
  19. package/deps/lcb/src/capi/cmd_get.hh +12 -0
  20. package/deps/lcb/src/capi/cmd_get_replica.hh +14 -1
  21. package/deps/lcb/src/capi/cmd_query.cc +13 -0
  22. package/deps/lcb/src/capi/cmd_query.hh +22 -14
  23. package/deps/lcb/src/capi/cmd_remove.hh +12 -0
  24. package/deps/lcb/src/capi/cmd_store.hh +12 -0
  25. package/deps/lcb/src/capi/cmd_subdoc.hh +12 -0
  26. package/deps/lcb/src/capi/cmd_touch.hh +12 -0
  27. package/deps/lcb/src/capi/cmd_unlock.hh +12 -0
  28. package/deps/lcb/src/capi/collection_qualifier.hh +4 -9
  29. package/deps/lcb/src/instance.cc +19 -0
  30. package/deps/lcb/src/internal.h +2 -1
  31. package/deps/lcb/src/mcserver/negotiate.cc +3 -0
  32. package/deps/lcb/src/n1ql/n1ql.cc +5 -1
  33. package/deps/lcb/src/n1ql/query_handle.cc +55 -30
  34. package/deps/lcb/src/n1ql/query_handle.hh +14 -2
  35. package/deps/lcb/src/operations/counter.cc +12 -0
  36. package/deps/lcb/src/operations/exists.cc +12 -0
  37. package/deps/lcb/src/operations/get.cc +12 -0
  38. package/deps/lcb/src/operations/get_replica.cc +18 -6
  39. package/deps/lcb/src/operations/ping.cc +2 -2
  40. package/deps/lcb/src/operations/remove.cc +12 -0
  41. package/deps/lcb/src/operations/store.cc +12 -0
  42. package/deps/lcb/src/operations/subdoc.cc +12 -0
  43. package/deps/lcb/src/operations/touch.cc +12 -0
  44. package/deps/lcb/src/operations/unlock.cc +12 -0
  45. package/deps/lcb/src/search/search_handle.cc +1 -2
  46. package/deps/lcb/src/settings.cc +1 -0
  47. package/deps/lcb/src/ssl/ssl_common.c +111 -22
  48. package/deps/lcb/src/utilities.cc +21 -0
  49. package/deps/lcb/src/utilities.h +3 -0
  50. package/deps/lcb/src/vbucket/vbucket.c +16 -7
  51. package/deps/lcb/tests/CMakeLists.txt +1 -1
  52. package/deps/lcb/tests/iotests/mock-environment.cc +13 -1
  53. package/deps/lcb/tests/iotests/mock-environment.h +3 -1
  54. package/deps/lcb/tests/iotests/serverparams.h +7 -2
  55. package/deps/lcb/tests/iotests/t_ratelimit.cc +739 -0
  56. package/deps/lcb/tests/iotests/testutil.cc +174 -0
  57. package/deps/lcb/tests/iotests/testutil.h +53 -0
  58. package/deps/lcb/tools/CMakeLists.txt +1 -1
  59. package/deps/lcb/tools/cbc-handlers.h +39 -0
  60. package/deps/lcb/tools/cbc-n1qlback.cc +1 -0
  61. package/deps/lcb/tools/cbc-pillowfight.cc +45 -35
  62. package/deps/lcb/tools/cbc.cc +31 -0
  63. package/deps/lcb/tools/docgen/docgen.h +11 -10
  64. package/dist/analyticsexecutor.js +2 -2
  65. package/dist/analyticsindexmanager.js +3 -3
  66. package/dist/binarycollection.d.ts +17 -0
  67. package/dist/binding.js +1 -1
  68. package/dist/bindingutilities.js +5 -1
  69. package/dist/bucketmanager.d.ts +1 -22
  70. package/dist/bucketmanager.js +5 -5
  71. package/dist/cluster.js +1 -1
  72. package/dist/collection.js +6 -6
  73. package/dist/collectionmanager.js +2 -2
  74. package/dist/connection.js +3 -3
  75. package/dist/connspec.js +5 -1
  76. package/dist/couchbase.js +5 -1
  77. package/dist/httpexecutor.d.ts +1 -0
  78. package/dist/httpexecutor.js +5 -1
  79. package/dist/logging.js +1 -1
  80. package/dist/queryexecutor.js +3 -3
  81. package/dist/searchindexmanager.js +1 -1
  82. package/dist/usermanager.js +2 -2
  83. package/dist/utilities.d.ts +1 -2
  84. package/dist/utilities.js +9 -2
  85. package/dist/viewexecutor.js +1 -1
  86. package/package.json +1 -1
  87. package/src/uv-plugin-all.cpp +1 -0
  88. package/dist/cas.d.ts +0 -0
  89. package/dist/cas.js +0 -1
@@ -0,0 +1,739 @@
1
+ /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
+ /*
3
+ * Copyright 2021 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
+ #include "iotests.h"
19
+ #include <libcouchbase/couchbase.h>
20
+ #include "internal.h"
21
+ #include <chrono>
22
+
23
+ class RateLimitTest : public MockUnitTest
24
+ {
25
+ public:
26
+ lcb_STATUS retry_connect_on_auth_failure(HandleWrap &hw, lcb_INSTANCE **instance, const std::string &username,
27
+ const std::string &password,
28
+ std::chrono::seconds timeout = std::chrono::seconds(10))
29
+ {
30
+ lcb_CREATEOPTS *options = nullptr;
31
+ MockEnvironment::getInstance()->makeConnectParams(options);
32
+ lcb_createopts_credentials(options, username.c_str(), username.size(), password.c_str(), password.size());
33
+
34
+ auto start = std::chrono::high_resolution_clock::now();
35
+ lcb_STATUS err;
36
+
37
+ while (std::chrono::high_resolution_clock::now() < start + timeout) {
38
+ err = tryCreateConnection(hw, instance, options);
39
+ if (err != LCB_ERR_AUTHENTICATION_FAILURE) {
40
+ break;
41
+ }
42
+ hw.destroy();
43
+ }
44
+
45
+ return err;
46
+ }
47
+ };
48
+
49
+ extern "C" {
50
+ static void store_callback(lcb_INSTANCE *, int cbtype, const lcb_RESPSTORE *resp)
51
+ {
52
+ lcb_STATUS *err;
53
+ lcb_respstore_cookie(resp, (void **)&err);
54
+ *err = lcb_respstore_status(resp);
55
+ }
56
+
57
+ static void query_callback(lcb_INSTANCE *, int, const lcb_RESPQUERY *resp)
58
+ {
59
+ lcb_STATUS *err;
60
+ lcb_respquery_cookie(resp, (void **)&err);
61
+ *err = lcb_respquery_status(resp);
62
+ }
63
+
64
+ static void concurrent_query_callback(lcb_INSTANCE *, int, const lcb_RESPQUERY *resp)
65
+ {
66
+ std::vector<lcb_STATUS> *errors;
67
+ lcb_respquery_cookie(resp, (void **)&errors);
68
+ auto err = lcb_respquery_status(resp);
69
+ errors->emplace_back(err);
70
+ }
71
+
72
+ static void search_callback(lcb_INSTANCE *, int, const lcb_RESPSEARCH *resp)
73
+ {
74
+ lcb_STATUS *err;
75
+ lcb_respsearch_cookie(resp, (void **)&err);
76
+ *err = lcb_respsearch_status(resp);
77
+ }
78
+
79
+ static void concurrent_search_callback(lcb_INSTANCE *, int, const lcb_RESPSEARCH *resp)
80
+ {
81
+ std::vector<lcb_STATUS> *errors;
82
+ lcb_respsearch_cookie(resp, (void **)&errors);
83
+ auto err = lcb_respsearch_status(resp);
84
+ errors->emplace_back(err);
85
+ }
86
+ }
87
+
88
+ TEST_F(RateLimitTest, testRateLimitsKVNumOps)
89
+ {
90
+ SKIP_IF_MOCK()
91
+ SKIP_IF_CLUSTER_VERSION_IS_LOWER_THAN(MockEnvironment::VERSION_71)
92
+ SKIP_IF_CLUSTER_VERSION_IS_HIGHER_THAN(MockEnvironment::VERSION_72)
93
+ HandleWrap hw;
94
+ lcb_INSTANCE *instance;
95
+ createConnection(hw, &instance);
96
+
97
+ enforce_rate_limits(instance);
98
+ rate_limits limits{};
99
+ limits.kv_limits.enforce = true;
100
+ limits.kv_limits.num_ops_per_min = 10;
101
+
102
+ auto username = unique_name("rate_limited_user");
103
+
104
+ create_rate_limited_user(instance, username, limits);
105
+
106
+ hw.destroy();
107
+
108
+ ASSERT_STATUS_EQ(LCB_SUCCESS, retry_connect_on_auth_failure(hw, &instance, username, "password"));
109
+
110
+ bool found_error = false;
111
+ auto start = std::chrono::high_resolution_clock::now();
112
+
113
+ while (std::chrono::high_resolution_clock::now() < start + std::chrono::seconds(10)) {
114
+ std::string key{"ratelimit"};
115
+ std::string value{"test"};
116
+ Item req = Item(key, value);
117
+ KVOperation kvo = KVOperation(&req);
118
+ kvo.ignoreErrors = true;
119
+ kvo.store(instance);
120
+ if (kvo.result.err == LCB_ERR_RATE_LIMITED) {
121
+ found_error = true;
122
+ break;
123
+ }
124
+ }
125
+
126
+ ASSERT_TRUE(found_error);
127
+
128
+ drop_user(instance, username);
129
+ }
130
+
131
+ TEST_F(RateLimitTest, testRateLimitsKVIngress)
132
+ {
133
+ SKIP_IF_MOCK()
134
+ SKIP_IF_CLUSTER_VERSION_IS_LOWER_THAN(MockEnvironment::VERSION_71)
135
+ SKIP_IF_CLUSTER_VERSION_IS_HIGHER_THAN(MockEnvironment::VERSION_72)
136
+ HandleWrap hw;
137
+ lcb_INSTANCE *instance;
138
+ createConnection(hw, &instance);
139
+
140
+ enforce_rate_limits(instance);
141
+ rate_limits limits{};
142
+ limits.kv_limits.enforce = true;
143
+ limits.kv_limits.ingress_mib_per_min = 1;
144
+ auto username = unique_name("rate_limited_user");
145
+ create_rate_limited_user(instance, username, limits);
146
+
147
+ hw.destroy();
148
+
149
+ ASSERT_STATUS_EQ(LCB_SUCCESS, retry_connect_on_auth_failure(hw, &instance, username, "password"));
150
+
151
+ std::string key{"ratelimitingress"};
152
+ std::string value(1025 * 1024, '*');
153
+
154
+ Item req = Item(key, value);
155
+ KVOperation kvo = KVOperation(&req);
156
+ kvo.ignoreErrors = true;
157
+ kvo.store(instance);
158
+ ASSERT_STATUS_EQ(kvo.result.err, LCB_ERR_RATE_LIMITED);
159
+
160
+ drop_user(instance, username);
161
+ }
162
+
163
+ TEST_F(RateLimitTest, testRateLimitsKVEgress)
164
+ {
165
+ SKIP_IF_MOCK()
166
+ SKIP_IF_CLUSTER_VERSION_IS_LOWER_THAN(MockEnvironment::VERSION_71)
167
+ SKIP_IF_CLUSTER_VERSION_IS_HIGHER_THAN(MockEnvironment::VERSION_72)
168
+ HandleWrap hw;
169
+ lcb_INSTANCE *instance;
170
+ createConnection(hw, &instance);
171
+
172
+ enforce_rate_limits(instance);
173
+ rate_limits limits{};
174
+ limits.kv_limits.enforce = true;
175
+ limits.kv_limits.egress_mib_per_min = 1;
176
+ auto username = unique_name("rate_limited_user");
177
+ create_rate_limited_user(instance, username, limits);
178
+
179
+ hw.destroy();
180
+
181
+ ASSERT_STATUS_EQ(LCB_SUCCESS, retry_connect_on_auth_failure(hw, &instance, username, "password"));
182
+
183
+ std::string key{"ratelimitegress"};
184
+ std::string value(512 * 1024, '*');
185
+
186
+ storeKey(instance, key, value);
187
+ Item item;
188
+ getKey(instance, key, item);
189
+ getKey(instance, key, item);
190
+
191
+ Item req = Item(key);
192
+ KVOperation kvo = KVOperation(&req);
193
+ kvo.ignoreErrors = true;
194
+ kvo.get(instance);
195
+ ASSERT_STATUS_EQ(kvo.result.err, LCB_ERR_RATE_LIMITED);
196
+
197
+ drop_user(instance, username);
198
+ }
199
+
200
+ TEST_F(RateLimitTest, testRateLimitsKVMaxConnections)
201
+ {
202
+ SKIP_IF_MOCK()
203
+ SKIP_IF_CLUSTER_VERSION_IS_LOWER_THAN(MockEnvironment::VERSION_71)
204
+ SKIP_IF_CLUSTER_VERSION_IS_HIGHER_THAN(MockEnvironment::VERSION_72)
205
+ HandleWrap hw;
206
+ lcb_INSTANCE *instance;
207
+ createConnection(hw, &instance);
208
+
209
+ enforce_rate_limits(instance);
210
+ rate_limits limits{};
211
+ limits.kv_limits.enforce = true;
212
+ limits.kv_limits.num_connections = 1;
213
+ auto username = unique_name("rate_limited_user");
214
+ std::string password{"password"};
215
+ create_rate_limited_user(instance, username, limits);
216
+
217
+ hw.destroy();
218
+
219
+ ASSERT_STATUS_EQ(LCB_SUCCESS, retry_connect_on_auth_failure(hw, &instance, username, "password"));
220
+
221
+ // max connections is per node so if we have multiple nodes, we need to force a connection to all of them to trigger
222
+ // an error
223
+
224
+ lcb_CMDPING *cmd;
225
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdping_create(&cmd));
226
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdping_all(cmd));
227
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_ping(instance, nullptr, cmd));
228
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_wait(instance, LCB_WAIT_DEFAULT));
229
+
230
+ lcb_INSTANCE *instance2;
231
+ HandleWrap hw2;
232
+
233
+ lcb_CREATEOPTS *options = nullptr;
234
+ MockEnvironment::getInstance()->makeConnectParams(options);
235
+ lcb_createopts_credentials(options, username.c_str(), username.size(), password.c_str(), password.size());
236
+
237
+ lcb_CREATEOPTS *bucketless_options = nullptr;
238
+ MockEnvironment::getInstance()->makeConnectParams(bucketless_options, nullptr, LCB_TYPE_CLUSTER);
239
+ lcb_createopts_credentials(bucketless_options, username.c_str(), username.size(), password.c_str(), password.size());
240
+
241
+ ASSERT_STATUS_EQ(LCB_ERR_RATE_LIMITED, tryCreateConnection(hw2, &instance2, options));
242
+ ASSERT_STATUS_EQ(LCB_ERR_RATE_LIMITED, tryCreateConnection(hw2, &instance2, bucketless_options));
243
+ }
244
+
245
+ TEST_F(RateLimitTest, testRateLimitsQueryNumQueries)
246
+ {
247
+ SKIP_IF_MOCK()
248
+ SKIP_IF_CLUSTER_VERSION_IS_LOWER_THAN(MockEnvironment::VERSION_71)
249
+ HandleWrap hw;
250
+ lcb_INSTANCE *instance;
251
+ createConnection(hw, &instance);
252
+
253
+ enforce_rate_limits(instance);
254
+ rate_limits limits{};
255
+ limits.query_limits.enforce = true;
256
+ limits.query_limits.num_queries_per_min = 1;
257
+ auto username = unique_name("rate_limited_user");
258
+ std::string password{"password"};
259
+ create_rate_limited_user(instance, username, limits);
260
+
261
+ hw.destroy();
262
+
263
+ ASSERT_STATUS_EQ(LCB_SUCCESS, retry_connect_on_auth_failure(hw, &instance, username, "password"));
264
+
265
+ bool found_error = false;
266
+ auto start = std::chrono::high_resolution_clock::now();
267
+
268
+ while (std::chrono::high_resolution_clock::now() < start + std::chrono::seconds(10)) {
269
+ lcb_STATUS err;
270
+ lcb_CMDQUERY *cmd{};
271
+ std::string statement{"select * from " + MockEnvironment::getInstance()->getBucket() + " limit 1"};
272
+
273
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_create(&cmd));
274
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_statement(cmd, statement.c_str(), statement.size()));
275
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_callback(cmd, query_callback));
276
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_query(instance, &err, cmd));
277
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_destroy(cmd));
278
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_wait(instance, LCB_WAIT_DEFAULT));
279
+ if (err == LCB_ERR_RATE_LIMITED) {
280
+ found_error = true;
281
+ break;
282
+ }
283
+ }
284
+
285
+ ASSERT_TRUE(found_error);
286
+
287
+ drop_user(instance, username);
288
+ }
289
+
290
+ TEST_F(RateLimitTest, testRateLimitsQueryEgress)
291
+ {
292
+ SKIP_IF_MOCK()
293
+ SKIP_IF_CLUSTER_VERSION_IS_LOWER_THAN(MockEnvironment::VERSION_71)
294
+ HandleWrap hw;
295
+ lcb_INSTANCE *instance;
296
+ createConnection(hw, &instance);
297
+
298
+ enforce_rate_limits(instance);
299
+ rate_limits limits{};
300
+ limits.query_limits.enforce = true;
301
+ limits.query_limits.egress_mib_per_min = 1;
302
+ auto username = unique_name("rate_limited_user");
303
+ std::string password{"password"};
304
+ create_rate_limited_user(instance, username, limits);
305
+
306
+ hw.destroy();
307
+
308
+ ASSERT_STATUS_EQ(LCB_SUCCESS, retry_connect_on_auth_failure(hw, &instance, username, "password"));
309
+
310
+ std::string key{"ratelimitingress"};
311
+ // query only returns json
312
+ std::string value_inner(1024 * 1024, '1');
313
+ std::string value{"[" + value_inner + "]"};
314
+
315
+ storeKey(instance, key, value);
316
+
317
+ bool found_error = false;
318
+ auto start = std::chrono::high_resolution_clock::now();
319
+
320
+ while (std::chrono::high_resolution_clock::now() < start + std::chrono::seconds(10)) {
321
+ lcb_STATUS err;
322
+ lcb_CMDQUERY *cmd{};
323
+ std::string statement{"select * from " + MockEnvironment::getInstance()->getBucket() + " where META().id = '" + key + "'"};
324
+
325
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_create(&cmd));
326
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_statement(cmd, statement.c_str(), statement.size()));
327
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_callback(cmd, query_callback));
328
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_query(instance, &err, cmd));
329
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_destroy(cmd));
330
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_wait(instance, LCB_WAIT_DEFAULT));
331
+ if (err == LCB_ERR_RATE_LIMITED) {
332
+ found_error = true;
333
+ break;
334
+ }
335
+ }
336
+
337
+ ASSERT_TRUE(found_error);
338
+
339
+ drop_user(instance, username);
340
+ }
341
+
342
+ TEST_F(RateLimitTest, testRateLimitsQueryIngress)
343
+ {
344
+ SKIP_IF_MOCK()
345
+ SKIP_IF_CLUSTER_VERSION_IS_LOWER_THAN(MockEnvironment::VERSION_71)
346
+ HandleWrap hw;
347
+ lcb_INSTANCE *instance;
348
+ createConnection(hw, &instance);
349
+
350
+ enforce_rate_limits(instance);
351
+ rate_limits limits{};
352
+ limits.query_limits.enforce = true;
353
+ limits.query_limits.ingress_mib_per_min = 1;
354
+ auto username = unique_name("rate_limited_user");
355
+ std::string password{"password"};
356
+ create_rate_limited_user(instance, username, limits);
357
+
358
+ hw.destroy();
359
+
360
+ ASSERT_STATUS_EQ(LCB_SUCCESS, retry_connect_on_auth_failure(hw, &instance, username, "password"));
361
+
362
+ std::string key{"ratelimitingress"};
363
+ // query only accepts json
364
+ std::string value_inner(1024 * 1024, '1');
365
+ std::string value{"[" + value_inner + "]"};
366
+
367
+ bool found_error = false;
368
+ auto start = std::chrono::high_resolution_clock::now();
369
+
370
+ while (std::chrono::high_resolution_clock::now() < start + std::chrono::seconds(10)) {
371
+ lcb_STATUS err;
372
+ lcb_CMDQUERY *cmd{};
373
+ std::string statement{"upsert into " + MockEnvironment::getInstance()->getBucket() + " (KEY, VALUE) VALUES (\"" + key + "\", \"" + value + "\")"};
374
+
375
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_create(&cmd));
376
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_statement(cmd, statement.c_str(), statement.size()));
377
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_callback(cmd, query_callback));
378
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_query(instance, &err, cmd));
379
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_destroy(cmd));
380
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_wait(instance, LCB_WAIT_DEFAULT));
381
+ if (err == LCB_ERR_RATE_LIMITED) {
382
+ found_error = true;
383
+ break;
384
+ }
385
+ }
386
+
387
+ ASSERT_TRUE(found_error);
388
+
389
+ drop_user(instance, username);
390
+ }
391
+
392
+ TEST_F(RateLimitTest, testRateLimitsQueryConcurrentRequests)
393
+ {
394
+ SKIP_IF_MOCK()
395
+ SKIP_IF_CLUSTER_VERSION_IS_LOWER_THAN(MockEnvironment::VERSION_71)
396
+ HandleWrap hw;
397
+ lcb_INSTANCE *instance;
398
+ createConnection(hw, &instance);
399
+
400
+ enforce_rate_limits(instance);
401
+ rate_limits limits{};
402
+ limits.query_limits.enforce = true;
403
+ limits.query_limits.num_concurrent_requests = 1;
404
+ auto username = unique_name("rate_limited_user");
405
+ std::string password{"password"};
406
+ create_rate_limited_user(instance, username, limits);
407
+
408
+ hw.destroy();
409
+
410
+ ASSERT_STATUS_EQ(LCB_SUCCESS, retry_connect_on_auth_failure(hw, &instance, username, "password"));
411
+
412
+ std::string key{"ratelimitingress"};
413
+ // query only accepts json
414
+ std::string value_inner(1024 * 1024, '1');
415
+ std::string value{"[" + value_inner + "]"};
416
+
417
+ std::vector<lcb_STATUS> errors{};
418
+ errors.reserve(10);
419
+
420
+ lcb_sched_enter(instance);
421
+ for (int ii = 0; ii < 10; ++ii) {
422
+ lcb_CMDQUERY *cmd{};
423
+ std::string statement{"select * from " + MockEnvironment::getInstance()->getBucket() + " limit 1"};
424
+
425
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_create(&cmd));
426
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_statement(cmd, statement.c_str(), statement.size()));
427
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_callback(cmd, concurrent_query_callback));
428
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_query(instance, &errors, cmd));
429
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_destroy(cmd));
430
+ }
431
+ lcb_sched_leave(instance);
432
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_wait(instance, LCB_WAIT_DEFAULT));
433
+
434
+ bool found_error = std::count_if(errors.begin(), errors.end(), [](lcb_STATUS err) { return err == LCB_ERR_RATE_LIMITED; }) > 0;
435
+
436
+ ASSERT_TRUE(found_error);
437
+
438
+ drop_user(instance, username);
439
+ }
440
+
441
+ TEST_F(RateLimitTest, testRateLimitsKVScopeDataSize)
442
+ {
443
+ SKIP_IF_MOCK()
444
+ SKIP_IF_CLUSTER_VERSION_IS_LOWER_THAN(MockEnvironment::VERSION_71)
445
+ SKIP_IF_CLUSTER_VERSION_IS_HIGHER_THAN(MockEnvironment::VERSION_72)
446
+ HandleWrap hw;
447
+ lcb_INSTANCE *instance;
448
+ createConnection(hw, &instance);
449
+ auto bucket = MockEnvironment::getInstance()->getBucket();
450
+ auto scope = unique_name("scope");
451
+ auto collection = unique_name("collection");
452
+
453
+ enforce_rate_limits(instance);
454
+ scope_rate_limits limits{};
455
+ limits.kv_scope_limits.enforce = true;
456
+ // each vbucket gets a separate limit of 1024 bytes
457
+ // so this limits the size of value you can store to 1024 bytes
458
+ limits.kv_scope_limits.data_size = 1024 * 1024;
459
+ create_rate_limited_scope(instance, bucket, scope, limits);
460
+ create_collection(instance, scope, collection);
461
+
462
+ auto key = unique_name("ratelimitdata");
463
+
464
+ (void)lcb_install_callback(instance, LCB_CALLBACK_STORE, (lcb_RESPCALLBACK)store_callback);
465
+
466
+ std::string value(1025, '*');
467
+ lcb_CMDSTORE *cmd;
468
+ lcb_STATUS err;
469
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdstore_create(&cmd, LCB_STORE_UPSERT));
470
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdstore_collection(cmd, scope.c_str(), scope.size(), collection.c_str(),
471
+ collection.size()));
472
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdstore_key(cmd, key.c_str(), key.size()));
473
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdstore_value(cmd, value.c_str(), value.size()));
474
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdstore_timeout(cmd, LCB_S2US(10)));
475
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_store(instance, &err, cmd));
476
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_wait(instance, LCB_WAIT_DEFAULT));
477
+ ASSERT_STATUS_EQ(LCB_ERR_QUOTA_LIMITED, err);
478
+
479
+ drop_scope(instance, scope);
480
+ }
481
+
482
+ TEST_F(RateLimitTest, testRateLimitsQueryNumIndexes)
483
+ {
484
+ SKIP_IF_MOCK()
485
+ SKIP_IF_CLUSTER_VERSION_IS_LOWER_THAN(MockEnvironment::VERSION_71)
486
+ SKIP_IF_CLUSTER_VERSION_IS_HIGHER_THAN(MockEnvironment::VERSION_72)
487
+ HandleWrap hw;
488
+ lcb_INSTANCE *instance;
489
+ createConnection(hw, &instance);
490
+ auto bucket = MockEnvironment::getInstance()->getBucket();
491
+ auto scope = unique_name("scope");
492
+ auto collection = unique_name("collection");
493
+
494
+ enforce_rate_limits(instance);
495
+ scope_rate_limits limits{};
496
+ limits.index_scope_limits.enforce = true;
497
+ limits.index_scope_limits.num_indexes = 1;
498
+ create_rate_limited_scope(instance, bucket, scope, limits);
499
+ create_collection(instance, scope, collection);
500
+
501
+ {
502
+ lcb_STATUS err;
503
+ lcb_CMDQUERY *cmd{};
504
+ std::string statement{"CREATE PRIMARY INDEX ON `" + collection + "`"};
505
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_create(&cmd));
506
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_statement(cmd, statement.c_str(), statement.size()));
507
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_callback(cmd, query_callback));
508
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_scope_name(cmd, scope.c_str(), scope.size()));
509
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_query(instance, &err, cmd));
510
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_destroy(cmd));
511
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_wait(instance, LCB_WAIT_DEFAULT));
512
+ ASSERT_STATUS_EQ(LCB_SUCCESS, err);
513
+ }
514
+
515
+ {
516
+ lcb_STATUS err;
517
+ lcb_CMDQUERY *cmd{};
518
+ std::string statement{"CREATE INDEX ratelimit ON `" + collection + "`(somefield)"};
519
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_create(&cmd));
520
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_statement(cmd, statement.c_str(), statement.size()));
521
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_callback(cmd, query_callback));
522
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_scope_name(cmd, scope.c_str(), scope.size()));
523
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_query(instance, &err, cmd));
524
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdquery_destroy(cmd));
525
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_wait(instance, LCB_WAIT_DEFAULT));
526
+ ASSERT_STATUS_EQ(LCB_ERR_QUOTA_LIMITED, err);
527
+ }
528
+
529
+ drop_scope(instance, scope);
530
+ }
531
+
532
+ TEST_F(RateLimitTest, testRateLimitsSearchNumQueries)
533
+ {
534
+ SKIP_IF_MOCK()
535
+ SKIP_IF_CLUSTER_VERSION_IS_LOWER_THAN(MockEnvironment::VERSION_71)
536
+ SKIP_IF_CLUSTER_VERSION_IS_HIGHER_THAN(MockEnvironment::VERSION_72)
537
+ HandleWrap hw;
538
+ lcb_INSTANCE *instance;
539
+ createConnection(hw, &instance);
540
+
541
+ enforce_rate_limits(instance);
542
+ rate_limits limits{};
543
+ limits.search_limits.enforce = true;
544
+ limits.search_limits.num_queries_per_min = 1;
545
+ auto username = unique_name("rate_limited_user");
546
+ std::string password{"password"};
547
+ create_rate_limited_user(instance, username, limits);
548
+
549
+ auto index_name = unique_name("index");
550
+ create_search_index(instance, index_name, "fulltext-index", "couchbase",
551
+ MockEnvironment::getInstance()->getBucket());
552
+
553
+ hw.destroy();
554
+
555
+ ASSERT_STATUS_EQ(LCB_SUCCESS, retry_connect_on_auth_failure(hw, &instance, username, "password"));
556
+
557
+ bool found_error = false;
558
+ auto start = std::chrono::high_resolution_clock::now();
559
+
560
+ while (std::chrono::high_resolution_clock::now() < start + std::chrono::seconds(10)) {
561
+ lcb_STATUS err;
562
+ lcb_CMDSEARCH *cmd{};
563
+ std::string statement{R"({"indexName":")" + index_name + R"(","limit":2,"query":{"query":"*"}})"};
564
+
565
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdsearch_create(&cmd));
566
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdsearch_payload(cmd, statement.c_str(), statement.size()));
567
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdsearch_callback(cmd, search_callback));
568
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_search(instance, &err, cmd));
569
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdsearch_destroy(cmd));
570
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_wait(instance, LCB_WAIT_DEFAULT));
571
+ if (err == LCB_ERR_RATE_LIMITED) {
572
+ found_error = true;
573
+ break;
574
+ }
575
+ }
576
+
577
+ ASSERT_TRUE(found_error);
578
+
579
+ drop_user(instance, username);
580
+ }
581
+
582
+ TEST_F(RateLimitTest, testRateLimitsSearchEgress)
583
+ {
584
+ SKIP_IF_MOCK()
585
+ SKIP_IF_CLUSTER_VERSION_IS_LOWER_THAN(MockEnvironment::VERSION_71)
586
+ SKIP_IF_CLUSTER_VERSION_IS_HIGHER_THAN(MockEnvironment::VERSION_72)
587
+ HandleWrap hw;
588
+ lcb_INSTANCE *instance;
589
+ createConnection(hw, &instance);
590
+
591
+ enforce_rate_limits(instance);
592
+ rate_limits limits{};
593
+ limits.search_limits.enforce = true;
594
+ limits.search_limits.egress_mib_per_min = 1;
595
+ auto username = unique_name("rate_limited_user");
596
+ std::string password{"password"};
597
+ create_rate_limited_user(instance, username, limits);
598
+
599
+ auto index_name = unique_name("index");
600
+ create_search_index(instance, index_name, "fulltext-index", "couchbase",
601
+ MockEnvironment::getInstance()->getBucket());
602
+
603
+ hw.destroy();
604
+
605
+ ASSERT_STATUS_EQ(LCB_SUCCESS, retry_connect_on_auth_failure(hw, &instance, username, "password"));
606
+
607
+ std::string key{"ratelimitingress"};
608
+ std::string value_inner(1024 * 1024, 'a');
609
+ std::string value{R"({"value": ")" + value_inner + R"("})"};
610
+
611
+ storeKey(instance, key, value);
612
+
613
+ bool found_error = false;
614
+ auto start = std::chrono::high_resolution_clock::now();
615
+
616
+ while (std::chrono::high_resolution_clock::now() < start + std::chrono::seconds(10)) {
617
+ lcb_STATUS err;
618
+ lcb_CMDSEARCH *cmd{};
619
+ std::string statement{R"({"indexName":")" + index_name + R"(","limit":1,"query":{"query":"a*"}})"};
620
+
621
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdsearch_create(&cmd));
622
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdsearch_payload(cmd, statement.c_str(), statement.size()));
623
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdsearch_callback(cmd, search_callback));
624
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_search(instance, &err, cmd));
625
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdsearch_destroy(cmd));
626
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_wait(instance, LCB_WAIT_DEFAULT));
627
+ if (err == LCB_ERR_RATE_LIMITED) {
628
+ found_error = true;
629
+ break;
630
+ }
631
+ }
632
+
633
+ ASSERT_TRUE(found_error);
634
+
635
+ drop_user(instance, username);
636
+ }
637
+
638
+ TEST_F(RateLimitTest, testRateLimitsSearchIngress)
639
+ {
640
+ SKIP_IF_MOCK()
641
+ SKIP_IF_CLUSTER_VERSION_IS_LOWER_THAN(MockEnvironment::VERSION_71)
642
+ SKIP_IF_CLUSTER_VERSION_IS_HIGHER_THAN(MockEnvironment::VERSION_72)
643
+ HandleWrap hw;
644
+ lcb_INSTANCE *instance;
645
+ createConnection(hw, &instance);
646
+
647
+ enforce_rate_limits(instance);
648
+ rate_limits limits{};
649
+ limits.search_limits.enforce = true;
650
+ limits.search_limits.ingress_mib_per_min = 1;
651
+ auto username = unique_name("rate_limited_user");
652
+ std::string password{"password"};
653
+ create_rate_limited_user(instance, username, limits);
654
+
655
+ auto index_name = unique_name("index");
656
+ create_search_index(instance, index_name, "fulltext-index", "couchbase",
657
+ MockEnvironment::getInstance()->getBucket());
658
+
659
+ hw.destroy();
660
+
661
+ ASSERT_STATUS_EQ(LCB_SUCCESS, retry_connect_on_auth_failure(hw, &instance, username, "password"));
662
+
663
+ std::string key{"ratelimitingress"};
664
+ // fts chokes on a large payload
665
+ std::string query(1024, 'a');
666
+
667
+ bool found_error = false;
668
+ auto start = std::chrono::high_resolution_clock::now();
669
+
670
+ while (std::chrono::high_resolution_clock::now() < start + std::chrono::seconds(10)) {
671
+ lcb_STATUS err;
672
+ lcb_CMDSEARCH *cmd{};
673
+ std::string statement{R"({"indexName":")" + index_name + R"(","limit":1,"query":{"query":")" + query + R"("}})"};
674
+
675
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdsearch_create(&cmd));
676
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdsearch_payload(cmd, statement.c_str(), statement.size()));
677
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdsearch_callback(cmd, search_callback));
678
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_search(instance, &err, cmd));
679
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdsearch_destroy(cmd));
680
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_wait(instance, LCB_WAIT_DEFAULT));
681
+ if (err == LCB_ERR_RATE_LIMITED) {
682
+ found_error = true;
683
+ break;
684
+ }
685
+ }
686
+
687
+ ASSERT_TRUE(found_error);
688
+
689
+ drop_user(instance, username);
690
+ }
691
+
692
+ TEST_F(RateLimitTest, testRateLimitsSearchConcurrentRequests)
693
+ {
694
+ SKIP_IF_MOCK()
695
+ SKIP_IF_CLUSTER_VERSION_IS_LOWER_THAN(MockEnvironment::VERSION_71)
696
+ SKIP_IF_CLUSTER_VERSION_IS_HIGHER_THAN(MockEnvironment::VERSION_72)
697
+ HandleWrap hw;
698
+ lcb_INSTANCE *instance;
699
+ createConnection(hw, &instance);
700
+
701
+ enforce_rate_limits(instance);
702
+ rate_limits limits{};
703
+ limits.search_limits.enforce = true;
704
+ limits.search_limits.num_queries_per_min = 1;
705
+ auto username = unique_name("rate_limited_user");
706
+ std::string password{"password"};
707
+ create_rate_limited_user(instance, username, limits);
708
+
709
+ auto index_name = unique_name("index");
710
+ create_search_index(instance, index_name, "fulltext-index", "couchbase",
711
+ MockEnvironment::getInstance()->getBucket());
712
+
713
+ hw.destroy();
714
+
715
+ ASSERT_STATUS_EQ(LCB_SUCCESS, retry_connect_on_auth_failure(hw, &instance, username, "password"));
716
+
717
+ std::vector<lcb_STATUS> errors{};
718
+ errors.reserve(10);
719
+
720
+ lcb_sched_enter(instance);
721
+ for (int ii = 0; ii < 10; ++ii) {
722
+ lcb_CMDSEARCH *cmd{};
723
+ std::string statement{R"({"indexName":")" + index_name + R"(","limit":2,"query":{"query":"*"}})"};
724
+
725
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdsearch_create(&cmd));
726
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdsearch_payload(cmd, statement.c_str(), statement.size()));
727
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdsearch_callback(cmd, concurrent_search_callback));
728
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_search(instance, &errors, cmd));
729
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_cmdsearch_destroy(cmd));
730
+ }
731
+ lcb_sched_leave(instance);
732
+ ASSERT_STATUS_EQ(LCB_SUCCESS, lcb_wait(instance, LCB_WAIT_DEFAULT));
733
+
734
+ bool found_error = std::count_if(errors.begin(), errors.end(), [](lcb_STATUS err) { return err == LCB_ERR_RATE_LIMITED; }) > 0;
735
+
736
+ ASSERT_TRUE(found_error);
737
+
738
+ drop_user(instance, username);
739
+ }