Initial work on an integrated system update mechanism

This commit is contained in:
Michael Zanetti 2019-04-07 20:03:48 +02:00
parent 1329fab9a6
commit 3944e94699
22 changed files with 607 additions and 9 deletions

View File

@ -28,6 +28,8 @@
#include <avahi-common/strlst.h>
#include <avahi-common/error.h>
#include <QPointer>
namespace nymeaserver {
QtAvahiServiceBrowserImplementationPrivate::QtAvahiServiceBrowserImplementationPrivate(QtAvahiClient *client) :

View File

@ -57,6 +57,7 @@
#include "configurationhandler.h"
#include "networkmanagerhandler.h"
#include "tagshandler.h"
#include "systemhandler.h"
#include <QJsonDocument>
#include <QStringList>
@ -518,6 +519,7 @@ void JsonRPCServer::setup()
registerHandler(new ConfigurationHandler(this));
registerHandler(new NetworkManagerHandler(this));
registerHandler(new TagsHandler(this));
registerHandler(new SystemHandler(NymeaCore::instance()->system(), this));
connect(NymeaCore::instance()->cloudManager(), &CloudManager::pairingReply, this, &JsonRPCServer::pairingFinished);
connect(NymeaCore::instance()->cloudManager(), &CloudManager::connectionStateChanged, this, &JsonRPCServer::onCloudConnectionStateChanged);

View File

@ -0,0 +1,147 @@
#include "systemhandler.h"
#include "system/system.h"
SystemHandler::SystemHandler(System *system, QObject *parent):
JsonHandler(parent),
m_system(system)
{
// Methods
QVariantMap params; QVariantMap returns;
setDescription("GetCapabilities", "Get the list of capabilites on this system. This allows reading whether things like rebooting or shutting down the system running nymea:core is supported on this host.");
setParams("GetCapabilities", params);
returns.insert("powerManagement", JsonTypes::basicTypeToString(JsonTypes::Bool));
returns.insert("updateManagement", JsonTypes::basicTypeToString(JsonTypes::Bool));
setReturns("GetCapabilities", returns);
params.clear(); returns.clear();
setDescription("Reboot", "Initiate a reboot of the system. The return value will indicate whether the procedure has been initiated successfully.");
setParams("Reboot", params);
returns.insert("success", JsonTypes::basicTypeToString(JsonTypes::Bool));
setReturns("Reboot", returns);
params.clear(); returns.clear();
setDescription("Shutdown", "Initiate a shutdown of the system. The return value will indicate whether the procedure has been initiated successfully.");
setParams("Shutdown", params);
returns.insert("success", JsonTypes::basicTypeToString(JsonTypes::Bool));
setReturns("Shutdown", returns);
params.clear(); returns.clear();
setDescription("GetUpdateStatus", "Get the current system status in regard to updates. That is, the currently installed version, any candidate version available etc.");
setParams("GetUpdateStatus", params);
returns.insert("updateAvailable", JsonTypes::basicTypeToString(JsonTypes::Bool));
returns.insert("currentVersion", JsonTypes::basicTypeToString(JsonTypes::String));
returns.insert("candidateVersion", JsonTypes::basicTypeToString(JsonTypes::String));
returns.insert("availableChannels", JsonTypes::basicTypeToString(JsonTypes::StringList));
returns.insert("currentChannel", JsonTypes::basicTypeToString(JsonTypes::String));
returns.insert("updateInProgress", JsonTypes::basicTypeToString(JsonTypes::Bool));
setReturns("GetUpdateStatus", returns);
params.clear(); returns.clear();
setDescription("StartUpdate", "Starts a system update. Returns true if the upgrade has been started successfully.");
setParams("StartUpdate", params);
returns.insert("success", JsonTypes::basicTypeToString(JsonTypes::Bool));
setReturns("StartUpdate", returns);
params.clear(); returns.clear();
setDescription("SelectChannel", "Select an update channel.");
params.insert("channel", JsonTypes::basicTypeToString(JsonTypes::String));
setParams("SelectChannel", params);
returns.insert("success", JsonTypes::basicTypeToString(JsonTypes::Bool));
setReturns("SelectChannel", returns);
// Notifications
params.clear();
setDescription("UpdateStatusChanged", "Emitted whenever there is a change in the information from GetUpdateStatus");
params.insert("updateAvailable", JsonTypes::basicTypeToString(JsonTypes::Bool));
params.insert("currentVersion", JsonTypes::basicTypeToString(JsonTypes::String));
params.insert("candidateVersion", JsonTypes::basicTypeToString(JsonTypes::String));
params.insert("availableChannels", JsonTypes::basicTypeToString(JsonTypes::StringList));
params.insert("currentChannel", JsonTypes::basicTypeToString(JsonTypes::String));
params.insert("updateInProgress", JsonTypes::basicTypeToString(JsonTypes::Bool));
setParams("UpdateStatusChanged", params);
connect(m_system, &System::updateStatusChanged, this, &SystemHandler::onUpdateStatusChanged);
}
QString SystemHandler::name() const
{
return "System";
}
JsonReply *SystemHandler::GetCapabilities(const QVariantMap &params)
{
Q_UNUSED(params)
QVariantMap data;
data.insert("powerManagement", m_system->powerManagementAvailable());
data.insert("updateManagement", m_system->updateManagementAvailable());
return createReply(data);
}
JsonReply *SystemHandler::Reboot(const QVariantMap &params) const
{
Q_UNUSED(params);
bool status = m_system->reboot();
QVariantMap returns;
returns.insert("success", status);
return createReply(returns);
}
JsonReply *SystemHandler::Shutdown(const QVariantMap &params) const
{
Q_UNUSED(params);
bool status = m_system->shutdown();
QVariantMap returns;
returns.insert("success", status);
return createReply(returns);
}
JsonReply *SystemHandler::GetUpdateStatus(const QVariantMap &params) const
{
Q_UNUSED(params);
QVariantMap returns;
returns.insert("updateAvailable", m_system->updateAvailable());
returns.insert("currentVersion", m_system->currentVersion());
returns.insert("candidateVersion", m_system->candidateVersion());
returns.insert("availableChannels", m_system->availableChannels());
returns.insert("currentChannel", m_system->currentChannel());
returns.insert("updateInProgress", m_system->updateInProgress());
return createReply(returns);
}
JsonReply *SystemHandler::StartUpdate(const QVariantMap &params)
{
Q_UNUSED(params)
QVariantMap returns;
bool success = m_system->startUpdate();
returns.insert("success", success);
return createReply(returns);
}
JsonReply *SystemHandler::SelectChannel(const QVariantMap &params)
{
QString channel = params.value("channel").toString();
QVariantMap returns;
if (m_system->availableChannels().contains(channel)) {
bool success = m_system->selectChannel(channel);
returns.insert("success", success);
} else {
returns.insert("success", false);
}
return createReply(returns);
}
void SystemHandler::onUpdateStatusChanged()
{
QVariantMap params;
params.insert("updateAvailable", m_system->updateAvailable());
params.insert("currentVersion", m_system->currentVersion());
params.insert("candidateVersion", m_system->candidateVersion());
params.insert("availableChannels", m_system->availableChannels());
params.insert("currentChannel", m_system->currentChannel());
params.insert("updateInProgress", m_system->updateInProgress());
emit UpdateStatusChanged(params);
}

View File

@ -0,0 +1,60 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* This file is part of nymea. *
* *
* nymea is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, version 2 of the License. *
* *
* nymea is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with nymea. If not, see <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef SYSTEMHANDLER_H
#define SYSTEMHANDLER_H
#include <QObject>
#include "jsonhandler.h"
namespace nymeaserver {
class System;
class SystemHandler : public JsonHandler
{
Q_OBJECT
public:
explicit SystemHandler(System* system, QObject *parent = nullptr);
QString name() const override;
Q_INVOKABLE JsonReply *GetCapabilities(const QVariantMap &params);
Q_INVOKABLE JsonReply *Reboot(const QVariantMap &params) const;
Q_INVOKABLE JsonReply *Shutdown(const QVariantMap &params) const;
Q_INVOKABLE JsonReply *GetUpdateStatus(const QVariantMap &params) const;
Q_INVOKABLE JsonReply *StartUpdate(const QVariantMap &params);
Q_INVOKABLE JsonReply *SelectChannel(const QVariantMap &params);
signals:
void UpdateStatusChanged(const QVariantMap &params);
private slots:
void onUpdateStatusChanged();
private:
System *m_system = nullptr;
};
}
#endif // SYSTEMHANDLER_H

View File

@ -99,7 +99,10 @@ HEADERS += nymeacore.h \
tagging/tag.h \
jsonrpc/tagshandler.h \
cloud/cloudtransport.h \
debugreportgenerator.h
debugreportgenerator.h \
platform/platform.h \
system/system.h \
jsonrpc/systemhandler.h
SOURCES += nymeacore.cpp \
ruleengine.cpp \
@ -184,3 +187,6 @@ SOURCES += nymeacore.cpp \
jsonrpc/tagshandler.cpp \
cloud/cloudtransport.cpp \
debugreportgenerator.cpp \
platform/platform.cpp \
system/system.cpp \
jsonrpc/systemhandler.cpp

View File

@ -106,11 +106,13 @@
#include "nymeacore.h"
#include "loggingcategories.h"
#include "platform/platform.h"
#include "jsonrpc/jsonrpcserver.h"
#include "ruleengine.h"
#include "networkmanager/networkmanager.h"
#include "nymeasettings.h"
#include "tagging/tagsstorage.h"
#include "system/system.h"
#include "devicemanager.h"
#include "plugin/device.h"
@ -143,12 +145,18 @@ NymeaCore::NymeaCore(QObject *parent) :
void NymeaCore::init() {
qCDebug(dcApplication()) << "Initializing NymeaCore";
qCDebug(dcPlatform()) << "Loading platform abstraction";
m_platform = new Platform(this);
qCDebug(dcApplication()) << "Loading nymea configurations" << NymeaSettings(NymeaSettings::SettingsRoleGlobal).fileName();
m_configuration = new NymeaConfiguration(this);
qCDebug(dcApplication()) << "Creating Time Manager";
m_timeManager = new TimeManager(m_configuration->timeZone(), this);
qCDebug(dcApplication()) << "Loading System platform integration";
m_system = new System(m_platform, this);
qCDebug(dcApplication) << "Creating Log Engine";
m_logger = new LogEngine(m_configuration->logDBDriver(), m_configuration->logDBName(), m_configuration->logDBHost(), m_configuration->logDBUser(), m_configuration->logDBPassword(), m_configuration->logDBMaxEntries(), this);
@ -660,6 +668,12 @@ TagsStorage *NymeaCore::tagsStorage() const
return m_tagsStorage;
}
/*! Returns a pointer to the \l{System} instance owned by NymeaCore. */
System *NymeaCore::system() const
{
return m_system;
}
/*! Connected to the DeviceManager's emitEvent signal. Events received in

View File

@ -51,6 +51,8 @@ class NetworkManager;
class NymeaConfiguration;
class TagsStorage;
class UserManager;
class Platform;
class System;
class NymeaCore : public QObject
{
@ -88,6 +90,7 @@ public:
CloudManager *cloudManager() const;
DebugServerHandler *debugServerHandler() const;
TagsStorage *tagsStorage() const;
System *system() const;
static QStringList getAvailableLanguages();
@ -116,6 +119,8 @@ private:
explicit NymeaCore(QObject *parent = nullptr);
static NymeaCore *s_instance;
Platform *m_platform = nullptr;
NymeaConfiguration *m_configuration;
ServerManager *m_serverManager;
DeviceManager *m_deviceManager;
@ -129,6 +134,7 @@ private:
NetworkManager *m_networkManager;
UserManager *m_userManager;
System *m_system;
QHash<ActionId, Action> m_pendingActions;
QList<RuleId> m_executingRules;

View File

@ -0,0 +1,76 @@
#include "platform.h"
#include "plugin/platformplugin.h"
#include "loggingcategories.h"
#include <QCoreApplication>
#include <QDir>
#include <QPluginLoader>
namespace nymeaserver {
Platform::Platform(QObject *parent) : QObject(parent)
{
foreach (const QString &path, pluginSearchDirs()) {
QDir dir(path);
qCDebug(dcPlatform) << "Loading plugins from:" << dir.absolutePath();
foreach (const QString &entry, dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot)) {
qCDebug(dcPlatform()) << "Found dir entry" << entry;
QFileInfo fi;
if (entry.startsWith("libnymea_systemplugin") && entry.endsWith(".so")) {
fi.setFile(path + "/" + entry);
} else {
fi.setFile(path + "/" + entry + "/libnymea_platformplugin" + entry + ".so");
}
if (!fi.exists())
continue;
QPluginLoader loader;
loader.setFileName(fi.absoluteFilePath());
loader.setLoadHints(QLibrary::ResolveAllSymbolsHint);
if (!loader.load()) {
qCWarning(dcPlatform) << "Could not load plugin data of" << entry << "\n" << loader.errorString();
continue;
}
m_platformPlugin = qobject_cast<PlatformPlugin *>(loader.instance());
if (!m_platformPlugin) {
qCWarning(dcPlatform) << "Could not get plugin instance of" << entry;
continue;
}
qCDebug(dcPlatform()) << "Loaded platform plugin:" << entry;
m_platformPlugin->setParent(this);
break;
}
if (m_platformPlugin) {
break;
}
}
if (!m_platformPlugin) {
qCWarning(dcPlatform()) << "Could not load a platform plugin. Platform related features won't be available.";
}
}
PlatformSystemController *Platform::systemController() const
{
return m_platformPlugin->systemController();
}
PlatformUpdateController *Platform::updateController() const
{
return m_platformPlugin->updateController();
}
QStringList Platform::pluginSearchDirs() const
{
QStringList ret;
ret << QString(qgetenv("NYMEA_PLATFORM_PLUGINS_PATH")).split(':');
ret << QCoreApplication::applicationDirPath();
return ret;
}
}

View File

@ -0,0 +1,30 @@
#ifndef PLATFORM_H
#define PLATFORM_H
#include <QObject>
class PlatformPlugin;
class PlatformSystemController;
class PlatformUpdateController;
namespace nymeaserver {
class Platform : public QObject
{
Q_OBJECT
public:
explicit Platform(QObject *parent = nullptr);
PlatformSystemController *systemController() const;
PlatformUpdateController *updateController() const;
private:
QStringList pluginSearchDirs() const;
private:
PlatformPlugin *m_platformPlugin = nullptr;
};
}
#endif // PLATFORM_H

View File

@ -0,0 +1,88 @@
#include "system.h"
#include "loggingcategories.h"
#include "platform/platform.h"
#include "plugin/platformsystemcontroller.h"
#include "plugin/platformupdatecontroller.h"
namespace nymeaserver {
System::System(Platform *platform, QObject *parent):
QObject(parent),
m_platform(platform)
{
connect(m_platform->updateController(), &PlatformUpdateController::updateStatusChanged, this, &System::updateStatusChanged);
}
bool System::powerManagementAvailable() const
{
return m_platform->systemController()->capabilities().testFlag(PlatformSystemController::CapabilityPower);
}
bool System::reboot()
{
return m_platform->systemController()->reboot();
}
bool System::shutdown()
{
return m_platform->systemController()->shutdown();
}
bool System::updateManagementAvailable() const
{
return m_platform->updateController()->updateManagementAvailable();
}
bool System::updateAvailable() const
{
return m_platform->updateController()->updateAvailable();
}
QString System::currentVersion() const
{
return m_platform->updateController()->currentVersion();
}
QString System::candidateVersion() const
{
return m_platform->updateController()->candidateVersion();
}
QStringList System::availableChannels() const
{
return m_platform->updateController()->channels();
}
QString System::currentChannel() const
{
return m_platform->updateController()->currentChannel();
}
bool System::selectChannel(const QString &channel) const
{
return m_platform->updateController()->selectChannel(channel);
}
bool System::canUpdate() const
{
return m_platform->updateController() ;
}
bool System::startUpdate()
{
return m_platform->updateController()->startUpdate();
}
bool System::updateInProgress() const
{
return m_platform->updateController()->updateInProgress();
}
bool System::rollbackAvailable() const
{
return m_platform->updateController()->rollbackAvailable();
}
}

View File

@ -0,0 +1,45 @@
#ifndef SYSTEM_H
#define SYSTEM_H
#include <QObject>
namespace nymeaserver {
class Platform;
class System : public QObject
{
Q_OBJECT
public:
explicit System(Platform *platform, QObject *parent = nullptr);
bool powerManagementAvailable() const;
bool reboot();
bool shutdown();
bool updateManagementAvailable() const;
bool updateAvailable() const;
QString currentVersion() const;
QString candidateVersion() const;
QStringList availableChannels() const;
QString currentChannel() const;
bool selectChannel(const QString &channel) const;
bool canUpdate() const;
bool startUpdate();
bool updateInProgress() const;
bool rollbackAvailable() const;
bool startRollback();
signals:
void updateStatusChanged();
private:
Platform *m_platform = nullptr;
};
}
#endif // SYSTEM_H

View File

@ -43,8 +43,6 @@
*
*/
Q_DECLARE_LOGGING_CATEGORY(dcCoap)
class LIBNYMEA_EXPORT Coap : public QObject
{
Q_OBJECT

View File

@ -65,7 +65,10 @@ HEADERS += devicemanager.h \
nymeadbusservice.h \
network/mqtt/mqttprovider.h \
network/mqtt/mqttchannel.h \
translator.h
translator.h \
plugin/platformplugin.h \
plugin/platformsystemcontroller.h \
plugin/platformupdatecontroller.h
SOURCES += devicemanager.cpp \
loggingcategories.cpp \
@ -120,7 +123,10 @@ SOURCES += devicemanager.cpp \
nymeadbusservice.cpp \
network/mqtt/mqttprovider.cpp \
network/mqtt/mqttchannel.cpp \
translator.cpp
translator.cpp \
plugin/platformplugin.cpp \
plugin/platformsystemcontroller.cpp \
plugin/platformupdatecontroller.cpp
RESOURCES += \

View File

@ -24,6 +24,8 @@
Q_LOGGING_CATEGORY(dcApplication, "Application")
Q_LOGGING_CATEGORY(dcDeviceManager, "DeviceManager")
Q_LOGGING_CATEGORY(dcSystem, "System")
Q_LOGGING_CATEGORY(dcPlatform, "Platform")
Q_LOGGING_CATEGORY(dcTimeManager, "TimeManager")
Q_LOGGING_CATEGORY(dcRuleEngine, "RuleEngine")
Q_LOGGING_CATEGORY(dcRuleEngineDebug, "RuleEngineDebug")

View File

@ -26,12 +26,11 @@
#include <QLoggingCategory>
#include <QDebug>
// Include dcCoap
#include "coap/coap.h"
// Core / libnymea
Q_DECLARE_LOGGING_CATEGORY(dcApplication)
Q_DECLARE_LOGGING_CATEGORY(dcDeviceManager)
Q_DECLARE_LOGGING_CATEGORY(dcSystem)
Q_DECLARE_LOGGING_CATEGORY(dcPlatform)
Q_DECLARE_LOGGING_CATEGORY(dcTimeManager)
Q_DECLARE_LOGGING_CATEGORY(dcRuleEngine)
Q_DECLARE_LOGGING_CATEGORY(dcRuleEngineDebug)
@ -63,5 +62,6 @@ Q_DECLARE_LOGGING_CATEGORY(dcBluetoothServer)
Q_DECLARE_LOGGING_CATEGORY(dcBluetoothServerTraffic)
Q_DECLARE_LOGGING_CATEGORY(dcMqtt)
Q_DECLARE_LOGGING_CATEGORY(dcTranslations)
Q_DECLARE_LOGGING_CATEGORY(dcCoap)
#endif // LOGGINGCATEGORYS_H

View File

@ -0,0 +1,6 @@
#include "platformplugin.h"
PlatformPlugin::PlatformPlugin(QObject *parent) : QObject(parent)
{
}

View File

@ -0,0 +1,24 @@
#ifndef PLATFORMPLUGIN_H
#define PLATFORMPLUGIN_H
#include <QObject>
#include "libnymea.h"
class PlatformSystemController;
class PlatformUpdateController;
class LIBNYMEA_EXPORT PlatformPlugin: public QObject
{
Q_OBJECT
public:
explicit PlatformPlugin(QObject *parent = nullptr);
virtual ~PlatformPlugin() = default;
virtual PlatformSystemController *systemController() const = 0;
virtual PlatformUpdateController *updateController() const = 0;
};
Q_DECLARE_INTERFACE(PlatformPlugin, "io.nymea.PlatformPlugin")
#endif // PLATFORMPLUGIN_H

View File

@ -0,0 +1,6 @@
#include "platformsystemcontroller.h"
PlatformSystemController::PlatformSystemController(QObject *parent) : QObject(parent)
{
}

View File

@ -0,0 +1,26 @@
#ifndef PLATFORMSYSTEMCONTROLLER_H
#define PLATFORMSYSTEMCONTROLLER_H
#include <QObject>
class PlatformSystemController : public QObject
{
Q_OBJECT
public:
enum Capability {
CapabilityNone = 0x00,
CapabilityPower = 0x01,
CapabilityAll = 0xFF
};
Q_ENUM(Capability)
Q_DECLARE_FLAGS(Capabilities, Capability)
explicit PlatformSystemController(QObject *parent = nullptr);
virtual ~PlatformSystemController() = default;
virtual Capabilities capabilities() const = 0;
virtual bool reboot() = 0;
virtual bool shutdown() = 0;
};
#endif // PLATFORMSYSTEMCONTROLLER_H

View File

@ -0,0 +1,15 @@
#include "platformupdatecontroller.h"
PlatformUpdateController::PlatformUpdateController(QObject *parent) : QObject(parent)
{
}
/*! Override this to indicate whether update management is available. Defaults to false.
When a plugin returns true here, it is assumed that the system is capable of updating and nymea
has permissions to do so.
*/
bool PlatformUpdateController::updateManagementAvailable()
{
return false;
}

View File

@ -0,0 +1,37 @@
#ifndef PLATFORMUPDATECONTROLLER_H
#define PLATFORMUPDATECONTROLLER_H
#include <QObject>
class PlatformUpdateController : public QObject
{
Q_OBJECT
public:
explicit PlatformUpdateController(QObject *parent = nullptr);
virtual ~PlatformUpdateController() = default;
virtual bool updateManagementAvailable();
virtual QString currentVersion() const = 0;
virtual QString candidateVersion() const = 0;
// virtual QMap<QString, QString> changelog() const = 0;
virtual void checkForUpdates() = 0;
virtual bool updateAvailable() const = 0;
virtual bool startUpdate() = 0;
virtual bool rollbackAvailable() const = 0;
virtual bool startRollback() = 0;
virtual bool updateInProgress() const = 0;
virtual QStringList channels() const = 0;
virtual QString currentChannel() const = 0;
virtual bool selectChannel(const QString &channel) = 0;
signals:
void updateStatusChanged();
};
#endif // PLATFORMUPDATECONTROLLER_H

View File

@ -99,8 +99,10 @@ int main(int argc, char *argv[])
// logging filers for core and libnymea
QStringList loggingFilters = {
"Application",
"Warnings",
"Application",
"System",
"Platform",
"DeviceManager",
"RuleEngine",
"RuleEngineDebug",