couchbase 3.2.4 → 3.2.5

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