mirror of
https://github.com/ceph/ceph
synced 2026-08-02 07:03:18 +00:00
Merge pull request #60942 from AliMasarweh/wip-alimasa-bucket-logging-filter
RGW/logging: add filtering for bucket logging Reviewed-by: yuvalif<ylifshit@redhat.com>
This commit is contained in:
commit
a508216657
@ -40,8 +40,8 @@ Journal
|
||||
If logging type is set to "Journal", the records are written to the log bucket before the bucket operation is completed.
|
||||
This means that if the logging action fails, the operation will not be executed, and an error will be returned to the client.
|
||||
An exception to the above are "multi/delete" log records: if writing these log records fail, the operation continues and may still be successful.
|
||||
Note that it may happen that the log records were successfully written, but the bucket operation failed, since the logs are written
|
||||
before such a failure, there will be no indication for that in the log records.
|
||||
Journal mode supports filtering out records based on matches of the prefixes and suffixes of the logged object keys. Regular-expression matching can also be used on these to create filters.
|
||||
Note that it may happen that the log records were successfully written, but the bucket operation failed, since the logs are written.
|
||||
|
||||
|
||||
Bucket Logging REST API
|
||||
|
||||
@ -751,6 +751,14 @@ Parameters are XML encoded in the body of the request, in the following format:
|
||||
<TargetPrefix>string</TargetPrefix>
|
||||
<LoggingType>Standard|Journal</LoggingType>
|
||||
<ObjectRollTime>integer</ObjectRollTime>
|
||||
<Filter>
|
||||
<S3Key>
|
||||
<FilterRule>
|
||||
<Name>suffix/prefix/regex</Name>
|
||||
<Value></Value>
|
||||
</FilterRule>
|
||||
</S3Key>
|
||||
</Filter>
|
||||
</LoggingEnabled>
|
||||
</BucketLoggingStatus>
|
||||
|
||||
@ -881,6 +889,14 @@ Response is XML encoded in the body of the request, in the following format:
|
||||
<TargetPrefix>string</TargetPrefix>
|
||||
<LoggingType>Standard|Journal</LoggingType>
|
||||
<ObjectRollTime>integer</ObjectRollTime>
|
||||
<Filter>
|
||||
<S3Key>
|
||||
<FilterRule>
|
||||
<Name>suffix/prefix/regex</Name>
|
||||
<Value></Value>
|
||||
</FilterRule>
|
||||
</S3Key>
|
||||
</Filter>
|
||||
</LoggingEnabled>
|
||||
</BucketLoggingStatus>
|
||||
|
||||
|
||||
@ -39,6 +39,17 @@ bucket_logging_conf = {'LoggingEnabled': {
|
||||
},
|
||||
'ObjectRollTime': 60,
|
||||
'LoggingType': 'Journal',
|
||||
"Filter": {
|
||||
"Key": {
|
||||
"FilterRules":
|
||||
[
|
||||
{
|
||||
"Name": "prefix",
|
||||
"Value": "myfile"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -310,6 +310,10 @@
|
||||
"RecordsBatchSize":{
|
||||
"shape":"RecordsBatchSize",
|
||||
"documentation":"indicates how many records to batch in memory before writing to the object. if set to zero, records are written syncronously to the object. if <code>ObjectRollTime</code>e is reached, the batch of records will be written to the object regardless of the number of records. </p>"
|
||||
},
|
||||
"Filter":{
|
||||
"shape":"LoggingConfigurationFilter",
|
||||
"documentation":"<p>A filter for all log object. Filter for the object by its key (prefix, suffix and regex).</p>"
|
||||
}
|
||||
},
|
||||
"documentation":"<p>Describes where logs are stored the prefix assigned to all log object keys for a bucket, and their format. also, the level the delivery guarantee of the records.</p>"
|
||||
@ -363,6 +367,18 @@
|
||||
"Standard",
|
||||
"Journal"
|
||||
]
|
||||
},
|
||||
"LoggingConfigurationFilter":{
|
||||
"type":"structure",
|
||||
"members":{
|
||||
"Key":{
|
||||
"shape":"S3KeyFilter",
|
||||
"documentation":"<p/>",
|
||||
"locationName":"S3Key"
|
||||
}
|
||||
},
|
||||
"documentation":"<p>A filter for all log object. Filter for the object by its key (prefix, suffix and regex).</p>",
|
||||
"locationName":"Filter"
|
||||
}
|
||||
},
|
||||
"documentation":"<p/>"
|
||||
|
||||
@ -90,6 +90,7 @@ set(librgw_common_srcs
|
||||
rgw_notify_event_type.cc
|
||||
rgw_period_history.cc
|
||||
rgw_period_puller.cc
|
||||
rgw_s3_filter.cc
|
||||
rgw_pubsub.cc
|
||||
rgw_coroutine.cc
|
||||
rgw_cr_rest.cc
|
||||
|
||||
@ -31,6 +31,9 @@ bool configuration::decode_xml(XMLObj* obj) {
|
||||
logging_type = LoggingType::Standard;
|
||||
} else if (type == "Journal") {
|
||||
logging_type = LoggingType::Journal;
|
||||
if (iter = o->find("Filter"); XMLObj* const filter_o = iter.get_next()) {
|
||||
RGWXMLDecoder::decode_xml("S3Key", key_filter, filter_o);
|
||||
}
|
||||
} else {
|
||||
// we don't allow for type "Any" in the configuration
|
||||
throw RGWXMLDecoder::err("invalid bucket logging record type: '" + type + "'");
|
||||
@ -73,6 +76,11 @@ void configuration::dump_xml(Formatter *f) const {
|
||||
break;
|
||||
case LoggingType::Journal:
|
||||
::encode_xml("LoggingType", "Journal", f);
|
||||
if (key_filter.has_content()) {
|
||||
f->open_object_section("Filter");
|
||||
::encode_xml("S3Key", key_filter, f);
|
||||
f->close_section(); // Filter
|
||||
}
|
||||
break;
|
||||
case LoggingType::Any:
|
||||
::encode_xml("LoggingType", "", f);
|
||||
@ -118,6 +126,10 @@ void configuration::dump(Formatter *f) const {
|
||||
break;
|
||||
case LoggingType::Journal:
|
||||
encode_json("loggingType", "Journal", f);
|
||||
if (key_filter.has_content()) {
|
||||
Formatter::ObjectSection s(*f, "Filter");
|
||||
encode_json("S3Key", key_filter, f);
|
||||
}
|
||||
break;
|
||||
case LoggingType::Any:
|
||||
encode_json("loggingType", "", f);
|
||||
@ -526,6 +538,11 @@ int log_record(rgw::sal::Driver* driver,
|
||||
if (type != LoggingType::Any && configuration.logging_type != type) {
|
||||
return 0;
|
||||
}
|
||||
if (configuration.key_filter.has_content()) {
|
||||
if (!match(configuration.key_filter, obj->get_name())) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
ldpp_dout(dpp, 20) << "INFO: found matching logging configuration of bucket '" << s->bucket->get_name() <<
|
||||
"' configuration: " << configuration.to_json_str() << dendl;
|
||||
if (auto ret = log_record(driver, obj, s, op_name, etag, size, configuration, dpp, y, async_completion, log_source_bucket); ret < 0) {
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#include "include/buffer.h"
|
||||
#include "include/encoding.h"
|
||||
#include "common/async/yield_context.h"
|
||||
#include "rgw_s3_filter.h"
|
||||
|
||||
class XMLObj;
|
||||
namespace ceph { class Formatter; }
|
||||
@ -48,6 +49,14 @@ namespace rgw::bucketlogging {
|
||||
<LoggingType>Standard|Journal</LoggingType> <!-- Ceph extension -->
|
||||
<ObjectRollTime>integer</ObjectRollTime> <!-- Ceph extension -->
|
||||
<RecordsBatchSize>integer</RecordsBatchSize> <!-- Ceph extension -->
|
||||
<Filter>
|
||||
<S3Key>
|
||||
<FilterRule>
|
||||
<Name>suffix/prefix/regex</Name>
|
||||
<Value></Value>
|
||||
</FilterRule>
|
||||
</S3Key>
|
||||
</Filter>
|
||||
</LoggingEnabled>
|
||||
</BucketLoggingStatus>
|
||||
*/
|
||||
@ -78,6 +87,7 @@ struct configuration {
|
||||
PartitionDateSource date_source = PartitionDateSource::DeliveryTime;
|
||||
// EventTime: use only year, month, and day. The hour, minutes and seconds are set to 00 in the key
|
||||
// DeliveryTime: the time the log object was created
|
||||
rgw_s3_key_filter key_filter;
|
||||
bool decode_xml(XMLObj *obj);
|
||||
void dump_xml(Formatter *f) const;
|
||||
void dump(Formatter *f) const; // json
|
||||
@ -92,6 +102,9 @@ struct configuration {
|
||||
encode(static_cast<int>(logging_type), bl);
|
||||
encode(records_batch_size, bl);
|
||||
encode(static_cast<int>(date_source), bl);
|
||||
if (logging_type == LoggingType::Journal) {
|
||||
encode(key_filter, bl);
|
||||
}
|
||||
ENCODE_FINISH(bl);
|
||||
}
|
||||
|
||||
@ -108,6 +121,9 @@ struct configuration {
|
||||
decode(records_batch_size, bl);
|
||||
decode(type, bl);
|
||||
date_source = static_cast<PartitionDateSource>(type);
|
||||
if (logging_type == LoggingType::Journal) {
|
||||
decode(key_filter, bl);
|
||||
}
|
||||
DECODE_FINISH(bl);
|
||||
}
|
||||
};
|
||||
|
||||
@ -62,214 +62,6 @@ void set_event_id(std::string& id, const std::string& hash, const utime_t& ts) {
|
||||
}
|
||||
}
|
||||
|
||||
void rgw_s3_key_filter::dump(Formatter *f) const {
|
||||
if (!has_content()) {
|
||||
return;
|
||||
}
|
||||
f->open_array_section("FilterRules");
|
||||
if (!prefix_rule.empty()) {
|
||||
f->open_object_section("");
|
||||
::encode_json("Name", "prefix", f);
|
||||
::encode_json("Value", prefix_rule, f);
|
||||
f->close_section();
|
||||
}
|
||||
if (!suffix_rule.empty()) {
|
||||
f->open_object_section("");
|
||||
::encode_json("Name", "suffix", f);
|
||||
::encode_json("Value", suffix_rule, f);
|
||||
f->close_section();
|
||||
}
|
||||
if (!regex_rule.empty()) {
|
||||
f->open_object_section("");
|
||||
::encode_json("Name", "regex", f);
|
||||
::encode_json("Value", regex_rule, f);
|
||||
f->close_section();
|
||||
}
|
||||
f->close_section();
|
||||
}
|
||||
|
||||
bool rgw_s3_key_filter::decode_xml(XMLObj* obj) {
|
||||
XMLObjIter iter = obj->find("FilterRule");
|
||||
XMLObj *o;
|
||||
|
||||
const auto throw_if_missing = true;
|
||||
auto prefix_not_set = true;
|
||||
auto suffix_not_set = true;
|
||||
auto regex_not_set = true;
|
||||
std::string name;
|
||||
|
||||
while ((o = iter.get_next())) {
|
||||
RGWXMLDecoder::decode_xml("Name", name, o, throw_if_missing);
|
||||
if (name == "prefix" && prefix_not_set) {
|
||||
prefix_not_set = false;
|
||||
RGWXMLDecoder::decode_xml("Value", prefix_rule, o, throw_if_missing);
|
||||
} else if (name == "suffix" && suffix_not_set) {
|
||||
suffix_not_set = false;
|
||||
RGWXMLDecoder::decode_xml("Value", suffix_rule, o, throw_if_missing);
|
||||
} else if (name == "regex" && regex_not_set) {
|
||||
regex_not_set = false;
|
||||
RGWXMLDecoder::decode_xml("Value", regex_rule, o, throw_if_missing);
|
||||
} else {
|
||||
throw RGWXMLDecoder::err("invalid/duplicate S3Key filter rule name: '" + name + "'");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void rgw_s3_key_filter::dump_xml(Formatter *f) const {
|
||||
if (!prefix_rule.empty()) {
|
||||
f->open_object_section("FilterRule");
|
||||
::encode_xml("Name", "prefix", f);
|
||||
::encode_xml("Value", prefix_rule, f);
|
||||
f->close_section();
|
||||
}
|
||||
if (!suffix_rule.empty()) {
|
||||
f->open_object_section("FilterRule");
|
||||
::encode_xml("Name", "suffix", f);
|
||||
::encode_xml("Value", suffix_rule, f);
|
||||
f->close_section();
|
||||
}
|
||||
if (!regex_rule.empty()) {
|
||||
f->open_object_section("FilterRule");
|
||||
::encode_xml("Name", "regex", f);
|
||||
::encode_xml("Value", regex_rule, f);
|
||||
f->close_section();
|
||||
}
|
||||
}
|
||||
|
||||
bool rgw_s3_key_filter::has_content() const {
|
||||
return !(prefix_rule.empty() && suffix_rule.empty() && regex_rule.empty());
|
||||
}
|
||||
|
||||
void rgw_s3_key_value_filter::dump(Formatter *f) const {
|
||||
if (!has_content()) {
|
||||
return;
|
||||
}
|
||||
f->open_array_section("FilterRules");
|
||||
for (const auto& key_value : kv) {
|
||||
f->open_object_section("");
|
||||
::encode_json("Name", key_value.first, f);
|
||||
::encode_json("Value", key_value.second, f);
|
||||
f->close_section();
|
||||
}
|
||||
f->close_section();
|
||||
}
|
||||
|
||||
bool rgw_s3_key_value_filter::decode_xml(XMLObj* obj) {
|
||||
kv.clear();
|
||||
XMLObjIter iter = obj->find("FilterRule");
|
||||
XMLObj *o;
|
||||
|
||||
const auto throw_if_missing = true;
|
||||
|
||||
std::string key;
|
||||
std::string value;
|
||||
|
||||
while ((o = iter.get_next())) {
|
||||
RGWXMLDecoder::decode_xml("Name", key, o, throw_if_missing);
|
||||
RGWXMLDecoder::decode_xml("Value", value, o, throw_if_missing);
|
||||
kv.emplace(key, value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void rgw_s3_key_value_filter::dump_xml(Formatter *f) const {
|
||||
for (const auto& key_value : kv) {
|
||||
f->open_object_section("FilterRule");
|
||||
::encode_xml("Name", key_value.first, f);
|
||||
::encode_xml("Value", key_value.second, f);
|
||||
f->close_section();
|
||||
}
|
||||
}
|
||||
|
||||
bool rgw_s3_key_value_filter::has_content() const {
|
||||
return !kv.empty();
|
||||
}
|
||||
|
||||
void rgw_s3_filter::dump(Formatter *f) const {
|
||||
encode_json("S3Key", key_filter, f);
|
||||
encode_json("S3Metadata", metadata_filter, f);
|
||||
encode_json("S3Tags", tag_filter, f);
|
||||
}
|
||||
|
||||
bool rgw_s3_filter::decode_xml(XMLObj* obj) {
|
||||
RGWXMLDecoder::decode_xml("S3Key", key_filter, obj);
|
||||
RGWXMLDecoder::decode_xml("S3Metadata", metadata_filter, obj);
|
||||
RGWXMLDecoder::decode_xml("S3Tags", tag_filter, obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
void rgw_s3_filter::dump_xml(Formatter *f) const {
|
||||
if (key_filter.has_content()) {
|
||||
::encode_xml("S3Key", key_filter, f);
|
||||
}
|
||||
if (metadata_filter.has_content()) {
|
||||
::encode_xml("S3Metadata", metadata_filter, f);
|
||||
}
|
||||
if (tag_filter.has_content()) {
|
||||
::encode_xml("S3Tags", tag_filter, f);
|
||||
}
|
||||
}
|
||||
|
||||
bool rgw_s3_filter::has_content() const {
|
||||
return key_filter.has_content() ||
|
||||
metadata_filter.has_content() ||
|
||||
tag_filter.has_content();
|
||||
}
|
||||
|
||||
bool match(const rgw_s3_key_filter& filter, const std::string& key) {
|
||||
const auto key_size = key.size();
|
||||
const auto prefix_size = filter.prefix_rule.size();
|
||||
if (prefix_size != 0) {
|
||||
// prefix rule exists
|
||||
if (prefix_size > key_size) {
|
||||
// if prefix is longer than key, we fail
|
||||
return false;
|
||||
}
|
||||
if (!std::equal(filter.prefix_rule.begin(), filter.prefix_rule.end(), key.begin())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const auto suffix_size = filter.suffix_rule.size();
|
||||
if (suffix_size != 0) {
|
||||
// suffix rule exists
|
||||
if (suffix_size > key_size) {
|
||||
// if suffix is longer than key, we fail
|
||||
return false;
|
||||
}
|
||||
if (!std::equal(filter.suffix_rule.begin(), filter.suffix_rule.end(), (key.end() - suffix_size))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!filter.regex_rule.empty()) {
|
||||
// TODO add regex chaching in the filter
|
||||
const std::regex base_regex(filter.regex_rule);
|
||||
if (!std::regex_match(key, base_regex)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool match(const rgw_s3_key_value_filter& filter, const KeyValueMap& kv) {
|
||||
// all filter pairs must exist with the same value in the object's metadata/tags
|
||||
// object metadata/tags may include items not in the filter
|
||||
return std::includes(kv.begin(), kv.end(), filter.kv.begin(), filter.kv.end());
|
||||
}
|
||||
|
||||
bool match(const rgw_s3_key_value_filter& filter, const KeyMultiValueMap& kv) {
|
||||
// all filter pairs must exist with the same value in the object's metadata/tags
|
||||
// object metadata/tags may include items not in the filter
|
||||
for (auto& filter : filter.kv) {
|
||||
auto result = kv.equal_range(filter.first);
|
||||
if (std::any_of(result.first, result.second, [&filter](const std::pair<std::string, std::string>& p) { return p.second == filter.second;}))
|
||||
continue;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool match(const rgw::notify::EventTypeList& events, rgw::notify::EventType event) {
|
||||
// if event list exists, and none of the events in the list matches the event type, filter the message
|
||||
if (!events.empty() && std::find(events.begin(), events.end(), event) == events.end()) {
|
||||
|
||||
@ -9,94 +9,10 @@
|
||||
#include "rgw_zone.h"
|
||||
#include "rgw_notify_event_type.h"
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include "rgw_s3_filter.h"
|
||||
|
||||
class XMLObj;
|
||||
|
||||
struct rgw_s3_key_filter {
|
||||
std::string prefix_rule;
|
||||
std::string suffix_rule;
|
||||
std::string regex_rule;
|
||||
|
||||
bool has_content() const;
|
||||
|
||||
void dump(Formatter *f) const;
|
||||
bool decode_xml(XMLObj *obj);
|
||||
void dump_xml(Formatter *f) const;
|
||||
|
||||
void encode(bufferlist& bl) const {
|
||||
ENCODE_START(1, 1, bl);
|
||||
encode(prefix_rule, bl);
|
||||
encode(suffix_rule, bl);
|
||||
encode(regex_rule, bl);
|
||||
ENCODE_FINISH(bl);
|
||||
}
|
||||
|
||||
void decode(bufferlist::const_iterator& bl) {
|
||||
DECODE_START(1, bl);
|
||||
decode(prefix_rule, bl);
|
||||
decode(suffix_rule, bl);
|
||||
decode(regex_rule, bl);
|
||||
DECODE_FINISH(bl);
|
||||
}
|
||||
};
|
||||
WRITE_CLASS_ENCODER(rgw_s3_key_filter)
|
||||
|
||||
using KeyValueMap = boost::container::flat_map<std::string, std::string>;
|
||||
using KeyMultiValueMap = std::multimap<std::string, std::string>;
|
||||
|
||||
struct rgw_s3_key_value_filter {
|
||||
KeyValueMap kv;
|
||||
|
||||
bool has_content() const;
|
||||
|
||||
void dump(Formatter *f) const;
|
||||
bool decode_xml(XMLObj *obj);
|
||||
void dump_xml(Formatter *f) const;
|
||||
|
||||
void encode(bufferlist& bl) const {
|
||||
ENCODE_START(1, 1, bl);
|
||||
encode(kv, bl);
|
||||
ENCODE_FINISH(bl);
|
||||
}
|
||||
void decode(bufferlist::const_iterator& bl) {
|
||||
DECODE_START(1, bl);
|
||||
decode(kv, bl);
|
||||
DECODE_FINISH(bl);
|
||||
}
|
||||
};
|
||||
WRITE_CLASS_ENCODER(rgw_s3_key_value_filter)
|
||||
|
||||
struct rgw_s3_filter {
|
||||
rgw_s3_key_filter key_filter;
|
||||
rgw_s3_key_value_filter metadata_filter;
|
||||
rgw_s3_key_value_filter tag_filter;
|
||||
|
||||
bool has_content() const;
|
||||
|
||||
void dump(Formatter *f) const;
|
||||
bool decode_xml(XMLObj *obj);
|
||||
void dump_xml(Formatter *f) const;
|
||||
|
||||
void encode(bufferlist& bl) const {
|
||||
ENCODE_START(2, 1, bl);
|
||||
encode(key_filter, bl);
|
||||
encode(metadata_filter, bl);
|
||||
encode(tag_filter, bl);
|
||||
ENCODE_FINISH(bl);
|
||||
}
|
||||
|
||||
void decode(bufferlist::const_iterator& bl) {
|
||||
DECODE_START(2, bl);
|
||||
decode(key_filter, bl);
|
||||
decode(metadata_filter, bl);
|
||||
if (struct_v >= 2) {
|
||||
decode(tag_filter, bl);
|
||||
}
|
||||
DECODE_FINISH(bl);
|
||||
}
|
||||
};
|
||||
WRITE_CLASS_ENCODER(rgw_s3_filter)
|
||||
|
||||
using OptionalFilter = std::optional<rgw_s3_filter>;
|
||||
|
||||
struct rgw_pubsub_topic_filter;
|
||||
|
||||
269
src/rgw/rgw_s3_filter.cc
Normal file
269
src/rgw/rgw_s3_filter.cc
Normal file
@ -0,0 +1,269 @@
|
||||
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
|
||||
// vim: ts=8 sw=2 smarttab ft=cpp
|
||||
|
||||
#include "rgw_pubsub.h"
|
||||
#include "rgw_tools.h"
|
||||
#include "rgw_xml.h"
|
||||
#include "rgw_s3_filter.h"
|
||||
#include "common/errno.h"
|
||||
#include "rgw_sal.h"
|
||||
#include <regex>
|
||||
#include <algorithm>
|
||||
|
||||
void rgw_s3_key_filter::dump(Formatter *f) const {
|
||||
if (!has_content()) {
|
||||
return;
|
||||
}
|
||||
f->open_array_section("FilterRules");
|
||||
if (!prefix_rule.empty()) {
|
||||
f->open_object_section("");
|
||||
::encode_json("Name", "prefix", f);
|
||||
::encode_json("Value", prefix_rule, f);
|
||||
f->close_section();
|
||||
}
|
||||
if (!suffix_rule.empty()) {
|
||||
f->open_object_section("");
|
||||
::encode_json("Name", "suffix", f);
|
||||
::encode_json("Value", suffix_rule, f);
|
||||
f->close_section();
|
||||
}
|
||||
if (!regex_rule.empty()) {
|
||||
f->open_object_section("");
|
||||
::encode_json("Name", "regex", f);
|
||||
::encode_json("Value", regex_rule, f);
|
||||
f->close_section();
|
||||
}
|
||||
f->close_section();
|
||||
}
|
||||
|
||||
bool rgw_s3_key_filter::decode_xml(XMLObj* obj) {
|
||||
XMLObjIter iter = obj->find("FilterRule");
|
||||
XMLObj *o;
|
||||
|
||||
const auto throw_if_missing = true;
|
||||
auto prefix_not_set = true;
|
||||
auto suffix_not_set = true;
|
||||
auto regex_not_set = true;
|
||||
std::string name;
|
||||
|
||||
while ((o = iter.get_next())) {
|
||||
RGWXMLDecoder::decode_xml("Name", name, o, throw_if_missing);
|
||||
if (name == "prefix" && prefix_not_set) {
|
||||
prefix_not_set = false;
|
||||
RGWXMLDecoder::decode_xml("Value", prefix_rule, o, throw_if_missing);
|
||||
} else if (name == "suffix" && suffix_not_set) {
|
||||
suffix_not_set = false;
|
||||
RGWXMLDecoder::decode_xml("Value", suffix_rule, o, throw_if_missing);
|
||||
} else if (name == "regex" && regex_not_set) {
|
||||
regex_not_set = false;
|
||||
RGWXMLDecoder::decode_xml("Value", regex_rule, o, throw_if_missing);
|
||||
} else {
|
||||
throw RGWXMLDecoder::err("invalid/duplicate S3Key filter rule name: '" + name + "'");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void rgw_s3_key_filter::dump_xml(Formatter *f) const {
|
||||
if (!prefix_rule.empty()) {
|
||||
f->open_object_section("FilterRule");
|
||||
::encode_xml("Name", "prefix", f);
|
||||
::encode_xml("Value", prefix_rule, f);
|
||||
f->close_section();
|
||||
}
|
||||
if (!suffix_rule.empty()) {
|
||||
f->open_object_section("FilterRule");
|
||||
::encode_xml("Name", "suffix", f);
|
||||
::encode_xml("Value", suffix_rule, f);
|
||||
f->close_section();
|
||||
}
|
||||
if (!regex_rule.empty()) {
|
||||
f->open_object_section("FilterRule");
|
||||
::encode_xml("Name", "regex", f);
|
||||
::encode_xml("Value", regex_rule, f);
|
||||
f->close_section();
|
||||
}
|
||||
}
|
||||
|
||||
bool rgw_s3_key_filter::has_content() const {
|
||||
return !(prefix_rule.empty() && suffix_rule.empty() && regex_rule.empty());
|
||||
}
|
||||
|
||||
void rgw_s3_key_value_filter::dump(Formatter *f) const {
|
||||
if (!has_content()) {
|
||||
return;
|
||||
}
|
||||
f->open_array_section("FilterRules");
|
||||
for (const auto& key_value : kv) {
|
||||
f->open_object_section("");
|
||||
::encode_json("Name", key_value.first, f);
|
||||
::encode_json("Value", key_value.second, f);
|
||||
f->close_section();
|
||||
}
|
||||
f->close_section();
|
||||
}
|
||||
|
||||
bool rgw_s3_key_value_filter::decode_xml(XMLObj* obj) {
|
||||
kv.clear();
|
||||
XMLObjIter iter = obj->find("FilterRule");
|
||||
XMLObj *o;
|
||||
|
||||
const auto throw_if_missing = true;
|
||||
|
||||
std::string key;
|
||||
std::string value;
|
||||
|
||||
while ((o = iter.get_next())) {
|
||||
RGWXMLDecoder::decode_xml("Name", key, o, throw_if_missing);
|
||||
RGWXMLDecoder::decode_xml("Value", value, o, throw_if_missing);
|
||||
kv.emplace(key, value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void rgw_s3_key_value_filter::dump_xml(Formatter *f) const {
|
||||
for (const auto& key_value : kv) {
|
||||
f->open_object_section("FilterRule");
|
||||
::encode_xml("Name", key_value.first, f);
|
||||
::encode_xml("Value", key_value.second, f);
|
||||
f->close_section();
|
||||
}
|
||||
}
|
||||
|
||||
bool rgw_s3_key_value_filter::has_content() const {
|
||||
return !kv.empty();
|
||||
}
|
||||
|
||||
void rgw_s3_filter::dump(Formatter *f) const {
|
||||
encode_json("S3Key", key_filter, f);
|
||||
encode_json("S3Metadata", metadata_filter, f);
|
||||
encode_json("S3Tags", tag_filter, f);
|
||||
}
|
||||
|
||||
bool rgw_s3_filter::decode_xml(XMLObj* obj) {
|
||||
RGWXMLDecoder::decode_xml("S3Key", key_filter, obj);
|
||||
RGWXMLDecoder::decode_xml("S3Metadata", metadata_filter, obj);
|
||||
RGWXMLDecoder::decode_xml("S3Tags", tag_filter, obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
void rgw_s3_filter::dump_xml(Formatter *f) const {
|
||||
if (key_filter.has_content()) {
|
||||
::encode_xml("S3Key", key_filter, f);
|
||||
}
|
||||
if (metadata_filter.has_content()) {
|
||||
::encode_xml("S3Metadata", metadata_filter, f);
|
||||
}
|
||||
if (tag_filter.has_content()) {
|
||||
::encode_xml("S3Tags", tag_filter, f);
|
||||
}
|
||||
}
|
||||
|
||||
bool rgw_s3_filter::has_content() const {
|
||||
return key_filter.has_content() ||
|
||||
metadata_filter.has_content() ||
|
||||
tag_filter.has_content();
|
||||
}
|
||||
|
||||
bool match(const rgw_s3_key_filter& filter, const std::string& key) {
|
||||
const auto key_size = key.size();
|
||||
const auto prefix_size = filter.prefix_rule.size();
|
||||
if (prefix_size != 0) {
|
||||
// prefix rule exists
|
||||
if (prefix_size > key_size) {
|
||||
// if prefix is longer than key, we fail
|
||||
return false;
|
||||
}
|
||||
if (!std::equal(filter.prefix_rule.begin(), filter.prefix_rule.end(), key.begin())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const auto suffix_size = filter.suffix_rule.size();
|
||||
if (suffix_size != 0) {
|
||||
// suffix rule exists
|
||||
if (suffix_size > key_size) {
|
||||
// if suffix is longer than key, we fail
|
||||
return false;
|
||||
}
|
||||
if (!std::equal(filter.suffix_rule.begin(), filter.suffix_rule.end(), (key.end() - suffix_size))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!filter.regex_rule.empty()) {
|
||||
// TODO add regex caching in the filter
|
||||
const std::regex base_regex(filter.regex_rule);
|
||||
if (!std::regex_match(key, base_regex)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool match(const rgw_s3_key_value_filter& filter, const KeyValueMap& kv) {
|
||||
// all filter pairs must exist with the same value in the object's metadata/tags
|
||||
// object metadata/tags may include items not in the filter
|
||||
return std::includes(kv.begin(), kv.end(), filter.kv.begin(), filter.kv.end());
|
||||
}
|
||||
|
||||
bool match(const rgw_s3_key_value_filter& filter, const KeyMultiValueMap& kv) {
|
||||
// all filter pairs must exist with the same value in the object's metadata/tags
|
||||
// object metadata/tags may include items not in the filter
|
||||
for (auto& filter : filter.kv) {
|
||||
auto result = kv.equal_range(filter.first);
|
||||
if (std::any_of(result.first, result.second, [&filter](const std::pair<std::string, std::string>& p) { return p.second == filter.second;}))
|
||||
continue;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool match(const rgw_s3_filter& s3_filter, const rgw::sal::Object* obj) {
|
||||
if (obj == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (match(s3_filter.key_filter, obj->get_name())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto &attrs = obj->get_attrs();
|
||||
if (!s3_filter.metadata_filter.kv.empty()) {
|
||||
KeyValueMap attrs_map;
|
||||
for (auto& attr : attrs) {
|
||||
if (boost::algorithm::starts_with(attr.first, RGW_ATTR_META_PREFIX)) {
|
||||
std::string_view key(attr.first);
|
||||
key.remove_prefix(sizeof(RGW_ATTR_PREFIX)-1);
|
||||
// we want to pass a null terminated version
|
||||
// of the bufferlist, hence "to_str().c_str()"
|
||||
attrs_map.emplace(key, attr.second.to_str().c_str());
|
||||
}
|
||||
}
|
||||
if (match(s3_filter.metadata_filter, attrs_map)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!s3_filter.tag_filter.kv.empty()) {
|
||||
// tag filter exists
|
||||
// try to fetch tags from the attributes
|
||||
KeyMultiValueMap tags;
|
||||
const auto attr_iter = attrs.find(RGW_ATTR_TAGS);
|
||||
if (attr_iter != attrs.end()) {
|
||||
auto bliter = attr_iter->second.cbegin();
|
||||
RGWObjTags obj_tags;
|
||||
try {
|
||||
::decode(obj_tags, bliter);
|
||||
} catch (buffer::error &) {
|
||||
// not able to decode tags
|
||||
return false;
|
||||
}
|
||||
tags = std::move(obj_tags.get_tags());
|
||||
}
|
||||
if (match(s3_filter.tag_filter, tags)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
102
src/rgw/rgw_s3_filter.h
Normal file
102
src/rgw/rgw_s3_filter.h
Normal file
@ -0,0 +1,102 @@
|
||||
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
|
||||
// vim: ts=8 sw=2 smarttab ft=cpp
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rgw_tools.h"
|
||||
#include <boost/container/flat_map.hpp>
|
||||
|
||||
class XMLObj;
|
||||
|
||||
struct rgw_s3_key_filter {
|
||||
std::string prefix_rule;
|
||||
std::string suffix_rule;
|
||||
std::string regex_rule;
|
||||
|
||||
bool has_content() const;
|
||||
|
||||
void dump(Formatter *f) const;
|
||||
bool decode_xml(XMLObj *obj);
|
||||
void dump_xml(Formatter *f) const;
|
||||
|
||||
void encode(bufferlist& bl) const {
|
||||
ENCODE_START(1, 1, bl);
|
||||
encode(prefix_rule, bl);
|
||||
encode(suffix_rule, bl);
|
||||
encode(regex_rule, bl);
|
||||
ENCODE_FINISH(bl);
|
||||
}
|
||||
|
||||
void decode(bufferlist::const_iterator& bl) {
|
||||
DECODE_START(1, bl);
|
||||
decode(prefix_rule, bl);
|
||||
decode(suffix_rule, bl);
|
||||
decode(regex_rule, bl);
|
||||
DECODE_FINISH(bl);
|
||||
}
|
||||
};
|
||||
WRITE_CLASS_ENCODER(rgw_s3_key_filter)
|
||||
|
||||
using KeyValueMap = boost::container::flat_map<std::string, std::string>;
|
||||
using KeyMultiValueMap = std::multimap<std::string, std::string>;
|
||||
|
||||
struct rgw_s3_key_value_filter {
|
||||
KeyValueMap kv;
|
||||
|
||||
bool has_content() const;
|
||||
|
||||
void dump(Formatter *f) const;
|
||||
bool decode_xml(XMLObj *obj);
|
||||
void dump_xml(Formatter *f) const;
|
||||
|
||||
void encode(bufferlist& bl) const {
|
||||
ENCODE_START(1, 1, bl);
|
||||
encode(kv, bl);
|
||||
ENCODE_FINISH(bl);
|
||||
}
|
||||
void decode(bufferlist::const_iterator& bl) {
|
||||
DECODE_START(1, bl);
|
||||
decode(kv, bl);
|
||||
DECODE_FINISH(bl);
|
||||
}
|
||||
};
|
||||
WRITE_CLASS_ENCODER(rgw_s3_key_value_filter)
|
||||
|
||||
struct rgw_s3_filter {
|
||||
rgw_s3_key_filter key_filter;
|
||||
rgw_s3_key_value_filter metadata_filter;
|
||||
rgw_s3_key_value_filter tag_filter;
|
||||
|
||||
bool has_content() const;
|
||||
|
||||
void dump(Formatter *f) const;
|
||||
bool decode_xml(XMLObj *obj);
|
||||
void dump_xml(Formatter *f) const;
|
||||
|
||||
void encode(bufferlist& bl) const {
|
||||
ENCODE_START(2, 1, bl);
|
||||
encode(key_filter, bl);
|
||||
encode(metadata_filter, bl);
|
||||
encode(tag_filter, bl);
|
||||
ENCODE_FINISH(bl);
|
||||
}
|
||||
|
||||
void decode(bufferlist::const_iterator& bl) {
|
||||
DECODE_START(2, bl);
|
||||
decode(key_filter, bl);
|
||||
decode(metadata_filter, bl);
|
||||
if (struct_v >= 2) {
|
||||
decode(tag_filter, bl);
|
||||
}
|
||||
DECODE_FINISH(bl);
|
||||
}
|
||||
};
|
||||
WRITE_CLASS_ENCODER(rgw_s3_filter)
|
||||
|
||||
bool match(const rgw_s3_key_filter& filter, const std::string& key);
|
||||
|
||||
bool match(const rgw_s3_key_value_filter& filter, const KeyValueMap& kv);
|
||||
|
||||
bool match(const rgw_s3_key_value_filter& filter, const KeyMultiValueMap& kv);
|
||||
|
||||
bool match(const rgw_s3_filter& filter, const rgw::sal::Object* obj);
|
||||
Loading…
Reference in New Issue
Block a user