MB-16181: Filters with deleted collections
[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  * Try and create filter for collections which exist, but have been deleted
202  * i.e. they aren't writable so should never feature in a new VB::Filter
203  */
204 TEST_F(CollectionsVBFilterTest, deleted_collection) {
205     Collections::Manifest m1(
206             R"({"revision":0,"separator":"$",)"
207             R"("collections":["$default", "vegetable", "fruit", "meat", "dairy"]})");
208     Collections::Manifest m2(
209             R"({"revision":1,"separator":"$",)"
210             R"("collections":["$default", "meat", "dairy"]})");
211
212     // Create the "producer" level filter so that we in theory produce at least
213     // these collections
214     std::string jsonFilter = R"({"collections":["vegetable", "fruit"]})";
215     boost::optional<const std::string&> json(jsonFilter);
216     Collections::Filter f(json, m1);
217
218     Collections::VB::Manifest vbm({});
219     // push creates
220     vbm.wlock().update(vb, m1);
221     // push deletes, removing both filtered collections
222     vbm.wlock().update(vb, m2);
223
224     // Construction will fail as the filter would not match anything valid
225     EXPECT_THROW(std::make_unique<Collections::VB::Filter>(f, vbm),
226                  std::invalid_argument);
227 }
228
229 /**
230  * Create a filter with collections and check we allow what should be allowed.
231  */
232 TEST_F(CollectionsVBFilterTest, basic_allow) {
233     Collections::Manifest m(
234             R"({"revision":0,"separator":"$",)"
235             R"("collections":["$default", "vegetable", "fruit", "meat", "dairy"]})");
236
237     std::string jsonFilter = R"({"collections":["$default", "fruit", "meat"]})";
238     boost::optional<const std::string&> json(jsonFilter);
239     Collections::Filter f(json, m);
240
241     Collections::VB::Manifest vbm({});
242     vbm.wlock().update(vb, m);
243
244     Collections::VB::Filter vbf(f, vbm);
245
246     // Yes to these guys
247     EXPECT_TRUE(vbf.allow({"anykey", DocNamespace::DefaultCollection}));
248     EXPECT_TRUE(vbf.allow({"fruit$apple", DocNamespace::Collections}));
249     EXPECT_TRUE(vbf.allow({"meat$bacon", DocNamespace::Collections}));
250
251     // No to these keys
252     EXPECT_FALSE(vbf.allow({"dairy$milk", DocNamespace::Collections}));
253     EXPECT_FALSE(vbf.allow({"vegetable$cabbage", DocNamespace::Collections}));
254
255     // There's no need yet to call the filter with DocKey's in system space, so
256     // it throws
257     EXPECT_THROW(vbf.allow({"meat$bacon", DocNamespace::System}),
258                  std::invalid_argument);
259 }
260
261 /**
262  * Create a filter as if a legacy DCP connection would, i.e. the optional
263  * JSON filter is not initialised (because DCP open does not send a value).
264  */
265 TEST_F(CollectionsVBFilterTest, legacy_filter) {
266     Collections::Manifest m(
267             R"({"revision":0,"separator":"$","collections":["$default", "meat"]})");
268
269     boost::optional<const std::string&> json;
270     Collections::Filter f(json, m);
271
272     Collections::VB::Manifest vbm({});
273     vbm.wlock().update(vb, m);
274
275     Collections::VB::Filter vbf(f, vbm);
276     // Legacy would only allow default
277     EXPECT_TRUE(vbf.allow({"anykey", DocNamespace::DefaultCollection}));
278     EXPECT_FALSE(vbf.allow({"fruit$apple", DocNamespace::Collections}));
279 }
280
281 /**
282  * Create a passthrough filter and check it allows anything
283  */
284 TEST_F(CollectionsVBFilterTest, passthrough) {
285     Collections::Manifest m(
286             R"({"revision":0,"separator":"$","collections":["meat"]})");
287     std::string filterJson; // empty string
288     boost::optional<const std::string&> json(filterJson);
289     Collections::Filter f(json, m);
290
291     Collections::VB::Manifest vbm({});
292     vbm.wlock().update(vb, m);
293
294     // Everything is allowed (even junk, which isn't the filter's job to police)
295     Collections::VB::Filter vbf(f, vbm);
296     EXPECT_TRUE(vbf.allow({"anykey", DocNamespace::DefaultCollection}));
297     EXPECT_TRUE(vbf.allow({"fruit$apple", DocNamespace::Collections}));
298     EXPECT_TRUE(vbf.allow({"meat$steak", DocNamespace::Collections}));
299     EXPECT_TRUE(vbf.allow({"dairy$milk", DocNamespace::Collections}));
300     EXPECT_TRUE(vbf.allow({"JUNK!!", DocNamespace::Collections}));
301 }
302
303 /**
304  * Create a filter which blocks the default collection
305  */
306 TEST_F(CollectionsVBFilterTest, no_default) {
307     Collections::Manifest m(
308             R"({"revision":0,"separator":"$",)"
309             R"("collections":["$default", "vegetable", "fruit", "meat", "dairy"]})");
310     Collections::VB::Manifest vbm({});
311     vbm.wlock().update(vb, m);
312
313     std::string jsonFilter = R"({"collections":["fruit", "meat"]})";
314     boost::optional<const std::string&> json(jsonFilter);
315     Collections::Filter f(json, m);
316
317     // Now filter!
318     Collections::VB::Filter vbf(f, vbm);
319     EXPECT_FALSE(vbf.allow({"anykey", DocNamespace::DefaultCollection}));
320     EXPECT_TRUE(vbf.allow({"fruit$apple", DocNamespace::Collections}));
321     EXPECT_TRUE(vbf.allow({"meat$steak", DocNamespace::Collections}));
322     EXPECT_FALSE(vbf.allow({"dairy$milk", DocNamespace::Collections}));
323     EXPECT_FALSE(vbf.allow({"JUNK!!", DocNamespace::Collections}));
324 }
325
326 /**
327  * Check we can remove collections from the filter (which live DCP may do)and
328  * check ::allow works as expected
329  */
330 TEST_F(CollectionsVBFilterTest, remove1) {
331     Collections::Manifest m(
332             R"({"revision":0,"separator":"$",)"
333             R"("collections":["vegetable", "fruit", "meat", "dairy"]})");
334     Collections::VB::Manifest vbm({});
335     vbm.wlock().update(vb, m);
336
337     std::string jsonFilter = R"({"collections":["fruit", "meat"]})";
338     boost::optional<const std::string&> json(jsonFilter);
339
340     Collections::Filter f(json, m);
341     Collections::VB::Filter vbf(f, vbm);
342     EXPECT_TRUE(vbf.allow({"fruit$apple", DocNamespace::Collections}));
343     EXPECT_FALSE(vbf.remove("fruit"));
344     EXPECT_FALSE(vbf.allow({"fruit$apple", DocNamespace::Collections}));
345
346     EXPECT_TRUE(vbf.allow({"meat$steak", DocNamespace::Collections}));
347     EXPECT_TRUE(vbf.remove("meat"));
348     EXPECT_FALSE(vbf.allow({"meat$apple", DocNamespace::Collections}));
349 }
350
351 /**
352  * Check we can remove collections from the filter (which live DCP may do) and
353  * check ::allow works as expected
354  * This test includes checking we can remove $default
355  */
356 TEST_F(CollectionsVBFilterTest, remove2) {
357     Collections::Manifest m(
358             R"({"revision":0,"separator":"$",)"
359             R"("collections":["$default", "fruit", "meat", "dairy"]})");
360     Collections::VB::Manifest vbm({});
361     vbm.wlock().update(vb, m);
362
363     std::string jsonFilter = R"({"collections":["$default", "meat"]})";
364     boost::optional<const std::string&> json(jsonFilter);
365
366     Collections::Filter f(json, m);
367     Collections::VB::Filter vbf(f, vbm);
368     EXPECT_TRUE(vbf.allow({"anykey", DocNamespace::DefaultCollection}));
369     EXPECT_FALSE(vbf.remove("$default"));
370     EXPECT_FALSE(vbf.allow({"anykey", DocNamespace::DefaultCollection}));
371
372     EXPECT_TRUE(vbf.allow({"meat$steak", DocNamespace::Collections}));
373     EXPECT_TRUE(vbf.remove("meat"));
374     EXPECT_FALSE(vbf.allow({"meat$apple", DocNamespace::Collections}));
375 }
376
377 std::unique_ptr<SystemEventConsumerMessage> makeTestMessage(
378         const std::string name, SystemEvent ev, const int* rev) {
379     cb::const_byte_buffer n{reinterpret_cast<const uint8_t*>(name.data()),
380                             name.size()};
381     cb::const_byte_buffer r{reinterpret_cast<const uint8_t*>(rev), sizeof(int)};
382     return std::make_unique<SystemEventConsumerMessage>(
383             0, ev, 0 /*seq*/, 0 /*vb*/, n, r);
384 }
385
386 /**
387  * System events are checked by a different interface (allowSystemEvent)
388  * Test that a filter allows the right events, this is a passthrough filter
389  * so everything is allowed.
390  */
391 TEST_F(CollectionsVBFilterTest, system_events1) {
392     Collections::Manifest m(
393             R"({"revision":0,"separator":"$",)"
394             R"("collections":["$default", "fruit", "meat", "dairy"]})");
395     Collections::VB::Manifest vbm({});
396     vbm.wlock().update(vb, m);
397
398     std::string jsonFilter;
399     boost::optional<const std::string&> json(jsonFilter);
400
401     Collections::Filter f(json, m);
402     Collections::VB::Filter vbf(f, vbm);
403
404     int rev = 0;
405     // create and delete of meat is allowed by the meat filter
406     std::string name = "meat";
407     EXPECT_TRUE(vbf.allowSystemEvent(
408             makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
409     EXPECT_TRUE(vbf.allowSystemEvent(
410             makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
411                     .get()));
412
413     // create and delete of $default is allowed by the filter
414     name = "$default";
415     EXPECT_TRUE(vbf.allowSystemEvent(
416             makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
417     EXPECT_TRUE(vbf.allowSystemEvent(
418             makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
419                     .get()));
420
421     // create and delete of dairy is not allowed by the filter
422     name = "dairy";
423     EXPECT_TRUE(vbf.allowSystemEvent(
424             makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
425     EXPECT_TRUE(vbf.allowSystemEvent(
426             makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
427                     .get()));
428
429     // A change of separator is also allowed
430     EXPECT_TRUE(vbf.allowSystemEvent(
431             makeTestMessage(
432                     name, SystemEvent::CollectionsSeparatorChanged, &rev)
433                     .get()));
434 }
435
436 /**
437  * System events are checked by a different interface (allowSystemEvent)
438  * Test that a filter allows the right events
439  */
440 TEST_F(CollectionsVBFilterTest, system_events2) {
441     Collections::Manifest m(
442             R"({"revision":0,"separator":"$",)"
443             R"("collections":["$default", "fruit", "meat", "dairy"]})");
444     Collections::VB::Manifest vbm({});
445     vbm.wlock().update(vb, m);
446
447     std::string jsonFilter = R"({"collections":["$default", "meat"]})";
448     boost::optional<const std::string&> json(jsonFilter);
449
450     Collections::Filter f(json, m);
451     Collections::VB::Filter vbf(f, vbm);
452
453     int rev = 0;
454     // create and delete of meat is allowed by the meat filter
455     std::string name = "meat";
456     EXPECT_TRUE(vbf.allowSystemEvent(
457             makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
458     EXPECT_TRUE(vbf.allowSystemEvent(
459             makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
460                     .get()));
461
462     // create and delete of $default is allowed by the filter
463     name = "$default";
464     EXPECT_TRUE(vbf.allowSystemEvent(
465             makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
466     EXPECT_TRUE(vbf.allowSystemEvent(
467             makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
468                     .get()));
469
470     // create and delete of dairy is not allowed by the filter
471     name = "dairy";
472     EXPECT_FALSE(vbf.allowSystemEvent(
473             makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
474     EXPECT_FALSE(vbf.allowSystemEvent(
475             makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
476                     .get()));
477
478     // A change of separator is also allowed
479     EXPECT_TRUE(vbf.allowSystemEvent(
480             makeTestMessage(
481                     name, SystemEvent::CollectionsSeparatorChanged, &rev)
482                     .get()));
483 }
484
485 /**
486  * System events are checked by a different interface
487  * Test that a legacy filter denies all system events, they shouldn't be sent
488  * to legacy clients.
489  */
490 TEST_F(CollectionsVBFilterTest, system_events3) {
491     Collections::Manifest m(
492             R"({"revision":0,"separator":"$",)"
493             R"("collections":["$default", "fruit", "meat", "dairy"]})");
494     Collections::VB::Manifest vbm({});
495     vbm.wlock().update(vb, m);
496
497     boost::optional<const std::string&> json;
498
499     Collections::Filter f(json, m);
500     Collections::VB::Filter vbf(f, vbm);
501
502     // All system events dropped by this empty/legacy filter
503     std::string name = "meat";
504     int rev = 0;
505     EXPECT_FALSE(vbf.allowSystemEvent(
506             makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
507     EXPECT_FALSE(vbf.allowSystemEvent(
508             makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
509                     .get()));
510     EXPECT_FALSE(vbf.allowSystemEvent(
511             makeTestMessage(
512                     name, SystemEvent::CollectionsSeparatorChanged, &rev)
513                     .get()));
514 }