Cleanup/stabilize file_events-related APIs

This commit is contained in:
Teddy Reed 2016-02-10 20:08:34 -08:00
parent 90d2ac4c76
commit 4031e299bb
20 changed files with 681 additions and 393 deletions

View File

@ -216,6 +216,36 @@ class Config : private boost::noncopyable {
static const std::shared_ptr<ConfigParserPlugin> getParser( static const std::shared_ptr<ConfigParserPlugin> getParser(
const std::string& parser); const std::string& parser);
protected:
/**
* @brief Call the genConfig method of the config retriever plugin.
*
* This may perform a resource load such as TCP request or filesystem read.
*/
Status load();
/// A step method for Config::update.
Status updateSource(const std::string& source, const std::string& json);
/**
* @brief Generate pack content from a resource handled by the Plugin.
*
* Configuration content may set pack values to JSON strings instead of an
* embedded dictionary representing the pack content. When a string is
* encountered the config assumes this is a 'resource' handled by the Plugin.
*
* The value, or target, is sent to the ConfigPlugin via a registry request.
* The plugin response is assumed, and used, as the pack content.
*
* @param name A pack name provided and handled by the ConfigPlugin.
* @param source The config content source identifier.
* @param target A resource (path, URL, etc) handled by the ConfigPlugin.
* @return status On success the response will be JSON parsed.
*/
Status genPack(const std::string& name,
const std::string& source,
const std::string& target);
/** /**
* @brief Apply each ConfigParser to an input property tree. * @brief Apply each ConfigParser to an input property tree.
* *
@ -231,21 +261,10 @@ class Config : private boost::noncopyable {
* @param tree The input configuration tree. * @param tree The input configuration tree.
* @param pack True if the tree was built from pack data, otherwise false. * @param pack True if the tree was built from pack data, otherwise false.
*/ */
static void applyParsers(const std::string& source, void applyParsers(const std::string& source,
const boost::property_tree::ptree& tree, const boost::property_tree::ptree& tree,
bool pack = false); bool pack = false);
protected:
/**
* @brief Call the genConfig method of the config retriever plugin.
*
* This may perform a resource load such as TCP request or filesystem read.
*/
Status load();
/// A step method for Config::update.
Status updateSource(const std::string& name, const std::string& json);
/** /**
* @brief When config sources are updated the config will 'purge'. * @brief When config sources are updated the config will 'purge'.
* *
@ -257,6 +276,11 @@ class Config : private boost::noncopyable {
*/ */
void purge(); void purge();
/**
* @brief Reset the configuration state, reserved for testing only.
*/
void reset();
protected: protected:
/// Schedule of packs and their queries. /// Schedule of packs and their queries.
std::shared_ptr<Schedule> schedule_; std::shared_ptr<Schedule> schedule_;
@ -285,21 +309,14 @@ class Config : private boost::noncopyable {
friend class Initializer; friend class Initializer;
private: private:
FRIEND_TEST(ConfigTests, test_plugin_reconfigure); friend class ConfigTests;
FRIEND_TEST(ConfigTests, test_parse);
FRIEND_TEST(ConfigTests, test_remove);
FRIEND_TEST(ConfigTests, test_get_scheduled_queries);
FRIEND_TEST(ConfigTests, test_get_parser);
FRIEND_TEST(ConfigTests, test_add_remove_pack);
FRIEND_TEST(ConfigTests, test_update_clear);
FRIEND_TEST(ConfigTests, test_pack_file_paths);
FRIEND_TEST(ConfigTests, test_noninline_pack);
friend class FilePathsConfigParserPluginTests; friend class FilePathsConfigParserPluginTests;
friend class FileEventsTableTests;
FRIEND_TEST(OptionsConfigParserPluginTests, test_get_option); FRIEND_TEST(OptionsConfigParserPluginTests, test_get_option);
FRIEND_TEST(PacksTests, test_discovery_cache); FRIEND_TEST(PacksTests, test_discovery_cache);
FRIEND_TEST(SchedulerTests, test_monitor); FRIEND_TEST(SchedulerTests, test_monitor);
FRIEND_TEST(SchedulerTests, test_config_results_purge); FRIEND_TEST(SchedulerTests, test_config_results_purge);
FRIEND_TEST(EventsTests, test_event_subscriber_configure);
}; };
/** /**

View File

@ -106,8 +106,8 @@ enum EventSubscriberState {
}; };
/// Use a single placeholder for the EventContextRef passed to EventCallback. /// Use a single placeholder for the EventContextRef passed to EventCallback.
using EventCallback = std::function< using EventCallback = std::function<Status(const EventContextRef&,
Status(const EventContextRef&, const SubscriptionContextRef&)>; const SubscriptionContextRef&)>;
/// An EventPublisher must track every subscription added. /// An EventPublisher must track every subscription added.
using SubscriptionVector = std::vector<SubscriptionRef>; using SubscriptionVector = std::vector<SubscriptionRef>;
@ -171,6 +171,9 @@ struct Subscription : private boost::noncopyable {
subscription->callback = ec; subscription->callback = ec;
return subscription; return subscription;
} }
public:
Subscription() = delete;
}; };
class EventPublisherPlugin : public Plugin { class EventPublisherPlugin : public Plugin {
@ -236,10 +239,8 @@ class EventPublisherPlugin : public Plugin {
return Status(0); return Status(0);
} }
/// Remove all subscriptions. /// Remove all subscriptions from a named subscriber.
virtual void removeSubscriptions() { virtual void removeSubscriptions(const std::string& subscriber);
SubscriptionVector().swap(subscriptions_);
}
public: public:
/// Overriding the EventPublisher constructor is not recommended. /// Overriding the EventPublisher constructor is not recommended.
@ -273,7 +274,7 @@ class EventPublisherPlugin : public Plugin {
void hasStarted(bool started) { started_ = started; } void hasStarted(bool started) { started_ = started; }
/// Get the number of publisher restarts. /// Get the number of publisher restarts.
size_t restartCount() { return restart_count_; } size_t restartCount() const { return restart_count_; }
public: public:
explicit EventPublisherPlugin(EventPublisherPlugin const&) = delete; explicit EventPublisherPlugin(EventPublisherPlugin const&) = delete;
@ -320,11 +321,20 @@ class EventPublisherPlugin : public Plugin {
friend class EventFactory; friend class EventFactory;
private: private:
FRIEND_TEST(EventsTests, test_event_pub); FRIEND_TEST(EventsTests, test_event_publisher);
FRIEND_TEST(EventsTests, test_fire_event); FRIEND_TEST(EventsTests, test_fire_event);
}; };
class EventSubscriberPlugin : public Plugin { class EventSubscriberPlugin : public Plugin {
public:
/**
* @brief Add Subscription%s to the EventPublisher this module will act on.
*
* When the EventSubscriber%'s `init` method is called you are assured the
* EventPublisher has `setUp` and is ready to subscription for events.
*/
virtual Status init() { return Status(0); }
protected: protected:
/** /**
* @brief Store parsed event data from an EventCallback in a backing store. * @brief Store parsed event data from an EventCallback in a backing store.
@ -482,11 +492,24 @@ class EventSubscriberPlugin : public Plugin {
* publishers. The namespace is a combination of the publisher and subscriber * publishers. The namespace is a combination of the publisher and subscriber
* registry plugin names. * registry plugin names.
*/ */
virtual EventPublisherID dbNamespace() const = 0; /// See getType for lookup rational.
virtual EventPublisherID dbNamespace() const {
return getType() + '.' + getName();
}
/// Disable event expiration for this subscriber. /// Disable event expiration for this subscriber.
void doNotExpire() { expire_events_ = false; } void doNotExpire() { expire_events_ = false; }
/// Trampoline into the EventFactory and lookup the name of the publisher.
virtual EventPublisherID& getType() const = 0;
/// Get a handle to the EventPublisher.
EventPublisherRef getPublisher() const;
/// Remove all subscriptions from this subscriber.
void removeSubscriptions();
protected:
/// A helper value counting the number of fired events tracked by publishers. /// A helper value counting the number of fired events tracked by publishers.
EventContextID event_count_{0}; EventContextID event_count_{0};
@ -494,7 +517,7 @@ class EventSubscriberPlugin : public Plugin {
size_t subscription_count_{0}; size_t subscription_count_{0};
private: private:
Status setUp() { return Status(0, "Setup never used"); } Status setUp() override { return Status(0, "Setup never used"); }
private: private:
/// Do not respond to periodic/scheduled/triggered event expiration requests. /// Do not respond to periodic/scheduled/triggered event expiration requests.
@ -799,7 +822,7 @@ class EventPublisher : public EventPublisherPlugin {
* @param ec The event that was fired. * @param ec The event that was fired.
*/ */
void fireCallback(const SubscriptionRef& sub, void fireCallback(const SubscriptionRef& sub,
const EventContextRef& ec) const { const EventContextRef& ec) const override {
auto pub_sc = getSubscriptionContext(sub->context); auto pub_sc = getSubscriptionContext(sub->context);
auto pub_ec = getEventContext(ec); auto pub_ec = getEventContext(ec);
if (shouldFire(pub_sc, pub_ec) && sub->callback != nullptr) { if (shouldFire(pub_sc, pub_ec) && sub->callback != nullptr) {
@ -822,8 +845,8 @@ class EventPublisher : public EventPublisherPlugin {
} }
private: private:
FRIEND_TEST(EventsTests, test_event_sub_subscribe); FRIEND_TEST(EventsTests, test_event_subscriber_subscribe);
FRIEND_TEST(EventsTests, test_event_sub_context); FRIEND_TEST(EventsTests, test_event_subscriber_context);
FRIEND_TEST(EventsTests, test_fire_event); FRIEND_TEST(EventsTests, test_fire_event);
}; };
@ -847,14 +870,6 @@ class EventSubscriber : public EventSubscriberPlugin {
using ECRef = typename PUB::ECRef; using ECRef = typename PUB::ECRef;
public: public:
/**
* @brief Add Subscription%s to the EventPublisher this module will act on.
*
* When the EventSubscriber%'s `init` method is called you are assured the
* EventPublisher has `setUp` and is ready to subscription for events.
*/
virtual Status init() { return Status(0); }
/** /**
* @brief The registry plugin name for the subscriber's publisher. * @brief The registry plugin name for the subscriber's publisher.
* *
@ -862,7 +877,7 @@ class EventSubscriber : public EventSubscriberPlugin {
* plugin name assigned to publishers. The corresponding publisher name is * plugin name assigned to publishers. The corresponding publisher name is
* interpreted as the subscriber's event 'type'. * interpreted as the subscriber's event 'type'.
*/ */
virtual EventPublisherID& getType() const { virtual EventPublisherID& getType() const override {
static EventPublisherID type = EventFactory::getType<PUB>(); static EventPublisherID type = EventFactory::getType<PUB>();
return type; return type;
}; };
@ -901,16 +916,6 @@ class EventSubscriber : public EventSubscriberPlugin {
} }
} }
/// See getType for lookup rational.
virtual EventPublisherID dbNamespace() const {
return getType() + '.' + getName();
}
/// Get a handle to the EventPublisher.
EventPublisherRef getPublisher() {
return EventFactory::getEventPublisher(getType());
}
public: public:
/** /**
* @brief Request the subscriber's initialization state. * @brief Request the subscriber's initialization state.

View File

@ -456,6 +456,19 @@ class RegistryHelper : public RegistryHelperCore {
return ditems; return ditems;
} }
protected:
/**
* @brief Add an existing plugin to this registry, used for testing only.
*
* @param item A PluginType-cased registry item.
* @param item_name An identifier for this registry plugin.
* @return A success/failure status.
*/
Status add(const std::shared_ptr<RegistryType>& item) {
items_[item->getName()] = item;
return RegistryHelperCore::add(item->getName(), true);
}
public: public:
RegistryHelper(RegistryHelper const&) = delete; RegistryHelper(RegistryHelper const&) = delete;
void operator=(RegistryHelper const&) = delete; void operator=(RegistryHelper const&) = delete;
@ -463,6 +476,9 @@ class RegistryHelper : public RegistryHelperCore {
private: private:
AddExternalCallback add_; AddExternalCallback add_;
RemoveExternalCallback remove_; RemoveExternalCallback remove_;
private:
FRIEND_TEST(EventsTests, test_event_subscriber_configure);
}; };
/// Helper definition for a shared pointer to a Plugin. /// Helper definition for a shared pointer to a Plugin.
@ -782,5 +798,5 @@ class RegistryFactory : private boost::noncopyable {
* The actual plugins must add themselves to a registry type and should * The actual plugins must add themselves to a registry type and should
* implement the Plugin and RegistryType interfaces. * implement the Plugin and RegistryType interfaces.
*/ */
class Registry : public RegistryFactory {}; using Registry = RegistryFactory;
} }

