From 5cd3154b64d61af95283960e4fdd2b7fb9765f5a Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sun, 14 Dec 2014 16:42:25 +0100 Subject: [PATCH] add possibility to disable/enable rules Fixes #58 --- server/guhcore.cpp | 14 ++- server/guhcore.h | 4 +- server/jsonrpc/jsonhandler.cpp | 7 ++ server/jsonrpc/jsonhandler.h | 1 + server/jsonrpc/jsonrpcserver.cpp | 2 +- server/jsonrpc/jsontypes.cpp | 2 + server/jsonrpc/ruleshandler.cpp | 32 ++++++- server/jsonrpc/ruleshandler.h | 2 + server/rule.cpp | 12 +++ server/rule.h | 5 + server/ruleengine.cpp | 60 +++++++++++- server/ruleengine.h | 8 +- tests/auto/api.json | 24 ++++- tests/auto/rules/testrules.cpp | 152 ++++++++++++++++++++++++++++++- 14 files changed, 307 insertions(+), 18 deletions(-) diff --git a/server/guhcore.cpp b/server/guhcore.cpp index debf8da5..63415d3f 100644 --- a/server/guhcore.cpp +++ b/server/guhcore.cpp @@ -183,9 +183,9 @@ Rule GuhCore::findRule(const RuleId &ruleId) return m_ruleEngine->findRule(ruleId); } -RuleEngine::RuleError GuhCore::addRule(const RuleId &id, const QList &eventDescriptorList, const QList &actionList) +RuleEngine::RuleError GuhCore::addRule(const RuleId &id, const QList &eventDescriptorList, const QList &actionList, bool enabled) { - return m_ruleEngine->addRule(id, eventDescriptorList, actionList); + return m_ruleEngine->addRule(id, eventDescriptorList, actionList, enabled); } RuleEngine::RuleError GuhCore::removeRule(const RuleId &id) @@ -198,6 +198,16 @@ QList GuhCore::findRules(const DeviceId &deviceId) return m_ruleEngine->findRules(deviceId); } +RuleEngine::RuleError GuhCore::enableRule(const RuleId &ruleId) +{ + return m_ruleEngine->enableRule(ruleId); +} + +RuleEngine::RuleError GuhCore::disableRule(const RuleId &ruleId) +{ + return m_ruleEngine->disableRule(ruleId); +} + /*! Returns a pointer to the \l{DeviceManager} instance owned by GuhCore.*/ DeviceManager *GuhCore::deviceManager() const { diff --git a/server/guhcore.h b/server/guhcore.h index cb74798b..2f20e058 100644 --- a/server/guhcore.h +++ b/server/guhcore.h @@ -66,9 +66,11 @@ public: QList rules() const; QList ruleIds() const; Rule findRule(const RuleId &ruleId); - RuleEngine::RuleError addRule(const RuleId &id, const QList &eventDescriptorList, const QList &actionList); + RuleEngine::RuleError addRule(const RuleId &id, const QList &eventDescriptorList, const QList &actionList, bool enabled = true); RuleEngine::RuleError removeRule(const RuleId &id); QList findRules(const DeviceId &deviceId); + RuleEngine::RuleError enableRule(const RuleId &ruleId); + RuleEngine::RuleError disableRule(const RuleId &ruleId); signals: void eventTriggered(const Event &event); diff --git a/server/jsonrpc/jsonhandler.cpp b/server/jsonrpc/jsonhandler.cpp index 73f5a149..f2cfa313 100644 --- a/server/jsonrpc/jsonhandler.cpp +++ b/server/jsonrpc/jsonhandler.cpp @@ -139,6 +139,13 @@ QVariantMap JsonHandler::statusToReply(DeviceManager::DeviceError status) const return returns; } +QVariantMap JsonHandler::statusToReply(RuleEngine::RuleError status) const +{ + QVariantMap returns; + returns.insert("ruleError", JsonTypes::ruleErrorToString(status)); + return returns; +} + JsonReply::JsonReply(Type type, JsonHandler *handler, const QString &method, const QVariantMap &data): m_type(type), diff --git a/server/jsonrpc/jsonhandler.h b/server/jsonrpc/jsonhandler.h index 05c02cb4..d91104e4 100644 --- a/server/jsonrpc/jsonhandler.h +++ b/server/jsonrpc/jsonhandler.h @@ -104,6 +104,7 @@ protected: JsonReply *createReply(const QVariantMap &data) const; JsonReply *createAsyncReply(const QString &method) const; QVariantMap statusToReply(DeviceManager::DeviceError status) const; + QVariantMap statusToReply(RuleEngine::RuleError status) const; private: QHash m_descriptions; diff --git a/server/jsonrpc/jsonrpcserver.cpp b/server/jsonrpc/jsonrpcserver.cpp index 47252391..dca17caf 100644 --- a/server/jsonrpc/jsonrpcserver.cpp +++ b/server/jsonrpc/jsonrpcserver.cpp @@ -42,7 +42,7 @@ #include #include -#define JSON_PROTOCOL_VERSION 9 +#define JSON_PROTOCOL_VERSION 10 JsonRPCServer::JsonRPCServer(QObject *parent): JsonHandler(parent), diff --git a/server/jsonrpc/jsontypes.cpp b/server/jsonrpc/jsontypes.cpp index 98596869..3402295d 100644 --- a/server/jsonrpc/jsontypes.cpp +++ b/server/jsonrpc/jsontypes.cpp @@ -169,6 +169,7 @@ void JsonTypes::init() // Rule s_rule.insert("id", basicTypeToString(Uuid)); + s_rule.insert("enabled", basicTypeToString(Bool)); s_rule.insert("eventDescriptors", QVariantList() << eventDescriptorRef()); s_rule.insert("actions", QVariantList() << actionRef()); s_rule.insert("stateEvaluator", stateEvaluatorRef()); @@ -447,6 +448,7 @@ QVariantMap JsonTypes::packRule(const Rule &rule) { QVariantMap ruleMap; ruleMap.insert("id", rule.id()); + ruleMap.insert("enabled", rule.enabled()); QVariantList eventDescriptorList; foreach (const EventDescriptor &eventDescriptor, rule.eventDescriptors()) { eventDescriptorList.append(JsonTypes::packEventDescriptor(eventDescriptor)); diff --git a/server/jsonrpc/ruleshandler.cpp b/server/jsonrpc/ruleshandler.cpp index 2188b084..32700a9b 100644 --- a/server/jsonrpc/ruleshandler.cpp +++ b/server/jsonrpc/ruleshandler.cpp @@ -44,10 +44,12 @@ RulesHandler::RulesHandler(QObject *parent) : params.clear(); returns.clear(); setDescription("AddRule", "Add a rule. You can describe rules by one or many EventDesciptors and a StateEvaluator. Note that only" - "one of either eventDescriptor or eventDescriptorList may be passed at a time."); + "one of either eventDescriptor or eventDescriptorList may be passed at a time. A rule can be created but left disabled," + "meaning it won't actually be executed until set to enabled. If not given, enabled defaults to true."); params.insert("o:eventDescriptor", JsonTypes::eventDescriptorRef()); params.insert("o:eventDescriptorList", QVariantList() << JsonTypes::eventDescriptorRef()); params.insert("o:stateEvaluator", JsonTypes::stateEvaluatorRef()); + params.insert("o:enabled", JsonTypes::basicTypeToString(JsonTypes::Bool)); QVariantList actions; actions.append(JsonTypes::actionRef()); params.insert("actions", actions); @@ -69,6 +71,20 @@ RulesHandler::RulesHandler(QObject *parent) : setParams("FindRules", params); returns.insert("ruleIds", QVariantList() << JsonTypes::basicTypeToString(JsonTypes::Uuid)); setReturns("FindRules", returns); + + params.clear(); returns.clear(); + setDescription("EnableRule", "Enabled a rule that has previously been disabled."); + params.insert("ruleId", JsonTypes::basicTypeToString(JsonTypes::Uuid)); + setParams("EnableRule", params); + returns.insert("ruleError", JsonTypes::ruleErrorRef()); + setReturns("EnableRule", returns); + + params.clear(); returns.clear(); + setDescription("DisableRule", "Disable a rule. The rule won't be triggered by it's events or state changes while it is disabled."); + params.insert("ruleId", JsonTypes::basicTypeToString(JsonTypes::Uuid)); + setParams("DisableRule", params); + returns.insert("ruleError", JsonTypes::ruleErrorRef()); + setReturns("DisableRule", returns); } QString RulesHandler::name() const @@ -136,8 +152,10 @@ JsonReply* RulesHandler::AddRule(const QVariantMap ¶ms) return createReply(returns); } + bool enabled = params.value("enabled", true).toBool(); + RuleId newRuleId = RuleId::createRuleId(); - RuleEngine::RuleError status = GuhCore::instance()->addRule(newRuleId, eventDescriptorList, actions); + RuleEngine::RuleError status = GuhCore::instance()->addRule(newRuleId, eventDescriptorList, actions, enabled); if (status == RuleEngine::RuleErrorNoError) { returns.insert("ruleId", newRuleId.toString()); } @@ -168,3 +186,13 @@ JsonReply *RulesHandler::FindRules(const QVariantMap ¶ms) returns.insert("ruleIds", rulesList); return createReply(returns); } + +JsonReply *RulesHandler::EnableRule(const QVariantMap ¶ms) +{ + return createReply(statusToReply(GuhCore::instance()->enableRule(RuleId(params.value("ruleId").toString())))); +} + +JsonReply *RulesHandler::DisableRule(const QVariantMap ¶ms) +{ + return createReply(statusToReply(GuhCore::instance()->disableRule(RuleId(params.value("ruleId").toString())))); +} diff --git a/server/jsonrpc/ruleshandler.h b/server/jsonrpc/ruleshandler.h index c1fa9c95..c0d1baca 100644 --- a/server/jsonrpc/ruleshandler.h +++ b/server/jsonrpc/ruleshandler.h @@ -36,6 +36,8 @@ public: Q_INVOKABLE JsonReply* RemoveRule(const QVariantMap ¶ms); Q_INVOKABLE JsonReply* FindRules(const QVariantMap ¶ms); + Q_INVOKABLE JsonReply* EnableRule(const QVariantMap ¶ms); + Q_INVOKABLE JsonReply* DisableRule(const QVariantMap ¶ms); }; #endif // RULESHANDLER_H diff --git a/server/rule.cpp b/server/rule.cpp index b885f987..2dffd1a3 100644 --- a/server/rule.cpp +++ b/server/rule.cpp @@ -86,3 +86,15 @@ QList Rule::actions() const { return m_actions; } + +/*! Returns wheter the rule is enabled or not. */ +bool Rule::enabled() const { + return m_enabled; +} + +/*! Set the disabled flag of this rule. In order to actually disable the rule you still need to + * update the RulesEngine */ +void Rule::setEnabled(bool enabled) +{ + m_enabled = enabled; +} diff --git a/server/rule.h b/server/rule.h index c40fb32d..e3c7ca3d 100644 --- a/server/rule.h +++ b/server/rule.h @@ -37,11 +37,16 @@ public: StateEvaluator stateEvaluator() const; QList actions() const; + bool enabled() const; + void setEnabled(bool enabled); + private: RuleId m_id; QList m_eventDescriptors; StateEvaluator m_stateEvaluator; QList m_actions; + + bool m_enabled; }; #endif // RULE_H diff --git a/server/ruleengine.cpp b/server/ruleengine.cpp index c90f6605..8dcd4c6e 100644 --- a/server/ruleengine.cpp +++ b/server/ruleengine.cpp @@ -79,6 +79,8 @@ RuleEngine::RuleEngine(QObject *parent) : settings.beginGroup(idString); + bool enabled = settings.value("enabled", true).toBool(); + QList eventDescriptorList; settings.beginGroup("events"); @@ -133,6 +135,7 @@ RuleEngine::RuleEngine(QObject *parent) : settings.endGroup(); Rule rule = Rule(RuleId(idString), eventDescriptorList, stateEvaluator, actions); + rule.setEnabled(enabled); appendRule(rule); } @@ -151,6 +154,11 @@ QList RuleEngine::evaluateEvent(const Event &event) QList actions; foreach (const RuleId &id, m_ruleIds) { Rule rule = m_rules.value(id); + if (!rule.enabled()) { + qDebug() << "Not triggering rule because it is disabled:" << rule.id(); + continue; + } + if (containsEvent(rule, event)) { if (rule.stateEvaluator().evaluate()) { qDebug() << "states matching!"; @@ -164,13 +172,13 @@ QList RuleEngine::evaluateEvent(const Event &event) /*! Add a new \l{Rule} with the given \a EventDescriptorList and \a actions to the engine. For convenience, this creates a Rule without any \l{State} comparison. */ -RuleEngine::RuleError RuleEngine::addRule(const RuleId &ruleId, const QList &eventDescriptorList, const QList &actions) +RuleEngine::RuleError RuleEngine::addRule(const RuleId &ruleId, const QList &eventDescriptorList, const QList &actions, bool enabled) { - return addRule(ruleId, eventDescriptorList, StateEvaluator(), actions); + return addRule(ruleId, eventDescriptorList, StateEvaluator(), actions, enabled); } /*! Add a new \l{Rule} with the given \a event, \a states and \a actions to the engine. */ -RuleEngine::RuleError RuleEngine::addRule(const RuleId &ruleId, const QList &eventDescriptorList, const StateEvaluator &stateEvaluator, const QList &actions) +RuleEngine::RuleError RuleEngine::addRule(const RuleId &ruleId, const QList &eventDescriptorList, const StateEvaluator &stateEvaluator, const QList &actions, bool enabled) { if (ruleId.isNull()) { return RuleErrorInvalidRuleId; @@ -220,11 +228,13 @@ RuleEngine::RuleError RuleEngine::addRule(const RuleId &ruleId, const QList evaluateEvent(const Event &event); - RuleError addRule(const RuleId &ruleId, const QList &eventDescriptorList, const QList &actions); - RuleError addRule(const RuleId &ruleId, const QList &eventDescriptorList, const StateEvaluator &stateEvaluator, const QList &actions); + RuleError addRule(const RuleId &ruleId, const QList &eventDescriptorList, const QList &actions, bool enabled = true); + RuleError addRule(const RuleId &ruleId, const QList &eventDescriptorList, const StateEvaluator &stateEvaluator, const QList &actions, bool enabled = true); QList rules() const; QList ruleIds() const; RuleError removeRule(const RuleId &ruleId); + RuleError enableRule(const RuleId &ruleId); + RuleError disableRule(const RuleId &ruleId); + Rule findRule(const RuleId &ruleId); QList findRules(const DeviceId &deviceId); @@ -68,6 +71,7 @@ public: signals: void ruleAdded(const RuleId &ruleId); void ruleRemoved(const RuleId &ruleId); + void ruleChanged(const RuleId &ruleId); private: bool containsEvent(const Rule &rule, const Event &event); diff --git a/tests/auto/api.json b/tests/auto/api.json index ad4e4c72..4c741509 100644 --- a/tests/auto/api.json +++ b/tests/auto/api.json @@ -1,4 +1,4 @@ -9 +10 { "methods": { "Actions.ExecuteAction": { @@ -263,11 +263,12 @@ } }, "Rules.AddRule": { - "description": "Add a rule. You can describe rules by one or many EventDesciptors and a StateEvaluator. Note that onlyone of either eventDescriptor or eventDescriptorList may be passed at a time.", + "description": "Add a rule. You can describe rules by one or many EventDesciptors and a StateEvaluator. Note that onlyone of either eventDescriptor or eventDescriptorList may be passed at a time. A rule can be created but left disabled,meaning it won't actually be executed until set to enabled. If not given, enabled defaults to true.", "params": { "actions": [ "$ref:Action" ], + "o:enabled": "Bool", "o:eventDescriptor": "$ref:EventDescriptor", "o:eventDescriptorList": [ "$ref:EventDescriptor" @@ -279,6 +280,24 @@ "ruleError": "$ref:RuleError" } }, + "Rules.DisableRule": { + "description": "Disable a rule. The rule won't be triggered by it's events or state changes while it is disabled.", + "params": { + "ruleId": "Uuid" + }, + "returns": { + "ruleError": "$ref:RuleError" + } + }, + "Rules.EnableRule": { + "description": "Enabled a rule that has previously been disabled.", + "params": { + "ruleId": "Uuid" + }, + "returns": { + "ruleError": "$ref:RuleError" + } + }, "Rules.FindRules": { "description": "Find a list of rules containing any of the given parameters.", "params": { @@ -478,6 +497,7 @@ "actions": [ "$ref:Action" ], + "enabled": "Bool", "eventDescriptors": [ "$ref:EventDescriptor" ], diff --git a/tests/auto/rules/testrules.cpp b/tests/auto/rules/testrules.cpp index c0814876..27058746 100644 --- a/tests/auto/rules/testrules.cpp +++ b/tests/auto/rules/testrules.cpp @@ -33,7 +33,14 @@ class TestRules: public GuhTestBase { Q_OBJECT +private: + void cleanupMockHistory(); + void cleanupRules(); + private slots: + + void cleanup(); + void addRemoveRules_data(); void addRemoveRules(); @@ -48,8 +55,34 @@ private slots: void testStateEvaluator2_data(); void testStateEvaluator2(); + + void enableDisableRule(); }; +void TestRules::cleanupMockHistory() { + QNetworkAccessManager nam; + QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*))); + QNetworkRequest request(QUrl(QString("http://localhost:%1/clearactionhistory").arg(m_mockDevice1Port).arg(mockEvent1Id.toString()))); + QNetworkReply *reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + reply->deleteLater(); +} + +void TestRules::cleanupRules() { + QVariant response = injectAndWait("Rules.GetRules"); + foreach (const QVariant &ruleId, response.toMap().value("params").toMap().value("ruleIds").toList()) { + QVariantMap params; + params.insert("ruleId", ruleId.toString()); + verifyRuleError(injectAndWait("Rules.RemoveRule", params)); + } +} + +void TestRules::cleanup() { + cleanupMockHistory(); + cleanupRules(); +} + void TestRules::addRemoveRules_data() { QVariantMap validActionNoParams; @@ -100,6 +133,7 @@ void TestRules::addRemoveRules_data() invalidEventDescriptor.insert("deviceId", DeviceId()); invalidEventDescriptor.insert("paramDescriptors", QVariantList()); + QTest::addColumn("enabled"); QTest::addColumn("action1"); QTest::addColumn("eventDescriptor"); QTest::addColumn("eventDescriptorList"); @@ -107,17 +141,19 @@ void TestRules::addRemoveRules_data() QTest::addColumn("error"); - QTest::newRow("valid rule. 1 EventDescriptor, StateEvaluator, 1 Action") << validActionNoParams << validEventDescriptor1 << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorNoError; - QTest::newRow("valid rule. 2 EventDescriptors, 1 Action") << validActionNoParams << QVariantMap() << eventDescriptorList << validStateEvaluator << RuleEngine::RuleErrorNoError; - QTest::newRow("invalid rule: eventDescriptor and eventDescriptorList used") << validActionNoParams << validEventDescriptor1 << eventDescriptorList << validStateEvaluator << RuleEngine::RuleErrorInvalidParameter; - QTest::newRow("invalid action") << invalidAction << validEventDescriptor1 << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorActionTypeNotFound; - QTest::newRow("invalid event descriptor") << validActionNoParams << invalidEventDescriptor << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorDeviceNotFound; + QTest::newRow("valid rule. 1 EventDescriptor, StateEvaluator, 1 Action, enabled") << true << validActionNoParams << validEventDescriptor1 << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorNoError; + QTest::newRow("valid rule. 1 EventDescriptor, StateEvaluator, 1 Action, diabled") << false << validActionNoParams << validEventDescriptor1 << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorNoError; + QTest::newRow("valid rule. 2 EventDescriptors, 1 Action") << true << validActionNoParams << QVariantMap() << eventDescriptorList << validStateEvaluator << RuleEngine::RuleErrorNoError; + QTest::newRow("invalid rule: eventDescriptor and eventDescriptorList used") << true << validActionNoParams << validEventDescriptor1 << eventDescriptorList << validStateEvaluator << RuleEngine::RuleErrorInvalidParameter; + QTest::newRow("invalid action") << true << invalidAction << validEventDescriptor1 << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorActionTypeNotFound; + QTest::newRow("invalid event descriptor") <deleteLater(); + + // Verify rule got executed + spy.clear(); + request.setUrl(QUrl(QString("http://localhost:%1/actionhistory").arg(m_mockDevice1Port))); + reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + + QByteArray actionHistory = reply->readAll(); + qDebug() << "have action history" << actionHistory; + QVERIFY2(mockActionIdNoParams == ActionTypeId(actionHistory), "Action not triggered"); + reply->deleteLater(); + + cleanupMockHistory(); + + // Now disable the rule + QVariantMap disableParams; + disableParams.insert("ruleId", id.toString()); + response = injectAndWait("Rules.DisableRule", disableParams); + verifyRuleError(response); + + + // trigger event in mock device + spy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:%1/generateevent?eventtypeid=%2").arg(m_mockDevice1Port).arg(mockEvent1Id.toString()))); + reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + reply->deleteLater(); + + // Verify rule got *NOT* executed + spy.clear(); + request.setUrl(QUrl(QString("http://localhost:%1/actionhistory").arg(m_mockDevice1Port))); + reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + + actionHistory = reply->readAll(); + qDebug() << "have action history" << actionHistory; + QVERIFY2(actionHistory.isEmpty(), "Action is triggered while rule is disabled"); + reply->deleteLater(); + + cleanupMockHistory(); + + // Now enable the rule again + response = injectAndWait("Rules.EnableRule", disableParams); + verifyRuleError(response); + + + // trigger event in mock device + spy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:%1/generateevent?eventtypeid=%2").arg(m_mockDevice1Port).arg(mockEvent1Id.toString()))); + reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + reply->deleteLater(); + + // Verify rule got executed + spy.clear(); + request.setUrl(QUrl(QString("http://localhost:%1/actionhistory").arg(m_mockDevice1Port))); + reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + + actionHistory = reply->readAll(); + qDebug() << "have action history" << actionHistory; + QVERIFY2(mockActionIdNoParams == ActionTypeId(actionHistory), "Action not triggered"); + reply->deleteLater(); + +} + #include "testrules.moc" QTEST_MAIN(TestRules)