add some housekeeping

This commit is contained in:
Michael Zanetti 2017-09-28 15:42:31 +02:00
parent 2781ae9288
commit ac0c035566
12 changed files with 265 additions and 3 deletions

View File

@ -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.";
}
}

View File

@ -123,6 +123,7 @@ private slots:
void onLocaleChanged();
void actionExecutionFinished(const ActionId &id, DeviceManager::DeviceError status);
void onDeviceDisappeared(const DeviceId &deviceId);
void deviceManagerLoaded();
};

View File

@ -349,6 +349,24 @@ void LogEngine::removeRuleLogs(const RuleId &ruleId)
}
}
QList<DeviceId> 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<DeviceId> 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');")

View File

@ -56,6 +56,7 @@ public:
void logRuleExitActionsExecuted(const Rule &rule);
void removeDeviceLogs(const DeviceId &deviceId);
void removeRuleLogs(const RuleId &ruleId);
QList<DeviceId> devicesInLogs() const;
signals:
void logEntryAdded(const LogEntry &logEntry);

View File

@ -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<RuleId> RuleEngine::findRules(const DeviceId &deviceId)
QList<RuleId> RuleEngine::findRules(const DeviceId &deviceId) const
{
// Find all offending rules
QList<RuleId> offendingRules;
@ -884,6 +884,35 @@ QList<RuleId> RuleEngine::findRules(const DeviceId &deviceId)
return offendingRules;
}
/*! Returns all devices that are somehow contained in a rule */
QList<DeviceId> RuleEngine::devicesInRules() const
{
QList<DeviceId> 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

View File

@ -88,7 +88,8 @@ public:
RuleError executeExitActions(const RuleId &ruleId);
Rule findRule(const RuleId &ruleId);
QList<RuleId> findRules(const DeviceId &deviceId);
QList<RuleId> findRules(const DeviceId &deviceId) const;
QList<DeviceId> devicesInRules() const;
void removeDeviceFromRule(const RuleId &id, const DeviceId &deviceId);

View File

@ -157,6 +157,16 @@ void StateEvaluator::removeDevice(const DeviceId &deviceId)
}
}
QList<DeviceId> StateEvaluator::containedDevices() const
{
QList<DeviceId> 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

View File

@ -50,6 +50,7 @@ public:
bool containsDevice(const DeviceId &deviceId) const;
void removeDevice(const DeviceId &deviceId);
QList<DeviceId> containedDevices() const;
void dumpToSettings(GuhSettings &settings, const QString &groupName) const;
static StateEvaluator loadFromSettings(GuhSettings &settings, const QString &groupPrefix);

View File

@ -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<Device*>(sender());

View File

@ -175,6 +175,8 @@ private slots:
void slotPairingFinished(const PairingTransactionId &pairingTransactionId, DeviceManager::DeviceSetupStatus status);
void onAutoDevicesAppeared(const DeviceClassId &deviceClassId, const QList<DeviceDescriptor> &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);

View File

@ -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

View File

@ -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<bool>("testAction");
QTest::addColumn<bool>("testExitAction");
QTest::addColumn<bool>("testStateEvaluator");
QTest::addColumn<bool>("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)