View File

@ -341,14 +341,15 @@ inline void stripConfigComments(std::string& json) {
json = sink; json = sink;
} }
Status Config::updateSource(const std::string& name, const std::string& json) { Status Config::updateSource(const std::string& source,
const std::string& json) {
// Compute a 'synthesized' hash using the content before it is parsed. // Compute a 'synthesized' hash using the content before it is parsed.
hashSource(name, json); hashSource(source, json);
// Remove all packs from this source. // Remove all packs from this source.
schedule_->removeAll(name); schedule_->removeAll(source);
// Remove all files from this source. // Remove all files from this source.
removeFiles(name); removeFiles(source);
// load the config (source.second) into a pt::ptree // load the config (source.second) into a pt::ptree
pt::ptree tree; pt::ptree tree;
@ -367,7 +368,7 @@ Status Config::updateSource(const std::string& name, const std::string& json) {
auto& schedule = tree.get_child("schedule"); auto& schedule = tree.get_child("schedule");
pt::ptree main_pack; pt::ptree main_pack;
main_pack.add_child("queries", schedule); main_pack.add_child("queries", schedule);
addPack("main", name, main_pack); addPack("main", source, main_pack);
} }
if (tree.count("scheduledQueries") > 0 && !Registry::external()) { if (tree.count("scheduledQueries") > 0 && !Registry::external()) {
@ -382,7 +383,7 @@ Status Config::updateSource(const std::string& name, const std::string& json) {
} }
pt::ptree legacy_pack; pt::ptree legacy_pack;
legacy_pack.add_child("queries", queries); legacy_pack.add_child("queries", queries);
addPack("legacy_main", name, legacy_pack); addPack("legacy_main", source, legacy_pack);
} }
// extract the "packs" key into additional pack objects // extract the "packs" key into additional pack objects
@ -392,34 +393,41 @@ Status Config::updateSource(const std::string& name, const std::string& json) {
auto value = packs.get<std::string>(pack.first, ""); auto value = packs.get<std::string>(pack.first, "");
if (value.empty()) { if (value.empty()) {
// The pack is a JSON object, treat the content as pack data. // The pack is a JSON object, treat the content as pack data.
addPack(pack.first, name, pack.second); addPack(pack.first, source, pack.second);
} else { } else {
genPack(pack.first, source, value);
}
}
}
applyParsers(source, tree, false);
return Status(0, "OK");
}
Status Config::genPack(const std::string& name,
const std::string& source,
const std::string& target) {
// If the pack value is a string (and not a JSON object) then it is a // If the pack value is a string (and not a JSON object) then it is a
// resource to be handled by the config plugin. // resource to be handled by the config plugin.
PluginResponse response; PluginResponse response;
PluginRequest request = { PluginRequest request = {
{"action", "genPack"}, {"name", pack.first}, {"value", value}}; {"action", "genPack"}, {"name", name}, {"value", target}};
Registry::call("config", request, response); Registry::call("config", request, response);
if (response.size() == 0 || response[0].count(pack.first) == 0) { if (response.size() == 0 || response[0].count(name) == 0) {
continue; return Status(1, "Invalid plugin response");
} }
try { try {
pt::ptree pack_tree; pt::ptree pack_tree;
std::stringstream pack_stream; std::stringstream pack_stream;
pack_stream << response[0][pack.first]; pack_stream << response[0][name];
pt::read_json(pack_stream, pack_tree); pt::read_json(pack_stream, pack_tree);
addPack(pack.first, name, pack_tree); addPack(name, source, pack_tree);
} catch (const pt::json_parser::json_parser_error& e) { } catch (const pt::json_parser::json_parser_error& e) {
LOG(WARNING) << "Error parsing the pack JSON: " << pack.first; LOG(WARNING) << "Error parsing the pack JSON: " << name;
} }
} return Status(0);
}
}
applyParsers(name, tree, false);
return Status(0, "OK");
} }
void Config::applyParsers(const std::string& source, void Config::applyParsers(const std::string& source,
@ -560,6 +568,16 @@ void Config::purge() {
} }
} }
void Config::reset() {
schedule_ = std::make_shared<Schedule>();
std::map<std::string, QueryPerformance>().swap(performance_);
std::map<std::string, FileCategories>().swap(files_);
std::map<std::string, std::string>().swap(hash_);
valid_ = false;
loaded_ = false;
start_time_ = 0;
}
void Config::recordQueryPerformance(const std::string& name, void Config::recordQueryPerformance(const std::string& name,
size_t delay, size_t delay,
size_t size, size_t size,

View File

@ -35,10 +35,18 @@ extern void saveScheduleBlacklist(
extern void stripConfigComments(std::string& json); extern void stripConfigComments(std::string& json);
class ConfigTests : public testing::Test { class ConfigTests : public testing::Test {
public:
ConfigTests() { Config::getInstance().reset(); }
protected: protected:
void SetUp() { createMockFileStructure(); } void SetUp() { createMockFileStructure(); }
void TearDown() { tearDownMockFileStructure(); } void TearDown() { tearDownMockFileStructure(); }
protected:
Status load() { return Config::getInstance().load(); }
void setLoaded() { Config::getInstance().loaded_ = true; }
Config& get() { return Config::getInstance(); }
}; };
class TestConfigPlugin : public ConfigPlugin { class TestConfigPlugin : public ConfigPlugin {
@ -66,6 +74,7 @@ class TestConfigPlugin : public ConfigPlugin {
return Status(0, "OK"); return Status(0, "OK");
} }
public:
int genConfigCount{0}; int genConfigCount{0};
int genPackCount{0}; int genPackCount{0};
}; };
@ -83,37 +92,9 @@ TEST_F(ConfigTests, test_plugin) {
EXPECT_EQ(status.toString(), "OK"); EXPECT_EQ(status.toString(), "OK");
} }
TEST_F(ConfigTests, test_bad_config_update) { TEST_F(ConfigTests, test_invalid_content) {
std::string bad_json = "{\"options\": {},}"; std::string bad_json = "{\"options\": {},}";
ASSERT_NO_THROW(Config::getInstance().update({{"bad_source", bad_json}})); ASSERT_NO_THROW(get().update({{"bad_source", bad_json}}));
}
class PlaceboConfigParserPlugin : public ConfigParserPlugin {
public:
std::vector<std::string> keys() const override { return {}; }
Status update(const std::string&, const ParserConfig&) override {
return Status(0);
}
/// Make sure configure is called.
void configure() override { configures++; }
size_t configures{0};
};
TEST_F(ConfigTests, test_plugin_reconfigure) {
// Add a configuration plugin (could be any plugin) that will react to
// config updates.
Registry::add<PlaceboConfigParserPlugin>("config_parser", "placebo");
// Create a config that has been loaded.
Config c;
c.loaded_ = true;
c.update({{"data", "{}"}});
// Get the placebo.
auto placebo = std::static_pointer_cast<PlaceboConfigParserPlugin>(
Registry::get("config_parser", "placebo"));
EXPECT_EQ(placebo->configures, 1U);
} }
TEST_F(ConfigTests, test_strip_comments) { TEST_F(ConfigTests, test_strip_comments) {
@ -127,7 +108,132 @@ TEST_F(ConfigTests, test_strip_comments) {
EXPECT_EQ(actual, expected); EXPECT_EQ(actual, expected);
// Make sure the config update source logic applies the stripping. // Make sure the config update source logic applies the stripping.
EXPECT_TRUE(Config::getInstance().update({{"data", json_comments}})); EXPECT_TRUE(get().update({{"data", json_comments}}));
}
TEST_F(ConfigTests, test_schedule_blacklist) {
auto current_time = getUnixTime();
std::map<std::string, size_t> blacklist;
saveScheduleBlacklist(blacklist);
restoreScheduleBlacklist(blacklist);
EXPECT_EQ(blacklist.size(), 0U);
// Create some entries.
blacklist["test_1"] = current_time * 2;
blacklist["test_2"] = current_time * 3;
saveScheduleBlacklist(blacklist);
blacklist.clear();
restoreScheduleBlacklist(blacklist);
ASSERT_EQ(blacklist.count("test_1"), 1U);
ASSERT_EQ(blacklist.count("test_2"), 1U);
EXPECT_EQ(blacklist.at("test_1"), current_time * 2);
EXPECT_EQ(blacklist.at("test_2"), current_time * 3);
// Now save an expired query.
blacklist["test_1"] = 1;
saveScheduleBlacklist(blacklist);
blacklist.clear();
// When restoring, the values below the current time will not be included.
restoreScheduleBlacklist(blacklist);
EXPECT_EQ(blacklist.size(), 1U);
}
TEST_F(ConfigTests, test_pack_noninline) {
Registry::add<TestConfigPlugin>("config", "test");
// Change the active config plugin.
EXPECT_TRUE(Registry::setActive("config", "test").ok());
// Get a specialized config/test plugin.
const auto& plugin = std::dynamic_pointer_cast<TestConfigPlugin>(
Registry::get("config", "test"));
this->load();
// Expect the test plugin to have recorded 1 pack.
// This value is incremented when its genPack method is called.
EXPECT_EQ(plugin->genPackCount, 1);
int total_packs = 0;
// Expect the config to have recorded a pack for the inline and non-inline.
get().packs(
[&total_packs](const std::shared_ptr<Pack>& pack) { total_packs++; });
EXPECT_EQ(total_packs, 2);
}
TEST_F(ConfigTests, test_pack_restrictions) {
auto tree = getExamplePacksConfig();
auto packs = tree.get_child("packs");
for (const auto& pack : packs) {
get().addPack(pack.first, "", pack.second);
}
std::map<std::string, bool> results = {
{"unrestricted_pack", true},
{"discovery_pack", false},
{"fake_version_pack", false},
// Although this is a valid discovery query, there is no SQL plugin in
// the core tests.
{"valid_discovery_pack", false},
{"restricted_pack", false},
};
get().packs(([&results](std::shared_ptr<Pack>& pack) {
if (results[pack->getName()]) {
EXPECT_TRUE(pack->shouldPackExecute());
} else {
EXPECT_FALSE(pack->shouldPackExecute());
}
}));
}
TEST_F(ConfigTests, test_pack_removal) {
size_t pack_count = 0;
get().packs(([&pack_count](std::shared_ptr<Pack>& pack) { pack_count++; }));
EXPECT_EQ(pack_count, 0U);
pack_count = 0;
get().addPack("unrestricted_pack", "", getUnrestrictedPack());
get().packs(([&pack_count](std::shared_ptr<Pack>& pack) { pack_count++; }));
EXPECT_EQ(pack_count, 1U);
pack_count = 0;
get().removePack("unrestricted_pack");
get().packs(([&pack_count](std::shared_ptr<Pack>& pack) { pack_count++; }));
EXPECT_EQ(pack_count, 0U);
}
TEST_F(ConfigTests, test_content_update) {
// Read config content manually.
std::string content;
readFile(kTestDataPath + "test_parse_items.conf", content);
// Create the output of a `genConfig`.
std::map<std::string, std::string> config_data;
config_data["awesome"] = content;
// Update, then clear, packs should have been cleared.
get().update(config_data);
size_t count = 0;
auto packCounter = [&count](std::shared_ptr<Pack>& pack) { count++; };
get().packs(packCounter);
EXPECT_GT(count, 0U);
// Now clear.
config_data["awesome"] = "";
get().update(config_data);
count = 0;
get().packs(packCounter);
EXPECT_EQ(count, 0U);
}
TEST_F(ConfigTests, test_get_scheduled_queries) {
std::vector<ScheduledQuery> queries;
get().addPack("unrestricted_pack", "", getUnrestrictedPack());
get().scheduledQueries(
([&queries](const std::string&, const ScheduledQuery& query) {
queries.push_back(query);
}));
EXPECT_EQ(queries.size(), getUnrestrictedPack().get_child("queries").size());
} }
class TestConfigParserPlugin : public ConfigParserPlugin { class TestConfigParserPlugin : public ConfigParserPlugin {
@ -161,107 +267,14 @@ class TestConfigParserPlugin : public ConfigParserPlugin {
// An intermediate boolean to check parser updates. // An intermediate boolean to check parser updates.
bool TestConfigParserPlugin::update_called = false; bool TestConfigParserPlugin::update_called = false;
TEST_F(ConfigTests, test_parse) {
Config c;
auto tree = getExamplePacksConfig();
auto packs = tree.get_child("packs");
for (const auto& pack : packs) {
c.addPack(pack.first, "", pack.second);
}
std::map<std::string, bool> results = {
{"unrestricted_pack", true},
{"discovery_pack", false},
{"fake_version_pack", false},
// Although this is a valid discovery query, there is no SQL plugin in
// the core tests.
{"valid_discovery_pack", false},
{"restricted_pack", false},
};
c.packs(([&results](std::shared_ptr<Pack>& pack) {
if (results[pack->getName()]) {
EXPECT_TRUE(pack->shouldPackExecute());
} else {
EXPECT_FALSE(pack->shouldPackExecute());
}
}));
}
TEST_F(ConfigTests, test_remove) {
Config c;
c.addPack("unrestricted_pack", "", getUnrestrictedPack());
c.removePack("unrestricted_pack");
c.packs(([](std::shared_ptr<Pack>& pack) {
EXPECT_NE("unrestricted_pack", pack->getName());
}));
}
TEST_F(ConfigTests, test_add_remove_pack) {
Config c;
size_t pack_count = 0;
c.packs(([&pack_count](std::shared_ptr<Pack>& pack) { pack_count++; }));
EXPECT_EQ(pack_count, 0U);
pack_count = 0;
c.addPack("unrestricted_pack", "", getUnrestrictedPack());
c.packs(([&pack_count](std::shared_ptr<Pack>& pack) { pack_count++; }));
EXPECT_EQ(pack_count, 1U);
pack_count = 0;
c.removePack("unrestricted_pack");
c.packs(([&pack_count](std::shared_ptr<Pack>& pack) { pack_count++; }));
EXPECT_EQ(pack_count, 0U);
}
TEST_F(ConfigTests, test_update_clear) {
// Read config content manually.
std::string content;
readFile(kTestDataPath + "test_parse_items.conf", content);
// Create the output of a `genConfig`.
std::map<std::string, std::string> config_data;
config_data["awesome"] = content;
// Update, then clear, packs should have been cleared.
Config c;
c.update(config_data);
size_t count = 0;
auto packCounter = [&count](std::shared_ptr<Pack>& pack) { count++; };
c.packs(packCounter);
EXPECT_GT(count, 0U);
// Now clear.
config_data["awesome"] = "";
c.update(config_data);
count = 0;
c.packs(packCounter);
EXPECT_EQ(count, 0U);
}
TEST_F(ConfigTests, test_get_scheduled_queries) {
Config c;
std::vector<ScheduledQuery> queries;
c.addPack("unrestricted_pack", "", getUnrestrictedPack());
c.scheduledQueries(
([&queries](const std::string&, const ScheduledQuery& query) {
queries.push_back(query);
}));
EXPECT_EQ(queries.size(), getUnrestrictedPack().get_child("queries").size());
}
TEST_F(ConfigTests, test_get_parser) { TEST_F(ConfigTests, test_get_parser) {
Registry::add<TestConfigParserPlugin>("config_parser", "test"); Registry::add<TestConfigParserPlugin>("config_parser", "test");
EXPECT_TRUE(Registry::setActive("config_parser", "test").ok());
Config c; auto s = get().update(getTestConfigMap());
auto s = c.update(getTestConfigMap());
EXPECT_TRUE(s.ok()); EXPECT_TRUE(s.ok());
EXPECT_EQ(s.toString(), "OK"); EXPECT_EQ(s.toString(), "OK");
auto plugin = Config::getInstance().getParser("test"); auto plugin = get().getParser("test");
EXPECT_TRUE(plugin != nullptr); EXPECT_TRUE(plugin != nullptr);
EXPECT_TRUE(plugin.get() != nullptr); EXPECT_TRUE(plugin.get() != nullptr);
@ -273,73 +286,63 @@ TEST_F(ConfigTests, test_get_parser) {
EXPECT_EQ(data.count("dictionary"), 1U); EXPECT_EQ(data.count("dictionary"), 1U);
} }
TEST_F(ConfigTests, test_pack_file_paths) { class PlaceboConfigParserPlugin : public ConfigParserPlugin {
Config c; public:
std::vector<std::string> keys() const override { return {}; }
Status update(const std::string&, const ParserConfig&) override {
return Status(0);
}
/// Make sure configure is called.
void configure() override { configures++; }
size_t configures{0};
};
TEST_F(ConfigTests, test_plugin_reconfigure) {
// Add a configuration plugin (could be any plugin) that will react to
// config updates.
Registry::add<PlaceboConfigParserPlugin>("config_parser", "placebo");
// Create a config that has been loaded.
setLoaded();
get().update({{"data", "{}"}});
// Get the placebo.
auto placebo = std::static_pointer_cast<PlaceboConfigParserPlugin>(
Registry::get("config_parser", "placebo"));
EXPECT_EQ(placebo->configures, 1U);
}
TEST_F(ConfigTests, test_pack_file_paths) {
size_t count = 0; size_t count = 0;
auto fileCounter = auto fileCounter =
[&count](const std::string& c, const std::vector<std::string>& files) { [&count](const std::string& c, const std::vector<std::string>& files) {
count += files.size(); count += files.size();
}; };
c.addPack("unrestricted_pack", "", getUnrestrictedPack()); get().addPack("unrestricted_pack", "", getUnrestrictedPack());
Config::getInstance().files(fileCounter); get().files(fileCounter);
EXPECT_EQ(count, 7U); EXPECT_EQ(count, 2U);
c.removePack("unrestricted_pack");
count = 0; count = 0;
Config::getInstance().files(fileCounter); get().removePack("unrestricted_pack");
EXPECT_EQ(count, 5U); get().files(fileCounter);
EXPECT_EQ(count, 0U);
c.addPack("restricted_pack", "", getRestrictedPack());
count = 0; count = 0;
Config::getInstance().files(fileCounter); get().addPack("restricted_pack", "", getRestrictedPack());
EXPECT_EQ(count, 5U); get().files(fileCounter);
} EXPECT_EQ(count, 0U);
TEST_F(ConfigTests, test_noninline_pack) { // Test a more-generic update.
Registry::add<TestConfigPlugin>("config", "test"); count = 0;
get().update({{"data", "{\"file_paths\": {\"new\": [\"/new\"]}}"}});
get().files(fileCounter);
EXPECT_EQ(count, 1U);
// Change the active config plugin. count = 0;
EXPECT_TRUE(Registry::setActive("config", "test").ok()); get().update({{"data", "{}"}});
get().files(fileCounter);
const auto& plugin = std::dynamic_pointer_cast<TestConfigPlugin>( EXPECT_EQ(count, 0U);
Registry::get("config", "test"));
Config c;
c.load();
EXPECT_EQ(plugin->genPackCount, 1);
int total_packs = 0;
c.packs([&total_packs](const std::shared_ptr<Pack>& pack) { total_packs++; });
EXPECT_EQ(total_packs, 2);
}
TEST_F(ConfigTests, test_blacklist) {
auto current_time = getUnixTime();
std::map<std::string, size_t> blacklist;
saveScheduleBlacklist(blacklist);
restoreScheduleBlacklist(blacklist);
EXPECT_EQ(blacklist.size(), 0U);
// Create some entries.
blacklist["test_1"] = current_time * 2;
blacklist["test_2"] = current_time * 3;
saveScheduleBlacklist(blacklist);
blacklist.clear();
restoreScheduleBlacklist(blacklist);
ASSERT_EQ(blacklist.count("test_1"), 1U);
ASSERT_EQ(blacklist.count("test_2"), 1U);
EXPECT_EQ(blacklist.at("test_1"), current_time * 2);
EXPECT_EQ(blacklist.at("test_2"), current_time * 3);
// Now save an expired query.
blacklist["test_1"] = 1;
saveScheduleBlacklist(blacklist);
blacklist.clear();
// When restoring, the values below the current time will not be included.
restoreScheduleBlacklist(blacklist);
EXPECT_EQ(blacklist.size(), 1U);
} }
} }

View File

@ -55,6 +55,10 @@ void FSEventsSubscriptionContext::requireAction(const std::string& action) {
} }
void FSEventsEventPublisher::restart() { void FSEventsEventPublisher::restart() {
// Build paths as CFStrings
std::vector<CFStringRef> cf_paths;
{
ReadLock lock(mutex_);
if (paths_.empty()) { if (paths_.empty()) {
// There are no paths to watch. // There are no paths to watch.
paths_.insert("/dev/null"); paths_.insert("/dev/null");
@ -65,13 +69,12 @@ void FSEventsEventPublisher::restart() {
return; return;
} }
// Build paths as CFStrings
std::vector<CFStringRef> cf_paths;
for (const auto& path : paths_) { for (const auto& path : paths_) {
auto cf_path = auto cf_path = CFStringCreateWithCString(
CFStringCreateWithCString(nullptr, path.c_str(), kCFStringEncodingUTF8); nullptr, path.c_str(), kCFStringEncodingUTF8);
cf_paths.push_back(cf_path); cf_paths.push_back(cf_path);
} }
}
// The FSEvents watch takes a CFArrayRef // The FSEvents watch takes a CFArrayRef
auto watch_list = CFArrayCreate(nullptr, auto watch_list = CFArrayCreate(nullptr,
@ -144,14 +147,9 @@ void FSEventsEventPublisher::tearDown() {
run_loop_ = nullptr; run_loop_ = nullptr;
} }
void FSEventsEventPublisher::configure() { std::set<std::string> FSEventsEventPublisher::transformSubscription(
// Rebuild the watch paths. FSEventsSubscriptionContextRef& sc) const {
for (auto& sub : subscriptions_) { std::set<std::string> paths;
auto sc = getSubscriptionContext(sub->context);
if (sc->discovered_.size() > 0) {
continue;
}
sc->discovered_ = sc->path; sc->discovered_ = sc->path;
if (sc->path.find("**") != std::string::npos) { if (sc->path.find("**") != std::string::npos) {
// Double star will indicate recursive matches, restricted to endings. // Double star will indicate recursive matches, restricted to endings.
@ -172,16 +170,31 @@ void FSEventsEventPublisher::configure() {
// FSEvents needs a real path, if the wildcard is within the path then // FSEvents needs a real path, if the wildcard is within the path then
// a configure-time resolve is required. // a configure-time resolve is required.
if (sc->discovered_.find('*') != std::string::npos) { if (sc->discovered_.find('*') != std::string::npos) {
std::vector<std::string> paths; std::vector<std::string> exploded_paths;
resolveFilePattern(sc->discovered_, paths); resolveFilePattern(sc->discovered_, exploded_paths);
for (const auto& path : paths) { for (const auto& path : exploded_paths) {
paths_.insert(path); paths.insert(path);
} }
sc->recursive_match = sc->recursive; sc->recursive_match = sc->recursive;
return paths;
}
}
paths.insert(sc->discovered_);
return paths;
}
void FSEventsEventPublisher::configure() {
// Rebuild the watch paths.
{
WriteLock lock(mutex_);
for (auto& sub : subscriptions_) {
auto sc = getSubscriptionContext(sub->context);
if (sc->discovered_.size() > 0) {
continue; continue;
} }
auto paths = transformSubscription(sc);
paths_.insert(paths.begin(), paths.end());
} }
paths_.insert(sc->discovered_);
} }
restart(); restart();
@ -270,9 +283,13 @@ bool FSEventsEventPublisher::shouldFire(
return true; return true;
} }
void FSEventsEventPublisher::removeSubscriptions() { void FSEventsEventPublisher::removeSubscriptions(
const std::string& subscription) {
{
WriteLock lock(mutex_);
std::set<std::string>().swap(paths_); std::set<std::string>().swap(paths_);
EventPublisherPlugin::removeSubscriptions(); }
EventPublisherPlugin::removeSubscriptions(subscription);
} }
void FSEventsEventPublisher::flush(bool async) { void FSEventsEventPublisher::flush(bool async) {
@ -285,11 +302,11 @@ void FSEventsEventPublisher::flush(bool async) {
} }
} }
size_t FSEventsEventPublisher::numSubscriptionedPaths() { size_t FSEventsEventPublisher::numSubscriptionedPaths() const {
return paths_.size(); return paths_.size();
} }
bool FSEventsEventPublisher::isStreamRunning() { bool FSEventsEventPublisher::isStreamRunning() const {
if (stream_ == nullptr || !stream_started_ || run_loop_ == nullptr) { if (stream_ == nullptr || !stream_started_ || run_loop_ == nullptr) {
return false; return false;
} }

View File

@ -102,7 +102,7 @@ class FSEventsEventPublisher
void end() override { stop(); } void end() override { stop(); }
/// Delete all paths from prior configuration. /// Delete all paths from prior configuration.
void removeSubscriptions() override; void removeSubscriptions(const std::string& subscriber) override;
public: public:
/// FSEvents registers a client callback instead of using a select/poll loop. /// FSEvents registers a client callback instead of using a select/poll loop.
@ -114,32 +114,46 @@ class FSEventsEventPublisher
const FSEventStreamEventId fsevent_ids[]); const FSEventStreamEventId fsevent_ids[]);
public: public:
bool shouldFire(const FSEventsSubscriptionContextRef& mc, bool shouldFire(const FSEventsSubscriptionContextRef& sc,
const FSEventsEventContextRef& ec) const override; const FSEventsEventContextRef& ec) const override;
private: private:
// Restart the run loop. /// Restart the run loop.
void restart(); void restart();
// Stop the stream and the run loop. /// Stop the stream and the run loop.
void stop(); void stop();
// Cause the FSEvents to flush kernel-buffered events. /// Cause the FSEvents to flush kernel-buffered events.
void flush(bool async = false); void flush(bool async = false);
private: /**
// Check if the stream (and run loop) are running. * @brief Each subscription is 'parsed' during configuration.
bool isStreamRunning(); *
* For each subscription, FSEvents will 'parse' the requested path and
* options. Requests for recursion or path globbing will be resolved.
* The input subscription will be modified such that 'fire'-matching can
* backtrace event paths to requested subscriptions.
*
* @params subscription The mutable subscription.
* @return A set of output paths to monitor.
*/
std::set<std::string> transformSubscription(
FSEventsSubscriptionContextRef& sc) const;
// Count the number of subscriptioned paths. private:
size_t numSubscriptionedPaths(); /// Check if the stream (and run loop) are running.
bool isStreamRunning() const;
/// Count the number of subscriptioned paths.
size_t numSubscriptionedPaths() const;
private: private:
/// Local reference to the start, stop, restart event stream. /// Local reference to the start, stop, restart event stream.
FSEventStreamRef stream_{nullptr}; FSEventStreamRef stream_{nullptr};
/// Has the FSEvents run loop and stream been started. /// Has the FSEvents run loop and stream been started.
bool stream_started_{false}; std::atomic<bool> stream_started_{false};
/// Set of paths to monitor, determined by a configure step. /// Set of paths to monitor, determined by a configure step.
std::set<std::string> paths_; std::set<std::string> paths_;
@ -154,6 +168,9 @@ class FSEventsEventPublisher
/// For testing only, allow the event stream to publish its own events. /// For testing only, allow the event stream to publish its own events.
bool no_self_{true}; bool no_self_{true};
/// Access to watched path set.
mutable boost::shared_mutex mutex_;
private: private:
friend class FSEventsTests; friend class FSEventsTests;
FRIEND_TEST(FSEventsTests, test_register_event_pub); FRIEND_TEST(FSEventsTests, test_register_event_pub);

View File

@ -31,6 +31,9 @@ DECLARE_bool(verbose);
class FSEventsTests : public testing::Test { class FSEventsTests : public testing::Test {
protected: protected:
void SetUp() override { void SetUp() override {
// FSEvents will use data from the config and config parsers.
Registry::registry("config_parser")->setUp();
FLAGS_verbose = true; FLAGS_verbose = true;
trigger_path = kTestWorkingDirectory + "fsevents" + trigger_path = kTestWorkingDirectory + "fsevents" +
std::to_string(rand() % 10000 + 10000); std::to_string(rand() % 10000 + 10000);

View File

@ -512,6 +512,15 @@ Status EventSubscriberPlugin::add(Row& r, EventTime event_time) {
return status; return status;
} }
EventPublisherRef EventSubscriberPlugin::getPublisher() const {
return EventFactory::getEventPublisher(getType());
}
void EventSubscriberPlugin::removeSubscriptions() {
subscription_count_ = 0;
getPublisher()->removeSubscriptions(getName());
}
void EventFactory::delay() { void EventFactory::delay() {
// Caller may disable event publisher threads. // Caller may disable event publisher threads.
if (FLAGS_disable_events) { if (FLAGS_disable_events) {
@ -530,6 +539,16 @@ void EventFactory::delay() {
} }
} }
void EventPublisherPlugin::removeSubscriptions(const std::string& subscriber) {
auto end =
std::remove_if(subscriptions_.begin(),
subscriptions_.end(),
[&subscriber](const SubscriptionRef& subscription) {
return (subscription->subscriber_name == subscriber);
});
subscriptions_.erase(end, subscriptions_.end());
}
Status EventFactory::run(EventPublisherID& type_id) { Status EventFactory::run(EventPublisherID& type_id) {
if (FLAGS_disable_events) { if (FLAGS_disable_events) {
return Status(0, "Events disabled"); return Status(0, "Events disabled");

View File

@ -85,6 +85,7 @@ bool INotifyEventPublisher::monitorSubscription(
return true; return true;
} }
} }
if (isDirectory(sc->discovered_) && sc->discovered_.back() != '/') { if (isDirectory(sc->discovered_) && sc->discovered_.back() != '/') {
sc->path += '/'; sc->path += '/';
sc->discovered_ += '/'; sc->discovered_ += '/';
@ -117,12 +118,21 @@ Status INotifyEventPublisher::restartMonitoring() {
last_restart_ = getUnixTime(); last_restart_ = getUnixTime();
VLOG(1) << "inotify was overflown, attempting to restart handle"; VLOG(1) << "inotify was overflown, attempting to restart handle";
for (const auto& desc : descriptors_) {
// Create a copy of the descriptors, then remove each.
auto descriptors = descriptors_;
for (const auto& desc : descriptors) {
removeMonitor(desc, true); removeMonitor(desc, true);
} }
{
// Then remove all path/descriptor mappings.
WriteLock lock(mutex_);
path_descriptors_.clear(); path_descriptors_.clear();
descriptor_paths_.clear(); descriptor_paths_.clear();
}
// Reconfigure ourself, the subscribers will not reconfigure.
configure(); configure();
return Status(0, "OK"); return Status(0, "OK");
} }
@ -186,13 +196,13 @@ Status INotifyEventPublisher::run() {
} }
INotifyEventContextRef INotifyEventPublisher::createEventContextFrom( INotifyEventContextRef INotifyEventPublisher::createEventContextFrom(
struct inotify_event* event) { struct inotify_event* event) const {
auto shared_event = std::make_shared<struct inotify_event>(*event); auto shared_event = std::make_shared<struct inotify_event>(*event);
auto ec = createEventContext(); auto ec = createEventContext();
ec->event = shared_event; ec->event = shared_event;
// Get the pathname the watch fired on. // Get the pathname the watch fired on.
ec->path = descriptor_paths_[event->wd]; ec->path = descriptor_paths_.at(event->wd);
if (event->len > 1) { if (event->len > 1) {
ec->path += event->name; ec->path += event->name;
} }
@ -250,6 +260,8 @@ bool INotifyEventPublisher::addMonitor(const std::string& path,
return false; return false;
} }
{
WriteLock lock(mutex_);
// Keep a list of the watch descriptors // Keep a list of the watch descriptors
descriptors_.push_back(watch); descriptors_.push_back(watch);
// Keep a map of the path -> watch descriptor // Keep a map of the path -> watch descriptor
@ -257,6 +269,7 @@ bool INotifyEventPublisher::addMonitor(const std::string& path,
// Keep a map of the opposite (descriptor -> path) // Keep a map of the opposite (descriptor -> path)
descriptor_paths_[watch] = path; descriptor_paths_[watch] = path;
} }
}
if (recursive && isDirectory(path).ok()) { if (recursive && isDirectory(path).ok()) {
std::vector<std::string> children; std::vector<std::string> children;
@ -274,17 +287,24 @@ bool INotifyEventPublisher::addMonitor(const std::string& path,
} }
bool INotifyEventPublisher::removeMonitor(const std::string& path, bool force) { bool INotifyEventPublisher::removeMonitor(const std::string& path, bool force) {
{
ReadLock lock(mutex_);
// If force then remove from INotify, otherwise cleanup file descriptors. // If force then remove from INotify, otherwise cleanup file descriptors.
if (path_descriptors_.find(path) == path_descriptors_.end()) { if (path_descriptors_.find(path) == path_descriptors_.end()) {
return false; return false;
} }
}
int watch = path_descriptors_[path]; int watch = 0;
{
WriteLock lock(mutex_);
watch = path_descriptors_[path];
path_descriptors_.erase(path); path_descriptors_.erase(path);
descriptor_paths_.erase(watch); descriptor_paths_.erase(watch);
auto position = std::find(descriptors_.begin(), descriptors_.end(), watch); auto position = std::find(descriptors_.begin(), descriptors_.end(), watch);
descriptors_.erase(position); descriptors_.erase(position);
}
if (force) { if (force) {
::inotify_rm_watch(getHandle(), watch); ::inotify_rm_watch(getHandle(), watch);
@ -293,23 +313,27 @@ bool INotifyEventPublisher::removeMonitor(const std::string& path, bool force) {
} }
bool INotifyEventPublisher::removeMonitor(int watch, bool force) { bool INotifyEventPublisher::removeMonitor(int watch, bool force) {
std::string path;
{
ReadLock lock(mutex_);
if (descriptor_paths_.find(watch) == descriptor_paths_.end()) { if (descriptor_paths_.find(watch) == descriptor_paths_.end()) {
return false; return false;
} }
path = descriptor_paths_[watch];
auto path = descriptor_paths_[watch]; }
return removeMonitor(path, force); return removeMonitor(path, force);
} }
void INotifyEventPublisher::removeSubscriptions() { void INotifyEventPublisher::removeSubscriptions(const std::string& subscriber) {
auto paths = descriptor_paths_; auto paths = descriptor_paths_;
for (const auto& path : paths) { for (const auto& path : paths) {
removeMonitor(path.first, true); removeMonitor(path.first, true);
} }
EventPublisherPlugin::removeSubscriptions(); EventPublisherPlugin::removeSubscriptions(subscriber);
} }
bool INotifyEventPublisher::isPathMonitored(const std::string& path) { bool INotifyEventPublisher::isPathMonitored(const std::string& path) const {
ReadLock lock(mutex_);
std::string parent_path; std::string parent_path;
if (!isDirectory(path).ok()) { if (!isDirectory(path).ok()) {
if (path_descriptors_.find(path) != path_descriptors_.end()) { if (path_descriptors_.find(path) != path_descriptors_.end()) {

View File

@ -131,17 +131,18 @@ class INotifyEventPublisher
Status run() override; Status run() override;
/// Remove all monitors and subscriptions. /// Remove all monitors and subscriptions.
void removeSubscriptions() override; void removeSubscriptions(const std::string& subscriber) override;
private: private:
/// Helper/specialized event context creation. /// Helper/specialized event context creation.
INotifyEventContextRef createEventContextFrom(struct inotify_event* event); INotifyEventContextRef createEventContextFrom(
struct inotify_event* event) const;
/// Check if the application-global `inotify` handle is alive. /// Check if the application-global `inotify` handle is alive.
bool isHandleOpen() { return inotify_handle_ > 0; } bool isHandleOpen() const { return inotify_handle_ > 0; }
/// Check all added Subscription%s for a path. /// Check all added Subscription%s for a path.
bool isPathMonitored(const std::string& path); bool isPathMonitored(const std::string& path) const;
/** /**
* @brief Add an INotify watch (monitor) on this path. * @brief Add an INotify watch (monitor) on this path.
@ -176,10 +177,10 @@ class INotifyEventPublisher
const INotifyEventContextRef& ec) const override; const INotifyEventContextRef& ec) const override;
/// Get the INotify file descriptor. /// Get the INotify file descriptor.
int getHandle() { return inotify_handle_; } int getHandle() const { return inotify_handle_; }
/// Get the number of actual INotify active descriptors. /// Get the number of actual INotify active descriptors.
size_t numDescriptors() { return descriptors_.size(); } size_t numDescriptors() const { return descriptors_.size(); }
/// If we overflow, try and restart the monitor /// If we overflow, try and restart the monitor
Status restartMonitoring(); Status restartMonitoring();
@ -194,10 +195,13 @@ class INotifyEventPublisher
DescriptorPathMap descriptor_paths_; DescriptorPathMap descriptor_paths_;
/// The inotify file descriptor handle. /// The inotify file descriptor handle.
int inotify_handle_{-1}; std::atomic<int> inotify_handle_{-1};
/// Time in seconds of the last inotify restart. /// Time in seconds of the last inotify restart.
int last_restart_{-1}; std::atomic<int> last_restart_{-1};
/// Access to path and descriptor mappings.
mutable boost::shared_mutex mutex_;
public: public:
friend class INotifyTests; friend class INotifyTests;

View File

@ -32,6 +32,9 @@ const int kMaxEventLatency = 3000;
class INotifyTests : public testing::Test { class INotifyTests : public testing::Test {
protected: protected:
void SetUp() override { void SetUp() override {
// INotify will use data from the config and config parsers.
Registry::registry("config_parser")->setUp();
real_test_path = kTestWorkingDirectory + "inotify-trigger"; real_test_path = kTestWorkingDirectory + "inotify-trigger";
real_test_dir = kTestWorkingDirectory + "inotify-triggers"; real_test_dir = kTestWorkingDirectory + "inotify-triggers";
real_test_dir_path = real_test_dir + "/1"; real_test_dir_path = real_test_dir + "/1";
@ -287,7 +290,7 @@ TEST_F(INotifyTests, test_inotify_run) {
auto sub = std::make_shared<TestINotifyEventSubscriber>(); auto sub = std::make_shared<TestINotifyEventSubscriber>();
EventFactory::registerEventSubscriber(sub); EventFactory::registerEventSubscriber(sub);
// Create a subscriptioning context // Create a subscription context
auto mc = std::make_shared<INotifySubscriptionContext>(); auto mc = std::make_shared<INotifySubscriptionContext>();
mc->path = real_test_path; mc->path = real_test_path;
mc->mask = IN_ALL_EVENTS; mc->mask = IN_ALL_EVENTS;

View File

@ -12,6 +12,7 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <osquery/config.h>
#include <osquery/events.h> #include <osquery/events.h>
#include <osquery/tables.h> #include <osquery/tables.h>
@ -19,6 +20,7 @@ namespace osquery {
class EventsTests : public ::testing::Test { class EventsTests : public ::testing::Test {
public: public:
void SetUp() override { Registry::registry("config_parser")->setUp(); }
void TearDown() override { EventFactory::end(true); } void TearDown() override { EventFactory::end(true); }
}; };
@ -53,7 +55,7 @@ class AnotherFakeEventPublisher
DECLARE_PUBLISHER("AnotherFakePublisher"); DECLARE_PUBLISHER("AnotherFakePublisher");
}; };
TEST_F(EventsTests, test_event_pub) { TEST_F(EventsTests, test_event_publisher) {
auto pub = std::make_shared<FakeEventPublisher>(); auto pub = std::make_shared<FakeEventPublisher>();
EXPECT_EQ(pub->type(), "FakePublisher"); EXPECT_EQ(pub->type(), "FakePublisher");
@ -62,7 +64,7 @@ TEST_F(EventsTests, test_event_pub) {
EXPECT_EQ(typeid(FakeSubscriptionContext), typeid(*pub_sub)); EXPECT_EQ(typeid(FakeSubscriptionContext), typeid(*pub_sub));
} }
TEST_F(EventsTests, test_register_event_pub) { TEST_F(EventsTests, test_register_event_publisher) {
auto basic_pub = std::make_shared<BasicEventPublisher>(); auto basic_pub = std::make_shared<BasicEventPublisher>();
auto status = EventFactory::registerEventPublisher(basic_pub); auto status = EventFactory::registerEventPublisher(basic_pub);
@ -82,7 +84,7 @@ TEST_F(EventsTests, test_register_event_pub) {
EXPECT_TRUE(status.ok()); EXPECT_TRUE(status.ok());
} }
TEST_F(EventsTests, test_event_pub_types) { TEST_F(EventsTests, test_event_publisher_types) {
auto pub = std::make_shared<FakeEventPublisher>(); auto pub = std::make_shared<FakeEventPublisher>();
EXPECT_EQ(pub->type(), "FakePublisher"); EXPECT_EQ(pub->type(), "FakePublisher");
@ -91,7 +93,7 @@ TEST_F(EventsTests, test_event_pub_types) {
EXPECT_EQ(pub->type(), pub2->type()); EXPECT_EQ(pub->type(), pub2->type());
} }
TEST_F(EventsTests, test_create_event_pub) { TEST_F(EventsTests, test_duplicate_event_publisher) {
auto pub = std::make_shared<BasicEventPublisher>(); auto pub = std::make_shared<BasicEventPublisher>();
auto status = EventFactory::registerEventPublisher(pub); auto status = EventFactory::registerEventPublisher(pub);
EXPECT_TRUE(status.ok()); EXPECT_TRUE(status.ok());
@ -196,7 +198,7 @@ class TestEventPublisher
int smallest_ever_{0}; int smallest_ever_{0};
}; };
TEST_F(EventsTests, test_create_custom_event_pub) { TEST_F(EventsTests, test_create_custom_event_publisher) {
auto basic_pub = std::make_shared<BasicEventPublisher>(); auto basic_pub = std::make_shared<BasicEventPublisher>();
EventFactory::registerEventPublisher(basic_pub); EventFactory::registerEventPublisher(basic_pub);
auto pub = std::make_shared<TestEventPublisher>(); auto pub = std::make_shared<TestEventPublisher>();
@ -267,9 +269,12 @@ class FakeEventSubscriber : public EventSubscriber<FakeEventPublisher> {
bool bellHathTolled{false}; bool bellHathTolled{false};
bool contextBellHathTolled{false}; bool contextBellHathTolled{false};
bool shouldFireBethHathTolled{false}; bool shouldFireBethHathTolled{false};
size_t timesConfigured{0};
FakeEventSubscriber() { setName("FakeSubscriber"); } FakeEventSubscriber() { setName("FakeSubscriber"); }
void configure() override { timesConfigured++; }
Status Callback(const ECRef& ec, const SCRef& sc) { Status Callback(const ECRef& ec, const SCRef& sc) {
// We don't care about the subscription or the event contexts. // We don't care about the subscription or the event contexts.
bellHathTolled = true; bellHathTolled = true;
@ -299,13 +304,13 @@ class FakeEventSubscriber : public EventSubscriber<FakeEventPublisher> {
FRIEND_TEST(EventsTests, test_subscriber_names); FRIEND_TEST(EventsTests, test_subscriber_names);
}; };
TEST_F(EventsTests, test_event_sub) { TEST_F(EventsTests, test_event_subscriber) {
auto sub = std::make_shared<FakeEventSubscriber>(); auto sub = std::make_shared<FakeEventSubscriber>();
EXPECT_EQ(sub->getType(), "FakePublisher"); EXPECT_EQ(sub->getType(), "FakePublisher");
EXPECT_EQ(sub->getName(), "FakeSubscriber"); EXPECT_EQ(sub->getName(), "FakeSubscriber");
} }
TEST_F(EventsTests, test_event_sub_subscribe) { TEST_F(EventsTests, test_event_subscriber_subscribe) {
auto pub = std::make_shared<FakeEventPublisher>(); auto pub = std::make_shared<FakeEventPublisher>();
EventFactory::registerEventPublisher(pub); EventFactory::registerEventPublisher(pub);
@ -323,7 +328,7 @@ TEST_F(EventsTests, test_event_sub_subscribe) {
EXPECT_TRUE(sub->bellHathTolled); EXPECT_TRUE(sub->bellHathTolled);
} }
TEST_F(EventsTests, test_event_sub_context) { TEST_F(EventsTests, test_event_subscriber_context) {
auto pub = std::make_shared<FakeEventPublisher>(); auto pub = std::make_shared<FakeEventPublisher>();
EventFactory::registerEventPublisher(pub); EventFactory::registerEventPublisher(pub);
@ -339,6 +344,26 @@ TEST_F(EventsTests, test_event_sub_context) {
EXPECT_TRUE(sub->contextBellHathTolled); EXPECT_TRUE(sub->contextBellHathTolled);
} }
TEST_F(EventsTests, test_event_subscriber_configure) {
auto sub = std::make_shared<FakeEventSubscriber>();
EventFactory::registerEventSubscriber(sub);
// Register this subscriber (within the RegistryFactory), so it receives
// configure/reconfigure events.
auto registry = Registry::registry("event_subscriber");
registry->add(sub);
// Assure we start from a base state.
EXPECT_EQ(sub->timesConfigured, 0U);
// Force the config into a loaded state.
Config::getInstance().loaded_ = true;
Config::getInstance().update({{"data", "{}"}});
EXPECT_EQ(sub->timesConfigured, 1U);
registry->remove(sub->getName());
Config::getInstance().update({{"data", "{}"}});
EXPECT_EQ(sub->timesConfigured, 1U);
}
TEST_F(EventsTests, test_fire_event) { TEST_F(EventsTests, test_fire_event) {
Status status; Status status;

View File

@ -188,7 +188,7 @@ void RegistryHelperCore::setUp() {
} }
void RegistryHelperCore::configure() { void RegistryHelperCore::configure() {
if (active_.size() != 0 && exists(active_, true)) { if (!active_.empty() && exists(active_, true)) {
items_.at(active_)->configure(); items_.at(active_)->configure();
} else { } else {
for (auto& item : items_) { for (auto& item : items_) {

View File

@ -62,8 +62,7 @@ REGISTER(FileEventSubscriber, "event_subscriber", "file_events");
void FileEventSubscriber::configure() { void FileEventSubscriber::configure() {
// Clear all paths from FSEvents. // Clear all paths from FSEvents.
// There may be a better way to find the set intersection/difference. // There may be a better way to find the set intersection/difference.
auto pub = getPublisher(); removeSubscriptions();
pub->removeSubscriptions();
Config::getInstance().files([this](const std::string& category, Config::getInstance().files([this](const std::string& category,
const std::vector<std::string>& files) { const std::vector<std::string>& files) {

View File

@ -59,8 +59,7 @@ REGISTER(FileEventSubscriber, "event_subscriber", "file_events");
void FileEventSubscriber::configure() { void FileEventSubscriber::configure() {
// Clear all monitors from INotify. // Clear all monitors from INotify.
// There may be a better way to find the set intersection/difference. // There may be a better way to find the set intersection/difference.
auto pub = getPublisher(); removeSubscriptions();
pub->removeSubscriptions();
auto parser = Config::getParser("file_paths"); auto parser = Config::getParser("file_paths");
auto& accesses = parser->getData().get_child("file_accesses"); auto& accesses = parser->getData().get_child("file_accesses");
@ -95,8 +94,8 @@ Status FileEventSubscriber::Callback(const ECRef& ec, const SCRef& sc) {
if ((sc->mask & kFileAccessMasks) != kFileAccessMasks) { if ((sc->mask & kFileAccessMasks) != kFileAccessMasks) {
// Add hashing and 'join' against the file table for stat-information. // Add hashing and 'join' against the file table for stat-information.
decorateFileEvent( decorateFileEvent(ec->path,
ec->path, (ec->action == "CREATED" || ec->action == "UPDATED"), r); (ec->action == "CREATED" || ec->action == "UPDATED"), r);
} else { } else {
// The access event on Linux would generate additional events if stated. // The access event on Linux would generate additional events if stated.
for (const auto& column : kCommonFileColumns) { for (const auto& column : kCommonFileColumns) {

View File

@ -40,10 +40,8 @@ class ProcessFileEventSubscriber
REGISTER(ProcessFileEventSubscriber, "event_subscriber", "process_file_events"); REGISTER(ProcessFileEventSubscriber, "event_subscriber", "process_file_events");
void ProcessFileEventSubscriber::configure() { void ProcessFileEventSubscriber::configure() {
// There may be a better way to find the set intersection/difference. // There may be a better way to find the set intersection/difference.
auto pub = getPublisher(); removeSubscriptions();
pub->removeSubscriptions();
Config::getInstance().files([this](const std::string &category, Config::getInstance().files([this](const std::string &category,
const std::vector<std::string> &files) { const std::vector<std::string> &files) {

View File

@ -0,0 +1,106 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#include <gtest/gtest.h>
#include <boost/property_tree/json_parser.hpp>
#include <osquery/config.h>
#include <osquery/events.h>
#include <osquery/flags.h>
#include <osquery/logger.h>
#include <osquery/registry.h>
#include <osquery/sql.h>
#include "osquery/core/test_util.h"
#include "osquery/tables/events/event_utils.h"
namespace osquery {
DECLARE_bool(registry_exceptions);
class FileEventSubscriber;
class FileEventsTableTests : public testing::Test {
public:
void SetUp() override {
Config::getInstance().reset();
// Promote registry access exceptions when testing tables and SQL.
exceptions_ = FLAGS_registry_exceptions;
FLAGS_registry_exceptions = true;
// Setup configuration parsers for file paths accesses.
Registry::registry("config_parser")->setUp();
}
void TearDown() override { FLAGS_registry_exceptions = exceptions_; }
protected:
Status load() { return Config::getInstance().load(); }
private:
bool exceptions_{false};
};
TEST_F(FileEventsTableTests, test_subscriber_exists) {
ASSERT_TRUE(Registry::exists("event_subscriber", "file_events"));
// Note: do not perform a reinterpret cast like this.
auto plugin = Registry::get("event_subscriber", "file_events");
auto* subscriber =
reinterpret_cast<std::shared_ptr<FileEventSubscriber>*>(&plugin);
EXPECT_NE(subscriber, nullptr);
}
TEST_F(FileEventsTableTests, test_table_empty) {
// Attach/create the publishers.
attachEvents();
auto results = SQL::selectAllFrom("file_events");
EXPECT_EQ(results.size(), 0U);
}
class FileEventsTestsConfigPlugin : public ConfigPlugin {
public:
Status genConfig(std::map<std::string, std::string>& config) override {
std::stringstream ss;
pt::write_json(ss, getUnrestrictedPack(), false);
config["data"] = ss.str();
return Status(0);
}
};
TEST_F(FileEventsTableTests, test_configure_subscriptions) {
// Attach/create the publishers.
attachEvents();
// Load a configuration with file paths, verify subscriptions.
Registry::add<FileEventsTestsConfigPlugin>("config", "file_events_tests");
Registry::setActive("config", "file_events_tests");
this->load();
// Explicitly request a configure for subscribers.
Registry::registry("event_subscriber")->configure();
std::string q = "select * from osquery_events where name = 'file_events'";
auto results = SQL::SQL(q);
ASSERT_EQ(results.rows().size(), 1U);
auto& row = results.rows()[0];
// Expect the paths within "unrestricted_pack" to be created as subscriptions.
EXPECT_EQ(row.at("subscriptions"), "2");
// The most important part, make sure a reconfigure removes the subscriptions.
Config::getInstance().update({{"data", "{}"}});
results = SQL::SQL(q);
auto& row2 = results.rows()[0];
EXPECT_EQ(row2.at("subscriptions"), "0");
}
}

View File

@ -50,7 +50,12 @@ using FileSubscriptionContextRef = INotifySubscriptionContextRef;
*/ */
class YARAEventSubscriber : public FileEventSubscriber { class YARAEventSubscriber : public FileEventSubscriber {
public: public:
Status init() override; Status init() override {
configure();
return Status(0);
}
void configure() override;
private: private:
/** /**
@ -74,23 +79,33 @@ class YARAEventSubscriber : public FileEventSubscriber {
*/ */
REGISTER(YARAEventSubscriber, "event_subscriber", "yara_events"); REGISTER(YARAEventSubscriber, "event_subscriber", "yara_events");
Status YARAEventSubscriber::init() { void YARAEventSubscriber::configure() {
Status status; removeSubscriptions();
// There is a special yara parser that tracks the related top-level keys.
auto plugin = Config::getParser("yara"); auto plugin = Config::getParser("yara");
if (plugin == nullptr || plugin.get() == nullptr) { if (plugin == nullptr || plugin.get() == nullptr) {
return Status(1, "Could not get yara config parser"); return;
} }
// Bail if there is no configured set of opt-in paths for yara.
const auto& yara_config = plugin->getData(); const auto& yara_config = plugin->getData();
if (yara_config.count("file_paths") == 0) { if (yara_config.count("file_paths") == 0) {
return Status(0, "OK"); return;
} }
const auto& yara_paths = yara_config.get_child("file_paths");
// Collect the set of paths, we are mostly concerned with the categories.
// But the subscriber must duplicate the set of subscriptions such that the
// publisher's 'fire'-matching logic routes related events to our callback.
std::map<std::string, std::vector<std::string> > file_map; std::map<std::string, std::vector<std::string> > file_map;
Config::getInstance().files([&file_map]( Config::getInstance().files([&file_map](
const std::string& category, const std::string& category, const std::vector<std::string>& files) {
const std::vector<std::string>& files) { file_map[category] = files; }); file_map[category] = files;
});
// For each category within yara's file_paths, add a subscription to the
// corresponding set of paths.
const auto& yara_paths = yara_config.get_child("file_paths");
for (const auto& yara_path_element : yara_paths) { for (const auto& yara_path_element : yara_paths) {
// Subscribe to each file for the given key (category). // Subscribe to each file for the given key (category).
if (file_map.count(yara_path_element.first) == 0) { if (file_map.count(yara_path_element.first) == 0) {
@ -102,15 +117,13 @@ Status YARAEventSubscriber::init() {
for (const auto& file : file_map.at(yara_path_element.first)) { for (const auto& file : file_map.at(yara_path_element.first)) {
VLOG(1) << "Added YARA listener to: " << file; VLOG(1) << "Added YARA listener to: " << file;
auto sc = createSubscriptionContext(); auto sc = createSubscriptionContext();
sc->recursive = 0;
sc->path = file; sc->path = file;
sc->mask = FILE_CHANGE_MASK; sc->mask = FILE_CHANGE_MASK;
sc->recursive = true;
sc->category = yara_path_element.first; sc->category = yara_path_element.first;
subscribe(&YARAEventSubscriber::Callback, sc); subscribe(&YARAEventSubscriber::Callback, sc);
} }
} }
return Status(0, "OK");
} }
Status YARAEventSubscriber::Callback(const FileEventContextRef& ec, Status YARAEventSubscriber::Callback(const FileEventContextRef& ec,

View File

@ -1,6 +1,8 @@
{ {
"packs": { "packs": {
// This pack is "non-inline", meaning it should trigger genPack.
"tester": "lester", "tester": "lester",
// This pack is "inlined", the content is a JSON dictionary.
"foobar": { "foobar": {
"version": "1.5.0", "version": "1.5.0",
"queries": { "queries": {