From ac0c0355662901b71afe737433bef9cca412c6f4 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Thu, 28 Sep 2017 15:42:31 +0200 Subject: [PATCH] add some housekeeping --- libguh-core/guhcore.cpp | 23 ++++++ libguh-core/guhcore.h | 1 + libguh-core/logging/logengine.cpp | 18 +++++ libguh-core/logging/logengine.h | 1 + libguh-core/ruleengine.cpp | 32 ++++++++- libguh-core/ruleengine.h | 3 +- libguh-core/stateevaluator.cpp | 10 +++ libguh-core/stateevaluator.h | 1 + libguh/devicemanager.cpp | 25 ++++++- libguh/devicemanager.h | 2 + tests/auto/logging/testlogging.cpp | 42 +++++++++++ tests/auto/rules/testrules.cpp | 110 +++++++++++++++++++++++++++++ 12 files changed, 265 insertions(+), 3 deletions(-) diff --git a/libguh-core/guhcore.cpp b/libguh-core/guhcore.cpp index ff71b8f2..8f293c9f 100644 --- a/libguh-core/guhcore.cpp +++ b/libguh-core/guhcore.cpp @@ -453,6 +453,7 @@ void GuhCore::init() { connect(m_deviceManager, &DeviceManager::deviceSetupFinished, this, &GuhCore::deviceSetupFinished); connect(m_deviceManager, &DeviceManager::deviceReconfigurationFinished, this, &GuhCore::deviceReconfigurationFinished); connect(m_deviceManager, &DeviceManager::pairingFinished, this, &GuhCore::pairingFinished); + connect(m_deviceManager, &DeviceManager::loaded, this, &GuhCore::deviceManagerLoaded); connect(m_ruleEngine, &RuleEngine::ruleAdded, this, &GuhCore::ruleAdded); connect(m_ruleEngine, &RuleEngine::ruleRemoved, this, &GuhCore::ruleRemoved); @@ -631,4 +632,26 @@ void GuhCore::onDeviceDisappeared(const DeviceId &deviceId) } } +void GuhCore::deviceManagerLoaded() +{ + // Do some houskeeping... + qCDebug(dcApplication()) << "Starting housekeeping..."; + QDateTime startTime = QDateTime::currentDateTime(); + foreach (const DeviceId &deviceId, m_logger->devicesInLogs()) { + if (!m_deviceManager->findConfiguredDevice(deviceId)) { + qCDebug(dcApplication()) << "Cleaning stale device entries from log DB for device id" << deviceId; + m_logger->removeDeviceLogs(deviceId); + } + } + foreach (const DeviceId &deviceId, m_ruleEngine->devicesInRules()) { + if (!m_deviceManager->findConfiguredDevice(deviceId)) { + qCDebug(dcApplication()) << "Cleaning stale rule entries for device id" << deviceId; + foreach (const RuleId &ruleId, m_ruleEngine->findRules(deviceId)) { + m_ruleEngine->removeDeviceFromRule(ruleId, deviceId); + } + } + } + qCDebug(dcApplication()) << "Housekeeping done in" << startTime.msecsTo(QDateTime::currentDateTime()) << "ms."; +} + } diff --git a/libguh-core/guhcore.h b/libguh-core/guhcore.h index 1c9f1c3c..ad203511 100644 --- a/libguh-core/guhcore.h +++ b/libguh-core/guhcore.h @@ -123,6 +123,7 @@ private slots: void onLocaleChanged(); void actionExecutionFinished(const ActionId &id, DeviceManager::DeviceError status); void onDeviceDisappeared(const DeviceId &deviceId); + void deviceManagerLoaded(); }; diff --git a/libguh-core/logging/logengine.cpp b/libguh-core/logging/logengine.cpp index 209f0207..746fc9e8 100644 --- a/libguh-core/logging/logengine.cpp +++ b/libguh-core/logging/logengine.cpp @@ -349,6 +349,24 @@ void LogEngine::removeRuleLogs(const RuleId &ruleId) } } +QList LogEngine::devicesInLogs() const +{ + QString queryString = QString("SELECT deviceId FROM entries WHERE deviceId != \"%1\" GROUP BY deviceId;").arg(QUuid().toString()); + QSqlQuery result = m_db.exec(queryString); + QList ret; + if (result.lastError().type() != QSqlError::NoError) { + qCWarning(dcLogEngine()) << "Error fetching device entries from log database:" << m_db.lastError().driverText() << m_db.lastError().databaseText(); + return ret; + } + if (!result.first()) { + return ret; + } + do { + ret.append(DeviceId::fromUuid(result.value("deviceId").toUuid())); + } while (result.next()); + return ret; +} + void LogEngine::appendLogEntry(const LogEntry &entry) { QString queryString = QString("INSERT INTO entries (timestamp, loggingEventType, loggingLevel, sourceType, typeId, deviceId, value, active, errorCode) values ('%1', '%2', '%3', '%4', '%5', '%6', '%7', '%8', '%9');") diff --git a/libguh-core/logging/logengine.h b/libguh-core/logging/logengine.h index fa8f40ed..53dc9b20 100644 --- a/libguh-core/logging/logengine.h +++ b/libguh-core/logging/logengine.h @@ -56,6 +56,7 @@ public: void logRuleExitActionsExecuted(const Rule &rule); void removeDeviceLogs(const DeviceId &deviceId); void removeRuleLogs(const RuleId &ruleId); + QList devicesInLogs() const; signals: void logEntryAdded(const LogEntry &logEntry); diff --git a/libguh-core/ruleengine.cpp b/libguh-core/ruleengine.cpp index 57ab50bf..d4c55aff 100644 --- a/libguh-core/ruleengine.cpp +++ b/libguh-core/ruleengine.cpp @@ -843,7 +843,7 @@ Rule RuleEngine::findRule(const RuleId &ruleId) } /*! Returns a list of all \l{Rule}{Rules} loaded in this Engine, which contains a \l{Device} with the given \a deviceId. */ -QList RuleEngine::findRules(const DeviceId &deviceId) +QList RuleEngine::findRules(const DeviceId &deviceId) const { // Find all offending rules QList offendingRules; @@ -884,6 +884,35 @@ QList RuleEngine::findRules(const DeviceId &deviceId) return offendingRules; } +/*! Returns all devices that are somehow contained in a rule */ +QList RuleEngine::devicesInRules() const +{ + QList tmp; + foreach (const Rule &rule, m_rules) { + foreach (const EventDescriptor &descriptor, rule.eventDescriptors()) { + if (!tmp.contains(descriptor.deviceId())) { + tmp.append(descriptor.deviceId()); + } + } + foreach (const DeviceId &deviceId, rule.stateEvaluator().containedDevices()) { + if (!tmp.contains(deviceId)) { + tmp.append(deviceId); + } + } + foreach (const RuleAction &action, rule.actions()) { + if (!tmp.contains(action.deviceId())) { + tmp.append(action.deviceId()); + } + } + foreach (const RuleAction &exitAction, rule.exitActions()) { + if (!tmp.contains(exitAction.deviceId())) { + tmp.append(exitAction.deviceId()); + } + } + } + return tmp; +} + /*! Removes a \l{Device} from a \l{Rule} with the given \a id and \a deviceId. */ void RuleEngine::removeDeviceFromRule(const RuleId &id, const DeviceId &deviceId) { @@ -942,6 +971,7 @@ void RuleEngine::removeDeviceFromRule(const RuleId &id, const DeviceId &deviceId newRule.setEventDescriptors(eventDescriptors); newRule.setStateEvaluator(stateEvalatuator); newRule.setActions(actions); + newRule.setExitActions(exitActions); m_rules[id] = newRule; // save it diff --git a/libguh-core/ruleengine.h b/libguh-core/ruleengine.h index b840592e..96cfd25a 100644 --- a/libguh-core/ruleengine.h +++ b/libguh-core/ruleengine.h @@ -88,7 +88,8 @@ public: RuleError executeExitActions(const RuleId &ruleId); Rule findRule(const RuleId &ruleId); - QList findRules(const DeviceId &deviceId); + QList findRules(const DeviceId &deviceId) const; + QList devicesInRules() const; void removeDeviceFromRule(const RuleId &id, const DeviceId &deviceId); diff --git a/libguh-core/stateevaluator.cpp b/libguh-core/stateevaluator.cpp index a113b11f..6b8f82f7 100644 --- a/libguh-core/stateevaluator.cpp +++ b/libguh-core/stateevaluator.cpp @@ -157,6 +157,16 @@ void StateEvaluator::removeDevice(const DeviceId &deviceId) } } +QList StateEvaluator::containedDevices() const +{ + QList ret; + ret.append(m_stateDescriptor.deviceId()); + foreach (const StateEvaluator &childEvaluator, m_childEvaluators) { + ret.append(childEvaluator.containedDevices()); + } + return ret; +} + /*! This method will be used to save this \l StateEvaluator to the given \a settings. The \a groupName will normally be the corresponding \l Rule. */ void StateEvaluator::dumpToSettings(GuhSettings &settings, const QString &groupName) const diff --git a/libguh-core/stateevaluator.h b/libguh-core/stateevaluator.h index 69dd9a81..15bab77a 100644 --- a/libguh-core/stateevaluator.h +++ b/libguh-core/stateevaluator.h @@ -50,6 +50,7 @@ public: bool containsDevice(const DeviceId &deviceId) const; void removeDevice(const DeviceId &deviceId); + QList containedDevices() const; void dumpToSettings(GuhSettings &settings, const QString &groupName) const; static StateEvaluator loadFromSettings(GuhSettings &settings, const QString &groupPrefix); diff --git a/libguh/devicemanager.cpp b/libguh/devicemanager.cpp index c3875f24..76b7dd38 100644 --- a/libguh/devicemanager.cpp +++ b/libguh/devicemanager.cpp @@ -241,7 +241,7 @@ DeviceManager::DeviceManager(const QLocale &locale, QObject *parent) : QMetaObject::invokeMethod(this, "loadConfiguredDevices", Qt::QueuedConnection); QMetaObject::invokeMethod(this, "startMonitoringAutoDevices", Qt::QueuedConnection); // Make sure this is always emitted after plugins and devices are loaded - QMetaObject::invokeMethod(this, "loaded", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, "onLoaded", Qt::QueuedConnection); } /*! Destructor of the DeviceManager. Each loaded \l{DevicePlugin} will be deleted. */ @@ -790,6 +790,9 @@ DeviceManager::DeviceError DeviceManager::removeConfiguredDevice(const DeviceId settings.remove(""); settings.endGroup(); + GuhSettings stateCache(GuhSettings::SettingsRoleDeviceStates); + stateCache.remove(deviceId.toString()); + emit deviceRemoved(deviceId); return DeviceErrorNoError; @@ -1412,6 +1415,26 @@ void DeviceManager::onAutoDeviceDisappeared(const DeviceId &deviceId) emit deviceDisappeared(deviceId); } +void DeviceManager::onLoaded() +{ + emit loaded(); + + // schedule some housekeeping... + QTimer::singleShot(0, this, &DeviceManager::cleanupDeviceStateCache); +} + +void DeviceManager::cleanupDeviceStateCache() +{ + GuhSettings settings(GuhSettings::SettingsRoleDeviceStates); + foreach (const QString &entry, settings.childGroups()) { + DeviceId deviceId(entry); + if (!m_configuredDevices.contains(deviceId)) { + qCDebug(dcDeviceManager()) << "Device ID" << deviceId << "not found in configured devices. Cleaning up stale device state cache."; + settings.remove(entry); + } + } +} + void DeviceManager::slotDeviceStateValueChanged(const QUuid &stateTypeId, const QVariant &value) { Device *device = qobject_cast(sender()); diff --git a/libguh/devicemanager.h b/libguh/devicemanager.h index aa05065b..90da495e 100644 --- a/libguh/devicemanager.h +++ b/libguh/devicemanager.h @@ -175,6 +175,8 @@ private slots: void slotPairingFinished(const PairingTransactionId &pairingTransactionId, DeviceManager::DeviceSetupStatus status); void onAutoDevicesAppeared(const DeviceClassId &deviceClassId, const QList &deviceDescriptors); void onAutoDeviceDisappeared(const DeviceId &deviceId); + void onLoaded(); + void cleanupDeviceStateCache(); // Only connect this to Devices. It will query the sender() void slotDeviceStateValueChanged(const QUuid &stateTypeId, const QVariant &value); diff --git a/tests/auto/logging/testlogging.cpp b/tests/auto/logging/testlogging.cpp index 0ffe35fb..167d604f 100644 --- a/tests/auto/logging/testlogging.cpp +++ b/tests/auto/logging/testlogging.cpp @@ -56,6 +56,8 @@ private slots: void deviceLogs(); + void testHouseKeeping(); + // this has to be the last test void removeDevice(); }; @@ -402,6 +404,46 @@ void TestLogging::deviceLogs() } +void TestLogging::testHouseKeeping() +{ + QVariantMap params; + params.insert("deviceClassId", mockDeviceClassId); + params.insert("name", "TestDeviceToBeRemoved"); + QVariantList deviceParams; + QVariantMap httpParam; + httpParam.insert("paramTypeId", httpportParamTypeId); + httpParam.insert("value", 6667); + deviceParams.append(httpParam); + params.insert("deviceParams", deviceParams); + QVariant response = injectAndWait("Devices.AddConfiguredDevice", params); + DeviceId deviceId = DeviceId::fromUuid(response.toMap().value("params").toMap().value("deviceId").toUuid()); + QVERIFY2(!deviceId.isNull(), "Something went wrong creating the device for testing."); + + // Trigger something that creates a logging entry + QNetworkAccessManager nam; + QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*))); + QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(6667).arg(mockIntStateId.toString()).arg(4321))); + QNetworkReply *reply = nam.get(request); + connect(reply, SIGNAL(finished()), reply, SLOT(deleteLater())); + spy.wait(); + + params.clear(); + params.insert("deviceIds", QVariantList() << deviceId); + response = injectAndWait("Logging.GetLogEntries", params); + QVERIFY2(response.toMap().value("params").toMap().value("logEntries").toList().count() > 0, "Couldn't find state change event in log..."); + + // Manually delete this device from config + GuhSettings settings(GuhSettings::SettingsRoleDevices); + settings.beginGroup("DeviceConfig"); + settings.remove(deviceId.toString()); + settings.endGroup(); + + restartServer(); + + response = injectAndWait("Logging.GetLogEntries", params); + QVERIFY2(response.toMap().value("params").toMap().value("logEntries").toList().count() == 0, "Device state change event still in log. Should've been cleaned by housekeeping."); +} + void TestLogging::removeDevice() { // enable notifications diff --git a/tests/auto/rules/testrules.cpp b/tests/auto/rules/testrules.cpp index 843c5035..bc864131 100644 --- a/tests/auto/rules/testrules.cpp +++ b/tests/auto/rules/testrules.cpp @@ -90,6 +90,9 @@ private slots: void testRuleActionParams_data(); void testRuleActionParams(); + + void testHousekeeping_data(); + void testHousekeeping(); }; void TestRules::cleanupMockHistory() { @@ -1814,5 +1817,112 @@ void TestRules::testRuleActionParams() verifyRuleError(response, error); } +void TestRules::testHousekeeping_data() +{ + QTest::addColumn("testAction"); + QTest::addColumn("testExitAction"); + QTest::addColumn("testStateEvaluator"); + QTest::addColumn("testEventDescriptor"); + + QTest::newRow("action") << true << false << false << false; + QTest::newRow("exitAction") << false << true << false << false; + QTest::newRow("stateDescriptor") << false << false << true << false; + QTest::newRow("eventDescriptor")<< false << false << false << true; +} + +void TestRules::testHousekeeping() +{ + QFETCH(bool, testAction); + QFETCH(bool, testExitAction); + QFETCH(bool, testStateEvaluator); + QFETCH(bool, testEventDescriptor); + + QVariantMap params; + params.insert("deviceClassId", mockDeviceClassId); + params.insert("name", "TestDeviceToBeRemoved"); + QVariantList deviceParams; + QVariantMap httpParam; + httpParam.insert("paramTypeId", httpportParamTypeId); + httpParam.insert("value", 6667); + deviceParams.append(httpParam); + params.insert("deviceParams", deviceParams); + QVariant response = injectAndWait("Devices.AddConfiguredDevice", params); + DeviceId deviceId = DeviceId::fromUuid(response.toMap().value("params").toMap().value("deviceId").toUuid()); + QVERIFY2(!deviceId.isNull(), "Something went wrong creating the device for testing."); + + // Create a rule with this device + params.clear(); + params.insert("name", "testrule"); + if (testEventDescriptor) { + QVariantList eventDescriptors; + QVariantMap eventDescriptor; + eventDescriptor.insert("eventTypeId", mockEvent1Id); + eventDescriptor.insert("deviceId", testEventDescriptor ? deviceId : m_mockDeviceId); + eventDescriptors.append(eventDescriptor); + params.insert("eventDescriptors", eventDescriptors); + } + + QVariantMap stateEvaluator; + QVariantMap stateDescriptor; + stateDescriptor.insert("stateTypeId", mockIntStateId); + stateDescriptor.insert("operator", "ValueOperatorGreater"); + stateDescriptor.insert("value", 555); + stateDescriptor.insert("deviceId", testStateEvaluator ? deviceId : m_mockDeviceId); + stateEvaluator.insert("stateDescriptor", stateDescriptor); + params.insert("stateEvaluator", stateEvaluator); + + QVariantList actions; + QVariantMap action; + action.insert("actionTypeId", mockActionIdNoParams); + action.insert("deviceId", testAction ? deviceId : m_mockDeviceId); + actions.append(action); + params.insert("actions", actions); + + if (!testEventDescriptor) { + QVariantList exitActions; + QVariantMap exitAction; + exitAction.insert("actionTypeId", mockActionIdNoParams); + exitAction.insert("deviceId", testExitAction ? deviceId : m_mockDeviceId); + exitActions.append(exitAction); + params.insert("exitActions", exitActions); + } + + response = injectAndWait("Rules.AddRule", params); + RuleId ruleId = RuleId::fromUuid(response.toMap().value("params").toMap().value("ruleId").toUuid()); + + + // Verfy that the rule has been created successfully and our device is in there. + params.clear(); + params.insert("ruleId", ruleId); + response = injectAndWait("Rules.GetRuleDetails", params); + if (testEventDescriptor) { + QVERIFY2(response.toMap().value("params").toMap().value("rule").toMap().value("eventDescriptors").toList().first().toMap().value("deviceId").toUuid().toString() == (testEventDescriptor ? deviceId.toString() : m_mockDeviceId.toString()), "Couldn't find device in eventDescriptor of rule"); + } + QVERIFY2(response.toMap().value("params").toMap().value("rule").toMap().value("stateEvaluator").toMap().value("stateDescriptor").toMap().value("deviceId").toUuid().toString() == (testStateEvaluator ? deviceId.toString() : m_mockDeviceId.toString()), "Couldn't find device in stateEvaluator of rule"); + QVERIFY2(response.toMap().value("params").toMap().value("rule").toMap().value("actions").toList().first().toMap().value("deviceId").toUuid().toString() == (testAction ? deviceId.toString() : m_mockDeviceId.toString()), "Couldn't find device in actions of rule"); + if (!testEventDescriptor) { + QVERIFY2(response.toMap().value("params").toMap().value("rule").toMap().value("exitActions").toList().first().toMap().value("deviceId").toUuid().toString() == (testExitAction ? deviceId.toString() : m_mockDeviceId.toString()), "Couldn't find device in exitActions of rule"); + } + + // Manually delete this device from config + GuhSettings settings(GuhSettings::SettingsRoleDevices); + settings.beginGroup("DeviceConfig"); + settings.remove(deviceId.toString()); + settings.endGroup(); + + restartServer(); + + // Now make sure the appropriate entries with our device have disappeared + response = injectAndWait("Rules.GetRuleDetails", params); + if (testEventDescriptor) { + QVERIFY2(response.toMap().value("params").toMap().value("rule").toMap().value("eventDescriptors").toList().count() == (testEventDescriptor ? 0: 1), "EventDescriptor still in rule... should've been removed by housekeeping."); + } + QVERIFY2(response.toMap().value("params").toMap().value("rule").toMap().value("stateEvaluator").toMap().value("stateDescriptor").toMap().isEmpty() == (testStateEvaluator ? true : false), "StateEvaluator still in rule... should've been removed by housekeeping."); + QVERIFY2(response.toMap().value("params").toMap().value("rule").toMap().value("actions").toList().count() == (testAction ? 0 : 1), "Action still in rule... should've been removed by housekeeping."); + if (!testEventDescriptor) { + QVERIFY2(response.toMap().value("params").toMap().value("rule").toMap().value("exitActions").toList().count() == (testExitAction ? 0: 1), "ExitAction still in rule... should've been removed by housekeeping."); + } +} + #include "testrules.moc" QTEST_MAIN(TestRules)