diff --git a/.gitattributes b/.gitattributes index 06c6af5fec3955708871bac655e7574c3104f6f5..a75c0ab764f960e9e2a69832daedd5dcfa4abf02 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2582,6 +2582,67 @@ LCS/MessageDaemons/webmonitor/Schema.svg.ccu001 -text LCS/MessageDaemons/webmonitor/Schema.svg.ccu099 -text LCS/MessageDaemons/webmonitor/start_QPIDWebMonitor -text svneol=unset#text/x-shellscript LCS/MessageDaemons/webmonitor/stop_QPIDWebMonitor -text svneol=unset#text/x-shellscript +LCS/Messaging/CMakeLists.txt -text +LCS/Messaging/include/Messaging/CMakeLists.txt -text +LCS/Messaging/include/Messaging/DefaultSettings.h -text +LCS/Messaging/include/Messaging/EventMessage.h -text +LCS/Messaging/include/Messaging/Exceptions.h -text +LCS/Messaging/include/Messaging/FromBus.h -text +LCS/Messaging/include/Messaging/LofarMessage.h -text +LCS/Messaging/include/Messaging/LofarMessages.h -text +LCS/Messaging/include/Messaging/Message.h -text +LCS/Messaging/include/Messaging/Message.h.orig -text +LCS/Messaging/include/Messaging/MonitoringMessage.h -text +LCS/Messaging/include/Messaging/ProgressMessage.h -text +LCS/Messaging/include/Messaging/ServiceMessage.h -text +LCS/Messaging/include/Messaging/ToBus.h -text +LCS/Messaging/package.dox -text +LCS/Messaging/patch.txt -text +LCS/Messaging/python/CMakeLists.txt -text +LCS/Messaging/python/common/CMakeLists.txt -text +LCS/Messaging/python/common/__init__.py -text +LCS/Messaging/python/common/factory.py -text +LCS/Messaging/python/common/util.py -text +LCS/Messaging/python/examples/ToUpperClient -text +LCS/Messaging/python/examples/ToUpperMapClient -text +LCS/Messaging/python/examples/ToUpperService -text +LCS/Messaging/python/messaging/CMakeLists.txt -text +LCS/Messaging/python/messaging/RPC.py -text +LCS/Messaging/python/messaging/Service.py -text +LCS/Messaging/python/messaging/__init__.py -text +LCS/Messaging/python/messaging/exceptions.py -text +LCS/Messaging/python/messaging/messagebus.py -text +LCS/Messaging/python/messaging/messages.py -text +LCS/Messaging/python/messaging/test/CMakeLists.txt -text +LCS/Messaging/python/messaging/test/t_RPC.py -text +LCS/Messaging/python/messaging/test/t_RPC.run -text +LCS/Messaging/python/messaging/test/t_RPC.sh -text +LCS/Messaging/python/messaging/test/t_messagebus.py -text +LCS/Messaging/python/messaging/test/t_messagebus.run -text +LCS/Messaging/python/messaging/test/t_messagebus.sh -text +LCS/Messaging/python/messaging/test/t_messages.py -text +LCS/Messaging/python/messaging/test/t_messages.run -text +LCS/Messaging/python/messaging/test/t_messages.sh -text +LCS/Messaging/src/CMakeLists.txt -text +LCS/Messaging/src/DefaultSettings.cc -text +LCS/Messaging/src/EventMessage.cc -text +LCS/Messaging/src/FromBus.cc -text +LCS/Messaging/src/Helpers.cc -text +LCS/Messaging/src/Helpers.h -text +LCS/Messaging/src/LofarMessage.cc -text +LCS/Messaging/src/Message.cc -text +LCS/Messaging/src/Message.cc.ok -text +LCS/Messaging/src/Message.h.ok -text +LCS/Messaging/src/MonitoringMessage.cc -text +LCS/Messaging/src/ProgressMessage.cc -text +LCS/Messaging/src/ServiceMessage.cc -text +LCS/Messaging/src/ToBus.cc -text +LCS/Messaging/test/CMakeLists.txt -text +LCS/Messaging/test/tLofarMessages.cc -text +LCS/Messaging/test/tLofarMessages.log_prop -text +LCS/Messaging/test/tLofarMessages.sh -text +LCS/Messaging/test/tMessaging.cc -text +LCS/Messaging/test/tTimeOut.cc -text LCS/Tools/src/checkcomp.py -text LCS/Tools/src/countalllines -text LCS/Tools/src/countlines -text diff --git a/CMake/LofarPackageList.cmake b/CMake/LofarPackageList.cmake index c7d2ae93e486ecc8807b49cdbb84b1ad7288aa20..1e2efe49f3fc4aab51141a21b84b52c770b9dc87 100644 --- a/CMake/LofarPackageList.cmake +++ b/CMake/LofarPackageList.cmake @@ -65,6 +65,7 @@ if(NOT DEFINED LOFAR_PACKAGE_LIST_INCLUDED) set(Common_SOURCE_DIR ${CMAKE_SOURCE_DIR}/LCS/Common) set(MessageBus_SOURCE_DIR ${CMAKE_SOURCE_DIR}/LCS/MessageBus) set(MessageDaemons_SOURCE_DIR ${CMAKE_SOURCE_DIR}/LCS/MessageDaemons) + set(Messaging_SOURCE_DIR ${CMAKE_SOURCE_DIR}/LCS/Messaging) set(MSLofar_SOURCE_DIR ${CMAKE_SOURCE_DIR}/LCS/MSLofar) set(pyparameterset_SOURCE_DIR ${CMAKE_SOURCE_DIR}/LCS/pyparameterset) set(pytools_SOURCE_DIR ${CMAKE_SOURCE_DIR}/LCS/pytools) diff --git a/CMake/testscripts/runctest.sh.in b/CMake/testscripts/runctest.sh.in index a88113d35c8eb90fcdfb9343cdc21e193f34a3be..2e89abee2004b1a68acfdbe30150ca5c9822335f 100755 --- a/CMake/testscripts/runctest.sh.in +++ b/CMake/testscripts/runctest.sh.in @@ -26,7 +26,7 @@ srcdir="@srcdir@"; export srcdir # Add the bin directory in the build tree to PATH -PATH="@CMAKE_BINARY_DIR@/bin:$PATH"; export PATH +PATH="@CMAKE_BINARY_DIR@/bin:${PATH}"; export PATH # Add the Python build directory to PYTHONPATH. PYTHONPATH="@srcdir@:@PYTHON_BUILD_DIR@:${PYTHONPATH}"; export PYTHONPATH diff --git a/LCS/CMakeLists.txt b/LCS/CMakeLists.txt index eaf721a214020ba8ed6cfc3118664179f15592c1..e0a5f530ab2cb77bfe159a96c481021a30a39c66 100644 --- a/LCS/CMakeLists.txt +++ b/LCS/CMakeLists.txt @@ -6,6 +6,7 @@ lofar_add_package(ApplCommon) # Application common stuff lofar_add_package(Blob) # Binary Large Objects lofar_add_package(Common) # Common stuff lofar_add_package(MessageBus) # Support for QPID message exchange +lofar_add_package(Messaging) # Support for new QPID messaging libraries lofar_add_package(MessageDaemons) # Daemons to do message routing and handling lofar_add_package(MSLofar) # MS for LOFAR based on ICD lofar_add_package(pyparameterset) # Python ParameterSet bindings @@ -14,4 +15,5 @@ lofar_add_package(Stream) # Low-level support for streaming data lofar_add_package(Tools) # Useful tools lofar_add_package(Transport) # Low-level transport library lofar_add_package(MSLofar) # LOFAR MeasurementSet definition -lofar_add_package(LofarStMan) # Storage Manager for the main table of a LOFAR MS +lofar_add_package(LofarStMan) # Storage Manager for the main table of a LOFAR MS + diff --git a/LCS/Messaging/CMakeLists.txt b/LCS/Messaging/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..96d98ebaf71332c3758c4ee7dc6c5e0d7e753c78 --- /dev/null +++ b/LCS/Messaging/CMakeLists.txt @@ -0,0 +1,10 @@ +# $Id: CMakeLists.txt 1455 2015-08-18 07:31:18Z loose $ + +lofar_package(Messaging 1.0 DEPENDS Common) + +include(LofarFindPackage) +lofar_find_package(QPID REQUIRED) +add_subdirectory(include/Messaging) +add_subdirectory(src) +add_subdirectory(test) +add_subdirectory(python) diff --git a/LCS/Messaging/include/Messaging/CMakeLists.txt b/LCS/Messaging/include/Messaging/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..42238a3cd465dc817cd27c4701366703d50dcda3 --- /dev/null +++ b/LCS/Messaging/include/Messaging/CMakeLists.txt @@ -0,0 +1,24 @@ +# $Id: CMakeLists.txt 1455 2015-08-18 07:31:18Z loose $ + +# List of header files that will be installed. +set(inst_HEADERS + LofarMessage.h + LofarMessages.h + DefaultSettings.h + EventMessage.h + Exceptions.h + FromBus.h + Message.h + MonitoringMessage.h + ProgressMessage.h + ServiceMessage.h + ToBus.h +) + +# Create symbolic link to include directory. +execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_BINARY_DIR}/include/${PACKAGE_NAME}) + +# Install header files. +install(FILES ${inst_HEADERS} DESTINATION include/${PACKAGE_NAME}) diff --git a/LCS/Messaging/include/Messaging/DefaultSettings.h b/LCS/Messaging/include/Messaging/DefaultSettings.h new file mode 100644 index 0000000000000000000000000000000000000000..8dacc193ab7634e03f2927585151b47e479fcde4 --- /dev/null +++ b/LCS/Messaging/include/Messaging/DefaultSettings.h @@ -0,0 +1,56 @@ +//# DefaultSettings.h: Default settings for often used parameters. +//# +//# Copyright (C) 2015 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR Software Suite. +//# +//# The LOFAR Software Suite 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 3 of the License, or (at your +//# option) any later version. +//# +//# The LOFAR Software Suite 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 +//# The LOFAR Software Suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: DefaultSettings.h 1483 2015-08-23 19:19:44Z loose $ + +#ifndef LOFAR_MESSAGING_DEFAULTSETTINGS_H +#define LOFAR_MESSAGING_DEFAULTSETTINGS_H + +// @file +// Default settings for often used parameters. + +#define USE_TRACE +#ifdef USE_TRACE +# include <iostream> +# define TRACE \ + std::cout << __PRETTY_FUNCTION__ << std::endl +#else +# define TRACE +#endif + +#include <string> + +namespace LOFAR +{ + namespace Messaging + { + // @addtogroup Messaging + // @{ + extern std::string defaultAddressOptions; + extern std::string defaultBroker; + extern std::string defaultBrokerOptions; + extern unsigned defaultReceiverCapacity; + extern double defaultTimeOut; + // @} + } +} + +#endif diff --git a/LCS/Messaging/include/Messaging/EventMessage.h b/LCS/Messaging/include/Messaging/EventMessage.h new file mode 100644 index 0000000000000000000000000000000000000000..58aa6126e7e2356036575e5f409d8ba3b06ceee5 --- /dev/null +++ b/LCS/Messaging/include/Messaging/EventMessage.h @@ -0,0 +1,63 @@ +//# EventMessage.h: Message class used for event messages. +//# +//# Copyright (C) 2015 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR Software Suite. +//# +//# The LOFAR Software Suite 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 3 of the License, or (at your +//# option) any later version. +//# +//# The LOFAR Software Suite 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 +//# The LOFAR Software Suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: EventMessage.h 1483 2015-08-23 19:19:44Z loose $ + +#ifndef LOFAR_MESSAGING_EVENTMESSAGE_H +#define LOFAR_MESSAGING_EVENTMESSAGE_H + +// @file +// Message class used for event messages. + +#include <Messaging/LofarMessage.h> +#include <string> + +namespace LOFAR +{ + namespace Messaging + { + // @addtogroup Messaging + // @{ + + // Message class used for event messages. Events are messages that @e must + // be delivered. If the message cannot be delivered to the recipient, it + // will be stored in a persistent queue for later delivery. + class EventMessage : public LofarMessage + { + public: + // Default constructor. + EventMessage(); + + private: + // Construct a message from a Qpid message. This constructor is used + // by the ObjectFactory. + EventMessage(const qpid::messaging::Message& qmsg); + + // The ObjectFactory needs to have access to the private constructor. + friend class LOFAR::ObjectFactory< + Message*(const qpid::messaging::Message& msg), std::string >; + }; + + // @} + } +} + +#endif diff --git a/LCS/Messaging/include/Messaging/Exceptions.h b/LCS/Messaging/include/Messaging/Exceptions.h new file mode 100644 index 0000000000000000000000000000000000000000..394f19f8c3eeb482f004f8be49f2e36cef88cea0 --- /dev/null +++ b/LCS/Messaging/include/Messaging/Exceptions.h @@ -0,0 +1,61 @@ +//# Exceptions.h: Exception classes used by the Messaging package +//# +//# Copyright (C) 2015 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR Software Suite. +//# +//# The LOFAR Software Suite 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 3 of the License, or (at your +//# option) any later version. +//# +//# The LOFAR Software Suite 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 +//# The LOFAR Software Suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: Exceptions.h 1483 2015-08-23 19:19:44Z loose $ + +#ifndef LOFAR_MESSAGING_EXCEPTIONS_H +#define LOFAR_MESSAGING_EXCEPTIONS_H + +// @file +// Exception classes used by the Messaging package + +#include <Common/Exception.h> + +namespace LOFAR +{ + namespace Messaging + { + // @addtogroup Messaging + // @{ + + EXCEPTION_CLASS(Exception, LOFAR::Exception); + + // Exception class used when an invalid message is received. + EXCEPTION_CLASS(InvalidMessage, Exception); + + // Exception class used when a message property cannot be found. + EXCEPTION_CLASS(MessagePropertyNotFound, Exception); + + // Exception class used when a message for the wrong system is received. + // EXCEPTION_CLASS(WrongSystemName, Exception); + + // Exception class used when a message of unknown type is received. + EXCEPTION_CLASS(UnknownMessageType, Exception); + + // Exception class used when errors occur while sending or receiving + // messages. + EXCEPTION_CLASS(MessagingException, Exception); + + // @} + } +} + +#endif diff --git a/LCS/Messaging/include/Messaging/FromBus.h b/LCS/Messaging/include/Messaging/FromBus.h new file mode 100644 index 0000000000000000000000000000000000000000..bd6e9c87ae5a106a12bf7611ee3e00b6eebe2ef0 --- /dev/null +++ b/LCS/Messaging/include/Messaging/FromBus.h @@ -0,0 +1,107 @@ +//# FromBus.h: Provide an easy way to fetch messages from the message bus. +//# +//# Copyright (C) 2015 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR Software Suite. +//# +//# The LOFAR Software Suite 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 3 of the License, or (at your +//# option) any later version. +//# +//# The LOFAR Software Suite 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 +//# The LOFAR Software Suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: FromBus.h 1483 2015-08-23 19:19:44Z loose $ + +#ifndef LOFAR_MESSAGING_FROMBUS_H +#define LOFAR_MESSAGING_FROMBUS_H + +// @file +// Provide an easy way to fetch messages from the message bus. + +#include <Messaging/DefaultSettings.h> +#include <qpid/messaging/Connection.h> +#include <qpid/messaging/Session.h> +#include <string> + +namespace LOFAR +{ + namespace Messaging + { + // @addtogroup Messaging + // @{ + + //# Forward declarations + class Message; + + // This class provides an easy way to fetch messages from the message bus. + class FromBus + { + public: + // Default constructor. + FromBus(); + + // Constructor. + // @param address valid Qpid address. + // @param options valid Qpid address options. + // @param broker valid Qpid broker URL. + // @note Please consult the Qpid documentation for more details. + FromBus(const std::string& address, + const std::string& options = defaultAddressOptions, + const std::string& broker = defaultBroker); + + // Destructor. Report the number of missing acknowledgements if non-zero. + ~FromBus(); + + // Add a queue that you want to receive messages from. + // @param address valid Qpid address + // @param options valid Qpid address options. + void addQueue(const std::string& address, + const std::string& options = defaultAddressOptions); + + // Retrieve the next message from any of the queues we're listening on. + // @param timeout Maximum time in seconds to wait for a message. + // @return Pointer to a new Message object or a null pointer if no message + // arrived within @a timeout seconds. The user is responsible for deleting + // the object. + Message* getMessage(double timeout = defaultTimeOut); + + // Acknowledge a message. This will inform Qpid that the message can + // safely be removed from the queue. + // @param message Message to acknowledge. + void ack(Message& message); + + // Do not acknowledge a message. This will inform Qpid that the message + // has to be redelivered. You cannot nack a message that has already + // been acknowledged. + // @param message Message to not be acknowledged. + void nack(Message& message); + + // Reject a message. This will inform Qpid that the message should not be + // redelivered. You cannot reject a message that has already been + // acknowledged. + // @param message Message to be rejected. + void reject(Message& message); + + private: + qpid::messaging::Connection itsConnection; + qpid::messaging::Session itsSession; + + // Keep track of the number of missing acknowledgements. + unsigned itsNrMissingACKs; + }; + + // @} + + } +} + +#endif diff --git a/LCS/Messaging/include/Messaging/LofarMessage.h b/LCS/Messaging/include/Messaging/LofarMessage.h new file mode 100644 index 0000000000000000000000000000000000000000..47baa47f8a61408e88d2950c6625c78d06fc11c9 --- /dev/null +++ b/LCS/Messaging/include/Messaging/LofarMessage.h @@ -0,0 +1,54 @@ +//# LofarMessage.h: Top-level message class for LOFAR messages. +//# +//# Copyright (C) 2015 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR Software Suite. +//# +//# The LOFAR Software Suite 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 3 of the License, or (at your +//# option) any later version. +//# +//# The LOFAR Software Suite 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 +//# The LOFAR Software Suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: LofarMessage.h 1483 2015-08-23 19:19:44Z loose $ + +#ifndef LOFAR_MESSAGING_LOFARMESSAGE_H +#define LOFAR_MESSAGING_LOFARMESSAGE_H + +// @file +// Top-level message class for LOFAR messages. + +#include <Messaging/Message.h> +#include <Common/ObjectFactory.h> +#include <string> + +namespace LOFAR +{ + namespace Messaging + { + // @addtogroup Messaging + // @{ + + // Top-level message class for LOFAR messages. + // We may not need this class, but I've put it in just to be sure. + class LofarMessage : public Message + { + protected: + LofarMessage(); + LofarMessage(const qpid::messaging::Message& msg); + }; + // @} + + } +} + +#endif diff --git a/LCS/Messaging/include/Messaging/LofarMessages.h b/LCS/Messaging/include/Messaging/LofarMessages.h new file mode 100644 index 0000000000000000000000000000000000000000..4ec67dd307bea3572786f70c27ad64f038df1b6d --- /dev/null +++ b/LCS/Messaging/include/Messaging/LofarMessages.h @@ -0,0 +1,35 @@ +//# LofarMessages.h: Convenience header file includes message header files. +//# +//# Copyright (C) 2015 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR Software Suite. +//# +//# The LOFAR Software Suite 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 3 of the License, or (at your +//# option) any later version. +//# +//# The LOFAR Software Suite 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 +//# The LOFAR Software Suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: LofarMessages.h 1483 2015-08-23 19:19:44Z loose $ + +#ifndef LOFAR_MESSAGING_LOFARMESSAGES_H +#define LOFAR_MESSAGING_LOFARMESSAGES_H + +// @file +// Convenience header file includes message header files. + +#include <Messaging/EventMessage.h> +#include <Messaging/ProgressMessage.h> +#include <Messaging/MonitoringMessage.h> +#include <Messaging/ServiceMessage.h> + +#endif diff --git a/LCS/Messaging/include/Messaging/Message.h b/LCS/Messaging/include/Messaging/Message.h new file mode 100644 index 0000000000000000000000000000000000000000..d23e7d52292657b33f9ae387a0aef8c859ad2eda --- /dev/null +++ b/LCS/Messaging/include/Messaging/Message.h @@ -0,0 +1,94 @@ +//# Message.h: Top-level message class. +//# +//# Copyright (C) 2015 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR Software Suite. +//# +//# The LOFAR Software Suite 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 3 of the License, or (at your +//# option) any later version. +//# +//# The LOFAR Software Suite 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 +//# The LOFAR Software Suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: Message.h 1483 2015-08-23 19:19:44Z loose $ + +#ifndef LOFAR_MESSAGING_MESSAGE_H +#define LOFAR_MESSAGING_MESSAGE_H + +// @file +// Top-level message class. + +#include <Common/ObjectFactory.h> +#include <Common/Singleton.h> +#include <qpid/messaging/Message.h> +#include <qpid/types/Variant.h> +#include <set> +#include <string> + +namespace LOFAR +{ + namespace Messaging + { + // @addtogroup Messaging + // @{ + + // Top-level message class. + // This class publicly inherits from the Qpid Message class, hence it can be + // used as a Qpid Message anywhere you like. + class Message : public qpid::messaging::Message + { + public: + // Create a Message object from a Qpid message. The Qpid message property + // @a MessageType is used to determine which Message object must be + // constructed. + // @throw UnknownMessageType + static Message* create(const qpid::messaging::Message& qmsg); + + // Retrieve a single property directly from a Qpid message. + // @throw MessagePropertyNotFound + static qpid::types::Variant + getProperty(const std::string& property, + const qpid::messaging::Message& qmsg); + + // Virtual destructor, because you can inherit from this class. + virtual ~Message() = 0; + + // Retrieve a single message property. + // @throw MessagePropertyNotFound + qpid::types::Variant getProperty(const std::string& property) const; + + // Return the message type as a string + std::string type() const; + + protected: + // Default constructor. Creates an empty message object. + Message(); + + // Construct a Message object from a Qpid message. + Message(const qpid::messaging::Message& msg); + + // // Return a set of property names, i.e. the keys of the properties map. + // std::set<std::string> propertyNames() const; + }; + + // Generic factory for Message objects. + typedef LOFAR::Singleton< + LOFAR::ObjectFactory< + Message*(const qpid::messaging::Message& msg), std::string > + > MessageFactory; + + // @} + + } +} + +#endif diff --git a/LCS/Messaging/include/Messaging/Message.h.orig b/LCS/Messaging/include/Messaging/Message.h.orig new file mode 100644 index 0000000000000000000000000000000000000000..9b53b3429ad1781401b875fbdf2c919c6891df61 --- /dev/null +++ b/LCS/Messaging/include/Messaging/Message.h.orig @@ -0,0 +1,98 @@ +#ifndef LOFAR_MESSAGING_MESSAGE_H +#define LOFAR_MESSAGING_MESSAGE_H + +#include <Common/ObjectFactory.h> +#include <Common/Singleton.h> +#include <boost/shared_ptr.hpp> +#include <string> + +//# Forward declarations +namespace qpid { + namespace messaging { class Message; } + namespace types { class Variant; } +} + +namespace LOFAR +{ + namespace Messaging + { + //# Forward declarations + class MessageImpl; + + // Top-level message class. + // @todo: move outside of LOFAR source code tree. + class Message + { + public: + // Properties that must be present in every Message. + struct Properties + { + // System name, for example @c LOFAR or @c LOFAR. + std::string systemName; + // Message type, for example @c EventMessage. + std::string messageType; + }; + + // Create a Message object from a Qpid message. The Qpid message property + // @a MessageType is used to determine which Message object must be + // constructed. + // @throw UnknownMessageType + static Message* create(const qpid::messaging::Message& qmsg); + + // Return the names of the properties that must be present in every + // message. Obviously, these names can be different from names of the + // members of the struct Properties. + static const Properties& propertyNames(); + + // Retrieve a single property directly from a Qpid message. + // @throw MessagePropertyNotFound + static qpid::types::Variant + getProperty(const std::string& property, + const qpid::messaging::Message& qmsg); + + // Virtual destructor, because you can inherit from this class. + virtual ~Message() = 0; + + // Return the message type as a string + virtual const std::string& type() const = 0; + + // Return a reference to the implementation. + // @todo Can we get rid of exposure of internals? + const boost::shared_ptr< MessageImpl > getImpl() const; + + protected: + // Default constructor. Creates an empty message object. + Message(); + + // Construct a Message object from a set of message properties. + Message(const Properties& properties); + + // Construct a Message object from a Qpid message. + Message(const qpid::messaging::Message& msg); + + // Retrieve a message property. + qpid::types::Variant getProperty(const std::string& property) const; + + // Set a message property. + void setProperty(const std::string& property, + const qpid::types::Variant& variant); + + private: + // Pointer to our implementation. + boost::shared_ptr< MessageImpl > itsImpl; + }; + + // Compare two Message::Properties structs member-wise for equality. + bool operator==(const Message::Properties& lhs, + const Message::Properties& rhs); + + // Generic factory for Message objects. + typedef LOFAR::Singleton< + LOFAR::ObjectFactory< Message*(const qpid::messaging::Message& msg), + std::string > + > MessageFactory; + + } +} + +#endif diff --git a/LCS/Messaging/include/Messaging/MonitoringMessage.h b/LCS/Messaging/include/Messaging/MonitoringMessage.h new file mode 100644 index 0000000000000000000000000000000000000000..51a93d9590d1fc28dafcc265aff4ec780c0aebd7 --- /dev/null +++ b/LCS/Messaging/include/Messaging/MonitoringMessage.h @@ -0,0 +1,63 @@ +//# MonitoringMessage.h: Message class used for Monitoring messages. +//# +//# Copyright (C) 2015 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR Software Suite. +//# +//# The LOFAR Software Suite 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 3 of the License, or (at your +//# option) any later version. +//# +//# The LOFAR Software Suite 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 +//# The LOFAR Software Suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: MonitoringMessage.h 1483 2015-08-23 19:19:44Z loose $ + +#ifndef LOFAR_MESSAGING_MONITORINGMESSAGE_H +#define LOFAR_MESSAGING_MONITORINGMESSAGE_H + +// @file +// Message class used for Monitoring messages. + +#include <Messaging/LofarMessage.h> +#include <string> + +namespace LOFAR +{ + namespace Messaging + { + // @addtogroup Messaging + // @{ + + // Message class used for Monitoring messages. Monitoring messages are + // publish-subscribe type of messages. They will be not be queued, so they + // will be lost if there are no subscribers. + class MonitoringMessage : public LofarMessage + { + public: + // Default constructor. + MonitoringMessage(); + + private: + // Construct a message from a Qpid message. This constructor is used + // by the ObjectFactory. + MonitoringMessage(const qpid::messaging::Message& qmsg); + + // The ObjectFactory needs to have access to the private constructor. + friend class LOFAR::ObjectFactory< + Message*(const qpid::messaging::Message& msg), std::string >; + }; + + // @} + } +} + +#endif diff --git a/LCS/Messaging/include/Messaging/ProgressMessage.h b/LCS/Messaging/include/Messaging/ProgressMessage.h new file mode 100644 index 0000000000000000000000000000000000000000..7f25bea56e53a3b746b1beb83a8e9a619b061d41 --- /dev/null +++ b/LCS/Messaging/include/Messaging/ProgressMessage.h @@ -0,0 +1,63 @@ +//# ProgressMessage.h: Message class used for progress messages. +//# +//# Copyright (C) 2015 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR Software Suite. +//# +//# The LOFAR Software Suite 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 3 of the License, or (at your +//# option) any later version. +//# +//# The LOFAR Software Suite 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 +//# The LOFAR Software Suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: ProgressMessage.h 1483 2015-08-23 19:19:44Z loose $ + +#ifndef LOFAR_MESSAGING_PROGRESSMESSAGE_H +#define LOFAR_MESSAGING_PROGRESSMESSAGE_H + +// @file +// Message class used for progress messages. + +#include <Messaging/LofarMessage.h> +#include <string> + +namespace LOFAR +{ + namespace Messaging + { + // @addtogroup Messaging + // @{ + + // Message class used for progress messages. Progress messages are + // publish-subscribe type of messages. They will be not be queued, so they + // will be lost if there are no subscribers. + class ProgressMessage : public LofarMessage + { + public: + // Default constructor. + ProgressMessage(); + + private: + // Construct a message from a Qpid message. This constructor is used + // by the ObjectFactory. + ProgressMessage(const qpid::messaging::Message& qmsg); + + // The ObjectFactory needs to have access to the private constructor. + friend class LOFAR::ObjectFactory< + Message*(const qpid::messaging::Message& msg), std::string >; + }; + + // @} + } +} + +#endif diff --git a/LCS/Messaging/include/Messaging/ServiceMessage.h b/LCS/Messaging/include/Messaging/ServiceMessage.h new file mode 100644 index 0000000000000000000000000000000000000000..042fbc26992947fe9ccf04202cacbc27c36871f9 --- /dev/null +++ b/LCS/Messaging/include/Messaging/ServiceMessage.h @@ -0,0 +1,63 @@ +//# ServiceMessage.h: Message class used for service messages. +//# +//# Copyright (C) 2015 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR Software Suite. +//# +//# The LOFAR Software Suite 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 3 of the License, or (at your +//# option) any later version. +//# +//# The LOFAR Software Suite 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 +//# The LOFAR Software Suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: ServiceMessage.h 1483 2015-08-23 19:19:44Z loose $ + +#ifndef LOFAR_MESSAGING_SERVICEMESSAGE_H +#define LOFAR_MESSAGING_SERVICEMESSAGE_H + +// @file +// Message class used for service messages. + +#include <Messaging/LofarMessage.h> +#include <string> + +namespace LOFAR +{ + namespace Messaging + { + // @addtogroup Messaging + // @{ + + // Message class used for service messages. Service messages are + // request-reply type of messages. They are typically used to query a + // subsystem. A service message must contain a valid @c ReplyTo property. + class ServiceMessage : public LofarMessage + { + public: + // Default constructor. + ServiceMessage(); + + private: + // Construct a message from a Qpid message. This constructor is used + // by the ObjectFactory. + ServiceMessage(const qpid::messaging::Message& qmsg); + + // The ObjectFactory needs to have access to the private constructor. + friend class LOFAR::ObjectFactory< + Message*(const qpid::messaging::Message& msg), std::string >; + }; + + // @} + } +} + +#endif diff --git a/LCS/Messaging/include/Messaging/ToBus.h b/LCS/Messaging/include/Messaging/ToBus.h new file mode 100644 index 0000000000000000000000000000000000000000..099e27754401d3ca6e0fb90b5fb496e3f4473208 --- /dev/null +++ b/LCS/Messaging/include/Messaging/ToBus.h @@ -0,0 +1,76 @@ +//# ToBus.h: Provide an easy way to put messages onto the message bus. +//# +//# Copyright (C) 2015 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR Software Suite. +//# +//# The LOFAR Software Suite 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 3 of the License, or (at your +//# option) any later version. +//# +//# The LOFAR Software Suite 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 +//# The LOFAR Software Suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: ToBus.h 1483 2015-08-23 19:19:44Z loose $ + +#ifndef LOFAR_MESSAGING_TOBUS_H +#define LOFAR_MESSAGING_TOBUS_H + +// @file ToBus.h +// Provide an easy way to put messages onto the message bus. + +#include <Messaging/DefaultSettings.h> +#include <qpid/messaging/Connection.h> +#include <qpid/messaging/Session.h> +#include <qpid/messaging/Sender.h> +#include <string> + +namespace LOFAR +{ + namespace Messaging + { + // @addtogroup Messaging + // @{ + + // This class provides an easy way to put messages onto the message bus. + class ToBus + { + public: + // Constructor. + // @param address valid Qpid address. + // @param options valid Qpid address options. + // @param broker valid Qpid broker URL. + // @note Please consult the Qpid documentation for more details. + ToBus(const std::string& address, + const std::string& options = defaultAddressOptions, + const std::string& broker = defaultBroker); + + // Destructor. + ~ToBus(); + + // Put the given message onto the bus. + void send(const Message& message); + + private: + // Add a queue that you want to put messages into. + void addQueue(const std::string& address, + const std::string& options); + + qpid::messaging::Connection itsConnection; + qpid::messaging::Session itsSession; + qpid::messaging::Sender itsSender; + }; + + // @} + } +} + +#endif diff --git a/LCS/Messaging/package.dox b/LCS/Messaging/package.dox new file mode 100644 index 0000000000000000000000000000000000000000..e9765773487110b05379d355d18893a5cce7443a --- /dev/null +++ b/LCS/Messaging/package.dox @@ -0,0 +1,9 @@ +/** + +@ingroup LOFAR +@defgroup Messaging Messaging + +The Messaging package contains software that provides a messaging middle-ware +layer based in Apache Qpid. + +*/ diff --git a/LCS/Messaging/patch.txt b/LCS/Messaging/patch.txt new file mode 100644 index 0000000000000000000000000000000000000000..d241ac794a795dc0391b908d7855761bb8fe35b1 --- /dev/null +++ b/LCS/Messaging/patch.txt @@ -0,0 +1,149 @@ +Index: include/Messaging/Exceptions.h +=================================================================== +--- include/Messaging/Exceptions.h (revision 1400) ++++ include/Messaging/Exceptions.h (working copy) +@@ -8,25 +8,20 @@ + namespace Messaging + { + // Top-level exception class for the Messaging package. +- EXCEPTION_CLASS(Exception, LOFAR::Exception); ++ EXCEPTION_CLASS(MessagingException, LOFAR::Exception); + +- // // Exception class used when the message factory cannot create an instance +- // // of the desired class. +- // EXCEPTION_CLASS(MessageFactoryException, Exception); ++ // Exception class used when a message is invalid. ++ EXCEPTION_CLASS(InvalidMessage, MessagingException); + +- // Exception class used when an LOFAR message cannot be constructed, +- // because a required property cannot be found. +- EXCEPTION_CLASS(MessagePropertyNotFound, Exception); +- + // Exception class used when a message for the wrong system is received. +- EXCEPTION_CLASS(WrongSystemName, Exception); ++ EXCEPTION_CLASS(WrongSystemName, InvalidMessage); + + // Exception class used when a message of unknown type is received. +- EXCEPTION_CLASS(UnknownMessageType, Exception); ++ EXCEPTION_CLASS(UnknownMessageType, InvalidMessage); + +- // Exception class used when errors occur while sending or receiving +- // messages. +- EXCEPTION_CLASS(MessagingException, Exception); ++ // Exception class used when a message property cannot be found. ++ EXCEPTION_CLASS(MessagePropertyNotFound, InvalidMessage); ++ + } + } + +Index: include/Messaging/Message.h +=================================================================== +--- include/Messaging/Message.h (revision 1400) ++++ include/Messaging/Message.h (working copy) +@@ -63,9 +63,11 @@ + Message(const Properties& properties); + + // Construct a Message object from a Qpid message. ++ // @throw InvalidMessage + Message(const qpid::messaging::Message& msg); + + // Retrieve a message property. ++ // @throw MessagePropertyNotFound + qpid::types::Variant getProperty(const std::string& property) const; + + // Set a message property. +Index: src/MessageImpl.cc +=================================================================== +--- src/MessageImpl.cc (revision 1400) ++++ src/MessageImpl.cc (working copy) +@@ -54,12 +54,15 @@ + + void MessageImpl::init() + { +- itsQpidMsg.setMessageId(qpid::types::Uuid(true).str()); ++ // itsQpidMsg.setMessageId(qpid::types::Uuid(true).str()); + } + + + void MessageImpl::validate(const qpid::messaging::Message& msg) + { ++ // if (msg.getMessageId().asUuid().isNull()) { ++ // THROW (InvalidMessage, "Message does not contain valid ID"); ++ // } + } + + +Index: test/tLofarMessages.cc +=================================================================== +--- test/tLofarMessages.cc (revision 1400) ++++ test/tLofarMessages.cc (working copy) +@@ -20,11 +20,17 @@ + { + QpidMsgFixture() { + qpidMsg.setProperty("System", "LOFAR"); +- qpidMsg.setMessageId(qpid::types::Uuid(true).str()); ++ // qpidMsg.setMessageId(qpid::types::Uuid(true).str()); ++ qpidMsg.setMessageId(qpid::types::Uuid().str()); + } + qpid::messaging::Message qpidMsg; + }; + ++TEST_FIXTURE(QpidMsgFixture, InvalidMessage) ++{ ++ qpidMsg.setMessageId(qpid::types::Uuid().str()); ++} ++ + TEST_FIXTURE(QpidMsgFixture, WrongSystemName) + { + qpidMsg.setProperty("System", "LOFAR"); +Index: test/tMessaging.cc +=================================================================== +--- test/tMessaging.cc (revision 1400) ++++ test/tMessaging.cc (working copy) +@@ -7,6 +7,11 @@ + + #include <UnitTest++.h> + ++#include <qpid/messaging/Message.h> ++#include <qpid/messaging/Address.h> ++ ++#include "../src/MessageImpl.h" ++ + #include <iostream> + #include <memory> + +@@ -25,6 +30,26 @@ + double timeOut; + }; + ++void show(const qpid::messaging::Message& msg) ++{ ++ cout << "ReplyTo = " << msg.getReplyTo().str() << endl; ++ cout << "Subject = " << msg.getSubject() << endl; ++ cout << "ContentType = " << msg.getContentType() << endl; ++ cout << "MessageId = " << msg.getMessageId() << endl; ++ cout << "UserId = " << msg.getUserId() << endl; ++ cout << "CorrelationId = " << msg.getCorrelationId() << endl; ++ cout << "Priority = " << msg.getPriority() << endl; ++ cout << "Ttl = " << msg.getTtl().getMilliseconds() << endl; ++ cout << "Durable = " << msg.getDurable() << endl; ++ cout << "Redelivered = " << msg.getRedelivered() << endl; ++ cout << "Properties = " << msg.getProperties() << endl; ++ cout << "Content = " << msg.getContent() << endl; ++ cout << "ContentBytes = " << msg.getContentBytes() << endl; ++ cout << "ContentObject = " << msg.getContentObject() << endl; ++ cout << "ContentPtr = " << msg.getContentPtr() << endl; ++ cout << "ContentSize = " << msg.getContentSize() << endl; ++} ++ + TEST_FIXTURE(BusFixture, EventMessage) + { + cout << "******** EventMessage ********" << endl; +@@ -34,6 +59,7 @@ + fromBus.ack(*recvMsg); + CHECK(sendMsg.type() == recvMsg->type()); + CHECK(sendMsg == *recvMsg); ++ show(recvMsg->getImpl()->getQpidMsg()); + } + + TEST_FIXTURE(BusFixture, MonitoringMessage) diff --git a/LCS/Messaging/python/CMakeLists.txt b/LCS/Messaging/python/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..d7491b93d5eb9ff3ff79442198b3d25ad5be98c7 --- /dev/null +++ b/LCS/Messaging/python/CMakeLists.txt @@ -0,0 +1,4 @@ +# $Id: CMakeLists.txt 1584 2015-10-02 12:10:14Z loose $ + +add_subdirectory(common) +add_subdirectory(messaging) diff --git a/LCS/Messaging/python/common/CMakeLists.txt b/LCS/Messaging/python/common/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..d419ad2ef7595b79b4d828cd02dec136e68c5bab --- /dev/null +++ b/LCS/Messaging/python/common/CMakeLists.txt @@ -0,0 +1,13 @@ +# $Id: CMakeLists.txt 720 2014-12-08 16:29:33Z loose $ + +lofar_package(PyCommon 1.0) + +include(PythonInstall) + +#add_subdirectory(test) + +set(_py_files + factory.py + util.py) + +python_install(${_py_files} DESTINATION lofar/common) diff --git a/LCS/Messaging/python/common/__init__.py b/LCS/Messaging/python/common/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/LCS/Messaging/python/common/factory.py b/LCS/Messaging/python/common/factory.py new file mode 100644 index 0000000000000000000000000000000000000000..a80c6137e0aad1042ef22ab3aa149d2135452003 --- /dev/null +++ b/LCS/Messaging/python/common/factory.py @@ -0,0 +1,81 @@ +# factory.py: Generic object factory. +# +# Copyright (C) 2015 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the +# License, or (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# +# $Id: factory.py 1584 2015-10-02 12:10:14Z loose $ + +""" +Module for a generic object factory. +""" + +class Factory(object): + """Generic object factory. + + Factory allows you to create any object that has been registered with it, by + using its `clsid`. The `clsid` can be any type that is usable as a + dictionary key. The `create` method is responsible for creating the object; + it passes `args` and `kwargs` to the constructor of the class that must be + instantiated. + """ + + def __init__(self): + """ + Constructor. + """ + self.__registry = dict() + + def register(self, clsid, cls): + """ + Register the class `cls` using `clsid` as its unique identifier. + Registration will fail if `clsid` already exists in the registry. + + :return: True if registration was successful; otherwise False. + """ + return self.__registry.setdefault(clsid, cls) == cls + + def deregister(self, clsid): + """ + Deregister the class identified by `clsid`. + The class identified by `clsid` will be removed from registry. + + :return: True if deregistration was successful; otherwise False. + + """ + return self.__registry.pop(clsid, None) is not None + + def create(self, clsid, *args, **kwargs): + """ + Create a new class instance of the class identified by `clsid`. + Positional arguments `args` and keyword arguments `kwargs` are passed + to the constructor of the class. + + :return: instance of class identified by `clsid`, or None if `clsid` + cannnot be found in the registry. + + """ + if clsid in self.__registry: + return self.__registry.get(clsid)(*args, **kwargs) + else: + return None + + def registry(self): + """ + Return a copy of the contents of the registry. + """ + return self.__registry.copy() diff --git a/LCS/Messaging/python/common/util.py b/LCS/Messaging/python/common/util.py new file mode 100644 index 0000000000000000000000000000000000000000..100ac836e25b23d1098cdb72ab533dd91a567295 --- /dev/null +++ b/LCS/Messaging/python/common/util.py @@ -0,0 +1,114 @@ +# util.py: utils for lofar software +# +# Copyright (C) 2015 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the +# License, or (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# +# $Id: util.py 1584 2015-10-02 12:10:14Z loose $ +# +""" +This package contains different utilities that are common for LOFAR software +""" + +import sys + + +def check_bit(value, bit): + """ + function to check if a given bit is set + :param value: value to check + :param bit: bit to be checked + :return: true or false + """ + return bool(value & (1 << bit)) + + +def set_bit(value, bit): + """ + function to set a bit in a given value + :param value: value to set bit in + :param bit: bit to set + :return: value after the bit is set + """ + return value | (1 << bit) + + +def clear_bit(value, bit): + """ + function to clear a given bit + :param value: value to clear the bit in + :param bit: bit to clear + :return: value after the bit is cleared + """ + return value & ~(1 << bit) + + +def feq(val_a, val_b, rtol=1e-05, atol=1e-08): + """ + + :param val_a: float a to compare with + :param val_b: float b + :param rtol: The relative tolerance parameter + :param atol: The absolute tolerance parameter + :return: + """ + return abs(val_a - val_b) <= rtol * (abs(val_a) + abs(val_b)) + atol + + +def chunker(seq, size): + """ + function to divide a list into equal chunks + :param seq: initial list + :param size: size of the chunks + :return: + """ + return (seq[pos:pos + size] for pos in xrange(0, len(seq), size)) + + +def raise_exception(cls, msg): + """ + Raise an exception of type `cls`. Augment the exception message `msg` with + the type and value of the last active exception if any. + :param cls: type of exception that will be raised + :param msg: exception message + """ + exc_type, exc_val = sys.exc_info()[:2] + if exc_type is not None: + msg = "%s [%s: %s]" % (msg, exc_type.__name__, exc_val) + raise cls(msg) + + +def isIntList(lst): + """ + function to see if a value is a list of ints + :param lst: the list that needs to be examined + :return: True if it is a list of ints otherwise False + """ + if not isinstance(lst, list): + return False + return all(isinstance(x, int) for x in lst) + + +def isFloatList(lst): + """ + function to see if a value is a list of floats + :param lst: the list that needs to be examined + :return: True if it is a list of ints otherwise False + """ + if not isinstance(lst, list): + return False + return all(isinstance(x, float) for x in lst) diff --git a/LCS/Messaging/python/examples/ToUpperClient b/LCS/Messaging/python/examples/ToUpperClient new file mode 100755 index 0000000000000000000000000000000000000000..fcc226e41155d4a71d58dc9b828e1b462530a547 --- /dev/null +++ b/LCS/Messaging/python/examples/ToUpperClient @@ -0,0 +1,24 @@ +#!/usr/bin/python + +from lofar.messaging.RPC import RPC + +# Simple RPC client for Service 'ToUpper' + +# Used settings +ServiceName="ToUpper" +BusName="simpletest" +NumRequests=10 +Test="Hello World ToUpper " + +# Initialize a Remote Procedure Call object +RPCService=RPC(BusName,ServiceName,timeout=10) + +# 'with' tells the RPC object to connect to the bus +with RPCService: + for requestnr in range(NumRequests): + # execute the RPC Service + replymessage=RPCService(Test+str(requestnr)) + + # show the result from the RPC client + print replymessage + diff --git a/LCS/Messaging/python/examples/ToUpperMapClient b/LCS/Messaging/python/examples/ToUpperMapClient new file mode 100755 index 0000000000000000000000000000000000000000..0171a003a2abce7d28caedbb9c50e2d4eb816ee6 --- /dev/null +++ b/LCS/Messaging/python/examples/ToUpperMapClient @@ -0,0 +1,26 @@ +#!/usr/bin/python + +#from messagebus.RPC import RPC +from lofar.messaging.RPC import RPC + +# Simple RPC client for Service 'ToUpper' + +# Used settings +ServiceName="ToUpper" +BusName="simpletest" +NumRequests=10 +Test={} +Test["One"]="Hello World ToUpper" +Test["Two"]="Bla " + +RPCService=RPC(BusName,ServiceName,timeout=10) + +# loop forever +with RPCService: + for requestnr in range(NumRequests): + # send to the RPC Service + Test["sequence"]="nr:"+str(requestnr) + replymessage=RPCService(Test) + + # show the result from the RPC client + print replymessage diff --git a/LCS/Messaging/python/examples/ToUpperService b/LCS/Messaging/python/examples/ToUpperService new file mode 100755 index 0000000000000000000000000000000000000000..7bd3f2004fe95ad29a7c136426adadfe148078e8 --- /dev/null +++ b/LCS/Messaging/python/examples/ToUpperService @@ -0,0 +1,39 @@ +#!/usr/bin/python + +from lofar.messaging.Service import Service + +# Simple RPC server 'ToUpper' + +# create service function: + +def ToUpper( Text ): + if (isinstance(Text,dict)): + ret={} + for name,value in Text.iteritems(): + ret[name]=str(value).upper() + return ret + if (isinstance(Text,str)): + print "ToUpper : %s" %(Text) + return str(Text).upper() + ret="Failed to convert to upper" + try: + ret="Cannot conver to upper object:" + str(Text) + except Exception as e: + ret="Cannot conver to upper object:" + str(e) + return ret + +# Used settings +ServiceName="ToUpper" +BusName="simpletest" + +# Register function as a service handler listeneing at Bus BusName and ServiceName +myserv = Service(BusName,ServiceName,ToUpper,numthreads=4) + +# 'with' sets up the connection context and defines the scope of the service. +with myserv: + # Start listening in the background. This will start as many threads as defined by the instance + myserv.StartListening() + # wait for control-C or kill + myserv.WaitForInterrupt() + # Tell all background listener threads to stop and wait for them to finish. + myserv.StopListening() diff --git a/LCS/Messaging/python/messaging/CMakeLists.txt b/LCS/Messaging/python/messaging/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..142cd51e6ae2bcf14fc2a5e83ff7dc3c38635948 --- /dev/null +++ b/LCS/Messaging/python/messaging/CMakeLists.txt @@ -0,0 +1,18 @@ +# $Id$ + +#lofar_package(PyMessaging 1.0 DEPENDS PyCommon) + +include(PythonInstall) + +set(_py_files + __init__.py + exceptions.py + messagebus.py + messages.py + RPC.py + Service.py +) + +python_install(${_py_files} DESTINATION lofar/messaging) + +add_subdirectory(test) diff --git a/LCS/Messaging/python/messaging/RPC.py b/LCS/Messaging/python/messaging/RPC.py new file mode 100644 index 0000000000000000000000000000000000000000..fcc7868aac43437b5a871b3ec0b4f8c6b664e660 --- /dev/null +++ b/LCS/Messaging/python/messaging/RPC.py @@ -0,0 +1,143 @@ +# RPC.py: RPC client side used by the lofar.messaging module. +# +# Copyright (C) 2015 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the +# License, or (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# + +# RPC invocation with possible timeout +from lofar.messaging.messagebus import ToBus, FromBus +from lofar.messaging.messages import ServiceMessage, ReplyMessage +import uuid + +class RPC(): + """ + This class provides an easy way to invoke a Remote Rrocedure Call to a + Services on the message bus. + + Note that most methods require that the RPC object is used *inside* a + context. When entering the context, the connection with the broker is + opened, and a session and a sender are created. For each rpc invocation + a new unique reply context is created as to avoid cross talk. + When exiting the context the connection to the broker is closed. + As a side-effect the sender and session are destroyed. + + """ + def __init__(self, bus, service, timeout=None, ForwardExceptions=None, Verbose=None): + """ + Initialize an Remote procedure call using: + bus= <str> Bus Name + service= <str> Service Name + timeout= <float> Time to wait in seconds before the call is considered a failure. + Verbose= <bool> If True output extra logging to stdout. + + Use with extra care: ForwardExceptions= <bool> + This enables forwarding exceptions from the server side tobe raised at the client side durting RPC invocation. + """ + self.timeout = timeout + self.ForwardExceptions = False + self.Verbose = False + if ForwardExceptions is True: + self.ForwardExceptions = True + if Verbose is True: + self.Verbose = True + self.BusName = bus + self.ServiceName = service + if self.BusName is None: + self.Request = ToBus(self.ServiceName) + else: + self.Request = ToBus(self.BusName + "/" + self.ServiceName) + + def __enter__(self): + """ + Internal use only. (handles scope 'with') + """ + self.Request.open() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """ + Internal use only. (handles scope 'with') + """ + self.Request.close() + + def __call__(self, msg, timeout=None): + """ + Enable the use of the object to directly invoke the RPC. + + example: + + with RPC(bus,service) as myrpc: + result=myrpc(request) + + """ + if timeout is None: + timeout = self.timeout + # create unique reply address for this rpc call + options={'create':'always','delete':'receiver'} + ReplyAddress= "reply." + str(uuid.uuid4()) + if self.BusName is None: + Reply = FromBus(ReplyAddress+" ; "+str(options)) + else: + Reply = FromBus(self.BusName + "/" + ReplyAddress) + with Reply: + MyMsg = ServiceMessage(msg, ReplyAddress) + MyMsg.ttl = timeout + self.Request.send(MyMsg) + answer = Reply.receive(timeout) + + status = {} + # Check for Time-Out + if answer is None: + status["state"] = "TIMEOUT" + status["errmsg"] = "RPC Timed out" + status["backtrace"] = "" + return (None, status) + + # Check for illegal message type + if isinstance(answer, ReplyMessage) is False: + # if we come here we had a Time-Out + status["state"] = "ERROR" + status["errmsg"] = "Incorrect messagetype (" + str(type(answer)) + ") received." + status["backtrace"] = "" + return (None, status) + + # return content and status if status is 'OK' + if (answer.status == "OK"): + return (answer.content, answer.status) + + # Compile error handling from status + try: + status["state"] = answer.status + status["errmsg"] = answer.errmsg + status["backtrace"] = answer.backtrace + except Exception as e: + status["state"] = "ERROR" + status["errmsg"] = "Return state in message not found" + status["backtrace"] = "" + return (Null, status) + + # Does the client expect us to throw the exception? + if self.ForwardExceptions is True: + excep_mod = __import__("exceptions") + excep_class_ = getattr(excep_mod, answer.errmsg.split(':')[0], None) + if (excep_class_ != None): + instance = excep_class_(answer.backtrace) + raise (instance) + else: + raise (Exception(answer.errmsg)) + return (None,status) diff --git a/LCS/Messaging/python/messaging/Service.py b/LCS/Messaging/python/messaging/Service.py new file mode 100644 index 0000000000000000000000000000000000000000..aacc512325f3b3b0c7dcd1d935e5b115cc53dcfe --- /dev/null +++ b/LCS/Messaging/python/messaging/Service.py @@ -0,0 +1,257 @@ +#!/usr/bin/python +# Service.py: Service definition for the lofar.messaging module. +# +# Copyright (C) 2015 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the +# License, or (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# + +from lofar.messaging.messagebus import ToBus,FromBus +from lofar.messaging.messages import ReplyMessage,ServiceMessage +import threading +import time +import uuid +import sys +import traceback +import logging + +logger = logging.getLogger(__name__) + + +# create service: +class Service: + """ + Service class for registering python functions with a Service name on a message bus. + create new service with Service( BusName, ServiceName, ServiceHandler ) + Additional options: + options=<dict> for the QPID connection + numthreads=<int> amount of threads processing messages + startonwith=<bool> automatically start listening when in scope using 'with' + verbose=<bool> show debug text + """ + + def __init__(self, busname, servicename, servicehandler, options=None, exclusive=True, numthreads=1, parsefullmessage=False, startonwith=False, verbose=False): + """ + Initialize Service object with busname (str) ,servicename (str) and servicehandler function. + additional parameters: + options= <dict> Dictionary of options passed to QPID + exclusive= <bool> Create an exclusive binding so no other services can consume duplicate messages (default: True) + numthreads= <int> Number of parallel threads processing messages (default: 1) + verbose= <bool> Output extra logging over stdout (default: False) + """ + self.BusName = busname + self.ServiceName = servicename + self.ServiceHandler = servicehandler + self.connected = False + self.running = False + self.exclusive = exclusive + self.link_uuid = str(uuid.uuid4()) + self._numthreads = numthreads + self.Verbose = verbose + self.options = {"capacity": numthreads*20} + self.parsefullmessage=parsefullmessage + self.startonwith = startonwith + + # Set appropriate flags for exclusive binding + if self.exclusive is True: + self.options["link"] = '{name:"' + self.link_uuid + '", x-bindings:[{key:' + self.ServiceName + ', arguments: {"qpid.exclusive-binding":True}}]}' + + # only add options if it is given as a dictionary + if isinstance(options,dict): + for key,val in options.iteritems(): + self.options[key] = val + + def _debug(self, txt): + """ + Internal use only. + """ + if self.Verbose is True: + logger.debug("[Service: %s]", txt) + + def StartListening(self, numthreads=None): + """ + Start the background threads and process incoming messages. + """ + if numthreads is not None: + self._numthreads = numthreads + if self.connected is False: + raise Exception("StartListening Called on closed connections") + + self.running = True + self._tr = [] + self.reccounter = [] + self.okcounter =[] + for i in range(self._numthreads): + self._tr.append(threading.Thread(target=self._loop, args=[i])) + self.reccounter.append(0) + self.okcounter.append(0) + self._tr[i].start() + + def StopListening(self): + """ + Stop the background threads that listen to incoming messages. + """ + # stop all running threads + if self.running is True: + self.running = False + for i in range(self._numthreads): + self._tr[i].join() + logger.info("Thread %2d: STOPPED Listening for messages on Bus %s and service name %s." % (i, self.BusName, self.ServiceName)) + logger.info(" %d messages received and %d processed OK." % (self.reccounter[i], self.okcounter[i])) + + def WaitForInterrupt(self): + """ + Useful (low cpu load) loop that waits for keyboard interrupt. + """ + looping = True + while looping: + try: + time.sleep(10) + except KeyboardInterrupt: + looping = False + logger.info("Keyboard interrupt received.") + + + def __enter__(self): + """ + Internal use only. Handles scope with keyword 'with' + """ + # Usually a service will be listening on a 'bus' implemented by a topic exchange + if self.BusName is not None: + self.Listen = FromBus(self.BusName+"/"+self.ServiceName, options=self.options) + self.Reply = ToBus(self.BusName) + self.Listen.open() + self.Reply.open() + # Handle case when queues are used + else: + # assume that we are listening on a queue and therefore we cannot use a generic ToBus() for replies. + self.Listen = FromBus(self.ServiceName, options=self.options) + self.Listen.open() + self.Reply=None + + self.connected = True + + # If required start listening on 'with' + if self.startonwith is True: + self.StartListening() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """ + Internal use only. Handles scope with keyword 'with' + """ + self.StopListening() + # close the listeners + if self.connected is True: + self.connected = False + if isinstance(self.Listen, FromBus): + self.Listen.close() + if isinstance(self.Reply, ToBus): + self.Reply.close() + + def _send_reply(self, replymessage, status, reply_to, errtxt="",backtrace=""): + """ + Internal use only. Send a reply message to the RPC client including exception info. + """ + # Compose Reply message from reply and status. + if isinstance(replymessage,ReplyMessage): + ToSend = replymessage + else: + ToSend = ReplyMessage(replymessage, reply_to) + ToSend.status = status + ToSend.errmsg = errtxt + ToSend.backtrace = backtrace + + # show the message content if required by the Verbose flag. + if self.Verbose is True: + ToSend.show() + + # send the result to the RPC client + if isinstance(self.Reply,ToBus): + ToSend.subject = reply_to + self.Reply.send(ToSend) + else: + try: + with ToBus(reply_to) as dest: + dest.send(ToSend) + except MessageBusError as e: + logger.error("Failed to send reply to reply address %s" %(reply_to)) + + + def _loop(self, index): + """ + Internal use only. Message listener loop that receives messages and starts the attached function with the message content as argument. + """ + logger.info( "Thread %d START Listening for messages on Bus %s and service name %s." %(index, self.BusName, self.ServiceName)) + while self.running: + try: + # get the next message + msg = self.Listen.receive(1) + # retry if timed-out + if msg is None: + continue + + # report if messages are not Service Messages + if isinstance(msg, ServiceMessage) is not True: + logger.error( "Received wrong messagetype %s, ServiceMessage expected." %(str(type(msg)))) + self.Listen.ack(msg) + continue + + # Keep track of number of received messages + self.reccounter[index] += 1 + + # Execute the service handler function and send reply back to client + try: + self._debug("Running handler") + if self.parsefullmessage is True: + replymessage = self.ServiceHandler(msg) + else: + replymessage = self.ServiceHandler(msg.content) + self._debug("finished handler") + self._send_reply(replymessage,"OK",msg.reply_to) + self.okcounter[index] += 1 + self.Listen.ack(msg) + continue + + except Exception as e: + # Any thrown exceptions either Service exception or unhandled exception + # during the execution of the service handler is caught here. + self._debug("handling exception") + exc_info = sys.exc_info() + status="ERROR" + rawbacktrace = traceback.format_exception(*exc_info) + errtxt = rawbacktrace[-1] + self._debug(rawbacktrace) + # cleanup the backtrace print by removing the first two lines and the last + del rawbacktrace[1] + del rawbacktrace[0] + del rawbacktrace[-1] + backtrace = ''.join(rawbacktrace).encode('latin-1').decode('unicode_escape') + self._debug(backtrace) + if self.Verbose is True: + logger.info("[Service:] Status: %s", str(status)) + logger.info("[Service:] ERRTXT: %s", str(errtxt)) + logger.info("[Service:] BackTrace: %s", str( backtrace )) + self._send_reply(None, status, msg.reply_to, errtxt=errtxt, backtrace=backtrace) + + except Exception as e: + # Unknown problem in the library. Report this and continue. + excinfo = sys.exc_info() + logger.error("[Service:] ERROR during processing of incoming message.") + traceback.print_exception(*excinfo) + logger.info( "Thread %d: Resuming listening on bus %s for service %s" % (index, self.BusName, self.ServiceName)) + diff --git a/LCS/Messaging/python/messaging/__init__.py b/LCS/Messaging/python/messaging/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..ab3e262510f54c3fa75b93fc31e859908c22085f --- /dev/null +++ b/LCS/Messaging/python/messaging/__init__.py @@ -0,0 +1,31 @@ +# __init__.py: Module initialization file. +# +# Copyright (C) 2015 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the +# License, or (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# +# $Id: __init__.py 1568 2015-09-18 15:21:11Z loose $ + +""" +Module initialization file. +""" + +from exceptions import * +from messages import * +from messagebus import * +from RPC import RPC +from Service import Service diff --git a/LCS/Messaging/python/messaging/exceptions.py b/LCS/Messaging/python/messaging/exceptions.py new file mode 100644 index 0000000000000000000000000000000000000000..32e837f3c3b8b92411bf8b2aaa8be833634121ac --- /dev/null +++ b/LCS/Messaging/python/messaging/exceptions.py @@ -0,0 +1,53 @@ +# exceptions.py: Exceptions used by the lofar.messaging module. +# +# Copyright (C) 2015 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the +# License, or (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# +# $Id: exceptions.py 1580 2015-09-30 14:18:57Z loose $ + +""" +Exceptions used by the lofar.messaging module. +""" + +class MessagingError(Exception): + """ + Base exception class for the lofar.messaging package. + """ + pass + + +class InvalidMessage(MessagingError): + """ + Exception raised when an invalid message is received. + """ + pass + + +class MessageBusError(MessagingError): + """ + Exception raised by the FromBus and ToBus classes if communication fails. + """ + pass + +class MessageFactoryError(MessagingError): + """ + Exception raised when the `MessageFactory` does not know how to create + a message object, because it wasn't registered with it. + """ + pass + diff --git a/LCS/Messaging/python/messaging/messagebus.py b/LCS/Messaging/python/messaging/messagebus.py new file mode 100644 index 0000000000000000000000000000000000000000..a6cc7b208c459c5744b22a8522e5608288765f03 --- /dev/null +++ b/LCS/Messaging/python/messaging/messagebus.py @@ -0,0 +1,405 @@ +# messagebus.py: Provide an easy way exchange messages on the message bus. +# +# Copyright (C) 2015 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the +# License, or (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# +# $Id: messagebus.py 1580 2015-09-30 14:18:57Z loose $ + +""" +Provide an easy way exchange messages on the message bus. +""" + +from lofar.messaging.exceptions import MessageBusError, MessageFactoryError +from lofar.messaging.messages import to_qpid_message, MESSAGE_FACTORY +from lofar.common.util import raise_exception + +import qpid.messaging +import logging +import sys + +logger = logging.getLogger(__name__) + +# Default settings for often used parameters. +DEFAULT_ADDRESS_OPTIONS = {'create': 'never'} +DEFAULT_BROKER = "localhost:5672" +DEFAULT_BROKER_OPTIONS = {} +DEFAULT_RECEIVER_CAPACITY = 1 +DEFAULT_TIMEOUT = 5 + + +class FromBus(object): + """ + This class provides an easy way to fetch messages from the message bus. + Note that most methods require that a FromBus object is used *inside* a + context. When entering the context, the connection with the broker is + opened, and a session and a receiver are created. When exiting the context, + the connection to the broker is closed; as a side-effect the receiver(s) + and session are destroyed. + + note:: The rationale behind using a context is that this is (unfortunately) + the *only* way that we can guarantee proper resource management. If there + were a __deinit__() as counterpart to __init__(), we could have used that. + We cannot use __del__(), because it is not the counterpart of __init__(), + but that of __new__(). + """ + + def __init__(self, address, options=None, broker=None): + """ + Initializer. + :param address: valid Qpid address + :param options: valid Qpid address options, e.g. {'create': 'never'} + :param broker: valid Qpid broker URL, e.g. "localhost:5672" + """ + self.address = address + self.options = options if options else DEFAULT_ADDRESS_OPTIONS + self.broker = broker if broker else DEFAULT_BROKER + + self.connection = qpid.messaging.Connection(self.broker, + **DEFAULT_BROKER_OPTIONS) + self.session = None + self.opened=0 + + def open(self): + """ + The following actions will be performed when entering a context: + * connect to the broker + * create a session + * add a receiver + The connection to the broker will be closed if any of these failed. + :raise MessageBusError: if any of the above actions failed. + :return: self + """ + if (self.opened==0): + try: + self.connection.open() + logger.info("[FromBus] Connected to broker: %s", self.broker) + self.session = self.connection.session() + logger.debug("[FromBus] Created session: %s", self.session.name) + self.add_queue(self.address, self.options) + except qpid.messaging.MessagingError: + self.__exit__(*sys.exc_info()) + raise_exception(MessageBusError, "[FromBus] Initialization failed") + except MessageBusError: + self.__exit__(*sys.exc_info()) + raise + self.opened+=1 + + + def __enter__(self): + self.open() + return self + + def close(self): + """ + The following actions will be performed: + * close the connection to the broker + * set session to None + :param exc_type: type of exception thrown in context + :param exc_val: value of exception thrown in context + :param exc_tb: traceback of exception thrown in context + """ + if (self.opened==1): + try: + if self.connection.opened(): + self.connection.close(DEFAULT_TIMEOUT) + except qpid.messaging.exceptions.Timeout: + raise_exception(MessageBusError, + "[FromBus] Failed to disconnect from broker: %s" % + self.broker) + finally: + self.session = None + logger.info("[FromBus] Disconnected from broker: %s", self.broker) + self.opened-=1 + + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + + def _check_session(self): + """ + Check if there's an active session. + :raise MessageBusError: if there's no active session + """ + if self.session is None: + raise MessageBusError( + "[FromBus] No active session (broker: %s)" % self.broker) + + def add_queue(self, address, options=None): + """ + Add a queue that you want to receive messages from. + :param address: valid Qpid address + :param options: dict containing valid Qpid address options + """ + self._check_session() + options = options if options else self.options + options.setdefault("capacity", DEFAULT_RECEIVER_CAPACITY) + what = "receiver for source: %s (broker: %s, session: %s)" % \ + (address, self.broker, self.session.name) + try: + self.session.receiver(address, **options) + except qpid.messaging.MessagingError: + raise_exception(MessageBusError, + "[FromBus] Failed to create %s" % what) + logger.info("[FromBus] Created %s", what) + + def receive(self, timeout=DEFAULT_TIMEOUT): + """ + Receive the next message from any of the queues we're listening on. + :param timeout: maximum time in seconds to wait for a message. + :return: received message, None if timeout occurred. + """ + self._check_session() + logger.debug("[FromBus] Waiting %s seconds for next message", timeout) + try: + recv = self.session.next_receiver(timeout) + msg = recv.fetch() + except qpid.messaging.exceptions.Empty: + logger.debug( + "[FromBus] No message received within %s seconds", timeout) + return None + except qpid.messaging.MessagingError: + raise_exception(MessageBusError, + "[FromBus] Failed to fetch message from queue: " + "%s" % self.address) + logger.info("[FromBus] Message received on queue: %s", self.address) + logger.debug("[FromBus] %s", msg) + try: + amsg = MESSAGE_FACTORY.create(msg) + except MessageFactoryError: + self.reject(msg) + raise_exception(MessageBusError, "[FromBus] Message rejected") + # self.ack(msg) + return amsg + + def ack(self, msg): + """ + Acknowledge a message. This will inform Qpid that the message can + safely be removed from the queue. + :param msg: message to be acknowledged + """ + self._check_session() + qmsg = to_qpid_message(msg) + self.session.acknowledge(qmsg) + logger.debug("[FromBus] acknowledged message: %s", qmsg) + + def nack(self, msg): + """ + Do not acknowledge a message. This will inform Qpid that the message + has to be redelivered. You cannot nack a message that has already + been acknowledged. + :param msg: message to not be acknowledged + + .. attention:: + We should call qpid.messaging.Session.release() here,but that is + not available in Qpid-Python 0.32. We therefore use + qpid.messaging.Session.acknowledge() instead. + """ + logger.warning("[FromBus] nack() is not supported, using ack() instead") + self.ack(msg) + + def reject(self, msg): + """ + Reject a message. This will inform Qpid that the message should not be + redelivered. You cannot reject a message that has already been + acknowledged. + :param msg: message to be rejected + + .. attention:: + We should call qpid.messaging.Session.reject() here, but that is + not available in Qpid-Python 0.32. We therefore use + qpid.messaging.Session.acknowledge() instead. + """ + logger.warning( + "[FromBus] reject() is not supported, using ack() instead") + self.ack(msg) + + +class ToBus(object): + """ + This class provides an easy way to post messages onto the message bus. + + Note that most methods require that a ToBus object is used *inside* a + context. When entering the context, the connection with the broker is + opened, and a session and a sender are created. When exiting the context, + the connection to the broker is closed; as a side-effect the sender and + session are destroyed. + + note:: The rationale behind using a context is that this is (unfortunately) + the *only* way that we can guarantee proper resource management. If there + were a __deinit__() as counterpart to __init__(), we could have used that. + We cannot use __del__(), because it is not the counterpart of __init__(), + but that of __new__(). + """ + + def __init__(self, address, options=None, broker=None): + """ + Initializer. + :param address: valid Qpid address + :param options: valid Qpid address options, e.g. {'create': 'never'} + :param broker: valid Qpid broker URL, e.g. "localhost:5672" + """ + self.address = address + self.options = options if options else DEFAULT_ADDRESS_OPTIONS + self.broker = broker if broker else DEFAULT_BROKER + + self.connection = qpid.messaging.Connection(self.broker, + **DEFAULT_BROKER_OPTIONS) + self.session = None + self.opened = 0 + + def open(self): + if (self.opened==0): + try: + self.connection.open() + logger.info("[ToBus] Connected to broker: %s", self.broker) + self.session = self.connection.session() + logger.debug("[ToBus] Created session: %s", self.session.name) + self._add_queue(self.address, self.options) + except qpid.messaging.MessagingError: + self.__exit__(*sys.exc_info()) + raise_exception(MessageBusError, "[ToBus] Initialization failed") + except MessageBusError: + self.__exit__(*sys.exc_info()) + raise + self.opened+=1 + + + def __enter__(self): + """ + The following actions will be performed when entering a context: + * connect to the broker + * create a session + * add a sender + The connection to the broker will be closed if any of these failed. + :raise MessageBusError: if any of the above actions failed. + :return: self + """ + """ + try: + self.connection.open() + logger.info("[ToBus] Connected to broker: %s", self.broker) + self.session = self.connection.session() + logger.debug("[ToBus] Created session: %s", self.session.name) + self._add_queue(self.address, self.options) + except qpid.messaging.MessagingError: + self.__exit__(*sys.exc_info()) + raise_exception(MessageBusError, "[ToBus] Initialization failed") + except MessageBusError: + self.__exit__(*sys.exc_info()) + raise + """ + self.open() + return self + + def close(self): + if (self.opened==1): + try: + if self.connection.opened(): + self.connection.close(DEFAULT_TIMEOUT) + except qpid.messaging.exceptions.Timeout: + raise_exception(MessageBusError, + "[ToBus] Failed to disconnect from broker %s" % + self.broker) + finally: + self.session = None + self.opened-=1 + + def __exit__(self, exc_type, exc_val, exc_tb): + """ + The following actions will be performed: + * close the connection to the broker + * set `session` and `sender` to None + :param exc_type: type of exception thrown in context + :param exc_val: value of exception thrown in context + :param exc_tb: traceback of exception thrown in context + :raise MessageBusError: if disconnect from broker fails + """ + try: + if self.connection.opened(): + self.connection.close(DEFAULT_TIMEOUT) + except qpid.messaging.exceptions.Timeout: + raise_exception(MessageBusError, + "[ToBus] Failed to disconnect from broker %s" % + self.broker) + finally: + self.session = None + logger.info("[ToBus] Disconnected from broker: %s", self.broker) + + def _check_session(self): + """ + Check if there's an active session. + :raise MessageBusError: if there's no active session + """ + if self.session is None: + raise MessageBusError("[ToBus] No active session (broker: %s)" % + self.broker) + + def _get_sender(self): + """ + Get the sender associated with the current session. + :raise MessageBusError: if there's no active session, or if there's not + exactly one sender + :return: sender object + """ + self._check_session() + nr_senders = len(self.session.senders) + if nr_senders == 1: + return self.session.senders[0] + else: + msg = "No senders" if nr_senders == 0 else "More than one sender" + raise MessageBusError("[ToBus] %s (broker: %s, session %s)" % + (msg, self.broker, self.session)) + + def _add_queue(self, address, options): + """ + Add a queue that you want to sends messages to. + :param address: valid Qpid address + :param options: dict containing valid Qpid address options + :raise MessageBusError: if sender could not be created + """ + self._check_session() + what = "sender for target: %s (broker: %s, session: %s)" % ( + address, self.broker, self.session.name) + try: + self.session.sender(address, **options) + except qpid.messaging.MessagingError: + raise_exception(MessageBusError, + "[ToBus] Failed to create %s" % what) + logger.info("[ToBus] Created %s", what) + + def send(self, message, timeout=DEFAULT_TIMEOUT): + """ + Send a message to the exchange (target) we're connected to. + :param message: message to be sent + :param timeout: maximum time in seconds to wait for send action + :return: + """ + sender = self._get_sender() + qmsg = to_qpid_message(message) + logger.debug("[ToBus] Sending message to queue: %s (%s)", + sender.target, qmsg) + try: + sender.send(qmsg, timeout=timeout) + except qpid.messaging.MessagingError: + raise_exception(MessageBusError, + "[ToBus] Failed to send message to queue: %s" % + sender.target) + logger.info("[ToBus] Message sent to queue: %s", sender.target) + + +__all__ = ["FromBus", "ToBus"] diff --git a/LCS/Messaging/python/messaging/messages.py b/LCS/Messaging/python/messaging/messages.py new file mode 100644 index 0000000000000000000000000000000000000000..9dcf7131286e6a9d24b65110b36ed06fe3db520e --- /dev/null +++ b/LCS/Messaging/python/messaging/messages.py @@ -0,0 +1,305 @@ +# messages.py: Message classes used by the package lofar.messaging. +# +# Copyright (C) 2015 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the +# License, or (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# +# $Id: messages.py 1580 2015-09-30 14:18:57Z loose $ + +""" +Message classes used by the package lofar.messaging. +""" + +import qpid.messaging +import uuid + +from lofar.common.factory import Factory +from lofar.messaging.exceptions import InvalidMessage, MessageFactoryError + + +# Valid QPID message fields (from qpid.messaging.Message) +_QPID_MESSAGE_FIELDS = { + 'content', 'content_type', 'correlation_id', 'durable', 'id', + 'priority', 'properties', 'reply_to', 'subject', 'ttl', 'user_id'} + + +def validate_qpid_message(qmsg): + """ + Validate Qpid message `qmsg`. A Qpid message is required to contain the + following properties in order to be considered valid: + - "SystemName" : "LOFAR" + - "MessageType" : message type string + - "MessageId" : a valid UUID string + :raises InvalidMessage: if any of the required properties are missing in + the Qpid message + """ + required_props = {"SystemName", "MessageType", "MessageId"} + if not isinstance(qmsg, qpid.messaging.Message): + raise InvalidMessage( + "Not a Qpid Message: %r" % type(qmsg) + ) + msg_props = qmsg.properties + if not isinstance(msg_props, dict): + raise InvalidMessage( + "Invalid message properties type: %r (expected %r)" % + (type(msg_props), type(dict())) + ) + illegal_props = _QPID_MESSAGE_FIELDS.intersection(msg_props) + if illegal_props: + raise InvalidMessage( + "Illegal message propert%s (Qpid reserved): %r" % + ("ies" if len(illegal_props) > 1 else "y", ', '.join(illegal_props)) + ) + missing_props = required_props.difference(msg_props) + if missing_props: + raise InvalidMessage( + "Missing message propert%s: %s" % + ("ies" if len(missing_props) > 1 else "y", ', '.join(missing_props)) + ) + sysname, _, msgid = (msg_props[prop] for prop in required_props) + if sysname != "LOFAR": + raise InvalidMessage( + "Invalid message property 'SystemName': %s" % sysname + ) + try: + uuid.UUID(msgid) + except Exception: + raise InvalidMessage( + "Invalid message property 'MessageId': %s" % msgid + ) + + +def to_qpid_message(msg): + """ + Convert `msg` into a Qpid message. + :param msg: Message to be converted into a Qpid message. + :return: Qpid message + :raise InvalidMessage if `msg` cannot be converted into a Qpid message. + """ + if isinstance(msg, qpid.messaging.Message): + return msg + if isinstance(msg, LofarMessage): + return msg.qpid_msg + raise InvalidMessage("Invalid message type: %r" % type(msg)) + + +class MessageFactory(Factory): + """ + Factory to produce LofarMessage objects. + """ + + def create(self, qmsg): + """ + Override the create method to restrict the number of arguments to one + and to do some extra testing. + :param qmsg: Qpid message that must be converted into an LofarMessage. + :return: Instance of a child class of LofarMessage. + :raise MessageFactoryError: if the MessageType property of the Qpid + message `qmsg` contains a type name that is not registered with the + factory. + """ + validate_qpid_message(qmsg) + clsid = qmsg.properties['MessageType'] + msg = super(MessageFactory, self).create(clsid, qmsg) + if msg is None: + raise MessageFactoryError( + "Don't know how to create a message of type %s. " + "Did you register the class with the factory?" % clsid) + return msg + + +# Global MessageFactory object, to be used by the lofar.messaging package, +MESSAGE_FACTORY = MessageFactory() + + +class LofarMessage(object): + """ + Describes the content of a message, which can be constructed from either a + set of fields, or from an existing QPID message. + + We want to provide a uniform interface to the user so that any message + property or field, either defined by Qpid of by us, can be accessed as an + object attribute. To do so, we needed to implement the `__getattr__` and + `__setattr` methods; they "extract" our message properties from the Qpid + message properties and provide direct access to them. + """ + + def __init__(self, content=None): + """Constructor. + + :param content: Content can either be a qpid.messaging.Message object, + or an object that is valid content for a qpid.messaging.Message. In the + first case, `content` must contain all the message properties that are + required by LOFAR. + :raise InvalidMessage if `content` cannot be used to initialize an + LofarMessage object. + + note:: Because every access to attributes will be caught by + `__getattr__` and `__setattr__`, we need to use `self.__dict__` to + initialize our attributes; otherwise a `KeyError` exception will be + raised. + """ + if isinstance(content, qpid.messaging.Message): + validate_qpid_message(content) + self.__dict__['_qpid_msg'] = content + else: + try: + self.__dict__['_qpid_msg'] = qpid.messaging.Message(content) + except KeyError: + raise InvalidMessage( + "Unsupported content type: %r" % type(content)) + else: + self._qpid_msg.properties.update({ + 'SystemName': 'LOFAR', + 'MessageId': str(uuid.uuid4()), + 'MessageType': self.__class__.__name__}) + + def __getattr__(self, name): + """ + Catch read-access to attributes to fetch our message properties from + the Qpid message properties field. Direct access to the Qpid message + properties is not allowed. + + :raises: AttributeError + """ + if name != 'properties': + if name in _QPID_MESSAGE_FIELDS: + return self.__dict__['_qpid_msg'].__dict__[name] + if name in self.__dict__['_qpid_msg'].__dict__['properties']: + return self.__dict__['_qpid_msg'].__dict__['properties'][name] + raise AttributeError("%r object has no attribute %r" % + (self.__class__.__name__, name)) + + def __setattr__(self, name, value): + """ + Catch write-access to data members to put our message properties into + the Qpid message properties field. Direct access to the Qpid message + properties is not allowed. + + :raises: AttributeError + """ + if name != 'properties': + if name in _QPID_MESSAGE_FIELDS: + self.__dict__['_qpid_msg'].__dict__[name] = value + else: + self.__dict__['_qpid_msg'].__dict__['properties'][name] = value + else: + raise AttributeError("%r object has no attribute %r" % + (self.__class__.__name__, name)) + + def prop_names(self): + """ + Return a list of all the message property names that are currently + defined. + """ + return list( + _QPID_MESSAGE_FIELDS.union(self._qpid_msg.properties) - + {'properties'} + ) + + @property + def qpid_msg(self): + """ + Return the Qpid message object itself. + """ + return self._qpid_msg + + def show(self): + """ + Print all the properties of the current message. Make a distinction + between user-defined properties and standard Qpid properties. + """ + for (key, value) in \ + self.__dict__['_qpid_msg'].__dict__['properties'].iteritems(): + print key, ":", value + print "---" + for key in _QPID_MESSAGE_FIELDS: + if (key != 'properties' and + self.__dict__['_qpid_msg'].__dict__[key] is not None): + print key, ":", self.__dict__['_qpid_msg'].__dict__[key] + print "===" + + +class EventMessage(LofarMessage): + """ + Message class used for event messages. Events are messages that *must* + be delivered. If the message cannot be delivered to the recipient, it + will be stored in a persistent queue for later delivery. + """ + + def __init__(self, content=None): + super(EventMessage, self).__init__(content) + self.durable = True + + +class MonitoringMessage(LofarMessage): + """ + Message class used for monitoring messages. Monitoring messages are + publish-subscribe type of messages. They will be not be queued, so they + will be lost if there are no subscribers. + """ + + def __init__(self, content=None): + super(MonitoringMessage, self).__init__(content) + + +class ProgressMessage(LofarMessage): + """ + Message class used for progress messages. Progress messages are + publish-subscribe type of messages. They will be not be queued, so they + will be lost if there are no subscribers. + """ + + def __init__(self, content=None): + super(ProgressMessage, self).__init__(content) + + +class ServiceMessage(LofarMessage): + """ + Message class used for service messages. Service messages are + request-reply type of messages. They are typically used to query a + subsystem. A service message must contain a valid ``ReplyTo`` property. + """ + + def __init__(self, content=None, reply_to=None): + super(ServiceMessage, self).__init__(content) + if (reply_to!=None): + self.reply_to = reply_to + + +class ReplyMessage(LofarMessage): + """ + Message class used for reply messages. Reply messages are part of the + request-reply type of messages. They are typically used as a reply on a service + message. These use topic exchanges and thus are routed by the 'subject' property + """ + + def __init__(self, content=None, reply_to=None): + super(ReplyMessage, self).__init__(content) + if (reply_to!=None): + self.subject = reply_to + + + +MESSAGE_FACTORY.register("EventMessage", EventMessage) +MESSAGE_FACTORY.register("MonitoringMessage", MonitoringMessage) +MESSAGE_FACTORY.register("ProgressMessage", ProgressMessage) +MESSAGE_FACTORY.register("ServiceMessage", ServiceMessage) +MESSAGE_FACTORY.register("ReplyMessage", ReplyMessage) + +__all__ = ["EventMessage", "MonitoringMessage", "ProgressMessage", + "ServiceMessage", "ReplyMessage"] diff --git a/LCS/Messaging/python/messaging/test/CMakeLists.txt b/LCS/Messaging/python/messaging/test/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..12bdeb7d46f7690b42347c09a2cbcb0d24f5e889 --- /dev/null +++ b/LCS/Messaging/python/messaging/test/CMakeLists.txt @@ -0,0 +1,7 @@ +# $Id: CMakeLists.txt 1576 2015-09-29 15:22:28Z loose $ + +include(LofarCTest) + +lofar_add_test(t_messages) +lofar_add_test(t_messagebus) +lofar_add_test(t_RPC) diff --git a/LCS/Messaging/python/messaging/test/t_RPC.py b/LCS/Messaging/python/messaging/test/t_RPC.py new file mode 100644 index 0000000000000000000000000000000000000000..a3703443793320f746b9aef0f2c7200fa4a8c0e1 --- /dev/null +++ b/LCS/Messaging/python/messaging/test/t_RPC.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python +""" +Program to test the RPC and Service class of the Messaging package. +It defines 5 functions and first calls those functions directly to check +that the functions are OK. Next the same tests are done with the RPC and +Service classes in between. This should give the same results. +""" +import sys +from lofar.messaging import Service, RPC + +class UserException(Exception): + "Always thrown in one of the functions" + pass +class InvalidArgType(Exception): + "Thrown when the input is wrong for one of the functions" + pass + +# create several function: +def ErrorFunc(input_value): + " Always thrown a predefined exception" + raise UserException("Exception thrown by the user") + +def ExceptionFunc(input_value): + "Generate a exception not caught by the function" + a = "aap" + b = a[23] + +def StringFunc(input_value): + "Convert the string to uppercase." + if not isinstance(input_value, str) and not isinstance(input_value, unicode): + raise InvalidArgType("Input value must be of the type 'string'") + return input_value.upper() + +def ListFunc(input_value): + "Convert the list to uppercase." + if not isinstance(input_value, list): + raise InvalidArgType("Input value must be of the type 'list'") + result = [] + for item in input_value: + if isinstance(item, str) or isinstance(item, unicode): + result.append(item.upper()) + elif isinstance(item, list): + result.append(ListFunc(item)) + elif isinstance(item, dict): + result.append(DictFunc(item)) + else: + result.append(item) + return result + +def DictFunc(input_value): + "Convert the dict to uppercase." + if not isinstance(input_value, dict): + raise InvalidArgType("Input value must be of the type 'dict'") + result = {} + for key, value in input_value.items(): + if isinstance(value, str) or isinstance(value, unicode): + result[key] = str(value).upper() + elif isinstance(value, list): + result[key] = ListFunc(value) + elif isinstance(value, dict): + result[key] = DictFunc(value) + else: + result[key] = value + return result + +if __name__ == '__main__': + # First do basic test for the functions + # ErrorFunc + try: + result = ErrorFunc("aap noot mies") + except UserException as e: + pass + + # ExceptionFunc + try: + result = ExceptionFunc("aap noot mies") + except IndexError as e: + pass + + # StringFunc + try: + result = StringFunc(25) + except InvalidArgType as e: + pass + result = StringFunc("aap noot mies") + if result != "AAP NOOT MIES": + raise Exception("String function failed:{}".format(result)) + + # ListFunc + try: + result = ListFunc(25) + except InvalidArgType as e: + pass + result = ListFunc(["aap", 25, [1, 2], {'mies' : "meisje"}]) + if result != ["AAP", 25, [1, 2], {'mies' : "MEISJE"}]: + raise Exception("List function failed:{}".format(result)) + + # DictFunc + try: + result = DictFunc(25) + except InvalidArgType as e: + pass + result = DictFunc({'mies' : "meisje", "aap" : 125, "noot" : [2, 3]}) + if result != {'mies' : "MEISJE", "aap" : 125, "noot" : [2, 3]}: + raise Exception("Dict function failed:{}".format(result)) + + print "Functions tested outside RPC: All OK" + + # Used settings + busname = sys.argv[1] if len(sys.argv) > 1 else "simpletest" + + # Register functs as a service handler listening at busname and ServiceName + serv1 = Service(busname, "ErrorService", ErrorFunc, numthreads=1) + serv2 = Service(busname, "ExceptionService", ExceptionFunc, numthreads=1) + serv3 = Service(busname, "StringService", StringFunc, numthreads=1) + serv4 = Service(busname, "ListService", ListFunc, numthreads=1) + serv5 = Service(busname, "DictService", DictFunc, numthreads=1) + + # 'with' sets up the connection context and defines the scope of the service. + with serv1, serv2, serv3, serv4, serv5: + # Start listening in the background. This will start as many threads as defined by the instance + serv1.StartListening() + serv2.StartListening() + serv3.StartListening() + serv4.StartListening() + serv5.StartListening() + + # Redo all tests but via through RPC + # ErrorFunc + with RPC(busname, "ErrorService") as rpc: + try: + result = rpc("aap noot mies") + except UserException as e: + pass + + # ExceptionFunc + with RPC(busname, "ExceptionService") as rpc: + try: + result = rpc("aap noot mies") + except IndexError as e: + pass + + # StringFunc + with RPC(busname, "StringService") as rpc: + try: + result = rpc([25]) + except InvalidArgType as e: + pass + result = rpc("aap noot mies") + if result[0] != "AAP NOOT MIES": + raise Exception("String function failed:{}".format(result)) + + # ListFunc + with RPC(busname, "ListService") as rpc: + try: + result = rpc("25") + except InvalidArgType as e: + pass + result = rpc(["aap", 25, [1, 2], {'mies' : "meisje"}]) + if result[0] != ["AAP", 25, [1, 2], {'mies' : "MEISJE"}]: + raise Exception("List function failed:{}".format(result)) + + # DictFunc + with RPC(busname, "DictService") as rpc: + try: + result = rpc([25]) + except InvalidArgType as e: + pass + result = rpc({'mies' : "meisje", "aap" : 125, "noot" : [2, 3]}) + if result[0] != {'mies' : "MEISJE", "aap" : 125, "noot" : [2, 3]}: + raise Exception("Dict function failed:{}".format(result)) + + print "Functions tested with RPC: All OK" + + # Tell all background listener threads to stop and wait for them to finish. + serv1.StopListening() + serv2.StopListening() + serv3.StopListening() + serv4.StopListening() + serv5.StopListening() diff --git a/LCS/Messaging/python/messaging/test/t_RPC.run b/LCS/Messaging/python/messaging/test/t_RPC.run new file mode 100755 index 0000000000000000000000000000000000000000..425d6e5804747dd93ce539c2fa49dac358a74d87 --- /dev/null +++ b/LCS/Messaging/python/messaging/test/t_RPC.run @@ -0,0 +1,38 @@ +#!/bin/sh -e + +#cleanup on normal exit and on SIGHUP, SIGINT, SIGQUIT, and SIGTERM +trap 'qpid-config del exchange --force $queue' 0 1 2 3 15 + +# Generate randome queue name +queue=$(< /dev/urandom tr -dc [:alnum:] | head -c16) + +# Create the queue +qpid-config add exchange topic $queue + +# Run the unit test +# either with or without code coverage measurements, +# depending wheter coverage has been installed + +if type "coverage" > /dev/null; then + #run test using python coverage tool + + #erase previous results + coverage erase + + #setup coverage config file + printf "[report]\nexclude_lines = \n if __name__ == .__main__.\n def main\n" > .coveragerc + + coverage run --branch --include=*Messaging/python* t_RPC.py $queue + RESULT=$? + if [ $RESULT -eq 0 ]; then + echo " *** Code coverage results *** " + coverage report -m + echo " *** End coverage results *** " + fi + exit $RESULT +else + #coverage not available + echo "Please run: 'pip install coverage' to enable code coverage reporting of the unit tests" + #run plain test script + python t_RPC.py $queue +fi diff --git a/LCS/Messaging/python/messaging/test/t_RPC.sh b/LCS/Messaging/python/messaging/test/t_RPC.sh new file mode 100755 index 0000000000000000000000000000000000000000..eb1689b70c276d6ddad0d069dfc98ca2522749e0 --- /dev/null +++ b/LCS/Messaging/python/messaging/test/t_RPC.sh @@ -0,0 +1,4 @@ +#!/bin/sh +./runctest.sh t_RPC + + diff --git a/LCS/Messaging/python/messaging/test/t_messagebus.py b/LCS/Messaging/python/messaging/test/t_messagebus.py new file mode 100644 index 0000000000000000000000000000000000000000..4bd9fb998ed7b7f842330ce6c673d030f3a73b25 --- /dev/null +++ b/LCS/Messaging/python/messaging/test/t_messagebus.py @@ -0,0 +1,292 @@ +# t_messagebus.py: Test program for the module lofar.messaging.messagebus +# +# Copyright (C) 2015 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the +# License, or (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# +# $Id: t_messagebus.py 1580 2015-09-30 14:18:57Z loose $ + +""" +Test program for the module lofar.messaging.messagebus +""" + +import re +import struct +import sys +import unittest + +from lofar.messaging.messages import * +from lofar.messaging.messagebus import * +from lofar.messaging.exceptions import MessageBusError, InvalidMessage + +TIMEOUT = 0.1 + + +# ======== FromBus unit tests ======== # + +class FromBusInitFailed(unittest.TestCase): + """ + Class to test initialization failures of FromBus + """ + + def setUp(self): + self.error = "[FromBus] Initialization failed" + + def test_no_broker_address(self): + """ + Connecting to non-existent broker address must raise MessageBusError + """ + regexp = re.escape(self.error) + regexp += '.*' + 'No address associated with hostname' + with self.assertRaisesRegexp(MessageBusError, regexp): + with FromBus(QUEUE, broker="foo.bar"): + pass + + def test_connection_refused(self): + """ + Connecting to broker on wrong port must raise MessageBusError + """ + regexp = re.escape(self.error) + '.*' + 'Connection refused' + with self.assertRaisesRegexp(MessageBusError, regexp): + with FromBus("fake" + QUEUE, broker="localhost:4"): + pass + + +class FromBusNotInContext(unittest.TestCase): + """ + Class to test that exception is raised when FromBus is used outside context + """ + + def setUp(self): + self.frombus = FromBus(QUEUE) + self.error = "[FromBus] No active session" + + def test_add_queue_raises(self): + """ + Adding a queue when outside context must raise MessageBusError + """ + with self.assertRaisesRegexp(MessageBusError, re.escape(self.error)): + self.frombus.add_queue("fooqueue") + + def test_receive_raises(self): + """ + Getting a message when outside context must raise MessageBusError + """ + with self.assertRaisesRegexp(MessageBusError, re.escape(self.error)): + self.frombus.receive() + + def test_ack_raises(self): + """ + Ack-ing a message when outside context must raise MessageBusError + """ + with self.assertRaisesRegexp(MessageBusError, re.escape(self.error)): + self.frombus.ack(None) + + def test_nack_raises(self): + """ + Nack-ing a message when outside context must raise MessageBusError + """ + with self.assertRaisesRegexp(MessageBusError, re.escape(self.error)): + self.frombus.nack(None) + + def test_reject_raises(self): + """ + Rejecting a message when outside context must raise MessageBusError + """ + with self.assertRaisesRegexp(MessageBusError, re.escape(self.error)): + self.frombus.reject(None) + + +class FromBusInContext(unittest.TestCase): + """ + Class to test FromBus when inside context. + """ + + def setUp(self): + self.frombus = FromBus(QUEUE) + self.error = "[FromBus] Failed to create receiver for source" + + def test_add_queue_fails(self): + """ + Adding a non-existent queue must raise MessageBusError + """ + queue = "fake" + QUEUE + regexp = re.escape(self.error) + '.*' + 'NotFound: no such queue' + with self.assertRaisesRegexp(MessageBusError, regexp): + with self.frombus: + self.frombus.add_queue(queue) + + def test_add_queue_succeeds(self): + """ + Adding an existing queue must succeed, resulting in one more receiver + """ + with self.frombus: + nr_recv = len(self.frombus.session.receivers) + self.frombus.add_queue(QUEUE) + self.assertEqual(nr_recv + 1, len(self.frombus.session.receivers)) + + def test_receive_timeout(self): + """ + Getting a message when there's none must yield None after timeout. + """ + with self.frombus: + self.assertIsNone(self.frombus.receive(timeout=TIMEOUT)) + + +# ======== ToBus unit tests ======== # + +class ToBusInitFailed(unittest.TestCase): + """ + Class to test initialization failures of ToBus + """ + + def setUp(self): + self.error = "[ToBus] Initialization failed" + + def test_no_broker_address(self): + """ + Connecting to non-existent broker address must raise MessageBusError + """ + regexp = re.escape(self.error) + regexp += '.*' + 'No address associated with hostname' + with self.assertRaisesRegexp(MessageBusError, regexp): + with ToBus(QUEUE, broker="foo.bar"): + pass + + def test_connection_refused(self): + """ + Connecting to broker on wrong port must raise MessageBusError + """ + regexp = re.escape(self.error) + '.*' + 'Connection refused' + with self.assertRaisesRegexp(MessageBusError, regexp): + with ToBus(QUEUE, broker="localhost:4"): + pass + + +class ToBusSendMessage(unittest.TestCase): + """ + Class to test different error conditions when sending a message + """ + + def setUp(self): + self.tobus = ToBus(QUEUE) + + def test_send_outside_context_raises(self): + """ + If a ToBus object is used outside a context, then there's no active + session, and a MessageBusError must be raised. + """ + regexp = re.escape("[ToBus] No active session") + with self.assertRaisesRegexp(MessageBusError, regexp): + self.tobus.send(None) + + def test_no_senders_raises(self): + """ + If there are no senders, then a MessageBusError must be raised. + Note that this can only happen if someone has deliberately tampered with + the ToBus object. + """ + with self.tobus: + del self.tobus.session.senders[0] + regexp = re.escape("[ToBus] No senders") + self.assertRaisesRegexp(MessageBusError, regexp, + self.tobus.send, None) + + def test_multiple_senders_raises(self): + """ + If there's more than one sender, then a MessageBusError must be raised. + Note that this can only happen if someone has deliberately tampered with + the ToBus object (e.g., by using the protected _add_queue() method). + """ + with self.tobus: + self.tobus._add_queue(QUEUE, {}) + regexp = re.escape("[ToBus] More than one sender") + self.assertRaisesRegexp(MessageBusError, regexp, + self.tobus.send, None) + + def test_send_invalid_message_raises(self): + """ + If an invalid message is sent (i.e., not an LofarMessage), then an + InvalidMessage must be raised. + """ + with self.tobus: + regexp = re.escape("Invalid message type") + self.assertRaisesRegexp(InvalidMessage, regexp, + self.tobus.send, "Blah blah blah") + + +# ======== Combined FromBus/ToBus unit tests ======== # + +class SendReceiveMessage(unittest.TestCase): + """ + Class to test sending and receiving a message. + """ + + def setUp(self): + self.frombus = FromBus(QUEUE) + self.tobus = ToBus(QUEUE) + + def _test_sendrecv(self, send_msg): + """ + Helper class that implements the send/receive logic and message checks. + :param send_msg: Message to send + """ + with self.tobus: + self.tobus.send(send_msg) + with self.frombus: + recv_msg = self.frombus.receive(timeout=TIMEOUT) + self.frombus.ack(recv_msg) + self.assertEqual( + (send_msg.SystemName, send_msg.MessageId, send_msg.MessageType), + (recv_msg.SystemName, recv_msg.MessageId, recv_msg.MessageType)) + self.assertEqual( + (send_msg.content, send_msg.content_type), + (recv_msg.content, recv_msg.content_type)) + + def test_sendrecv_event_message(self): + """ + Test send/receive of an EventMessage, containing a string. + """ + content = "An event message" + self._test_sendrecv(EventMessage(content)) + + def test_sendrecv_monitoring_message(self): + """ + Test send/receive of an MonitoringMessage, containing a python list. + """ + content = ["A", "monitoring", "message"] + self._test_sendrecv(MonitoringMessage(content)) + + def test_sendrecv_progress_message(self): + """ + Test send/receive of an ProgressMessage, containing a python dict. + """ + content = {"Progress": "Message"} + self._test_sendrecv(ProgressMessage(content)) + + def test_sendrecv_service_message(self): + """ + Test send/receive of an ServiceMessage, containing a byte array. + """ + content = struct.pack("17B", *(ord(c)+32 for c in "A service message")) + self._test_sendrecv(ServiceMessage(content, reply_to=QUEUE)) + + +if __name__ == '__main__': + QUEUE = sys.argv[1] if len(sys.argv) > 1 else "testqueue" + del sys.argv[1:] + unittest.main(verbosity=2) diff --git a/LCS/Messaging/python/messaging/test/t_messagebus.run b/LCS/Messaging/python/messaging/test/t_messagebus.run new file mode 100755 index 0000000000000000000000000000000000000000..a4f60936f26653a1b80028b60e0a4092f54ab3d1 --- /dev/null +++ b/LCS/Messaging/python/messaging/test/t_messagebus.run @@ -0,0 +1,38 @@ +#!/bin/sh -e + +# Cleanup on normal exit and on SIGHUP, SIGINT, SIGQUIT, and SIGTERM +trap 'qpid-config del queue --force $queue' 0 1 2 3 15 + +# Generate randome queue name +queue=$(< /dev/urandom tr -dc [:alnum:] | head -c16) + +# Create the queue +qpid-config add queue $queue + +# Run the unit test +# either with or without code coverage measurements, +# depending wheter coverage has been installed + +if type "coverage" > /dev/null; then + #run test using python coverage tool + + #erase previous results + coverage erase + + #setup coverage config file + printf "[report]\nexclude_lines = \n if __name__ == .__main__.\n def main\n" > .coveragerc + + coverage run --branch --include=*Messaging/python* t_messagebus.py $queue + RESULT=$? + if [ $RESULT -eq 0 ]; then + echo " *** Code coverage results *** " + coverage report -m + echo " *** End coverage results *** " + fi + exit $RESULT +else + #coverage not available + echo "Please run: 'pip install coverage' to enable code coverage reporting of the unit tests" + #run plain test script + python t_messagebus.py $queue +fi diff --git a/LCS/Messaging/python/messaging/test/t_messagebus.sh b/LCS/Messaging/python/messaging/test/t_messagebus.sh new file mode 100755 index 0000000000000000000000000000000000000000..173f4505d8ee91269ad579535651ef935dbb93d2 --- /dev/null +++ b/LCS/Messaging/python/messaging/test/t_messagebus.sh @@ -0,0 +1,2 @@ +#!/bin/sh +./runctest.sh t_messagebus diff --git a/LCS/Messaging/python/messaging/test/t_messages.py b/LCS/Messaging/python/messaging/test/t_messages.py new file mode 100644 index 0000000000000000000000000000000000000000..dfc8f4de3a574284f7ea653358989ba6309ca3f1 --- /dev/null +++ b/LCS/Messaging/python/messaging/test/t_messages.py @@ -0,0 +1,289 @@ +# t_message.py: Test program for the module lofar.messaging.message +# +# Copyright (C) 2015 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the +# License, or (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# +# $Id: t_messages.py 1576 2015-09-29 15:22:28Z loose $ + +""" +Test program for the module lofar.messaging.message +""" + +import unittest +import uuid +import struct +import qpid.messaging +from lofar.messaging.messages import LofarMessage, InvalidMessage + + +class DefaultLofarMessage(unittest.TestCase): + """ + Class to test default constructed LofarMessage class + """ + + def setUp(self): + """ + Create default constructed object + """ + self.message = LofarMessage() + + def test_system_name(self): + """ + Object attribute SystemName must be set to 'LOFAR' + """ + self.assertEqual(self.message.SystemName, "LOFAR") + + def test_message_id(self): + """ + Object attribute MessageId must be a valid UUID string + """ + self.assertIsNotNone(uuid.UUID(self.message.MessageId)) + + +class QpidLofarMessage(unittest.TestCase): + """ + Class to test LofarMessage constructed from a Qpid message + """ + + def setUp(self): + """ + Create Qpid message with all required properties set + """ + self.qmsg = qpid.messaging.Message() + self.qmsg.properties = { + "SystemName": "LOFAR", + "MessageType": None, + "MessageId": str(uuid.uuid4()) + } + + def test_invalid_properties_type(self): + """ + Test that exception is raised if Qpid message properties attribute is + of incorrect type (i.e. not 'dict'). + """ + self.qmsg.properties = 42 + self.assertRaisesRegexp(InvalidMessage, + "^Invalid message properties type:", + LofarMessage, self.qmsg) + + def test_illegal_properties(self): + """ + Test that exception is raised if a Qpid-reserved attribute (like + 'content', 'content_type', etc.) is used as property. + """ + self.qmsg.properties['content'] = 'blah blah blah' + self.assertRaisesRegexp(InvalidMessage, + "^Illegal message propert(y|ies).*:", + LofarMessage, self.qmsg) + + def test_missing_properties(self): + """ + Test that exception is raised if required properties for constructing + an LofarMessage are missing. + """ + self.qmsg.properties = {} + self.assertRaisesRegexp(InvalidMessage, + "^Missing message propert(y|ies):", + LofarMessage, self.qmsg) + + def test_missing_property_systemname(self): + """ + Test that exception is raised if required property 'SystemName' is + missing. + """ + self.qmsg.properties.pop("SystemName") + self.assertRaisesRegexp(InvalidMessage, + "^Missing message property: SystemName", + LofarMessage, self.qmsg) + + def test_missing_property_messageid(self): + """ + Test that exception is raised if required property 'MessageId' is + missing. + """ + self.qmsg.properties.pop("MessageId") + self.assertRaisesRegexp(InvalidMessage, + "^Missing message property: MessageId", + LofarMessage, self.qmsg) + + def test_missing_property_messagetype(self): + """ + Test that exception is raised if required property 'MessageType' is + missing. + """ + self.qmsg.properties.pop("MessageType") + self.assertRaisesRegexp(InvalidMessage, + "^Missing message property: MessageType", + LofarMessage, self.qmsg) + + def test_invalid_property_systemname(self): + """ + Test that exception is raised if 'SystemName' has wrong value (i.e. + not equal to 'LOFAR') + """ + self.qmsg.properties["SystemName"] = "NOTLOFAR" + self.assertRaisesRegexp(InvalidMessage, + "^Invalid message property 'SystemName':", + LofarMessage, self.qmsg) + + def test_invalid_property_messageid(self): + """ + Test that exception is raised if 'MessageId' contains an invalid + UUID-string. + """ + self.qmsg.properties["MessageId"] = "Invalid-UUID-string" + self.assertRaisesRegexp(InvalidMessage, + "^Invalid message property 'MessageId':", + LofarMessage, self.qmsg) + + def test_getattr_raises(self): + """ + Test that exception is raised if a non-existent attribute is read. + """ + msg = LofarMessage(self.qmsg) + with self.assertRaisesRegexp(AttributeError, "object has no attribute"): + _ = msg.non_existent + + def test_getattr_raises_on_properties(self): + """ + Test that exception is raised if attribute 'properties' is read. + This attribute should not be visible. + """ + msg = LofarMessage(self.qmsg) + with self.assertRaisesRegexp(AttributeError, "object has no attribute"): + _ = msg.properties + + def test_setattr_raises_on_properties(self): + """ + Test that exception is raised if attribute 'properties' is written. + This attribute should not be visible. + """ + msg = LofarMessage(self.qmsg) + with self.assertRaisesRegexp(AttributeError, "object has no attribute"): + msg.properties = {} + + def test_getattr_qpid_field(self): + """ + Test that a Qpid message field becomes an LofarMessage attribute. + """ + msg = LofarMessage(self.qmsg) + msg.qpid_msg.ttl = 100 + self.assertEqual(self.qmsg.ttl, msg.ttl) + self.assertEqual(msg.ttl, 100) + + def test_setattr_qpid_field(self): + """ + Test that an LofarMessage attribute becomes a Qpid message field. + """ + msg = LofarMessage(self.qmsg) + msg.ttl = 100 + self.assertEqual(self.qmsg.ttl, msg.ttl) + self.assertEqual(self.qmsg.ttl, 100) + + def test_getattr_qpid_property(self): + """ + Test that a Qpid message property becomes an LofarMessage attribute. + """ + self.qmsg.properties["NewProperty"] = "New Property" + msg = LofarMessage(self.qmsg) + self.assertEqual(msg.qpid_msg.properties["NewProperty"], + msg.NewProperty) + + def test_setattr_qpid_property(self): + """ + Test that an LofarMessage attribute becomes a Qpid message property. + """ + msg = LofarMessage(self.qmsg) + msg.NewProperty = "New Property" + self.assertEqual(msg.qpid_msg.properties["NewProperty"], + msg.NewProperty) + + def test_propname_not_contains_properties(self): + """ + Test that prop_names() does not return the property 'properties'. + This attribute should not be visible. + """ + msg = LofarMessage(self.qmsg) + self.assertNotIn('properties', msg.prop_names()) + + +class ContentLofarMessage(unittest.TestCase): + """ + Class to test that an LofarMessage can be constructed from different types + of content. The content is used to initialize a Qpid Message object. + """ + + def test_construct_from_string(self): + """ + Test that an LofarMessage can be constructed from an ASCII string. + """ + content = "ASCII string" + msg = LofarMessage(content) + self.assertEqual((msg.content, msg.content_type), + (content, None)) + + def test_construct_from_unicode(self): + """ + Test that an LofarMessage can be constructed from a Unicode string. + :return: + """ + content = u"Unicode string" + msg = LofarMessage(content) + self.assertEqual((msg.content, msg.content_type), + (content, "text/plain")) + + def test_construct_from_list(self): + """ + Test that an LofarMessage can be constructed from a python list. + """ + content = range(10) + msg = LofarMessage(content) + self.assertEqual((msg.content, msg.content_type), + (content, "amqp/list")) + + def test_construct_from_dict(self): + """ + Test that an LofarMessage can be constructed from a python dict. + """ + content = {1: 'one', 2: 'two', 3: 'three'} + msg = LofarMessage(content) + self.assertEqual((msg.content, msg.content_type), + (content, "amqp/map")) + + def test_construct_from_binary(self): + """ + Test that an LofarMessage can be constructed from binary data. + Use struct.pack() to create a byte array + """ + content = struct.pack("<256B", *range(256)) + msg = LofarMessage(content) + self.assertEqual((msg.content, msg.content_type), + (content, None)) + + def test_construct_from_unsupported(self): + """ + Test that an LofarMessage cannot be constructed from unsupported + data type like 'int'. + """ + content = 42 + self.assertRaisesRegexp(InvalidMessage, "^Unsupported content type:", + LofarMessage, content) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/LCS/Messaging/python/messaging/test/t_messages.run b/LCS/Messaging/python/messaging/test/t_messages.run new file mode 100755 index 0000000000000000000000000000000000000000..b38b2b90bbc09fa72f5c8fb18ccdfc2a967444ae --- /dev/null +++ b/LCS/Messaging/python/messaging/test/t_messages.run @@ -0,0 +1,29 @@ +#!/bin/sh -e + +# Run the unit test +# either with or without code coverage measurements, +# depending wheter coverage has been installed + +if type "coverage" > /dev/null; then + #run test using python coverage tool + + #erase previous results + coverage erase + + #setup coverage config file + printf "[report]\nexclude_lines = \n if __name__ == .__main__.\n def main\n" > .coveragerc + + coverage run --branch --include=*Messaging/python* t_messages.py + RESULT=$? + if [ $RESULT -eq 0 ]; then + echo " *** Code coverage results *** " + coverage report -m + echo " *** End coverage results *** " + fi + exit $RESULT +else + #coverage not available + echo "Please run: 'pip install coverage' to enable code coverage reporting of the unit tests" + #run plain test script + python t_messages.py +fi diff --git a/LCS/Messaging/python/messaging/test/t_messages.sh b/LCS/Messaging/python/messaging/test/t_messages.sh new file mode 100755 index 0000000000000000000000000000000000000000..4945c9e8adf808aeb8c7ed0378e1c440b3976566 --- /dev/null +++ b/LCS/Messaging/python/messaging/test/t_messages.sh @@ -0,0 +1,2 @@ +#!/bin/sh +./runctest.sh t_messages diff --git a/LCS/Messaging/src/CMakeLists.txt b/LCS/Messaging/src/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..b7b574c391048638853221258180cf14d4b52b13 --- /dev/null +++ b/LCS/Messaging/src/CMakeLists.txt @@ -0,0 +1,13 @@ +# $Id: CMakeLists.txt 1457 2015-08-18 14:44:52Z loose $ + +lofar_add_library(messaging + LofarMessage.cc + DefaultSettings.cc + EventMessage.cc + FromBus.cc + Helpers.cc + Message.cc + MonitoringMessage.cc + ProgressMessage.cc + ServiceMessage.cc + ToBus.cc) diff --git a/LCS/Messaging/src/DefaultSettings.cc b/LCS/Messaging/src/DefaultSettings.cc new file mode 100644 index 0000000000000000000000000000000000000000..dabe2bb5f3a0080abc48a62df5530854b851bfd3 --- /dev/null +++ b/LCS/Messaging/src/DefaultSettings.cc @@ -0,0 +1,39 @@ +//# DefaultSettings.cc: Default settings for often used parameters. +//# +//# Copyright (C) 2015 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR Software Suite. +//# +//# The LOFAR Software Suite 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 3 of the License, or (at your +//# option) any later version. +//# +//# The LOFAR Software Suite 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 +//# The LOFAR Software Suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: DefaultSettings.cc 1483 2015-08-23 19:19:44Z loose $ + +#include <lofar_config.h> +#include <Messaging/DefaultSettings.h> + +using namespace std; + +namespace LOFAR +{ + namespace Messaging + { + string defaultAddressOptions = "{create: never}"; + string defaultBroker = "amqp:tcp:127.0.0.1:5672"; + string defaultBrokerOptions = "{reconnect: true}"; + unsigned defaultReceiverCapacity = 1; + double defaultTimeOut = 0.0; + } +} diff --git a/LCS/Messaging/src/EventMessage.cc b/LCS/Messaging/src/EventMessage.cc new file mode 100644 index 0000000000000000000000000000000000000000..d74a81b7830314ccb5862a986934c40eda5e6073 --- /dev/null +++ b/LCS/Messaging/src/EventMessage.cc @@ -0,0 +1,58 @@ +//# EventMessage.cc: Message class used for event messages. +//# +//# Copyright (C) 2015 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR Software Suite. +//# +//# The LOFAR Software Suite 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 3 of the License, or (at your +//# option) any later version. +//# +//# The LOFAR Software Suite 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 +//# The LOFAR Software Suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: EventMessage.cc 1483 2015-08-23 19:19:44Z loose $ + +#include <lofar_config.h> +#include <Messaging/EventMessage.h> +#include <Common/LofarLogger.h> + +namespace LOFAR +{ + namespace Messaging + { + using namespace std; + + namespace + { + // @todo: Find a different way to register class with the factory, + // because doing it this way generates "unused-variable" warning. + // Probably need a static initializer class, the way iostream does it. + bool dummy = MessageFactory::instance() + .registerClass<EventMessage>("EventMessage"); + } + + + EventMessage::EventMessage() : + LofarMessage() + { + setProperty("MessageType", "EventMessage"); + } + + + EventMessage::EventMessage(const qpid::messaging::Message& msg) : + LofarMessage(msg) + { + ASSERT(this->type() == "EventMessage"); + } + + } +} diff --git a/LCS/Messaging/src/FromBus.cc b/LCS/Messaging/src/FromBus.cc new file mode 100644 index 0000000000000000000000000000000000000000..024a9d2d7b5744a8a4610c85285e8eb87915667d --- /dev/null +++ b/LCS/Messaging/src/FromBus.cc @@ -0,0 +1,138 @@ +//# FromBus.cc: Provide an easy way to fetch messages from the message bus. +//# +//# Copyright (C) 2015 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR Software Suite. +//# +//# The LOFAR Software Suite 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 3 of the License, or (at your +//# option) any later version. +//# +//# The LOFAR Software Suite 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 +//# The LOFAR Software Suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: FromBus.cc 1483 2015-08-23 19:19:44Z loose $ + +#include <lofar_config.h> + +#include "Helpers.h" + +#include <Messaging/Exceptions.h> +#include <Messaging/FromBus.h> +#include <Messaging/Message.h> +#include <Common/LofarLogger.h> + +#include <qpid/messaging/Receiver.h> +#include <qpid/types/Exception.h> + +namespace LOFAR +{ + namespace Messaging + { + using namespace std; + using qpid::messaging::Receiver; + + FromBus::FromBus() + { + } + + FromBus::FromBus(const std::string& address, + const std::string& options, + const std::string& broker) + // We need to use a function try-block here, because we want to catch + // exceptions that may be thrown during member initialization as well. + try : + itsConnection(broker, defaultBrokerOptions), + itsNrMissingACKs(0) + { + LOG_DEBUG_STR("[FromBus] Connecting to broker: " << broker); + itsConnection.open(); + LOG_INFO_STR("[FromBus] Connected to broker: " << itsConnection.getUrl()); + itsSession = itsConnection.createSession(); + addQueue(address, options); + } + catch (qpid::types::Exception& ex) { + THROW (MessagingException, ex.what()); + } + + + FromBus::~FromBus() + { + if (itsNrMissingACKs) { + LOG_ERROR_STR( + "[FromBus] " << itsNrMissingACKs << " message" << + (itsNrMissingACKs == 1 ? "" : "s") << " not acknowledged"); + } + string url(itsConnection.getUrl()); + LOG_DEBUG_STR("[FromBus] Closing connection: " << url); + itsConnection.close(); + LOG_INFO_STR("[FromBus] Closed connection: " << url); + } + + + void FromBus::addQueue(const std::string& address, + const std::string& options) + { + try { + string fullAddr(address + (options.empty() ? "" : "; " + options)); + LOG_DEBUG_STR("[FromBus] Creating receiver: " << fullAddr); + Receiver receiver = itsSession.createReceiver(fullAddr); + receiver.setCapacity(defaultReceiverCapacity); + LOG_INFO_STR("[FromBus] Receiver created at: " << receiver.getName()); + } + catch (qpid::types::Exception& ex) { + THROW (MessagingException, ex.what()); + } + } + + Message* FromBus::getMessage(double timeout) + { + Receiver next; + qpid::messaging::Message qmsg; + + LOG_DEBUG_STR("[FromBus] Waiting for message"); + if (!itsSession.nextReceiver(next,TimeOutDuration(timeout))) { + LOG_DEBUG_STR("[FromBus] Time-out while waiting for message"); + return 0; + } + LOG_DEBUG_STR("[FromBus] Message available on queue " << next.getName()); + itsNrMissingACKs++; + if (next.get(qmsg)) { + Message* message = Message::create(qmsg); + LOG_INFO_STR("[FromBus] Received " << message->type() + << " on queue " << next.getName()); + return message; + } else { + LOG_ERROR_STR("[FromBus] Could not retrieve available message " + "on queue " << next.getName()); + return 0; + } + } + + void FromBus::ack(Message& message) + { + itsSession.acknowledge(message); + itsNrMissingACKs--; + } + + void FromBus::nack(Message& message) + { + itsSession.release(message); + itsNrMissingACKs--; + } + + void FromBus::reject(Message& message) + { + itsSession.reject(message); + } + + } +} diff --git a/LCS/Messaging/src/Helpers.cc b/LCS/Messaging/src/Helpers.cc new file mode 100644 index 0000000000000000000000000000000000000000..fcf138d25f4254c5d319ed2f7260e13b4297b305 --- /dev/null +++ b/LCS/Messaging/src/Helpers.cc @@ -0,0 +1,40 @@ +//# Helpers.h: Collection of helper functions. +//# +//# Copyright (C) 2015 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR Software Suite. +//# +//# The LOFAR Software Suite 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 3 of the License, or (at your +//# option) any later version. +//# +//# The LOFAR Software Suite 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 +//# The LOFAR Software Suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: Helpers.cc 1483 2015-08-23 19:19:44Z loose $ + +#include <lofar_config.h> +#include "Helpers.h" + +namespace LOFAR +{ + namespace Messaging + { + using namespace qpid::messaging; + + Duration TimeOutDuration(double secs) + { + if (secs < 0.0) return Duration::FOREVER; + else return Duration(Duration::SECOND.getMilliseconds() * secs); + } + + } +} diff --git a/LCS/Messaging/src/Helpers.h b/LCS/Messaging/src/Helpers.h new file mode 100644 index 0000000000000000000000000000000000000000..4574a2d139290de46208a848d30c31108879c799 --- /dev/null +++ b/LCS/Messaging/src/Helpers.h @@ -0,0 +1,47 @@ +//# Helpers.h: Collection of helper functions. +//# +//# Copyright (C) 2015 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR Software Suite. +//# +//# The LOFAR Software Suite 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 3 of the License, or (at your +//# option) any later version. +//# +//# The LOFAR Software Suite 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 +//# The LOFAR Software Suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: Helpers.h 1483 2015-08-23 19:19:44Z loose $ + +#ifndef LOFAR_MESSAGING_HELPERS_H +#define LOFAR_MESSAGING_HELPERS_H + +// @file +// Collection of helper functions. + +#include <qpid/messaging/Duration.h> + +namespace LOFAR +{ + namespace Messaging + { + // @addtogroup Messaging + // @{ + + // Convert a time-out in seconds into a Duration object. + // If \a secs is negative, the time-out will be infinite. + qpid::messaging::Duration TimeOutDuration(double secs); + + // @} + } +} + +#endif diff --git a/LCS/Messaging/src/LofarMessage.cc b/LCS/Messaging/src/LofarMessage.cc new file mode 100644 index 0000000000000000000000000000000000000000..f68c531c0043bfd9d90ac00d7e193e68d23cc920 --- /dev/null +++ b/LCS/Messaging/src/LofarMessage.cc @@ -0,0 +1,68 @@ +//# LofarMessage.cc: Top-level message class for LOFAR messages. +//# +//# Copyright (C) 2015 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR Software Suite. +//# +//# The LOFAR Software Suite 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 3 of the License, or (at your +//# option) any later version. +//# +//# The LOFAR Software Suite 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 +//# The LOFAR Software Suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: LofarMessage.cc 1483 2015-08-23 19:19:44Z loose $ + +#include <lofar_config.h> +#include <Messaging/LofarMessage.h> +#include <Messaging/Exceptions.h> + +#include <sstream> + +namespace LOFAR +{ + namespace Messaging + { + using namespace std; + + //## ---- Definition of protected methods ---- ##// + + LofarMessage::LofarMessage() : + Message() + { + setProperty("SystemName", "LOFAR"); + setProperty("MessageId", qpid::types::Uuid(true).str()); + } + + + LofarMessage::LofarMessage(const qpid::messaging::Message& qmsg) : + Message(qmsg) + { + // Check systemName: must be "LOFAR" + string systemName(getProperty("SystemName")); + if (systemName != "LOFAR") { + THROW (InvalidMessage, + "Message contains wrong system name: " << systemName); + } + + // Check messageId: must be a stringified Uuid. + string messageId(getProperty("MessageId")); + istringstream iss(messageId); + qpid::types::Uuid uuid; + iss >> uuid; + if (uuid.isNull()) { + THROW (InvalidMessage, "Message contains invalid ID: " << messageId); + } + + } + + } +} diff --git a/LCS/Messaging/src/Message.cc b/LCS/Messaging/src/Message.cc new file mode 100644 index 0000000000000000000000000000000000000000..ad814920ac894c0d28b83d7b67ddb7b23339dec9 --- /dev/null +++ b/LCS/Messaging/src/Message.cc @@ -0,0 +1,107 @@ +//# Message.cc: Top-level message class. +//# +//# Copyright (C) 2015 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR Software Suite. +//# +//# The LOFAR Software Suite 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 3 of the License, or (at your +//# option) any later version. +//# +//# The LOFAR Software Suite 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 +//# The LOFAR Software Suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: Message.cc 1483 2015-08-23 19:19:44Z loose $ + +#include <lofar_config.h> +#include <Messaging/Message.h> +#include <Messaging/Exceptions.h> + +namespace LOFAR +{ + namespace Messaging + { + using namespace std; + + //## ---- Definition of public methods ---- ##// + + Message* Message::create(const qpid::messaging::Message& qmsg) + { + string msgType(getProperty("MessageType", qmsg)); + Message* msg = MessageFactory::instance().create(msgType, qmsg); + if (!msg) { + THROW (UnknownMessageType, + "Don't know how to create a message of type " << msgType << + ". Did you register the class with the factory?"); + } + return msg; + } + + + qpid::types::Variant + Message::getProperty(const string& property, + const qpid::messaging::Message& qmsg) + { + const qpid::types::Variant::Map& properties(qmsg.getProperties()); + qpid::types::Variant::Map::const_iterator it; + it = properties.find(property); + if (it == properties.end()) { + THROW (MessagePropertyNotFound, + "Message property '" << property << "' not found"); + } + return it->second; + } + + + Message::~Message() + { + } + + + string Message::type() const + { + return getProperty("MessageType"); + } + + + //## ---- Definition of protected methods ---- ##// + + Message::Message() : + qpid::messaging::Message() + { + } + + + Message::Message(const qpid::messaging::Message& qmsg) : + qpid::messaging::Message(qmsg) + { + } + + + qpid::types::Variant Message::getProperty(const std::string& property) const + { + return getProperty(property, *this); + } + + + // set<string> Message::propertyNames() const + // { + // set<string> keys; + // const qpid::types::Variant::Map& properties(getProperties()); + // qpid::types::Variant::Map::const_iterator it; + // for(it = properties.begin(); it != properties.end(); ++it) { + // keys.insert(it->first); + // } + // return keys; + // } + + } +} diff --git a/LCS/Messaging/src/Message.cc.ok b/LCS/Messaging/src/Message.cc.ok new file mode 100644 index 0000000000000000000000000000000000000000..6a4899e2abe7b86f1493abae1ba8f0e13e9b2346 --- /dev/null +++ b/LCS/Messaging/src/Message.cc.ok @@ -0,0 +1,91 @@ +#include <lofar_config.h> +#include <Messaging/Message.h> +#include <qpid/messaging/Message.h> +#include <qpid/types/Variant.h> +#include <string> +#include <iostream> + +using namespace std; + +namespace LOFAR +{ + namespace Messaging + { + + //## ---- Initialization of static data ---- ##// + + const string Message::theirQpidPropertyName = "MessageType"; + + + //## ---- Definition of public methods ---- ##// + + Message::~Message() + { + TRACE; + } + + + Message* Message::create(const qpid::messaging::Message& qmsg) + { + TRACE; + qpid::types::Variant prop(getProperty(theirQpidPropertyName, qmsg)); + if (prop.isVoid()) { + THROW (MessageFactoryException, "Don't know what message to create, " + "because Qpid message property '" << theirQpidPropertyName + << "' was not found"); + } + string msgType(prop); + Message* msg = MessageFactory::instance().create(msgType, qmsg); + if (!msg) { + THROW (MessageFactoryException, + "Don't know how to create an instance of '" << msgType + << "'. Did you register the class with the factory?"); + } + return msg; + } + + + //## ---- Definition of protected methods ---- ##// + + Message::Message() + { + TRACE; + } + + Message::Message(const qpid::messaging::Message& msg) : + itsQpidMsg(new qpid::messaging::Message(msg)) + { + TRACE; + } + + boost::shared_ptr< qpid::messaging::Message > Message::getQpidMsg() const + { + TRACE; + return itsQpidMsg; + } + + qpid::types::Variant Message::getProperty(const string& property) const + { + TRACE; + return Message::getProperty(property, *itsQpidMsg); + } + + + //## ---- Definition of private methods ---- ##// + + qpid::types::Variant + Message::getProperty(const std::string& property, + const qpid::messaging::Message& qmsg) + { + TRACE; + const qpid::types::Variant::Map& properties(qmsg.getProperties()); + qpid::types::Variant::Map::const_iterator it; + it = properties.find(property); + if (it == properties.end()) return qpid::types::Variant(); + else return it->second; + // if (it == properties.end()) THROW (MessagePropertyNotFound, property); + // return it->second; + } + + } +} diff --git a/LCS/Messaging/src/Message.h.ok b/LCS/Messaging/src/Message.h.ok new file mode 100644 index 0000000000000000000000000000000000000000..6b3a803960746f925fa039defd2a59d2af984f9c --- /dev/null +++ b/LCS/Messaging/src/Message.h.ok @@ -0,0 +1,76 @@ +#ifndef LOFAR_MESSAGING_MESSAGE_H +#define LOFAR_MESSAGING_MESSAGE_H + +#include <Messaging/Exceptions.h> +#include <Messaging/DefaultSettings.h> // for TRACE macro +#include <Messaging/ForwardDeclarations.h> +#include <Common/ObjectFactory.h> +#include <Common/Singleton.h> +#include <boost/shared_ptr.hpp> +#include <map> +#include <string> +#include <iostream> + + + + +namespace LOFAR +{ + namespace Messaging + { + // // Typedef copied from Qpid header file; avoids inclusion. + // typedef std::map<std::string, qpid::types::Variant> qpid_variant_map_t; + + // Top-level message class. + // @todo: move outside of LOFAR source code tree. + class Message + { + public: + // Virtual destructor, because you can inherit from this class. + virtual ~Message(); + + // Create a message of the correct type. + // This factory method uses the Qpid message property \a MessageType to + // determine the exact kind of Message object that must be constructed. + // @throw MessageFactoryError + static Message* create(const qpid::messaging::Message& qmsg); + + // Name of the Qpid message property that contains the type name of a + // Message. + static const std::string theirQpidPropertyName; + + protected: + // Default constructor. Creates an empty message object. + Message(); + + // Construct a Message object from a Qpid message. + Message(const qpid::messaging::Message& msg); + + // Return a managed pointer to the stored Qpid message. + boost::shared_ptr< qpid::messaging::Message > getQpidMsg() const; + + // Helper function to retrieve a single property. + // @throw MessagePropertyNotFound + qpid::types::Variant getProperty(const std::string& property) const; + + private: + // Retrieve a single property directly from a Qpid message. + static qpid::types::Variant + getProperty(const std::string& property, + const qpid::messaging::Message& qmsg); + + // Store a (managed) pointer to the Qpid message. By using a pointer, we + // keep Qpid out of the interface. + boost::shared_ptr< qpid::messaging::Message > itsQpidMsg; + }; + + // Generic factory for Message objects. + typedef LOFAR::Singleton< + LOFAR::ObjectFactory< Message*(const qpid::messaging::Message& msg), + std::string > + > MessageFactory; + + } +} + +#endif diff --git a/LCS/Messaging/src/MonitoringMessage.cc b/LCS/Messaging/src/MonitoringMessage.cc new file mode 100644 index 0000000000000000000000000000000000000000..cb530675d956305f04273f8ab301b8c72088417a --- /dev/null +++ b/LCS/Messaging/src/MonitoringMessage.cc @@ -0,0 +1,58 @@ +//# MonitoringMessage.cc: Message class used for Monitoring messages. +//# +//# Copyright (C) 2015 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR Software Suite. +//# +//# The LOFAR Software Suite 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 3 of the License, or (at your +//# option) any later version. +//# +//# The LOFAR Software Suite 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 +//# The LOFAR Software Suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: MonitoringMessage.cc 1483 2015-08-23 19:19:44Z loose $ + +#include <lofar_config.h> +#include <Messaging/MonitoringMessage.h> +#include <Common/LofarLogger.h> + +namespace LOFAR +{ + namespace Messaging + { + using namespace std; + + namespace + { + // @todo: Find a different way to register class with the factory, + // because doing it this way generates "unused-variable" warning. + // Probably need a static initializer class, the way iostream does it. + bool dummy = MessageFactory::instance() + .registerClass<MonitoringMessage>("MonitoringMessage"); + } + + + MonitoringMessage::MonitoringMessage() : + LofarMessage() + { + setProperty("MessageType", "MonitoringMessage"); + } + + + MonitoringMessage::MonitoringMessage(const qpid::messaging::Message& msg) : + LofarMessage(msg) + { + ASSERT(this->type() == "MonitoringMessage"); + } + + } +} diff --git a/LCS/Messaging/src/ProgressMessage.cc b/LCS/Messaging/src/ProgressMessage.cc new file mode 100644 index 0000000000000000000000000000000000000000..e99c658ee19a985f364c7be1d862af23af504b3a --- /dev/null +++ b/LCS/Messaging/src/ProgressMessage.cc @@ -0,0 +1,58 @@ +//# ProgressMessage.cc: Message class used for progress messages. +//# +//# Copyright (C) 2015 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR Software Suite. +//# +//# The LOFAR Software Suite 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 3 of the License, or (at your +//# option) any later version. +//# +//# The LOFAR Software Suite 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 +//# The LOFAR Software Suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: ProgressMessage.cc 1483 2015-08-23 19:19:44Z loose $ + +#include <lofar_config.h> +#include <Messaging/ProgressMessage.h> +#include <Common/LofarLogger.h> + +namespace LOFAR +{ + namespace Messaging + { + using namespace std; + + namespace + { + // @todo: Find a different way to register class with the factory, + // because doing it this way generates "unused-variable" warning. + // Probably need a static initializer class, the way iostream does it. + bool dummy = MessageFactory::instance() + .registerClass<ProgressMessage>("ProgressMessage"); + } + + + ProgressMessage::ProgressMessage() : + LofarMessage() + { + setProperty("MessageType", "ProgressMessage"); + } + + + ProgressMessage::ProgressMessage(const qpid::messaging::Message& msg) : + LofarMessage(msg) + { + ASSERT(this->type() == "ProgressMessage"); + } + + } +} diff --git a/LCS/Messaging/src/ServiceMessage.cc b/LCS/Messaging/src/ServiceMessage.cc new file mode 100644 index 0000000000000000000000000000000000000000..f0d59be241f6c51c11a6e2c7727d804fd7f234a4 --- /dev/null +++ b/LCS/Messaging/src/ServiceMessage.cc @@ -0,0 +1,58 @@ +//# ServiceMessage.cc: Message class used for service messages. +//# +//# Copyright (C) 2015 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR Software Suite. +//# +//# The LOFAR Software Suite 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 3 of the License, or (at your +//# option) any later version. +//# +//# The LOFAR Software Suite 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 +//# The LOFAR Software Suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: ServiceMessage.cc 1483 2015-08-23 19:19:44Z loose $ + +#include <lofar_config.h> +#include <Messaging/ServiceMessage.h> +#include <Common/LofarLogger.h> + +namespace LOFAR +{ + namespace Messaging + { + using namespace std; + + namespace + { + // @todo: Find a different way to register class with the factory, + // because doing it this way generates "unused-variable" warning. + // Probably need a static initializer class, the way iostream does it. + bool dummy = MessageFactory::instance() + .registerClass<ServiceMessage>("ServiceMessage"); + } + + + ServiceMessage::ServiceMessage() : + LofarMessage() + { + setProperty("MessageType", "ServiceMessage"); + } + + + ServiceMessage::ServiceMessage(const qpid::messaging::Message& msg) : + LofarMessage(msg) + { + ASSERT(this->type() == "ServiceMessage"); + } + + } +} diff --git a/LCS/Messaging/src/ToBus.cc b/LCS/Messaging/src/ToBus.cc new file mode 100644 index 0000000000000000000000000000000000000000..6d7db7b52396dd7b3cd8d351e61b070da9b25844 --- /dev/null +++ b/LCS/Messaging/src/ToBus.cc @@ -0,0 +1,86 @@ +//# ToBus.cc: Provide an easy way to put messages onto the message bus. +//# +//# Copyright (C) 2015 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR Software Suite. +//# +//# The LOFAR Software Suite 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 3 of the License, or (at your +//# option) any later version. +//# +//# The LOFAR Software Suite 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 +//# The LOFAR Software Suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: ToBus.cc 1483 2015-08-23 19:19:44Z loose $ + +#include <lofar_config.h> + +#include <Messaging/Exceptions.h> +#include <Messaging/Message.h> +#include <Messaging/ToBus.h> +#include <Common/LofarLogger.h> + +#include <qpid/types/Exception.h> + +namespace LOFAR +{ + namespace Messaging + { + using namespace std; + + ToBus::ToBus(const string& address, + const string& options, + const string& broker) + // We need to use a function try-block here, because we want to catch + // exceptions that may be thrown during member initialization as well. + try : + itsConnection(broker, defaultBrokerOptions) + { + LOG_DEBUG_STR("[ToBus] Connecting to broker: " << broker); + itsConnection.open(); + LOG_INFO_STR("[ToBus] Connected to broker: " << itsConnection.getUrl()); + itsSession = itsConnection.createSession(); + addQueue(address, options); + } + catch (qpid::types::Exception& ex) { + THROW (MessagingException, ex.what()); + } + + + ToBus::~ToBus() + { + string url(itsConnection.getUrl()); + LOG_DEBUG_STR("[ToBus] Closing connection: " << url); + itsConnection.close(); + LOG_INFO_STR("[ToBus] Closed connection: " << url); + } + + + void ToBus::send(const Message& message) + { + LOG_DEBUG_STR("[ToBus] Sending message to queue " << itsSender.getName()); + itsSender.send(message, /*sync*/ true); + LOG_INFO_STR("[ToBus] Sent message to queue " << itsSender.getName()); + } + + + void ToBus::addQueue(const string& address, + const string& options) + { + string fullAddr(address + (options.empty() ? "" : "; " + options)); + LOG_DEBUG_STR("[ToBus] Creating sender: " << fullAddr); + itsSender = itsSession.createSender(fullAddr); + LOG_INFO_STR("[ToBus] Sender created at: " << itsSender.getName()); + } + + + } +} diff --git a/LCS/Messaging/test/CMakeLists.txt b/LCS/Messaging/test/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..ca870a8628a6ccfbe1094b87f7ac5d1adb120f42 --- /dev/null +++ b/LCS/Messaging/test/CMakeLists.txt @@ -0,0 +1,20 @@ +# $Id: CMakeLists.txt 1457 2015-08-18 14:44:52Z loose $ + +include(LofarCTest) + +set(_unit_tests + tLofarMessages + tMessaging +) + +lofar_find_package(UnitTest++) + +if(UNITTEST++_FOUND) + foreach(_test ${_unit_tests}) + lofar_add_test(${_test} ${_test}.cc) + endforeach() +else() + lofar_join_arguments(_unit_tests) + message(WARNING "UnitTest++ not found. " + "The following tests will not be run: ${_unit_tests}") +endif() diff --git a/LCS/Messaging/test/tLofarMessages.cc b/LCS/Messaging/test/tLofarMessages.cc new file mode 100644 index 0000000000000000000000000000000000000000..10af49372f0e822fad42ec10d1659f4f03ab7cc4 --- /dev/null +++ b/LCS/Messaging/test/tLofarMessages.cc @@ -0,0 +1,146 @@ +#include <lofar_config.h> + +#include <Messaging/LofarMessages.h> +#include <Messaging/Exceptions.h> +#include <Common/LofarLogger.h> + +#include <qpid/messaging/Message.h> + +#include <UnitTest++.h> +#include <memory> + +using namespace LOFAR::Messaging; +using namespace std; + +struct QpidMsgFixture +{ + QpidMsgFixture() { + qpidMsg.setProperty("SystemName", "LOFAR"); + qpidMsg.setProperty("MessageId", "1b4e28ba-2fa1-11d2-883f-b9a761bde3fb"); + qpidMsg.setProperty("MessageType", "EventMessage"); + } + qpid::messaging::Message qpidMsg; +}; + +TEST_FIXTURE(QpidMsgFixture, UnknownMessageType) +{ + cout << "** UnknownMessageType **" << endl; + qpidMsg.setProperty("MessageType", "FooBarMessage"); + CHECK_THROW(Message::create(qpidMsg), UnknownMessageType); +} + +TEST_FIXTURE(QpidMsgFixture, WrongSystemName) +{ + cout << "** WrongSystemName **" << endl; + qpidMsg.setProperty("SystemName", "NOTLOFAR"); + CHECK_THROW(Message::create(qpidMsg), InvalidMessage); +} + +TEST_FIXTURE(QpidMsgFixture, InvalidMessageId) +{ + cout << "** InvalidMessageId **" << endl; + qpidMsg.setProperty("MessageId", "invalid-uuid-string"); + CHECK_THROW(Message::create(qpidMsg), InvalidMessage); +} + +SUITE(EventMessage) +{ + TEST(DefaultEventMessage) + { + cout << "** DefaultEventMessage ** " << endl; + EventMessage msg; + CHECK(msg.type() == "EventMessage"); + CHECK(msg.getProperty("SystemName") == "LOFAR"); + string messageId(msg.getProperty("MessageId")); + CHECK(messageId.size() == 36 && + messageId != "00000000-0000-0000-0000-000000000000"); + } + + TEST_FIXTURE(QpidMsgFixture, QpidEventMessage) + { + cout << "** QpidEventMessage ** " << endl; + qpidMsg.setProperty("MessageType", "EventMessage"); + auto_ptr<Message> msg(Message::create(qpidMsg)); + CHECK(dynamic_cast<EventMessage*>(msg.get())); + CHECK(msg->type() == "EventMessage"); + } +} + + +SUITE(MonitoringMessage) +{ + TEST(DefaultMonitoringMessage) + { + cout << "** DefaultMonitoringMessage ** " << endl; + MonitoringMessage msg; + CHECK(msg.type() == "MonitoringMessage"); + CHECK(msg.getProperty("SystemName") == "LOFAR"); + string messageId(msg.getProperty("MessageId")); + CHECK(messageId.size() == 36 && + messageId != "00000000-0000-0000-0000-000000000000"); + } + + TEST_FIXTURE(QpidMsgFixture, QpidMonitoringMessage) + { + cout << "** QpidMonitoringMessage ** " << endl; + qpidMsg.setProperty("MessageType", "MonitoringMessage"); + auto_ptr<Message> msg(Message::create(qpidMsg)); + CHECK(dynamic_cast<MonitoringMessage*>(msg.get())); + CHECK(msg->type() == "MonitoringMessage"); + } +} + + +SUITE(ProgressMessage) +{ + TEST(DefaultProgressMessage) + { + cout << "** DefaultProgressMessage ** " << endl; + ProgressMessage msg; + CHECK(msg.type() == "ProgressMessage"); + CHECK(msg.getProperty("SystemName") == "LOFAR"); + string messageId(msg.getProperty("MessageId")); + CHECK(messageId.size() == 36 && + messageId != "00000000-0000-0000-0000-000000000000"); + } + + TEST_FIXTURE(QpidMsgFixture, QpidProgressMessage) + { + cout << "** QpidProgressMessage ** " << endl; + qpidMsg.setProperty("MessageType", "ProgressMessage"); + auto_ptr<Message> msg(Message::create(qpidMsg)); + CHECK(dynamic_cast<ProgressMessage*>(msg.get())); + CHECK(msg->type() == "ProgressMessage"); + } +} + + +SUITE(ServiceMessage) +{ + TEST(DefaultServiceMessage) + { + cout << "** DefaultServiceMessage ** " << endl; + ServiceMessage msg; + CHECK(msg.type() == "ServiceMessage"); + CHECK(msg.getProperty("SystemName") == "LOFAR"); + string messageId(msg.getProperty("MessageId")); + CHECK(messageId.size() == 36 && + messageId != "00000000-0000-0000-0000-000000000000"); + } + + TEST_FIXTURE(QpidMsgFixture, QpidServiceMessage) + { + cout << "** QpidServiceMessage ** " << endl; + qpidMsg.setProperty("MessageType", "ServiceMessage"); + auto_ptr<Message> msg(Message::create(qpidMsg)); + CHECK(dynamic_cast<ServiceMessage*>(msg.get())); + CHECK(msg->type() == "ServiceMessage"); + } +} + + +int main() +{ + INIT_LOGGER("tLofarMessages"); + return UnitTest::RunAllTests() > 0; +} diff --git a/LCS/Messaging/test/tLofarMessages.log_prop b/LCS/Messaging/test/tLofarMessages.log_prop new file mode 100644 index 0000000000000000000000000000000000000000..7566394aac7d94256d17e9b84c1c948685f9fa9c --- /dev/null +++ b/LCS/Messaging/test/tLofarMessages.log_prop @@ -0,0 +1,6 @@ +log4cplus.rootLogger=TRACE, TESTOUT +log4cplus.logger.TRC=DEBUG +log4cplus.appender.TESTOUT=log4cplus::ConsoleAppender +log4cplus.appender.TESTOUT.logToStdErr=true +log4cplus.appender.TESTOUT.layout=log4cplus::PatternLayout +log4cplus.appender.TESTOUT.layout.ConversionPattern=%-5p %c{3} [%b:%L] - %m%n diff --git a/LCS/Messaging/test/tLofarMessages.sh b/LCS/Messaging/test/tLofarMessages.sh new file mode 100755 index 0000000000000000000000000000000000000000..da452c23483acc923b7cf650553fb0809381c977 --- /dev/null +++ b/LCS/Messaging/test/tLofarMessages.sh @@ -0,0 +1,2 @@ +#!/bin/sh +./runctest.sh tLofarMessages diff --git a/LCS/Messaging/test/tMessaging.cc b/LCS/Messaging/test/tMessaging.cc new file mode 100644 index 0000000000000000000000000000000000000000..6e37a8e37d4985111b6cbd487a4b5539e782f1fe --- /dev/null +++ b/LCS/Messaging/test/tMessaging.cc @@ -0,0 +1,99 @@ +#include <lofar_config.h> + +#include <Messaging/FromBus.h> +#include <Messaging/ToBus.h> +#include <Messaging/LofarMessages.h> +#include <Common/LofarLogger.h> + +// #include <qpid/messaging/Message.h> //# for debuggging +// #include <qpid/messaging/Address.h> //# for debuggging + +#include <UnitTest++.h> + +#include <iostream> +#include <memory> + +using namespace LOFAR::Messaging; +using namespace std; + +// //# for debugging +// void show(const Message& msg) +// { +// cout << "ReplyTo = " << msg.getReplyTo().str() << endl; +// cout << "Subject = " << msg.getSubject() << endl; +// cout << "ContentType = " << msg.getContentType() << endl; +// cout << "MessageId = " << msg.getMessageId() << endl; +// cout << "UserId = " << msg.getUserId() << endl; +// cout << "CorrelationId = " << msg.getCorrelationId() << endl; +// cout << "Priority = " << msg.getPriority() << endl; +// cout << "Ttl = " << msg.getTtl().getMilliseconds() << endl; +// cout << "Durable = " << msg.getDurable() << endl; +// cout << "Redelivered = " << msg.getRedelivered() << endl; +// cout << "Properties = " << msg.getProperties() << endl; +// cout << "Content = " << msg.getContent() << endl; +// cout << "ContentBytes = " << msg.getContentBytes() << endl; +// cout << "ContentObject = " << msg.getContentObject() << endl; +// cout << "ContentPtr = " << msg.getContentPtr() << endl; +// cout << "ContentSize = " << msg.getContentSize() << endl; +// } + +struct BusFixture +{ + BusFixture() : + fromBus("testqueue", "{create: always, delete: always}"), + toBus ("testqueue"), + timeOut(0.5) + {} + FromBus fromBus; + ToBus toBus; + double timeOut; +}; + +TEST_FIXTURE(BusFixture, EventMessage) +{ + cout << "** EventMessage **" << endl; + EventMessage sendMsg; + toBus.send(sendMsg); + auto_ptr<Message> recvMsg(fromBus.getMessage(timeOut)); + fromBus.ack(*recvMsg); + CHECK(sendMsg.type() == recvMsg->type()); + // show(sendMsg); + // show(*recvMsg); +} + +TEST_FIXTURE(BusFixture, MonitoringMessage) +{ + cout << "** MonitoringMessage **" << endl; + MonitoringMessage sendMsg; + toBus.send(sendMsg); + auto_ptr<Message> recvMsg(fromBus.getMessage(timeOut)); + fromBus.ack(*recvMsg); + CHECK(sendMsg.type() == recvMsg->type()); +} + +TEST_FIXTURE(BusFixture, ProgressMessage) +{ + cout << "** ProgressMessage **" << endl; + ProgressMessage sendMsg; + toBus.send(sendMsg); + auto_ptr<Message> recvMsg(fromBus.getMessage(timeOut)); + fromBus.ack(*recvMsg); + CHECK(sendMsg.type() == recvMsg->type()); +} + +TEST_FIXTURE(BusFixture, ServiceMessage) +{ + cout << "** ServiceMessage **" << endl; + ServiceMessage sendMsg; + toBus.send(sendMsg); + auto_ptr<Message> recvMsg(fromBus.getMessage(timeOut)); + fromBus.ack(*recvMsg); + CHECK(sendMsg.type() == recvMsg->type()); +} + + +int main() +{ + INIT_LOGGER("tMessaging"); + return UnitTest::RunAllTests() > 0; +} diff --git a/LCS/Messaging/test/tTimeOut.cc b/LCS/Messaging/test/tTimeOut.cc new file mode 100644 index 0000000000000000000000000000000000000000..05dc07cc99af727aecc076636322b2d5bc5a3a85 --- /dev/null +++ b/LCS/Messaging/test/tTimeOut.cc @@ -0,0 +1,13 @@ +#include "../src/Helpers.h" +#include <iostream> + +using namespace std; +using namespace LOFAR::Messaging; + +int main() +{ + cout << "TimeOutDuration(0.5) = " << TimeOutDuration(0.5).getMilliseconds() + << endl; + cout << "TimeOutDuration(1.5) = " << TimeOutDuration(1.5).getMilliseconds() + << endl; +}