Commit 91e3805b authored by Micha Müller's avatar Micha Müller
Browse files

Pusher: introduce EntityInterface

parent 2fa55ec2
//================================================================================
// Name : EntityInterface.h
// Author : Micha Mueller
// Copyright : Leibniz Supercomputing Centre
// Description : Abstract interface defining sensor entity functionality.
//================================================================================
//================================================================================
// This file is part of DCDB (DataCenter DataBase)
// Copyright (C) 2019-2019 Leibniz Supercomputing Centre
//
// This program 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; either version 2
// of the License, or (at your option) any later version.
//
// This program 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 this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//================================================================================
#ifndef DCDBPUSHER_INCLUDES_ENTITYINTERFACE_H_
#define DCDBPUSHER_INCLUDES_ENTITYINTERFACE_H_
#include <memory>
#include <boost/asio.hpp>
#include "logging.h"
/** Indentation when printing configuration */
#define eInd " "
/**
* @brief Abstract interface defining sensor entity functionality.
*
* @ingroup pusherplugins
*/
class EntityInterface {
public:
using strand = boost::asio::io_service::strand;
/**
* @brief Constructor
*
* @details Does not initialize _strand.
*
* @param name Name of the entity.
*/
EntityInterface(const std::string& name) :
_name(name),
_mqttPart(""),
_strand(nullptr) {}
/**
* @brief Copy constructor
*
* @details Does not initialize _strand.
*
* @param other Entity to copy construct from.
*/
EntityInterface(const EntityInterface& other) :
_name(other._name),
_mqttPart(other._mqttPart),
_strand(nullptr) {}
/**
* @brief Destructor
*/
virtual ~EntityInterface() {}
/**
* @brief Assignment operator
*
* @details _strand is uninitialized afterwards.
*
* @param other Entity to assign from.
*
* @return EntityInterface
*/
EntityInterface& operator=(const EntityInterface& other) {
_name = other._name;
_mqttPart = other._mqttPart;
_strand = nullptr;
return *this;
}
const std::string& getEntityName() const { return _name; }
const std::string& getMqttPart() const { return _mqttPart; }
const std::unique_ptr<strand>& getStrand() const { return _strand; }
void setEntityName(const std::string& name) { _name = name; }
void setMqttPart(const std::string& mqttPart) {
_mqttPart = mqttPart;
//sanitize mqttPart into uniform /xxxx format
if (_mqttPart.front() != '/') {
_mqttPart.insert(0, "/");
}
if (_mqttPart.back() == '/') {
_mqttPart.erase(_mqttPart.size()-1);
}
}
/**
* @brief Initialize this entity.
*
* @details Initializes base class and subsequently calls init().
*
* @param io IO service to initialize _strand with.
*/
void initEntity(boost::asio::io_service& io) {
if (!_strand) {
_strand.reset(new strand(io));
}
this->init();
}
/**
* @brief Print complete configuration of this entity.
*
* @details Prints configuration of base class and subsequently calls
* printConfig().
*
* @param ll Log severity level to be used from logger.
*/
void printEntityConfig(LOG_LEVEL ll) {
LOG_VAR(ll) << " " << "Entity " << _name;
if (_mqttPart != "") {
LOG_VAR(ll) << eInd << "MQTT part: " << _mqttPart;
}
this->printConfig(ll);
}
/**
* @brief Initialize derived class (if necessary).
*/
virtual void init() { /* do nothing if not overwritten */ };
/**
* @brief Print configuration of derived class.
*
* @param ll Log severity level to be used from logger.
*/
virtual void printConfig(LOG_LEVEL ll) = 0;
protected:
std::string _name; /**< Name of the entity */
std::string _mqttPart; /**< Partial MQTT topic identifying this entity */
std::unique_ptr<strand> _strand; /**< Provides serialized handler execution
to avoid race conditions */
LOGGER lg; /**< Logging instance */
};
#endif /* DCDBPUSHER_INCLUDES_ENTITYINTERFACE_H_ */
......@@ -40,18 +40,15 @@
double BACnetClient::_presentValue;
uint8_t BACnetClient::_handlerTransmitBuffer[MAX_PDU];
BACnetClient::BACnetClient() {
_strand = NULL;
_invokeId = 0;
_presentValue = 0;
_timeout = 1000;
BACnetClient::BACnetClient(const std::string& name) :
EntityInterface(name),
_invokeId(0),
_timeout(1000) {
_presentValue = 0;
_targetAddress = {0};
}
BACnetClient::~BACnetClient() {
if (_strand) {
delete _strand;
}
datalink_cleanup();
}
......@@ -194,13 +191,6 @@ double BACnetClient::readProperty(uint32_t deviceObjInstance, uint32_t objInstan
return _presentValue;
}
void BACnetClient::initializeStrand(boost::asio::io_service& io) {
if (!_strand) {
_strand = new boost::asio::io_service::strand(io);
}
}
void BACnetClient::unrecognizedServiceHandler(uint8_t * service_request, uint16_t service_len, BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_DATA * service_data) {
boost::log::sources::severity_logger<boost::log::trivial::severity_level> lg;
int pdu_len = 0;
......@@ -309,7 +299,6 @@ void BACnetClient::rejectHandler(BACNET_ADDRESS * src, uint8_t invokeId, uint8_t
}
void BACnetClient::printConfig(LOG_LEVEL ll) {
LOG_VAR(ll) << " BACnetClient here";
LOG_VAR(ll) << " Timeout: " << _timeout;
LOG_VAR(ll) << eInd << "Timeout: " << _timeout;
}
......@@ -27,88 +27,121 @@
#ifndef BACNETCLIENT_H_
#define BACNETCLIENT_H_
#include "../../includes/EntityInterface.h"
#include "bacnet/apdu.h"
#include "bacnet/bacenum.h"
#include "bacnet/datalink.h"
#include <boost/asio.hpp>
#include "logging.h"
/*
* NOTE
* Had to make some member variables static because of BACnet Stack handler function requirements.
* This should be no problem as we are using only one instance of BACnetClient with serialized access (_strand).
* One should ensure to keep the single instance property in the future.
* Had to make some member variables static because of BACnet Stack handler
* function requirements. This should be no problem as we are using only one
* instance of BACnetClient with serialized access (_strand). One should ensure
* to keep the single instance property in the future.
*/
/**
* @brief Client to handle BACnet protocol communication. Only one instance allowed!
* @brief Client to handle BACnet protocol communication. Only one instance
* allowed!
*
* @ingroup bacnet
*/
class BACnetClient {
public:
BACnetClient();
virtual ~BACnetClient();
class BACnetClient : public EntityInterface {
void initializeStrand(boost::asio::io_service& io);
public:
boost::asio::io_service::strand* getStrand() const {
return _strand;
}
BACnetClient(const std::string& name = "BACnetClient");
BACnetClient(const BACnetClient& other) = delete;
virtual ~BACnetClient();
BACnetClient& operator=(const BACnetClient& other) = delete;
/**
* Initialize datalink layer and address cache.
* We assume BACnet/IP protocol is used. Also you need to compile with environment variable BACNET_ADDRESS_CACHE_FILE set
* to enable initialization of address cache from file "address_cache".
* @brief Initialize datalink layer and address cache.
*
* @details We assume BACnet/IP protocol is used. Also you need to compile
* with environment variable BACNET_ADDRESS_CACHE_FILE set to
* enable initialization of address cache from file "address_cache".
*
* @param interface Name of network interface to use
* @param address_cache (Path and) filename of the address cache file where the addresses of BACnet devices are stored
* @param port Which port to use of the interface
* @param timeout Number of milliseconds to wait for a packet when receiving
* @param apdu_timeout Number of milliseconds before timeout when sending
* @param retries Number of retries after an apdu timeout occurs
* @param interface Name of network interface to use.
* @param address_cache (Path and) filename of the address cache file where
* the addresses of BACnet devices are stored.
* @param port Which port to use of the interface.
* @param timeout Number of milliseconds to wait for a packet when
* receiving.
* @param apdu_timeout Number of milliseconds before timeout when sending.
* @param retries Number of retries after an apdu timeout occurs.
*/
void init(std::string interface, const std::string& address_cache, unsigned port = 47808, unsigned timeout = 1000, unsigned apdu_timeout = 200, unsigned retries = 0);
void init(std::string interface,
const std::string& address_cache,
unsigned port = 47808,
unsigned timeout = 1000,
unsigned apdu_timeout = 200,
unsigned retries = 0);
/**
* Sends a READ_PROPERTY request for PROP_PRESENT_VALUE to specified device and decodes the response (READ_PROPERTY_ACK).
* @brief Sends a READ_PROPERTY request for PROP_PRESENT_VALUE to specified
* device and decodes the response (READ_PROPERTY_ACK).
*
* @param deviceObjectInstance Number of device from which to request the current value.
* @param objectInstance
* @param objectType
* @param objectProperty
* @param objectIndex
* @param deviceObjInstance Number of device from which to request
* the current value.
* @param objInstance Object instance
* @param objType Object type
* @param objProperty Object property
* @param objIndex Object index
*
* @return The value sent as response from the device. If an error occurs a runtime exception is thrown.
* @return The value sent as response from the device. If an error occurs a
* runtime exception is thrown.
*
* @throws Runtime error
*/
double readProperty(uint32_t deviceObjInstance, uint32_t objInstance = 0, BACNET_OBJECT_TYPE objType = OBJECT_DEVICE, BACNET_PROPERTY_ID objProperty = PROP_PRESENT_VALUE, int32_t objIndex = BACNET_ARRAY_ALL);
double readProperty(uint32_t deviceObjInstance,
uint32_t objInstance = 0,
BACNET_OBJECT_TYPE objType = OBJECT_DEVICE,
BACNET_PROPERTY_ID objProperty = PROP_PRESENT_VALUE,
int32_t objIndex = BACNET_ARRAY_ALL);
/**
* Print information about configured attributes
*
* @param ll Severity level of the log messages.
*/
void printConfig(LOG_LEVEL ll);
* @brief Print configuration of this class.
*
* @param ll Log severity level to be used from logger.
*/
void printConfig(LOG_LEVEL ll) override;
private:
/* Handler to process incoming BACnet data */
static void unrecognizedServiceHandler(uint8_t * service_request, uint16_t service_len, BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_DATA * service_data);
/**
* Handler for a ReadProperty ACK.
* Here the actual processing of a valid response takes place
* @brief Handler for a ReadProperty ACK.
* @details Here the actual processing of a valid response takes place.
*
* @param service_request The contents of the service request.
* @param service_len The length of the service_request.
* @param src BACNET_ADDRESS of the source of the message
* @param service_data The BACNET_CONFIRMED_SERVICE_DATA information decoded from the APDU header of this message.
* @param src BACNET_ADDRESS of the source of the message.
* @param service_data The BACNET_CONFIRMED_SERVICE_DATA information
* decoded from the APDU header of this message.
*/
static void readPropertyAckHandler(uint8_t * service_request, uint16_t service_len, BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_ACK_DATA * service_data);
static void errorHandler(BACNET_ADDRESS * src, uint8_t invokeId, BACNET_ERROR_CLASS error_class, BACNET_ERROR_CODE error_code);
static void abortHandler(BACNET_ADDRESS * src, uint8_t invokeId, uint8_t abort_reason, bool server);
static void rejectHandler(BACNET_ADDRESS * src, uint8_t invokeId, uint8_t reject_reason);
static void readPropertyAckHandler(uint8_t * service_request,
uint16_t service_len,
BACNET_ADDRESS * src,
BACNET_CONFIRMED_SERVICE_ACK_DATA * service_data);
static void unrecognizedServiceHandler(uint8_t * service_request,
uint16_t service_len,
BACNET_ADDRESS * src,
BACNET_CONFIRMED_SERVICE_DATA * service_data);
static void errorHandler(BACNET_ADDRESS * src,
uint8_t invokeId,
BACNET_ERROR_CLASS error_class,
BACNET_ERROR_CODE error_code);
static void abortHandler(BACNET_ADDRESS * src,
uint8_t invokeId,
uint8_t abort_reason,
bool server);
static void rejectHandler(BACNET_ADDRESS * src,
uint8_t invokeId,
uint8_t reject_reason);
uint8_t _invokeId;
unsigned _timeout;
......@@ -116,10 +149,6 @@ private:
static double _presentValue;
static uint8_t _handlerTransmitBuffer[MAX_PDU];
BACNET_ADDRESS _targetAddress; //store as member variable to enable access in handler methods
boost::asio::io_service::strand* _strand;
LOGGER lg;
};
#endif /* BACNETCLIENT_H_ */
......@@ -49,7 +49,7 @@ BACnetSensorGroup& BACnetSensorGroup::operator=(const BACnetSensorGroup& other)
void BACnetSensorGroup::init(boost::asio::io_service& io) {
SensorGroupTemplate::init(io);
if(_bacClient) {
_bacClient->initializeStrand(io);
_bacClient->initEntity(io);
} else {
LOG(error) << "No BACnetClient set for sensor " << _groupName << "! Cannot initialize sensor.";
}
......
......@@ -43,29 +43,59 @@
#define RETRIES 2
IPMIHost::IPMIHost() {
_ipmiCtx = nullptr;
_sensorReadCtx = nullptr;
_hostName = "";
_userName = std::string("admin");
_password = std::string("admin");
_cache = "";
_auth = IPMI_AUTHENTICATION_TYPE_MD5;
_priv = IPMI_PRIVILEGE_LEVEL_ADMIN;
_cipher = 3;
_ipmiVersion = 1;
_mqttPart = "";
_retransmissionTimeout = 0;
_sessionTimeout = 0;
_strand = nullptr;
_errorCount = 0;
_delayNextReadUntil = 0;
IPMIHost::IPMIHost(const std::string& name) :
EntityInterface(name),
_ipmiCtx(nullptr),
_sensorReadCtx(nullptr),
_userName("admin"),
_password("admin"),
_cache(""),
_auth(IPMI_AUTHENTICATION_TYPE_MD5),
_priv(IPMI_PRIVILEGE_LEVEL_ADMIN),
_cipher(3),
_ipmiVersion(1),
_sessionTimeout(0),
_retransmissionTimeout(0),
_errorCount(0),
_delayNextReadUntil(0) {
}
IPMIHost::~IPMIHost() {
if (_strand) {
delete _strand;
}
IPMIHost::IPMIHost(const IPMIHost& other) :
EntityInterface(other),
_ipmiCtx(nullptr),
_sensorReadCtx(nullptr),
_userName(other._userName),
_password(other._password),
_cache(other._cache),
_auth(other._auth),
_priv(other._priv),
_cipher(other._cipher),
_ipmiVersion(other._ipmiVersion),
_retransmissionTimeout(other._retransmissionTimeout),
_sessionTimeout(other._sessionTimeout),
_errorCount(0),
_delayNextReadUntil(other._delayNextReadUntil) {
}
IPMIHost::~IPMIHost() {}
IPMIHost& IPMIHost::operator=(const IPMIHost& other) {
EntityInterface::operator=(other);
_ipmiCtx = nullptr;
_sensorReadCtx = nullptr;
_userName = other._userName;
_password = other._password;
_cache = other._cache;
_auth = other._auth;
_priv = other._priv;
_cipher = other._cipher;
_ipmiVersion = other._ipmiVersion;
_sessionTimeout = other._sessionTimeout;
_retransmissionTimeout = other._retransmissionTimeout;
_errorCount = 0;
_delayNextReadUntil = other._delayNextReadUntil;
return *this;
}
int IPMIHost::connect() {
......@@ -82,9 +112,9 @@ int IPMIHost::connect() {
int flags = IPMI_FLAGS_DEFAULT;
int rc;
if (_ipmiVersion == 1) {
rc = ipmi_ctx_open_outofband(_ipmiCtx, _hostName.c_str(), _userName.c_str(), _password.c_str(), _auth, _priv, _sessionTimeout, _retransmissionTimeout, workaround_flags, flags);
rc = ipmi_ctx_open_outofband(_ipmiCtx, _name.c_str(), _userName.c_str(), _password.c_str(), _auth, _priv, _sessionTimeout, _retransmissionTimeout, workaround_flags, flags);
} else {
rc = ipmi_ctx_open_outofband_2_0(_ipmiCtx, _hostName.c_str(), _userName.c_str(), _password.c_str(), NULL, 0, _priv, _cipher, _sessionTimeout, _retransmissionTimeout, workaround_flags, flags);
rc = ipmi_ctx_open_outofband_2_0(_ipmiCtx, _name.c_str(), _userName.c_str(), _password.c_str(), NULL, 0, _priv, _cipher, _sessionTimeout, _retransmissionTimeout, workaround_flags, flags);
}
if (rc < 0) {
_errorMsg = "Error opening IPMI connection: " + std::string(ipmi_ctx_errormsg(_ipmiCtx));
......@@ -126,14 +156,14 @@ bool IPMIHost::getSdrRecord(uint16_t recordId, std::vector<uint8_t>& record) {
if (ipmi_sdr_cache_open(sdrCtx, _ipmiCtx, _cache.c_str()) < 0) {
if ((ipmi_sdr_ctx_errnum(sdrCtx) == IPMI_SDR_ERR_CACHE_READ_CACHE_DOES_NOT_EXIST) || (ipmi_sdr_ctx_errnum(sdrCtx) == IPMI_SDR_ERR_CACHE_INVALID) || (ipmi_sdr_ctx_errnum(sdrCtx) == IPMI_SDR_ERR_CACHE_OUT_OF_DATE)) {
if ((ipmi_sdr_ctx_errnum(sdrCtx) == IPMI_SDR_ERR_CACHE_INVALID) || (ipmi_sdr_ctx_errnum(sdrCtx) == IPMI_SDR_ERR_CACHE_OUT_OF_DATE)) {
LOG(debug) << _hostName << "Deleting SDR cache " << _cache;
LOG(debug) << _name << "Deleting SDR cache " << _cache;
ipmi_sdr_cache_close(sdrCtx);
ipmi_sdr_cache_delete(sdrCtx, _cache.c_str());
}
if (ipmi_sdr_cache_create(sdrCtx, _ipmiCtx, _cache.c_str(), IPMI_SDR_CACHE_CREATE_FLAGS_DEFAULT, NULL, NULL) == 0) {
LOG(debug) << _hostName << ": Created new SDR cache " << _cache;
LOG(debug) << _name << ": Created new SDR cache " << _cache;
} else {
LOG(debug) << _hostName << ": Error creating new SDR cache " << _cache;
LOG(debug) << _name << ": Error creating new SDR cache " << _cache;
}
} else {
_errorMsg = "Error opening SDR cache: " + std::string(ipmi_sdr_ctx_errormsg(sdrCtx));
......@@ -289,26 +319,19 @@ void IPMIHost::increaseErrorCount() {
}
}
void IPMIHost::initializeStrand(boost::asio::io_service& io) {
if (!_strand) {
_strand = new boost::asio::io_service::strand(io);
}
}
void IPMIHost::printConfig(LOG_LEVEL ll) {
LOG_VAR(ll) << " IPMIHost: " << _hostName;
LOG_VAR(ll) << " UserName: " << _userName;
LOG_VAR(ll) << eInd << "IPMIHost: " << getHostName();
LOG_VAR(ll) << eInd << "UserName: " << getUserName();
#ifdef DEBUG
LOG_VAR(ll) << " Password: " << _password;
LOG_VAR(ll) << eInd << "Password: " << getPassword();
#else
LOG_VAR(ll) << " Password not shown";
LOG_VAR(ll) << eInd << "Password not shown";
#endif
LOG_VAR(ll) << " Cache: " << _cache;
LOG_VAR(ll) << " Auth: " << _auth;
LOG_VAR(ll) << " Priv: " << _priv;
LOG_VAR(ll) << " Cipher: " << _cipher;
LOG_VAR(ll) << " IPMI Version: " << _ipmiVersion;
LOG_VAR(ll) << " MQTT Part: " << _mqttPart;
LOG_VAR(ll) << " Session Timeout: " << _sessionTimeout;
LOG_VAR(ll) << " Retransmission Timeout: " << _retransmissionTimeout;
LOG_VAR(ll) << eInd << "Cache: " << getCache();
LOG_VAR(ll) << eInd << "Auth: " << getAuth();
LOG_VAR(ll) << eInd << "Priv: " << getPriv();
LOG_VAR(ll) << eInd << "Cipher: " << getCipher();
LOG_VAR(ll) << eInd << "IPMI Version: " << getIpmiVersion();
LOG_VAR(ll) << eInd << "Session Timeout: " << _sessionTimeout;
LOG_VAR(ll) << eInd << "Retransmission Timeout: " << _retransmissionTimeout;
}
......@@ -27,21 +27,23 @@
#ifndef IPMIHOST_H_
#define IPMIHOST_H_
#include "../../includes/EntityInterface.h"
#include <string>
#include <list>
#include <freeipmi/freeipmi.h>
#include <boost/asio.hpp>
#include "logging.h"
/**
* @brief Handles all connections to the same IPMI host.
*
* @ingroup ipmi
*/
class IPMIHost {
class IPMIHost : public EntityInterface {
public:
IPMIHost();
IPMIHost(const std::string& name = "");
IPMIHost(const IPMIHost& other);
virtual ~IPMIHost();
IPMIHost& operator=(const IPMIHost& other);
/* Translate recordId to SDR record */
bool getSdrRecord(uint16_t recordId, std::vector<uint8_t>& record);
......@@ -54,40 +56,28 @@ public:
const uint64_t getDelayFactor() const;
void setAuth(uint8_t auth) { _auth = auth; }
void setHostName(const std::string& hostName) { _hostName = hostName; }
void setHostName(const std::string& hostName) { _name = hostName; }
void setPassword(const std::string& password) { _password = password; }
void setCache(const std::string& cacheDir) { _cache = cacheDir + ".ipmiPluginSdrCache." + _hostName; }
void setCache(const std::string& cacheDir) { _cache = cacheDir + ".ipmiPluginSdrCache." + _name; }
void setPriv(uint8_t priv) { _priv = priv; }
void setCipher(const std::string& cipher) { _cipher = stoi(cipher); }
void setIpmiVersion(const std::string& ipmiVersion) { _ipmiVersion = stoi(ipmiVersion); }
void setUserName(const std::string& userName) { _userName = userName; }
void setMqttPart(const std::string& mqttPart) {
_mqttPart = mqttPart;
if (_mqttPart.front() != '/') {
_mqttPart.insert(0, "/");
}
if (_mqttPart.back() == '/') {
_mqttPart.erase(_mqttPart.size()-1);
}
}
void setSessionTimeout(uint32_t sessionTimeout) { _sessionTimeout = sessionTimeout; }
void setRetransmissionTimeout(uint32_t retransmissionTimeout) { _retransmissionTimeout = retransmissionTimeout; }
void initializeStrand(boost::asio::io_service& io);
void setDelayNextReadUntil(uint64_t delayNextReadUntil) { _delayNextReadUntil = delayNextReadUntil; }
uint8_t getAuth() const { return _auth; }