fcf56faebf1bd8ab02239216bbbed16c3670b57c
[ep-engine.git] / tests / module_tests / collections / filter_test.cc
1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2017 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 "collections/filter.h"
19 #include "collections/vbucket_filter.h"
20 #include "collections/vbucket_manifest.h"
21 #include "ep_vb.h"
22 #include "failover-table.h"
23
24 #include <gtest/gtest.h>
25
26 #include <limits>
27
28 class CollectionsFilterTest : public ::testing::Test {
29 public:
30     /// Dummy callback to replace the flusher callback so we can create VBuckets
31     class DummyCB : public Callback<uint16_t> {
32     public:
33         DummyCB() {
34         }
35
36         void callback(uint16_t& dummy) {
37         }
38     };
39
40     CollectionsFilterTest()
41         : vb(0,
42              vbucket_state_active,
43              global_stats,
44              checkpoint_config,
45              /*kvshard*/ nullptr,
46              /*lastSeqno*/ 0,
47              /*lastSnapStart*/ 0,
48              /*lastSnapEnd*/ 0,
49              /*table*/ nullptr,
50              std::make_shared<DummyCB>(),
51              /*newSeqnoCb*/ nullptr,
52              config,
53              VALUE_ONLY) {
54     }
55
56     EPStats global_stats;
57     CheckpointConfig checkpoint_config;
58     Configuration config;
59     EPVBucket vb;
60 };
61
62 /**
63  * Test invalid inputs to the filter.
64  */
65 TEST_F(CollectionsFilterTest, junk_in) {
66     Collections::Manifest m(
67             R"({"revision":0,"separator":"::",)"
68             R"("collections":["$default", "vegetable"]})");
69
70     std::vector<std::string> inputs = {"{}",
71                                        R"({"collections":1})",
72                                        R"({"collections:"this"})",
73                                        R"({"collections:{"a":1})",
74                                        R"({"collection:["a"])",
75                                        R"({"collections:[a])"};
76
77     for (const auto& s : inputs) {
78         boost::optional<const std::string&> json = s;
79         EXPECT_THROW(std::make_unique<Collections::Filter>(json, m),
80                      std::invalid_argument)
81                 << "Failed for " << s;
82     }
83 }
84
85 /**
86  * Test valid inputs to the filter.
87  */
88 TEST_F(CollectionsFilterTest, validation1) {
89     Collections::Manifest m(
90             R"({"revision":0,"separator":"::",)"
91             R"("collections":["$default", "vegetable", "fruit", "meat", "dairy"]})");
92
93     std::vector<std::string> inputs = {R"({"collections":["$default"]})",
94                                        R"({"collections":["vegetable"]})",
95                                        R"({"collections":["fruit", "meat"]})"};
96
97     for (const auto& s : inputs) {
98         boost::optional<const std::string&> json = s;
99
100         EXPECT_NO_THROW(std::make_unique<Collections::Filter>(json, m))
101                 << "Exception thrown with input " << s;
102     }
103 }
104
105 /**
106  * Test valid inputs to the filter, but they are not known collections, so
107  * should trigger an exception.
108  */
109 TEST_F(CollectionsFilterTest, validation2) {
110     Collections::Manifest m(
111             R"({"revision":0,"separator":"::",)"
112             R"("collections":["$default", "vegetable", "fruit", "meat", "dairy"]})");
113
114     std::vector<std::string> inputs = {R"({"collections":["cheese"]})",
115                                        R"({"collections":["fruit","beer"]})",
116                                        R"({"collections":["$dufault"]})"};
117
118     for (const auto& s : inputs) {
119         boost::optional<const std::string&> json = s;
120         EXPECT_THROW(std::make_unique<Collections::Filter>(json, m),
121                      std::invalid_argument);
122     }
123 }
124
125 /**
126  * Test that we cannot create default collection filter when no default
127  * collection exists
128  */
129 TEST_F(CollectionsFilterTest, validation_no_default) {
130     // m does not include $default
131     Collections::Manifest m(
132             R"({"revision":0,"separator":"::",)"
133             R"("collections":["vegetable", "fruit", "meat", "dairy"]})");
134
135     boost::optional<const std::string&> json;
136     EXPECT_THROW(std::make_unique<Collections::Filter>(json, m),
137                  std::logic_error);
138 }
139
140 /**
141  * Construct a valid Collections::Filter and check its public methods
142  * This creates a filter which contains a set of collections
143  */
144 TEST_F(CollectionsFilterTest, filter_basic1) {
145     Collections::Manifest m(
146             R"({"revision":0,"separator":"$",)"
147             R"("collections":["$default", "vegetable", "fruit", "meat", "dairy"]})");
148
149     std::string jsonFilter = R"({"collections":["$default", "fruit", "meat"]})";
150     boost::optional<const std::string&> json(jsonFilter);
151     Collections::Filter f(json, m);
152
153     // This is not a passthrough filter
154     EXPECT_FALSE(f.isPassthrough());
155
156     // But this filter would send the default
157     EXPECT_TRUE(f.allowDefaultCollection());
158     // and allow system events
159     EXPECT_TRUE(f.allowSystemEvents());
160
161     // The actual filter "list" only stores fruit and meat though, default is
162     // special cased via doesDefaultCollectionExist
163     EXPECT_EQ(2, f.getFilter().size());
164
165     auto list = f.getFilter();
166     EXPECT_TRUE(std::find(std::begin(list), std::end(list), "fruit") !=
167                 list.end());
168     EXPECT_TRUE(std::find(std::begin(list), std::end(list), "meat") !=
169                 list.end());
170 }
171
172 /**
173  * Construct a valid Collections::Filter and check its public methods
174  * This creates a filter which is passthrough
175  */
176 TEST_F(CollectionsFilterTest, filter_basic2) {
177     Collections::Manifest m(
178             R"({"revision":0,"separator":"$",)"
179             R"("collections":["$default", "vegetable", "fruit", "meat", "dairy"]})");
180
181     std::string jsonFilter; // empty string creates a pass through
182     boost::optional<const std::string&> json(jsonFilter);
183     Collections::Filter f(json, m);
184
185     // This is a passthrough filter
186     EXPECT_TRUE(f.isPassthrough());
187
188     // So this filter would send the default
189     EXPECT_TRUE(f.allowDefaultCollection());
190
191     // and still allow system events
192     EXPECT_TRUE(f.allowSystemEvents());
193
194     // The actual filter "list" stores nothing
195     EXPECT_EQ(0, f.getFilter().size());
196 }
197
198 class CollectionsVBFilterTest : public CollectionsFilterTest {};
199
200 /**
201  * Create a filter with collections and check we allow what should be allowed.
202  */
203 TEST_F(CollectionsVBFilterTest, basic_allow) {
204     Collections::Manifest m(
205             R"({"revision":0,"separator":"$",)"
206             R"("collections":["$default", "vegetable", "fruit", "meat", "dairy"]})");
207
208     std::string jsonFilter = R"({"collections":["$default", "fruit", "meat"]})";
209     boost::optional<const std::string&> json(jsonFilter);
210     Collections::Filter f(json, m);
211
212     Collections::VB::Manifest vbm({});
213     vbm.wlock().update(vb, m);
214
215     Collections::VB::Filter vbf(f, vbm);
216
217     // Yes to these guys
218     EXPECT_TRUE(vbf.allow({"anykey", DocNamespace::DefaultCollection}));
219     EXPECT_TRUE(vbf.allow({"fruit$apple", DocNamespace::Collections}));
220     EXPECT_TRUE(vbf.allow({"meat$bacon", DocNamespace::Collections}));
221
222     // No to these keys
223     EXPECT_FALSE(vbf.allow({"dairy$milk", DocNamespace::Collections}));
224     EXPECT_FALSE(vbf.allow({"vegetable$cabbage", DocNamespace::Collections}));
225
226     // There's no need yet to call the filter with DocKey's in system space, so
227     // it throws
228     EXPECT_THROW(vbf.allow({"meat$bacon", DocNamespace::System}),
229                  std::invalid_argument);
230 }
231
232 /**
233  * Create a filter as if a legacy DCP connection would, i.e. the optional
234  * JSON filter is not initialised (because DCP open does not send a value).
235  */
236 TEST_F(CollectionsVBFilterTest, legacy_filter) {
237     Collections::Manifest m(
238             R"({"revision":0,"separator":"$","collections":["$default", "meat"]})");
239
240     boost::optional<const std::string&> json;
241     Collections::Filter f(json, m);
242
243     Collections::VB::Manifest vbm({});
244     vbm.wlock().update(vb, m);
245
246     Collections::VB::Filter vbf(f, vbm);
247     // Legacy would only allow default
248     EXPECT_TRUE(vbf.allow({"anykey", DocNamespace::DefaultCollection}));
249     EXPECT_FALSE(vbf.allow({"fruit$apple", DocNamespace::Collections}));
250 }
251
252 /**
253  * Create a passthrough filter and check it allows anything
254  */
255 TEST_F(CollectionsVBFilterTest, passthrough) {
256     Collections::Manifest m(
257             R"({"revision":0,"separator":"$","collections":["meat"]})");
258     std::string filterJson; // empty string
259     boost::optional<const std::string&> json(filterJson);
260     Collections::Filter f(json, m);
261
262     Collections::VB::Manifest vbm({});
263     vbm.wlock().update(vb, m);
264
265     // Everything is allowed (even junk, which isn't the filter's job to police)
266     Collections::VB::Filter vbf(f, vbm);
267     EXPECT_TRUE(vbf.allow({"anykey", DocNamespace::DefaultCollection}));
268     EXPECT_TRUE(vbf.allow({"fruit$apple", DocNamespace::Collections}));
269     EXPECT_TRUE(vbf.allow({"meat$steak", DocNamespace::Collections}));
270     EXPECT_TRUE(vbf.allow({"dairy$milk", DocNamespace::Collections}));
271     EXPECT_TRUE(vbf.allow({"JUNK!!", DocNamespace::Collections}));
272 }
273
274 /**
275  * Create a filter which blocks the default collection
276  */
277 TEST_F(CollectionsVBFilterTest, no_default) {
278     Collections::Manifest m(
279             R"({"revision":0,"separator":"$",)"
280             R"("collections":["$default", "vegetable", "fruit", "meat", "dairy"]})");
281     Collections::VB::Manifest vbm({});
282     vbm.wlock().update(vb, m);
283
284     std::string jsonFilter = R"({"collections":["fruit", "meat"]})";
285     boost::optional<const std::string&> json(jsonFilter);
286     Collections::Filter f(json, m);
287
288     // Now filter!
289     Collections::VB::Filter vbf(f, vbm);
290     EXPECT_FALSE(vbf.allow({"anykey", DocNamespace::DefaultCollection}));
291     EXPECT_TRUE(vbf.allow({"fruit$apple", DocNamespace::Collections}));
292     EXPECT_TRUE(vbf.allow({"meat$steak", DocNamespace::Collections}));
293     EXPECT_FALSE(vbf.allow({"dairy$milk", DocNamespace::Collections}));
294     EXPECT_FALSE(vbf.allow({"JUNK!!", DocNamespace::Collections}));
295 }
296
297 /**
298  * Check we can remove collections from the filter (which live DCP may do)and
299  * check ::allow works as expected
300  */
301 TEST_F(CollectionsVBFilterTest, remove1) {
302     Collections::Manifest m(
303             R"({"revision":0,"separator":"$",)"
304             R"("collections":["vegetable", "fruit", "meat", "dairy"]})");
305     Collections::VB::Manifest vbm({});
306     vbm.wlock().update(vb, m);
307
308     std::string jsonFilter = R"({"collections":["fruit", "meat"]})";
309     boost::optional<const std::string&> json(jsonFilter);
310
311     Collections::Filter f(json, m);
312     Collections::VB::Filter vbf(f, vbm);
313     EXPECT_TRUE(vbf.allow({"fruit$apple", DocNamespace::Collections}));
314     EXPECT_FALSE(vbf.remove("fruit"));
315     EXPECT_FALSE(vbf.allow({"fruit$apple", DocNamespace::Collections}));
316
317     EXPECT_TRUE(vbf.allow({"meat$steak", DocNamespace::Collections}));
318     EXPECT_TRUE(vbf.remove("meat"));
319     EXPECT_FALSE(vbf.allow({"meat$apple", DocNamespace::Collections}));
320 }
321
322 /**
323  * Check we can remove collections from the filter (which live DCP may do) and
324  * check ::allow works as expected
325  * This test includes checking we can remove $default
326  */
327 TEST_F(CollectionsVBFilterTest, remove2) {
328     Collections::Manifest m(
329             R"({"revision":0,"separator":"$",)"
330             R"("collections":["$default", "fruit", "meat", "dairy"]})");
331     Collections::VB::Manifest vbm({});
332     vbm.wlock().update(vb, m);
333
334     std::string jsonFilter = R"({"collections":["$default", "meat"]})";
335     boost::optional<const std::string&> json(jsonFilter);
336
337     Collections::Filter f(json, m);
338     Collections::VB::Filter vbf(f, vbm);
339     EXPECT_TRUE(vbf.allow({"anykey", DocNamespace::DefaultCollection}));
340     EXPECT_FALSE(vbf.remove("$default"));
341     EXPECT_FALSE(vbf.allow({"anykey", DocNamespace::DefaultCollection}));
342
343     EXPECT_TRUE(vbf.allow({"meat$steak", DocNamespace::Collections}));
344     EXPECT_TRUE(vbf.remove("meat"));
345     EXPECT_FALSE(vbf.allow({"meat$apple", DocNamespace::Collections}));
346 }
347
348 std::unique_ptr<SystemEventConsumerMessage> makeTestMessage(
349         const std::string name, SystemEvent ev, const int* rev) {
350     cb::const_byte_buffer n{reinterpret_cast<const uint8_t*>(name.data()),
351                             name.size()};
352     cb::const_byte_buffer r{reinterpret_cast<const uint8_t*>(rev), sizeof(int)};
353     return std::make_unique<SystemEventConsumerMessage>(
354             0, ev, 0 /*seq*/, 0 /*vb*/, n, r);
355 }
356
357 /**
358  * System events are checked by a different interface (allowSystemEvent)
359  * Test that a filter allows the right events, this is a passthrough filter
360  * so everything is allowed.
361  */
362 TEST_F(CollectionsVBFilterTest, system_events1) {
363     Collections::Manifest m(
364             R"({"revision":0,"separator":"$",)"
365             R"("collections":["$default", "fruit", "meat", "dairy"]})");
366     Collections::VB::Manifest vbm({});
367     vbm.wlock().update(vb, m);
368
369     std::string jsonFilter;
370     boost::optional<const std::string&> json(jsonFilter);
371
372     Collections::Filter f(json, m);
373     Collections::VB::Filter vbf(f, vbm);
374
375     int rev = 0;
376     // create and delete of meat is allowed by the meat filter
377     std::string name = "meat";
378     EXPECT_TRUE(vbf.allowSystemEvent(
379             makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
380     EXPECT_TRUE(vbf.allowSystemEvent(
381             makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
382                     .get()));
383
384     // create and delete of $default is allowed by the filter
385     name = "$default";
386     EXPECT_TRUE(vbf.allowSystemEvent(
387             makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
388     EXPECT_TRUE(vbf.allowSystemEvent(
389             makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
390                     .get()));
391
392     // create and delete of dairy is not allowed by the filter
393     name = "dairy";
394     EXPECT_TRUE(vbf.allowSystemEvent(
395             makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
396     EXPECT_TRUE(vbf.allowSystemEvent(
397             makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
398                     .get()));
399
400     // A change of separator is also allowed
401     EXPECT_TRUE(vbf.allowSystemEvent(
402             makeTestMessage(
403                     name, SystemEvent::CollectionsSeparatorChanged, &rev)
404                     .get()));
405 }
406
407 /**
408  * System events are checked by a different interface (allowSystemEvent)
409  * Test that a filter allows the right events
410  */
411 TEST_F(CollectionsVBFilterTest, system_events2) {
412     Collections::Manifest m(
413             R"({"revision":0,"separator":"$",)"
414             R"("collections":["$default", "fruit", "meat", "dairy"]})");
415     Collections::VB::Manifest vbm({});
416     vbm.wlock().update(vb, m);
417
418     std::string jsonFilter = R"({"collections":["$default", "meat"]})";
419     boost::optional<const std::string&> json(jsonFilter);
420
421     Collections::Filter f(json, m);
422     Collections::VB::Filter vbf(f, vbm);
423
424     int rev = 0;
425     // create and delete of meat is allowed by the meat filter
426     std::string name = "meat";
427     EXPECT_TRUE(vbf.allowSystemEvent(
428             makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
429     EXPECT_TRUE(vbf.allowSystemEvent(
430             makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
431                     .get()));
432
433     // create and delete of $default is allowed by the filter
434     name = "$default";
435     EXPECT_TRUE(vbf.allowSystemEvent(
436             makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
437     EXPECT_TRUE(vbf.allowSystemEvent(
438             makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
439                     .get()));
440
441     // create and delete of dairy is not allowed by the filter
442     name = "dairy";
443     EXPECT_FALSE(vbf.allowSystemEvent(
444             makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
445     EXPECT_FALSE(vbf.allowSystemEvent(
446             makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
447                     .get()));
448
449     // A change of separator is also allowed
450     EXPECT_TRUE(vbf.allowSystemEvent(
451             makeTestMessage(
452                     name, SystemEvent::CollectionsSeparatorChanged, &rev)
453                     .get()));
454 }
455
456 /**
457  * System events are checked by a different interface
458  * Test that a legacy filter denies all system events, they shouldn't be sent
459  * to legacy clients.
460  */
461 TEST_F(CollectionsVBFilterTest, system_events3) {
462     Collections::Manifest m(
463             R"({"revision":0,"separator":"$",)"
464             R"("collections":["$default", "fruit", "meat", "dairy"]})");
465     Collections::VB::Manifest vbm({});
466     vbm.wlock().update(vb, m);
467
468     boost::optional<const std::string&> json;
469
470     Collections::Filter f(json, m);
471     Collections::VB::Filter vbf(f, vbm);
472
473     // All system events dropped by this empty/legacy filter
474     std::string name = "meat";
475     int rev = 0;
476     EXPECT_FALSE(vbf.allowSystemEvent(
477             makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
478     EXPECT_FALSE(vbf.allowSystemEvent(
479             makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
480                     .get()));
481     EXPECT_FALSE(vbf.allowSystemEvent(
482             makeTestMessage(
483                     name, SystemEvent::CollectionsSeparatorChanged, &rev)
484                     .get()));
485 }