couchbase 3.2.4 → 3.2.5
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.
- package/binding.gyp +5 -0
- package/deps/lcb/CMakeLists.txt +1 -1
- package/deps/lcb/RELEASE_NOTES.markdown +12 -0
- package/deps/lcb/cmake/Modules/GetVersionInfo.cmake +1 -1
- package/deps/lcb/doc/Doxyfile +1 -1
- package/deps/lcb/gyp_config/common/libcouchbase/configuration.h +3 -3
- package/deps/lcb/include/libcouchbase/couchbase.h +52 -0
- package/deps/lcb/include/libcouchbase/error.h +4 -1
- package/deps/lcb/libcouchbase.gyp +7 -1
- package/deps/lcb/src/capi/cmd_counter.hh +12 -0
- package/deps/lcb/src/capi/cmd_exists.hh +12 -0
- package/deps/lcb/src/capi/cmd_get.hh +12 -0
- package/deps/lcb/src/capi/cmd_get_replica.hh +14 -1
- package/deps/lcb/src/capi/cmd_query.cc +13 -0
- package/deps/lcb/src/capi/cmd_query.hh +22 -14
- package/deps/lcb/src/capi/cmd_remove.hh +12 -0
- package/deps/lcb/src/capi/cmd_store.hh +12 -0
- package/deps/lcb/src/capi/cmd_subdoc.hh +12 -0
- package/deps/lcb/src/capi/cmd_touch.hh +12 -0
- package/deps/lcb/src/capi/cmd_unlock.hh +12 -0
- package/deps/lcb/src/capi/collection_qualifier.hh +4 -6
- package/deps/lcb/src/internal.h +2 -1
- package/deps/lcb/src/mcserver/negotiate.cc +3 -0
- package/deps/lcb/src/n1ql/n1ql.cc +5 -1
- package/deps/lcb/src/n1ql/query_handle.cc +55 -30
- package/deps/lcb/src/n1ql/query_handle.hh +14 -2
- package/deps/lcb/src/operations/counter.cc +12 -0
- package/deps/lcb/src/operations/exists.cc +12 -0
- package/deps/lcb/src/operations/get.cc +12 -0
- package/deps/lcb/src/operations/get_replica.cc +18 -6
- package/deps/lcb/src/operations/remove.cc +12 -0
- package/deps/lcb/src/operations/store.cc +12 -0
- package/deps/lcb/src/operations/subdoc.cc +12 -0
- package/deps/lcb/src/operations/touch.cc +12 -0
- package/deps/lcb/src/operations/unlock.cc +12 -0
- package/deps/lcb/src/search/search_handle.cc +1 -2
- package/deps/lcb/src/ssl/ssl_common.c +1 -1
- package/deps/lcb/src/utilities.cc +21 -0
- package/deps/lcb/src/utilities.h +3 -0
- package/deps/lcb/tests/iotests/mock-environment.cc +10 -1
- package/deps/lcb/tests/iotests/mock-environment.h +2 -1
- package/deps/lcb/tests/iotests/serverparams.h +7 -2
- package/deps/lcb/tests/iotests/t_ratelimit.cc +729 -0
- package/deps/lcb/tests/iotests/testutil.cc +174 -0
- package/deps/lcb/tests/iotests/testutil.h +53 -0
- package/dist/analyticsexecutor.js +2 -2
- package/dist/analyticsindexmanager.js +3 -3
- package/dist/binarycollection.d.ts +17 -0
- package/dist/binding.js +1 -1
- package/dist/bindingutilities.js +5 -1
- package/dist/bucketmanager.d.ts +1 -22
- package/dist/bucketmanager.js +5 -5
- package/dist/cluster.js +1 -1
- package/dist/collection.js +6 -6
- package/dist/collectionmanager.js +2 -2
- package/dist/connection.js +3 -3
- package/dist/connspec.js +5 -1
- package/dist/couchbase.js +5 -1
- package/dist/httpexecutor.js +5 -1
- package/dist/logging.js +1 -1
- package/dist/queryexecutor.js +3 -3
- package/dist/searchindexmanager.js +1 -1
- package/dist/usermanager.js +2 -2
- package/dist/utilities.d.ts +1 -2
- package/dist/utilities.js +9 -2
- package/dist/viewexecutor.js +1 -1
- package/package.json +1 -1
- package/src/uv-plugin-all.cpp +1 -0
- package/dist/cas.d.ts +0 -0
- 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
|
+
}
|