diff options
author | Stephen D. Huston <shuston@apache.org> | 2011-10-21 14:42:12 +0000 |
---|---|---|
committer | Stephen D. Huston <shuston@apache.org> | 2011-10-21 14:42:12 +0000 |
commit | f83677056891e436bf5ba99e79240df2a44528cd (patch) | |
tree | 625bfd644b948e89105630759cf6decb0435354d /cpp/src | |
parent | ebfd9ff053b04ab379acfc0fefedee5a31b6d8a5 (diff) | |
download | qpid-python-QPID-2519.tar.gz |
Merged out from trunkQPID-2519
git-svn-id: https://svn.apache.org/repos/asf/qpid/branches/QPID-2519@1187375 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'cpp/src')
362 files changed, 15704 insertions, 7213 deletions
diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt index 60f505a10e..bb46f1258b 100644 --- a/cpp/src/CMakeLists.txt +++ b/cpp/src/CMakeLists.txt @@ -96,7 +96,7 @@ MACRO (add_msvc_version_full verProject verProjectType verProjectFileExt verFN1 inherit_value ("winver_${verProject}_InternalName" "${verProject}") inherit_value ("winver_${verProject}_OriginalFilename" "${verProject}.${verProjectFileExt}") inherit_value ("winver_${verProject}_ProductName" "${winver_DESCRIPTION_SUMMARY}") - + # Create strings to be substituted into the template file set ("winverFileVersionBinary" "${winver_${verProject}_FileVersionBinary}") set ("winverProductVersionBinary" "${winver_${verProject}_ProductVersionBinary}") @@ -126,7 +126,7 @@ ENDMACRO (add_msvc_version_full) # MACRO (add_msvc_version verProject verProjectType verProjectFileExt) if (MSVC) - add_msvc_version_full (${verProject} + add_msvc_version_full (${verProject} ${verProjectType} ${verProjectFileExt} ${winver_FILE_VERSION_N1} @@ -313,10 +313,6 @@ if (NOT Boost_FILESYSTEM_LIBRARY) set(Boost_FILESYSTEM_LIBRARY boost_filesystem) endif (NOT Boost_FILESYSTEM_LIBRARY) -if (NOT Boost_SYSTEM_LIBRARY) - set(Boost_SYSTEM_LIBRARY boost_system) -endif (NOT Boost_SYSTEM_LIBRARY) - if (NOT Boost_UNIT_TEST_FRAMEWORK_LIBRARY) set(Boost_UNIT_TEST_FRAMEWORK_LIBRARY boost_unit_test_framework) endif (NOT Boost_UNIT_TEST_FRAMEWORK_LIBRARY) @@ -584,6 +580,15 @@ include (ssl.cmake) check_symbol_exists (LOG_AUTHPRIV "sys/syslog.h" HAVE_LOG_AUTHPRIV) check_symbol_exists (LOG_FTP "sys/syslog.h" HAVE_LOG_FTP) +# Allow MSVC user to select 'WinXP-SP3/Windows Server 2003' as build target version +set (win32_winnt_default OFF) +if (CMAKE_SYSTEM_NAME STREQUAL Windows) + if (MSVC) + set (win32_winnt_default ON) + endif (MSVC) +endif (CMAKE_SYSTEM_NAME STREQUAL Windows) +option(SET_WIN32_WINNT "In Windows-MSVC build: define _WIN32_WINNT=0x0502 to select target version: Windows XP with SP3" ${win32_winnt_default}) + if (CMAKE_SYSTEM_NAME STREQUAL Windows) if (MSVC) add_definitions( @@ -593,10 +598,11 @@ if (CMAKE_SYSTEM_NAME STREQUAL Windows) /wd4244 /wd4800 /wd4355 + /wd4267 ) - if (MSVC80) - add_definitions(/D "_WIN32_WINNT=0x0501") - endif (MSVC80) + if (SET_WIN32_WINNT) + add_definitions(/D "_WIN32_WINNT=0x0502") + endif (SET_WIN32_WINNT) # set the RelWithDebInfo compile/link switches to equal Release set (CMAKE_CXX_FLAGS_RELWITHDEBINFO "/MD /O2 /Ob2 /D NDEBUG") @@ -640,8 +646,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL Windows) ) set (qpidcommon_platform_LIBS - ${windows_ssl_libs} ws2_32 - ) + ${Boost_THREAD_LIBRARY} ${windows_ssl_libs} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ws2_32 ) set (qpidbroker_platform_SOURCES qpid/broker/windows/BrokerDefaults.cpp qpid/broker/windows/SaslAuthenticator.cpp @@ -662,7 +667,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL Windows) windows/QpiddBroker.cpp windows/SCM.cpp ) - + set (qpidmessaging_platform_SOURCES qpid/messaging/HandleInstantiator.cpp ) @@ -920,8 +925,6 @@ set (qpidmessaging_SOURCES qpid/client/amqp0_10/SessionImpl.cpp qpid/client/amqp0_10/SenderImpl.h qpid/client/amqp0_10/SenderImpl.cpp - qpid/client/amqp0_10/SimpleUrlParser.h - qpid/client/amqp0_10/SimpleUrlParser.cpp ) add_msvc_version (qpidmessaging library dll) @@ -943,7 +946,7 @@ if (NOT QPID_GENERATED_HEADERS_IN_SOURCE) endif (NOT QPID_GENERATED_HEADERS_IN_SOURCE) -if (WIN32) +if (MSVC) # Install the DtcPlugin project and call it qpidxarm. set(AMQP_WCF_DIR ${qpid-cpp_SOURCE_DIR}/../wcf) set(qpidxarm_SOURCES ${AMQP_WCF_DIR}/src/Apache/Qpid/DtcPlugin/DtcPlugin.cpp) @@ -956,7 +959,7 @@ if (WIN32) COMPONENT ${QPID_COMPONENT_CLIENT}) install_pdb (qpidxarm ${QPID_COMPONENT_CLIENT}) endif (EXISTS ${qpidxarm_SOURCES}) -endif (WIN32) +endif (MSVC) set (qpidbroker_SOURCES ${mgen_broker_cpp} @@ -974,6 +977,8 @@ set (qpidbroker_SOURCES qpid/broker/Queue.cpp qpid/broker/QueueCleaner.cpp qpid/broker/QueueListeners.cpp + qpid/broker/FifoDistributor.cpp + qpid/broker/MessageGroupManager.cpp qpid/broker/PersistableMessage.cpp qpid/broker/Bridge.cpp qpid/broker/Connection.cpp @@ -990,7 +995,6 @@ set (qpidbroker_SOURCES qpid/broker/ExchangeRegistry.cpp qpid/broker/FanOutExchange.cpp qpid/broker/HeadersExchange.cpp - qpid/broker/IncompleteMessageList.cpp qpid/broker/Link.cpp qpid/broker/LinkRegistry.cpp qpid/broker/Message.cpp @@ -1003,7 +1007,7 @@ set (qpidbroker_SOURCES qpid/broker/QueueEvents.cpp qpid/broker/QueuePolicy.cpp qpid/broker/QueueRegistry.cpp - qpid/broker/RateTracker.cpp + qpid/broker/QueueFlowLimit.cpp qpid/broker/RecoveryManagerImpl.cpp qpid/broker/RecoveredEnqueue.cpp qpid/broker/RecoveredDequeue.cpp @@ -1069,13 +1073,15 @@ endif (CPACK_GENERATOR STREQUAL "NSIS") # REVISION => Version of underlying implementation. # Bump if implementation changes but API/ABI doesn't # AGE => Number of API/ABI versions this is backward compatible with -set (qmf_version 1.0.0) +set (qmf_version 2.0.0) +set (qmf2_version 1.0.0) set (qmfengine_version 1.0.0) set (qmf_SOURCES qpid/agent/ManagementAgentImpl.cpp qpid/agent/ManagementAgentImpl.h ) + add_msvc_version (qmf library dll) add_library (qmf SHARED ${qmf_SOURCES}) target_link_libraries (qmf qpidclient) @@ -1086,6 +1092,88 @@ install (TARGETS qmf OPTIONAL COMPONENT ${QPID_COMPONENT_QMF}) install_pdb (qmf ${QPID_COMPONENT_QMF}) +if(NOT WIN32) + set (qmf2_HEADERS + ../include/qmf/AgentEvent.h + ../include/qmf/Agent.h + ../include/qmf/AgentSession.h + ../include/qmf/ConsoleEvent.h + ../include/qmf/ConsoleSession.h + ../include/qmf/DataAddr.h + ../include/qmf/Data.h + ../include/qmf/exceptions.h + ../include/qmf/Handle.h + ../include/qmf/ImportExport.h + ../include/qmf/posix/EventNotifier.h + ../include/qmf/Query.h + ../include/qmf/Schema.h + ../include/qmf/SchemaId.h + ../include/qmf/SchemaMethod.h + ../include/qmf/SchemaProperty.h + ../include/qmf/SchemaTypes.h + ../include/qmf/Subscription.h + ) + + set (qmf2_SOURCES + ${qmf2_HEADERS} + qmf/agentCapability.h + qmf/Agent.cpp + qmf/AgentEvent.cpp + qmf/AgentEventImpl.h + qmf/AgentImpl.h + qmf/AgentSession.cpp + qmf/AgentSubscription.cpp + qmf/AgentSubscription.h + qmf/ConsoleEvent.cpp + qmf/ConsoleEventImpl.h + qmf/ConsoleSession.cpp + qmf/ConsoleSessionImpl.h + qmf/constants.cpp + qmf/constants.h + qmf/DataAddr.cpp + qmf/DataAddrImpl.h + qmf/Data.cpp + qmf/DataImpl.h + qmf/EventNotifierImpl.h + qmf/EventNotifierImpl.cpp + qmf/PosixEventNotifier.cpp + qmf/PosixEventNotifierImpl.cpp + qmf/exceptions.cpp + qmf/Expression.cpp + qmf/Expression.h + qmf/Hash.cpp + qmf/Hash.h + qmf/PrivateImplRef.h + qmf/Query.cpp + qmf/QueryImpl.h + qmf/Schema.cpp + qmf/SchemaCache.cpp + qmf/SchemaCache.h + qmf/SchemaId.cpp + qmf/SchemaIdImpl.h + qmf/SchemaImpl.h + qmf/SchemaMethod.cpp + qmf/SchemaMethodImpl.h + qmf/SchemaProperty.cpp + qmf/SchemaPropertyImpl.h + qmf/Subscription.cpp + qmf/SubscriptionImpl.h + ) + + add_msvc_version (qmf2 library dll) + add_library (qmf2 SHARED ${qmf2_SOURCES}) + target_link_libraries (qmf2 qpidmessaging qpidtypes qpidclient qpidcommon) + set_target_properties (qmf2 PROPERTIES + VERSION ${qmf2_version}) + install (TARGETS qmf2 OPTIONAL + DESTINATION ${QPID_INSTALL_LIBDIR} + COMPONENT ${QPID_COMPONENT_QMF}) + install (FILES ${qmf2_HEADERS} + DESTINATION ${QPID_INSTALL_INCLUDEDIR}/qmf + COMPONENT ${QPID_COMPONENT_QMF}) + install_pdb (qmf2 ${QPID_COMPONENT_QMF}) +endif (NOT WIN32) + set (qmfengine_SOURCES qmf/engine/Agent.cpp qmf/engine/BrokerProxyImpl.cpp diff --git a/cpp/src/CMakeWinVersions.cmake b/cpp/src/CMakeWinVersions.cmake index 9bffd2ba0e..0bac7cab47 100644 --- a/cpp/src/CMakeWinVersions.cmake +++ b/cpp/src/CMakeWinVersions.cmake @@ -34,11 +34,11 @@ # set ("winver_PACKAGE_NAME" "qpid-cpp")
# set ("winver_DESCRIPTION_SUMMARY" "Apache Qpid C++")
# set ("winver_FILE_VERSION_N1" "0")
-# set ("winver_FILE_VERSION_N2" "9")
+# set ("winver_FILE_VERSION_N2" "11")
# set ("winver_FILE_VERSION_N3" "0")
# set ("winver_FILE_VERSION_N4" "0")
# set ("winver_PRODUCT_VERSION_N1" "0")
-# set ("winver_PRODUCT_VERSION_N2" "9")
+# set ("winver_PRODUCT_VERSION_N2" "11")
# set ("winver_PRODUCT_VERSION_N3" "0")
# set ("winver_PRODUCT_VERSION_N4" "0")
# set ("winver_LEGAL_COPYRIGHT" "")
@@ -46,10 +46,10 @@ #
# Specification of per-project settings:
#
-# set ("winver_${projectName}_FileVersionBinary" "0,9,0,0")
-# set ("winver_${projectName}_ProductVersionBinary" "0,9,0,0")
-# set ("winver_${projectName}_FileVersionString" "0, 9, 0, 0")
-# set ("winver_${projectName}_ProductVersionString" "0, 9, 0, 0")
+# set ("winver_${projectName}_FileVersionBinary" "0,11,0,0")
+# set ("winver_${projectName}_ProductVersionBinary" "0,11,0,0")
+# set ("winver_${projectName}_FileVersionString" "0, 11, 0, 0")
+# set ("winver_${projectName}_ProductVersionString" "0, 11, 0, 0")
# set ("winver_${projectName}_FileDescription" "qpid-cpp-qpidcommon Library")
# set ("winver_${projectName}_LegalCopyright" "")
# set ("winver_${projectName}_InternalName" "qpidcommon")
diff --git a/cpp/src/Makefile.am b/cpp/src/Makefile.am index 2cd6ad462f..6230a8f6f6 100644 --- a/cpp/src/Makefile.am +++ b/cpp/src/Makefile.am @@ -37,6 +37,7 @@ windows_dist = \ qpid/sys/windows/IOHandle.cpp \ qpid/sys/windows/IoHandlePrivate.h \ qpid/sys/windows/LockFile.cpp \ + qpid/sys/windows/mingw32_compat.h \ qpid/sys/windows/PollableCondition.cpp \ qpid/sys/windows/PipeHandle.cpp \ ../include/qpid/sys/windows/Mutex.h \ @@ -88,7 +89,7 @@ rgen_cmd=ruby -I $(rgen_dir) $(rgen_dir)/generate . ../include $(specs) all $(rgen_srcs) $(srcdir)/rubygen.mk: rgen.timestamp rgen.timestamp: $(rgen_generator) $(specs) - $(rgen_cmd) $(srcdir)/rubygen.mk; touch $@ + $(rgen_cmd) $(srcdir)/rubygen.mk && touch $@ $(rgen_generator): # The CMake version is needed for dist @@ -127,14 +128,14 @@ qpidexec_SCRIPTS = qpidtestdir = $(qpidexecdir)/tests qpidtest_PROGRAMS = qpidtest_SCRIPTS = -tmoduledir = $(libdir)/qpid/tests -tmodule_LTLIBRARIES= +tmoduleexecdir = $(libdir)/qpid/tests +tmoduleexec_LTLIBRARIES= AM_CXXFLAGS += -DBOOST_FILESYSTEM_VERSION=2 ## Automake macros to build libraries and executables. -qpidd_CXXFLAGS = $(AM_CXXFLAGS) -DQPIDD_MODULE_DIR=\"$(dmoduledir)\" -DQPIDD_CONF_FILE=\"$(sysconfdir)/qpidd.conf\" -libqpidclient_la_CXXFLAGS = $(AM_CXXFLAGS) -DQPIDC_MODULE_DIR=\"$(cmoduledir)\" -DQPIDC_CONF_FILE=\"$(confdir)/qpidc.conf\" +qpidd_CXXFLAGS = $(AM_CXXFLAGS) -DQPIDD_MODULE_DIR=\"$(dmoduleexecdir)\" -DQPIDD_CONF_FILE=\"$(sysconfdir)/qpidd.conf\" +libqpidclient_la_CXXFLAGS = $(AM_CXXFLAGS) -DQPIDC_MODULE_DIR=\"$(cmoduleexecdir)\" -DQPIDC_CONF_FILE=\"$(confdir)/qpidc.conf\" qpidd_LDADD = \ libqpidbroker.la \ @@ -176,7 +177,7 @@ nobase_include_HEADERS += \ ../include/qpid/sys/posix/Time.h \ ../include/qpid/sys/posix/check.h -if HAVE_EPOLL +if HAVE_EPOLL poller = qpid/sys/epoll/EpollPoller.cpp endif @@ -195,15 +196,15 @@ libqpidcommon_la_SOURCES += $(poller) $(systeminfo) posix_broker_src = \ qpid/broker/posix/BrokerDefaults.cpp -lib_LTLIBRARIES = libqpidtypes.la libqpidcommon.la libqpidbroker.la libqpidclient.la libqpidmessaging.la +lib_LTLIBRARIES = libqpidtypes.la libqpidcommon.la libqpidbroker.la libqpidclient.la libqpidmessaging.la # Definitions for client and daemon plugins PLUGINLDFLAGS=-no-undefined -module -avoid-version confdir=$(sysconfdir)/qpid -dmoduledir=$(libdir)/qpid/daemon -cmoduledir=$(libdir)/qpid/client -dmodule_LTLIBRARIES = -cmodule_LTLIBRARIES = +dmoduleexecdir=$(libdir)/qpid/daemon +cmoduleexecdir=$(libdir)/qpid/client +dmoduleexec_LTLIBRARIES = +cmoduleexec_LTLIBRARIES = include cluster.mk include acl.mk @@ -245,7 +246,7 @@ rdma_la_LIBADD = \ rdma_la_LDFLAGS = $(PLUGINLDFLAGS) rdma_la_CXXFLAGS = \ $(AM_CXXFLAGS) -Wno-missing-field-initializers -dmodule_LTLIBRARIES += \ +dmoduleexec_LTLIBRARIES += \ rdma.la rdmaconnector_la_SOURCES = \ @@ -257,7 +258,7 @@ rdmaconnector_la_LIBADD = \ rdmaconnector_la_LDFLAGS = $(PLUGINLDFLAGS) rdmaconnector_la_CXXFLAGS = \ $(AM_CXXFLAGS) -Wno-missing-field-initializers -cmodule_LTLIBRARIES += \ +cmoduleexec_LTLIBRARIES += \ rdmaconnector.la # RDMA test/sample programs @@ -332,6 +333,7 @@ libqpidcommon_la_SOURCES += \ qpid/Address.cpp \ qpid/DataDir.cpp \ qpid/DataDir.h \ + qpid/DisableExceptionLogging.h \ qpid/Exception.cpp \ qpid/Modules.cpp \ qpid/Modules.h \ @@ -341,6 +343,7 @@ libqpidcommon_la_SOURCES += \ qpid/RefCounted.h \ qpid/RefCountedBuffer.cpp \ qpid/RefCountedBuffer.h \ + qpid/BufferRef.h \ qpid/Sasl.h \ qpid/SaslFactory.cpp \ qpid/SaslFactory.h \ @@ -561,8 +564,7 @@ libqpidbroker_la_SOURCES = \ qpid/broker/HandlerImpl.h \ qpid/broker/HeadersExchange.cpp \ qpid/broker/HeadersExchange.h \ - qpid/broker/IncompleteMessageList.cpp \ - qpid/broker/IncompleteMessageList.h \ + qpid/broker/AsyncCompletion.h \ qpid/broker/LegacyLVQ.h \ qpid/broker/LegacyLVQ.cpp \ qpid/broker/Link.cpp \ @@ -612,9 +614,9 @@ libqpidbroker_la_SOURCES = \ qpid/broker/QueueRegistry.cpp \ qpid/broker/QueueRegistry.h \ qpid/broker/QueuedMessage.h \ + qpid/broker/QueueFlowLimit.h \ + qpid/broker/QueueFlowLimit.cpp \ qpid/broker/RateFlowcontrol.h \ - qpid/broker/RateTracker.cpp \ - qpid/broker/RateTracker.h \ qpid/broker/RecoverableConfig.h \ qpid/broker/RecoverableExchange.h \ qpid/broker/RecoverableMessage.h \ @@ -651,6 +653,7 @@ libqpidbroker_la_SOURCES = \ qpid/broker/SessionState.h \ qpid/broker/SignalHandler.cpp \ qpid/broker/SignalHandler.h \ + qpid/broker/StatefulQueueObserver.h \ qpid/broker/System.cpp \ qpid/broker/System.h \ qpid/broker/ThresholdAlerts.cpp \ @@ -668,6 +671,11 @@ libqpidbroker_la_SOURCES = \ qpid/broker/TxPublish.h \ qpid/broker/Vhost.cpp \ qpid/broker/Vhost.h \ + qpid/broker/MessageDistributor.h \ + qpid/broker/FifoDistributor.h \ + qpid/broker/FifoDistributor.cpp \ + qpid/broker/MessageGroupManager.cpp \ + qpid/broker/MessageGroupManager.h \ qpid/management/ManagementAgent.cpp \ qpid/management/ManagementAgent.h \ qpid/management/ManagementDirectExchange.cpp \ @@ -739,7 +747,7 @@ libqpidclient_la_SOURCES = \ QPIDCLIENT_VERSION_INFO = 2:0:0 libqpidclient_la_LDFLAGS = -version-info $(QPIDCLIENT_VERSION_INFO) -libqpidtypes_la_libadd=-luuid +libqpidtypes_la_LIBADD= -luuid libqpidtypes_la_SOURCES= \ qpid/types/Exception.cpp \ qpid/types/Uuid.cpp \ @@ -786,9 +794,7 @@ libqpidmessaging_la_SOURCES = \ qpid/client/amqp0_10/SessionImpl.h \ qpid/client/amqp0_10/SessionImpl.cpp \ qpid/client/amqp0_10/SenderImpl.h \ - qpid/client/amqp0_10/SenderImpl.cpp \ - qpid/client/amqp0_10/SimpleUrlParser.h \ - qpid/client/amqp0_10/SimpleUrlParser.cpp + qpid/client/amqp0_10/SenderImpl.cpp QPIDMESSAGING_VERSION_INFO = 2:0:0 libqpidmessaging_la_LDFLAGS = -version-info $(QPIDMESSAGING_VERSION_INFO) @@ -801,6 +807,7 @@ nobase_include_HEADERS += \ ../include/qpid/Address.h \ ../include/qpid/CommonImportExport.h \ ../include/qpid/Exception.h \ + ../include/qpid/ImportExport.h \ ../include/qpid/InlineAllocator.h \ ../include/qpid/InlineVector.h \ ../include/qpid/Msg.h \ @@ -883,14 +890,10 @@ nobase_include_HEADERS += \ ../include/qpid/types/Variant.h \ ../include/qpid/types/ImportExport.h -# Force build of qpidd during dist phase so help2man will work. -dist-hook: $(BUILT_SOURCES) - $(MAKE) qpidd - # Create the default data directory install-data-local: $(mkinstalldirs) $(DESTDIR)/$(localstatedir)/lib/qpidd -# Support for pkg-config +# Support for pkg-config pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = qpid.pc diff --git a/cpp/src/acl.mk b/cpp/src/acl.mk index bcd1d88335..b8e2ff0e13 100644 --- a/cpp/src/acl.mk +++ b/cpp/src/acl.mk @@ -18,8 +18,8 @@ # # # acl library makefile fragment, to be included in Makefile.am -# -dmodule_LTLIBRARIES += acl.la +# +dmoduleexec_LTLIBRARIES += acl.la acl_la_SOURCES = \ qpid/acl/Acl.cpp \ diff --git a/cpp/src/cluster.mk b/cpp/src/cluster.mk index a791b2d41a..3ce4ce25b3 100644 --- a/cpp/src/cluster.mk +++ b/cpp/src/cluster.mk @@ -18,7 +18,7 @@ # # # Cluster library makefile fragment, to be included in Makefile.am -# +# # Optional CMAN support @@ -34,7 +34,7 @@ endif if HAVE_LIBCPG -dmodule_LTLIBRARIES += cluster.la +dmoduleexec_LTLIBRARIES += cluster.la cluster_la_SOURCES = \ $(CMAN_SOURCES) \ @@ -102,7 +102,7 @@ cluster_la_CXXFLAGS = $(AM_CXXFLAGS) -fno-strict-aliasing cluster_la_LDFLAGS = $(PLUGINLDFLAGS) # The watchdog plugin and helper executable -dmodule_LTLIBRARIES += watchdog.la +dmoduleexec_LTLIBRARIES += watchdog.la watchdog_la_SOURCES = qpid/cluster/WatchDogPlugin.cpp watchdog_la_LIBADD = libqpidbroker.la watchdog_la_LDFLAGS = $(PLUGINLDFLAGS) diff --git a/cpp/src/posix/QpiddBroker.cpp b/cpp/src/posix/QpiddBroker.cpp index 86504ba7fc..1cebcfc3ac 100644 --- a/cpp/src/posix/QpiddBroker.cpp +++ b/cpp/src/posix/QpiddBroker.cpp @@ -138,6 +138,9 @@ struct QpiddDaemon : public Daemon { brokerPtr->accept(); uint16_t port=brokerPtr->getPort(options->daemon.transport); ready(port); // Notify parent. + if (options->parent->broker.enableMgmt && (options->parent->broker.port == 0 || options->daemon.transport != TCP)) { + dynamic_cast<qmf::org::apache::qpid::broker::Broker*>(brokerPtr->GetManagementObject())->set_port(port); + } brokerPtr->run(); } }; @@ -182,8 +185,13 @@ int QpiddBroker::execute (QpiddOptions *options) { boost::intrusive_ptr<Broker> brokerPtr(new Broker(options->broker)); ScopedSetBroker ssb(brokerPtr); brokerPtr->accept(); - if (options->broker.port == 0 || myOptions->daemon.transport != TCP) - cout << uint16_t(brokerPtr->getPort(myOptions->daemon.transport)) << endl; + if (options->broker.port == 0 || myOptions->daemon.transport != TCP) { + uint16_t port = brokerPtr->getPort(myOptions->daemon.transport); + cout << port << endl; + if (options->broker.enableMgmt) { + dynamic_cast<qmf::org::apache::qpid::broker::Broker*>(brokerPtr->GetManagementObject())->set_port(port); + } + } brokerPtr->run(); } return 0; diff --git a/cpp/src/qmf.mk b/cpp/src/qmf.mk index f3462f1a93..3b6583bfaf 100644 --- a/cpp/src/qmf.mk +++ b/cpp/src/qmf.mk @@ -43,6 +43,7 @@ QMF2_API = \ ../include/qmf/ConsoleSession.h \ ../include/qmf/DataAddr.h \ ../include/qmf/Data.h \ + ../include/qmf/posix/EventNotifier.h \ ../include/qmf/exceptions.h \ ../include/qmf/Handle.h \ ../include/qmf/ImportExport.h \ @@ -92,6 +93,7 @@ libqmf2_la_SOURCES = \ qmf/AgentEventImpl.h \ qmf/AgentImpl.h \ qmf/AgentSession.cpp \ + qmf/AgentSessionImpl.h \ qmf/AgentSubscription.cpp \ qmf/AgentSubscription.h \ qmf/ConsoleEvent.cpp \ @@ -104,17 +106,22 @@ libqmf2_la_SOURCES = \ qmf/DataAddrImpl.h \ qmf/Data.cpp \ qmf/DataImpl.h \ + qmf/EventNotifierImpl.cpp \ + qmf/EventNotifierImpl.h \ qmf/exceptions.cpp \ qmf/Expression.cpp \ qmf/Expression.h \ qmf/Hash.cpp \ qmf/Hash.h \ + qmf/PosixEventNotifier.cpp \ + qmf/PosixEventNotifierImpl.cpp \ + qmf/PosixEventNotifierImpl.h \ qmf/PrivateImplRef.h \ qmf/Query.cpp \ qmf/QueryImpl.h \ - qmf/Schema.cpp \ qmf/SchemaCache.cpp \ qmf/SchemaCache.h \ + qmf/Schema.cpp \ qmf/SchemaId.cpp \ qmf/SchemaIdImpl.h \ qmf/SchemaImpl.h \ diff --git a/cpp/src/qmf/Agent.cpp b/cpp/src/qmf/Agent.cpp index 915f2a1c88..684f8e4fba 100644 --- a/cpp/src/qmf/Agent.cpp +++ b/cpp/src/qmf/Agent.cpp @@ -72,7 +72,7 @@ Schema Agent::getSchema(const SchemaId& s, Duration t) { return impl->getSchema( AgentImpl::AgentImpl(const std::string& n, uint32_t e, ConsoleSessionImpl& s) : name(n), directSubject(n), epoch(e), session(s), touched(true), untouchedCount(0), capability(0), - sender(session.directSender), nextCorrelator(1), schemaCache(s.schemaCache) + sender(session.directSender), schemaCache(s.schemaCache) { } @@ -102,12 +102,11 @@ const Variant& AgentImpl::getAttribute(const string& k) const ConsoleEvent AgentImpl::query(const Query& query, Duration timeout) { boost::shared_ptr<SyncContext> context(new SyncContext()); - uint32_t correlator; + uint32_t correlator(session.correlator()); ConsoleEvent result; { qpid::sys::Mutex::ScopedLock l(lock); - correlator = nextCorrelator++; contextMap[correlator] = context; } try { @@ -151,12 +150,7 @@ ConsoleEvent AgentImpl::query(const string& text, Duration timeout) uint32_t AgentImpl::queryAsync(const Query& query) { - uint32_t correlator; - - { - qpid::sys::Mutex::ScopedLock l(lock); - correlator = nextCorrelator++; - } + uint32_t correlator(session.correlator()); sendQuery(query, correlator); return correlator; @@ -172,12 +166,11 @@ uint32_t AgentImpl::queryAsync(const string& text) ConsoleEvent AgentImpl::callMethod(const string& method, const Variant::Map& args, const DataAddr& addr, Duration timeout) { boost::shared_ptr<SyncContext> context(new SyncContext()); - uint32_t correlator; + uint32_t correlator(session.correlator()); ConsoleEvent result; { qpid::sys::Mutex::ScopedLock l(lock); - correlator = nextCorrelator++; contextMap[correlator] = context; } try { @@ -213,12 +206,7 @@ ConsoleEvent AgentImpl::callMethod(const string& method, const Variant::Map& arg uint32_t AgentImpl::callMethodAsync(const string& method, const Variant::Map& args, const DataAddr& addr) { - uint32_t correlator; - - { - qpid::sys::Mutex::ScopedLock l(lock); - correlator = nextCorrelator++; - } + uint32_t correlator(session.correlator()); sendMethod(method, args, addr, correlator); return correlator; @@ -596,12 +584,7 @@ void AgentImpl::sendMethod(const string& method, const Variant::Map& args, const void AgentImpl::sendSchemaRequest(const SchemaId& id) { - uint32_t correlator; - - { - qpid::sys::Mutex::ScopedLock l(lock); - correlator = nextCorrelator++; - } + uint32_t correlator(session.correlator()); if (capability >= AGENT_CAPABILITY_V2_SCHEMA) { Query query(QUERY_SCHEMA, id); diff --git a/cpp/src/qmf/AgentImpl.h b/cpp/src/qmf/AgentImpl.h index 7fa4f4373a..09754a3a7e 100644 --- a/cpp/src/qmf/AgentImpl.h +++ b/cpp/src/qmf/AgentImpl.h @@ -99,7 +99,6 @@ namespace qmf { uint32_t capability; qpid::messaging::Sender sender; qpid::types::Variant::Map attributes; - uint32_t nextCorrelator; std::map<uint32_t, boost::shared_ptr<SyncContext> > contextMap; boost::shared_ptr<SchemaCache> schemaCache; mutable std::set<std::string> packageSet; diff --git a/cpp/src/qmf/AgentSession.cpp b/cpp/src/qmf/AgentSession.cpp index 4c5a72a467..251c25fd44 100644 --- a/cpp/src/qmf/AgentSession.cpp +++ b/cpp/src/qmf/AgentSession.cpp @@ -19,132 +19,7 @@ * */ -#include "qpid/RefCounted.h" -#include "qmf/PrivateImplRef.h" -#include "qmf/exceptions.h" -#include "qmf/AgentSession.h" -#include "qmf/AgentEventImpl.h" -#include "qmf/SchemaIdImpl.h" -#include "qmf/SchemaImpl.h" -#include "qmf/DataAddrImpl.h" -#include "qmf/DataImpl.h" -#include "qmf/QueryImpl.h" -#include "qmf/agentCapability.h" -#include "qmf/constants.h" -#include "qpid/sys/Mutex.h" -#include "qpid/sys/Condition.h" -#include "qpid/sys/Thread.h" -#include "qpid/sys/Runnable.h" -#include "qpid/log/Statement.h" -#include "qpid/messaging/Connection.h" -#include "qpid/messaging/Session.h" -#include "qpid/messaging/Receiver.h" -#include "qpid/messaging/Sender.h" -#include "qpid/messaging/Message.h" -#include "qpid/messaging/AddressParser.h" -#include "qpid/management/Buffer.h" -#include <queue> -#include <map> -#include <set> -#include <iostream> -#include <memory> - -using namespace std; -using namespace qpid::messaging; -using namespace qmf; -using qpid::types::Variant; - -namespace qmf { - class AgentSessionImpl : public virtual qpid::RefCounted, public qpid::sys::Runnable { - public: - ~AgentSessionImpl(); - - // - // Methods from API handle - // - AgentSessionImpl(Connection& c, const string& o); - void setDomain(const string& d) { checkOpen(); domain = d; } - void setVendor(const string& v) { checkOpen(); attributes["_vendor"] = v; } - void setProduct(const string& p) { checkOpen(); attributes["_product"] = p; } - void setInstance(const string& i) { checkOpen(); attributes["_instance"] = i; } - void setAttribute(const string& k, const qpid::types::Variant& v) { checkOpen(); attributes[k] = v; } - const string& getName() const { return agentName; } - void open(); - void close(); - bool nextEvent(AgentEvent& e, Duration t); - - void registerSchema(Schema& s); - DataAddr addData(Data& d, const string& n, bool persist); - void delData(const DataAddr&); - - void authAccept(AgentEvent& e); - void authReject(AgentEvent& e, const string& m); - void raiseException(AgentEvent& e, const string& s); - void raiseException(AgentEvent& e, const Data& d); - void response(AgentEvent& e, const Data& d); - void complete(AgentEvent& e); - void methodSuccess(AgentEvent& e); - void raiseEvent(const Data& d); - void raiseEvent(const Data& d, int s); - - private: - typedef map<DataAddr, Data, DataAddrCompare> DataIndex; - typedef map<SchemaId, Schema, SchemaIdCompare> SchemaMap; - - mutable qpid::sys::Mutex lock; - qpid::sys::Condition cond; - Connection connection; - Session session; - Sender directSender; - Sender topicSender; - string domain; - Variant::Map attributes; - Variant::Map options; - string agentName; - bool opened; - queue<AgentEvent> eventQueue; - qpid::sys::Thread* thread; - bool threadCanceled; - uint32_t bootSequence; - uint32_t interval; - uint64_t lastHeartbeat; - uint64_t lastVisit; - bool forceHeartbeat; - bool externalStorage; - bool autoAllowQueries; - bool autoAllowMethods; - uint32_t maxSubscriptions; - uint32_t minSubInterval; - uint32_t subLifetime; - bool publicEvents; - bool listenOnDirect; - bool strictSecurity; - uint64_t schemaUpdateTime; - string directBase; - string topicBase; - - SchemaMap schemata; - DataIndex globalIndex; - map<SchemaId, DataIndex, SchemaIdCompareNoHash> schemaIndex; - - void checkOpen(); - void setAgentName(); - void enqueueEvent(const AgentEvent&); - void handleLocateRequest(const Variant::List& content, const Message& msg); - void handleMethodRequest(const Variant::Map& content, const Message& msg); - void handleQueryRequest(const Variant::Map& content, const Message& msg); - void handleSchemaRequest(AgentEvent&); - void handleV1SchemaRequest(qpid::management::Buffer&, uint32_t, const Message&); - void dispatch(Message); - void sendHeartbeat(); - void send(Message, const Address&); - void flushResponses(AgentEvent&, bool); - void periodicProcessing(uint64_t); - void run(); - }; -} - -typedef qmf::PrivateImplRef<AgentSession> PI; +#include "qmf/AgentSessionImpl.h" AgentSession::AgentSession(AgentSessionImpl* impl) { PI::ctor(*this, impl); } AgentSession::AgentSession(const AgentSession& s) : qmf::Handle<AgentSessionImpl>() { PI::copy(*this, s); } @@ -161,6 +36,7 @@ const string& AgentSession::getName() const { return impl->getName(); } void AgentSession::open() { impl->open(); } void AgentSession::close() { impl->close(); } bool AgentSession::nextEvent(AgentEvent& e, Duration t) { return impl->nextEvent(e, t); } +int AgentSession::pendingEvents() const { return impl->pendingEvents(); } void AgentSession::registerSchema(Schema& s) { impl->registerSchema(s); } DataAddr AgentSession::addData(Data& d, const string& n, bool p) { return impl->addData(d, n, p); } void AgentSession::delData(const DataAddr& a) { impl->delData(a); } @@ -179,11 +55,11 @@ void AgentSession::raiseEvent(const Data& d, int s) { impl->raiseEvent(d, s); } //======================================================================================== AgentSessionImpl::AgentSessionImpl(Connection& c, const string& options) : - connection(c), domain("default"), opened(false), thread(0), threadCanceled(false), + connection(c), domain("default"), opened(false), eventNotifier(0), thread(0), threadCanceled(false), bootSequence(1), interval(60), lastHeartbeat(0), lastVisit(0), forceHeartbeat(false), externalStorage(false), autoAllowQueries(true), autoAllowMethods(true), maxSubscriptions(64), minSubInterval(3000), subLifetime(300), publicEvents(true), - listenOnDirect(true), strictSecurity(false), + listenOnDirect(true), strictSecurity(false), maxThreadWaitTime(5), schemaUpdateTime(uint64_t(qpid::sys::Duration(qpid::sys::EPOCH, qpid::sys::now()))) { // @@ -244,7 +120,14 @@ AgentSessionImpl::AgentSessionImpl(Connection& c, const string& options) : iter = optMap.find("strict-security"); if (iter != optMap.end()) strictSecurity = iter->second.asBool(); + + iter = optMap.find("max-thread-wait-time"); + if (iter != optMap.end()) + maxThreadWaitTime = iter->second.asUint32(); } + + if (maxThreadWaitTime > interval) + maxThreadWaitTime = interval; } @@ -252,6 +135,11 @@ AgentSessionImpl::~AgentSessionImpl() { if (opened) close(); + + if (thread) { + thread->join(); + delete thread; + } } @@ -260,6 +148,12 @@ void AgentSessionImpl::open() if (opened) throw QmfException("The session is already open"); + // If the thread exists, join and delete it before creating a new one. + if (thread) { + thread->join(); + delete thread; + } + const string addrArgs(";{create:never,node:{type:topic}}"); const string routableAddr("direct-agent.route." + qpid::types::Uuid(true).str()); attributes["_direct_subject"] = routableAddr; @@ -297,34 +191,47 @@ void AgentSessionImpl::open() } -void AgentSessionImpl::close() +void AgentSessionImpl::closeAsync() { if (!opened) return; - // Stop and join the receiver thread + // Stop the receiver thread. Don't join it until the destructor is called or open() is called. threadCanceled = true; - thread->join(); - delete thread; - - // Close the AMQP session - session.close(); opened = false; } +void AgentSessionImpl::close() +{ + closeAsync(); + + if (thread) { + thread->join(); + delete thread; + thread = 0; + } +} + + bool AgentSessionImpl::nextEvent(AgentEvent& event, Duration timeout) { uint64_t milliseconds = timeout.getMilliseconds(); qpid::sys::Mutex::ScopedLock l(lock); - if (eventQueue.empty()) - cond.wait(lock, qpid::sys::AbsTime(qpid::sys::now(), - qpid::sys::Duration(milliseconds * qpid::sys::TIME_MSEC))); + if (eventQueue.empty() && milliseconds > 0) { + int64_t nsecs(qpid::sys::TIME_INFINITE); + if ((uint64_t)(nsecs / 1000000) > milliseconds) + nsecs = (int64_t) milliseconds * 1000000; + qpid::sys::Duration then(nsecs); + cond.wait(lock, qpid::sys::AbsTime(qpid::sys::now(), then)); + } if (!eventQueue.empty()) { event = eventQueue.front(); eventQueue.pop(); + if (eventQueue.empty()) + alertEventNotifierLH(false); return true; } @@ -332,6 +239,26 @@ bool AgentSessionImpl::nextEvent(AgentEvent& event, Duration timeout) } +int AgentSessionImpl::pendingEvents() const +{ + qpid::sys::Mutex::ScopedLock l(lock); + return eventQueue.size(); +} + + +void AgentSessionImpl::setEventNotifier(EventNotifierImpl* notifier) +{ + qpid::sys::Mutex::ScopedLock l(lock); + eventNotifier = notifier; +} + +EventNotifierImpl* AgentSessionImpl::getEventNotifier() const +{ + qpid::sys::Mutex::ScopedLock l(lock); + return eventNotifier; +} + + void AgentSessionImpl::registerSchema(Schema& schema) { if (!schema.isFinalized()) @@ -587,8 +514,10 @@ void AgentSessionImpl::enqueueEvent(const AgentEvent& event) qpid::sys::Mutex::ScopedLock l(lock); bool notify = eventQueue.empty(); eventQueue.push(event); - if (notify) + if (notify) { cond.notify(); + alertEventNotifierLH(true); + } } @@ -1032,6 +961,13 @@ void AgentSessionImpl::periodicProcessing(uint64_t seconds) } +void AgentSessionImpl::alertEventNotifierLH(bool readable) +{ + if (eventNotifier) + eventNotifier->setReadable(readable); +} + + void AgentSessionImpl::run() { QPID_LOG(debug, "AgentSession thread started for agent " << agentName); @@ -1041,7 +977,7 @@ void AgentSessionImpl::run() periodicProcessing((uint64_t) qpid::sys::Duration(qpid::sys::EPOCH, qpid::sys::now()) / qpid::sys::TIME_SEC); Receiver rx; - bool valid = session.nextReceiver(rx, Duration::SECOND); + bool valid = session.nextReceiver(rx, Duration::SECOND * maxThreadWaitTime); if (threadCanceled) break; if (valid) { @@ -1058,6 +994,19 @@ void AgentSessionImpl::run() enqueueEvent(AgentEvent(new AgentEventImpl(AGENT_THREAD_FAILED))); } + session.close(); QPID_LOG(debug, "AgentSession thread exiting for agent " << agentName); } + +AgentSessionImpl& AgentSessionImplAccess::get(AgentSession& session) +{ + return *session.impl; +} + + +const AgentSessionImpl& AgentSessionImplAccess::get(const AgentSession& session) +{ + return *session.impl; +} + diff --git a/cpp/src/qmf/AgentSessionImpl.h b/cpp/src/qmf/AgentSessionImpl.h new file mode 100644 index 0000000000..ae512a4054 --- /dev/null +++ b/cpp/src/qmf/AgentSessionImpl.h @@ -0,0 +1,175 @@ +#ifndef __QMF_AGENT_SESSION_IMPL_H +#define __QMF_AGENT_SESSION_IMPL_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/RefCounted.h" +#include "qmf/PrivateImplRef.h" +#include "qmf/exceptions.h" +#include "qmf/AgentSession.h" +#include "qmf/AgentEventImpl.h" +#include "qmf/EventNotifierImpl.h" +#include "qpid/messaging/Connection.h" +#include "qpid/sys/Runnable.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Condition.h" +#include "qpid/sys/Thread.h" +#include "qpid/sys/Runnable.h" +#include "qpid/log/Statement.h" +#include "qpid/messaging/Connection.h" +#include "qpid/messaging/Session.h" +#include "qpid/messaging/Receiver.h" +#include "qpid/messaging/Sender.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/AddressParser.h" +#include "qpid/management/Buffer.h" +#include "qpid/RefCounted.h" +#include "qmf/PrivateImplRef.h" +#include "qmf/AgentSession.h" +#include "qmf/exceptions.h" +#include "qmf/AgentSession.h" +#include "qmf/SchemaIdImpl.h" +#include "qmf/SchemaImpl.h" +#include "qmf/DataAddrImpl.h" +#include "qmf/DataImpl.h" +#include "qmf/QueryImpl.h" +#include "qmf/agentCapability.h" +#include "qmf/constants.h" + +#include <queue> +#include <map> +#include <iostream> +#include <memory> + +using namespace std; +using namespace qpid::messaging; +using namespace qmf; +using qpid::types::Variant; + +typedef qmf::PrivateImplRef<AgentSession> PI; + +namespace qmf { + class AgentSessionImpl : public virtual qpid::RefCounted, public qpid::sys::Runnable { + public: + ~AgentSessionImpl(); + + // + // Methods from API handle + // + AgentSessionImpl(Connection& c, const string& o); + void setDomain(const string& d) { checkOpen(); domain = d; } + void setVendor(const string& v) { checkOpen(); attributes["_vendor"] = v; } + void setProduct(const string& p) { checkOpen(); attributes["_product"] = p; } + void setInstance(const string& i) { checkOpen(); attributes["_instance"] = i; } + void setAttribute(const string& k, const qpid::types::Variant& v) { checkOpen(); attributes[k] = v; } + const string& getName() const { return agentName; } + void open(); + void closeAsync(); + void close(); + bool nextEvent(AgentEvent& e, Duration t); + int pendingEvents() const; + + void setEventNotifier(EventNotifierImpl* eventNotifier); + EventNotifierImpl* getEventNotifier() const; + + void registerSchema(Schema& s); + DataAddr addData(Data& d, const string& n, bool persist); + void delData(const DataAddr&); + + void authAccept(AgentEvent& e); + void authReject(AgentEvent& e, const string& m); + void raiseException(AgentEvent& e, const string& s); + void raiseException(AgentEvent& e, const Data& d); + void response(AgentEvent& e, const Data& d); + void complete(AgentEvent& e); + void methodSuccess(AgentEvent& e); + void raiseEvent(const Data& d); + void raiseEvent(const Data& d, int s); + + private: + typedef map<DataAddr, Data, DataAddrCompare> DataIndex; + typedef map<SchemaId, Schema, SchemaIdCompare> SchemaMap; + + mutable qpid::sys::Mutex lock; + qpid::sys::Condition cond; + Connection connection; + Session session; + Sender directSender; + Sender topicSender; + string domain; + Variant::Map attributes; + Variant::Map options; + string agentName; + bool opened; + queue<AgentEvent> eventQueue; + EventNotifierImpl* eventNotifier; + qpid::sys::Thread* thread; + bool threadCanceled; + uint32_t bootSequence; + uint32_t interval; + uint64_t lastHeartbeat; + uint64_t lastVisit; + bool forceHeartbeat; + bool externalStorage; + bool autoAllowQueries; + bool autoAllowMethods; + uint32_t maxSubscriptions; + uint32_t minSubInterval; + uint32_t subLifetime; + bool publicEvents; + bool listenOnDirect; + bool strictSecurity; + uint32_t maxThreadWaitTime; + uint64_t schemaUpdateTime; + string directBase; + string topicBase; + + SchemaMap schemata; + DataIndex globalIndex; + map<SchemaId, DataIndex, SchemaIdCompareNoHash> schemaIndex; + + void checkOpen(); + void setAgentName(); + void enqueueEvent(const AgentEvent&); + void alertEventNotifierLH(bool readable); + void handleLocateRequest(const Variant::List& content, const Message& msg); + void handleMethodRequest(const Variant::Map& content, const Message& msg); + void handleQueryRequest(const Variant::Map& content, const Message& msg); + void handleSchemaRequest(AgentEvent&); + void handleV1SchemaRequest(qpid::management::Buffer&, uint32_t, const Message&); + void dispatch(Message); + void sendHeartbeat(); + void send(Message, const Address&); + void flushResponses(AgentEvent&, bool); + void periodicProcessing(uint64_t); + void run(); + }; + + struct AgentSessionImplAccess { + static AgentSessionImpl& get(AgentSession& session); + static const AgentSessionImpl& get(const AgentSession& session); + }; +} + + +#endif + diff --git a/cpp/src/qmf/ConsoleSession.cpp b/cpp/src/qmf/ConsoleSession.cpp index e12c1152f6..2dfc894c58 100644 --- a/cpp/src/qmf/ConsoleSession.cpp +++ b/cpp/src/qmf/ConsoleSession.cpp @@ -54,6 +54,7 @@ void ConsoleSession::setAgentFilter(const string& f) { impl->setAgentFilter(f); void ConsoleSession::open() { impl->open(); } void ConsoleSession::close() { impl->close(); } bool ConsoleSession::nextEvent(ConsoleEvent& e, Duration t) { return impl->nextEvent(e, t); } +int ConsoleSession::pendingEvents() const { return impl->pendingEvents(); } uint32_t ConsoleSession::getAgentCount() const { return impl->getAgentCount(); } Agent ConsoleSession::getAgent(uint32_t i) const { return impl->getAgent(i); } Agent ConsoleSession::getConnectedBrokerAgent() const { return impl->getConnectedBrokerAgent(); } @@ -65,9 +66,9 @@ Subscription ConsoleSession::subscribe(const string& q, const string& f, const s //======================================================================================== ConsoleSessionImpl::ConsoleSessionImpl(Connection& c, const string& options) : - connection(c), domain("default"), maxAgentAgeMinutes(5), - opened(false), thread(0), threadCanceled(false), lastVisit(0), lastAgePass(0), - connectedBrokerInAgentList(false), schemaCache(new SchemaCache()) + connection(c), domain("default"), maxAgentAgeMinutes(5), listenOnDirect(true), strictSecurity(false), maxThreadWaitTime(5), + opened(false), eventNotifier(0), thread(0), threadCanceled(false), lastVisit(0), lastAgePass(0), + connectedBrokerInAgentList(false), schemaCache(new SchemaCache()), nextCorrelator(1) { if (!options.empty()) { qpid::messaging::AddressParser parser(options); @@ -91,7 +92,14 @@ ConsoleSessionImpl::ConsoleSessionImpl(Connection& c, const string& options) : iter = optMap.find("strict-security"); if (iter != optMap.end()) strictSecurity = iter->second.asBool(); + + iter = optMap.find("max-thread-wait-time"); + if (iter != optMap.end()) + maxThreadWaitTime = iter->second.asUint32(); } + + if (maxThreadWaitTime > 60) + maxThreadWaitTime = 60; } @@ -99,6 +107,11 @@ ConsoleSessionImpl::~ConsoleSessionImpl() { if (opened) close(); + + if (thread) { + thread->join(); + delete thread; + } } @@ -153,6 +166,12 @@ void ConsoleSessionImpl::open() if (opened) throw QmfException("The session is already open"); + // If the thread exists, join and delete it before creating a new one. + if (thread) { + thread->join(); + delete thread; + } + // Establish messaging addresses directBase = "qmf." + domain + ".direct"; topicBase = "qmf." + domain + ".topic"; @@ -181,45 +200,57 @@ void ConsoleSessionImpl::open() // Start the receiver thread threadCanceled = false; + opened = true; thread = new qpid::sys::Thread(*this); // Send an agent_locate to direct address 'broker' to identify the connected-broker-agent. sendBrokerLocate(); if (agentQuery) sendAgentLocate(); - - opened = true; } -void ConsoleSessionImpl::close() +void ConsoleSessionImpl::closeAsync() { if (!opened) throw QmfException("The session is already closed"); - // Stop and join the receiver thread + // Stop the receiver thread. Don't join it until the destructor is called or open() is called. threadCanceled = true; - thread->join(); - delete thread; - - // Close the AMQP session - session.close(); opened = false; } +void ConsoleSessionImpl::close() +{ + closeAsync(); + + if (thread) { + thread->join(); + delete thread; + thread = 0; + } +} + + bool ConsoleSessionImpl::nextEvent(ConsoleEvent& event, Duration timeout) { uint64_t milliseconds = timeout.getMilliseconds(); qpid::sys::Mutex::ScopedLock l(lock); - if (eventQueue.empty()) - cond.wait(lock, qpid::sys::AbsTime(qpid::sys::now(), - qpid::sys::Duration(milliseconds * qpid::sys::TIME_MSEC))); + if (eventQueue.empty() && milliseconds > 0) { + int64_t nsecs(qpid::sys::TIME_INFINITE); + if ((uint64_t)(nsecs / 1000000) > milliseconds) + nsecs = (int64_t) milliseconds * 1000000; + qpid::sys::Duration then(nsecs); + cond.wait(lock, qpid::sys::AbsTime(qpid::sys::now(), then)); + } if (!eventQueue.empty()) { event = eventQueue.front(); eventQueue.pop(); + if (eventQueue.empty()) + alertEventNotifierLH(false); return true; } @@ -227,6 +258,27 @@ bool ConsoleSessionImpl::nextEvent(ConsoleEvent& event, Duration timeout) } +int ConsoleSessionImpl::pendingEvents() const +{ + qpid::sys::Mutex::ScopedLock l(lock); + return eventQueue.size(); +} + + +void ConsoleSessionImpl::setEventNotifier(EventNotifierImpl* notifier) +{ + qpid::sys::Mutex::ScopedLock l(lock); + eventNotifier = notifier; +} + + +EventNotifierImpl* ConsoleSessionImpl::getEventNotifier() const +{ + qpid::sys::Mutex::ScopedLock l(lock); + return eventNotifier; +} + + uint32_t ConsoleSessionImpl::getAgentCount() const { qpid::sys::Mutex::ScopedLock l(lock); @@ -268,8 +320,10 @@ void ConsoleSessionImpl::enqueueEventLH(const ConsoleEvent& event) { bool notify = eventQueue.empty(); eventQueue.push(event); - if (notify) + if (notify) { cond.notify(); + alertEventNotifierLH(true); + } } @@ -421,7 +475,23 @@ void ConsoleSessionImpl::handleAgentUpdate(const string& agentName, const Varian iter = content.find("_values"); if (iter == content.end()) return; - Variant::Map attrs(iter->second.asMap()); + const Variant::Map& in_attrs(iter->second.asMap()); + Variant::Map attrs; + + // + // Copy the map from the message to "attrs". Translate any old-style + // keys to their new key values in the process. + // + for (iter = in_attrs.begin(); iter != in_attrs.end(); iter++) { + if (iter->first == "epoch") + attrs[protocol::AGENT_ATTR_EPOCH] = iter->second; + else if (iter->first == "timestamp") + attrs[protocol::AGENT_ATTR_TIMESTAMP] = iter->second; + else if (iter->first == "heartbeat_interval") + attrs[protocol::AGENT_ATTR_HEARTBEAT_INTERVAL] = iter->second; + else + attrs[iter->first] = iter->second; + } iter = attrs.find(protocol::AGENT_ATTR_EPOCH); if (iter != attrs.end()) @@ -562,6 +632,13 @@ void ConsoleSessionImpl::periodicProcessing(uint64_t seconds) } +void ConsoleSessionImpl::alertEventNotifierLH(bool readable) +{ + if (eventNotifier) + eventNotifier->setReadable(readable); +} + + void ConsoleSessionImpl::run() { QPID_LOG(debug, "ConsoleSession thread started"); @@ -572,7 +649,7 @@ void ConsoleSessionImpl::run() qpid::sys::TIME_SEC); Receiver rx; - bool valid = session.nextReceiver(rx, Duration::SECOND); + bool valid = session.nextReceiver(rx, Duration::SECOND * maxThreadWaitTime); if (threadCanceled) break; if (valid) { @@ -589,6 +666,18 @@ void ConsoleSessionImpl::run() enqueueEvent(ConsoleEvent(new ConsoleEventImpl(CONSOLE_THREAD_FAILED))); } + session.close(); QPID_LOG(debug, "ConsoleSession thread exiting"); } + +ConsoleSessionImpl& ConsoleSessionImplAccess::get(ConsoleSession& session) +{ + return *session.impl; +} + + +const ConsoleSessionImpl& ConsoleSessionImplAccess::get(const ConsoleSession& session) +{ + return *session.impl; +} diff --git a/cpp/src/qmf/ConsoleSessionImpl.h b/cpp/src/qmf/ConsoleSessionImpl.h index 675c8bcfb5..e2b30602fa 100644 --- a/cpp/src/qmf/ConsoleSessionImpl.h +++ b/cpp/src/qmf/ConsoleSessionImpl.h @@ -27,6 +27,7 @@ #include "qmf/SchemaId.h" #include "qmf/Schema.h" #include "qmf/ConsoleEventImpl.h" +#include "qmf/EventNotifierImpl.h" #include "qmf/SchemaCache.h" #include "qmf/Query.h" #include "qpid/sys/Mutex.h" @@ -41,9 +42,13 @@ #include "qpid/messaging/Address.h" #include "qpid/management/Buffer.h" #include "qpid/types/Variant.h" + +#include <boost/shared_ptr.hpp> #include <map> #include <queue> +using namespace std; + namespace qmf { class ConsoleSessionImpl : public virtual qpid::RefCounted, public qpid::sys::Runnable { public: @@ -56,8 +61,14 @@ namespace qmf { void setDomain(const std::string& d) { domain = d; } void setAgentFilter(const std::string& f); void open(); + void closeAsync(); void close(); bool nextEvent(ConsoleEvent& e, qpid::messaging::Duration t); + int pendingEvents() const; + + void setEventNotifier(EventNotifierImpl* notifier); + EventNotifierImpl* getEventNotifier() const; + uint32_t getAgentCount() const; Agent getAgent(uint32_t i) const; Agent getConnectedBrokerAgent() const { return connectedBrokerAgent; } @@ -75,9 +86,11 @@ namespace qmf { uint32_t maxAgentAgeMinutes; bool listenOnDirect; bool strictSecurity; + uint32_t maxThreadWaitTime; Query agentQuery; bool opened; std::queue<ConsoleEvent> eventQueue; + EventNotifierImpl* eventNotifier; qpid::sys::Thread* thread; bool threadCanceled; uint64_t lastVisit; @@ -89,6 +102,8 @@ namespace qmf { std::string directBase; std::string topicBase; boost::shared_ptr<SchemaCache> schemaCache; + qpid::sys::Mutex corrlock; + uint32_t nextCorrelator; void enqueueEvent(const ConsoleEvent&); void enqueueEventLH(const ConsoleEvent&); @@ -98,10 +113,17 @@ namespace qmf { void handleAgentUpdate(const std::string&, const qpid::types::Variant::Map&, const qpid::messaging::Message&); void handleV1SchemaResponse(qpid::management::Buffer&, uint32_t, const qpid::messaging::Message&); void periodicProcessing(uint64_t); + void alertEventNotifierLH(bool readable); void run(); + uint32_t correlator() { qpid::sys::Mutex::ScopedLock l(corrlock); return nextCorrelator++; } friend class AgentImpl; }; + + struct ConsoleSessionImplAccess { + static ConsoleSessionImpl& get(ConsoleSession& session); + static const ConsoleSessionImpl& get(const ConsoleSession& session); + }; } #endif diff --git a/cpp/src/qmf/DataAddr.cpp b/cpp/src/qmf/DataAddr.cpp index fb51d5787f..d16e12062e 100644 --- a/cpp/src/qmf/DataAddr.cpp +++ b/cpp/src/qmf/DataAddr.cpp @@ -36,7 +36,9 @@ DataAddr::~DataAddr() { PI::dtor(*this); } DataAddr& DataAddr::operator=(const DataAddr& s) { return PI::assign(*this, s); } bool DataAddr::operator==(const DataAddr& o) { return *impl == *o.impl; } +bool DataAddr::operator==(const DataAddr& o) const { return *impl == *o.impl; } bool DataAddr::operator<(const DataAddr& o) { return *impl < *o.impl; } +bool DataAddr::operator<(const DataAddr& o) const { return *impl < *o.impl; } DataAddr::DataAddr(const qpid::types::Variant::Map& m) { PI::ctor(*this, new DataAddrImpl(m)); } DataAddr::DataAddr(const string& n, const string& a, uint32_t e) { PI::ctor(*this, new DataAddrImpl(n, a, e)); } @@ -45,7 +47,7 @@ const string& DataAddr::getAgentName() const { return impl->getAgentName(); } uint32_t DataAddr::getAgentEpoch() const { return impl->getAgentEpoch(); } Variant::Map DataAddr::asMap() const { return impl->asMap(); } -bool DataAddrImpl::operator==(const DataAddrImpl& other) +bool DataAddrImpl::operator==(const DataAddrImpl& other) const { return agentName == other.agentName && @@ -54,7 +56,7 @@ bool DataAddrImpl::operator==(const DataAddrImpl& other) } -bool DataAddrImpl::operator<(const DataAddrImpl& other) +bool DataAddrImpl::operator<(const DataAddrImpl& other) const { if (agentName < other.agentName) return true; if (agentName > other.agentName) return false; diff --git a/cpp/src/qmf/DataAddrImpl.h b/cpp/src/qmf/DataAddrImpl.h index 3f9cae9453..11d512f0c4 100644 --- a/cpp/src/qmf/DataAddrImpl.h +++ b/cpp/src/qmf/DataAddrImpl.h @@ -38,8 +38,8 @@ namespace qmf { // // Methods from API handle // - bool operator==(const DataAddrImpl&); - bool operator<(const DataAddrImpl&); + bool operator==(const DataAddrImpl&) const; + bool operator<(const DataAddrImpl&) const; DataAddrImpl(const qpid::types::Variant::Map&); DataAddrImpl(const std::string& _name, const std::string& _agentName, uint32_t _agentEpoch=0) : agentName(_agentName), name(_name), agentEpoch(_agentEpoch) {} diff --git a/cpp/src/qmf/EventNotifierImpl.cpp b/cpp/src/qmf/EventNotifierImpl.cpp new file mode 100644 index 0000000000..20114aaa5e --- /dev/null +++ b/cpp/src/qmf/EventNotifierImpl.cpp @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "qmf/EventNotifierImpl.h" +#include "qmf/AgentSessionImpl.h" +#include "qmf/ConsoleSessionImpl.h" + +EventNotifierImpl::EventNotifierImpl(AgentSession& agentSession) + : readable(false), agent(agentSession) +{ + AgentSessionImplAccess::get(agent).setEventNotifier(this); +} + + +EventNotifierImpl::EventNotifierImpl(ConsoleSession& consoleSession) + : readable(false), console(consoleSession) +{ + ConsoleSessionImplAccess::get(console).setEventNotifier(this); +} + + +EventNotifierImpl::~EventNotifierImpl() +{ + if (agent.isValid()) + AgentSessionImplAccess::get(agent).setEventNotifier(NULL); + if (console.isValid()) + ConsoleSessionImplAccess::get(console).setEventNotifier(NULL); +} + +void EventNotifierImpl::setReadable(bool readable) +{ + update(readable); + this->readable = readable; +} + + +bool EventNotifierImpl::isReadable() const +{ + return this->readable; +} diff --git a/cpp/src/tests/qrsh_utils/qrsh_example_command.cpp b/cpp/src/qmf/EventNotifierImpl.h index 386e2f73f0..d85f9979d2 100644 --- a/cpp/src/tests/qrsh_utils/qrsh_example_command.cpp +++ b/cpp/src/qmf/EventNotifierImpl.h @@ -1,5 +1,7 @@ +#ifndef __QMF_EVENT_NOTIFIER_IMPL_H +#define __QMF_EVENT_NOTIFIER_IMPL_H + /* - * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -7,46 +9,40 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. - * - */ - -#include <stdio.h> -#include <unistd.h> - + */ +#include "qmf/AgentSession.h" +#include "qmf/ConsoleSession.h" -main ( int argc, char ** argv ) +namespace qmf { - fprintf ( stderr, "Hello, I am the Example Child!\n"); - fprintf ( stderr, "my arguments %d are:\n", argc - 1 ); - fprintf ( stdout, "And hello to stdout, too!\n"); - - int i; - for ( i = 1; i < argc; ++ i ) - { - fprintf ( stderr, "arg %d: |%s|\n", i, argv[i] ); - } - - for ( i = 0; i < 15; ++ i ) - { - fprintf ( stderr, "child sleeping...\n" ); - sleep ( 1 ); - } - - fprintf ( stderr, "child exiting with code 13.\n" ); - - return 13; + class EventNotifierImpl { + private: + bool readable; + AgentSession agent; + ConsoleSession console; + + public: + EventNotifierImpl(AgentSession& agentSession); + EventNotifierImpl(ConsoleSession& consoleSession); + virtual ~EventNotifierImpl(); + + void setReadable(bool readable); + bool isReadable() const; + + protected: + virtual void update(bool readable) = 0; + }; } - - +#endif diff --git a/cpp/src/qmf/PosixEventNotifier.cpp b/cpp/src/qmf/PosixEventNotifier.cpp new file mode 100644 index 0000000000..a364cc155d --- /dev/null +++ b/cpp/src/qmf/PosixEventNotifier.cpp @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "qmf/posix/EventNotifier.h" +#include "qmf/PosixEventNotifierImpl.h" +#include "qmf/PrivateImplRef.h" + +using namespace qmf; +using namespace std; + +typedef qmf::PrivateImplRef<posix::EventNotifier> PI; + +posix::EventNotifier::EventNotifier(PosixEventNotifierImpl* impl) { PI::ctor(*this, impl); } + +posix::EventNotifier::EventNotifier(AgentSession& agentSession) +{ + PI::ctor(*this, new PosixEventNotifierImpl(agentSession)); +} + + +posix::EventNotifier::EventNotifier(ConsoleSession& consoleSession) +{ + PI::ctor(*this, new PosixEventNotifierImpl(consoleSession)); +} + + +posix::EventNotifier::EventNotifier(const posix::EventNotifier& that) + : Handle<PosixEventNotifierImpl>() +{ + PI::copy(*this, that); +} + + +posix::EventNotifier::~EventNotifier() +{ + PI::dtor(*this); +} + +posix::EventNotifier& posix::EventNotifier::operator=(const posix::EventNotifier& that) +{ + return PI::assign(*this, that); +} + + +int posix::EventNotifier::getHandle() const +{ + return impl->getHandle(); +} + diff --git a/cpp/src/qmf/PosixEventNotifierImpl.cpp b/cpp/src/qmf/PosixEventNotifierImpl.cpp new file mode 100644 index 0000000000..011dbcc214 --- /dev/null +++ b/cpp/src/qmf/PosixEventNotifierImpl.cpp @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "PosixEventNotifierImpl.h" +#include "qpid/log/Statement.h" + +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +#define BUFFER_SIZE 10 + +using namespace qmf; + +PosixEventNotifierImpl::PosixEventNotifierImpl(AgentSession& agentSession) + : EventNotifierImpl(agentSession) +{ + openHandle(); +} + + +PosixEventNotifierImpl::PosixEventNotifierImpl(ConsoleSession& consoleSession) + : EventNotifierImpl(consoleSession) +{ + openHandle(); +} + + +PosixEventNotifierImpl::~PosixEventNotifierImpl() +{ + closeHandle(); +} + + +void PosixEventNotifierImpl::update(bool readable) +{ + char buffer[BUFFER_SIZE]; + + if(readable && !this->isReadable()) { + if (::write(myHandle, "1", 1) == -1) + QPID_LOG(error, "PosixEventNotifierImpl::update write failed: " << errno); + } + else if(!readable && this->isReadable()) { + if (::read(yourHandle, buffer, BUFFER_SIZE) == -1) + QPID_LOG(error, "PosixEventNotifierImpl::update read failed: " << errno); + } +} + + +void PosixEventNotifierImpl::openHandle() +{ + int pair[2]; + + if(::pipe(pair) == -1) + throw QmfException("Unable to open event notifier handle."); + + yourHandle = pair[0]; + myHandle = pair[1]; + + int flags; + + flags = ::fcntl(yourHandle, F_GETFL); + if((::fcntl(yourHandle, F_SETFL, flags | O_NONBLOCK)) == -1) + throw QmfException("Unable to make remote handle non-blocking."); + + flags = ::fcntl(myHandle, F_GETFL); + if((::fcntl(myHandle, F_SETFL, flags | O_NONBLOCK)) == -1) + throw QmfException("Unable to make local handle non-blocking."); +} + + +void PosixEventNotifierImpl::closeHandle() +{ + if(myHandle > 0) { + ::close(myHandle); + myHandle = -1; + } + + if(yourHandle > 0) { + ::close(yourHandle); + yourHandle = -1; + } +} + + +PosixEventNotifierImpl& PosixEventNotifierImplAccess::get(posix::EventNotifier& notifier) +{ + return *notifier.impl; +} + + +const PosixEventNotifierImpl& PosixEventNotifierImplAccess::get(const posix::EventNotifier& notifier) +{ + return *notifier.impl; +} + diff --git a/cpp/src/qmf/PosixEventNotifierImpl.h b/cpp/src/qmf/PosixEventNotifierImpl.h new file mode 100644 index 0000000000..c8a7446bd5 --- /dev/null +++ b/cpp/src/qmf/PosixEventNotifierImpl.h @@ -0,0 +1,61 @@ +#ifndef __QMF_POSIX_EVENT_NOTIFIER_IMPL_H +#define __QMF_POSIX_EVENT_NOTIFIER_IMPL_H + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "qmf/posix/EventNotifier.h" +#include "qmf/EventNotifierImpl.h" +#include "qpid/RefCounted.h" + +namespace qmf +{ + class AgentSession; + class ConsoleSession; + + class PosixEventNotifierImpl : public EventNotifierImpl, public virtual qpid::RefCounted + { + public: + PosixEventNotifierImpl(AgentSession& agentSession); + PosixEventNotifierImpl(ConsoleSession& consoleSession); + virtual ~PosixEventNotifierImpl(); + + int getHandle() const { return yourHandle; } + + private: + int myHandle; + int yourHandle; + + void openHandle(); + void closeHandle(); + + protected: + void update(bool readable); + }; + + struct PosixEventNotifierImplAccess + { + static PosixEventNotifierImpl& get(posix::EventNotifier& notifier); + static const PosixEventNotifierImpl& get(const posix::EventNotifier& notifier); + }; + +} + +#endif + diff --git a/cpp/src/qmf/PrivateImplRef.h b/cpp/src/qmf/PrivateImplRef.h index 8b698c4199..960cbb2e09 100644 --- a/cpp/src/qmf/PrivateImplRef.h +++ b/cpp/src/qmf/PrivateImplRef.h @@ -23,8 +23,8 @@ */ #include "qmf/ImportExport.h" -#include <boost/intrusive_ptr.hpp> #include "qpid/RefCounted.h" +#include <boost/intrusive_ptr.hpp> namespace qmf { diff --git a/cpp/src/qmf/engine/ResilientConnection.cpp b/cpp/src/qmf/engine/ResilientConnection.cpp index ab65b8d768..41dd9ff00c 100644 --- a/cpp/src/qmf/engine/ResilientConnection.cpp +++ b/cpp/src/qmf/engine/ResilientConnection.cpp @@ -334,8 +334,7 @@ void ResilientConnectionImpl::notify() { if (notifyFd != -1) { - int unused_ret; //Suppress warnings about ignoring return value. - unused_ret = ::write(notifyFd, ".", 1); + (void) ::write(notifyFd, ".", 1); } } @@ -432,8 +431,7 @@ void ResilientConnectionImpl::EnqueueEvent(ResilientConnectionEvent::EventKind k if (notifyFd != -1) { - int unused_ret; //Suppress warnings about ignoring return value. - unused_ret = ::write(notifyFd, ".", 1); + (void) ::write(notifyFd, ".", 1); } } diff --git a/cpp/src/qmf/engine/SchemaImpl.cpp b/cpp/src/qmf/engine/SchemaImpl.cpp index e0948a9911..9d363d3012 100644 --- a/cpp/src/qmf/engine/SchemaImpl.cpp +++ b/cpp/src/qmf/engine/SchemaImpl.cpp @@ -35,17 +35,17 @@ using qpid::framing::Uuid; SchemaHash::SchemaHash() { for (int idx = 0; idx < 16; idx++) - hash[idx] = 0x5A; + hash.b[idx] = 0x5A; } void SchemaHash::encode(Buffer& buffer) const { - buffer.putBin128(hash); + buffer.putBin128(hash.b); } void SchemaHash::decode(Buffer& buffer) { - buffer.getBin128(hash); + buffer.getBin128(hash.b); } void SchemaHash::update(uint8_t data) @@ -55,9 +55,8 @@ void SchemaHash::update(uint8_t data) void SchemaHash::update(const char* data, uint32_t len) { - uint64_t* first = (uint64_t*) hash; - uint64_t* second = (uint64_t*) hash + 1; - + uint64_t* first = &hash.q[0]; + uint64_t* second = &hash.q[1]; for (uint32_t idx = 0; idx < len; idx++) { *first = *first ^ (uint64_t) data[idx]; *second = *second << 1; diff --git a/cpp/src/qmf/engine/SchemaImpl.h b/cpp/src/qmf/engine/SchemaImpl.h index 8b079a5ec6..683fb6f8f0 100644 --- a/cpp/src/qmf/engine/SchemaImpl.h +++ b/cpp/src/qmf/engine/SchemaImpl.h @@ -35,7 +35,10 @@ namespace engine { // they've been registered. class SchemaHash { - uint8_t hash[16]; + union h { + uint8_t b[16]; + uint64_t q[2]; + } hash; public: SchemaHash(); void encode(qpid::framing::Buffer& buffer) const; @@ -47,7 +50,7 @@ namespace engine { void update(Direction d) { update((uint8_t) d); } void update(Access a) { update((uint8_t) a); } void update(bool b) { update((uint8_t) (b ? 1 : 0)); } - const uint8_t* get() const { return hash; } + const uint8_t* get() const { return hash.b; } bool operator==(const SchemaHash& other) const; bool operator<(const SchemaHash& other) const; bool operator>(const SchemaHash& other) const; diff --git a/cpp/src/qpid/Address.cpp b/cpp/src/qpid/Address.cpp index e2b2dfbcdf..bed3d592df 100644 --- a/cpp/src/qpid/Address.cpp +++ b/cpp/src/qpid/Address.cpp @@ -28,7 +28,13 @@ namespace qpid { const string Address::TCP("tcp"); ostream& operator<<(ostream& os, const Address& a) { - return os << a.protocol << ":" << a.host << ":" << a.port; + // If the host is an IPv6 literal we need to print "[]" around it + // (we detect IPv6 literals because they contain ":" which is otherwise illegal) + if (a.host.find(':') != string::npos) { + return os << a.protocol << ":[" << a.host << "]:" << a.port; + } else { + return os << a.protocol << ":" << a.host << ":" << a.port; + } } bool operator==(const Address& x, const Address& y) { diff --git a/cpp/src/qpid/BufferRef.h b/cpp/src/qpid/BufferRef.h new file mode 100644 index 0000000000..bfe1f9ebaa --- /dev/null +++ b/cpp/src/qpid/BufferRef.h @@ -0,0 +1,70 @@ +#ifndef QPID_BUFFERREF_H +#define QPID_BUFFERREF_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/RefCounted.h" +#include <boost/intrusive_ptr.hpp> + +namespace qpid { + +/** Template for mutable or const buffer references */ +template <class T> class BufferRefT { + public: + BufferRefT() : begin_(0), end_(0) {} + + BufferRefT(boost::intrusive_ptr<RefCounted> c, T* begin, T* end) : + counter(c), begin_(begin), end_(end) {} + + template <class U> BufferRefT(const BufferRefT<U>& other) : + counter(other.counter), begin_(other.begin_), end_(other.end_) {} + + T* begin() const { return begin_; } + T* end() const { return end_; } + + /** Return a sub-buffer of the current buffer */ + BufferRefT sub_buffer(T* begin, T* end) { + assert(begin_ <= begin && begin <= end_); + assert(begin_ <= end && end <= end_); + assert(begin <= end); + return BufferRefT(counter, begin, end); + } + + private: + boost::intrusive_ptr<RefCounted> counter; + T* begin_; + T* end_; +}; + +/** + * Reference to a mutable ref-counted buffer. + */ +typedef BufferRefT<char> BufferRef; + +/** + * Reference to a const ref-counted buffer. + */ +typedef BufferRefT<const char> ConstBufferRef; + +} // namespace qpid + +#endif /*!QPID_BUFFERREF_H*/ diff --git a/cpp/src/qpid/client/amqp0_10/SimpleUrlParser.h b/cpp/src/qpid/DisableExceptionLogging.h index 24f90ca9d6..04a9240513 100644 --- a/cpp/src/qpid/client/amqp0_10/SimpleUrlParser.h +++ b/cpp/src/qpid/DisableExceptionLogging.h @@ -1,5 +1,5 @@ -#ifndef QPID_CLIENT_AMQP0_10_SIMPLEURLPARSER_H -#define QPID_CLIENT_AMQP0_10_SIMPLEURLPARSER_H +#ifndef QPID_DISABLEEXCEPTIONLOGGING_H +#define QPID_DISABLEEXCEPTIONLOGGING_H /* * @@ -10,9 +10,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -21,22 +21,19 @@ * under the License. * */ -#include <string> +#include "qpid/CommonImportExport.h" namespace qpid { -namespace client { - -struct ConnectionSettings; - -namespace amqp0_10 { /** - * Parses a simple url of the form user/password@hostname:port + * Temporarily disable logging in qpid::Exception constructor. + * Used by log::Logger to avoid logging exceptions during Logger construction. */ -struct SimpleUrlParser +struct DisableExceptionLogging { - static void parse(const std::string& url, qpid::client::ConnectionSettings& result); + QPID_COMMON_EXTERN DisableExceptionLogging(); + QPID_COMMON_EXTERN ~DisableExceptionLogging(); }; -}}} // namespace qpid::client::amqp0_10 +} // namespace qpid -#endif /*!QPID_CLIENT_AMQP0_10_SIMPLEURLPARSER_H*/ +#endif /*!QPID_DISABLEEXCEPTIONLOGGING_H*/ diff --git a/cpp/src/qpid/Exception.cpp b/cpp/src/qpid/Exception.cpp index 16a3a13d17..a6696f06e1 100644 --- a/cpp/src/qpid/Exception.cpp +++ b/cpp/src/qpid/Exception.cpp @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -21,13 +21,25 @@ #include "qpid/log/Statement.h" #include "qpid/Exception.h" +#include "qpid/DisableExceptionLogging.h" #include <typeinfo> #include <assert.h> #include <string.h> namespace qpid { +// Note on static initialization order: if an exception is constructed +// in a static constructor before disableExceptionLogging has been +// initialized, the worst that can happen is we lose an exception log +// message. Since we shouldn't be throwing a lot of exceptions during +// static construction this seems safe. +static bool disableExceptionLogging = false; + +DisableExceptionLogging::DisableExceptionLogging() { disableExceptionLogging = true; } +DisableExceptionLogging::~DisableExceptionLogging() { disableExceptionLogging = false; } + Exception::Exception(const std::string& msg) throw() : message(msg) { + if (disableExceptionLogging) return; QPID_LOG_IF(debug, !msg.empty(), "Exception constructed: " << message); } diff --git a/cpp/src/qpid/Modules.cpp b/cpp/src/qpid/Modules.cpp index 8f58df6ed1..727e05d212 100644 --- a/cpp/src/qpid/Modules.cpp +++ b/cpp/src/qpid/Modules.cpp @@ -64,7 +64,6 @@ void tryShlib(const char* libname_, bool noThrow) { if (!isShlibName(libname)) libname += suffix(); try { sys::Shlib shlib(libname); - QPID_LOG (info, "Loaded Module: " << libname); } catch (const std::exception& /*e*/) { if (!noThrow) @@ -82,7 +81,7 @@ void loadModuleDir (std::string dirname, bool isDefault) return; throw Exception ("Directory not found: " + dirname); } - if (!fs::is_directory(dirPath)) + if (!fs::is_directory(dirPath)) { throw Exception ("Invalid value for module-dir: " + dirname + " is not a directory"); } diff --git a/cpp/src/qpid/Options.cpp b/cpp/src/qpid/Options.cpp index 499fb71bc3..4b13e349f5 100644 --- a/cpp/src/qpid/Options.cpp +++ b/cpp/src/qpid/Options.cpp @@ -30,23 +30,6 @@ namespace qpid { using namespace std; -/* - * --------------------------------------------- - * Explanation for Boost 103200 conditional code - * --------------------------------------------- - * - * Please see large comment in Options.h . - * - */ - -#if ( BOOST_VERSION == 103200 ) -std::vector<std::string> Options::long_names; -std::vector<std::string> Options::short_names; -#endif - - - - namespace { struct EnvOptMapper { @@ -69,49 +52,11 @@ struct EnvOptMapper { static const std::string prefix("QPID_"); if (envVar.substr(0, prefix.size()) == prefix) { string env = envVar.substr(prefix.size()); -#if (BOOST_VERSION >= 103300) typedef const std::vector< boost::shared_ptr<po::option_description> > OptDescs; OptDescs::const_iterator i = find_if(opts.options().begin(), opts.options().end(), boost::bind(matchStr, env, _1)); if (i != opts.options().end()) return (*i)->long_name(); -#else - /* - * For Boost version 103200 and below. - * - * In Boost version 103200, the options_description::options member, - * used above, is private. So what I will do here is use the - * count() funtion, which returns a 1 or 0 indicating presence or - * absence of the environment variable. - * - * If it is present, I will return its name. Env vars do not have - * short and long forms, so the name is synonymous with the long - * name. (This would not work for command line args.) - * And if it's absent -- an empty string. - */ - - - /* - * The env vars come in unaltered, i.e. QPID_FOO, but the - * options are stored normalized as "qpid-foo". Change the - * local variable "env" so it can be found by "opts". - */ - for (std::string::iterator i = env.begin(); i != env.end(); ++i) - { - *i = (*i == '_') - ? '-' - : ::tolower(*i); - } - - if ( opts.count(env.c_str()) > 0 ) - { - return env.c_str(); - } - else - { - return string(); - } -#endif } return string(); } @@ -166,10 +111,6 @@ std::string prettyArg(const std::string& name, const std::string& value) { Options::Options(const string& name) : po::options_description(name) - -#if ( BOOST_VERSION == 103200 ) - , m_less_easy(this, this) -#endif { } @@ -186,7 +127,6 @@ void Options::parse(int argc, char const* const* argv, const std::string& config parsing="command line options"; if (argc > 0 && argv != 0) { if (allowUnknown) { -#if (BOOST_VERSION >= 103300) // This hideous workaround is required because boost 1.33 has a bug // that causes 'allow_unregistered' to not work. po::command_line_parser clp = po::command_line_parser(argc, const_cast<char**>(argv)). @@ -200,113 +140,6 @@ void Options::parse(int argc, char const* const* argv, const std::string& config filtopts.options.push_back (*i); po::store(filtopts, vm); -#elif ( BOOST_VERSION == 103200 ) - - /* - * "Tokenize" the argv to get rid of equals signs. - */ - vector<string> tokenized_argv; - vector<string>::iterator iter; - - for ( int i = 0; i < argc; ++ i ) - { - string s = argv[i]; - size_t equals_pos = s.find_first_of ( '=' ); - - if ( string::npos == equals_pos ) // There's no equals sign. This is a token. - { - tokenized_argv.push_back(s); - } - else - { - // Two tokens -- before and after the equals position. - tokenized_argv.push_back ( s.substr(0, equals_pos) ); - tokenized_argv.push_back ( s.substr(equals_pos+1) ); - } - } - - - /* - * Now "filter" the tokenized argv, to get rid of all - * unrecognized options. Because Boost 103200 has no - * facility for dealing gracefully with "unregistered" - * options. - */ - vector<string> filtered_argv; - vector<string>::iterator the_end = tokenized_argv.end(); - - // The program-name gets in for free... - iter = tokenized_argv.begin(); - filtered_argv.push_back ( * iter ); - ++ iter; - - // ...but all other args get checked. - while ( iter < the_end ) - { - /* - * If this is an argument that is registered, - * copy it to filtered_argv and also copy all - * of its arguments. - */ - if ( is_registered_option ( * iter ) ) - { - // Store this recognized arg. - filtered_argv.push_back ( * iter ); - ++ iter; - - // Copy all values for the above arg. - // Args are tokens that do not start with a minus. - while ( (iter < the_end) && ((* iter)[0] != '-') ) - { - filtered_argv.push_back ( * iter ); - ++ iter; - } - } - else - { - // Skip this unrecognized arg. - ++ iter; - - // Copy all values for the above arg. - // Values are tokens that do not start with a minus. - while ( (iter < the_end) && ( '-' != (*iter)[0] ) ) - { - ++ iter; - } - } - } - - // Make an array of temporary C strings, because - // the interface I can use wants it that way. - int new_argc = filtered_argv.size(); - char ** new_argv = new char * [ new_argc ]; - int i = 0; - - // cout << "NEW ARGV: |"; - for ( iter = filtered_argv.begin(); - iter < filtered_argv.end(); - ++ iter, ++ i - ) - { - new_argv[i] = strdup( (* iter).c_str() ); - // cout << " " << new_argv[i] ; - } - // cout << "|\n"; - - - // Use the array of C strings. - po::basic_parsed_options<char> bpo = po::parse_command_line(new_argc, const_cast<char**>(new_argv), *this); - po::store(bpo, vm); - - - // Now free the temporary C strings. - for ( i = 0; i < new_argc; ++ i ) - { - free ( new_argv[i] ); - } - delete[] new_argv; - -#endif } else po::store(po::parse_command_line(argc, const_cast<char**>(argv), *this), vm); @@ -363,107 +196,5 @@ CommonOptions::CommonOptions(const string& name, const string& configfile) } - - -#if ( BOOST_VERSION == 103200 ) -options_description_less_easy_init& -options_description_less_easy_init::operator()(char const * name, - char const * description) -{ - // Snoop on the arguments.... - owner->register_names ( name ); - // ... then call parent function explicitly. - po::options_description_easy_init::operator() ( name, description ); - return * this; -} - - -options_description_less_easy_init& -options_description_less_easy_init::operator()(char const * name, - const po::value_semantic* s) -{ - // Snoop on the arguments.... - owner->register_names ( name ); - // ... then call parent function explicitly. - po::options_description_easy_init::operator() ( name, s ); - return * this; -} - - -options_description_less_easy_init& -options_description_less_easy_init::operator()(const char* name, - const po::value_semantic* s, - const char* description) -{ - // Snoop on the arguments.... - owner->register_names ( name ); - // ... then call parent function explicitly. - po::options_description_easy_init::operator() ( name, s, description ); - return * this; -} - - - - - -void -Options::register_names ( std::string s ) -{ - - std::string::size_type comma_pos = s.find_first_of ( ',' ); - - if ( std::string::npos == comma_pos ) - { - // There is no short-name. - long_names.push_back ( s ); - } - else - { - std::string long_name = s.substr(0, comma_pos), - short_name = s.substr(comma_pos+1); - long_names .push_back ( long_name ); - short_names.push_back ( short_name ); - } - - /* - * There is no way to tell when the adding of new options is finished, - * so I re-sort after each one. - */ - std::sort ( long_names .begin(), long_names .end() ); - std::sort ( short_names.begin(), short_names.end() ); -} - - - - - -bool -Options::is_registered_option ( std::string s ) -{ - std::string without_dashes = s.substr ( s.find_first_not_of ( '-' ) ); - std::vector<std::string>::iterator i; - - // Look among the long names. - i = std::find ( long_names.begin(), - long_names.end(), - without_dashes - ); - if ( i != long_names.end() ) - return true; - - // Look among the short names. - i = std::find ( short_names.begin(), - short_names.end(), - without_dashes - ); - if ( i != short_names.end() ) - return true; - - - return false; -} -#endif - - } // namespace qpid diff --git a/cpp/src/qpid/RefCounted.h b/cpp/src/qpid/RefCounted.h index e7a9bf31c1..f9e0107103 100644 --- a/cpp/src/qpid/RefCounted.h +++ b/cpp/src/qpid/RefCounted.h @@ -53,8 +53,10 @@ protected: // intrusive_ptr support. namespace boost { -inline void intrusive_ptr_add_ref(const qpid::RefCounted* p) { p->addRef(); } -inline void intrusive_ptr_release(const qpid::RefCounted* p) { p->release(); } +template <typename T> +inline void intrusive_ptr_add_ref(const T* p) { p->qpid::RefCounted::addRef(); } +template <typename T> +inline void intrusive_ptr_release(const T* p) { p->qpid::RefCounted::release(); } } diff --git a/cpp/src/qpid/RefCountedBuffer.cpp b/cpp/src/qpid/RefCountedBuffer.cpp index 9b8f1ebd5e..a82e1a02ab 100644 --- a/cpp/src/qpid/RefCountedBuffer.cpp +++ b/cpp/src/qpid/RefCountedBuffer.cpp @@ -20,34 +20,27 @@ */ #include "qpid/RefCountedBuffer.h" +#include <stdlib.h> #include <new> namespace qpid { -RefCountedBuffer::RefCountedBuffer() : count(0) {} - -void RefCountedBuffer::destroy() const { +void RefCountedBuffer::released() const { this->~RefCountedBuffer(); - ::delete[] reinterpret_cast<const char*>(this); -} - -char* RefCountedBuffer::addr() const { - return const_cast<char*>(reinterpret_cast<const char*>(this)+sizeof(RefCountedBuffer)); + ::free (reinterpret_cast<void *>(const_cast<RefCountedBuffer *>(this))); } -RefCountedBuffer::pointer RefCountedBuffer::create(size_t n) { - char* store=::new char[n+sizeof(RefCountedBuffer)]; +BufferRef RefCountedBuffer::create(size_t n) { + void* store=::malloc (n + sizeof(RefCountedBuffer)); + if (NULL == store) + throw std::bad_alloc(); new(store) RefCountedBuffer; - return pointer(reinterpret_cast<RefCountedBuffer*>(store)); + char* start = reinterpret_cast<char *>(store) + sizeof(RefCountedBuffer); + return BufferRef( + boost::intrusive_ptr<RefCounted>(reinterpret_cast<RefCountedBuffer*>(store)), + start, start+n); } -RefCountedBuffer::pointer::pointer() {} -RefCountedBuffer::pointer::pointer(RefCountedBuffer* x) : p(x) {} -RefCountedBuffer::pointer::pointer(const pointer& x) : p(x.p) {} -RefCountedBuffer::pointer::~pointer() {} -RefCountedBuffer::pointer& RefCountedBuffer::pointer::operator=(const RefCountedBuffer::pointer& x) { p = x.p; return *this; } - -char* RefCountedBuffer::pointer::cp() const { return p ? p->get() : 0; } } // namespace qpid diff --git a/cpp/src/qpid/RefCountedBuffer.h b/cpp/src/qpid/RefCountedBuffer.h index 75a23862be..f0ea86130b 100644 --- a/cpp/src/qpid/RefCountedBuffer.h +++ b/cpp/src/qpid/RefCountedBuffer.h @@ -22,68 +22,23 @@ * */ -#include <boost/utility.hpp> -#include <boost/detail/atomic_count.hpp> -#include <boost/intrusive_ptr.hpp> +#include <qpid/RefCounted.h> +#include <qpid/BufferRef.h> namespace qpid { /** - * Reference-counted byte buffer. - * No alignment guarantees. + * Reference-counted byte buffer. No alignment guarantees. */ -class RefCountedBuffer : boost::noncopyable { - mutable boost::detail::atomic_count count; - RefCountedBuffer(); - void destroy() const; - char* addr() const; - -public: - /** Smart char pointer to a reference counted buffer */ - class pointer { - boost::intrusive_ptr<RefCountedBuffer> p; - char* cp() const; - pointer(RefCountedBuffer* x); - friend class RefCountedBuffer; - - public: - pointer(); - pointer(const pointer&); - ~pointer(); - pointer& operator=(const pointer&); - - char* get() { return cp(); } - operator char*() { return cp(); } - char& operator*() { return *cp(); } - char& operator[](size_t i) { return cp()[i]; } - - const char* get() const { return cp(); } - operator const char*() const { return cp(); } - const char& operator*() const { return *cp(); } - const char& operator[](size_t i) const { return cp()[i]; } - }; - +class RefCountedBuffer : public RefCounted { + public: /** Create a reference counted buffer of size n */ - static pointer create(size_t n); - - /** Get a pointer to the start of the buffer. */ - char* get() { return addr(); } - const char* get() const { return addr(); } - char& operator[](size_t i) { return get()[i]; } - const char& operator[](size_t i) const { return get()[i]; } + static BufferRef create(size_t n); - void addRef() const { ++count; } - void release() const { if (--count==0) destroy(); } - long refCount() { return count; } + protected: + void released() const; }; } // namespace qpid -// intrusive_ptr support. -namespace boost { -inline void intrusive_ptr_add_ref(const qpid::RefCountedBuffer* p) { p->addRef(); } -inline void intrusive_ptr_release(const qpid::RefCountedBuffer* p) { p->release(); } -} - - #endif /*!QPID_REFCOUNTEDBUFFER_H*/ diff --git a/cpp/src/qpid/Sasl.h b/cpp/src/qpid/Sasl.h index 9a9d61b037..4d579fa051 100644 --- a/cpp/src/qpid/Sasl.h +++ b/cpp/src/qpid/Sasl.h @@ -47,8 +47,8 @@ class Sasl * client supports. * @param externalSecuritySettings security related details from the underlying transport */ - virtual std::string start(const std::string& mechanisms, - const qpid::sys::SecuritySettings* externalSecuritySettings = 0) = 0; + virtual bool start(const std::string& mechanisms, std::string& response, + const qpid::sys::SecuritySettings* externalSecuritySettings = 0) = 0; virtual std::string step(const std::string& challenge) = 0; virtual std::string getMechanism() = 0; virtual std::string getUserId() = 0; diff --git a/cpp/src/qpid/SaslFactory.cpp b/cpp/src/qpid/SaslFactory.cpp index 055883abee..a8d1f94c1e 100644 --- a/cpp/src/qpid/SaslFactory.cpp +++ b/cpp/src/qpid/SaslFactory.cpp @@ -112,7 +112,7 @@ class CyrusSasl : public Sasl public: CyrusSasl(const std::string & username, const std::string & password, const std::string & serviceName, const std::string & hostName, int minSsf, int maxSsf, bool allowInteraction); ~CyrusSasl(); - std::string start(const std::string& mechanisms, const SecuritySettings* externalSettings); + bool start(const std::string& mechanisms, std::string& response, const SecuritySettings* externalSettings); std::string step(const std::string& challenge); std::string getMechanism(); std::string getUserId(); @@ -182,17 +182,18 @@ CyrusSasl::CyrusSasl(const std::string & username, const std::string & password, callbacks[i].id = SASL_CB_AUTHNAME; callbacks[i].proc = (CallbackProc*) &getUserFromSettings; callbacks[i++].context = &settings; - } - callbacks[i].id = SASL_CB_PASS; - if (settings.password.empty()) { - callbacks[i].proc = 0; - callbacks[i++].context = 0; - } else { - callbacks[i].proc = (CallbackProc*) &getPasswordFromSettings; - callbacks[i++].context = &settings; + callbacks[i].id = SASL_CB_PASS; + if (settings.password.empty()) { + callbacks[i].proc = 0; + callbacks[i++].context = 0; + } else { + callbacks[i].proc = (CallbackProc*) &getPasswordFromSettings; + callbacks[i++].context = &settings; + } } + callbacks[i].id = SASL_CB_LIST_END; callbacks[i].proc = 0; callbacks[i++].context = 0; @@ -209,7 +210,7 @@ namespace { const std::string SSL("ssl"); } -std::string CyrusSasl::start(const std::string& mechanisms, const SecuritySettings* externalSettings) +bool CyrusSasl::start(const std::string& mechanisms, std::string& response, const SecuritySettings* externalSettings) { QPID_LOG(debug, "CyrusSasl::start(" << mechanisms << ")"); int result = sasl_client_new(settings.service.c_str(), @@ -282,7 +283,12 @@ std::string CyrusSasl::start(const std::string& mechanisms, const SecuritySettin mechanism = std::string(chosenMechanism); QPID_LOG(debug, "CyrusSasl::start(" << mechanisms << "): selected " << mechanism << " response: '" << std::string(out, outlen) << "'"); - return std::string(out, outlen); + if (out) { + response = std::string(out, outlen); + return true; + } else { + return false; + } } std::string CyrusSasl::step(const std::string& challenge) diff --git a/cpp/src/qpid/Url.cpp b/cpp/src/qpid/Url.cpp index ab796f4642..f699b60c17 100644 --- a/cpp/src/qpid/Url.cpp +++ b/cpp/src/qpid/Url.cpp @@ -156,11 +156,12 @@ class UrlParser { return false; } - // TODO aconway 2008-11-20: this does not fully implement - // http://www.ietf.org/rfc/rfc3986.txt. Works for DNS names and - // ipv4 literals but won't handle ipv6. + // A liberal interpretation of http://www.ietf.org/rfc/rfc3986.txt. + // Works for DNS names and and ipv4 and ipv6 literals // bool host(string& h) { + if (ip6literal(h)) return true; + const char* start=i; while (unreserved() || pctEncoded()) ; @@ -169,6 +170,22 @@ class UrlParser { return true; } + // This is a bit too liberal for IPv6 literal addresses, but probably good enough + bool ip6literal(string& h) { + if (literal("[")) { + const char* start = i; + while (hexDigit() || literal(":") || literal(".")) + ; + const char* end = i; + if ( end-start < 2 ) return false; // Smallest valid address is "::" + if (literal("]")) { + h.assign(start, end); + return true; + } + } + return false; + } + bool unreserved() { return (::isalnum(*i) || ::strchr("-._~", *i)) && advance(); } bool pctEncoded() { return literal("%") && hexDigit() && hexDigit(); } diff --git a/cpp/src/qpid/acl/Acl.cpp b/cpp/src/qpid/acl/Acl.cpp index 67603015d7..4b3dda7962 100644 --- a/cpp/src/qpid/acl/Acl.cpp +++ b/cpp/src/qpid/acl/Acl.cpp @@ -180,7 +180,10 @@ Acl::Acl (AclValues& av, Broker& b): aclValues(av), broker(&b), transferAcl(fals { case _qmf::Acl::METHOD_RELOADACLFILE : readAclFile(text); - status = Manageable::STATUS_USER; + if (text.empty()) + status = Manageable::STATUS_OK; + else + status = Manageable::STATUS_USER; break; } diff --git a/cpp/src/qpid/acl/AclPlugin.cpp b/cpp/src/qpid/acl/AclPlugin.cpp index e4d721ea44..d611797c49 100644 --- a/cpp/src/qpid/acl/AclPlugin.cpp +++ b/cpp/src/qpid/acl/AclPlugin.cpp @@ -69,7 +69,7 @@ struct AclPlugin : public Plugin { } acl = new Acl(values, b); - b.setAcl(acl.get()); + b.setAcl(acl.get()); b.addFinalizer(boost::bind(&AclPlugin::shutdown, this)); } diff --git a/cpp/src/qpid/agent/ManagementAgentImpl.cpp b/cpp/src/qpid/agent/ManagementAgentImpl.cpp index 593d403a11..f183ff8e0c 100644 --- a/cpp/src/qpid/agent/ManagementAgentImpl.cpp +++ b/cpp/src/qpid/agent/ManagementAgentImpl.cpp @@ -305,43 +305,47 @@ void ManagementAgentImpl::raiseEvent(const ManagementEvent& event, severity_t se "emerg", "alert", "crit", "error", "warn", "note", "info", "debug" }; - sys::Mutex::ScopedLock lock(agentLock); - Buffer outBuffer(eventBuffer, MA_BUFFER_SIZE); - uint8_t sev = (severity == SEV_DEFAULT) ? event.getSeverity() : (uint8_t) severity; + string content; stringstream key; - - // key << "console.event." << assignedBrokerBank << "." << assignedAgentBank << "." << - // event.getPackageName() << "." << event.getEventName(); - key << "agent.ind.event." << keyifyNameStr(event.getPackageName()) - << "." << keyifyNameStr(event.getEventName()) - << "." << severityStr[sev] - << "." << vendorNameKey - << "." << productNameKey - << "." << instanceNameKey; - - Variant::Map map_; - Variant::Map schemaId; - Variant::Map values; Variant::Map headers; - string content; - map_["_schema_id"] = mapEncodeSchemaId(event.getPackageName(), - event.getEventName(), - event.getMd5Sum(), - ManagementItem::CLASS_KIND_EVENT); - event.mapEncode(values); - map_["_values"] = values; - map_["_timestamp"] = uint64_t(Duration(EPOCH, now())); - map_["_severity"] = sev; + { + sys::Mutex::ScopedLock lock(agentLock); + Buffer outBuffer(eventBuffer, MA_BUFFER_SIZE); + uint8_t sev = (severity == SEV_DEFAULT) ? event.getSeverity() : (uint8_t) severity; + + // key << "console.event." << assignedBrokerBank << "." << assignedAgentBank << "." << + // event.getPackageName() << "." << event.getEventName(); + key << "agent.ind.event." << keyifyNameStr(event.getPackageName()) + << "." << keyifyNameStr(event.getEventName()) + << "." << severityStr[sev] + << "." << vendorNameKey + << "." << productNameKey + << "." << instanceNameKey; + + Variant::Map map_; + Variant::Map schemaId; + Variant::Map values; + + map_["_schema_id"] = mapEncodeSchemaId(event.getPackageName(), + event.getEventName(), + event.getMd5Sum(), + ManagementItem::CLASS_KIND_EVENT); + event.mapEncode(values); + map_["_values"] = values; + map_["_timestamp"] = uint64_t(Duration(EPOCH, now())); + map_["_severity"] = sev; - headers["method"] = "indication"; - headers["qmf.opcode"] = "_data_indication"; - headers["qmf.content"] = "_event"; - headers["qmf.agent"] = name_address; + headers["method"] = "indication"; + headers["qmf.opcode"] = "_data_indication"; + headers["qmf.content"] = "_event"; + headers["qmf.agent"] = name_address; + + Variant::List list; + list.push_back(map_); + ListCodec::encode(list, content); + } - Variant::List list; - list.push_back(map_); - ListCodec::encode(list, content); connThreadBody.sendBuffer(content, "", headers, topicExchange, key.str(), "amqp/list"); } @@ -521,9 +525,12 @@ void ManagementAgentImpl::sendException(const string& rte, const string& rtk, co void ManagementAgentImpl::handleSchemaRequest(Buffer& inBuffer, uint32_t sequence, const string& rte, const string& rtk) { - sys::Mutex::ScopedLock lock(agentLock); string packageName; SchemaClassKey key; + uint32_t outLen(0); + char localBuffer[MA_BUFFER_SIZE]; + Buffer outBuffer(localBuffer, MA_BUFFER_SIZE); + bool found(false); inBuffer.getShortString(packageName); inBuffer.getShortString(key.name); @@ -531,26 +538,30 @@ void ManagementAgentImpl::handleSchemaRequest(Buffer& inBuffer, uint32_t sequenc QPID_LOG(trace, "RCVD SchemaRequest: package=" << packageName << " class=" << key.name); - PackageMap::iterator pIter = packages.find(packageName); - if (pIter != packages.end()) { - ClassMap& cMap = pIter->second; - ClassMap::iterator cIter = cMap.find(key); - if (cIter != cMap.end()) { - SchemaClass& schema = cIter->second; - Buffer outBuffer(outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; - string body; - - encodeHeader(outBuffer, 's', sequence); - schema.writeSchemaCall(body); - outBuffer.putRawData(body); - outLen = MA_BUFFER_SIZE - outBuffer.available(); - outBuffer.reset(); - connThreadBody.sendBuffer(outBuffer, outLen, rte, rtk); - - QPID_LOG(trace, "SENT SchemaInd: package=" << packageName << " class=" << key.name); + { + sys::Mutex::ScopedLock lock(agentLock); + PackageMap::iterator pIter = packages.find(packageName); + if (pIter != packages.end()) { + ClassMap& cMap = pIter->second; + ClassMap::iterator cIter = cMap.find(key); + if (cIter != cMap.end()) { + SchemaClass& schema = cIter->second; + string body; + + encodeHeader(outBuffer, 's', sequence); + schema.writeSchemaCall(body); + outBuffer.putRawData(body); + outLen = MA_BUFFER_SIZE - outBuffer.available(); + outBuffer.reset(); + found = true; + } } } + + if (found) { + connThreadBody.sendBuffer(outBuffer, outLen, rte, rtk); + QPID_LOG(trace, "SENT SchemaInd: package=" << packageName << " class=" << key.name); + } } void ManagementAgentImpl::handleConsoleAddedIndication() @@ -969,18 +980,6 @@ ManagementAgentImpl::PackageMap::iterator ManagementAgentImpl::findOrAddPackage( pair<PackageMap::iterator, bool> result = packages.insert(pair<string, ClassMap>(name, ClassMap())); - if (connected) { - // Publish a package-indication message - Buffer outBuffer(outputBuffer, MA_BUFFER_SIZE); - uint32_t outLen; - - encodeHeader(outBuffer, 'p'); - encodePackageIndication(outBuffer, result.first); - outLen = MA_BUFFER_SIZE - outBuffer.available(); - outBuffer.reset(); - connThreadBody.sendBuffer(outBuffer, outLen, "qpid.management", "schema.package"); - } - return result.first; } @@ -1038,131 +1037,146 @@ void ManagementAgentImpl::encodeClassIndication(Buffer& buf, QPID_LOG(trace, "SENT ClassInd: package=" << (*pIter).first << " class=" << key.name); } +struct MessageItem { + string content; + Variant::Map headers; + string key; + MessageItem(const Variant::Map& h, const string& k) : headers(h), key(k) {} +}; + void ManagementAgentImpl::periodicProcessing() { string addr_key_base = "agent.ind.data."; - sys::Mutex::ScopedLock lock(agentLock); list<ObjectId> deleteList; - - if (!connected) - return; + list<boost::shared_ptr<MessageItem> > message_list; sendHeartbeat(); - moveNewObjectsLH(); - - // - // Clear the been-here flag on all objects in the map. - // - for (ObjectMap::iterator iter = managementObjects.begin(); - iter != managementObjects.end(); - iter++) { - ManagementObject* object = iter->second.get(); - object->setFlags(0); - if (publishAllData) { - object->setForcePublish(true); - } - } - - publishAllData = false; + { + sys::Mutex::ScopedLock lock(agentLock); - // - // Process the entire object map. - // - uint32_t v2Objs = 0; + if (!connected) + return; - for (ObjectMap::iterator baseIter = managementObjects.begin(); - baseIter != managementObjects.end(); - baseIter++) { - ManagementObject* baseObject = baseIter->second.get(); + moveNewObjectsLH(); // - // Skip until we find a base object requiring a sent message. + // Clear the been-here flag on all objects in the map. // - if (baseObject->getFlags() == 1 || - (!baseObject->getConfigChanged() && - !baseObject->getInstChanged() && - !baseObject->getForcePublish() && - !baseObject->isDeleted())) - continue; - - std::string packageName = baseObject->getPackageName(); - std::string className = baseObject->getClassName(); - - Variant::List list_; - string content; - std::stringstream addr_key; - Variant::Map headers; - - addr_key << addr_key_base; - addr_key << keyifyNameStr(packageName) - << "." << keyifyNameStr(className) - << "." << vendorNameKey - << "." << productNameKey - << "." << instanceNameKey; - - headers["method"] = "indication"; - headers["qmf.opcode"] = "_data_indication"; - headers["qmf.content"] = "_data"; - headers["qmf.agent"] = name_address; - - for (ObjectMap::iterator iter = baseIter; + for (ObjectMap::iterator iter = managementObjects.begin(); iter != managementObjects.end(); iter++) { ManagementObject* object = iter->second.get(); - bool send_stats, send_props; - if (baseObject->isSameClass(*object) && object->getFlags() == 0) { - object->setFlags(1); - if (object->getConfigChanged() || object->getInstChanged()) - object->setUpdateTime(); + object->setFlags(0); + if (publishAllData) { + object->setForcePublish(true); + } + } + + publishAllData = false; + + // + // Process the entire object map. + // + uint32_t v2Objs = 0; + + for (ObjectMap::iterator baseIter = managementObjects.begin(); + baseIter != managementObjects.end(); + baseIter++) { + ManagementObject* baseObject = baseIter->second.get(); + + // + // Skip until we find a base object requiring a sent message. + // + if (baseObject->getFlags() == 1 || + (!baseObject->getConfigChanged() && + !baseObject->getInstChanged() && + !baseObject->getForcePublish() && + !baseObject->isDeleted())) + continue; + + std::string packageName = baseObject->getPackageName(); + std::string className = baseObject->getClassName(); + + Variant::List list_; + std::stringstream addr_key; + Variant::Map headers; + + addr_key << addr_key_base; + addr_key << keyifyNameStr(packageName) + << "." << keyifyNameStr(className) + << "." << vendorNameKey + << "." << productNameKey + << "." << instanceNameKey; + + headers["method"] = "indication"; + headers["qmf.opcode"] = "_data_indication"; + headers["qmf.content"] = "_data"; + headers["qmf.agent"] = name_address; + + for (ObjectMap::iterator iter = baseIter; + iter != managementObjects.end(); + iter++) { + ManagementObject* object = iter->second.get(); + bool send_stats, send_props; + if (baseObject->isSameClass(*object) && object->getFlags() == 0) { + object->setFlags(1); + if (object->getConfigChanged() || object->getInstChanged()) + object->setUpdateTime(); + + send_props = (object->getConfigChanged() || object->getForcePublish() || object->isDeleted()); + send_stats = (object->hasInst() && (object->getInstChanged() || object->getForcePublish())); + + if (send_stats || send_props) { + Variant::Map map_; + Variant::Map values; + Variant::Map oid; + + object->getObjectId().mapEncode(oid); + map_["_object_id"] = oid; + map_["_schema_id"] = mapEncodeSchemaId(object->getPackageName(), + object->getClassName(), + object->getMd5Sum()); + object->writeTimestamps(map_); + object->mapEncodeValues(values, send_props, send_stats); + map_["_values"] = values; + list_.push_back(map_); - send_props = (object->getConfigChanged() || object->getForcePublish() || object->isDeleted()); - send_stats = (object->hasInst() && (object->getInstChanged() || object->getForcePublish())); - - if (send_stats || send_props) { - Variant::Map map_; - Variant::Map values; - Variant::Map oid; - - object->getObjectId().mapEncode(oid); - map_["_object_id"] = oid; - map_["_schema_id"] = mapEncodeSchemaId(object->getPackageName(), - object->getClassName(), - object->getMd5Sum()); - object->writeTimestamps(map_); - object->mapEncodeValues(values, send_props, send_stats); - map_["_values"] = values; - list_.push_back(map_); - - if (++v2Objs >= maxV2ReplyObjs) { - v2Objs = 0; - ListCodec::encode(list_, content); - - connThreadBody.sendBuffer(content, "", headers, topicExchange, addr_key.str(), "amqp/list"); - list_.clear(); - content.clear(); - QPID_LOG(trace, "SENT DataIndication"); + if (++v2Objs >= maxV2ReplyObjs) { + v2Objs = 0; + boost::shared_ptr<MessageItem> item(new MessageItem(headers, addr_key.str())); + ListCodec::encode(list_, item->content); + message_list.push_back(item); + list_.clear(); + } } + + if (object->isDeleted()) + deleteList.push_back(iter->first); + object->setForcePublish(false); } + } - if (object->isDeleted()) - deleteList.push_back(iter->first); - object->setForcePublish(false); + if (!list_.empty()) { + boost::shared_ptr<MessageItem> item(new MessageItem(headers, addr_key.str())); + ListCodec::encode(list_, item->content); + message_list.push_back(item); } } - if (!list_.empty()) { - ListCodec::encode(list_, content); - connThreadBody.sendBuffer(content, "", headers, topicExchange, addr_key.str(), "amqp/list"); - QPID_LOG(trace, "SENT DataIndication"); - } + // Delete flagged objects + for (list<ObjectId>::reverse_iterator iter = deleteList.rbegin(); + iter != deleteList.rend(); + iter++) + managementObjects.erase(*iter); } - // Delete flagged objects - for (list<ObjectId>::reverse_iterator iter = deleteList.rbegin(); - iter != deleteList.rend(); - iter++) - managementObjects.erase(*iter); + while (!message_list.empty()) { + boost::shared_ptr<MessageItem> item(message_list.front()); + message_list.pop_front(); + connThreadBody.sendBuffer(item->content, "", item->headers, topicExchange, item->key, "amqp/list"); + QPID_LOG(trace, "SENT DataIndication"); + } } @@ -1364,13 +1378,26 @@ bool ManagementAgentImpl::ConnectionThread::isSleeping() const void ManagementAgentImpl::PublishThread::run() { - uint16_t totalSleep; + uint16_t totalSleep; + uint16_t sleepTime; while (!shutdown) { agent.periodicProcessing(); totalSleep = 0; - while (totalSleep++ < agent.getInterval() && !shutdown) { - ::sleep(1); + + // + // Calculate a sleep time that is no greater than 5 seconds and + // no less than 1 second. + // + sleepTime = agent.getInterval(); + if (sleepTime > 5) + sleepTime = 5; + else if (sleepTime == 0) + sleepTime = 1; + + while (totalSleep < agent.getInterval() && !shutdown) { + ::sleep(sleepTime); + totalSleep += sleepTime; } } } diff --git a/cpp/src/qpid/agent/ManagementAgentImpl.h b/cpp/src/qpid/agent/ManagementAgentImpl.h index bf340777d1..53f3c13a91 100644 --- a/cpp/src/qpid/agent/ManagementAgentImpl.h +++ b/cpp/src/qpid/agent/ManagementAgentImpl.h @@ -62,8 +62,8 @@ class ManagementAgentImpl : public ManagementAgent, public client::MessageListen uint16_t intervalSeconds = 10, bool useExternalThread = false, const std::string& storeFile = "", - const std::string& uid = "guest", - const std::string& pwd = "guest", + const std::string& uid = "", + const std::string& pwd = "", const std::string& mech = "PLAIN", const std::string& proto = "tcp"); void init(const management::ConnectionSettings& settings, diff --git a/cpp/src/qpid/amqp_0_10/Codecs.cpp b/cpp/src/qpid/amqp_0_10/Codecs.cpp index 0fbe2a60b9..b976a5d09b 100644 --- a/cpp/src/qpid/amqp_0_10/Codecs.cpp +++ b/cpp/src/qpid/amqp_0_10/Codecs.cpp @@ -127,10 +127,10 @@ Variant toVariant(boost::shared_ptr<FieldValue> in) switch (in->getType()) { //Fixed Width types: case 0x01: out.setEncoding(amqp0_10_binary); - case 0x02: out = in->getIntegerValue<int8_t, 1>(); break; - case 0x03: out = in->getIntegerValue<uint8_t, 1>(); break; + case 0x02: out = in->getIntegerValue<int8_t>(); break; + case 0x03: out = in->getIntegerValue<uint8_t>(); break; case 0x04: break; //TODO: iso-8859-15 char - case 0x08: out = static_cast<bool>(in->getIntegerValue<uint8_t, 1>()); break; + case 0x08: out = static_cast<bool>(in->getIntegerValue<uint8_t>()); break; case 0x10: out.setEncoding(amqp0_10_binary); case 0x11: out = in->getIntegerValue<int16_t, 2>(); break; case 0x12: out = in->getIntegerValue<uint16_t, 2>(); break; diff --git a/cpp/src/qpid/amqp_0_10/SessionHandler.cpp b/cpp/src/qpid/amqp_0_10/SessionHandler.cpp index b113d49a73..578598a146 100644 --- a/cpp/src/qpid/amqp_0_10/SessionHandler.cpp +++ b/cpp/src/qpid/amqp_0_10/SessionHandler.cpp @@ -130,9 +130,6 @@ void SessionHandler::handleException(const qpid::SessionException& e) } namespace { -bool isControl(const AMQFrame& f) { - return f.getMethod() && f.getMethod()->type() == framing::SEGMENT_TYPE_CONTROL; -} bool isCommand(const AMQFrame& f) { return f.getMethod() && f.getMethod()->type() == framing::SEGMENT_TYPE_COMMAND; } @@ -191,9 +188,10 @@ void SessionHandler::detach(const std::string& name) { void SessionHandler::detached(const std::string& name, uint8_t code) { CHECK_NAME(name, "session.detached"); awaitingDetached = false; - if (code != session::DETACH_CODE_NORMAL) + if (code != session::DETACH_CODE_NORMAL) { + sendReady = receiveReady = false; channelException(convert(code), "session.detached from peer."); - else { + } else { handleDetach(); } } diff --git a/cpp/src/qpid/amqp_0_10/SessionHandler.h b/cpp/src/qpid/amqp_0_10/SessionHandler.h index a87a1a155f..8b072fa05c 100644 --- a/cpp/src/qpid/amqp_0_10/SessionHandler.h +++ b/cpp/src/qpid/amqp_0_10/SessionHandler.h @@ -41,8 +41,8 @@ namespace amqp_0_10 { * to a session state. */ -class SessionHandler : public framing::AMQP_AllOperations::SessionHandler, - public framing::FrameHandler::InOutHandler +class QPID_COMMON_CLASS_EXTERN SessionHandler : public framing::AMQP_AllOperations::SessionHandler, + public framing::FrameHandler::InOutHandler { public: QPID_COMMON_EXTERN SessionHandler(framing::FrameHandler* out=0, uint16_t channel=0); @@ -66,7 +66,7 @@ class SessionHandler : public framing::AMQP_AllOperations::SessionHandler, QPID_COMMON_EXTERN void handleException(const qpid::SessionException& e); /** True if the handler is ready to send and receive */ - bool ready() const; + QPID_COMMON_EXTERN bool ready() const; // Protocol methods QPID_COMMON_EXTERN void attach(const std::string& name, bool force); diff --git a/cpp/src/qpid/broker/AsyncCompletion.h b/cpp/src/qpid/broker/AsyncCompletion.h new file mode 100644 index 0000000000..fef994438f --- /dev/null +++ b/cpp/src/qpid/broker/AsyncCompletion.h @@ -0,0 +1,201 @@ +#ifndef _AsyncCompletion_ +#define _AsyncCompletion_ + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <boost/intrusive_ptr.hpp> + +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/sys/AtomicValue.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Monitor.h" + +namespace qpid { +namespace broker { + +/** + * Class to implement asynchronous notification of completion. + * + * Use-case: An "initiator" needs to wait for a set of "completers" to + * finish a unit of work before an action can occur. This object + * tracks the progress of the set of completers, and allows the action + * to occur once all completers have signalled that they are done. + * + * The initiator and completers may be running in separate threads. + * + * The initiating thread is the thread that initiates the action, + * i.e. the connection read thread. + * + * A completing thread is any thread that contributes to completion, + * e.g. a store thread that does an async write. + * There may be zero or more completers. + * + * When the work is complete, a callback is invoked. The callback + * may be invoked in the Initiator thread, or one of the Completer + * threads. The callback is passed a flag indicating whether or not + * the callback is running under the context of the Initiator thread. + * + * Use model: + * 1) Initiator thread invokes begin() + * 2) After begin() has been invoked, zero or more Completers invoke + * startCompleter(). Completers may be running in the same or + * different thread as the Initiator, as long as they guarantee that + * startCompleter() is invoked at least once before the Initiator invokes end(). + * 3) Completers may invoke finishCompleter() at any time, even after the + * initiator has invoked end(). finishCompleter() may be called from any + * thread. + * 4) startCompleter()/finishCompleter() calls "nest": for each call to + * startCompleter(), a corresponding call to finishCompleter() must be made. + * Once the last finishCompleter() is called, the Completer must no longer + * reference the completion object. + * 5) The Initiator invokes end() at the point where it has finished + * dispatching work to the Completers, and is prepared for the callback + * handler to be invoked. Note: if there are no outstanding Completers + * pending when the Initiator invokes end(), the callback will be invoked + * directly, and the sync parameter will be set true. This indicates to the + * Initiator that the callback is executing in the context of the end() call, + * and the Initiator is free to optimize the handling of the completion, + * assuming no need for synchronization with Completer threads. + */ + +class AsyncCompletion +{ + public: + + /** Supplied by the Initiator to the end() method, allows for a callback + * when all outstanding completers are done. If the callback cannot be + * made during the end() call, the clone() method must supply a copy of + * this callback object that persists after end() returns. The cloned + * callback object will be used by the last completer thread, and + * released when the callback returns. + */ + class Callback : public RefCounted + { + public: + virtual void completed(bool) = 0; + virtual boost::intrusive_ptr<Callback> clone() = 0; + }; + + private: + mutable qpid::sys::AtomicValue<uint32_t> completionsNeeded; + mutable qpid::sys::Monitor callbackLock; + bool inCallback, active; + + void invokeCallback(bool sync) { + qpid::sys::Mutex::ScopedLock l(callbackLock); + if (active) { + if (callback.get()) { + inCallback = true; + { + qpid::sys::Mutex::ScopedUnlock ul(callbackLock); + callback->completed(sync); + } + inCallback = false; + callback = boost::intrusive_ptr<Callback>(); + callbackLock.notifyAll(); + } + active = false; + } + } + + protected: + /** Invoked when all completers have signalled that they have completed + * (via calls to finishCompleter()). bool == true if called via end() + */ + boost::intrusive_ptr<Callback> callback; + + public: + AsyncCompletion() : completionsNeeded(0), inCallback(false), active(true) {}; + virtual ~AsyncCompletion() { cancel(); } + + + /** True when all outstanding operations have compeleted + */ + bool isDone() + { + return !active; + } + + /** Called to signal the start of an asynchronous operation. The operation + * is considered pending until finishCompleter() is called. + * E.g. called when initiating an async store operation. + */ + void startCompleter() { ++completionsNeeded; } + + /** Called by completer to signal that it has finished the operation started + * when startCompleter() was invoked. + * e.g. called when async write complete. + */ + void finishCompleter() + { + if (--completionsNeeded == 0) { + invokeCallback(false); + } + } + + /** called by initiator before any calls to startCompleter can be done. + */ + void begin() + { + ++completionsNeeded; + } + + /** called by initiator after all potential completers have called + * startCompleter(). + */ + void end(Callback& cb) + { + assert(completionsNeeded.get() > 0); // ensure begin() has been called! + // the following only "decrements" the count if it is 1. This means + // there are no more outstanding completers and we are done. + if (completionsNeeded.boolCompareAndSwap(1, 0)) { + // done! Complete immediately + cb.completed(true); + return; + } + + // the compare-and-swap did not succeed. This means there are + // outstanding completers pending (count > 1). Get a persistent + // Callback object to use when the last completer is done. + // Decrement after setting up the callback ensures that pending + // completers cannot touch the callback until it is ready. + callback = cb.clone(); + if (--completionsNeeded == 0) { + // note that a completer may have completed during the + // callback setup or decrement: + invokeCallback(true); + } + } + + /** may be called by Initiator to cancel the callback. Will wait for + * callback to complete if in progress. + */ + virtual void cancel() { + qpid::sys::Mutex::ScopedLock l(callbackLock); + while (inCallback) callbackLock.wait(); + callback = boost::intrusive_ptr<Callback>(); + active = false; + } +}; + +}} // qpid::broker:: +#endif /*!_AsyncCompletion_*/ diff --git a/cpp/src/qpid/broker/Bridge.cpp b/cpp/src/qpid/broker/Bridge.cpp index 7fbbf4e2c4..c709606c17 100644 --- a/cpp/src/qpid/broker/Bridge.cpp +++ b/cpp/src/qpid/broker/Bridge.cpp @@ -164,6 +164,12 @@ void Bridge::destroy() listener(this); } +bool Bridge::isSessionReady() const +{ + SessionHandler& sessionHandler = conn->getChannel(id); + return sessionHandler.ready(); +} + void Bridge::setPersistenceId(uint64_t pId) const { persistenceId = pId; diff --git a/cpp/src/qpid/broker/Bridge.h b/cpp/src/qpid/broker/Bridge.h index a846254c57..8b4559a871 100644 --- a/cpp/src/qpid/broker/Bridge.h +++ b/cpp/src/qpid/broker/Bridge.h @@ -59,6 +59,8 @@ public: void destroy(); bool isDurable() { return args.i_durable; } + bool isSessionReady() const; + management::ManagementObject* GetManagementObject() const; management::Manageable::status_t ManagementMethod(uint32_t methodId, management::Args& args, diff --git a/cpp/src/qpid/broker/Broker.cpp b/cpp/src/qpid/broker/Broker.cpp index 3c692fc368..ec3cf9d340 100644 --- a/cpp/src/qpid/broker/Broker.cpp +++ b/cpp/src/qpid/broker/Broker.cpp @@ -20,6 +20,7 @@ */ #include "qpid/broker/Broker.h" +#include "qpid/broker/ConnectionState.h" #include "qpid/broker/DirectExchange.h" #include "qpid/broker/FanOutExchange.h" #include "qpid/broker/HeadersExchange.h" @@ -31,12 +32,26 @@ #include "qpid/broker/TopicExchange.h" #include "qpid/broker/Link.h" #include "qpid/broker/ExpiryPolicy.h" +#include "qpid/broker/QueueFlowLimit.h" +#include "qpid/broker/MessageGroupManager.h" #include "qmf/org/apache/qpid/broker/Package.h" +#include "qmf/org/apache/qpid/broker/ArgsBrokerCreate.h" +#include "qmf/org/apache/qpid/broker/ArgsBrokerDelete.h" +#include "qmf/org/apache/qpid/broker/ArgsBrokerQuery.h" #include "qmf/org/apache/qpid/broker/ArgsBrokerEcho.h" #include "qmf/org/apache/qpid/broker/ArgsBrokerGetLogLevel.h" #include "qmf/org/apache/qpid/broker/ArgsBrokerQueueMoveMessages.h" #include "qmf/org/apache/qpid/broker/ArgsBrokerSetLogLevel.h" +#include "qmf/org/apache/qpid/broker/ArgsBrokerSetTimestampConfig.h" +#include "qmf/org/apache/qpid/broker/ArgsBrokerGetTimestampConfig.h" +#include "qmf/org/apache/qpid/broker/EventExchangeDeclare.h" +#include "qmf/org/apache/qpid/broker/EventExchangeDelete.h" +#include "qmf/org/apache/qpid/broker/EventQueueDeclare.h" +#include "qmf/org/apache/qpid/broker/EventQueueDelete.h" +#include "qmf/org/apache/qpid/broker/EventBind.h" +#include "qmf/org/apache/qpid/broker/EventUnbind.h" +#include "qpid/amqp_0_10/Codecs.h" #include "qpid/management/ManagementDirectExchange.h" #include "qpid/management/ManagementTopicExchange.h" #include "qpid/log/Logger.h" @@ -44,7 +59,9 @@ #include "qpid/log/Statement.h" #include "qpid/log/posix/SinkOptions.h" #include "qpid/framing/AMQFrame.h" +#include "qpid/framing/FieldTable.h" #include "qpid/framing/ProtocolInitiation.h" +#include "qpid/framing/reply_exceptions.h" #include "qpid/framing/Uuid.h" #include "qpid/sys/ProtocolFactory.h" #include "qpid/sys/Poller.h" @@ -76,7 +93,10 @@ using qpid::management::ManagementAgent; using qpid::management::ManagementObject; using qpid::management::Manageable; using qpid::management::Args; +using qpid::management::getManagementExecutionContext; +using qpid::types::Variant; using std::string; +using std::make_pair; namespace _qmf = qmf::org::apache::qpid::broker; @@ -103,7 +123,12 @@ Broker::Options::Options(const std::string& name) : maxSessionRate(0), asyncQueueEvents(false), // Must be false in a cluster. qmf2Support(true), - qmf1Support(true) + qmf1Support(true), + queueFlowStopRatio(80), + queueFlowResumeRatio(70), + queueThresholdEventRatio(80), + defaultMsgGroup("qpid.no-group"), + timestampRcvMsgs(false) // set the 0.10 timestamp delivery property { int c = sys::SystemInfo::concurrency(); workerThreads=c+1; @@ -134,9 +159,14 @@ Broker::Options::Options(const std::string& name) : ("tcp-nodelay", optValue(tcpNoDelay), "Set TCP_NODELAY on TCP connections") ("require-encryption", optValue(requireEncrypted), "Only accept connections that are encrypted") ("known-hosts-url", optValue(knownHosts, "URL or 'none'"), "URL to send as 'known-hosts' to clients ('none' implies empty list)") - ("sasl-config", optValue(saslConfigPath, "FILE"), "gets sasl config from nonstandard location") + ("sasl-config", optValue(saslConfigPath, "DIR"), "gets sasl config info from nonstandard location") ("max-session-rate", optValue(maxSessionRate, "MESSAGES/S"), "Sets the maximum message rate per session (0=unlimited)") - ("async-queue-events", optValue(asyncQueueEvents, "yes|no"), "Set Queue Events async, used for services like replication"); + ("async-queue-events", optValue(asyncQueueEvents, "yes|no"), "Set Queue Events async, used for services like replication") + ("default-flow-stop-threshold", optValue(queueFlowStopRatio, "PERCENT"), "Percent of queue's maximum capacity at which flow control is activated.") + ("default-flow-resume-threshold", optValue(queueFlowResumeRatio, "PERCENT"), "Percent of queue's maximum capacity at which flow control is de-activated.") + ("default-event-threshold-ratio", optValue(queueThresholdEventRatio, "%age of limit"), "The ratio of any specified queue limit at which an event will be raised") + ("default-message-group", optValue(defaultMsgGroup, "GROUP-IDENTIFER"), "Group identifier to assign to messages delivered to a message group queue that do not contain an identifier.") + ("enable-timestamp", optValue(timestampRcvMsgs, "yes|no"), "Add current time to each received message."); } const std::string empty; @@ -166,9 +196,10 @@ Broker::Broker(const Broker::Options& conf) : conf.replayFlushLimit*1024, // convert kb to bytes. conf.replayHardLimit*1024), *this), - queueCleaner(queues, timer), - queueEvents(poller,!conf.asyncQueueEvents), + queueCleaner(queues, &timer), + queueEvents(poller,!conf.asyncQueueEvents), recovery(true), + inCluster(false), clusterUpdatee(false), expiryPolicy(new ExpiryPolicy), connectionCounter(conf.maxConnections), @@ -225,8 +256,11 @@ Broker::Broker(const Broker::Options& conf) : // Early-Initialize plugins Plugin::earlyInitAll(*this); + QueueFlowLimit::setDefaults(conf.queueLimit, conf.queueFlowStopRatio, conf.queueFlowResumeRatio); + MessageGroupManager::setDefaults(conf.defaultMsgGroup); + // If no plugin store module registered itself, set up the null store. - if (NullMessageStore::isNullStore(store.get())) + if (NullMessageStore::isNullStore(store.get())) setStore(); exchanges.declare(empty, DirectExchange::typeName); // Default exchange. @@ -271,6 +305,11 @@ Broker::Broker(const Broker::Options& conf) : else QPID_LOG(info, "Management not enabled"); + // this feature affects performance, so let's be sure that gets logged! + if (conf.timestampRcvMsgs) { + QPID_LOG(notice, "Receive message timestamping is ENABLED."); + } + /** * SASL setup, can fail and terminate startup */ @@ -345,14 +384,14 @@ void Broker::run() { Dispatcher d(poller); int numIOThreads = config.workerThreads; std::vector<Thread> t(numIOThreads-1); - + // Run n-1 io threads for (int i=0; i<numIOThreads-1; ++i) t[i] = Thread(d); - + // Run final thread d.run(); - + // Now wait for n-1 io threads to exit for (int i=0; i<numIOThreads-1; ++i) { t[i].join(); @@ -399,9 +438,9 @@ Manageable::status_t Broker::ManagementMethod (uint32_t methodId, { case _qmf::Broker::METHOD_ECHO : QPID_LOG (debug, "Broker::echo(" - << dynamic_cast<_qmf::ArgsBrokerEcho&>(args).io_sequence - << ", " - << dynamic_cast<_qmf::ArgsBrokerEcho&>(args).io_body + << dynamic_cast<_qmf::ArgsBrokerEcho&>(args).io_sequence + << ", " + << dynamic_cast<_qmf::ArgsBrokerEcho&>(args).io_body << ")"); status = Manageable::STATUS_OK; break; @@ -409,8 +448,9 @@ Manageable::status_t Broker::ManagementMethod (uint32_t methodId, _qmf::ArgsBrokerConnect& hp= dynamic_cast<_qmf::ArgsBrokerConnect&>(args); - QPID_LOG (debug, "Broker::connect()"); string transport = hp.i_transport.empty() ? TCP_TRANSPORT : hp.i_transport; + QPID_LOG (debug, "Broker::connect() " << hp.i_host << ":" << hp.i_port << "; transport=" << transport << + "; durable=" << (hp.i_durable?"T":"F") << "; authMech=\"" << hp.i_authMechanism << "\""); if (!getProtocolFactory(transport)) { QPID_LOG(error, "Transport '" << transport << "' not supported"); return Manageable::STATUS_NOT_IMPLEMENTED; @@ -427,9 +467,9 @@ Manageable::status_t Broker::ManagementMethod (uint32_t methodId, _qmf::ArgsBrokerQueueMoveMessages& moveArgs= dynamic_cast<_qmf::ArgsBrokerQueueMoveMessages&>(args); QPID_LOG (debug, "Broker::queueMoveMessages()"); - if (queueMoveMessages(moveArgs.i_srcQueue, moveArgs.i_destQueue, moveArgs.i_qty)) + if (queueMoveMessages(moveArgs.i_srcQueue, moveArgs.i_destQueue, moveArgs.i_qty, moveArgs.i_filter)) status = Manageable::STATUS_OK; - else + else return Manageable::STATUS_PARAMETER_INVALID; break; } @@ -443,6 +483,38 @@ Manageable::status_t Broker::ManagementMethod (uint32_t methodId, QPID_LOG (debug, "Broker::getLogLevel()"); status = Manageable::STATUS_OK; break; + case _qmf::Broker::METHOD_CREATE : + { + _qmf::ArgsBrokerCreate& a = dynamic_cast<_qmf::ArgsBrokerCreate&>(args); + createObject(a.i_type, a.i_name, a.i_properties, a.i_strict, getManagementExecutionContext()); + status = Manageable::STATUS_OK; + break; + } + case _qmf::Broker::METHOD_DELETE : + { + _qmf::ArgsBrokerDelete& a = dynamic_cast<_qmf::ArgsBrokerDelete&>(args); + deleteObject(a.i_type, a.i_name, a.i_options, getManagementExecutionContext()); + status = Manageable::STATUS_OK; + break; + } + case _qmf::Broker::METHOD_QUERY : + { + _qmf::ArgsBrokerQuery& a = dynamic_cast<_qmf::ArgsBrokerQuery&>(args); + status = queryObject(a.i_type, a.i_name, a.o_results, getManagementExecutionContext()); + break; + } + case _qmf::Broker::METHOD_GETTIMESTAMPCONFIG: + { + _qmf::ArgsBrokerGetTimestampConfig& a = dynamic_cast<_qmf::ArgsBrokerGetTimestampConfig&>(args); + status = getTimestampConfig(a.o_receive, getManagementExecutionContext()); + break; + } + case _qmf::Broker::METHOD_SETTIMESTAMPCONFIG: + { + _qmf::ArgsBrokerSetTimestampConfig& a = dynamic_cast<_qmf::ArgsBrokerSetTimestampConfig&>(args); + status = setTimestampConfig(a.i_receive, getManagementExecutionContext()); + break; + } default: QPID_LOG (debug, "Broker ManagementMethod not implemented: id=" << methodId << "]"); status = Manageable::STATUS_NOT_IMPLEMENTED; @@ -452,6 +524,240 @@ Manageable::status_t Broker::ManagementMethod (uint32_t methodId, return status; } +namespace +{ +const std::string TYPE_QUEUE("queue"); +const std::string TYPE_EXCHANGE("exchange"); +const std::string TYPE_TOPIC("topic"); +const std::string TYPE_BINDING("binding"); +const std::string DURABLE("durable"); +const std::string AUTO_DELETE("auto-delete"); +const std::string ALTERNATE_EXCHANGE("alternate-exchange"); +const std::string EXCHANGE_TYPE("exchange-type"); +const std::string QUEUE_NAME("queue"); +const std::string EXCHANGE_NAME("exchange"); + +const std::string ATTRIBUTE_TIMESTAMP_0_10("timestamp-0.10"); + +const std::string _TRUE("true"); +const std::string _FALSE("false"); +} + +struct InvalidBindingIdentifier : public qpid::Exception +{ + InvalidBindingIdentifier(const std::string& name) : qpid::Exception(name) {} + std::string getPrefix() const { return "invalid binding"; } +}; + +struct BindingIdentifier +{ + std::string exchange; + std::string queue; + std::string key; + + BindingIdentifier(const std::string& name) + { + std::vector<std::string> path; + split(path, name, "/"); + switch (path.size()) { + case 1: + queue = path[0]; + break; + case 2: + exchange = path[0]; + queue = path[1]; + break; + case 3: + exchange = path[0]; + queue = path[1]; + key = path[2]; + break; + default: + throw InvalidBindingIdentifier(name); + } + } +}; + +struct ObjectAlreadyExists : public qpid::Exception +{ + ObjectAlreadyExists(const std::string& name) : qpid::Exception(name) {} + std::string getPrefix() const { return "object already exists"; } +}; + +struct UnknownObjectType : public qpid::Exception +{ + UnknownObjectType(const std::string& type) : qpid::Exception(type) {} + std::string getPrefix() const { return "unknown object type"; } +}; + +void Broker::createObject(const std::string& type, const std::string& name, + const Variant::Map& properties, bool /*strict*/, const ConnectionState* context) +{ + std::string userId; + std::string connectionId; + if (context) { + userId = context->getUserId(); + connectionId = context->getUrl(); + } + //TODO: implement 'strict' option (check there are no unrecognised properties) + QPID_LOG (debug, "Broker::create(" << type << ", " << name << "," << properties << ")"); + if (type == TYPE_QUEUE) { + bool durable(false); + bool autodelete(false); + std::string alternateExchange; + Variant::Map extensions; + for (Variant::Map::const_iterator i = properties.begin(); i != properties.end(); ++i) { + // extract durable, auto-delete and alternate-exchange properties + if (i->first == DURABLE) durable = i->second; + else if (i->first == AUTO_DELETE) autodelete = i->second; + else if (i->first == ALTERNATE_EXCHANGE) alternateExchange = i->second.asString(); + //treat everything else as extension properties + else extensions[i->first] = i->second; + } + framing::FieldTable arguments; + amqp_0_10::translate(extensions, arguments); + + std::pair<boost::shared_ptr<Queue>, bool> result = + createQueue(name, durable, autodelete, 0, alternateExchange, arguments, userId, connectionId); + if (!result.second) { + throw ObjectAlreadyExists(name); + } + } else if (type == TYPE_EXCHANGE || type == TYPE_TOPIC) { + bool durable(false); + std::string exchangeType("topic"); + std::string alternateExchange; + Variant::Map extensions; + for (Variant::Map::const_iterator i = properties.begin(); i != properties.end(); ++i) { + // extract durable, auto-delete and alternate-exchange properties + if (i->first == DURABLE) durable = i->second; + else if (i->first == EXCHANGE_TYPE) exchangeType = i->second.asString(); + else if (i->first == ALTERNATE_EXCHANGE) alternateExchange = i->second.asString(); + //treat everything else as extension properties + else extensions[i->first] = i->second; + } + framing::FieldTable arguments; + amqp_0_10::translate(extensions, arguments); + + try { + std::pair<boost::shared_ptr<Exchange>, bool> result = + createExchange(name, exchangeType, durable, alternateExchange, arguments, userId, connectionId); + if (!result.second) { + throw ObjectAlreadyExists(name); + } + } catch (const UnknownExchangeTypeException&) { + throw Exception(QPID_MSG("Invalid exchange type: " << exchangeType)); + } + } else if (type == TYPE_BINDING) { + BindingIdentifier binding(name); + std::string exchangeType("topic"); + Variant::Map extensions; + for (Variant::Map::const_iterator i = properties.begin(); i != properties.end(); ++i) { + // extract durable, auto-delete and alternate-exchange properties + if (i->first == EXCHANGE_TYPE) exchangeType = i->second.asString(); + //treat everything else as extension properties + else extensions[i->first] = i->second; + } + framing::FieldTable arguments; + amqp_0_10::translate(extensions, arguments); + + bind(binding.queue, binding.exchange, binding.key, arguments, userId, connectionId); + } else { + throw UnknownObjectType(type); + } +} + +void Broker::deleteObject(const std::string& type, const std::string& name, + const Variant::Map& options, const ConnectionState* context) +{ + std::string userId; + std::string connectionId; + if (context) { + userId = context->getUserId(); + connectionId = context->getUrl(); + } + QPID_LOG (debug, "Broker::delete(" << type << ", " << name << "," << options << ")"); + if (type == TYPE_QUEUE) { + deleteQueue(name, userId, connectionId); + } else if (type == TYPE_EXCHANGE || type == TYPE_TOPIC) { + deleteExchange(name, userId, connectionId); + } else if (type == TYPE_BINDING) { + BindingIdentifier binding(name); + unbind(binding.queue, binding.exchange, binding.key, userId, connectionId); + } else { + throw UnknownObjectType(type); + } + +} + +Manageable::status_t Broker::queryObject(const std::string& type, + const std::string& name, + Variant::Map& results, + const ConnectionState* context) +{ + std::string userId; + std::string connectionId; + if (context) { + userId = context->getUserId(); + connectionId = context->getUrl(); + } + QPID_LOG (debug, "Broker::query(" << type << ", " << name << ")"); + + if (type == TYPE_QUEUE) + return queryQueue( name, userId, connectionId, results ); + + if (type == TYPE_EXCHANGE || + type == TYPE_TOPIC || + type == TYPE_BINDING) + return Manageable::STATUS_NOT_IMPLEMENTED; + + throw UnknownObjectType(type); +} + +Manageable::status_t Broker::queryQueue( const std::string& name, + const std::string& userId, + const std::string& /*connectionId*/, + Variant::Map& results ) +{ + (void) results; + if (acl) { + if (!acl->authorise(userId, acl::ACT_ACCESS, acl::OBJ_QUEUE, name, NULL) ) + throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied queue query request from " << userId)); + } + + boost::shared_ptr<Queue> q(queues.find(name)); + if (!q) { + QPID_LOG(error, "Query failed: queue not found, name=" << name); + return Manageable::STATUS_UNKNOWN_OBJECT; + } + q->query( results ); + return Manageable::STATUS_OK;; +} + +Manageable::status_t Broker::getTimestampConfig(bool& receive, + const ConnectionState* context) +{ + std::string name; // none needed for broker + std::string userId = context->getUserId(); + if (acl && !acl->authorise(userId, acl::ACT_ACCESS, acl::OBJ_BROKER, name, NULL)) { + throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied broker timestamp get request from " << userId)); + } + receive = config.timestampRcvMsgs; + return Manageable::STATUS_OK; +} + +Manageable::status_t Broker::setTimestampConfig(const bool receive, + const ConnectionState* context) +{ + std::string name; // none needed for broker + std::string userId = context->getUserId(); + if (acl && !acl->authorise(userId, acl::ACT_UPDATE, acl::OBJ_BROKER, name, NULL)) { + throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied broker timestamp set request from " << userId)); + } + config.timestampRcvMsgs = receive; + QPID_LOG(notice, "Receive message timestamping is " << ((config.timestampRcvMsgs) ? "ENABLED." : "DISABLED.")); + return Manageable::STATUS_OK; +} + void Broker::setLogLevel(const std::string& level) { QPID_LOG(notice, "Changing log level to " << level); @@ -466,7 +772,7 @@ std::string Broker::getLogLevel() const std::vector<std::string>& selectors = qpid::log::Logger::instance().getOptions().selectors; for (std::vector<std::string>::const_iterator i = selectors.begin(); i != selectors.end(); ++i) { if (i != selectors.begin()) level += std::string(","); - level += *i; + level += *i; } return level; } @@ -499,7 +805,7 @@ void Broker::accept() { } void Broker::connect( - const std::string& host, uint16_t port, const std::string& transport, + const std::string& host, const std::string& port, const std::string& transport, boost::function2<void, int, std::string> failed, sys::ConnectionCodec::Factory* f) { @@ -515,13 +821,14 @@ void Broker::connect( { url.throwIfEmpty(); const Address& addr=url[0]; - connect(addr.host, addr.port, addr.protocol, failed, f); + connect(addr.host, boost::lexical_cast<std::string>(addr.port), addr.protocol, failed, f); } uint32_t Broker::queueMoveMessages( const std::string& srcQueue, const std::string& destQueue, - uint32_t qty) + uint32_t qty, + const Variant::Map& filter) { Queue::shared_ptr src_queue = queues.find(srcQueue); if (!src_queue) @@ -530,7 +837,7 @@ uint32_t Broker::queueMoveMessages( if (!dest_queue) return 0; - return src_queue->move(dest_queue, qty); + return src_queue->move(dest_queue, qty, &filter); } @@ -548,9 +855,228 @@ bool Broker::deferDeliveryImpl(const std::string& , void Broker::setClusterTimer(std::auto_ptr<sys::Timer> t) { clusterTimer = t; + queueCleaner.setTimer(clusterTimer.get()); + dtxManager.setTimer(*clusterTimer.get()); } const std::string Broker::TCP_TRANSPORT("tcp"); + +std::pair<boost::shared_ptr<Queue>, bool> Broker::createQueue( + const std::string& name, + bool durable, + bool autodelete, + const OwnershipToken* owner, + const std::string& alternateExchange, + const qpid::framing::FieldTable& arguments, + const std::string& userId, + const std::string& connectionId) +{ + if (acl) { + std::map<acl::Property, std::string> params; + params.insert(make_pair(acl::PROP_ALTERNATE, alternateExchange)); + params.insert(make_pair(acl::PROP_PASSIVE, _FALSE)); + params.insert(make_pair(acl::PROP_DURABLE, durable ? _TRUE : _FALSE)); + params.insert(make_pair(acl::PROP_EXCLUSIVE, owner ? _TRUE : _FALSE)); + params.insert(make_pair(acl::PROP_AUTODELETE, autodelete ? _TRUE : _FALSE)); + params.insert(make_pair(acl::PROP_POLICYTYPE, arguments.getAsString("qpid.policy_type"))); + params.insert(make_pair(acl::PROP_MAXQUEUECOUNT, boost::lexical_cast<string>(arguments.getAsInt("qpid.max_count")))); + params.insert(make_pair(acl::PROP_MAXQUEUESIZE, boost::lexical_cast<string>(arguments.getAsInt64("qpid.max_size")))); + + if (!acl->authorise(userId,acl::ACT_CREATE,acl::OBJ_QUEUE,name,¶ms) ) + throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied queue create request from " << userId)); + } + + Exchange::shared_ptr alternate; + if (!alternateExchange.empty()) { + alternate = exchanges.get(alternateExchange); + if (!alternate) throw framing::NotFoundException(QPID_MSG("Alternate exchange does not exist: " << alternateExchange)); + } + + std::pair<Queue::shared_ptr, bool> result = queues.declare(name, durable, autodelete, owner, alternate, arguments); + if (result.second) { + //add default binding: + result.first->bind(exchanges.getDefault(), name); + + if (managementAgent.get()) { + //TODO: debatable whether we should raise an event here for + //create when this is a 'declare' event; ideally add a create + //event instead? + managementAgent->raiseEvent( + _qmf::EventQueueDeclare(connectionId, userId, name, + durable, owner, autodelete, + ManagementAgent::toMap(arguments), + "created")); + } + } + return result; +} + +void Broker::deleteQueue(const std::string& name, const std::string& userId, + const std::string& connectionId, QueueFunctor check) +{ + if (acl && !acl->authorise(userId,acl::ACT_DELETE,acl::OBJ_QUEUE,name,NULL)) { + throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied queue delete request from " << userId)); + } + + Queue::shared_ptr queue = queues.find(name); + if (queue) { + if (check) check(queue); + queues.destroy(name); + queue->destroyed(); + } else { + throw framing::NotFoundException(QPID_MSG("Delete failed. No such queue: " << name)); + } + + if (managementAgent.get()) + managementAgent->raiseEvent(_qmf::EventQueueDelete(connectionId, userId, name)); + +} + +std::pair<Exchange::shared_ptr, bool> Broker::createExchange( + const std::string& name, + const std::string& type, + bool durable, + const std::string& alternateExchange, + const qpid::framing::FieldTable& arguments, + const std::string& userId, + const std::string& connectionId) +{ + if (acl) { + std::map<acl::Property, std::string> params; + params.insert(make_pair(acl::PROP_TYPE, type)); + params.insert(make_pair(acl::PROP_ALTERNATE, alternateExchange)); + params.insert(make_pair(acl::PROP_PASSIVE, _FALSE)); + params.insert(make_pair(acl::PROP_DURABLE, durable ? _TRUE : _FALSE)); + if (!acl->authorise(userId,acl::ACT_CREATE,acl::OBJ_EXCHANGE,name,¶ms) ) + throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied exchange create request from " << userId)); + } + + Exchange::shared_ptr alternate; + if (!alternateExchange.empty()) { + alternate = exchanges.get(alternateExchange); + if (!alternate) throw framing::NotFoundException(QPID_MSG("Alternate exchange does not exist: " << alternateExchange)); + } + + std::pair<Exchange::shared_ptr, bool> result; + result = exchanges.declare(name, type, durable, arguments); + if (result.second) { + if (alternate) { + result.first->setAlternate(alternate); + alternate->incAlternateUsers(); + } + if (durable) { + store->create(*result.first, arguments); + } + if (managementAgent.get()) { + //TODO: debatable whether we should raise an event here for + //create when this is a 'declare' event; ideally add a create + //event instead? + managementAgent->raiseEvent(_qmf::EventExchangeDeclare(connectionId, + userId, + name, + type, + alternateExchange, + durable, + false, + ManagementAgent::toMap(arguments), + "created")); + } + } + return result; +} + +void Broker::deleteExchange(const std::string& name, const std::string& userId, + const std::string& connectionId) +{ + if (acl) { + if (!acl->authorise(userId,acl::ACT_DELETE,acl::OBJ_EXCHANGE,name,NULL) ) + throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied exchange delete request from " << userId)); + } + + if (name.empty()) { + throw framing::InvalidArgumentException(QPID_MSG("Delete not allowed for default exchange")); + } + Exchange::shared_ptr exchange(exchanges.get(name)); + if (!exchange) throw framing::NotFoundException(QPID_MSG("Delete failed. No such exchange: " << name)); + if (exchange->inUseAsAlternate()) throw framing::NotAllowedException(QPID_MSG("Exchange in use as alternate-exchange.")); + if (exchange->isDurable()) store->destroy(*exchange); + if (exchange->getAlternate()) exchange->getAlternate()->decAlternateUsers(); + exchanges.destroy(name); + + if (managementAgent.get()) + managementAgent->raiseEvent(_qmf::EventExchangeDelete(connectionId, userId, name)); + +} + +void Broker::bind(const std::string& queueName, + const std::string& exchangeName, + const std::string& key, + const qpid::framing::FieldTable& arguments, + const std::string& userId, + const std::string& connectionId) +{ + if (acl) { + std::map<acl::Property, std::string> params; + params.insert(make_pair(acl::PROP_QUEUENAME, queueName)); + params.insert(make_pair(acl::PROP_ROUTINGKEY, key)); + + if (!acl->authorise(userId,acl::ACT_BIND,acl::OBJ_EXCHANGE,exchangeName,¶ms)) + throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied exchange bind request from " << userId)); + } + if (exchangeName.empty()) { + throw framing::InvalidArgumentException(QPID_MSG("Bind not allowed for default exchange")); + } + + Queue::shared_ptr queue = queues.find(queueName); + Exchange::shared_ptr exchange = exchanges.get(exchangeName); + if (!queue) { + throw framing::NotFoundException(QPID_MSG("Bind failed. No such queue: " << queueName)); + } else if (!exchange) { + throw framing::NotFoundException(QPID_MSG("Bind failed. No such exchange: " << exchangeName)); + } else { + if (queue->bind(exchange, key, arguments)) { + if (managementAgent.get()) { + managementAgent->raiseEvent(_qmf::EventBind(connectionId, userId, exchangeName, + queueName, key, ManagementAgent::toMap(arguments))); + } + } + } +} + +void Broker::unbind(const std::string& queueName, + const std::string& exchangeName, + const std::string& key, + const std::string& userId, + const std::string& connectionId) +{ + if (acl) { + std::map<acl::Property, std::string> params; + params.insert(make_pair(acl::PROP_QUEUENAME, queueName)); + params.insert(make_pair(acl::PROP_ROUTINGKEY, key)); + if (!acl->authorise(userId,acl::ACT_UNBIND,acl::OBJ_EXCHANGE,exchangeName,¶ms) ) + throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied exchange unbind request from " << userId)); + } + if (exchangeName.empty()) { + throw framing::InvalidArgumentException(QPID_MSG("Unbind not allowed for default exchange")); + } + Queue::shared_ptr queue = queues.find(queueName); + Exchange::shared_ptr exchange = exchanges.get(exchangeName); + if (!queue) { + throw framing::NotFoundException(QPID_MSG("Unbind failed. No such queue: " << queueName)); + } else if (!exchange) { + throw framing::NotFoundException(QPID_MSG("Unbind failed. No such exchange: " << exchangeName)); + } else { + if (exchange->unbind(queue, key, 0)) { + if (exchange->isDurable() && queue->isDurable()) { + store->unbind(*exchange, *queue, key, qpid::framing::FieldTable()); + } + if (managementAgent.get()) { + managementAgent->raiseEvent(_qmf::EventUnbind(connectionId, userId, exchangeName, queueName, key)); + } + } + } +} + }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/Broker.h b/cpp/src/qpid/broker/Broker.h index cd6f81dc70..b3b751be98 100644 --- a/cpp/src/qpid/broker/Broker.h +++ b/cpp/src/qpid/broker/Broker.h @@ -10,9 +10,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -49,6 +49,7 @@ #include "qpid/framing/ProtocolInitiation.h" #include "qpid/sys/Runnable.h" #include "qpid/sys/Timer.h" +#include "qpid/types/Variant.h" #include "qpid/RefCounted.h" #include "qpid/broker/AclModule.h" #include "qpid/sys/Mutex.h" @@ -57,7 +58,7 @@ #include <string> #include <vector> -namespace qpid { +namespace qpid { namespace sys { class ProtocolFactory; @@ -68,6 +69,7 @@ struct Url; namespace broker { +class ConnectionState; class ExpiryPolicy; class Message; @@ -80,7 +82,7 @@ struct NoSuchTransportException : qpid::Exception }; /** - * A broker instance. + * A broker instance. */ class Broker : public sys::Runnable, public Plugin::Target, public management::Manageable, @@ -116,29 +118,34 @@ public: bool asyncQueueEvents; bool qmf2Support; bool qmf1Support; + uint queueFlowStopRatio; // producer flow control: on + uint queueFlowResumeRatio; // producer flow control: off + uint16_t queueThresholdEventRatio; + std::string defaultMsgGroup; + bool timestampRcvMsgs; private: std::string getHome(); }; - + class ConnectionCounter { int maxConnections; int connectionCount; sys::Mutex connectionCountLock; public: ConnectionCounter(int mc): maxConnections(mc),connectionCount(0) {}; - void inc_connectionCount() { - sys::ScopedLock<sys::Mutex> l(connectionCountLock); + void inc_connectionCount() { + sys::ScopedLock<sys::Mutex> l(connectionCountLock); connectionCount++; - } - void dec_connectionCount() { - sys::ScopedLock<sys::Mutex> l(connectionCountLock); + } + void dec_connectionCount() { + sys::ScopedLock<sys::Mutex> l(connectionCountLock); connectionCount--; } bool allowConnection() { - sys::ScopedLock<sys::Mutex> l(connectionCountLock); + sys::ScopedLock<sys::Mutex> l(connectionCountLock); return (maxConnections <= connectionCount); - } + } }; private: @@ -148,7 +155,20 @@ public: void setStore (); void setLogLevel(const std::string& level); std::string getLogLevel(); - + void createObject(const std::string& type, const std::string& name, + const qpid::types::Variant::Map& properties, bool strict, const ConnectionState* context); + void deleteObject(const std::string& type, const std::string& name, + const qpid::types::Variant::Map& options, const ConnectionState* context); + Manageable::status_t queryObject(const std::string& type, const std::string& name, + qpid::types::Variant::Map& results, const ConnectionState* context); + Manageable::status_t queryQueue( const std::string& name, + const std::string& userId, + const std::string& connectionId, + qpid::types::Variant::Map& results); + Manageable::status_t getTimestampConfig(bool& receive, + const ConnectionState* context); + Manageable::status_t setTimestampConfig(const bool receive, + const ConnectionState* context); boost::shared_ptr<sys::Poller> poller; sys::Timer timer; std::auto_ptr<sys::Timer> clusterTimer; @@ -176,10 +196,10 @@ public: const boost::intrusive_ptr<Message>& msg); std::string federationTag; bool recovery; - bool clusterUpdatee; + bool inCluster, clusterUpdatee; boost::intrusive_ptr<ExpiryPolicy> expiryPolicy; ConnectionCounter connectionCounter; - + public: virtual ~Broker(); @@ -235,7 +255,7 @@ public: QPID_BROKER_EXTERN void accept(); /** Create a connection to another broker. */ - void connect(const std::string& host, uint16_t port, + void connect(const std::string& host, const std::string& port, const std::string& transport, boost::function2<void, int, std::string> failed, sys::ConnectionCodec::Factory* =0); @@ -247,9 +267,10 @@ public: /** Move messages from one queue to another. A zero quantity means to move all messages */ - uint32_t queueMoveMessages( const std::string& srcQueue, + uint32_t queueMoveMessages( const std::string& srcQueue, const std::string& destQueue, - uint32_t qty); + uint32_t qty, + const qpid::types::Variant::Map& filter); boost::shared_ptr<sys::ProtocolFactory> getProtocolFactory(const std::string& name = TCP_TRANSPORT) const; @@ -273,11 +294,20 @@ public: void setRecovery(bool set) { recovery = set; } bool getRecovery() const { return recovery; } - void setClusterUpdatee(bool set) { clusterUpdatee = set; } + /** True of this broker is part of a cluster. + * Only valid after early initialization of plugins is complete. + */ + bool isInCluster() const { return inCluster; } + void setInCluster(bool set) { inCluster = set; } + + /** True if this broker is joining a cluster and in the process of + * receiving a state update. + */ bool isClusterUpdatee() const { return clusterUpdatee; } + void setClusterUpdatee(bool set) { clusterUpdatee = set; } management::ManagementAgent* getManagementAgent() { return managementAgent.get(); } - + ConnectionCounter& getConnectionCounter() {return connectionCounter;} /** @@ -290,6 +320,43 @@ public: const boost::intrusive_ptr<Message>& msg)> deferDelivery; bool isAuthenticating ( ) { return config.auth; } + bool isTimestamping() { return config.timestampRcvMsgs; } + + typedef boost::function1<void, boost::shared_ptr<Queue> > QueueFunctor; + + std::pair<boost::shared_ptr<Queue>, bool> createQueue( + const std::string& name, + bool durable, + bool autodelete, + const OwnershipToken* owner, + const std::string& alternateExchange, + const qpid::framing::FieldTable& arguments, + const std::string& userId, + const std::string& connectionId); + void deleteQueue(const std::string& name, + const std::string& userId, + const std::string& connectionId, + QueueFunctor check = QueueFunctor()); + std::pair<Exchange::shared_ptr, bool> createExchange( + const std::string& name, + const std::string& type, + bool durable, + const std::string& alternateExchange, + const qpid::framing::FieldTable& args, + const std::string& userId, const std::string& connectionId); + void deleteExchange(const std::string& name, const std::string& userId, + const std::string& connectionId); + void bind(const std::string& queue, + const std::string& exchange, + const std::string& key, + const qpid::framing::FieldTable& arguments, + const std::string& userId, + const std::string& connectionId); + void unbind(const std::string& queue, + const std::string& exchange, + const std::string& key, + const std::string& userId, + const std::string& connectionId); }; }} diff --git a/cpp/src/qpid/broker/BrokerImportExport.h b/cpp/src/qpid/broker/BrokerImportExport.h index 4edf8c9844..ee05788063 100644 --- a/cpp/src/qpid/broker/BrokerImportExport.h +++ b/cpp/src/qpid/broker/BrokerImportExport.h @@ -20,14 +20,23 @@ * under the License. */ -#if defined(WIN32) && !defined(QPID_BROKER_STATIC) -#if defined(BROKER_EXPORT) || defined (qpidbroker_EXPORTS) -#define QPID_BROKER_EXTERN __declspec(dllexport) +#if defined(WIN32) && !defined(QPID_DECLARE_STATIC) +# if defined(BROKER_EXPORT) || defined (qpidbroker_EXPORTS) +# define QPID_BROKER_EXTERN __declspec(dllexport) +# else +# define QPID_BROKER_EXTERN __declspec(dllimport) +# endif +# ifdef _MSC_VER +# define QPID_BROKER_CLASS_EXTERN +# define QPID_BROKER_INLINE_EXTERN QPID_BROKER_EXTERN +# else +# define QPID_BROKER_CLASS_EXTERN QPID_BROKER_EXTERN +# define QPID_BROKER_INLINE_EXTERN +# endif #else -#define QPID_BROKER_EXTERN __declspec(dllimport) -#endif -#else -#define QPID_BROKER_EXTERN +# define QPID_BROKER_EXTERN +# define QPID_BROKER_CLASS_EXTERN +# define QPID_BROKER_INLINE_EXTERN #endif #endif diff --git a/cpp/src/qpid/broker/Connection.cpp b/cpp/src/qpid/broker/Connection.cpp index 460799280e..0b3059d26c 100644 --- a/cpp/src/qpid/broker/Connection.cpp +++ b/cpp/src/qpid/broker/Connection.cpp @@ -156,16 +156,7 @@ Connection::~Connection() void Connection::received(framing::AMQFrame& frame) { // Received frame on connection so delay timeout restartTimeout(); - - if (frame.getChannel() == 0 && frame.getMethod()) { - adapter.handle(frame); - } else { - if (adapter.isOpen()) - getChannel(frame.getChannel()).in(frame); - else - close(connection::CLOSE_CODE_FRAMING_ERROR, "Connection not yet open, invalid frame received."); - } - + adapter.handle(frame); if (isLink) //i.e. we are acting as the client to another broker recordFromServer(frame); else @@ -278,8 +269,7 @@ void Connection::setUserId(const string& userId) ConnectionState::setUserId(userId); // In a cluster, the cluster code will raise the connect event // when the connection is replicated to the cluster. - if (!sys::isCluster()) - raiseConnectEvent(); + if (!broker.isInCluster()) raiseConnectEvent(); } void Connection::raiseConnectEvent() { @@ -289,11 +279,11 @@ void Connection::raiseConnectEvent() { } } -void Connection::setFederationLink(bool b) +void Connection::setUserProxyAuth(bool b) { - ConnectionState::setFederationLink(b); + ConnectionState::setUserProxyAuth(b); if (mgmtObject != 0) - mgmtObject->set_federationLink(b); + mgmtObject->set_userProxyAuth(b); } void Connection::close(connection::CloseCode code, const string& text) @@ -332,31 +322,30 @@ void Connection::closed(){ // Physically closed, suspend open sessions. try { while (!channels.empty()) ptr_map_ptr(channels.begin())->handleDetach(); - while (!exclusiveQueues.empty()) { - boost::shared_ptr<Queue> q(exclusiveQueues.front()); - q->releaseExclusiveOwnership(); - if (q->canAutoDelete()) { - Queue::tryAutoDelete(broker, q); - } - exclusiveQueues.erase(exclusiveQueues.begin()); - } } catch(std::exception& e) { QPID_LOG(error, QPID_MSG("While closing connection: " << e.what())); assert(0); } } +void Connection::doIoCallbacks() { + { + ScopedLock<Mutex> l(ioCallbackLock); + // Although IO callbacks execute in the connection thread context, they are + // not cluster safe because they are queued for execution in non-IO threads. + ClusterUnsafeScope cus; + while (!ioCallbacks.empty()) { + boost::function0<void> cb = ioCallbacks.front(); + ioCallbacks.pop(); + ScopedUnlock<Mutex> ul(ioCallbackLock); + cb(); // Lend the IO thread for management processing + } + } +} + bool Connection::doOutput() { try { - { - ScopedLock<Mutex> l(ioCallbackLock); - while (!ioCallbacks.empty()) { - boost::function0<void> cb = ioCallbacks.front(); - ioCallbacks.pop(); - ScopedUnlock<Mutex> ul(ioCallbackLock); - cb(); // Lend the IO thread for management processing - } - } + doIoCallbacks(); if (mgmtClosing) { closed(); close(connection::CLOSE_CODE_CONNECTION_FORCED, "Closed by Management Request"); @@ -476,8 +465,8 @@ void Connection::OutboundFrameTracker::abort() { next->abort(); } void Connection::OutboundFrameTracker::activateOutput() { next->activateOutput(); } void Connection::OutboundFrameTracker::giveReadCredit(int32_t credit) { next->giveReadCredit(credit); } void Connection::OutboundFrameTracker::send(framing::AMQFrame& f) -{ - next->send(f); +{ + next->send(f); con.sent(f); } void Connection::OutboundFrameTracker::wrap(sys::ConnectionOutputHandlerPtr& p) diff --git a/cpp/src/qpid/broker/Connection.h b/cpp/src/qpid/broker/Connection.h index b751848d73..3522d70b35 100644 --- a/cpp/src/qpid/broker/Connection.h +++ b/cpp/src/qpid/broker/Connection.h @@ -125,7 +125,7 @@ class Connection : public sys::ConnectionInputHandler, const std::string& getUserId() const { return ConnectionState::getUserId(); } const std::string& getMgmtId() const { return mgmtId; } management::ManagementAgent* getAgent() const { return agent; } - void setFederationLink(bool b); + void setUserProxyAuth(bool b); /** Connection does not delete the listener. 0 resets. */ void setErrorListener(ErrorListener* l) { errorListener=l; } ErrorListener* getErrorListener() { return errorListener; } @@ -153,13 +153,16 @@ class Connection : public sys::ConnectionInputHandler, void addManagementObject(); const qpid::sys::SecuritySettings& getExternalSecuritySettings() const - { + { return securitySettings; } /** @return true if the initial connection negotiation is complete. */ bool isOpen(); + // Used by cluster during catch-up, see cluster::OutputInterceptor + void doIoCallbacks(); + private: typedef boost::ptr_map<framing::ChannelId, SessionHandler> ChannelMap; typedef std::vector<boost::shared_ptr<Queue> >::iterator queue_iterator; @@ -201,7 +204,7 @@ class Connection : public sys::ConnectionInputHandler, sys::ConnectionOutputHandler* next; }; OutboundFrameTracker outboundTracker; - + void sent(const framing::AMQFrame& f); public: diff --git a/cpp/src/qpid/broker/ConnectionHandler.cpp b/cpp/src/qpid/broker/ConnectionHandler.cpp index 3f97e5b9de..7cd91ae539 100644 --- a/cpp/src/qpid/broker/ConnectionHandler.cpp +++ b/cpp/src/qpid/broker/ConnectionHandler.cpp @@ -26,6 +26,7 @@ #include "qpid/broker/SecureConnection.h" #include "qpid/Url.h" #include "qpid/framing/AllInvoker.h" +#include "qpid/framing/ConnectionStartOkBody.h" #include "qpid/framing/enum.h" #include "qpid/log/Statement.h" #include "qpid/sys/SecurityLayer.h" @@ -63,13 +64,31 @@ void ConnectionHandler::heartbeat() handler->proxy.heartbeat(); } +bool ConnectionHandler::handle(const framing::AMQMethodBody& method) +{ + //Need special handling for start-ok, in order to distinguish + //between null and empty response + if (method.isA<ConnectionStartOkBody>()) { + handler->startOk(dynamic_cast<const ConnectionStartOkBody&>(method)); + return true; + } else { + return invoke(static_cast<AMQP_AllOperations::ConnectionHandler&>(*handler), method); + } +} + void ConnectionHandler::handle(framing::AMQFrame& frame) { AMQMethodBody* method=frame.getBody()->getMethod(); Connection::ErrorListener* errorListener = handler->connection.getErrorListener(); try{ - if (!invoke(static_cast<AMQP_AllOperations::ConnectionHandler&>(*handler.get()), *method)) { + if (method && handle(*method)) { + // This is a connection control frame, nothing more to do. + } else if (isOpen()) { handler->connection.getChannel(frame.getChannel()).in(frame); + } else { + handler->proxy.close( + connection::CLOSE_CODE_FRAMING_ERROR, + "Connection not yet open, invalid frame received."); } }catch(ConnectionException& e){ if (errorListener) errorListener->connectionError(e.what()); @@ -89,13 +108,10 @@ ConnectionHandler::ConnectionHandler(Connection& connection, bool isClient, bool ConnectionHandler::Handler::Handler(Connection& c, bool isClient, bool isShadow) : proxy(c.getOutput()), - connection(c), serverMode(!isClient), acl(0), secured(0), + connection(c), serverMode(!isClient), secured(0), isOpen(false) { if (serverMode) { - - acl = connection.getBroker().getAcl(); - FieldTable properties; Array mechanisms(0x95); @@ -118,13 +134,20 @@ ConnectionHandler::Handler::Handler(Connection& c, bool isClient, bool isShadow) ConnectionHandler::Handler::~Handler() {} -void ConnectionHandler::Handler::startOk(const framing::FieldTable& clientProperties, - const string& mechanism, - const string& response, +void ConnectionHandler::Handler::startOk(const framing::FieldTable& /*clientProperties*/, + const string& /*mechanism*/, + const string& /*response*/, const string& /*locale*/) { + //Need special handling for start-ok, in order to distinguish + //between null and empty response -> should never use this method + assert(false); +} + +void ConnectionHandler::Handler::startOk(const ConnectionStartOkBody& body) +{ try { - authenticator->start(mechanism, response); + authenticator->start(body.getMechanism(), body.hasResponse() ? &body.getResponse() : 0); } catch (std::exception& /*e*/) { management::ManagementAgent* agent = connection.getAgent(); if (agent) { @@ -136,9 +159,14 @@ void ConnectionHandler::Handler::startOk(const framing::FieldTable& clientProper } throw; } + const framing::FieldTable& clientProperties = body.getClientProperties(); connection.setFederationLink(clientProperties.get(QPID_FED_LINK)); - connection.setFederationPeerTag(clientProperties.getAsString(QPID_FED_TAG)); + if (clientProperties.isSet(QPID_FED_TAG)) { + connection.setFederationPeerTag(clientProperties.getAsString(QPID_FED_TAG)); + } if (connection.isFederationLink()) { + AclModule* acl = connection.getBroker().getAcl(); + FieldTable properties; if (acl && !acl->authorise(connection.getUserId(),acl::ACT_CREATE,acl::OBJ_LINK,"")){ proxy.close(framing::connection::CLOSE_CODE_CONNECTION_FORCED,"ACL denied creating a federation link"); return; @@ -183,7 +211,7 @@ void ConnectionHandler::Handler::secureOk(const string& response) void ConnectionHandler::Handler::tuneOk(uint16_t /*channelmax*/, uint16_t framemax, uint16_t heartbeat) { - connection.setFrameMax(framemax); + if (framemax) connection.setFrameMax(framemax); connection.setHeartbeatInterval(heartbeat); } @@ -256,7 +284,6 @@ void ConnectionHandler::Handler::start(const FieldTable& serverProperties, false ); // disallow interaction } std::string supportedMechanismsList; - bool requestedMechanismIsSupported = false; Array::const_iterator i; /* @@ -269,11 +296,9 @@ void ConnectionHandler::Handler::start(const FieldTable& serverProperties, if (i != supportedMechanisms.begin()) supportedMechanismsList += SPACE; supportedMechanismsList += (*i)->get<std::string>(); - requestedMechanismIsSupported = true; } } else { - requestedMechanismIsSupported = false; /* The caller has requested a mechanism. If it's available, make sure it ends up at the head of the list. @@ -282,7 +307,6 @@ void ConnectionHandler::Handler::start(const FieldTable& serverProperties, string currentMechanism = (*i)->get<std::string>(); if ( requestedMechanism == currentMechanism ) { - requestedMechanismIsSupported = true; supportedMechanismsList = currentMechanism + SPACE + supportedMechanismsList; } else { if (i != supportedMechanisms.begin()) @@ -292,7 +316,9 @@ void ConnectionHandler::Handler::start(const FieldTable& serverProperties, } } - connection.setFederationPeerTag(serverProperties.getAsString(QPID_FED_TAG)); + if (serverProperties.isSet(QPID_FED_TAG)) { + connection.setFederationPeerTag(serverProperties.getAsString(QPID_FED_TAG)); + } FieldTable ft; ft.setInt(QPID_FED_LINK,1); @@ -301,11 +327,21 @@ void ConnectionHandler::Handler::start(const FieldTable& serverProperties, string response; if (sasl.get()) { const qpid::sys::SecuritySettings& ss = connection.getExternalSecuritySettings(); - response = sasl->start ( requestedMechanism.empty() - ? supportedMechanismsList - : requestedMechanism, - & ss ); - proxy.startOk ( ft, sasl->getMechanism(), response, en_US ); + if (sasl->start ( requestedMechanism.empty() + ? supportedMechanismsList + : requestedMechanism, + response, + & ss )) { + proxy.startOk ( ft, sasl->getMechanism(), response, en_US ); + } else { + //response was null + ConnectionStartOkBody body; + body.setClientProperties(ft); + body.setMechanism(sasl->getMechanism()); + //Don't set response, as none was given + body.setLocale(en_US); + proxy.send(body); + } } else { response = ((char)0) + username + ((char)0) + password; diff --git a/cpp/src/qpid/broker/ConnectionHandler.h b/cpp/src/qpid/broker/ConnectionHandler.h index b32167669e..05c5f00c57 100644 --- a/cpp/src/qpid/broker/ConnectionHandler.h +++ b/cpp/src/qpid/broker/ConnectionHandler.h @@ -26,8 +26,10 @@ #include "qpid/broker/SaslAuthenticator.h" #include "qpid/framing/amqp_types.h" #include "qpid/framing/AMQFrame.h" +#include "qpid/framing/AMQMethodBody.h" #include "qpid/framing/AMQP_AllOperations.h" #include "qpid/framing/AMQP_AllProxy.h" +#include "qpid/framing/ConnectionStartOkBody.h" #include "qpid/framing/enum.h" #include "qpid/framing/FrameHandler.h" #include "qpid/framing/ProtocolInitiation.h" @@ -57,12 +59,12 @@ class ConnectionHandler : public framing::FrameHandler Connection& connection; bool serverMode; std::auto_ptr<SaslAuthenticator> authenticator; - AclModule* acl; SecureConnection* secured; bool isOpen; Handler(Connection& connection, bool isClient, bool isShadow=false); ~Handler(); + void startOk(const qpid::framing::ConnectionStartOkBody& body); void startOk(const qpid::framing::FieldTable& clientProperties, const std::string& mechanism, const std::string& response, const std::string& locale); @@ -96,7 +98,7 @@ class ConnectionHandler : public framing::FrameHandler }; std::auto_ptr<Handler> handler; - + bool handle(const qpid::framing::AMQMethodBody& method); public: ConnectionHandler(Connection& connection, bool isClient, bool isShadow=false ); void close(framing::connection::CloseCode code, const std::string& text); diff --git a/cpp/src/qpid/broker/ConnectionState.h b/cpp/src/qpid/broker/ConnectionState.h index 774c37408d..fdd3c4ddc0 100644 --- a/cpp/src/qpid/broker/ConnectionState.h +++ b/cpp/src/qpid/broker/ConnectionState.h @@ -46,6 +46,7 @@ class ConnectionState : public ConnectionToken, public management::Manageable framemax(65535), heartbeat(0), heartbeatmax(120), + userProxyAuth(false), // Can proxy msgs with non-matching auth ids when true (used by federation links & clustering) federationLink(true), clientSupportsThrottling(false), clusterOrderOut(0) @@ -67,8 +68,10 @@ class ConnectionState : public ConnectionToken, public management::Manageable void setUrl(const std::string& _url) { url = _url; } const std::string& getUrl() const { return url; } - void setFederationLink(bool b) { federationLink = b; } - bool isFederationLink() const { return federationLink; } + void setUserProxyAuth(const bool b) { userProxyAuth = b; } + bool isUserProxyAuth() const { return userProxyAuth || federationPeerTag.size() > 0; } // links can proxy msgs with non-matching auth ids + void setFederationLink(bool b) { federationLink = b; } // deprecated - use setFederationPeerTag() instead + bool isFederationLink() const { return federationPeerTag.size() > 0; } void setFederationPeerTag(const std::string& tag) { federationPeerTag = std::string(tag); } const std::string& getFederationPeerTag() const { return federationPeerTag; } std::vector<Url>& getKnownHosts() { return knownHosts; } @@ -79,7 +82,6 @@ class ConnectionState : public ConnectionToken, public management::Manageable Broker& getBroker() { return broker; } Broker& broker; - std::vector<boost::shared_ptr<Queue> > exclusiveQueues; //contained output tasks sys::AggregateOutput outputTasks; @@ -106,6 +108,7 @@ class ConnectionState : public ConnectionToken, public management::Manageable uint16_t heartbeatmax; std::string userId; std::string url; + bool userProxyAuth; bool federationLink; std::string federationPeerTag; std::vector<Url> knownHosts; diff --git a/cpp/src/qpid/broker/Consumer.h b/cpp/src/qpid/broker/Consumer.h index b96443fa7c..2af9b0c121 100644 --- a/cpp/src/qpid/broker/Consumer.h +++ b/cpp/src/qpid/broker/Consumer.h @@ -29,22 +29,33 @@ namespace qpid { namespace broker { class Queue; +class QueueListeners; class Consumer { const bool acquires; - public: - typedef boost::shared_ptr<Consumer> shared_ptr; - + // inListeners allows QueueListeners to efficiently track if this instance is registered + // for notifications without having to search its containers + bool inListeners; + // the name is generated by broker and is unique within broker scope. It is not + // provided or known by the remote Consumer. + const std::string name; + public: + typedef boost::shared_ptr<Consumer> shared_ptr; + framing::SequenceNumber position; - - Consumer(bool preAcquires = true) : acquires(preAcquires) {} + + Consumer(const std::string& _name, bool preAcquires = true) + : acquires(preAcquires), inListeners(false), name(_name), position(0) {} bool preAcquires() const { return acquires; } + const std::string& getName() const { return name; } + virtual bool deliver(QueuedMessage& msg) = 0; virtual void notify() = 0; virtual bool filter(boost::intrusive_ptr<Message>) { return true; } virtual bool accept(boost::intrusive_ptr<Message>) { return true; } virtual OwnershipToken* getSession() = 0; virtual ~Consumer(){} + friend class QueueListeners; }; }} diff --git a/cpp/src/qpid/broker/Daemon.cpp b/cpp/src/qpid/broker/Daemon.cpp index b30e5f18cb..c36538beb7 100644 --- a/cpp/src/qpid/broker/Daemon.cpp +++ b/cpp/src/qpid/broker/Daemon.cpp @@ -93,11 +93,10 @@ void Daemon::fork() catch (const exception& e) { QPID_LOG(critical, "Unexpected error: " << e.what()); uint16_t port = 0; - int unused_ret; //Supress warning about ignoring return value. - unused_ret = write(pipeFds[1], &port, sizeof(uint16_t)); + (void) write(pipeFds[1], &port, sizeof(uint16_t)); std::string pipeFailureMessage = e.what(); - unused_ret = write ( pipeFds[1], + (void) write ( pipeFds[1], pipeFailureMessage.c_str(), strlen(pipeFailureMessage.c_str()) ); diff --git a/cpp/src/qpid/broker/DeliverableMessage.h b/cpp/src/qpid/broker/DeliverableMessage.h index ce613e7b6e..c8d21001eb 100644 --- a/cpp/src/qpid/broker/DeliverableMessage.h +++ b/cpp/src/qpid/broker/DeliverableMessage.h @@ -29,7 +29,7 @@ namespace qpid { namespace broker { - class DeliverableMessage : public Deliverable{ + class QPID_BROKER_CLASS_EXTERN DeliverableMessage : public Deliverable{ boost::intrusive_ptr<Message> msg; public: QPID_BROKER_EXTERN DeliverableMessage(const boost::intrusive_ptr<Message>& msg); diff --git a/cpp/src/qpid/broker/DeliveryRecord.cpp b/cpp/src/qpid/broker/DeliveryRecord.cpp index 9443eb6ea5..0b8fe95d5e 100644 --- a/cpp/src/qpid/broker/DeliveryRecord.cpp +++ b/cpp/src/qpid/broker/DeliveryRecord.cpp @@ -75,7 +75,7 @@ void DeliveryRecord::deliver(framing::FrameHandler& h, DeliveryId deliveryId, ui { id = deliveryId; if (msg.payload->getRedelivered()){ - msg.payload->getProperties<framing::DeliveryProperties>()->setRedelivered(true); + msg.payload->setRedelivered(); } msg.payload->adjustTtl(); @@ -131,18 +131,20 @@ void DeliveryRecord::committed() const{ void DeliveryRecord::reject() { - Exchange::shared_ptr alternate = queue->getAlternateExchange(); - if (alternate) { - DeliverableMessage delivery(msg.payload); - alternate->route(delivery, msg.payload->getRoutingKey(), msg.payload->getApplicationHeaders()); - QPID_LOG(info, "Routed rejected message from " << queue->getName() << " to " - << alternate->getName()); - } else { - //just drop it - QPID_LOG(info, "Dropping rejected message from " << queue->getName()); + if (acquired && !ended) { + Exchange::shared_ptr alternate = queue->getAlternateExchange(); + if (alternate) { + DeliverableMessage delivery(msg.payload); + alternate->routeWithAlternate(delivery); + QPID_LOG(info, "Routed rejected message from " << queue->getName() << " to " + << alternate->getName()); + } else { + //just drop it + QPID_LOG(info, "Dropping rejected message from " << queue->getName()); + } + dequeue(); + setEnded(); } - - dequeue(); } uint32_t DeliveryRecord::getCredit() const @@ -151,7 +153,7 @@ uint32_t DeliveryRecord::getCredit() const } void DeliveryRecord::acquire(DeliveryIds& results) { - if (queue->acquire(msg)) { + if (queue->acquire(msg, tag)) { acquired = true; results.push_back(id); if (!acceptExpected) { diff --git a/cpp/src/qpid/broker/DeliveryRecord.h b/cpp/src/qpid/broker/DeliveryRecord.h index d388ba94be..5a331357be 100644 --- a/cpp/src/qpid/broker/DeliveryRecord.h +++ b/cpp/src/qpid/broker/DeliveryRecord.h @@ -46,7 +46,7 @@ class DeliveryRecord { QueuedMessage msg; mutable boost::shared_ptr<Queue> queue; - std::string tag; + std::string tag; // name of consumer DeliveryId id; bool acquired : 1; bool acceptExpected : 1; @@ -90,7 +90,7 @@ class DeliveryRecord bool isAcquired() const { return acquired; } bool isComplete() const { return completed; } - bool isRedundant() const { return ended && (!windowing || completed); } + bool isRedundant() const { return ended && (!windowing || completed || cancelled); } bool isCancelled() const { return cancelled; } bool isAccepted() const { return !acceptExpected; } bool isEnded() const { return ended; } diff --git a/cpp/src/qpid/broker/DirectExchange.cpp b/cpp/src/qpid/broker/DirectExchange.cpp index 5b8104c77c..5591539853 100644 --- a/cpp/src/qpid/broker/DirectExchange.cpp +++ b/cpp/src/qpid/broker/DirectExchange.cpp @@ -94,7 +94,7 @@ bool DirectExchange::bind(Queue::shared_ptr queue, const string& routingKey, con propagate = bk.fedBinding.delOrigin(queue->getName(), fedOrigin); if (bk.fedBinding.countFedBindings(queue->getName()) == 0) - unbind(queue, routingKey, 0); + unbind(queue, routingKey, args); } else if (fedOp == fedOpReorigin) { /** gather up all the keys that need rebinding in a local vector @@ -124,20 +124,24 @@ bool DirectExchange::bind(Queue::shared_ptr queue, const string& routingKey, con return true; } -bool DirectExchange::unbind(Queue::shared_ptr queue, const string& routingKey, const FieldTable* /*args*/) +bool DirectExchange::unbind(Queue::shared_ptr queue, const string& routingKey, const FieldTable* args) { + string fedOrigin(args ? args->getAsString(qpidFedOrigin) : ""); bool propagate = false; - QPID_LOG(debug, "Unbind key [" << routingKey << "] from queue " << queue->getName()); - + QPID_LOG(debug, "Unbinding key [" << routingKey << "] from queue " << queue->getName() + << " on exchange " << getName() << " origin=" << fedOrigin << ")" ); { Mutex::ScopedLock l(lock); BoundKey& bk = bindings[routingKey]; if (bk.queues.remove_if(MatchQueue(queue))) { - propagate = bk.fedBinding.delOrigin(); + propagate = bk.fedBinding.delOrigin(queue->getName(), fedOrigin); if (mgmtExchange != 0) { mgmtExchange->dec_bindingCount(); } + if (bk.queues.empty()) { + bindings.erase(routingKey); + } } else { return false; } diff --git a/cpp/src/qpid/broker/DtxAck.cpp b/cpp/src/qpid/broker/DtxAck.cpp index bca3f90bbe..c558681d62 100644 --- a/cpp/src/qpid/broker/DtxAck.cpp +++ b/cpp/src/qpid/broker/DtxAck.cpp @@ -32,6 +32,10 @@ DtxAck::DtxAck(const qpid::framing::SequenceSet& acked, DeliveryRecords& unacked not1(bind2nd(mem_fun_ref(&DeliveryRecord::coveredBy), &acked))); } +DtxAck::DtxAck(DeliveryRecords& unacked) { + pending = unacked; +} + bool DtxAck::prepare(TransactionContext* ctxt) throw() { try{ diff --git a/cpp/src/qpid/broker/DtxAck.h b/cpp/src/qpid/broker/DtxAck.h index 166147e58d..16c3ff8ba0 100644 --- a/cpp/src/qpid/broker/DtxAck.h +++ b/cpp/src/qpid/broker/DtxAck.h @@ -1,3 +1,6 @@ +#ifndef QPID_BROKER_DTXACK_H +#define QPID_BROKER_DTXACK_H + /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -18,9 +21,6 @@ * under the License. * */ -#ifndef _DtxAck_ -#define _DtxAck_ - #include <algorithm> #include <functional> #include <list> @@ -29,20 +29,21 @@ #include "qpid/broker/TxOp.h" namespace qpid { - namespace broker { - class DtxAck : public TxOp{ - DeliveryRecords pending; +namespace broker { +class DtxAck : public TxOp{ + DeliveryRecords pending; - public: - DtxAck(const framing::SequenceSet& acked, DeliveryRecords& unacked); - virtual bool prepare(TransactionContext* ctxt) throw(); - virtual void commit() throw(); - virtual void rollback() throw(); - virtual ~DtxAck(){} - virtual void accept(TxOpConstVisitor& visitor) const { visitor(*this); } - }; - } -} + public: + DtxAck(const framing::SequenceSet& acked, DeliveryRecords& unacked); + DtxAck(DeliveryRecords& unacked); + virtual bool prepare(TransactionContext* ctxt) throw(); + virtual void commit() throw(); + virtual void rollback() throw(); + virtual ~DtxAck(){} + virtual void accept(TxOpConstVisitor& visitor) const { visitor(*this); } + const DeliveryRecords& getPending() const { return pending; } +}; +}} // qpid::broker -#endif +#endif /*!QPID_BROKER_DTXACK_H*/ diff --git a/cpp/src/qpid/broker/DtxBuffer.cpp b/cpp/src/qpid/broker/DtxBuffer.cpp index f1b8169cf7..13177d3b72 100644 --- a/cpp/src/qpid/broker/DtxBuffer.cpp +++ b/cpp/src/qpid/broker/DtxBuffer.cpp @@ -23,8 +23,11 @@ using namespace qpid::broker; using qpid::sys::Mutex; -DtxBuffer::DtxBuffer(const std::string& _xid) - : xid(_xid), ended(false), suspended(false), failed(false), expired(false) {} +DtxBuffer::DtxBuffer( + const std::string& _xid, + bool ended_, bool suspended_, bool failed_, bool expired_) + : xid(_xid), ended(ended_), suspended(suspended_), failed(failed_), expired(expired_) +{} DtxBuffer::~DtxBuffer() {} @@ -34,7 +37,7 @@ void DtxBuffer::markEnded() ended = true; } -bool DtxBuffer::isEnded() +bool DtxBuffer::isEnded() const { Mutex::ScopedLock locker(lock); return ended; @@ -45,7 +48,7 @@ void DtxBuffer::setSuspended(bool isSuspended) suspended = isSuspended; } -bool DtxBuffer::isSuspended() +bool DtxBuffer::isSuspended() const { return suspended; } @@ -58,13 +61,13 @@ void DtxBuffer::fail() ended = true; } -bool DtxBuffer::isRollbackOnly() +bool DtxBuffer::isRollbackOnly() const { Mutex::ScopedLock locker(lock); return failed; } -const std::string& DtxBuffer::getXid() +std::string DtxBuffer::getXid() const { return xid; } @@ -76,8 +79,13 @@ void DtxBuffer::timedout() fail(); } -bool DtxBuffer::isExpired() +bool DtxBuffer::isExpired() const { Mutex::ScopedLock locker(lock); return expired; } + +bool DtxBuffer::isFailed() const +{ + return failed; +} diff --git a/cpp/src/qpid/broker/DtxBuffer.h b/cpp/src/qpid/broker/DtxBuffer.h index 1511cb032f..cabd37647a 100644 --- a/cpp/src/qpid/broker/DtxBuffer.h +++ b/cpp/src/qpid/broker/DtxBuffer.h @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -26,31 +26,34 @@ #include "qpid/sys/Mutex.h" namespace qpid { - namespace broker { - class DtxBuffer : public TxBuffer{ - sys::Mutex lock; - const std::string xid; - bool ended; - bool suspended; - bool failed; - bool expired; +namespace broker { +class DtxBuffer : public TxBuffer{ + mutable sys::Mutex lock; + const std::string xid; + bool ended; + bool suspended; + bool failed; + bool expired; - public: - typedef boost::shared_ptr<DtxBuffer> shared_ptr; + public: + typedef boost::shared_ptr<DtxBuffer> shared_ptr; - QPID_BROKER_EXTERN DtxBuffer(const std::string& xid = ""); - QPID_BROKER_EXTERN ~DtxBuffer(); - QPID_BROKER_EXTERN void markEnded(); - bool isEnded(); - void setSuspended(bool suspended); - bool isSuspended(); - void fail(); - bool isRollbackOnly(); - void timedout(); - bool isExpired(); - const std::string& getXid(); - }; - } + QPID_BROKER_EXTERN DtxBuffer( + const std::string& xid = "", + bool ended=false, bool suspended=false, bool failed=false, bool expired=false); + QPID_BROKER_EXTERN ~DtxBuffer(); + QPID_BROKER_EXTERN void markEnded(); + bool isEnded() const; + void setSuspended(bool suspended); + bool isSuspended() const; + void fail(); + bool isRollbackOnly() const; + void timedout(); + bool isExpired() const; + bool isFailed() const; + std::string getXid() const; +}; +} } diff --git a/cpp/src/qpid/broker/DtxManager.cpp b/cpp/src/qpid/broker/DtxManager.cpp index 3caa41c3f4..febd547478 100644 --- a/cpp/src/qpid/broker/DtxManager.cpp +++ b/cpp/src/qpid/broker/DtxManager.cpp @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -34,7 +34,7 @@ using qpid::ptr_map_ptr; using namespace qpid::broker; using namespace qpid::framing; -DtxManager::DtxManager(qpid::sys::Timer& t) : store(0), timer(t) {} +DtxManager::DtxManager(qpid::sys::Timer& t) : store(0), timer(&t) {} DtxManager::~DtxManager() {} @@ -53,8 +53,8 @@ void DtxManager::recover(const std::string& xid, std::auto_ptr<TPCTransactionCon createWork(xid)->recover(txn, ops); } -bool DtxManager::prepare(const std::string& xid) -{ +bool DtxManager::prepare(const std::string& xid) +{ QPID_LOG(debug, "preparing: " << xid); try { return getWork(xid)->prepare(); @@ -64,8 +64,8 @@ bool DtxManager::prepare(const std::string& xid) } } -bool DtxManager::commit(const std::string& xid, bool onePhase) -{ +bool DtxManager::commit(const std::string& xid, bool onePhase) +{ QPID_LOG(debug, "committing: " << xid); try { bool result = getWork(xid)->commit(onePhase); @@ -77,8 +77,8 @@ bool DtxManager::commit(const std::string& xid, bool onePhase) } } -void DtxManager::rollback(const std::string& xid) -{ +void DtxManager::rollback(const std::string& xid) +{ QPID_LOG(debug, "rolling back: " << xid); try { getWork(xid)->rollback(); @@ -91,7 +91,7 @@ void DtxManager::rollback(const std::string& xid) DtxWorkRecord* DtxManager::getWork(const std::string& xid) { - Mutex::ScopedLock locker(lock); + Mutex::ScopedLock locker(lock); WorkMap::iterator i = work.find(xid); if (i == work.end()) { throw NotFoundException(QPID_MSG("Unrecognised xid " << xid)); @@ -99,9 +99,14 @@ DtxWorkRecord* DtxManager::getWork(const std::string& xid) return ptr_map_ptr(i); } +bool DtxManager::exists(const std::string& xid) { + Mutex::ScopedLock locker(lock); + return work.find(xid) != work.end(); +} + void DtxManager::remove(const std::string& xid) { - Mutex::ScopedLock locker(lock); + Mutex::ScopedLock locker(lock); WorkMap::iterator i = work.find(xid); if (i == work.end()) { throw NotFoundException(QPID_MSG("Unrecognised xid " << xid)); @@ -110,14 +115,15 @@ void DtxManager::remove(const std::string& xid) } } -DtxWorkRecord* DtxManager::createWork(std::string xid) +DtxWorkRecord* DtxManager::createWork(const std::string& xid) { - Mutex::ScopedLock locker(lock); + Mutex::ScopedLock locker(lock); WorkMap::iterator i = work.find(xid); if (i != work.end()) { throw NotAllowedException(QPID_MSG("Xid " << xid << " is already known (use 'join' to add work to an existing xid)")); } else { - return ptr_map_ptr(work.insert(xid, new DtxWorkRecord(xid, store)).first); + std::string ncxid = xid; // Work around const correctness problems in ptr_map. + return ptr_map_ptr(work.insert(ncxid, new DtxWorkRecord(ncxid, store)).first); } } @@ -131,7 +137,7 @@ void DtxManager::setTimeout(const std::string& xid, uint32_t secs) } timeout = intrusive_ptr<DtxTimeout>(new DtxTimeout(secs, *this, xid)); record->setTimeout(timeout); - timer.add(timeout); + timer->add(timeout); } uint32_t DtxManager::getTimeout(const std::string& xid) @@ -142,7 +148,7 @@ uint32_t DtxManager::getTimeout(const std::string& xid) void DtxManager::timedout(const std::string& xid) { - Mutex::ScopedLock locker(lock); + Mutex::ScopedLock locker(lock); WorkMap::iterator i = work.find(xid); if (i == work.end()) { QPID_LOG(warning, "Transaction timeout failed: no record for xid"); @@ -153,7 +159,7 @@ void DtxManager::timedout(const std::string& xid) } } -DtxManager::DtxCleanup::DtxCleanup(uint32_t _timeout, DtxManager& _mgr, const std::string& _xid) +DtxManager::DtxCleanup::DtxCleanup(uint32_t _timeout, DtxManager& _mgr, const std::string& _xid) : TimerTask(qpid::sys::Duration(_timeout * qpid::sys::TIME_SEC),"DtxCleanup"), mgr(_mgr), xid(_xid) {} void DtxManager::DtxCleanup::fire() diff --git a/cpp/src/qpid/broker/DtxManager.h b/cpp/src/qpid/broker/DtxManager.h index 680b62eeb2..11895695a3 100644 --- a/cpp/src/qpid/broker/DtxManager.h +++ b/cpp/src/qpid/broker/DtxManager.h @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -26,8 +26,8 @@ #include "qpid/broker/DtxWorkRecord.h" #include "qpid/broker/TransactionalStore.h" #include "qpid/framing/amqp_types.h" -#include "qpid/sys/Timer.h" #include "qpid/sys/Mutex.h" +#include "qpid/ptr_map.h" namespace qpid { namespace broker { @@ -39,22 +39,21 @@ class DtxManager{ { DtxManager& mgr; const std::string& xid; - - DtxCleanup(uint32_t timeout, DtxManager& mgr, const std::string& xid); + + DtxCleanup(uint32_t timeout, DtxManager& mgr, const std::string& xid); void fire(); }; WorkMap work; TransactionalStore* store; qpid::sys::Mutex lock; - qpid::sys::Timer& timer; + qpid::sys::Timer* timer; void remove(const std::string& xid); - DtxWorkRecord* getWork(const std::string& xid); - DtxWorkRecord* createWork(std::string xid); + DtxWorkRecord* createWork(const std::string& xid); public: - DtxManager(qpid::sys::Timer&); + DtxManager(sys::Timer&); ~DtxManager(); void start(const std::string& xid, DtxBuffer::shared_ptr work); void join(const std::string& xid, DtxBuffer::shared_ptr work); @@ -66,6 +65,15 @@ public: uint32_t getTimeout(const std::string& xid); void timedout(const std::string& xid); void setStore(TransactionalStore* store); + void setTimer(sys::Timer& t) { timer = &t; } + + // Used by cluster for replication. + template<class F> void each(F f) const { + for (WorkMap::const_iterator i = work.begin(); i != work.end(); ++i) + f(*ptr_map_ptr(i)); + } + DtxWorkRecord* getWork(const std::string& xid); + bool exists(const std::string& xid); }; } diff --git a/cpp/src/qpid/broker/DtxTimeout.cpp b/cpp/src/qpid/broker/DtxTimeout.cpp index c4c52ec40a..58700846ef 100644 --- a/cpp/src/qpid/broker/DtxTimeout.cpp +++ b/cpp/src/qpid/broker/DtxTimeout.cpp @@ -25,7 +25,7 @@ using namespace qpid::broker; DtxTimeout::DtxTimeout(uint32_t _timeout, DtxManager& _mgr, const std::string& _xid) - : TimerTask(qpid::sys::Duration(_timeout * qpid::sys::TIME_SEC),"DtxTimeout"), timeout(_timeout), mgr(_mgr), xid(_xid) + : TimerTask(qpid::sys::Duration(_timeout * qpid::sys::TIME_SEC),"DtxTimeout-"+_xid), timeout(_timeout), mgr(_mgr), xid(_xid) { } diff --git a/cpp/src/qpid/broker/DtxTimeout.h b/cpp/src/qpid/broker/DtxTimeout.h index 680a210e4f..1fcb4cee2a 100644 --- a/cpp/src/qpid/broker/DtxTimeout.h +++ b/cpp/src/qpid/broker/DtxTimeout.h @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -29,7 +29,9 @@ namespace broker { class DtxManager; -struct DtxTimeoutException : public Exception {}; +struct DtxTimeoutException : public Exception { + DtxTimeoutException(const std::string& msg=std::string()) : Exception(msg) {} +}; struct DtxTimeout : public sys::TimerTask { @@ -37,7 +39,7 @@ struct DtxTimeout : public sys::TimerTask DtxManager& mgr; const std::string xid; - DtxTimeout(uint32_t timeout, DtxManager& mgr, const std::string& xid); + DtxTimeout(uint32_t timeout, DtxManager& mgr, const std::string& xid); void fire(); }; diff --git a/cpp/src/qpid/broker/DtxWorkRecord.cpp b/cpp/src/qpid/broker/DtxWorkRecord.cpp index 9f33e698db..a413fe418d 100644 --- a/cpp/src/qpid/broker/DtxWorkRecord.cpp +++ b/cpp/src/qpid/broker/DtxWorkRecord.cpp @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -28,19 +28,19 @@ using qpid::sys::Mutex; using namespace qpid::broker; using namespace qpid::framing; -DtxWorkRecord::DtxWorkRecord(const std::string& _xid, TransactionalStore* const _store) : +DtxWorkRecord::DtxWorkRecord(const std::string& _xid, TransactionalStore* const _store) : xid(_xid), store(_store), completed(false), rolledback(false), prepared(false), expired(false) {} -DtxWorkRecord::~DtxWorkRecord() +DtxWorkRecord::~DtxWorkRecord() { - if (timeout.get()) { + if (timeout.get()) { timeout->cancel(); } } bool DtxWorkRecord::prepare() { - Mutex::ScopedLock locker(lock); + Mutex::ScopedLock locker(lock); if (check()) { txn = store->begin(xid); if (prepare(txn.get())) { @@ -68,7 +68,7 @@ bool DtxWorkRecord::prepare(TransactionContext* _txn) bool DtxWorkRecord::commit(bool onePhase) { - Mutex::ScopedLock locker(lock); + Mutex::ScopedLock locker(lock); if (check()) { if (prepared) { //already prepared i.e. 2pc @@ -78,13 +78,13 @@ bool DtxWorkRecord::commit(bool onePhase) store->commit(*txn); txn.reset(); - + std::for_each(work.begin(), work.end(), mem_fn(&TxBuffer::commit)); return true; } else { //1pc commit optimisation, don't need a 2pc transaction context: if (!onePhase) { - throw IllegalStateException(QPID_MSG("Branch with xid " << xid << " has not been prepared, one-phase option required!")); + throw IllegalStateException(QPID_MSG("Branch with xid " << xid << " has not been prepared, one-phase option required!")); } std::auto_ptr<TransactionContext> localtxn = store->begin(); if (prepare(localtxn.get())) { @@ -107,16 +107,16 @@ bool DtxWorkRecord::commit(bool onePhase) void DtxWorkRecord::rollback() { - Mutex::ScopedLock locker(lock); + Mutex::ScopedLock locker(lock); check(); abort(); } void DtxWorkRecord::add(DtxBuffer::shared_ptr ops) { - Mutex::ScopedLock locker(lock); + Mutex::ScopedLock locker(lock); if (expired) { - throw DtxTimeoutException(); + throw DtxTimeoutException(QPID_MSG("Branch with xid " << xid << " has timed out.")); } if (completed) { throw CommandInvalidException(QPID_MSG("Branch with xid " << xid << " has been completed!")); @@ -163,7 +163,7 @@ void DtxWorkRecord::recover(std::auto_ptr<TPCTransactionContext> _txn, DtxBuffer void DtxWorkRecord::timedout() { - Mutex::ScopedLock locker(lock); + Mutex::ScopedLock locker(lock); expired = true; rolledback = true; if (!completed) { @@ -175,3 +175,17 @@ void DtxWorkRecord::timedout() } abort(); } + +size_t DtxWorkRecord::indexOf(const DtxBuffer::shared_ptr& buf) { + Work::iterator i = std::find(work.begin(), work.end(), buf); + if (i == work.end()) throw NotFoundException( + QPID_MSG("Can't find DTX buffer for xid: " << buf->getXid())); + return i - work.begin(); +} + +DtxBuffer::shared_ptr DtxWorkRecord::operator[](size_t i) const { + if (i > work.size()) + throw NotFoundException( + QPID_MSG("Can't find DTX buffer " << i << " for xid: " << xid)); + return work[i]; +} diff --git a/cpp/src/qpid/broker/DtxWorkRecord.h b/cpp/src/qpid/broker/DtxWorkRecord.h index aec2d2aed4..331e42fefd 100644 --- a/cpp/src/qpid/broker/DtxWorkRecord.h +++ b/cpp/src/qpid/broker/DtxWorkRecord.h @@ -73,9 +73,19 @@ public: void timedout(); void setTimeout(boost::intrusive_ptr<DtxTimeout> t) { timeout = t; } boost::intrusive_ptr<DtxTimeout> getTimeout() { return timeout; } + std::string getXid() const { return xid; } + bool isCompleted() const { return completed; } + bool isRolledback() const { return rolledback; } + bool isPrepared() const { return prepared; } + bool isExpired() const { return expired; } + + // Used by cluster update; + size_t size() const { return work.size(); } + DtxBuffer::shared_ptr operator[](size_t i) const; + uint32_t getTimeout() const { return timeout? timeout->timeout : 0; } + size_t indexOf(const DtxBuffer::shared_ptr&); }; -} -} +}} // qpid::broker #endif diff --git a/cpp/src/qpid/broker/Exchange.cpp b/cpp/src/qpid/broker/Exchange.cpp index d143471559..d68845062d 100644 --- a/cpp/src/qpid/broker/Exchange.cpp +++ b/cpp/src/qpid/broker/Exchange.cpp @@ -19,16 +19,18 @@ * */ +#include "qpid/broker/Broker.h" +#include "qpid/broker/DeliverableMessage.h" #include "qpid/broker/Exchange.h" #include "qpid/broker/ExchangeRegistry.h" #include "qpid/broker/FedOps.h" -#include "qpid/broker/Broker.h" -#include "qpid/management/ManagementAgent.h" #include "qpid/broker/Queue.h" -#include "qpid/log/Statement.h" #include "qpid/framing/MessageProperties.h" #include "qpid/framing/reply_exceptions.h" -#include "qpid/broker/DeliverableMessage.h" +#include "qpid/log/Statement.h" +#include "qpid/management/ManagementAgent.h" +#include "qpid/sys/ExceptionHolder.h" +#include <stdexcept> using namespace qpid::broker; using namespace qpid::framing; @@ -56,7 +58,7 @@ Exchange::PreRoute::PreRoute(Deliverable& msg, Exchange* _p):parent(_p) { if (parent->sequence){ parent->sequenceNo++; - msg.getMessage().getProperties<MessageProperties>()->getApplicationHeaders().setInt64(qpidMsgSequence,parent->sequenceNo); + msg.getMessage().insertCustomProperty(qpidMsgSequence,parent->sequenceNo); } if (parent->ive) { parent->lastMsg = &( msg.getMessage()); @@ -70,6 +72,36 @@ Exchange::PreRoute::~PreRoute(){ } } +namespace { +/** Store information about an exception to be thrown later. + * If multiple exceptions are stored, save the first of the "most severe" + * exceptions, SESSION is les sever than CONNECTION etc. + */ +class ExInfo { + public: + enum Type { NONE, SESSION, CONNECTION, OTHER }; + + ExInfo(string exchange) : type(NONE), exchange(exchange) {} + void store(Type type_, const qpid::sys::ExceptionHolder& exception_, const boost::shared_ptr<Queue>& queue) { + QPID_LOG(warning, "Exchange " << exchange << " cannot deliver to queue " + << queue->getName() << ": " << exception_.what()); + if (type < type_) { // Replace less severe exception + type = type_; + exception = exception_; + } + } + + void raise() { + exception.raise(); + } + + private: + Type type; + string exchange; + qpid::sys::ExceptionHolder exception; +}; +} + void Exchange::doRoute(Deliverable& msg, ConstBindingList b) { int count = 0; @@ -80,11 +112,25 @@ void Exchange::doRoute(Deliverable& msg, ConstBindingList b) msg.getMessage().blockContentRelease(); } + + ExInfo error(getName()); // Save exception to throw at the end. for(std::vector<Binding::shared_ptr>::const_iterator i = b->begin(); i != b->end(); i++, count++) { - msg.deliverTo((*i)->queue); - if ((*i)->mgmtBinding != 0) - (*i)->mgmtBinding->inc_msgMatched(); + try { + msg.deliverTo((*i)->queue); + if ((*i)->mgmtBinding != 0) + (*i)->mgmtBinding->inc_msgMatched(); + } + catch (const SessionException& e) { + error.store(ExInfo::SESSION, framing::createSessionException(e.code, e.what()),(*i)->queue); + } + catch (const ConnectionException& e) { + error.store(ExInfo::CONNECTION, framing::createConnectionException(e.code, e.what()), (*i)->queue); + } + catch (const std::exception& e) { + error.store(ExInfo::OTHER, qpid::sys::ExceptionHolder(new Exception(e.what())), (*i)->queue); + } } + error.raise(); } if (mgmtExchange != 0) @@ -115,7 +161,7 @@ void Exchange::routeIVE(){ Exchange::Exchange (const string& _name, Manageable* parent, Broker* b) : name(_name), durable(false), persistenceId(0), sequence(false), - sequenceNo(0), ive(false), mgmtExchange(0), broker(b) + sequenceNo(0), ive(false), mgmtExchange(0), broker(b), destroyed(false) { if (parent != 0 && broker != 0) { @@ -133,7 +179,7 @@ Exchange::Exchange (const string& _name, Manageable* parent, Broker* b) : Exchange::Exchange(const string& _name, bool _durable, const qpid::framing::FieldTable& _args, Manageable* parent, Broker* b) : name(_name), durable(_durable), alternateUsers(0), persistenceId(0), - args(_args), sequence(false), sequenceNo(0), ive(false), mgmtExchange(0), broker(b) + args(_args), sequence(false), sequenceNo(0), ive(false), mgmtExchange(0), broker(b), destroyed(false) { if (parent != 0 && broker != 0) { @@ -155,7 +201,11 @@ Exchange::Exchange(const string& _name, bool _durable, const qpid::framing::Fiel } ive = _args.get(qpidIVE); - if (ive) QPID_LOG(debug, "Configured exchange " << _name << " with Initial Value"); + if (ive) { + if (broker && broker->isInCluster()) + throw framing::NotImplementedException("Cannot use Initial Value Exchanges in a cluster"); + QPID_LOG(debug, "Configured exchange " << _name << " with Initial Value"); + } } Exchange::~Exchange () @@ -340,5 +390,14 @@ bool Exchange::MatchQueue::operator()(Exchange::Binding::shared_ptr b) } void Exchange::setProperties(const boost::intrusive_ptr<Message>& msg) { - msg->getProperties<DeliveryProperties>()->setExchange(getName()); + msg->setExchange(getName()); +} + +bool Exchange::routeWithAlternate(Deliverable& msg) +{ + route(msg, msg.getMessage().getRoutingKey(), msg.getMessage().getApplicationHeaders()); + if (!msg.delivered && alternate) { + alternate->route(msg, msg.getMessage().getRoutingKey(), msg.getMessage().getApplicationHeaders()); + } + return msg.delivered; } diff --git a/cpp/src/qpid/broker/Exchange.h b/cpp/src/qpid/broker/Exchange.h index 3c8b5ca2cd..b12af9a1dd 100644 --- a/cpp/src/qpid/broker/Exchange.h +++ b/cpp/src/qpid/broker/Exchange.h @@ -10,9 +10,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -39,7 +39,7 @@ namespace broker { class Broker; class ExchangeRegistry; -class Exchange : public PersistableExchange, public management::Manageable { +class QPID_BROKER_CLASS_EXTERN Exchange : public PersistableExchange, public management::Manageable { public: struct Binding : public management::Manageable { typedef boost::shared_ptr<Binding> shared_ptr; @@ -82,15 +82,15 @@ protected: private: Exchange* parent; }; - + typedef boost::shared_ptr<const std::vector<boost::shared_ptr<qpid::broker::Exchange::Binding> > > ConstBindingList; typedef boost::shared_ptr< std::vector<boost::shared_ptr<qpid::broker::Exchange::Binding> > > BindingList; void doRoute(Deliverable& msg, ConstBindingList b); void routeIVE(); - + struct MatchQueue { - const boost::shared_ptr<Queue> queue; + const boost::shared_ptr<Queue> queue; MatchQueue(boost::shared_ptr<Queue> q); bool operator()(Exchange::Binding::shared_ptr b); }; @@ -133,15 +133,15 @@ protected: /** Returns true if propagation is needed. */ bool delOrigin(const std::string& queueName, const std::string& origin){ - fedBindings[queueName].erase(origin); - return true; - } - - /** Returns true if propagation is needed. */ - bool delOrigin() { - if (localBindings > 0) - localBindings--; - return localBindings == 0; + if (origin.empty()) { // no remote == local binding + if (localBindings > 0) + localBindings--; + return localBindings == 0; + } + size_t match = fedBindings[queueName].erase(origin); + if (fedBindings[queueName].empty()) + fedBindings.erase(queueName); + return match != 0; } uint32_t count() { @@ -149,7 +149,11 @@ protected: } uint32_t countFedBindings(const std::string& queueName) { - return fedBindings[queueName].size(); + // don't use '[]' - it may increase size of fedBindings! + std::map<std::string, originSet>::iterator i; + if ((i = fedBindings.find(queueName)) != fedBindings.end()) + return i->second.size(); + return 0; } }; @@ -162,7 +166,7 @@ public: Broker* broker = 0); QPID_BROKER_EXTERN Exchange(const std::string& _name, bool _durable, const qpid::framing::FieldTable& _args, management::Manageable* parent = 0, Broker* broker = 0); - QPID_BROKER_EXTERN virtual ~Exchange(); + QPID_BROKER_INLINE_EXTERN virtual ~Exchange(); const std::string& getName() const { return name; } bool isDurable() { return durable; } @@ -191,7 +195,7 @@ public: virtual bool isBound(boost::shared_ptr<Queue> queue, const std::string* const routingKey, const qpid::framing::FieldTable* const args) = 0; QPID_BROKER_EXTERN virtual void setProperties(const boost::intrusive_ptr<Message>&); virtual void route(Deliverable& msg, const std::string& routingKey, const qpid::framing::FieldTable* args) = 0; - + //PersistableExchange: QPID_BROKER_EXTERN void setPersistenceId(uint64_t id) const; uint64_t getPersistenceId() const { return persistenceId; } @@ -222,14 +226,20 @@ public: */ void recoveryComplete(ExchangeRegistry& exchanges); + bool routeWithAlternate(Deliverable& message); + + void destroy() { destroyed = true; } + bool isDestroyed() const { return destroyed; } + protected: qpid::sys::Mutex bridgeLock; std::vector<DynamicBridge*> bridgeVector; Broker* broker; + bool destroyed; QPID_BROKER_EXTERN virtual void handleHelloRequest(); void propagateFedOp(const std::string& routingKey, const std::string& tags, - const std::string& op, const std::string& origin, + const std::string& op, const std::string& origin, qpid::framing::FieldTable* extra_args=0); }; diff --git a/cpp/src/qpid/broker/ExchangeRegistry.cpp b/cpp/src/qpid/broker/ExchangeRegistry.cpp index 99b121cbce..1c8d26c4f7 100644 --- a/cpp/src/qpid/broker/ExchangeRegistry.cpp +++ b/cpp/src/qpid/broker/ExchangeRegistry.cpp @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -39,7 +39,7 @@ pair<Exchange::shared_ptr, bool> ExchangeRegistry::declare(const string& name, c return declare(name, type, false, FieldTable()); } -pair<Exchange::shared_ptr, bool> ExchangeRegistry::declare(const string& name, const string& type, +pair<Exchange::shared_ptr, bool> ExchangeRegistry::declare(const string& name, const string& type, bool durable, const FieldTable& args){ RWlock::ScopedWlock locker(lock); ExchangeMap::iterator i = exchanges.find(name); @@ -61,7 +61,7 @@ pair<Exchange::shared_ptr, bool> ExchangeRegistry::declare(const string& name, c }else{ FunctionMap::iterator i = factory.find(type); if (i == factory.end()) { - throw UnknownExchangeTypeException(); + throw UnknownExchangeTypeException(); } else { exchange = i->second(name, durable, args, parent, broker); } @@ -82,6 +82,7 @@ void ExchangeRegistry::destroy(const string& name){ RWlock::ScopedWlock locker(lock); ExchangeMap::iterator i = exchanges.find(name); if (i != exchanges.end()) { + i->second->destroy(); exchanges.erase(i); } } @@ -104,7 +105,7 @@ void ExchangeRegistry::registerType(const std::string& type, FactoryFunction f) } -namespace +namespace { const std::string empty; } diff --git a/cpp/src/qpid/broker/ExpiryPolicy.cpp b/cpp/src/qpid/broker/ExpiryPolicy.cpp index 64a12d918a..62cb3fc116 100644 --- a/cpp/src/qpid/broker/ExpiryPolicy.cpp +++ b/cpp/src/qpid/broker/ExpiryPolicy.cpp @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -27,12 +27,12 @@ namespace broker { ExpiryPolicy::~ExpiryPolicy() {} -void ExpiryPolicy::willExpire(Message&) {} - bool ExpiryPolicy::hasExpired(Message& m) { return m.getExpiration() < sys::AbsTime::now(); } -void ExpiryPolicy::forget(Message&) {} +sys::AbsTime ExpiryPolicy::getCurrentTime() { + return sys::AbsTime::now(); +} }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/ExpiryPolicy.h b/cpp/src/qpid/broker/ExpiryPolicy.h index 40e793bf2c..2caf00ce00 100644 --- a/cpp/src/qpid/broker/ExpiryPolicy.h +++ b/cpp/src/qpid/broker/ExpiryPolicy.h @@ -10,9 +10,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -26,6 +26,11 @@ #include "qpid/broker/BrokerImportExport.h" namespace qpid { + +namespace sys { +class AbsTime; +} + namespace broker { class Message; @@ -33,13 +38,12 @@ class Message; /** * Default expiry policy. */ -class ExpiryPolicy : public RefCounted +class QPID_BROKER_CLASS_EXTERN ExpiryPolicy : public RefCounted { public: QPID_BROKER_EXTERN virtual ~ExpiryPolicy(); - QPID_BROKER_EXTERN virtual void willExpire(Message&); QPID_BROKER_EXTERN virtual bool hasExpired(Message&); - QPID_BROKER_EXTERN virtual void forget(Message&); + QPID_BROKER_EXTERN virtual qpid::sys::AbsTime getCurrentTime(); }; }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/Fairshare.cpp b/cpp/src/qpid/broker/Fairshare.cpp index e6bbf86691..313aa746f1 100644 --- a/cpp/src/qpid/broker/Fairshare.cpp +++ b/cpp/src/qpid/broker/Fairshare.cpp @@ -24,6 +24,7 @@ #include "qpid/log/Statement.h" #include <boost/format.hpp> #include <boost/lexical_cast.hpp> +#include <boost/assign/list_of.hpp> namespace qpid { namespace broker { @@ -104,51 +105,80 @@ bool Fairshare::setState(Messages& m, uint priority, uint count) return fairshare && fairshare->setState(priority, count); } -int getIntegerSetting(const qpid::framing::FieldTable& settings, const std::string& key) +int getIntegerSetting(const qpid::framing::FieldTable& settings, const std::vector<std::string>& keys) { - qpid::framing::FieldTable::ValuePtr v = settings.get(key); + qpid::framing::FieldTable::ValuePtr v; + std::vector<std::string>::const_iterator i = keys.begin(); + while (!v && i != keys.end()) { + v = settings.get(*i++); + } + if (!v) { return 0; } else if (v->convertsTo<int>()) { return v->get<int>(); } else if (v->convertsTo<std::string>()){ std::string s = v->get<std::string>(); - try { - return boost::lexical_cast<int>(s); + try { + return boost::lexical_cast<int>(s); } catch(const boost::bad_lexical_cast&) { - QPID_LOG(warning, "Ignoring invalid integer value for " << key << ": " << s); + QPID_LOG(warning, "Ignoring invalid integer value for " << *i << ": " << s); return 0; } } else { - QPID_LOG(warning, "Ignoring invalid integer value for " << key << ": " << *v); + QPID_LOG(warning, "Ignoring invalid integer value for " << *i << ": " << *v); return 0; } } -int getSetting(const qpid::framing::FieldTable& settings, const std::string& key, int minvalue, int maxvalue) +int getIntegerSettingForKey(const qpid::framing::FieldTable& settings, const std::string& key) +{ + return getIntegerSetting(settings, boost::assign::list_of<std::string>(key)); +} + +int getSetting(const qpid::framing::FieldTable& settings, const std::vector<std::string>& keys, int minvalue, int maxvalue) +{ + return std::max(minvalue,std::min(getIntegerSetting(settings, keys), maxvalue)); +} + +std::auto_ptr<Fairshare> getFairshareForKey(const qpid::framing::FieldTable& settings, uint levels, const std::string& key) +{ + uint defaultLimit = getIntegerSettingForKey(settings, key); + std::auto_ptr<Fairshare> fairshare(new Fairshare(levels, defaultLimit)); + for (uint i = 0; i < levels; i++) { + std::string levelKey = (boost::format("%1%-%2%") % key % i).str(); + if(settings.isSet(levelKey)) { + fairshare->setLimit(i, getIntegerSettingForKey(settings, levelKey)); + } + } + if (!fairshare->isNull()) { + return fairshare; + } else { + return std::auto_ptr<Fairshare>(); + } +} + +std::auto_ptr<Fairshare> getFairshare(const qpid::framing::FieldTable& settings, + uint levels, + const std::vector<std::string>& keys) { - return std::max(minvalue,std::min(getIntegerSetting(settings, key), maxvalue)); + std::auto_ptr<Fairshare> fairshare; + for (std::vector<std::string>::const_iterator i = keys.begin(); i != keys.end() && !fairshare.get(); ++i) { + fairshare = getFairshareForKey(settings, levels, *i); + } + return fairshare; } std::auto_ptr<Messages> Fairshare::create(const qpid::framing::FieldTable& settings) { + using boost::assign::list_of; std::auto_ptr<Messages> result; - size_t levels = getSetting(settings, "x-qpid-priorities", 1, 100); + size_t levels = getSetting(settings, list_of<std::string>("qpid.priorities")("x-qpid-priorities"), 0, 100); if (levels) { - uint defaultLimit = getIntegerSetting(settings, "x-qpid-fairshare"); - std::auto_ptr<Fairshare> fairshare(new Fairshare(levels, defaultLimit)); - for (uint i = 0; i < levels; i++) { - std::string key = (boost::format("x-qpid-fairshare-%1%") % i).str(); - if(settings.isSet(key)) { - fairshare->setLimit(i, getIntegerSetting(settings, key)); - } - } - - if (fairshare->isNull()) { - result = std::auto_ptr<Messages>(new PriorityQueue(levels)); - } else { - result = fairshare; - } + std::auto_ptr<Fairshare> fairshare = + getFairshare(settings, levels, list_of<std::string>("qpid.fairshare")("x-qpid-fairshare")); + if (fairshare.get()) result = fairshare; + else result = std::auto_ptr<Messages>(new PriorityQueue(levels)); } return result; } diff --git a/cpp/src/qpid/broker/Fairshare.h b/cpp/src/qpid/broker/Fairshare.h index 6c4b87f857..1b25721e0c 100644 --- a/cpp/src/qpid/broker/Fairshare.h +++ b/cpp/src/qpid/broker/Fairshare.h @@ -41,18 +41,18 @@ class Fairshare : public PriorityQueue bool getState(uint& priority, uint& count) const; bool setState(uint priority, uint count); void setLimit(size_t level, uint limit); + bool isNull(); static std::auto_ptr<Messages> create(const qpid::framing::FieldTable& settings); static bool getState(const Messages&, uint& priority, uint& count); static bool setState(Messages&, uint priority, uint count); private: std::vector<uint> limits; - + uint priority; uint count; - + uint currentLevel(); uint nextLevel(); - bool isNull(); bool limitReached(); bool findFrontLevel(uint& p, PriorityLevels&); }; diff --git a/cpp/src/qpid/broker/FanOutExchange.cpp b/cpp/src/qpid/broker/FanOutExchange.cpp index ac2c914a97..5879fa0892 100644 --- a/cpp/src/qpid/broker/FanOutExchange.cpp +++ b/cpp/src/qpid/broker/FanOutExchange.cpp @@ -18,6 +18,7 @@ * under the License. * */ +#include "qpid/log/Statement.h" #include "qpid/broker/FanOutExchange.h" #include "qpid/broker/FedOps.h" #include <algorithm> @@ -65,7 +66,7 @@ bool FanOutExchange::bind(Queue::shared_ptr queue, const string& /*key*/, const } else if (fedOp == fedOpUnbind) { propagate = fedBinding.delOrigin(queue->getName(), fedOrigin); if (fedBinding.countFedBindings(queue->getName()) == 0) - unbind(queue, "", 0); + unbind(queue, "", args); } else if (fedOp == fedOpReorigin) { if (fedBinding.hasLocal()) { propagateFedOp(string(), string(), fedOpBind, string()); @@ -78,12 +79,16 @@ bool FanOutExchange::bind(Queue::shared_ptr queue, const string& /*key*/, const return true; } -bool FanOutExchange::unbind(Queue::shared_ptr queue, const string& /*key*/, const FieldTable* /*args*/) +bool FanOutExchange::unbind(Queue::shared_ptr queue, const string& /*key*/, const FieldTable* args) { + string fedOrigin(args ? args->getAsString(qpidFedOrigin) : ""); bool propagate = false; + QPID_LOG(debug, "Unbinding queue " << queue->getName() + << " from exchange " << getName() << " origin=" << fedOrigin << ")" ); + if (bindings.remove_if(MatchQueue(queue))) { - propagate = fedBinding.delOrigin(); + propagate = fedBinding.delOrigin(queue->getName(), fedOrigin); if (mgmtExchange != 0) { mgmtExchange->dec_bindingCount(); } diff --git a/cpp/src/qpid/broker/FifoDistributor.cpp b/cpp/src/qpid/broker/FifoDistributor.cpp new file mode 100644 index 0000000000..cdb32d8c8c --- /dev/null +++ b/cpp/src/qpid/broker/FifoDistributor.cpp @@ -0,0 +1,58 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + + +#include "qpid/broker/Queue.h" +#include "qpid/broker/FifoDistributor.h" + +using namespace qpid::broker; + +FifoDistributor::FifoDistributor(Messages& container) + : messages(container) {} + +bool FifoDistributor::nextConsumableMessage( Consumer::shared_ptr&, QueuedMessage& next ) +{ + if (!messages.empty()) { + next = messages.front(); // by default, consume oldest msg + return true; + } + return false; +} + +bool FifoDistributor::allocate(const std::string&, const QueuedMessage& ) +{ + // by default, all messages present on the queue may be allocated as they have yet to + // be acquired. + return true; +} + +bool FifoDistributor::nextBrowsableMessage( Consumer::shared_ptr& c, QueuedMessage& next ) +{ + if (!messages.empty() && messages.next(c->position, next)) + return true; + return false; +} + +void FifoDistributor::query(qpid::types::Variant::Map&) const +{ + // nothing to see here.... +} + diff --git a/cpp/src/qpid/broker/IncompleteMessageList.h b/cpp/src/qpid/broker/FifoDistributor.h index a4debd1233..245537ed12 100644 --- a/cpp/src/qpid/broker/IncompleteMessageList.h +++ b/cpp/src/qpid/broker/FifoDistributor.h @@ -1,3 +1,6 @@ +#ifndef _broker_FifoDistributor_h +#define _broker_FifoDistributor_h + /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -7,9 +10,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -18,41 +21,38 @@ * under the License. * */ -#ifndef _IncompleteMessageList_ -#define _IncompleteMessageList_ -#include "qpid/broker/BrokerImportExport.h" -#include "qpid/sys/Monitor.h" -#include "qpid/broker/Message.h" -#include <boost/intrusive_ptr.hpp> -#include <boost/function.hpp> -#include <list> +/** Simple MessageDistributor for FIFO Queues - the HEAD message is always the next + * available message for consumption. + */ + +#include "qpid/broker/MessageDistributor.h" namespace qpid { namespace broker { -class IncompleteMessageList +class Messages; + +class FifoDistributor : public MessageDistributor { - typedef std::list< boost::intrusive_ptr<Message> > Messages; + public: + FifoDistributor(Messages& container); - void enqueueComplete(const boost::intrusive_ptr<Message>&); + /** Locking Note: all methods assume the caller is holding the Queue::messageLock + * during the method call. + */ - sys::Monitor lock; - Messages incomplete; - Message::MessageCallback callback; + /** MessageDistributor interface */ -public: - typedef Message::MessageCallback CompletionListener; + bool nextConsumableMessage( Consumer::shared_ptr& consumer, QueuedMessage& next ); + bool allocate(const std::string& consumer, const QueuedMessage& target); + bool nextBrowsableMessage( Consumer::shared_ptr& consumer, QueuedMessage& next ); + void query(qpid::types::Variant::Map&) const; - QPID_BROKER_EXTERN IncompleteMessageList(); - QPID_BROKER_EXTERN ~IncompleteMessageList(); - - QPID_BROKER_EXTERN void add(boost::intrusive_ptr<Message> msg); - QPID_BROKER_EXTERN void process(const CompletionListener& l, bool sync); - void each(const CompletionListener& l); + private: + Messages& messages; }; - }} #endif diff --git a/cpp/src/qpid/broker/HeadersExchange.cpp b/cpp/src/qpid/broker/HeadersExchange.cpp index 82ac5911ee..4bda70d313 100644 --- a/cpp/src/qpid/broker/HeadersExchange.cpp +++ b/cpp/src/qpid/broker/HeadersExchange.cpp @@ -112,9 +112,14 @@ bool HeadersExchange::bind(Queue::shared_ptr queue, const string& bindingKey, co { Mutex::ScopedLock l(lock); - Binding::shared_ptr binding (new Binding (bindingKey, queue, this, *args)); + //NOTE: do not include the fed op/tags/origin in the + //arguments as when x-match is 'all' these would prevent + //matching (they are internally added properties + //controlling binding propagation but not relevant to + //actual routing) + Binding::shared_ptr binding (new Binding (bindingKey, queue, this, extra_args)); BoundKey bk(binding); - if (bindings.add_unless(bk, MatchArgs(queue, args))) { + if (bindings.add_unless(bk, MatchArgs(queue, &extra_args))) { binding->startManagement(); propagate = bk.fedBinding.addOrigin(queue->getName(), fedOrigin); if (mgmtExchange != 0) { @@ -158,12 +163,13 @@ bool HeadersExchange::bind(Queue::shared_ptr queue, const string& bindingKey, co return true; } -bool HeadersExchange::unbind(Queue::shared_ptr queue, const string& bindingKey, const FieldTable*){ +bool HeadersExchange::unbind(Queue::shared_ptr queue, const string& bindingKey, const FieldTable *args){ bool propagate = false; + string fedOrigin(args ? args->getAsString(qpidFedOrigin) : ""); { Mutex::ScopedLock l(lock); - FedUnbindModifier modifier; + FedUnbindModifier modifier(queue->getName(), fedOrigin); MatchKey match_key(queue, bindingKey); bindings.modify_if(match_key, modifier); propagate = modifier.shouldPropagate; @@ -330,11 +336,7 @@ HeadersExchange::FedUnbindModifier::FedUnbindModifier() : shouldUnbind(false), s bool HeadersExchange::FedUnbindModifier::operator()(BoundKey & bk) { - if ("" == fedOrigin) { - shouldPropagate = bk.fedBinding.delOrigin(); - } else { - shouldPropagate = bk.fedBinding.delOrigin(queueName, fedOrigin); - } + shouldPropagate = bk.fedBinding.delOrigin(queueName, fedOrigin); if (bk.fedBinding.countFedBindings(queueName) == 0) { shouldUnbind = true; diff --git a/cpp/src/qpid/broker/IncompleteMessageList.cpp b/cpp/src/qpid/broker/IncompleteMessageList.cpp deleted file mode 100644 index 34d92fa752..0000000000 --- a/cpp/src/qpid/broker/IncompleteMessageList.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -#include "qpid/broker/IncompleteMessageList.h" - -namespace qpid { -namespace broker { - -IncompleteMessageList::IncompleteMessageList() : - callback(boost::bind(&IncompleteMessageList::enqueueComplete, this, _1)) -{} - -IncompleteMessageList::~IncompleteMessageList() -{ - // No lock here. We are relying on Messsag::reset*CompleteCallback - // to ensure no callbacks are in progress before they return. - for (Messages::iterator i = incomplete.begin(); i != incomplete.end(); ++i) { - (*i)->resetEnqueueCompleteCallback(); - (*i)->resetDequeueCompleteCallback(); - } -} - -void IncompleteMessageList::add(boost::intrusive_ptr<Message> msg) -{ - sys::Mutex::ScopedLock l(lock); - msg->setEnqueueCompleteCallback(callback); - incomplete.push_back(msg); -} - -void IncompleteMessageList::enqueueComplete(const boost::intrusive_ptr<Message>& ) { - sys::Mutex::ScopedLock l(lock); - lock.notify(); -} - -void IncompleteMessageList::process(const CompletionListener& listen, bool sync) -{ - sys::Mutex::ScopedLock l(lock); - while (!incomplete.empty()) { - boost::intrusive_ptr<Message>& msg = incomplete.front(); - if (!msg->isEnqueueComplete()) { - if (sync){ - { - sys::Mutex::ScopedUnlock u(lock); - msg->flush(); // Can re-enter IncompleteMessageList::enqueueComplete - } - while (!msg->isEnqueueComplete()) - lock.wait(); - } else { - //leave the message as incomplete for now - return; - } - } - listen(msg); - incomplete.pop_front(); - } -} - -void IncompleteMessageList::each(const CompletionListener& listen) { - Messages snapshot; - { - sys::Mutex::ScopedLock l(lock); - snapshot = incomplete; - } - std::for_each(incomplete.begin(), incomplete.end(), listen); -} - -}} diff --git a/cpp/src/qpid/broker/LegacyLVQ.cpp b/cpp/src/qpid/broker/LegacyLVQ.cpp index a811a86492..3262e343a3 100644 --- a/cpp/src/qpid/broker/LegacyLVQ.cpp +++ b/cpp/src/qpid/broker/LegacyLVQ.cpp @@ -93,11 +93,7 @@ void LegacyLVQ::removeIf(Predicate p) //purging of an LVQ is not enabled if the broker is clustered //(expired messages will be removed on delivery and consolidated //by key as part of normal LVQ operation). - - //TODO: Is there a neater way to check whether broker is - //clustered? Here we assume that if the clustered timer is the - //same as the regular timer, we are not clustered: - if (!broker || &(broker->getClusterTimer()) == &(broker->getTimer())) + if (!broker || !broker->isInCluster()) MessageMap::removeIf(p); } diff --git a/cpp/src/qpid/broker/Link.cpp b/cpp/src/qpid/broker/Link.cpp index e1091df724..8010bf43e7 100644 --- a/cpp/src/qpid/broker/Link.cpp +++ b/cpp/src/qpid/broker/Link.cpp @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -30,7 +30,6 @@ #include "qpid/framing/enum.h" #include "qpid/framing/reply_exceptions.h" #include "qpid/broker/AclModule.h" -#include "qpid/sys/ClusterSafe.h" using namespace qpid::broker; using qpid::framing::Buffer; @@ -57,8 +56,8 @@ Link::Link(LinkRegistry* _links, string& _password, Broker* _broker, Manageable* parent) - : links(_links), store(_store), host(_host), port(_port), - transport(_transport), + : links(_links), store(_store), host(_host), port(_port), + transport(_transport), durable(_durable), authMechanism(_authMechanism), username(_username), password(_password), persistenceId(0), mgmtObject(0), broker(_broker), state(0), @@ -97,7 +96,8 @@ void Link::setStateLH (int newState) return; state = newState; - if (mgmtObject == 0) + + if (hideManagement()) return; switch (state) @@ -117,12 +117,12 @@ void Link::startConnectionLH () // Set the state before calling connect. It is possible that connect // will fail synchronously and call Link::closed before returning. setStateLH(STATE_CONNECTING); - broker->connect (host, port, transport, + broker->connect (host, boost::lexical_cast<std::string>(port), transport, boost::bind (&Link::closed, this, _1, _2)); QPID_LOG (debug, "Inter-broker link connecting to " << host << ":" << port); } catch(std::exception& e) { setStateLH(STATE_WAITING); - if (mgmtObject != 0) + if (!hideManagement()) mgmtObject->set_lastError (e.what()); } } @@ -133,8 +133,7 @@ void Link::established () addr << host << ":" << port; QPID_LOG (info, "Inter-broker link established to " << addr.str()); - // Don't raise the management event in a cluster, other members wont't get this call. - if (!sys::isCluster()) + if (!hideManagement() && agent) agent->raiseEvent(_qmf::EventBrokerLinkUp(addr.str())); { @@ -154,12 +153,11 @@ void Link::closed (int, std::string text) connection = 0; - // Don't raise the management event in a cluster, other members wont't get this call. if (state == STATE_OPERATIONAL) { stringstream addr; addr << host << ":" << port; QPID_LOG (warning, "Inter-broker link disconnected from " << addr.str()); - if (!sys::isCluster()) + if (!hideManagement() && agent) agent->raiseEvent(_qmf::EventBrokerLinkDown(addr.str())); } @@ -172,7 +170,7 @@ void Link::closed (int, std::string text) if (state != STATE_FAILED) { setStateLH(STATE_WAITING); - if (mgmtObject != 0) + if (!hideManagement()) mgmtObject->set_lastError (text); } @@ -221,7 +219,7 @@ void Link::cancel(Bridge::shared_ptr bridge) { { Mutex::ScopedLock mutex(lock); - + for (Bridges::iterator i = created.begin(); i != created.end(); i++) { if ((*i).get() == bridge.get()) { created.erase(i); @@ -250,6 +248,19 @@ void Link::ioThreadProcessing() return; QPID_LOG(debug, "Link::ioThreadProcessing()"); + // check for bridge session errors and recover + if (!active.empty()) { + Bridges::iterator removed = std::remove_if( + active.begin(), active.end(), !boost::bind(&Bridge::isSessionReady, _1)); + for (Bridges::iterator i = removed; i != active.end(); ++i) { + Bridge::shared_ptr bridge = *i; + bridge->closed(); + bridge->cancel(*connection); + created.push_back(bridge); + } + active.erase(removed, active.end()); + } + //process any pending creates and/or cancellations if (!created.empty()) { for (Bridges::iterator i = created.begin(); i != created.end(); ++i) { @@ -277,9 +288,9 @@ void Link::maintenanceVisit () { Mutex::ScopedLock mutex(lock); - if (connection && updateUrls) { + if (connection && updateUrls) { urls.reset(connection->getKnownHosts()); - QPID_LOG(debug, "Known hosts for peer of inter-broker link: " << urls); + QPID_LOG(debug, "Known hosts for peer of inter-broker link: " << urls); updateUrls = false; } @@ -298,7 +309,7 @@ void Link::maintenanceVisit () } } } - else if (state == STATE_OPERATIONAL && (!created.empty() || !cancellations.empty()) && connection != 0) + else if (state == STATE_OPERATIONAL && (!active.empty() || !created.empty() || !cancellations.empty()) && connection != 0) connection->requestIOProcessing (boost::bind(&Link::ioThreadProcessing, this)); } @@ -309,7 +320,7 @@ void Link::reconnect(const qpid::Address& a) port = a.port; transport = a.protocol; startConnectionLH(); - if (mgmtObject != 0) { + if (!hideManagement()) { stringstream errorString; errorString << "Failed over to " << a; mgmtObject->set_lastError(errorString.str()); @@ -319,7 +330,7 @@ void Link::reconnect(const qpid::Address& a) bool Link::tryFailover() { Address next; - if (urls.next(next) && + if (urls.next(next) && (next.host != host || next.port != port || next.protocol != transport)) { links->changeAddress(Address(transport, host, port), next); QPID_LOG(debug, "Link failing over to " << host << ":" << port); @@ -329,6 +340,12 @@ bool Link::tryFailover() } } +// Management updates for a linke are inconsistent in a cluster, so they are +// suppressed. +bool Link::hideManagement() const { + return !mgmtObject || ( broker && broker->isInCluster()); +} + uint Link::nextChannel() { Mutex::ScopedLock mutex(lock); @@ -341,7 +358,7 @@ void Link::notifyConnectionForced(const string text) Mutex::ScopedLock mutex(lock); setStateLH(STATE_FAILED); - if (mgmtObject != 0) + if (!hideManagement()) mgmtObject->set_lastError(text); } @@ -363,7 +380,7 @@ Link::shared_ptr Link::decode(LinkRegistry& links, Buffer& buffer) string authMechanism; string username; string password; - + buffer.getShortString(host); port = buffer.getShort(); buffer.getShortString(transport); @@ -375,7 +392,7 @@ Link::shared_ptr Link::decode(LinkRegistry& links, Buffer& buffer) return links.declare(host, port, transport, durable, authMechanism, username, password).first; } -void Link::encode(Buffer& buffer) const +void Link::encode(Buffer& buffer) const { buffer.putShortString(string("link")); buffer.putShortString(host); @@ -387,8 +404,8 @@ void Link::encode(Buffer& buffer) const buffer.putShortString(password); } -uint32_t Link::encodedSize() const -{ +uint32_t Link::encodedSize() const +{ return host.size() + 1 // short-string (host) + 5 // short-string ("link") + 2 // port diff --git a/cpp/src/qpid/broker/Link.h b/cpp/src/qpid/broker/Link.h index 75a680ff5d..4badd8b3a1 100644 --- a/cpp/src/qpid/broker/Link.h +++ b/cpp/src/qpid/broker/Link.h @@ -10,9 +10,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -85,6 +85,7 @@ namespace qpid { void destroy(); // Called when mgmt deletes this link void ioThreadProcessing(); // Called on connection's IO thread by request bool tryFailover(); // Called during maintenance visit + bool hideManagement() const; public: typedef boost::shared_ptr<Link> shared_ptr; @@ -122,12 +123,12 @@ namespace qpid { void notifyConnectionForced(const std::string text); void setPassive(bool p); - + // PersistableConfig: void setPersistenceId(uint64_t id) const; uint64_t getPersistenceId() const { return persistenceId; } uint32_t encodedSize() const; - void encode(framing::Buffer& buffer) const; + void encode(framing::Buffer& buffer) const; const std::string& getName() const; static Link::shared_ptr decode(LinkRegistry& links, framing::Buffer& buffer); @@ -135,6 +136,7 @@ namespace qpid { // Manageable entry points management::ManagementObject* GetManagementObject(void) const; management::Manageable::status_t ManagementMethod(uint32_t, management::Args&, std::string&); + }; } } diff --git a/cpp/src/qpid/broker/LinkRegistry.cpp b/cpp/src/qpid/broker/LinkRegistry.cpp index 7b1c75db74..e9885f5462 100644 --- a/cpp/src/qpid/broker/LinkRegistry.cpp +++ b/cpp/src/qpid/broker/LinkRegistry.cpp @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -381,7 +381,7 @@ std::string LinkRegistry::createKey(const std::string& host, uint16_t port) { return keystream.str(); } -void LinkRegistry::setPassive(bool p) +void LinkRegistry::setPassive(bool p) { Mutex::ScopedLock locker(lock); passiveChanged = p != passive; diff --git a/cpp/src/qpid/broker/Message.cpp b/cpp/src/qpid/broker/Message.cpp index c589669e5a..d13109dad1 100644 --- a/cpp/src/qpid/broker/Message.cpp +++ b/cpp/src/qpid/broker/Message.cpp @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -30,6 +30,7 @@ #include "qpid/framing/SendContent.h" #include "qpid/framing/SequenceNumber.h" #include "qpid/framing/TypeFilter.h" +#include "qpid/framing/reply_exceptions.h" #include "qpid/log/Statement.h" #include <time.h> @@ -49,27 +50,16 @@ TransferAdapter Message::TRANSFER; Message::Message(const framing::SequenceNumber& id) : frames(id), persistenceId(0), redelivered(false), loaded(false), - staged(false), forcePersistentPolicy(false), publisher(0), adapter(0), - expiration(FAR_FUTURE), enqueueCallback(0), dequeueCallback(0), - inCallback(false), requiredCredit(0) {} + staged(false), forcePersistentPolicy(false), publisher(0), adapter(0), + expiration(FAR_FUTURE), dequeueCallback(0), + inCallback(false), requiredCredit(0), isManagementMessage(false), copyHeaderOnWrite(false) +{} -Message::Message(const Message& original) : - PersistableMessage(), frames(original.frames), persistenceId(0), redelivered(false), loaded(false), - staged(false), forcePersistentPolicy(false), publisher(0), adapter(0), - expiration(original.expiration), enqueueCallback(0), dequeueCallback(0), - inCallback(false), requiredCredit(0) -{ - setExpiryPolicy(original.expiryPolicy); -} - -Message::~Message() -{ - if (expiryPolicy) - expiryPolicy->forget(*this); -} +Message::~Message() {} void Message::forcePersistent() { + sys::Mutex::ScopedLock l(lock); // only set forced bit if we actually need to force. if (! getAdapter().isPersistent(frames) ){ forcePersistentPolicy = true; @@ -86,7 +76,7 @@ std::string Message::getRoutingKey() const return getAdapter().getRoutingKey(frames); } -std::string Message::getExchangeName() const +std::string Message::getExchangeName() const { return getAdapter().getExchange(frames); } @@ -95,7 +85,7 @@ const boost::shared_ptr<Exchange> Message::getExchange(ExchangeRegistry& registr { if (!exchange) { exchange = registry.get(getExchangeName()); - } + } return exchange; } @@ -106,16 +96,19 @@ bool Message::isImmediate() const const FieldTable* Message::getApplicationHeaders() const { + sys::Mutex::ScopedLock l(lock); return getAdapter().getApplicationHeaders(frames); } std::string Message::getAppId() const { + sys::Mutex::ScopedLock l(lock); return getAdapter().getAppId(frames); } bool Message::isPersistent() const { + sys::Mutex::ScopedLock l(lock); return (getAdapter().isPersistent(frames) || forcePersistentPolicy); } @@ -195,7 +188,7 @@ void Message::decodeContent(framing::Buffer& buffer) } else { //adjust header flags MarkLastSegment f; - frames.map_if(f, TypeFilter<HEADER_BODY>()); + frames.map_if(f, TypeFilter<HEADER_BODY>()); } //mark content loaded loaded = true; @@ -247,7 +240,7 @@ void Message::destroy() bool Message::getContentFrame(const Queue& queue, AMQFrame& frame, uint16_t maxContentSize, uint64_t offset) const { intrusive_ptr<const PersistableMessage> pmsg(this); - + bool done = false; string& data = frame.castBody<AMQContentBody>()->getData(); store->loadContent(queue, pmsg, data, offset, maxContentSize); @@ -272,7 +265,7 @@ void Message::sendContent(const Queue& queue, framing::FrameHandler& out, uint16 uint16_t maxContentSize = maxFrameSize - AMQFrame::frameOverhead(); bool morecontent = true; for (uint64_t offset = 0; morecontent; offset += maxContentSize) - { + { AMQFrame frame((AMQContentBody())); morecontent = getContentFrame(queue, frame, maxContentSize, offset); out.handle(frame); @@ -290,7 +283,10 @@ void Message::sendHeader(framing::FrameHandler& out, uint16_t /*maxFrameSize*/) { sys::Mutex::ScopedLock l(lock); Relay f(out); - frames.map_if(f, TypeFilter<HEADER_BODY>()); + frames.map_if(f, TypeFilter<HEADER_BODY>()); + //as frame (and pointer to body) has now been passed to handler, + //subsequent modifications should use a copy + copyHeaderOnWrite = true; } // TODO aconway 2007-11-09: Obsolete, remove. Was used to cover over @@ -320,13 +316,14 @@ bool Message::isContentLoaded() const } -namespace +namespace { const std::string X_QPID_TRACE("x-qpid.trace"); } bool Message::isExcluded(const std::vector<std::string>& excludes) const { + sys::Mutex::ScopedLock l(lock); const FieldTable* headers = getApplicationHeaders(); if (headers) { std::string traceStr = headers->getAsString(X_QPID_TRACE); @@ -345,11 +342,30 @@ bool Message::isExcluded(const std::vector<std::string>& excludes) const return false; } +class CloneHeaderBody +{ +public: + void operator()(AMQFrame& f) + { + f.cloneBody(); + } +}; + +AMQHeaderBody* Message::getHeaderBody() +{ + if (copyHeaderOnWrite) { + CloneHeaderBody f; + frames.map_if(f, TypeFilter<HEADER_BODY>()); + copyHeaderOnWrite = false; + } + return frames.getHeaders(); +} + void Message::addTraceId(const std::string& id) { sys::Mutex::ScopedLock l(lock); if (isA<MessageTransferBody>()) { - FieldTable& headers = getProperties<MessageProperties>()->getApplicationHeaders(); + FieldTable& headers = getModifiableProperties<MessageProperties>()->getApplicationHeaders(); std::string trace = headers.getAsString(X_QPID_TRACE); if (trace.empty()) { headers.setString(X_QPID_TRACE, id); @@ -357,13 +373,22 @@ void Message::addTraceId(const std::string& id) trace += ","; trace += id; headers.setString(X_QPID_TRACE, trace); - } + } } } -void Message::setTimestamp(const boost::intrusive_ptr<ExpiryPolicy>& e) +void Message::setTimestamp() +{ + sys::Mutex::ScopedLock l(lock); + DeliveryProperties* props = getModifiableProperties<DeliveryProperties>(); + time_t now = ::time(0); + props->setTimestamp(now); // AMQP-0.10: posix time_t - secs since Epoch +} + +void Message::computeExpiration(const boost::intrusive_ptr<ExpiryPolicy>& e) { - DeliveryProperties* props = getProperties<DeliveryProperties>(); + sys::Mutex::ScopedLock l(lock); + DeliveryProperties* props = getModifiableProperties<DeliveryProperties>(); if (props->getTtl()) { // AMQP requires setting the expiration property to be posix // time_t in seconds. TTL is in milliseconds @@ -372,26 +397,70 @@ void Message::setTimestamp(const boost::intrusive_ptr<ExpiryPolicy>& e) time_t now = ::time(0); props->setExpiration(now + (props->getTtl()/1000)); } - // Use higher resolution time for the internal expiry calculation. - expiration = AbsTime(AbsTime::now(), Duration(props->getTtl() * TIME_MSEC)); - setExpiryPolicy(e); + if (e) { + // Use higher resolution time for the internal expiry calculation. + // Prevent overflow as a signed int64_t + Duration ttl(std::min(props->getTtl() * TIME_MSEC, + (uint64_t) std::numeric_limits<int64_t>::max())); + expiration = AbsTime(e->getCurrentTime(), ttl); + setExpiryPolicy(e); + } } } void Message::adjustTtl() { - DeliveryProperties* props = getProperties<DeliveryProperties>(); + sys::Mutex::ScopedLock l(lock); + DeliveryProperties* props = getModifiableProperties<DeliveryProperties>(); if (props->getTtl()) { - sys::Mutex::ScopedLock l(lock); - sys::Duration d(sys::AbsTime::now(), getExpiration()); - props->setTtl(int64_t(d) > 0 ? int64_t(d)/1000000 : 1); // convert from ns to ms; set to 1 if expired + if (expiration < FAR_FUTURE) { + sys::AbsTime current( + expiryPolicy ? expiryPolicy->getCurrentTime() : sys::AbsTime::now()); + sys::Duration ttl(current, getExpiration()); + // convert from ns to ms; set to 1 if expired + props->setTtl(int64_t(ttl) >= 1000000 ? int64_t(ttl)/1000000 : 1); + } } } +void Message::setRedelivered() +{ + sys::Mutex::ScopedLock l(lock); + getModifiableProperties<framing::DeliveryProperties>()->setRedelivered(true); +} + +void Message::insertCustomProperty(const std::string& key, int64_t value) +{ + sys::Mutex::ScopedLock l(lock); + getModifiableProperties<MessageProperties>()->getApplicationHeaders().setInt64(key,value); +} + +void Message::insertCustomProperty(const std::string& key, const std::string& value) +{ + sys::Mutex::ScopedLock l(lock); + getModifiableProperties<MessageProperties>()->getApplicationHeaders().setString(key,value); +} + +void Message::removeCustomProperty(const std::string& key) +{ + sys::Mutex::ScopedLock l(lock); + getModifiableProperties<MessageProperties>()->getApplicationHeaders().erase(key); +} + +void Message::setExchange(const std::string& exchange) +{ + sys::Mutex::ScopedLock l(lock); + getModifiableProperties<DeliveryProperties>()->setExchange(exchange); +} + +void Message::clearApplicationHeadersFlag() +{ + sys::Mutex::ScopedLock l(lock); + getModifiableProperties<MessageProperties>()->clearApplicationHeadersFlag(); +} + void Message::setExpiryPolicy(const boost::intrusive_ptr<ExpiryPolicy>& e) { expiryPolicy = e; - if (expiryPolicy) - expiryPolicy->willExpire(*this); } bool Message::hasExpired() @@ -415,30 +484,12 @@ struct ScopedSet { }; } -void Message::allEnqueuesComplete() { - ScopedSet ss(callbackLock, inCallback); - MessageCallback* cb = enqueueCallback; - if (cb && *cb) (*cb)(intrusive_ptr<Message>(this)); -} - void Message::allDequeuesComplete() { ScopedSet ss(callbackLock, inCallback); MessageCallback* cb = dequeueCallback; if (cb && *cb) (*cb)(intrusive_ptr<Message>(this)); } -void Message::setEnqueueCompleteCallback(MessageCallback& cb) { - sys::Mutex::ScopedLock l(callbackLock); - while (inCallback) callbackLock.wait(); - enqueueCallback = &cb; -} - -void Message::resetEnqueueCompleteCallback() { - sys::Mutex::ScopedLock l(callbackLock); - while (inCallback) callbackLock.wait(); - enqueueCallback = 0; -} - void Message::setDequeueCompleteCallback(MessageCallback& cb) { sys::Mutex::ScopedLock l(callbackLock); while (inCallback) callbackLock.wait(); @@ -452,12 +503,11 @@ void Message::resetDequeueCompleteCallback() { } uint8_t Message::getPriority() const { + sys::Mutex::ScopedLock l(lock); return getAdapter().getPriority(frames); } -framing::FieldTable& Message::getOrInsertHeaders() -{ - return getProperties<MessageProperties>()->getApplicationHeaders(); -} +bool Message::getIsManagementMessage() const { return isManagementMessage; } +void Message::setIsManagementMessage(bool b) { isManagementMessage = b; } }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/Message.h b/cpp/src/qpid/broker/Message.h index f7dd2734b6..dda45d73e6 100644 --- a/cpp/src/qpid/broker/Message.h +++ b/cpp/src/qpid/broker/Message.h @@ -10,9 +10,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -29,17 +29,21 @@ #include "qpid/sys/Monitor.h" #include "qpid/sys/Time.h" #include <boost/function.hpp> +#include <boost/intrusive_ptr.hpp> #include <boost/shared_ptr.hpp> +#include <memory> #include <string> #include <vector> namespace qpid { - + namespace framing { +class AMQBody; +class AMQHeaderBody; class FieldTable; class SequenceNumber; } - + namespace broker { class ConnectionToken; class Exchange; @@ -51,11 +55,10 @@ class ExpiryPolicy; class Message : public PersistableMessage { public: typedef boost::function<void (const boost::intrusive_ptr<Message>&)> MessageCallback; - + QPID_BROKER_EXTERN Message(const framing::SequenceNumber& id = framing::SequenceNumber()); - QPID_BROKER_EXTERN Message(const Message&); QPID_BROKER_EXTERN ~Message(); - + uint64_t getPersistenceId() const { return persistenceId; } void setPersistenceId(uint64_t _persistenceId) const { persistenceId = _persistenceId; } @@ -75,27 +78,31 @@ public: bool isImmediate() const; QPID_BROKER_EXTERN const framing::FieldTable* getApplicationHeaders() const; QPID_BROKER_EXTERN std::string getAppId() const; - framing::FieldTable& getOrInsertHeaders(); QPID_BROKER_EXTERN bool isPersistent() const; bool requiresAccept(); - QPID_BROKER_EXTERN void setTimestamp(const boost::intrusive_ptr<ExpiryPolicy>& e); + /** determine msg expiration time using the TTL value if present */ + QPID_BROKER_EXTERN void computeExpiration(const boost::intrusive_ptr<ExpiryPolicy>& e); void setExpiryPolicy(const boost::intrusive_ptr<ExpiryPolicy>& e); bool hasExpired(); sys::AbsTime getExpiration() const { return expiration; } + void setExpiration(sys::AbsTime exp) { expiration = exp; } void adjustTtl(); - - framing::FrameSet& getFrames() { return frames; } - const framing::FrameSet& getFrames() const { return frames; } - - template <class T> T* getProperties() { - qpid::framing::AMQHeaderBody* p = frames.getHeaders(); - return p->get<T>(true); - } + void setRedelivered(); + QPID_BROKER_EXTERN void insertCustomProperty(const std::string& key, int64_t value); + QPID_BROKER_EXTERN void insertCustomProperty(const std::string& key, const std::string& value); + QPID_BROKER_EXTERN void removeCustomProperty(const std::string& key); + void setExchange(const std::string&); + void clearApplicationHeadersFlag(); + /** set the timestamp delivery property to the current time-of-day */ + QPID_BROKER_EXTERN void setTimestamp(); + + framing::FrameSet& getFrames() { return frames; } + const framing::FrameSet& getFrames() const { return frames; } template <class T> const T* getProperties() const { - qpid::framing::AMQHeaderBody* p = frames.getHeaders(); - return p->get<T>(true); + const qpid::framing::AMQHeaderBody* p = frames.getHeaders(); + return p->get<T>(); } template <class T> const T* hasProperties() const { @@ -103,6 +110,11 @@ public: return p->get<T>(); } + template <class T> void eraseProperties() { + qpid::framing::AMQHeaderBody* p = frames.getHeaders(); + p->erase<T>(); + } + template <class T> const T* getMethod() const { return frames.as<T>(); } @@ -135,7 +147,7 @@ public: QPID_BROKER_EXTERN void decodeHeader(framing::Buffer& buffer); QPID_BROKER_EXTERN void decodeContent(framing::Buffer& buffer); - + void QPID_BROKER_EXTERN tryReleaseContent(); void releaseContent(); void releaseContent(MessageStore* s);//deprecated, use 'setStore(store); releaseContent();' instead @@ -149,24 +161,19 @@ public: bool isExcluded(const std::vector<std::string>& excludes) const; void addTraceId(const std::string& id); - - void forcePersistent(); - bool isForcedPersistent(); - - /** Call cb when enqueue is complete, may call immediately. Holds cb by reference. */ - void setEnqueueCompleteCallback(MessageCallback& cb); - void resetEnqueueCompleteCallback(); + void forcePersistent(); + bool isForcedPersistent(); /** Call cb when dequeue is complete, may call immediately. Holds cb by reference. */ void setDequeueCompleteCallback(MessageCallback& cb); void resetDequeueCompleteCallback(); uint8_t getPriority() const; - + bool getIsManagementMessage() const; + void setIsManagementMessage(bool b); private: MessageAdapter& getAdapter() const; - void allEnqueuesComplete(); void allDequeuesComplete(); mutable sys::Mutex lock; @@ -176,7 +183,7 @@ public: bool redelivered; bool loaded; bool staged; - bool forcePersistentPolicy; // used to force message as durable, via a broker policy + bool forcePersistentPolicy; // used to force message as durable, via a broker policy ConnectionToken* publisher; mutable MessageAdapter* adapter; qpid::sys::AbsTime expiration; @@ -187,11 +194,20 @@ public: mutable boost::intrusive_ptr<Message> empty; sys::Monitor callbackLock; - MessageCallback* enqueueCallback; MessageCallback* dequeueCallback; bool inCallback; uint32_t requiredCredit; + bool isManagementMessage; + mutable bool copyHeaderOnWrite; + + /** + * Expects lock to be held + */ + template <class T> T* getModifiableProperties() { + return getHeaderBody()->get<T>(true); + } + qpid::framing::AMQHeaderBody* getHeaderBody(); }; }} diff --git a/cpp/src/qpid/broker/MessageBuilder.h b/cpp/src/qpid/broker/MessageBuilder.h index 75dfd6781d..b99b8efee6 100644 --- a/cpp/src/qpid/broker/MessageBuilder.h +++ b/cpp/src/qpid/broker/MessageBuilder.h @@ -33,7 +33,7 @@ namespace qpid { class Message; class MessageStore; - class MessageBuilder : public framing::FrameHandler{ + class QPID_BROKER_CLASS_EXTERN MessageBuilder : public framing::FrameHandler{ public: QPID_BROKER_EXTERN MessageBuilder(MessageStore* const store); QPID_BROKER_EXTERN void handle(framing::AMQFrame& frame); diff --git a/cpp/src/qpid/broker/MessageDistributor.h b/cpp/src/qpid/broker/MessageDistributor.h new file mode 100644 index 0000000000..090393c160 --- /dev/null +++ b/cpp/src/qpid/broker/MessageDistributor.h @@ -0,0 +1,76 @@ +#ifndef _broker_MessageDistributor_h +#define _broker_MessageDistributor_h + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/** Abstraction used by Queue to determine the next "most desirable" message to provide to + * a particular consuming client + */ + + +#include "qpid/broker/Consumer.h" + +namespace qpid { +namespace broker { + +struct QueuedMessage; + +class MessageDistributor +{ + public: + virtual ~MessageDistributor() {}; + + /** Locking Note: all methods assume the caller is holding the Queue::messageLock + * during the method call. + */ + + /** Determine the next message available for consumption by the consumer + * @param consumer the consumer that needs a message to consume + * @param next set to the next message that the consumer may consume. + * @return true if message is available and next is set + */ + virtual bool nextConsumableMessage( Consumer::shared_ptr& consumer, + QueuedMessage& next ) = 0; + + /** Allow the comsumer to take ownership of the given message. + * @param consumer the name of the consumer that is attempting to acquire the message + * @param qm the message to be acquired, previously returned from nextConsumableMessage() + * @return true if ownership is permitted, false if ownership cannot be assigned. + */ + virtual bool allocate( const std::string& consumer, + const QueuedMessage& target) = 0; + + /** Determine the next message available for browsing by the consumer + * @param consumer the consumer that is browsing the queue + * @param next set to the next message that the consumer may browse. + * @return true if a message is available and next is returned + */ + virtual bool nextBrowsableMessage( Consumer::shared_ptr& consumer, + QueuedMessage& next ) = 0; + + /** hook to add any interesting management state to the status map */ + virtual void query(qpid::types::Variant::Map&) const = 0; +}; + +}} + +#endif diff --git a/cpp/src/qpid/broker/MessageGroupManager.cpp b/cpp/src/qpid/broker/MessageGroupManager.cpp new file mode 100644 index 0000000000..07b05f3b92 --- /dev/null +++ b/cpp/src/qpid/broker/MessageGroupManager.cpp @@ -0,0 +1,411 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/framing/FieldTable.h" +#include "qpid/types/Variant.h" +#include "qpid/log/Statement.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/MessageGroupManager.h" + +using namespace qpid::broker; + +namespace { + const std::string GROUP_QUERY_KEY("qpid.message_group_queue"); + const std::string GROUP_HEADER_KEY("group_header_key"); + const std::string GROUP_STATE_KEY("group_state"); + const std::string GROUP_ID_KEY("group_id"); + const std::string GROUP_MSG_COUNT("msg_count"); + const std::string GROUP_TIMESTAMP("timestamp"); + const std::string GROUP_CONSUMER("consumer"); +} + + +const std::string MessageGroupManager::qpidMessageGroupKey("qpid.group_header_key"); +const std::string MessageGroupManager::qpidSharedGroup("qpid.shared_msg_group"); +const std::string MessageGroupManager::qpidMessageGroupTimestamp("qpid.group_timestamp"); + + +void MessageGroupManager::unFree( const GroupState& state ) +{ + GroupFifo::iterator pos = freeGroups.find( state.members.front() ); + assert( pos != freeGroups.end() && pos->second == &state ); + freeGroups.erase( pos ); +} + +void MessageGroupManager::own( GroupState& state, const std::string& owner ) +{ + state.owner = owner; + unFree( state ); +} + +void MessageGroupManager::disown( GroupState& state ) +{ + state.owner.clear(); + assert(state.members.size()); + assert(freeGroups.find(state.members.front()) == freeGroups.end()); + freeGroups[state.members.front()] = &state; +} + +const std::string MessageGroupManager::getGroupId( const QueuedMessage& qm ) const +{ + const qpid::framing::FieldTable* headers = qm.payload->getApplicationHeaders(); + if (!headers) return defaultGroupId; + qpid::framing::FieldTable::ValuePtr id = headers->get( groupIdHeader ); + if (!id || !id->convertsTo<std::string>()) return defaultGroupId; + return id->get<std::string>(); +} + + +void MessageGroupManager::enqueued( const QueuedMessage& qm ) +{ + // @todo KAG optimization - store reference to group state in QueuedMessage + // issue: const-ness?? + std::string group( getGroupId(qm) ); + GroupState &state(messageGroups[group]); + state.members.push_back(qm.position); + uint32_t total = state.members.size(); + QPID_LOG( trace, "group queue " << qName << + ": added message to group id=" << group << " total=" << total ); + if (total == 1) { + // newly created group, no owner + state.group = group; + assert(freeGroups.find(qm.position) == freeGroups.end()); + freeGroups[qm.position] = &state; + } +} + + +void MessageGroupManager::acquired( const QueuedMessage& qm ) +{ + // @todo KAG avoid lookup: retrieve direct reference to group state from QueuedMessage + // issue: const-ness?? + std::string group( getGroupId(qm) ); + GroupMap::iterator gs = messageGroups.find( group ); + assert( gs != messageGroups.end() ); + GroupState& state( gs->second ); + state.acquired += 1; + QPID_LOG( trace, "group queue " << qName << + ": acquired message in group id=" << group << " acquired=" << state.acquired ); +} + + +void MessageGroupManager::requeued( const QueuedMessage& qm ) +{ + // @todo KAG avoid lookup: retrieve direct reference to group state from QueuedMessage + // issue: const-ness?? + std::string group( getGroupId(qm) ); + GroupMap::iterator gs = messageGroups.find( group ); + assert( gs != messageGroups.end() ); + GroupState& state( gs->second ); + assert( state.acquired != 0 ); + state.acquired -= 1; + if (state.acquired == 0 && state.owned()) { + QPID_LOG( trace, "group queue " << qName << + ": consumer name=" << state.owner << " released group id=" << gs->first); + disown(state); + } + QPID_LOG( trace, "group queue " << qName << + ": requeued message to group id=" << group << " acquired=" << state.acquired ); +} + + +void MessageGroupManager::dequeued( const QueuedMessage& qm ) +{ + // @todo KAG avoid lookup: retrieve direct reference to group state from QueuedMessage + // issue: const-ness?? + std::string group( getGroupId(qm) ); + GroupMap::iterator gs = messageGroups.find( group ); + assert( gs != messageGroups.end() ); + GroupState& state( gs->second ); + assert( state.members.size() != 0 ); + assert( state.acquired != 0 ); + state.acquired -= 1; + + // likely to be at or near begin() if dequeued in order + bool reFreeNeeded = false; + if (state.members.front() == qm.position) { + if (!state.owned()) { + // will be on the freeGroups list if mgmt is dequeueing rather than a consumer! + // if on freelist, it is indexed by first member, which is about to be removed! + unFree(state); + reFreeNeeded = true; + } + state.members.pop_front(); + } else { + GroupState::PositionFifo::iterator pos = state.members.begin() + 1; + GroupState::PositionFifo::iterator end = state.members.end(); + while (pos != end) { + if (*pos == qm.position) { + state.members.erase(pos); + break; + } + ++pos; + } + } + + uint32_t total = state.members.size(); + if (total == 0) { + QPID_LOG( trace, "group queue " << qName << ": deleting group id=" << gs->first); + messageGroups.erase( gs ); + } else if (state.acquired == 0 && state.owned()) { + QPID_LOG( trace, "group queue " << qName << + ": consumer name=" << state.owner << " released group id=" << gs->first); + disown(state); + } else if (reFreeNeeded) { + disown(state); + } + QPID_LOG( trace, "group queue " << qName << + ": dequeued message from group id=" << group << " total=" << total ); +} + +bool MessageGroupManager::nextConsumableMessage( Consumer::shared_ptr& c, QueuedMessage& next ) +{ + if (messages.empty()) + return false; + + if (!freeGroups.empty()) { + framing::SequenceNumber nextFree = freeGroups.begin()->first; + if (nextFree < c->position) { // next free group's msg is older than current position + bool ok = messages.find(nextFree, next); + (void) ok; assert( ok ); + } else { + if (!messages.next( c->position, next )) + return false; // shouldn't happen - should find nextFree + } + } else { // no free groups available + if (!messages.next( c->position, next )) + return false; + } + + do { + // @todo KAG avoid lookup: retrieve direct reference to group state from QueuedMessage + std::string group( getGroupId( next ) ); + GroupMap::iterator gs = messageGroups.find( group ); + assert( gs != messageGroups.end() ); + GroupState& state( gs->second ); + if (!state.owned() || state.owner == c->getName()) { + return true; + } + } while (messages.next( next.position, next )); + return false; +} + + +bool MessageGroupManager::allocate(const std::string& consumer, const QueuedMessage& qm) +{ + // @todo KAG avoid lookup: retrieve direct reference to group state from QueuedMessage + std::string group( getGroupId(qm) ); + GroupMap::iterator gs = messageGroups.find( group ); + assert( gs != messageGroups.end() ); + GroupState& state( gs->second ); + + if (!state.owned()) { + own( state, consumer ); + QPID_LOG( trace, "group queue " << qName << + ": consumer name=" << consumer << " has acquired group id=" << gs->first); + return true; + } + return state.owner == consumer; +} + +bool MessageGroupManager::nextBrowsableMessage( Consumer::shared_ptr& c, QueuedMessage& next ) +{ + // browse: allow access to any available msg, regardless of group ownership (?ok?) + if (!messages.empty() && messages.next(c->position, next)) + return true; + return false; +} + +void MessageGroupManager::query(qpid::types::Variant::Map& status) const +{ + /** Add a description of the current state of the message groups for this queue. + FORMAT: + { "qpid.message_group_queue": + { "group_header_key" : "<KEY>", + "group_state" : + [ { "group_id" : "<name>", + "msg_count" : <int>, + "timestamp" : <absTime>, + "consumer" : <consumer name> }, + {...} // one for each known group + ] + } + } + **/ + + assert(status.find(GROUP_QUERY_KEY) == status.end()); + qpid::types::Variant::Map state; + qpid::types::Variant::List groups; + + state[GROUP_HEADER_KEY] = groupIdHeader; + for (GroupMap::const_iterator g = messageGroups.begin(); + g != messageGroups.end(); ++g) { + qpid::types::Variant::Map info; + info[GROUP_ID_KEY] = g->first; + info[GROUP_MSG_COUNT] = g->second.members.size(); + info[GROUP_TIMESTAMP] = 0; /** @todo KAG - NEED HEAD MSG TIMESTAMP */ + info[GROUP_CONSUMER] = g->second.owner; + groups.push_back(info); + } + state[GROUP_STATE_KEY] = groups; + status[GROUP_QUERY_KEY] = state; +} + + +boost::shared_ptr<MessageGroupManager> MessageGroupManager::create( const std::string& qName, + Messages& messages, + const qpid::framing::FieldTable& settings ) +{ + boost::shared_ptr<MessageGroupManager> empty; + + if (settings.isSet(qpidMessageGroupKey)) { + + // @todo: remove once "sticky" consumers are supported - see QPID-3347 + if (!settings.isSet(qpidSharedGroup)) { + QPID_LOG( error, "Only shared groups are supported in this version of the broker. Use '--shared-groups' in qpid-config." ); + return empty; + } + + std::string headerKey = settings.getAsString(qpidMessageGroupKey); + if (headerKey.empty()) { + QPID_LOG( error, "A Message Group header key must be configured, queue=" << qName); + return empty; + } + unsigned int timestamp = settings.getAsInt(qpidMessageGroupTimestamp); + + boost::shared_ptr<MessageGroupManager> manager( new MessageGroupManager( headerKey, qName, messages, timestamp ) ); + + QPID_LOG( debug, "Configured Queue '" << qName << + "' for message grouping using header key '" << headerKey << "'" << + " (timestamp=" << timestamp << ")"); + return manager; + } + return empty; +} + +std::string MessageGroupManager::defaultGroupId; +void MessageGroupManager::setDefaults(const std::string& groupId) // static +{ + defaultGroupId = groupId; +} + +/** Cluster replication: + + state map format: + + { "group-state": [ {"name": <group-name>, + "owner": <consumer-name>-or-empty, + "acquired-ct": <acquired count>, + "positions": [Seqnumbers, ... ]}, + {...} + ] + } +*/ + +namespace { + const std::string GROUP_NAME("name"); + const std::string GROUP_OWNER("owner"); + const std::string GROUP_ACQUIRED_CT("acquired-ct"); + const std::string GROUP_POSITIONS("positions"); + const std::string GROUP_STATE("group-state"); +} + + +/** Runs on UPDATER to snapshot current state */ +void MessageGroupManager::getState(qpid::framing::FieldTable& state ) const +{ + using namespace qpid::framing; + state.clear(); + framing::Array groupState(TYPE_CODE_MAP); + for (GroupMap::const_iterator g = messageGroups.begin(); + g != messageGroups.end(); ++g) { + + framing::FieldTable group; + group.setString(GROUP_NAME, g->first); + group.setString(GROUP_OWNER, g->second.owner); + group.setInt(GROUP_ACQUIRED_CT, g->second.acquired); + framing::Array positions(TYPE_CODE_UINT32); + for (GroupState::PositionFifo::const_iterator p = g->second.members.begin(); + p != g->second.members.end(); ++p) + positions.push_back(framing::Array::ValuePtr(new IntegerValue( *p ))); + group.setArray(GROUP_POSITIONS, positions); + groupState.push_back(framing::Array::ValuePtr(new FieldTableValue(group))); + } + state.setArray(GROUP_STATE, groupState); + + QPID_LOG(debug, "Queue \"" << qName << "\": replicating message group state, key=" << groupIdHeader); +} + + +/** called on UPDATEE to set state from snapshot */ +void MessageGroupManager::setState(const qpid::framing::FieldTable& state) +{ + using namespace qpid::framing; + messageGroups.clear(); + //consumers.clear(); + freeGroups.clear(); + + framing::Array groupState(TYPE_CODE_MAP); + + bool ok = state.getArray(GROUP_STATE, groupState); + if (!ok) { + QPID_LOG(error, "Unable to find message group state information for queue \"" << + qName << "\": cluster inconsistency error!"); + return; + } + + for (framing::Array::const_iterator g = groupState.begin(); + g != groupState.end(); ++g) { + framing::FieldTable group; + ok = framing::getEncodedValue<FieldTable>(*g, group); + if (!ok) { + QPID_LOG(error, "Invalid message group state information for queue \"" << + qName << "\": table encoding error!"); + return; + } + MessageGroupManager::GroupState state; + if (!group.isSet(GROUP_NAME) || !group.isSet(GROUP_OWNER) || !group.isSet(GROUP_ACQUIRED_CT)) { + QPID_LOG(error, "Invalid message group state information for queue \"" << + qName << "\": fields missing error!"); + return; + } + state.group = group.getAsString(GROUP_NAME); + state.owner = group.getAsString(GROUP_OWNER); + state.acquired = group.getAsInt(GROUP_ACQUIRED_CT); + framing::Array positions(TYPE_CODE_UINT32); + ok = group.getArray(GROUP_POSITIONS, positions); + if (!ok) { + QPID_LOG(error, "Invalid message group state information for queue \"" << + qName << "\": position encoding error!"); + return; + } + + for (Array::const_iterator p = positions.begin(); p != positions.end(); ++p) + state.members.push_back((*p)->getIntegerValue<uint32_t, 4>()); + messageGroups[state.group] = state; + if (!state.owned()) { + assert(state.members.size()); + freeGroups[state.members.front()] = &messageGroups[state.group]; + } + } + + QPID_LOG(debug, "Queue \"" << qName << "\": message group state replicated, key =" << groupIdHeader) +} diff --git a/cpp/src/qpid/broker/MessageGroupManager.h b/cpp/src/qpid/broker/MessageGroupManager.h new file mode 100644 index 0000000000..6c81ec14d1 --- /dev/null +++ b/cpp/src/qpid/broker/MessageGroupManager.h @@ -0,0 +1,107 @@ +#ifndef _broker_MessageGroupManager_h +#define _broker_MessageGroupManager_h + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/* for managing message grouping on Queues */ + +#include "qpid/broker/StatefulQueueObserver.h" +#include "qpid/broker/MessageDistributor.h" + + +namespace qpid { +namespace broker { + +class QueueObserver; +class MessageDistributor; + +class MessageGroupManager : public StatefulQueueObserver, public MessageDistributor +{ + static std::string defaultGroupId; // assigned if no group id header present + + const std::string groupIdHeader; // msg header holding group identifier + const unsigned int timestamp; // mark messages with timestamp if set + Messages& messages; // parent Queue's in memory message container + const std::string qName; // name of parent queue (for logs) + + struct GroupState { + // note: update getState()/setState() when changing this object's state implementation + typedef std::deque<framing::SequenceNumber> PositionFifo; + + std::string group; // group identifier + std::string owner; // consumer with outstanding acquired messages + uint32_t acquired; // count of outstanding acquired messages + PositionFifo members; // msgs belonging to this group + + GroupState() : acquired(0) {} + bool owned() const {return !owner.empty();} + }; + typedef std::map<std::string, struct GroupState> GroupMap; + typedef std::map<framing::SequenceNumber, struct GroupState *> GroupFifo; + + GroupMap messageGroups; // index: group name + GroupFifo freeGroups; // ordered by oldest free msg + //Consumers consumers; // index: consumer name + + static const std::string qpidMessageGroupKey; + static const std::string qpidSharedGroup; // if specified, one group can be consumed by multiple receivers + static const std::string qpidMessageGroupTimestamp; + + const std::string getGroupId( const QueuedMessage& qm ) const; + void unFree( const GroupState& state ); + void own( GroupState& state, const std::string& owner ); + void disown( GroupState& state ); + + public: + + static QPID_BROKER_EXTERN void setDefaults(const std::string& groupId); + static boost::shared_ptr<MessageGroupManager> create( const std::string& qName, + Messages& messages, + const qpid::framing::FieldTable& settings ); + + MessageGroupManager(const std::string& header, const std::string& _qName, + Messages& container, unsigned int _timestamp=0 ) + : StatefulQueueObserver(std::string("MessageGroupManager:") + header), + groupIdHeader( header ), timestamp(_timestamp), messages(container), qName(_qName) {} + + // QueueObserver iface + void enqueued( const QueuedMessage& qm ); + void acquired( const QueuedMessage& qm ); + void requeued( const QueuedMessage& qm ); + void dequeued( const QueuedMessage& qm ); + void consumerAdded( const Consumer& ) {}; + void consumerRemoved( const Consumer& ) {}; + void getState(qpid::framing::FieldTable& state ) const; + void setState(const qpid::framing::FieldTable&); + + // MessageDistributor iface + bool nextConsumableMessage(Consumer::shared_ptr& c, QueuedMessage& next); + bool allocate(const std::string& c, const QueuedMessage& qm); + bool nextBrowsableMessage(Consumer::shared_ptr& c, QueuedMessage& next); + void query(qpid::types::Variant::Map&) const; + + bool match(const qpid::types::Variant::Map*, const QueuedMessage&) const; +}; + +}} + +#endif diff --git a/cpp/src/qpid/broker/Messages.h b/cpp/src/qpid/broker/Messages.h index 0d75417640..448f17432a 100644 --- a/cpp/src/qpid/broker/Messages.h +++ b/cpp/src/qpid/broker/Messages.h @@ -32,7 +32,8 @@ struct QueuedMessage; /** * This interface abstracts out the access to the messages held for - * delivery by a Queue instance. + * delivery by a Queue instance. Note the the assumption at present is + * that all locking is done in the Queue itself. */ class Messages { @@ -75,7 +76,6 @@ class Messages * @return true if there is another message, false otherwise. */ virtual bool next(const framing::SequenceNumber&, QueuedMessage&) = 0; - /** * Note: Caller is responsible for ensuring that there is a front * (e.g. empty() returns false) diff --git a/cpp/src/qpid/broker/NullMessageStore.cpp b/cpp/src/qpid/broker/NullMessageStore.cpp index dc8615d58b..43f600eaf1 100644 --- a/cpp/src/qpid/broker/NullMessageStore.cpp +++ b/cpp/src/qpid/broker/NullMessageStore.cpp @@ -126,21 +126,25 @@ std::auto_ptr<TPCTransactionContext> NullMessageStore::begin(const std::string& void NullMessageStore::prepare(TPCTransactionContext& ctxt) { + qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock); prepared.insert(DummyCtxt::getXid(ctxt)); } void NullMessageStore::commit(TransactionContext& ctxt) { + qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock); prepared.erase(DummyCtxt::getXid(ctxt)); } void NullMessageStore::abort(TransactionContext& ctxt) { + qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock); prepared.erase(DummyCtxt::getXid(ctxt)); } void NullMessageStore::collectPreparedXids(std::set<std::string>& out) { + qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock); out.insert(prepared.begin(), prepared.end()); } diff --git a/cpp/src/qpid/broker/NullMessageStore.h b/cpp/src/qpid/broker/NullMessageStore.h index e148ec4d51..c6f402662e 100644 --- a/cpp/src/qpid/broker/NullMessageStore.h +++ b/cpp/src/qpid/broker/NullMessageStore.h @@ -25,6 +25,7 @@ #include "qpid/broker/BrokerImportExport.h" #include "qpid/broker/MessageStore.h" #include "qpid/broker/Queue.h" +#include "qpid/sys/Mutex.h" #include <boost/intrusive_ptr.hpp> @@ -34,10 +35,11 @@ namespace broker { /** * A null implementation of the MessageStore interface */ -class NullMessageStore : public MessageStore +class QPID_BROKER_CLASS_EXTERN NullMessageStore : public MessageStore { std::set<std::string> prepared; uint64_t nextPersistenceId; + qpid::sys::Mutex lock; public: QPID_BROKER_EXTERN NullMessageStore(); diff --git a/cpp/src/qpid/broker/PersistableMessage.cpp b/cpp/src/qpid/broker/PersistableMessage.cpp index e5fbb71cbd..7ba28eb293 100644 --- a/cpp/src/qpid/broker/PersistableMessage.cpp +++ b/cpp/src/qpid/broker/PersistableMessage.cpp @@ -34,7 +34,6 @@ class MessageStore; PersistableMessage::~PersistableMessage() {} PersistableMessage::PersistableMessage() : - asyncEnqueueCounter(0), asyncDequeueCounter(0), store(0) {} @@ -68,24 +67,6 @@ bool PersistableMessage::isContentReleased() const return contentReleaseState.released; } -bool PersistableMessage::isEnqueueComplete() { - sys::ScopedLock<sys::Mutex> l(asyncEnqueueLock); - return asyncEnqueueCounter == 0; -} - -void PersistableMessage::enqueueComplete() { - bool notify = false; - { - sys::ScopedLock<sys::Mutex> l(asyncEnqueueLock); - if (asyncEnqueueCounter > 0) { - if (--asyncEnqueueCounter == 0) { - notify = true; - } - } - } - if (notify) - allEnqueuesComplete(); -} bool PersistableMessage::isStoredOnQueue(PersistableQueue::shared_ptr queue){ if (store && (queue->getPersistenceId()!=0)) { @@ -109,12 +90,7 @@ void PersistableMessage::addToSyncList(PersistableQueue::shared_ptr queue, Messa void PersistableMessage::enqueueAsync(PersistableQueue::shared_ptr queue, MessageStore* _store) { addToSyncList(queue, _store); - enqueueAsync(); -} - -void PersistableMessage::enqueueAsync() { - sys::ScopedLock<sys::Mutex> l(asyncEnqueueLock); - asyncEnqueueCounter++; + enqueueStart(); } bool PersistableMessage::isDequeueComplete() { diff --git a/cpp/src/qpid/broker/PersistableMessage.h b/cpp/src/qpid/broker/PersistableMessage.h index 96fb922c1a..d29c2c45b4 100644 --- a/cpp/src/qpid/broker/PersistableMessage.h +++ b/cpp/src/qpid/broker/PersistableMessage.h @@ -31,6 +31,7 @@ #include "qpid/framing/amqp_types.h" #include "qpid/sys/Mutex.h" #include "qpid/broker/PersistableQueue.h" +#include "qpid/broker/AsyncCompletion.h" namespace qpid { namespace broker { @@ -43,18 +44,19 @@ class MessageStore; class PersistableMessage : public Persistable { typedef std::list< boost::weak_ptr<PersistableQueue> > syncList; - sys::Mutex asyncEnqueueLock; sys::Mutex asyncDequeueLock; sys::Mutex storeLock; - + /** - * Tracks the number of outstanding asynchronous enqueue - * operations. When the message is enqueued asynchronously the - * count is incremented; when that enqueue completes it is - * decremented. Thus when it is 0, there are no outstanding - * enqueues. + * "Ingress" messages == messages sent _to_ the broker. + * Tracks the number of outstanding asynchronous operations that must + * complete before an inbound message can be considered fully received by the + * broker. E.g. all enqueues have completed, the message has been written + * to store, credit has been replenished, etc. Once all outstanding + * operations have completed, the transfer of this message from the client + * may be considered complete. */ - int asyncEnqueueCounter; + AsyncCompletion ingressCompletion; /** * Tracks the number of outstanding asynchronous dequeue @@ -65,7 +67,6 @@ class PersistableMessage : public Persistable */ int asyncDequeueCounter; - void enqueueAsync(); void dequeueAsync(); syncList synclist; @@ -80,8 +81,6 @@ class PersistableMessage : public Persistable ContentReleaseState contentReleaseState; protected: - /** Called when all enqueues are complete for this message. */ - virtual void allEnqueuesComplete() = 0; /** Called when all dequeues are complete for this message. */ virtual void allDequeuesComplete() = 0; @@ -115,9 +114,12 @@ class PersistableMessage : public Persistable virtual QPID_BROKER_EXTERN bool isPersistent() const = 0; - QPID_BROKER_EXTERN bool isEnqueueComplete(); + /** track the progress of a message received by the broker - see ingressCompletion above */ + QPID_BROKER_INLINE_EXTERN bool isIngressComplete() { return ingressCompletion.isDone(); } + QPID_BROKER_INLINE_EXTERN AsyncCompletion& getIngressCompletion() { return ingressCompletion; } - QPID_BROKER_EXTERN void enqueueComplete(); + QPID_BROKER_INLINE_EXTERN void enqueueStart() { ingressCompletion.startCompleter(); } + QPID_BROKER_INLINE_EXTERN void enqueueComplete() { ingressCompletion.finishCompleter(); } QPID_BROKER_EXTERN void enqueueAsync(PersistableQueue::shared_ptr queue, MessageStore* _store); @@ -133,7 +135,6 @@ class PersistableMessage : public Persistable bool isStoredOnQueue(PersistableQueue::shared_ptr queue); void addToSyncList(PersistableQueue::shared_ptr queue, MessageStore* _store); - }; }} diff --git a/cpp/src/qpid/broker/Queue.cpp b/cpp/src/qpid/broker/Queue.cpp index 27c1cc4ad7..4627b1409a 100644 --- a/cpp/src/qpid/broker/Queue.cpp +++ b/cpp/src/qpid/broker/Queue.cpp @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -31,7 +31,10 @@ #include "qpid/broker/MessageStore.h" #include "qpid/broker/NullMessageStore.h" #include "qpid/broker/QueueRegistry.h" +#include "qpid/broker/QueueFlowLimit.h" #include "qpid/broker/ThresholdAlerts.h" +#include "qpid/broker/FifoDistributor.h" +#include "qpid/broker/MessageGroupManager.h" #include "qpid/StringUtils.h" #include "qpid/log/Statement.h" @@ -41,6 +44,7 @@ #include "qpid/sys/ClusterSafe.h" #include "qpid/sys/Monitor.h" #include "qpid/sys/Time.h" +#include "qpid/types/Variant.h" #include "qmf/org/apache/qpid/broker/ArgsQueuePurge.h" #include "qmf/org/apache/qpid/broker/ArgsQueueReroute.h" @@ -64,7 +68,7 @@ using std::mem_fun; namespace _qmf = qmf::org::apache::qpid::broker; -namespace +namespace { const std::string qpidMaxSize("qpid.max_size"); const std::string qpidMaxCount("qpid.max_count"); @@ -86,16 +90,16 @@ const int ENQUEUE_ONLY=1; const int ENQUEUE_AND_DEQUEUE=2; } -Queue::Queue(const string& _name, bool _autodelete, +Queue::Queue(const string& _name, bool _autodelete, MessageStore* const _store, const OwnershipToken* const _owner, Manageable* parent, Broker* b) : - name(_name), + name(_name), autodelete(_autodelete), store(_store), - owner(_owner), + owner(_owner), consumerCount(0), exclusive(0), noLocal(false), @@ -110,7 +114,8 @@ Queue::Queue(const string& _name, bool _autodelete, broker(b), deleted(false), barrier(*this), - autoDeleteTimeout(0) + autoDeleteTimeout(0), + allocator(new FifoDistributor( *messages )) { if (parent != 0 && broker != 0) { ManagementAgent* agent = broker->getManagementAgent(); @@ -163,13 +168,8 @@ void Queue::deliver(boost::intrusive_ptr<Message> msg){ //drop message QPID_LOG(info, "Dropping excluded message from " << getName()); } else { - // if no store then mark as enqueued - if (!enqueue(0, msg)){ - push(msg); - msg->enqueueComplete(); - }else { - push(msg); - } + enqueue(0, msg); + push(msg); QPID_LOG(debug, "Message " << msg << " enqueued on " << name); } } @@ -183,11 +183,10 @@ void Queue::recover(boost::intrusive_ptr<Message>& msg){ if (policy.get()) policy->recoverEnqueued(msg); push(msg, true); - if (store){ + if (store){ // setup synclist for recovered messages, so they don't get re-stored on lastNodeFailure - msg->addToSyncList(shared_from_this(), store); + msg->addToSyncList(shared_from_this(), store); } - msg->enqueueComplete(); // mark the message as enqueued if (store && (!msg->isContentLoaded() || msg->checkContentReleasable())) { //content has not been loaded, need to ensure that lazy loading mode is set: @@ -211,14 +210,13 @@ void Queue::process(boost::intrusive_ptr<Message>& msg){ void Queue::requeue(const QueuedMessage& msg){ assertClusterSafe(); QueueListeners::NotificationSet copy; - { + { Mutex::ScopedLock locker(messageLock); if (!isEnqueued(msg)) return; - msg.payload->enqueueComplete(); // mark the message as enqueued messages->reinsert(msg); listeners.populate(copy); - // for persistLastNode - don't force a message twice to disk, but force it if no force before + // for persistLastNode - don't force a message twice to disk, but force it if no force before if(inLastNodeFailure && persistLastNode && !msg.payload->isStoredOnQueue(shared_from_this())) { msg.payload->forcePersistent(); if (msg.payload->isForcedPersistent() ){ @@ -226,16 +224,17 @@ void Queue::requeue(const QueuedMessage& msg){ enqueue(0, payload); } } + observeRequeue(msg, locker); } copy.notify(); } -bool Queue::acquireMessageAt(const SequenceNumber& position, QueuedMessage& message) +bool Queue::acquireMessageAt(const SequenceNumber& position, QueuedMessage& message) { Mutex::ScopedLock locker(messageLock); assertClusterSafe(); QPID_LOG(debug, "Attempting to acquire message at " << position); - if (messages->remove(position, message)) { + if (acquire(position, message, locker)) { QPID_LOG(debug, "Acquired message at " << position << " from " << name); return true; } else { @@ -244,9 +243,24 @@ bool Queue::acquireMessageAt(const SequenceNumber& position, QueuedMessage& mess } } -bool Queue::acquire(const QueuedMessage& msg) { - QueuedMessage copy = msg; - return acquireMessageAt(msg.position, copy); +bool Queue::acquire(const QueuedMessage& msg, const std::string& consumer) +{ + Mutex::ScopedLock locker(messageLock); + assertClusterSafe(); + QPID_LOG(debug, consumer << " attempting to acquire message at " << msg.position); + + if (!allocator->allocate( consumer, msg )) { + QPID_LOG(debug, "Not permitted to acquire msg at " << msg.position << " from '" << name); + return false; + } + + QueuedMessage copy(msg); + if (acquire( msg.position, copy, locker)) { + QPID_LOG(debug, "Acquired message at " << msg.position << " from " << name); + return true; + } + QPID_LOG(debug, "Could not acquire message at " << msg.position << " from " << name << "; no message at that position"); + return false; } void Queue::notifyListener() @@ -262,7 +276,7 @@ void Queue::notifyListener() set.notify(); } -bool Queue::getNextMessage(QueuedMessage& m, Consumer::shared_ptr c) +bool Queue::getNextMessage(QueuedMessage& m, Consumer::shared_ptr& c) { checkNotDeleted(); if (c->preAcquires()) { @@ -274,52 +288,71 @@ bool Queue::getNextMessage(QueuedMessage& m, Consumer::shared_ptr c) case NO_MESSAGES: default: return false; - } + } } else { return browseNextMessage(m, c); } } -Queue::ConsumeCode Queue::consumeNextMessage(QueuedMessage& m, Consumer::shared_ptr c) +Queue::ConsumeCode Queue::consumeNextMessage(QueuedMessage& m, Consumer::shared_ptr& c) { while (true) { Mutex::ScopedLock locker(messageLock); - if (messages->empty()) { - QPID_LOG(debug, "No messages to dispatch on queue '" << name << "'"); + QueuedMessage msg; + + if (!allocator->nextConsumableMessage(c, msg)) { // no next available + QPID_LOG(debug, "No messages available to dispatch to consumer " << + c->getName() << " on queue '" << name << "'"); listeners.addListener(c); return NO_MESSAGES; - } else { - QueuedMessage msg = messages->front(); - if (msg.payload->hasExpired()) { - QPID_LOG(debug, "Message expired from queue '" << name << "'"); - popAndDequeue(); - continue; - } + } - if (c->filter(msg.payload)) { - if (c->accept(msg.payload)) { - m = msg; - pop(); - return CONSUMED; - } else { - //message(s) are available but consumer hasn't got enough credit - QPID_LOG(debug, "Consumer can't currently accept message from '" << name << "'"); - return CANT_CONSUME; - } + if (msg.payload->hasExpired()) { + QPID_LOG(debug, "Message expired from queue '" << name << "'"); + c->position = msg.position; + acquire( msg.position, msg, locker); + dequeue( 0, msg ); + continue; + } + + // a message is available for this consumer - can the consumer use it? + + if (c->filter(msg.payload)) { + if (c->accept(msg.payload)) { + bool ok = allocator->allocate( c->getName(), msg ); // inform allocator + (void) ok; assert(ok); + ok = acquire( msg.position, msg, locker); + (void) ok; assert(ok); + m = msg; + c->position = m.position; + return CONSUMED; } else { - //consumer will never want this message - QPID_LOG(debug, "Consumer doesn't want message from '" << name << "'"); + //message(s) are available but consumer hasn't got enough credit + QPID_LOG(debug, "Consumer can't currently accept message from '" << name << "'"); return CANT_CONSUME; - } + } + } else { + //consumer will never want this message + QPID_LOG(debug, "Consumer doesn't want message from '" << name << "'"); + c->position = msg.position; + return CANT_CONSUME; } } } - -bool Queue::browseNextMessage(QueuedMessage& m, Consumer::shared_ptr c) +bool Queue::browseNextMessage(QueuedMessage& m, Consumer::shared_ptr& c) { - QueuedMessage msg(this); - while (seek(msg, c)) { + while (true) { + Mutex::ScopedLock locker(messageLock); + QueuedMessage msg; + + if (!allocator->nextBrowsableMessage(c, msg)) { // no next available + QPID_LOG(debug, "No browsable messages available for consumer " << + c->getName() << " on queue '" << name << "'"); + listeners.addListener(c); + return false; + } + if (c->filter(msg.payload) && !msg.payload->hasExpired()) { if (c->accept(msg.payload)) { //consumer wants the message @@ -333,8 +366,8 @@ bool Queue::browseNextMessage(QueuedMessage& m, Consumer::shared_ptr c) } } else { //consumer will never want this message, continue seeking - c->position = msg.position; QPID_LOG(debug, "Browser skipping message from '" << name << "'"); + c->position = msg.position; } } return false; @@ -364,61 +397,71 @@ bool Queue::dispatch(Consumer::shared_ptr c) } } -// Find the next message -bool Queue::seek(QueuedMessage& msg, Consumer::shared_ptr c) { - Mutex::ScopedLock locker(messageLock); - if (messages->next(c->position, msg)) { - return true; - } else { - listeners.addListener(c); - return false; - } -} - -QueuedMessage Queue::find(SequenceNumber pos) const { +bool Queue::find(SequenceNumber pos, QueuedMessage& msg) const { Mutex::ScopedLock locker(messageLock); - QueuedMessage msg; - messages->find(pos, msg); - return msg; + if (messages->find(pos, msg)) + return true; + return false; } void Queue::consume(Consumer::shared_ptr c, bool requestExclusive){ assertClusterSafe(); - Mutex::ScopedLock locker(consumerLock); - if(exclusive) { - throw ResourceLockedException( - QPID_MSG("Queue " << getName() << " has an exclusive consumer. No more consumers allowed.")); - } else if(requestExclusive) { - if(consumerCount) { + { + Mutex::ScopedLock locker(consumerLock); + if(exclusive) { throw ResourceLockedException( - QPID_MSG("Queue " << getName() << " already has consumers. Exclusive access denied.")); - } else { - exclusive = c->getSession(); + QPID_MSG("Queue " << getName() << " has an exclusive consumer. No more consumers allowed.")); + } else if(requestExclusive) { + if(consumerCount) { + throw ResourceLockedException( + QPID_MSG("Queue " << getName() << " already has consumers. Exclusive access denied.")); + } else { + exclusive = c->getSession(); + } + } + consumerCount++; + if (mgmtObject != 0) + mgmtObject->inc_consumerCount (); + //reset auto deletion timer if necessary + if (autoDeleteTimeout && autoDeleteTask) { + autoDeleteTask->cancel(); } } - consumerCount++; - if (mgmtObject != 0) - mgmtObject->inc_consumerCount (); - //reset auto deletion timer if necessary - if (autoDeleteTimeout && autoDeleteTask) { - autoDeleteTask->cancel(); + Mutex::ScopedLock locker(messageLock); + for (Observers::const_iterator i = observers.begin(); i != observers.end(); ++i) { + try{ + (*i)->consumerAdded(*c); + } catch (const std::exception& e) { + QPID_LOG(warning, "Exception on notification of new consumer for queue " << getName() << ": " << e.what()); + } } } void Queue::cancel(Consumer::shared_ptr c){ removeListener(c); - Mutex::ScopedLock locker(consumerLock); - consumerCount--; - if(exclusive) exclusive = 0; - if (mgmtObject != 0) - mgmtObject->dec_consumerCount (); + { + Mutex::ScopedLock locker(consumerLock); + consumerCount--; + if(exclusive) exclusive = 0; + if (mgmtObject != 0) + mgmtObject->dec_consumerCount (); + } + Mutex::ScopedLock locker(messageLock); + for (Observers::const_iterator i = observers.begin(); i != observers.end(); ++i) { + try{ + (*i)->consumerRemoved(*c); + } catch (const std::exception& e) { + QPID_LOG(warning, "Exception on notification of removed consumer for queue " << getName() << ": " << e.what()); + } + } } QueuedMessage Queue::get(){ Mutex::ScopedLock locker(messageLock); QueuedMessage msg(this); - messages->pop(msg); + if (messages->pop(msg)) + observeAcquire(msg, locker); return msg; } @@ -432,22 +475,135 @@ bool collect_if_expired(std::deque<QueuedMessage>& expired, QueuedMessage& messa } } -void Queue::purgeExpired() +/** + *@param lapse: time since the last purgeExpired + */ +void Queue::purgeExpired(qpid::sys::Duration lapse) { //As expired messages are discarded during dequeue also, only //bother explicitly expiring if the rate of dequeues since last - //attempt is less than one per second. - - if (dequeueTracker.sampleRatePerSecond() < 1) { + //attempt is less than one per second. + int count = dequeueSincePurge.get(); + dequeueSincePurge -= count; + int seconds = int64_t(lapse)/qpid::sys::TIME_SEC; + if (seconds == 0 || count / seconds < 1) { std::deque<QueuedMessage> expired; { Mutex::ScopedLock locker(messageLock); - messages->removeIf(boost::bind(&collect_if_expired, expired, _1)); + messages->removeIf(boost::bind(&collect_if_expired, boost::ref(expired), _1)); + } + + for (std::deque<QueuedMessage>::const_iterator i = expired.begin(); + i != expired.end(); ++i) { + { + Mutex::ScopedLock locker(messageLock); + observeAcquire(*i, locker); + } + dequeue( 0, *i ); } - for_each(expired.begin(), expired.end(), bind(&Queue::dequeue, this, (TransactionContext*) 0, _1)); } } + +namespace { + // for use with purge/move below - collect messages that match a given filter + // + class MessageFilter + { + public: + static const std::string typeKey; + static const std::string paramsKey; + static MessageFilter *create( const ::qpid::types::Variant::Map *filter ); + virtual bool match( const QueuedMessage& ) const { return true; } + virtual ~MessageFilter() {} + protected: + MessageFilter() {}; + }; + const std::string MessageFilter::typeKey("filter_type"); + const std::string MessageFilter::paramsKey("filter_params"); + + // filter by message header string value exact match + class HeaderMatchFilter : public MessageFilter + { + public: + /* Config: + { 'filter_type' : 'header_match_str', + 'filter_params' : { 'header_key' : "<header name>", + 'header_value' : "<value to match>" + } + } + */ + static const std::string typeKey; + static const std::string headerKey; + static const std::string valueKey; + HeaderMatchFilter( const std::string& _header, const std::string& _value ) + : MessageFilter (), header(_header), value(_value) {} + bool match( const QueuedMessage& msg ) const + { + const qpid::framing::FieldTable* headers = msg.payload->getApplicationHeaders(); + if (!headers) return false; + FieldTable::ValuePtr h = headers->get(header); + if (!h || !h->convertsTo<std::string>()) return false; + return h->get<std::string>() == value; + } + private: + const std::string header; + const std::string value; + }; + const std::string HeaderMatchFilter::typeKey("header_match_str"); + const std::string HeaderMatchFilter::headerKey("header_key"); + const std::string HeaderMatchFilter::valueKey("header_value"); + + // factory to create correct filter based on map + MessageFilter* MessageFilter::create( const ::qpid::types::Variant::Map *filter ) + { + using namespace qpid::types; + if (filter && !filter->empty()) { + Variant::Map::const_iterator i = filter->find(MessageFilter::typeKey); + if (i != filter->end()) { + + if (i->second.asString() == HeaderMatchFilter::typeKey) { + Variant::Map::const_iterator p = filter->find(MessageFilter::paramsKey); + if (p != filter->end() && p->second.getType() == VAR_MAP) { + Variant::Map::const_iterator k = p->second.asMap().find(HeaderMatchFilter::headerKey); + Variant::Map::const_iterator v = p->second.asMap().find(HeaderMatchFilter::valueKey); + if (k != p->second.asMap().end() && v != p->second.asMap().end()) { + std::string headerKey(k->second.asString()); + std::string value(v->second.asString()); + QPID_LOG(debug, "Message filtering by header value configured. key: " << headerKey << " value: " << value ); + return new HeaderMatchFilter( headerKey, value ); + } + } + } + } + QPID_LOG(error, "Ignoring unrecognized message filter: '" << *filter << "'"); + } + return new MessageFilter(); + } + + // used by removeIf() to collect all messages matching a filter, maximum match count is + // optional. + struct Collector { + const uint32_t maxMatches; + MessageFilter& filter; + std::deque<QueuedMessage> matches; + Collector(MessageFilter& filter, uint32_t max) + : maxMatches(max), filter(filter) {} + bool operator() (QueuedMessage& qm) + { + if (maxMatches == 0 || matches.size() < maxMatches) { + if (filter.match( qm )) { + matches.push_back(qm); + return true; + } + } + return false; + } + }; + +} // end namespace + + /** * purge - for purging all or some messages on a queue * depending on the purge_request @@ -459,63 +615,77 @@ void Queue::purgeExpired() * The dest exchange may be supplied to re-route messages through the exchange. * It is safe to re-route messages such that they arrive back on the same queue, * even if the queue is ordered by priority. + * + * An optional filter can be supplied that will be applied against each message. The + * message is purged only if the filter matches. See MessageDistributor for more detail. */ -uint32_t Queue::purge(const uint32_t purge_request, boost::shared_ptr<Exchange> dest) +uint32_t Queue::purge(const uint32_t purge_request, boost::shared_ptr<Exchange> dest, + const qpid::types::Variant::Map *filter) { - Mutex::ScopedLock locker(messageLock); - uint32_t purge_count = purge_request; // only comes into play if >0 - std::deque<DeliverableMessage> rerouteQueue; + std::auto_ptr<MessageFilter> mf(MessageFilter::create(filter)); + Collector c(*mf.get(), purge_request); - uint32_t count = 0; - // Either purge them all or just the some (purge_count) while the queue isn't empty. - while((!purge_request || purge_count--) && !messages->empty()) { + Mutex::ScopedLock locker(messageLock); + messages->removeIf( boost::bind<bool>(boost::ref(c), _1) ); + for (std::deque<QueuedMessage>::iterator qmsg = c.matches.begin(); + qmsg != c.matches.end(); ++qmsg) { + // Update observers and message state: + observeAcquire(*qmsg, locker); + dequeue(0, *qmsg); + // now reroute if necessary if (dest.get()) { - // - // If there is a destination exchange, stage the messages onto a reroute queue - // so they don't wind up getting purged more than once. - // - DeliverableMessage msg(messages->front().payload); - rerouteQueue.push_back(msg); + assert(qmsg->payload); + DeliverableMessage dmsg(qmsg->payload); + dest->routeWithAlternate(dmsg); } - popAndDequeue(); - count++; } - - // - // Re-route purged messages into the destination exchange. Note that there's no need - // to test dest.get() here because if it is NULL, the rerouteQueue will be empty. - // - while (!rerouteQueue.empty()) { - DeliverableMessage msg(rerouteQueue.front()); - rerouteQueue.pop_front(); - dest->route(msg, msg.getMessage().getRoutingKey(), - msg.getMessage().getApplicationHeaders()); - } - - return count; + return c.matches.size(); } -uint32_t Queue::move(const Queue::shared_ptr destq, uint32_t qty) { - Mutex::ScopedLock locker(messageLock); - uint32_t move_count = qty; // only comes into play if qty >0 - uint32_t count = 0; // count how many were moved for returning +uint32_t Queue::move(const Queue::shared_ptr destq, uint32_t qty, + const qpid::types::Variant::Map *filter) +{ + std::auto_ptr<MessageFilter> mf(MessageFilter::create(filter)); + Collector c(*mf.get(), qty); - while((!qty || move_count--) && !messages->empty()) { - QueuedMessage qmsg = messages->front(); - boost::intrusive_ptr<Message> msg = qmsg.payload; - destq->deliver(msg); // deliver message to the destination queue - pop(); - dequeue(0, qmsg); - count++; + Mutex::ScopedLock locker(messageLock); + messages->removeIf( boost::bind<bool>(boost::ref(c), _1) ); + + for (std::deque<QueuedMessage>::iterator qmsg = c.matches.begin(); + qmsg != c.matches.end(); ++qmsg) { + // Update observers and message state: + observeAcquire(*qmsg, locker); + dequeue(0, *qmsg); + // and move to destination Queue. + assert(qmsg->payload); + destq->deliver(qmsg->payload); } - return count; + return c.matches.size(); } -void Queue::pop() +/** Acquire the front (oldest) message from the in-memory queue. + * assumes messageLock held by caller + */ +void Queue::pop(const Mutex::ScopedLock& locker) { assertClusterSafe(); - messages->pop(); - ++dequeueTracker; + QueuedMessage msg; + if (messages->pop(msg)) { + observeAcquire(msg, locker); + ++dequeueSincePurge; + } +} + +/** Acquire the message at the given position, return true and msg if acquire succeeds */ +bool Queue::acquire(const qpid::framing::SequenceNumber& position, QueuedMessage& msg, + const Mutex::ScopedLock& locker) +{ + if (messages->remove(position, msg)) { + observeAcquire(msg, locker); + ++dequeueSincePurge; + return true; + } + return false; } void Queue::push(boost::intrusive_ptr<Message>& msg, bool isRecovery){ @@ -524,13 +694,15 @@ void Queue::push(boost::intrusive_ptr<Message>& msg, bool isRecovery){ QueuedMessage removed; bool dequeueRequired = false; { - Mutex::ScopedLock locker(messageLock); + Mutex::ScopedLock locker(messageLock); QueuedMessage qm(this, msg, ++sequence); - if (insertSeqNo) msg->getOrInsertHeaders().setInt64(seqNoKey, sequence); - + if (insertSeqNo) msg->insertCustomProperty(seqNoKey, sequence); + dequeueRequired = messages->push(qm, removed); + if (dequeueRequired) + observeAcquire(removed, locker); listeners.populate(copy); - enqueued(qm); + observeEnqueue(qm, locker); } copy.notify(); if (dequeueRequired) { @@ -546,7 +718,7 @@ void Queue::push(boost::intrusive_ptr<Message>& msg, bool isRecovery){ void isEnqueueComplete(uint32_t* result, const QueuedMessage& message) { - if (message.payload->isEnqueueComplete()) (*result)++; + if (message.payload->isIngressComplete()) (*result)++; } /** function only provided for unit tests, or code not in critical message path */ @@ -606,7 +778,7 @@ void Queue::setLastNodeFailure() } -// return true if store exists, +// return true if store exists, bool Queue::enqueue(TransactionContext* ctxt, boost::intrusive_ptr<Message>& msg, bool suppressPolicyCheck) { ScopedUse u(barrier); @@ -620,24 +792,21 @@ bool Queue::enqueue(TransactionContext* ctxt, boost::intrusive_ptr<Message>& msg policy->getPendingDequeues(dequeues); } //depending on policy, may have some dequeues that need to performed without holding the lock - for_each(dequeues.begin(), dequeues.end(), boost::bind(&Queue::dequeue, this, (TransactionContext*) 0, _1)); + for_each(dequeues.begin(), dequeues.end(), boost::bind(&Queue::dequeue, this, (TransactionContext*) 0, _1)); } if (inLastNodeFailure && persistLastNode){ msg->forcePersistent(); } - + if (traceId.size()) { - //copy on write: take deep copy of message before modifying it - //as the frames may already be available for delivery on other - //threads - boost::intrusive_ptr<Message> copy(new Message(*msg)); - msg = copy; msg->addTraceId(traceId); } if ((msg->isPersistent() || msg->checkContentReleasable()) && store) { - msg->enqueueAsync(shared_from_this(), store); //increment to async counter -- for message sent to more than one queue + // mark the message as being enqueued - the store MUST CALL msg->enqueueComplete() + // when it considers the message stored. + msg->enqueueAsync(shared_from_this(), store); boost::intrusive_ptr<PersistableMessage> pmsg = boost::static_pointer_cast<PersistableMessage>(msg); store->enqueue(ctxt, pmsg, *this); return true; @@ -654,10 +823,10 @@ bool Queue::enqueue(TransactionContext* ctxt, boost::intrusive_ptr<Message>& msg void Queue::enqueueAborted(boost::intrusive_ptr<Message> msg) { Mutex::ScopedLock locker(messageLock); - if (policy.get()) policy->enqueueAborted(msg); + if (policy.get()) policy->enqueueAborted(msg); } -// return true if store exists, +// return true if store exists, bool Queue::dequeue(TransactionContext* ctxt, const QueuedMessage& msg) { ScopedUse u(barrier); @@ -666,8 +835,8 @@ bool Queue::dequeue(TransactionContext* ctxt, const QueuedMessage& msg) { Mutex::ScopedLock locker(messageLock); if (!isEnqueued(msg)) return false; - if (!ctxt) { - dequeued(msg); + if (!ctxt) { + observeDequeue(msg, locker); } } // This check prevents messages which have been forced persistent on one queue from dequeuing @@ -687,7 +856,7 @@ bool Queue::dequeue(TransactionContext* ctxt, const QueuedMessage& msg) void Queue::dequeueCommitted(const QueuedMessage& msg) { Mutex::ScopedLock locker(messageLock); - dequeued(msg); + observeDequeue(msg, locker); if (mgmtObject != 0) { mgmtObject->inc_msgTxnDequeues(); mgmtObject->inc_byteTxnDequeues(msg.payload->contentSize()); @@ -695,21 +864,23 @@ void Queue::dequeueCommitted(const QueuedMessage& msg) } /** - * Removes a message from the in-memory delivery queue as well - * dequeing it from the logical (and persistent if applicable) queue + * Removes the first (oldest) message from the in-memory delivery queue as well dequeing + * it from the logical (and persistent if applicable) queue */ -void Queue::popAndDequeue() +void Queue::popAndDequeue(const Mutex::ScopedLock& held) { - QueuedMessage msg = messages->front(); - pop(); - dequeue(0, msg); + if (!messages->empty()) { + QueuedMessage msg = messages->front(); + pop(held); + dequeue(0, msg); + } } /** * Updates policy and management when a message has been dequeued, * expects messageLock to be held */ -void Queue::dequeued(const QueuedMessage& msg) +void Queue::observeDequeue(const QueuedMessage& msg, const Mutex::ScopedLock&) { if (policy.get()) policy->dequeued(msg); mgntDeqStats(msg.payload); @@ -722,6 +893,33 @@ void Queue::dequeued(const QueuedMessage& msg) } } +/** updates queue observers when a message has become unavailable for transfer, + * expects messageLock to be held + */ +void Queue::observeAcquire(const QueuedMessage& msg, const Mutex::ScopedLock&) +{ + for (Observers::const_iterator i = observers.begin(); i != observers.end(); ++i) { + try{ + (*i)->acquired(msg); + } catch (const std::exception& e) { + QPID_LOG(warning, "Exception on notification of message removal for queue " << getName() << ": " << e.what()); + } + } +} + +/** updates queue observers when a message has become re-available for transfer, + * expects messageLock to be held + */ +void Queue::observeRequeue(const QueuedMessage& msg, const Mutex::ScopedLock&) +{ + for (Observers::const_iterator i = observers.begin(); i != observers.end(); ++i) { + try{ + (*i)->requeued(msg); + } catch (const std::exception& e) { + QPID_LOG(warning, "Exception on notification of message requeue for queue " << getName() << ": " << e.what()); + } + } +} void Queue::create(const FieldTable& _settings) { @@ -729,7 +927,7 @@ void Queue::create(const FieldTable& _settings) if (store) { store->create(*this, _settings); } - configure(_settings); + configureImpl(_settings); } @@ -742,8 +940,8 @@ int getIntegerSetting(const qpid::framing::FieldTable& settings, const std::stri return v->get<int>(); } else if (v->convertsTo<std::string>()){ std::string s = v->get<std::string>(); - try { - return boost::lexical_cast<int>(s); + try { + return boost::lexical_cast<int>(s); } catch(const boost::bad_lexical_cast&) { QPID_LOG(warning, "Ignoring invalid integer value for " << key << ": " << s); return 0; @@ -754,15 +952,45 @@ int getIntegerSetting(const qpid::framing::FieldTable& settings, const std::stri } } -void Queue::configure(const FieldTable& _settings, bool recovering) +bool getBoolSetting(const qpid::framing::FieldTable& settings, const std::string& key) { + qpid::framing::FieldTable::ValuePtr v = settings.get(key); + if (!v) { + return false; + } else if (v->convertsTo<int>()) { + return v->get<int>() != 0; + } else if (v->convertsTo<std::string>()){ + std::string s = v->get<std::string>(); + if (s == "True") return true; + if (s == "true") return true; + if (s == "False") return false; + if (s == "false") return false; + try { + return boost::lexical_cast<bool>(s); + } catch(const boost::bad_lexical_cast&) { + QPID_LOG(warning, "Ignoring invalid boolean value for " << key << ": " << s); + return false; + } + } else { + QPID_LOG(warning, "Ignoring invalid boolean value for " << key << ": " << *v); + return false; + } +} + +void Queue::configure(const FieldTable& _settings) +{ + settings = _settings; + configureImpl(settings); +} +void Queue::configureImpl(const FieldTable& _settings) +{ eventMode = _settings.getAsInt(qpidQueueEventGeneration); if (eventMode && broker) { broker->getQueueEvents().observe(*this, eventMode == ENQUEUE_ONLY); } - if (QueuePolicy::getType(_settings) == QueuePolicy::FLOW_TO_DISK && + if (QueuePolicy::getType(_settings) == QueuePolicy::FLOW_TO_DISK && (!store || NullMessageStore::isNullStore(store) || (broker && !(broker->getQueueEvents().isSync())) )) { if ( NullMessageStore::isNullStore(store)) { QPID_LOG(warning, "Flow to disk not valid for non-persisted queue:" << getName()); @@ -776,32 +1004,43 @@ void Queue::configure(const FieldTable& _settings, bool recovering) setPolicy(QueuePolicy::createQueuePolicy(getName(), _settings)); } if (broker && broker->getManagementAgent()) { - ThresholdAlerts::observe(*this, *(broker->getManagementAgent()), _settings); + ThresholdAlerts::observe(*this, *(broker->getManagementAgent()), _settings, broker->getOptions().queueThresholdEventRatio); } //set this regardless of owner to allow use of no-local with exclusive consumers also - noLocal = _settings.get(qpidNoLocal); + noLocal = getBoolSetting(_settings, qpidNoLocal); QPID_LOG(debug, "Configured queue " << getName() << " with no-local=" << noLocal); std::string lvqKey = _settings.getAsString(qpidLastValueQueueKey); if (lvqKey.size()) { QPID_LOG(debug, "Configured queue " << getName() << " as Last Value Queue with key " << lvqKey); messages = std::auto_ptr<Messages>(new MessageMap(lvqKey)); - } else if (_settings.get(qpidLastValueQueueNoBrowse)) { + allocator = boost::shared_ptr<MessageDistributor>(new FifoDistributor( *messages )); + } else if (getBoolSetting(_settings, qpidLastValueQueueNoBrowse)) { QPID_LOG(debug, "Configured queue " << getName() << " as Legacy Last Value Queue with 'no-browse' on"); messages = LegacyLVQ::updateOrReplace(messages, qpidVQMatchProperty, true, broker); - } else if (_settings.get(qpidLastValueQueue)) { + allocator = boost::shared_ptr<MessageDistributor>(new FifoDistributor( *messages )); + } else if (getBoolSetting(_settings, qpidLastValueQueue)) { QPID_LOG(debug, "Configured queue " << getName() << " as Legacy Last Value Queue"); messages = LegacyLVQ::updateOrReplace(messages, qpidVQMatchProperty, false, broker); + allocator = boost::shared_ptr<MessageDistributor>(new FifoDistributor( *messages )); } else { std::auto_ptr<Messages> m = Fairshare::create(_settings); if (m.get()) { messages = m; + allocator = boost::shared_ptr<MessageDistributor>(new FifoDistributor( *messages )); QPID_LOG(debug, "Configured queue " << getName() << " as priority queue."); + } else { // default (FIFO) queue type + // override default message allocator if message groups configured. + boost::shared_ptr<MessageGroupManager> mgm(MessageGroupManager::create( getName(), *messages, _settings)); + if (mgm) { + allocator = mgm; + addObserver(mgm); + } } } - - persistLastNode= _settings.get(qpidPersistLastNode); + + persistLastNode = getBoolSetting(_settings, qpidPersistLastNode); if (persistLastNode) QPID_LOG(debug, "Configured queue to Persist data if cluster fails to one node for: " << getName()); traceId = _settings.getAsString(qpidTraceIdentity); @@ -809,32 +1048,32 @@ void Queue::configure(const FieldTable& _settings, bool recovering) if (excludeList.size()) { split(traceExclude, excludeList, ", "); } - QPID_LOG(debug, "Configured queue " << getName() << " with qpid.trace.id='" << traceId + QPID_LOG(debug, "Configured queue " << getName() << " with qpid.trace.id='" << traceId << "' and qpid.trace.exclude='"<< excludeList << "' i.e. " << traceExclude.size() << " elements"); FieldTable::ValuePtr p =_settings.get(qpidInsertSequenceNumbers); if (p && p->convertsTo<std::string>()) insertSequenceNumbers(p->get<std::string>()); autoDeleteTimeout = getIntegerSetting(_settings, qpidAutoDeleteTimeout); - if (autoDeleteTimeout) - QPID_LOG(debug, "Configured queue " << getName() << " with qpid.auto_delete_timeout=" << autoDeleteTimeout); + if (autoDeleteTimeout) + QPID_LOG(debug, "Configured queue " << getName() << " with qpid.auto_delete_timeout=" << autoDeleteTimeout); - if (mgmtObject != 0) + if (mgmtObject != 0) { mgmtObject->set_arguments(ManagementAgent::toMap(_settings)); + } - if ( isDurable() && ! getPersistenceId() && ! recovering ) - store->create(*this, _settings); + QueueFlowLimit::observe(*this, _settings); } -void Queue::destroy() +void Queue::destroyed() { + unbind(broker->getExchanges()); if (alternateExchange.get()) { Mutex::ScopedLock locker(messageLock); while(!messages->empty()){ DeliverableMessage msg(messages->front().payload); - alternateExchange->route(msg, msg.getMessage().getRoutingKey(), - msg.getMessage().getApplicationHeaders()); - popAndDequeue(); + alternateExchange->routeWithAlternate(msg); + popAndDequeue(locker); } alternateExchange->decAlternateUsers(); } @@ -846,6 +1085,7 @@ void Queue::destroy() store = 0;//ensure we make no more calls to the store for this queue } if (autoDeleteTask) autoDeleteTask = boost::intrusive_ptr<TimerTask>(); + notifyDeleted(); } void Queue::notifyDeleted() @@ -865,9 +1105,9 @@ void Queue::bound(const string& exchange, const string& key, bindings.add(exchange, key, args); } -void Queue::unbind(ExchangeRegistry& exchanges, Queue::shared_ptr shared_ref) +void Queue::unbind(ExchangeRegistry& exchanges) { - bindings.unbind(exchanges, shared_ref); + bindings.unbind(exchanges, shared_from_this()); } void Queue::setPolicy(std::auto_ptr<QueuePolicy> _policy) @@ -880,9 +1120,9 @@ const QueuePolicy* Queue::getPolicy() return policy.get(); } -uint64_t Queue::getPersistenceId() const -{ - return persistenceId; +uint64_t Queue::getPersistenceId() const +{ + return persistenceId; } void Queue::setPersistenceId(uint64_t _persistenceId) const @@ -896,11 +1136,11 @@ void Queue::setPersistenceId(uint64_t _persistenceId) const persistenceId = _persistenceId; } -void Queue::encode(Buffer& buffer) const +void Queue::encode(Buffer& buffer) const { buffer.putShortString(name); buffer.put(settings); - if (policy.get()) { + if (policy.get()) { buffer.put(*policy); } buffer.putShortString(alternateExchange.get() ? alternateExchange->getName() : std::string("")); @@ -914,13 +1154,14 @@ uint32_t Queue::encodedSize() const + (policy.get() ? (*policy).encodedSize() : 0); } -Queue::shared_ptr Queue::decode ( QueueRegistry& queues, Buffer& buffer, bool recovering ) +Queue::shared_ptr Queue::restore( QueueRegistry& queues, Buffer& buffer ) { string name; buffer.getShortString(name); - std::pair<Queue::shared_ptr, bool> result = queues.declare(name, true); - buffer.get(result.first->settings); - result.first->configure(result.first->settings, recovering ); + FieldTable settings; + buffer.get(settings); + boost::shared_ptr<Exchange> alternate; + std::pair<Queue::shared_ptr, bool> result = queues.declare(name, true, false, 0, alternate, settings, true); if (result.first->policy.get() && buffer.available() >= result.first->policy->encodedSize()) { buffer.get ( *(result.first->policy) ); } @@ -952,11 +1193,10 @@ boost::shared_ptr<Exchange> Queue::getAlternateExchange() void tryAutoDeleteImpl(Broker& broker, Queue::shared_ptr queue) { - if (broker.getQueues().destroyIf(queue->getName(), + if (broker.getQueues().destroyIf(queue->getName(), boost::bind(boost::mem_fn(&Queue::canAutoDelete), queue))) { QPID_LOG(debug, "Auto-deleting " << queue->getName()); - queue->unbind(broker.getExchanges(), queue); - queue->destroy(); + queue->destroyed(); } } @@ -965,7 +1205,7 @@ struct AutoDeleteTask : qpid::sys::TimerTask Broker& broker; Queue::shared_ptr queue; - AutoDeleteTask(Broker& b, Queue::shared_ptr q, AbsTime fireTime) + AutoDeleteTask(Broker& b, Queue::shared_ptr q, AbsTime fireTime) : qpid::sys::TimerTask(fireTime, "DelayedAutoDeletion"), broker(b), queue(q) {} void fire() @@ -983,27 +1223,27 @@ void Queue::tryAutoDelete(Broker& broker, Queue::shared_ptr queue) if (queue->autoDeleteTimeout && queue->canAutoDelete()) { AbsTime time(now(), Duration(queue->autoDeleteTimeout * TIME_SEC)); queue->autoDeleteTask = boost::intrusive_ptr<qpid::sys::TimerTask>(new AutoDeleteTask(broker, queue, time)); - broker.getClusterTimer().add(queue->autoDeleteTask); + broker.getClusterTimer().add(queue->autoDeleteTask); QPID_LOG(debug, "Timed auto-delete for " << queue->getName() << " initiated"); } else { tryAutoDeleteImpl(broker, queue); } } -bool Queue::isExclusiveOwner(const OwnershipToken* const o) const -{ +bool Queue::isExclusiveOwner(const OwnershipToken* const o) const +{ Mutex::ScopedLock locker(ownershipLock); - return o == owner; + return o == owner; } -void Queue::releaseExclusiveOwnership() -{ +void Queue::releaseExclusiveOwnership() +{ Mutex::ScopedLock locker(ownershipLock); - owner = 0; + owner = 0; } -bool Queue::setExclusiveOwner(const OwnershipToken* const o) -{ +bool Queue::setExclusiveOwner(const OwnershipToken* const o) +{ //reset auto deletion timer if necessary if (autoDeleteTimeout && autoDeleteTask) { autoDeleteTask->cancel(); @@ -1012,25 +1252,25 @@ bool Queue::setExclusiveOwner(const OwnershipToken* const o) if (owner) { return false; } else { - owner = o; + owner = o; return true; } } -bool Queue::hasExclusiveOwner() const -{ +bool Queue::hasExclusiveOwner() const +{ Mutex::ScopedLock locker(ownershipLock); - return owner != 0; + return owner != 0; } -bool Queue::hasExclusiveConsumer() const -{ - return exclusive; +bool Queue::hasExclusiveConsumer() const +{ + return exclusive; } void Queue::setExternalQueueStore(ExternalQueueStore* inst) { - if (externalQueueStore!=inst && externalQueueStore) - delete externalQueueStore; + if (externalQueueStore!=inst && externalQueueStore) + delete externalQueueStore; externalQueueStore = inst; if (inst) { @@ -1055,7 +1295,7 @@ Manageable::status_t Queue::ManagementMethod (uint32_t methodId, Args& args, str case _qmf::Queue::METHOD_PURGE : { _qmf::ArgsQueuePurge& purgeArgs = (_qmf::ArgsQueuePurge&) args; - purge(purgeArgs.i_request); + purge(purgeArgs.i_request, boost::shared_ptr<Exchange>(), &purgeArgs.i_filter); status = Manageable::STATUS_OK; } break; @@ -1076,7 +1316,7 @@ Manageable::status_t Queue::ManagementMethod (uint32_t methodId, Args& args, str } } - purge(rerouteArgs.i_request, dest); + purge(rerouteArgs.i_request, dest, &rerouteArgs.i_filter); status = Manageable::STATUS_OK; } break; @@ -1085,6 +1325,14 @@ Manageable::status_t Queue::ManagementMethod (uint32_t methodId, Args& args, str return status; } + +void Queue::query(qpid::types::Variant::Map& results) const +{ + Mutex::ScopedLock locker(messageLock); + /** @todo add any interesting queue state into results */ + if (allocator) allocator->query(results); +} + void Queue::setPosition(SequenceNumber n) { Mutex::ScopedLock locker(messageLock); sequence = n; @@ -1119,7 +1367,10 @@ void Queue::insertSequenceNumbers(const std::string& key) QPID_LOG(debug, "Inserting sequence numbers as " << key); } -void Queue::enqueued(const QueuedMessage& m) +/** updates queue observers and state when a message has become available for transfer, + * expects messageLock to be held + */ +void Queue::observeEnqueue(const QueuedMessage& m, const Mutex::ScopedLock&) { for (Observers::iterator i = observers.begin(); i != observers.end(); ++i) { try { @@ -1142,7 +1393,8 @@ void Queue::updateEnqueued(const QueuedMessage& m) if (policy.get()) { policy->recoverEnqueued(payload); } - enqueued(m); + Mutex::ScopedLock locker(messageLock); + observeEnqueue(m, locker); } else { QPID_LOG(warning, "Queue informed of enqueued message that has no payload"); } @@ -1166,6 +1418,7 @@ void Queue::checkNotDeleted() void Queue::addObserver(boost::shared_ptr<QueueObserver> observer) { + Mutex::ScopedLock locker(messageLock); observers.insert(observer); } @@ -1175,6 +1428,32 @@ void Queue::flush() if (u.acquired && store) store->flush(*this); } + +bool Queue::bind(boost::shared_ptr<Exchange> exchange, const std::string& key, + const qpid::framing::FieldTable& arguments) +{ + if (exchange->bind(shared_from_this(), key, &arguments)) { + bound(exchange->getName(), key, arguments); + if (exchange->isDurable() && isDurable()) { + store->bind(*exchange, *this, key, arguments); + } + return true; + } else { + return false; + } +} + + +const Broker* Queue::getBroker() +{ + return broker; +} + +void Queue::setDequeueSincePurge(uint32_t value) { + dequeueSincePurge = value; +} + + Queue::UsageBarrier::UsageBarrier(Queue& q) : parent(q), count(0) {} bool Queue::UsageBarrier::acquire() diff --git a/cpp/src/qpid/broker/Queue.h b/cpp/src/qpid/broker/Queue.h index 12a3d273be..59ae41e768 100644 --- a/cpp/src/qpid/broker/Queue.h +++ b/cpp/src/qpid/broker/Queue.h @@ -10,9 +10,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -32,9 +32,9 @@ #include "qpid/broker/QueueBindings.h" #include "qpid/broker/QueueListeners.h" #include "qpid/broker/QueueObserver.h" -#include "qpid/broker/RateTracker.h" #include "qpid/framing/FieldTable.h" +#include "qpid/sys/AtomicValue.h" #include "qpid/sys/Monitor.h" #include "qpid/sys/Timer.h" #include "qpid/management/Manageable.h" @@ -59,7 +59,7 @@ class MessageStore; class QueueEvents; class QueueRegistry; class TransactionContext; -class Exchange; +class MessageDistributor; /** * The brokers representation of an amqp queue. Messages are @@ -74,13 +74,13 @@ class Queue : public boost::enable_shared_from_this<Queue>, { Queue& parent; uint count; - + UsageBarrier(Queue&); bool acquire(); void release(); void destroy(); }; - + struct ScopedUse { UsageBarrier& barrier; @@ -88,7 +88,7 @@ class Queue : public boost::enable_shared_from_this<Queue>, ScopedUse(UsageBarrier& b) : barrier(b), acquired(barrier.acquire()) {} ~ScopedUse() { if (acquired) barrier.release(); } }; - + typedef std::set< boost::shared_ptr<QueueObserver> > Observers; enum ConsumeCode {NO_MESSAGES=0, CANT_CONSUME=1, CONSUMED=2}; @@ -119,7 +119,7 @@ class Queue : public boost::enable_shared_from_this<Queue>, boost::shared_ptr<Exchange> alternateExchange; framing::SequenceNumber sequence; qmf::org::apache::qpid::broker::Queue* mgmtObject; - RateTracker dequeueTracker; + sys::AtomicValue<uint32_t> dequeueSincePurge; // Count dequeues since last purge. int eventMode; Observers observers; bool insertSeqNo; @@ -129,26 +129,36 @@ class Queue : public boost::enable_shared_from_this<Queue>, UsageBarrier barrier; int autoDeleteTimeout; boost::intrusive_ptr<qpid::sys::TimerTask> autoDeleteTask; + boost::shared_ptr<MessageDistributor> allocator; void push(boost::intrusive_ptr<Message>& msg, bool isRecovery=false); void setPolicy(std::auto_ptr<QueuePolicy> policy); - bool seek(QueuedMessage& msg, Consumer::shared_ptr position); - bool getNextMessage(QueuedMessage& msg, Consumer::shared_ptr c); - ConsumeCode consumeNextMessage(QueuedMessage& msg, Consumer::shared_ptr c); - bool browseNextMessage(QueuedMessage& msg, Consumer::shared_ptr c); + bool getNextMessage(QueuedMessage& msg, Consumer::shared_ptr& c); + ConsumeCode consumeNextMessage(QueuedMessage& msg, Consumer::shared_ptr& c); + bool browseNextMessage(QueuedMessage& msg, Consumer::shared_ptr& c); void notifyListener(); void removeListener(Consumer::shared_ptr); bool isExcluded(boost::intrusive_ptr<Message>& msg); - void enqueued(const QueuedMessage& msg); - void dequeued(const QueuedMessage& msg); - void pop(); - void popAndDequeue(); - QueuedMessage getFront(); + /** update queue observers, stats, policy, etc when the messages' state changes. Lock + * must be held by caller */ + void observeEnqueue(const QueuedMessage& msg, const sys::Mutex::ScopedLock& lock); + void observeAcquire(const QueuedMessage& msg, const sys::Mutex::ScopedLock& lock); + void observeRequeue(const QueuedMessage& msg, const sys::Mutex::ScopedLock& lock); + void observeDequeue(const QueuedMessage& msg, const sys::Mutex::ScopedLock& lock); + + /** modify the Queue's message container - assumes messageLock held */ + void pop(const sys::Mutex::ScopedLock& held); // acquire front msg + void popAndDequeue(const sys::Mutex::ScopedLock& held); // acquire and dequeue front msg + // acquire message @ position, return true and set msg if acquire succeeds + bool acquire(const qpid::framing::SequenceNumber& position, QueuedMessage& msg, + const sys::Mutex::ScopedLock& held); + void forcePersistent(QueuedMessage& msg); int getEventMode(); + void configureImpl(const qpid::framing::FieldTable& settings); inline void mgntEnqStats(const boost::intrusive_ptr<Message>& msg) { @@ -172,8 +182,9 @@ class Queue : public boost::enable_shared_from_this<Queue>, } } } - + void checkNotDeleted(); + void notifyDeleted(); public: @@ -182,29 +193,50 @@ class Queue : public boost::enable_shared_from_this<Queue>, typedef std::vector<shared_ptr> vector; QPID_BROKER_EXTERN Queue(const std::string& name, - bool autodelete = false, - MessageStore* const store = 0, + bool autodelete = false, + MessageStore* const store = 0, const OwnershipToken* const owner = 0, management::Manageable* parent = 0, Broker* broker = 0); QPID_BROKER_EXTERN ~Queue(); + /** allow the Consumer to consume or browse the next available message */ QPID_BROKER_EXTERN bool dispatch(Consumer::shared_ptr); - void create(const qpid::framing::FieldTable& settings); + /** allow the Consumer to acquire a message that it has browsed. + * @param msg - message to be acquired. + * @return false if message is no longer available for acquire. + */ + QPID_BROKER_EXTERN bool acquire(const QueuedMessage& msg, const std::string& consumer); - // "recovering" means we are doing a MessageStore recovery. - QPID_BROKER_EXTERN void configure(const qpid::framing::FieldTable& settings, - bool recovering = false); - void destroy(); - void notifyDeleted(); + /** + * Used to configure a new queue and create a persistent record + * for it in store if required. + */ + QPID_BROKER_EXTERN void create(const qpid::framing::FieldTable& settings); + + /** + * Used to reconfigure a recovered queue (does not create + * persistent record in store). + */ + QPID_BROKER_EXTERN void configure(const qpid::framing::FieldTable& settings); + void destroyed(); QPID_BROKER_EXTERN void bound(const std::string& exchange, const std::string& key, const qpid::framing::FieldTable& args); - QPID_BROKER_EXTERN void unbind(ExchangeRegistry& exchanges, - Queue::shared_ptr shared_ref); + //TODO: get unbind out of the public interface; only there for purposes of one unit test + QPID_BROKER_EXTERN void unbind(ExchangeRegistry& exchanges); + /** + * Bind self to specified exchange, and record that binding for unbinding on delete. + */ + bool bind(boost::shared_ptr<Exchange> exchange, const std::string& key, + const qpid::framing::FieldTable& arguments=qpid::framing::FieldTable()); - QPID_BROKER_EXTERN bool acquire(const QueuedMessage& msg); + /** Acquire the message at the given position if it is available for acquire. Not to + * be used by clients, but used by the broker for queue management. + * @param message - set to the acquired message if true returned. + * @return true if the message has been acquired. + */ QPID_BROKER_EXTERN bool acquireMessageAt(const qpid::framing::SequenceNumber& position, QueuedMessage& message); /** @@ -233,11 +265,14 @@ class Queue : public boost::enable_shared_from_this<Queue>, bool exclusive = false); QPID_BROKER_EXTERN void cancel(Consumer::shared_ptr c); - uint32_t purge(const uint32_t purge_request=0, boost::shared_ptr<Exchange> dest=boost::shared_ptr<Exchange>()); //defaults to all messages - QPID_BROKER_EXTERN void purgeExpired(); + uint32_t purge(const uint32_t purge_request=0, //defaults to all messages + boost::shared_ptr<Exchange> dest=boost::shared_ptr<Exchange>(), + const ::qpid::types::Variant::Map *filter=0); + QPID_BROKER_EXTERN void purgeExpired(sys::Duration); //move qty # of messages to destination Queue destq - uint32_t move(const Queue::shared_ptr destq, uint32_t qty); + uint32_t move(const Queue::shared_ptr destq, uint32_t qty, + const qpid::types::Variant::Map *filter=0); QPID_BROKER_EXTERN uint32_t getMessageCount() const; QPID_BROKER_EXTERN uint32_t getEnqueueCompleteMessageCount() const; @@ -276,8 +311,8 @@ class Queue : public boost::enable_shared_from_this<Queue>, * Inform queue of messages that were enqueued, have since * been acquired but not yet accepted or released (and * thus are still logically on the queue) - used in - * clustered broker. - */ + * clustered broker. + */ void updateEnqueued(const QueuedMessage& msg); /** @@ -288,14 +323,14 @@ class Queue : public boost::enable_shared_from_this<Queue>, * accepted it). */ bool isEnqueued(const QueuedMessage& msg); - + /** - * Gets the next available message + * Acquires the next available (oldest) message */ QPID_BROKER_EXTERN QueuedMessage get(); - /** Get the message at position pos */ - QPID_BROKER_EXTERN QueuedMessage find(framing::SequenceNumber pos) const; + /** Get the message at position pos, returns true if found and sets msg */ + QPID_BROKER_EXTERN bool find(framing::SequenceNumber pos, QueuedMessage& msg ) const; const QueuePolicy* getPolicy(); @@ -309,8 +344,13 @@ class Queue : public boost::enable_shared_from_this<Queue>, void encode(framing::Buffer& buffer) const; uint32_t encodedSize() const; - // "recovering" means we are doing a MessageStore recovery. - static Queue::shared_ptr decode(QueueRegistry& queues, framing::Buffer& buffer, bool recovering = false ); + /** + * Restores a queue from encoded data (used in recovery) + * + * Note: restored queue will be neither auto-deleted or have an + * exclusive owner + */ + static Queue::shared_ptr restore(QueueRegistry& queues, framing::Buffer& buffer); static void tryAutoDelete(Broker& broker, Queue::shared_ptr); virtual void setExternalQueueStore(ExternalQueueStore* inst); @@ -319,6 +359,7 @@ class Queue : public boost::enable_shared_from_this<Queue>, management::ManagementObject* GetManagementObject (void) const; management::Manageable::status_t ManagementMethod (uint32_t methodId, management::Args& args, std::string& text); + void query(::qpid::types::Variant::Map&) const; /** Apply f to each Message on the queue. */ template <class F> void eachMessage(F f) { @@ -331,6 +372,11 @@ class Queue : public boost::enable_shared_from_this<Queue>, bindings.eachBinding(f); } + /** Apply f to each Observer on the queue */ + template <class F> void eachObserver(F f) { + std::for_each<Observers::iterator, F>(observers.begin(), observers.end(), f); + } + /** Set the position sequence number for the next message on the queue. * Must be >= the current sequence number. * Used by cluster to replicate queues. @@ -358,6 +404,11 @@ class Queue : public boost::enable_shared_from_this<Queue>, void recoverPrepared(boost::intrusive_ptr<Message>& msg); void flush(); + + const Broker* getBroker(); + + uint32_t getDequeueSincePurge() { return dequeueSincePurge.get(); } + void setDequeueSincePurge(uint32_t value); }; } } diff --git a/cpp/src/qpid/broker/QueueCleaner.cpp b/cpp/src/qpid/broker/QueueCleaner.cpp index 3499ea8a4d..838bc28be8 100644 --- a/cpp/src/qpid/broker/QueueCleaner.cpp +++ b/cpp/src/qpid/broker/QueueCleaner.cpp @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -27,7 +27,7 @@ namespace qpid { namespace broker { -QueueCleaner::QueueCleaner(QueueRegistry& q, sys::Timer& t) : queues(q), timer(t) {} +QueueCleaner::QueueCleaner(QueueRegistry& q, sys::Timer* t) : queues(q), timer(t) {} QueueCleaner::~QueueCleaner() { @@ -36,10 +36,16 @@ QueueCleaner::~QueueCleaner() void QueueCleaner::start(qpid::sys::Duration p) { + period = p; task = new Task(*this, p); - timer.add(task); + timer->add(task); } +void QueueCleaner::setTimer(qpid::sys::Timer* timer) { + this->timer = timer; +} + + QueueCleaner::Task::Task(QueueCleaner& p, qpid::sys::Duration d) : sys::TimerTask(d,"QueueCleaner"), parent(p) {} void QueueCleaner::Task::fire() @@ -65,9 +71,9 @@ void QueueCleaner::fired() std::vector<Queue::shared_ptr> copy; CollectQueues collect(©); queues.eachQueue(collect); - std::for_each(copy.begin(), copy.end(), boost::bind(&Queue::purgeExpired, _1)); + std::for_each(copy.begin(), copy.end(), boost::bind(&Queue::purgeExpired, _1, period)); task->setupNextFire(); - timer.add(task); + timer->add(task); } diff --git a/cpp/src/qpid/broker/QueueCleaner.h b/cpp/src/qpid/broker/QueueCleaner.h index 11c2d180ac..ffebfe3e1b 100644 --- a/cpp/src/qpid/broker/QueueCleaner.h +++ b/cpp/src/qpid/broker/QueueCleaner.h @@ -10,9 +10,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -35,14 +35,15 @@ class QueueRegistry; class QueueCleaner { public: - QPID_BROKER_EXTERN QueueCleaner(QueueRegistry& queues, sys::Timer& timer); + QPID_BROKER_EXTERN QueueCleaner(QueueRegistry& queues, sys::Timer* timer); QPID_BROKER_EXTERN ~QueueCleaner(); - QPID_BROKER_EXTERN void start(qpid::sys::Duration period); + QPID_BROKER_EXTERN void start(sys::Duration period); + QPID_BROKER_EXTERN void setTimer(sys::Timer* timer); private: class Task : public sys::TimerTask { public: - Task(QueueCleaner& parent, qpid::sys::Duration duration); + Task(QueueCleaner& parent, sys::Duration duration); void fire(); private: QueueCleaner& parent; @@ -50,7 +51,8 @@ class QueueCleaner boost::intrusive_ptr<sys::TimerTask> task; QueueRegistry& queues; - sys::Timer& timer; + sys::Timer* timer; + sys::Duration period; void fired(); }; diff --git a/cpp/src/qpid/broker/QueueEvents.cpp b/cpp/src/qpid/broker/QueueEvents.cpp index 2c540ff1ad..c66bdabf0f 100644 --- a/cpp/src/qpid/broker/QueueEvents.cpp +++ b/cpp/src/qpid/broker/QueueEvents.cpp @@ -129,6 +129,10 @@ class EventGenerator : public QueueObserver { if (!enqueueOnly) manager.dequeued(m); } + + void acquired(const QueuedMessage&) {}; + void requeued(const QueuedMessage&) {}; + private: QueueEvents& manager; const bool enqueueOnly; diff --git a/cpp/src/qpid/broker/QueueFlowLimit.cpp b/cpp/src/qpid/broker/QueueFlowLimit.cpp new file mode 100644 index 0000000000..f15bb45c01 --- /dev/null +++ b/cpp/src/qpid/broker/QueueFlowLimit.cpp @@ -0,0 +1,410 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/broker/QueueFlowLimit.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/Queue.h" +#include "qpid/Exception.h" +#include "qpid/framing/FieldValue.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Mutex.h" +#include "qpid/broker/SessionState.h" +#include "qpid/sys/ClusterSafe.h" + +#include "qmf/org/apache/qpid/broker/Queue.h" + +#include <sstream> + +using namespace qpid::broker; +using namespace qpid::framing; + +namespace { + /** ensure that the configured flow control stop and resume values are + * valid with respect to the maximum queue capacity, and each other + */ + template <typename T> + void validateFlowConfig(T max, T& stop, T& resume, const std::string& type, const std::string& queue) + { + if (resume > stop) { + throw InvalidArgumentException(QPID_MSG("Queue \"" << queue << "\": qpid.flow_resume_" << type + << "=" << resume + << " must be less than qpid.flow_stop_" << type + << "=" << stop)); + } + if (resume == 0) resume = stop; + if (max != 0 && (max < stop)) { + throw InvalidArgumentException(QPID_MSG("Queue \"" << queue << "\": qpid.flow_stop_" << type + << "=" << stop + << " must be less than qpid.max_" << type + << "=" << max)); + } + } + + /** extract a capacity value as passed in an argument map + */ + uint64_t getCapacity(const FieldTable& settings, const std::string& key, uint64_t defaultValue) + { + FieldTable::ValuePtr v = settings.get(key); + + int64_t result = 0; + + if (!v) return defaultValue; + if (v->getType() == 0x23) { + QPID_LOG(debug, "Value for " << key << " specified as float: " << v->get<float>()); + } else if (v->getType() == 0x33) { + QPID_LOG(debug, "Value for " << key << " specified as double: " << v->get<double>()); + } else if (v->convertsTo<int64_t>()) { + result = v->get<int64_t>(); + QPID_LOG(debug, "Got integer value for " << key << ": " << result); + if (result >= 0) return result; + } else if (v->convertsTo<string>()) { + string s(v->get<string>()); + QPID_LOG(debug, "Got string value for " << key << ": " << s); + std::istringstream convert(s); + if (convert >> result && result >= 0) return result; + } + + QPID_LOG(warning, "Cannot convert " << key << " to unsigned integer, using default (" << defaultValue << ")"); + return defaultValue; + } +} + + + +QueueFlowLimit::QueueFlowLimit(Queue *_queue, + uint32_t _flowStopCount, uint32_t _flowResumeCount, + uint64_t _flowStopSize, uint64_t _flowResumeSize) + : StatefulQueueObserver(std::string("QueueFlowLimit")), queue(_queue), queueName("<unknown>"), + flowStopCount(_flowStopCount), flowResumeCount(_flowResumeCount), + flowStopSize(_flowStopSize), flowResumeSize(_flowResumeSize), + flowStopped(false), count(0), size(0), queueMgmtObj(0), broker(0) +{ + uint32_t maxCount(0); + uint64_t maxSize(0); + + if (queue) { + queueName = _queue->getName(); + if (queue->getPolicy()) { + maxSize = _queue->getPolicy()->getMaxSize(); + maxCount = _queue->getPolicy()->getMaxCount(); + } + broker = queue->getBroker(); + queueMgmtObj = dynamic_cast<_qmfBroker::Queue*> (queue->GetManagementObject()); + if (queueMgmtObj) { + queueMgmtObj->set_flowStopped(isFlowControlActive()); + } + } + validateFlowConfig( maxCount, flowStopCount, flowResumeCount, "count", queueName ); + validateFlowConfig( maxSize, flowStopSize, flowResumeSize, "size", queueName ); + QPID_LOG(info, "Queue \"" << queueName << "\": Flow limit created: flowStopCount=" << flowStopCount + << ", flowResumeCount=" << flowResumeCount + << ", flowStopSize=" << flowStopSize << ", flowResumeSize=" << flowResumeSize ); +} + + +QueueFlowLimit::~QueueFlowLimit() +{ + sys::Mutex::ScopedLock l(indexLock); + if (!index.empty()) { + // we're gone - release all pending msgs + for (std::map<framing::SequenceNumber, boost::intrusive_ptr<Message> >::iterator itr = index.begin(); + itr != index.end(); ++itr) + if (itr->second) + try { + itr->second->getIngressCompletion().finishCompleter(); + } catch (...) {} // ignore - not safe for a destructor to throw. + index.clear(); + } +} + + +void QueueFlowLimit::enqueued(const QueuedMessage& msg) +{ + sys::Mutex::ScopedLock l(indexLock); + + ++count; + size += msg.payload->contentSize(); + + if (!flowStopped) { + if (flowStopCount && count > flowStopCount) { + flowStopped = true; + QPID_LOG(info, "Queue \"" << queueName << "\": has reached " << flowStopCount << " enqueued messages. Producer flow control activated." ); + } else if (flowStopSize && size > flowStopSize) { + flowStopped = true; + QPID_LOG(info, "Queue \"" << queueName << "\": has reached " << flowStopSize << " enqueued bytes. Producer flow control activated." ); + } + if (flowStopped && queueMgmtObj) { + queueMgmtObj->set_flowStopped(true); + queueMgmtObj->inc_flowStoppedCount(); + } + } + + if (flowStopped || !index.empty()) { + // ignore flow control if we are populating the queue due to cluster replication: + if (broker && broker->isClusterUpdatee()) { + QPID_LOG(trace, "Queue \"" << queueName << "\": ignoring flow control for msg pos=" << msg.position); + return; + } + QPID_LOG(trace, "Queue \"" << queueName << "\": setting flow control for msg pos=" << msg.position); + msg.payload->getIngressCompletion().startCompleter(); // don't complete until flow resumes + bool unique; + unique = index.insert(std::pair<framing::SequenceNumber, boost::intrusive_ptr<Message> >(msg.position, msg.payload)).second; + // Like this to avoid tripping up unused variable warning when NDEBUG set + if (!unique) assert(unique); + } +} + + + +void QueueFlowLimit::dequeued(const QueuedMessage& msg) +{ + sys::Mutex::ScopedLock l(indexLock); + + if (count > 0) { + --count; + } else { + throw Exception(QPID_MSG("Flow limit count underflow on dequeue. Queue=" << queueName)); + } + + uint64_t _size = msg.payload->contentSize(); + if (_size <= size) { + size -= _size; + } else { + throw Exception(QPID_MSG("Flow limit size underflow on dequeue. Queue=" << queueName)); + } + + if (flowStopped && + (flowResumeSize == 0 || size < flowResumeSize) && + (flowResumeCount == 0 || count < flowResumeCount)) { + flowStopped = false; + if (queueMgmtObj) + queueMgmtObj->set_flowStopped(false); + QPID_LOG(info, "Queue \"" << queueName << "\": has drained below the flow control resume level. Producer flow control deactivated." ); + } + + if (!index.empty()) { + if (!flowStopped) { + // flow enabled - release all pending msgs + for (std::map<framing::SequenceNumber, boost::intrusive_ptr<Message> >::iterator itr = index.begin(); + itr != index.end(); ++itr) + if (itr->second) + itr->second->getIngressCompletion().finishCompleter(); + index.clear(); + } else { + // even if flow controlled, we must release this msg as it is being dequeued + std::map<framing::SequenceNumber, boost::intrusive_ptr<Message> >::iterator itr = index.find(msg.position); + if (itr != index.end()) { // this msg is flow controlled, release it: + msg.payload->getIngressCompletion().finishCompleter(); + index.erase(itr); + } + } + } +} + + +void QueueFlowLimit::encode(Buffer& buffer) const +{ + buffer.putLong(flowStopCount); + buffer.putLong(flowResumeCount); + buffer.putLongLong(flowStopSize); + buffer.putLongLong(flowResumeSize); + buffer.putLong(count); + buffer.putLongLong(size); +} + + +void QueueFlowLimit::decode ( Buffer& buffer ) +{ + flowStopCount = buffer.getLong(); + flowResumeCount = buffer.getLong(); + flowStopSize = buffer.getLongLong(); + flowResumeSize = buffer.getLongLong(); + count = buffer.getLong(); + size = buffer.getLongLong(); +} + + +uint32_t QueueFlowLimit::encodedSize() const { + return sizeof(uint32_t) + // flowStopCount + sizeof(uint32_t) + // flowResumecount + sizeof(uint64_t) + // flowStopSize + sizeof(uint64_t) + // flowResumeSize + sizeof(uint32_t) + // count + sizeof(uint64_t); // size +} + + +const std::string QueueFlowLimit::flowStopCountKey("qpid.flow_stop_count"); +const std::string QueueFlowLimit::flowResumeCountKey("qpid.flow_resume_count"); +const std::string QueueFlowLimit::flowStopSizeKey("qpid.flow_stop_size"); +const std::string QueueFlowLimit::flowResumeSizeKey("qpid.flow_resume_size"); +uint64_t QueueFlowLimit::defaultMaxSize; +uint QueueFlowLimit::defaultFlowStopRatio; +uint QueueFlowLimit::defaultFlowResumeRatio; + + +void QueueFlowLimit::setDefaults(uint64_t maxQueueSize, uint flowStopRatio, uint flowResumeRatio) +{ + defaultMaxSize = maxQueueSize; + defaultFlowStopRatio = flowStopRatio; + defaultFlowResumeRatio = flowResumeRatio; + + /** @todo KAG: Verify valid range on Broker::Options instead of here */ + if (flowStopRatio > 100 || flowResumeRatio > 100) + throw InvalidArgumentException(QPID_MSG("Default queue flow ratios must be between 0 and 100, inclusive:" + << " flowStopRatio=" << flowStopRatio + << " flowResumeRatio=" << flowResumeRatio)); + if (flowResumeRatio > flowStopRatio) + throw InvalidArgumentException(QPID_MSG("Default queue flow stop ratio must be >= flow resume ratio:" + << " flowStopRatio=" << flowStopRatio + << " flowResumeRatio=" << flowResumeRatio)); +} + + +void QueueFlowLimit::observe(Queue& queue, const qpid::framing::FieldTable& settings) +{ + QueueFlowLimit *ptr = createLimit( &queue, settings ); + if (ptr) { + boost::shared_ptr<QueueFlowLimit> observer(ptr); + queue.addObserver(observer); + } +} + +/** returns ptr to a QueueFlowLimit, else 0 if no limit */ +QueueFlowLimit *QueueFlowLimit::createLimit(Queue *queue, const qpid::framing::FieldTable& settings) +{ + std::string type(QueuePolicy::getType(settings)); + + if (type == QueuePolicy::RING || type == QueuePolicy::RING_STRICT) { + // The size of a RING queue is limited by design - no need for flow control. + return 0; + } + + if (settings.get(flowStopCountKey) || settings.get(flowStopSizeKey) || + settings.get(flowResumeCountKey) || settings.get(flowResumeSizeKey)) { + // user provided (some) flow settings manually... + uint32_t flowStopCount = getCapacity(settings, flowStopCountKey, 0); + uint32_t flowResumeCount = getCapacity(settings, flowResumeCountKey, 0); + uint64_t flowStopSize = getCapacity(settings, flowStopSizeKey, 0); + uint64_t flowResumeSize = getCapacity(settings, flowResumeSizeKey, 0); + if (flowStopCount == 0 && flowStopSize == 0) { // disable flow control + return 0; + } + return new QueueFlowLimit(queue, flowStopCount, flowResumeCount, flowStopSize, flowResumeSize); + } + + if (defaultFlowStopRatio) { // broker has a default ratio setup... + uint64_t maxByteCount = getCapacity(settings, QueuePolicy::maxSizeKey, defaultMaxSize); + uint64_t flowStopSize = (uint64_t)(maxByteCount * (defaultFlowStopRatio/100.0) + 0.5); + uint64_t flowResumeSize = (uint64_t)(maxByteCount * (defaultFlowResumeRatio/100.0)); + uint32_t maxMsgCount = getCapacity(settings, QueuePolicy::maxCountKey, 0); // no size by default + uint32_t flowStopCount = (uint32_t)(maxMsgCount * (defaultFlowStopRatio/100.0) + 0.5); + uint32_t flowResumeCount = (uint32_t)(maxMsgCount * (defaultFlowResumeRatio/100.0)); + + return new QueueFlowLimit(queue, flowStopCount, flowResumeCount, flowStopSize, flowResumeSize); + } + return 0; +} + +/* Cluster replication */ + +namespace { + /** pack a set of sequence number ranges into a framing::Array */ + void buildSeqRangeArray(qpid::framing::Array *seqs, + const qpid::framing::SequenceNumber& first, + const qpid::framing::SequenceNumber& last) + { + seqs->push_back(qpid::framing::Array::ValuePtr(new Unsigned32Value(first))); + seqs->push_back(qpid::framing::Array::ValuePtr(new Unsigned32Value(last))); + } +} + +/** Runs on UPDATER to snapshot current state */ +void QueueFlowLimit::getState(qpid::framing::FieldTable& state ) const +{ + sys::Mutex::ScopedLock l(indexLock); + state.clear(); + + framing::SequenceSet ss; + if (!index.empty()) { + /* replicate the set of messages pending flow control */ + for (std::map<framing::SequenceNumber, boost::intrusive_ptr<Message> >::const_iterator itr = index.begin(); + itr != index.end(); ++itr) { + ss.add(itr->first); + } + framing::Array seqs(TYPE_CODE_UINT32); + typedef boost::function<void(framing::SequenceNumber, framing::SequenceNumber)> arrayBuilder; + ss.for_each((arrayBuilder)boost::bind(&buildSeqRangeArray, &seqs, _1, _2)); + state.setArray("pendingMsgSeqs", seqs); + } + QPID_LOG(debug, "Queue \"" << queueName << "\": flow limit replicating pending msgs, range=" << ss); +} + + +/** called on UPDATEE to set state from snapshot */ +void QueueFlowLimit::setState(const qpid::framing::FieldTable& state) +{ + sys::Mutex::ScopedLock l(indexLock); + index.clear(); + + framing::SequenceSet fcmsg; + framing::Array seqArray(TYPE_CODE_UINT32); + if (state.getArray("pendingMsgSeqs", seqArray)) { + assert((seqArray.count() & 0x01) == 0); // must be even since they are sequence ranges + framing::Array::const_iterator i = seqArray.begin(); + while (i != seqArray.end()) { + framing::SequenceNumber first((*i)->getIntegerValue<uint32_t, 4>()); + ++i; + framing::SequenceNumber last((*i)->getIntegerValue<uint32_t, 4>()); + ++i; + fcmsg.add(first, last); + for (SequenceNumber seq = first; seq <= last; ++seq) { + QueuedMessage msg; + queue->find(seq, msg); // fyi: may not be found if msg is acquired & unacked + bool unique; + unique = index.insert(std::pair<framing::SequenceNumber, boost::intrusive_ptr<Message> >(seq, msg.payload)).second; + // Like this to avoid tripping up unused variable warning when NDEBUG set + if (!unique) assert(unique); + } + } + } + + flowStopped = index.size() != 0; + if (queueMgmtObj) { + queueMgmtObj->set_flowStopped(isFlowControlActive()); + } + QPID_LOG(debug, "Queue \"" << queueName << "\": flow limit replicated the pending msgs, range=" << fcmsg) +} + + +namespace qpid { + namespace broker { + +std::ostream& operator<<(std::ostream& out, const QueueFlowLimit& f) +{ + out << "; flowStopCount=" << f.flowStopCount << ", flowResumeCount=" << f.flowResumeCount; + out << "; flowStopSize=" << f.flowStopSize << ", flowResumeSize=" << f.flowResumeSize; + return out; +} + + } +} + diff --git a/cpp/src/qpid/broker/QueueFlowLimit.h b/cpp/src/qpid/broker/QueueFlowLimit.h new file mode 100644 index 0000000000..ad8a2720ef --- /dev/null +++ b/cpp/src/qpid/broker/QueueFlowLimit.h @@ -0,0 +1,132 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#ifndef _QueueFlowLimit_ +#define _QueueFlowLimit_ + +#include <list> +#include <set> +#include <iostream> +#include <memory> +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/QueuedMessage.h" +#include "qpid/broker/StatefulQueueObserver.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/sys/AtomicValue.h" +#include "qpid/sys/Mutex.h" + +namespace qmf { +namespace org { +namespace apache { +namespace qpid { +namespace broker { + class Queue; +}}}}} +namespace _qmfBroker = qmf::org::apache::qpid::broker; + +namespace qpid { +namespace broker { + +class Broker; + +/** + * Producer flow control: when level is > flowStop*, flow control is ON. + * then level is < flowResume*, flow control is OFF. If == 0, flow control + * is not used. If both byte and msg count thresholds are set, then + * passing _either_ level may turn flow control ON, but _both_ must be + * below level before flow control will be turned OFF. + */ + class QueueFlowLimit : public StatefulQueueObserver +{ + static uint64_t defaultMaxSize; + static uint defaultFlowStopRatio; + static uint defaultFlowResumeRatio; + + Queue *queue; + std::string queueName; + + uint32_t flowStopCount; + uint32_t flowResumeCount; + uint64_t flowStopSize; + uint64_t flowResumeSize; + bool flowStopped; // true = producers held in flow control + + // current queue utilization + uint32_t count; + uint64_t size; + + public: + static QPID_BROKER_EXTERN const std::string flowStopCountKey; + static QPID_BROKER_EXTERN const std::string flowResumeCountKey; + static QPID_BROKER_EXTERN const std::string flowStopSizeKey; + static QPID_BROKER_EXTERN const std::string flowResumeSizeKey; + + QPID_BROKER_EXTERN virtual ~QueueFlowLimit(); + + /** the queue has added QueuedMessage. Returns true if flow state changes */ + QPID_BROKER_EXTERN void enqueued(const QueuedMessage&); + /** the queue has removed QueuedMessage. Returns true if flow state changes */ + QPID_BROKER_EXTERN void dequeued(const QueuedMessage&); + /** ignored */ + QPID_BROKER_EXTERN void acquired(const QueuedMessage&) {}; + QPID_BROKER_EXTERN void requeued(const QueuedMessage&) {}; + + /** for clustering: */ + QPID_BROKER_EXTERN void getState(qpid::framing::FieldTable&) const; + QPID_BROKER_EXTERN void setState(const qpid::framing::FieldTable&); + + uint32_t getFlowStopCount() const { return flowStopCount; } + uint32_t getFlowResumeCount() const { return flowResumeCount; } + uint64_t getFlowStopSize() const { return flowStopSize; } + uint64_t getFlowResumeSize() const { return flowResumeSize; } + + uint32_t getFlowCount() const { return count; } + uint64_t getFlowSize() const { return size; } + bool isFlowControlActive() const { return flowStopped; } + bool monitorFlowControl() const { return flowStopCount || flowStopSize; } + + void encode(framing::Buffer& buffer) const; + void decode(framing::Buffer& buffer); + uint32_t encodedSize() const; + + static QPID_BROKER_EXTERN void observe(Queue& queue, const qpid::framing::FieldTable& settings); + static QPID_BROKER_EXTERN void setDefaults(uint64_t defaultMaxSize, uint defaultFlowStopRatio, uint defaultFlowResumeRatio); + + friend QPID_BROKER_EXTERN std::ostream& operator<<(std::ostream&, const QueueFlowLimit&); + + protected: + // msgs waiting for flow to become available. + std::map<framing::SequenceNumber, boost::intrusive_ptr<Message> > index; + mutable qpid::sys::Mutex indexLock; + + _qmfBroker::Queue *queueMgmtObj; + + const Broker *broker; + + QPID_BROKER_EXTERN QueueFlowLimit(Queue *queue, + uint32_t flowStopCount, uint32_t flowResumeCount, + uint64_t flowStopSize, uint64_t flowResumeSize); + static QPID_BROKER_EXTERN QueueFlowLimit *createLimit(Queue *queue, const qpid::framing::FieldTable& settings); +}; + +}} + + +#endif diff --git a/cpp/src/qpid/broker/QueueListeners.cpp b/cpp/src/qpid/broker/QueueListeners.cpp index 4d2c57d6b4..591f4443bb 100644 --- a/cpp/src/qpid/broker/QueueListeners.cpp +++ b/cpp/src/qpid/broker/QueueListeners.cpp @@ -26,19 +26,25 @@ namespace broker { void QueueListeners::addListener(Consumer::shared_ptr c) { - if (c->preAcquires()) { - add(consumers, c); - } else { - add(browsers, c); + if (!c->inListeners) { + if (c->acquires) { + add(consumers, c); + } else { + add(browsers, c); + } + c->inListeners = true; } } void QueueListeners::removeListener(Consumer::shared_ptr c) { - if (c->preAcquires()) { - remove(consumers, c); - } else { - remove(browsers, c); + if (c->inListeners) { + if (c->acquires) { + remove(consumers, c); + } else { + remove(browsers, c); + } + c->inListeners = false; } } @@ -46,18 +52,20 @@ void QueueListeners::populate(NotificationSet& set) { if (consumers.size()) { set.consumer = consumers.front(); - consumers.erase(consumers.begin()); + consumers.pop_front(); + set.consumer->inListeners = false; } else { - // Don't swap the vectors, hang on to the memory allocated. + // Don't swap the deques, hang on to the memory allocated. set.browsers = browsers; browsers.clear(); + for (Listeners::iterator i = set.browsers.begin(); i != set.browsers.end(); i++) + (*i)->inListeners = false; } } void QueueListeners::add(Listeners& listeners, Consumer::shared_ptr c) { - Listeners::iterator i = std::find(listeners.begin(), listeners.end(), c); - if (i == listeners.end()) listeners.push_back(c); + listeners.push_back(c); } void QueueListeners::remove(Listeners& listeners, Consumer::shared_ptr c) @@ -73,9 +81,7 @@ void QueueListeners::NotificationSet::notify() } bool QueueListeners::contains(Consumer::shared_ptr c) const { - return - std::find(browsers.begin(), browsers.end(), c) != browsers.end() || - std::find(consumers.begin(), consumers.end(), c) != consumers.end(); + return c->inListeners; } void QueueListeners::ListenerSet::notifyAll() diff --git a/cpp/src/qpid/broker/QueueListeners.h b/cpp/src/qpid/broker/QueueListeners.h index 59d1c84ca4..0659499253 100644 --- a/cpp/src/qpid/broker/QueueListeners.h +++ b/cpp/src/qpid/broker/QueueListeners.h @@ -22,7 +22,7 @@ * */ #include "qpid/broker/Consumer.h" -#include <vector> +#include <deque> namespace qpid { namespace broker { @@ -40,7 +40,7 @@ namespace broker { class QueueListeners { public: - typedef std::vector<Consumer::shared_ptr> Listeners; + typedef std::deque<Consumer::shared_ptr> Listeners; class NotificationSet { diff --git a/cpp/src/qpid/broker/QueueObserver.h b/cpp/src/qpid/broker/QueueObserver.h index a711213dee..b58becd2ae 100644 --- a/cpp/src/qpid/broker/QueueObserver.h +++ b/cpp/src/qpid/broker/QueueObserver.h @@ -24,18 +24,52 @@ namespace qpid { namespace broker { -class QueuedMessage; +struct QueuedMessage; +class Consumer; + /** - * Interface for notifying classes who want to act as 'observers' of a - * queue of particular events. + * Interface for notifying classes who want to act as 'observers' of a queue of particular + * events. + * + * The events that are monitored reflect the relationship between a particular message and + * the queue it has been delivered to. A message can be considered in one of three states + * with respect to the queue: + * + * 1) "Available" - available for transfer to consumers (i.e. for browse or acquire), + * + * 2) "Acquired" - owned by a particular consumer, no longer available to other consumers + * (by either browse or acquire), but still considered on the queue. + * + * 3) "Dequeued" - removed from the queue and no longer available to any consumer. + * + * The queue events that are observable are: + * + * "Enqueued" - the message is "Available" - on the queue for transfer to any consumer + * (e.g. browse or acquire) + * + * "Acquired" - - a consumer has claimed exclusive access to it. It is no longer available + * for other consumers to browse or acquire, but it is not yet considered dequeued as it + * may be requeued by the consumer. + * + * "Requeued" - a previously-acquired message is released by its owner: it is put back on + * the queue at its original position and returns to the "Available" state. + * + * "Dequeued" - a message is no longer queued. At this point, the queue no longer tracks + * the message, and the broker considers the consumer's transaction complete. */ class QueueObserver { public: virtual ~QueueObserver() {} + + // note: the Queue will hold the messageLock while calling these methods! virtual void enqueued(const QueuedMessage&) = 0; virtual void dequeued(const QueuedMessage&) = 0; - private: + virtual void acquired(const QueuedMessage&) = 0; + virtual void requeued(const QueuedMessage&) = 0; + virtual void consumerAdded( const Consumer& ) {}; + virtual void consumerRemoved( const Consumer& ) {}; + private: }; }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/QueuePolicy.cpp b/cpp/src/qpid/broker/QueuePolicy.cpp index 4168221ad0..0c245700af 100644 --- a/cpp/src/qpid/broker/QueuePolicy.cpp +++ b/cpp/src/qpid/broker/QueuePolicy.cpp @@ -117,30 +117,30 @@ void QueuePolicy::update(FieldTable& settings) settings.setString(typeKey, type); } -uint32_t QueuePolicy::getCapacity(const FieldTable& settings, const std::string& key, uint32_t defaultValue) +template <typename T> +T getCapacity(const FieldTable& settings, const std::string& key, T defaultValue) { FieldTable::ValuePtr v = settings.get(key); - int32_t result = 0; + T result = 0; if (!v) return defaultValue; if (v->getType() == 0x23) { QPID_LOG(debug, "Value for " << key << " specified as float: " << v->get<float>()); } else if (v->getType() == 0x33) { QPID_LOG(debug, "Value for " << key << " specified as double: " << v->get<double>()); - } else if (v->convertsTo<int>()) { - result = v->get<int>(); + } else if (v->convertsTo<T>()) { + result = v->get<T>(); QPID_LOG(debug, "Got integer value for " << key << ": " << result); if (result >= 0) return result; } else if (v->convertsTo<string>()) { string s(v->get<string>()); QPID_LOG(debug, "Got string value for " << key << ": " << s); std::istringstream convert(s); - if (convert >> result && result >= 0) return result; + if (convert >> result && result >= 0 && convert.eof()) return result; } - QPID_LOG(warning, "Cannot convert " << key << " to unsigned integer, using default (" << defaultValue << ")"); - return defaultValue; + throw IllegalArgumentException(QPID_MSG("Cannot convert " << key << " to unsigned integer: " << *v)); } std::string QueuePolicy::getType(const FieldTable& settings) @@ -247,7 +247,7 @@ bool RingQueuePolicy::checkLimit(boost::intrusive_ptr<Message> m) { // If the message is bigger than the queue size, give up - if (m->contentSize() > getMaxSize()) { + if (getMaxSize() && m->contentSize() > getMaxSize()) { QPID_LOG(debug, "Message too large for ring queue " << name << " [" << *this << "] " << ": message size = " << m->contentSize() << " bytes" @@ -269,8 +269,7 @@ bool RingQueuePolicy::checkLimit(boost::intrusive_ptr<Message> m) do { QueuedMessage oldest = queue.front(); - - if (oldest.queue->acquire(oldest) || !strict) { + if (oldest.queue->acquireMessageAt(oldest.position, oldest) || !strict) { queue.pop_front(); pendingDequeues.push_back(oldest); QPID_LOG(debug, "Ring policy triggered in " << name @@ -320,8 +319,8 @@ std::auto_ptr<QueuePolicy> QueuePolicy::createQueuePolicy(const qpid::framing::F std::auto_ptr<QueuePolicy> QueuePolicy::createQueuePolicy(const std::string& name, const qpid::framing::FieldTable& settings) { - uint32_t maxCount = getCapacity(settings, maxCountKey, 0); - uint32_t maxSize = getCapacity(settings, maxSizeKey, defaultMaxSize); + uint32_t maxCount = getCapacity<int32_t>(settings, maxCountKey, 0); + uint64_t maxSize = getCapacity<int64_t>(settings, maxSizeKey, defaultMaxSize); if (maxCount || maxSize) { return createQueuePolicy(name, maxCount, maxSize, getType(settings)); } else { diff --git a/cpp/src/qpid/broker/QueuePolicy.h b/cpp/src/qpid/broker/QueuePolicy.h index 3cdd63784d..ec7f846704 100644 --- a/cpp/src/qpid/broker/QueuePolicy.h +++ b/cpp/src/qpid/broker/QueuePolicy.h @@ -43,8 +43,7 @@ class QueuePolicy uint32_t count; uint64_t size; bool policyExceeded; - - static uint32_t getCapacity(const qpid::framing::FieldTable& settings, const std::string& key, uint32_t defaultValue); + protected: uint64_t getCurrentQueueSize() const { return size; } diff --git a/cpp/src/qpid/broker/QueueRegistry.cpp b/cpp/src/qpid/broker/QueueRegistry.cpp index ea2531dae7..135a3543d9 100644 --- a/cpp/src/qpid/broker/QueueRegistry.cpp +++ b/cpp/src/qpid/broker/QueueRegistry.cpp @@ -21,6 +21,7 @@ #include "qpid/broker/Queue.h" #include "qpid/broker/QueueRegistry.h" #include "qpid/broker/QueueEvents.h" +#include "qpid/broker/Exchange.h" #include "qpid/log/Statement.h" #include <sstream> #include <assert.h> @@ -36,7 +37,13 @@ QueueRegistry::~QueueRegistry(){} std::pair<Queue::shared_ptr, bool> QueueRegistry::declare(const string& declareName, bool durable, - bool autoDelete, const OwnershipToken* owner) + bool autoDelete, const OwnershipToken* owner, + boost::shared_ptr<Exchange> alternate, + const qpid::framing::FieldTable& arguments, + bool recovering/*true if this declare is a + result of recovering queue + definition from persistente + record*/) { RWlock::ScopedWlock locker(lock); string name = declareName.empty() ? generateName() : declareName; @@ -45,6 +52,17 @@ QueueRegistry::declare(const string& declareName, bool durable, if (i == queues.end()) { Queue::shared_ptr queue(new Queue(name, autoDelete, durable ? store : 0, owner, parent, broker)); + if (alternate) { + queue->setAlternateExchange(alternate);//need to do this *before* create + alternate->incAlternateUsers(); + } + if (!recovering) { + //apply settings & create persistent record if required + queue->create(arguments); + } else { + //i.e. recovering a queue for which we already have a persistent record + queue->configure(arguments); + } queues[name] = queue; if (lastNode) queue->setLastNodeFailure(); diff --git a/cpp/src/qpid/broker/QueueRegistry.h b/cpp/src/qpid/broker/QueueRegistry.h index 57859fe639..8a32a64f05 100644 --- a/cpp/src/qpid/broker/QueueRegistry.h +++ b/cpp/src/qpid/broker/QueueRegistry.h @@ -24,6 +24,7 @@ #include "qpid/broker/BrokerImportExport.h" #include "qpid/sys/Mutex.h" #include "qpid/management/Manageable.h" +#include "qpid/framing/FieldTable.h" #include <boost/bind.hpp> #include <boost/shared_ptr.hpp> #include <algorithm> @@ -34,6 +35,7 @@ namespace broker { class Queue; class QueueEvents; +class Exchange; class OwnershipToken; class Broker; class MessageStore; @@ -60,7 +62,10 @@ class QueueRegistry { const std::string& name, bool durable = false, bool autodelete = false, - const OwnershipToken* owner = 0); + const OwnershipToken* owner = 0, + boost::shared_ptr<Exchange> alternateExchange = boost::shared_ptr<Exchange>(), + const qpid::framing::FieldTable& args = framing::FieldTable(), + bool recovering = false); /** * Destroy the named queue. diff --git a/cpp/src/qpid/broker/RateTracker.cpp b/cpp/src/qpid/broker/RateTracker.cpp deleted file mode 100644 index 048349b658..0000000000 --- a/cpp/src/qpid/broker/RateTracker.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -#include "qpid/broker/RateTracker.h" - -using qpid::sys::AbsTime; -using qpid::sys::Duration; -using qpid::sys::TIME_SEC; - -namespace qpid { -namespace broker { - -RateTracker::RateTracker() : currentCount(0), lastCount(0), lastTime(AbsTime::now()) {} - -RateTracker& RateTracker::operator++() -{ - ++currentCount; - return *this; -} - -double RateTracker::sampleRatePerSecond() -{ - int32_t increment = currentCount - lastCount; - AbsTime now = AbsTime::now(); - Duration interval(lastTime, now); - lastCount = currentCount; - lastTime = now; - //if sampling at higher frequency than supported, will just return the number of increments - if (interval < TIME_SEC) return increment; - else if (increment == 0) return 0; - else return increment / (interval / TIME_SEC); -} - -}} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/RateTracker.h b/cpp/src/qpid/broker/RateTracker.h deleted file mode 100644 index 0c20b37312..0000000000 --- a/cpp/src/qpid/broker/RateTracker.h +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef QPID_BROKER_RATETRACKER_H -#define QPID_BROKER_RATETRACKER_H - -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -#include "qpid/sys/Time.h" - -namespace qpid { -namespace broker { - -/** - * Simple rate tracker: represents some value that can be incremented, - * then can periodcially sample the rate of increments. - */ -class RateTracker -{ - public: - RateTracker(); - /** - * Increments the count being tracked. Can be called concurrently - * with other calls to this operator as well as with calls to - * sampleRatePerSecond(). - */ - RateTracker& operator++(); - /** - * Returns the rate of increments per second since last - * called. Calls to this method should be serialised, but can be - * called concurrently with the increment operator - */ - double sampleRatePerSecond(); - private: - volatile int32_t currentCount; - int32_t lastCount; - qpid::sys::AbsTime lastTime; -}; -}} // namespace qpid::broker - -#endif /*!QPID_BROKER_RATETRACKER_H*/ diff --git a/cpp/src/qpid/broker/RecoveredDequeue.cpp b/cpp/src/qpid/broker/RecoveredDequeue.cpp index 38cb8043c9..cd6735328f 100644 --- a/cpp/src/qpid/broker/RecoveredDequeue.cpp +++ b/cpp/src/qpid/broker/RecoveredDequeue.cpp @@ -43,7 +43,6 @@ void RecoveredDequeue::commit() throw() void RecoveredDequeue::rollback() throw() { - msg->enqueueComplete(); queue->process(msg); } diff --git a/cpp/src/qpid/broker/RecoveredEnqueue.cpp b/cpp/src/qpid/broker/RecoveredEnqueue.cpp index 6263c63e3d..6d2eaee6c4 100644 --- a/cpp/src/qpid/broker/RecoveredEnqueue.cpp +++ b/cpp/src/qpid/broker/RecoveredEnqueue.cpp @@ -36,7 +36,6 @@ bool RecoveredEnqueue::prepare(TransactionContext*) throw(){ } void RecoveredEnqueue::commit() throw(){ - msg->enqueueComplete(); queue->process(msg); } diff --git a/cpp/src/qpid/broker/RecoveryManagerImpl.cpp b/cpp/src/qpid/broker/RecoveryManagerImpl.cpp index 2f04943581..d08409695e 100644 --- a/cpp/src/qpid/broker/RecoveryManagerImpl.cpp +++ b/cpp/src/qpid/broker/RecoveryManagerImpl.cpp @@ -113,7 +113,7 @@ RecoverableExchange::shared_ptr RecoveryManagerImpl::recoverExchange(framing::Bu RecoverableQueue::shared_ptr RecoveryManagerImpl::recoverQueue(framing::Buffer& buffer) { - Queue::shared_ptr queue = Queue::decode(queues, buffer, true); + Queue::shared_ptr queue = Queue::restore(queues, buffer); try { Exchange::shared_ptr exchange = exchanges.getDefault(); if (exchange) { @@ -252,7 +252,6 @@ void RecoverableMessageImpl::dequeue(DtxBuffer::shared_ptr buffer, Queue::shared void RecoverableMessageImpl::enqueue(DtxBuffer::shared_ptr buffer, Queue::shared_ptr queue) { - msg->enqueueComplete(); // recoved nmessage to enqueued in store already buffer->enlist(TxOp::shared_ptr(new RecoveredEnqueue(queue, msg))); } diff --git a/cpp/src/qpid/broker/SaslAuthenticator.cpp b/cpp/src/qpid/broker/SaslAuthenticator.cpp index acdb4934d4..d7adbd68ab 100644 --- a/cpp/src/qpid/broker/SaslAuthenticator.cpp +++ b/cpp/src/qpid/broker/SaslAuthenticator.cpp @@ -30,6 +30,7 @@ #include <boost/format.hpp> #if HAVE_SASL +#include <sys/stat.h> #include <sasl/sasl.h> #include "qpid/sys/cyrus/CyrusSecurityLayer.h" using qpid::sys::cyrus::CyrusSecurityLayer; @@ -57,7 +58,7 @@ public: NullAuthenticator(Connection& connection, bool encrypt); ~NullAuthenticator(); void getMechanisms(framing::Array& mechanisms); - void start(const std::string& mechanism, const std::string& response); + void start(const std::string& mechanism, const std::string* response); void step(const std::string&) {} std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize); }; @@ -81,7 +82,7 @@ public: ~CyrusAuthenticator(); void init(); void getMechanisms(framing::Array& mechanisms); - void start(const std::string& mechanism, const std::string& response); + void start(const std::string& mechanism, const std::string* response); void step(const std::string& response); void getError(std::string& error); void getUid(std::string& uid) { getUsername(uid); } @@ -98,11 +99,33 @@ void SaslAuthenticator::init(const std::string& saslName, std::string const & sa // Check if we have a version of SASL that supports sasl_set_path() #if (SASL_VERSION_FULL >= ((2<<16)|(1<<8)|22)) // If we are not given a sasl path, do nothing and allow the default to be used. - if ( ! saslConfigPath.empty() ) { - int code = sasl_set_path(SASL_PATH_TYPE_CONFIG, - const_cast<char *>(saslConfigPath.c_str())); + if ( saslConfigPath.empty() ) { + QPID_LOG ( info, "SASL: no config path set - using default." ); + } + else { + struct stat st; + + // Make sure the directory exists and we can read up to it. + if ( ::stat ( saslConfigPath.c_str(), & st) ) { + // Note: not using strerror() here because I think its messages are a little too hazy. + if ( errno == ENOENT ) + throw Exception ( QPID_MSG ( "SASL: sasl_set_path failed: no such directory: " << saslConfigPath ) ); + if ( errno == EACCES ) + throw Exception ( QPID_MSG ( "SASL: sasl_set_path failed: cannot read parent of: " << saslConfigPath ) ); + // catch-all stat failure + throw Exception ( QPID_MSG ( "SASL: sasl_set_path failed: cannot stat: " << saslConfigPath ) ); + } + + // Make sure the directory is readable. + if ( ::access ( saslConfigPath.c_str(), R_OK ) ) { + throw Exception ( QPID_MSG ( "SASL: sasl_set_path failed: directory not readable:" << saslConfigPath ) ); + } + + // This shouldn't fail now, but check anyway. + int code = sasl_set_path(SASL_PATH_TYPE_CONFIG, const_cast<char *>(saslConfigPath.c_str())); if(SASL_OK != code) throw Exception(QPID_MSG("SASL: sasl_set_path failed [" << code << "] " )); + QPID_LOG(info, "SASL: config path set to " << saslConfigPath ); } #endif @@ -164,7 +187,7 @@ void NullAuthenticator::getMechanisms(Array& mechanisms) mechanisms.add(boost::shared_ptr<FieldValue>(new Str16Value("PLAIN")));//useful for testing } -void NullAuthenticator::start(const string& mechanism, const string& response) +void NullAuthenticator::start(const string& mechanism, const string* response) { if (encrypt) { #if HAVE_SASL @@ -180,16 +203,16 @@ void NullAuthenticator::start(const string& mechanism, const string& response) } } if (mechanism == "PLAIN") { // Old behavior - if (response.size() > 0) { + if (response && response->size() > 0) { string uid; - string::size_type i = response.find((char)0); - if (i == 0 && response.size() > 1) { + string::size_type i = response->find((char)0); + if (i == 0 && response->size() > 1) { //no authorization id; use authentication id - i = response.find((char)0, 1); - if (i != string::npos) uid = response.substr(1, i-1); + i = response->find((char)0, 1); + if (i != string::npos) uid = response->substr(1, i-1); } else if (i != string::npos) { //authorization id is first null delimited field - uid = response.substr(0, i); + uid = response->substr(0, i); }//else not a valid SASL PLAIN response, throw error? if (!uid.empty()) { //append realm if it has not already been added @@ -376,18 +399,22 @@ void CyrusAuthenticator::getMechanisms(Array& mechanisms) } } -void CyrusAuthenticator::start(const string& mechanism, const string& response) +void CyrusAuthenticator::start(const string& mechanism, const string* response) { const char *challenge; unsigned int challenge_len; - QPID_LOG(debug, "SASL: Starting authentication with mechanism: " << mechanism); + // This should be at same debug level as mech list in getMechanisms(). + QPID_LOG(info, "SASL: Starting authentication with mechanism: " << mechanism); int code = sasl_server_start(sasl_conn, mechanism.c_str(), - response.c_str(), response.length(), + (response ? response->c_str() : 0), (response ? response->size() : 0), &challenge, &challenge_len); processAuthenticationStep(code, challenge, challenge_len); + qmf::org::apache::qpid::broker::Connection* cnxMgmt = connection.getMgmtObject(); + if ( cnxMgmt ) + cnxMgmt->set_saslMechanism(mechanism); } void CyrusAuthenticator::step(const string& response) @@ -424,10 +451,12 @@ void CyrusAuthenticator::processAuthenticationStep(int code, const char *challen client.secure(challenge_str); } else { std::string uid; + //save error detail before trying to retrieve username as error in doing so will overwrite it + std::string errordetail = sasl_errdetail(sasl_conn); if (!getUsername(uid)) { - QPID_LOG(info, "SASL: Authentication failed (no username available):" << sasl_errdetail(sasl_conn)); + QPID_LOG(info, "SASL: Authentication failed (no username available yet):" << errordetail); } else { - QPID_LOG(info, "SASL: Authentication failed for " << uid << ":" << sasl_errdetail(sasl_conn)); + QPID_LOG(info, "SASL: Authentication failed for " << uid << ":" << errordetail); } // TODO: Change to more specific exceptions, when they are @@ -459,6 +488,9 @@ std::auto_ptr<SecurityLayer> CyrusAuthenticator::getSecurityLayer(uint16_t maxFr if (ssf) { securityLayer = std::auto_ptr<SecurityLayer>(new CyrusSecurityLayer(sasl_conn, maxFrameSize)); } + qmf::org::apache::qpid::broker::Connection* cnxMgmt = connection.getMgmtObject(); + if ( cnxMgmt ) + cnxMgmt->set_saslSsf(ssf); return securityLayer; } diff --git a/cpp/src/qpid/broker/SaslAuthenticator.h b/cpp/src/qpid/broker/SaslAuthenticator.h index cfbe1a0cd1..4e5d43214c 100644 --- a/cpp/src/qpid/broker/SaslAuthenticator.h +++ b/cpp/src/qpid/broker/SaslAuthenticator.h @@ -41,7 +41,7 @@ class SaslAuthenticator public: virtual ~SaslAuthenticator() {} virtual void getMechanisms(framing::Array& mechanisms) = 0; - virtual void start(const std::string& mechanism, const std::string& response) = 0; + virtual void start(const std::string& mechanism, const std::string* response) = 0; virtual void step(const std::string& response) = 0; virtual void getUid(std::string&) {} virtual bool getUsername(std::string&) { return false; }; diff --git a/cpp/src/qpid/broker/SemanticState.cpp b/cpp/src/qpid/broker/SemanticState.cpp index c91cfba2f8..fbcb21eab9 100644 --- a/cpp/src/qpid/broker/SemanticState.cpp +++ b/cpp/src/qpid/broker/SemanticState.cpp @@ -70,14 +70,12 @@ SemanticState::SemanticState(DeliveryAdapter& da, SessionContext& ss) deliveryAdapter(da), tagGenerator("sgen"), dtxSelected(false), - authMsg(getSession().getBroker().getOptions().auth && !getSession().getConnection().isFederationLink()), + authMsg(getSession().getBroker().getOptions().auth && !getSession().getConnection().isUserProxyAuth()), userID(getSession().getConnection().getUserId()), userName(getSession().getConnection().getUserId().substr(0,getSession().getConnection().getUserId().find('@'))), isDefaultRealm(userID.find('@') != std::string::npos && getSession().getBroker().getOptions().realm == userID.substr(userID.find('@')+1,userID.size())), closeComplete(false) -{ - acl = getSession().getBroker().getAcl(); -} +{} SemanticState::~SemanticState() { closed(); @@ -88,7 +86,7 @@ void SemanticState::closed() { //prevent requeued messages being redelivered to consumers for (ConsumerImplMap::iterator i = consumers.begin(); i != consumers.end(); i++) { disable(i->second); - } + } if (dtxBuffer.get()) { dtxBuffer->fail(); } @@ -107,16 +105,24 @@ bool SemanticState::exists(const string& consumerTag){ return consumers.find(consumerTag) != consumers.end(); } -void SemanticState::consume(const string& tag, +namespace { + const std::string SEPARATOR("::"); +} + +void SemanticState::consume(const string& tag, Queue::shared_ptr queue, bool ackRequired, bool acquire, bool exclusive, const string& resumeId, uint64_t resumeTtl, const FieldTable& arguments) { - ConsumerImpl::shared_ptr c(new ConsumerImpl(this, tag, queue, ackRequired, acquire, exclusive, resumeId, resumeTtl, arguments)); + // "tag" is only guaranteed to be unique to this session (see AMQP 0-10 Message.subscribe, destination). + // Create a globally unique name so the broker can identify individual consumers + std::string name = session.getSessionId().str() + SEPARATOR + tag; + ConsumerImpl::shared_ptr c(new ConsumerImpl(this, name, queue, ackRequired, acquire, exclusive, tag, resumeId, resumeTtl, arguments)); queue->consume(c, exclusive);//may throw exception consumers[tag] = c; } -void SemanticState::cancel(const string& tag){ +bool SemanticState::cancel(const string& tag) +{ ConsumerImplMap::iterator i = consumers.find(tag); if (i != consumers.end()) { cancel(i->second); @@ -124,7 +130,13 @@ void SemanticState::cancel(const string& tag){ //should cancel all unacked messages for this consumer so that //they are not redelivered on recovery for_each(unacked.begin(), unacked.end(), boost::bind(&DeliveryRecord::cancel, _1, tag)); - + //can also remove any records that are now redundant + DeliveryRecords::iterator removed = + remove_if(unacked.begin(), unacked.end(), bind(&DeliveryRecord::isRedundant, _1)); + unacked.erase(removed, unacked.end()); + return true; + } else { + return false; } } @@ -167,8 +179,8 @@ void SemanticState::startDtx(const std::string& xid, DtxManager& mgr, bool join) if (!dtxSelected) { throw CommandInvalidException(QPID_MSG("Session has not been selected for use with dtx")); } - dtxBuffer = DtxBuffer::shared_ptr(new DtxBuffer(xid)); - txBuffer = boost::static_pointer_cast<TxBuffer>(dtxBuffer); + dtxBuffer.reset(new DtxBuffer(xid)); + txBuffer = dtxBuffer; if (join) { mgr.join(xid, dtxBuffer); } else { @@ -194,7 +206,7 @@ void SemanticState::endDtx(const std::string& xid, bool fail) dtxBuffer->fail(); } else { dtxBuffer->markEnded(); - } + } dtxBuffer.reset(); } @@ -236,7 +248,7 @@ void SemanticState::resumeDtx(const std::string& xid) checkDtxTimeout(); dtxBuffer->setSuspended(false); - txBuffer = boost::static_pointer_cast<TxBuffer>(dtxBuffer); + txBuffer = dtxBuffer; } void SemanticState::checkDtxTimeout() @@ -254,31 +266,33 @@ void SemanticState::record(const DeliveryRecord& delivery) const std::string QPID_SYNC_FREQUENCY("qpid.sync_frequency"); -SemanticState::ConsumerImpl::ConsumerImpl(SemanticState* _parent, - const string& _name, - Queue::shared_ptr _queue, +SemanticState::ConsumerImpl::ConsumerImpl(SemanticState* _parent, + const string& _name, + Queue::shared_ptr _queue, bool ack, bool _acquire, bool _exclusive, + const string& _tag, const string& _resumeId, uint64_t _resumeTtl, const framing::FieldTable& _arguments -) : - Consumer(_acquire), - parent(_parent), - name(_name), - queue(_queue), - ackExpected(ack), +) : + Consumer(_name, _acquire), + parent(_parent), + queue(_queue), + ackExpected(ack), acquire(_acquire), - blocked(true), + blocked(true), windowing(true), + windowActive(false), exclusive(_exclusive), resumeId(_resumeId), + tag(_tag), resumeTtl(_resumeTtl), arguments(_arguments), - msgCredit(0), + msgCredit(0), byteCredit(0), notifyEnabled(true), syncFrequency(_arguments.getAsInt(QPID_SYNC_FREQUENCY)), @@ -289,10 +303,10 @@ SemanticState::ConsumerImpl::ConsumerImpl(SemanticState* _parent, { ManagementAgent* agent = parent->session.getBroker().getManagementAgent(); qpid::management::Manageable* ms = dynamic_cast<qpid::management::Manageable*> (&(parent->session)); - + if (agent != 0) { - mgmtObject = new _qmf::Subscription(agent, this, ms , queue->GetManagementObject()->getObjectId() ,name, + mgmtObject = new _qmf::Subscription(agent, this, ms , queue->GetManagementObject()->getObjectId(), getTag(), !acquire, ackExpected, exclusive, ManagementAgent::toMap(arguments)); agent->addObject (mgmtObject); mgmtObject->set_creditMode("WINDOW"); @@ -324,16 +338,16 @@ bool SemanticState::ConsumerImpl::deliver(QueuedMessage& msg) { assertClusterSafe(); allocateCredit(msg.payload); - DeliveryRecord record(msg, queue, name, acquire, !ackExpected, windowing); + DeliveryRecord record(msg, queue, getTag(), acquire, !ackExpected, windowing); bool sync = syncFrequency && ++deliveryCount >= syncFrequency; if (sync) deliveryCount = 0;//reset parent->deliver(record, sync); - if (!ackExpected && acquire) record.setEnded();//allows message to be released now its been delivered if (windowing || ackExpected || !acquire) { parent->record(record); - } - if (acquire && !ackExpected) { - queue->dequeue(0, msg); + } + if (acquire && !ackExpected) { // auto acquire && auto accept + queue->dequeue(0 /*ctxt*/, msg); + record.setEnded(); } if (mgmtObject) { mgmtObject->inc_delivered(); } return true; @@ -351,7 +365,7 @@ bool SemanticState::ConsumerImpl::accept(intrusive_ptr<Message> msg) // checkCredit fails because the message is to big, we should // remain on queue's listener list for possible smaller messages // in future. - // + // blocked = !(filter(msg) && checkCredit(msg)); return !blocked; } @@ -363,7 +377,7 @@ struct ConsumerName { }; ostream& operator<<(ostream& o, const ConsumerName& pc) { - return o << pc.consumer.getName() << " on " + return o << pc.consumer.getTag() << " on " << pc.consumer.getParent().getSession().getSessionId(); } } @@ -372,7 +386,7 @@ void SemanticState::ConsumerImpl::allocateCredit(intrusive_ptr<Message>& msg) { assertClusterSafe(); uint32_t originalMsgCredit = msgCredit; - uint32_t originalByteCredit = byteCredit; + uint32_t originalByteCredit = byteCredit; if (msgCredit != 0xFFFFFFFF) { msgCredit--; } @@ -382,7 +396,7 @@ void SemanticState::ConsumerImpl::allocateCredit(intrusive_ptr<Message>& msg) QPID_LOG(debug, "Credit allocated for " << ConsumerName(*this) << ", was " << " bytes: " << originalByteCredit << " msgs: " << originalMsgCredit << " now bytes: " << byteCredit << " msgs: " << msgCredit); - + } bool SemanticState::ConsumerImpl::checkCredit(intrusive_ptr<Message>& msg) @@ -396,7 +410,7 @@ bool SemanticState::ConsumerImpl::checkCredit(intrusive_ptr<Message>& msg) return enoughCredit; } -SemanticState::ConsumerImpl::~ConsumerImpl() +SemanticState::ConsumerImpl::~ConsumerImpl() { if (mgmtObject != 0) mgmtObject->resourceDestroy (); @@ -414,7 +428,7 @@ void SemanticState::unsubscribe(ConsumerImpl::shared_ptr c) Queue::shared_ptr queue = c->getQueue(); if(queue) { queue->cancel(c); - if (queue->canAutoDelete() && !queue->hasExclusiveOwner()) { + if (queue->canAutoDelete() && !queue->hasExclusiveOwner()) { Queue::tryAutoDelete(session.getBroker(), queue); } } @@ -456,23 +470,23 @@ const std::string nullstring; } void SemanticState::route(intrusive_ptr<Message> msg, Deliverable& strategy) { - msg->setTimestamp(getSession().getBroker().getExpiryPolicy()); - + msg->computeExpiration(getSession().getBroker().getExpiryPolicy()); + std::string exchangeName = msg->getExchangeName(); - if (!cacheExchange || cacheExchange->getName() != exchangeName) + if (!cacheExchange || cacheExchange->getName() != exchangeName || cacheExchange->isDestroyed()) cacheExchange = session.getBroker().getExchanges().get(exchangeName); cacheExchange->setProperties(msg); /* verify the userid if specified: */ std::string id = msg->hasProperties<MessageProperties>() ? msg->getProperties<MessageProperties>()->getUserId() : nullstring; - if (authMsg && !id.empty() && !(id == userID || (isDefaultRealm && id == userName))) { QPID_LOG(debug, "authorised user id : " << userID << " but user id in message declared as " << id); throw UnauthorizedAccessException(QPID_MSG("authorised user id : " << userID << " but user id in message declared as " << id)); } + AclModule* acl = getSession().getBroker().getAcl(); if (acl && acl->doTransferAcl()) { if (!acl->authorise(getSession().getConnection().getUserId(),acl::ACT_PUBLISH,acl::OBJ_EXCHANGE,exchangeName, msg->getRoutingKey() )) @@ -484,7 +498,7 @@ void SemanticState::route(intrusive_ptr<Message> msg, Deliverable& strategy) { if (!strategy.delivered) { //TODO:if discard-unroutable, just drop it - //TODO:else if accept-mode is explicit, reject it + //TODO:else if accept-mode is explicit, reject it //else route it to alternate exchange if (cacheExchange->getAlternate()) { cacheExchange->getAlternate()->route(strategy, msg->getRoutingKey(), msg->getApplicationHeaders()); @@ -513,7 +527,7 @@ void SemanticState::ConsumerImpl::requestDispatch() } bool SemanticState::complete(DeliveryRecord& delivery) -{ +{ ConsumerImplMap::iterator i = consumers.find(delivery.getTag()); if (i != consumers.end()) { i->second->complete(delivery); @@ -525,7 +539,7 @@ void SemanticState::ConsumerImpl::complete(DeliveryRecord& delivery) { if (!delivery.isComplete()) { delivery.complete(); - if (windowing) { + if (windowing && windowActive) { if (msgCredit != 0xFFFFFFFF) msgCredit++; if (byteCredit != 0xFFFFFFFF) byteCredit += delivery.getCredit(); } @@ -541,7 +555,7 @@ void SemanticState::recover(bool requeue) unacked.clear(); for_each(copy.rbegin(), copy.rend(), mem_fun_ref(&DeliveryRecord::requeue)); }else{ - for_each(unacked.begin(), unacked.end(), boost::bind(&DeliveryRecord::redeliver, _1, this)); + for_each(unacked.begin(), unacked.end(), boost::bind(&DeliveryRecord::redeliver, _1, this)); //unconfirmed messages re redelivered and therefore have their //id adjusted, confirmed messages are not and so the ordering //w.r.t id is lost @@ -554,50 +568,61 @@ void SemanticState::deliver(DeliveryRecord& msg, bool sync) return deliveryAdapter.deliver(msg, sync); } -SemanticState::ConsumerImpl& SemanticState::find(const std::string& destination) +const SemanticState::ConsumerImpl::shared_ptr SemanticState::find(const std::string& destination) const { - ConsumerImplMap::iterator i = consumers.find(destination); - if (i == consumers.end()) { - throw NotFoundException(QPID_MSG("Unknown destination " << destination)); + ConsumerImpl::shared_ptr consumer; + if (!find(destination, consumer)) { + throw NotFoundException(QPID_MSG("Unknown destination " << destination << " session=" << session.getSessionId())); } else { - return *(i->second); + return consumer; + } +} + +bool SemanticState::find(const std::string& destination, ConsumerImpl::shared_ptr& consumer) const +{ + // @todo KAG gsim: shouldn't the consumers map be locked???? + ConsumerImplMap::const_iterator i = consumers.find(destination); + if (i == consumers.end()) { + return false; } + consumer = i->second; + return true; } void SemanticState::setWindowMode(const std::string& destination) { - find(destination).setWindowMode(); + find(destination)->setWindowMode(); } void SemanticState::setCreditMode(const std::string& destination) { - find(destination).setCreditMode(); + find(destination)->setCreditMode(); } void SemanticState::addByteCredit(const std::string& destination, uint32_t value) { - ConsumerImpl& c = find(destination); - c.addByteCredit(value); - c.requestDispatch(); + ConsumerImpl::shared_ptr c = find(destination); + c->addByteCredit(value); + c->requestDispatch(); } void SemanticState::addMessageCredit(const std::string& destination, uint32_t value) { - ConsumerImpl& c = find(destination); - c.addMessageCredit(value); - c.requestDispatch(); + ConsumerImpl::shared_ptr c = find(destination); + c->addMessageCredit(value); + c->requestDispatch(); } void SemanticState::flush(const std::string& destination) { - find(destination).flush(); + find(destination)->flush(); } void SemanticState::stop(const std::string& destination) { - find(destination).stop(); + find(destination)->stop(); } void SemanticState::ConsumerImpl::setWindowMode() @@ -621,6 +646,7 @@ void SemanticState::ConsumerImpl::setCreditMode() void SemanticState::ConsumerImpl::addByteCredit(uint32_t value) { assertClusterSafe(); + if (windowing) windowActive = true; if (byteCredit != 0xFFFFFFFF) { if (value == 0xFFFFFFFF) byteCredit = value; else byteCredit += value; @@ -630,6 +656,7 @@ void SemanticState::ConsumerImpl::addByteCredit(uint32_t value) void SemanticState::ConsumerImpl::addMessageCredit(uint32_t value) { assertClusterSafe(); + if (windowing) windowActive = true; if (msgCredit != 0xFFFFFFFF) { if (value == 0xFFFFFFFF) msgCredit = value; else msgCredit += value; @@ -650,7 +677,8 @@ void SemanticState::ConsumerImpl::flush() { while(haveCredit() && queue->dispatch(shared_from_this())) ; - stop(); + msgCredit = 0; + byteCredit = 0; } void SemanticState::ConsumerImpl::stop() @@ -658,6 +686,7 @@ void SemanticState::ConsumerImpl::stop() assertClusterSafe(); msgCredit = 0; byteCredit = 0; + windowActive = false; } Queue::shared_ptr SemanticState::getQueue(const string& name) const { @@ -673,7 +702,7 @@ Queue::shared_ptr SemanticState::getQueue(const string& name) const { } AckRange SemanticState::findRange(DeliveryId first, DeliveryId last) -{ +{ return DeliveryRecord::findRange(unacked, first, last); } @@ -691,14 +720,21 @@ void SemanticState::release(DeliveryId first, DeliveryId last, bool setRedeliver DeliveryRecords::reverse_iterator start(range.end); DeliveryRecords::reverse_iterator end(range.start); for_each(start, end, boost::bind(&DeliveryRecord::release, _1, setRedelivered)); + + DeliveryRecords::iterator removed = + remove_if(range.start, range.end, bind(&DeliveryRecord::isRedundant, _1)); + unacked.erase(removed, range.end); } void SemanticState::reject(DeliveryId first, DeliveryId last) { AckRange range = findRange(first, last); for_each(range.start, range.end, mem_fun_ref(&DeliveryRecord::reject)); - //need to remove the delivery records as well - unacked.erase(range.start, range.end); + //may need to remove the delivery records as well + for (DeliveryRecords::iterator i = range.start; i != unacked.end() && i->getId() <= last; ) { + if (i->isRedundant()) i = unacked.erase(i); + else i++; + } } bool SemanticState::ConsumerImpl::doOutput() @@ -761,13 +797,13 @@ void SemanticState::accepted(const SequenceSet& commands) { //in transactional mode, don't dequeue or remove, just //maintain set of acknowledged messages: accumulatedAck.add(commands); - + if (dtxBuffer.get()) { //if enlisted in a dtx, copy the relevant slice from //unacked and record it against that transaction TxOp::shared_ptr txAck(new DtxAck(accumulatedAck, unacked)); accumulatedAck.clear(); - dtxBuffer->enlist(txAck); + dtxBuffer->enlist(txAck); //mark the relevant messages as 'ended' in unacked //if the messages are already completed, they can be @@ -789,7 +825,6 @@ void SemanticState::accepted(const SequenceSet& commands) { } void SemanticState::completed(const SequenceSet& commands) { - assertClusterSafe(); DeliveryRecords::iterator removed = remove_if(unacked.begin(), unacked.end(), isInSequenceSetAnd(commands, @@ -800,7 +835,6 @@ void SemanticState::completed(const SequenceSet& commands) { void SemanticState::attached() { - assertClusterSafe(); for (ConsumerImplMap::iterator i = consumers.begin(); i != consumers.end(); i++) { i->second->enableNotify(); session.getConnection().outputTasks.addOutputTask(i->second.get()); @@ -810,7 +844,6 @@ void SemanticState::attached() void SemanticState::detached() { - assertClusterSafe(); for (ConsumerImplMap::iterator i = consumers.begin(); i != consumers.end(); i++) { i->second->disableNotify(); session.getConnection().outputTasks.removeOutputTask(i->second.get()); diff --git a/cpp/src/qpid/broker/SemanticState.h b/cpp/src/qpid/broker/SemanticState.h index b2e648410a..6d88dd56d9 100644 --- a/cpp/src/qpid/broker/SemanticState.h +++ b/cpp/src/qpid/broker/SemanticState.h @@ -65,7 +65,7 @@ class SessionContext; * * Message delivery is driven by ConsumerImpl::doOutput(), which is * called when a client's socket is ready to write data. - * + * */ class SemanticState : private boost::noncopyable { public: @@ -75,14 +75,15 @@ class SemanticState : private boost::noncopyable { { mutable qpid::sys::Mutex lock; SemanticState* const parent; - const std::string name; const boost::shared_ptr<Queue> queue; const bool ackExpected; const bool acquire; bool blocked; bool windowing; + bool windowActive; bool exclusive; std::string resumeId; + const std::string tag; // <destination> from AMQP 0-10 Message.subscribe command uint64_t resumeTtl; framing::FieldTable arguments; uint32_t msgCredit; @@ -99,15 +100,16 @@ class SemanticState : private boost::noncopyable { public: typedef boost::shared_ptr<ConsumerImpl> shared_ptr; - ConsumerImpl(SemanticState* parent, + ConsumerImpl(SemanticState* parent, const std::string& name, boost::shared_ptr<Queue> queue, bool ack, bool acquire, bool exclusive, - const std::string& resumeId, uint64_t resumeTtl, const framing::FieldTable& arguments); + const std::string& tag, const std::string& resumeId, + uint64_t resumeTtl, const framing::FieldTable& arguments); ~ConsumerImpl(); OwnershipToken* getSession(); - bool deliver(QueuedMessage& msg); - bool filter(boost::intrusive_ptr<Message> msg); - bool accept(boost::intrusive_ptr<Message> msg); + bool deliver(QueuedMessage& msg); + bool filter(boost::intrusive_ptr<Message> msg); + bool accept(boost::intrusive_ptr<Message> msg); void disableNotify(); void enableNotify(); @@ -122,15 +124,13 @@ class SemanticState : private boost::noncopyable { void addMessageCredit(uint32_t value); void flush(); void stop(); - void complete(DeliveryRecord&); + void complete(DeliveryRecord&); boost::shared_ptr<Queue> getQueue() const { return queue; } bool isBlocked() const { return blocked; } bool setBlocked(bool set) { std::swap(set, blocked); return set; } bool doOutput(); - std::string getName() const { return name; } - bool isAckExpected() const { return ackExpected; } bool isAcquire() const { return acquire; } bool isWindowing() const { return windowing; } @@ -138,6 +138,7 @@ class SemanticState : private boost::noncopyable { uint32_t getMsgCredit() const { return msgCredit; } uint32_t getByteCredit() const { return byteCredit; } std::string getResumeId() const { return resumeId; }; + const std::string& getTag() const { return tag; } uint64_t getResumeTtl() const { return resumeTtl; } const framing::FieldTable& getArguments() const { return arguments; } @@ -148,9 +149,10 @@ class SemanticState : private boost::noncopyable { management::Manageable::status_t ManagementMethod (uint32_t methodId, management::Args& args, std::string& text); }; + typedef std::map<std::string, DtxBuffer::shared_ptr> DtxBufferMap; + private: typedef std::map<std::string, ConsumerImpl::shared_ptr> ConsumerImplMap; - typedef std::map<std::string, DtxBuffer::shared_ptr> DtxBufferMap; SessionContext& session; DeliveryAdapter& deliveryAdapter; @@ -163,7 +165,6 @@ class SemanticState : private boost::noncopyable { DtxBufferMap suspendedXids; framing::SequenceSet accumulatedAck; boost::shared_ptr<Exchange> cacheExchange; - AclModule* acl; const bool authMsg; const std::string userID; const std::string userName; @@ -181,14 +182,16 @@ class SemanticState : private boost::noncopyable { void disable(ConsumerImpl::shared_ptr); public: + SemanticState(DeliveryAdapter&, SessionContext&); ~SemanticState(); SessionContext& getSession() { return session; } const SessionContext& getSession() const { return session; } - ConsumerImpl& find(const std::string& destination); - + const ConsumerImpl::shared_ptr find(const std::string& destination) const; + bool find(const std::string& destination, ConsumerImpl::shared_ptr&) const; + /** * Get named queue, never returns 0. * @return: named queue @@ -196,16 +199,16 @@ class SemanticState : private boost::noncopyable { * @exception: ConnectionException if name="" and session has no default. */ boost::shared_ptr<Queue> getQueue(const std::string& name) const; - + bool exists(const std::string& consumerTag); - void consume(const std::string& destination, - boost::shared_ptr<Queue> queue, + void consume(const std::string& destination, + boost::shared_ptr<Queue> queue, bool ackRequired, bool acquire, bool exclusive, const std::string& resumeId=std::string(), uint64_t resumeTtl=0, const framing::FieldTable& = framing::FieldTable()); - void cancel(const std::string& tag); + bool cancel(const std::string& tag); void setWindowMode(const std::string& destination); void setCreditMode(const std::string& destination); @@ -218,12 +221,13 @@ class SemanticState : private boost::noncopyable { void commit(MessageStore* const store); void rollback(); void selectDtx(); + bool getDtxSelected() const { return dtxSelected; } void startDtx(const std::string& xid, DtxManager& mgr, bool join); void endDtx(const std::string& xid, bool fail); void suspendDtx(const std::string& xid); void resumeDtx(const std::string& xid); void recover(bool requeue); - void deliver(DeliveryRecord& message, bool sync); + void deliver(DeliveryRecord& message, bool sync); void acquire(DeliveryId first, DeliveryId last, DeliveryIds& acquired); void release(DeliveryId first, DeliveryId last, bool setRedelivered); void reject(DeliveryId first, DeliveryId last); @@ -244,9 +248,12 @@ class SemanticState : private boost::noncopyable { DeliveryRecords& getUnacked() { return unacked; } framing::SequenceSet getAccumulatedAck() const { return accumulatedAck; } TxBuffer::shared_ptr getTxBuffer() const { return txBuffer; } + DtxBuffer::shared_ptr getDtxBuffer() const { return dtxBuffer; } void setTxBuffer(const TxBuffer::shared_ptr& txb) { txBuffer = txb; } + void setDtxBuffer(const DtxBuffer::shared_ptr& dtxb) { dtxBuffer = dtxb; txBuffer = dtxb; } void setAccumulatedAck(const framing::SequenceSet& s) { accumulatedAck = s; } void record(const DeliveryRecord& delivery); + DtxBufferMap& getSuspendedXids() { return suspendedXids; } }; }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/SessionAdapter.cpp b/cpp/src/qpid/broker/SessionAdapter.cpp index 3d62e73185..63c4b660b2 100644 --- a/cpp/src/qpid/broker/SessionAdapter.cpp +++ b/cpp/src/qpid/broker/SessionAdapter.cpp @@ -24,6 +24,7 @@ #include "qpid/log/Statement.h" #include "qpid/framing/SequenceSet.h" #include "qpid/management/ManagementAgent.h" +#include "qpid/broker/SessionState.h" #include "qmf/org/apache/qpid/broker/EventExchangeDeclare.h" #include "qmf/org/apache/qpid/broker/EventExchangeDelete.h" #include "qmf/org/apache/qpid/broker/EventQueueDeclare.h" @@ -64,53 +65,56 @@ void SessionAdapter::ExchangeHandlerImpl::declare(const string& exchange, const const string& alternateExchange, bool passive, bool durable, bool /*autoDelete*/, const FieldTable& args){ - AclModule* acl = getBroker().getAcl(); - if (acl) { - std::map<acl::Property, std::string> params; - params.insert(make_pair(acl::PROP_TYPE, type)); - params.insert(make_pair(acl::PROP_ALTERNATE, alternateExchange)); - params.insert(make_pair(acl::PROP_PASSIVE, std::string(passive ? _TRUE : _FALSE) )); - params.insert(make_pair(acl::PROP_DURABLE, std::string(durable ? _TRUE : _FALSE))); - if (!acl->authorise(getConnection().getUserId(),acl::ACT_CREATE,acl::OBJ_EXCHANGE,exchange,¶ms) ) - throw UnauthorizedAccessException(QPID_MSG("ACL denied exchange declare request from " << getConnection().getUserId())); - } - //TODO: implement autoDelete Exchange::shared_ptr alternate; if (!alternateExchange.empty()) { alternate = getBroker().getExchanges().get(alternateExchange); } if(passive){ + AclModule* acl = getBroker().getAcl(); + if (acl) { + //TODO: why does a passive declare require create + //permission? The purpose of the passive flag is to state + //that the exchange should *not* created. For + //authorisation a passive declare is similar to + //exchange-query. + std::map<acl::Property, std::string> params; + params.insert(make_pair(acl::PROP_TYPE, type)); + params.insert(make_pair(acl::PROP_ALTERNATE, alternateExchange)); + params.insert(make_pair(acl::PROP_PASSIVE, _TRUE)); + params.insert(make_pair(acl::PROP_DURABLE, durable ? _TRUE : _FALSE)); + if (!acl->authorise(getConnection().getUserId(),acl::ACT_CREATE,acl::OBJ_EXCHANGE,exchange,¶ms) ) + throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied exchange create request from " << getConnection().getUserId())); + } Exchange::shared_ptr actual(getBroker().getExchanges().get(exchange)); checkType(actual, type); checkAlternate(actual, alternate); - }else{ + }else{ if(exchange.find("amq.") == 0 || exchange.find("qpid.") == 0) { throw framing::NotAllowedException(QPID_MSG("Exchange names beginning with \"amq.\" or \"qpid.\" are reserved. (exchange=\"" << exchange << "\")")); } try{ - std::pair<Exchange::shared_ptr, bool> response = getBroker().getExchanges().declare(exchange, type, durable, args); - if (response.second) { - if (alternate) { - response.first->setAlternate(alternate); - alternate->incAlternateUsers(); - } - if (durable) { - getBroker().getStore().create(*response.first, args); - } - } else { + std::pair<Exchange::shared_ptr, bool> response = + getBroker().createExchange(exchange, type, durable, alternateExchange, args, + getConnection().getUserId(), getConnection().getUrl()); + if (!response.second) { + //exchange already there, not created checkType(response.first, type); checkAlternate(response.first, alternate); + ManagementAgent* agent = getBroker().getManagementAgent(); + if (agent) + agent->raiseEvent(_qmf::EventExchangeDeclare(getConnection().getUrl(), + getConnection().getUserId(), + exchange, + type, + alternateExchange, + durable, + false, + ManagementAgent::toMap(args), + "existing")); } - - ManagementAgent* agent = getBroker().getManagementAgent(); - if (agent) - agent->raiseEvent(_qmf::EventExchangeDeclare(getConnection().getUrl(), getConnection().getUserId(), exchange, type, - alternateExchange, durable, false, ManagementAgent::toMap(args), - response.second ? "created" : "existing")); - }catch(UnknownExchangeTypeException& /*e*/){ - throw CommandInvalidException(QPID_MSG("Exchange type not implemented: " << type)); + throw NotFoundException(QPID_MSG("Exchange type not implemented: " << type)); } } } @@ -134,22 +138,8 @@ void SessionAdapter::ExchangeHandlerImpl::checkAlternate(Exchange::shared_ptr ex void SessionAdapter::ExchangeHandlerImpl::delete_(const string& name, bool /*ifUnused*/) { - AclModule* acl = getBroker().getAcl(); - if (acl) { - if (!acl->authorise(getConnection().getUserId(),acl::ACT_DELETE,acl::OBJ_EXCHANGE,name,NULL) ) - throw UnauthorizedAccessException(QPID_MSG("ACL denied exchange delete request from " << getConnection().getUserId())); - } - - //TODO: implement unused - Exchange::shared_ptr exchange(getBroker().getExchanges().get(name)); - if (exchange->inUseAsAlternate()) throw NotAllowedException(QPID_MSG("Exchange in use as alternate-exchange.")); - if (exchange->isDurable()) getBroker().getStore().destroy(*exchange); - if (exchange->getAlternate()) exchange->getAlternate()->decAlternateUsers(); - getBroker().getExchanges().destroy(name); - - ManagementAgent* agent = getBroker().getManagementAgent(); - if (agent) - agent->raiseEvent(_qmf::EventExchangeDelete(getConnection().getUrl(), getConnection().getUserId(), name)); + //TODO: implement if-unused + getBroker().deleteExchange(name, getConnection().getUserId(), getConnection().getUrl()); } ExchangeQueryResult SessionAdapter::ExchangeHandlerImpl::query(const string& name) @@ -169,67 +159,19 @@ ExchangeQueryResult SessionAdapter::ExchangeHandlerImpl::query(const string& nam } void SessionAdapter::ExchangeHandlerImpl::bind(const string& queueName, - const string& exchangeName, const string& routingKey, - const FieldTable& arguments) + const string& exchangeName, const string& routingKey, + const FieldTable& arguments) { - AclModule* acl = getBroker().getAcl(); - if (acl) { - std::map<acl::Property, std::string> params; - params.insert(make_pair(acl::PROP_QUEUENAME, queueName)); - params.insert(make_pair(acl::PROP_ROUTINGKEY, routingKey)); - - if (!acl->authorise(getConnection().getUserId(),acl::ACT_BIND,acl::OBJ_EXCHANGE,exchangeName,¶ms)) - throw UnauthorizedAccessException(QPID_MSG("ACL denied exchange bind request from " << getConnection().getUserId())); - } - - Queue::shared_ptr queue = getQueue(queueName); - Exchange::shared_ptr exchange = getBroker().getExchanges().get(exchangeName); - if(exchange){ - string exchangeRoutingKey = routingKey.empty() && queueName.empty() ? queue->getName() : routingKey; - if (exchange->bind(queue, exchangeRoutingKey, &arguments)) { - queue->bound(exchangeName, routingKey, arguments); - if (exchange->isDurable() && queue->isDurable()) { - getBroker().getStore().bind(*exchange, *queue, routingKey, arguments); - } - - ManagementAgent* agent = getBroker().getManagementAgent(); - if (agent) - agent->raiseEvent(_qmf::EventBind(getConnection().getUrl(), getConnection().getUserId(), exchangeName, - queueName, exchangeRoutingKey, ManagementAgent::toMap(arguments))); - } - }else{ - throw NotFoundException("Bind failed. No such exchange: " + exchangeName); - } + getBroker().bind(queueName, exchangeName, routingKey, arguments, + getConnection().getUserId(), getConnection().getUrl()); } void SessionAdapter::ExchangeHandlerImpl::unbind(const string& queueName, const string& exchangeName, const string& routingKey) { - AclModule* acl = getBroker().getAcl(); - if (acl) { - std::map<acl::Property, std::string> params; - params.insert(make_pair(acl::PROP_QUEUENAME, queueName)); - params.insert(make_pair(acl::PROP_ROUTINGKEY, routingKey)); - if (!acl->authorise(getConnection().getUserId(),acl::ACT_UNBIND,acl::OBJ_EXCHANGE,exchangeName,¶ms) ) - throw UnauthorizedAccessException(QPID_MSG("ACL denied exchange unbind request from " << getConnection().getUserId())); - } - - Queue::shared_ptr queue = getQueue(queueName); - if (!queue.get()) throw NotFoundException("Unbind failed. No such exchange: " + exchangeName); - - Exchange::shared_ptr exchange = getBroker().getExchanges().get(exchangeName); - if (!exchange.get()) throw NotFoundException("Unbind failed. No such exchange: " + exchangeName); - - //TODO: revise unbind to rely solely on binding key (not args) - if (exchange->unbind(queue, routingKey, 0)) { - if (exchange->isDurable() && queue->isDurable()) - getBroker().getStore().unbind(*exchange, *queue, routingKey, FieldTable()); - - ManagementAgent* agent = getBroker().getManagementAgent(); - if (agent) - agent->raiseEvent(_qmf::EventUnbind(getConnection().getUrl(), getConnection().getUserId(), exchangeName, queueName, routingKey)); - } + getBroker().unbind(queueName, exchangeName, routingKey, + getConnection().getUserId(), getConnection().getUrl()); } ExchangeBoundResult SessionAdapter::ExchangeHandlerImpl::bound(const std::string& exchangeName, @@ -332,52 +274,42 @@ QueueQueryResult SessionAdapter::QueueHandlerImpl::query(const string& name) void SessionAdapter::QueueHandlerImpl::declare(const string& name, const string& alternateExchange, bool passive, bool durable, bool exclusive, bool autoDelete, const qpid::framing::FieldTable& arguments) -{ - AclModule* acl = getBroker().getAcl(); - if (acl) { - std::map<acl::Property, std::string> params; - params.insert(make_pair(acl::PROP_ALTERNATE, alternateExchange)); - params.insert(make_pair(acl::PROP_PASSIVE, std::string(passive ? _TRUE : _FALSE) )); - params.insert(make_pair(acl::PROP_DURABLE, std::string(durable ? _TRUE : _FALSE))); - params.insert(make_pair(acl::PROP_EXCLUSIVE, std::string(exclusive ? _TRUE : _FALSE))); - params.insert(make_pair(acl::PROP_AUTODELETE, std::string(autoDelete ? _TRUE : _FALSE))); - params.insert(make_pair(acl::PROP_POLICYTYPE, arguments.getAsString("qpid.policy_type"))); - params.insert(make_pair(acl::PROP_MAXQUEUECOUNT, boost::lexical_cast<string>(arguments.getAsInt("qpid.max_count")))); - params.insert(make_pair(acl::PROP_MAXQUEUESIZE, boost::lexical_cast<string>(arguments.getAsInt64("qpid.max_size")))); - - if (!acl->authorise(getConnection().getUserId(),acl::ACT_CREATE,acl::OBJ_QUEUE,name,¶ms) ) - throw UnauthorizedAccessException(QPID_MSG("ACL denied queue create request from " << getConnection().getUserId())); - } - - Exchange::shared_ptr alternate; - if (!alternateExchange.empty()) { - alternate = getBroker().getExchanges().get(alternateExchange); - } +{ Queue::shared_ptr queue; if (passive && !name.empty()) { - queue = getQueue(name); + AclModule* acl = getBroker().getAcl(); + if (acl) { + //TODO: why does a passive declare require create + //permission? The purpose of the passive flag is to state + //that the queue should *not* created. For + //authorisation a passive declare is similar to + //queue-query (or indeed a qmf query). + std::map<acl::Property, std::string> params; + params.insert(make_pair(acl::PROP_ALTERNATE, alternateExchange)); + params.insert(make_pair(acl::PROP_PASSIVE, _TRUE)); + params.insert(make_pair(acl::PROP_DURABLE, std::string(durable ? _TRUE : _FALSE))); + params.insert(make_pair(acl::PROP_EXCLUSIVE, std::string(exclusive ? _TRUE : _FALSE))); + params.insert(make_pair(acl::PROP_AUTODELETE, std::string(autoDelete ? _TRUE : _FALSE))); + params.insert(make_pair(acl::PROP_POLICYTYPE, arguments.getAsString("qpid.policy_type"))); + params.insert(make_pair(acl::PROP_MAXQUEUECOUNT, boost::lexical_cast<string>(arguments.getAsInt("qpid.max_count")))); + params.insert(make_pair(acl::PROP_MAXQUEUESIZE, boost::lexical_cast<string>(arguments.getAsInt64("qpid.max_size")))); + if (!acl->authorise(getConnection().getUserId(),acl::ACT_CREATE,acl::OBJ_QUEUE,name,¶ms) ) + throw UnauthorizedAccessException(QPID_MSG("ACL denied queue create request from " << getConnection().getUserId())); + } + queue = getQueue(name); //TODO: check alternate-exchange is as expected } else { - std::pair<Queue::shared_ptr, bool> queue_created = - getBroker().getQueues().declare(name, durable, - autoDelete, - exclusive ? &session : 0); + std::pair<Queue::shared_ptr, bool> queue_created = + getBroker().createQueue(name, durable, + autoDelete, + exclusive ? &session : 0, + alternateExchange, + arguments, + getConnection().getUserId(), + getConnection().getUrl()); queue = queue_created.first; assert(queue); if (queue_created.second) { // This is a new queue - if (alternate) { - queue->setAlternateExchange(alternate); - alternate->incAlternateUsers(); - } - - //apply settings & create persistent record if required - try { queue_created.first->create(arguments); } - catch (...) { getBroker().getQueues().destroy(name); throw; } - - //add default binding: - getBroker().getExchanges().getDefault()->bind(queue, name, 0); - queue->bound(getBroker().getExchanges().getDefault()->getName(), name, arguments); - //handle automatic cleanup: if (exclusive) { exclusiveQueues.push_back(queue); @@ -386,21 +318,20 @@ void SessionAdapter::QueueHandlerImpl::declare(const string& name, const string& if (exclusive && queue->setExclusiveOwner(&session)) { exclusiveQueues.push_back(queue); } + ManagementAgent* agent = getBroker().getManagementAgent(); + if (agent) + agent->raiseEvent(_qmf::EventQueueDeclare(getConnection().getUrl(), getConnection().getUserId(), + name, durable, exclusive, autoDelete, ManagementAgent::toMap(arguments), + "existing")); } - ManagementAgent* agent = getBroker().getManagementAgent(); - if (agent) - agent->raiseEvent(_qmf::EventQueueDeclare(getConnection().getUrl(), getConnection().getUserId(), - name, durable, exclusive, autoDelete, ManagementAgent::toMap(arguments), - queue_created.second ? "created" : "existing")); } - if (exclusive && !queue->isExclusiveOwner(&session)) + if (exclusive && !queue->isExclusiveOwner(&session)) throw ResourceLockedException(QPID_MSG("Cannot grant exclusive access to queue " << queue->getName())); -} - - +} + void SessionAdapter::QueueHandlerImpl::purge(const string& queue){ AclModule* acl = getBroker().getAcl(); if (acl) @@ -409,40 +340,32 @@ void SessionAdapter::QueueHandlerImpl::purge(const string& queue){ throw UnauthorizedAccessException(QPID_MSG("ACL denied queue purge request from " << getConnection().getUserId())); } getQueue(queue)->purge(); -} - -void SessionAdapter::QueueHandlerImpl::delete_(const string& queue, bool ifUnused, bool ifEmpty){ - - AclModule* acl = getBroker().getAcl(); - if (acl) - { - if (!acl->authorise(getConnection().getUserId(),acl::ACT_DELETE,acl::OBJ_QUEUE,queue,NULL) ) - throw UnauthorizedAccessException(QPID_MSG("ACL denied queue delete request from " << getConnection().getUserId())); - } +} - Queue::shared_ptr q = getQueue(queue); - if (q->hasExclusiveOwner() && !q->isExclusiveOwner(&session)) +void SessionAdapter::QueueHandlerImpl::checkDelete(Queue::shared_ptr queue, bool ifUnused, bool ifEmpty) +{ + if (queue->hasExclusiveOwner() && !queue->isExclusiveOwner(&session)) { throw ResourceLockedException(QPID_MSG("Cannot delete queue " - << queue << "; it is exclusive to another session")); - if(ifEmpty && q->getMessageCount() > 0){ - throw PreconditionFailedException("Queue not empty."); - }else if(ifUnused && q->getConsumerCount() > 0){ - throw PreconditionFailedException("Queue in use."); - }else{ + << queue->getName() << "; it is exclusive to another session")); + } else if(ifEmpty && queue->getMessageCount() > 0) { + throw PreconditionFailedException(QPID_MSG("Cannot delete queue " + << queue->getName() << "; queue not empty")); + } else if(ifUnused && queue->getConsumerCount() > 0) { + throw PreconditionFailedException(QPID_MSG("Cannot delete queue " + << queue->getName() << "; queue in use")); + } else if (queue->isExclusiveOwner(&session)) { //remove the queue from the list of exclusive queues if necessary - if(q->isExclusiveOwner(&getConnection())){ - QueueVector::iterator i = std::find(getConnection().exclusiveQueues.begin(), getConnection().exclusiveQueues.end(), q); - if(i < getConnection().exclusiveQueues.end()) getConnection().exclusiveQueues.erase(i); - } - q->destroy(); - getBroker().getQueues().destroy(queue); - q->unbind(getBroker().getExchanges(), q); - - ManagementAgent* agent = getBroker().getManagementAgent(); - if (agent) - agent->raiseEvent(_qmf::EventQueueDelete(getConnection().getUrl(), getConnection().getUserId(), queue)); - q->notifyDeleted(); - } + QueueVector::iterator i = std::find(exclusiveQueues.begin(), + exclusiveQueues.end(), + queue); + if (i < exclusiveQueues.end()) exclusiveQueues.erase(i); + } +} + +void SessionAdapter::QueueHandlerImpl::delete_(const string& queue, bool ifUnused, bool ifEmpty) +{ + getBroker().deleteQueue(queue, getConnection().getUserId(), getConnection().getUrl(), + boost::bind(&SessionAdapter::QueueHandlerImpl::checkDelete, this, _1, ifUnused, ifEmpty)); } SessionAdapter::MessageHandlerImpl::MessageHandlerImpl(SemanticState& s) : @@ -508,7 +431,9 @@ SessionAdapter::MessageHandlerImpl::subscribe(const string& queueName, void SessionAdapter::MessageHandlerImpl::cancel(const string& destination ) { - state.cancel(destination); + if (!state.cancel(destination)) { + throw NotFoundException(QPID_MSG("No such subscription: " << destination)); + } ManagementAgent* agent = getBroker().getManagementAgent(); if (agent) @@ -587,7 +512,12 @@ framing::MessageResumeResult SessionAdapter::MessageHandlerImpl::resume(const st -void SessionAdapter::ExecutionHandlerImpl::sync() {} //essentially a no-op +void SessionAdapter::ExecutionHandlerImpl::sync() +{ + session.addPendingExecutionSync(); + /** @todo KAG - need a generic mechanism to allow a command to returning "not completed" status back to SessionState */ + +} void SessionAdapter::ExecutionHandlerImpl::result(const SequenceNumber& /*commandId*/, const string& /*value*/) { diff --git a/cpp/src/qpid/broker/SessionAdapter.h b/cpp/src/qpid/broker/SessionAdapter.h index ca27fb6e1d..8987c4812f 100644 --- a/cpp/src/qpid/broker/SessionAdapter.h +++ b/cpp/src/qpid/broker/SessionAdapter.h @@ -138,6 +138,7 @@ class Queue; bool isLocal(const ConnectionToken* t) const; void destroyExclusiveQueues(); + void checkDelete(boost::shared_ptr<Queue> queue, bool ifUnused, bool ifEmpty); template <class F> void eachExclusiveQueue(F f) { std::for_each(exclusiveQueues.begin(), exclusiveQueues.end(), f); diff --git a/cpp/src/qpid/broker/SessionContext.h b/cpp/src/qpid/broker/SessionContext.h index afbbb2cc22..253ce8dcf2 100644 --- a/cpp/src/qpid/broker/SessionContext.h +++ b/cpp/src/qpid/broker/SessionContext.h @@ -46,6 +46,7 @@ class SessionContext : public OwnershipToken, public sys::OutputControl virtual Broker& getBroker() = 0; virtual uint16_t getChannel() const = 0; virtual const SessionId& getSessionId() const = 0; + virtual void addPendingExecutionSync() = 0; }; }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/SessionHandler.cpp b/cpp/src/qpid/broker/SessionHandler.cpp index 69b364ad7b..752fa55535 100644 --- a/cpp/src/qpid/broker/SessionHandler.cpp +++ b/cpp/src/qpid/broker/SessionHandler.cpp @@ -40,11 +40,6 @@ SessionHandler::SessionHandler(Connection& c, ChannelId ch) SessionHandler::~SessionHandler() {} -namespace { -ClassId classId(AMQMethodBody* m) { return m ? m->amqpMethodId() : 0; } -MethodId methodId(AMQMethodBody* m) { return m ? m->amqpClassId() : 0; } -} // namespace - void SessionHandler::connectionException(framing::connection::CloseCode code, const std::string& msg) { // NOTE: must tell the error listener _before_ calling connection.close() if (connection.getErrorListener()) connection.getErrorListener()->connectionError(msg); diff --git a/cpp/src/qpid/broker/SessionState.cpp b/cpp/src/qpid/broker/SessionState.cpp index 1ca7b6dfc1..1ab17e9893 100644 --- a/cpp/src/qpid/broker/SessionState.cpp +++ b/cpp/src/qpid/broker/SessionState.cpp @@ -25,6 +25,7 @@ #include "qpid/broker/SessionManager.h" #include "qpid/broker/SessionHandler.h" #include "qpid/broker/RateFlowcontrol.h" +#include "qpid/sys/ClusterSafe.h" #include "qpid/sys/Timer.h" #include "qpid/framing/AMQContentBody.h" #include "qpid/framing/AMQHeaderBody.h" @@ -60,9 +61,9 @@ SessionState::SessionState( semanticState(*this, *this), adapter(semanticState), msgBuilder(&broker.getStore()), - enqueuedOp(boost::bind(&SessionState::enqueued, this, _1)), mgmtObject(0), - rateFlowcontrol(0) + rateFlowcontrol(0), + asyncCommandCompleter(new AsyncCommandCompleter(this)) { uint32_t maxRate = broker.getOptions().maxSessionRate; if (maxRate) { @@ -95,6 +96,7 @@ void SessionState::addManagementObject() { } SessionState::~SessionState() { + asyncCommandCompleter->cancel(); semanticState.closed(); if (mgmtObject != 0) mgmtObject->resourceDestroy (); @@ -125,6 +127,7 @@ bool SessionState::isLocal(const ConnectionToken* t) const void SessionState::detach() { QPID_LOG(debug, getId() << ": detached on broker."); + asyncCommandCompleter->detached(); disableOutput(); handler = 0; if (mgmtObject != 0) @@ -145,6 +148,7 @@ void SessionState::attach(SessionHandler& h) { mgmtObject->set_connectionRef (h.getConnection().GetManagementObject()->getObjectId()); mgmtObject->set_channelId (h.getChannel()); } + asyncCommandCompleter->attached(); } void SessionState::abort() { @@ -202,15 +206,17 @@ Manageable::status_t SessionState::ManagementMethod (uint32_t methodId, } void SessionState::handleCommand(framing::AMQMethodBody* method, const SequenceNumber& id) { + currentCommandComplete = true; // assumed, can be overridden by invoker method (this sucks). Invoker::Result invocation = invoke(adapter, *method); - receiverCompleted(id); + if (currentCommandComplete) receiverCompleted(id); + if (!invocation.wasHandled()) { throw NotImplementedException(QPID_MSG("Not implemented: " << *method)); } else if (invocation.hasResult()) { getProxy().getExecution().result(id, invocation.getResult()); } - if (method->isSync()) { - incomplete.process(enqueuedOp, true); + + if (method->isSync() && currentCommandComplete) { sendAcceptAndCompletion(); } } @@ -253,23 +259,14 @@ void SessionState::handleContent(AMQFrame& frame, const SequenceNumber& id) header.setEof(false); msg->getFrames().append(header); } + if (broker.isTimestamping()) + msg->setTimestamp(); msg->setPublisher(&getConnection()); + msg->getIngressCompletion().begin(); semanticState.handle(msg); msgBuilder.end(); - - if (msg->isEnqueueComplete()) { - enqueued(msg); - } else { - incomplete.add(msg); - } - - //hold up execution until async enqueue is complete - if (msg->getFrames().getMethod()->isSync()) { - incomplete.process(enqueuedOp, true); - sendAcceptAndCompletion(); - } else { - incomplete.process(enqueuedOp, false); - } + IncompleteIngressMsgXfer xfer(this, msg); + msg->getIngressCompletion().end(xfer); // allows msg to complete xfer } // Handle producer session flow control @@ -319,11 +316,41 @@ void SessionState::sendAcceptAndCompletion() sendCompletion(); } -void SessionState::enqueued(boost::intrusive_ptr<Message> msg) +/** Invoked when the given inbound message is finished being processed + * by all interested parties (eg. it is done being enqueued to all queues, + * its credit has been accounted for, etc). At this point, msg is considered + * by this receiver as 'completed' (as defined by AMQP 0_10) + */ +void SessionState::completeRcvMsg(SequenceNumber id, + bool requiresAccept, + bool requiresSync) { - receiverCompleted(msg->getCommandId()); - if (msg->requiresAccept()) - accepted.add(msg->getCommandId()); + // Mark this as a cluster-unsafe scope since it can be called in + // journal threads or connection threads as part of asynchronous + // command completion. + sys::ClusterUnsafeScope cus; + + bool callSendCompletion = false; + receiverCompleted(id); + if (requiresAccept) + // will cause msg's seq to appear in the next message.accept we send. + accepted.add(id); + + // Are there any outstanding Execution.Sync commands pending the + // completion of this msg? If so, complete them. + while (!pendingExecutionSyncs.empty() && + receiverGetIncomplete().front() >= pendingExecutionSyncs.front()) { + const SequenceNumber id = pendingExecutionSyncs.front(); + pendingExecutionSyncs.pop(); + QPID_LOG(debug, getId() << ": delayed execution.sync " << id << " is completed."); + receiverCompleted(id); + callSendCompletion = true; // likely peer is pending for this completion. + } + + // if the sender has requested immediate notification of the completion... + if (requiresSync || callSendCompletion) { + sendAcceptAndCompletion(); + } } void SessionState::handleIn(AMQFrame& frame) { @@ -396,4 +423,176 @@ framing::AMQP_ClientProxy& SessionState::getClusterOrderProxy() { return handler->getClusterOrderProxy(); } + +// Current received command is an execution.sync command. +// Complete this command only when all preceding commands have completed. +// (called via the invoker() in handleCommand() above) +void SessionState::addPendingExecutionSync() +{ + SequenceNumber syncCommandId = receiverGetCurrent(); + if (receiverGetIncomplete().front() < syncCommandId) { + currentCommandComplete = false; + pendingExecutionSyncs.push(syncCommandId); + asyncCommandCompleter->flushPendingMessages(); + QPID_LOG(debug, getId() << ": delaying completion of execution.sync " << syncCommandId); + } +} + + +/** factory for creating a reference-counted IncompleteIngressMsgXfer object + * which will be attached to a message that will be completed asynchronously. + */ +boost::intrusive_ptr<AsyncCompletion::Callback> +SessionState::IncompleteIngressMsgXfer::clone() +{ + // Optimization: this routine is *only* invoked when the message needs to be asynchronously completed. + // If the client is pending the message.transfer completion, flush now to force immediate write to journal. + if (requiresSync) + msg->flush(); + else { + // otherwise, we need to track this message in order to flush it if an execution.sync arrives + // before it has been completed (see flushPendingMessages()) + pending = true; + completerContext->addPendingMessage(msg); + } + + return boost::intrusive_ptr<SessionState::IncompleteIngressMsgXfer>(new SessionState::IncompleteIngressMsgXfer(*this)); +} + + +/** Invoked by the asynchronous completer associated with a received + * msg that is pending Completion. May be invoked by the IO thread + * (sync == true), or some external thread (!sync). + */ +void SessionState::IncompleteIngressMsgXfer::completed(bool sync) +{ + if (pending) completerContext->deletePendingMessage(id); + if (!sync) { + /** note well: this path may execute in any thread. It is safe to access + * the scheduledCompleterContext, since *this has a shared pointer to it. + * but not session! + */ + session = 0; + QPID_LOG(debug, ": async completion callback scheduled for msg seq=" << id); + completerContext->scheduleMsgCompletion(id, requiresAccept, requiresSync); + } else { + // this path runs directly from the ac->end() call in handleContent() above, + // so *session is definately valid. + if (session->isAttached()) { + QPID_LOG(debug, ": receive completed for msg seq=" << id); + session->completeRcvMsg(id, requiresAccept, requiresSync); + } + } + completerContext = boost::intrusive_ptr<AsyncCommandCompleter>(); +} + + +/** Scheduled from an asynchronous command's completed callback to run on + * the IO thread. + */ +void SessionState::AsyncCommandCompleter::schedule(boost::intrusive_ptr<AsyncCommandCompleter> ctxt) +{ + ctxt->completeCommands(); +} + + +/** Track an ingress message that is pending completion */ +void SessionState::AsyncCommandCompleter::addPendingMessage(boost::intrusive_ptr<Message> msg) +{ + qpid::sys::ScopedLock<qpid::sys::Mutex> l(completerLock); + std::pair<SequenceNumber, boost::intrusive_ptr<Message> > item(msg->getCommandId(), msg); + bool unique = pendingMsgs.insert(item).second; + if (!unique) { + assert(false); + } +} + + +/** pending message has completed */ +void SessionState::AsyncCommandCompleter::deletePendingMessage(SequenceNumber id) +{ + qpid::sys::ScopedLock<qpid::sys::Mutex> l(completerLock); + pendingMsgs.erase(id); +} + + +/** done when an execution.sync arrives */ +void SessionState::AsyncCommandCompleter::flushPendingMessages() +{ + std::map<SequenceNumber, boost::intrusive_ptr<Message> > copy; + { + qpid::sys::ScopedLock<qpid::sys::Mutex> l(completerLock); + pendingMsgs.swap(copy); // we've only tracked these in case a flush is needed, so nuke 'em now. + } + // drop lock, so it is safe to call "flush()" + for (std::map<SequenceNumber, boost::intrusive_ptr<Message> >::iterator i = copy.begin(); + i != copy.end(); ++i) { + i->second->flush(); + } +} + + +/** mark an ingress Message.Transfer command as completed. + * This method must be thread safe - it may run on any thread. + */ +void SessionState::AsyncCommandCompleter::scheduleMsgCompletion(SequenceNumber cmd, + bool requiresAccept, + bool requiresSync) +{ + qpid::sys::ScopedLock<qpid::sys::Mutex> l(completerLock); + + if (session && isAttached) { + MessageInfo msg(cmd, requiresAccept, requiresSync); + completedMsgs.push_back(msg); + if (completedMsgs.size() == 1) { + session->getConnection().requestIOProcessing(boost::bind(&schedule, + session->asyncCommandCompleter)); + } + } +} + + +/** Cause the session to complete all completed commands. + * Executes on the IO thread. + */ +void SessionState::AsyncCommandCompleter::completeCommands() +{ + qpid::sys::ScopedLock<qpid::sys::Mutex> l(completerLock); + + // when session is destroyed, it clears the session pointer via cancel(). + if (session && session->isAttached()) { + for (std::vector<MessageInfo>::iterator msg = completedMsgs.begin(); + msg != completedMsgs.end(); ++msg) { + session->completeRcvMsg(msg->cmd, msg->requiresAccept, msg->requiresSync); + } + } + completedMsgs.clear(); +} + + +/** cancel any pending calls to scheduleComplete */ +void SessionState::AsyncCommandCompleter::cancel() +{ + qpid::sys::ScopedLock<qpid::sys::Mutex> l(completerLock); + session = 0; +} + + +/** inform the completer that the session has attached, + * allows command completion scheduling from any thread */ +void SessionState::AsyncCommandCompleter::attached() +{ + qpid::sys::ScopedLock<qpid::sys::Mutex> l(completerLock); + isAttached = true; +} + + +/** inform the completer that the session has detached, + * disables command completion scheduling from any thread */ +void SessionState::AsyncCommandCompleter::detached() +{ + qpid::sys::ScopedLock<qpid::sys::Mutex> l(completerLock); + isAttached = false; +} + }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/SessionState.h b/cpp/src/qpid/broker/SessionState.h index be79eb0eab..506af85c47 100644 --- a/cpp/src/qpid/broker/SessionState.h +++ b/cpp/src/qpid/broker/SessionState.h @@ -30,13 +30,15 @@ #include "qmf/org/apache/qpid/broker/Session.h" #include "qpid/broker/SessionAdapter.h" #include "qpid/broker/DeliveryAdapter.h" -#include "qpid/broker/IncompleteMessageList.h" +#include "qpid/broker/AsyncCompletion.h" #include "qpid/broker/MessageBuilder.h" #include "qpid/broker/SessionContext.h" #include "qpid/broker/SemanticState.h" +#include "qpid/sys/Monitor.h" #include <boost/noncopyable.hpp> #include <boost/scoped_ptr.hpp> +#include <boost/intrusive_ptr.hpp> #include <set> #include <vector> @@ -123,6 +125,10 @@ class SessionState : public qpid::SessionState, const SessionId& getSessionId() const { return getId(); } + // Used by ExecutionHandler sync command processing. Notifies + // the SessionState of a received Execution.Sync command. + void addPendingExecutionSync(); + // Used to delay creation of management object for sessions // belonging to inter-broker bridges void addManagementObject(); @@ -130,7 +136,10 @@ class SessionState : public qpid::SessionState, private: void handleCommand(framing::AMQMethodBody* method, const framing::SequenceNumber& id); void handleContent(framing::AMQFrame& frame, const framing::SequenceNumber& id); - void enqueued(boost::intrusive_ptr<Message> msg); + + // indicate that the given ingress msg has been completely received by the + // broker, and the msg's message.transfer command can be considered completed. + void completeRcvMsg(SequenceNumber id, bool requiresAccept, bool requiresSync); void handleIn(framing::AMQFrame& frame); void handleOut(framing::AMQFrame& frame); @@ -156,8 +165,6 @@ class SessionState : public qpid::SessionState, SemanticState semanticState; SessionAdapter adapter; MessageBuilder msgBuilder; - IncompleteMessageList incomplete; - IncompleteMessageList::CompletionListener enqueuedOp; qmf::org::apache::qpid::broker::Session* mgmtObject; qpid::framing::SequenceSet accepted; @@ -166,6 +173,110 @@ class SessionState : public qpid::SessionState, boost::scoped_ptr<RateFlowcontrol> rateFlowcontrol; boost::intrusive_ptr<sys::TimerTask> flowControlTimer; + // sequence numbers for pending received Execution.Sync commands + std::queue<SequenceNumber> pendingExecutionSyncs; + bool currentCommandComplete; + + /** This class provides a context for completing asynchronous commands in a thread + * safe manner. Asynchronous commands save their completion state in this class. + * This class then schedules the completeCommands() method in the IO thread. + * While running in the IO thread, completeCommands() may safely complete all + * saved commands without the risk of colliding with other operations on this + * SessionState. + */ + class AsyncCommandCompleter : public RefCounted { + private: + SessionState *session; + bool isAttached; + qpid::sys::Mutex completerLock; + + // special-case message.transfer commands for optimization + struct MessageInfo { + SequenceNumber cmd; // message.transfer command id + bool requiresAccept; + bool requiresSync; + MessageInfo(SequenceNumber c, bool a, bool s) + : cmd(c), requiresAccept(a), requiresSync(s) {} + }; + std::vector<MessageInfo> completedMsgs; + // If an ingress message does not require a Sync, we need to + // hold a reference to it in case an Execution.Sync command is received and we + // have to manually flush the message. + std::map<SequenceNumber, boost::intrusive_ptr<Message> > pendingMsgs; + + /** complete all pending commands, runs in IO thread */ + void completeCommands(); + + /** for scheduling a run of "completeCommands()" on the IO thread */ + static void schedule(boost::intrusive_ptr<AsyncCommandCompleter>); + + public: + AsyncCommandCompleter(SessionState *s) : session(s), isAttached(s->isAttached()) {}; + ~AsyncCommandCompleter() {}; + + /** track a message pending ingress completion */ + void addPendingMessage(boost::intrusive_ptr<Message> m); + void deletePendingMessage(SequenceNumber id); + void flushPendingMessages(); + /** schedule the processing of a completed ingress message.transfer command */ + void scheduleMsgCompletion(SequenceNumber cmd, + bool requiresAccept, + bool requiresSync); + void cancel(); // called by SessionState destructor. + void attached(); // called by SessionState on attach() + void detached(); // called by SessionState on detach() + }; + boost::intrusive_ptr<AsyncCommandCompleter> asyncCommandCompleter; + + /** Abstract class that represents a single asynchronous command that is + * pending completion. + */ + class AsyncCommandContext : public AsyncCompletion::Callback + { + public: + AsyncCommandContext( SessionState *ss, SequenceNumber _id ) + : id(_id), completerContext(ss->asyncCommandCompleter) {} + virtual ~AsyncCommandContext() {} + + protected: + SequenceNumber id; + boost::intrusive_ptr<AsyncCommandCompleter> completerContext; + }; + + /** incomplete Message.transfer commands - inbound to broker from client + */ + class IncompleteIngressMsgXfer : public SessionState::AsyncCommandContext + { + public: + IncompleteIngressMsgXfer( SessionState *ss, + boost::intrusive_ptr<Message> m ) + : AsyncCommandContext(ss, m->getCommandId()), + session(ss), + msg(m), + requiresAccept(m->requiresAccept()), + requiresSync(m->getFrames().getMethod()->isSync()), + pending(false) {} + IncompleteIngressMsgXfer( const IncompleteIngressMsgXfer& x ) + : AsyncCommandContext(x.session, x.msg->getCommandId()), + session(x.session), + msg(x.msg), + requiresAccept(x.requiresAccept), + requiresSync(x.requiresSync), + pending(x.pending) {} + + virtual ~IncompleteIngressMsgXfer() {}; + + virtual void completed(bool); + virtual boost::intrusive_ptr<AsyncCompletion::Callback> clone(); + + private: + SessionState *session; // only valid if sync flag in callback is true + boost::intrusive_ptr<Message> msg; + bool requiresAccept; + bool requiresSync; + bool pending; // true if msg saved on pending list... + }; + friend class SessionManager; }; diff --git a/cpp/src/qpid/broker/StatefulQueueObserver.h b/cpp/src/qpid/broker/StatefulQueueObserver.h new file mode 100644 index 0000000000..c682d460b7 --- /dev/null +++ b/cpp/src/qpid/broker/StatefulQueueObserver.h @@ -0,0 +1,63 @@ +#ifndef QPID_BROKER_STATEFULQUEUEOBSERVER_H +#define QPID_BROKER_STATEFULQUEUEOBSERVER_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/broker/QueueObserver.h" +#include "qpid/framing/FieldTable.h" + +namespace qpid { +namespace broker { + +/** + * Specialized type of QueueObserver that maintains internal state that has to + * be replicated across clustered brokers. + */ +class StatefulQueueObserver : public QueueObserver +{ + public: + StatefulQueueObserver(std::string _id) : id(_id) {} + virtual ~StatefulQueueObserver() {} + + /** This identifier must uniquely identify this particular observer amoung + * all observers on a queue. For cluster replication, this id will be used + * to identify the peer queue observer for synchronization across + * brokers. + */ + const std::string& getId() const { return id; } + + /** This method should return the observer's internal state as an opaque + * map. + */ + virtual void getState(qpid::framing::FieldTable& state ) const = 0; + + /** The input map represents the internal state of the peer observer that + * this observer should synchonize to. + */ + virtual void setState(const qpid::framing::FieldTable&) = 0; + + + private: + std::string id; +}; +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_STATEFULQUEUEOBSERVER_H*/ diff --git a/cpp/src/qpid/broker/ThresholdAlerts.cpp b/cpp/src/qpid/broker/ThresholdAlerts.cpp index 4f35884af8..3c9e210d4d 100644 --- a/cpp/src/qpid/broker/ThresholdAlerts.cpp +++ b/cpp/src/qpid/broker/ThresholdAlerts.cpp @@ -28,6 +28,52 @@ namespace qpid { namespace broker { +namespace { +const qmf::org::apache::qpid::broker::EventQueueThresholdExceeded EVENT("dummy", 0, 0); +bool isQMFv2(const boost::intrusive_ptr<Message> message) +{ + const qpid::framing::MessageProperties* props = message->getProperties<qpid::framing::MessageProperties>(); + return props && props->getAppId() == "qmf2"; +} + +bool isThresholdEvent(const boost::intrusive_ptr<Message> message) +{ + if (message->getIsManagementMessage()) { + //is this a qmf event? if so is it a threshold event? + if (isQMFv2(message)) { + const qpid::framing::FieldTable* headers = message->getApplicationHeaders(); + if (headers && headers->getAsString("qmf.content") == "_event") { + //decode as list + std::string content = message->getFrames().getContent(); + qpid::types::Variant::List list; + qpid::amqp_0_10::ListCodec::decode(content, list); + if (list.empty() || list.front().getType() != qpid::types::VAR_MAP) return false; + qpid::types::Variant::Map map = list.front().asMap(); + try { + std::string eventName = map["_schema_id"].asMap()["_class_name"].asString(); + return eventName == EVENT.getEventName(); + } catch (const std::exception& e) { + QPID_LOG(error, "Error checking for recursive threshold alert: " << e.what()); + } + } + } else { + std::string content = message->getFrames().getContent(); + qpid::framing::Buffer buffer(const_cast<char*>(content.data()), content.size()); + if (buffer.getOctet() == 'A' && buffer.getOctet() == 'M' && buffer.getOctet() == '2' && buffer.getOctet() == 'e') { + buffer.getLong();//sequence + std::string packageName; + buffer.getShortString(packageName); + if (packageName != EVENT.getPackageName()) return false; + std::string eventName; + buffer.getShortString(eventName); + return eventName == EVENT.getEventName(); + } + } + } + return false; +} +} + ThresholdAlerts::ThresholdAlerts(const std::string& n, qpid::management::ManagementAgent& a, const uint32_t ct, @@ -44,8 +90,14 @@ void ThresholdAlerts::enqueued(const QueuedMessage& m) if ((countThreshold && count >= countThreshold) || (sizeThreshold && size >= sizeThreshold)) { if ((repeatInterval == 0 && lastAlert == qpid::sys::EPOCH) || qpid::sys::Duration(lastAlert, qpid::sys::now()) > repeatInterval) { - agent.raiseEvent(qmf::org::apache::qpid::broker::EventQueueThresholdExceeded(name, count, size)); + //Note: Raising an event may result in messages being + //enqueued on queues; it may even be that this event + //causes a message to be enqueued on the queue we are + //tracking, and so we need to avoid recursing + if (isThresholdEvent(m.payload)) return; lastAlert = qpid::sys::now(); + agent.raiseEvent(qmf::org::apache::qpid::broker::EventQueueThresholdExceeded(name, count, size)); + QPID_LOG(info, "Threshold event triggered for " << name << ", count=" << count << ", size=" << size); } } } @@ -75,12 +127,12 @@ void ThresholdAlerts::observe(Queue& queue, qpid::management::ManagementAgent& a } void ThresholdAlerts::observe(Queue& queue, qpid::management::ManagementAgent& agent, - const qpid::framing::FieldTable& settings) + const qpid::framing::FieldTable& settings, uint16_t limitRatio) { qpid::types::Variant::Map map; qpid::amqp_0_10::translate(settings, map); - observe(queue, agent, map); + observe(queue, agent, map, limitRatio); } template <class T> @@ -118,19 +170,19 @@ class Option }; void ThresholdAlerts::observe(Queue& queue, qpid::management::ManagementAgent& agent, - const qpid::types::Variant::Map& settings) + const qpid::types::Variant::Map& settings, uint16_t limitRatio) { //Note: aliases are keys defined by java broker Option<int64_t> repeatInterval("qpid.alert_repeat_gap", 60); repeatInterval.addAlias("x-qpid-minimum-alert-repeat-gap"); - //If no explicit threshold settings were given use 80% of any - //limit from the policy. + //If no explicit threshold settings were given use specified + //percentage of any limit from the policy. const QueuePolicy* policy = queue.getPolicy(); - Option<uint32_t> countThreshold("qpid.alert_count", (uint32_t) (policy ? policy->getMaxCount()*0.8 : 0)); + Option<uint32_t> countThreshold("qpid.alert_count", (uint32_t) (policy && limitRatio ? (policy->getMaxCount()*limitRatio/100) : 0)); countThreshold.addAlias("x-qpid-maximum-message-count"); - Option<uint64_t> sizeThreshold("qpid.alert_size", (uint64_t) (policy ? policy->getMaxSize()*0.8 : 0)); + Option<uint64_t> sizeThreshold("qpid.alert_size", (uint64_t) (policy && limitRatio ? (policy->getMaxSize()*limitRatio/100) : 0)); sizeThreshold.addAlias("x-qpid-maximum-message-size"); observe(queue, agent, countThreshold.get(settings), sizeThreshold.get(settings), repeatInterval.get(settings)); diff --git a/cpp/src/qpid/broker/ThresholdAlerts.h b/cpp/src/qpid/broker/ThresholdAlerts.h index e1f59252c4..2b4a46b736 100644 --- a/cpp/src/qpid/broker/ThresholdAlerts.h +++ b/cpp/src/qpid/broker/ThresholdAlerts.h @@ -50,14 +50,17 @@ class ThresholdAlerts : public QueueObserver const long repeatInterval); void enqueued(const QueuedMessage&); void dequeued(const QueuedMessage&); + void acquired(const QueuedMessage&) {}; + void requeued(const QueuedMessage&) {}; + static void observe(Queue& queue, qpid::management::ManagementAgent& agent, const uint64_t countThreshold, const uint64_t sizeThreshold, const long repeatInterval); static void observe(Queue& queue, qpid::management::ManagementAgent& agent, - const qpid::framing::FieldTable& settings); + const qpid::framing::FieldTable& settings, uint16_t limitRatio); static void observe(Queue& queue, qpid::management::ManagementAgent& agent, - const qpid::types::Variant::Map& settings); + const qpid::types::Variant::Map& settings, uint16_t limitRatio); private: const std::string name; qpid::management::ManagementAgent& agent; diff --git a/cpp/src/qpid/broker/TopicExchange.cpp b/cpp/src/qpid/broker/TopicExchange.cpp index 1b0fe71bcf..644a3d628e 100644 --- a/cpp/src/qpid/broker/TopicExchange.cpp +++ b/cpp/src/qpid/broker/TopicExchange.cpp @@ -221,6 +221,7 @@ TopicExchange::TopicExchange(const std::string& _name, bool _durable, bool TopicExchange::bind(Queue::shared_ptr queue, const string& routingKey, const FieldTable* args) { + ClearCache cc(&cacheLock,&bindingCache); // clear the cache on function exit. string fedOp(args ? args->getAsString(qpidFedOp) : fedOpBind); string fedTags(args ? args->getAsString(qpidFedTags) : ""); string fedOrigin(args ? args->getAsString(qpidFedOrigin) : ""); @@ -249,21 +250,21 @@ bool TopicExchange::bind(Queue::shared_ptr queue, const string& routingKey, cons if (mgmtExchange != 0) { mgmtExchange->inc_bindingCount(); } - QPID_LOG(debug, "Bound key [" << routingPattern << "] to queue " << queue->getName() - << " (origin=" << fedOrigin << ")"); + QPID_LOG(debug, "Binding key [" << routingPattern << "] to queue " << queue->getName() + << " on exchange " << getName() << " (origin=" << fedOrigin << ")"); } } else if (fedOp == fedOpUnbind) { - bool reallyUnbind = false; - { - RWlock::ScopedWlock l(lock); - BindingKey* bk = bindingTree.getBindingKey(routingPattern); - if (bk) { - propagate = bk->fedBinding.delOrigin(queue->getName(), fedOrigin); - reallyUnbind = bk->fedBinding.countFedBindings(queue->getName()) == 0; + RWlock::ScopedWlock l(lock); + BindingKey* bk = getQueueBinding(queue, routingPattern); + if (bk) { + QPID_LOG(debug, "FedOpUnbind [" << routingPattern << "] from exchange " << getName() + << " on queue=" << queue->getName() << " origin=" << fedOrigin); + propagate = bk->fedBinding.delOrigin(queue->getName(), fedOrigin); + // if this was the last binding for the queue, delete the binding + if (bk->fedBinding.countFedBindings(queue->getName()) == 0) { + deleteBinding(queue, routingPattern, bk); } } - if (reallyUnbind) - unbind(queue, routingPattern, 0); } else if (fedOp == fedOpReorigin) { /** gather up all the keys that need rebinding in a local vector * while holding the lock. Then propagate once the lock is @@ -281,20 +282,38 @@ bool TopicExchange::bind(Queue::shared_ptr queue, const string& routingKey, cons } } + cc.clearCache(); // clear the cache before we IVE route. routeIVE(); if (propagate) propagateFedOp(routingKey, fedTags, fedOp, fedOrigin); return true; } -bool TopicExchange::unbind(Queue::shared_ptr queue, const string& constRoutingKey, const FieldTable* /*args*/){ +bool TopicExchange::unbind(Queue::shared_ptr queue, const string& constRoutingKey, const FieldTable* args) +{ + string fedOrigin(args ? args->getAsString(qpidFedOrigin) : ""); + QPID_LOG(debug, "Unbinding key [" << constRoutingKey << "] from queue " << queue->getName() + << " on exchange " << getName() << " origin=" << fedOrigin << ")" ); + + ClearCache cc(&cacheLock,&bindingCache); // clear the cache on function exit. RWlock::ScopedWlock l(lock); string routingKey = normalize(constRoutingKey); - BindingKey* bk = bindingTree.getBindingKey(routingKey); + BindingKey* bk = getQueueBinding(queue, routingKey); if (!bk) return false; - Binding::vector& qv(bk->bindingVector); - bool propagate = false; + bool propagate = bk->fedBinding.delOrigin(queue->getName(), fedOrigin); + deleteBinding(queue, routingKey, bk); + if (propagate) + propagateFedOp(routingKey, string(), fedOpUnbind, string()); + return true; +} + +bool TopicExchange::deleteBinding(Queue::shared_ptr queue, + const std::string& routingKey, + BindingKey *bk) +{ + // Note well: write lock held by caller + Binding::vector& qv(bk->bindingVector); Binding::vector::iterator q; for (q = qv.begin(); q != qv.end(); q++) if ((*q)->queue == queue) @@ -303,42 +322,55 @@ bool TopicExchange::unbind(Queue::shared_ptr queue, const string& constRoutingKe qv.erase(q); assert(nBindings > 0); nBindings--; - propagate = bk->fedBinding.delOrigin(); + if(qv.empty()) { bindingTree.removeBindingKey(routingKey); } if (mgmtExchange != 0) { mgmtExchange->dec_bindingCount(); } - QPID_LOG(debug, "Unbound [" << routingKey << "] from queue " << queue->getName()); - - if (propagate) - propagateFedOp(routingKey, string(), fedOpUnbind, string()); + QPID_LOG(debug, "Unbound key [" << routingKey << "] from queue " << queue->getName() + << " on exchange " << getName()); return true; } -bool TopicExchange::isBound(Queue::shared_ptr queue, const string& pattern) +/** returns a pointer to the BindingKey if the given queue is bound to this + * exchange using the routing pattern. 0 if queue binding does not exist. + */ +TopicExchange::BindingKey *TopicExchange::getQueueBinding(Queue::shared_ptr queue, const string& pattern) { // Note well: lock held by caller.... BindingKey *bk = bindingTree.getBindingKey(pattern); // Exact match against binding pattern - if (!bk) return false; + if (!bk) return 0; Binding::vector& qv(bk->bindingVector); Binding::vector::iterator q; for (q = qv.begin(); q != qv.end(); q++) if ((*q)->queue == queue) break; - return q != qv.end(); + return (q != qv.end()) ? bk : 0; } void TopicExchange::route(Deliverable& msg, const string& routingKey, const FieldTable* /*args*/) { // Note: PERFORMANCE CRITICAL!!! - BindingList b(new std::vector<boost::shared_ptr<qpid::broker::Exchange::Binding> >); + BindingList b; + std::map<std::string, BindingList>::iterator it; + { // only lock the cache for read + RWlock::ScopedRlock cl(cacheLock); + it = bindingCache.find(routingKey); + if (it != bindingCache.end()) { + b = it->second; + } + } PreRoute pr(msg, this); - BindingsFinderIter bindingsFinder(b); + if (!b.get()) // no cache hit { RWlock::ScopedRlock l(lock); + b = BindingList(new std::vector<boost::shared_ptr<qpid::broker::Exchange::Binding> >); + BindingsFinderIter bindingsFinder(b); bindingTree.iterateMatch(routingKey, bindingsFinder); + RWlock::ScopedWlock cwl(cacheLock); + bindingCache[routingKey] = b; // update cache } doRoute(msg, b); } @@ -348,7 +380,7 @@ bool TopicExchange::isBound(Queue::shared_ptr queue, const string* const routing RWlock::ScopedRlock l(lock); if (routingKey && queue) { string key(normalize(*routingKey)); - return isBound(queue, key); + return getQueueBinding(queue, key) != 0; } else if (!routingKey && !queue) { return nBindings > 0; } else if (routingKey) { diff --git a/cpp/src/qpid/broker/TopicExchange.h b/cpp/src/qpid/broker/TopicExchange.h index a6c457dcb3..636918f8a1 100644 --- a/cpp/src/qpid/broker/TopicExchange.h +++ b/cpp/src/qpid/broker/TopicExchange.h @@ -56,7 +56,7 @@ class TopicExchange : public virtual Exchange { // | +-->d-->... // +-->x-->y-->... // - class BindingNode { + class QPID_BROKER_CLASS_EXTERN BindingNode { public: typedef boost::shared_ptr<BindingNode> shared_ptr; @@ -135,8 +135,31 @@ class TopicExchange : public virtual Exchange { BindingNode bindingTree; unsigned long nBindings; qpid::sys::RWlock lock; // protects bindingTree and nBindings - - bool isBound(Queue::shared_ptr queue, const std::string& pattern); + qpid::sys::RWlock cacheLock; // protects cache + std::map<std::string, BindingList> bindingCache; // cache of matched routes. + class ClearCache { + private: + qpid::sys::RWlock* cacheLock; + std::map<std::string, BindingList>* bindingCache; + bool cleared; + public: + ClearCache(qpid::sys::RWlock* l, std::map<std::string, BindingList>* bc): cacheLock(l), + bindingCache(bc),cleared(false) {}; + void clearCache() { + qpid::sys::RWlock::ScopedWlock l(*cacheLock); + if (!cleared) { + bindingCache->clear(); + cleared =true; + } + }; + ~ClearCache(){ + clearCache(); + }; + }; + BindingKey *getQueueBinding(Queue::shared_ptr queue, const std::string& pattern); + bool deleteBinding(Queue::shared_ptr queue, + const std::string& routingKey, + BindingKey *bk); class ReOriginIter; class BindingsFinderIter; diff --git a/cpp/src/qpid/broker/TxBuffer.cpp b/cpp/src/qpid/broker/TxBuffer.cpp index b509778e89..d92e6ace48 100644 --- a/cpp/src/qpid/broker/TxBuffer.cpp +++ b/cpp/src/qpid/broker/TxBuffer.cpp @@ -76,5 +76,5 @@ bool TxBuffer::commitLocal(TransactionalStore* const store) } void TxBuffer::accept(TxOpConstVisitor& v) const { - std::for_each(ops.begin(), ops.end(), boost::bind(&TxOp::accept, _1, boost::ref(v))); + std::for_each(ops.begin(), ops.end(), boost::bind(&TxOp::accept, _1, boost::ref(v))); } diff --git a/cpp/src/qpid/broker/TxPublish.cpp b/cpp/src/qpid/broker/TxPublish.cpp index 36a451e62c..9c2cf4a467 100644 --- a/cpp/src/qpid/broker/TxPublish.cpp +++ b/cpp/src/qpid/broker/TxPublish.cpp @@ -90,14 +90,7 @@ void TxPublish::deliverTo(const boost::shared_ptr<Queue>& queue){ void TxPublish::prepare(TransactionContext* ctxt, const boost::shared_ptr<Queue> queue) { - if (!queue->enqueue(ctxt, msg)){ - /** - * if not store then mark message for ack and deleivery once - * commit happens, as async IO will never set it when no store - * exists - */ - msg->enqueueComplete(); - } + queue->enqueue(ctxt, msg); } TxPublish::Commit::Commit(intrusive_ptr<Message>& _msg) : msg(_msg){} diff --git a/cpp/src/qpid/broker/TxPublish.h b/cpp/src/qpid/broker/TxPublish.h index effa585676..dba7878af2 100644 --- a/cpp/src/qpid/broker/TxPublish.h +++ b/cpp/src/qpid/broker/TxPublish.h @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -34,57 +34,58 @@ #include <boost/intrusive_ptr.hpp> namespace qpid { - namespace broker { - /** - * Defines the behaviour for publish operations on a - * transactional channel. Messages are routed through - * exchanges when received but are not at that stage delivered - * to the matching queues, rather the queues are held in an - * instance of this class. On prepare() the message is marked - * enqueued to the relevant queues in the MessagesStore. On - * commit() the messages will be passed to the queue for - * dispatch or to be added to the in-memory queue. - */ - class TxPublish : public TxOp, public Deliverable{ +namespace broker { +/** + * Defines the behaviour for publish operations on a + * transactional channel. Messages are routed through + * exchanges when received but are not at that stage delivered + * to the matching queues, rather the queues are held in an + * instance of this class. On prepare() the message is marked + * enqueued to the relevant queues in the MessagesStore. On + * commit() the messages will be passed to the queue for + * dispatch or to be added to the in-memory queue. + */ +class QPID_BROKER_CLASS_EXTERN TxPublish : public TxOp, public Deliverable{ - class Commit{ - boost::intrusive_ptr<Message>& msg; - public: - Commit(boost::intrusive_ptr<Message>& msg); - void operator()(const boost::shared_ptr<Queue>& queue); - }; - class Rollback{ - boost::intrusive_ptr<Message>& msg; - public: - Rollback(boost::intrusive_ptr<Message>& msg); - void operator()(const boost::shared_ptr<Queue>& queue); - }; + class Commit{ + boost::intrusive_ptr<Message>& msg; + public: + Commit(boost::intrusive_ptr<Message>& msg); + void operator()(const boost::shared_ptr<Queue>& queue); + }; + class Rollback{ + boost::intrusive_ptr<Message>& msg; + public: + Rollback(boost::intrusive_ptr<Message>& msg); + void operator()(const boost::shared_ptr<Queue>& queue); + }; - boost::intrusive_ptr<Message> msg; - std::list<boost::shared_ptr<Queue> > queues; - std::list<boost::shared_ptr<Queue> > prepared; + boost::intrusive_ptr<Message> msg; + std::list<boost::shared_ptr<Queue> > queues; + std::list<boost::shared_ptr<Queue> > prepared; - void prepare(TransactionContext* ctxt, boost::shared_ptr<Queue>); + void prepare(TransactionContext* ctxt, boost::shared_ptr<Queue>); - public: - QPID_BROKER_EXTERN TxPublish(boost::intrusive_ptr<Message> msg); - QPID_BROKER_EXTERN virtual bool prepare(TransactionContext* ctxt) throw(); - QPID_BROKER_EXTERN virtual void commit() throw(); - QPID_BROKER_EXTERN virtual void rollback() throw(); + public: + QPID_BROKER_EXTERN TxPublish(boost::intrusive_ptr<Message> msg); + QPID_BROKER_EXTERN virtual bool prepare(TransactionContext* ctxt) throw(); + QPID_BROKER_EXTERN virtual void commit() throw(); + QPID_BROKER_EXTERN virtual void rollback() throw(); - virtual Message& getMessage() { return *msg; }; - - QPID_BROKER_EXTERN virtual void deliverTo(const boost::shared_ptr<Queue>& queue); + virtual Message& getMessage() { return *msg; }; - virtual ~TxPublish(){} - virtual void accept(TxOpConstVisitor& visitor) const { visitor(*this); } + QPID_BROKER_EXTERN virtual void deliverTo(const boost::shared_ptr<Queue>& queue); - QPID_BROKER_EXTERN uint64_t contentSize(); + virtual ~TxPublish(){} + virtual void accept(TxOpConstVisitor& visitor) const { visitor(*this); } - boost::intrusive_ptr<Message> getMessage() const { return msg; } - const std::list<boost::shared_ptr<Queue> > getQueues() const { return queues; } - }; - } + QPID_BROKER_EXTERN uint64_t contentSize(); + + boost::intrusive_ptr<Message> getMessage() const { return msg; } + const std::list<boost::shared_ptr<Queue> >& getQueues() const { return queues; } + const std::list<boost::shared_ptr<Queue> >& getPrepared() const { return prepared; } +}; +} } diff --git a/cpp/src/qpid/broker/windows/BrokerDefaults.cpp b/cpp/src/qpid/broker/windows/BrokerDefaults.cpp index b6862f0418..b65440b5ad 100644 --- a/cpp/src/qpid/broker/windows/BrokerDefaults.cpp +++ b/cpp/src/qpid/broker/windows/BrokerDefaults.cpp @@ -31,10 +31,16 @@ const std::string Broker::Options::DEFAULT_DATA_DIR_NAME("\\QPIDD.DATA"); std::string Broker::Options::getHome() { std::string home; +#ifdef _MSC_VER char home_c[MAX_PATH+1]; size_t unused; if (0 == getenv_s (&unused, home_c, sizeof(home_c), "HOME")) home += home_c; +#else + char *home_c = getenv("HOME"); + if (home_c) + home += home_c; +#endif return home; } diff --git a/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp b/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp index 608a8f7dae..2acc09cded 100644 --- a/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp +++ b/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp @@ -42,7 +42,7 @@ public: NullAuthenticator(Connection& connection); ~NullAuthenticator(); void getMechanisms(framing::Array& mechanisms); - void start(const std::string& mechanism, const std::string& response); + void start(const std::string& mechanism, const std::string* response); void step(const std::string&) {} std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize); }; @@ -57,7 +57,7 @@ public: SspiAuthenticator(Connection& connection); ~SspiAuthenticator(); void getMechanisms(framing::Array& mechanisms); - void start(const std::string& mechanism, const std::string& response); + void start(const std::string& mechanism, const std::string* response); void step(const std::string& response); std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize); }; @@ -93,14 +93,15 @@ NullAuthenticator::~NullAuthenticator() {} void NullAuthenticator::getMechanisms(Array& mechanisms) { mechanisms.add(boost::shared_ptr<FieldValue>(new Str16Value("ANONYMOUS"))); + mechanisms.add(boost::shared_ptr<FieldValue>(new Str16Value("PLAIN"))); } -void NullAuthenticator::start(const string& mechanism, const string& response) +void NullAuthenticator::start(const string& mechanism, const string* response) { QPID_LOG(warning, "SASL: No Authentication Performed"); if (mechanism == "PLAIN") { // Old behavior - if (response.size() > 0 && response[0] == (char) 0) { - string temp = response.substr(1); + if (response && response->size() > 0 && (*response).c_str()[0] == (char) 0) { + string temp = response->substr(1); string::size_type i = temp.find((char)0); string uid = temp.substr(0, i); string pwd = temp.substr(i + 1); @@ -138,7 +139,7 @@ void SspiAuthenticator::getMechanisms(Array& mechanisms) QPID_LOG(info, "SASL: Mechanism list: ANONYMOUS PLAIN"); } -void SspiAuthenticator::start(const string& mechanism, const string& response) +void SspiAuthenticator::start(const string& mechanism, const string* response) { QPID_LOG(info, "SASL: Starting authentication with mechanism: " << mechanism); if (mechanism == "ANONYMOUS") { @@ -151,16 +152,19 @@ void SspiAuthenticator::start(const string& mechanism, const string& response) // PLAIN's response is composed of 3 strings separated by 0 bytes: // authorization id, authentication id (user), clear-text password. - if (response.size() == 0) + if (!response || response->size() == 0) throw ConnectionForcedException("Authentication failed"); - string::size_type i = response.find((char)0); - string auth = response.substr(0, i); - string::size_type j = response.find((char)0, i+1); - string uid = response.substr(i+1, j-1); - string pwd = response.substr(j+1); + string::size_type i = response->find((char)0); + string auth = response->substr(0, i); + string::size_type j = response->find((char)0, i+1); + string uid = response->substr(i+1, j-1); + string pwd = response->substr(j+1); + string dot("."); int error = 0; - if (!LogonUser(uid.c_str(), ".", pwd.c_str(), + if (!LogonUser(const_cast<char*>(uid.c_str()), + const_cast<char*>(dot.c_str()), + const_cast<char*>(pwd.c_str()), LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, &userToken)) @@ -176,7 +180,7 @@ void SspiAuthenticator::start(const string& mechanism, const string& response) client.tune(framing::CHANNEL_MAX, connection.getFrameMax(), 0, 0); } -void SspiAuthenticator::step(const string& response) +void SspiAuthenticator::step(const string& /*response*/) { QPID_LOG(info, "SASL: Need another step!!!"); } diff --git a/cpp/src/qpid/broker/windows/SslProtocolFactory.cpp b/cpp/src/qpid/broker/windows/SslProtocolFactory.cpp index fd0e537192..1dff1ddc8f 100644 --- a/cpp/src/qpid/broker/windows/SslProtocolFactory.cpp +++ b/cpp/src/qpid/broker/windows/SslProtocolFactory.cpp @@ -27,10 +27,14 @@ #include "qpid/sys/AsynchIOHandler.h" #include "qpid/sys/ConnectionCodec.h" #include "qpid/sys/Socket.h" +#include "qpid/sys/SocketAddress.h" #include "qpid/sys/SystemInfo.h" #include "qpid/sys/windows/SslAsynchIO.h" + #include <boost/bind.hpp> +#include <boost/ptr_container/ptr_vector.hpp> #include <memory> + // security.h needs to see this to distinguish from kernel use. #define SECURITY_WIN32 #include <security.h> @@ -68,9 +72,10 @@ struct SslServerOptions : qpid::Options }; class SslProtocolFactory : public qpid::sys::ProtocolFactory { - qpid::sys::Socket listener; const bool tcpNoDelay; - const uint16_t listeningPort; + boost::ptr_vector<Socket> listeners; + boost::ptr_vector<AsynchAcceptor> acceptors; + uint16_t listeningPort; std::string brokerHost; const bool clientAuthSelected; std::auto_ptr<qpid::sys::AsynchAcceptor> acceptor; @@ -78,15 +83,14 @@ class SslProtocolFactory : public qpid::sys::ProtocolFactory { CredHandle credHandle; public: - SslProtocolFactory(const SslServerOptions&, int backlog, bool nodelay); + SslProtocolFactory(const SslServerOptions&, const std::string& host, const std::string& port, int backlog, bool nodelay); ~SslProtocolFactory(); void accept(sys::Poller::shared_ptr, sys::ConnectionCodec::Factory*); - void connect(sys::Poller::shared_ptr, const std::string& host, int16_t port, + void connect(sys::Poller::shared_ptr, const std::string& host, const std::string& port, sys::ConnectionCodec::Factory*, ConnectFailedCallback failed); uint16_t getPort() const; - std::string getHost() const; bool supports(const std::string& capability); private: @@ -115,6 +119,7 @@ static struct SslPlugin : public Plugin { try { const broker::Broker::Options& opts = broker->getOptions(); ProtocolFactory::shared_ptr protocol(new SslProtocolFactory(options, + "", boost::lexical_cast<std::string>(options.port), opts.connectionBacklog, opts.tcpNoDelay)); QPID_LOG(notice, "Listening for SSL connections on TCP port " << protocol->getPort()); @@ -127,12 +132,13 @@ static struct SslPlugin : public Plugin { } sslPlugin; SslProtocolFactory::SslProtocolFactory(const SslServerOptions& options, - int backlog, + const std::string& host, const std::string& port, int backlog, bool nodelay) : tcpNoDelay(nodelay), - listeningPort(listener.listen(options.port, backlog)), clientAuthSelected(options.clientAuth) { + // Make sure that certificate store is good before listening to sockets + // to avoid having open and listening sockets when there is no cert store SecInvalidateHandle(&credHandle); // Get the certificate for this server. @@ -177,6 +183,23 @@ SslProtocolFactory::SslProtocolFactory(const SslServerOptions& options, throw QPID_WINDOWS_ERROR(status); ::CertFreeCertificateContext(certContext); ::CertCloseStore(certStoreHandle, 0); + + // Listen to socket(s) + SocketAddress sa(host, port); + + // We must have at least one resolved address + QPID_LOG(info, "SSL Listening to: " << sa.asString()) + Socket* s = new Socket; + listeningPort = s->listen(sa, backlog); + listeners.push_back(s); + + // Try any other resolved addresses + while (sa.nextAddress()) { + QPID_LOG(info, "SSL Listening to: " << sa.asString()) + Socket* s = new Socket; + s->listen(sa, backlog); + listeners.push_back(s); + } } SslProtocolFactory::~SslProtocolFactory() { @@ -237,21 +260,19 @@ uint16_t SslProtocolFactory::getPort() const { return listeningPort; // Immutable no need for lock. } -std::string SslProtocolFactory::getHost() const { - return listener.getSockname(); -} - void SslProtocolFactory::accept(sys::Poller::shared_ptr poller, sys::ConnectionCodec::Factory* fact) { - acceptor.reset( - AsynchAcceptor::create(listener, - boost::bind(&SslProtocolFactory::established, this, poller, _1, fact, false))); - acceptor->start(poller); + for (unsigned i = 0; i<listeners.size(); ++i) { + acceptors.push_back( + AsynchAcceptor::create(listeners[i], + boost::bind(&SslProtocolFactory::established, this, poller, _1, fact, false))); + acceptors[i].start(poller); + } } void SslProtocolFactory::connect(sys::Poller::shared_ptr poller, const std::string& host, - int16_t port, + const std::string& port, sys::ConnectionCodec::Factory* fact, ConnectFailedCallback failed) { diff --git a/cpp/src/qpid/client/ConnectionHandler.cpp b/cpp/src/qpid/client/ConnectionHandler.cpp index 8dc1e8338a..ab0d8e0700 100644 --- a/cpp/src/qpid/client/ConnectionHandler.cpp +++ b/cpp/src/qpid/client/ConnectionHandler.cpp @@ -22,6 +22,7 @@ #include "qpid/client/ConnectionHandler.h" #include "qpid/SaslFactory.h" +#include "qpid/StringUtils.h" #include "qpid/client/Bounds.h" #include "qpid/framing/amqp_framing.h" #include "qpid/framing/all_method_bodies.h" @@ -142,7 +143,9 @@ void ConnectionHandler::outgoing(AMQFrame& frame) void ConnectionHandler::waitForOpen() { waitFor(ESTABLISHED); - if (getState() == FAILED || getState() == CLOSED) { + if (getState() == FAILED) { + throw TransportFailure(errorText); + } else if (getState() == CLOSED) { throw ConnectionException(errorCode, errorText); } } @@ -202,6 +205,24 @@ void ConnectionHandler::fail(const std::string& message) namespace { std::string SPACE(" "); + +std::string join(const std::vector<std::string>& in) +{ + std::string result; + for (std::vector<std::string>::const_iterator i = in.begin(); i != in.end(); ++i) { + if (result.size()) result += SPACE; + result += *i; + } + return result; +} + +void intersection(const std::vector<std::string>& a, const std::vector<std::string>& b, std::vector<std::string>& results) +{ + for (std::vector<std::string>::const_iterator i = a.begin(); i != a.end(); ++i) { + if (std::find(b.begin(), b.end(), *i) != b.end()) results.push_back(*i); + } +} + } void ConnectionHandler::start(const FieldTable& /*serverProps*/, const Array& mechanisms, const Array& /*locales*/) @@ -216,26 +237,35 @@ void ConnectionHandler::start(const FieldTable& /*serverProps*/, const Array& me maxSsf ); - std::string mechlist; - bool chosenMechanismSupported = mechanism.empty(); - for (Array::const_iterator i = mechanisms.begin(); i != mechanisms.end(); ++i) { - if (!mechanism.empty() && mechanism == (*i)->get<std::string>()) { - chosenMechanismSupported = true; - mechlist = (*i)->get<std::string>() + SPACE + mechlist; - } else { - if (i != mechanisms.begin()) mechlist += SPACE; - mechlist += (*i)->get<std::string>(); + std::vector<std::string> mechlist; + if (mechanism.empty()) { + //mechlist is simply what the server offers + mechanisms.collect(mechlist); + } else { + //mechlist is the intersection of those indicated by user and + //those supported by server, in the order listed by user + std::vector<std::string> allowed = split(mechanism, " "); + std::vector<std::string> supported; + mechanisms.collect(supported); + intersection(allowed, supported, mechlist); + if (mechlist.empty()) { + throw Exception(QPID_MSG("Desired mechanism(s) not valid: " << mechanism << " (supported: " << join(supported) << ")")); } } - if (!chosenMechanismSupported) { - fail("Selected mechanism not supported: " + mechanism); - } - if (sasl.get()) { - string response = sasl->start(mechanism.empty() ? mechlist : mechanism, - getSecuritySettings ? getSecuritySettings() : 0); - proxy.startOk(properties, sasl->getMechanism(), response, locale); + string response; + if (sasl->start(join(mechlist), response, getSecuritySettings ? getSecuritySettings() : 0)) { + proxy.startOk(properties, sasl->getMechanism(), response, locale); + } else { + //response was null + ConnectionStartOkBody body; + body.setClientProperties(properties); + body.setMechanism(sasl->getMechanism()); + //Don't set response, as none was given + body.setLocale(locale); + proxy.send(body); + } } else { //TODO: verify that desired mechanism and locale are supported string response = ((char)0) + username + ((char)0) + password; diff --git a/cpp/src/qpid/client/ConnectionImpl.cpp b/cpp/src/qpid/client/ConnectionImpl.cpp index 40c004f166..db97f1e0f4 100644 --- a/cpp/src/qpid/client/ConnectionImpl.cpp +++ b/cpp/src/qpid/client/ConnectionImpl.cpp @@ -36,6 +36,7 @@ #include <boost/bind.hpp> #include <boost/format.hpp> +#include <boost/lexical_cast.hpp> #include <boost/shared_ptr.hpp> #include <limits> @@ -258,16 +259,16 @@ void ConnectionImpl::open() connector->setInputHandler(&handler); connector->setShutdownHandler(this); try { - connector->connect(host, port); - + std::string p = boost::lexical_cast<std::string>(port); + connector->connect(host, p); + } catch (const std::exception& e) { QPID_LOG(debug, "Failed to connect to " << protocol << ":" << host << ":" << port << " " << e.what()); connector.reset(); - throw; + throw TransportFailure(e.what()); } connector->init(); - QPID_LOG(info, *this << " connected to " << protocol << ":" << host << ":" << port); - + // Enable heartbeat if requested uint16_t heartbeat = static_cast<ConnectionSettings&>(handler).heartbeat; if (heartbeat) { @@ -281,6 +282,7 @@ void ConnectionImpl::open() // - in that case in connector.reset() above; // - or when we are deleted handler.waitForOpen(); + QPID_LOG(info, *this << " connected to " << protocol << ":" << host << ":" << port); // If the SASL layer has provided an "operational" userId for the connection, // put it in the negotiated settings. diff --git a/cpp/src/qpid/client/Connector.h b/cpp/src/qpid/client/Connector.h index 586012f9d6..bc611ffe0d 100644 --- a/cpp/src/qpid/client/Connector.h +++ b/cpp/src/qpid/client/Connector.h @@ -61,7 +61,7 @@ class Connector : public framing::OutputHandler static void registerFactory(const std::string& proto, Factory* connectorFactory); virtual ~Connector() {}; - virtual void connect(const std::string& host, int port) = 0; + virtual void connect(const std::string& host, const std::string& port) = 0; virtual void init() {}; virtual void close() = 0; virtual void send(framing::AMQFrame& frame) = 0; diff --git a/cpp/src/qpid/client/RdmaConnector.cpp b/cpp/src/qpid/client/RdmaConnector.cpp index 6af607198c..664640f5e7 100644 --- a/cpp/src/qpid/client/RdmaConnector.cpp +++ b/cpp/src/qpid/client/RdmaConnector.cpp @@ -95,7 +95,7 @@ class RdmaConnector : public Connector, public sys::Codec std::string identifier; - void connect(const std::string& host, int port); + void connect(const std::string& host, const std::string& port); void close(); void send(framing::AMQFrame& frame); void abort() {} // TODO: need to fix this for heartbeat timeouts to work @@ -173,7 +173,7 @@ RdmaConnector::~RdmaConnector() { } } -void RdmaConnector::connect(const std::string& host, int port){ +void RdmaConnector::connect(const std::string& host, const std::string& port){ Mutex::ScopedLock l(dataConnectedLock); assert(!dataConnected); @@ -184,7 +184,7 @@ void RdmaConnector::connect(const std::string& host, int port){ boost::bind(&RdmaConnector::disconnected, this), boost::bind(&RdmaConnector::rejected, this, poller, _1, _2)); - SocketAddress sa(host, boost::lexical_cast<std::string>(port)); + SocketAddress sa(host, port); acon->start(poller, sa); } diff --git a/cpp/src/qpid/client/SessionImpl.cpp b/cpp/src/qpid/client/SessionImpl.cpp index b507625b11..7cf4ef648e 100644 --- a/cpp/src/qpid/client/SessionImpl.cpp +++ b/cpp/src/qpid/client/SessionImpl.cpp @@ -170,6 +170,7 @@ Demux& SessionImpl::getDemux() void SessionImpl::waitForCompletion(const SequenceNumber& id) { Lock l(state); + sys::Waitable::ScopedWait w(state); waitForCompletionImpl(id); } diff --git a/cpp/src/qpid/client/SslConnector.cpp b/cpp/src/qpid/client/SslConnector.cpp index 35c7e6bdf6..26c2335eda 100644 --- a/cpp/src/qpid/client/SslConnector.cpp +++ b/cpp/src/qpid/client/SslConnector.cpp @@ -114,7 +114,7 @@ class SslConnector : public Connector std::string identifier; - void connect(const std::string& host, int port); + void connect(const std::string& host, const std::string& port); void init(); void close(); void send(framing::AMQFrame& frame); @@ -190,14 +190,14 @@ SslConnector::~SslConnector() { close(); } -void SslConnector::connect(const std::string& host, int port){ +void SslConnector::connect(const std::string& host, const std::string& port){ Mutex::ScopedLock l(closedLock); assert(closed); try { socket.connect(host, port); } catch (const std::exception& e) { socket.close(); - throw ConnectionException(framing::connection::CLOSE_CODE_FRAMING_ERROR, e.what()); + throw TransportFailure(e.what()); } identifier = str(format("[%1% %2%]") % socket.getLocalPort() % socket.getPeerAddress()); diff --git a/cpp/src/qpid/client/TCPConnector.cpp b/cpp/src/qpid/client/TCPConnector.cpp index e284d57bec..0070b24ec0 100644 --- a/cpp/src/qpid/client/TCPConnector.cpp +++ b/cpp/src/qpid/client/TCPConnector.cpp @@ -88,7 +88,7 @@ TCPConnector::~TCPConnector() { close(); } -void TCPConnector::connect(const std::string& host, int port) { +void TCPConnector::connect(const std::string& host, const std::string& port) { Mutex::ScopedLock l(lock); assert(closed); connector = AsynchConnector::create( @@ -117,11 +117,11 @@ void TCPConnector::connected(const Socket&) { void TCPConnector::start(sys::AsynchIO* aio_) { aio = aio_; - for (int i = 0; i < 32; i++) { + for (int i = 0; i < 4; i++) { aio->queueReadBuffer(new Buff(maxFrameSize)); } - identifier = str(format("[%1% %2%]") % socket.getLocalPort() % socket.getPeerAddress()); + identifier = str(format("[%1%]") % socket.getFullAddress()); } void TCPConnector::initAmqp() { diff --git a/cpp/src/qpid/client/TCPConnector.h b/cpp/src/qpid/client/TCPConnector.h index c756469182..eb3f696013 100644 --- a/cpp/src/qpid/client/TCPConnector.h +++ b/cpp/src/qpid/client/TCPConnector.h @@ -98,7 +98,7 @@ class TCPConnector : public Connector, public sys::Codec protected: virtual ~TCPConnector(); - void connect(const std::string& host, int port); + void connect(const std::string& host, const std::string& port); void start(sys::AsynchIO* aio_); void initAmqp(); virtual void connectFailed(const std::string& msg); diff --git a/cpp/src/qpid/client/amqp0_10/AcceptTracker.cpp b/cpp/src/qpid/client/amqp0_10/AcceptTracker.cpp index bfb20118b5..d2accddcd0 100644 --- a/cpp/src/qpid/client/amqp0_10/AcceptTracker.cpp +++ b/cpp/src/qpid/client/amqp0_10/AcceptTracker.cpp @@ -30,12 +30,23 @@ void AcceptTracker::State::accept() unaccepted.clear(); } -void AcceptTracker::State::accept(qpid::framing::SequenceNumber id) +SequenceSet AcceptTracker::State::accept(qpid::framing::SequenceNumber id, bool cumulative) { - if (unaccepted.contains(id)) { - unaccepted.remove(id); - unconfirmed.add(id); + SequenceSet accepting; + if (cumulative) { + for (SequenceSet::iterator i = unaccepted.begin(); i != unaccepted.end() && *i <= id; ++i) { + accepting.add(*i); + } + unconfirmed.add(accepting); + unaccepted.remove(accepting); + } else { + if (unaccepted.contains(id)) { + unaccepted.remove(id); + unconfirmed.add(id); + accepting.add(id); + } } + return accepting; } void AcceptTracker::State::release() @@ -59,6 +70,18 @@ void AcceptTracker::delivered(const std::string& destination, const qpid::framin destinationState[destination].unaccepted.add(id); } +namespace +{ +const size_t FLUSH_FREQUENCY = 1024; +} + +void AcceptTracker::addToPending(qpid::client::AsyncSession& session, const Record& record) +{ + pending.push_back(record); + if (pending.size() % FLUSH_FREQUENCY == 0) session.flush(); +} + + void AcceptTracker::accept(qpid::client::AsyncSession& session) { for (StateMap::iterator i = destinationState.begin(); i != destinationState.end(); ++i) { @@ -67,20 +90,19 @@ void AcceptTracker::accept(qpid::client::AsyncSession& session) Record record; record.status = session.messageAccept(aggregateState.unaccepted); record.accepted = aggregateState.unaccepted; - pending.push_back(record); + addToPending(session, record); aggregateState.accept(); } -void AcceptTracker::accept(qpid::framing::SequenceNumber id, qpid::client::AsyncSession& session) +void AcceptTracker::accept(qpid::framing::SequenceNumber id, qpid::client::AsyncSession& session, bool cumulative) { for (StateMap::iterator i = destinationState.begin(); i != destinationState.end(); ++i) { - i->second.accept(id); + i->second.accept(id, cumulative); } Record record; - record.accepted.add(id); + record.accepted = aggregateState.accept(id, cumulative); record.status = session.messageAccept(record.accepted); - pending.push_back(record); - aggregateState.accept(id); + addToPending(session, record); } void AcceptTracker::release(qpid::client::AsyncSession& session) diff --git a/cpp/src/qpid/client/amqp0_10/AcceptTracker.h b/cpp/src/qpid/client/amqp0_10/AcceptTracker.h index 87890e41cc..85209c3b87 100644 --- a/cpp/src/qpid/client/amqp0_10/AcceptTracker.h +++ b/cpp/src/qpid/client/amqp0_10/AcceptTracker.h @@ -42,7 +42,7 @@ class AcceptTracker public: void delivered(const std::string& destination, const qpid::framing::SequenceNumber& id); void accept(qpid::client::AsyncSession&); - void accept(qpid::framing::SequenceNumber, qpid::client::AsyncSession&); + void accept(qpid::framing::SequenceNumber, qpid::client::AsyncSession&, bool cumulative); void release(qpid::client::AsyncSession&); uint32_t acceptsPending(); uint32_t acceptsPending(const std::string& destination); @@ -62,7 +62,7 @@ class AcceptTracker qpid::framing::SequenceSet unconfirmed; void accept(); - void accept(qpid::framing::SequenceNumber); + qpid::framing::SequenceSet accept(qpid::framing::SequenceNumber, bool cumulative); void release(); uint32_t acceptsPending(); void completed(qpid::framing::SequenceSet&); @@ -79,6 +79,7 @@ class AcceptTracker StateMap destinationState; Records pending; + void addToPending(qpid::client::AsyncSession&, const Record&); void checkPending(); void completed(qpid::framing::SequenceSet&); }; diff --git a/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp b/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp index f1295a3b66..16e5fde075 100644 --- a/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp +++ b/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp @@ -129,6 +129,10 @@ const std::string HEADERS_EXCHANGE("headers"); const std::string XML_EXCHANGE("xml"); const std::string WILDCARD_ANY("#"); +//exchange prefixes: +const std::string PREFIX_AMQ("amq."); +const std::string PREFIX_QPID("qpid."); + const Verifier verifier; } @@ -199,6 +203,7 @@ class Exchange : protected Node void checkCreate(qpid::client::AsyncSession&, CheckMode); void checkAssert(qpid::client::AsyncSession&, CheckMode); void checkDelete(qpid::client::AsyncSession&, CheckMode); + bool isReservedName(); protected: const std::string specifiedType; @@ -233,6 +238,8 @@ class Subscription : public Exchange, public MessageSource const bool reliable; const bool durable; const std::string actualType; + const bool exclusiveQueue; + const bool exclusiveSubscription; FieldTable queueOptions; FieldTable subscriptionOptions; Bindings bindings; @@ -307,6 +314,7 @@ struct Opt Opt& operator/(const std::string& name); operator bool() const; std::string str() const; + bool asBool(bool defaultValue) const; const Variant::List& asList() const; void collect(qpid::framing::FieldTable& args) const; @@ -338,6 +346,12 @@ Opt::operator bool() const return value && !value->isVoid() && value->asBool(); } +bool Opt::asBool(bool defaultValue) const +{ + if (value) return value->asBool(); + else return defaultValue; +} + std::string Opt::str() const { if (value) return value->asString(); @@ -481,7 +495,7 @@ std::string Subscription::getSubscriptionName(const std::string& base, const std if (name.empty()) { return (boost::format("%1%_%2%") % base % Uuid(true).str()).str(); } else { - return (boost::format("%1%_%2%") % base % name).str(); + return name; } } @@ -490,7 +504,9 @@ Subscription::Subscription(const Address& address, const std::string& type) queue(getSubscriptionName(name, (Opt(address)/LINK/NAME).str())), reliable(AddressResolution::is_reliable(address)), durable(Opt(address)/LINK/DURABLE), - actualType(type.empty() ? (specifiedType.empty() ? TOPIC_EXCHANGE : specifiedType) : type) + actualType(type.empty() ? (specifiedType.empty() ? TOPIC_EXCHANGE : specifiedType) : type), + exclusiveQueue((Opt(address)/LINK/X_DECLARE/EXCLUSIVE).asBool(true)), + exclusiveSubscription((Opt(address)/LINK/X_SUBSCRIBE/EXCLUSIVE).asBool(exclusiveQueue)) { (Opt(address)/LINK/X_DECLARE/ARGUMENTS).collect(queueOptions); (Opt(address)/LINK/X_SUBSCRIBE/ARGUMENTS).collect(subscriptionOptions); @@ -550,7 +566,7 @@ void Subscription::subscribe(qpid::client::AsyncSession& session, const std::str checkAssert(session, FOR_RECEIVER); //create subscription queue: - session.queueDeclare(arg::queue=queue, arg::exclusive=true, + session.queueDeclare(arg::queue=queue, arg::exclusive=exclusiveQueue, arg::autoDelete=!reliable, arg::durable=durable, arg::arguments=queueOptions); //'default' binding: bindings.bind(session); @@ -559,15 +575,15 @@ void Subscription::subscribe(qpid::client::AsyncSession& session, const std::str linkBindings.bind(session); //subscribe to subscription queue: AcceptMode accept = reliable ? ACCEPT_MODE_EXPLICIT : ACCEPT_MODE_NONE; - session.messageSubscribe(arg::queue=queue, arg::destination=destination, - arg::exclusive=true, arg::acceptMode=accept, arg::arguments=subscriptionOptions); + session.messageSubscribe(arg::queue=queue, arg::destination=destination, + arg::exclusive=exclusiveSubscription, arg::acceptMode=accept, arg::arguments=subscriptionOptions); } void Subscription::cancel(qpid::client::AsyncSession& session, const std::string& destination) { linkBindings.unbind(session); session.messageCancel(destination); - session.queueDelete(arg::queue=queue); + if (exclusiveQueue) session.queueDelete(arg::queue=queue, arg::ifUnused=true); checkDelete(session, FOR_RECEIVER); } @@ -761,18 +777,32 @@ Exchange::Exchange(const Address& a) : Node(a), linkBindings.setDefaultExchange(name); } +bool Exchange::isReservedName() +{ + return name.find(PREFIX_AMQ) != std::string::npos || name.find(PREFIX_QPID) != std::string::npos; +} + void Exchange::checkCreate(qpid::client::AsyncSession& session, CheckMode mode) { if (enabled(createPolicy, mode)) { try { - std::string type = specifiedType; - if (type.empty()) type = TOPIC_EXCHANGE; - session.exchangeDeclare(arg::exchange=name, - arg::type=type, - arg::durable=durable, - arg::autoDelete=autoDelete, - arg::alternateExchange=alternateExchange, - arg::arguments=arguments); + if (isReservedName()) { + try { + sync(session).exchangeDeclare(arg::exchange=name, arg::passive=true); + } catch (const qpid::framing::NotFoundException& /*e*/) { + throw ResolutionError((boost::format("Cannot create exchange %1%; names beginning with \"amq.\" or \"qpid.\" are reserved.") % name).str()); + } + + } else { + std::string type = specifiedType; + if (type.empty()) type = TOPIC_EXCHANGE; + session.exchangeDeclare(arg::exchange=name, + arg::type=type, + arg::durable=durable, + arg::autoDelete=autoDelete, + arg::alternateExchange=alternateExchange, + arg::arguments=arguments); + } nodeBindings.bind(session); session.sync(); } catch (const qpid::framing::NotAllowedException& e) { @@ -822,7 +852,7 @@ void Exchange::checkAssert(qpid::client::AsyncSession& session, CheckMode mode) FieldTable::ValuePtr v = result.getArguments().get(i->first); if (!v) { throw AssertionFailed((boost::format("Option %1% not set for %2%") % i->first % name).str()); - } else if (i->second != v) { + } else if (*i->second != *v) { throw AssertionFailed((boost::format("Option %1% does not match for %2%, expected %3%, got %4%") % i->first % name % *(i->second) % *v).str()); } diff --git a/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp b/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp index 5a545c1f6a..cc6e9b9ab2 100644 --- a/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp +++ b/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp @@ -20,7 +20,6 @@ */ #include "ConnectionImpl.h" #include "SessionImpl.h" -#include "SimpleUrlParser.h" #include "qpid/messaging/exceptions.h" #include "qpid/messaging/Session.h" #include "qpid/messaging/PrivateImplRef.h" @@ -39,26 +38,18 @@ using qpid::types::Variant; using qpid::types::VAR_LIST; using qpid::framing::Uuid; -void convert(const Variant::List& from, std::vector<std::string>& to) -{ - for (Variant::List::const_iterator i = from.begin(); i != from.end(); ++i) { - to.push_back(i->asString()); - } +namespace { +void merge(const std::string& value, std::vector<std::string>& list) { + if (std::find(list.begin(), list.end(), value) == list.end()) + list.push_back(value); } -template <class T> bool setIfFound(const Variant::Map& map, const std::string& key, T& value) +void merge(const Variant::List& from, std::vector<std::string>& to) { - Variant::Map::const_iterator i = map.find(key); - if (i != map.end()) { - value = (T) i->second; - QPID_LOG(debug, "option " << key << " specified as " << i->second); - return true; - } else { - return false; - } + for (Variant::List::const_iterator i = from.begin(); i != from.end(); ++i) + merge(i->asString(), to); } -namespace { std::string asString(const std::vector<std::string>& v) { std::stringstream os; os << "["; @@ -71,49 +62,8 @@ std::string asString(const std::vector<std::string>& v) { } } -template <> bool setIfFound< std::vector<std::string> >(const Variant::Map& map, - const std::string& key, - std::vector<std::string>& value) -{ - Variant::Map::const_iterator i = map.find(key); - if (i != map.end()) { - value.clear(); - if (i->second.getType() == VAR_LIST) { - convert(i->second.asList(), value); - } else { - value.push_back(i->second.asString()); - } - QPID_LOG(debug, "option " << key << " specified as " << asString(value)); - return true; - } else { - return false; - } -} - -void convert(const Variant::Map& from, ConnectionSettings& to) -{ - setIfFound(from, "username", to.username); - setIfFound(from, "password", to.password); - setIfFound(from, "sasl-mechanism", to.mechanism); - setIfFound(from, "sasl-service", to.service); - setIfFound(from, "sasl-min-ssf", to.minSsf); - setIfFound(from, "sasl-max-ssf", to.maxSsf); - - setIfFound(from, "heartbeat", to.heartbeat); - setIfFound(from, "tcp-nodelay", to.tcpNoDelay); - - setIfFound(from, "locale", to.locale); - setIfFound(from, "max-channels", to.maxChannels); - setIfFound(from, "max-frame-size", to.maxFrameSize); - setIfFound(from, "bounds", to.bounds); - - setIfFound(from, "transport", to.protocol); - - setIfFound(from, "ssl-cert-name", to.sslCertName); -} - ConnectionImpl::ConnectionImpl(const std::string& url, const Variant::Map& options) : - reconnect(false), timeout(-1), limit(-1), + replaceUrls(false), reconnect(false), timeout(-1), limit(-1), minReconnectInterval(3), maxReconnectInterval(60), retries(0), reconnectOnLimitExceeded(true) { @@ -124,27 +74,69 @@ ConnectionImpl::ConnectionImpl(const std::string& url, const Variant::Map& optio void ConnectionImpl::setOptions(const Variant::Map& options) { - sys::Mutex::ScopedLock l(lock); - convert(options, settings); - setIfFound(options, "reconnect", reconnect); - setIfFound(options, "reconnect-timeout", timeout); - setIfFound(options, "reconnect-limit", limit); - int64_t reconnectInterval; - if (setIfFound(options, "reconnect-interval", reconnectInterval)) { - minReconnectInterval = maxReconnectInterval = reconnectInterval; - } else { - setIfFound(options, "reconnect-interval-min", minReconnectInterval); - setIfFound(options, "reconnect-interval-max", maxReconnectInterval); + for (Variant::Map::const_iterator i = options.begin(); i != options.end(); ++i) { + setOption(i->first, i->second); } - setIfFound(options, "reconnect-urls", urls); - setIfFound(options, "x-reconnect-on-limit-exceeded", reconnectOnLimitExceeded); } void ConnectionImpl::setOption(const std::string& name, const Variant& value) { - Variant::Map options; - options[name] = value; - setOptions(options); + sys::Mutex::ScopedLock l(lock); + if (name == "reconnect") { + reconnect = value; + } else if (name == "reconnect-timeout" || name == "reconnect_timeout") { + timeout = value; + } else if (name == "reconnect-limit" || name == "reconnect_limit") { + limit = value; + } else if (name == "reconnect-interval" || name == "reconnect_interval") { + maxReconnectInterval = minReconnectInterval = value; + } else if (name == "reconnect-interval-min" || name == "reconnect_interval_min") { + minReconnectInterval = value; + } else if (name == "reconnect-interval-max" || name == "reconnect_interval_max") { + maxReconnectInterval = value; + } else if (name == "reconnect-urls-replace" || name == "reconnect_urls_replace") { + replaceUrls = value.asBool(); + } else if (name == "reconnect-urls" || name == "reconnect_urls") { + if (replaceUrls) urls.clear(); + if (value.getType() == VAR_LIST) { + merge(value.asList(), urls); + } else { + merge(value.asString(), urls); + } + } else if (name == "username") { + settings.username = value.asString(); + } else if (name == "password") { + settings.password = value.asString(); + } else if (name == "sasl-mechanism" || name == "sasl_mechanism" || + name == "sasl-mechanisms" || name == "sasl_mechanisms") { + settings.mechanism = value.asString(); + } else if (name == "sasl-service" || name == "sasl_service") { + settings.service = value.asString(); + } else if (name == "sasl-min-ssf" || name == "sasl_min_ssf") { + settings.minSsf = value; + } else if (name == "sasl-max-ssf" || name == "sasl_max_ssf") { + settings.maxSsf = value; + } else if (name == "heartbeat") { + settings.heartbeat = value; + } else if (name == "tcp-nodelay" || name == "tcp_nodelay") { + settings.tcpNoDelay = value; + } else if (name == "locale") { + settings.locale = value.asString(); + } else if (name == "max-channels" || name == "max_channels") { + settings.maxChannels = value; + } else if (name == "max-frame-size" || name == "max_frame_size") { + settings.maxFrameSize = value; + } else if (name == "bounds") { + settings.bounds = value; + } else if (name == "transport") { + settings.protocol = value.asString(); + } else if (name == "ssl-cert-name" || name == "ssl_cert_name") { + settings.sslCertName = value.asString(); + } else if (name == "x-reconnect-on-limit-exceeded" || name == "x_reconnect_on_limit_exceeded") { + reconnectOnLimitExceeded = value; + } else { + throw qpid::messaging::MessagingException(QPID_MSG("Invalid option: " << name << " not recognised")); + } } @@ -214,7 +206,7 @@ qpid::messaging::Session ConnectionImpl::newSession(bool transactional, const st sessions[name] = impl; break; } catch (const qpid::TransportFailure&) { - open(); + reopen(); } catch (const qpid::SessionException& e) { throw qpid::messaging::SessionError(e.what()); } catch (const std::exception& e) { @@ -235,6 +227,15 @@ void ConnectionImpl::open() catch (const qpid::Exception& e) { throw messaging::ConnectionError(e.what()); } } +void ConnectionImpl::reopen() +{ + if (!reconnect) { + throw qpid::messaging::TransportFailure("Failed to connect (reconnect disabled)"); + } + open(); +} + + bool expired(const qpid::sys::AbsTime& start, int64_t timeout) { if (timeout == 0) return true; @@ -262,14 +263,9 @@ void ConnectionImpl::connect(const qpid::sys::AbsTime& started) } void ConnectionImpl::mergeUrls(const std::vector<Url>& more, const sys::Mutex::ScopedLock&) { - if (more.size()) { - for (size_t i = 0; i < more.size(); ++i) { - if (std::find(urls.begin(), urls.end(), more[i].str()) == urls.end()) { - urls.push_back(more[i].str()); - } - } - QPID_LOG(debug, "Added known-hosts, reconnect-urls=" << asString(urls)); - } + for (std::vector<Url>::const_iterator i = more.begin(); i != more.end(); ++i) + merge(i->str(), urls); + QPID_LOG(debug, "Added known-hosts, reconnect-urls=" << asString(urls)); } bool ConnectionImpl::tryConnect() @@ -278,21 +274,14 @@ bool ConnectionImpl::tryConnect() for (std::vector<std::string>::const_iterator i = urls.begin(); i != urls.end(); ++i) { try { QPID_LOG(info, "Trying to connect to " << *i << "..."); - //TODO: when url support is more complete can avoid this test here - if (i->find("amqp:") == 0) { - Url url(*i); - connection.open(url, settings); - } else { - SimpleUrlParser::parse(*i, settings); - connection.open(settings); - } + Url url(*i); + if (url.getUser().size()) settings.username = url.getUser(); + if (url.getPass().size()) settings.password = url.getPass(); + connection.open(url, settings); QPID_LOG(info, "Connected to " << *i); mergeUrls(connection.getInitialBrokers(), l); return resetSessions(l); - } catch (const qpid::ConnectionException& e) { - //TODO: need to fix timeout on - //qpid::client::Connection::open() so that it throws - //TransportFailure rather than a ConnectionException + } catch (const qpid::TransportFailure& e) { QPID_LOG(info, "Failed to connect to " << *i << ": " << e.what()); } } diff --git a/cpp/src/qpid/client/amqp0_10/ConnectionImpl.h b/cpp/src/qpid/client/amqp0_10/ConnectionImpl.h index 09f2038312..1b58cbbe3e 100644 --- a/cpp/src/qpid/client/amqp0_10/ConnectionImpl.h +++ b/cpp/src/qpid/client/amqp0_10/ConnectionImpl.h @@ -43,6 +43,7 @@ class ConnectionImpl : public qpid::messaging::ConnectionImpl public: ConnectionImpl(const std::string& url, const qpid::types::Variant::Map& options); void open(); + void reopen(); bool isOpen() const; void close(); qpid::messaging::Session newSession(bool transactional, const std::string& name); @@ -59,6 +60,7 @@ class ConnectionImpl : public qpid::messaging::ConnectionImpl qpid::sys::Semaphore semaphore;//used to coordinate reconnection Sessions sessions; qpid::client::Connection connection; + bool replaceUrls; // Replace rather than merging with reconnect-urls std::vector<std::string> urls; qpid::client::ConnectionSettings settings; bool reconnect; diff --git a/cpp/src/qpid/client/amqp0_10/IncomingMessages.cpp b/cpp/src/qpid/client/amqp0_10/IncomingMessages.cpp index 71e89bdba1..3badaf40ba 100644 --- a/cpp/src/qpid/client/amqp0_10/IncomingMessages.cpp +++ b/cpp/src/qpid/client/amqp0_10/IncomingMessages.cpp @@ -144,10 +144,10 @@ void IncomingMessages::accept() acceptTracker.accept(session); } -void IncomingMessages::accept(qpid::framing::SequenceNumber id) +void IncomingMessages::accept(qpid::framing::SequenceNumber id, bool cumulative) { sys::Mutex::ScopedLock l(lock); - acceptTracker.accept(id, session); + acceptTracker.accept(id, session, cumulative); } @@ -301,6 +301,7 @@ const std::string SUBJECT("qpid.subject"); const std::string X_APP_ID("x-amqp-0-10.app-id"); const std::string X_ROUTING_KEY("x-amqp-0-10.routing-key"); const std::string X_CONTENT_ENCODING("x-amqp-0-10.content-encoding"); +const std::string X_TIMESTAMP("x-amqp-0-10.timestamp"); } void populateHeaders(qpid::messaging::Message& message, @@ -334,10 +335,13 @@ void populateHeaders(qpid::messaging::Message& message, if (messageProperties->hasContentEncoding()) { message.getProperties()[X_CONTENT_ENCODING] = messageProperties->getContentEncoding(); } - // routing-key, others? + // routing-key, timestamp, others? if (deliveryProperties && deliveryProperties->hasRoutingKey()) { message.getProperties()[X_ROUTING_KEY] = deliveryProperties->getRoutingKey(); } + if (deliveryProperties && deliveryProperties->hasTimestamp()) { + message.getProperties()[X_TIMESTAMP] = deliveryProperties->getTimestamp(); + } } } diff --git a/cpp/src/qpid/client/amqp0_10/IncomingMessages.h b/cpp/src/qpid/client/amqp0_10/IncomingMessages.h index f6a291bc68..9053b70312 100644 --- a/cpp/src/qpid/client/amqp0_10/IncomingMessages.h +++ b/cpp/src/qpid/client/amqp0_10/IncomingMessages.h @@ -72,7 +72,7 @@ class IncomingMessages bool get(Handler& handler, qpid::sys::Duration timeout); bool getNextDestination(std::string& destination, qpid::sys::Duration timeout); void accept(); - void accept(qpid::framing::SequenceNumber id); + void accept(qpid::framing::SequenceNumber id, bool cumulative); void releaseAll(); void releasePending(const std::string& destination); diff --git a/cpp/src/qpid/client/amqp0_10/OutgoingMessage.cpp b/cpp/src/qpid/client/amqp0_10/OutgoingMessage.cpp index 82358961c8..d93416da75 100644 --- a/cpp/src/qpid/client/amqp0_10/OutgoingMessage.cpp +++ b/cpp/src/qpid/client/amqp0_10/OutgoingMessage.cpp @@ -59,7 +59,9 @@ void OutgoingMessage::convert(const qpid::messaging::Message& from) message.getMessageProperties().setReplyTo(AddressResolution::convert(address)); } translate(from.getProperties(), message.getMessageProperties().getApplicationHeaders()); - message.getDeliveryProperties().setTtl(from.getTtl().getMilliseconds()); + if (from.getTtl().getMilliseconds()) { + message.getDeliveryProperties().setTtl(from.getTtl().getMilliseconds()); + } if (from.getDurable()) { message.getDeliveryProperties().setDeliveryMode(DELIVERY_MODE_PERSISTENT); } diff --git a/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp b/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp index e1b75ec0cf..f2f0f1a9e5 100644 --- a/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp +++ b/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp @@ -135,6 +135,7 @@ void SenderImpl::sendUnreliable(const qpid::messaging::Message& m) void SenderImpl::replay(const sys::Mutex::ScopedLock&) { for (OutgoingMessages::iterator i = outgoing.begin(); i != outgoing.end(); ++i) { + i->message.setRedelivered(true); sink->send(session, name, *i); } } @@ -147,7 +148,7 @@ uint32_t SenderImpl::checkPendingSends(bool flush) { uint32_t SenderImpl::checkPendingSends(bool flush, const sys::Mutex::ScopedLock&) { if (flush) { - session.flush(); + session.flush(); flushed = true; } else { flushed = false; diff --git a/cpp/src/qpid/client/amqp0_10/SessionImpl.cpp b/cpp/src/qpid/client/amqp0_10/SessionImpl.cpp index 75a71997fd..be5eab1f2b 100644 --- a/cpp/src/qpid/client/amqp0_10/SessionImpl.cpp +++ b/cpp/src/qpid/client/amqp0_10/SessionImpl.cpp @@ -60,12 +60,14 @@ SessionImpl::SessionImpl(ConnectionImpl& c, bool t) : connection(&c), transactio void SessionImpl::checkError() { + ScopedLock l(lock); qpid::client::SessionBase_0_10Access s(session); s.get()->assertOpen(); } bool SessionImpl::hasError() { + ScopedLock l(lock); qpid::client::SessionBase_0_10Access s(session); return s.get()->hasError(); } @@ -112,13 +114,14 @@ void SessionImpl::release(qpid::messaging::Message& m) execute1<Release>(m); } -void SessionImpl::acknowledge(qpid::messaging::Message& m) +void SessionImpl::acknowledge(qpid::messaging::Message& m, bool cumulative) { //Should probably throw an exception on failure here, or indicate //it through a return type at least. Failure means that the //message may be redelivered; i.e. the application cannot delete //any state necessary for preventing reprocessing of the message - execute1<Acknowledge1>(m); + Acknowledge2 ack(*this, m, cumulative); + execute(ack); } void SessionImpl::close() @@ -128,27 +131,29 @@ void SessionImpl::close() senders.clear(); receivers.clear(); } else { - while (true) { - Sender s; - { - ScopedLock l(lock); - if (senders.empty()) break; - s = senders.begin()->second; - } - s.close(); // outside the lock, will call senderCancelled + Senders sCopy; + Receivers rCopy; + { + ScopedLock l(lock); + senders.swap(sCopy); + receivers.swap(rCopy); } - while (true) { - Receiver r; - { - ScopedLock l(lock); - if (receivers.empty()) break; - r = receivers.begin()->second; - } - r.close(); // outside the lock, will call receiverCancelled + for (Senders::iterator i = sCopy.begin(); i != sCopy.end(); ++i) + { + // outside the lock, will call senderCancelled + i->second.close(); + } + for (Receivers::iterator i = rCopy.begin(); i != rCopy.end(); ++i) + { + // outside the lock, will call receiverCancelled + i->second.close(); } } connection->closed(*this); - if (!hasError()) session.close(); + if (!hasError()) { + ScopedLock l(lock); + session.close(); + } } template <class T, class S> boost::intrusive_ptr<S> getImplPtr(T& t) @@ -431,8 +436,11 @@ uint32_t SessionImpl::getUnsettledAcksImpl(const std::string* destination) void SessionImpl::syncImpl(bool block) { - if (block) session.sync(); - else session.flush(); + { + ScopedLock l(lock); + if (block) session.sync(); + else session.flush(); + } //cleanup unconfirmed accept records: incoming.pendingAccept(); } @@ -467,10 +475,10 @@ void SessionImpl::acknowledgeImpl() if (!transactional) incoming.accept(); } -void SessionImpl::acknowledgeImpl(qpid::messaging::Message& m) +void SessionImpl::acknowledgeImpl(qpid::messaging::Message& m, bool cumulative) { ScopedLock l(lock); - if (!transactional) incoming.accept(MessageImplAccess::get(m).getInternalId()); + if (!transactional) incoming.accept(MessageImplAccess::get(m).getInternalId(), cumulative); } void SessionImpl::rejectImpl(qpid::messaging::Message& m) @@ -509,7 +517,7 @@ void SessionImpl::senderCancelled(const std::string& name) void SessionImpl::reconnect() { - connection->open(); + connection->reopen(); } bool SessionImpl::backoff() diff --git a/cpp/src/qpid/client/amqp0_10/SessionImpl.h b/cpp/src/qpid/client/amqp0_10/SessionImpl.h index 2a2aa47df6..c7dea77d18 100644 --- a/cpp/src/qpid/client/amqp0_10/SessionImpl.h +++ b/cpp/src/qpid/client/amqp0_10/SessionImpl.h @@ -63,7 +63,7 @@ class SessionImpl : public qpid::messaging::SessionImpl void acknowledge(bool sync); void reject(qpid::messaging::Message&); void release(qpid::messaging::Message&); - void acknowledge(qpid::messaging::Message& msg); + void acknowledge(qpid::messaging::Message& msg, bool cumulative); void close(); void sync(bool block); qpid::messaging::Sender createSender(const qpid::messaging::Address& address); @@ -139,7 +139,7 @@ class SessionImpl : public qpid::messaging::SessionImpl void commitImpl(); void rollbackImpl(); void acknowledgeImpl(); - void acknowledgeImpl(qpid::messaging::Message&); + void acknowledgeImpl(qpid::messaging::Message&, bool cumulative); void rejectImpl(qpid::messaging::Message&); void releaseImpl(qpid::messaging::Message&); void closeImpl(); @@ -204,12 +204,13 @@ class SessionImpl : public qpid::messaging::SessionImpl void operator()() { impl.releaseImpl(message); } }; - struct Acknowledge1 : Command + struct Acknowledge2 : Command { qpid::messaging::Message& message; + bool cumulative; - Acknowledge1(SessionImpl& i, qpid::messaging::Message& m) : Command(i), message(m) {} - void operator()() { impl.acknowledgeImpl(message); } + Acknowledge2(SessionImpl& i, qpid::messaging::Message& m, bool c) : Command(i), message(m), cumulative(c) {} + void operator()() { impl.acknowledgeImpl(message, cumulative); } }; struct CreateSender; diff --git a/cpp/src/qpid/client/amqp0_10/SimpleUrlParser.cpp b/cpp/src/qpid/client/amqp0_10/SimpleUrlParser.cpp deleted file mode 100644 index 327c2274a6..0000000000 --- a/cpp/src/qpid/client/amqp0_10/SimpleUrlParser.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -#include "SimpleUrlParser.h" -#include "qpid/client/ConnectionSettings.h" -#include "qpid/Exception.h" -#include <boost/lexical_cast.hpp> - -namespace qpid { -namespace client { -namespace amqp0_10 { - -bool split(const std::string& in, char delim, std::pair<std::string, std::string>& result) -{ - std::string::size_type i = in.find(delim); - if (i != std::string::npos) { - result.first = in.substr(0, i); - if (i+1 < in.size()) { - result.second = in.substr(i+1); - } - return true; - } else { - return false; - } -} - -void parseUsernameAndPassword(const std::string& in, qpid::client::ConnectionSettings& result) -{ - std::pair<std::string, std::string> parts; - if (!split(in, '/', parts)) { - result.username = in; - } else { - result.username = parts.first; - result.password = parts.second; - } -} - -void parseHostAndPort(const std::string& in, qpid::client::ConnectionSettings& result) -{ - std::pair<std::string, std::string> parts; - if (!split(in, ':', parts)) { - result.host = in; - } else { - result.host = parts.first; - if (parts.second.size()) { - result.port = boost::lexical_cast<uint16_t>(parts.second); - } - } -} - -void SimpleUrlParser::parse(const std::string& url, qpid::client::ConnectionSettings& result) -{ - std::pair<std::string, std::string> parts; - if (!split(url, '@', parts)) { - parseHostAndPort(url, result); - } else { - parseUsernameAndPassword(parts.first, result); - parseHostAndPort(parts.second, result); - } -} - -}}} // namespace qpid::client::amqp0_10 diff --git a/cpp/src/qpid/client/windows/SaslFactory.cpp b/cpp/src/qpid/client/windows/SaslFactory.cpp index 63c7fa3d1f..53d825771b 100644 --- a/cpp/src/qpid/client/windows/SaslFactory.cpp +++ b/cpp/src/qpid/client/windows/SaslFactory.cpp @@ -71,7 +71,7 @@ class WindowsSasl : public Sasl public: WindowsSasl( const std::string &, const std::string &, const std::string &, const std::string &, int, int ); ~WindowsSasl(); - std::string start(const std::string& mechanisms, const SecuritySettings* externalSettings); + bool start(const std::string& mechanisms, std::string& response, const SecuritySettings* externalSettings); std::string step(const std::string& challenge); std::string getMechanism(); std::string getUserId(); @@ -121,8 +121,8 @@ WindowsSasl::~WindowsSasl() { } -std::string WindowsSasl::start(const std::string& mechanisms, - const SecuritySettings* /*externalSettings*/) +bool WindowsSasl::start(const std::string& mechanisms, std::string& response, + const SecuritySettings* /*externalSettings*/) { QPID_LOG(debug, "WindowsSasl::start(" << mechanisms << ")"); @@ -142,18 +142,18 @@ std::string WindowsSasl::start(const std::string& mechanisms, if (!haveAnon && !havePlain) throw InternalErrorException(QPID_MSG("Sasl error: no common mechanism")); - std::string resp = ""; if (havePlain) { mechanism = PLAIN; - resp = ((char)0) + settings.username + ((char)0) + settings.password; + response = ((char)0) + settings.username + ((char)0) + settings.password; } else { mechanism = ANONYMOUS; + response = ""; } - return resp; + return true; } -std::string WindowsSasl::step(const std::string& challenge) +std::string WindowsSasl::step(const std::string& /*challenge*/) { // Shouldn't get this for PLAIN... throw InternalErrorException(QPID_MSG("Sasl step error")); @@ -169,7 +169,7 @@ std::string WindowsSasl::getUserId() return std::string(); // TODO - when GSSAPI is supported, return userId for connection. } -std::auto_ptr<SecurityLayer> WindowsSasl::getSecurityLayer(uint16_t maxFrameSize) +std::auto_ptr<SecurityLayer> WindowsSasl::getSecurityLayer(uint16_t /*maxFrameSize*/) { return std::auto_ptr<SecurityLayer>(0); } diff --git a/cpp/src/qpid/client/windows/SslConnector.cpp b/cpp/src/qpid/client/windows/SslConnector.cpp index a33713e1a8..785c817928 100644 --- a/cpp/src/qpid/client/windows/SslConnector.cpp +++ b/cpp/src/qpid/client/windows/SslConnector.cpp @@ -77,7 +77,7 @@ public: framing::ProtocolVersion pVersion, const ConnectionSettings&, ConnectionImpl*); - virtual void connect(const std::string& host, int port); + virtual void connect(const std::string& host, const std::string& port); virtual void connected(const Socket&); unsigned int getSSF(); }; @@ -153,7 +153,7 @@ SslConnector::~SslConnector() // Will this get reach via virtual method via boost::bind???? -void SslConnector::connect(const std::string& host, int port) { +void SslConnector::connect(const std::string& host, const std::string& port) { brokerHost = host; TCPConnector::connect(host, port); } diff --git a/cpp/src/qpid/cluster/Cluster.cpp b/cpp/src/qpid/cluster/Cluster.cpp index dd4882774b..e6e3de64f2 100644 --- a/cpp/src/qpid/cluster/Cluster.cpp +++ b/cpp/src/qpid/cluster/Cluster.cpp @@ -36,45 +36,45 @@ * * IMPORTANT NOTE: any time code is added to the broker that uses timers, * the cluster may need to be updated to take account of this. - * + * * * USE OF TIMESTAMPS IN THE BROKER - * + * * The following are the current areas where broker uses timers or timestamps: - * + * * - Producer flow control: broker::SemanticState uses * connection::getClusterOrderOutput. a FrameHandler that sends * frames to the client via the cluster. Used by broker::SessionState - * + * * - QueueCleaner, Message TTL: uses ExpiryPolicy, which is * implemented by cluster::ExpiryPolicy. - * + * * - Connection heartbeat: sends connection controls, not part of * session command counting so OK to ignore. - * + * * - LinkRegistry: only cluster elder is ever active for links. - * + * * - management::ManagementBroker: uses MessageHandler supplied by cluster * to send messages to the broker via the cluster. - * - * - Dtx: not yet supported with cluster. * - * cluster::ExpiryPolicy implements the strategy for message expiry. + * cluster::ExpiryPolicy uses cluster time. * * ClusterTimer implements periodic timed events in the cluster context. - * Used for periodic management events. + * Used for: + * - periodic management events. + * - DTX transaction timeouts. * * <h1>CLUSTER PROTOCOL OVERVIEW</h1> - * + * * Messages sent to/from CPG are called Events. * * An Event carries a ConnectionId, which includes a MemberId and a * connection number. - * + * * Events are either * - Connection events: non-0 connection number and are associated with a connection. * - Cluster Events: 0 connection number, are not associated with a connection. - * + * * Events are further categorized as: * - Control: carries method frame(s) that affect cluster behavior. * - Data: carries raw data received from a client connection. @@ -146,6 +146,7 @@ #include "qpid/framing/AMQP_AllOperations.h" #include "qpid/framing/AllInvoker.h" #include "qpid/framing/ClusterConfigChangeBody.h" +#include "qpid/framing/ClusterClockBody.h" #include "qpid/framing/ClusterConnectionDeliverCloseBody.h" #include "qpid/framing/ClusterConnectionAbortBody.h" #include "qpid/framing/ClusterRetractOfferBody.h" @@ -198,7 +199,7 @@ namespace _qmf = ::qmf::org::apache::qpid::cluster; * Currently use SVN revision to avoid clashes with versions from * different branches. */ -const uint32_t Cluster::CLUSTER_VERSION = 1058747; +const uint32_t Cluster::CLUSTER_VERSION = 1159329; struct ClusterDispatcher : public framing::AMQP_AllOperations::ClusterHandler { qpid::cluster::Cluster& cluster; @@ -214,7 +215,7 @@ struct ClusterDispatcher : public framing::AMQP_AllOperations::ClusterHandler { { cluster.initialStatus( member, version, active, clusterId, - framing::cluster::StoreState(storeState), shutdownId, + framing::cluster::StoreState(storeState), shutdownId, firstConfig, l); } void ready(const std::string& url) { @@ -230,21 +231,21 @@ struct ClusterDispatcher : public framing::AMQP_AllOperations::ClusterHandler { cluster.updateOffer(member, updatee, l); } void retractOffer(uint64_t updatee) { cluster.retractOffer(member, updatee, l); } - void messageExpired(uint64_t id) { cluster.messageExpired(member, id, l); } void errorCheck(uint8_t type, const framing::SequenceNumber& frameSeq) { cluster.errorCheck(member, type, frameSeq, l); } void timerWakeup(const std::string& name) { cluster.timerWakeup(member, name, l); } - void timerDrop(const std::string& name) { cluster.timerWakeup(member, name, l); } + void timerDrop(const std::string& name) { cluster.timerDrop(member, name, l); } void shutdown(const Uuid& id) { cluster.shutdown(member, id, l); } void deliverToQueue(const std::string& queue, const std::string& message) { cluster.deliverToQueue(queue, message, l); } + void clock(uint64_t time) { cluster.clock(time, l); } bool invoke(AMQBody& body) { return framing::invoke(*this, body).wasHandled(); } }; Cluster::Cluster(const ClusterSettings& set, broker::Broker& b) : - settings(set), + settings(set), broker(b), mgmtObject(0), poller(b.getPoller()), @@ -253,7 +254,7 @@ Cluster::Cluster(const ClusterSettings& set, broker::Broker& b) : self(cpg.self()), clusterId(true), mAgent(0), - expiryPolicy(new ExpiryPolicy(mcast, self, broker.getTimer())), + expiryPolicy(new ExpiryPolicy(*this)), mcast(cpg, poller, boost::bind(&Cluster::leave, this)), dispatcher(cpg, poller, boost::bind(&Cluster::leave, this)), deliverEventQueue(boost::bind(&Cluster::deliveredEvent, this, _1), @@ -277,8 +278,11 @@ Cluster::Cluster(const ClusterSettings& set, broker::Broker& b) : lastBroker(false), updateRetracted(false), updateClosed(false), - error(*this) + error(*this), + acl(0) { + broker.setInCluster(true); + // We give ownership of the timer to the broker and keep a plain pointer. // This is OK as it means the timer has the same lifetime as the broker. timer = new ClusterTimer(*this); @@ -299,7 +303,7 @@ Cluster::Cluster(const ClusterSettings& set, broker::Broker& b) : // Load my store status before we go into initialization if (! broker::NullMessageStore::isNullStore(&broker.getStore())) { store.load(); - clusterId = store.getClusterId(); + clusterId = store.getClusterId(); QPID_LOG(notice, "Cluster store state: " << store) } cpg.join(name); @@ -360,14 +364,15 @@ void Cluster::addShadowConnection(const boost::intrusive_ptr<Connection>& c) { // Safe to use connections here because we're pre-catchup, stalled // and discarding, so deliveredFrame is not processing any // connection events. - assert(discarding); + assert(discarding); pair<ConnectionMap::iterator, bool> ib = connections.insert(ConnectionMap::value_type(c->getId(), c)); - assert(ib.second); + // Like this to avoid tripping up unused variable warning when NDEBUG set + if (!ib.second) assert(ib.second); } void Cluster::erase(const ConnectionId& id) { - Lock l(lock); + Lock l(lock); erase(id,l); } @@ -393,9 +398,9 @@ std::vector<Url> Cluster::getUrls() const { std::vector<Url> Cluster::getUrls(Lock&) const { return map.memberUrls(); -} +} -void Cluster::leave() { +void Cluster::leave() { Lock l(lock); leave(l); } @@ -405,7 +410,7 @@ void Cluster::leave() { QPID_LOG(warning, *this << " error leaving cluster: " << e.what()); \ } do {} while(0) -void Cluster::leave(Lock&) { +void Cluster::leave(Lock&) { if (state != LEFT) { state = LEFT; QPID_LOG(notice, *this << " leaving cluster " << name); @@ -424,7 +429,7 @@ void Cluster::deliver( uint32_t nodeid, uint32_t pid, void* msg, - int msg_len) + int msg_len) { MemberId from(nodeid, pid); framing::Buffer buf(static_cast<char*>(msg), msg_len); @@ -455,7 +460,7 @@ void Cluster::deliveredEvent(const Event& e) { EventFrame ef(e, e.getFrame()); // Stop the deliverEventQueue on update offers. // This preserves the connection decoder fragments for an update. - // Only do this for the two brokers that are directly involved in this + // Only do this for the two brokers that are directly involved in this // offer: the one making the offer, or the one receiving it. const ClusterUpdateOfferBody* offer = castUpdateOffer(ef.frame.getBody()); if (offer && ( e.getMemberId() == self || MemberId(offer->getUpdatee()) == self) ) { @@ -465,7 +470,7 @@ void Cluster::deliveredEvent(const Event& e) { } deliverFrame(ef); } - else if(!discarding) { + else if(!discarding) { if (e.isControl()) deliverFrame(EventFrame(e, e.getFrame())); else { @@ -507,7 +512,7 @@ void Cluster::deliveredFrame(const EventFrame& efConst) { // the event queue. e.frame = AMQFrame( ClusterRetractOfferBody(ProtocolVersion(), offer->getUpdatee())); - deliverEventQueue.start(); + deliverEventQueue.start(); } // Process each frame through the error checker. if (error.isUnresolved()) { @@ -515,14 +520,14 @@ void Cluster::deliveredFrame(const EventFrame& efConst) { while (error.canProcess()) // There is a frame ready to process. processFrame(error.getNext(), l); } - else + else processFrame(e, l); } void Cluster::processFrame(const EventFrame& e, Lock& l) { if (e.isCluster()) { - QPID_LOG(trace, *this << " DLVR: " << e); + QPID_LOG_IF(trace, loggable(e.frame), *this << " DLVR: " << e); ClusterDispatcher dispatch(*this, e.connectionId.getMember(), l); if (!framing::invoke(dispatch, *e.frame.getBody()).wasHandled()) throw Exception(QPID_MSG("Invalid cluster control")); @@ -531,14 +536,15 @@ void Cluster::processFrame(const EventFrame& e, Lock& l) { map.incrementFrameSeq(); ConnectionPtr connection = getConnection(e, l); if (connection) { - QPID_LOG(trace, *this << " DLVR " << map.getFrameSeq() << ": " << e); + QPID_LOG_IF(trace, loggable(e.frame), + *this << " DLVR " << map.getFrameSeq() << ": " << e); connection->deliveredFrame(e); } else - QPID_LOG(trace, *this << " DROP (no connection): " << e); + throw Exception(QPID_MSG("Unknown connection: " << e)); } else // Drop connection frames while state < CATCHUP - QPID_LOG(trace, *this << " DROP (joining): " << e); + QPID_LOG_IF(trace, loggable(e.frame), *this << " DROP (joining): " << e); } // Called in deliverFrameQueue thread @@ -577,7 +583,7 @@ Cluster::ConnectionVector Cluster::getConnections(Lock&) { } // CPG config-change callback. -void Cluster::configChange ( +void Cluster::configChange ( cpg_handle_t /*handle*/, const cpg_name */*group*/, const cpg_address *members, int nMembers, @@ -607,7 +613,7 @@ void Cluster::setReady(Lock&) { } // Set the management status from the Cluster::state. -// +// // NOTE: Management updates are sent based on property changes. In // order to keep consistency across the cluster, we touch the local // management status property even if it is locally unchanged for any @@ -618,7 +624,7 @@ void Cluster::setMgmtStatus(Lock&) { } void Cluster::initMapCompleted(Lock& l) { - // Called on completion of the initial status map. + // Called on completion of the initial status map. QPID_LOG(debug, *this << " initial status map complete. "); setMgmtStatus(l); if (state == PRE_INIT) { @@ -665,6 +671,8 @@ void Cluster::initMapCompleted(Lock& l) { else { // I can go ready. discarding = false; setReady(l); + // Must be called *before* memberUpdate so first update will be generated. + failoverExchange->setReady(); memberUpdate(l); updateMgmtMembership(l); mcast.mcastControl(ClusterReadyBody(ProtocolVersion(), myUrl.str()), self); @@ -701,8 +709,8 @@ void Cluster::configChange(const MemberId&, if (initMap.isResendNeeded()) { mcast.mcastControl( ClusterInitialStatusBody( - ProtocolVersion(), CLUSTER_VERSION, state > INIT, clusterId, - store.getState(), store.getShutdownId(), + ProtocolVersion(), CLUSTER_VERSION, state > INIT, clusterId, + store.getState(), store.getShutdownId(), initMap.getFirstConfigStr() ), self); @@ -717,6 +725,20 @@ void Cluster::configChange(const MemberId&, updateMgmtMembership(l); // Update on every config change for consistency } +struct ClusterClockTask : public sys::TimerTask { + Cluster& cluster; + sys::Timer& timer; + + ClusterClockTask(Cluster& cluster, sys::Timer& timer, uint16_t clockInterval) + : TimerTask(Duration(clockInterval * TIME_MSEC),"ClusterClock"), cluster(cluster), timer(timer) {} + + void fire() { + cluster.sendClockUpdate(); + setupNextFire(); + timer.add(this); + } +}; + void Cluster::becomeElder(Lock&) { if (elder) return; // We were already the elder. // We are the oldest, reactive links if necessary @@ -724,6 +746,8 @@ void Cluster::becomeElder(Lock&) { elder = true; broker.getLinks().setPassive(false); timer->becomeElder(); + + clockTimer.add(new ClusterClockTask(*this, clockTimer, settings.clockInterval)); } void Cluster::makeOffer(const MemberId& id, Lock& ) { @@ -759,7 +783,7 @@ std::string Cluster::debugSnapshot() { // point we know the poller has stopped so no poller callbacks will be // invoked. We must ensure that CPG has also shut down so no CPG // callbacks will be invoked. -// +// void Cluster::brokerShutdown() { sys::ClusterSafeScope css; // Don't trigger cluster-safe asserts. try { cpg.shutdown(); } @@ -775,7 +799,7 @@ void Cluster::updateRequest(const MemberId& id, const std::string& url, Lock& l) } void Cluster::initialStatus(const MemberId& member, uint32_t version, bool active, - const framing::Uuid& id, + const framing::Uuid& id, framing::cluster::StoreState store, const framing::Uuid& shutdownId, const std::string& firstConfig, @@ -833,6 +857,8 @@ void Cluster::updateOffer(const MemberId& updater, uint64_t updateeInt, Lock& l) else if (updatee == self && url) { assert(state == JOINER); state = UPDATEE; + acl = broker.getAcl(); + broker.setAcl(0); // Disable ACL during update QPID_LOG(notice, *this << " receiving update from " << updater); checkUpdateIn(l); } @@ -844,7 +870,7 @@ void Cluster::updateOffer(const MemberId& updater, uint64_t updateeInt, Lock& l) if (updatee != self && url) { QPID_LOG(debug, debugSnapshot()); if (mAgent) mAgent->clusterUpdate(); - // Updatee will call clusterUpdate when update completes + // Updatee will call clusterUpdate() via checkUpdateIn() when update completes } } @@ -925,13 +951,15 @@ void Cluster::checkUpdateIn(Lock& l) { if (!updateClosed) return; // Wait till update connection closes. if (updatedMap) { // We're up to date map = *updatedMap; - failoverExchange->setUrls(getUrls(l)); mcast.mcastControl(ClusterReadyBody(ProtocolVersion(), myUrl.str()), self); state = CATCHUP; memberUpdate(l); + // Must be called *after* memberUpdate() to avoid sending an extra update. + failoverExchange->setReady(); // NB: don't updateMgmtMembership() here as we are not in the deliver // thread. It will be updated on delivery of the "ready" we just mcast. broker.setClusterUpdatee(false); + broker.setAcl(acl); // Restore ACL discarding = false; // OK to set, we're stalled for update. QPID_LOG(notice, *this << " update complete, starting catch-up."); QPID_LOG(debug, debugSnapshot()); // OK to call because we're stalled. @@ -941,6 +969,10 @@ void Cluster::checkUpdateIn(Lock& l) { mAgent->suppress(false); // Enable management output. mAgent->clusterUpdate(); } + // Restore alternate exchange settings on exchanges. + broker.getExchanges().eachExchange( + boost::bind(&broker::Exchange::recoveryComplete, _1, + boost::ref(broker.getExchanges()))); enableClusterSafe(); // Enable cluster-safe assertions deliverEventQueue.start(); } @@ -969,7 +1001,7 @@ void Cluster::updateOutDone(Lock& l) { void Cluster::updateOutError(const std::exception& e) { Monitor::ScopedLock l(lock); - QPID_LOG(error, *this << " error sending update: " << e.what()); + QPID_LOG(error, *this << " error sending update: " << e.what()); updateOutDone(l); } @@ -1067,7 +1099,7 @@ void Cluster::memberUpdate(Lock& l) { void Cluster::updateMgmtMembership(Lock& l) { if (!mgmtObject) return; std::vector<Url> urls = getUrls(l); - mgmtObject->set_clusterSize(urls.size()); + mgmtObject->set_clusterSize(urls.size()); string urlstr; for(std::vector<Url>::iterator i = urls.begin(); i != urls.end(); i++ ) { if (i != urls.begin()) urlstr += ";"; @@ -1114,10 +1146,6 @@ void Cluster::setClusterId(const Uuid& uuid, Lock&) { QPID_LOG(notice, *this << " cluster-uuid = " << clusterId); } -void Cluster::messageExpired(const MemberId&, uint64_t id, Lock&) { - expiryPolicy->deliverExpire(id); -} - void Cluster::errorCheck(const MemberId& from, uint8_t type, framing::SequenceNumber frameSeq, Lock&) { // If we see an errorCheck here (rather than in the ErrorCheck // class) then we have processed succesfully past the point of the @@ -1155,6 +1183,35 @@ void Cluster::deliverToQueue(const std::string& queue, const std::string& messag q->deliver(msg); } +sys::AbsTime Cluster::getClusterTime() { + Mutex::ScopedLock l(lock); + return clusterTime; +} + +// This method is called during update on the updatee to set the initial cluster time. +void Cluster::clock(const uint64_t time) { + Mutex::ScopedLock l(lock); + clock(time, l); +} + +// called when broadcast message received +void Cluster::clock(const uint64_t time, Lock&) { + clusterTime = AbsTime(EPOCH, time); + AbsTime now = AbsTime::now(); + + if (!elder) { + clusterTimeOffset = Duration(now, clusterTime); + } +} + +// called by elder timer to send clock broadcast +void Cluster::sendClockUpdate() { + Mutex::ScopedLock l(lock); + int64_t nanosecondsSinceEpoch = Duration(EPOCH, now()); + nanosecondsSinceEpoch += clusterTimeOffset; + mcast.mcastControl(ClusterClockBody(ProtocolVersion(), nanosecondsSinceEpoch), self); +} + bool Cluster::deferDeliveryImpl(const std::string& queue, const boost::intrusive_ptr<broker::Message>& msg) { @@ -1167,4 +1224,12 @@ bool Cluster::deferDeliveryImpl(const std::string& queue, return true; } +bool Cluster::loggable(const AMQFrame& f) { + const AMQMethodBody* method = (f.getMethod()); + if (!method) return true; // Not a method + bool isClock = method->amqpClassId() == ClusterClockBody::CLASS_ID + && method->amqpMethodId() == ClusterClockBody::METHOD_ID; + return !isClock; +} + }} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/Cluster.h b/cpp/src/qpid/cluster/Cluster.h index 8f73c6acca..ccec4948e6 100644 --- a/cpp/src/qpid/cluster/Cluster.h +++ b/cpp/src/qpid/cluster/Cluster.h @@ -56,17 +56,25 @@ namespace qpid { namespace broker { class Message; +class AclModule; } namespace framing { +class AMQFrame; class AMQBody; -class Uuid; +struct Uuid; +} + +namespace sys { +class Timer; +class AbsTime; +class Duration; } namespace cluster { class Connection; -class EventFrame; +struct EventFrame; class ClusterTimer; class UpdateDataExchange; @@ -89,10 +97,10 @@ class Cluster : private Cpg::Handler, public management::Manageable { void initialize(); // Connection map. - void addLocalConnection(const ConnectionPtr&); - void addShadowConnection(const ConnectionPtr&); - void erase(const ConnectionId&); - + void addLocalConnection(const ConnectionPtr&); + void addShadowConnection(const ConnectionPtr&); + void erase(const ConnectionId&); + // URLs of current cluster members. std::vector<std::string> getIds() const; std::vector<Url> getUrls() const; @@ -107,7 +115,7 @@ class Cluster : private Cpg::Handler, public management::Manageable { void updateInRetracted(); // True if we are expecting to receive catch-up connections. bool isExpectingUpdate(); - + MemberId getId() const; broker::Broker& getBroker() const; Multicaster& getMulticast() { return mcast; } @@ -135,6 +143,12 @@ class Cluster : private Cpg::Handler, public management::Manageable { bool deferDeliveryImpl(const std::string& queue, const boost::intrusive_ptr<broker::Message>& msg); + sys::AbsTime getClusterTime(); + void sendClockUpdate(); + void clock(const uint64_t time); + + static bool loggable(const framing::AMQFrame&); // True if the frame should be logged. + private: typedef sys::Monitor::ScopedLock Lock; @@ -144,10 +158,10 @@ class Cluster : private Cpg::Handler, public management::Manageable { /** Version number of the cluster protocol, to avoid mixed versions. */ static const uint32_t CLUSTER_VERSION; - + // NB: A dummy Lock& parameter marks functions that must only be // called with Cluster::lock locked. - + void leave(Lock&); std::vector<std::string> getIds(Lock&) const; std::vector<Url> getUrls(Lock&) const; @@ -156,11 +170,11 @@ class Cluster : private Cpg::Handler, public management::Manageable { void brokerShutdown(); // == Called in deliverEventQueue thread - void deliveredEvent(const Event&); + void deliveredEvent(const Event&); // == Called in deliverFrameQueue thread - void deliveredFrame(const EventFrame&); - void processFrame(const EventFrame&, Lock&); + void deliveredFrame(const EventFrame&); + void processFrame(const EventFrame&, Lock&); // Cluster controls implement XML methods from cluster.xml. void updateRequest(const MemberId&, const std::string&, Lock&); @@ -180,12 +194,12 @@ class Cluster : private Cpg::Handler, public management::Manageable { const std::string& left, const std::string& joined, Lock& l); - void messageExpired(const MemberId&, uint64_t, Lock& l); void errorCheck(const MemberId&, uint8_t type, SequenceNumber frameSeq, Lock&); void timerWakeup(const MemberId&, const std::string& name, Lock&); void timerDrop(const MemberId&, const std::string& name, Lock&); void shutdown(const MemberId&, const framing::Uuid& shutdownId, Lock&); void deliverToQueue(const std::string& queue, const std::string& message, Lock&); + void clock(const uint64_t time, Lock&); // Helper functions ConnectionPtr getConnection(const EventFrame&, Lock&); @@ -195,7 +209,7 @@ class Cluster : private Cpg::Handler, public management::Manageable { void setReady(Lock&); void memberUpdate(Lock&); void setClusterId(const framing::Uuid&, Lock&); - void erase(const ConnectionId&, Lock&); + void erase(const ConnectionId&, Lock&); void requestUpdate(Lock& ); void initMapCompleted(Lock&); void becomeElder(Lock&); @@ -203,7 +217,7 @@ class Cluster : private Cpg::Handler, public management::Manageable { void updateMgmtMembership(Lock&); // == Called in CPG dispatch thread - void deliver( // CPG deliver callback. + void deliver( // CPG deliver callback. cpg_handle_t /*handle*/, const struct cpg_name *group, uint32_t /*nodeid*/, @@ -212,7 +226,7 @@ class Cluster : private Cpg::Handler, public management::Manageable { int /*msg_len*/); void deliverEvent(const Event&); - + void configChange( // CPG config change callback. cpg_handle_t /*handle*/, const struct cpg_name */*group*/, @@ -263,7 +277,7 @@ class Cluster : private Cpg::Handler, public management::Manageable { // Used only in deliverEventQueue thread or when stalled for update. Decoder decoder; bool discarding; - + // Remaining members are protected by lock. mutable sys::Monitor lock; @@ -276,7 +290,7 @@ class Cluster : private Cpg::Handler, public management::Manageable { JOINER, ///< Sent update request, waiting for update offer. UPDATEE, ///< Stalled receive queue at update offer, waiting for update to complete. CATCHUP, ///< Update complete, unstalled but has not yet seen own "ready" event. - READY, ///< Fully operational + READY, ///< Fully operational OFFER, ///< Sent an offer, waiting for accept/reject. UPDATER, ///< Offer accepted, sending a state update. LEFT ///< Final state, left the cluster. @@ -296,9 +310,13 @@ class Cluster : private Cpg::Handler, public management::Manageable { ErrorCheck error; UpdateReceiver updateReceiver; ClusterTimer* timer; + sys::Timer clockTimer; + sys::AbsTime clusterTime; + sys::Duration clusterTimeOffset; + broker::AclModule* acl; friend std::ostream& operator<<(std::ostream&, const Cluster&); - friend class ClusterDispatcher; + friend struct ClusterDispatcher; }; }} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/ClusterMap.cpp b/cpp/src/qpid/cluster/ClusterMap.cpp index 040e129970..a8389095c9 100644 --- a/cpp/src/qpid/cluster/ClusterMap.cpp +++ b/cpp/src/qpid/cluster/ClusterMap.cpp @@ -50,11 +50,6 @@ void insertFieldTableFromMapValue(FieldTable& ft, const ClusterMap::Map::value_t ft.setString(vt.first.str(), vt.second.str()); } -void assignFieldTable(FieldTable& ft, const ClusterMap::Map& map) { - ft.clear(); - for_each(map.begin(), map.end(), bind(&insertFieldTableFromMapValue, ref(ft), _1)); -} - } ClusterMap::ClusterMap() : frameSeq(0) {} diff --git a/cpp/src/qpid/cluster/ClusterPlugin.cpp b/cpp/src/qpid/cluster/ClusterPlugin.cpp index 2962daaa07..69ba095f16 100644 --- a/cpp/src/qpid/cluster/ClusterPlugin.cpp +++ b/cpp/src/qpid/cluster/ClusterPlugin.cpp @@ -72,6 +72,7 @@ struct ClusterOptions : public Options { ("cluster-cman", optValue(settings.quorum), "Integrate with Cluster Manager (CMAN) cluster.") #endif ("cluster-size", optValue(settings.size, "N"), "Wait for N cluster members before allowing clients to connect.") + ("cluster-clock-interval", optValue(settings.clockInterval,"N"), "How often to broadcast the current time to the cluster nodes, in milliseconds. A value between 5 and 1000 is recommended.") ("cluster-read-max", optValue(settings.readMax,"N"), "Experimental: flow-control limit reads per connection. 0=no limit.") ; } diff --git a/cpp/src/qpid/cluster/ClusterSettings.h b/cpp/src/qpid/cluster/ClusterSettings.h index 8e708aa139..2f7b5be20a 100644 --- a/cpp/src/qpid/cluster/ClusterSettings.h +++ b/cpp/src/qpid/cluster/ClusterSettings.h @@ -35,8 +35,9 @@ struct ClusterSettings { size_t readMax; std::string username, password, mechanism; size_t size; + uint16_t clockInterval; - ClusterSettings() : quorum(false), readMax(10), size(1) + ClusterSettings() : quorum(false), readMax(10), size(1), clockInterval(10) {} Url getUrl(uint16_t port) const { diff --git a/cpp/src/qpid/cluster/ClusterTimer.cpp b/cpp/src/qpid/cluster/ClusterTimer.cpp index f6e1c7a849..b4f7d00f38 100644 --- a/cpp/src/qpid/cluster/ClusterTimer.cpp +++ b/cpp/src/qpid/cluster/ClusterTimer.cpp @@ -70,6 +70,7 @@ void ClusterTimer::add(intrusive_ptr<TimerTask> task) if (i != map.end()) throw Exception(QPID_MSG("Task already exists with name " << task->getName())); map[task->getName()] = task; + // Only the elder actually activates the task with the Timer base class. if (cluster.isElder()) { QPID_LOG(trace, "Elder activating cluster timer task " << task->getName()); @@ -112,6 +113,9 @@ void ClusterTimer::deliverWakeup(const std::string& name) { else { intrusive_ptr<TimerTask> t = i->second; map.erase(i); + // Move the nextFireTime so readyToFire() is true. This is to ensure we + // don't get an error if the fired task calls setupNextFire() + t->setFired(); Timer::fire(t); } } diff --git a/cpp/src/qpid/cluster/Connection.cpp b/cpp/src/qpid/cluster/Connection.cpp index e9b718e6de..394749aad2 100644 --- a/cpp/src/qpid/cluster/Connection.cpp +++ b/cpp/src/qpid/cluster/Connection.cpp @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -24,6 +24,8 @@ #include "Cluster.h" #include "UpdateReceiver.h" #include "qpid/assert.h" +#include "qpid/broker/DtxAck.h" +#include "qpid/broker/DtxBuffer.h" #include "qpid/broker/SessionState.h" #include "qpid/broker/SemanticState.h" #include "qpid/broker/TxBuffer.h" @@ -35,6 +37,7 @@ #include "qpid/broker/Fairshare.h" #include "qpid/broker/Link.h" #include "qpid/broker/Bridge.h" +#include "qpid/broker/StatefulQueueObserver.h" #include "qpid/broker/Queue.h" #include "qpid/framing/enum.h" #include "qpid/framing/AMQFrame.h" @@ -78,7 +81,7 @@ const std::string shadowPrefix("[shadow]"); Connection::Connection(Cluster& c, sys::ConnectionOutputHandler& out, const std::string& mgmtId, const ConnectionId& id, const qpid::sys::SecuritySettings& external) - : cluster(c), self(id), catchUp(false), output(*this, out), + : cluster(c), self(id), catchUp(false), announced(false), output(*this, out), connectionCtor(&output, cluster.getBroker(), mgmtId, external, false, 0, true), expectProtocolHeader(false), mcastFrameHandler(cluster.getMulticast(), self), @@ -90,13 +93,15 @@ Connection::Connection(Cluster& c, sys::ConnectionOutputHandler& out, Connection::Connection(Cluster& c, sys::ConnectionOutputHandler& out, const std::string& mgmtId, MemberId member, bool isCatchUp, bool isLink, const qpid::sys::SecuritySettings& external -) : cluster(c), self(member, ++idCounter), catchUp(isCatchUp), output(*this, out), +) : cluster(c), self(member, ++idCounter), catchUp(isCatchUp), announced(false), output(*this, out), connectionCtor(&output, cluster.getBroker(), mgmtId, external, isLink, isCatchUp ? ++catchUpId : 0, - isCatchUp), // isCatchUp => shadow + // The first catch-up connection is not considered a shadow + // as it needs to be authenticated. + isCatchUp && self.second > 1), expectProtocolHeader(isLink), mcastFrameHandler(cluster.getMulticast(), self), updateIn(c.getUpdateReceiver()), @@ -113,7 +118,7 @@ Connection::Connection(Cluster& c, sys::ConnectionOutputHandler& out, if (!updateIn.nextShadowMgmtId.empty()) connectionCtor.mgmtId = updateIn.nextShadowMgmtId; updateIn.nextShadowMgmtId.clear(); - } + } init(); QPID_LOG(debug, cluster << " local connection " << *this); } @@ -143,7 +148,7 @@ void Connection::init() { // Called when we have consumed a read buffer to give credit to the // connection layer to continue reading. void Connection::giveReadCredit(int credit) { - if (cluster.getSettings().readMax && credit) + if (cluster.getSettings().readMax && credit) output.giveReadCredit(credit); } @@ -166,7 +171,7 @@ void Connection::announce( AMQFrame frame; while (frame.decode(buf)) connection->received(frame); - connection->setUserId(username); + connection->setUserId(username); } // Do managment actions now that the connection is replicated. connection->raiseConnectEvent(); @@ -193,7 +198,7 @@ void Connection::received(framing::AMQFrame& f) { << *this << ": " << f); return; } - QPID_LOG(trace, cluster << " RECV " << *this << ": " << f); + QPID_LOG_IF(trace, Cluster::loggable(f), cluster << " RECV " << *this << ": " << f); if (isLocal()) { // Local catch-up connection. currentChannel = f.getChannel(); if (!framing::invoke(*this, *f.getBody()).wasHandled()) @@ -201,7 +206,7 @@ void Connection::received(framing::AMQFrame& f) { } else { // Shadow or updated catch-up connection. if (f.getMethod() && f.getMethod()->isA<ConnectionCloseBody>()) { - if (isShadow()) + if (isShadow()) cluster.addShadowConnection(this); AMQFrame ok((ConnectionCloseOkBody())); connection->getOutput().send(ok); @@ -213,16 +218,9 @@ void Connection::received(framing::AMQFrame& f) { } } -bool Connection::checkUnsupported(const AMQBody& body) { - std::string message; - if (body.getMethod()) { - switch (body.getMethod()->amqpClassId()) { - case DTX_CLASS_ID: message = "DTX transactions are not currently supported by cluster."; break; - } - } - if (!message.empty()) - connection->close(connection::CLOSE_CODE_FRAMING_ERROR, message); - return !message.empty(); +bool Connection::checkUnsupported(const AMQBody&) { + // Throw an exception for unsupported commands. Currently all are supported. + return false; } struct GiveReadCreditOnExit { @@ -241,7 +239,7 @@ void Connection::deliverDoOutput(uint32_t limit) { void Connection::deliveredFrame(const EventFrame& f) { GiveReadCreditOnExit gc(*this, f.readCredit); assert(!catchUp); - currentChannel = f.frame.getChannel(); + currentChannel = f.frame.getChannel(); if (f.frame.getBody() // frame can be emtpy with just readCredit && !framing::invoke(*this, *f.frame.getBody()).wasHandled() // Connection contol. && !checkUnsupported(*f.frame.getBody())) // Unsupported operation. @@ -255,7 +253,7 @@ void Connection::deliveredFrame(const EventFrame& f) { } } -// A local connection is closed by the network layer. +// A local connection is closed by the network layer. Called in the connection thread. void Connection::closed() { try { if (isUpdated()) { @@ -272,8 +270,9 @@ void Connection::closed() { // closed and process any outstanding frames from the cluster // until self-delivery of deliver-close. output.closeOutput(); - cluster.getMulticast().mcastControl( - ClusterConnectionDeliverCloseBody(), self); + if (announced) + cluster.getMulticast().mcastControl( + ClusterConnectionDeliverCloseBody(), self); } } catch (const std::exception& e) { @@ -287,7 +286,7 @@ void Connection::deliverClose () { cluster.erase(self); } -// Close the connection +// Close the connection void Connection::close() { if (connection.get()) { QPID_LOG(debug, cluster << " closed connection " << *this); @@ -320,10 +319,10 @@ size_t Connection::decode(const char* data, size_t size) { while (localDecoder.decode(buf)) received(localDecoder.getFrame()); if (!wasOpen && connection->isOpen()) { - // Connections marked as federation links are allowed to proxy + // Connections marked with setUserProxyAuth are allowed to proxy // messages with user-ID that doesn't match the connection's // authenticated ID. This is important for updates. - connection->setFederationLink(isCatchUp()); + connection->setUserProxyAuth(isCatchUp()); } } else { // Multicast local connections. @@ -332,9 +331,9 @@ size_t Connection::decode(const char* data, size_t size) { if (!checkProtocolHeader(ptr, size)) // Updates ptr return 0; // Incomplete header - if (!connection->isOpen()) + if (!connection->isOpen()) processInitialFrames(ptr, end-ptr); // Updates ptr - + if (connection->isOpen() && end - ptr > 0) { // We're multi-casting, we will give read credit on delivery. grc.credit = 0; @@ -384,6 +383,7 @@ void Connection::processInitialFrames(const char*& ptr, size_t size) { connection->getUserId(), initialFrames), getId()); + announced = true; initialFrames.clear(); } } @@ -406,11 +406,11 @@ void Connection::shadowSetUser(const std::string& userId) { void Connection::consumerState(const string& name, bool blocked, bool notifyEnabled, const SequenceNumber& position) { - broker::SemanticState::ConsumerImpl& c = semanticState().find(name); - c.position = position; - c.setBlocked(blocked); - if (notifyEnabled) c.enableNotify(); else c.disableNotify(); - updateIn.consumerNumbering.add(c.shared_from_this()); + broker::SemanticState::ConsumerImpl::shared_ptr c = semanticState().find(name); + c->position = position; + c->setBlocked(blocked); + if (notifyEnabled) c->enableNotify(); else c->disableNotify(); + updateIn.consumerNumbering.add(c); } @@ -421,7 +421,8 @@ void Connection::sessionState( const SequenceNumber& expected, const SequenceNumber& received, const SequenceSet& unknownCompleted, - const SequenceSet& receivedIncomplete) + const SequenceSet& receivedIncomplete, + bool dtxSelected) { sessionState().setState( replayStart, @@ -431,8 +432,10 @@ void Connection::sessionState( received, unknownCompleted, receivedIncomplete); - QPID_LOG(debug, cluster << " received session state update for " << sessionState().getId()); - // The output tasks will be added later in the update process. + if (dtxSelected) semanticState().selectDtx(); + QPID_LOG(debug, cluster << " received session state update for " + << sessionState().getId()); + // The output tasks will be added later in the update process. connection->getOutputTasks().removeAll(); } @@ -441,7 +444,7 @@ void Connection::outputTask(uint16_t channel, const std::string& name) { if (!session) throw Exception(QPID_MSG(cluster << " channel not attached " << *this << "[" << channel << "] ")); - OutputTask* task = &session->getSemanticState().find(name); + OutputTask* task = session->getSemanticState().find(name).get(); connection->getOutputTasks().addOutputTask(task); } @@ -461,11 +464,24 @@ void Connection::shadowReady( output.setSendMax(sendMax); } +void Connection::setDtxBuffer(const UpdateReceiver::DtxBufferRef& bufRef) { + broker::DtxManager& mgr = cluster.getBroker().getDtxManager(); + broker::DtxWorkRecord* record = mgr.getWork(bufRef.xid); + broker::DtxBuffer::shared_ptr buffer = (*record)[bufRef.index]; + if (bufRef.suspended) + bufRef.semanticState->getSuspendedXids()[bufRef.xid] = buffer; + else + bufRef.semanticState->setDtxBuffer(buffer); +} + +// Marks the end of the update. void Connection::membership(const FieldTable& joiners, const FieldTable& members, const framing::SequenceNumber& frameSeq) { QPID_LOG(debug, cluster << " incoming update complete on connection " << *this); updateIn.consumerNumbering.clear(); + for_each(updateIn.dtxBuffers.begin(), updateIn.dtxBuffers.end(), + boost::bind(&Connection::setDtxBuffer, this, _1)); closeUpdated(); cluster.updateInDone(ClusterMap(joiners, members, frameSeq)); } @@ -478,7 +494,7 @@ void Connection::retractOffer() { void Connection::closeUpdated() { self.second = 0; // Mark this as completed update connection. - if (connection.get()) + if (connection.get()) connection->close(connection::CLOSE_CODE_NORMAL, "OK"); } @@ -529,12 +545,20 @@ void Connection::deliveryRecord(const string& qname, m = getUpdateMessage(); m.queue = queue.get(); m.position = position; - if (enqueued) queue->updateEnqueued(m); //inform queue of the message + if (enqueued) queue->updateEnqueued(m); //inform queue of the message } else { // Message at original position in original queue - m = queue->find(position); + queue->find(position, m); } - if (!m.payload) - throw Exception(QPID_MSG("deliveryRecord no update message")); + // FIXME aconway 2011-08-19: removed: + // if (!m.payload) + // throw Exception(QPID_MSG("deliveryRecord no update message")); + // + // It seems this could happen legitimately in the case one + // session browses message M, then another session acquires + // it. In that case the browsers delivery record is !acquired + // but the message is not on its original Queue. In that case + // we'll get a deliveryRecord with no payload for the browser. + // } broker::DeliveryRecord dr(m, queue, tag, acquired, accepted, windowing, credit); @@ -542,7 +566,11 @@ void Connection::deliveryRecord(const string& qname, if (cancelled) dr.cancel(dr.getTag()); if (completed) dr.complete(); if (ended) dr.setEnded(); // Exsitance of message - semanticState().record(dr); // Part of the session's unacked list. + + if (dtxBuffer) // Record for next dtx-ack + dtxAckRecords.push_back(dr); + else + semanticState().record(dr); // Record on session's unacked list. } void Connection::queuePosition(const string& qname, const SequenceNumber& position) { @@ -556,8 +584,46 @@ void Connection::queueFairshareState(const std::string& qname, const uint8_t pri } } -void Connection::expiryId(uint64_t id) { - cluster.getExpiryPolicy().setId(id); + +namespace { +// find a StatefulQueueObserver that matches a given identifier +class ObserverFinder { + const std::string id; + boost::shared_ptr<broker::QueueObserver> target; + ObserverFinder(const ObserverFinder&) {} + public: + ObserverFinder(const std::string& _id) : id(_id) {} + broker::StatefulQueueObserver *getObserver() + { + if (target) + return dynamic_cast<broker::StatefulQueueObserver *>(target.get()); + return 0; + } + void operator() (boost::shared_ptr<broker::QueueObserver> o) + { + if (!target) { + broker::StatefulQueueObserver *p = dynamic_cast<broker::StatefulQueueObserver *>(o.get()); + if (p && p->getId() == id) { + target = o; + } + } + } +}; +} + + +void Connection::queueObserverState(const std::string& qname, const std::string& observerId, const FieldTable& state) +{ + boost::shared_ptr<broker::Queue> queue(findQueue(qname)); + ObserverFinder finder(observerId); // find this observer + queue->eachObserver<ObserverFinder &>(finder); + broker::StatefulQueueObserver *so = finder.getObserver(); + if (so) { + so->setState( state ); + QPID_LOG(debug, "updated queue observer " << observerId << "'s state on queue " << qname << "; ..."); + return; + } + QPID_LOG(error, "Failed to find observer " << observerId << " state on queue " << qname << "; this will result in inconsistencies."); } std::ostream& operator<<(std::ostream& o, const Connection& c) { @@ -574,6 +640,7 @@ std::ostream& operator<<(std::ostream& o, const Connection& c) { void Connection::txStart() { txBuffer.reset(new broker::TxBuffer()); } + void Connection::txAccept(const framing::SequenceSet& acked) { txBuffer->enlist(boost::shared_ptr<broker::TxAccept>( new broker::TxAccept(acked, semanticState().getUnacked()))); @@ -589,9 +656,11 @@ void Connection::txEnqueue(const std::string& queue) { new broker::RecoveredEnqueue(findQueue(queue), getUpdateMessage().payload))); } -void Connection::txPublish(const framing::Array& queues, bool delivered) { - boost::shared_ptr<broker::TxPublish> txPub(new broker::TxPublish(getUpdateMessage().payload)); - for (framing::Array::const_iterator i = queues.begin(); i != queues.end(); ++i) +void Connection::txPublish(const framing::Array& queues, bool delivered) +{ + boost::shared_ptr<broker::TxPublish> txPub( + new broker::TxPublish(getUpdateMessage().payload)); + for (framing::Array::const_iterator i = queues.begin(); i != queues.end(); ++i) txPub->deliverTo(findQueue((*i)->get<std::string>())); txPub->delivered = delivered; txBuffer->enlist(txPub); @@ -605,6 +674,51 @@ void Connection::accumulatedAck(const qpid::framing::SequenceSet& s) { semanticState().setAccumulatedAck(s); } +void Connection::dtxStart(const std::string& xid, + bool ended, + bool suspended, + bool failed, + bool expired) +{ + dtxBuffer.reset(new broker::DtxBuffer(xid, ended, suspended, failed, expired)); + txBuffer = dtxBuffer; +} + +void Connection::dtxEnd() { + broker::DtxManager& mgr = cluster.getBroker().getDtxManager(); + std::string xid = dtxBuffer->getXid(); + if (mgr.exists(xid)) + mgr.join(xid, dtxBuffer); + else + mgr.start(xid, dtxBuffer); + dtxBuffer.reset(); + txBuffer.reset(); +} + +// Sent after all DeliveryRecords for a dtx-ack have been collected in dtxAckRecords +void Connection::dtxAck() { + dtxBuffer->enlist( + boost::shared_ptr<broker::DtxAck>(new broker::DtxAck(dtxAckRecords))); + dtxAckRecords.clear(); +} + +void Connection::dtxBufferRef(const std::string& xid, uint32_t index, bool suspended) { + // Save the association between DtxBuffers and the session so we + // can set the DtxBuffers at the end of the update when the + // DtxManager has been replicated. + updateIn.dtxBuffers.push_back( + UpdateReceiver::DtxBufferRef(xid, index, suspended, &semanticState())); +} + +// Sent at end of work record. +void Connection::dtxWorkRecord(const std::string& xid, bool prepared, uint32_t timeout) +{ + broker::DtxManager& mgr = cluster.getBroker().getDtxManager(); + if (timeout) mgr.setTimeout(xid, timeout); + if (prepared) mgr.prepare(xid); +} + + void Connection::exchange(const std::string& encoded) { Buffer buf(const_cast<char*>(encoded.data()), encoded.size()); broker::Exchange::shared_ptr ex = broker::Exchange::decode(cluster.getBroker().getExchanges(), buf); @@ -614,12 +728,6 @@ void Connection::exchange(const std::string& encoded) { QPID_LOG(debug, cluster << " updated exchange " << ex->getName()); } -void Connection::queue(const std::string& encoded) { - Buffer buf(const_cast<char*>(encoded.data()), encoded.size()); - broker::Queue::shared_ptr q = broker::Queue::decode(cluster.getBroker().getQueues(), buf); - QPID_LOG(debug, cluster << " updated queue " << q->getName()); -} - void Connection::sessionError(uint16_t , const std::string& msg) { // Ignore errors before isOpen(), we're not multicasting yet. if (connection->isOpen()) @@ -678,6 +786,23 @@ void Connection::config(const std::string& encoded) { else throw Exception(QPID_MSG("Update failed, invalid kind of config: " << kind)); } +void Connection::doCatchupIoCallbacks() { + // We need to process IO callbacks during the catch-up phase in + // order to service asynchronous completions for messages + // transferred during catch-up. + + if (catchUp) getBrokerConnection()->doIoCallbacks(); +} + +void Connection::clock(uint64_t time) { + QPID_LOG(debug, "Cluster connection received time update"); + cluster.clock(time); +} + +void Connection::queueDequeueSincePurgeState(const std::string& qname, uint32_t dequeueSincePurge) { + boost::shared_ptr<broker::Queue> queue(findQueue(qname)); + queue->setDequeueSincePurge(dequeueSincePurge); +} }} // Namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/Connection.h b/cpp/src/qpid/cluster/Connection.h index 7ee85bf1aa..fe66b77238 100644 --- a/cpp/src/qpid/cluster/Connection.h +++ b/cpp/src/qpid/cluster/Connection.h @@ -10,9 +10,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -24,11 +24,12 @@ #include "types.h" #include "OutputInterceptor.h" -#include "EventFrame.h" #include "McastFrameHandler.h" #include "UpdateReceiver.h" +#include "qpid/RefCounted.h" #include "qpid/broker/Connection.h" +#include "qpid/broker/DeliveryRecord.h" #include "qpid/broker/SecureConnection.h" #include "qpid/broker/SemanticState.h" #include "qpid/amqp_0_10/Connection.h" @@ -47,7 +48,7 @@ namespace framing { class AMQFrame; } namespace broker { class SemanticState; -class QueuedMessage; +struct QueuedMessage; class TxBuffer; class TxAccept; } @@ -55,6 +56,7 @@ class TxAccept; namespace cluster { class Cluster; class Event; +struct EventFrame; /** Intercept broker::Connection calls for shadow and local cluster connections. */ class Connection : @@ -62,7 +64,7 @@ class Connection : public sys::ConnectionInputHandler, public framing::AMQP_AllOperations::ClusterConnectionHandler, private broker::Connection::ErrorListener - + { public: @@ -73,7 +75,7 @@ class Connection : Connection(Cluster&, sys::ConnectionOutputHandler& out, const std::string& mgmtId, const ConnectionId& id, const qpid::sys::SecuritySettings& external); ~Connection(); - + ConnectionId getId() const { return self; } broker::Connection* getBrokerConnection() { return connection.get(); } const broker::Connection* getBrokerConnection() const { return connection.get(); } @@ -108,9 +110,9 @@ class Connection : void deliveredFrame(const EventFrame&); void consumerState(const std::string& name, bool blocked, bool notifyEnabled, const qpid::framing::SequenceNumber& position); - + // ==== Used in catch-up mode to build initial state. - // + // // State update methods. void shadowPrepare(const std::string&); @@ -122,10 +124,11 @@ class Connection : const framing::SequenceNumber& expected, const framing::SequenceNumber& received, const framing::SequenceSet& unknownCompleted, - const SequenceSet& receivedIncomplete); - + const SequenceSet& receivedIncomplete, + bool dtxSelected); + void outputTask(uint16_t channel, const std::string& name); - + void shadowReady(uint64_t memberId, uint64_t connectionId, const std::string& managementId, @@ -153,7 +156,7 @@ class Connection : void queuePosition(const std::string&, const framing::SequenceNumber&); void queueFairshareState(const std::string&, const uint8_t priority, const uint8_t count); - void expiryId(uint64_t); + void queueObserverState(const std::string&, const std::string&, const framing::FieldTable&); void txStart(); void txAccept(const framing::SequenceSet&); @@ -163,8 +166,18 @@ class Connection : void txEnd(); void accumulatedAck(const framing::SequenceSet&); - // Encoded queue/exchange replication. - void queue(const std::string& encoded); + // Dtx state + void dtxStart(const std::string& xid, + bool ended, + bool suspended, + bool failed, + bool expired); + void dtxEnd(); + void dtxAck(); + void dtxBufferRef(const std::string& xid, uint32_t index, bool suspended); + void dtxWorkRecord(const std::string& xid, bool prepared, uint32_t timeout); + + // Encoded exchange replication. void exchange(const std::string& encoded); void giveReadCredit(int credit); @@ -189,6 +202,12 @@ class Connection : void setSecureConnection ( broker::SecureConnection * sc ); + void doCatchupIoCallbacks(); + + void clock(uint64_t time); + + void queueDequeueSincePurgeState(const std::string&, uint32_t); + private: struct NullFrameHandler : public framing::FrameHandler { void handle(framing::AMQFrame&) {} @@ -233,7 +252,7 @@ class Connection : // Error listener functions void connectionError(const std::string&); void sessionError(uint16_t channel, const std::string&); - + void init(); bool checkUnsupported(const framing::AMQBody& body); void deliverDoOutput(uint32_t limit); @@ -245,10 +264,11 @@ class Connection : broker::SemanticState& semanticState(); broker::QueuedMessage getUpdateMessage(); void closeUpdated(); - + void setDtxBuffer(const UpdateReceiver::DtxBuffers::value_type &); Cluster& cluster; ConnectionId self; bool catchUp; + bool announced; OutputInterceptor output; framing::FrameDecoder localDecoder; ConnectionCtor connectionCtor; @@ -256,6 +276,9 @@ class Connection : framing::SequenceNumber deliverSeq; framing::ChannelId currentChannel; boost::shared_ptr<broker::TxBuffer> txBuffer; + boost::shared_ptr<broker::DtxBuffer> dtxBuffer; + broker::DeliveryRecords dtxAckRecords; + broker::DtxWorkRecord* dtxCurrent; bool expectProtocolHeader; McastFrameHandler mcastFrameHandler; UpdateReceiver& updateIn; diff --git a/cpp/src/qpid/cluster/Decoder.h b/cpp/src/qpid/cluster/Decoder.h index 2e2af2868f..3b5ada4a81 100644 --- a/cpp/src/qpid/cluster/Decoder.h +++ b/cpp/src/qpid/cluster/Decoder.h @@ -31,7 +31,7 @@ namespace qpid { namespace cluster { -class EventFrame; +struct EventFrame; class EventHeader; /** diff --git a/cpp/src/qpid/cluster/ErrorCheck.h b/cpp/src/qpid/cluster/ErrorCheck.h index de8cedafb3..a417b2ec25 100644 --- a/cpp/src/qpid/cluster/ErrorCheck.h +++ b/cpp/src/qpid/cluster/ErrorCheck.h @@ -33,7 +33,7 @@ namespace qpid { namespace cluster { -class EventFrame; +struct EventFrame; class Cluster; class Multicaster; class Connection; diff --git a/cpp/src/qpid/cluster/Event.cpp b/cpp/src/qpid/cluster/Event.cpp index cd775ce2f1..da2bc89d8c 100644 --- a/cpp/src/qpid/cluster/Event.cpp +++ b/cpp/src/qpid/cluster/Event.cpp @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -23,6 +23,7 @@ #include "qpid/cluster/Cpg.h" #include "qpid/framing/Buffer.h" #include "qpid/framing/AMQFrame.h" +#include "qpid/RefCountedBuffer.h" #include "qpid/assert.h" #include <ostream> #include <iterator> diff --git a/cpp/src/qpid/cluster/Event.h b/cpp/src/qpid/cluster/Event.h index 07f74d3ba5..13283edff7 100644 --- a/cpp/src/qpid/cluster/Event.h +++ b/cpp/src/qpid/cluster/Event.h @@ -10,9 +10,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -23,7 +23,7 @@ */ #include "qpid/cluster/types.h" -#include "qpid/RefCountedBuffer.h" +#include "qpid/BufferRef.h" #include "qpid/framing/AMQFrame.h" #include <sys/uio.h> // For iovec #include <iosfwd> @@ -53,7 +53,7 @@ class EventHeader { /** Size of payload data, excluding header. */ size_t getSize() const { return size; } - /** Size of header + payload. */ + /** Size of header + payload. */ size_t getStoreSize() const { return size + HEADER_SIZE; } bool isCluster() const { return connectionId.getNumber() == 0; } @@ -62,7 +62,7 @@ class EventHeader { protected: static const size_t HEADER_SIZE; - + EventType type; ConnectionId connectionId; size_t size; @@ -86,25 +86,25 @@ class Event : public EventHeader { /** Create a control event. */ static Event control(const framing::AMQFrame&, const ConnectionId&); - + // Data excluding header. - char* getData() { return store + HEADER_SIZE; } - const char* getData() const { return store + HEADER_SIZE; } + char* getData() { return store.begin() + HEADER_SIZE; } + const char* getData() const { return store.begin() + HEADER_SIZE; } // Store including header - char* getStore() { return store; } - const char* getStore() const { return store; } + char* getStore() { return store.begin(); } + const char* getStore() const { return store.begin(); } + + const framing::AMQFrame& getFrame() const; - const framing::AMQFrame& getFrame() const; - operator framing::Buffer() const; iovec toIovec() const; - + private: void encodeHeader() const; - RefCountedBuffer::pointer store; + BufferRef store; mutable framing::AMQFrame frame; }; diff --git a/cpp/src/qpid/cluster/EventFrame.h b/cpp/src/qpid/cluster/EventFrame.h index 61447c5525..6b702a9bf8 100644 --- a/cpp/src/qpid/cluster/EventFrame.h +++ b/cpp/src/qpid/cluster/EventFrame.h @@ -10,9 +10,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -48,7 +48,7 @@ struct EventFrame ConnectionId connectionId; - framing::AMQFrame frame; + framing::AMQFrame frame; int readCredit; ///< last frame in an event, give credit when processed. EventType type; }; diff --git a/cpp/src/qpid/cluster/ExpiryPolicy.cpp b/cpp/src/qpid/cluster/ExpiryPolicy.cpp index d9a7b0122a..0ef5c2a35d 100644 --- a/cpp/src/qpid/cluster/ExpiryPolicy.cpp +++ b/cpp/src/qpid/cluster/ExpiryPolicy.cpp @@ -21,106 +21,21 @@ #include "qpid/broker/Message.h" #include "qpid/cluster/ExpiryPolicy.h" -#include "qpid/cluster/Multicaster.h" -#include "qpid/framing/ClusterMessageExpiredBody.h" +#include "qpid/cluster/Cluster.h" #include "qpid/sys/Time.h" -#include "qpid/sys/Timer.h" #include "qpid/log/Statement.h" namespace qpid { namespace cluster { -ExpiryPolicy::ExpiryPolicy(Multicaster& m, const MemberId& id, sys::Timer& t) - : expiryId(1), expiredPolicy(new Expired), mcast(m), memberId(id), timer(t) {} +ExpiryPolicy::ExpiryPolicy(Cluster& cluster) : cluster(cluster) {} -struct ExpiryTask : public sys::TimerTask { - ExpiryTask(const boost::intrusive_ptr<ExpiryPolicy>& policy, uint64_t id, sys::AbsTime when) - : TimerTask(when,"ExpiryPolicy"), expiryPolicy(policy), expiryId(id) {} - void fire() { expiryPolicy->sendExpire(expiryId); } - boost::intrusive_ptr<ExpiryPolicy> expiryPolicy; - const uint64_t expiryId; -}; - -// Called while receiving an update -void ExpiryPolicy::setId(uint64_t id) { - sys::Mutex::ScopedLock l(lock); - expiryId = id; -} - -// Called while giving an update -uint64_t ExpiryPolicy::getId() const { - sys::Mutex::ScopedLock l(lock); - return expiryId; -} - -// Called in enqueuing connection thread -void ExpiryPolicy::willExpire(broker::Message& m) { - uint64_t id; - { - // When messages are fanned out to multiple queues, update sends - // them as independenty messages so we can have multiple messages - // with the same expiry ID. - // - sys::Mutex::ScopedLock l(lock); - id = expiryId++; - if (!id) { // This is an update of an already-expired message. - m.setExpiryPolicy(expiredPolicy); - } - else { - assert(unexpiredByMessage.find(&m) == unexpiredByMessage.end()); - // If this is an update, the id may already exist - unexpiredById.insert(IdMessageMap::value_type(id, &m)); - unexpiredByMessage[&m] = id; - } - } - timer.add(new ExpiryTask(this, id, m.getExpiration())); -} - -// Called in dequeueing connection thread -void ExpiryPolicy::forget(broker::Message& m) { - sys::Mutex::ScopedLock l(lock); - MessageIdMap::iterator i = unexpiredByMessage.find(&m); - assert(i != unexpiredByMessage.end()); - unexpiredById.erase(i->second); - unexpiredByMessage.erase(i); -} - -// Called in dequeueing connection or cleanup thread. bool ExpiryPolicy::hasExpired(broker::Message& m) { - sys::Mutex::ScopedLock l(lock); - return unexpiredByMessage.find(&m) == unexpiredByMessage.end(); -} - -// Called in timer thread -void ExpiryPolicy::sendExpire(uint64_t id) { - { - sys::Mutex::ScopedLock l(lock); - // Don't multicast an expiry notice if message is already forgotten. - if (unexpiredById.find(id) == unexpiredById.end()) return; - } - mcast.mcastControl(framing::ClusterMessageExpiredBody(framing::ProtocolVersion(), id), memberId); + return m.getExpiration() < cluster.getClusterTime(); } -// Called in CPG deliver thread. -void ExpiryPolicy::deliverExpire(uint64_t id) { - sys::Mutex::ScopedLock l(lock); - std::pair<IdMessageMap::iterator, IdMessageMap::iterator> expired = unexpiredById.equal_range(id); - IdMessageMap::iterator i = expired.first; - while (i != expired.second) { - i->second->setExpiryPolicy(expiredPolicy); // hasExpired() == true; - unexpiredByMessage.erase(i->second); - unexpiredById.erase(i++); - } +sys::AbsTime ExpiryPolicy::getCurrentTime() { + return cluster.getClusterTime(); } -// Called in update thread on the updater. -boost::optional<uint64_t> ExpiryPolicy::getId(broker::Message& m) { - sys::Mutex::ScopedLock l(lock); - MessageIdMap::iterator i = unexpiredByMessage.find(&m); - return i == unexpiredByMessage.end() ? boost::optional<uint64_t>() : i->second; -} - -bool ExpiryPolicy::Expired::hasExpired(broker::Message&) { return true; } -void ExpiryPolicy::Expired::willExpire(broker::Message&) { } - }} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/ExpiryPolicy.h b/cpp/src/qpid/cluster/ExpiryPolicy.h index 77a656aa68..d8ddbca8b3 100644 --- a/cpp/src/qpid/cluster/ExpiryPolicy.h +++ b/cpp/src/qpid/cluster/ExpiryPolicy.h @@ -36,12 +36,8 @@ namespace broker { class Message; } -namespace sys { -class Timer; -} - namespace cluster { -class Multicaster; +class Cluster; /** * Cluster expiry policy @@ -49,43 +45,13 @@ class Multicaster; class ExpiryPolicy : public broker::ExpiryPolicy { public: - ExpiryPolicy(Multicaster&, const MemberId&, sys::Timer&); + ExpiryPolicy(Cluster& cluster); - void willExpire(broker::Message&); bool hasExpired(broker::Message&); - void forget(broker::Message&); - - // Send expiration notice to cluster. - void sendExpire(uint64_t); + qpid::sys::AbsTime getCurrentTime(); - // Cluster delivers expiry notice. - void deliverExpire(uint64_t); - - void setId(uint64_t id); - uint64_t getId() const; - - boost::optional<uint64_t> getId(broker::Message&); - private: - typedef std::map<broker::Message*, uint64_t> MessageIdMap; - // When messages are fanned out to multiple queues, update sends - // them as independenty messages so we can have multiple messages - // with the same expiry ID. - typedef std::multimap<uint64_t, broker::Message*> IdMessageMap; - - struct Expired : public broker::ExpiryPolicy { - bool hasExpired(broker::Message&); - void willExpire(broker::Message&); - }; - - mutable sys::Mutex lock; - MessageIdMap unexpiredByMessage; - IdMessageMap unexpiredById; - uint64_t expiryId; - boost::intrusive_ptr<Expired> expiredPolicy; - Multicaster& mcast; - MemberId memberId; - sys::Timer& timer; + Cluster& cluster; }; }} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/FailoverExchange.cpp b/cpp/src/qpid/cluster/FailoverExchange.cpp index 84232dac1b..cfbe34a460 100644 --- a/cpp/src/qpid/cluster/FailoverExchange.cpp +++ b/cpp/src/qpid/cluster/FailoverExchange.cpp @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -39,8 +39,10 @@ using namespace broker; using namespace framing; const string FailoverExchange::typeName("amq.failover"); - -FailoverExchange::FailoverExchange(management::Manageable* parent, Broker* b) : Exchange(typeName, parent, b ) { + +FailoverExchange::FailoverExchange(management::Manageable* parent, Broker* b) + : Exchange(typeName, parent, b ), ready(false) +{ if (mgmtExchange != 0) mgmtExchange->set_type(typeName); } @@ -53,16 +55,17 @@ void FailoverExchange::setUrls(const vector<Url>& u) { void FailoverExchange::updateUrls(const vector<Url>& u) { Lock l(lock); urls=u; - if (urls.empty()) return; - std::for_each(queues.begin(), queues.end(), - boost::bind(&FailoverExchange::sendUpdate, this, _1)); + if (ready && !urls.empty()) { + std::for_each(queues.begin(), queues.end(), + boost::bind(&FailoverExchange::sendUpdate, this, _1)); + } } string FailoverExchange::getType() const { return typeName; } bool FailoverExchange::bind(Queue::shared_ptr queue, const string&, const framing::FieldTable*) { Lock l(lock); - sendUpdate(queue); + if (ready) sendUpdate(queue); return queues.insert(queue).second; } @@ -84,7 +87,7 @@ void FailoverExchange::sendUpdate(const Queue::shared_ptr& queue) { // Called with lock held. if (urls.empty()) return; framing::Array array(0x95); - for (Urls::const_iterator i = urls.begin(); i != urls.end(); ++i) + for (Urls::const_iterator i = urls.begin(); i != urls.end(); ++i) array.add(boost::shared_ptr<Str16Value>(new Str16Value(i->str()))); const ProtocolVersion v; boost::intrusive_ptr<Message> msg(new Message); @@ -96,9 +99,12 @@ void FailoverExchange::sendUpdate(const Queue::shared_ptr& queue) { header.get<MessageProperties>(true)->getApplicationHeaders().setArray(typeName, array); AMQFrame headerFrame(header); headerFrame.setFirstSegment(false); - msg->getFrames().append(headerFrame); + msg->getFrames().append(headerFrame); DeliverableMessage(msg).deliverTo(queue); } +void FailoverExchange::setReady() { + ready = true; +} }} // namespace cluster diff --git a/cpp/src/qpid/cluster/FailoverExchange.h b/cpp/src/qpid/cluster/FailoverExchange.h index 2e1edfc0ae..c3e50c6929 100644 --- a/cpp/src/qpid/cluster/FailoverExchange.h +++ b/cpp/src/qpid/cluster/FailoverExchange.h @@ -10,9 +10,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -46,6 +46,8 @@ class FailoverExchange : public broker::Exchange void setUrls(const std::vector<Url>&); /** Set the URLs and send an update.*/ void updateUrls(const std::vector<Url>&); + /** Flag the failover exchange as ready to generate updates (caught up) */ + void setReady(); // Exchange overrides std::string getType() const; @@ -56,7 +58,7 @@ class FailoverExchange : public broker::Exchange private: void sendUpdate(const boost::shared_ptr<broker::Queue>&); - + typedef sys::Mutex::ScopedLock Lock; typedef std::vector<Url> Urls; typedef std::set<boost::shared_ptr<broker::Queue> > Queues; @@ -64,7 +66,7 @@ class FailoverExchange : public broker::Exchange sys::Mutex lock; Urls urls; Queues queues; - + bool ready; }; }} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/Multicaster.cpp b/cpp/src/qpid/cluster/Multicaster.cpp index 8916de9628..217641841c 100644 --- a/cpp/src/qpid/cluster/Multicaster.cpp +++ b/cpp/src/qpid/cluster/Multicaster.cpp @@ -21,6 +21,7 @@ #include "qpid/cluster/Multicaster.h" #include "qpid/cluster/Cpg.h" +#include "qpid/cluster/Cluster.h" #include "qpid/log/Statement.h" #include "qpid/framing/AMQBody.h" #include "qpid/framing/AMQFrame.h" @@ -58,7 +59,7 @@ void Multicaster::mcast(const Event& e) { return; } } - QPID_LOG(trace, "MCAST " << e); + QPID_LOG_IF(trace, e.isControl() && Cluster::loggable(e.getFrame()), "MCAST " << e); if (bypass) { // direct, don't queue iovec iov = e.toIovec(); while (!cpg.mcast(&iov, 1)) diff --git a/cpp/src/qpid/cluster/OutputInterceptor.cpp b/cpp/src/qpid/cluster/OutputInterceptor.cpp index 1354dab17b..4bf03eefa2 100644 --- a/cpp/src/qpid/cluster/OutputInterceptor.cpp +++ b/cpp/src/qpid/cluster/OutputInterceptor.cpp @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -45,12 +45,11 @@ void OutputInterceptor::send(framing::AMQFrame& f) { } void OutputInterceptor::activateOutput() { - if (parent.isCatchUp()) { - sys::Mutex::ScopedLock l(lock); + sys::Mutex::ScopedLock l(lock); + if (parent.isCatchUp()) next->activateOutput(); - } else - sendDoOutput(sendMax); + sendDoOutput(sendMax, l); } void OutputInterceptor::abort() { @@ -66,29 +65,38 @@ void OutputInterceptor::giveReadCredit(int32_t credit) { } // Called in write thread when the IO layer has no more data to write. -// We do nothing in the write thread, we run doOutput only on delivery -// of doOutput requests. -bool OutputInterceptor::doOutput() { return false; } +// We only process IO callbacks in the write thread during catch-up. +// Normally we run doOutput only on delivery of doOutput requests. +bool OutputInterceptor::doOutput() { + parent.doCatchupIoCallbacks(); + return false; +} -// Send output up to limit, calculate new limit. +// Send output up to limit, calculate new limit. void OutputInterceptor::deliverDoOutput(uint32_t limit) { + sys::Mutex::ScopedLock l(lock); sentDoOutput = false; sendMax = limit; size_t newLimit = limit; if (parent.isLocal()) { - size_t buffered = getBuffered(); + size_t buffered = next->getBuffered(); if (buffered == 0 && sent == sendMax) // Could have sent more, increase the limit. - newLimit = sendMax*2; + newLimit = sendMax*2; else if (buffered > 0 && sent > 1) // Data left unsent, reduce the limit. newLimit = (sendMax + sent) / 2; } sent = 0; - while (sent < limit && parent.getBrokerConnection()->doOutput()) + while (sent < limit) { + { + sys::Mutex::ScopedUnlock u(lock); + if (!parent.getBrokerConnection()->doOutput()) break; + } ++sent; - if (sent == limit) sendDoOutput(newLimit); + } + if (sent == limit) sendDoOutput(newLimit, l); } -void OutputInterceptor::sendDoOutput(size_t newLimit) { +void OutputInterceptor::sendDoOutput(size_t newLimit, const sys::Mutex::ScopedLock&) { if (parent.isLocal() && !sentDoOutput && !closing) { sentDoOutput = true; parent.getCluster().getMulticast().mcastControl( @@ -97,6 +105,7 @@ void OutputInterceptor::sendDoOutput(size_t newLimit) { } } +// Called in connection thread when local connection closes. void OutputInterceptor::closeOutput() { sys::Mutex::ScopedLock l(lock); closing = true; diff --git a/cpp/src/qpid/cluster/OutputInterceptor.h b/cpp/src/qpid/cluster/OutputInterceptor.h index 65bd82a4fc..3abf5273a0 100644 --- a/cpp/src/qpid/cluster/OutputInterceptor.h +++ b/cpp/src/qpid/cluster/OutputInterceptor.h @@ -10,9 +10,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -58,13 +58,13 @@ class OutputInterceptor : public sys::ConnectionOutputHandler { uint32_t getSendMax() const { return sendMax; } void setSendMax(uint32_t sendMax_) { sendMax=sendMax_; } - + cluster::Connection& parent; - + private: typedef sys::Mutex::ScopedLock Locker; - void sendDoOutput(size_t newLimit); + void sendDoOutput(size_t newLimit, const sys::Mutex::ScopedLock&); mutable sys::Mutex lock; bool closing; diff --git a/cpp/src/qpid/cluster/SecureConnectionFactory.cpp b/cpp/src/qpid/cluster/SecureConnectionFactory.cpp index 6ddef66226..2672d8360c 100644 --- a/cpp/src/qpid/cluster/SecureConnectionFactory.cpp +++ b/cpp/src/qpid/cluster/SecureConnectionFactory.cpp @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -48,7 +48,7 @@ SecureConnectionFactory::create(ProtocolVersion v, sys::OutputControl& out, cons if (clusterCodec) { SecureConnectionPtr sc(new SecureConnection()); clusterCodec->setSecureConnection(sc.get()); - sc->setCodec(codec); + sc->setCodec(codec); return sc.release(); } return 0; @@ -63,7 +63,7 @@ SecureConnectionFactory::create(sys::OutputControl& out, const std::string& id, if (clusterCodec) { SecureConnectionPtr sc(new SecureConnection()); clusterCodec->setSecureConnection(sc.get()); - sc->setCodec(codec); + sc->setCodec(codec); return sc.release(); } return 0; diff --git a/cpp/src/qpid/cluster/UpdateClient.cpp b/cpp/src/qpid/cluster/UpdateClient.cpp index 8f751add9b..2446c12f2b 100644 --- a/cpp/src/qpid/cluster/UpdateClient.cpp +++ b/cpp/src/qpid/cluster/UpdateClient.cpp @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -26,9 +26,9 @@ #include "qpid/cluster/Decoder.h" #include "qpid/cluster/ExpiryPolicy.h" #include "qpid/cluster/UpdateDataExchange.h" -#include "qpid/client/SessionBase_0_10Access.h" -#include "qpid/client/ConnectionAccess.h" -#include "qpid/client/SessionImpl.h" +#include "qpid/client/SessionBase_0_10Access.h" +#include "qpid/client/ConnectionAccess.h" +#include "qpid/client/SessionImpl.h" #include "qpid/client/ConnectionImpl.h" #include "qpid/client/Future.h" #include "qpid/broker/Broker.h" @@ -45,10 +45,13 @@ #include "qpid/broker/SessionState.h" #include "qpid/broker/TxOpVisitor.h" #include "qpid/broker/DtxAck.h" +#include "qpid/broker/DtxBuffer.h" +#include "qpid/broker/DtxWorkRecord.h" #include "qpid/broker/TxAccept.h" #include "qpid/broker/TxPublish.h" #include "qpid/broker/RecoveredDequeue.h" #include "qpid/broker/RecoveredEnqueue.h" +#include "qpid/broker/StatefulQueueObserver.h" #include "qpid/framing/MessageTransferBody.h" #include "qpid/framing/ClusterConnectionMembershipBody.h" #include "qpid/framing/ClusterConnectionShadowReadyBody.h" @@ -64,6 +67,7 @@ #include <boost/bind.hpp> #include <boost/cast.hpp> #include <algorithm> +#include <iterator> #include <sstream> namespace qpid { @@ -82,11 +86,20 @@ using namespace framing; namespace arg=client::arg; using client::SessionBase_0_10Access; +// Reserved exchange/queue name for catch-up, avoid clashes with user queues/exchanges. +const std::string UpdateClient::UPDATE("x-qpid.cluster-update"); +// Name for header used to carry expiration information. +const std::string UpdateClient::X_QPID_EXPIRATION = "x-qpid.expiration"; +// Headers used to flag headers/properties added by the UpdateClient so they can be +// removed on the other side. +const std::string UpdateClient::X_QPID_NO_MESSAGE_PROPS = "x-qpid.no-message-props"; +const std::string UpdateClient::X_QPID_NO_HEADERS = "x-qpid.no-headers"; + std::ostream& operator<<(std::ostream& o, const UpdateClient& c) { return o << "cluster(" << c.updaterId << " UPDATER)"; } -struct ClusterConnectionProxy : public AMQP_AllProxy::ClusterConnection, public framing::FrameHandler +struct ClusterConnectionProxy : public AMQP_AllProxy::ClusterConnection, public framing::FrameHandler { boost::shared_ptr<qpid::client::ConnectionImpl> connection; @@ -120,7 +133,7 @@ void send(client::AsyncSession& s, const AMQBody& body) { // TODO aconway 2008-09-24: optimization: update connections/sessions in parallel. UpdateClient::UpdateClient(const MemberId& updater, const MemberId& updatee, const Url& url, - broker::Broker& broker, const ClusterMap& m, ExpiryPolicy& expiry_, + broker::Broker& broker, const ClusterMap& m, ExpiryPolicy& expiry_, const Cluster::ConnectionVector& cons, Decoder& decoder_, const boost::function<void()>& ok, const boost::function<void(const std::exception&)>& fail, @@ -134,13 +147,11 @@ UpdateClient::UpdateClient(const MemberId& updater, const MemberId& updatee, con UpdateClient::~UpdateClient() {} -// Reserved exchange/queue name for catch-up, avoid clashes with user queues/exchanges. -const std::string UpdateClient::UPDATE("qpid.cluster-update"); - void UpdateClient::run() { try { connection.open(updateeUrl, connectionSettings); session = connection.newSession(UPDATE); + session.sync(); update(); done(); } catch (const std::exception& e) { @@ -154,6 +165,13 @@ void UpdateClient::update() { << " at " << updateeUrl); Broker& b = updaterBroker; + if(b.getExpiryPolicy()) { + QPID_LOG(debug, *this << "Updating updatee with cluster time"); + qpid::sys::AbsTime clusterTime = b.getExpiryPolicy()->getCurrentTime(); + int64_t time = qpid::sys::Duration(qpid::sys::EPOCH, clusterTime); + ClusterConnectionProxy(session).clock(time); + } + updateManagementSetupState(); b.getExchanges().eachExchange(boost::bind(&UpdateClient::updateExchange, this, _1)); @@ -163,16 +181,20 @@ void UpdateClient::update() { // longer on their original queue. session.queueDeclare(arg::queue=UPDATE, arg::autoDelete=true); session.sync(); + std::for_each(connections.begin(), connections.end(), boost::bind(&UpdateClient::updateConnection, this, _1)); - session.queueDelete(arg::queue=UPDATE); + + // some Queue Observers need session state & msgs synced first, so sync observers now + b.getQueues().eachQueue(boost::bind(&UpdateClient::updateQueueObservers, this, _1)); // Update queue listeners: must come after sessions so consumerNumbering is populated b.getQueues().eachQueue(boost::bind(&UpdateClient::updateQueueListeners, this, _1)); - ClusterConnectionProxy(session).expiryId(expiry.getId()); updateLinks(); updateManagementAgent(); + updateDtxManager(); + session.queueDelete(arg::queue=UPDATE); session.close(); @@ -184,7 +206,7 @@ void UpdateClient::update() { // NOTE: connection will be closed from the other end, don't close // it here as that causes a race. - + // TODO aconway 2010-03-15: This sleep avoids the race condition // described in // https://bugzilla.redhat.com/show_bug.cgi?id=568831. // It allows the connection to fully close before destroying the @@ -276,7 +298,7 @@ class MessageUpdater { framing::SequenceNumber lastPos; client::AsyncSession session; ExpiryPolicy& expiry; - + public: MessageUpdater(const string& q, const client::AsyncSession s, ExpiryPolicy& expiry_) : queue(q), haveLastPos(false), session(s), expiry(expiry_) { @@ -293,7 +315,6 @@ class MessageUpdater { } } - void updateQueuedMessage(const broker::QueuedMessage& message) { // Send the queue position if necessary. if (!haveLastPos || message.position - lastPos != 1) { @@ -302,10 +323,23 @@ class MessageUpdater { } lastPos = message.position; - // Send the expiry ID if necessary. - if (message.payload->getProperties<DeliveryProperties>()->getTtl()) { - boost::optional<uint64_t> expiryId = expiry.getId(*message.payload); - ClusterConnectionProxy(session).expiryId(expiryId?*expiryId:0); + // if the ttl > 0, we need to send the calculated expiration time to the updatee + const DeliveryProperties* dprops = + message.payload->getProperties<DeliveryProperties>(); + if (dprops && dprops->getTtl() > 0) { + bool hadMessageProps = + message.payload->hasProperties<framing::MessageProperties>(); + const framing::MessageProperties* mprops = + message.payload->getProperties<framing::MessageProperties>(); + bool hadApplicationHeaders = mprops->hasApplicationHeaders(); + message.payload->insertCustomProperty(UpdateClient::X_QPID_EXPIRATION, + sys::Duration(sys::EPOCH, message.payload->getExpiration())); + // If message properties or application headers didn't exist + // prior to us adding data, we want to remove them on the other side. + if (!hadMessageProps) + message.payload->insertCustomProperty(UpdateClient::X_QPID_NO_MESSAGE_PROPS, 0); + else if (!hadApplicationHeaders) + message.payload->insertCustomProperty(UpdateClient::X_QPID_NO_HEADERS, 0); } // We can't send a broker::Message via the normal client API, @@ -318,7 +352,7 @@ class MessageUpdater { framing::MessageTransferBody transfer( *message.payload->getFrames().as<framing::MessageTransferBody>()); transfer.setDestination(UpdateClient::UPDATE); - + sb.get()->send(transfer, message.payload->getFrames(), !message.payload->isContentReleased()); if (message.payload->isContentReleased()){ @@ -326,9 +360,10 @@ class MessageUpdater { uint16_t maxContentSize = maxFrameSize - AMQFrame::frameOverhead(); bool morecontent = true; for (uint64_t offset = 0; morecontent; offset += maxContentSize) - { + { AMQFrame frame((AMQContentBody())); - morecontent = message.payload->getContentFrame(*(message.queue), frame, maxContentSize, offset); + morecontent = message.payload->getContentFrame( + *(message.queue), frame, maxContentSize, offset); sb.get()->sendRawFrame(frame); } } @@ -357,6 +392,8 @@ void UpdateClient::updateQueue(client::AsyncSession& s, const boost::shared_ptr< if (qpid::broker::Fairshare::getState(q->getMessages(), priority, count)) { ClusterConnectionProxy(s).queueFairshareState(q->getName(), priority, count); } + + ClusterConnectionProxy(s).queueDequeueSincePurgeState(q->getName(), q->getDequeueSincePurge()); } void UpdateClient::updateExclusiveQueue(const boost::shared_ptr<broker::Queue>& q) { @@ -372,7 +409,11 @@ void UpdateClient::updateNonExclusiveQueue(const boost::shared_ptr<broker::Queue } void UpdateClient::updateBinding(client::AsyncSession& s, const std::string& queue, const QueueBinding& binding) { - s.exchangeBind(queue, binding.exchange, binding.key, binding.args); + if (binding.exchange.size()) + s.exchangeBind(queue, binding.exchange, binding.key, binding.args); + //else its the default exchange and there is no need to replicate + //the binding, the creation of the queue will have done so + //automatically } void UpdateClient::updateOutputTask(const sys::OutputTask* task) { @@ -380,8 +421,8 @@ void UpdateClient::updateOutputTask(const sys::OutputTask* task) { boost::polymorphic_downcast<const SemanticState::ConsumerImpl*> (task); SemanticState::ConsumerImpl* ci = const_cast<SemanticState::ConsumerImpl*>(cci); uint16_t channel = ci->getParent().getSession().getChannel(); - ClusterConnectionProxy(shadowConnection).outputTask(channel, ci->getName()); - QPID_LOG(debug, *this << " updating output task " << ci->getName() + ClusterConnectionProxy(shadowConnection).outputTask(channel, ci->getTag()); + QPID_LOG(debug, *this << " updating output task " << ci->getTag() << " channel=" << channel); } @@ -389,7 +430,7 @@ void UpdateClient::updateConnection(const boost::intrusive_ptr<Connection>& upda QPID_LOG(debug, *this << " updating connection " << *updateConnection); assert(updateConnection->getBrokerConnection()); broker::Connection& bc = *updateConnection->getBrokerConnection(); - + // Send the management ID first on the main connection. std::string mgmtId = updateConnection->getBrokerConnection()->getMgmtId(); ClusterConnectionProxy(session).shadowPrepare(mgmtId); @@ -426,7 +467,7 @@ void UpdateClient::updateSession(broker::SessionHandler& sh) { QPID_LOG(debug, *this << " updating session " << ss->getId()); - // Create a client session to update session state. + // Create a client session to update session state. boost::shared_ptr<client::ConnectionImpl> cimpl = client::ConnectionAccess::getImpl(shadowConnection); boost::shared_ptr<client::SessionImpl> simpl = cimpl->newSession(ss->getId().getName(), ss->getTimeout(), sh.getChannel()); simpl->disableAutoDetach(); @@ -445,19 +486,19 @@ void UpdateClient::updateSession(broker::SessionHandler& sh) { QPID_LOG(debug, *this << " updating unacknowledged messages."); broker::DeliveryRecords& drs = ss->getSemanticState().getUnacked(); std::for_each(drs.begin(), drs.end(), - boost::bind(&UpdateClient::updateUnacked, this, _1)); + boost::bind(&UpdateClient::updateUnacked, this, _1, shadowSession)); - updateTxState(ss->getSemanticState()); // Tx transaction state. + updateTransactionState(ss->getSemanticState()); // Adjust command counter for message in progress, will be sent after state update. boost::intrusive_ptr<Message> inProgress = ss->getMessageInProgress(); SequenceNumber received = ss->receiverGetReceived().command; - if (inProgress) + if (inProgress) --received; // Sync the session to ensure all responses from broker have been processed. shadowSession.sync(); - + // Reset command-sequence state. proxy.sessionState( ss->senderGetReplayPoint().command, @@ -466,7 +507,8 @@ void UpdateClient::updateSession(broker::SessionHandler& sh) { std::max(received, ss->receiverGetExpected().command), received, ss->receiverGetUnknownComplete(), - ss->receiverGetIncomplete() + ss->receiverGetIncomplete(), + ss->getSemanticState().getDtxSelected() ); // Send frames for partial message in progress. @@ -479,13 +521,13 @@ void UpdateClient::updateSession(broker::SessionHandler& sh) { void UpdateClient::updateConsumer( const broker::SemanticState::ConsumerImpl::shared_ptr& ci) { - QPID_LOG(debug, *this << " updating consumer " << ci->getName() << " on " + QPID_LOG(debug, *this << " updating consumer " << ci->getTag() << " on " << shadowSession.getId()); using namespace message; shadowSession.messageSubscribe( arg::queue = ci->getQueue()->getName(), - arg::destination = ci->getName(), + arg::destination = ci->getTag(), arg::acceptMode = ci->isAckExpected() ? ACCEPT_MODE_EXPLICIT : ACCEPT_MODE_NONE, arg::acquireMode = ci->isAcquire() ? ACQUIRE_MODE_PRE_ACQUIRED : ACQUIRE_MODE_NOT_ACQUIRED, arg::exclusive = ci->isExclusive(), @@ -493,29 +535,32 @@ void UpdateClient::updateConsumer( arg::resumeTtl = ci->getResumeTtl(), arg::arguments = ci->getArguments() ); - shadowSession.messageSetFlowMode(ci->getName(), ci->isWindowing() ? FLOW_MODE_WINDOW : FLOW_MODE_CREDIT); - shadowSession.messageFlow(ci->getName(), CREDIT_UNIT_MESSAGE, ci->getMsgCredit()); - shadowSession.messageFlow(ci->getName(), CREDIT_UNIT_BYTE, ci->getByteCredit()); + shadowSession.messageSetFlowMode(ci->getTag(), ci->isWindowing() ? FLOW_MODE_WINDOW : FLOW_MODE_CREDIT); + shadowSession.messageFlow(ci->getTag(), CREDIT_UNIT_MESSAGE, ci->getMsgCredit()); + shadowSession.messageFlow(ci->getTag(), CREDIT_UNIT_BYTE, ci->getByteCredit()); ClusterConnectionProxy(shadowSession).consumerState( - ci->getName(), + ci->getTag(), ci->isBlocked(), ci->isNotifyEnabled(), ci->position ); consumerNumbering.add(ci.get()); - QPID_LOG(debug, *this << " updated consumer " << ci->getName() + QPID_LOG(debug, *this << " updated consumer " << ci->getTag() << " on " << shadowSession.getId()); } - -void UpdateClient::updateUnacked(const broker::DeliveryRecord& dr) { - if (!dr.isEnded() && dr.isAcquired() && dr.getMessage().payload) { + +void UpdateClient::updateUnacked(const broker::DeliveryRecord& dr, + client::AsyncSession& updateSession) +{ + if (!dr.isEnded() && dr.isAcquired()) { + assert(dr.getMessage().payload); // If the message is acquired then it is no longer on the // updatees queue, put it on the update queue for updatee to pick up. // - MessageUpdater(UPDATE, shadowSession, expiry).updateQueuedMessage(dr.getMessage()); + MessageUpdater(UPDATE, updateSession, expiry).updateQueuedMessage(dr.getMessage()); } - ClusterConnectionProxy(shadowSession).deliveryRecord( + ClusterConnectionProxy(updateSession).deliveryRecord( dr.getQueue()->getName(), dr.getMessage().position, dr.getTag(), @@ -536,10 +581,12 @@ class TxOpUpdater : public broker::TxOpConstVisitor, public MessageUpdater { TxOpUpdater(UpdateClient& dc, client::AsyncSession s, ExpiryPolicy& expiry) : MessageUpdater(UpdateClient::UPDATE, s, expiry), parent(dc), session(s), proxy(s) {} - void operator()(const broker::DtxAck& ) { - throw InternalErrorException("DTX transactions not currently supported by cluster."); + void operator()(const broker::DtxAck& ack) { + std::for_each(ack.getPending().begin(), ack.getPending().end(), + boost::bind(&UpdateClient::updateUnacked, &parent, _1, session)); + proxy.dtxAck(); } - + void operator()(const broker::RecoveredDequeue& rdeq) { updateMessage(rdeq.getMessage()); proxy.txEnqueue(rdeq.getQueue()->getName()); @@ -554,13 +601,18 @@ class TxOpUpdater : public broker::TxOpConstVisitor, public MessageUpdater { proxy.txAccept(txAccept.getAcked()); } + typedef std::list<Queue::shared_ptr> QueueList; + + void copy(const QueueList& l, Array& a) { + for (QueueList::const_iterator i = l.begin(); i!=l.end(); ++i) + a.push_back(Array::ValuePtr(new Str8Value((*i)->getName()))); + } + void operator()(const broker::TxPublish& txPub) { updateMessage(txPub.getMessage()); - typedef std::list<Queue::shared_ptr> QueueList; - const QueueList& qlist = txPub.getQueues(); + assert(txPub.getQueues().empty() || txPub.getPrepared().empty()); Array qarray(TYPE_CODE_STR8); - for (QueueList::const_iterator i = qlist.begin(); i != qlist.end(); ++i) - qarray.push_back(Array::ValuePtr(new Str8Value((*i)->getName()))); + copy(txPub.getQueues().empty() ? txPub.getPrepared() : txPub.getQueues(), qarray); proxy.txPublish(qarray, txPub.delivered); } @@ -569,18 +621,44 @@ class TxOpUpdater : public broker::TxOpConstVisitor, public MessageUpdater { client::AsyncSession session; ClusterConnectionProxy proxy; }; - -void UpdateClient::updateTxState(broker::SemanticState& s) { - QPID_LOG(debug, *this << " updating TX transaction state."); + +void UpdateClient::updateBufferRef(const broker::DtxBuffer::shared_ptr& dtx,bool suspended) +{ + ClusterConnectionProxy proxy(shadowSession); + broker::DtxWorkRecord* record = + updaterBroker.getDtxManager().getWork(dtx->getXid()); + proxy.dtxBufferRef(dtx->getXid(), record->indexOf(dtx), suspended); + +} + +void UpdateClient::updateTransactionState(broker::SemanticState& s) { ClusterConnectionProxy proxy(shadowSession); proxy.accumulatedAck(s.getAccumulatedAck()); - broker::TxBuffer::shared_ptr txBuffer = s.getTxBuffer(); - if (txBuffer) { + broker::TxBuffer::shared_ptr tx = s.getTxBuffer(); + broker::DtxBuffer::shared_ptr dtx = s.getDtxBuffer(); + if (dtx) { + updateBufferRef(dtx, false); // Current transaction. + } else if (tx) { proxy.txStart(); TxOpUpdater updater(*this, shadowSession, expiry); - txBuffer->accept(updater); + tx->accept(updater); proxy.txEnd(); } + for (SemanticState::DtxBufferMap::iterator i = s.getSuspendedXids().begin(); + i != s.getSuspendedXids().end(); + ++i) + { + updateBufferRef(i->second, true); + } +} + +void UpdateClient::updateDtxBuffer(const broker::DtxBuffer::shared_ptr& dtx) { + ClusterConnectionProxy proxy(session); + proxy.dtxStart( + dtx->getXid(), dtx->isEnded(), dtx->isSuspended(), dtx->isFailed(), dtx->isExpired()); + TxOpUpdater updater(*this, session, expiry); + dtx->accept(updater); + proxy.dtxEnd(); } void UpdateClient::updateQueueListeners(const boost::shared_ptr<broker::Queue>& queue) { @@ -615,4 +693,35 @@ void UpdateClient::updateBridge(const boost::shared_ptr<broker::Bridge>& bridge) ClusterConnectionProxy(session).config(encode(*bridge)); } +void UpdateClient::updateQueueObservers(const boost::shared_ptr<broker::Queue>& q) +{ + q->eachObserver(boost::bind(&UpdateClient::updateObserver, this, q, _1)); +} + +void UpdateClient::updateObserver(const boost::shared_ptr<broker::Queue>& q, + boost::shared_ptr<broker::QueueObserver> o) +{ + qpid::framing::FieldTable state; + broker::StatefulQueueObserver *so = dynamic_cast<broker::StatefulQueueObserver *>(o.get()); + if (so) { + so->getState( state ); + std::string id(so->getId()); + QPID_LOG(debug, *this << " updating queue " << q->getName() << "'s observer " << id); + ClusterConnectionProxy(session).queueObserverState( q->getName(), id, state ); + } +} + +void UpdateClient::updateDtxManager() { + broker::DtxManager& dtm = updaterBroker.getDtxManager(); + dtm.each(boost::bind(&UpdateClient::updateDtxWorkRecord, this, _1)); +} + +void UpdateClient::updateDtxWorkRecord(const broker::DtxWorkRecord& r) { + QPID_LOG(debug, *this << " updating DTX transaction: " << r.getXid()); + for (size_t i = 0; i < r.size(); ++i) + updateDtxBuffer(r[i]); + ClusterConnectionProxy(session).dtxWorkRecord( + r.getXid(), r.isPrepared(), r.getTimeout()); +} + }} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/UpdateClient.h b/cpp/src/qpid/cluster/UpdateClient.h index 7520bb82cb..481ee357c7 100644 --- a/cpp/src/qpid/cluster/UpdateClient.h +++ b/cpp/src/qpid/cluster/UpdateClient.h @@ -10,9 +10,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -34,7 +34,7 @@ namespace qpid { -class Url; +struct Url; namespace broker { @@ -42,8 +42,8 @@ class Broker; class Queue; class Exchange; class QueueBindings; -class QueueBinding; -class QueuedMessage; +struct QueueBinding; +struct QueuedMessage; class SessionHandler; class DeliveryRecord; class SessionState; @@ -51,7 +51,8 @@ class SemanticState; class Decoder; class Link; class Bridge; - +class QueueObserver; +class DtxBuffer; } // namespace broker namespace cluster { @@ -68,21 +69,26 @@ class ExpiryPolicy; class UpdateClient : public sys::Runnable { public: static const std::string UPDATE; // Name for special update queue and exchange. + static const std::string X_QPID_EXPIRATION; // Update message expiration + // Flag to remove props/headers that were added by the UpdateClient + static const std::string X_QPID_NO_MESSAGE_PROPS; + static const std::string X_QPID_NO_HEADERS; + static client::Connection catchUpConnection(); - + UpdateClient(const MemberId& updater, const MemberId& updatee, const Url&, broker::Broker& donor, const ClusterMap& map, ExpiryPolicy& expiry, const std::vector<boost::intrusive_ptr<Connection> >&, Decoder&, const boost::function<void()>& done, const boost::function<void(const std::exception&)>& fail, - const client::ConnectionSettings& + const client::ConnectionSettings& ); ~UpdateClient(); void update(); void run(); // Will delete this when finished. - void updateUnacked(const broker::DeliveryRecord&); + void updateUnacked(const broker::DeliveryRecord&, client::AsyncSession&); private: void updateQueue(client::AsyncSession&, const boost::shared_ptr<broker::Queue>&); @@ -94,7 +100,8 @@ class UpdateClient : public sys::Runnable { void updateBinding(client::AsyncSession&, const std::string& queue, const broker::QueueBinding& binding); void updateConnection(const boost::intrusive_ptr<Connection>& connection); void updateSession(broker::SessionHandler& s); - void updateTxState(broker::SemanticState& s); + void updateBufferRef(const broker::DtxBuffer::shared_ptr& dtx, bool suspended); + void updateTransactionState(broker::SemanticState& s); void updateOutputTask(const sys::OutputTask* task); void updateConsumer(const broker::SemanticState::ConsumerImpl::shared_ptr&); void updateQueueListeners(const boost::shared_ptr<broker::Queue>&); @@ -104,6 +111,11 @@ class UpdateClient : public sys::Runnable { void updateLinks(); void updateLink(const boost::shared_ptr<broker::Link>&); void updateBridge(const boost::shared_ptr<broker::Bridge>&); + void updateQueueObservers(const boost::shared_ptr<broker::Queue>&); + void updateObserver(const boost::shared_ptr<broker::Queue>&, boost::shared_ptr<broker::QueueObserver>); + void updateDtxManager(); + void updateDtxBuffer(const boost::shared_ptr<broker::DtxBuffer>& ); + void updateDtxWorkRecord(const broker::DtxWorkRecord&); Numbering<broker::SemanticState::ConsumerImpl*> consumerNumbering; diff --git a/cpp/src/qpid/cluster/UpdateDataExchange.cpp b/cpp/src/qpid/cluster/UpdateDataExchange.cpp index 2a079b8881..e5cd82e3d3 100644 --- a/cpp/src/qpid/cluster/UpdateDataExchange.cpp +++ b/cpp/src/qpid/cluster/UpdateDataExchange.cpp @@ -36,13 +36,8 @@ const std::string UpdateDataExchange::MANAGEMENT_AGENTS_KEY("management-agents") const std::string UpdateDataExchange::MANAGEMENT_SCHEMAS_KEY("management-schemas"); const std::string UpdateDataExchange::MANAGEMENT_DELETED_OBJECTS_KEY("management-deleted-objects"); -std::ostream& operator<<(std::ostream& o, const UpdateDataExchange& c) { - return o << "cluster(" << c.clusterId << " UPDATER)"; -} - UpdateDataExchange::UpdateDataExchange(Cluster& cluster) : - Exchange(EXCHANGE_NAME, &cluster), - clusterId(cluster.getId()) + Exchange(EXCHANGE_NAME, &cluster) {} void UpdateDataExchange::route(broker::Deliverable& msg, const std::string& routingKey, @@ -62,11 +57,9 @@ void UpdateDataExchange::updateManagementAgent(management::ManagementAgent* agen framing::Buffer buf1(const_cast<char*>(managementAgents.data()), managementAgents.size()); agent->importAgents(buf1); - QPID_LOG(debug, *this << " updated management agents."); framing::Buffer buf2(const_cast<char*>(managementSchemas.data()), managementSchemas.size()); agent->importSchemas(buf2); - QPID_LOG(debug, *this << " updated management schemas."); using amqp_0_10::ListCodec; using types::Variant; @@ -78,7 +71,6 @@ void UpdateDataExchange::updateManagementAgent(management::ManagementAgent* agen new management::ManagementAgent::DeletedObject(*i))); } agent->importDeletedObjects(objects); - QPID_LOG(debug, *this << " updated management deleted objects."); } diff --git a/cpp/src/qpid/cluster/UpdateDataExchange.h b/cpp/src/qpid/cluster/UpdateDataExchange.h index 8c493e400a..d2f6c35ad0 100644 --- a/cpp/src/qpid/cluster/UpdateDataExchange.h +++ b/cpp/src/qpid/cluster/UpdateDataExchange.h @@ -74,11 +74,9 @@ class UpdateDataExchange : public broker::Exchange void updateManagementAgent(management::ManagementAgent* agent); private: - MemberId clusterId; std::string managementAgents; std::string managementSchemas; std::string managementDeletedObjects; - friend std::ostream& operator<<(std::ostream&, const UpdateDataExchange&); }; }} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/UpdateExchange.cpp b/cpp/src/qpid/cluster/UpdateExchange.cpp index 11937f296f..cb1376004e 100644 --- a/cpp/src/qpid/cluster/UpdateExchange.cpp +++ b/cpp/src/qpid/cluster/UpdateExchange.cpp @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -19,6 +19,7 @@ * */ #include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/FieldTable.h" #include "qpid/broker/Message.h" #include "UpdateExchange.h" @@ -27,6 +28,8 @@ namespace cluster { using framing::MessageTransferBody; using framing::DeliveryProperties; +using framing::MessageProperties; +using framing::FieldTable; UpdateExchange::UpdateExchange(management::Manageable* parent) : broker::Exchange(UpdateClient::UPDATE, parent), @@ -34,6 +37,7 @@ UpdateExchange::UpdateExchange(management::Manageable* parent) void UpdateExchange::setProperties(const boost::intrusive_ptr<broker::Message>& msg) { + // Copy exchange name to destination property. MessageTransferBody* transfer = msg->getMethod<MessageTransferBody>(); assert(transfer); const DeliveryProperties* props = msg->getProperties<DeliveryProperties>(); @@ -42,6 +46,23 @@ void UpdateExchange::setProperties(const boost::intrusive_ptr<broker::Message>& transfer->setDestination(props->getExchange()); else transfer->clearDestinationFlag(); -} + // Copy expiration from x-property if present. + if (msg->hasProperties<MessageProperties>()) { + const MessageProperties* mprops = msg->getProperties<MessageProperties>(); + if (mprops->hasApplicationHeaders()) { + const FieldTable& headers = mprops->getApplicationHeaders(); + if (headers.isSet(UpdateClient::X_QPID_EXPIRATION)) { + msg->setExpiration( + sys::AbsTime(sys::EPOCH, headers.getAsInt64(UpdateClient::X_QPID_EXPIRATION))); + msg->removeCustomProperty(UpdateClient::X_QPID_EXPIRATION); + // Erase props/headers that were added by the UpdateClient + if (headers.isSet(UpdateClient::X_QPID_NO_MESSAGE_PROPS)) + msg->eraseProperties<MessageProperties>(); + else if (headers.isSet(UpdateClient::X_QPID_NO_HEADERS)) + msg->clearApplicationHeadersFlag(); + } + } + } +} }} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/UpdateReceiver.h b/cpp/src/qpid/cluster/UpdateReceiver.h index 7e8ce47662..81ee3a5ffe 100644 --- a/cpp/src/qpid/cluster/UpdateReceiver.h +++ b/cpp/src/qpid/cluster/UpdateReceiver.h @@ -39,6 +39,20 @@ class UpdateReceiver { /** Management-id for the next shadow connection */ std::string nextShadowMgmtId; + + /** Record the position of a DtxBuffer in the DtxManager (xid + index) + * and the association with a session, either suspended or current. + */ + struct DtxBufferRef { + std::string xid; + uint32_t index; // Index in WorkRecord in DtxManager + bool suspended; // Is this a suspended or current transaction? + broker::SemanticState* semanticState; // Associated session + DtxBufferRef(const std::string& x, uint32_t i, bool s, broker::SemanticState* ss) + : xid(x), index(i), suspended(s), semanticState(ss) {} + }; + typedef std::vector<DtxBufferRef> DtxBuffers; + DtxBuffers dtxBuffers; }; }} // namespace qpid::cluster diff --git a/cpp/src/qpid/cluster/types.h b/cpp/src/qpid/cluster/types.h index 0795e5e77a..bfb4fd5b9e 100644 --- a/cpp/src/qpid/cluster/types.h +++ b/cpp/src/qpid/cluster/types.h @@ -24,6 +24,7 @@ #include "config.h" #include "qpid/Url.h" +#include "qpid/RefCounted.h" #include "qpid/sys/IntegerTypes.h" #include <boost/intrusive_ptr.hpp> #include <utility> diff --git a/cpp/src/qpid/console/SessionManager.cpp b/cpp/src/qpid/console/SessionManager.cpp index 80c5959417..910ae22be8 100644 --- a/cpp/src/qpid/console/SessionManager.cpp +++ b/cpp/src/qpid/console/SessionManager.cpp @@ -362,12 +362,11 @@ void SessionManager::handleCommandComplete(Broker* broker, Buffer& inBuffer, uin void SessionManager::handleClassInd(Broker* broker, Buffer& inBuffer, uint32_t) { - uint8_t kind; string packageName; string className; uint8_t hash[16]; - kind = inBuffer.getOctet(); + /*kind*/ (void) inBuffer.getOctet(); inBuffer.getShortString(packageName); inBuffer.getShortString(className); inBuffer.getBin128(hash); diff --git a/cpp/src/qpid/framing/AMQBody.h b/cpp/src/qpid/framing/AMQBody.h index 60ac2d3b7e..56d1d250c1 100644 --- a/cpp/src/qpid/framing/AMQBody.h +++ b/cpp/src/qpid/framing/AMQBody.h @@ -46,7 +46,7 @@ struct AMQBodyConstVisitor { virtual void visit(const AMQMethodBody&) = 0; }; -class AMQBody : public RefCounted { +class QPID_COMMON_CLASS_EXTERN AMQBody : public RefCounted { public: AMQBody() {} QPID_COMMON_EXTERN virtual ~AMQBody(); diff --git a/cpp/src/qpid/framing/AMQContentBody.h b/cpp/src/qpid/framing/AMQContentBody.h index 69813b221c..e25451e354 100644 --- a/cpp/src/qpid/framing/AMQContentBody.h +++ b/cpp/src/qpid/framing/AMQContentBody.h @@ -29,7 +29,7 @@ namespace qpid { namespace framing { -class AMQContentBody : public AMQBody +class QPID_COMMON_CLASS_EXTERN AMQContentBody : public AMQBody { string data; @@ -37,15 +37,15 @@ public: QPID_COMMON_EXTERN AMQContentBody(); QPID_COMMON_EXTERN AMQContentBody(const string& data); inline virtual ~AMQContentBody(){} - QPID_COMMON_EXTERN inline uint8_t type() const { return CONTENT_BODY; }; - QPID_COMMON_EXTERN inline const string& getData() const { return data; } - QPID_COMMON_EXTERN inline string& getData() { return data; } + inline uint8_t type() const { return CONTENT_BODY; }; + inline const string& getData() const { return data; } + inline string& getData() { return data; } QPID_COMMON_EXTERN uint32_t encodedSize() const; QPID_COMMON_EXTERN void encode(Buffer& buffer) const; QPID_COMMON_EXTERN void decode(Buffer& buffer, uint32_t size); QPID_COMMON_EXTERN void print(std::ostream& out) const; - QPID_COMMON_EXTERN void accept(AMQBodyConstVisitor& v) const { v.visit(*this); } - QPID_COMMON_EXTERN boost::intrusive_ptr<AMQBody> clone() const { return BodyFactory::copy(*this); } + void accept(AMQBodyConstVisitor& v) const { v.visit(*this); } + boost::intrusive_ptr<AMQBody> clone() const { return BodyFactory::copy(*this); } }; } diff --git a/cpp/src/qpid/framing/AMQFrame.cpp b/cpp/src/qpid/framing/AMQFrame.cpp index cd60cd971f..5b9673f0d0 100644 --- a/cpp/src/qpid/framing/AMQFrame.cpp +++ b/cpp/src/qpid/framing/AMQFrame.cpp @@ -139,6 +139,11 @@ bool AMQFrame::decode(Buffer& buffer) return true; } +void AMQFrame::cloneBody() +{ + body = body->clone(); +} + std::ostream& operator<<(std::ostream& out, const AMQFrame& f) { return diff --git a/cpp/src/qpid/framing/AMQFrame.h b/cpp/src/qpid/framing/AMQFrame.h index d7b04f0f65..4f6faf4199 100644 --- a/cpp/src/qpid/framing/AMQFrame.h +++ b/cpp/src/qpid/framing/AMQFrame.h @@ -33,7 +33,7 @@ namespace qpid { namespace framing { -class AMQFrame : public AMQDataBlock +class QPID_COMMON_CLASS_EXTERN AMQFrame : public AMQDataBlock { public: QPID_COMMON_EXTERN AMQFrame(const boost::intrusive_ptr<AMQBody>& b=0); @@ -59,6 +59,11 @@ class AMQFrame : public AMQDataBlock return boost::polymorphic_downcast<const T*>(getBody()); } + /** + * Take a deep copy of the body currently referenced + */ + QPID_COMMON_EXTERN void cloneBody(); + QPID_COMMON_EXTERN void encode(Buffer& buffer) const; QPID_COMMON_EXTERN bool decode(Buffer& buffer); QPID_COMMON_EXTERN uint32_t encodedSize() const; diff --git a/cpp/src/qpid/framing/AMQHeaderBody.h b/cpp/src/qpid/framing/AMQHeaderBody.h index 8d96e35720..452154eb5c 100644 --- a/cpp/src/qpid/framing/AMQHeaderBody.h +++ b/cpp/src/qpid/framing/AMQHeaderBody.h @@ -10,9 +10,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -35,7 +35,7 @@ namespace qpid { namespace framing { -class AMQHeaderBody : public AMQBody +class QPID_COMMON_CLASS_EXTERN AMQHeaderBody : public AMQBody { template <class T> struct OptProps { boost::optional<T> props; }; template <class Base, class T> @@ -58,7 +58,7 @@ class AMQHeaderBody : public AMQBody } else return Base::decode(buffer, size, type); - } + } void print(std::ostream& out) const { const boost::optional<T>& p=this->OptProps<T>::props; if (p) out << *p; @@ -77,7 +77,7 @@ class AMQHeaderBody : public AMQBody typedef PropSet<PropSet<Empty, DeliveryProperties>, MessageProperties> Properties; Properties properties; - + public: inline uint8_t type() const { return HEADER_BODY; } @@ -99,6 +99,10 @@ public: return properties.OptProps<T>::props.get_ptr(); } + template <class T> void erase() { + properties.OptProps<T>::props.reset(); + } + boost::intrusive_ptr<AMQBody> clone() const { return BodyFactory::copy(*this); } }; diff --git a/cpp/src/qpid/framing/AMQHeartbeatBody.h b/cpp/src/qpid/framing/AMQHeartbeatBody.h index 9b1fe8a4c1..19ac2be013 100644 --- a/cpp/src/qpid/framing/AMQHeartbeatBody.h +++ b/cpp/src/qpid/framing/AMQHeartbeatBody.h @@ -29,7 +29,7 @@ namespace qpid { namespace framing { -class AMQHeartbeatBody : public AMQBody +class QPID_COMMON_CLASS_EXTERN AMQHeartbeatBody : public AMQBody { public: QPID_COMMON_EXTERN virtual ~AMQHeartbeatBody(); diff --git a/cpp/src/qpid/framing/FieldTable.cpp b/cpp/src/qpid/framing/FieldTable.cpp index 023e4af819..f80d2f9fb1 100644 --- a/cpp/src/qpid/framing/FieldTable.cpp +++ b/cpp/src/qpid/framing/FieldTable.cpp @@ -129,7 +129,7 @@ FieldTable::ValuePtr FieldTable::get(const std::string& name) const namespace { template <class T> T default_value() { return T(); } template <> int default_value<int>() { return 0; } - template <> uint64_t default_value<uint64_t>() { return 0; } + //template <> uint64_t default_value<uint64_t>() { return 0; } } template <class T> @@ -198,10 +198,12 @@ void FieldTable::encode(Buffer& buffer) const { void FieldTable::decode(Buffer& buffer){ clear(); + if (buffer.available() < 4) + throw IllegalArgumentException(QPID_MSG("Not enough data for field table.")); uint32_t len = buffer.getLong(); if (len) { uint32_t available = buffer.available(); - if (available < len) + if ((available < len) || (available < 4)) throw IllegalArgumentException(QPID_MSG("Not enough data for field table.")); uint32_t count = buffer.getLong(); uint32_t leftover = available - len; diff --git a/cpp/src/qpid/framing/List.cpp b/cpp/src/qpid/framing/List.cpp index 963ebc206b..d7ea172bac 100644 --- a/cpp/src/qpid/framing/List.cpp +++ b/cpp/src/qpid/framing/List.cpp @@ -49,6 +49,9 @@ void List::encode(Buffer& buffer) const void List::decode(Buffer& buffer) { values.clear(); + if (buffer.available() < 4) + throw IllegalArgumentException(QPID_MSG("Not enough data for list, expected at least " + " 4 bytes but only " << buffer.available() << " available")); uint32_t size = buffer.getLong(); uint32_t available = buffer.available(); if (available < size) { @@ -56,6 +59,9 @@ void List::decode(Buffer& buffer) << size << " bytes but only " << available << " available")); } if (size) { + if (buffer.available() < 4) + throw IllegalArgumentException(QPID_MSG("Not enough data for list, expected at least " + " 4 bytes but only " << buffer.available() << " available")); uint32_t count = buffer.getLong(); for (uint32_t i = 0; i < count; i++) { ValuePtr value(new FieldValue); diff --git a/cpp/src/qpid/framing/MethodBodyFactory.h b/cpp/src/qpid/framing/MethodBodyFactory.h index 607ec9d959..88bc444795 100644 --- a/cpp/src/qpid/framing/MethodBodyFactory.h +++ b/cpp/src/qpid/framing/MethodBodyFactory.h @@ -22,6 +22,7 @@ * */ #include "qpid/framing/amqp_types.h" +#include "qpid/framing/AMQBody.h" #include <boost/intrusive_ptr.hpp> namespace qpid { diff --git a/cpp/src/qpid/framing/SendContent.h b/cpp/src/qpid/framing/SendContent.h index 745c948c9e..1c464b9c8b 100644 --- a/cpp/src/qpid/framing/SendContent.h +++ b/cpp/src/qpid/framing/SendContent.h @@ -37,7 +37,7 @@ namespace framing { */ class SendContent { - mutable FrameHandler& handler; + FrameHandler& handler; const uint16_t maxFrameSize; uint expectedFrameCount; uint frameCount; diff --git a/cpp/src/qpid/framing/TransferContent.h b/cpp/src/qpid/framing/TransferContent.h index 5fe1a513a9..9a698a1823 100644 --- a/cpp/src/qpid/framing/TransferContent.h +++ b/cpp/src/qpid/framing/TransferContent.h @@ -32,7 +32,7 @@ namespace qpid { namespace framing { /** Message content */ -class TransferContent : public MethodContent +class QPID_COMMON_CLASS_EXTERN TransferContent : public MethodContent { AMQHeaderBody header; std::string data; diff --git a/cpp/src/qpid/framing/Uuid.cpp b/cpp/src/qpid/framing/Uuid.cpp index 945c0a4d24..b3d1e2e1e4 100644 --- a/cpp/src/qpid/framing/Uuid.cpp +++ b/cpp/src/qpid/framing/Uuid.cpp @@ -59,7 +59,9 @@ void Uuid::clear() { // Force int 0/!0 to false/true; avoids compile warnings. bool Uuid::isNull() const { - return !!uuid_is_null(data()); + // This const cast is for Solaris which has a + // uuid_is_null that takes a non const argument + return !!uuid_is_null(const_cast<uint8_t*>(data())); } void Uuid::encode(Buffer& buf) const { diff --git a/cpp/src/qpid/log/Logger.cpp b/cpp/src/qpid/log/Logger.cpp index 2217cdddbd..1600822142 100644 --- a/cpp/src/qpid/log/Logger.cpp +++ b/cpp/src/qpid/log/Logger.cpp @@ -22,6 +22,7 @@ #include "qpid/memory.h" #include "qpid/sys/Thread.h" #include "qpid/sys/Time.h" +#include "qpid/DisableExceptionLogging.h" #include <boost/pool/detail/singleton.hpp> #include <boost/bind.hpp> #include <boost/function.hpp> @@ -48,11 +49,16 @@ Logger& Logger::instance() { } Logger::Logger() : flags(0) { + // Disable automatic logging in Exception constructors to avoid + // re-entrant use of logger singleton if there is an error in + // option parsing. + DisableExceptionLogging del; + // Initialize myself from env variables so all programs // (e.g. tests) can use logging even if they don't parse // command line args. Options opts(""); - opts.parse(0, 0); + opts.parse(0, 0); configure(opts); } @@ -73,8 +79,12 @@ void Logger::log(const Statement& s, const std::string& msg) { std::ostringstream os; if (!prefix.empty()) os << prefix << ": "; - if (flags&TIME) - qpid::sys::outputFormattedNow(os); + if (flags&TIME) { + if (flags&HIRES) + qpid::sys::outputHiresNow(os); + else + qpid::sys::outputFormattedNow(os); + } if (flags&LEVEL) os << LevelTraits::name(s.level) << " "; if (flags&THREAD) @@ -123,7 +133,8 @@ int Logger::format(const Options& opts) { bitIf(opts.time, TIME) | bitIf(opts.source, (FILE|LINE)) | bitIf(opts.function, FUNCTION) | - bitIf(opts.thread, THREAD); + bitIf(opts.thread, THREAD) | + bitIf(opts.hiresTs, HIRES); format(flags); return flags; } @@ -140,7 +151,7 @@ void Logger::configure(const Options& opts) { Options o(opts); if (o.trace) o.selectors.push_back("trace+"); - format(o); + format(o); select(Selector(o)); setPrefix(opts.prefix); options.sinkOptions->setup(this); diff --git a/cpp/src/qpid/log/Options.cpp b/cpp/src/qpid/log/Options.cpp index 24ef413cbc..0001d00bdf 100644 --- a/cpp/src/qpid/log/Options.cpp +++ b/cpp/src/qpid/log/Options.cpp @@ -38,6 +38,7 @@ Options::Options(const std::string& argv0_, const std::string& name_) : thread(false), source(false), function(false), + hiresTs(false), trace(false), sinkOptions (SinkOptions::create(argv0_)) { @@ -65,6 +66,7 @@ Options::Options(const std::string& argv0_, const std::string& name_) : ("log-source", optValue(source,"yes|no"), "Include source file:line in log messages") ("log-thread", optValue(thread,"yes|no"), "Include thread ID in log messages") ("log-function", optValue(function,"yes|no"), "Include function signature in log messages") + ("log-hires-timestamp", optValue(hiresTs,"yes|no"), "Use unformatted hi-res timestamp in log messages") ("log-prefix", optValue(prefix,"STRING"), "Prefix to append to all log messages") ; add(*sinkOptions); @@ -80,6 +82,7 @@ Options::Options(const Options &o) : thread(o.thread), source(o.source), function(o.function), + hiresTs(o.hiresTs), trace(o.trace), prefix(o.prefix), sinkOptions (SinkOptions::create(o.argv0)) @@ -97,6 +100,7 @@ Options& Options::operator=(const Options& x) { thread = x.thread; source = x.source; function = x.function; + hiresTs = x.hiresTs; trace = x.trace; prefix = x.prefix; *sinkOptions = *x.sinkOptions; diff --git a/cpp/src/qpid/log/Statement.cpp b/cpp/src/qpid/log/Statement.cpp index 6a32b50096..7dfdf08703 100644 --- a/cpp/src/qpid/log/Statement.cpp +++ b/cpp/src/qpid/log/Statement.cpp @@ -27,8 +27,6 @@ namespace qpid { namespace log { namespace { -using namespace std; - struct NonPrint { bool operator()(unsigned char c) { return !isprint(c) && !isspace(c); } }; const char hex[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; @@ -39,7 +37,7 @@ std::string quote(const std::string& str) { if (n==0) return str; std::string ret; ret.reserve(str.size()+2*n); // Avoid extra allocations. - for (string::const_iterator i = str.begin(); i != str.end(); ++i) { + for (std::string::const_iterator i = str.begin(); i != str.end(); ++i) { if (nonPrint(*i)) { ret.push_back('\\'); ret.push_back('x'); @@ -50,7 +48,6 @@ std::string quote(const std::string& str) { } return ret; } - } void Statement::log(const std::string& message) { diff --git a/cpp/src/qpid/log/posix/SinkOptions.cpp b/cpp/src/qpid/log/posix/SinkOptions.cpp index 292e9147f6..ffa7633e3b 100644 --- a/cpp/src/qpid/log/posix/SinkOptions.cpp +++ b/cpp/src/qpid/log/posix/SinkOptions.cpp @@ -180,7 +180,7 @@ qpid::log::SinkOptions& SinkOptions::operator=(const qpid::log::SinkOptions& rhs } void SinkOptions::detached(void) { - if (logToStderr && !logToStdout && !logToSyslog) { + if (logToStderr && !logToStdout && !logToSyslog && logFile.empty()) { logToStderr = false; logToSyslog = true; } diff --git a/cpp/src/qpid/log/windows/SinkOptions.cpp b/cpp/src/qpid/log/windows/SinkOptions.cpp index 28f4b267e0..0c74bea64e 100644 --- a/cpp/src/qpid/log/windows/SinkOptions.cpp +++ b/cpp/src/qpid/log/windows/SinkOptions.cpp @@ -53,7 +53,7 @@ static int eventTypes[qpid::log::LevelTraits::COUNT] = { class EventLogOutput : public qpid::log::Logger::Output { public: - EventLogOutput(const std::string& sourceName) : logHandle(0) + EventLogOutput(const std::string& /*sourceName*/) : logHandle(0) { logHandle = OpenEventLog(0, "Application"); } @@ -83,7 +83,7 @@ private: HANDLE logHandle; }; -SinkOptions::SinkOptions(const std::string& argv0) +SinkOptions::SinkOptions(const std::string& /*argv0*/) : qpid::log::SinkOptions(), logToStderr(true), logToStdout(false), diff --git a/cpp/src/qpid/log/windows/SinkOptions.h b/cpp/src/qpid/log/windows/SinkOptions.h index 605822fd46..f270c504a2 100644 --- a/cpp/src/qpid/log/windows/SinkOptions.h +++ b/cpp/src/qpid/log/windows/SinkOptions.h @@ -26,7 +26,7 @@ namespace qpid { namespace log { namespace windows { -struct SinkOptions : public qpid::log::SinkOptions { +struct QPID_COMMON_CLASS_EXTERN SinkOptions : public qpid::log::SinkOptions { QPID_COMMON_EXTERN SinkOptions(const std::string& argv0); virtual ~SinkOptions() {} diff --git a/cpp/src/qpid/management/ManagementAgent.cpp b/cpp/src/qpid/management/ManagementAgent.cpp index 23c999a98a..5799a1adca 100644 --- a/cpp/src/qpid/management/ManagementAgent.cpp +++ b/cpp/src/qpid/management/ManagementAgent.cpp @@ -31,6 +31,7 @@ #include <qpid/broker/Message.h> #include "qpid/framing/MessageTransferBody.h" #include "qpid/sys/Time.h" +#include "qpid/sys/Thread.h" #include "qpid/broker/ConnectionState.h" #include "qpid/broker/AclModule.h" #include "qpid/types/Variant.h" @@ -74,6 +75,18 @@ namespace { } return n2; } + +struct ScopedManagementContext +{ + ScopedManagementContext(const qpid::broker::ConnectionState* context) + { + setManagementExecutionContext(context); + } + ~ScopedManagementContext() + { + setManagementExecutionContext(0); + } +}; } @@ -535,6 +548,7 @@ void ManagementAgent::sendBufferLH(Buffer& buf, dp->setRoutingKey(routingKey); msg->getFrames().append(content); + msg->setIsManagementMessage(true); { sys::Mutex::ScopedUnlock u(userLock); @@ -600,7 +614,7 @@ void ManagementAgent::sendBufferLH(const string& data, props->setAppId("qmf2"); for (i = headers.begin(); i != headers.end(); ++i) { - msg->getOrInsertHeaders().setString(i->first, i->second.asString()); + msg->insertCustomProperty(i->first, i->second.asString()); } DeliveryProperties* dp = @@ -608,9 +622,10 @@ void ManagementAgent::sendBufferLH(const string& data, dp->setRoutingKey(routingKey); if (ttl_msec) { dp->setTtl(ttl_msec); - msg->setTimestamp(broker->getExpiryPolicy()); + msg->computeExpiration(broker->getExpiryPolicy()); } msg->getFrames().append(content); + msg->setIsManagementMessage(true); { sys::Mutex::ScopedUnlock u(userLock); @@ -2237,6 +2252,7 @@ void ManagementAgent::dispatchAgentCommandLH(Message& msg, bool viaLocal) uint32_t bufferLen = inBuffer.getPosition(); inBuffer.reset(); + ScopedManagementContext context((const qpid::broker::ConnectionState*) msg.getPublisher()); const framing::FieldTable *headers = msg.getApplicationHeaders(); if (headers && msg.getAppId() == "qmf2") { @@ -2740,200 +2756,14 @@ void ManagementAgent::debugSnapshot(const char* title) { title << ": new objects" << dumpVector(newManagementObjects)); } + Variant::Map ManagementAgent::toMap(const FieldTable& from) { Variant::Map map; - - for (FieldTable::const_iterator iter = from.begin(); iter != from.end(); iter++) { - const string& key(iter->first); - const FieldTable::ValuePtr& val(iter->second); - - map[key] = toVariant(val); - } - + qpid::amqp_0_10::translate(from, map); return map; } -Variant::List ManagementAgent::toList(const List& from) -{ - Variant::List _list; - - for (List::const_iterator iter = from.begin(); iter != from.end(); iter++) { - const List::ValuePtr& val(*iter); - - _list.push_back(toVariant(val)); - } - - return _list; -} - -qpid::framing::FieldTable ManagementAgent::fromMap(const Variant::Map& from) -{ - qpid::framing::FieldTable ft; - - for (Variant::Map::const_iterator iter = from.begin(); - iter != from.end(); - iter++) { - const string& key(iter->first); - const Variant& val(iter->second); - - ft.set(key, toFieldValue(val)); - } - - return ft; -} - - -List ManagementAgent::fromList(const Variant::List& from) -{ - List fa; - - for (Variant::List::const_iterator iter = from.begin(); - iter != from.end(); - iter++) { - const Variant& val(*iter); - - fa.push_back(toFieldValue(val)); - } - - return fa; -} - - -boost::shared_ptr<FieldValue> ManagementAgent::toFieldValue(const Variant& in) -{ - - switch(in.getType()) { - - case types::VAR_VOID: return boost::shared_ptr<FieldValue>(new VoidValue()); - case types::VAR_BOOL: return boost::shared_ptr<FieldValue>(new BoolValue(in.asBool())); - case types::VAR_UINT8: return boost::shared_ptr<FieldValue>(new Unsigned8Value(in.asUint8())); - case types::VAR_UINT16: return boost::shared_ptr<FieldValue>(new Unsigned16Value(in.asUint16())); - case types::VAR_UINT32: return boost::shared_ptr<FieldValue>(new Unsigned32Value(in.asUint32())); - case types::VAR_UINT64: return boost::shared_ptr<FieldValue>(new Unsigned64Value(in.asUint64())); - case types::VAR_INT8: return boost::shared_ptr<FieldValue>(new Integer8Value(in.asInt8())); - case types::VAR_INT16: return boost::shared_ptr<FieldValue>(new Integer16Value(in.asInt16())); - case types::VAR_INT32: return boost::shared_ptr<FieldValue>(new Integer32Value(in.asInt32())); - case types::VAR_INT64: return boost::shared_ptr<FieldValue>(new Integer64Value(in.asInt64())); - case types::VAR_FLOAT: return boost::shared_ptr<FieldValue>(new FloatValue(in.asFloat())); - case types::VAR_DOUBLE: return boost::shared_ptr<FieldValue>(new DoubleValue(in.asDouble())); - case types::VAR_STRING: return boost::shared_ptr<FieldValue>(new Str16Value(in.asString())); - case types::VAR_UUID: return boost::shared_ptr<FieldValue>(new UuidValue(in.asUuid().data())); - case types::VAR_MAP: return boost::shared_ptr<FieldValue>(new FieldTableValue(ManagementAgent::fromMap(in.asMap()))); - case types::VAR_LIST: return boost::shared_ptr<FieldValue>(new ListValue(ManagementAgent::fromList(in.asList()))); - } - - QPID_LOG(error, "Unknown Variant type - not converted: [" << in.getType() << "]"); - return boost::shared_ptr<FieldValue>(new VoidValue()); -} - -// stolen from qpid/client/amqp0_10/Codecs.cpp - TODO: make Codecs public, and remove this dup. -Variant ManagementAgent::toVariant(const boost::shared_ptr<FieldValue>& in) -{ - const string iso885915("iso-8859-15"); - const string utf8("utf8"); - const string utf16("utf16"); - //const string binary("binary"); - const string amqp0_10_binary("amqp0-10:binary"); - //const string amqp0_10_bit("amqp0-10:bit"); - const string amqp0_10_datetime("amqp0-10:datetime"); - const string amqp0_10_struct("amqp0-10:struct"); - Variant out; - - //based on AMQP 0-10 typecode, pick most appropriate variant type - switch (in->getType()) { - //Fixed Width types: - case 0x00: //bin8 - case 0x01: out.setEncoding(amqp0_10_binary); // int8 - case 0x02: out = in->getIntegerValue<int8_t, 1>(); break; //uint8 - case 0x03: out = in->getIntegerValue<uint8_t, 1>(); break; // - // case 0x04: break; //TODO: iso-8859-15 char // char - case 0x08: out = static_cast<bool>(in->getIntegerValue<uint8_t, 1>()); break; // bool int8 - - case 0x10: out.setEncoding(amqp0_10_binary); // bin16 - case 0x11: out = in->getIntegerValue<int16_t, 2>(); break; // int16 - case 0x12: out = in->getIntegerValue<uint16_t, 2>(); break; //uint16 - - case 0x20: out.setEncoding(amqp0_10_binary); // bin32 - case 0x21: out = in->getIntegerValue<int32_t, 4>(); break; // int32 - case 0x22: out = in->getIntegerValue<uint32_t, 4>(); break; // uint32 - - case 0x23: out = in->get<float>(); break; // float(32) - - // case 0x27: break; //TODO: utf-32 char - - case 0x30: out.setEncoding(amqp0_10_binary); // bin64 - case 0x31: out = in->getIntegerValue<int64_t, 8>(); break; //int64 - - case 0x38: out.setEncoding(amqp0_10_datetime); //treat datetime as uint64_t, but set encoding - case 0x32: out = in->getIntegerValue<uint64_t, 8>(); break; //uint64 - case 0x33: out = in->get<double>(); break; // double - - case 0x48: // uuid - { - unsigned char data[16]; - in->getFixedWidthValue<16>(data); - out = qpid::types::Uuid(data); - } break; - - //TODO: figure out whether and how to map values with codes 0x40-0xd8 - - case 0xf0: break;//void, which is the default value for Variant - // case 0xf1: out.setEncoding(amqp0_10_bit); break;//treat 'bit' as void, which is the default value for Variant - - //Variable Width types: - //strings: - case 0x80: // str8 - case 0x90: // str16 - case 0xa0: // str32 - out = in->get<string>(); - out.setEncoding(amqp0_10_binary); - break; - - case 0x84: // str8 - case 0x94: // str16 - out = in->get<string>(); - out.setEncoding(iso885915); - break; - - case 0x85: // str8 - case 0x95: // str16 - out = in->get<string>(); - out.setEncoding(utf8); - break; - - case 0x86: // str8 - case 0x96: // str16 - out = in->get<string>(); - out.setEncoding(utf16); - break; - - case 0xab: // str32 - out = in->get<string>(); - out.setEncoding(amqp0_10_struct); - break; - - case 0xa8: // map - out = ManagementAgent::toMap(in->get<FieldTable>()); - break; - - case 0xa9: // list of variant types - out = ManagementAgent::toList(in->get<List>()); - break; - //case 0xaa: //convert amqp0-10 array (uniform type) into variant list - // out = Variant::List(); - // translate<Array>(in, out.asList(), &toVariant); - // break; - - default: - //error? - QPID_LOG(error, "Unknown FieldValue type - not converted: [" << (unsigned int)(in->getType()) << "]"); - break; - } - - return out; -} - // Build up a list of the current set of deleted objects that are pending their // next (last) publish-ment. @@ -3085,3 +2915,21 @@ bool ManagementAgent::moveDeletedObjectsLH() { } return !deleteList.empty(); } + +namespace qpid { +namespace management { + +namespace { +QPID_TSS const qpid::broker::ConnectionState* executionContext = 0; +} + +void setManagementExecutionContext(const qpid::broker::ConnectionState* ctxt) +{ + executionContext = ctxt; +} +const qpid::broker::ConnectionState* getManagementExecutionContext() +{ + return executionContext; +} + +}} diff --git a/cpp/src/qpid/management/ManagementAgent.h b/cpp/src/qpid/management/ManagementAgent.h index 0db19594a7..c21f384433 100644 --- a/cpp/src/qpid/management/ManagementAgent.h +++ b/cpp/src/qpid/management/ManagementAgent.h @@ -41,6 +41,9 @@ #include <map> namespace qpid { +namespace broker { +class ConnectionState; +} namespace management { class ManagementAgent @@ -142,13 +145,7 @@ public: const framing::Uuid& getUuid() const { return uuid; } void setUuid(const framing::Uuid& id) { uuid = id; writeData(); } - // TODO: remove these when Variant API moved into common library. static types::Variant::Map toMap(const framing::FieldTable& from); - static framing::FieldTable fromMap(const types::Variant::Map& from); - static types::Variant::List toList(const framing::List& from); - static framing::List fromList(const types::Variant::List& from); - static boost::shared_ptr<framing::FieldValue> toFieldValue(const types::Variant& in); - static types::Variant toVariant(const boost::shared_ptr<framing::FieldValue>& val); // For Clustering: management objects that have been marked as // "deleted", but are waiting for their last published object @@ -422,6 +419,8 @@ private: void debugSnapshot(const char* title); }; +void setManagementExecutionContext(const qpid::broker::ConnectionState*); +const qpid::broker::ConnectionState* getManagementExecutionContext(); }} - + #endif /*!_ManagementAgent_*/ diff --git a/cpp/src/qpid/messaging/AddressParser.cpp b/cpp/src/qpid/messaging/AddressParser.cpp index 4c8f35fbc5..d76210ee5d 100644 --- a/cpp/src/qpid/messaging/AddressParser.cpp +++ b/cpp/src/qpid/messaging/AddressParser.cpp @@ -151,7 +151,7 @@ bool AddressParser::readValueIfExists(Variant& value) bool AddressParser::readString(std::string& value, char delimiter) { if (readChar(delimiter)) { - std::string::size_type start = current++; + std::string::size_type start = current; while (!eos()) { if (input.at(current) == delimiter) { if (current > start) { @@ -201,7 +201,8 @@ bool AddressParser::readSimpleValue(Variant& value) { std::string s; if (readWord(s)) { - value.parse(s); + value.parse(s); + if (value.getType() == VAR_STRING) value.setEncoding("utf8"); return true; } else { return false; diff --git a/cpp/src/qpid/messaging/Duration.cpp b/cpp/src/qpid/messaging/Duration.cpp index a2c443c746..a23e9f5bcb 100644 --- a/cpp/src/qpid/messaging/Duration.cpp +++ b/cpp/src/qpid/messaging/Duration.cpp @@ -37,6 +37,16 @@ Duration operator*(uint64_t multiplier, const Duration& duration) return Duration(duration.getMilliseconds() * multiplier); } +bool operator==(const Duration& a, const Duration& b) +{ + return a.getMilliseconds() == b.getMilliseconds(); +} + +bool operator!=(const Duration& a, const Duration& b) +{ + return a.getMilliseconds() != b.getMilliseconds(); +} + const Duration Duration::FOREVER(std::numeric_limits<uint64_t>::max()); const Duration Duration::IMMEDIATE(0); const Duration Duration::SECOND(1000); diff --git a/cpp/src/qpid/messaging/Message.cpp b/cpp/src/qpid/messaging/Message.cpp index 83cdfd3c55..ef70c103e9 100644 --- a/cpp/src/qpid/messaging/Message.cpp +++ b/cpp/src/qpid/messaging/Message.cpp @@ -21,6 +21,7 @@ #include "qpid/messaging/Message.h" #include "qpid/messaging/MessageImpl.h" #include "qpid/amqp_0_10/Codecs.h" +#include <qpid/Exception.h> #include <boost/format.hpp> namespace qpid { @@ -115,7 +116,11 @@ template <class C> struct MessageCodec static void decode(const Message& message, typename C::ObjectType& object, const std::string& encoding) { checkEncoding(message, encoding); - C::decode(message.getContent(), object); + try { + C::decode(message.getContent(), object); + } catch (const qpid::Exception &ex) { + throw EncodingException(ex.what()); + } } static void encode(const typename C::ObjectType& map, Message& message, const std::string& encoding) diff --git a/cpp/src/qpid/messaging/Session.cpp b/cpp/src/qpid/messaging/Session.cpp index 496953a8e5..cccfd9a873 100644 --- a/cpp/src/qpid/messaging/Session.cpp +++ b/cpp/src/qpid/messaging/Session.cpp @@ -39,7 +39,8 @@ Session& Session::operator=(const Session& s) { return PI::assign(*this, s); } void Session::commit() { impl->commit(); } void Session::rollback() { impl->rollback(); } void Session::acknowledge(bool sync) { impl->acknowledge(sync); } -void Session::acknowledge(Message& m, bool s) { impl->acknowledge(m); sync(s); } +void Session::acknowledge(Message& m, bool s) { impl->acknowledge(m, false); sync(s); } +void Session::acknowledgeUpTo(Message& m, bool s) { impl->acknowledge(m, true); sync(s); } void Session::reject(Message& m) { impl->reject(m); } void Session::release(Message& m) { impl->release(m); } void Session::close() { impl->close(); } diff --git a/cpp/src/qpid/messaging/SessionImpl.h b/cpp/src/qpid/messaging/SessionImpl.h index 02a254e4f2..60ae615253 100644 --- a/cpp/src/qpid/messaging/SessionImpl.h +++ b/cpp/src/qpid/messaging/SessionImpl.h @@ -41,7 +41,7 @@ class SessionImpl : public virtual qpid::RefCounted virtual void commit() = 0; virtual void rollback() = 0; virtual void acknowledge(bool sync) = 0; - virtual void acknowledge(Message&) = 0; + virtual void acknowledge(Message&, bool cumulative) = 0; virtual void reject(Message&) = 0; virtual void release(Message&) = 0; virtual void close() = 0; diff --git a/cpp/src/qpid/replication/ReplicatingEventListener.cpp b/cpp/src/qpid/replication/ReplicatingEventListener.cpp index b7d52372f4..0ced4d9161 100644 --- a/cpp/src/qpid/replication/ReplicatingEventListener.cpp +++ b/cpp/src/qpid/replication/ReplicatingEventListener.cpp @@ -69,10 +69,9 @@ void ReplicatingEventListener::deliverDequeueMessage(const QueuedMessage& dequeu void ReplicatingEventListener::deliverEnqueueMessage(const QueuedMessage& enqueued) { boost::intrusive_ptr<Message> msg(cloneMessage(*(enqueued.queue), enqueued.payload)); - FieldTable& headers = msg->getProperties<MessageProperties>()->getApplicationHeaders(); - headers.setString(REPLICATION_TARGET_QUEUE, enqueued.queue->getName()); - headers.setInt(REPLICATION_EVENT_TYPE, ENQUEUE); - headers.setInt(QUEUE_MESSAGE_POSITION,enqueued.position); + msg->insertCustomProperty(REPLICATION_TARGET_QUEUE, enqueued.queue->getName()); + msg->insertCustomProperty(REPLICATION_EVENT_TYPE, ENQUEUE); + msg->insertCustomProperty(QUEUE_MESSAGE_POSITION,enqueued.position); route(msg); } diff --git a/cpp/src/qpid/replication/ReplicationExchange.cpp b/cpp/src/qpid/replication/ReplicationExchange.cpp index 4b6d25ac7d..89a2bf516d 100644 --- a/cpp/src/qpid/replication/ReplicationExchange.cpp +++ b/cpp/src/qpid/replication/ReplicationExchange.cpp @@ -97,11 +97,10 @@ void ReplicationExchange::handleEnqueueEvent(const FieldTable* args, Deliverable } else { queue->setPosition(seqno1); - FieldTable& headers = msg.getMessage().getProperties<MessageProperties>()->getApplicationHeaders(); - headers.erase(REPLICATION_TARGET_QUEUE); - headers.erase(REPLICATION_EVENT_SEQNO); - headers.erase(REPLICATION_EVENT_TYPE); - headers.erase(QUEUE_MESSAGE_POSITION); + msg.getMessage().removeCustomProperty(REPLICATION_TARGET_QUEUE); + msg.getMessage().removeCustomProperty(REPLICATION_EVENT_SEQNO); + msg.getMessage().removeCustomProperty(REPLICATION_EVENT_TYPE); + msg.getMessage().removeCustomProperty(QUEUE_MESSAGE_POSITION); msg.deliverTo(queue); QPID_LOG(debug, "Enqueued replicated message onto " << queueName); if (mgmtExchange != 0) { diff --git a/cpp/src/qpid/store/StorageProvider.h b/cpp/src/qpid/store/StorageProvider.h index bc8d187517..d162cc58ec 100644 --- a/cpp/src/qpid/store/StorageProvider.h +++ b/cpp/src/qpid/store/StorageProvider.h @@ -54,7 +54,7 @@ struct QueueEntry { QueueEntry(uint64_t id, TplStatus tpl = NONE, const std::string& x = "") : queueId(id), tplStatus(tpl), xid(x) {} - bool operator==(const QueueEntry& rhs) { + bool operator==(const QueueEntry& rhs) const { if (queueId != rhs.queueId) return false; if (tplStatus == NONE && rhs.tplStatus == NONE) return true; return xid == rhs.xid; diff --git a/cpp/src/qpid/sys/AggregateOutput.h b/cpp/src/qpid/sys/AggregateOutput.h index 6dad998bb0..d7c0ff29e3 100644 --- a/cpp/src/qpid/sys/AggregateOutput.h +++ b/cpp/src/qpid/sys/AggregateOutput.h @@ -41,7 +41,7 @@ namespace sys { * doOutput is called in another. */ -class AggregateOutput : public OutputTask, public OutputControl +class QPID_COMMON_CLASS_EXTERN AggregateOutput : public OutputTask, public OutputControl { typedef std::deque<OutputTask*> TaskList; diff --git a/cpp/src/qpid/sys/AsynchIO.h b/cpp/src/qpid/sys/AsynchIO.h index 50da8fa4fc..41f74f7ed0 100644 --- a/cpp/src/qpid/sys/AsynchIO.h +++ b/cpp/src/qpid/sys/AsynchIO.h @@ -64,8 +64,8 @@ public: // deletes. To correctly manage heaps when needed, the allocate and // delete should both be done from the same class/library. QPID_COMMON_EXTERN static AsynchConnector* create(const Socket& s, - std::string hostname, - uint16_t port, + const std::string& hostname, + const std::string& port, ConnectedCallback connCb, FailedCallback failCb); virtual void start(boost::shared_ptr<Poller> poller) = 0; diff --git a/cpp/src/qpid/sys/AsynchIOHandler.h b/cpp/src/qpid/sys/AsynchIOHandler.h index e1885bac79..b9867606c4 100644 --- a/cpp/src/qpid/sys/AsynchIOHandler.h +++ b/cpp/src/qpid/sys/AsynchIOHandler.h @@ -57,7 +57,7 @@ class AsynchIOHandler : public OutputControl { QPID_COMMON_EXTERN ~AsynchIOHandler(); QPID_COMMON_EXTERN void init(AsynchIO* a, int numBuffs); - QPID_COMMON_EXTERN void setClient() { isClient = true; } + QPID_COMMON_INLINE_EXTERN void setClient() { isClient = true; } // Output side QPID_COMMON_EXTERN void abort(); diff --git a/cpp/src/qpid/sys/AtomicValue.h b/cpp/src/qpid/sys/AtomicValue.h index 6e90eafead..bf995f991e 100644 --- a/cpp/src/qpid/sys/AtomicValue.h +++ b/cpp/src/qpid/sys/AtomicValue.h @@ -22,7 +22,12 @@ * */ -#if defined( __GNUC__ ) && __GNUC__ >= 4 && ( defined( __i686__ ) || defined( __x86_64__ ) ) +// Have to check for clang before gcc as clang pretends to be gcc too +#if defined( __clang__ ) +// Use the clang doesn't support atomic builtins for 64 bit values, so use the slow versions +#include "qpid/sys/AtomicValue_mutex.h" + +#elif defined( __GNUC__ ) && __GNUC__ >= 4 && ( defined( __i686__ ) || defined( __x86_64__ ) ) // Use the Gnu C built-in atomic operations if compiling with gcc on a suitable platform. #include "qpid/sys/AtomicValue_gcc.h" diff --git a/cpp/src/qpid/sys/AtomicValue_gcc.h b/cpp/src/qpid/sys/AtomicValue_gcc.h index d022b07c1d..724bae422e 100644 --- a/cpp/src/qpid/sys/AtomicValue_gcc.h +++ b/cpp/src/qpid/sys/AtomicValue_gcc.h @@ -10,9 +10,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -39,6 +39,9 @@ class AtomicValue public: AtomicValue(T init=0) : value(init) {} + // Not atomic. Don't call concurrently with atomic ops. + AtomicValue<T>& operator=(T newValue) { value = newValue; return *this; } + // Update and return new value. inline T operator+=(T n) { return __sync_add_and_fetch(&value, n); } inline T operator-=(T n) { return __sync_sub_and_fetch(&value, n); } @@ -54,11 +57,11 @@ class AtomicValue /** If current value == testval then set to newval. Returns the old value. */ T valueCompareAndSwap(T testval, T newval) { return __sync_val_compare_and_swap(&value, testval, newval); } - /** If current value == testval then set to newval. Returns true if the swap was performed. */ + /** If current value == testval then set to newval. Returns true if the swap was performed. */ bool boolCompareAndSwap(T testval, T newval) { return __sync_bool_compare_and_swap(&value, testval, newval); } T get() const { return const_cast<AtomicValue<T>*>(this)->fetchAndAdd(static_cast<T>(0)); } - + private: T value; }; diff --git a/cpp/src/qpid/sys/ClusterSafe.cpp b/cpp/src/qpid/sys/ClusterSafe.cpp index c6b527dfdf..dd37615145 100644 --- a/cpp/src/qpid/sys/ClusterSafe.cpp +++ b/cpp/src/qpid/sys/ClusterSafe.cpp @@ -34,8 +34,6 @@ QPID_TSS bool inContext = false; bool isClusterSafe() { return !inCluster || inContext; } -bool isCluster() { return inCluster; } - void assertClusterSafe() { if (!isClusterSafe()) { QPID_LOG(critical, "Modified cluster state outside of cluster context"); @@ -53,6 +51,16 @@ ClusterSafeScope::~ClusterSafeScope() { inContext = save; } +ClusterUnsafeScope::ClusterUnsafeScope() { + save = inContext; + inContext = false; +} + +ClusterUnsafeScope::~ClusterUnsafeScope() { + assert(!inContext); + inContext = save; +} + void enableClusterSafe() { inCluster = true; } }} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/ClusterSafe.h b/cpp/src/qpid/sys/ClusterSafe.h index 15675e8cc5..27e4eb46a5 100644 --- a/cpp/src/qpid/sys/ClusterSafe.h +++ b/cpp/src/qpid/sys/ClusterSafe.h @@ -52,14 +52,9 @@ QPID_COMMON_EXTERN void assertClusterSafe(); */ QPID_COMMON_EXTERN bool isClusterSafe(); -/** Return true in a clustered broker */ -QPID_COMMON_EXTERN bool isCluster(); - /** - * Base class for classes that encapsulate state which is replicated - * to all members of a cluster. Acts as a marker for clustered state - * and provides functions to assist detecting bugs in cluster - * behavior. + * Mark a scope as cluster safe. Sets isClusterSafe in constructor and resets + * to previous value in destructor. */ class ClusterSafeScope { public: @@ -70,6 +65,18 @@ class ClusterSafeScope { }; /** + * Mark a scope as cluster unsafe. Clears isClusterSafe in constructor and resets + * to previous value in destructor. + */ +class ClusterUnsafeScope { + public: + QPID_COMMON_EXTERN ClusterUnsafeScope(); + QPID_COMMON_EXTERN ~ClusterUnsafeScope(); + private: + bool save; +}; + +/** * Enable cluster-safe assertions. By default they are no-ops. * Called by cluster code. */ diff --git a/cpp/src/qpid/sys/CopyOnWriteArray.h b/cpp/src/qpid/sys/CopyOnWriteArray.h index 45a231dfd8..41384fc5a4 100644 --- a/cpp/src/qpid/sys/CopyOnWriteArray.h +++ b/cpp/src/qpid/sys/CopyOnWriteArray.h @@ -43,6 +43,12 @@ public: CopyOnWriteArray() {} CopyOnWriteArray(const CopyOnWriteArray& c) : array(c.array) {} + bool empty() + { + Mutex::ScopedLock l(lock); + return array ? array->empty() : true; + } + void add(T& t) { Mutex::ScopedLock l(lock); diff --git a/cpp/src/qpid/sys/PollableQueue.h b/cpp/src/qpid/sys/PollableQueue.h index 81c2301c1e..03b9d0084d 100644 --- a/cpp/src/qpid/sys/PollableQueue.h +++ b/cpp/src/qpid/sys/PollableQueue.h @@ -10,9 +10,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -28,7 +28,8 @@ #include <boost/function.hpp> #include <boost/bind.hpp> #include <algorithm> -#include <vector> +#include <deque> +#include "qpid/log/Statement.h" // FIXME aconway 2011-08-05: namespace qpid { namespace sys { @@ -44,7 +45,7 @@ class Poller; template <class T> class PollableQueue { public: - typedef std::vector<T> Batch; + typedef std::deque<T> Batch; typedef T value_type; /** @@ -68,11 +69,11 @@ class PollableQueue { const boost::shared_ptr<sys::Poller>& poller); ~PollableQueue(); - + /** Push a value onto the queue. Thread safe */ void push(const T& t); - /** Start polling. */ + /** Start polling. */ void start(); /** Stop polling and wait for the current callback, if any, to complete. */ @@ -90,14 +91,14 @@ class PollableQueue { * ensure clean shutdown with no events left on the queue. */ void shutdown(); - + private: typedef sys::Monitor::ScopedLock ScopedLock; typedef sys::Monitor::ScopedUnlock ScopedUnlock; void dispatch(PollableCondition& cond); void process(); - + mutable sys::Monitor lock; Callback callback; PollableCondition condition; @@ -107,7 +108,7 @@ class PollableQueue { }; template <class T> PollableQueue<T>::PollableQueue( - const Callback& cb, const boost::shared_ptr<sys::Poller>& p) + const Callback& cb, const boost::shared_ptr<sys::Poller>& p) : callback(cb), condition(boost::bind(&PollableQueue<T>::dispatch, this, _1), p), stopped(true) @@ -151,7 +152,7 @@ template <class T> void PollableQueue<T>::process() { putBack = callback(batch); } // put back unprocessed items. - queue.insert(queue.begin(), putBack, typename Batch::const_iterator(batch.end())); + queue.insert(queue.begin(), putBack, typename Batch::const_iterator(batch.end())); batch.clear(); } } diff --git a/cpp/src/qpid/sys/Poller.h b/cpp/src/qpid/sys/Poller.h index ec53b79bad..01ee139ee6 100644 --- a/cpp/src/qpid/sys/Poller.h +++ b/cpp/src/qpid/sys/Poller.h @@ -120,7 +120,7 @@ class PollerHandle { friend struct Poller::Event; PollerHandlePrivate* const impl; - QPID_COMMON_EXTERN virtual void processEvent(Poller::EventType) {}; + QPID_COMMON_INLINE_EXTERN virtual void processEvent(Poller::EventType) {}; public: QPID_COMMON_EXTERN PollerHandle(const IOHandle& h); diff --git a/cpp/src/qpid/sys/ProtocolFactory.h b/cpp/src/qpid/sys/ProtocolFactory.h index b233b2da1a..4d198a92da 100644 --- a/cpp/src/qpid/sys/ProtocolFactory.h +++ b/cpp/src/qpid/sys/ProtocolFactory.h @@ -39,11 +39,10 @@ class ProtocolFactory : public qpid::SharedObject<ProtocolFactory> virtual ~ProtocolFactory() = 0; virtual uint16_t getPort() const = 0; - virtual std::string getHost() const = 0; virtual void accept(boost::shared_ptr<Poller>, ConnectionCodec::Factory*) = 0; virtual void connect( boost::shared_ptr<Poller>, - const std::string& host, int16_t port, + const std::string& host, const std::string& port, ConnectionCodec::Factory* codec, ConnectFailedCallback failed) = 0; virtual bool supports(const std::string& /*capability*/) { return false; } diff --git a/cpp/src/qpid/sys/RdmaIOPlugin.cpp b/cpp/src/qpid/sys/RdmaIOPlugin.cpp index d53db20598..6769e5383c 100644 --- a/cpp/src/qpid/sys/RdmaIOPlugin.cpp +++ b/cpp/src/qpid/sys/RdmaIOPlugin.cpp @@ -31,7 +31,6 @@ #include "qpid/sys/SecuritySettings.h" #include <boost/bind.hpp> -#include <boost/lexical_cast.hpp> #include <memory> #include <netdb.h> @@ -212,10 +211,9 @@ void RdmaIOHandler::readbuff(Rdma::AsynchIO&, Rdma::Buffer* buff) { if (readError) { return; } - size_t decoded = 0; try { if (codec) { - decoded = codec->decode(buff->bytes(), buff->dataCount()); + (void) codec->decode(buff->bytes(), buff->dataCount()); }else{ // Need to start protocol processing initProtocolIn(buff); @@ -230,9 +228,7 @@ void RdmaIOHandler::readbuff(Rdma::AsynchIO&, Rdma::Buffer* buff) { void RdmaIOHandler::initProtocolIn(Rdma::Buffer* buff) { framing::Buffer in(buff->bytes(), buff->dataCount()); framing::ProtocolInitiation protocolInit; - size_t decoded = 0; if (protocolInit.decode(in)) { - decoded = in.getPosition(); QPID_LOG(debug, "Rdma: RECV [" << identifier << "] INIT(" << protocolInit << ")"); codec = factory->create(protocolInit.getVersion(), *this, identifier, SecuritySettings()); @@ -254,10 +250,9 @@ class RdmaIOProtocolFactory : public ProtocolFactory { public: RdmaIOProtocolFactory(int16_t port, int backlog); void accept(Poller::shared_ptr, ConnectionCodec::Factory*); - void connect(Poller::shared_ptr, const string& host, int16_t port, ConnectionCodec::Factory*, ConnectFailedCallback); + void connect(Poller::shared_ptr, const string& host, const std::string& port, ConnectionCodec::Factory*, ConnectFailedCallback); uint16_t getPort() const; - string getHost() const; private: bool request(Rdma::Connection::intrusive_ptr, const Rdma::ConnectionParams&, ConnectionCodec::Factory*); @@ -347,18 +342,7 @@ uint16_t RdmaIOProtocolFactory::getPort() const { return listeningPort; // Immutable no need for lock. } -string RdmaIOProtocolFactory::getHost() const { - //return listener.getSockname(); - return ""; -} - void RdmaIOProtocolFactory::accept(Poller::shared_ptr poller, ConnectionCodec::Factory* fact) { - ::sockaddr_in sin; - - sin.sin_family = AF_INET; - sin.sin_port = htons(listeningPort); - sin.sin_addr.s_addr = INADDR_ANY; - listener.reset( new Rdma::Listener( Rdma::ConnectionParams(65536, Rdma::DEFAULT_WR_ENTRIES), @@ -387,7 +371,7 @@ void RdmaIOProtocolFactory::connected(Poller::shared_ptr poller, Rdma::Connectio void RdmaIOProtocolFactory::connect( Poller::shared_ptr poller, - const std::string& host, int16_t port, + const std::string& host, const std::string& port, ConnectionCodec::Factory* f, ConnectFailedCallback failed) { @@ -399,7 +383,7 @@ void RdmaIOProtocolFactory::connect( boost::bind(&RdmaIOProtocolFactory::disconnected, this, _1), boost::bind(&RdmaIOProtocolFactory::rejected, this, _1, _2, failed)); - SocketAddress sa(host, boost::lexical_cast<std::string>(port)); + SocketAddress sa(host, port); c->start(poller, sa); } diff --git a/cpp/src/qpid/sys/Socket.h b/cpp/src/qpid/sys/Socket.h index 7d50afc59f..defec4879c 100644 --- a/cpp/src/qpid/sys/Socket.h +++ b/cpp/src/qpid/sys/Socket.h @@ -33,21 +33,21 @@ namespace sys { class Duration; class SocketAddress; -class Socket : public IOHandle +class QPID_COMMON_CLASS_EXTERN Socket : public IOHandle { public: /** Create a socket wrapper for descriptor. */ QPID_COMMON_EXTERN Socket(); - /** Set timeout for read and write */ - void setTimeout(const Duration& interval) const; + /** Create a new Socket which is the same address family as this one */ + QPID_COMMON_EXTERN Socket* createSameTypeSocket() const; /** Set socket non blocking */ void setNonblocking() const; QPID_COMMON_EXTERN void setTcpNoDelay() const; - QPID_COMMON_EXTERN void connect(const std::string& host, uint16_t port) const; + QPID_COMMON_EXTERN void connect(const std::string& host, const std::string& port) const; QPID_COMMON_EXTERN void connect(const SocketAddress&) const; QPID_COMMON_EXTERN void close() const; @@ -57,19 +57,9 @@ public: *@param backlog maximum number of pending connections. *@return The bound port. */ - QPID_COMMON_EXTERN int listen(uint16_t port = 0, int backlog = 10) const; + QPID_COMMON_EXTERN int listen(const std::string& host = "", const std::string& port = "0", int backlog = 10) const; QPID_COMMON_EXTERN int listen(const SocketAddress&, int backlog = 10) const; - /** Returns the "socket name" ie the address bound to - * the near end of the socket - */ - QPID_COMMON_EXTERN std::string getSockname() const; - - /** Returns the "peer name" ie the address bound to - * the remote end of the socket - */ - std::string getPeername() const; - /** * Returns an address (host and port) for the remote end of the * socket @@ -84,16 +74,13 @@ public: /** * Returns the full address of the connection: local and remote host and port. */ - QPID_COMMON_EXTERN std::string getFullAddress() const { return getLocalAddress()+"-"+getPeerAddress(); } - - QPID_COMMON_EXTERN uint16_t getLocalPort() const; - uint16_t getRemotePort() const; + QPID_COMMON_INLINE_EXTERN std::string getFullAddress() const { return getLocalAddress()+"-"+getPeerAddress(); } /** * Returns the error code stored in the socket. This may be used * to determine the result of a non-blocking connect. */ - int getError() const; + QPID_COMMON_EXTERN int getError() const; /** Accept a connection from a socket that is already listening * and has an incoming connection @@ -108,8 +95,13 @@ private: /** Create socket */ void createSocket(const SocketAddress&) const; +public: + /** Construct socket with existing handle */ Socket(IOHandlePrivate*); - mutable std::string connectname; + +protected: + mutable std::string localname; + mutable std::string peername; mutable bool nonblocking; mutable bool nodelay; }; diff --git a/cpp/src/qpid/sys/SocketAddress.h b/cpp/src/qpid/sys/SocketAddress.h index 27b9642f2c..dcca109d94 100644 --- a/cpp/src/qpid/sys/SocketAddress.h +++ b/cpp/src/qpid/sys/SocketAddress.h @@ -27,6 +27,7 @@ #include <string> struct addrinfo; +struct sockaddr; namespace qpid { namespace sys { @@ -41,12 +42,19 @@ public: QPID_COMMON_EXTERN SocketAddress& operator=(const SocketAddress&); QPID_COMMON_EXTERN ~SocketAddress(); - std::string asString() const; + QPID_COMMON_EXTERN bool nextAddress(); + QPID_COMMON_EXTERN std::string asString(bool numeric=true) const; + QPID_COMMON_EXTERN void setAddrInfoPort(uint16_t port); + + QPID_COMMON_EXTERN static std::string asString(::sockaddr const * const addr, size_t addrlen); + QPID_COMMON_EXTERN static uint16_t getPort(::sockaddr const * const addr); + private: std::string host; std::string port; mutable ::addrinfo* addrInfo; + mutable ::addrinfo* currentAddrInfo; }; }} diff --git a/cpp/src/qpid/sys/SslPlugin.cpp b/cpp/src/qpid/sys/SslPlugin.cpp index b0e791d60b..ab15785492 100644 --- a/cpp/src/qpid/sys/SslPlugin.cpp +++ b/cpp/src/qpid/sys/SslPlugin.cpp @@ -25,6 +25,8 @@ #include "qpid/sys/ssl/check.h" #include "qpid/sys/ssl/util.h" #include "qpid/sys/ssl/SslHandler.h" +#include "qpid/sys/AsynchIOHandler.h" +#include "qpid/sys/AsynchIO.h" #include "qpid/sys/ssl/SslIo.h" #include "qpid/sys/ssl/SslSocket.h" #include "qpid/broker/Broker.h" @@ -37,15 +39,19 @@ namespace qpid { namespace sys { +using namespace qpid::sys::ssl; + struct SslServerOptions : ssl::SslOptions { uint16_t port; bool clientAuth; bool nodict; + bool multiplex; SslServerOptions() : port(5671), clientAuth(false), - nodict(false) + nodict(false), + multiplex(false) { addOptions() ("ssl-port", optValue(port, "PORT"), "Port on which to listen for SSL connections") @@ -56,29 +62,37 @@ struct SslServerOptions : ssl::SslOptions } }; -class SslProtocolFactory : public ProtocolFactory { +template <class T> +class SslProtocolFactoryTmpl : public ProtocolFactory { + private: + + typedef SslAcceptorTmpl<T> SslAcceptor; + const bool tcpNoDelay; - qpid::sys::ssl::SslSocket listener; + T listener; const uint16_t listeningPort; - std::auto_ptr<qpid::sys::ssl::SslAcceptor> acceptor; + std::auto_ptr<SslAcceptor> acceptor; bool nodict; public: - SslProtocolFactory(const SslServerOptions&, int backlog, bool nodelay); + SslProtocolFactoryTmpl(const SslServerOptions&, int backlog, bool nodelay); void accept(Poller::shared_ptr, ConnectionCodec::Factory*); - void connect(Poller::shared_ptr, const std::string& host, int16_t port, + void connect(Poller::shared_ptr, const std::string& host, const std::string& port, ConnectionCodec::Factory*, boost::function2<void, int, std::string> failed); uint16_t getPort() const; - std::string getHost() const; bool supports(const std::string& capability); private: - void established(Poller::shared_ptr, const qpid::sys::ssl::SslSocket&, ConnectionCodec::Factory*, + void established(Poller::shared_ptr, const Socket&, ConnectionCodec::Factory*, bool isClient); }; +typedef SslProtocolFactoryTmpl<SslSocket> SslProtocolFactory; +typedef SslProtocolFactoryTmpl<SslMuxSocket> SslMuxProtocolFactory; + + // Static instance to initialise plugin static struct SslPlugin : public Plugin { SslServerOptions options; @@ -87,24 +101,48 @@ static struct SslPlugin : public Plugin { ~SslPlugin() { ssl::shutdownNSS(); } - void earlyInitialize(Target&) { + void earlyInitialize(Target& target) { + broker::Broker* broker = dynamic_cast<broker::Broker*>(&target); + if (broker && !options.certDbPath.empty()) { + const broker::Broker::Options& opts = broker->getOptions(); + + if (opts.port == options.port && // AMQP & AMQPS ports are the same + opts.port != 0) { + // The presence of this option is used to signal to the TCP + // plugin not to start listening on the shared port. The actual + // value cannot be configured through the command line or config + // file (other than by setting the ports to the same value) + // because we are only adding it after option parsing. + options.multiplex = true; + options.addOptions()("ssl-multiplex", optValue(options.multiplex), "Allow SSL and non-SSL connections on the same port"); + } + } } void initialize(Target& target) { + QPID_LOG(trace, "Initialising SSL plugin"); broker::Broker* broker = dynamic_cast<broker::Broker*>(&target); // Only provide to a Broker if (broker) { if (options.certDbPath.empty()) { - QPID_LOG(info, "SSL plugin not enabled, you must set --ssl-cert-db to enable it."); + QPID_LOG(notice, "SSL plugin not enabled, you must set --ssl-cert-db to enable it."); } else { try { ssl::initNSS(options, true); const broker::Broker::Options& opts = broker->getOptions(); - ProtocolFactory::shared_ptr protocol(new SslProtocolFactory(options, - opts.connectionBacklog, - opts.tcpNoDelay)); - QPID_LOG(notice, "Listening for SSL connections on TCP port " << protocol->getPort()); + + ProtocolFactory::shared_ptr protocol(options.multiplex ? + static_cast<ProtocolFactory*>(new SslMuxProtocolFactory(options, + opts.connectionBacklog, + opts.tcpNoDelay)) : + static_cast<ProtocolFactory*>(new SslProtocolFactory(options, + opts.connectionBacklog, + opts.tcpNoDelay))); + QPID_LOG(notice, "Listening for " << + (options.multiplex ? "SSL or TCP" : "SSL") << + " connections on TCP port " << + protocol->getPort()); broker->registerProtocolFactory("ssl", protocol); } catch (const std::exception& e) { QPID_LOG(error, "Failed to initialise SSL plugin: " << e.what()); @@ -114,13 +152,15 @@ static struct SslPlugin : public Plugin { } } sslPlugin; -SslProtocolFactory::SslProtocolFactory(const SslServerOptions& options, int backlog, bool nodelay) : +template <class T> +SslProtocolFactoryTmpl<T>::SslProtocolFactoryTmpl(const SslServerOptions& options, int backlog, bool nodelay) : tcpNoDelay(nodelay), listeningPort(listener.listen(options.port, backlog, options.certName, options.clientAuth)), nodict(options.nodict) {} -void SslProtocolFactory::established(Poller::shared_ptr poller, const qpid::sys::ssl::SslSocket& s, - ConnectionCodec::Factory* f, bool isClient) { +void SslEstablished(Poller::shared_ptr poller, const qpid::sys::SslSocket& s, + ConnectionCodec::Factory* f, bool isClient, + bool tcpNoDelay, bool nodict) { qpid::sys::ssl::SslHandler* async = new qpid::sys::ssl::SslHandler(s.getFullAddress(), f, nodict); if (tcpNoDelay) { @@ -128,8 +168,10 @@ void SslProtocolFactory::established(Poller::shared_ptr poller, const qpid::sys: QPID_LOG(info, "Set TCP_NODELAY on connection to " << s.getPeerAddress()); } - if (isClient) + if (isClient) { async->setClient(); + } + qpid::sys::ssl::SslIO* aio = new qpid::sys::ssl::SslIO(s, boost::bind(&qpid::sys::ssl::SslHandler::readbuff, async, _1, _2), boost::bind(&qpid::sys::ssl::SslHandler::eof, async, _1), @@ -142,25 +184,66 @@ void SslProtocolFactory::established(Poller::shared_ptr poller, const qpid::sys: aio->start(poller); } -uint16_t SslProtocolFactory::getPort() const { - return listeningPort; // Immutable no need for lock. +template <> +void SslProtocolFactory::established(Poller::shared_ptr poller, const Socket& s, + ConnectionCodec::Factory* f, bool isClient) { + const SslSocket *sslSock = dynamic_cast<const SslSocket*>(&s); + + SslEstablished(poller, *sslSock, f, isClient, tcpNoDelay, nodict); } -std::string SslProtocolFactory::getHost() const { - return listener.getSockname(); +template <class T> +uint16_t SslProtocolFactoryTmpl<T>::getPort() const { + return listeningPort; // Immutable no need for lock. } -void SslProtocolFactory::accept(Poller::shared_ptr poller, - ConnectionCodec::Factory* fact) { +template <class T> +void SslProtocolFactoryTmpl<T>::accept(Poller::shared_ptr poller, + ConnectionCodec::Factory* fact) { acceptor.reset( - new qpid::sys::ssl::SslAcceptor(listener, - boost::bind(&SslProtocolFactory::established, this, poller, _1, fact, false))); + new SslAcceptor(listener, + boost::bind(&SslProtocolFactoryTmpl<T>::established, + this, poller, _1, fact, false))); acceptor->start(poller); } -void SslProtocolFactory::connect( +template <> +void SslMuxProtocolFactory::established(Poller::shared_ptr poller, const Socket& s, + ConnectionCodec::Factory* f, bool isClient) { + const SslSocket *sslSock = dynamic_cast<const SslSocket*>(&s); + + if (sslSock) { + SslEstablished(poller, *sslSock, f, isClient, tcpNoDelay, nodict); + return; + } + + AsynchIOHandler* async = new AsynchIOHandler(s.getFullAddress(), f); + + if (tcpNoDelay) { + s.setTcpNoDelay(); + QPID_LOG(info, "Set TCP_NODELAY on connection to " << s.getPeerAddress()); + } + + if (isClient) { + async->setClient(); + } + AsynchIO* aio = AsynchIO::create + (s, + boost::bind(&AsynchIOHandler::readbuff, async, _1, _2), + boost::bind(&AsynchIOHandler::eof, async, _1), + boost::bind(&AsynchIOHandler::disconnect, async, _1), + boost::bind(&AsynchIOHandler::closedSocket, async, _1, _2), + boost::bind(&AsynchIOHandler::nobuffs, async, _1), + boost::bind(&AsynchIOHandler::idle, async, _1)); + + async->init(aio, 4); + aio->start(poller); +} + +template <class T> +void SslProtocolFactoryTmpl<T>::connect( Poller::shared_ptr poller, - const std::string& host, int16_t port, + const std::string& host, const std::string& port, ConnectionCodec::Factory* fact, ConnectFailedCallback failed) { @@ -171,9 +254,9 @@ void SslProtocolFactory::connect( // is no longer needed. qpid::sys::ssl::SslSocket* socket = new qpid::sys::ssl::SslSocket(); - new qpid::sys::ssl::SslConnector (*socket, poller, host, port, - boost::bind(&SslProtocolFactory::established, this, poller, _1, fact, true), - failed); + new SslConnector(*socket, poller, host, port, + boost::bind(&SslProtocolFactoryTmpl<T>::established, this, poller, _1, fact, true), + failed); } namespace @@ -181,6 +264,7 @@ namespace const std::string SSL = "ssl"; } +template <> bool SslProtocolFactory::supports(const std::string& capability) { std::string s = capability; @@ -188,4 +272,12 @@ bool SslProtocolFactory::supports(const std::string& capability) return s == SSL; } +template <> +bool SslMuxProtocolFactory::supports(const std::string& capability) +{ + std::string s = capability; + transform(s.begin(), s.end(), s.begin(), tolower); + return s == SSL || s == "tcp"; +} + }} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/StateMonitor.h b/cpp/src/qpid/sys/StateMonitor.h index 5a92756f3a..eac37a8543 100644 --- a/cpp/src/qpid/sys/StateMonitor.h +++ b/cpp/src/qpid/sys/StateMonitor.h @@ -41,9 +41,9 @@ class StateMonitor : public Waitable struct Set : public std::bitset<MaxEnum + 1> { Set() {} Set(Enum s) { set(s); } - Set(Enum s, Enum t) { set(s).set(t); } - Set(Enum s, Enum t, Enum u) { set(s).set(t).set(u); } - Set(Enum s, Enum t, Enum u, Enum v) { set(s).set(t).set(u).set(v); } + Set(Enum s, Enum t) { std::bitset<MaxEnum + 1>::set(s).set(t); } + Set(Enum s, Enum t, Enum u) { std::bitset<MaxEnum + 1>::set(s).set(t).set(u); } + Set(Enum s, Enum t, Enum u, Enum v) { std::bitset<MaxEnum + 1>::set(s).set(t).set(u).set(v); } }; @@ -60,13 +60,13 @@ class StateMonitor : public Waitable operator Enum() const { return state; } /** @pre Caller holds a ScopedLock */ - void waitFor(Enum s) { ScopedWait(*this); while (s != state) wait(); } + void waitFor(Enum s) { ScopedWait w(*this); while (s != state) wait(); } /** @pre Caller holds a ScopedLock */ - void waitFor(Set s) { ScopedWait(*this); while (!s.test(state)) wait(); } + void waitFor(Set s) { ScopedWait w(*this); while (!s.test(state)) wait(); } /** @pre Caller holds a ScopedLock */ - void waitNot(Enum s) { ScopedWait(*this); while (s == state) wait(); } + void waitNot(Enum s) { ScopedWait w(*this); while (s == state) wait(); } /** @pre Caller holds a ScopedLock */ - void waitNot(Set s) { ScopedWait(*this); while (s.test(state)) wait(); } + void waitNot(Set s) { ScopedWait w(*this); while (s.test(state)) wait(); } private: Enum state; diff --git a/cpp/src/qpid/sys/TCPIOPlugin.cpp b/cpp/src/qpid/sys/TCPIOPlugin.cpp index a6528f9ad9..8a99d8db71 100644 --- a/cpp/src/qpid/sys/TCPIOPlugin.cpp +++ b/cpp/src/qpid/sys/TCPIOPlugin.cpp @@ -25,31 +25,31 @@ #include "qpid/Plugin.h" #include "qpid/sys/Socket.h" +#include "qpid/sys/SocketAddress.h" #include "qpid/sys/Poller.h" #include "qpid/broker/Broker.h" #include "qpid/log/Statement.h" #include <boost/bind.hpp> -#include <memory> +#include <boost/ptr_container/ptr_vector.hpp> namespace qpid { namespace sys { class AsynchIOProtocolFactory : public ProtocolFactory { const bool tcpNoDelay; - Socket listener; - const uint16_t listeningPort; - std::auto_ptr<AsynchAcceptor> acceptor; + boost::ptr_vector<Socket> listeners; + boost::ptr_vector<AsynchAcceptor> acceptors; + uint16_t listeningPort; public: - AsynchIOProtocolFactory(int16_t port, int backlog, bool nodelay); + AsynchIOProtocolFactory(const std::string& host, const std::string& port, int backlog, bool nodelay, bool shouldListen); void accept(Poller::shared_ptr, ConnectionCodec::Factory*); - void connect(Poller::shared_ptr, const std::string& host, int16_t port, + void connect(Poller::shared_ptr, const std::string& host, const std::string& port, ConnectionCodec::Factory*, ConnectFailedCallback); uint16_t getPort() const; - std::string getHost() const; private: void established(Poller::shared_ptr, const Socket&, ConnectionCodec::Factory*, @@ -57,27 +57,78 @@ class AsynchIOProtocolFactory : public ProtocolFactory { void connectFailed(const Socket&, int, const std::string&, ConnectFailedCallback); }; +static bool sslMultiplexEnabled(void) +{ + Options o; + Plugin::addOptions(o); + + if (o.find_nothrow("ssl-multiplex", false)) { + // This option is added by the SSL plugin when the SSL port + // is configured to be the same as the main port. + QPID_LOG(notice, "SSL multiplexing enabled"); + return true; + } + return false; +} + // Static instance to initialise plugin static class TCPIOPlugin : public Plugin { void earlyInitialize(Target&) { } - + void initialize(Target& target) { broker::Broker* broker = dynamic_cast<broker::Broker*>(&target); // Only provide to a Broker if (broker) { const broker::Broker::Options& opts = broker->getOptions(); - ProtocolFactory::shared_ptr protocol(new AsynchIOProtocolFactory(opts.port, opts.connectionBacklog, - opts.tcpNoDelay)); - QPID_LOG(notice, "Listening on TCP port " << protocol->getPort()); - broker->registerProtocolFactory("tcp", protocol); + + // Check for SSL on the same port + bool shouldListen = !sslMultiplexEnabled(); + + ProtocolFactory::shared_ptr protocolt( + new AsynchIOProtocolFactory( + "", boost::lexical_cast<std::string>(opts.port), + opts.connectionBacklog, + opts.tcpNoDelay, + shouldListen)); + if (shouldListen) { + QPID_LOG(notice, "Listening on TCP/TCP6 port " << protocolt->getPort()); + } + broker->registerProtocolFactory("tcp", protocolt); } } } tcpPlugin; -AsynchIOProtocolFactory::AsynchIOProtocolFactory(int16_t port, int backlog, bool nodelay) : - tcpNoDelay(nodelay), listeningPort(listener.listen(port, backlog)) -{} +AsynchIOProtocolFactory::AsynchIOProtocolFactory(const std::string& host, const std::string& port, int backlog, bool nodelay, bool shouldListen) : + tcpNoDelay(nodelay) +{ + if (!shouldListen) { + return; + } + + SocketAddress sa(host, port); + + // We must have at least one resolved address + QPID_LOG(info, "Listening to: " << sa.asString()) + Socket* s = new Socket; + uint16_t lport = s->listen(sa, backlog); + QPID_LOG(debug, "Listened to: " << lport); + listeners.push_back(s); + + listeningPort = lport; + + // Try any other resolved addresses + while (sa.nextAddress()) { + // Hack to ensure that all listening connections are on the same port + sa.setAddrInfoPort(listeningPort); + QPID_LOG(info, "Listening to: " << sa.asString()) + Socket* s = new Socket; + uint16_t lport = s->listen(sa, backlog); + QPID_LOG(debug, "Listened to: " << lport); + listeners.push_back(s); + } + +} void AsynchIOProtocolFactory::established(Poller::shared_ptr poller, const Socket& s, ConnectionCodec::Factory* f, bool isClient) { @@ -107,16 +158,14 @@ uint16_t AsynchIOProtocolFactory::getPort() const { return listeningPort; // Immutable no need for lock. } -std::string AsynchIOProtocolFactory::getHost() const { - return listener.getSockname(); -} - void AsynchIOProtocolFactory::accept(Poller::shared_ptr poller, ConnectionCodec::Factory* fact) { - acceptor.reset( - AsynchAcceptor::create(listener, - boost::bind(&AsynchIOProtocolFactory::established, this, poller, _1, fact, false))); - acceptor->start(poller); + for (unsigned i = 0; i<listeners.size(); ++i) { + acceptors.push_back( + AsynchAcceptor::create(listeners[i], + boost::bind(&AsynchIOProtocolFactory::established, this, poller, _1, fact, false))); + acceptors[i].start(poller); + } } void AsynchIOProtocolFactory::connectFailed( @@ -130,7 +179,7 @@ void AsynchIOProtocolFactory::connectFailed( void AsynchIOProtocolFactory::connect( Poller::shared_ptr poller, - const std::string& host, int16_t port, + const std::string& host, const std::string& port, ConnectionCodec::Factory* fact, ConnectFailedCallback failed) { @@ -139,8 +188,8 @@ void AsynchIOProtocolFactory::connect( // upon connection failure or by the AsynchIO upon connection // shutdown. The allocated AsynchConnector frees itself when it // is no longer needed. - Socket* socket = new Socket(); + try { AsynchConnector* c = AsynchConnector::create( *socket, host, @@ -150,6 +199,12 @@ void AsynchIOProtocolFactory::connect( boost::bind(&AsynchIOProtocolFactory::connectFailed, this, _1, _2, _3, failed)); c->start(poller); + } catch (std::exception&) { + // TODO: Design question - should we do the error callback and also throw? + int errCode = socket->getError(); + connectFailed(*socket, errCode, strError(errCode), failed); + throw; + } } }} // namespace qpid::sys diff --git a/cpp/src/qpid/sys/Timer.cpp b/cpp/src/qpid/sys/Timer.cpp index a97ccd1bd1..47752e4584 100644 --- a/cpp/src/qpid/sys/Timer.cpp +++ b/cpp/src/qpid/sys/Timer.cpp @@ -75,6 +75,12 @@ void TimerTask::cancel() { cancelled = true; } +void TimerTask::setFired() { + // Set nextFireTime to just before now, making readyToFire() true. + nextFireTime = AbsTime(sys::now(), Duration(-1)); +} + + Timer::Timer() : active(false), late(50 * TIME_MSEC), @@ -131,12 +137,14 @@ void Timer::run() bool warningsEnabled; QPID_LOG_TEST(warning, warningsEnabled); if (warningsEnabled) { - if (delay > late && overrun > overran) - warn.lateAndOverran(t->name, delay, overrun, Duration(start, end)); + if (overrun > overran) { + if (delay > overran) // if delay is significant to an overrun. + warn.lateAndOverran(t->name, delay, overrun, Duration(start, end)); + else + warn.overran(t->name, overrun, Duration(start, end)); + } else if (delay > late) warn.late(t->name, delay); - else if (overrun > overran) - warn.overran(t->name, overrun, Duration(start, end)); } continue; } else { @@ -183,7 +191,11 @@ void Timer::stop() // Allow subclasses to override behavior when firing a task. void Timer::fire(boost::intrusive_ptr<TimerTask> t) { - t->fireTask(); + try { + t->fireTask(); + } catch (const std::exception& e) { + QPID_LOG(error, "Exception thrown by timer task " << t->getName() << ": " << e.what()); + } } // Provided for subclasses: called when a task is droped. diff --git a/cpp/src/qpid/sys/Timer.h b/cpp/src/qpid/sys/Timer.h index 98ba39ce38..fccb17dbc2 100644 --- a/cpp/src/qpid/sys/Timer.h +++ b/cpp/src/qpid/sys/Timer.h @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -64,6 +64,10 @@ class TimerTask : public RefCounted { std::string getName() const { return name; } + // Move the nextFireTime so readyToFire is true. + // Used by the cluster, where tasks are fired on cluster events, not on local time. + QPID_COMMON_EXTERN void setFired(); + protected: // Must be overridden with callback virtual void fire() = 0; diff --git a/cpp/src/qpid/sys/TimerWarnings.cpp b/cpp/src/qpid/sys/TimerWarnings.cpp index 48a56eb472..85e26da54a 100644 --- a/cpp/src/qpid/sys/TimerWarnings.cpp +++ b/cpp/src/qpid/sys/TimerWarnings.cpp @@ -56,20 +56,22 @@ void TimerWarnings::log() { std::string task = i->first; TaskStats& stats = i->second; if (stats.lateDelay.count) - QPID_LOG(warning, task << " task late " + QPID_LOG(info, task << " task late " << stats.lateDelay.count << " times by " << stats.lateDelay.average()/TIME_MSEC << "ms on average."); + if (stats.overranOverrun.count) - QPID_LOG(warning, task << " task overran " + QPID_LOG(info, task << " task overran " << stats.overranOverrun.count << " times by " << stats.overranOverrun.average()/TIME_MSEC << "ms (taking " << stats.overranTime.average() << "ns) on average."); - if (stats.lateAndOverranDelay.count) - QPID_LOG(warning, task << " task overran " - << stats.overranOverrun.count << " times by " - << stats.overranOverrun.average()/TIME_MSEC << "ms (taking " - << stats.overranTime.average() << "ns) on average."); + if (stats.lateAndOverranOverrun.count) + QPID_LOG(info, task << " task late and overran " + << stats.lateAndOverranOverrun.count << " times: late " + << stats.lateAndOverranDelay.average()/TIME_MSEC << "ms, overran " + << stats.lateAndOverranOverrun.average()/TIME_MSEC << "ms (taking " + << stats.lateAndOverranTime.average() << "ns) on average."); } nextReport = AbsTime(now(), interval); diff --git a/cpp/src/qpid/sys/alloca.h b/cpp/src/qpid/sys/alloca.h index e989670e4f..b3f59b7c3f 100644 --- a/cpp/src/qpid/sys/alloca.h +++ b/cpp/src/qpid/sys/alloca.h @@ -21,19 +21,22 @@ * */ -#if (defined(_WINDOWS) || defined (WIN32)) && defined(_MSC_VER) -#include <malloc.h> -#ifdef alloc -# undef alloc -#endif -#define alloc _alloc -#ifdef alloca -# undef alloca -#endif -#define alloca _alloca +#if (defined(_WINDOWS) || defined (WIN32)) +# include <malloc.h> + +# if defined(_MSC_VER) +# ifdef alloc +# undef alloc +# endif +# define alloc _alloc +# ifdef alloca +# undef alloca +# endif +# define alloca _alloca +# endif #endif #if !defined _WINDOWS && !defined WIN32 -#include <alloca.h> +# include <alloca.h> #endif #endif /*!QPID_SYS_ALLOCA_H*/ diff --git a/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.cpp b/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.cpp index 454ce62495..249b769051 100644 --- a/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.cpp +++ b/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.cpp @@ -57,6 +57,7 @@ size_t CyrusSecurityLayer::decode(const char* input, size_t size) copied += count; decodeBuffer.position += count; size_t decodedSize = codec->decode(decodeBuffer.data, decodeBuffer.position); + if (decodedSize == 0) break; if (decodedSize < decodeBuffer.position) { ::memmove(decodeBuffer.data, decodeBuffer.data + decodedSize, decodeBuffer.position - decodedSize); } @@ -106,7 +107,7 @@ size_t CyrusSecurityLayer::encode(const char* buffer, size_t size) bool CyrusSecurityLayer::canEncode() { - return encrypted || codec->canEncode(); + return codec && (encrypted || codec->canEncode()); } void CyrusSecurityLayer::init(qpid::sys::Codec* c) diff --git a/cpp/src/qpid/sys/epoll/EpollPoller.cpp b/cpp/src/qpid/sys/epoll/EpollPoller.cpp index 9ad05c71a3..dcc9d9181c 100644 --- a/cpp/src/qpid/sys/epoll/EpollPoller.cpp +++ b/cpp/src/qpid/sys/epoll/EpollPoller.cpp @@ -384,7 +384,12 @@ void PollerPrivate::resetMode(PollerHandlePrivate& eh) { epe.data.u64 = 0; // Keep valgrind happy epe.data.ptr = &eh; - QPID_POSIX_CHECK(::epoll_ctl(epollFd, EPOLL_CTL_MOD, eh.fd(), &epe)); + int rc = ::epoll_ctl(epollFd, EPOLL_CTL_MOD, eh.fd(), &epe); + // If something has closed the fd in the meantime try adding it back + if (rc ==-1 && errno == ENOENT) { + rc = ::epoll_ctl(epollFd, EPOLL_CTL_ADD, eh.fd(), &epe); + } + QPID_POSIX_CHECK(rc); eh.setActive(); return; diff --git a/cpp/src/qpid/sys/posix/AsynchIO.cpp b/cpp/src/qpid/sys/posix/AsynchIO.cpp index 119a6aa8a4..dab8bd09c6 100644 --- a/cpp/src/qpid/sys/posix/AsynchIO.cpp +++ b/cpp/src/qpid/sys/posix/AsynchIO.cpp @@ -149,11 +149,12 @@ private: ConnectedCallback connCallback; FailedCallback failCallback; const Socket& socket; + SocketAddress sa; public: AsynchConnector(const Socket& socket, - std::string hostname, - uint16_t port, + const std::string& hostname, + const std::string& port, ConnectedCallback connCb, FailedCallback failCb); void start(Poller::shared_ptr poller); @@ -161,8 +162,8 @@ public: }; AsynchConnector::AsynchConnector(const Socket& s, - std::string hostname, - uint16_t port, + const std::string& hostname, + const std::string& port, ConnectedCallback connCb, FailedCallback failCb) : DispatchHandle(s, @@ -171,11 +172,13 @@ AsynchConnector::AsynchConnector(const Socket& s, boost::bind(&AsynchConnector::connComplete, this, _1)), connCallback(connCb), failCallback(failCb), - socket(s) + socket(s), + sa(hostname, port) { socket.setNonblocking(); - SocketAddress sa(hostname, boost::lexical_cast<std::string>(port)); + // Note, not catching any exceptions here, also has effect of destructing + QPID_LOG(info, "Connecting: " << sa.asString()); socket.connect(sa); } @@ -191,11 +194,26 @@ void AsynchConnector::stop() void AsynchConnector::connComplete(DispatchHandle& h) { - h.stopWatch(); int errCode = socket.getError(); if (errCode == 0) { + h.stopWatch(); connCallback(socket); } else { + // Retry while we cause an immediate exception + // (asynch failure will be handled by re-entering here at the top) + while (sa.nextAddress()) { + try { + // Try next address without deleting ourselves + QPID_LOG(debug, "Ignored socket connect error: " << strError(errCode)); + QPID_LOG(info, "Retrying connect: " << sa.asString()); + socket.connect(sa); + return; + } catch (const std::exception& e) { + QPID_LOG(debug, "Ignored socket connect exception: " << e.what()); + } + errCode = socket.getError(); + } + h.stopWatch(); failCallback(socket, errCode, strError(errCode)); } DispatchHandle::doDelete(); @@ -589,8 +607,8 @@ AsynchAcceptor* AsynchAcceptor::create(const Socket& s, } AsynchConnector* AsynchConnector::create(const Socket& s, - std::string hostname, - uint16_t port, + const std::string& hostname, + const std::string& port, ConnectedCallback connCb, FailedCallback failCb) { diff --git a/cpp/src/qpid/sys/posix/LockFile.cpp b/cpp/src/qpid/sys/posix/LockFile.cpp index 1862ff6ac9..f5a6c292cb 100755 --- a/cpp/src/qpid/sys/posix/LockFile.cpp +++ b/cpp/src/qpid/sys/posix/LockFile.cpp @@ -58,8 +58,7 @@ LockFile::~LockFile() { if (impl) { int f = impl->fd; if (f >= 0) { - int unused_ret; - unused_ret = ::lockf(f, F_ULOCK, 0); // Suppress warnings about ignoring return value. + (void) ::lockf(f, F_ULOCK, 0); // Suppress warnings about ignoring return value. ::close(f); impl->fd = -1; } diff --git a/cpp/src/qpid/sys/posix/Socket.cpp b/cpp/src/qpid/sys/posix/Socket.cpp index 7b906f33e8..4a6dc66f80 100644 --- a/cpp/src/qpid/sys/posix/Socket.cpp +++ b/cpp/src/qpid/sys/posix/Socket.cpp @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -34,65 +34,35 @@ #include <netdb.h> #include <cstdlib> #include <string.h> -#include <iostream> - -#include <boost/format.hpp> -#include <boost/lexical_cast.hpp> namespace qpid { namespace sys { namespace { -std::string getName(int fd, bool local, bool includeService = false) +std::string getName(int fd, bool local) { - ::sockaddr_storage name; // big enough for any socket address - ::socklen_t namelen = sizeof(name); - - int result = -1; + ::sockaddr_storage name_s; // big enough for any socket address + ::sockaddr* name = (::sockaddr*)&name_s; + ::socklen_t namelen = sizeof(name_s); + if (local) { - result = ::getsockname(fd, (::sockaddr*)&name, &namelen); + QPID_POSIX_CHECK( ::getsockname(fd, name, &namelen) ); } else { - result = ::getpeername(fd, (::sockaddr*)&name, &namelen); + QPID_POSIX_CHECK( ::getpeername(fd, name, &namelen) ); } - QPID_POSIX_CHECK(result); - - char servName[NI_MAXSERV]; - char dispName[NI_MAXHOST]; - if (includeService) { - if (int rc=::getnameinfo((::sockaddr*)&name, namelen, dispName, sizeof(dispName), - servName, sizeof(servName), - NI_NUMERICHOST | NI_NUMERICSERV) != 0) - throw QPID_POSIX_ERROR(rc); - return std::string(dispName) + ":" + std::string(servName); - - } else { - if (int rc=::getnameinfo((::sockaddr*)&name, namelen, dispName, sizeof(dispName), 0, 0, NI_NUMERICHOST) != 0) - throw QPID_POSIX_ERROR(rc); - return dispName; - } + return SocketAddress::asString(name, namelen); } -std::string getService(int fd, bool local) +uint16_t getLocalPort(int fd) { - ::sockaddr_storage name; // big enough for any socket address - ::socklen_t namelen = sizeof(name); - - int result = -1; - if (local) { - result = ::getsockname(fd, (::sockaddr*)&name, &namelen); - } else { - result = ::getpeername(fd, (::sockaddr*)&name, &namelen); - } + ::sockaddr_storage name_s; // big enough for any socket address + ::sockaddr* name = (::sockaddr*)&name_s; + ::socklen_t namelen = sizeof(name_s); - QPID_POSIX_CHECK(result); + QPID_POSIX_CHECK( ::getsockname(fd, name, &namelen) ); - char servName[NI_MAXSERV]; - if (int rc=::getnameinfo((::sockaddr*)&name, namelen, 0, 0, - servName, sizeof(servName), - NI_NUMERICHOST | NI_NUMERICSERV) != 0) - throw QPID_POSIX_ERROR(rc); - return servName; + return SocketAddress::getPort(name); } } @@ -119,6 +89,11 @@ void Socket::createSocket(const SocketAddress& sa) const try { if (nonblocking) setNonblocking(); if (nodelay) setTcpNoDelay(); + if (getAddrInfo(sa).ai_family == AF_INET6) { + int flag = 1; + int result = ::setsockopt(socket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&flag, sizeof(flag)); + QPID_POSIX_CHECK(result); + } } catch (std::exception&) { ::close(s); socket = -1; @@ -126,13 +101,18 @@ void Socket::createSocket(const SocketAddress& sa) const } } -void Socket::setTimeout(const Duration& interval) const -{ - const int& socket = impl->fd; - struct timeval tv; - toTimeval(tv, interval); - setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); - setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); +Socket* Socket::createSameTypeSocket() const { + int& socket = impl->fd; + // Socket currently has no actual socket attached + if (socket == -1) + return new Socket; + + ::sockaddr_storage sa; + ::socklen_t salen = sizeof(sa); + QPID_POSIX_CHECK(::getsockname(socket, (::sockaddr*)&sa, &salen)); + int s = ::socket(sa.ss_family, SOCK_STREAM, 0); // Currently only work with SOCK_STREAM + if (s < 0) throw QPID_POSIX_ERROR(errno); + return new Socket(new IOHandlePrivate(s)); } void Socket::setNonblocking() const { @@ -149,20 +129,27 @@ void Socket::setTcpNoDelay() const nodelay = true; if (socket != -1) { int flag = 1; - int result = setsockopt(impl->fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag)); + int result = ::setsockopt(impl->fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag)); QPID_POSIX_CHECK(result); } } -void Socket::connect(const std::string& host, uint16_t port) const +void Socket::connect(const std::string& host, const std::string& port) const { - SocketAddress sa(host, boost::lexical_cast<std::string>(port)); + SocketAddress sa(host, port); connect(sa); } void Socket::connect(const SocketAddress& addr) const { - connectname = addr.asString(); + // The display name for an outbound connection needs to be the name that was specified + // for the address rather than a resolved IP address as we don't know which of + // the IP addresses is actually the one that will be connected to. + peername = addr.asString(false); + + // However the string we compare with the local port must be numeric or it might not + // match when it should as getLocalAddress() will always be numeric + std::string connectname = addr.asString(); createSocket(addr); @@ -170,7 +157,24 @@ void Socket::connect(const SocketAddress& addr) const // TODO the correct thing to do here is loop on failure until you've used all the returned addresses if ((::connect(socket, getAddrInfo(addr).ai_addr, getAddrInfo(addr).ai_addrlen) < 0) && (errno != EINPROGRESS)) { - throw Exception(QPID_MSG(strError(errno) << ": " << connectname)); + throw Exception(QPID_MSG(strError(errno) << ": " << peername)); + } + // When connecting to a port on the same host which no longer has + // a process associated with it, the OS occasionally chooses the + // remote port (which is unoccupied) as the port to bind the local + // end of the socket, resulting in a "circular" connection. + // + // This seems like something the OS should prevent but I have + // confirmed that sporadic hangs in + // cluster_tests.LongTests.test_failover on RHEL5 are caused by + // such a circular connection. + // + // Raise an error if we see such a connection, since we know there is + // no listener on the peer address. + // + if (getLocalAddress() == connectname) { + close(); + throw Exception(QPID_MSG("Connection refused: " << peername)); } } @@ -183,9 +187,9 @@ Socket::close() const socket = -1; } -int Socket::listen(uint16_t port, int backlog) const +int Socket::listen(const std::string& host, const std::string& port, int backlog) const { - SocketAddress sa("", boost::lexical_cast<std::string>(port)); + SocketAddress sa(host, port); return listen(sa, backlog); } @@ -195,26 +199,24 @@ int Socket::listen(const SocketAddress& sa, int backlog) const const int& socket = impl->fd; int yes=1; - QPID_POSIX_CHECK(setsockopt(socket,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(yes))); + QPID_POSIX_CHECK(::setsockopt(socket,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(yes))); if (::bind(socket, getAddrInfo(sa).ai_addr, getAddrInfo(sa).ai_addrlen) < 0) throw Exception(QPID_MSG("Can't bind to port " << sa.asString() << ": " << strError(errno))); if (::listen(socket, backlog) < 0) throw Exception(QPID_MSG("Can't listen on port " << sa.asString() << ": " << strError(errno))); - struct sockaddr_in name; - socklen_t namelen = sizeof(name); - if (::getsockname(socket, (struct sockaddr*)&name, &namelen) < 0) - throw QPID_POSIX_ERROR(errno); - - return ntohs(name.sin_port); + return getLocalPort(socket); } Socket* Socket::accept() const { int afd = ::accept(impl->fd, 0, 0); - if ( afd >= 0) - return new Socket(new IOHandlePrivate(afd)); + if ( afd >= 0) { + Socket* s = new Socket(new IOHandlePrivate(afd)); + s->localname = localname; + return s; + } else if (errno == EAGAIN) return 0; else throw QPID_POSIX_ERROR(errno); @@ -230,37 +232,20 @@ int Socket::write(const void *buf, size_t count) const return ::write(impl->fd, buf, count); } -std::string Socket::getSockname() const -{ - return getName(impl->fd, true); -} - -std::string Socket::getPeername() const -{ - return getName(impl->fd, false); -} - std::string Socket::getPeerAddress() const { - if (connectname.empty()) { - connectname = getName(impl->fd, false, true); + if (peername.empty()) { + peername = getName(impl->fd, false); } - return connectname; + return peername; } std::string Socket::getLocalAddress() const { - return getName(impl->fd, true, true); -} - -uint16_t Socket::getLocalPort() const -{ - return std::atoi(getService(impl->fd, true).c_str()); -} - -uint16_t Socket::getRemotePort() const -{ - return std::atoi(getService(impl->fd, true).c_str()); + if (localname.empty()) { + localname = getName(impl->fd, true); + } + return localname; } int Socket::getError() const diff --git a/cpp/src/qpid/sys/posix/SocketAddress.cpp b/cpp/src/qpid/sys/posix/SocketAddress.cpp index 8f5f29d793..077942ef2f 100644 --- a/cpp/src/qpid/sys/posix/SocketAddress.cpp +++ b/cpp/src/qpid/sys/posix/SocketAddress.cpp @@ -21,11 +21,13 @@ #include "qpid/sys/SocketAddress.h" -#include "qpid/sys/posix/check.h" +#include "qpid/Exception.h" +#include "qpid/Msg.h" #include <sys/socket.h> -#include <string.h> +#include <netinet/in.h> #include <netdb.h> +#include <string.h> namespace qpid { namespace sys { @@ -46,15 +48,9 @@ SocketAddress::SocketAddress(const SocketAddress& sa) : SocketAddress& SocketAddress::operator=(const SocketAddress& sa) { - if (&sa != this) { - host = sa.host; - port = sa.port; + SocketAddress temp(sa); - if (addrInfo) { - ::freeaddrinfo(addrInfo); - addrInfo = 0; - } - } + std::swap(temp, *this); return *this; } @@ -65,9 +61,61 @@ SocketAddress::~SocketAddress() } } -std::string SocketAddress::asString() const +std::string SocketAddress::asString(::sockaddr const * const addr, size_t addrlen) +{ + char servName[NI_MAXSERV]; + char dispName[NI_MAXHOST]; + if (int rc=::getnameinfo(addr, addrlen, + dispName, sizeof(dispName), + servName, sizeof(servName), + NI_NUMERICHOST | NI_NUMERICSERV) != 0) + throw qpid::Exception(QPID_MSG(gai_strerror(rc))); + std::string s; + switch (addr->sa_family) { + case AF_INET: s += dispName; break; + case AF_INET6: s += "["; s += dispName; s+= "]"; break; + default: throw Exception(QPID_MSG("Unexpected socket type")); + } + s += ":"; + s += servName; + return s; +} + +uint16_t SocketAddress::getPort(::sockaddr const * const addr) { - return host + ":" + port; + switch (addr->sa_family) { + case AF_INET: return ntohs(((::sockaddr_in*)addr)->sin_port); + case AF_INET6: return ntohs(((::sockaddr_in6*)addr)->sin6_port); + default:throw Exception(QPID_MSG("Unexpected socket type")); + } +} + +std::string SocketAddress::asString(bool numeric) const +{ + if (!numeric) + return host + ":" + port; + // Canonicalise into numeric id + const ::addrinfo& ai = getAddrInfo(*this); + + return asString(ai.ai_addr, ai.ai_addrlen); +} + +bool SocketAddress::nextAddress() { + bool r = currentAddrInfo->ai_next != 0; + if (r) + currentAddrInfo = currentAddrInfo->ai_next; + return r; +} + +void SocketAddress::setAddrInfoPort(uint16_t port) { + if (!currentAddrInfo) return; + + ::addrinfo& ai = *currentAddrInfo; + switch (ai.ai_family) { + case AF_INET: ((::sockaddr_in*)ai.ai_addr)->sin_port = htons(port); return; + case AF_INET6:((::sockaddr_in6*)ai.ai_addr)->sin6_port = htons(port); return; + default: throw Exception(QPID_MSG("Unexpected socket type")); + } } const ::addrinfo& getAddrInfo(const SocketAddress& sa) @@ -75,7 +123,8 @@ const ::addrinfo& getAddrInfo(const SocketAddress& sa) if (!sa.addrInfo) { ::addrinfo hints; ::memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_INET; // Change this to support IPv6 + hints.ai_flags = AI_ADDRCONFIG; // Only use protocols that we have configured interfaces for + hints.ai_family = AF_UNSPEC; // Allow both IPv4 and IPv6 hints.ai_socktype = SOCK_STREAM; const char* node = 0; @@ -88,10 +137,11 @@ const ::addrinfo& getAddrInfo(const SocketAddress& sa) int n = ::getaddrinfo(node, service, &hints, &sa.addrInfo); if (n != 0) - throw Exception(QPID_MSG("Cannot resolve " << sa.host << ": " << ::gai_strerror(n))); + throw Exception(QPID_MSG("Cannot resolve " << sa.asString(false) << ": " << ::gai_strerror(n))); + sa.currentAddrInfo = sa.addrInfo; } - return *sa.addrInfo; + return *sa.currentAddrInfo; } }} diff --git a/cpp/src/qpid/sys/posix/Thread.cpp b/cpp/src/qpid/sys/posix/Thread.cpp index b466733260..a1d6396763 100644 --- a/cpp/src/qpid/sys/posix/Thread.cpp +++ b/cpp/src/qpid/sys/posix/Thread.cpp @@ -37,7 +37,8 @@ void* runRunnable(void* p) } } -struct ThreadPrivate { +class ThreadPrivate { +public: pthread_t thread; ThreadPrivate(Runnable* runnable) { diff --git a/cpp/src/qpid/sys/posix/Time.cpp b/cpp/src/qpid/sys/posix/Time.cpp index b3858279b4..9661f0c5e8 100644 --- a/cpp/src/qpid/sys/posix/Time.cpp +++ b/cpp/src/qpid/sys/posix/Time.cpp @@ -27,6 +27,7 @@ #include <stdio.h> #include <sys/time.h> #include <unistd.h> +#include <iomanip> namespace { int64_t max_abstime() { return std::numeric_limits<int64_t>::max(); } @@ -103,6 +104,12 @@ void outputFormattedNow(std::ostream& o) { o << " "; } +void outputHiresNow(std::ostream& o) { + ::timespec time; + ::clock_gettime(CLOCK_REALTIME, &time); + o << time.tv_sec << "." << std::setw(9) << std::setfill('0') << time.tv_nsec << "s "; +} + void sleep(int secs) { ::sleep(secs); } diff --git a/cpp/src/qpid/sys/rdma/RdmaIO.cpp b/cpp/src/qpid/sys/rdma/RdmaIO.cpp index c80c94cba6..78bcdec68e 100644 --- a/cpp/src/qpid/sys/rdma/RdmaIO.cpp +++ b/cpp/src/qpid/sys/rdma/RdmaIO.cpp @@ -140,8 +140,8 @@ namespace Rdma { // Prepost recv buffers before we go any further qp->allocateRecvBuffers(recvBufferCount, bufferSize+FrameHeaderSize); - // Create xmit buffers - qp->createSendBuffers(xmitBufferCount, bufferSize+FrameHeaderSize); + // Create xmit buffers, reserve space for frame header. + qp->createSendBuffers(xmitBufferCount, bufferSize, FrameHeaderSize); } AsynchIO::~AsynchIO() { @@ -210,12 +210,14 @@ namespace Rdma { } break; case 1: - Buffer* ob = buff ? buff : getSendBuffer(); + if (!buff) + buff = getSendBuffer(); // Add FrameHeader after frame data FrameHeader header(credit); - ::memcpy(ob->bytes()+ob->dataCount(), &header, FrameHeaderSize); - ob->dataCount(ob->dataCount()+FrameHeaderSize); - qp->postSend(ob); + assert(buff->dataCount() <= buff->byteCount()); // ensure app data doesn't impinge on reserved space. + ::memcpy(buff->bytes()+buff->dataCount(), &header, FrameHeaderSize); + buff->dataCount(buff->dataCount()+FrameHeaderSize); + qp->postSend(buff); break; } } diff --git a/cpp/src/qpid/sys/rdma/rdma_wrap.cpp b/cpp/src/qpid/sys/rdma/rdma_wrap.cpp index 6d38c42502..efe454c5be 100644 --- a/cpp/src/qpid/sys/rdma/rdma_wrap.cpp +++ b/cpp/src/qpid/sys/rdma/rdma_wrap.cpp @@ -50,8 +50,9 @@ namespace Rdma { return count; } - Buffer::Buffer(uint32_t lkey, char* bytes, const int32_t byteCount) : - bufferSize(byteCount) + Buffer::Buffer(uint32_t lkey, char* bytes, const int32_t byteCount, + const int32_t reserve) : + bufferSize(byteCount + reserve), reserved(reserve) { sge.addr = (uintptr_t) bytes; sge.length = 0; @@ -163,21 +164,21 @@ namespace Rdma { } // Create buffers to use for writing - void QueuePair::createSendBuffers(int sendBufferCount, int bufferSize) + void QueuePair::createSendBuffers(int sendBufferCount, int bufferSize, int reserved) { assert(!smr); // Round up buffersize to cacheline (64 bytes) - bufferSize = (bufferSize+63) & (~63); + int dataLength = (bufferSize+reserved+63) & (~63); // Allocate memory block for all receive buffers - char* mem = new char [sendBufferCount * bufferSize]; - smr = regMr(pd.get(), mem, sendBufferCount * bufferSize, ::IBV_ACCESS_LOCAL_WRITE); + char* mem = new char [sendBufferCount * dataLength]; + smr = regMr(pd.get(), mem, sendBufferCount * dataLength, ::IBV_ACCESS_LOCAL_WRITE); sendBuffers.reserve(sendBufferCount); freeBuffers.reserve(sendBufferCount); for (int i = 0; i<sendBufferCount; ++i) { // Allocate xmit buffer - sendBuffers.push_back(Buffer(smr->lkey, &mem[i*bufferSize], bufferSize)); + sendBuffers.push_back(Buffer(smr->lkey, &mem[i*dataLength], bufferSize, reserved)); freeBuffers.push_back(i); } } diff --git a/cpp/src/qpid/sys/rdma/rdma_wrap.h b/cpp/src/qpid/sys/rdma/rdma_wrap.h index 28bddd2165..8e3429027b 100644 --- a/cpp/src/qpid/sys/rdma/rdma_wrap.h +++ b/cpp/src/qpid/sys/rdma/rdma_wrap.h @@ -57,8 +57,9 @@ namespace Rdma { void dataCount(int32_t); private: - Buffer(uint32_t lkey, char* bytes, const int32_t byteCount); + Buffer(uint32_t lkey, char* bytes, const int32_t byteCount, const int32_t reserve=0); int32_t bufferSize; + int32_t reserved; // for framing header ::ibv_sge sge; }; @@ -66,8 +67,9 @@ namespace Rdma { return (char*) sge.addr; } + /** return the number of bytes available for application data */ inline int32_t Buffer::byteCount() const { - return bufferSize; + return bufferSize - reserved; } inline int32_t Buffer::dataCount() const { @@ -75,6 +77,8 @@ namespace Rdma { } inline void Buffer::dataCount(int32_t s) { + // catch any attempt to overflow a buffer + assert(s <= bufferSize + reserved); sge.length = s; } @@ -136,7 +140,7 @@ namespace Rdma { typedef boost::intrusive_ptr<QueuePair> intrusive_ptr; // Create a buffers to use for writing - void createSendBuffers(int sendBufferCount, int bufferSize); + void createSendBuffers(int sendBufferCount, int dataSize, int headerSize); // Get a send buffer Buffer* getSendBuffer(); diff --git a/cpp/src/qpid/sys/ssl/SslHandler.h b/cpp/src/qpid/sys/ssl/SslHandler.h index a340109966..400fa317fd 100644 --- a/cpp/src/qpid/sys/ssl/SslHandler.h +++ b/cpp/src/qpid/sys/ssl/SslHandler.h @@ -35,7 +35,7 @@ namespace sys { namespace ssl { class SslIO; -class SslIOBufferBase; +struct SslIOBufferBase; class SslSocket; class SslHandler : public OutputControl { diff --git a/cpp/src/qpid/sys/ssl/SslIo.cpp b/cpp/src/qpid/sys/ssl/SslIo.cpp index a58a137473..4a59819183 100644 --- a/cpp/src/qpid/sys/ssl/SslIo.cpp +++ b/cpp/src/qpid/sys/ssl/SslIo.cpp @@ -68,29 +68,33 @@ __thread int64_t threadMaxReadTimeNs = 2 * 1000000; // start at 2ms * Asynch Acceptor */ -SslAcceptor::SslAcceptor(const SslSocket& s, Callback callback) : +template <class T> +SslAcceptorTmpl<T>::SslAcceptorTmpl(const T& s, Callback callback) : acceptedCallback(callback), - handle(s, boost::bind(&SslAcceptor::readable, this, _1), 0, 0), + handle(s, boost::bind(&SslAcceptorTmpl<T>::readable, this, _1), 0, 0), socket(s) { s.setNonblocking(); ignoreSigpipe(); } -SslAcceptor::~SslAcceptor() +template <class T> +SslAcceptorTmpl<T>::~SslAcceptorTmpl() { handle.stopWatch(); } -void SslAcceptor::start(Poller::shared_ptr poller) { +template <class T> +void SslAcceptorTmpl<T>::start(Poller::shared_ptr poller) { handle.startWatch(poller); } /* * We keep on accepting as long as there is something to accept */ -void SslAcceptor::readable(DispatchHandle& h) { - SslSocket* s; +template <class T> +void SslAcceptorTmpl<T>::readable(DispatchHandle& h) { + Socket* s; do { errno = 0; // TODO: Currently we ignore the peers address, perhaps we should @@ -110,6 +114,10 @@ void SslAcceptor::readable(DispatchHandle& h) { h.rewatch(); } +// Explicitly instantiate the templates we need +template class SslAcceptorTmpl<SslSocket>; +template class SslAcceptorTmpl<SslMuxSocket>; + /* * Asynch Connector */ @@ -117,7 +125,7 @@ void SslAcceptor::readable(DispatchHandle& h) { SslConnector::SslConnector(const SslSocket& s, Poller::shared_ptr poller, std::string hostname, - uint16_t port, + std::string port, ConnectedCallback connCb, FailedCallback failCb) : DispatchHandle(s, diff --git a/cpp/src/qpid/sys/ssl/SslIo.h b/cpp/src/qpid/sys/ssl/SslIo.h index 53ac69d8d6..c980d73831 100644 --- a/cpp/src/qpid/sys/ssl/SslIo.h +++ b/cpp/src/qpid/sys/ssl/SslIo.h @@ -29,26 +29,30 @@ namespace qpid { namespace sys { + +class Socket; + namespace ssl { - + class SslSocket; /* * Asynchronous ssl acceptor: accepts connections then does a callback * with the accepted fd */ -class SslAcceptor { +template <class T> +class SslAcceptorTmpl { public: - typedef boost::function1<void, const SslSocket&> Callback; + typedef boost::function1<void, const Socket&> Callback; private: Callback acceptedCallback; qpid::sys::DispatchHandle handle; - const SslSocket& socket; + const T& socket; public: - SslAcceptor(const SslSocket& s, Callback callback); - ~SslAcceptor(); + SslAcceptorTmpl(const T& s, Callback callback); + ~SslAcceptorTmpl(); void start(qpid::sys::Poller::shared_ptr poller); private: @@ -73,7 +77,7 @@ public: SslConnector(const SslSocket& socket, Poller::shared_ptr poller, std::string hostname, - uint16_t port, + std::string port, ConnectedCallback connCb, FailedCallback failCb = 0); diff --git a/cpp/src/qpid/sys/ssl/SslSocket.cpp b/cpp/src/qpid/sys/ssl/SslSocket.cpp index 01e2658877..30234bb686 100644 --- a/cpp/src/qpid/sys/ssl/SslSocket.cpp +++ b/cpp/src/qpid/sys/ssl/SslSocket.cpp @@ -25,11 +25,13 @@ #include "qpid/Exception.h" #include "qpid/sys/posix/check.h" #include "qpid/sys/posix/PrivatePosix.h" +#include "qpid/log/Statement.h" #include <fcntl.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/errno.h> +#include <poll.h> #include <netinet/in.h> #include <netinet/tcp.h> #include <netdb.h> @@ -50,36 +52,6 @@ namespace sys { namespace ssl { namespace { -std::string getName(int fd, bool local, bool includeService = false) -{ - ::sockaddr_storage name; // big enough for any socket address - ::socklen_t namelen = sizeof(name); - - int result = -1; - if (local) { - result = ::getsockname(fd, (::sockaddr*)&name, &namelen); - } else { - result = ::getpeername(fd, (::sockaddr*)&name, &namelen); - } - - QPID_POSIX_CHECK(result); - - char servName[NI_MAXSERV]; - char dispName[NI_MAXHOST]; - if (includeService) { - if (int rc=::getnameinfo((::sockaddr*)&name, namelen, dispName, sizeof(dispName), - servName, sizeof(servName), - NI_NUMERICHOST | NI_NUMERICSERV) != 0) - throw QPID_POSIX_ERROR(rc); - return std::string(dispName) + ":" + std::string(servName); - - } else { - if (int rc=::getnameinfo((::sockaddr*)&name, namelen, dispName, sizeof(dispName), 0, 0, NI_NUMERICHOST) != 0) - throw QPID_POSIX_ERROR(rc); - return dispName; - } -} - std::string getService(int fd, bool local) { ::sockaddr_storage name; // big enough for any socket address @@ -132,7 +104,7 @@ std::string getDomainFromSubject(std::string subject) } -SslSocket::SslSocket() : IOHandle(new IOHandlePrivate()), socket(0), prototype(0) +SslSocket::SslSocket() : socket(0), prototype(0) { impl->fd = ::socket (PF_INET, SOCK_STREAM, 0); if (impl->fd < 0) throw QPID_POSIX_ERROR(errno); @@ -144,7 +116,7 @@ SslSocket::SslSocket() : IOHandle(new IOHandlePrivate()), socket(0), prototype(0 * returned from accept. Because we use posix accept rather than * PR_Accept, we have to reset the handshake. */ -SslSocket::SslSocket(IOHandlePrivate* ioph, PRFileDesc* model) : IOHandle(ioph), socket(0), prototype(0) +SslSocket::SslSocket(IOHandlePrivate* ioph, PRFileDesc* model) : Socket(ioph), socket(0), prototype(0) { socket = SSL_ImportFD(model, PR_ImportTCPSocket(impl->fd)); NSS_CHECK(SSL_ResetHandshake(socket, true)); @@ -158,7 +130,7 @@ void SslSocket::setNonblocking() const PR_SetSocketOption(socket, &option); } -void SslSocket::connect(const std::string& host, uint16_t port) const +void SslSocket::connect(const std::string& host, const std::string& port) const { std::stringstream namestream; namestream << host << ":" << port; @@ -180,7 +152,7 @@ void SslSocket::connect(const std::string& host, uint16_t port) const PRHostEnt hostEntry; PR_CHECK(PR_GetHostByName(host.data(), hostBuffer, PR_NETDB_BUF_SIZE, &hostEntry)); PRNetAddr address; - int value = PR_EnumerateHostEnt(0, &hostEntry, port, &address); + int value = PR_EnumerateHostEnt(0, &hostEntry, boost::lexical_cast<PRUint16>(port), &address); if (value < 0) { throw Exception(QPID_MSG("Error getting address for host: " << ErrorString())); } else if (value == 0) { @@ -238,6 +210,7 @@ int SslSocket::listen(uint16_t port, int backlog, const std::string& certName, b SslSocket* SslSocket::accept() const { + QPID_LOG(trace, "Accepting SSL connection."); int afd = ::accept(impl->fd, 0, 0); if ( afd >= 0) { return new SslSocket(new IOHandlePrivate(afd), prototype); @@ -248,36 +221,109 @@ SslSocket* SslSocket::accept() const } } -int SslSocket::read(void *buf, size_t count) const -{ - return PR_Read(socket, buf, count); -} +#define SSL_STREAM_MAX_WAIT_ms 20 +#define SSL_STREAM_MAX_RETRIES 2 -int SslSocket::write(const void *buf, size_t count) const -{ - return PR_Write(socket, buf, count); -} +static bool isSslStream(int afd) { + int retries = SSL_STREAM_MAX_RETRIES; + unsigned char buf[5] = {}; -std::string SslSocket::getSockname() const -{ - return getName(impl->fd, true); + do { + struct pollfd fd = {afd, POLLIN, 0}; + + /* + * Note that this is blocking the accept thread, so connections that + * send no data can limit the rate at which we can accept new + * connections. + */ + if (::poll(&fd, 1, SSL_STREAM_MAX_WAIT_ms) > 0) { + errno = 0; + int result = recv(afd, buf, sizeof(buf), MSG_PEEK | MSG_DONTWAIT); + if (result == sizeof(buf)) { + break; + } + if (errno && errno != EAGAIN) { + int err = errno; + ::close(afd); + throw QPID_POSIX_ERROR(err); + } + } + } while (retries-- > 0); + + if (retries < 0) { + return false; + } + + /* + * SSLv2 Client Hello format + * http://www.mozilla.org/projects/security/pki/nss/ssl/draft02.html + * + * Bytes 0-1: RECORD-LENGTH + * Byte 2: MSG-CLIENT-HELLO (1) + * Byte 3: CLIENT-VERSION-MSB + * Byte 4: CLIENT-VERSION-LSB + * + * Allowed versions: + * 2.0 - SSLv2 + * 3.0 - SSLv3 + * 3.1 - TLS 1.0 + * 3.2 - TLS 1.1 + * 3.3 - TLS 1.2 + * + * The version sent in the Client-Hello is the latest version supported by + * the client. NSS may send version 3.x in an SSLv2 header for + * maximum compatibility. + */ + bool isSSL2Handshake = buf[2] == 1 && // MSG-CLIENT-HELLO + ((buf[3] == 3 && buf[4] <= 3) || // SSL 3.0 & TLS 1.0-1.2 (v3.1-3.3) + (buf[3] == 2 && buf[4] == 0)); // SSL 2 + + /* + * SSLv3/TLS Client Hello format + * RFC 2246 + * + * Byte 0: ContentType (handshake - 22) + * Bytes 1-2: ProtocolVersion {major, minor} + * + * Allowed versions: + * 3.0 - SSLv3 + * 3.1 - TLS 1.0 + * 3.2 - TLS 1.1 + * 3.3 - TLS 1.2 + */ + bool isSSL3Handshake = buf[0] == 22 && // handshake + (buf[1] == 3 && buf[2] <= 3); // SSL 3.0 & TLS 1.0-1.2 (v3.1-3.3) + + return isSSL2Handshake || isSSL3Handshake; } -std::string SslSocket::getPeername() const +Socket* SslMuxSocket::accept() const { - return getName(impl->fd, false); + int afd = ::accept(impl->fd, 0, 0); + if (afd >= 0) { + QPID_LOG(trace, "Accepting connection with optional SSL wrapper."); + if (isSslStream(afd)) { + QPID_LOG(trace, "Accepted SSL connection."); + return new SslSocket(new IOHandlePrivate(afd), prototype); + } else { + QPID_LOG(trace, "Accepted Plaintext connection."); + return new Socket(new IOHandlePrivate(afd)); + } + } else if (errno == EAGAIN) { + return 0; + } else { + throw QPID_POSIX_ERROR(errno); + } } -std::string SslSocket::getPeerAddress() const +int SslSocket::read(void *buf, size_t count) const { - if (!connectname.empty()) - return connectname; - return getName(impl->fd, false, true); + return PR_Read(socket, buf, count); } -std::string SslSocket::getLocalAddress() const +int SslSocket::write(const void *buf, size_t count) const { - return getName(impl->fd, true, true); + return PR_Write(socket, buf, count); } uint16_t SslSocket::getLocalPort() const @@ -290,17 +336,6 @@ uint16_t SslSocket::getRemotePort() const return atoi(getService(impl->fd, true).c_str()); } -int SslSocket::getError() const -{ - int result; - socklen_t rSize = sizeof (result); - - if (::getsockopt(impl->fd, SOL_SOCKET, SO_ERROR, &result, &rSize) < 0) - throw QPID_POSIX_ERROR(errno); - - return result; -} - void SslSocket::setTcpNoDelay(bool nodelay) const { if (nodelay) { diff --git a/cpp/src/qpid/sys/ssl/SslSocket.h b/cpp/src/qpid/sys/ssl/SslSocket.h index 25712c98d5..eabadcbe23 100644 --- a/cpp/src/qpid/sys/ssl/SslSocket.h +++ b/cpp/src/qpid/sys/ssl/SslSocket.h @@ -23,6 +23,7 @@ */ #include "qpid/sys/IOHandle.h" +#include "qpid/sys/Socket.h" #include <nspr.h> #include <string> @@ -36,7 +37,7 @@ class Duration; namespace ssl { -class SslSocket : public qpid::sys::IOHandle +class SslSocket : public qpid::sys::Socket { public: /** Create a socket wrapper for descriptor. */ @@ -53,7 +54,7 @@ public: * NSSInit().*/ void setCertName(const std::string& certName); - void connect(const std::string& host, uint16_t port) const; + void connect(const std::string& host, const std::string& port) const; void close() const; @@ -75,45 +76,13 @@ public: int read(void *buf, size_t count) const; int write(const void *buf, size_t count) const; - /** Returns the "socket name" ie the address bound to - * the near end of the socket - */ - std::string getSockname() const; - - /** Returns the "peer name" ie the address bound to - * the remote end of the socket - */ - std::string getPeername() const; - - /** - * Returns an address (host and port) for the remote end of the - * socket - */ - std::string getPeerAddress() const; - /** - * Returns an address (host and port) for the local end of the - * socket - */ - std::string getLocalAddress() const; - - /** - * Returns the full address of the connection: local and remote host and port. - */ - std::string getFullAddress() const { return getLocalAddress()+"-"+getPeerAddress(); } - uint16_t getLocalPort() const; uint16_t getRemotePort() const; - /** - * Returns the error code stored in the socket. This may be used - * to determine the result of a non-blocking connect. - */ - int getError() const; - int getKeyLen() const; std::string getClientAuthId() const; -private: +protected: mutable std::string connectname; mutable PRFileDesc* socket; std::string certname; @@ -126,6 +95,13 @@ private: mutable PRFileDesc* prototype; SslSocket(IOHandlePrivate* ioph, PRFileDesc* model); + friend class SslMuxSocket; +}; + +class SslMuxSocket : public SslSocket +{ +public: + Socket* accept() const; }; }}} diff --git a/cpp/src/qpid/sys/windows/AsynchIO.cpp b/cpp/src/qpid/sys/windows/AsynchIO.cpp index 38d8842521..30378d4c5f 100644 --- a/cpp/src/qpid/sys/windows/AsynchIO.cpp +++ b/cpp/src/qpid/sys/windows/AsynchIO.cpp @@ -30,6 +30,7 @@ #include "qpid/log/Statement.h" #include "qpid/sys/windows/check.h" +#include "qpid/sys/windows/mingw32_compat.h" #include <boost/thread/once.hpp> @@ -46,16 +47,13 @@ namespace { /* * The function pointers for AcceptEx and ConnectEx need to be looked up - * at run time. Make sure this is done only once. + * at run time. */ -boost::once_flag lookUpAcceptExOnce = BOOST_ONCE_INIT; -LPFN_ACCEPTEX fnAcceptEx = 0; -typedef void (*lookUpFunc)(const qpid::sys::Socket &); - -void lookUpAcceptEx() { - SOCKET h = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); +const LPFN_ACCEPTEX lookUpAcceptEx(const qpid::sys::Socket& s) { + SOCKET h = toSocketHandle(s); GUID guidAcceptEx = WSAID_ACCEPTEX; DWORD dwBytes = 0; + LPFN_ACCEPTEX fnAcceptEx; WSAIoctl(h, SIO_GET_EXTENSION_FUNCTION_POINTER, &guidAcceptEx, @@ -65,9 +63,9 @@ void lookUpAcceptEx() { &dwBytes, NULL, NULL); - closesocket(h); if (fnAcceptEx == 0) throw qpid::Exception(QPID_MSG("Failed to look up AcceptEx")); + return fnAcceptEx; } } @@ -94,18 +92,15 @@ private: AsynchAcceptor::Callback acceptedCallback; const Socket& socket; + const LPFN_ACCEPTEX fnAcceptEx; }; AsynchAcceptor::AsynchAcceptor(const Socket& s, Callback callback) : acceptedCallback(callback), - socket(s) { + socket(s), + fnAcceptEx(lookUpAcceptEx(s)) { s.setNonblocking(); -#if (BOOST_VERSION >= 103500) /* boost 1.35 or later reversed the args */ - boost::call_once(lookUpAcceptExOnce, lookUpAcceptEx); -#else - boost::call_once(lookUpAcceptEx, lookUpAcceptExOnce); -#endif } AsynchAcceptor::~AsynchAcceptor() @@ -114,7 +109,8 @@ AsynchAcceptor::~AsynchAcceptor() } void AsynchAcceptor::start(Poller::shared_ptr poller) { - poller->monitorHandle(PollerHandle(socket), Poller::INPUT); + PollerHandle ph = PollerHandle(socket); + poller->monitorHandle(ph, Poller::INPUT); restart (); } @@ -122,25 +118,26 @@ void AsynchAcceptor::restart(void) { DWORD bytesReceived = 0; // Not used, needed for AcceptEx API AsynchAcceptResult *result = new AsynchAcceptResult(acceptedCallback, this, - toSocketHandle(socket)); + socket); BOOL status; - status = ::fnAcceptEx(toSocketHandle(socket), - toSocketHandle(*result->newSocket), - result->addressBuffer, - 0, - AsynchAcceptResult::SOCKADDRMAXLEN, - AsynchAcceptResult::SOCKADDRMAXLEN, - &bytesReceived, - result->overlapped()); + status = fnAcceptEx(toSocketHandle(socket), + toSocketHandle(*result->newSocket), + result->addressBuffer, + 0, + AsynchAcceptResult::SOCKADDRMAXLEN, + AsynchAcceptResult::SOCKADDRMAXLEN, + &bytesReceived, + result->overlapped()); QPID_WINDOWS_CHECK_ASYNC_START(status); } AsynchAcceptResult::AsynchAcceptResult(AsynchAcceptor::Callback cb, AsynchAcceptor *acceptor, - SOCKET listener) - : callback(cb), acceptor(acceptor), listener(listener) { - newSocket.reset (new Socket()); + const Socket& listener) + : callback(cb), acceptor(acceptor), + listener(toSocketHandle(listener)), + newSocket(listener.createSameTypeSocket()) { } void AsynchAcceptResult::success(size_t /*bytesTransferred*/) { @@ -154,7 +151,7 @@ void AsynchAcceptResult::success(size_t /*bytesTransferred*/) { delete this; } -void AsynchAcceptResult::failure(int status) { +void AsynchAcceptResult::failure(int /*status*/) { //if (status != WSA_OPERATION_ABORTED) // Can there be anything else? ; delete this; @@ -173,20 +170,20 @@ private: FailedCallback failCallback; const Socket& socket; const std::string hostname; - const uint16_t port; + const std::string port; public: AsynchConnector(const Socket& socket, - std::string hostname, - uint16_t port, + const std::string& hostname, + const std::string& port, ConnectedCallback connCb, FailedCallback failCb = 0); void start(Poller::shared_ptr poller); }; AsynchConnector::AsynchConnector(const Socket& sock, - std::string hname, - uint16_t p, + const std::string& hname, + const std::string& p, ConnectedCallback connCb, FailedCallback failCb) : connCallback(connCb), failCallback(failCb), socket(sock), @@ -216,8 +213,8 @@ AsynchAcceptor* AsynchAcceptor::create(const Socket& s, } AsynchConnector* qpid::sys::AsynchConnector::create(const Socket& s, - std::string hostname, - uint16_t port, + const std::string& hostname, + const std::string& port, ConnectedCallback connCb, FailedCallback failCb) { @@ -410,8 +407,9 @@ void AsynchIO::queueForDeletion() { } void AsynchIO::start(Poller::shared_ptr poller0) { + PollerHandle ph = PollerHandle(socket); poller = poller0; - poller->monitorHandle(PollerHandle(socket), Poller::INPUT); + poller->monitorHandle(ph, Poller::INPUT); if (writeQueue.size() > 0) // Already have data queued for write notifyPendingWrite(); startReading(); @@ -584,7 +582,6 @@ void AsynchIO::notifyIdle(void) { void AsynchIO::startWrite(AsynchIO::BufferBase* buff) { writeInProgress = true; InterlockedIncrement(&opsInProgress); - int writeCount = buff->byteCount-buff->dataCount; AsynchWriteResult *result = new AsynchWriteResult(boost::bind(&AsynchIO::completion, this, _1), buff, diff --git a/cpp/src/qpid/sys/windows/AsynchIoResult.h b/cpp/src/qpid/sys/windows/AsynchIoResult.h index 66c89efc11..27e4c22138 100755 --- a/cpp/src/qpid/sys/windows/AsynchIoResult.h +++ b/cpp/src/qpid/sys/windows/AsynchIoResult.h @@ -83,22 +83,22 @@ class AsynchAcceptResult : public AsynchResult { public: AsynchAcceptResult(qpid::sys::AsynchAcceptor::Callback cb, AsynchAcceptor *acceptor, - SOCKET listener); + const qpid::sys::Socket& listener); virtual void success (size_t bytesTransferred); virtual void failure (int error); private: virtual void complete(void) {} // No-op for this class. - std::auto_ptr<qpid::sys::Socket> newSocket; qpid::sys::AsynchAcceptor::Callback callback; AsynchAcceptor *acceptor; SOCKET listener; + std::auto_ptr<qpid::sys::Socket> newSocket; // AcceptEx needs a place to write the local and remote addresses // when accepting the connection. Place those here; get enough for // IPv6 addresses, even if the socket is IPv4. - enum { SOCKADDRMAXLEN = sizeof sockaddr_in6 + 16, + enum { SOCKADDRMAXLEN = sizeof(sockaddr_in6) + 16, SOCKADDRBUFLEN = 2 * SOCKADDRMAXLEN }; char addressBuffer[SOCKADDRBUFLEN]; }; diff --git a/cpp/src/qpid/sys/windows/IocpPoller.cpp b/cpp/src/qpid/sys/windows/IocpPoller.cpp index d326ab02ac..1805dd2cd8 100755 --- a/cpp/src/qpid/sys/windows/IocpPoller.cpp +++ b/cpp/src/qpid/sys/windows/IocpPoller.cpp @@ -152,9 +152,9 @@ void Poller::monitorHandle(PollerHandle& handle, Direction dir) { } // All no-ops... -void Poller::unmonitorHandle(PollerHandle& handle, Direction dir) {} -void Poller::registerHandle(PollerHandle& handle) {} -void Poller::unregisterHandle(PollerHandle& handle) {} +void Poller::unmonitorHandle(PollerHandle& /*handle*/, Direction /*dir*/) {} +void Poller::registerHandle(PollerHandle& /*handle*/) {} +void Poller::unregisterHandle(PollerHandle& /*handle*/) {} Poller::Event Poller::wait(Duration timeout) { DWORD timeoutMs = 0; diff --git a/cpp/src/qpid/sys/windows/Shlib.cpp b/cpp/src/qpid/sys/windows/Shlib.cpp index 38027de93f..ba18747eb4 100644 --- a/cpp/src/qpid/sys/windows/Shlib.cpp +++ b/cpp/src/qpid/sys/windows/Shlib.cpp @@ -44,7 +44,8 @@ void Shlib::unload() { } void* Shlib::getSymbol(const char* name) { - void* sym = GetProcAddress(static_cast<HMODULE>(handle), name); + // Double cast avoids warning about casting function pointer to object + void *sym = reinterpret_cast<void*>(reinterpret_cast<intptr_t>(GetProcAddress(static_cast<HMODULE>(handle), name))); if (sym == NULL) throw QPID_WINDOWS_ERROR(GetLastError()); return sym; diff --git a/cpp/src/qpid/sys/windows/Socket.cpp b/cpp/src/qpid/sys/windows/Socket.cpp index 11fb8b4133..1fa4768329 100755..100644 --- a/cpp/src/qpid/sys/windows/Socket.cpp +++ b/cpp/src/qpid/sys/windows/Socket.cpp @@ -20,19 +20,18 @@ */ #include "qpid/sys/Socket.h" + #include "qpid/sys/SocketAddress.h" -#include "qpid/sys/windows/IoHandlePrivate.h" #include "qpid/sys/windows/check.h" -#include "qpid/sys/Time.h" +#include "qpid/sys/windows/IoHandlePrivate.h" -#include <cstdlib> -#include <string.h> +// Ensure we get all of winsock2.h +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 +#endif #include <winsock2.h> -#include <boost/format.hpp> -#include <boost/lexical_cast.hpp> - // Need to initialize WinSock. Ideally, this would be a singleton or embedded // in some one-time initialization function. I tried boost singleton and could // not get it to compile (and others located in google had the same problem). @@ -84,53 +83,30 @@ namespace sys { namespace { -std::string getName(SOCKET fd, bool local, bool includeService = false) +std::string getName(SOCKET fd, bool local) { - sockaddr_in name; // big enough for any socket address - socklen_t namelen = sizeof(name); + ::sockaddr_storage name_s; // big enough for any socket address + ::sockaddr* name = (::sockaddr*)&name_s; + ::socklen_t namelen = sizeof(name_s); + if (local) { - QPID_WINSOCK_CHECK(::getsockname(fd, (sockaddr*)&name, &namelen)); + QPID_WINSOCK_CHECK(::getsockname(fd, name, &namelen)); } else { - QPID_WINSOCK_CHECK(::getpeername(fd, (sockaddr*)&name, &namelen)); + QPID_WINSOCK_CHECK(::getpeername(fd, name, &namelen)); } - char servName[NI_MAXSERV]; - char dispName[NI_MAXHOST]; - if (includeService) { - if (int rc = ::getnameinfo((sockaddr*)&name, namelen, - dispName, sizeof(dispName), - servName, sizeof(servName), - NI_NUMERICHOST | NI_NUMERICSERV) != 0) - throw qpid::Exception(QPID_MSG(gai_strerror(rc))); - return std::string(dispName) + ":" + std::string(servName); - } else { - if (int rc = ::getnameinfo((sockaddr*)&name, namelen, - dispName, sizeof(dispName), - 0, 0, - NI_NUMERICHOST) != 0) - throw qpid::Exception(QPID_MSG(gai_strerror(rc))); - return dispName; - } + return SocketAddress::asString(name, namelen); } -std::string getService(SOCKET fd, bool local) +uint16_t getLocalPort(int fd) { - sockaddr_in name; // big enough for any socket address - socklen_t namelen = sizeof(name); - - if (local) { - QPID_WINSOCK_CHECK(::getsockname(fd, (sockaddr*)&name, &namelen)); - } else { - QPID_WINSOCK_CHECK(::getpeername(fd, (sockaddr*)&name, &namelen)); - } + ::sockaddr_storage name_s; // big enough for any socket address + ::sockaddr* name = (::sockaddr*)&name_s; + ::socklen_t namelen = sizeof(name_s); + + QPID_WINSOCK_CHECK(::getsockname(fd, name, &namelen)); - char servName[NI_MAXSERV]; - if (int rc = ::getnameinfo((sockaddr*)&name, namelen, - 0, 0, - servName, sizeof(servName), - NI_NUMERICHOST | NI_NUMERICSERV) != 0) - throw qpid::Exception(QPID_MSG(gai_strerror(rc))); - return servName; + return SocketAddress::getPort(name); } } // namespace @@ -138,13 +114,7 @@ Socket::Socket() : IOHandle(new IOHandlePrivate), nonblocking(false), nodelay(false) -{ - SOCKET& socket = impl->fd; - if (socket != INVALID_SOCKET) Socket::close(); - SOCKET s = ::socket (PF_INET, SOCK_STREAM, 0); - if (s == INVALID_SOCKET) throw QPID_WINDOWS_ERROR(WSAGetLastError()); - socket = s; -} +{} Socket::Socket(IOHandlePrivate* h) : IOHandle(h), @@ -152,8 +122,7 @@ Socket::Socket(IOHandlePrivate* h) : nodelay(false) {} -void -Socket::createSocket(const SocketAddress& sa) const +void Socket::createSocket(const SocketAddress& sa) const { SOCKET& socket = impl->fd; if (socket != INVALID_SOCKET) Socket::close(); @@ -168,24 +137,24 @@ Socket::createSocket(const SocketAddress& sa) const if (nonblocking) setNonblocking(); if (nodelay) setTcpNoDelay(); } catch (std::exception&) { - closesocket(s); + ::closesocket(s); socket = INVALID_SOCKET; throw; } } -void Socket::setTimeout(const Duration& interval) const -{ - const SOCKET& socket = impl->fd; - int64_t nanosecs = interval; - nanosecs /= (1000 * 1000); // nsecs -> usec -> msec - int msec = 0; - if (nanosecs > std::numeric_limits<int>::max()) - msec = std::numeric_limits<int>::max(); - else - msec = static_cast<int>(nanosecs); - setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, (char *)&msec, sizeof(msec)); - setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&msec, sizeof(msec)); +Socket* Socket::createSameTypeSocket() const { + SOCKET& socket = impl->fd; + // Socket currently has no actual socket attached + if (socket == INVALID_SOCKET) + return new Socket; + + ::sockaddr_storage sa; + ::socklen_t salen = sizeof(sa); + QPID_WINSOCK_CHECK(::getsockname(socket, (::sockaddr*)&sa, &salen)); + SOCKET s = ::socket(sa.ss_family, SOCK_STREAM, 0); // Currently only work with SOCK_STREAM + if (s == INVALID_SOCKET) throw QPID_WINDOWS_ERROR(WSAGetLastError()); + return new Socket(new IOHandlePrivate(s)); } void Socket::setNonblocking() const { @@ -193,30 +162,25 @@ void Socket::setNonblocking() const { QPID_WINSOCK_CHECK(ioctlsocket(impl->fd, FIONBIO, &nonblock)); } -void Socket::connect(const std::string& host, uint16_t port) const +void Socket::connect(const std::string& host, const std::string& port) const { - SocketAddress sa(host, boost::lexical_cast<std::string>(port)); + SocketAddress sa(host, port); connect(sa); } void Socket::connect(const SocketAddress& addr) const { + peername = addr.asString(false); + + createSocket(addr); + const SOCKET& socket = impl->fd; - const addrinfo *addrs = &(getAddrInfo(addr)); - int error = 0; + int err; WSASetLastError(0); - while (addrs != 0) { - if ((::connect(socket, addrs->ai_addr, addrs->ai_addrlen) == 0) || - (WSAGetLastError() == WSAEWOULDBLOCK)) - break; - // Error... save this error code and see if there are other address - // to try before throwing the exception. - error = WSAGetLastError(); - addrs = addrs->ai_next; - } - if (error) - throw qpid::Exception(QPID_MSG(strError(error) << ": " << connectname)); + if ((::connect(socket, getAddrInfo(addr).ai_addr, getAddrInfo(addr).ai_addrlen) != 0) && + ((err = ::WSAGetLastError()) != WSAEWOULDBLOCK)) + throw qpid::Exception(QPID_MSG(strError(err) << ": " << peername)); } void @@ -247,24 +211,26 @@ int Socket::read(void *buf, size_t count) const return received; } -int Socket::listen(uint16_t port, int backlog) const +int Socket::listen(const std::string& host, const std::string& port, int backlog) const +{ + SocketAddress sa(host, port); + return listen(sa, backlog); +} + +int Socket::listen(const SocketAddress& addr, int backlog) const { + createSocket(addr); + const SOCKET& socket = impl->fd; BOOL yes=1; QPID_WINSOCK_CHECK(setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (char *)&yes, sizeof(yes))); - struct sockaddr_in name; - memset(&name, 0, sizeof(name)); - name.sin_family = AF_INET; - name.sin_port = htons(port); - name.sin_addr.s_addr = 0; - if (::bind(socket, (struct sockaddr*)&name, sizeof(name)) == SOCKET_ERROR) - throw Exception(QPID_MSG("Can't bind to port " << port << ": " << strError(WSAGetLastError()))); + + if (::bind(socket, getAddrInfo(addr).ai_addr, getAddrInfo(addr).ai_addrlen) == SOCKET_ERROR) + throw Exception(QPID_MSG("Can't bind to " << addr.asString() << ": " << strError(WSAGetLastError()))); if (::listen(socket, backlog) == SOCKET_ERROR) - throw Exception(QPID_MSG("Can't listen on port " << port << ": " << strError(WSAGetLastError()))); - - socklen_t namelen = sizeof(name); - QPID_WINSOCK_CHECK(::getsockname(socket, (struct sockaddr*)&name, &namelen)); - return ntohs(name.sin_port); + throw Exception(QPID_MSG("Can't listen on " <<addr.asString() << ": " << strError(WSAGetLastError()))); + + return getLocalPort(socket); } Socket* Socket::accept() const @@ -277,36 +243,20 @@ Socket* Socket::accept() const else throw QPID_WINDOWS_ERROR(WSAGetLastError()); } -std::string Socket::getSockname() const -{ - return getName(impl->fd, true); -} - -std::string Socket::getPeername() const -{ - return getName(impl->fd, false); -} - std::string Socket::getPeerAddress() const { - if (!connectname.empty()) - return std::string (connectname); - return getName(impl->fd, false, true); + if (peername.empty()) { + peername = getName(impl->fd, false); + } + return peername; } std::string Socket::getLocalAddress() const { - return getName(impl->fd, true, true); -} - -uint16_t Socket::getLocalPort() const -{ - return atoi(getService(impl->fd, true).c_str()); -} - -uint16_t Socket::getRemotePort() const -{ - return atoi(getService(impl->fd, true).c_str()); + if (localname.empty()) { + localname = getName(impl->fd, true); + } + return localname; } int Socket::getError() const diff --git a/cpp/src/qpid/sys/windows/SocketAddress.cpp b/cpp/src/qpid/sys/windows/SocketAddress.cpp index 501cff1297..77bbf85810 100644 --- a/cpp/src/qpid/sys/windows/SocketAddress.cpp +++ b/cpp/src/qpid/sys/windows/SocketAddress.cpp @@ -21,7 +21,13 @@ #include "qpid/sys/SocketAddress.h" -#include "qpid/sys/windows/check.h" +#include "qpid/Exception.h" +#include "qpid/Msg.h" + +// Ensure we get all of winsock2.h +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 +#endif #include <winsock2.h> #include <ws2tcpip.h> @@ -35,37 +41,111 @@ SocketAddress::SocketAddress(const std::string& host0, const std::string& port0) port(port0), addrInfo(0) { - ::addrinfo hints; - ::memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_INET; // In order to allow AF_INET6 we'd have to change createTcp() as well - hints.ai_socktype = SOCK_STREAM; - - const char* node = 0; - if (host.empty()) { - hints.ai_flags |= AI_PASSIVE; - } else { - node = host.c_str(); - } - const char* service = port.empty() ? "0" : port.c_str(); +} - int n = ::getaddrinfo(node, service, &hints, &addrInfo); - if (n != 0) - throw Exception(QPID_MSG("Cannot resolve " << host << ": " << ::gai_strerror(n))); +SocketAddress::SocketAddress(const SocketAddress& sa) : + host(sa.host), + port(sa.port), + addrInfo(0) +{ +} + +SocketAddress& SocketAddress::operator=(const SocketAddress& sa) +{ + SocketAddress temp(sa); + + std::swap(temp, *this); + return *this; } SocketAddress::~SocketAddress() { - ::freeaddrinfo(addrInfo); + if (addrInfo) { + ::freeaddrinfo(addrInfo); + } } -std::string SocketAddress::asString() const +std::string SocketAddress::asString(::sockaddr const * const addr, size_t addrlen) { - return host + ":" + port; + char servName[NI_MAXSERV]; + char dispName[NI_MAXHOST]; + if (int rc=::getnameinfo(addr, addrlen, + dispName, sizeof(dispName), + servName, sizeof(servName), + NI_NUMERICHOST | NI_NUMERICSERV) != 0) + throw qpid::Exception(QPID_MSG(gai_strerror(rc))); + std::string s; + switch (addr->sa_family) { + case AF_INET: s += dispName; break; + case AF_INET6: s += "["; s += dispName; s+= "]"; break; + default: throw Exception(QPID_MSG("Unexpected socket type")); + } + s += ":"; + s += servName; + return s; +} + +uint16_t SocketAddress::getPort(::sockaddr const * const addr) +{ + switch (addr->sa_family) { + case AF_INET: return ntohs(((::sockaddr_in*)addr)->sin_port); + case AF_INET6: return ntohs(((::sockaddr_in6*)addr)->sin6_port); + default:throw Exception(QPID_MSG("Unexpected socket type")); + } +} + +std::string SocketAddress::asString(bool numeric) const +{ + if (!numeric) + return host + ":" + port; + // Canonicalise into numeric id + const ::addrinfo& ai = getAddrInfo(*this); + + return asString(ai.ai_addr, ai.ai_addrlen); +} + +bool SocketAddress::nextAddress() { + bool r = currentAddrInfo->ai_next != 0; + if (r) + currentAddrInfo = currentAddrInfo->ai_next; + return r; +} + +void SocketAddress::setAddrInfoPort(uint16_t port) { + if (!currentAddrInfo) return; + + ::addrinfo& ai = *currentAddrInfo; + switch (ai.ai_family) { + case AF_INET: ((::sockaddr_in*)ai.ai_addr)->sin_port = htons(port); return; + case AF_INET6:((::sockaddr_in6*)ai.ai_addr)->sin6_port = htons(port); return; + default: throw Exception(QPID_MSG("Unexpected socket type")); + } } const ::addrinfo& getAddrInfo(const SocketAddress& sa) { - return *sa.addrInfo; + if (!sa.addrInfo) { + ::addrinfo hints; + ::memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_ADDRCONFIG; // Only use protocols that we have configured interfaces for + hints.ai_family = AF_UNSPEC; // Allow both IPv4 and IPv6 + hints.ai_socktype = SOCK_STREAM; + + const char* node = 0; + if (sa.host.empty()) { + hints.ai_flags |= AI_PASSIVE; + } else { + node = sa.host.c_str(); + } + const char* service = sa.port.empty() ? "0" : sa.port.c_str(); + + int n = ::getaddrinfo(node, service, &hints, &sa.addrInfo); + if (n != 0) + throw Exception(QPID_MSG("Cannot resolve " << sa.asString(false) << ": " << ::gai_strerror(n))); + sa.currentAddrInfo = sa.addrInfo; + } + + return *sa.currentAddrInfo; } }} diff --git a/cpp/src/qpid/sys/windows/SslAsynchIO.h b/cpp/src/qpid/sys/windows/SslAsynchIO.h index 3cdf2c8f08..edec081ced 100644 --- a/cpp/src/qpid/sys/windows/SslAsynchIO.h +++ b/cpp/src/qpid/sys/windows/SslAsynchIO.h @@ -39,9 +39,6 @@ namespace qpid { namespace sys { namespace windows { -class Socket; -class Poller; - /* * SSL/Schannel shim between the frame-handling and AsynchIO layers. * SslAsynchIO creates a regular AsynchIO object to handle I/O and this class diff --git a/cpp/src/qpid/sys/windows/StrError.cpp b/cpp/src/qpid/sys/windows/StrError.cpp index 9c1bfcd79c..546d399d16 100755 --- a/cpp/src/qpid/sys/windows/StrError.cpp +++ b/cpp/src/qpid/sys/windows/StrError.cpp @@ -30,6 +30,7 @@ namespace sys { std::string strError(int err) { const size_t bufsize = 512; char buf[bufsize]; + buf[0] = 0; if (0 == FormatMessage (FORMAT_MESSAGE_MAX_WIDTH_MASK | FORMAT_MESSAGE_FROM_SYSTEM, 0, @@ -39,7 +40,11 @@ std::string strError(int err) { bufsize, 0)) { - strerror_s (buf, bufsize, err); +#ifdef _MSC_VER + strerror_s(buf, bufsize, err); +#else + return std::string(strerror(err)); +#endif } return std::string(buf); } diff --git a/cpp/src/qpid/sys/windows/Thread.cpp b/cpp/src/qpid/sys/windows/Thread.cpp index 583a9613a3..23b0033be4 100755 --- a/cpp/src/qpid/sys/windows/Thread.cpp +++ b/cpp/src/qpid/sys/windows/Thread.cpp @@ -19,6 +19,11 @@ * */ +// Ensure definition of OpenThread in mingw +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 +#endif + #include "qpid/sys/Thread.h" #include "qpid/sys/Runnable.h" #include "qpid/sys/windows/check.h" @@ -26,50 +31,204 @@ #include <process.h> #include <windows.h> -namespace { -unsigned __stdcall runRunnable(void* p) -{ - static_cast<qpid::sys::Runnable*>(p)->run(); - _endthreadex(0); - return 0; -} -} +/* + * This implementation distinguishes between two types of thread: Qpid + * threads (based on qpid::sys::Runnable) and the rest. It provides a + * join() that will not deadlock against the Windows loader lock for + * Qpid threads. + * + * System thread identifiers are unique per Windows thread; thread + * handles are not. Thread identifiers can be recycled, but keeping a + * handle open against the thread prevents recycling as long as + * shared_ptr references to a ThreadPrivate structure remain. + * + * There is a 1-1 relationship between Qpid threads and their + * ThreadPrivate structure. Non-Qpid threads do not need to find the + * qpidThreadDone handle, so there may be a 1-many relationship for + * them. + * + * TLS storage is used for a lockless solution for static library + * builds. The special case of LoadLibrary/FreeLibrary requires + * additional synchronization variables and resource cleanup in + * DllMain. _DLL marks the dynamic case. + */ namespace qpid { namespace sys { class ThreadPrivate { +public: friend class Thread; + friend unsigned __stdcall runThreadPrivate(void*); + typedef boost::shared_ptr<ThreadPrivate> shared_ptr; + ~ThreadPrivate(); - HANDLE threadHandle; +private: unsigned threadId; - - ThreadPrivate(Runnable* runnable) { - uintptr_t h = _beginthreadex(0, - 0, - runRunnable, - runnable, - 0, - &threadId); - QPID_WINDOWS_CHECK_CRT_NZ(h); - threadHandle = reinterpret_cast<HANDLE>(h); + HANDLE threadHandle; + HANDLE initCompleted; + HANDLE qpidThreadDone; + Runnable* runnable; + shared_ptr keepAlive; + + ThreadPrivate() : threadId(GetCurrentThreadId()), initCompleted(NULL), + qpidThreadDone(NULL), runnable(NULL) { + threadHandle = OpenThread (SYNCHRONIZE, FALSE, threadId); + QPID_WINDOWS_CHECK_CRT_NZ(threadHandle); } - - ThreadPrivate() - : threadHandle(GetCurrentThread()), threadId(GetCurrentThreadId()) {} + + ThreadPrivate(Runnable* r) : threadHandle(NULL), initCompleted(NULL), + qpidThreadDone(NULL), runnable(r) {} + + void start(shared_ptr& p); + static shared_ptr createThread(Runnable* r); }; +}} // namespace qpid::sys + + +namespace { +using namespace qpid::sys; + +#ifdef _DLL +class ScopedCriticalSection +{ + public: + ScopedCriticalSection(CRITICAL_SECTION& cs) : criticalSection(cs) { EnterCriticalSection(&criticalSection); } + ~ScopedCriticalSection() { LeaveCriticalSection(&criticalSection); } + private: + CRITICAL_SECTION& criticalSection; +}; + +CRITICAL_SECTION threadLock; +long runningThreads = 0; +HANDLE threadsDone; +bool terminating = false; +#endif + + +DWORD volatile tlsIndex = TLS_OUT_OF_INDEXES; + +DWORD getTlsIndex() { + if (tlsIndex != TLS_OUT_OF_INDEXES) + return tlsIndex; // already set + + DWORD trialIndex = TlsAlloc(); + QPID_WINDOWS_CHECK_NOT(trialIndex, TLS_OUT_OF_INDEXES); // No OS resource + + // only one thread gets to set the value + DWORD actualIndex = (DWORD) InterlockedCompareExchange((LONG volatile *) &tlsIndex, (LONG) trialIndex, (LONG) TLS_OUT_OF_INDEXES); + if (actualIndex == TLS_OUT_OF_INDEXES) + return trialIndex; // we won the race + else { + TlsFree(trialIndex); + return actualIndex; + } +} + +} // namespace + +namespace qpid { +namespace sys { + +unsigned __stdcall runThreadPrivate(void* p) +{ + ThreadPrivate* threadPrivate = static_cast<ThreadPrivate*>(p); + TlsSetValue(getTlsIndex(), threadPrivate); + + WaitForSingleObject (threadPrivate->initCompleted, INFINITE); + CloseHandle (threadPrivate->initCompleted); + threadPrivate->initCompleted = NULL; + + try { + threadPrivate->runnable->run(); + } catch (...) { + // not our concern + } + + SetEvent (threadPrivate->qpidThreadDone); // allow join() + threadPrivate->keepAlive.reset(); // may run ThreadPrivate destructor + +#ifdef _DLL + { + ScopedCriticalSection l(threadLock); + if (--runningThreads == 0) + SetEvent(threadsDone); + } +#endif + return 0; +} + + +ThreadPrivate::shared_ptr ThreadPrivate::createThread(Runnable* runnable) { + ThreadPrivate::shared_ptr tp(new ThreadPrivate(runnable)); + tp->start(tp); + return tp; +} + +void ThreadPrivate::start(ThreadPrivate::shared_ptr& tp) { + getTlsIndex(); // fail here if OS problem, not in new thread + + initCompleted = CreateEvent (NULL, TRUE, FALSE, NULL); + QPID_WINDOWS_CHECK_CRT_NZ(initCompleted); + qpidThreadDone = CreateEvent (NULL, TRUE, FALSE, NULL); + QPID_WINDOWS_CHECK_CRT_NZ(qpidThreadDone); + +#ifdef _DLL + { + ScopedCriticalSection l(threadLock); + if (terminating) + throw qpid::Exception(QPID_MSG("creating thread after exit/FreeLibrary")); + runningThreads++; + } +#endif + + uintptr_t h = _beginthreadex(0, + 0, + runThreadPrivate, + (void *)this, + 0, + &threadId); + +#ifdef _DLL + if (h == NULL) { + ScopedCriticalSection l(threadLock); + if (--runningThreads == 0) + SetEvent(threadsDone); + } +#endif + + QPID_WINDOWS_CHECK_CRT_NZ(h); + + // Success + keepAlive = tp; + threadHandle = reinterpret_cast<HANDLE>(h); + SetEvent (initCompleted); +} + +ThreadPrivate::~ThreadPrivate() { + if (threadHandle) + CloseHandle (threadHandle); + if (initCompleted) + CloseHandle (initCompleted); + if (qpidThreadDone) + CloseHandle (qpidThreadDone); +} + + Thread::Thread() {} -Thread::Thread(Runnable* runnable) : impl(new ThreadPrivate(runnable)) {} +Thread::Thread(Runnable* runnable) : impl(ThreadPrivate::createThread(runnable)) {} -Thread::Thread(Runnable& runnable) : impl(new ThreadPrivate(&runnable)) {} +Thread::Thread(Runnable& runnable) : impl(ThreadPrivate::createThread(&runnable)) {} Thread::operator bool() { return impl; } bool Thread::operator==(const Thread& t) const { + if (!impl || !t.impl) + return false; return impl->threadId == t.impl->threadId; } @@ -79,10 +238,17 @@ bool Thread::operator!=(const Thread& t) const { void Thread::join() { if (impl) { - DWORD status = WaitForSingleObject (impl->threadHandle, INFINITE); + DWORD status; + if (impl->runnable) { + HANDLE handles[2] = {impl->qpidThreadDone, impl->threadHandle}; + // wait for either. threadHandle not signalled if loader + // lock held (FreeLibrary). qpidThreadDone not signalled + // if thread terminated by exit(). + status = WaitForMultipleObjects (2, handles, false, INFINITE); + } + else + status = WaitForSingleObject (impl->threadHandle, INFINITE); QPID_WINDOWS_CHECK_NOT(status, WAIT_FAILED); - CloseHandle (impl->threadHandle); - impl->threadHandle = 0; } } @@ -92,9 +258,70 @@ unsigned long Thread::logId() { /* static */ Thread Thread::current() { + ThreadPrivate* tlsValue = (ThreadPrivate *) TlsGetValue(getTlsIndex()); Thread t; - t.impl.reset(new ThreadPrivate()); + if (tlsValue != NULL) { + // called from within Runnable->run(), so keepAlive has positive use count + t.impl = tlsValue->keepAlive; + } + else + t.impl.reset(new ThreadPrivate()); return t; } -}} /* qpid::sys */ +}} // namespace qpid::sys + + +#ifdef _DLL + +// DllMain: called possibly many times in a process lifetime if dll +// loaded and freed repeatedly . Be mindful of Windows loader lock +// and other DllMain restrictions. + +BOOL APIENTRY DllMain(HMODULE hm, DWORD reason, LPVOID reserved) { + switch (reason) { + case DLL_PROCESS_ATTACH: + InitializeCriticalSection(&threadLock); + threadsDone = CreateEvent(NULL, TRUE, FALSE, NULL); + break; + + case DLL_PROCESS_DETACH: + terminating = true; + if (reserved != NULL) { + // process exit(): threads are stopped arbitrarily and + // possibly in an inconsistent state. Not even threadLock + // can be trusted. All static destructors have been + // called at this point and any resources this unit knows + // about will be released as part of process tear down by + // the OS. Accordingly, do nothing. + return TRUE; + } + else { + // FreeLibrary(): threads are still running and we are + // encouraged to clean up to avoid leaks. Mostly we just + // want any straggler threads to finish and notify + // threadsDone as the last thing they do. + while (1) { + { + ScopedCriticalSection l(threadLock); + if (runningThreads == 0) + break; + ResetEvent(threadsDone); + } + WaitForSingleObject(threadsDone, INFINITE); + } + if (tlsIndex != TLS_OUT_OF_INDEXES) + TlsFree(getTlsIndex()); + CloseHandle(threadsDone); + DeleteCriticalSection(&threadLock); + } + break; + + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + } + return TRUE; +} + +#endif diff --git a/cpp/src/qpid/sys/windows/Time.cpp b/cpp/src/qpid/sys/windows/Time.cpp index 16d09fcdc0..25c50819cd 100644 --- a/cpp/src/qpid/sys/windows/Time.cpp +++ b/cpp/src/qpid/sys/windows/Time.cpp @@ -27,6 +27,17 @@ using namespace boost::posix_time; +namespace { + +// High-res timing support. This will display times since program start, +// more or less. Keep track of the start value and the conversion factor to +// seconds. +bool timeInitialized = false; +LARGE_INTEGER start; +double freq = 1.0; + +} + namespace qpid { namespace sys { @@ -91,10 +102,35 @@ void outputFormattedNow(std::ostream& o) { char time_string[100]; ::time( &rawtime ); +#ifdef _MSC_VER ::localtime_s(&timeinfo, &rawtime); +#else + timeinfo = *(::localtime(&rawtime)); +#endif ::strftime(time_string, 100, "%Y-%m-%d %H:%M:%S", &timeinfo); o << time_string << " "; } + +void outputHiresNow(std::ostream& o) { + if (!timeInitialized) { + start.QuadPart = 0; + LARGE_INTEGER iFreq; + iFreq.QuadPart = 1; + QueryPerformanceCounter(&start); + QueryPerformanceFrequency(&iFreq); + freq = static_cast<double>(iFreq.QuadPart); + timeInitialized = true; + } + LARGE_INTEGER iNow; + iNow.QuadPart = 0; + QueryPerformanceCounter(&iNow); + iNow.QuadPart -= start.QuadPart; + if (iNow.QuadPart < 0) + iNow.QuadPart = 0; + double now = static_cast<double>(iNow.QuadPart); + now /= freq; // now is seconds after this + o << std::fixed << std::setprecision(8) << std::setw(16) << std::setfill('0') << now << "s "; +} }} diff --git a/cpp/src/tests/qrsh_utils/6_get b/cpp/src/qpid/sys/windows/mingw32_compat.h index 4b35ca98e6..51f613cc25 100755..100644 --- a/cpp/src/tests/qrsh_utils/6_get +++ b/cpp/src/qpid/sys/windows/mingw32_compat.h @@ -1,3 +1,5 @@ +#ifndef _sys_windows_mingw32_compat +#define _sys_windows_mingw32_compat /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -9,7 +11,7 @@ * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -17,13 +19,21 @@ * specific language governing permissions and limitations * under the License. * - */ + */ -#! /bin/bash +#ifdef WIN32 +#ifndef _MSC_VER -echo "getting /tmp/foo ..." -./qrsh 127.0.0.1 5813 \ - mrg23 get /tmp/foo +// +// The following definitions for extension function GUIDs and signatures are taken from +// MswSock.h in the Windows32 SDK. These rightfully belong in the mingw32 version of +// mswsock.h, but are not included presently. +// +#define WSAID_ACCEPTEX {0xb5367df1,0xcbac,0x11cf,{0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92}} +typedef BOOL (PASCAL *LPFN_ACCEPTEX)(SOCKET,SOCKET,PVOID,DWORD,DWORD,DWORD,LPDWORD,LPOVERLAPPED); +#endif +#endif +#endif diff --git a/cpp/src/qpid/sys/windows/uuid.cpp b/cpp/src/qpid/sys/windows/uuid.cpp index b5360622dc..3316ecbc00 100644 --- a/cpp/src/qpid/sys/windows/uuid.cpp +++ b/cpp/src/qpid/sys/windows/uuid.cpp @@ -19,7 +19,7 @@ * */ -#include <Rpc.h> +#include <rpc.h> #ifdef uuid_t /* Done in rpcdce.h */ # undef uuid_t #endif @@ -52,7 +52,11 @@ int uuid_parse (const char *in, uuid_t uu) { void uuid_unparse (const uuid_t uu, char *out) { unsigned char *formatted; if (UuidToString((UUID*)uu, &formatted) == RPC_S_OK) { +#ifdef _MSC_VER strncpy_s (out, 36+1, (char*)formatted, _TRUNCATE); +#else + strncpy (out, (char*)formatted, 36+1); +#endif RpcStringFree(&formatted); } } diff --git a/cpp/src/qpid/types/Uuid.cpp b/cpp/src/qpid/types/Uuid.cpp index 9face4e5d2..9862fa8946 100644 --- a/cpp/src/qpid/types/Uuid.cpp +++ b/cpp/src/qpid/types/Uuid.cpp @@ -20,6 +20,7 @@ */ #include "qpid/types/Uuid.h" #include "qpid/sys/uuid.h" +#include "qpid/sys/IntegerTypes.h" #include <sstream> #include <iostream> #include <string.h> @@ -71,7 +72,8 @@ void Uuid::clear() // Force int 0/!0 to false/true; avoids compile warnings. bool Uuid::isNull() const { - return !!uuid_is_null(bytes); + // This const cast is for Solaris which has non const arguments + return !!uuid_is_null(const_cast<uint8_t*>(bytes)); } Uuid::operator bool() const { return !isNull(); } @@ -86,7 +88,8 @@ const unsigned char* Uuid::data() const bool operator==(const Uuid& a, const Uuid& b) { - return uuid_compare(a.bytes, b.bytes) == 0; + // This const cast is for Solaris which has non const arguments + return uuid_compare(const_cast<uint8_t*>(a.bytes), const_cast<uint8_t*>(b.bytes)) == 0; } bool operator!=(const Uuid& a, const Uuid& b) @@ -96,22 +99,26 @@ bool operator!=(const Uuid& a, const Uuid& b) bool operator<(const Uuid& a, const Uuid& b) { - return uuid_compare(a.bytes, b.bytes) < 0; + // This const cast is for Solaris which has non const arguments + return uuid_compare(const_cast<uint8_t*>(a.bytes), const_cast<uint8_t*>(b.bytes)) < 0; } bool operator>(const Uuid& a, const Uuid& b) { - return uuid_compare(a.bytes, b.bytes) > 0; + // This const cast is for Solaris which has non const arguments + return uuid_compare(const_cast<uint8_t*>(a.bytes), const_cast<uint8_t*>(b.bytes)) > 0; } bool operator<=(const Uuid& a, const Uuid& b) { - return uuid_compare(a.bytes, b.bytes) <= 0; + // This const cast is for Solaris which has non const arguments + return uuid_compare(const_cast<uint8_t*>(a.bytes), const_cast<uint8_t*>(b.bytes)) <= 0; } bool operator>=(const Uuid& a, const Uuid& b) { - return uuid_compare(a.bytes, b.bytes) >= 0; + // This const cast is for Solaris which has non const arguments + return uuid_compare(const_cast<uint8_t*>(a.bytes), const_cast<uint8_t*>(b.bytes)) >= 0; } ostream& operator<<(ostream& out, Uuid uuid) diff --git a/cpp/src/qpid/types/Variant.cpp b/cpp/src/qpid/types/Variant.cpp index 5d8878bdac..f563d5de5b 100644 --- a/cpp/src/qpid/types/Variant.cpp +++ b/cpp/src/qpid/types/Variant.cpp @@ -19,7 +19,6 @@ * */ #include "qpid/types/Variant.h" -#include "qpid/Msg.h" #include "qpid/log/Statement.h" #include <boost/format.hpp> #include <boost/lexical_cast.hpp> @@ -108,15 +107,27 @@ class VariantImpl } value; std::string encoding;//optional encoding for variable length data - std::string getTypeName(VariantType type) const; template<class T> T convertFromString() const { std::string* s = reinterpret_cast<std::string*>(value.v); - try { - return boost::lexical_cast<T>(*s); - } catch(const boost::bad_lexical_cast&) { - throw InvalidConversion(QPID_MSG("Cannot convert " << *s)); + if (std::numeric_limits<T>::is_signed || s->find('-') != 0) { + //lexical_cast won't fail if string is a negative number and T is unsigned + try { + return boost::lexical_cast<T>(*s); + } catch(const boost::bad_lexical_cast&) { + //don't return, throw exception below + } + } else { + //T is unsigned and number starts with '-' + try { + //handle special case of negative zero + if (boost::lexical_cast<int>(*s) == 0) return 0; + //else its a non-zero negative number so throw exception at end of function + } catch(const boost::bad_lexical_cast&) { + //wasn't a valid int, therefore not a valid uint + } } + throw InvalidConversion(QPID_MSG("Cannot convert " << *s)); } }; @@ -370,11 +381,11 @@ int8_t VariantImpl::asInt8() const return int8_t(value.ui16); break; case VAR_UINT32: - if (value.ui32 <= (uint) std::numeric_limits<int8_t>::max()) + if (value.ui32 <= (uint32_t) std::numeric_limits<int8_t>::max()) return int8_t(value.ui32); break; case VAR_UINT64: - if (value.ui64 <= (uint) std::numeric_limits<int8_t>::max()) + if (value.ui64 <= (uint64_t) std::numeric_limits<int8_t>::max()) return int8_t(value.ui64); break; case VAR_STRING: return convertFromString<int8_t>(); @@ -401,11 +412,11 @@ int16_t VariantImpl::asInt16() const return int16_t(value.ui16); break; case VAR_UINT32: - if (value.ui32 <= (uint) std::numeric_limits<int16_t>::max()) + if (value.ui32 <= (uint32_t) std::numeric_limits<int16_t>::max()) return int16_t(value.ui32); break; case VAR_UINT64: - if (value.ui64 <= (uint) std::numeric_limits<int16_t>::max()) + if (value.ui64 <= (uint64_t) std::numeric_limits<int16_t>::max()) return int16_t(value.ui64); break; case VAR_STRING: return convertFromString<int16_t>(); @@ -430,7 +441,7 @@ int32_t VariantImpl::asInt32() const return int32_t(value.ui32); break; case VAR_UINT64: - if (value.ui64 <= (uint32_t) std::numeric_limits<int32_t>::max()) + if (value.ui64 <= (uint64_t) std::numeric_limits<int32_t>::max()) return int32_t(value.ui64); break; case VAR_STRING: return convertFromString<int32_t>(); @@ -582,7 +593,7 @@ const std::string& VariantImpl::getString() const void VariantImpl::setEncoding(const std::string& s) { encoding = s; } const std::string& VariantImpl::getEncoding() const { return encoding; } -std::string VariantImpl::getTypeName(VariantType type) const +std::string getTypeName(VariantType type) { switch (type) { case VAR_VOID: return "void"; diff --git a/cpp/src/replication.mk b/cpp/src/replication.mk index dbe071f405..e5da32f88b 100644 --- a/cpp/src/replication.mk +++ b/cpp/src/replication.mk @@ -19,14 +19,14 @@ # Make file for building two plugins for asynchronously replicating # queues. -dmodule_LTLIBRARIES += replicating_listener.la replication_exchange.la +dmoduleexec_LTLIBRARIES += replicating_listener.la replication_exchange.la # a queue event listener plugin that creates messages on a replication # queue corresponding to enqueue and dequeue events: replicating_listener_la_SOURCES = \ qpid/replication/constants.h \ qpid/replication/ReplicatingEventListener.cpp \ - qpid/replication/ReplicatingEventListener.h + qpid/replication/ReplicatingEventListener.h replicating_listener_la_LIBADD = libqpidbroker.la if SUNOS @@ -41,7 +41,7 @@ replicating_listener_la_LDFLAGS = $(PLUGINLDFLAGS) replication_exchange_la_SOURCES = \ qpid/replication/constants.h \ qpid/replication/ReplicationExchange.cpp \ - qpid/replication/ReplicationExchange.h + qpid/replication/ReplicationExchange.h replication_exchange_la_LIBADD = libqpidbroker.la diff --git a/cpp/src/ssl.mk b/cpp/src/ssl.mk index 5fbdd55438..4dba9bb61c 100644 --- a/cpp/src/ssl.mk +++ b/cpp/src/ssl.mk @@ -18,7 +18,7 @@ # # # Makefile fragment, conditionally included in Makefile.am -# +# libsslcommon_la_SOURCES = \ qpid/sys/ssl/check.h \ qpid/sys/ssl/check.cpp \ @@ -47,7 +47,7 @@ ssl_la_CXXFLAGS=$(AM_CXXFLAGS) $(SSL_CFLAGS) ssl_la_LDFLAGS = $(PLUGINLDFLAGS) -dmodule_LTLIBRARIES += ssl.la +dmoduleexec_LTLIBRARIES += ssl.la sslconnector_la_SOURCES = \ qpid/client/SslConnector.cpp @@ -60,5 +60,5 @@ sslconnector_la_CXXFLAGS = $(AM_CXXFLAGS) -DQPIDC_CONF_FILE=\"$(confdir)/qpidc.c sslconnector_la_LDFLAGS = $(PLUGINLDFLAGS) -cmodule_LTLIBRARIES += \ +cmoduleexec_LTLIBRARIES += \ sslconnector.la diff --git a/cpp/src/tests/.valgrind.supp b/cpp/src/tests/.valgrind.supp index 0e3e045437..2c6a1509ff 100644 --- a/cpp/src/tests/.valgrind.supp +++ b/cpp/src/tests/.valgrind.supp @@ -73,61 +73,6 @@ } { - boost 103200 -- we think Boost is responsible for these leaks. - Memcheck:Leak - fun:_Znwm - fun:_ZN5boost15program_options??options_description* -} - -{ - boost 103200 -- we think Boost is responsible for these leaks. - Memcheck:Leak - fun:_Znwm - fun:_ZN5boost9unit_test9test_case* -} - -{ - boost 103200 -- we think Boost is responsible for these leaks. - Memcheck:Leak - fun:calloc - fun:_dlerror_run - fun:dlopen@@GLIBC_2.2.5 - fun:_ZN4qpid3sys5Shlib4loadEPKc - fun:_Z9testShlibv - fun:_ZN5boost9unit_test9ut_detail17unit_test_monitor8functionEv - obj:/usr/lib64/libboost_unit_test_framework.so.1.32.0 - fun:_ZN5boost17execution_monitor7executeEbi - fun:_ZN5boost9unit_test9ut_detail17unit_test_monitor21execute_and_translateEPNS0_9test_caseEMS3_FvvEi - fun:_ZN5boost9unit_test9test_case3runEv - fun:_ZN5boost9unit_test10test_suite6do_runEv - fun:_ZN5boost9unit_test9test_case3runEv - fun:main -} - -{ - boost 103200 -- we think Boost is responsible for these leaks. - Memcheck:Leak - fun:calloc - fun:_dl_allocate_tls - fun:pthread_create@@GLIBC_2.2.5 - fun:_ZN4qpid6broker5Timer5startEv - fun:_ZN4qpid6broker5TimerC1Ev - fun:_ZN4qpid6broker10DtxManagerC1Ev - fun:_ZN4qpid6broker6BrokerC1ERKNS1_7OptionsE - fun:_ZN4qpid6broker6Broker6createERKNS1_7OptionsE - fun:_ZN15SessionFixtureTI15ProxyConnectionEC2Ev - fun:_Z14testQueueQueryv - fun:_ZN5boost9unit_test9ut_detail17unit_test_monitor8functionEv - obj:/usr/lib64/libboost_unit_test_framework.so.1.32.0 - fun:_ZN5boost17execution_monitor7executeEbi - fun:_ZN5boost9unit_test9ut_detail17unit_test_monitor21execute_and_translateEPNS0_9test_caseEMS3_FvvEi - fun:_ZN5boost9unit_test9test_case3runEv - fun:_ZN5boost9unit_test10test_suite6do_runEv - fun:_ZN5boost9unit_test9test_case3runEv - fun:main -} - -{ INVESTIGATE Memcheck:Leak fun:calloc @@ -155,25 +100,6 @@ } { - boost 103200 -- mgoulish -- fix this, sometime - Memcheck:Leak - fun:* - fun:* - obj:* - fun:* - fun:_ZN4qpid34options_description_less_easy_initclEPKcPKN5boost15program_options14value_semanticES2_ -} - -{ - boost 103200 -- mgoulish -- fix this, sometime - Memcheck:Leak - fun:* - fun:* - fun:* - fun:_ZN4qpid34options_description_less_easy_initclEPKcPKN5boost15program_options14value_semanticES2_ -} - -{ INVESTIGATE Memcheck:Param socketcall.sendto(msg) diff --git a/cpp/src/tests/Address.cpp b/cpp/src/tests/Address.cpp index f41f27b6df..0fd3585958 100644 --- a/cpp/src/tests/Address.cpp +++ b/cpp/src/tests/Address.cpp @@ -119,6 +119,17 @@ QPID_AUTO_TEST_CASE(testParseQuotedNameAndSubject) BOOST_CHECK_EQUAL(std::string("my subject with ; in it"), address.getSubject()); } +QPID_AUTO_TEST_CASE(testParseOptionsWithEmptyStringAsValue) +{ + Address address("my-topic; {a:'', x:101}"); + BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName()); + Variant a = address.getOptions()["a"]; + BOOST_CHECK_EQUAL(VAR_STRING, a.getType()); + std::string aVal = a; + BOOST_CHECK(aVal.size() == 0); + BOOST_CHECK_EQUAL((uint16_t) 101, address.getOptions()["x"].asInt64()); +} + QPID_AUTO_TEST_SUITE_END() }} diff --git a/cpp/src/tests/BrokerFixture.h b/cpp/src/tests/BrokerFixture.h index 672d954572..92c6d22b57 100644 --- a/cpp/src/tests/BrokerFixture.h +++ b/cpp/src/tests/BrokerFixture.h @@ -22,8 +22,6 @@ * */ -#include "SocketProxy.h" - #include "qpid/broker/Broker.h" #include "qpid/client/Connection.h" #include "qpid/client/ConnectionImpl.h" @@ -71,16 +69,15 @@ struct BrokerFixture : private boost::noncopyable { brokerThread = qpid::sys::Thread(*broker); }; - void shutdownBroker() - { - broker->shutdown(); - broker = BrokerPtr(); + void shutdownBroker() { + if (broker) { + broker->shutdown(); + brokerThread.join(); + broker = BrokerPtr(); + } } - ~BrokerFixture() { - if (broker) broker->shutdown(); - brokerThread.join(); - } + ~BrokerFixture() { shutdownBroker(); } /** Open a connection to the broker. */ void open(qpid::client::Connection& c) { @@ -97,20 +94,6 @@ struct LocalConnection : public qpid::client::Connection { ~LocalConnection() { close(); } }; -/** A local client connection via a socket proxy. */ -struct ProxyConnection : public qpid::client::Connection { - SocketProxy proxy; - ProxyConnection(int brokerPort) : proxy(brokerPort) { - open("localhost", proxy.getPort()); - } - ProxyConnection(const qpid::client::ConnectionSettings& s) : proxy(s.port) { - qpid::client::ConnectionSettings proxySettings(s); - proxySettings.port = proxy.getPort(); - open(proxySettings); - } - ~ProxyConnection() { close(); } -}; - /** Convenience class to create and open a connection and session * and some related useful objects. */ @@ -147,7 +130,6 @@ struct SessionFixtureT : BrokerFixture, ClientT<ConnectionType,SessionType> { }; typedef SessionFixtureT<LocalConnection> SessionFixture; -typedef SessionFixtureT<ProxyConnection> ProxySessionFixture; }} // namespace qpid::tests diff --git a/cpp/src/tests/BrokerMgmtAgent.cpp b/cpp/src/tests/BrokerMgmtAgent.cpp index d0c6668b72..1d5289dc90 100644 --- a/cpp/src/tests/BrokerMgmtAgent.cpp +++ b/cpp/src/tests/BrokerMgmtAgent.cpp @@ -599,13 +599,12 @@ namespace qpid { // populate the agent with multiple test objects const size_t objCount = 50; std::vector<TestManageable *> tmv; - uint32_t objLen; for (size_t i = 0; i < objCount; i++) { std::stringstream key; key << "testobj-" << i; TestManageable *tm = new TestManageable(agent, key.str()); - objLen = tm->GetManagementObject()->writePropertiesSize(); + (void) tm->GetManagementObject()->writePropertiesSize(); agent->addObject(tm->GetManagementObject(), key.str()); tmv.push_back(tm); } diff --git a/cpp/src/tests/BrokerOptions.cpp b/cpp/src/tests/BrokerOptions.cpp new file mode 100644 index 0000000000..b36d96916a --- /dev/null +++ b/cpp/src/tests/BrokerOptions.cpp @@ -0,0 +1,79 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** Unit tests for various broker configuration options **/ + +#include "unit_test.h" +#include "test_tools.h" +#include "MessagingFixture.h" + +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Connection.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/Receiver.h" +#include "qpid/messaging/Sender.h" +#include "qpid/messaging/Session.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(BrokerOptionsTestSuite) + +using namespace qpid::broker; +using namespace qpid::messaging; +using namespace qpid::types; +using namespace qpid; + +QPID_AUTO_TEST_CASE(testDisabledTimestamp) +{ + // by default, there should be no timestamp added by the broker + MessagingFixture fix; + + Sender sender = fix.session.createSender("test-q; {create:always, delete:sender}"); + messaging::Message msg("hi"); + sender.send(msg); + + Receiver receiver = fix.session.createReceiver("test-q"); + messaging::Message in; + BOOST_CHECK(receiver.fetch(in, Duration::IMMEDIATE)); + Variant::Map props = in.getProperties(); + BOOST_CHECK(props.find("x-amqp-0-10.timestamp") == props.end()); +} + +QPID_AUTO_TEST_CASE(testEnabledTimestamp) +{ + // when enabled, the 0.10 timestamp is added by the broker + Broker::Options opts; + opts.timestampRcvMsgs = true; + MessagingFixture fix(opts, true); + + Sender sender = fix.session.createSender("test-q; {create:always, delete:sender}"); + messaging::Message msg("one"); + sender.send(msg); + + Receiver receiver = fix.session.createReceiver("test-q"); + messaging::Message in; + BOOST_CHECK(receiver.fetch(in, Duration::IMMEDIATE)); + Variant::Map props = in.getProperties(); + BOOST_CHECK(props.find("x-amqp-0-10.timestamp") != props.end()); + BOOST_CHECK(props["x-amqp-0-10.timestamp"]); +} + +QPID_AUTO_TEST_SUITE_END() + +}} diff --git a/cpp/src/tests/CMakeLists.txt b/cpp/src/tests/CMakeLists.txt index 3b3b232671..7d781e5eb3 100644 --- a/cpp/src/tests/CMakeLists.txt +++ b/cpp/src/tests/CMakeLists.txt @@ -107,7 +107,6 @@ set(unit_tests_to_build MessagingSessionTests SequenceSet StringUtils - IncompleteMessageList RangeSet AtomicValue QueueTest @@ -119,6 +118,7 @@ set(unit_tests_to_build MessageTest QueueRegistryTest QueuePolicyTest + QueueFlowLimitTest FramingTest HeaderTest SequenceNumberTest @@ -264,6 +264,19 @@ add_executable (qpid-send qpid-send.cpp Statistics.cpp ${platform_test_additions target_link_libraries (qpid-send qpidmessaging) remember_location(qpid-send) +add_executable (qpid-ping qpid-ping.cpp ${platform_test_additions}) +target_link_libraries (qpid-ping qpidclient) +remember_location(qpid-ping) + +add_executable (datagen datagen.cpp ${platform_test_additions}) +target_link_libraries (datagen qpidclient) +remember_location(datagen) + +add_executable (msg_group_test msg_group_test.cpp ${platform_test_additions}) +target_link_libraries (msg_group_test qpidmessaging) +remember_location(msg_group_test) + + # qpid-perftest and qpid-latency-test are generally useful so install them install (TARGETS qpid-perftest qpid-latency-test RUNTIME DESTINATION ${QPID_INSTALL_BINDIR}) @@ -278,7 +291,7 @@ set(test_wrap ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_test${test_script_suffix} add_test (unit_test ${test_wrap} ${unit_test_LOCATION}) add_test (start_broker ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/start_broker${test_script_suffix}) -add_test (qpid-client-test ${test_wrap} ${qpid-client_test_LOCATION}) +add_test (qpid-client-test ${test_wrap} ${qpid-client-test_LOCATION}) add_test (quick_perftest ${test_wrap} ${qpid-perftest_LOCATION} --summary --count 100) add_test (quick_topictest ${test_wrap} ${CMAKE_CURRENT_SOURCE_DIR}/quick_topictest${test_script_suffix}) add_test (quick_txtest ${test_wrap} ${qpid-txtest_LOCATION} --queues 4 --tx-count 10 --quiet) @@ -288,6 +301,7 @@ if (PYTHON_EXECUTABLE) endif (PYTHON_EXECUTABLE) add_test (stop_broker ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/stop_broker${test_script_suffix}) if (PYTHON_EXECUTABLE) + add_test (ipv6_test ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/ipv6_test${test_script_suffix}) add_test (federation_tests ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_federation_tests${test_script_suffix}) if (BUILD_ACL) add_test (acl_tests ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_acl_tests${test_script_suffix}) diff --git a/cpp/src/tests/ClientSessionTest.cpp b/cpp/src/tests/ClientSessionTest.cpp index 939f8f2b88..30441cd03c 100644 --- a/cpp/src/tests/ClientSessionTest.cpp +++ b/cpp/src/tests/ClientSessionTest.cpp @@ -102,9 +102,9 @@ struct SimpleListener : public MessageListener } }; -struct ClientSessionFixture : public ProxySessionFixture +struct ClientSessionFixture : public SessionFixture { - ClientSessionFixture(Broker::Options opts = Broker::Options()) : ProxySessionFixture(opts) { + ClientSessionFixture(Broker::Options opts = Broker::Options()) : SessionFixture(opts) { session.queueDeclare(arg::queue="my-queue"); } }; @@ -150,16 +150,6 @@ QPID_AUTO_TEST_CASE(testDispatcherThread) BOOST_CHECK_EQUAL(boost::lexical_cast<string>(i), listener.messages[i].getData()); } -// FIXME aconway 2009-06-17: test for unimplemented feature, enable when implemented. -void testSuspend0Timeout() { - ClientSessionFixture fix; - fix.session.suspend(); // session has 0 timeout. - try { - fix.connection.resume(fix.session); - BOOST_FAIL("Expected InvalidArgumentException."); - } catch(const InternalErrorException&) {} -} - QPID_AUTO_TEST_CASE(testUseSuspendedError) { ClientSessionFixture fix; @@ -171,18 +161,6 @@ QPID_AUTO_TEST_CASE(testUseSuspendedError) } catch(const NotAttachedException&) {} } -// FIXME aconway 2009-06-17: test for unimplemented feature, enable when implemented. -void testSuspendResume() { - ClientSessionFixture fix; - fix.session.timeout(60); - fix.session.suspend(); - // Make sure we are still subscribed after resume. - fix.connection.resume(fix.session); - fix.session.messageTransfer(arg::content=Message("my-message", "my-queue")); - BOOST_CHECK_EQUAL("my-message", fix.subs.get("my-queue", TIME_SEC).getData()); -} - - QPID_AUTO_TEST_CASE(testSendToSelf) { ClientSessionFixture fix; SimpleListener mylistener; @@ -271,8 +249,12 @@ QPID_AUTO_TEST_CASE(testOpenFailure) { QPID_AUTO_TEST_CASE(testPeriodicExpiration) { Broker::Options opts; opts.queueCleanInterval = 1; + opts.queueFlowStopRatio = 0; + opts.queueFlowResumeRatio = 0; ClientSessionFixture fix(opts); - fix.session.queueDeclare(arg::queue="my-queue", arg::exclusive=true, arg::autoDelete=true); + FieldTable args; + args.setInt("qpid.max_count",10); + fix.session.queueDeclare(arg::queue="my-queue", arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); for (uint i = 0; i < 10; i++) { Message m((boost::format("Message_%1%") % (i+1)).str(), "my-queue"); @@ -283,6 +265,7 @@ QPID_AUTO_TEST_CASE(testPeriodicExpiration) { BOOST_CHECK_EQUAL(fix.session.queueQuery(string("my-queue")).getMessageCount(), 10u); qpid::sys::sleep(2); BOOST_CHECK_EQUAL(fix.session.queueQuery(string("my-queue")).getMessageCount(), 5u); + fix.session.messageTransfer(arg::content=Message("Message_11", "my-queue"));//ensure policy is also updated } QPID_AUTO_TEST_CASE(testExpirationOnPop) { diff --git a/cpp/src/tests/ExchangeTest.cpp b/cpp/src/tests/ExchangeTest.cpp index 88a1cd99c2..fe72f42a46 100644 --- a/cpp/src/tests/ExchangeTest.cpp +++ b/cpp/src/tests/ExchangeTest.cpp @@ -253,7 +253,7 @@ QPID_AUTO_TEST_CASE(testIVEOption) TopicExchange topic ("topic1", false, args); intrusive_ptr<Message> msg1 = cmessage("direct1", "abc"); - msg1->getProperties<MessageProperties>()->getApplicationHeaders().setString("a", "abc"); + msg1->insertCustomProperty("a", "abc"); DeliverableMessage dmsg1(msg1); FieldTable args2; diff --git a/cpp/src/tests/ForkedBroker.cpp b/cpp/src/tests/ForkedBroker.cpp index 53eaa7e1ce..10674b5175 100644 --- a/cpp/src/tests/ForkedBroker.cpp +++ b/cpp/src/tests/ForkedBroker.cpp @@ -68,8 +68,7 @@ ForkedBroker::~ForkedBroker() { } if (!dataDir.empty()) { - int unused_ret; // Suppress warnings about ignoring return value. - unused_ret = ::system(("rm -rf "+dataDir).c_str()); + (void) ::system(("rm -rf "+dataDir).c_str()); } } diff --git a/cpp/src/tests/IncompleteMessageList.cpp b/cpp/src/tests/IncompleteMessageList.cpp deleted file mode 100644 index 10782572e5..0000000000 --- a/cpp/src/tests/IncompleteMessageList.cpp +++ /dev/null @@ -1,134 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -#include <iostream> -#include <sstream> -#include "qpid/broker/Message.h" -#include "qpid/broker/NullMessageStore.h" -#include "qpid/broker/Queue.h" -#include "qpid/broker/IncompleteMessageList.h" - -#include "unit_test.h" - -namespace qpid { -namespace tests { - -QPID_AUTO_TEST_SUITE(IncompleteMessageListTestSuite) - -using namespace qpid::broker; -using namespace qpid::framing; - -struct Checker -{ - std::list<SequenceNumber> ids; - - Checker() { } - - Checker(uint start, uint end) { - for (uint i = start; i <= end; i++) { - ids.push_back(i); - } - } - - Checker& expect(const SequenceNumber& id) { - ids.push_back(id); - return *this; - } - - void operator()(boost::intrusive_ptr<Message> msg) { - BOOST_CHECK(!ids.empty()); - BOOST_CHECK_EQUAL(msg->getCommandId(), ids.front()); - ids.pop_front(); - } -}; - -QPID_AUTO_TEST_CASE(testProcessSimple) -{ - IncompleteMessageList list; - SequenceNumber counter(1); - //fill up list with messages - for (int i = 0; i < 5; i++) { - boost::intrusive_ptr<Message> msg(new Message(counter++)); - list.add(msg); - } - //process and ensure they are all passed to completion listener - list.process(Checker(1, 5), false); - //process again and ensure none are resent to listener - list.process(Checker(), false); -} - -QPID_AUTO_TEST_CASE(testProcessWithIncomplete) -{ - Queue::shared_ptr queue; - IncompleteMessageList list; - SequenceNumber counter(1); - boost::intrusive_ptr<Message> middle; - //fill up list with messages - for (int i = 0; i < 5; i++) { - boost::intrusive_ptr<Message> msg(new Message(counter++)); - list.add(msg); - if (i == 2) { - //mark a message in the middle as incomplete - msg->enqueueAsync(queue, 0); - middle = msg; - } - } - //process and ensure only message upto incomplete message are passed to listener - list.process(Checker(1, 2), false); - //mark message complete and re-process to get remaining messages sent to listener - middle->enqueueComplete(); - list.process(Checker(3, 5), false); -} - - -struct MockStore : public NullMessageStore -{ - Queue::shared_ptr queue; - boost::intrusive_ptr<Message> msg; - - void flush(const qpid::broker::PersistableQueue& q) { - BOOST_CHECK_EQUAL(queue.get(), &q); - msg->enqueueComplete(); - } -}; - -QPID_AUTO_TEST_CASE(testSyncProcessWithIncomplete) -{ - IncompleteMessageList list; - SequenceNumber counter(1); - MockStore store; - store.queue = Queue::shared_ptr(new Queue("mock-queue", false, &store)); - //fill up list with messages - for (int i = 0; i < 5; i++) { - boost::intrusive_ptr<Message> msg(new Message(counter++)); - list.add(msg); - if (i == 2) { - //mark a message in the middle as incomplete - msg->enqueueAsync(store.queue, &store); - store.msg = msg; - } - } - //process with sync bit specified and ensure that all messages are passed to listener - list.process(Checker(1, 5), true); -} - -QPID_AUTO_TEST_SUITE_END() - -}} // namespace qpid::tests diff --git a/cpp/src/tests/Makefile.am b/cpp/src/tests/Makefile.am index 07405bcd8f..3c9ca1b70f 100644 --- a/cpp/src/tests/Makefile.am +++ b/cpp/src/tests/Makefile.am @@ -75,7 +75,7 @@ unit_test_SOURCES= unit_test.cpp unit_test.h \ MessagingThreadTests.cpp \ MessagingFixture.h \ ClientSessionTest.cpp \ - BrokerFixture.h SocketProxy.h \ + BrokerFixture.h \ exception_test.cpp \ RefCounted.cpp \ SessionState.cpp logging.cpp \ @@ -87,7 +87,6 @@ unit_test_SOURCES= unit_test.cpp unit_test.h \ InlineVector.cpp \ SequenceSet.cpp \ StringUtils.cpp \ - IncompleteMessageList.cpp \ RangeSet.cpp \ AtomicValue.cpp \ QueueTest.cpp \ @@ -99,6 +98,7 @@ unit_test_SOURCES= unit_test.cpp unit_test.h \ MessageTest.cpp \ QueueRegistryTest.cpp \ QueuePolicyTest.cpp \ + QueueFlowLimitTest.cpp \ FramingTest.cpp \ HeaderTest.cpp \ SequenceNumberTest.cpp \ @@ -124,7 +124,8 @@ unit_test_SOURCES= unit_test.cpp unit_test.h \ Variant.cpp \ Address.cpp \ ClientMessage.cpp \ - Qmf2.cpp + Qmf2.cpp \ + BrokerOptions.cpp if HAVE_XML unit_test_SOURCES+= XmlClientSessionTest.cpp @@ -286,31 +287,27 @@ check_PROGRAMS+=datagen datagen_SOURCES=datagen.cpp datagen_LDADD=$(lib_common) $(lib_client) -check_PROGRAMS+=qrsh_server -qrsh_server_SOURCES=qrsh_server.cpp -qrsh_server_LDADD=$(lib_client) - -check_PROGRAMS+=qrsh_run -qrsh_run_SOURCES=qrsh_run.cpp -qrsh_run_LDADD=$(lib_client) - -check_PROGRAMS+=qrsh -qrsh_SOURCES=qrsh.cpp -qrsh_LDADD=$(lib_client) - check_PROGRAMS+=qpid-stream qpid_stream_INCLUDES=$(PUBLIC_INCLUDES) qpid_stream_SOURCES=qpid-stream.cpp qpid_stream_LDADD=$(lib_messaging) +check_PROGRAMS+=msg_group_test +msg_group_test_INCLUDES=$(PUBLIC_INCLUDES) +msg_group_test_SOURCES=msg_group_test.cpp +msg_group_test_LDADD=$(lib_messaging) + TESTS_ENVIRONMENT = \ VALGRIND=$(VALGRIND) \ LIBTOOL="$(LIBTOOL)" \ QPID_DATA_DIR= \ $(srcdir)/run_test -system_tests = qpid-client-test quick_perftest quick_topictest run_header_test quick_txtest -TESTS += start_broker $(system_tests) python_tests stop_broker run_federation_tests run_acl_tests run_cli_tests replication_test dynamic_log_level_test +system_tests = qpid-client-test quick_perftest quick_topictest run_header_test quick_txtest \ + run_msg_group_tests +TESTS += start_broker $(system_tests) python_tests stop_broker run_federation_tests run_federation_sys_tests \ + run_acl_tests run_cli_tests replication_test dynamic_log_level_test \ + run_queue_flow_limit_tests ipv6_test EXTRA_DIST += \ run_test vg_check \ @@ -325,6 +322,8 @@ EXTRA_DIST += \ config.null \ ais_check \ run_federation_tests \ + run_federation_sys_tests \ + run_long_federation_sys_tests \ run_cli_tests \ run_acl_tests \ .valgrind.supp \ @@ -349,7 +348,10 @@ EXTRA_DIST += \ run_test.ps1 \ start_broker.ps1 \ stop_broker.ps1 \ - topictest.ps1 + topictest.ps1 \ + run_queue_flow_limit_tests \ + run_msg_group_tests \ + ipv6_test check_LTLIBRARIES += libdlclose_noop.la libdlclose_noop_la_LDFLAGS = -module -rpath $(abs_builddir) @@ -360,7 +362,11 @@ CLEANFILES+=valgrind.out *.log *.vglog* dummy_test qpidd.port $(unit_wrappers) # Longer running stability tests, not run by default check: target. # Not run under valgrind, too slow -LONG_TESTS+=start_broker fanout_perftest shared_perftest multiq_perftest topic_perftest run_ring_queue_test stop_broker \ +LONG_TESTS+=start_broker \ + fanout_perftest shared_perftest multiq_perftest topic_perftest run_ring_queue_test \ + run_msg_group_tests_soak \ + stop_broker \ + run_long_federation_sys_tests \ run_failover_soak reliable_replication_test \ federated_cluster_test_with_node_failure @@ -372,7 +378,8 @@ EXTRA_DIST+= \ run_failover_soak \ reliable_replication_test \ federated_cluster_test_with_node_failure \ - sasl_test_setup.sh + sasl_test_setup.sh \ + run_msg_group_tests_soak check-long: $(MAKE) check TESTS="$(LONG_TESTS)" VALGRIND= diff --git a/cpp/src/tests/MessageReplayTracker.cpp b/cpp/src/tests/MessageReplayTracker.cpp index 3d79ee53c2..e35f673683 100644 --- a/cpp/src/tests/MessageReplayTracker.cpp +++ b/cpp/src/tests/MessageReplayTracker.cpp @@ -51,7 +51,7 @@ class ReplayBufferChecker QPID_AUTO_TEST_CASE(testReplay) { - ProxySessionFixture fix; + SessionFixture fix; fix.session.queueDeclare(arg::queue="my-queue", arg::exclusive=true, arg::autoDelete=true); MessageReplayTracker tracker(10); @@ -77,7 +77,7 @@ QPID_AUTO_TEST_CASE(testReplay) QPID_AUTO_TEST_CASE(testCheckCompletion) { - ProxySessionFixture fix; + SessionFixture fix; fix.session.queueDeclare(arg::queue="my-queue", arg::exclusive=true, arg::autoDelete=true); MessageReplayTracker tracker(10); diff --git a/cpp/src/tests/MessagingFixture.h b/cpp/src/tests/MessagingFixture.h index 715de09bad..2312a87e9d 100644 --- a/cpp/src/tests/MessagingFixture.h +++ b/cpp/src/tests/MessagingFixture.h @@ -27,15 +27,19 @@ #include "qpid/client/Connection.h" #include "qpid/client/Session.h" #include "qpid/framing/Uuid.h" +#include "qpid/messaging/Address.h" #include "qpid/messaging/Connection.h" #include "qpid/messaging/Session.h" #include "qpid/messaging/Sender.h" #include "qpid/messaging/Receiver.h" #include "qpid/messaging/Message.h" +#include "qpid/types/Variant.h" namespace qpid { namespace tests { +using qpid::types::Variant; + struct BrokerAdmin { qpid::client::Connection connection; @@ -223,6 +227,119 @@ inline void receive(messaging::Receiver& receiver, uint count = 1, uint start = } } + +class MethodInvoker +{ + public: + MethodInvoker(messaging::Session& session) : replyTo("#; {create:always, node:{x-declare:{auto-delete:true}}}"), + sender(session.createSender("qmf.default.direct/broker")), + receiver(session.createReceiver(replyTo)) {} + + void createExchange(const std::string& name, const std::string& type, bool durable=false) + { + Variant::Map params; + params["name"]=name; + params["type"]="exchange"; + params["properties"] = Variant::Map(); + params["properties"].asMap()["exchange-type"] = type; + params["properties"].asMap()["durable"] = durable; + methodRequest("create", params); + } + + void deleteExchange(const std::string& name) + { + Variant::Map params; + params["name"]=name; + params["type"]="exchange"; + methodRequest("delete", params); + } + + void createQueue(const std::string& name, bool durable=false, bool autodelete=false, + const Variant::Map& options=Variant::Map()) + { + Variant::Map params; + params["name"]=name; + params["type"]="queue"; + params["properties"] = options; + params["properties"].asMap()["durable"] = durable; + params["properties"].asMap()["auto-delete"] = autodelete; + methodRequest("create", params); + } + + void deleteQueue(const std::string& name) + { + Variant::Map params; + params["name"]=name; + params["type"]="queue"; + methodRequest("delete", params); + } + + void bind(const std::string& exchange, const std::string& queue, const std::string& key, + const Variant::Map& options=Variant::Map()) + { + Variant::Map params; + params["name"]=(boost::format("%1%/%2%/%3%") % (exchange) % (queue) % (key)).str(); + params["type"]="binding"; + params["properties"] = options; + methodRequest("create", params); + } + + void unbind(const std::string& exchange, const std::string& queue, const std::string& key) + { + Variant::Map params; + params["name"]=(boost::format("%1%/%2%/%3%") % (exchange) % (queue) % (key)).str(); + params["type"]="binding"; + methodRequest("delete", params); + } + + void methodRequest(const std::string& method, const Variant::Map& inParams, Variant::Map* outParams = 0) + { + Variant::Map content; + Variant::Map objectId; + objectId["_object_name"] = "org.apache.qpid.broker:broker:amqp-broker"; + content["_object_id"] = objectId; + content["_method_name"] = method; + content["_arguments"] = inParams; + + messaging::Message request; + request.setReplyTo(replyTo); + request.getProperties()["x-amqp-0-10.app-id"] = "qmf2"; + request.getProperties()["qmf.opcode"] = "_method_request"; + encode(content, request); + + sender.send(request); + + messaging::Message response; + if (receiver.fetch(response, messaging::Duration::SECOND*5)) { + if (response.getProperties()["x-amqp-0-10.app-id"] == "qmf2") { + std::string opcode = response.getProperties()["qmf.opcode"]; + if (opcode == "_method_response") { + if (outParams) { + Variant::Map m; + decode(response, m); + *outParams = m["_arguments"].asMap(); + } + } else if (opcode == "_exception") { + Variant::Map m; + decode(response, m); + throw Exception(QPID_MSG("Error: " << m["_values"])); + } else { + throw Exception(QPID_MSG("Invalid response received, unexpected opcode: " << opcode)); + } + } else { + throw Exception(QPID_MSG("Invalid response received, not a qmfv2 message: app-id=" + << response.getProperties()["x-amqp-0-10.app-id"])); + } + } else { + throw Exception(QPID_MSG("No response received")); + } + } + private: + messaging::Address replyTo; + messaging::Sender sender; + messaging::Receiver receiver; +}; + }} // namespace qpid::tests #endif /*!TESTS_MESSAGINGFIXTURE_H*/ diff --git a/cpp/src/tests/MessagingSessionTests.cpp b/cpp/src/tests/MessagingSessionTests.cpp index 991ec847bf..9d5db84bb4 100644 --- a/cpp/src/tests/MessagingSessionTests.cpp +++ b/cpp/src/tests/MessagingSessionTests.cpp @@ -611,6 +611,28 @@ QPID_AUTO_TEST_CASE(testAssertPolicyQueue) fix.admin.deleteQueue("q"); } +QPID_AUTO_TEST_CASE(testAssertExchangeOption) +{ + MessagingFixture fix; + std::string a1 = "e; {create:always, assert:always, node:{type:topic, x-declare:{type:direct, arguments:{qpid.msg_sequence:True}}}}"; + Sender s1 = fix.session.createSender(a1); + s1.close(); + Receiver r1 = fix.session.createReceiver(a1); + r1.close(); + + std::string a2 = "e; {assert:receiver, node:{type:topic, x-declare:{type:fanout, arguments:{qpid.msg_sequence:True}}}}"; + Sender s2 = fix.session.createSender(a2); + s2.close(); + BOOST_CHECK_THROW(fix.session.createReceiver(a2), qpid::messaging::AssertionFailed); + + std::string a3 = "e; {assert:sender, node:{x-declare:{arguments:{qpid.msg_sequence:False}}}}"; + BOOST_CHECK_THROW(fix.session.createSender(a3), qpid::messaging::AssertionFailed); + Receiver r3 = fix.session.createReceiver(a3); + r3.close(); + + fix.admin.deleteExchange("e"); +} + QPID_AUTO_TEST_CASE(testGetSender) { QueueFixture fix; @@ -890,6 +912,212 @@ QPID_AUTO_TEST_CASE(testAcknowledge) BOOST_CHECK(!fix.session.createReceiver(fix.queue).fetch(m, Duration::IMMEDIATE)); } +QPID_AUTO_TEST_CASE(testQmfCreateAndDelete) +{ + MessagingFixture fix(Broker::Options(), true/*enable management*/); + MethodInvoker control(fix.session); + control.createQueue("my-queue"); + control.createExchange("my-exchange", "topic"); + control.bind("my-exchange", "my-queue", "subject1"); + + Sender sender = fix.session.createSender("my-exchange"); + Receiver receiver = fix.session.createReceiver("my-queue"); + Message out; + out.setSubject("subject1"); + out.setContent("one"); + sender.send(out); + Message in; + BOOST_CHECK(receiver.fetch(in, Duration::SECOND*5)); + BOOST_CHECK_EQUAL(out.getContent(), in.getContent()); + control.unbind("my-exchange", "my-queue", "subject1"); + control.bind("my-exchange", "my-queue", "subject2"); + + out.setContent("two"); + sender.send(out);//should be dropped + + out.setSubject("subject2"); + out.setContent("three"); + sender.send(out);//should not be dropped + + BOOST_CHECK(receiver.fetch(in, Duration::SECOND*5)); + BOOST_CHECK_EQUAL(out.getContent(), in.getContent()); + BOOST_CHECK(!receiver.fetch(in, Duration::IMMEDIATE)); + sender.close(); + receiver.close(); + + control.deleteExchange("my-exchange"); + messaging::Session other = fix.connection.createSession(); + { + ScopedSuppressLogging sl; + BOOST_CHECK_THROW(other.createSender("my-exchange"), qpid::messaging::NotFound); + } + control.deleteQueue("my-queue"); + other = fix.connection.createSession(); + { + ScopedSuppressLogging sl; + BOOST_CHECK_THROW(other.createReceiver("my-queue"), qpid::messaging::NotFound); + } +} + +QPID_AUTO_TEST_CASE(testRejectAndCredit) +{ + //Ensure credit is restored on completing rejected messages + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Receiver receiver = fix.session.createReceiver(fix.queue); + + const uint count(10); + receiver.setCapacity(count); + for (uint i = 0; i < count; i++) { + sender.send(Message((boost::format("Message_%1%") % (i+1)).str())); + } + + Message in; + for (uint i = 0; i < count; ++i) { + if (receiver.fetch(in, Duration::SECOND)) { + BOOST_CHECK_EQUAL(in.getContent(), (boost::format("Message_%1%") % (i+1)).str()); + fix.session.reject(in); + } else { + BOOST_FAIL((boost::format("Message_%1% not received as expected") % (i+1)).str()); + break; + } + } + //send another batch of messages + for (uint i = 0; i < count; i++) { + sender.send(Message((boost::format("Message_%1%") % (i+count)).str())); + } + + for (uint i = 0; i < count; ++i) { + if (receiver.fetch(in, Duration::SECOND)) { + BOOST_CHECK_EQUAL(in.getContent(), (boost::format("Message_%1%") % (i+count)).str()); + } else { + BOOST_FAIL((boost::format("Message_%1% not received as expected") % (i+count)).str()); + break; + } + } + fix.session.acknowledge(); + receiver.close(); + sender.close(); +} + +QPID_AUTO_TEST_CASE(testTtlForever) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message out("I want to live forever!"); + out.setTtl(Duration::FOREVER); + sender.send(out, true); + Receiver receiver = fix.session.createReceiver(fix.queue); + Message in = receiver.fetch(Duration::IMMEDIATE); + fix.session.acknowledge(); + BOOST_CHECK_EQUAL(in.getContent(), out.getContent()); + BOOST_CHECK(in.getTtl() == Duration::FOREVER); +} + +QPID_AUTO_TEST_CASE(testExclusiveTopicSubscriber) +{ + TopicFixture fix; + std::string address = (boost::format("%1%; { link: { name: 'my-subscription', x-declare: { auto-delete: true, exclusive: true }}}") % fix.topic).str(); + Sender sender = fix.session.createSender(fix.topic); + Receiver receiver1 = fix.session.createReceiver(address); + { + ScopedSuppressLogging sl; + try { + fix.session.createReceiver(address); + fix.session.sync(); + BOOST_FAIL("Expected exception."); + } catch (const MessagingException& /*e*/) {} + } +} + +QPID_AUTO_TEST_CASE(testNonExclusiveSubscriber) +{ + TopicFixture fix; + std::string address = (boost::format("%1%; {node:{type:topic}, link:{name:'my-subscription', x-declare:{auto-delete:true, exclusive:false}}}") % fix.topic).str(); + Receiver receiver1 = fix.session.createReceiver(address); + Receiver receiver2 = fix.session.createReceiver(address); + Sender sender = fix.session.createSender(fix.topic); + sender.send(Message("one"), true); + Message in = receiver1.fetch(Duration::IMMEDIATE); + BOOST_CHECK_EQUAL(in.getContent(), std::string("one")); + sender.send(Message("two"), true); + in = receiver2.fetch(Duration::IMMEDIATE); + BOOST_CHECK_EQUAL(in.getContent(), std::string("two")); + fix.session.acknowledge(); +} + +QPID_AUTO_TEST_CASE(testAcknowledgeUpTo) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + const uint count(20); + for (uint i = 0; i < count; ++i) { + sender.send(Message((boost::format("Message_%1%") % (i+1)).str())); + } + + Session other = fix.connection.createSession(); + Receiver receiver = other.createReceiver(fix.queue); + std::vector<Message> messages; + for (uint i = 0; i < count; ++i) { + Message msg = receiver.fetch(); + BOOST_CHECK_EQUAL(msg.getContent(), (boost::format("Message_%1%") % (i+1)).str()); + messages.push_back(msg); + } + const uint batch = 10; + other.acknowledgeUpTo(messages[batch-1]);//acknowledge first 10 messages only + + messages.clear(); + other.sync(); + other.close(); + + other = fix.connection.createSession(); + receiver = other.createReceiver(fix.queue); + Message msg; + for (uint i = 0; i < (count-batch); ++i) { + msg = receiver.fetch(); + BOOST_CHECK_EQUAL(msg.getContent(), (boost::format("Message_%1%") % (i+1+batch)).str()); + } + other.acknowledgeUpTo(msg); + other.sync(); + other.close(); + + Message m; + //check queue is empty + BOOST_CHECK(!fix.session.createReceiver(fix.queue).fetch(m, Duration::IMMEDIATE)); +} + +QPID_AUTO_TEST_CASE(testCreateBindingsOnStandardExchange) +{ + QueueFixture fix; + Sender sender = fix.session.createSender((boost::format("amq.direct; {create:always, node:{type:topic, x-bindings:[{queue:%1%, key:my-subject}]}}") % fix.queue).str()); + Message out("test-message"); + out.setSubject("my-subject"); + sender.send(out); + Receiver receiver = fix.session.createReceiver(fix.queue); + Message in = receiver.fetch(Duration::SECOND * 5); + fix.session.acknowledge(); + BOOST_CHECK_EQUAL(in.getContent(), out.getContent()); + BOOST_CHECK_EQUAL(in.getSubject(), out.getSubject()); +} + +QPID_AUTO_TEST_CASE(testUnsubscribeOnClose) +{ + MessagingFixture fix; + Sender sender = fix.session.createSender("my-exchange/my-subject; {create: always, delete:sender, node:{type:topic, x-declare:{alternate-exchange:amq.fanout}}}"); + Receiver receiver = fix.session.createReceiver("my-exchange/my-subject"); + Receiver deadletters = fix.session.createReceiver("amq.fanout"); + + sender.send(Message("first")); + Message in = receiver.fetch(Duration::SECOND); + BOOST_CHECK_EQUAL(in.getContent(), std::string("first")); + fix.session.acknowledge(); + receiver.close(); + sender.send(Message("second")); + in = deadletters.fetch(Duration::SECOND); + BOOST_CHECK_EQUAL(in.getContent(), std::string("second")); + fix.session.acknowledge(); +} + QPID_AUTO_TEST_SUITE_END() }} // namespace qpid::tests diff --git a/cpp/src/tests/Qmf2.cpp b/cpp/src/tests/Qmf2.cpp index 66c774accd..bc263d5c6d 100644 --- a/cpp/src/tests/Qmf2.cpp +++ b/cpp/src/tests/Qmf2.cpp @@ -23,12 +23,36 @@ #include "qmf/QueryImpl.h" #include "qmf/SchemaImpl.h" #include "qmf/exceptions.h" - +#include "qpid/messaging/Connection.h" +#include "qmf/PosixEventNotifierImpl.h" +#include "qmf/AgentSession.h" +#include "qmf/AgentSessionImpl.h" +#include "qmf/ConsoleSession.h" +#include "qmf/ConsoleSessionImpl.h" #include "unit_test.h" +using namespace std; using namespace qpid::types; +using namespace qpid::messaging; using namespace qmf; +bool isReadable(int fd) +{ + fd_set rfds; + struct timeval tv; + int nfds, result; + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + nfds = fd + 1; + tv.tv_sec = 0; + tv.tv_usec = 0; + + result = select(nfds, &rfds, NULL, NULL, &tv); + + return result > 0; +} + namespace qpid { namespace tests { @@ -315,6 +339,84 @@ QPID_AUTO_TEST_CASE(testSchema) BOOST_CHECK_THROW(method.getArgument(3), QmfException); } +QPID_AUTO_TEST_CASE(testAgentSessionEventListener) +{ + Connection connection("localhost"); + AgentSession session(connection, ""); + posix::EventNotifier notifier(session); + + AgentSessionImpl& sessionImpl = AgentSessionImplAccess::get(session); + + BOOST_CHECK(sessionImpl.getEventNotifier() != 0); +} + +QPID_AUTO_TEST_CASE(testConsoleSessionEventListener) +{ + Connection connection("localhost"); + ConsoleSession session(connection, ""); + posix::EventNotifier notifier(session); + + ConsoleSessionImpl& sessionImpl = ConsoleSessionImplAccess::get(session); + + BOOST_CHECK(sessionImpl.getEventNotifier() != 0); +} + +QPID_AUTO_TEST_CASE(testGetHandle) +{ + Connection connection("localhost"); + ConsoleSession session(connection, ""); + posix::EventNotifier notifier(session); + + BOOST_CHECK(notifier.getHandle() > 0); +} + +QPID_AUTO_TEST_CASE(testSetReadableToFalse) +{ + Connection connection("localhost"); + ConsoleSession session(connection, ""); + posix::EventNotifier notifier(session); + PosixEventNotifierImplAccess::get(notifier).setReadable(false); + + bool readable(isReadable(notifier.getHandle())); + BOOST_CHECK(!readable); +} + +QPID_AUTO_TEST_CASE(testSetReadable) +{ + Connection connection("localhost"); + ConsoleSession session(connection, ""); + posix::EventNotifier notifier(session); + PosixEventNotifierImplAccess::get(notifier).setReadable(true); + + bool readable(isReadable(notifier.getHandle())); + BOOST_CHECK(readable); +} + +QPID_AUTO_TEST_CASE(testSetReadableMultiple) +{ + Connection connection("localhost"); + ConsoleSession session(connection, ""); + posix::EventNotifier notifier(session); + for (int i = 0; i < 15; i++) + PosixEventNotifierImplAccess::get(notifier).setReadable(true); + PosixEventNotifierImplAccess::get(notifier).setReadable(false); + + bool readable(isReadable(notifier.getHandle())); + BOOST_CHECK(!readable); +} + +QPID_AUTO_TEST_CASE(testDeleteNotifier) +{ + Connection connection("localhost"); + ConsoleSession session(connection, ""); + ConsoleSessionImpl& sessionImpl = ConsoleSessionImplAccess::get(session); + { + posix::EventNotifier notifier(session); + BOOST_CHECK(sessionImpl.getEventNotifier() != 0); + } + BOOST_CHECK(sessionImpl.getEventNotifier() == 0); +} + QPID_AUTO_TEST_SUITE_END() }} // namespace qpid::tests diff --git a/cpp/src/tests/QueueEvents.cpp b/cpp/src/tests/QueueEvents.cpp index bd18fa45fb..cea8bbf0db 100644 --- a/cpp/src/tests/QueueEvents.cpp +++ b/cpp/src/tests/QueueEvents.cpp @@ -147,7 +147,7 @@ struct EventRecorder QPID_AUTO_TEST_CASE(testSystemLevelEventProcessing) { - ProxySessionFixture fixture; + SessionFixture fixture; //register dummy event listener to broker EventRecorder listener; fixture.broker->getQueueEvents().registerListener("recorder", boost::bind(&EventRecorder::handle, &listener, _1)); @@ -194,7 +194,7 @@ QPID_AUTO_TEST_CASE(testSystemLevelEventProcessing) QPID_AUTO_TEST_CASE(testSystemLevelEventProcessing_enqueuesOnly) { - ProxySessionFixture fixture; + SessionFixture fixture; //register dummy event listener to broker EventRecorder listener; fixture.broker->getQueueEvents().registerListener("recorder", boost::bind(&EventRecorder::handle, &listener, _1)); diff --git a/cpp/src/tests/QueueFlowLimitTest.cpp b/cpp/src/tests/QueueFlowLimitTest.cpp new file mode 100644 index 0000000000..8a6923fb09 --- /dev/null +++ b/cpp/src/tests/QueueFlowLimitTest.cpp @@ -0,0 +1,463 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include <sstream> +#include <deque> +#include "unit_test.h" +#include "test_tools.h" + +#include "qpid/broker/QueuePolicy.h" +#include "qpid/broker/QueueFlowLimit.h" +#include "qpid/sys/Time.h" +#include "qpid/framing/reply_exceptions.h" +#include "MessageUtils.h" +#include "BrokerFixture.h" + +using namespace qpid::broker; +using namespace qpid::framing; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(QueueFlowLimitTestSuite) + +namespace { + +class TestFlow : public QueueFlowLimit +{ +public: + TestFlow(uint32_t flowStopCount, uint32_t flowResumeCount, + uint64_t flowStopSize, uint64_t flowResumeSize) : + QueueFlowLimit(0, flowStopCount, flowResumeCount, flowStopSize, flowResumeSize) + {} + virtual ~TestFlow() {} + + static TestFlow *createTestFlow(const qpid::framing::FieldTable& settings) + { + FieldTable::ValuePtr v; + + v = settings.get(flowStopCountKey); + uint32_t flowStopCount = (v) ? (uint32_t)v->get<int64_t>() : 0; + v = settings.get(flowResumeCountKey); + uint32_t flowResumeCount = (v) ? (uint32_t)v->get<int64_t>() : 0; + v = settings.get(flowStopSizeKey); + uint64_t flowStopSize = (v) ? (uint64_t)v->get<int64_t>() : 0; + v = settings.get(flowResumeSizeKey); + uint64_t flowResumeSize = (v) ? (uint64_t)v->get<int64_t>() : 0; + + return new TestFlow(flowStopCount, flowResumeCount, flowStopSize, flowResumeSize); + } + + static QueueFlowLimit *getQueueFlowLimit(const qpid::framing::FieldTable& settings) + { + return QueueFlowLimit::createLimit(0, settings); + } +}; + + + +QueuedMessage createMessage(uint32_t size) +{ + static uint32_t seqNum; + QueuedMessage msg; + msg.payload = MessageUtils::createMessage(); + msg.position = ++seqNum; + MessageUtils::addContent(msg.payload, std::string (size, 'x')); + return msg; +} +} + +QPID_AUTO_TEST_CASE(testFlowCount) +{ + FieldTable args; + args.setInt(QueueFlowLimit::flowStopCountKey, 7); + args.setInt(QueueFlowLimit::flowResumeCountKey, 5); + + std::auto_ptr<TestFlow> flow(TestFlow::createTestFlow(args)); + + BOOST_CHECK_EQUAL((uint32_t) 7, flow->getFlowStopCount()); + BOOST_CHECK_EQUAL((uint32_t) 5, flow->getFlowResumeCount()); + BOOST_CHECK_EQUAL((uint32_t) 0, flow->getFlowStopSize()); + BOOST_CHECK_EQUAL((uint32_t) 0, flow->getFlowResumeSize()); + BOOST_CHECK(!flow->isFlowControlActive()); + BOOST_CHECK(flow->monitorFlowControl()); + + std::deque<QueuedMessage> msgs; + for (size_t i = 0; i < 6; i++) { + msgs.push_back(createMessage(10)); + flow->enqueued(msgs.back()); + BOOST_CHECK(!flow->isFlowControlActive()); + } + BOOST_CHECK(!flow->isFlowControlActive()); // 6 on queue + msgs.push_back(createMessage(10)); + flow->enqueued(msgs.back()); + BOOST_CHECK(!flow->isFlowControlActive()); // 7 on queue + msgs.push_back(createMessage(10)); + flow->enqueued(msgs.back()); + BOOST_CHECK(flow->isFlowControlActive()); // 8 on queue, ON + msgs.push_back(createMessage(10)); + flow->enqueued(msgs.back()); + BOOST_CHECK(flow->isFlowControlActive()); // 9 on queue, no change to flow control + + flow->dequeued(msgs.front()); + msgs.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); // 8 on queue + flow->dequeued(msgs.front()); + msgs.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); // 7 on queue + flow->dequeued(msgs.front()); + msgs.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); // 6 on queue + flow->dequeued(msgs.front()); + msgs.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); // 5 on queue, no change + + flow->dequeued(msgs.front()); + msgs.pop_front(); + BOOST_CHECK(!flow->isFlowControlActive()); // 4 on queue, OFF +} + + +QPID_AUTO_TEST_CASE(testFlowSize) +{ + FieldTable args; + args.setUInt64(QueueFlowLimit::flowStopSizeKey, 70); + args.setUInt64(QueueFlowLimit::flowResumeSizeKey, 50); + + std::auto_ptr<TestFlow> flow(TestFlow::createTestFlow(args)); + + BOOST_CHECK_EQUAL((uint32_t) 0, flow->getFlowStopCount()); + BOOST_CHECK_EQUAL((uint32_t) 0, flow->getFlowResumeCount()); + BOOST_CHECK_EQUAL((uint32_t) 70, flow->getFlowStopSize()); + BOOST_CHECK_EQUAL((uint32_t) 50, flow->getFlowResumeSize()); + BOOST_CHECK(!flow->isFlowControlActive()); + BOOST_CHECK(flow->monitorFlowControl()); + + std::deque<QueuedMessage> msgs; + for (size_t i = 0; i < 6; i++) { + msgs.push_back(createMessage(10)); + flow->enqueued(msgs.back()); + BOOST_CHECK(!flow->isFlowControlActive()); + } + BOOST_CHECK(!flow->isFlowControlActive()); // 60 on queue + BOOST_CHECK_EQUAL(6u, flow->getFlowCount()); + BOOST_CHECK_EQUAL(60u, flow->getFlowSize()); + + QueuedMessage msg_9 = createMessage(9); + flow->enqueued(msg_9); + BOOST_CHECK(!flow->isFlowControlActive()); // 69 on queue + QueuedMessage tinyMsg_1 = createMessage(1); + flow->enqueued(tinyMsg_1); + BOOST_CHECK(!flow->isFlowControlActive()); // 70 on queue + + QueuedMessage tinyMsg_2 = createMessage(1); + flow->enqueued(tinyMsg_2); + BOOST_CHECK(flow->isFlowControlActive()); // 71 on queue, ON + msgs.push_back(createMessage(10)); + flow->enqueued(msgs.back()); + BOOST_CHECK(flow->isFlowControlActive()); // 81 on queue + BOOST_CHECK_EQUAL(10u, flow->getFlowCount()); + BOOST_CHECK_EQUAL(81u, flow->getFlowSize()); + + flow->dequeued(msgs.front()); + msgs.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); // 71 on queue + flow->dequeued(msgs.front()); + msgs.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); // 61 on queue + flow->dequeued(msgs.front()); + msgs.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); // 51 on queue + + flow->dequeued(tinyMsg_1); + BOOST_CHECK(flow->isFlowControlActive()); // 50 on queue + flow->dequeued(tinyMsg_2); + BOOST_CHECK(!flow->isFlowControlActive()); // 49 on queue, OFF + + flow->dequeued(msg_9); + BOOST_CHECK(!flow->isFlowControlActive()); // 40 on queue + flow->dequeued(msgs.front()); + msgs.pop_front(); + BOOST_CHECK(!flow->isFlowControlActive()); // 30 on queue + flow->dequeued(msgs.front()); + msgs.pop_front(); + BOOST_CHECK(!flow->isFlowControlActive()); // 20 on queue + BOOST_CHECK_EQUAL(2u, flow->getFlowCount()); + BOOST_CHECK_EQUAL(20u, flow->getFlowSize()); +} + +QPID_AUTO_TEST_CASE(testFlowArgs) +{ + FieldTable args; + const uint64_t stop(0x2FFFFFFFFull); + const uint64_t resume(0x1FFFFFFFFull); + args.setInt(QueueFlowLimit::flowStopCountKey, 30); + args.setInt(QueueFlowLimit::flowResumeCountKey, 21); + args.setUInt64(QueueFlowLimit::flowStopSizeKey, stop); + args.setUInt64(QueueFlowLimit::flowResumeSizeKey, resume); + + std::auto_ptr<TestFlow> flow(TestFlow::createTestFlow(args)); + + BOOST_CHECK_EQUAL((uint32_t) 30, flow->getFlowStopCount()); + BOOST_CHECK_EQUAL((uint32_t) 21, flow->getFlowResumeCount()); + BOOST_CHECK_EQUAL(stop, flow->getFlowStopSize()); + BOOST_CHECK_EQUAL(resume, flow->getFlowResumeSize()); + BOOST_CHECK(!flow->isFlowControlActive()); + BOOST_CHECK(flow->monitorFlowControl()); +} + + +QPID_AUTO_TEST_CASE(testFlowCombo) +{ + FieldTable args; + args.setInt(QueueFlowLimit::flowStopCountKey, 10); + args.setInt(QueueFlowLimit::flowResumeCountKey, 5); + args.setUInt64(QueueFlowLimit::flowStopSizeKey, 200); + args.setUInt64(QueueFlowLimit::flowResumeSizeKey, 100); + + std::deque<QueuedMessage> msgs_1; + std::deque<QueuedMessage> msgs_10; + std::deque<QueuedMessage> msgs_50; + std::deque<QueuedMessage> msgs_100; + + QueuedMessage msg; + + std::auto_ptr<TestFlow> flow(TestFlow::createTestFlow(args)); + BOOST_CHECK(!flow->isFlowControlActive()); // count:0 size:0 + + // verify flow control comes ON when only count passes its stop point. + + for (size_t i = 0; i < 10; i++) { + msgs_10.push_back(createMessage(10)); + flow->enqueued(msgs_10.back()); + BOOST_CHECK(!flow->isFlowControlActive()); + } + // count:10 size:100 + + msgs_1.push_back(createMessage(1)); + flow->enqueued(msgs_1.back()); // count:11 size: 101 ->ON + BOOST_CHECK(flow->isFlowControlActive()); + + for (size_t i = 0; i < 6; i++) { + flow->dequeued(msgs_10.front()); + msgs_10.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); + } + // count:5 size: 41 + + flow->dequeued(msgs_1.front()); // count: 4 size: 40 ->OFF + msgs_1.pop_front(); + BOOST_CHECK(!flow->isFlowControlActive()); + + for (size_t i = 0; i < 4; i++) { + flow->dequeued(msgs_10.front()); + msgs_10.pop_front(); + BOOST_CHECK(!flow->isFlowControlActive()); + } + // count:0 size:0 + + // verify flow control comes ON when only size passes its stop point. + + msgs_100.push_back(createMessage(100)); + flow->enqueued(msgs_100.back()); // count:1 size: 100 + BOOST_CHECK(!flow->isFlowControlActive()); + + msgs_50.push_back(createMessage(50)); + flow->enqueued(msgs_50.back()); // count:2 size: 150 + BOOST_CHECK(!flow->isFlowControlActive()); + + msgs_50.push_back(createMessage(50)); + flow->enqueued(msgs_50.back()); // count:3 size: 200 + BOOST_CHECK(!flow->isFlowControlActive()); + + msgs_1.push_back(createMessage(1)); + flow->enqueued(msgs_1.back()); // count:4 size: 201 ->ON + BOOST_CHECK(flow->isFlowControlActive()); + + flow->dequeued(msgs_100.front()); // count:3 size:101 + msgs_100.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); + + flow->dequeued(msgs_1.front()); // count:2 size:100 + msgs_1.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); + + flow->dequeued(msgs_50.front()); // count:1 size:50 ->OFF + msgs_50.pop_front(); + BOOST_CHECK(!flow->isFlowControlActive()); + + // verify flow control remains ON until both thresholds drop below their + // resume point. + + for (size_t i = 0; i < 8; i++) { + msgs_10.push_back(createMessage(10)); + flow->enqueued(msgs_10.back()); + BOOST_CHECK(!flow->isFlowControlActive()); + } + // count:9 size:130 + + msgs_10.push_back(createMessage(10)); + flow->enqueued(msgs_10.back()); // count:10 size: 140 + BOOST_CHECK(!flow->isFlowControlActive()); + + msgs_1.push_back(createMessage(1)); + flow->enqueued(msgs_1.back()); // count:11 size: 141 ->ON + BOOST_CHECK(flow->isFlowControlActive()); + + msgs_100.push_back(createMessage(100)); + flow->enqueued(msgs_100.back()); // count:12 size: 241 (both thresholds crossed) + BOOST_CHECK(flow->isFlowControlActive()); + + // at this point: 9@10 + 1@50 + 1@100 + 1@1 == 12@241 + + flow->dequeued(msgs_50.front()); // count:11 size:191 + msgs_50.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); + + for (size_t i = 0; i < 9; i++) { + flow->dequeued(msgs_10.front()); + msgs_10.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); + } + // count:2 size:101 + flow->dequeued(msgs_1.front()); // count:1 size:100 + msgs_1.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); // still active due to size + + flow->dequeued(msgs_100.front()); // count:0 size:0 ->OFF + msgs_100.pop_front(); + BOOST_CHECK(!flow->isFlowControlActive()); +} + + +QPID_AUTO_TEST_CASE(testFlowDefaultArgs) +{ + QueueFlowLimit::setDefaults(2950001, // max queue byte count + 80, // 80% stop threshold + 70); // 70% resume threshold + FieldTable args; + QueueFlowLimit *ptr = TestFlow::getQueueFlowLimit(args); + + BOOST_CHECK(ptr); + std::auto_ptr<QueueFlowLimit> flow(ptr); + BOOST_CHECK_EQUAL((uint64_t) 2360001, flow->getFlowStopSize()); + BOOST_CHECK_EQUAL((uint64_t) 2065000, flow->getFlowResumeSize()); + BOOST_CHECK_EQUAL( 0u, flow->getFlowStopCount()); + BOOST_CHECK_EQUAL( 0u, flow->getFlowResumeCount()); + BOOST_CHECK(!flow->isFlowControlActive()); + BOOST_CHECK(flow->monitorFlowControl()); +} + + +QPID_AUTO_TEST_CASE(testFlowOverrideArgs) +{ + QueueFlowLimit::setDefaults(2950001, // max queue byte count + 80, // 80% stop threshold + 70); // 70% resume threshold + { + FieldTable args; + args.setInt(QueueFlowLimit::flowStopCountKey, 35000); + args.setInt(QueueFlowLimit::flowResumeCountKey, 30000); + + QueueFlowLimit *ptr = TestFlow::getQueueFlowLimit(args); + BOOST_CHECK(ptr); + std::auto_ptr<QueueFlowLimit> flow(ptr); + + BOOST_CHECK_EQUAL((uint32_t) 35000, flow->getFlowStopCount()); + BOOST_CHECK_EQUAL((uint32_t) 30000, flow->getFlowResumeCount()); + BOOST_CHECK_EQUAL((uint64_t) 0, flow->getFlowStopSize()); + BOOST_CHECK_EQUAL((uint64_t) 0, flow->getFlowResumeSize()); + BOOST_CHECK(!flow->isFlowControlActive()); + BOOST_CHECK(flow->monitorFlowControl()); + } + { + FieldTable args; + args.setInt(QueueFlowLimit::flowStopSizeKey, 350000); + args.setInt(QueueFlowLimit::flowResumeSizeKey, 300000); + + QueueFlowLimit *ptr = TestFlow::getQueueFlowLimit(args); + BOOST_CHECK(ptr); + std::auto_ptr<QueueFlowLimit> flow(ptr); + + BOOST_CHECK_EQUAL((uint32_t) 0, flow->getFlowStopCount()); + BOOST_CHECK_EQUAL((uint32_t) 0, flow->getFlowResumeCount()); + BOOST_CHECK_EQUAL((uint64_t) 350000, flow->getFlowStopSize()); + BOOST_CHECK_EQUAL((uint64_t) 300000, flow->getFlowResumeSize()); + BOOST_CHECK(!flow->isFlowControlActive()); + BOOST_CHECK(flow->monitorFlowControl()); + } + { + FieldTable args; + args.setInt(QueueFlowLimit::flowStopCountKey, 35000); + args.setInt(QueueFlowLimit::flowResumeCountKey, 30000); + args.setInt(QueueFlowLimit::flowStopSizeKey, 350000); + args.setInt(QueueFlowLimit::flowResumeSizeKey, 300000); + + QueueFlowLimit *ptr = TestFlow::getQueueFlowLimit(args); + BOOST_CHECK(ptr); + std::auto_ptr<QueueFlowLimit> flow(ptr); + + BOOST_CHECK_EQUAL((uint32_t) 35000, flow->getFlowStopCount()); + BOOST_CHECK_EQUAL((uint32_t) 30000, flow->getFlowResumeCount()); + BOOST_CHECK_EQUAL((uint64_t) 350000, flow->getFlowStopSize()); + BOOST_CHECK_EQUAL((uint64_t) 300000, flow->getFlowResumeSize()); + BOOST_CHECK(!flow->isFlowControlActive()); + BOOST_CHECK(flow->monitorFlowControl()); + } +} + + +QPID_AUTO_TEST_CASE(testFlowOverrideDefaults) +{ + QueueFlowLimit::setDefaults(2950001, // max queue byte count + 97, // stop threshold + 73); // resume threshold + FieldTable args; + QueueFlowLimit *ptr = TestFlow::getQueueFlowLimit(args); + BOOST_CHECK(ptr); + std::auto_ptr<QueueFlowLimit> flow(ptr); + + BOOST_CHECK_EQUAL((uint32_t) 2861501, flow->getFlowStopSize()); + BOOST_CHECK_EQUAL((uint32_t) 2153500, flow->getFlowResumeSize()); + BOOST_CHECK(!flow->isFlowControlActive()); + BOOST_CHECK(flow->monitorFlowControl()); +} + + +QPID_AUTO_TEST_CASE(testFlowDisable) +{ + { + FieldTable args; + args.setInt(QueueFlowLimit::flowStopCountKey, 0); + QueueFlowLimit *ptr = TestFlow::getQueueFlowLimit(args); + BOOST_CHECK(!ptr); + } + { + FieldTable args; + args.setInt(QueueFlowLimit::flowStopSizeKey, 0); + QueueFlowLimit *ptr = TestFlow::getQueueFlowLimit(args); + BOOST_CHECK(!ptr); + } +} + + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/cpp/src/tests/QueuePolicyTest.cpp b/cpp/src/tests/QueuePolicyTest.cpp index 90af9c7dd9..f735e09449 100644 --- a/cpp/src/tests/QueuePolicyTest.cpp +++ b/cpp/src/tests/QueuePolicyTest.cpp @@ -23,6 +23,7 @@ #include "test_tools.h" #include "qpid/broker/QueuePolicy.h" +#include "qpid/broker/QueueFlowLimit.h" #include "qpid/client/QueueOptions.h" #include "qpid/sys/Time.h" #include "qpid/framing/reply_exceptions.h" @@ -38,6 +39,7 @@ namespace tests { QPID_AUTO_TEST_SUITE(QueuePolicyTestSuite) +namespace { QueuedMessage createMessage(uint32_t size) { QueuedMessage msg; @@ -45,7 +47,7 @@ QueuedMessage createMessage(uint32_t size) MessageUtils::addContent(msg.payload, std::string (size, 'x')); return msg; } - +} QPID_AUTO_TEST_CASE(testCount) { @@ -150,7 +152,7 @@ QPID_AUTO_TEST_CASE(testRingPolicyCount) std::auto_ptr<QueuePolicy> policy = QueuePolicy::createQueuePolicy("test", 5, 0, QueuePolicy::RING); policy->update(args); - ProxySessionFixture f; + SessionFixture f; std::string q("my-ring-queue"); f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); for (int i = 0; i < 10; i++) { @@ -185,7 +187,7 @@ QPID_AUTO_TEST_CASE(testRingPolicySize) std::auto_ptr<QueuePolicy> policy = QueuePolicy::createQueuePolicy("test", 0, 500, QueuePolicy::RING); policy->update(args); - ProxySessionFixture f; + SessionFixture f; std::string q("my-ring-queue"); f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); @@ -257,7 +259,7 @@ QPID_AUTO_TEST_CASE(testStrictRingPolicy) std::auto_ptr<QueuePolicy> policy = QueuePolicy::createQueuePolicy("test", 5, 0, QueuePolicy::RING_STRICT); policy->update(args); - ProxySessionFixture f; + SessionFixture f; std::string q("my-ring-queue"); f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); LocalQueue incoming; @@ -283,7 +285,7 @@ QPID_AUTO_TEST_CASE(testPolicyWithDtx) std::auto_ptr<QueuePolicy> policy = QueuePolicy::createQueuePolicy("test", 5, 0, QueuePolicy::REJECT); policy->update(args); - ProxySessionFixture f; + SessionFixture f; std::string q("my-policy-queue"); f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); LocalQueue incoming; @@ -340,8 +342,10 @@ QPID_AUTO_TEST_CASE(testFlowToDiskWithNoStore) //fallback to rejecting messages QueueOptions args; args.setSizePolicy(FLOW_TO_DISK, 0, 5); + // Disable flow control, or else we'll never hit the max limit + args.setInt(QueueFlowLimit::flowStopCountKey, 0); - ProxySessionFixture f; + SessionFixture f; std::string q("my-queue"); f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); LocalQueue incoming; @@ -367,7 +371,7 @@ QPID_AUTO_TEST_CASE(testPolicyFailureOnCommit) std::auto_ptr<QueuePolicy> policy = QueuePolicy::createQueuePolicy("test", 5, 0, QueuePolicy::REJECT); policy->update(args); - ProxySessionFixture f; + SessionFixture f; std::string q("q"); f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); f.session.txSelect(); @@ -382,8 +386,9 @@ QPID_AUTO_TEST_CASE(testCapacityConversion) { FieldTable args; args.setString("qpid.max_count", "5"); + args.setString("qpid.flow_stop_count", "0"); - ProxySessionFixture f; + SessionFixture f; std::string q("q"); f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); for (int i = 0; i < 5; i++) { diff --git a/cpp/src/tests/QueueTest.cpp b/cpp/src/tests/QueueTest.cpp index 80c69ac386..aaa2721021 100644 --- a/cpp/src/tests/QueueTest.cpp +++ b/cpp/src/tests/QueueTest.cpp @@ -36,6 +36,9 @@ #include "qpid/framing/AMQFrame.h" #include "qpid/framing/MessageTransferBody.h" #include "qpid/framing/reply_exceptions.h" +#include "qpid/broker/QueuePolicy.h" +#include "qpid/broker/QueueFlowLimit.h" + #include <iostream> #include "boost/format.hpp" @@ -53,12 +56,12 @@ class TestConsumer : public virtual Consumer{ public: typedef boost::shared_ptr<TestConsumer> shared_ptr; - intrusive_ptr<Message> last; + QueuedMessage last; bool received; - TestConsumer(bool acquire = true):Consumer(acquire), received(false) {}; + TestConsumer(std::string name="test", bool acquire = true):Consumer(name, acquire), received(false) {}; virtual bool deliver(QueuedMessage& msg){ - last = msg.payload; + last = msg; received = true; return true; }; @@ -78,13 +81,14 @@ public: Message& getMessage() { return *(msg.get()); } }; -intrusive_ptr<Message> create_message(std::string exchange, std::string routingKey) { +intrusive_ptr<Message> create_message(std::string exchange, std::string routingKey, uint64_t ttl = 0) { intrusive_ptr<Message> msg(new Message()); AMQFrame method((MessageTransferBody(ProtocolVersion(), exchange, 0, 0))); AMQFrame header((AMQHeaderBody())); msg->getFrames().append(method); msg->getFrames().append(header); msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setRoutingKey(routingKey); + if (ttl) msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setTtl(ttl); return msg; } @@ -145,16 +149,16 @@ QPID_AUTO_TEST_CASE(testConsumers){ queue->deliver(msg1); BOOST_CHECK(queue->dispatch(c1)); - BOOST_CHECK_EQUAL(msg1.get(), c1->last.get()); + BOOST_CHECK_EQUAL(msg1.get(), c1->last.payload.get()); queue->deliver(msg2); BOOST_CHECK(queue->dispatch(c2)); - BOOST_CHECK_EQUAL(msg2.get(), c2->last.get()); + BOOST_CHECK_EQUAL(msg2.get(), c2->last.payload.get()); c1->received = false; queue->deliver(msg3); BOOST_CHECK(queue->dispatch(c1)); - BOOST_CHECK_EQUAL(msg3.get(), c1->last.get()); + BOOST_CHECK_EQUAL(msg3.get(), c1->last.payload.get()); //Test cancellation: queue->cancel(c1); @@ -210,7 +214,7 @@ QPID_AUTO_TEST_CASE(testDequeue){ if (!consumer->received) sleep(2); - BOOST_CHECK_EQUAL(msg3.get(), consumer->last.get()); + BOOST_CHECK_EQUAL(msg3.get(), consumer->last.payload.get()); BOOST_CHECK_EQUAL(uint32_t(0), queue->getMessageCount()); received = queue->get().payload; @@ -244,7 +248,7 @@ QPID_AUTO_TEST_CASE(testBound){ exchange2.reset(); //unbind the queue from all exchanges it knows it has been bound to: - queue->unbind(exchanges, queue); + queue->unbind(exchanges); //ensure the remaining exchanges don't still have the queue bound to them: FailOnDeliver deliverable; @@ -254,26 +258,26 @@ QPID_AUTO_TEST_CASE(testBound){ QPID_AUTO_TEST_CASE(testPersistLastNodeStanding){ client::QueueOptions args; - args.setPersistLastNode(); + args.setPersistLastNode(); - Queue::shared_ptr queue(new Queue("my-queue", true)); + Queue::shared_ptr queue(new Queue("my-queue", true)); queue->configure(args); intrusive_ptr<Message> msg1 = create_message("e", "A"); intrusive_ptr<Message> msg2 = create_message("e", "B"); intrusive_ptr<Message> msg3 = create_message("e", "C"); - //enqueue 2 messages + //enqueue 2 messages queue->deliver(msg1); queue->deliver(msg2); - //change mode - queue->setLastNodeFailure(); + //change mode + queue->setLastNodeFailure(); - //enqueue 1 message + //enqueue 1 message queue->deliver(msg3); - //check all have persistent ids. + //check all have persistent ids. BOOST_CHECK(msg1->isPersistent()); BOOST_CHECK(msg2->isPersistent()); BOOST_CHECK(msg3->isPersistent()); @@ -283,54 +287,58 @@ QPID_AUTO_TEST_CASE(testPersistLastNodeStanding){ QPID_AUTO_TEST_CASE(testSeek){ - Queue::shared_ptr queue(new Queue("my-queue", true)); + Queue::shared_ptr queue(new Queue("my-queue", true)); intrusive_ptr<Message> msg1 = create_message("e", "A"); intrusive_ptr<Message> msg2 = create_message("e", "B"); intrusive_ptr<Message> msg3 = create_message("e", "C"); - //enqueue 2 messages + //enqueue 2 messages queue->deliver(msg1); queue->deliver(msg2); queue->deliver(msg3); - TestConsumer::shared_ptr consumer(new TestConsumer(false)); + TestConsumer::shared_ptr consumer(new TestConsumer("test", false)); SequenceNumber seq(2); consumer->position = seq; QueuedMessage qm; queue->dispatch(consumer); - - BOOST_CHECK_EQUAL(msg3.get(), consumer->last.get()); + + BOOST_CHECK_EQUAL(msg3.get(), consumer->last.payload.get()); queue->dispatch(consumer); queue->dispatch(consumer); // make sure over-run is safe - + } QPID_AUTO_TEST_CASE(testSearch){ - Queue::shared_ptr queue(new Queue("my-queue", true)); + Queue::shared_ptr queue(new Queue("my-queue", true)); intrusive_ptr<Message> msg1 = create_message("e", "A"); intrusive_ptr<Message> msg2 = create_message("e", "B"); intrusive_ptr<Message> msg3 = create_message("e", "C"); - //enqueue 2 messages + //enqueue 2 messages queue->deliver(msg1); queue->deliver(msg2); queue->deliver(msg3); SequenceNumber seq(2); - QueuedMessage qm = queue->find(seq); - + QueuedMessage qm; + TestConsumer::shared_ptr c1(new TestConsumer()); + + BOOST_CHECK(queue->find(seq, qm)); + BOOST_CHECK_EQUAL(seq.getValue(), qm.position.getValue()); - - queue->acquire(qm); + + queue->acquire(qm, c1->getName()); BOOST_CHECK_EQUAL(queue->getMessageCount(), 2u); SequenceNumber seq1(3); - QueuedMessage qm1 = queue->find(seq1); + QueuedMessage qm1; + BOOST_CHECK(queue->find(seq1, qm1)); BOOST_CHECK_EQUAL(seq1.getValue(), qm1.position.getValue()); - + } const std::string nullxid = ""; @@ -416,10 +424,10 @@ QPID_AUTO_TEST_CASE(testLVQOrdering){ client::QueueOptions args; // set queue mode - args.setOrdering(client::LVQ); + args.setOrdering(client::LVQ); - Queue::shared_ptr queue(new Queue("my-queue", true )); - queue->configure(args); + Queue::shared_ptr queue(new Queue("my-queue", true )); + queue->configure(args); intrusive_ptr<Message> msg1 = create_message("e", "A"); intrusive_ptr<Message> msg2 = create_message("e", "B"); @@ -430,16 +438,16 @@ QPID_AUTO_TEST_CASE(testLVQOrdering){ //set deliever match for LVQ a,b,c,a string key; - args.getLVQKey(key); + args.getLVQKey(key); BOOST_CHECK_EQUAL(key, "qpid.LVQ_key"); - msg1->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"a"); - msg2->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"b"); - msg3->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"c"); - msg4->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"a"); + msg1->insertCustomProperty(key,"a"); + msg2->insertCustomProperty(key,"b"); + msg3->insertCustomProperty(key,"c"); + msg4->insertCustomProperty(key,"a"); - //enqueue 4 message + //enqueue 4 message queue->deliver(msg1); queue->deliver(msg2); queue->deliver(msg3); @@ -459,9 +467,9 @@ QPID_AUTO_TEST_CASE(testLVQOrdering){ intrusive_ptr<Message> msg5 = create_message("e", "A"); intrusive_ptr<Message> msg6 = create_message("e", "B"); intrusive_ptr<Message> msg7 = create_message("e", "C"); - msg5->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"a"); - msg6->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"b"); - msg7->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"c"); + msg5->insertCustomProperty(key,"a"); + msg6->insertCustomProperty(key,"b"); + msg7->insertCustomProperty(key,"c"); queue->deliver(msg5); queue->deliver(msg6); queue->deliver(msg7); @@ -496,7 +504,7 @@ QPID_AUTO_TEST_CASE(testLVQEmptyKey){ BOOST_CHECK_EQUAL(key, "qpid.LVQ_key"); - msg1->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"a"); + msg1->insertCustomProperty(key,"a"); queue->deliver(msg1); queue->deliver(msg2); BOOST_CHECK_EQUAL(queue->getMessageCount(), 2u); @@ -508,6 +516,8 @@ QPID_AUTO_TEST_CASE(testLVQAcquire){ client::QueueOptions args; // set queue mode args.setOrdering(client::LVQ); + // disable flow control, as this test violates the enqueue/dequeue sequence. + args.setInt(QueueFlowLimit::flowStopCountKey, 0); Queue::shared_ptr queue(new Queue("my-queue", true )); queue->configure(args); @@ -526,12 +536,12 @@ QPID_AUTO_TEST_CASE(testLVQAcquire){ BOOST_CHECK_EQUAL(key, "qpid.LVQ_key"); - msg1->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"a"); - msg2->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"b"); - msg3->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"c"); - msg4->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"a"); - msg5->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"b"); - msg6->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"c"); + msg1->insertCustomProperty(key,"a"); + msg2->insertCustomProperty(key,"b"); + msg3->insertCustomProperty(key,"c"); + msg4->insertCustomProperty(key,"a"); + msg5->insertCustomProperty(key,"b"); + msg6->insertCustomProperty(key,"c"); //enqueue 4 message queue->deliver(msg1); @@ -546,12 +556,13 @@ QPID_AUTO_TEST_CASE(testLVQAcquire){ QueuedMessage qmsg2(queue.get(), msg2, ++sequence); framing::SequenceNumber sequence1(10); QueuedMessage qmsg3(queue.get(), 0, sequence1); + TestConsumer::shared_ptr dummy(new TestConsumer()); - BOOST_CHECK(!queue->acquire(qmsg)); - BOOST_CHECK(queue->acquire(qmsg2)); + BOOST_CHECK(!queue->acquire(qmsg, dummy->getName())); + BOOST_CHECK(queue->acquire(qmsg2, dummy->getName())); // Acquire the massage again to test failure case. - BOOST_CHECK(!queue->acquire(qmsg2)); - BOOST_CHECK(!queue->acquire(qmsg3)); + BOOST_CHECK(!queue->acquire(qmsg2, dummy->getName())); + BOOST_CHECK(!queue->acquire(qmsg3, dummy->getName())); BOOST_CHECK_EQUAL(queue->getMessageCount(), 2u); @@ -561,7 +572,7 @@ QPID_AUTO_TEST_CASE(testLVQAcquire){ // set mode to no browse and check args.setOrdering(client::LVQ_NO_BROWSE); queue->configure(args); - TestConsumer::shared_ptr c1(new TestConsumer(false)); + TestConsumer::shared_ptr c1(new TestConsumer("test", false)); queue->dispatch(c1); queue->dispatch(c1); @@ -595,8 +606,8 @@ QPID_AUTO_TEST_CASE(testLVQMultiQueue){ args.getLVQKey(key); BOOST_CHECK_EQUAL(key, "qpid.LVQ_key"); - msg1->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"a"); - msg2->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"a"); + msg1->insertCustomProperty(key,"a"); + msg2->insertCustomProperty(key,"a"); queue1->deliver(msg1); queue2->deliver(msg1); @@ -630,7 +641,7 @@ QPID_AUTO_TEST_CASE(testLVQRecover){ Queue::shared_ptr queue1(new Queue("my-queue", true, &testStore)); intrusive_ptr<Message> received; - queue1->configure(args); + queue1->create(args); intrusive_ptr<Message> msg1 = create_message("e", "A"); intrusive_ptr<Message> msg2 = create_message("e", "A"); @@ -639,9 +650,9 @@ QPID_AUTO_TEST_CASE(testLVQRecover){ args.getLVQKey(key); BOOST_CHECK_EQUAL(key, "qpid.LVQ_key"); - msg1->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"a"); - msg2->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"a"); - // 3 + msg1->insertCustomProperty(key,"a"); + msg2->insertCustomProperty(key,"a"); + // 3 queue1->deliver(msg1); // 4 queue1->setLastNodeFailure(); @@ -660,13 +671,8 @@ QPID_AUTO_TEST_CASE(testLVQRecover){ void addMessagesToQueue(uint count, Queue& queue, uint oddTtl = 200, uint evenTtl = 0) { for (uint i = 0; i < count; i++) { - intrusive_ptr<Message> m = create_message("exchange", "key"); - if (i % 2) { - if (oddTtl) m->getProperties<DeliveryProperties>()->setTtl(oddTtl); - } else { - if (evenTtl) m->getProperties<DeliveryProperties>()->setTtl(evenTtl); - } - m->setTimestamp(new broker::ExpiryPolicy); + intrusive_ptr<Message> m = create_message("exchange", "key", i % 2 ? oddTtl : evenTtl); + m->computeExpiration(new broker::ExpiryPolicy); queue.deliver(m); } } @@ -676,7 +682,7 @@ QPID_AUTO_TEST_CASE(testPurgeExpired) { addMessagesToQueue(10, queue); BOOST_CHECK_EQUAL(queue.getMessageCount(), 10u); ::usleep(300*1000); - queue.purgeExpired(); + queue.purgeExpired(0); BOOST_CHECK_EQUAL(queue.getMessageCount(), 5u); } @@ -687,7 +693,7 @@ QPID_AUTO_TEST_CASE(testQueueCleaner) { addMessagesToQueue(10, *queue, 200, 400); BOOST_CHECK_EQUAL(queue->getMessageCount(), 10u); - QueueCleaner cleaner(queues, timer); + QueueCleaner cleaner(queues, &timer); cleaner.start(100 * qpid::sys::TIME_MSEC); ::usleep(300*1000); BOOST_CHECK_EQUAL(queue->getMessageCount(), 5u); @@ -695,6 +701,280 @@ QPID_AUTO_TEST_CASE(testQueueCleaner) { BOOST_CHECK_EQUAL(queue->getMessageCount(), 0u); } + +namespace { + // helper for group tests + void verifyAcquire( Queue::shared_ptr queue, + TestConsumer::shared_ptr c, + std::deque<QueuedMessage>& results, + const std::string& expectedGroup, + const int expectedId ) + { + queue->dispatch(c); + results.push_back(c->last); + std::string group = c->last.payload->getProperties<MessageProperties>()->getApplicationHeaders().getAsString("GROUP-ID"); + int id = c->last.payload->getProperties<MessageProperties>()->getApplicationHeaders().getAsInt("MY-ID"); + BOOST_CHECK_EQUAL( group, expectedGroup ); + BOOST_CHECK_EQUAL( id, expectedId ); + } +} + +QPID_AUTO_TEST_CASE(testGroupsMultiConsumer) { + // + // Verify that consumers of grouped messages own the groups once a message is acquired, + // and release the groups once all acquired messages have been dequeued or requeued + // + FieldTable args; + Queue::shared_ptr queue(new Queue("my_queue", true)); + args.setString("qpid.group_header_key", "GROUP-ID"); + args.setInt("qpid.shared_msg_group", 1); + queue->configure(args); + + std::string groups[] = { std::string("a"), std::string("a"), std::string("a"), + std::string("b"), std::string("b"), std::string("b"), + std::string("c"), std::string("c"), std::string("c") }; + for (int i = 0; i < 9; ++i) { + intrusive_ptr<Message> msg = create_message("e", "A"); + msg->insertCustomProperty("GROUP-ID", groups[i]); + msg->insertCustomProperty("MY-ID", i); + queue->deliver(msg); + } + + // Queue = a-0, a-1, a-2, b-3, b-4, b-5, c-6, c-7, c-8... + // Owners= ---, ---, ---, ---, ---, ---, ---, ---, ---, + + BOOST_CHECK_EQUAL(uint32_t(9), queue->getMessageCount()); + + TestConsumer::shared_ptr c1(new TestConsumer("C1")); + TestConsumer::shared_ptr c2(new TestConsumer("C2")); + + queue->consume(c1); + queue->consume(c2); + + std::deque<QueuedMessage> dequeMeC1; + std::deque<QueuedMessage> dequeMeC2; + + + verifyAcquire(queue, c1, dequeMeC1, "a", 0 ); // c1 now owns group "a" (acquire a-0) + verifyAcquire(queue, c2, dequeMeC2, "b", 3 ); // c2 should now own group "b" (acquire b-3) + + // now let c1 complete the 'a-0' message - this should free the 'a' group + queue->dequeue( 0, dequeMeC1.front() ); + dequeMeC1.pop_front(); + + // Queue = a-1, a-2, b-3, b-4, b-5, c-6, c-7, c-8... + // Owners= ---, ---, ^C2, ^C2, ^C2, ---, ---, --- + + // now c2 should pick up the next 'a-1', since it is oldest free + verifyAcquire(queue, c2, dequeMeC2, "a", 1 ); // c2 should now own groups "a" and "b" + + // Queue = a-1, a-2, b-3, b-4, b-5, c-6, c-7, c-8... + // Owners= ^C2, ^C2, ^C2, ^C2, ^C2, ---, ---, --- + + // c1 should only be able to snarf up the first "c" message now... + verifyAcquire(queue, c1, dequeMeC1, "c", 6 ); // should skip to the first "c" + + // Queue = a-1, a-2, b-3, b-4, b-5, c-6, c-7, c-8... + // Owners= ^C2, ^C2, ^C2, ^C2, ^C2, ^C1, ^C1, ^C1 + + // hmmm... what if c2 now dequeues "b-3"? (now only has a-1 acquired) + queue->dequeue( 0, dequeMeC2.front() ); + dequeMeC2.pop_front(); + + // Queue = a-1, a-2, b-4, b-5, c-6, c-7, c-8... + // Owners= ^C2, ^C2, ---, ---, ^C1, ^C1, ^C1 + + // b group is free, c is owned by c1 - c1's next get should grab 'b-4' + verifyAcquire(queue, c1, dequeMeC1, "b", 4 ); + + // Queue = a-1, a-2, b-4, b-5, c-6, c-7, c-8... + // Owners= ^C2, ^C2, ^C1, ^C1, ^C1, ^C1, ^C1 + + // c2 can now only grab a-2, and that's all + verifyAcquire(queue, c2, dequeMeC2, "a", 2 ); + + // now C2 can't get any more, since C1 owns "b" and "c" group... + bool gotOne = queue->dispatch(c2); + BOOST_CHECK( !gotOne ); + + // hmmm... what if c1 now dequeues "c-6"? (now only own's b-4) + queue->dequeue( 0, dequeMeC1.front() ); + dequeMeC1.pop_front(); + + // Queue = a-1, a-2, b-4, b-5, c-7, c-8... + // Owners= ^C2, ^C2, ^C1, ^C1, ---, --- + + // c2 can now grab c-7 + verifyAcquire(queue, c2, dequeMeC2, "c", 7 ); + + // Queue = a-1, a-2, b-4, b-5, c-7, c-8... + // Owners= ^C2, ^C2, ^C1, ^C1, ^C2, ^C2 + + // what happens if C-2 "requeues" a-1 and a-2? + queue->requeue( dequeMeC2.front() ); + dequeMeC2.pop_front(); + queue->requeue( dequeMeC2.front() ); + dequeMeC2.pop_front(); // now just has c-7 acquired + + // Queue = a-1, a-2, b-4, b-5, c-7, c-8... + // Owners= ---, ---, ^C1, ^C1, ^C2, ^C2 + + // now c1 will grab a-1 and a-2... + verifyAcquire(queue, c1, dequeMeC1, "a", 1 ); + verifyAcquire(queue, c1, dequeMeC1, "a", 2 ); + + // Queue = a-1, a-2, b-4, b-5, c-7, c-8... + // Owners= ^C1, ^C1, ^C1, ^C1, ^C2, ^C2 + + // c2 can now acquire c-8 only + verifyAcquire(queue, c2, dequeMeC2, "c", 8 ); + + // and c1 can get b-5 + verifyAcquire(queue, c1, dequeMeC1, "b", 5 ); + + // should be no more acquire-able for anyone now: + gotOne = queue->dispatch(c1); + BOOST_CHECK( !gotOne ); + gotOne = queue->dispatch(c2); + BOOST_CHECK( !gotOne ); + + // requeue all of C1's acquired messages, then cancel C1 + while (!dequeMeC1.empty()) { + queue->requeue(dequeMeC1.front()); + dequeMeC1.pop_front(); + } + queue->cancel(c1); + + // Queue = a-1, a-2, b-4, b-5, c-7, c-8... + // Owners= ---, ---, ---, ---, ^C2, ^C2 + + // b-4, a-1, a-2, b-5 all should be available, right? + verifyAcquire(queue, c2, dequeMeC2, "a", 1 ); + + while (!dequeMeC2.empty()) { + queue->dequeue(0, dequeMeC2.front()); + dequeMeC2.pop_front(); + } + + // Queue = a-2, b-4, b-5 + // Owners= ---, ---, --- + + TestConsumer::shared_ptr c3(new TestConsumer("C3")); + std::deque<QueuedMessage> dequeMeC3; + + verifyAcquire(queue, c3, dequeMeC3, "a", 2 ); + verifyAcquire(queue, c2, dequeMeC2, "b", 4 ); + + // Queue = a-2, b-4, b-5 + // Owners= ^C3, ^C2, ^C2 + + gotOne = queue->dispatch(c3); + BOOST_CHECK( !gotOne ); + + verifyAcquire(queue, c2, dequeMeC2, "b", 5 ); + + while (!dequeMeC2.empty()) { + queue->dequeue(0, dequeMeC2.front()); + dequeMeC2.pop_front(); + } + + // Queue = a-2, + // Owners= ^C3, + + intrusive_ptr<Message> msg = create_message("e", "A"); + msg->insertCustomProperty("GROUP-ID", "a"); + msg->insertCustomProperty("MY-ID", 9); + queue->deliver(msg); + + // Queue = a-2, a-9 + // Owners= ^C3, ^C3 + + gotOne = queue->dispatch(c2); + BOOST_CHECK( !gotOne ); + + msg = create_message("e", "A"); + msg->insertCustomProperty("GROUP-ID", "b"); + msg->insertCustomProperty("MY-ID", 10); + queue->deliver(msg); + + // Queue = a-2, a-9, b-10 + // Owners= ^C3, ^C3, ---- + + verifyAcquire(queue, c2, dequeMeC2, "b", 10 ); + verifyAcquire(queue, c3, dequeMeC3, "a", 9 ); + + gotOne = queue->dispatch(c3); + BOOST_CHECK( !gotOne ); + + queue->cancel(c2); + queue->cancel(c3); +} + + +QPID_AUTO_TEST_CASE(testGroupsMultiConsumerDefaults) { + // + // Verify that the same default group name is automatically applied to messages that + // do not specify a group name. + // + FieldTable args; + Queue::shared_ptr queue(new Queue("my_queue", true)); + args.setString("qpid.group_header_key", "GROUP-ID"); + args.setInt("qpid.shared_msg_group", 1); + queue->configure(args); + + for (int i = 0; i < 3; ++i) { + intrusive_ptr<Message> msg = create_message("e", "A"); + // no "GROUP-ID" header + msg->insertCustomProperty("MY-ID", i); + queue->deliver(msg); + } + + // Queue = 0, 1, 2 + + BOOST_CHECK_EQUAL(uint32_t(3), queue->getMessageCount()); + + TestConsumer::shared_ptr c1(new TestConsumer("C1")); + TestConsumer::shared_ptr c2(new TestConsumer("C2")); + + queue->consume(c1); + queue->consume(c2); + + std::deque<QueuedMessage> dequeMeC1; + std::deque<QueuedMessage> dequeMeC2; + + queue->dispatch(c1); // c1 now owns default group (acquired 0) + dequeMeC1.push_back(c1->last); + int id = c1->last.payload->getProperties<MessageProperties>()->getApplicationHeaders().getAsInt("MY-ID"); + BOOST_CHECK_EQUAL( id, 0 ); + + bool gotOne = queue->dispatch(c2); // c2 should get nothing + BOOST_CHECK( !gotOne ); + + queue->dispatch(c1); // c1 now acquires 1 + dequeMeC1.push_back(c1->last); + id = c1->last.payload->getProperties<MessageProperties>()->getApplicationHeaders().getAsInt("MY-ID"); + BOOST_CHECK_EQUAL( id, 1 ); + + gotOne = queue->dispatch(c2); // c2 should still get nothing + BOOST_CHECK( !gotOne ); + + while (!dequeMeC1.empty()) { + queue->dequeue(0, dequeMeC1.front()); + dequeMeC1.pop_front(); + } + + // now default group should be available... + queue->dispatch(c2); // c2 now owns default group (acquired 2) + id = c2->last.payload->getProperties<MessageProperties>()->getApplicationHeaders().getAsInt("MY-ID"); + BOOST_CHECK_EQUAL( id, 2 ); + + gotOne = queue->dispatch(c1); // c1 should get nothing + BOOST_CHECK( !gotOne ); + + queue->cancel(c1); + queue->cancel(c2); +} + QPID_AUTO_TEST_CASE(testMultiQueueLastNode){ TestMessageStoreOC testStore; @@ -702,9 +982,9 @@ QPID_AUTO_TEST_CASE(testMultiQueueLastNode){ args.setPersistLastNode(); Queue::shared_ptr queue1(new Queue("queue1", true, &testStore )); - queue1->configure(args); + queue1->create(args); Queue::shared_ptr queue2(new Queue("queue2", true, &testStore )); - queue2->configure(args); + queue2->create(args); intrusive_ptr<Message> msg1 = create_message("e", "A"); @@ -790,7 +1070,7 @@ not requeued to the store. Queue::shared_ptr queue1(new Queue("my-queue", true, &testStore)); intrusive_ptr<Message> received; - queue1->configure(args); + queue1->create(args); // check requeue 1 intrusive_ptr<Message> msg1 = create_message("e", "C"); @@ -870,28 +1150,40 @@ QPID_AUTO_TEST_CASE(testFlowToDiskBlocking){ intrusive_ptr<Message> msg02 = mkMsg(testStore, std::string(5, 'X')); // transient w/ content DeliverableMessage dmsg02(msg02); - BOOST_CHECK_THROW(sbtFanout1.route(dmsg02, "", 0), ResourceLimitExceededException); + { + ScopedSuppressLogging sl; // suppress expected error messages. + BOOST_CHECK_THROW(sbtFanout1.route(dmsg02, "", 0), ResourceLimitExceededException); + } msg02->tryReleaseContent(); BOOST_CHECK_EQUAL(msg02->isContentReleased(), false); BOOST_CHECK_EQUAL(1u, tq1->getMessageCount()); intrusive_ptr<Message> msg03 = mkMsg(testStore, std::string(5, 'X'), true); // durable w/ content DeliverableMessage dmsg03(msg03); - BOOST_CHECK_THROW(sbtFanout1.route(dmsg03, "", 0), ResourceLimitExceededException); + { + ScopedSuppressLogging sl; // suppress expected error messages. + BOOST_CHECK_THROW(sbtFanout1.route(dmsg03, "", 0), ResourceLimitExceededException); + } msg03->tryReleaseContent(); BOOST_CHECK_EQUAL(msg03->isContentReleased(), false); BOOST_CHECK_EQUAL(1u, tq1->getMessageCount()); intrusive_ptr<Message> msg04 = mkMsg(testStore); // transient no content DeliverableMessage dmsg04(msg04); - BOOST_CHECK_THROW(sbtFanout1.route(dmsg04, "", 0), ResourceLimitExceededException); + { + ScopedSuppressLogging sl; // suppress expected error messages. + BOOST_CHECK_THROW(sbtFanout1.route(dmsg04, "", 0), ResourceLimitExceededException); + } msg04->tryReleaseContent(); BOOST_CHECK_EQUAL(msg04->isContentReleased(), false); BOOST_CHECK_EQUAL(1u, tq1->getMessageCount()); intrusive_ptr<Message> msg05 = mkMsg(testStore, "", true); // durable no content DeliverableMessage dmsg05(msg05); - BOOST_CHECK_THROW(sbtFanout1.route(dmsg05, "", 0), ResourceLimitExceededException); + { + ScopedSuppressLogging sl; // suppress expected error messages. + BOOST_CHECK_THROW(sbtFanout1.route(dmsg05, "", 0), ResourceLimitExceededException); + } msg05->tryReleaseContent(); BOOST_CHECK_EQUAL(msg05->isContentReleased(), false); BOOST_CHECK_EQUAL(1u, tq1->getMessageCount()); diff --git a/cpp/src/tests/ReplicationTest.cpp b/cpp/src/tests/ReplicationTest.cpp index 7310a3fe20..1219a6b59e 100644 --- a/cpp/src/tests/ReplicationTest.cpp +++ b/cpp/src/tests/ReplicationTest.cpp @@ -74,7 +74,7 @@ QPID_AUTO_TEST_CASE(testReplicationExchange) { qpid::broker::Broker::Options brokerOpts(getBrokerOpts(list_of<string>("qpidd") ("--replication-exchange-name=qpid.replication"))); - ProxySessionFixture f(brokerOpts); + SessionFixture f(brokerOpts); std::string dataQ("queue-1"); diff --git a/cpp/src/tests/SessionState.cpp b/cpp/src/tests/SessionState.cpp index 157cabfb63..3be9bb0cbc 100644 --- a/cpp/src/tests/SessionState.cpp +++ b/cpp/src/tests/SessionState.cpp @@ -43,7 +43,7 @@ using namespace qpid::framing; // Apply f to [begin, end) and accumulate the result template <class Iter, class T, class F> T applyAccumulate(Iter begin, Iter end, T seed, const F& f) { - return std::accumulate(begin, end, seed, bind(std::plus<T>(), _1, bind(f, _2))); + return std::accumulate(begin, end, seed, boost::bind(std::plus<T>(), _1, boost::bind(f, _2))); } // Create a frame with a one-char string. @@ -105,8 +105,8 @@ size_t transferN(qpid::SessionState& s, string content) { char last = content[content.size()-1]; content.resize(content.size()-1); size += applyAccumulate(content.begin(), content.end(), 0, - bind(&send, ref(s), - bind(contentFrameChar, _1, false))); + boost::bind(&send, boost::ref(s), + boost::bind(contentFrameChar, _1, false))); size += send(s, contentFrameChar(last, true)); } return size; @@ -115,7 +115,7 @@ size_t transferN(qpid::SessionState& s, string content) { // Send multiple transfers with single-byte content. size_t transfers(qpid::SessionState& s, string content) { return applyAccumulate(content.begin(), content.end(), 0, - bind(transfer1Char, ref(s), _1)); + boost::bind(transfer1Char, boost::ref(s), _1)); } size_t contentFrameSize(size_t n=1) { return AMQFrame(( AMQContentBody())).encodedSize() + n; } diff --git a/cpp/src/tests/SocketProxy.h b/cpp/src/tests/SocketProxy.h deleted file mode 100644 index 0c6f39d62e..0000000000 --- a/cpp/src/tests/SocketProxy.h +++ /dev/null @@ -1,181 +0,0 @@ -#ifndef SOCKETPROXY_H -#define SOCKETPROXY_H - -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -#include "qpid/sys/IOHandle.h" -#ifdef _WIN32 -# include "qpid/sys/windows/IoHandlePrivate.h" - typedef SOCKET FdType; -#else -# include "qpid/sys/posix/PrivatePosix.h" - typedef int FdType; -#endif -#include "qpid/sys/Socket.h" -#include "qpid/sys/Runnable.h" -#include "qpid/sys/Thread.h" -#include "qpid/sys/Mutex.h" -#include "qpid/log/Statement.h" - -namespace qpid { -namespace tests { - -/** - * A simple socket proxy that forwards to another socket. - * Used between client & local broker to simulate network failures. - */ -class SocketProxy : private qpid::sys::Runnable -{ - // Need a Socket we can get the fd from - class LowSocket : public qpid::sys::Socket { - public: -#ifdef _WIN32 - FdType getFd() { return toSocketHandle(*this); } -#else - FdType getFd() { return toFd(impl); } -#endif - }; - - public: - /** Connect to connectPort on host, start a forwarding thread. - * Listen for connection on getPort(). - */ - SocketProxy(int connectPort, const std::string host="localhost") - : closed(false), joined(true), - port(listener.listen()), dropClient(), dropServer() - { - client.connect(host, connectPort); - joined = false; - thread = qpid::sys::Thread(static_cast<qpid::sys::Runnable*>(this)); - } - - ~SocketProxy() { close(); if (!joined) thread.join(); } - - /** Simulate a network disconnect. */ - void close() { - { - qpid::sys::Mutex::ScopedLock l(lock); - if (closed) { return; } - closed=true; - } - if (thread && thread != qpid::sys::Thread::current()) { - thread.join(); - joined = true; - } - client.close(); - } - - /** Simulate lost packets, drop data from client */ - void dropClientData(bool drop=true) { dropClient=drop; } - - /** Simulate lost packets, drop data from server */ - void dropServerData(bool drop=true) { dropServer=drop; } - - bool isClosed() const { - qpid::sys::Mutex::ScopedLock l(lock); - return closed; - } - - uint16_t getPort() const { return port; } - - private: - static void throwErrno(const std::string& msg) { - throw qpid::Exception(msg+":"+qpid::sys::strError(errno)); - } - static void throwIf(bool condition, const std::string& msg) { - if (condition) throw qpid::Exception(msg); - } - - void run() { - std::auto_ptr<LowSocket> server; - try { - fd_set socks; - FdType maxFd = listener.getFd(); - struct timeval tmo; - for (;;) { - FD_ZERO(&socks); - FD_SET(maxFd, &socks); - tmo.tv_sec = 0; - tmo.tv_usec = 500 * 1000; - if (select(maxFd+1, &socks, 0, 0, &tmo) == 0) { - qpid::sys::Mutex::ScopedLock l(lock); - throwIf(closed, "SocketProxy: Closed by close()"); - continue; - } - throwIf(!FD_ISSET(maxFd, &socks), "SocketProxy: Accept failed"); - break; // Accept ready... go to next step - } - server.reset(reinterpret_cast<LowSocket *>(listener.accept())); - maxFd = server->getFd(); - if (client.getFd() > maxFd) - maxFd = client.getFd(); - char buffer[1024]; - for (;;) { - FD_ZERO(&socks); - tmo.tv_sec = 0; - tmo.tv_usec = 500 * 1000; - FD_SET(client.getFd(), &socks); - FD_SET(server->getFd(), &socks); - if (select(maxFd+1, &socks, 0, 0, &tmo) == 0) { - qpid::sys::Mutex::ScopedLock l(lock); - throwIf(closed, "SocketProxy: Closed by close()"); - continue; - } - // Something is set; relay data as needed until something closes - if (FD_ISSET(server->getFd(), &socks)) { - int n = server->read(buffer, sizeof(buffer)); - throwIf(n <= 0, "SocketProxy: server disconnected"); - if (!dropServer) client.write(buffer, n); - } - if (FD_ISSET(client.getFd(), &socks)) { - int n = client.read(buffer, sizeof(buffer)); - throwIf(n <= 0, "SocketProxy: client disconnected"); - if (!dropServer) server->write(buffer, n); - } - if (!FD_ISSET(client.getFd(), &socks) && - !FD_ISSET(server->getFd(), &socks)) - throwIf(true, "SocketProxy: No handle ready"); - } - } - catch (const std::exception& e) { - QPID_LOG(debug, "SocketProxy::run exception: " << e.what()); - } - try { - if (server.get()) server->close(); - close(); - } - catch (const std::exception& e) { - QPID_LOG(debug, "SocketProxy::run exception in client/server close()" << e.what()); - } - } - - mutable qpid::sys::Mutex lock; - mutable bool closed; - bool joined; - LowSocket client, listener; - uint16_t port; - qpid::sys::Thread thread; - bool dropClient, dropServer; -}; - -}} // namespace qpid::tests - -#endif diff --git a/cpp/src/tests/TimerTest.cpp b/cpp/src/tests/TimerTest.cpp index 7df94164e0..6a0a196f4e 100644 --- a/cpp/src/tests/TimerTest.cpp +++ b/cpp/src/tests/TimerTest.cpp @@ -77,8 +77,10 @@ class TestTask : public TimerTask BOOST_CHECK(fired); BOOST_CHECK_EQUAL(expected_position, position); Duration actual(start, end); -#ifdef _WIN32 +#ifdef _MSC_VER uint64_t difference = _abs64(expected - actual); +#elif defined(_WIN32) + uint64_t difference = labs(expected - actual); #else uint64_t difference = abs(expected - actual); #endif diff --git a/cpp/src/tests/TxPublishTest.cpp b/cpp/src/tests/TxPublishTest.cpp index 6b44d95baa..152581e4ba 100644 --- a/cpp/src/tests/TxPublishTest.cpp +++ b/cpp/src/tests/TxPublishTest.cpp @@ -50,10 +50,9 @@ struct TxPublishTest TxPublishTest() : queue1(new Queue("queue1", false, &store, 0)), queue2(new Queue("queue2", false, &store, 0)), - msg(MessageUtils::createMessage("exchange", "routing_key", false, "id")), + msg(MessageUtils::createMessage("exchange", "routing_key", true)), op(msg) { - msg->getProperties<DeliveryProperties>()->setDeliveryMode(PERSISTENT); op.deliverTo(queue1); op.deliverTo(queue2); } @@ -74,7 +73,7 @@ QPID_AUTO_TEST_CASE(testPrepare) BOOST_CHECK_EQUAL(pmsg, t.store.enqueued[0].second); BOOST_CHECK_EQUAL(string("queue2"), t.store.enqueued[1].first); BOOST_CHECK_EQUAL(pmsg, t.store.enqueued[1].second); - BOOST_CHECK_EQUAL( true, ( boost::static_pointer_cast<PersistableMessage>(t.msg))->isEnqueueComplete()); + BOOST_CHECK_EQUAL( true, ( boost::static_pointer_cast<PersistableMessage>(t.msg))->isIngressComplete()); } QPID_AUTO_TEST_CASE(testCommit) @@ -87,7 +86,7 @@ QPID_AUTO_TEST_CASE(testCommit) BOOST_CHECK_EQUAL((uint32_t) 1, t.queue1->getMessageCount()); intrusive_ptr<Message> msg_dequeue = t.queue1->get().payload; - BOOST_CHECK_EQUAL( true, (boost::static_pointer_cast<PersistableMessage>(msg_dequeue))->isEnqueueComplete()); + BOOST_CHECK_EQUAL( true, (boost::static_pointer_cast<PersistableMessage>(msg_dequeue))->isIngressComplete()); BOOST_CHECK_EQUAL(t.msg, msg_dequeue); BOOST_CHECK_EQUAL((uint32_t) 1, t.queue2->getMessageCount()); diff --git a/cpp/src/tests/Url.cpp b/cpp/src/tests/Url.cpp index 234a62ee91..b30de682bc 100644 --- a/cpp/src/tests/Url.cpp +++ b/cpp/src/tests/Url.cpp @@ -60,6 +60,32 @@ QPID_AUTO_TEST_CASE(TestParseXyz) { BOOST_CHECK_EQUAL(Url("xyz:host").str(), "amqp:xyz:host:5672"); } +QPID_AUTO_TEST_CASE(TestParseTricky) { + BOOST_CHECK_EQUAL(Url("amqp").str(), "amqp:tcp:amqp:5672"); + BOOST_CHECK_EQUAL(Url("amqp:tcp").str(), "amqp:tcp:tcp:5672"); + // These are ambiguous parses and arguably not the best result + BOOST_CHECK_EQUAL(Url("amqp:876").str(), "amqp:tcp:876:5672"); + BOOST_CHECK_EQUAL(Url("tcp:567").str(), "amqp:tcp:567:5672"); +} + +QPID_AUTO_TEST_CASE(TestParseIPv6) { + Url u1("[::]"); + BOOST_CHECK_EQUAL(u1[0].host, "::"); + BOOST_CHECK_EQUAL(u1[0].port, 5672); + Url u2("[::1]"); + BOOST_CHECK_EQUAL(u2[0].host, "::1"); + BOOST_CHECK_EQUAL(u2[0].port, 5672); + Url u3("[::127.0.0.1]"); + BOOST_CHECK_EQUAL(u3[0].host, "::127.0.0.1"); + BOOST_CHECK_EQUAL(u3[0].port, 5672); + Url u4("[2002::222:68ff:fe0b:e61a]"); + BOOST_CHECK_EQUAL(u4[0].host, "2002::222:68ff:fe0b:e61a"); + BOOST_CHECK_EQUAL(u4[0].port, 5672); + Url u5("[2002::222:68ff:fe0b:e61a]:123"); + BOOST_CHECK_EQUAL(u5[0].host, "2002::222:68ff:fe0b:e61a"); + BOOST_CHECK_EQUAL(u5[0].port, 123); +} + QPID_AUTO_TEST_CASE(TestParseMultiAddress) { Url::addProtocol("xyz"); URL_CHECK_STR("amqp:tcp:host:0,xyz:foo:123,tcp:foo:0,xyz:bar:1"); diff --git a/cpp/src/tests/Variant.cpp b/cpp/src/tests/Variant.cpp index b4188f524b..40f1c0cf75 100644 --- a/cpp/src/tests/Variant.cpp +++ b/cpp/src/tests/Variant.cpp @@ -86,6 +86,64 @@ QPID_AUTO_TEST_CASE(testConversions) BOOST_CHECK_THROW(value.asBool(), InvalidConversion); } +QPID_AUTO_TEST_CASE(testConversionsFromString) +{ + Variant value; + value = "5"; + BOOST_CHECK_EQUAL(5, value.asInt16()); + BOOST_CHECK_EQUAL(5u, value.asUint16()); + + value = "-5"; + BOOST_CHECK_EQUAL(-5, value.asInt16()); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + + value = "18446744073709551615"; + BOOST_CHECK_EQUAL(18446744073709551615ull, value.asUint64()); + BOOST_CHECK_THROW(value.asInt64(), InvalidConversion); + + value = "9223372036854775808"; + BOOST_CHECK_EQUAL(9223372036854775808ull, value.asUint64()); + BOOST_CHECK_THROW(value.asInt64(), InvalidConversion); + + value = "-9223372036854775809"; + BOOST_CHECK_THROW(value.asUint64(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt64(), InvalidConversion); + + value = "2147483648"; + BOOST_CHECK_EQUAL(2147483648ul, value.asUint32()); + BOOST_CHECK_THROW(value.asInt32(), InvalidConversion); + + value = "-2147483649"; + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt32(), InvalidConversion); + + value = "32768"; + BOOST_CHECK_EQUAL(32768u, value.asUint16()); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + + value = "-32769"; + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + + value = "-2.5"; + BOOST_CHECK_EQUAL(-2.5, value.asFloat()); + + value = "-0.875432e10"; + BOOST_CHECK_EQUAL(-0.875432e10, value.asDouble()); + + value = "-0"; + BOOST_CHECK_EQUAL(0, value.asInt16()); + BOOST_CHECK_EQUAL(0u, value.asUint16()); + + value = "-000"; + BOOST_CHECK_EQUAL(0, value.asInt16()); + BOOST_CHECK_EQUAL(0u, value.asUint16()); + + value = "-0010"; + BOOST_CHECK_EQUAL(-10, value.asInt16()); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); +} + QPID_AUTO_TEST_CASE(testSizeConversionsUint) { Variant value; diff --git a/cpp/src/tests/XmlClientSessionTest.cpp b/cpp/src/tests/XmlClientSessionTest.cpp index b3b7f12b53..b94c35ece0 100644 --- a/cpp/src/tests/XmlClientSessionTest.cpp +++ b/cpp/src/tests/XmlClientSessionTest.cpp @@ -90,7 +90,7 @@ struct SimpleListener : public MessageListener } }; -struct ClientSessionFixture : public ProxySessionFixture +struct ClientSessionFixture : public SessionFixture { void declareSubscribe(const string& q="odd_blue", const string& dest="xml") diff --git a/cpp/src/tests/acl.py b/cpp/src/tests/acl.py index 2d6a5b489d..65d5242e51 100755 --- a/cpp/src/tests/acl.py +++ b/cpp/src/tests/acl.py @@ -26,10 +26,11 @@ from qpid.datatypes import uuid4 from qpid.testlib import TestBase010 from qmf.console import Session from qpid.datatypes import Message +import qpid.messaging class ACLFile: - def __init__(self): - self.f = open('data_dir/policy.acl','w'); + def __init__(self, policy='data_dir/policy.acl'): + self.f = open(policy,'w') def write(self,line): self.f.write(line) @@ -50,14 +51,24 @@ class ACLTests(TestBase010): acl = self.qmf.getObjects(_class="acl")[0] return acl.reloadACLFile() + def get_acl_file(self): + return ACLFile(self.config.defines.get("policy-file", "data_dir/policy.acl")) + def setUp(self): - aclf = ACLFile() + aclf = self.get_acl_file() aclf.write('acl allow all all\n') aclf.close() TestBase010.setUp(self) self.startQmf() self.reload_acl() - + + def tearDown(self): + aclf = self.get_acl_file() + aclf.write('acl allow all all\n') + aclf.close() + self.reload_acl() + TestBase010.tearDown(self) + #===================================== # ACL general tests #===================================== @@ -66,7 +77,7 @@ class ACLTests(TestBase010): """ Test the deny all mode """ - aclf = ACLFile() + aclf = self.get_acl_file() aclf.write('acl allow anonymous all all\n') aclf.write('acl allow bob@QPID create queue\n') aclf.write('acl deny all all') @@ -94,7 +105,7 @@ class ACLTests(TestBase010): """ Test the allow all mode """ - aclf = ACLFile() + aclf = self.get_acl_file() aclf.write('acl deny bob@QPID bind exchange\n') aclf.write('acl allow all all') aclf.close() @@ -126,7 +137,7 @@ class ACLTests(TestBase010): """ Test empty groups """ - aclf = ACLFile() + aclf = self.get_acl_file() aclf.write('acl group\n') aclf.write('acl group admins bob@QPID joe@QPID\n') aclf.write('acl allow all all') @@ -140,7 +151,7 @@ class ACLTests(TestBase010): """ Test illegal acl formats """ - aclf = ACLFile() + aclf = self.get_acl_file() aclf.write('acl group admins bob@QPID joe@QPID\n') aclf.write('acl allow all all') aclf.close() @@ -154,7 +165,7 @@ class ACLTests(TestBase010): Test illegal extension lines """ - aclf = ACLFile() + aclf = self.get_acl_file() aclf.write('group admins bob@QPID \n') aclf.write(' \ \n') aclf.write('joe@QPID \n') @@ -172,7 +183,7 @@ class ACLTests(TestBase010): """ Test proper extention lines """ - aclf = ACLFile() + aclf = self.get_acl_file() aclf.write('group test1 joe@EXAMPLE.com \\ \n') # should be allowed aclf.write(' jack@EXAMPLE.com \\ \n') # should be allowed aclf.write('jill@TEST.COM \\ \n') # should be allowed @@ -189,7 +200,7 @@ class ACLTests(TestBase010): Test a user defined without a realm Ex. group admin rajith """ - aclf = ACLFile() + aclf = self.get_acl_file() aclf.write('group admin bob\n') # shouldn't be allowed aclf.write('acl deny admin bind exchange\n') aclf.write('acl allow all all') @@ -204,7 +215,7 @@ class ACLTests(TestBase010): Test a user defined without a realm Ex. group admin rajith """ - aclf = ACLFile() + aclf = self.get_acl_file() aclf.write('group test1 joe@EXAMPLE.com\n') # should be allowed aclf.write('group test2 jack_123-jill@EXAMPLE.com\n') # should be allowed aclf.write('group test4 host/somemachine.example.com@EXAMPLE.COM\n') # should be allowed @@ -215,7 +226,7 @@ class ACLTests(TestBase010): if (result.text.find("ACL format error",0,len(result.text)) != -1): self.fail(result) - aclf = ACLFile() + aclf = self.get_acl_file() aclf.write('group test1 joe$H@EXAMPLE.com\n') # shouldn't be allowed aclf.write('acl allow all all') aclf.close() @@ -233,7 +244,7 @@ class ACLTests(TestBase010): Test illegal queue policy """ - aclf = ACLFile() + aclf = self.get_acl_file() aclf.write('acl deny bob@QPID create queue name=q2 exclusive=true policytype=ding\n') aclf.write('acl allow all all') aclf.close() @@ -249,7 +260,7 @@ class ACLTests(TestBase010): Test illegal queue policy """ - aclf = ACLFile() + aclf = self.get_acl_file() aclf.write('acl deny bob@QPID create queue name=q2 maxqueuesize=-1\n') aclf.write('acl allow all all') aclf.close() @@ -260,7 +271,7 @@ class ACLTests(TestBase010): if (result.text != expected): self.fail(result) - aclf = ACLFile() + aclf = self.get_acl_file() aclf.write('acl deny bob@QPID create queue name=q2 maxqueuesize=9223372036854775808\n') aclf.write('acl allow all all') aclf.close() @@ -277,7 +288,7 @@ class ACLTests(TestBase010): Test illegal queue policy """ - aclf = ACLFile() + aclf = self.get_acl_file() aclf.write('acl deny bob@QPID create queue name=q2 maxqueuecount=-1\n') aclf.write('acl allow all all') aclf.close() @@ -288,7 +299,7 @@ class ACLTests(TestBase010): if (result.text != expected): self.fail(result) - aclf = ACLFile() + aclf = self.get_acl_file() aclf.write('acl deny bob@QPID create queue name=q2 maxqueuecount=9223372036854775808\n') aclf.write('acl allow all all') aclf.close() @@ -308,7 +319,7 @@ class ACLTests(TestBase010): """ Test cases for queue acl in allow mode """ - aclf = ACLFile() + aclf = self.get_acl_file() aclf.write('acl deny bob@QPID create queue name=q1 durable=true passive=true\n') aclf.write('acl deny bob@QPID create queue name=q2 exclusive=true policytype=ring\n') aclf.write('acl deny bob@QPID access queue name=q3\n') @@ -411,7 +422,7 @@ class ACLTests(TestBase010): """ Test cases for queue acl in deny mode """ - aclf = ACLFile() + aclf = self.get_acl_file() aclf.write('acl allow bob@QPID create queue name=q1 durable=true passive=true\n') aclf.write('acl allow bob@QPID create queue name=q2 exclusive=true policytype=ring\n') aclf.write('acl allow bob@QPID access queue name=q3\n') @@ -534,7 +545,7 @@ class ACLTests(TestBase010): """ Test cases for exchange acl in allow mode """ - aclf = ACLFile() + aclf = self.get_acl_file() aclf.write('acl deny bob@QPID create exchange name=testEx durable=true passive=true\n') aclf.write('acl deny bob@QPID create exchange name=ex1 type=direct\n') aclf.write('acl deny bob@QPID access exchange name=myEx queuename=q1 routingkey=rk1.*\n') @@ -665,7 +676,7 @@ class ACLTests(TestBase010): """ Test cases for exchange acl in deny mode """ - aclf = ACLFile() + aclf = self.get_acl_file() aclf.write('acl allow bob@QPID create exchange name=myEx durable=true passive=false\n') aclf.write('acl allow bob@QPID bind exchange name=amq.topic queuename=bar routingkey=foo.*\n') aclf.write('acl allow bob@QPID unbind exchange name=amq.topic queuename=bar routingkey=foo.*\n') @@ -772,6 +783,52 @@ class ACLTests(TestBase010): if (403 == e.args[0].error_code): self.fail("ACL should allow exchange delete request for myEx"); + def test_create_and_delete_exchange_via_qmf(self): + """ + Test acl is enforced when creating/deleting via QMF + methods. Note that in order to be able to send the QMF methods + and receive the responses a significant amount of permissions + need to be enabled (TODO: can the set below be narrowed down + at all?) + """ + aclf = self.get_acl_file() + aclf.write('acl allow bob@QPID create exchange\n') + aclf.write('acl allow admin@QPID delete exchange\n') + aclf.write('acl allow all access exchange\n') + aclf.write('acl allow all bind exchange\n') + aclf.write('acl allow all create queue\n') + aclf.write('acl allow all access queue\n') + aclf.write('acl allow all delete queue\n') + aclf.write('acl allow all consume queue\n') + aclf.write('acl allow all access method\n') + aclf.write('acl deny all all') + aclf.close() + + result = self.reload_acl() + if (result.text.find("format error",0,len(result.text)) != -1): + self.fail(result) + + bob = BrokerAdmin(self.config.broker, "bob", "bob") + bob.create_exchange("my-exchange") #should pass + #cleanup by deleting exchange + try: + bob.delete_exchange("my-exchange") #should fail + self.fail("ACL should deny exchange delete request for my-exchange"); + except Exception, e: + self.assertEqual(7,e.args[0]["error_code"]) + assert e.args[0]["error_text"].find("unauthorized-access") == 0 + admin = BrokerAdmin(self.config.broker, "admin", "admin") + admin.delete_exchange("my-exchange") #should pass + + anonymous = BrokerAdmin(self.config.broker) + try: + anonymous.create_exchange("another-exchange") #should fail + self.fail("ACL should deny exchange create request for another-exchange"); + except Exception, e: + self.assertEqual(7,e.args[0]["error_code"]) + assert e.args[0]["error_text"].find("unauthorized-access") == 0 + + #===================================== # ACL consume tests #===================================== @@ -780,7 +837,7 @@ class ACLTests(TestBase010): """ Test cases for consume in allow mode """ - aclf = ACLFile() + aclf = self.get_acl_file() aclf.write('acl deny bob@QPID consume queue name=q1\n') aclf.write('acl deny bob@QPID consume queue name=q2\n') aclf.write('acl allow all all') @@ -826,7 +883,7 @@ class ACLTests(TestBase010): """ Test cases for consume in allow mode """ - aclf = ACLFile() + aclf = self.get_acl_file() aclf.write('acl allow bob@QPID consume queue name=q1\n') aclf.write('acl allow bob@QPID consume queue name=q2\n') aclf.write('acl allow bob@QPID create queue\n') @@ -872,7 +929,7 @@ class ACLTests(TestBase010): """ Test various publish acl """ - aclf = ACLFile() + aclf = self.get_acl_file() aclf.write('acl deny bob@QPID publish exchange name=amq.direct routingkey=rk1\n') aclf.write('acl deny bob@QPID publish exchange name=amq.topic\n') aclf.write('acl deny bob@QPID publish exchange name=myEx routingkey=rk2\n') @@ -921,7 +978,7 @@ class ACLTests(TestBase010): """ Test various publish acl """ - aclf = ACLFile() + aclf = self.get_acl_file() aclf.write('acl allow bob@QPID publish exchange name=amq.direct routingkey=rk1\n') aclf.write('acl allow bob@QPID publish exchange name=amq.topic\n') aclf.write('acl allow bob@QPID publish exchange name=myEx routingkey=rk2\n') @@ -972,3 +1029,113 @@ class ACLTests(TestBase010): except qpid.session.SessionException, e: if (403 == e.args[0].error_code): self.fail("ACL should allow message transfer to exchange amq.direct with routing key rk1"); + + #===================================== + # ACL broker configuration tests + #===================================== + + def test_broker_timestamp_config(self): + """ + Test ACL control of the broker timestamp configuration + """ + aclf = self.get_acl_file() + # enable lots of stuff to allow QMF to work + aclf.write('acl allow all create exchange\n') + aclf.write('acl allow all access exchange\n') + aclf.write('acl allow all bind exchange\n') + aclf.write('acl allow all publish exchange\n') + aclf.write('acl allow all create queue\n') + aclf.write('acl allow all access queue\n') + aclf.write('acl allow all delete queue\n') + aclf.write('acl allow all consume queue\n') + aclf.write('acl allow all access method\n') + # this should let bob access the timestamp configuration + aclf.write('acl allow bob@QPID access broker\n') + aclf.write('acl allow admin@QPID all all\n') + aclf.write('acl deny all all') + aclf.close() + + result = self.reload_acl() + if (result.text.find("format error",0,len(result.text)) != -1): + self.fail(result) + + ts = None + bob = BrokerAdmin(self.config.broker, "bob", "bob") + ts = bob.get_timestamp_cfg() #should work + bob.set_timestamp_cfg(ts); #should work + + obo = BrokerAdmin(self.config.broker, "obo", "obo") + try: + ts = obo.get_timestamp_cfg() #should fail + failed = False + except Exception, e: + failed = True + self.assertEqual(7,e.args[0]["error_code"]) + assert e.args[0]["error_text"].find("unauthorized-access") == 0 + assert(failed) + + try: + obo.set_timestamp_cfg(ts) #should fail + failed = False + except Exception, e: + failed = True + self.assertEqual(7,e.args[0]["error_code"]) + assert e.args[0]["error_text"].find("unauthorized-access") == 0 + assert(failed) + + admin = BrokerAdmin(self.config.broker, "admin", "admin") + ts = admin.get_timestamp_cfg() #should pass + admin.set_timestamp_cfg(ts) #should pass + + +class BrokerAdmin: + def __init__(self, broker, username=None, password=None): + self.connection = qpid.messaging.Connection(broker) + if username: + self.connection.username = username + self.connection.password = password + self.connection.sasl_mechanisms = "PLAIN" + self.connection.open() + self.session = self.connection.session() + self.sender = self.session.sender("qmf.default.direct/broker") + self.reply_to = "responses-#; {create:always}" + self.receiver = self.session.receiver(self.reply_to) + + def invoke(self, method, arguments): + content = { + "_object_id": {"_object_name": "org.apache.qpid.broker:broker:amqp-broker"}, + "_method_name": method, + "_arguments": arguments + } + request = qpid.messaging.Message(reply_to=self.reply_to, content=content) + request.properties["x-amqp-0-10.app-id"] = "qmf2" + request.properties["qmf.opcode"] = "_method_request" + self.sender.send(request) + response = self.receiver.fetch() + self.session.acknowledge() + if response.properties['x-amqp-0-10.app-id'] == 'qmf2': + if response.properties['qmf.opcode'] == '_method_response': + return response.content['_arguments'] + elif response.properties['qmf.opcode'] == '_exception': + raise Exception(response.content['_values']) + else: raise Exception("Invalid response received, unexpected opcode: %s" % response.properties['qmf.opcode']) + else: raise Exception("Invalid response received, not a qmfv2 method: %s" % response.properties['x-amqp-0-10.app-id']) + def create_exchange(self, name, exchange_type=None, options={}): + properties = options + if exchange_type: properties["exchange_type"] = exchange_type + self.invoke("create", {"type": "exchange", "name":name, "properties":properties}) + + def create_queue(self, name, properties={}): + self.invoke("create", {"type": "queue", "name":name, "properties":properties}) + + def delete_exchange(self, name): + self.invoke("delete", {"type": "exchange", "name":name}) + + def delete_queue(self, name): + self.invoke("delete", {"type": "queue", "name":name}) + + def get_timestamp_cfg(self): + return self.invoke("getTimestampConfig", {}) + + def set_timestamp_cfg(self, receive): + return self.invoke("getTimestampConfig", {"receive":receive}) diff --git a/cpp/src/tests/allhosts b/cpp/src/tests/allhosts index e43571aed4..4b4b943156 100755 --- a/cpp/src/tests/allhosts +++ b/cpp/src/tests/allhosts @@ -29,11 +29,12 @@ Options: -s SECONDS sleep between starting commands. -q don't print banner lines for each host. -o SUFFIX log output of each command to <host>.SUFFIX + -X passed to ssh - forward X connection. " exit 1 } -while getopts "tl:bs:dqo:" opt; do +while getopts "tl:bs:dqo:X" opt; do case $opt in l) SSHOPTS="-l$OPTARG $SSHOPTS" ;; t) SSHOPTS="-t $SSHOPTS" ;; @@ -42,6 +43,7 @@ while getopts "tl:bs:dqo:" opt; do s) SLEEP="sleep $OPTARG" ;; q) NOBANNER=1 ;; o) SUFFIX=$OPTARG ;; + X) SSHOPTS="-X $SSHOPTS" ;; *) usage;; esac done diff --git a/cpp/src/tests/brokertest.py b/cpp/src/tests/brokertest.py index 98f58ebfdd..16d7fb0b78 100644 --- a/cpp/src/tests/brokertest.py +++ b/cpp/src/tests/brokertest.py @@ -29,6 +29,7 @@ from unittest import TestCase from copy import copy from threading import Thread, Lock, Condition from logging import getLogger +import qmf.console log = getLogger("qpid.brokertest") @@ -61,24 +62,6 @@ def is_running(pid): class BadProcessStatus(Exception): pass -class ExceptionWrapper: - """Proxy object that adds a message to exceptions raised""" - def __init__(self, obj, msg): - self.obj = obj - self.msg = msg - - def __getattr__(self, name): - func = getattr(self.obj, name) - if type(func) != callable: - return func - return lambda *args, **kwargs: self._wrap(func, args, kwargs) - - def _wrap(self, func, args, kwargs): - try: - return func(*args, **kwargs) - except Exception, e: - raise Exception("%s: %s" %(self.msg, str(e))) - def error_line(filename, n=1): """Get the last n line(s) of filename for error messages""" result = [] @@ -88,7 +71,8 @@ def error_line(filename, n=1): for l in f: if len(result) == n: result.pop(0) result.append(" "+l) - finally: f.close() + finally: + f.close() except: return "" return ":\n" + "".join(result) @@ -96,111 +80,90 @@ def retry(function, timeout=10, delay=.01): """Call function until it returns True or timeout expires. Double the delay for each retry. Return True if function returns true, False if timeout expires.""" + deadline = time.time() + timeout while not function(): - if delay > timeout: delay = timeout + remaining = deadline - time.time() + if remaining <= 0: return False + delay = min(delay, remaining) time.sleep(delay) - timeout -= delay - if timeout <= 0: return False delay *= 2 return True +class AtomicCounter: + def __init__(self): + self.count = 0 + self.lock = Lock() + + def next(self): + self.lock.acquire(); + ret = self.count + self.count += 1 + self.lock.release(); + return ret + +_popen_id = AtomicCounter() # Popen identifier for use in output file names. + +# Constants for file descriptor arguments to Popen +FILE = "FILE" # Write to file named after process +PIPE = subprocess.PIPE + class Popen(subprocess.Popen): """ Can set and verify expectation of process status at end of test. Dumps command line, stdout, stderr to data dir for debugging. """ - class DrainThread(Thread): - """Thread to drain a file object and write the data to a file.""" - def __init__(self, infile, outname): - Thread.__init__(self) - self.infile, self.outname = infile, outname - self.outfile = None - - def run(self): - try: - for line in self.infile: - if self.outfile is None: - self.outfile = open(self.outname, "w") - self.outfile.write(line) - finally: - self.infile.close() - if self.outfile is not None: self.outfile.close() - - class OutStream(ExceptionWrapper): - """Wrapper for output streams, handles exceptions & draining output""" - def __init__(self, infile, outfile, msg): - ExceptionWrapper.__init__(self, infile, msg) - self.infile, self.outfile = infile, outfile - self.thread = None - - def drain(self): - if self.thread is None: - self.thread = Popen.DrainThread(self.infile, self.outfile) - self.thread.start() - - def outfile(self, ext): return "%s.%s" % (self.pname, ext) - - def __init__(self, cmd, expect=EXPECT_EXIT_OK, drain=True): - """Run cmd (should be a list of arguments) + def __init__(self, cmd, expect=EXPECT_EXIT_OK, stdin=None, stdout=FILE, stderr=FILE): + """Run cmd (should be a list of program and arguments) expect - if set verify expectation at end of test. - drain - if true (default) drain stdout/stderr to files. + stdout, stderr - can have the same values as for subprocess.Popen as well as + FILE (the default) which means write to a file named after the process. + stdin - like subprocess.Popen but defauts to PIPE """ self._clean = False self._clean_lock = Lock() assert find_exe(cmd[0]), "executable not found: "+cmd[0] if type(cmd) is type(""): cmd = [cmd] # Make it a list. self.cmd = [ str(x) for x in cmd ] - self.returncode = None self.expect = expect + self.id = _popen_id.next() + self.pname = "%s-%d" % (os.path.split(self.cmd[0])[1], self.id) + if stdout == FILE: stdout = open(self.outfile("out"), "w") + if stderr == FILE: stderr = open(self.outfile("err"), "w") try: - subprocess.Popen.__init__(self, self.cmd, 0, None, subprocess.PIPE, subprocess.PIPE, subprocess.PIPE, close_fds=True) - except ValueError: # Windows can't do close_fds - subprocess.Popen.__init__(self, self.cmd, 0, None, subprocess.PIPE, subprocess.PIPE, subprocess.PIPE) - self.pname = "%s-%d" % (os.path.split(self.cmd[0])[1], self.pid) - msg = "Process %s" % self.pname - self.stdin = ExceptionWrapper(self.stdin, msg) - self.stdout = Popen.OutStream(self.stdout, self.outfile("out"), msg) - self.stderr = Popen.OutStream(self.stderr, self.outfile("err"), msg) + subprocess.Popen.__init__(self, self.cmd, bufsize=0, executable=None, + stdin=stdin, stdout=stdout, stderr=stderr, + close_fds=True) + except ValueError: # Windows can't do close_fds + subprocess.Popen.__init__(self, self.cmd, bufsize=0, executable=None, + stdin=stdin, stdout=stdout, stderr=stderr) + f = open(self.outfile("cmd"), "w") - try: f.write(self.cmd_str()) + try: f.write("%s\n%d"%(self.cmd_str(), self.pid)) finally: f.close() log.debug("Started process %s: %s" % (self.pname, " ".join(self.cmd))) - if drain: self.drain() - def __str__(self): return "Popen<%s>"%(self.pname) + def __str__(self): return "Popen<%s>"%(self.pname) - def drain(self): - """Start threads to drain stdout/err""" - self.stdout.drain() - self.stderr.drain() - - def _cleanup(self): - """Close pipes to sub-process""" - self._clean_lock.acquire() - try: - if self._clean: return - self._clean = True - self.stdin.close() - self.drain() # Drain output pipes. - self.stdout.thread.join() # Drain thread closes pipe. - self.stderr.thread.join() - finally: self._clean_lock.release() + def outfile(self, ext): return "%s.%s" % (self.pname, ext) def unexpected(self,msg): err = error_line(self.outfile("err")) or error_line(self.outfile("out")) raise BadProcessStatus("%s %s%s" % (self.pname, msg, err)) - + def stop(self): # Clean up at end of test. try: if self.expect == EXPECT_UNKNOWN: try: self.kill() # Just make sure its dead except: pass elif self.expect == EXPECT_RUNNING: - try: - self.kill() - except: - self.unexpected("expected running, exit code %d" % self.wait()) + if self.poll() != None: + self.unexpected("expected running, exit code %d" % self.returncode) + else: + try: + self.kill() + except Exception,e: + self.unexpected("exception from kill: %s" % str(e)) else: retry(lambda: self.poll() is not None) if self.returncode is None: # Still haven't stopped @@ -212,40 +175,21 @@ class Popen(subprocess.Popen): self.unexpected("expected error") finally: self.wait() # Clean up the process. - + def communicate(self, input=None): - if input: - self.stdin.write(input) - self.stdin.close() - outerr = (self.stdout.read(), self.stderr.read()) - self.wait() - return outerr + ret = subprocess.Popen.communicate(self, input) + self.cleanup() + return ret - def is_running(self): - return self.poll() is None + def is_running(self): return self.poll() is None def assert_running(self): if not self.is_running(): self.unexpected("Exit code %d" % self.returncode) - def poll(self, _deadstate=None): # _deadstate required by base class in python 2.4 - if self.returncode is None: - # Pass _deadstate only if it has been set, there is no _deadstate - # parameter in Python 2.6 - if _deadstate is None: ret = subprocess.Popen.poll(self) - else: ret = subprocess.Popen.poll(self, _deadstate) - - if (ret != -1): - self.returncode = ret - self._cleanup() - return self.returncode - def wait(self): - if self.returncode is None: - self.drain() - try: self.returncode = subprocess.Popen.wait(self) - except OSError,e: raise OSError("Wait failed %s: %s"%(self.pname, e)) - self._cleanup() - return self.returncode + ret = subprocess.Popen.wait(self) + self._cleanup() + return ret def terminate(self): try: subprocess.Popen.terminate(self) @@ -254,7 +198,8 @@ class Popen(subprocess.Popen): os.kill( self.pid , signal.SIGTERM) except AttributeError: # no os.kill, using taskkill.. (Windows only) os.popen('TASKKILL /PID ' +str(self.pid) + ' /F') - + self._cleanup() + def kill(self): try: subprocess.Popen.kill(self) except AttributeError: # No terminate method @@ -262,6 +207,20 @@ class Popen(subprocess.Popen): os.kill( self.pid , signal.SIGKILL) except AttributeError: # no os.kill, using taskkill.. (Windows only) os.popen('TASKKILL /PID ' +str(self.pid) + ' /F') + self._cleanup() + + def _cleanup(self): + """Clean up after a dead process""" + self._clean_lock.acquire() + if not self._clean: + self._clean = True + try: self.stdin.close() + except: pass + try: self.stdout.close() + except: pass + try: self.stderr.close() + except: pass + self._clean_lock.release() def cmd_str(self): return " ".join([str(s) for s in self.cmd]) @@ -288,11 +247,11 @@ class Broker(Popen): while (os.path.exists(self.log)): self.log = "%s-%d.log" % (self.name, i) i += 1 - + def get_log(self): return os.path.abspath(self.log) - def __init__(self, test, args=[], name=None, expect=EXPECT_RUNNING, port=0, log_level=None, wait=None): + def __init__(self, test, args=[], name=None, expect=EXPECT_RUNNING, port=0, log_level=None, wait=None, show_cmd=False): """Start a broker daemon. name determines the data-dir and log file names.""" @@ -318,15 +277,20 @@ class Broker(Popen): cmd += ["--log-to-file", self.log] cmd += ["--log-to-stderr=no"] if log_level != None: - cmd += ["--log-enable=%s" % log_level] + cmd += ["--log-enable=%s" % log_level] self.datadir = self.name cmd += ["--data-dir", self.datadir] - Popen.__init__(self, cmd, expect, drain=False) + if show_cmd: print cmd + Popen.__init__(self, cmd, expect, stdout=PIPE) test.cleanup_stop(self) self._host = "127.0.0.1" log.debug("Started broker %s (%s, %s)" % (self.name, self.pname, self.log)) self._log_ready = False + def startQmf(self, handler=None): + self.qmf_session = qmf.console.Session(handler) + self.qmf_broker = self.qmf_session.addBroker("%s:%s" % (self.host(), self.port())) + def host(self): return self._host def port(self): @@ -357,7 +321,7 @@ class Broker(Popen): s = c.session(str(qpid.datatypes.uuid4())) s.queue_declare(queue=queue) c.close() - + def _prep_sender(self, queue, durable, xprops): s = queue + "; {create:always, node:{durable:" + str(durable) if xprops != None: s += ", x-declare:{" + xprops + "}" @@ -401,13 +365,14 @@ class Broker(Popen): def log_ready(self): """Return true if the log file exists and contains a broker ready message""" - if self._log_ready: return True - self._log_ready = find_in_file("notice Broker running", self.log) + if not self._log_ready: + self._log_ready = find_in_file("notice Broker running", self.log) + return self._log_ready def ready(self, **kwargs): """Wait till broker is ready to serve clients""" # First make sure the broker is listening by checking the log. - if not retry(self.log_ready, timeout=30): + if not retry(self.log_ready, timeout=60): raise Exception( "Timed out waiting for broker %s%s"%(self.name, error_line(self.log,5))) # Create a connection and a session. For a cluster broker this will @@ -416,23 +381,27 @@ class Broker(Popen): c = self.connect(**kwargs) try: c.session() finally: c.close() - except: raise RethrownException( - "Broker %s failed ready test%s"%(self.name,error_line(self.log, 5))) + except Exception,e: raise RethrownException( + "Broker %s not responding: (%s)%s"%(self.name,e,error_line(self.log, 5))) def store_state(self): - uuids = open(os.path.join(self.datadir, "cluster", "store.status")).readlines() + f = open(os.path.join(self.datadir, "cluster", "store.status")) + try: uuids = f.readlines() + finally: f.close() null_uuid="00000000-0000-0000-0000-000000000000\n" if len(uuids) < 2: return "unknown" # we looked while the file was being updated. if uuids[0] == null_uuid: return "empty" if uuids[1] == null_uuid: return "dirty" return "clean" - + class Cluster: """A cluster of brokers in a test.""" + # Client connection options for use in failover tests. + CONNECTION_OPTIONS = "reconnect:true,reconnect-timeout:10,reconnect-urls-replace:true" _cluster_count = 0 - def __init__(self, test, count=0, args=[], expect=EXPECT_RUNNING, wait=True): + def __init__(self, test, count=0, args=[], expect=EXPECT_RUNNING, wait=True, show_cmd=False): self.test = test self._brokers=[] self.name = "cluster%d" % Cluster._cluster_count @@ -443,16 +412,19 @@ class Cluster: self.args += [ "--log-enable=info+", "--log-enable=debug+:cluster"] assert BrokerTest.cluster_lib, "Cannot locate cluster plug-in" self.args += [ "--load-module", BrokerTest.cluster_lib ] - self.start_n(count, expect=expect, wait=wait) + self.start_n(count, expect=expect, wait=wait, show_cmd=show_cmd) - def start(self, name=None, expect=EXPECT_RUNNING, wait=True, args=[], port=0): + def start(self, name=None, expect=EXPECT_RUNNING, wait=True, args=[], port=0, show_cmd=False): """Add a broker to the cluster. Returns the index of the new broker.""" if not name: name="%s-%d" % (self.name, len(self._brokers)) - self._brokers.append(self.test.broker(self.args+args, name, expect, wait, port=port)) + self._brokers.append(self.test.broker(self.args+args, name, expect, wait, port=port, show_cmd=show_cmd)) return self._brokers[-1] - def start_n(self, count, expect=EXPECT_RUNNING, wait=True, args=[]): - for i in range(count): self.start(expect=expect, wait=wait, args=args) + def ready(self): + for b in self: b.ready() + + def start_n(self, count, expect=EXPECT_RUNNING, wait=True, args=[], show_cmd=False): + for i in range(count): self.start(expect=expect, wait=wait, args=args, show_cmd=show_cmd) # Behave like a list of brokers. def __len__(self): return len(self._brokers) @@ -481,7 +453,7 @@ class BrokerTest(TestCase): rootdir = os.getcwd() def configure(self, config): self.config=config - + def setUp(self): outdir = self.config.defines.get("OUTDIR") or "brokertest.tmp" self.dir = os.path.join(self.rootdir, outdir, self.id()) @@ -502,41 +474,50 @@ class BrokerTest(TestCase): """Call thing.stop at end of test""" self.stopem.append(stopable) - def popen(self, cmd, expect=EXPECT_EXIT_OK, drain=True): + def popen(self, cmd, expect=EXPECT_EXIT_OK, stdin=None, stdout=FILE, stderr=FILE): """Start a process that will be killed at end of test, in the test dir.""" os.chdir(self.dir) - p = Popen(cmd, expect, drain) + p = Popen(cmd, expect, stdin=stdin, stdout=stdout, stderr=stderr) self.cleanup_stop(p) return p - def broker(self, args=[], name=None, expect=EXPECT_RUNNING, wait=True, port=0, log_level=None): + def broker(self, args=[], name=None, expect=EXPECT_RUNNING, wait=True, port=0, log_level=None, show_cmd=False): """Create and return a broker ready for use""" - b = Broker(self, args=args, name=name, expect=expect, port=port, log_level=log_level) + b = Broker(self, args=args, name=name, expect=expect, port=port, log_level=log_level, show_cmd=show_cmd) if (wait): try: b.ready() except Exception, e: raise RethrownException("Failed to start broker %s(%s): %s" % (b.name, b.log, e)) return b - def cluster(self, count=0, args=[], expect=EXPECT_RUNNING, wait=True): + def cluster(self, count=0, args=[], expect=EXPECT_RUNNING, wait=True, show_cmd=False): """Create and return a cluster ready for use""" - cluster = Cluster(self, count, args, expect=expect, wait=wait) + cluster = Cluster(self, count, args, expect=expect, wait=wait, show_cmd=show_cmd) return cluster + def browse(self, session, queue, timeout=0): + """Return a list with the contents of each message on queue.""" + r = session.receiver("%s;{mode:browse}"%(queue)) + r.capacity = 100 + try: + contents = [] + try: + while True: contents.append(r.fetch(timeout=timeout).content) + except messaging.Empty: pass + finally: r.close() + return contents + def assert_browse(self, session, queue, expect_contents, timeout=0): """Assert that the contents of messages on queue (as retrieved using session and timeout) exactly match the strings in expect_contents""" - - r = session.receiver("%s;{mode:browse}"%(queue)) - actual_contents = [] - try: - for c in expect_contents: actual_contents.append(r.fetch(timeout=timeout).content) - while True: actual_contents.append(r.fetch(timeout=0).content) # Check for extra messages. - except messaging.Empty: pass - r.close() + actual_contents = self.browse(session, queue, timeout) self.assertEqual(expect_contents, actual_contents) +def join(thread, timeout=10): + thread.join(timeout) + if thread.isAlive(): raise Exception("Timed out joining thread %s"%thread) + class RethrownException(Exception): """Captures the stack trace of the current exception to be thrown later""" def __init__(self, msg=""): @@ -554,15 +535,16 @@ class StoppableThread(Thread): def stop(self): self.stopped = True - self.join() + join(self) if self.error: raise self.error - + class NumberedSender(Thread): """ Thread to run a sender client and send numbered messages until stopped. """ - def __init__(self, broker, max_depth=None, queue="test-queue"): + def __init__(self, broker, max_depth=None, queue="test-queue", + connection_options=Cluster.CONNECTION_OPTIONS): """ max_depth: enable flow control, ensure sent - received <= max_depth. Requires self.notify_received(n) to be called each time messages are received. @@ -573,9 +555,11 @@ class NumberedSender(Thread): "--broker", "localhost:%s"%broker.port(), "--address", "%s;{create:always}"%queue, "--failover-updates", + "--connection-options", "{%s}"%(connection_options), "--content-stdin" ], - expect=EXPECT_RUNNING) + expect=EXPECT_RUNNING, + stdin=PIPE) self.condition = Condition() self.max = max_depth self.received = 0 @@ -590,6 +574,7 @@ class NumberedSender(Thread): try: self.sent = 0 while not self.stopped: + self.sender.assert_running() if self.max: self.condition.acquire() while not self.stopped and self.sent - self.received > self.max: @@ -612,16 +597,17 @@ class NumberedSender(Thread): self.stopped = True self.condition.notify() finally: self.condition.release() - self.join() + join(self) self.write_message(-1) # end-of-messages marker. if self.error: raise self.error - + class NumberedReceiver(Thread): """ Thread to run a receiver client and verify it receives sequentially numbered messages. """ - def __init__(self, broker, sender = None, queue="test-queue"): + def __init__(self, broker, sender = None, queue="test-queue", + connection_options=Cluster.CONNECTION_OPTIONS): """ sender: enable flow control. Call sender.received(n) for each message received. """ @@ -632,22 +618,24 @@ class NumberedReceiver(Thread): "--broker", "localhost:%s"%broker.port(), "--address", "%s;{create:always}"%queue, "--failover-updates", + "--connection-options", "{%s}"%(connection_options), "--forever" ], expect=EXPECT_RUNNING, - drain=False) + stdout=PIPE) self.lock = Lock() self.error = None self.sender = sender + self.received = 0 def read_message(self): return int(self.receiver.stdout.readline()) - + def run(self): try: - self.received = 0 m = self.read_message() while m != -1: + self.receiver.assert_running() assert(m <= self.received) # Check for missing messages if (m == self.received): # Ignore duplicates self.received += 1 @@ -659,7 +647,7 @@ class NumberedReceiver(Thread): def stop(self): """Returns when termination message is received""" - self.join() + join(self) if self.error: raise self.error class ErrorGenerator(StoppableThread): @@ -674,7 +662,7 @@ class ErrorGenerator(StoppableThread): self.broker=broker broker.test.cleanup_stop(self) self.start() - + def run(self): c = self.broker.connect_old() try: diff --git a/cpp/src/tests/cli_tests.py b/cpp/src/tests/cli_tests.py index deef03279d..6c75927461 100755 --- a/cpp/src/tests/cli_tests.py +++ b/cpp/src/tests/cli_tests.py @@ -365,6 +365,26 @@ class CliTests(TestBase010): self.assertEqual(queue._altExchange_.name, altName) self.assertEqual(found, True) + def test_qpid_config_list_queues_arguments(self): + """ + Test to verify that when the type of a policy limit is + actually a string (though still a valid value), it does not + upset qpid-config + """ + self.startQmf(); + qmf = self.qmf + + names = ["queue_capacity%s" % (i) for i in range(1, 6)] + for name in names: + self.session.queue_declare(queue=name, exclusive=True, + arguments={'qpid.max_count' : str(i), 'qpid.max_size': '100'}) + + output = os.popen(self.qpid_config_command(" queues")).readlines() + queues = [line.split()[0] for line in output[2:len(output)]] #ignore first two lines (header) + + for name in names: + assert name in queues, "%s not in %s" % (name, queues) + def test_qpid_route(self): self.startQmf(); qmf = self.qmf @@ -405,7 +425,7 @@ class CliTests(TestBase010): qmf = self.qmf ret = self.qpid_route_api("dynamic add " - + " --sasl-mechanism PLAIN " + + " --client-sasl-mechanism PLAIN " + "guest/guest@localhost:"+str(self.broker.port) + " " + str(self.remote_host())+":"+str(self.remote_port()) + " " +"amq.direct") @@ -424,7 +444,7 @@ class CliTests(TestBase010): qmf = self.qmf ret = self.qpid_route_api("dynamic add " - + " --sasl-mechanism PLAIN " + + " --client-sasl-mechanism PLAIN " + "localhost:"+str(self.broker.port) + " " + str(self.remote_host())+":"+str(self.remote_port()) + " " +"amq.direct") diff --git a/cpp/src/tests/cluster_python_tests_failing.txt b/cpp/src/tests/cluster_python_tests_failing.txt index 7ba8089946..f8639d7b59 100644 --- a/cpp/src/tests/cluster_python_tests_failing.txt +++ b/cpp/src/tests/cluster_python_tests_failing.txt @@ -1,32 +1,4 @@ qpid_tests.broker_0_10.management.ManagementTest.test_purge_queue qpid_tests.broker_0_10.management.ManagementTest.test_connection_close -qpid_tests.broker_0_10.dtx.DtxTests.test_bad_resume -qpid_tests.broker_0_10.dtx.DtxTests.test_commit_unknown -qpid_tests.broker_0_10.dtx.DtxTests.test_end -qpid_tests.broker_0_10.dtx.DtxTests.test_end_suspend_and_fail -qpid_tests.broker_0_10.dtx.DtxTests.test_end_unknown_xid -qpid_tests.broker_0_10.dtx.DtxTests.test_forget_xid_on_completion -qpid_tests.broker_0_10.dtx.DtxTests.test_get_timeout -qpid_tests.broker_0_10.dtx.DtxTests.test_get_timeout_unknown -qpid_tests.broker_0_10.dtx.DtxTests.test_implicit_end -qpid_tests.broker_0_10.dtx.DtxTests.test_invalid_commit_not_ended -qpid_tests.broker_0_10.dtx.DtxTests.test_invalid_commit_one_phase_false -qpid_tests.broker_0_10.dtx.DtxTests.test_invalid_commit_one_phase_true -qpid_tests.broker_0_10.dtx.DtxTests.test_invalid_prepare_not_ended -qpid_tests.broker_0_10.dtx.DtxTests.test_invalid_rollback_not_ended -qpid_tests.broker_0_10.dtx.DtxTests.test_prepare_unknown -qpid_tests.broker_0_10.dtx.DtxTests.test_recover -qpid_tests.broker_0_10.dtx.DtxTests.test_rollback_unknown -qpid_tests.broker_0_10.dtx.DtxTests.test_select_required -qpid_tests.broker_0_10.dtx.DtxTests.test_set_timeout -qpid_tests.broker_0_10.dtx.DtxTests.test_simple_commit -qpid_tests.broker_0_10.dtx.DtxTests.test_simple_prepare_commit -qpid_tests.broker_0_10.dtx.DtxTests.test_simple_prepare_rollback -qpid_tests.broker_0_10.dtx.DtxTests.test_simple_rollback -qpid_tests.broker_0_10.dtx.DtxTests.test_start_already_known -qpid_tests.broker_0_10.dtx.DtxTests.test_start_join -qpid_tests.broker_0_10.dtx.DtxTests.test_start_join_and_resume -qpid_tests.broker_0_10.dtx.DtxTests.test_suspend_resume -qpid_tests.broker_0_10.dtx.DtxTests.test_suspend_start_end_resume qpid_tests.broker_0_10.message.MessageTests.test_ttl qpid_tests.broker_0_10.management.ManagementTest.test_broker_connectivity_oldAPI diff --git a/cpp/src/tests/cluster_test_logs.py b/cpp/src/tests/cluster_test_logs.py index 1fa9014d11..3c7e8e8020 100755 --- a/cpp/src/tests/cluster_test_logs.py +++ b/cpp/src/tests/cluster_test_logs.py @@ -53,16 +53,19 @@ def filter_log(log): 'stall for update|unstall, ignore update|cancelled offer .* unstall', 'caught up', 'active for links|Passivating links|Activating links', + 'info Connecting: .*', # UpdateClient connection 'info Connection.* connected to', # UpdateClient connection - 'warning Connection [\d+ [0-9.:]+] closed', # UpdateClient connection + 'warning Connection \\[[-0-9.: ]+\\] closed', # UpdateClient connection 'warning Broker closed connection: 200, OK', 'task late', 'task overran', 'warning CLOSING .* unsent data', 'Inter-broker link ', - 'Running in a cluster, marking store' + 'Running in a cluster, marking store', + 'debug Sending keepalive signal to watchdog', # Watchdog timer thread + 'last broker standing joined by 1 replicas, updating queue policies.', + 'Connection .* timed out: closing' # heartbeat connection close ]) - skip_re = re.compile(skip) # Regex to match a UUID uuid='\w\w\w\w\w\w\w\w-\w\w\w\w-\w\w\w\w-\w\w\w\w-\w\w\w\w\w\w\w\w\w\w\w\w' # Substitutions to remove expected differences @@ -80,6 +83,13 @@ def filter_log(log): (r' map={.*_object_name:([^,}]*)[,}].*', r' \1'), # V2 map - just keep name (r'\d+-\d+-\d+--\d+', 'X-X-X--X'), # V1 Object IDs ] + # Substitutions to mask known issue: durable test shows inconsistent "changed stats for com.redhat.rhm.store:journal" messages. + skip += '|Changed V[12] statistics com.redhat.rhm.store:journal' + subs += [(r'to=console.obj.1.0.com.redhat.rhm.store.journal props=\d+ stats=\d+', + 'to=console.obj.1.0.com.redhat.rhm.store.journal props=NN stats=NN')] + + skip_re = re.compile(skip) + subs = [(re.compile(pattern), subst) for pattern, subst in subs] for l in open(log): if skip_re.search(l): continue for pattern,subst in subs: l = re.sub(pattern,subst,l) diff --git a/cpp/src/tests/cluster_tests.py b/cpp/src/tests/cluster_tests.py index cbad4010b4..0e80e06d34 100755 --- a/cpp/src/tests/cluster_tests.py +++ b/cpp/src/tests/cluster_tests.py @@ -18,12 +18,13 @@ # under the License. # -import os, signal, sys, time, imp, re, subprocess, glob, cluster_test_logs +import os, signal, sys, time, imp, re, subprocess, glob, random, logging +import cluster_test_logs from qpid import datatypes, messaging from brokertest import * from qpid.harness import Skipped -from qpid.messaging import Message, Empty -from threading import Thread, Lock +from qpid.messaging import Message, Empty, Disposition, REJECTED, util +from threading import Thread, Lock, Condition from logging import getLogger from itertools import chain from tempfile import NamedTemporaryFile @@ -96,9 +97,15 @@ class ShortTests(BrokerTest): destination="amq.direct", message=qpid.datatypes.Message(props, "content")) + # Try message with TTL and differnet headers/properties + cluster[0].send_message("q", Message(durable=True, ttl=100000)) + cluster[0].send_message("q", Message(durable=True, properties={}, ttl=100000)) + cluster[0].send_message("q", Message(durable=True, properties={"x":10}, ttl=100000)) + # Now update a new member and compare their dumps. cluster.start(args=["--test-store-dump", "updatee.dump"]) assert readfile("direct.dump") == readfile("updatee.dump") + os.remove("direct.dump") os.remove("updatee.dump") @@ -108,19 +115,22 @@ class ShortTests(BrokerTest): acl=os.path.join(os.getcwd(), "policy.acl") aclf=file(acl,"w") aclf.write(""" -acl deny zag@QPID create queue -acl allow all all +acl allow zig@QPID all all +acl deny all all """) aclf.close() - cluster = self.cluster(2, args=["--auth", "yes", + cluster = self.cluster(1, args=["--auth", "yes", "--sasl-config", sasl_config, "--load-module", os.getenv("ACL_LIB"), "--acl-file", acl]) # Valid user/password, ensure queue is created. c = cluster[0].connect(username="zig", password="zig") - c.session().sender("ziggy;{create:always}") + c.session().sender("ziggy;{create:always,node:{x-declare:{exclusive:true}}}") c.close() + cluster.start() # Start second node. + + # Check queue is created on second node. c = cluster[1].connect(username="zig", password="zig") c.session().receiver("ziggy;{assert:always}") c.close() @@ -149,7 +159,7 @@ acl allow all all self.fail("Expected exception") except messaging.exceptions.UnauthorizedAccess: pass # make sure the queue was not created at the other node. - c = cluster[0].connect(username="zag", password="zag") + c = cluster[1].connect(username="zig", password="zig") try: s = c.session() s.sender("zaggy;{assert:always}") @@ -157,6 +167,35 @@ acl allow all all self.fail("Expected exception") except messaging.exceptions.NotFound: pass + def test_sasl_join(self): + """Verify SASL authentication between brokers when joining a cluster.""" + sasl_config=os.path.join(self.rootdir, "sasl_config") + # Test with a valid username/password + cluster = self.cluster(1, args=["--auth", "yes", + "--sasl-config", sasl_config, + "--load-module", os.getenv("ACL_LIB"), + "--cluster-username=zig", + "--cluster-password=zig", + "--cluster-mechanism=PLAIN" + ]) + cluster.start() + cluster.ready() + c = cluster[1].connect(username="zag", password="zag") + + # Test with an invalid username/password + cluster = self.cluster(1, args=["--auth", "yes", + "--sasl-config", sasl_config, + "--load-module", os.getenv("ACL_LIB"), + "--cluster-username=x", + "--cluster-password=y", + "--cluster-mechanism=PLAIN" + ]) + try: + cluster.start(expect=EXPECT_EXIT_OK) + cluster[1].ready() + self.fail("Expected exception") + except: pass + def test_user_id_update(self): """Ensure that user-id of an open session is updated to new cluster members""" sasl_config=os.path.join(self.rootdir, "sasl_config") @@ -246,25 +285,6 @@ acl allow all all session1 = cluster[1].connect().session() for q in queues: self.assert_browse(session1, "q1", ["foo"]) - def test_dr_no_message(self): - """Regression test for https://bugzilla.redhat.com/show_bug.cgi?id=655141 - Joining broker crashes with 'error deliveryRecord no update message' - """ - - cluster = self.cluster(1) - session0 = cluster[0].connect().session() - s = session0.sender("q1;{create:always}") - s.send(Message("a", ttl=0.05), sync=False) - s.send(Message("b", ttl=0.05), sync=False) - r1 = session0.receiver("q1") - self.assertEqual("a", r1.fetch(timeout=0).content) - r2 = session0.receiver("q1;{mode:browse}") - self.assertEqual("b", r2.fetch(timeout=0).content) - # Leave messages un-acknowledged, let the expire, then start new broker. - time.sleep(.1) - cluster.start() - self.assertRaises(Empty, cluster[1].connect().session().receiver("q1").fetch,0) - def test_route_update(self): """Regression test for https://issues.apache.org/jira/browse/QPID-2982 Links and bridges associated with routes were not replicated on update. @@ -272,6 +292,7 @@ acl allow all all client was attached. """ args=["--mgmt-pub-interval=1","--log-enable=trace+:management"] + # First broker will be killed. cluster0 = self.cluster(1, args=args) cluster1 = self.cluster(1, args=args) assert 0 == subprocess.call( @@ -301,9 +322,695 @@ acl allow all all qpid_tool.wait() scanner.join() assert scanner.found + # Regression test for https://issues.apache.org/jira/browse/QPID-3235 + # Inconsistent stats when changing elder. + + # Force a change of elder + cluster0.start() + cluster0[0].expect=EXPECT_EXIT_FAIL # About to die. + cluster0[0].kill() + time.sleep(2) # Allow a management interval to pass. # Verify logs are consistent cluster_test_logs.verify_logs() + def test_redelivered(self): + """Verify that redelivered flag is set correctly on replayed messages""" + cluster = self.cluster(2, expect=EXPECT_EXIT_FAIL) + url = "amqp:tcp:%s,tcp:%s" % (cluster[0].host_port(), cluster[1].host_port()) + queue = "my-queue" + cluster[0].declare_queue(queue) + self.sender = self.popen( + ["qpid-send", + "--broker", url, + "--address", queue, + "--sequence=true", + "--send-eos=1", + "--messages=100000", + "--connection-options={%s}"%(Cluster.CONNECTION_OPTIONS) + ]) + self.receiver = self.popen( + ["qpid-receive", + "--broker", url, + "--address", queue, + "--ignore-duplicates", + "--check-redelivered", + "--connection-options={%s}"%(Cluster.CONNECTION_OPTIONS), + "--forever" + ]) + time.sleep(1)#give sender enough time to have some messages to replay + cluster[0].kill() + self.sender.wait() + self.receiver.wait() + cluster[1].kill() + + class BlockedSend(Thread): + """Send a message, send is expected to block. + Verify that it does block (for a given timeout), then allow + waiting till it unblocks when it is expected to do so.""" + def __init__(self, sender, msg): + self.sender, self.msg = sender, msg + self.blocked = True + self.condition = Condition() + self.timeout = 0.1 # Time to wait for expected results. + Thread.__init__(self) + def run(self): + try: + self.sender.send(self.msg, sync=True) + self.condition.acquire() + try: + self.blocked = False + self.condition.notify() + finally: self.condition.release() + except Exception,e: print "BlockedSend exception: %s"%e + def start(self): + Thread.start(self) + time.sleep(self.timeout) + assert self.blocked # Expected to block + def assert_blocked(self): assert self.blocked + def wait(self): # Now expecting to unblock + self.condition.acquire() + try: + while self.blocked: + self.condition.wait(self.timeout) + if self.blocked: raise Exception("Timed out waiting for send to unblock") + finally: self.condition.release() + self.join() + + def queue_flowlimit_test(self, brokers): + """Verify that the queue's flowlimit configuration and state are + correctly replicated. + The brokers argument allows this test to run on single broker, + cluster of 2 pre-startd brokers or cluster where second broker + starts after queue is in flow control. + """ + # configure a queue with a specific flow limit on first broker + ssn0 = brokers.first().connect().session() + s0 = ssn0.sender("flq; {create:always, node:{type:queue, x-declare:{arguments:{'qpid.flow_stop_count':5, 'qpid.flow_resume_count':3}}}}") + brokers.first().startQmf() + q1 = [q for q in brokers.first().qmf_session.getObjects(_class="queue") if q.name == "flq"][0] + oid = q1.getObjectId() + self.assertEqual(q1.name, "flq") + self.assertEqual(q1.arguments, {u'qpid.flow_stop_count': 5L, u'qpid.flow_resume_count': 3L}) + assert not q1.flowStopped + self.assertEqual(q1.flowStoppedCount, 0) + + # fill the queue on one broker until flow control is active + for x in range(5): s0.send(Message(str(x))) + sender = ShortTests.BlockedSend(s0, Message(str(6))) + sender.start() # Tests that sender does block + # Verify the broker queue goes into a flowStopped state + deadline = time.time() + 1 + while not q1.flowStopped and time.time() < deadline: q1.update() + assert q1.flowStopped + self.assertEqual(q1.flowStoppedCount, 1) + sender.assert_blocked() # Still blocked + + # Now verify the both brokers in cluster have same configuration + brokers.second().startQmf() + qs = brokers.second().qmf_session.getObjects(_objectId=oid) + self.assertEqual(len(qs), 1) + q2 = qs[0] + self.assertEqual(q2.name, "flq") + self.assertEqual(q2.arguments, {u'qpid.flow_stop_count': 5L, u'qpid.flow_resume_count': 3L}) + assert q2.flowStopped + self.assertEqual(q2.flowStoppedCount, 1) + + # now drain the queue using a session to the other broker + ssn1 = brokers.second().connect().session() + r1 = ssn1.receiver("flq", capacity=6) + for x in range(4): + r1.fetch(timeout=0) + ssn1.acknowledge() + sender.wait() # Verify no longer blocked. + + # and re-verify state of queue on both brokers + q1.update() + assert not q1.flowStopped + q2.update() + assert not q2.flowStopped + + ssn0.connection.close() + ssn1.connection.close() + cluster_test_logs.verify_logs() + + def test_queue_flowlimit(self): + """Test flow limits on a standalone broker""" + broker = self.broker() + class Brokers: + def first(self): return broker + def second(self): return broker + self.queue_flowlimit_test(Brokers()) + + def test_queue_flowlimit_cluster(self): + cluster = self.cluster(2) + class Brokers: + def first(self): return cluster[0] + def second(self): return cluster[1] + self.queue_flowlimit_test(Brokers()) + + def test_queue_flowlimit_cluster_join(self): + cluster = self.cluster(1) + class Brokers: + def first(self): return cluster[0] + def second(self): + if len(cluster) == 1: cluster.start() + return cluster[1] + self.queue_flowlimit_test(Brokers()) + + def test_queue_flowlimit_replicate(self): + """ Verify that a queue which is in flow control BUT has drained BELOW + the flow control 'stop' threshold, is correctly replicated when a new + broker is added to the cluster. + """ + + class AsyncSender(Thread): + """Send a fixed number of msgs from a sender in a separate thread + so it may block without blocking the test. + """ + def __init__(self, broker, address, count=1, size=4): + Thread.__init__(self) + self.daemon = True + self.broker = broker + self.queue = address + self.count = count + self.size = size + self.done = False + + def run(self): + self.sender = subprocess.Popen(["qpid-send", + "--capacity=1", + "--content-size=%s" % self.size, + "--messages=%s" % self.count, + "--failover-updates", + "--connection-options={%s}"%(Cluster.CONNECTION_OPTIONS), + "--address=%s" % self.queue, + "--broker=%s" % self.broker.host_port()]) + self.sender.wait() + self.done = True + + cluster = self.cluster(2) + # create a queue with rather draconian flow control settings + ssn0 = cluster[0].connect().session() + s0 = ssn0.sender("flq; {create:always, node:{type:queue, x-declare:{arguments:{'qpid.flow_stop_count':100, 'qpid.flow_resume_count':20}}}}") + + # fire off the sending thread to broker[0], and wait until the queue + # hits flow control on broker[1] + sender = AsyncSender(cluster[0], "flq", count=110); + sender.start(); + + cluster[1].startQmf() + q_obj = [q for q in cluster[1].qmf_session.getObjects(_class="queue") if q.name == "flq"][0] + deadline = time.time() + 10 + while not q_obj.flowStopped and time.time() < deadline: + q_obj.update() + assert q_obj.flowStopped + assert not sender.done + assert q_obj.msgDepth < 110 + + # Now drain enough messages on broker[1] to drop below the flow stop + # threshold, but not relieve flow control... + receiver = subprocess.Popen(["qpid-receive", + "--messages=15", + "--timeout=1", + "--print-content=no", + "--failover-updates", + "--connection-options={%s}"%(Cluster.CONNECTION_OPTIONS), + "--ack-frequency=1", + "--address=flq", + "--broker=%s" % cluster[1].host_port()]) + receiver.wait() + q_obj.update() + assert q_obj.flowStopped + assert not sender.done + current_depth = q_obj.msgDepth + + # add a new broker to the cluster, and verify that the queue is in flow + # control on that broker + cluster.start() + cluster[2].startQmf() + q_obj = [q for q in cluster[2].qmf_session.getObjects(_class="queue") if q.name == "flq"][0] + assert q_obj.flowStopped + assert q_obj.msgDepth == current_depth + + # now drain the queue on broker[2], and verify that the sender becomes + # unblocked + receiver = subprocess.Popen(["qpid-receive", + "--messages=95", + "--timeout=1", + "--print-content=no", + "--failover-updates", + "--connection-options={%s}"%(Cluster.CONNECTION_OPTIONS), + "--ack-frequency=1", + "--address=flq", + "--broker=%s" % cluster[2].host_port()]) + receiver.wait() + q_obj.update() + assert not q_obj.flowStopped + self.assertEqual(q_obj.msgDepth, 0) + + # verify that the sender has become unblocked + sender.join(timeout=5) + assert not sender.isAlive() + assert sender.done + + def test_blocked_queue_delete(self): + """Verify that producers which are blocked on a queue due to flow + control are unblocked when that queue is deleted. + """ + + cluster = self.cluster(2) + cluster[0].startQmf() + cluster[1].startQmf() + + # configure a queue with a specific flow limit on first broker + ssn0 = cluster[0].connect().session() + s0 = ssn0.sender("flq; {create:always, node:{type:queue, x-declare:{arguments:{'qpid.flow_stop_count':5, 'qpid.flow_resume_count':3}}}}") + q1 = [q for q in cluster[0].qmf_session.getObjects(_class="queue") if q.name == "flq"][0] + oid = q1.getObjectId() + self.assertEqual(q1.name, "flq") + self.assertEqual(q1.arguments, {u'qpid.flow_stop_count': 5L, u'qpid.flow_resume_count': 3L}) + assert not q1.flowStopped + self.assertEqual(q1.flowStoppedCount, 0) + + # fill the queue on one broker until flow control is active + for x in range(5): s0.send(Message(str(x))) + sender = ShortTests.BlockedSend(s0, Message(str(6))) + sender.start() # Tests that sender does block + # Verify the broker queue goes into a flowStopped state + deadline = time.time() + 1 + while not q1.flowStopped and time.time() < deadline: q1.update() + assert q1.flowStopped + self.assertEqual(q1.flowStoppedCount, 1) + sender.assert_blocked() # Still blocked + + # Now verify the both brokers in cluster have same configuration + qs = cluster[1].qmf_session.getObjects(_objectId=oid) + self.assertEqual(len(qs), 1) + q2 = qs[0] + self.assertEqual(q2.name, "flq") + self.assertEqual(q2.arguments, {u'qpid.flow_stop_count': 5L, u'qpid.flow_resume_count': 3L}) + assert q2.flowStopped + self.assertEqual(q2.flowStoppedCount, 1) + + # now delete the blocked queue from other broker + ssn1 = cluster[1].connect().session() + self.evaluate_address(ssn1, "flq;{delete:always}") + sender.wait() # Verify no longer blocked. + + ssn0.connection.close() + ssn1.connection.close() + cluster_test_logs.verify_logs() + + + def test_alternate_exchange_update(self): + """Verify that alternate-exchange on exchanges and queues is propagated to new members of a cluster. """ + cluster = self.cluster(1) + s0 = cluster[0].connect().session() + # create alt queue bound to amq.fanout exchange, will be destination for alternate exchanges + self.evaluate_address(s0, "alt;{create:always,node:{x-bindings:[{exchange:'amq.fanout',queue:alt}]}}") + # create direct exchange ex with alternate-exchange amq.fanout and no queues bound + self.evaluate_address(s0, "ex;{create:always,node:{type:topic, x-declare:{type:'direct', alternate-exchange:'amq.fanout'}}}") + # create queue q with alternate-exchange amq.fanout + self.evaluate_address(s0, "q;{create:always,node:{type:queue, x-declare:{alternate-exchange:'amq.fanout'}}}") + + def verify(broker): + s = broker.connect().session() + # Verify unmatched message goes to ex's alternate. + s.sender("ex").send("foo") + self.assertEqual("foo", s.receiver("alt").fetch(timeout=0).content) + # Verify rejected message goes to q's alternate. + s.sender("q").send("bar") + msg = s.receiver("q").fetch(timeout=0) + self.assertEqual("bar", msg.content) + s.acknowledge(msg, Disposition(REJECTED)) # Reject the message + self.assertEqual("bar", s.receiver("alt").fetch(timeout=0).content) + + verify(cluster[0]) + cluster.start() + verify(cluster[1]) + + def test_binding_order(self): + """Regression test for binding order inconsistency in cluster""" + cluster = self.cluster(1) + c0 = cluster[0].connect() + s0 = c0.session() + # Declare multiple queues bound to same key on amq.topic + def declare(q,max=0): + if max: declare = 'x-declare:{arguments:{"qpid.max_count":%d, "qpid.flow_stop_count":0}}'%max + else: declare = 'x-declare:{}' + bind='x-bindings:[{queue:%s,key:key,exchange:"amq.topic"}]'%(q) + s0.sender("%s;{create:always,node:{%s,%s}}" % (q,declare,bind)) + declare('d',max=4) # Only one with a limit + for q in ['c', 'b','a']: declare(q) + # Add a cluster member, send enough messages to exceed the max count + cluster.start() + try: + s = s0.sender('amq.topic/key') + for m in xrange(1,6): s.send(Message(str(m))) + self.fail("Expected capacity exceeded exception") + except messaging.exceptions.TargetCapacityExceeded: pass + c1 = cluster[1].connect() + s1 = c1.session() + s0 = c0.session() # Old session s0 is broken by exception. + # Verify queue contents are consistent. + for q in ['a','b','c','d']: + self.assertEqual(self.browse(s0, q), self.browse(s1, q)) + # Verify queue contents are "best effort" + for q in ['a','b','c']: self.assert_browse(s1,q,[str(n) for n in xrange(1,6)]) + self.assert_browse(s1,'d',[str(n) for n in xrange(1,5)]) + + def test_deleted_exchange(self): + """QPID-3215: cached exchange reference can cause cluster inconsistencies + if exchange is deleted/recreated + Verify stand-alone case + """ + cluster = self.cluster() + # Verify we do not route message via an exchange that has been destroyed. + cluster.start() + s0 = cluster[0].connect().session() + self.evaluate_address(s0, "ex;{create:always,node:{type:topic}}") + self.evaluate_address(s0, "q;{create:always,node:{x-bindings:[{exchange:'ex',queue:q,key:foo}]}}") + send0 = s0.sender("ex/foo") + send0.send("foo") + self.assert_browse(s0, "q", ["foo"]) + self.evaluate_address(s0, "ex;{delete:always}") + try: + send0.send("bar") # Should fail, exchange is deleted. + self.fail("Expected not-found exception") + except qpid.messaging.NotFound: pass + self.assert_browse(cluster[0].connect().session(), "q", ["foo"]) + + def test_deleted_exchange_inconsistent(self): + """QPID-3215: cached exchange reference can cause cluster inconsistencies + if exchange is deleted/recreated + + Verify cluster inconsistency. + """ + cluster = self.cluster() + cluster.start() + s0 = cluster[0].connect().session() + self.evaluate_address(s0, "ex;{create:always,node:{type:topic}}") + self.evaluate_address(s0, "q;{create:always,node:{x-bindings:[{exchange:'ex',queue:q,key:foo}]}}") + send0 = s0.sender("ex/foo") + send0.send("foo") + self.assert_browse(s0, "q", ["foo"]) + + cluster.start() + s1 = cluster[1].connect().session() + self.evaluate_address(s0, "ex;{delete:always}") + try: + send0.send("bar") + self.fail("Expected not-found exception") + except qpid.messaging.NotFound: pass + + self.assert_browse(s1, "q", ["foo"]) + + + def test_ttl_consistent(self): + """Ensure we don't get inconsistent errors with message that have TTL very close together""" + messages = [ Message(str(i), ttl=i/1000.0) for i in xrange(0,1000)] + messages.append(Message("x")) + cluster = self.cluster(2) + sender = cluster[0].connect().session().sender("q;{create:always}") + + def fetch(b): + receiver = b.connect().session().receiver("q;{create:always}") + while receiver.fetch().content != "x": pass + + for m in messages: sender.send(m, sync=False) + for m in messages: sender.send(m, sync=False) + fetch(cluster[0]) + fetch(cluster[1]) + for m in messages: sender.send(m, sync=False) + cluster.start() + fetch(cluster[2]) + +# Some utility code for transaction tests +XA_RBROLLBACK = 1 +XA_RBTIMEOUT = 2 +XA_OK = 0 +dtx_branch_counter = 0 + +class DtxStatusException(Exception): + def __init__(self, expect, actual): + self.expect = expect + self.actual = actual + + def str(self): + return "DtxStatusException(expect=%s, actual=%s)"%(self.expect, self.actual) + +class DtxTestFixture: + """Bundle together some common requirements for dtx tests.""" + def __init__(self, test, broker, name, exclusive=False): + self.test = test + self.broker = broker + self.name = name + # Use old API. DTX is not supported in messaging API. + self.connection = broker.connect_old() + self.session = self.connection.session(name, 1) # 1 second timeout + self.queue = self.session.queue_declare(name, exclusive=exclusive) + self.session.dtx_select() + self.consumer = None + + def xid(self, id=None): + if id is None: id = self.name + return self.session.xid(format=0, global_id=id) + + def check_status(self, expect, actual): + if expect != actual: raise DtxStatusException(expect, actual) + + def start(self, id=None, resume=False): + self.check_status(XA_OK, self.session.dtx_start(xid=self.xid(id), resume=resume).status) + + def end(self, id=None, suspend=False): + self.check_status(XA_OK, self.session.dtx_end(xid=self.xid(id), suspend=suspend).status) + + def prepare(self, id=None): + self.check_status(XA_OK, self.session.dtx_prepare(xid=self.xid(id)).status) + + def commit(self, id=None, one_phase=True): + self.check_status( + XA_OK, self.session.dtx_commit(xid=self.xid(id), one_phase=one_phase).status) + + def rollback(self, id=None): + self.check_status(XA_OK, self.session.dtx_rollback(xid=self.xid(id)).status) + + def set_timeout(self, timeout, id=None): + self.session.dtx_set_timeout(xid=self.xid(id),timeout=timeout) + + def send(self, messages): + for m in messages: + dp=self.session.delivery_properties(routing_key=self.name) + mp=self.session.message_properties() + self.session.message_transfer(message=qpid.datatypes.Message(dp, mp, m)) + + def accept(self): + """Accept 1 message from queue""" + consumer_tag="%s-consumer"%(self.name) + self.session.message_subscribe(queue=self.name, destination=consumer_tag) + self.session.message_flow(unit = self.session.credit_unit.message, value = 1, destination = consumer_tag) + self.session.message_flow(unit = self.session.credit_unit.byte, value = 0xFFFFFFFFL, destination = consumer_tag) + msg = self.session.incoming(consumer_tag).get(timeout=1) + self.session.message_cancel(destination=consumer_tag) + self.session.message_accept(qpid.datatypes.RangedSet(msg.id)) + return msg + + + def verify(self, sessions, messages): + for s in sessions: + self.test.assert_browse(s, self.name, messages) + +class DtxTests(BrokerTest): + + def test_dtx_update(self): + """Verify that DTX transaction state is updated to a new broker. + Start a collection of transactions, then add a new cluster member, + then verify they commit/rollback correctly on the new broker.""" + + # Note: multiple test have been bundled into one to avoid the need to start/stop + # multiple brokers per test. + + cluster=self.cluster(1) + sessions = [cluster[0].connect().session()] # For verify + + # Transaction that will be open when new member joins, then committed. + t1 = DtxTestFixture(self, cluster[0], "t1") + t1.start() + t1.send(["1", "2"]) + t1.verify(sessions, []) # Not visible outside of transaction + + # Transaction that will be open when new member joins, then rolled back. + t2 = DtxTestFixture(self, cluster[0], "t2") + t2.start() + t2.send(["1", "2"]) + + # Transaction that will be prepared when new member joins, then committed. + t3 = DtxTestFixture(self, cluster[0], "t3") + t3.start() + t3.send(["1", "2"]) + t3.end() + t3.prepare() + t1.verify(sessions, []) # Not visible outside of transaction + + # Transaction that will be prepared when new member joins, then rolled back. + t4 = DtxTestFixture(self, cluster[0], "t4") + t4.start() + t4.send(["1", "2"]) + t4.end() + t4.prepare() + + # Transaction using an exclusive queue + t5 = DtxTestFixture(self, cluster[0], "t5", exclusive=True) + t5.start() + t5.send(["1", "2"]) + + # Accept messages in a transaction before/after join then commit + t6 = DtxTestFixture(self, cluster[0], "t6") + t6.send(["a","b","c"]) + t6.start() + self.assertEqual(t6.accept().body, "a"); + + # Accept messages in a transaction before/after join then roll back + t7 = DtxTestFixture(self, cluster[0], "t7") + t7.send(["a","b","c"]) + t7.start() + self.assertEqual(t7.accept().body, "a"); + + # Ended, suspended transactions across join. + t8 = DtxTestFixture(self, cluster[0], "t8") + t8.start(id="1") + t8.send(["x"]) + t8.end(id="1", suspend=True) + t8.start(id="2") + t8.send(["y"]) + t8.end(id="2") + t8.start() + t8.send("z") + + + # Start new cluster member + cluster.start() + sessions.append(cluster[1].connect().session()) + + # Commit t1 + t1.send(["3","4"]) + t1.verify(sessions, []) + t1.end() + t1.commit(one_phase=True) + t1.verify(sessions, ["1","2","3","4"]) + + # Rollback t2 + t2.send(["3","4"]) + t2.end() + t2.rollback() + t2.verify(sessions, []) + + # Commit t3 + t3.commit(one_phase=False) + t3.verify(sessions, ["1","2"]) + + # Rollback t4 + t4.rollback() + t4.verify(sessions, []) + + # Commit t5 + t5.send(["3","4"]) + t5.verify(sessions, []) + t5.end() + t5.commit(one_phase=True) + t5.verify(sessions, ["1","2","3","4"]) + + # Commit t6 + self.assertEqual(t6.accept().body, "b"); + t6.verify(sessions, ["c"]) + t6.end() + t6.commit(one_phase=True) + t6.session.close() # Make sure they're not requeued by the session. + t6.verify(sessions, ["c"]) + + # Rollback t7 + self.assertEqual(t7.accept().body, "b"); + t7.end() + t7.rollback() + t7.verify(sessions, ["a", "b", "c"]) + + # Resume t8 + t8.end() + t8.commit(one_phase=True) + t8.start("1", resume=True) + t8.end("1") + t8.commit("1", one_phase=True) + t8.commit("2", one_phase=True) + t8.verify(sessions, ["z", "x","y"]) + + + def test_dtx_failover_rollback(self): + """Kill a broker during a transaction, verify we roll back correctly""" + cluster=self.cluster(1, expect=EXPECT_EXIT_FAIL) + cluster.start(expect=EXPECT_RUNNING) + + # Test unprepared at crash + t1 = DtxTestFixture(self, cluster[0], "t1") + t1.send(["a"]) # Not in transaction + t1.start() + t1.send(["b"]) # In transaction + + # Test prepared at crash + t2 = DtxTestFixture(self, cluster[0], "t2") + t2.send(["a"]) # Not in transaction + t2.start() + t2.send(["b"]) # In transaction + t2.end() + t2.prepare() + + # Crash the broker + cluster[0].kill() + + # Transactional changes should not appear + s = cluster[1].connect().session(); + self.assert_browse(s, "t1", ["a"]) + self.assert_browse(s, "t2", ["a"]) + + def test_dtx_timeout(self): + """Verify that dtx timeout works""" + cluster = self.cluster(1) + t1 = DtxTestFixture(self, cluster[0], "t1") + t1.start() + t1.set_timeout(1) + time.sleep(1.1) + try: + t1.end() + self.fail("Expected rollback timeout.") + except DtxStatusException, e: + self.assertEqual(e.actual, XA_RBTIMEOUT) + +class TxTests(BrokerTest): + + def test_tx_update(self): + """Verify that transaction state is updated to a new broker""" + + def make_message(session, body=None, key=None, id=None): + dp=session.delivery_properties(routing_key=key) + mp=session.message_properties(correlation_id=id) + return qpid.datatypes.Message(dp, mp, body) + + cluster=self.cluster(1) + # Use old API. TX is not supported in messaging API. + c = cluster[0].connect_old() + s = c.session("tx-session", 1) + s.queue_declare(queue="q") + # Start transaction + s.tx_select() + s.message_transfer(message=make_message(s, "1", "q")) + # Start new member mid-transaction + cluster.start() + # Do more work + s.message_transfer(message=make_message(s, "2", "q")) + # Commit the transaction and verify the results. + s.tx_commit() + for b in cluster: self.assert_browse(b.connect().session(), "q", ["1","2"]) + + class LongTests(BrokerTest): """Tests that can run for a long time if -DDURATION=<minutes> is set""" def duration(self): @@ -316,22 +1023,28 @@ class LongTests(BrokerTest): # Original cluster will all be killed so expect exit with failure cluster = self.cluster(3, expect=EXPECT_EXIT_FAIL) + for b in cluster: b.ready() # Wait for brokers to be ready for b in cluster: ErrorGenerator(b) # Start sender and receiver threads cluster[0].declare_queue("test-queue") - sender = NumberedSender(cluster[1], 1000) # Max queue depth - receiver = NumberedReceiver(cluster[2], sender) + sender = NumberedSender(cluster[0], 1000) # Max queue depth + receiver = NumberedReceiver(cluster[0], sender) receiver.start() sender.start() + # Wait for sender & receiver to get up and running + retry(lambda: receiver.received > 0) # Kill original brokers, start new ones for the duration. endtime = time.time() + self.duration() i = 0 while time.time() < endtime: + sender.sender.assert_running() + receiver.receiver.assert_running() cluster[i].kill() i += 1 b = cluster.start(expect=EXPECT_EXIT_FAIL) + for b in cluster[i:]: b.ready() ErrorGenerator(b) time.sleep(5) sender.stop() @@ -362,24 +1075,24 @@ class LongTests(BrokerTest): if self.stopped: break self.process = self.broker.test.popen( self.cmd, expect=EXPECT_UNKNOWN) - finally: self.lock.release() - try: exit = self.process.wait() + finally: + self.lock.release() + try: + exit = self.process.wait() except OSError, e: - # Seems to be a race in wait(), it throws - # "no such process" during test shutdown. - # Doesn't indicate a test error, ignore. - return + # Process may already have been killed by self.stop() + break except Exception, e: self.process.unexpected( "client of %s: %s"%(self.broker.name, e)) self.lock.acquire() try: - # Quit and ignore errors if stopped or expecting failure. if self.stopped: break if exit != 0: self.process.unexpected( "client of %s exit code %s"%(self.broker.name, exit)) - finally: self.lock.release() + finally: + self.lock.release() except Exception, e: self.error = RethrownException("Error in ClientLoop.run") @@ -401,7 +1114,7 @@ class LongTests(BrokerTest): args += ["--log-enable=trace+:management"] # Use store if present. if BrokerTest.store_lib: args +=["--load-module", BrokerTest.store_lib] - cluster = self.cluster(3, args) + cluster = self.cluster(3, args, expect=EXPECT_EXIT_FAIL) # brokers will be killed clients = [] # Per-broker list of clients that only connect to one broker. mclients = [] # Management clients that connect to every broker in the cluster. @@ -410,10 +1123,12 @@ class LongTests(BrokerTest): """Start ordinary clients for a broker.""" cmds=[ ["qpid-tool", "localhost:%s"%(broker.port())], - ["qpid-perftest", "--count", 50000, + ["qpid-perftest", "--count=5000", "--durable=yes", "--base-name", str(qpid.datatypes.uuid4()), "--port", broker.port()], - ["qpid-queue-stats", "-a", "localhost:%s" %(broker.port())], - ["testagent", "localhost", str(broker.port())] ] + ["qpid-txtest", "--queue-base-name", "tx-%s"%str(qpid.datatypes.uuid4()), + "--port", broker.port()], + ["qpid-queue-stats", "-a", "localhost:%s" %(broker.port())] + ] clients.append([ClientLoop(broker, cmd) for cmd in cmds]) def start_mclients(broker): @@ -422,7 +1137,8 @@ class LongTests(BrokerTest): mclients.append(ClientLoop(broker, cmd)) endtime = time.time() + self.duration() - runtime = self.duration() / 4 # First run is longer, use quarter of duration. + # For long duration, first run is a quarter of the duration. + runtime = min(5.0, self.duration() / 3.0) alive = 0 # First live cluster member for i in range(len(cluster)): start_clients(cluster[i]) start_mclients(cluster[alive]) @@ -433,7 +1149,7 @@ class LongTests(BrokerTest): for b in cluster[alive:]: b.ready() # Check if a broker crashed. # Kill the first broker, expect the clients to fail. b = cluster[alive] - b.expect = EXPECT_EXIT_FAIL + b.ready() b.kill() # Stop the brokers clients and all the mclients. for c in clients[alive] + mclients: @@ -443,26 +1159,251 @@ class LongTests(BrokerTest): mclients = [] # Start another broker and clients alive += 1 - cluster.start() + cluster.start(expect=EXPECT_EXIT_FAIL) + cluster[-1].ready() # Wait till its ready start_clients(cluster[-1]) start_mclients(cluster[alive]) for c in chain(mclients, *clients): c.stop() - + for b in cluster[alive:]: + b.ready() # Verify still alive + b.kill() # Verify that logs are consistent cluster_test_logs.verify_logs() def test_management_qmf2(self): self.test_management(args=["--mgmt-qmf2=yes"]) - def test_connect_consistent(self): # FIXME aconway 2011-01-18: + def test_connect_consistent(self): args=["--mgmt-pub-interval=1","--log-enable=trace+:management"] cluster = self.cluster(2, args=args) end = time.time() + self.duration() while (time.time() < end): # Get a management interval for i in xrange(1000): cluster[0].connect().close() + cluster_test_logs.verify_logs() + + def test_flowlimit_failover(self): + """Test fail-over during continuous send-receive with flow control + active. + """ + + # Original cluster will all be killed so expect exit with failure + cluster = self.cluster(3, expect=EXPECT_EXIT_FAIL) + for b in cluster: b.ready() # Wait for brokers to be ready + + # create a queue with rather draconian flow control settings + ssn0 = cluster[0].connect().session() + s0 = ssn0.sender("test-queue; {create:always, node:{type:queue, x-declare:{arguments:{'qpid.flow_stop_count':2000, 'qpid.flow_resume_count':100}}}}") + + receiver = NumberedReceiver(cluster[0]) + receiver.start() + senders = [NumberedSender(cluster[0]) for i in range(1,3)] + for s in senders: + s.start() + # Wait for senders & receiver to get up and running + retry(lambda: receiver.received > 2*senders) + + # Kill original brokers, start new ones for the duration. + endtime = time.time() + self.duration(); + i = 0 + while time.time() < endtime: + for s in senders: s.sender.assert_running() + receiver.receiver.assert_running() + for b in cluster[i:]: b.ready() # Check if any broker crashed. + cluster[i].kill() + i += 1 + b = cluster.start(expect=EXPECT_EXIT_FAIL) + time.sleep(5) + for s in senders: + s.stop() + receiver.stop() + for i in range(i, len(cluster)): cluster[i].kill() + + def test_ttl_failover(self): + """Test that messages with TTL don't cause problems in a cluster with failover""" + + class Client(StoppableThread): + + def __init__(self, broker): + StoppableThread.__init__(self) + self.connection = broker.connect(reconnect=True) + self.auto_fetch_reconnect_urls(self.connection) + self.session = self.connection.session() + + def auto_fetch_reconnect_urls(self, conn): + """Replacment for qpid.messaging.util version which is noisy""" + ssn = conn.session("auto-fetch-reconnect-urls") + rcv = ssn.receiver("amq.failover") + rcv.capacity = 10 + + def main(): + while True: + try: + msg = rcv.fetch() + qpid.messaging.util.set_reconnect_urls(conn, msg) + ssn.acknowledge(msg, sync=False) + except messaging.exceptions.LinkClosed: return + except messaging.exceptions.ConnectionError: return + + thread = Thread(name="auto-fetch-reconnect-urls", target=main) + thread.setDaemon(True) + thread.start() + + def stop(self): + StoppableThread.stop(self) + self.connection.detach() + + class Sender(Client): + def __init__(self, broker, address): + Client.__init__(self, broker) + self.sent = 0 # Number of messages _reliably_ sent. + self.sender = self.session.sender(address, capacity=1000) + + def send_counted(self, ttl): + self.sender.send(Message(str(self.sent), ttl=ttl)) + self.sent += 1 + + def run(self): + while not self.stopped: + choice = random.randint(0,4) + if choice == 0: self.send_counted(None) # No ttl + elif choice == 1: self.send_counted(100000) # Large ttl + else: # Small ttl, might expire + self.sender.send(Message("", ttl=random.random()/10)) + self.sender.send(Message("z"), sync=True) # Chaser. + + class Receiver(Client): + + def __init__(self, broker, address): + Client.__init__(self, broker) + self.received = 0 # Number of non-empty (reliable) messages received. + self.receiver = self.session.receiver(address, capacity=1000) + def run(self): + try: + while True: + m = self.receiver.fetch(1) + if m.content == "z": break + if m.content: # Ignore unreliable messages + # Ignore duplicates + if int(m.content) == self.received: self.received += 1 + except Exception,e: self.error = e + + # def test_ttl_failover + + # Original cluster will all be killed so expect exit with failure + # Set small purge interval. + cluster = self.cluster(3, expect=EXPECT_EXIT_FAIL, args=["--queue-purge-interval=1"]) + for b in cluster: b.ready() # Wait for brokers to be ready + + # Python client failover produces noisy WARN logs, disable temporarily + logger = logging.getLogger() + log_level = logger.getEffectiveLevel() + logger.setLevel(logging.ERROR) + sender = None + receiver = None + try: + # Start sender and receiver threads + receiver = Receiver(cluster[0], "q;{create:always}") + receiver.start() + sender = Sender(cluster[0], "q;{create:always}") + sender.start() + # Wait for sender & receiver to get up and running + retry(lambda: receiver.received > 0) + + # Kill brokers in a cycle. + endtime = time.time() + self.duration() + runtime = min(5.0, self.duration() / 4.0) + i = 0 + while time.time() < endtime: + for b in cluster[i:]: b.ready() # Check if any broker crashed. + cluster[i].kill() + i += 1 + b = cluster.start(expect=EXPECT_EXIT_FAIL) + b.ready() + time.sleep(runtime) + sender.stop() + receiver.stop() + for b in cluster[i:]: + b.ready() # Check it didn't crash + b.kill() + self.assertEqual(sender.sent, receiver.received) cluster_test_logs.verify_logs() + finally: + # Detach to avoid slow reconnect attempts during shut-down if test fails. + if sender: sender.connection.detach() + if receiver: receiver.connection.detach() + logger.setLevel(log_level) + + def test_msg_group_failover(self): + """Test fail-over during continuous send-receive of grouped messages. + """ + + class GroupedTrafficGenerator(Thread): + def __init__(self, url, queue, group_key): + Thread.__init__(self) + self.url = url + self.queue = queue + self.group_key = group_key + self.status = -1 + + def run(self): + # generate traffic for approx 10 seconds (2011msgs / 200 per-sec) + cmd = ["msg_group_test", + "--broker=%s" % self.url, + "--address=%s" % self.queue, + "--connection-options={%s}" % (Cluster.CONNECTION_OPTIONS), + "--group-key=%s" % self.group_key, + "--receivers=2", + "--senders=3", + "--messages=2011", + "--send-rate=200", + "--capacity=11", + "--ack-frequency=23", + "--allow-duplicates", + "--group-size=37", + "--randomize-group-size", + "--interleave=13"] + # "--trace"] + self.generator = Popen( cmd ); + self.status = self.generator.wait() + return self.status + + def results(self): + self.join(timeout=30) # 3x assumed duration + if self.isAlive(): return -1 + return self.status + + # Original cluster will all be killed so expect exit with failure + cluster = self.cluster(3, expect=EXPECT_EXIT_FAIL, args=["-t"]) + for b in cluster: b.ready() # Wait for brokers to be ready + + # create a queue with rather draconian flow control settings + ssn0 = cluster[0].connect().session() + q_args = "{'qpid.group_header_key':'group-id', 'qpid.shared_msg_group':1}" + s0 = ssn0.sender("test-group-q; {create:always, node:{type:queue, x-declare:{arguments:%s}}}" % q_args) + + # Kill original brokers, start new ones for the duration. + endtime = time.time() + self.duration(); + i = 0 + while time.time() < endtime: + traffic = GroupedTrafficGenerator( cluster[i].host_port(), + "test-group-q", "group-id" ) + traffic.start() + time.sleep(1) + + for x in range(2): + for b in cluster[i:]: b.ready() # Check if any broker crashed. + cluster[i].kill() + i += 1 + b = cluster.start(expect=EXPECT_EXIT_FAIL) + time.sleep(1) + + # wait for traffic to finish, verify success + self.assertEqual(0, traffic.results()) + + for i in range(i, len(cluster)): cluster[i].kill() + class StoreTests(BrokerTest): """ diff --git a/cpp/src/tests/exception_test.cpp b/cpp/src/tests/exception_test.cpp index 3536ffddbe..3e844b4e58 100644 --- a/cpp/src/tests/exception_test.cpp +++ b/cpp/src/tests/exception_test.cpp @@ -92,32 +92,30 @@ QPID_AUTO_TEST_CASE(TestSessionBusy) { } QPID_AUTO_TEST_CASE(DisconnectedPop) { - ProxySessionFixture fix; - ProxyConnection c(fix.broker->getPort(Broker::TCP_TRANSPORT)); + SessionFixture fix; fix.session.queueDeclare(arg::queue="q"); fix.subs.subscribe(fix.lq, "q"); Catcher<TransportFailure> pop(bind(&LocalQueue::pop, &fix.lq, sys::TIME_SEC)); - fix.connection.proxy.close(); + fix.shutdownBroker(); BOOST_CHECK(pop.join()); } QPID_AUTO_TEST_CASE(DisconnectedListen) { - ProxySessionFixture fix; + SessionFixture fix; struct NullListener : public MessageListener { void received(Message&) { BOOST_FAIL("Unexpected message"); } } l; - ProxyConnection c(fix.broker->getPort(Broker::TCP_TRANSPORT)); fix.session.queueDeclare(arg::queue="q"); fix.subs.subscribe(l, "q"); Catcher<TransportFailure> runner(bind(&SubscriptionManager::run, boost::ref(fix.subs))); - fix.connection.proxy.close(); - runner.join(); + fix.shutdownBroker(); + runner.join(); BOOST_CHECK_THROW(fix.session.queueDeclare(arg::queue="x"), TransportFailure); } QPID_AUTO_TEST_CASE(NoSuchQueueTest) { - ProxySessionFixture fix; + SessionFixture fix; ScopedSuppressLogging sl; // Suppress messages for expected errors. BOOST_CHECK_THROW(fix.subs.subscribe(fix.lq, "no such queue"), NotFoundException); } diff --git a/cpp/src/tests/federated_topic_test b/cpp/src/tests/federated_topic_test index b1063c7e8c..2e55ddcfaa 100755 --- a/cpp/src/tests/federated_topic_test +++ b/cpp/src/tests/federated_topic_test @@ -42,13 +42,12 @@ while getopts "s:m:b:" opt ; do esac done -MY_DIR=$(dirname $(which $0)) source ./test_env.sh trap stop_brokers EXIT start_broker() { - ${MY_DIR}/../qpidd --daemon --port 0 --no-module-dir --no-data-dir --auth no > qpidd.port + $QPIDD_EXEC --daemon --port 0 --no-module-dir --no-data-dir --auth no > qpidd.port } start_brokers() { @@ -76,39 +75,39 @@ subscribe() { echo Subscriber $1 connecting on $MY_PORT LOG="subscriber_$1.log" - ${MY_DIR}/topic_listener -p $MY_PORT > $LOG 2>&1 && rm -f $LOG + ./qpid-topic-listener -p $MY_PORT > $LOG 2>&1 && rm -f $LOG } publish() { - ${MY_DIR}/topic_publisher --messages $MESSAGES --batches $BATCHES --subscribers $SUBSCRIBERS -p $PORT_A + ./qpid-topic-publisher --messages $MESSAGES --batches $BATCHES --subscribers $SUBSCRIBERS -p $PORT_A } setup_routes() { - BROKER_A="localhost:$PORT_A" - BROKER_B="localhost:$PORT_B" - BROKER_C="localhost:$PORT_C" + BROKER_A="daffodil:$PORT_A" + BROKER_B="daffodil:$PORT_B" + BROKER_C="daffodil:$PORT_C" if (($VERBOSE)); then echo "Establishing routes for topic..." fi - $PYTHON_COMMANDS/qpid-route route add $BROKER_B $BROKER_A amq.topic topic_control B B - $PYTHON_COMMANDS/qpid-route route add $BROKER_C $BROKER_B amq.topic topic_control C C + $QPID_ROUTE_EXEC route add $BROKER_B $BROKER_A amq.topic topic_control B B + $QPID_ROUTE_EXEC route add $BROKER_C $BROKER_B amq.topic topic_control C C if (($VERBOSE)); then echo "linked A->B->C" fi - $PYTHON_COMMANDS/qpid-route route add $BROKER_B $BROKER_C amq.topic topic_control B B - $PYTHON_COMMANDS/qpid-route route add $BROKER_A $BROKER_B amq.topic topic_control A A + $QPID_ROUTE_EXEC route add $BROKER_B $BROKER_C amq.topic topic_control B B + $QPID_ROUTE_EXEC route add $BROKER_A $BROKER_B amq.topic topic_control A A if (($VERBOSE)); then echo "linked C->B->A" echo "Establishing routes for response queue..." fi - $PYTHON_COMMANDS/qpid-route route add $BROKER_B $BROKER_C amq.direct response B B - $PYTHON_COMMANDS/qpid-route route add $BROKER_A $BROKER_B amq.direct response A A + $QPID_ROUTE_EXEC route add $BROKER_B $BROKER_C amq.direct response B B + $QPID_ROUTE_EXEC route add $BROKER_A $BROKER_B amq.direct response A A if (($VERBOSE)); then echo "linked C->B->A" for b in $BROKER_A $BROKER_B $BROKER_C; do echo "Routes for $b" - $PYTHON_COMMANDS/qpid-route route list $b + $QPID_ROUTE_EXEC route list $b done fi } diff --git a/cpp/src/tests/federation.py b/cpp/src/tests/federation.py index 973a1d366c..7d613b98ce 100755 --- a/cpp/src/tests/federation.py +++ b/cpp/src/tests/federation.py @@ -23,7 +23,7 @@ from qpid.testlib import TestBase010 from qpid.datatypes import Message from qpid.queue import Empty from qpid.util import URL -from time import sleep +from time import sleep, time class _FedBroker(object): @@ -111,18 +111,18 @@ class FederationTests(TestBase010): broker = qmf.getObjects(_class="broker")[0] result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") - self.assertEqual(result.status, 0) + self.assertEqual(result.status, 0, result) link = qmf.getObjects(_class="link")[0] result = link.bridge(False, "amq.direct", "amq.direct", "my-key", "", "", False, False, False, 0) - self.assertEqual(result.status, 0) + self.assertEqual(result.status, 0, result) bridge = qmf.getObjects(_class="bridge")[0] result = bridge.close() - self.assertEqual(result.status, 0) + self.assertEqual(result.status, 0, result) result = link.close() - self.assertEqual(result.status, 0) + self.assertEqual(result.status, 0, result) self.verify_cleanup() @@ -133,11 +133,11 @@ class FederationTests(TestBase010): qmf = self.qmf broker = qmf.getObjects(_class="broker")[0] result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") - self.assertEqual(result.status, 0) + self.assertEqual(result.status, 0, result) link = qmf.getObjects(_class="link")[0] result = link.bridge(False, "amq.direct", "amq.fanout", "my-key", "", "", False, False, False, 0) - self.assertEqual(result.status, 0) + self.assertEqual(result.status, 0, result) bridge = qmf.getObjects(_class="bridge")[0] @@ -165,9 +165,9 @@ class FederationTests(TestBase010): except Empty: None result = bridge.close() - self.assertEqual(result.status, 0) + self.assertEqual(result.status, 0, result) result = link.close() - self.assertEqual(result.status, 0) + self.assertEqual(result.status, 0, result) self.verify_cleanup() @@ -178,11 +178,11 @@ class FederationTests(TestBase010): qmf = self.qmf broker = qmf.getObjects(_class="broker")[0] result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") - self.assertEqual(result.status, 0) + self.assertEqual(result.status, 0, result) link = qmf.getObjects(_class="link")[0] result = link.bridge(False, "amq.direct", "amq.fanout", "my-key", "", "", False, True, False, 0) - self.assertEqual(result.status, 0) + self.assertEqual(result.status, 0, result) bridge = qmf.getObjects(_class="bridge")[0] @@ -209,9 +209,9 @@ class FederationTests(TestBase010): except Empty: None result = bridge.close() - self.assertEqual(result.status, 0) + self.assertEqual(result.status, 0, result) result = link.close() - self.assertEqual(result.status, 0) + self.assertEqual(result.status, 0, result) self.verify_cleanup() @@ -236,11 +236,11 @@ class FederationTests(TestBase010): qmf = self.qmf broker = qmf.getObjects(_class="broker")[0] result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") - self.assertEqual(result.status, 0) + self.assertEqual(result.status, 0, result) link = qmf.getObjects(_class="link")[0] result = link.bridge(False, "my-bridge-queue", "amq.fanout", "my-key", "", "", True, False, False, 1) - self.assertEqual(result.status, 0) + self.assertEqual(result.status, 0, result) bridge = qmf.getObjects(_class="bridge")[0] sleep(3) @@ -262,6 +262,63 @@ class FederationTests(TestBase010): except Empty: None result = bridge.close() + self.assertEqual(result.status, 0, result) + result = link.close() + self.assertEqual(result.status, 0, result) + + self.verify_cleanup() + + def test_pull_from_queue_recovery(self): + session = self.session + + #setup queue on remote broker and add some messages + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_pull_from_queue_recovery") + r_session.queue_declare(queue="my-bridge-queue", auto_delete=True) + for i in range(1, 6): + dp = r_session.delivery_properties(routing_key="my-bridge-queue") + r_session.message_transfer(message=Message(dp, "Message %d" % i)) + + #setup queue to receive messages from local broker + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="amq.fanout") + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + self.startQmf() + qmf = self.qmf + broker = qmf.getObjects(_class="broker")[0] + result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0, result) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "my-bridge-queue", "amq.fanout", "my-key", "", "", True, False, False, 1) + self.assertEqual(result.status, 0, result) + + bridge = qmf.getObjects(_class="bridge")[0] + sleep(5) + + #recreate the remote bridge queue to invalidate the bridge session + r_session.queue_delete (queue="my-bridge-queue", if_empty=False, if_unused=False) + r_session.queue_declare(queue="my-bridge-queue", auto_delete=True) + + #add some more messages (i.e. after bridge was created) + for i in range(6, 11): + dp = r_session.delivery_properties(routing_key="my-bridge-queue") + r_session.message_transfer(message=Message(dp, "Message %d" % i)) + + for i in range(1, 11): + try: + msg = queue.get(timeout=5) + self.assertEqual("Message %d" % i, msg.body) + except Empty: + self.fail("Failed to find expected message containing 'Message %d'" % i) + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected message in queue: " + extra.body) + except Empty: None + + result = bridge.close() self.assertEqual(result.status, 0) result = link.close() self.assertEqual(result.status, 0) @@ -649,10 +706,17 @@ class FederationTests(TestBase010): self.verify_cleanup() - def test_dynamic_headers(self): + def test_dynamic_headers_any(self): + self.do_test_dynamic_headers('any') + + def test_dynamic_headers_all(self): + self.do_test_dynamic_headers('all') + + + def do_test_dynamic_headers(self, match_mode): session = self.session r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) - r_session = r_conn.session("test_dynamic_headers") + r_session = r_conn.session("test_dynamic_headers_%s" % match_mode) session.exchange_declare(exchange="fed.headers", type="headers") r_session.exchange_declare(exchange="fed.headers", type="headers") @@ -671,7 +735,7 @@ class FederationTests(TestBase010): sleep(5) session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) - session.exchange_bind(queue="fed1", exchange="fed.headers", binding_key="key1", arguments={'x-match':'any', 'class':'first'}) + session.exchange_bind(queue="fed1", exchange="fed.headers", binding_key="key1", arguments={'x-match':match_mode, 'class':'first'}) self.subscribe(queue="fed1", destination="f1") queue = session.incoming("f1") @@ -1791,3 +1855,301 @@ class FederationTests(TestBase010): if headers: return headers[name] return None + + def test_dynamic_topic_bounce(self): + """ Bounce the connection between federated Topic Exchanges. + """ + class Params: + def exchange_type(self): return "topic" + def bind_queue(self, ssn, qname, ename): + ssn.exchange_bind(queue=qname, exchange=ename, + binding_key="spud.*") + def unbind_queue(self, ssn, qname, ename): + ssn.exchange_unbind(queue=qname, exchange=ename, binding_key="spud.*") + def delivery_properties(self, ssn): + return ssn.delivery_properties(routing_key="spud.boy") + + self.generic_dynamic_bounce_test(Params()) + + def test_dynamic_direct_bounce(self): + """ Bounce the connection between federated Direct Exchanges. + """ + class Params: + def exchange_type(self): return "direct" + def bind_queue(self, ssn, qname, ename): + ssn.exchange_bind(queue=qname, exchange=ename, binding_key="spud") + def unbind_queue(self, ssn, qname, ename): + ssn.exchange_unbind(queue=qname, exchange=ename, binding_key="spud") + def delivery_properties(self, ssn): + return ssn.delivery_properties(routing_key="spud") + self.generic_dynamic_bounce_test(Params()) + + def test_dynamic_fanout_bounce(self): + """ Bounce the connection between federated Fanout Exchanges. + """ + class Params: + def exchange_type(self): return "fanout" + def bind_queue(self, ssn, qname, ename): + ssn.exchange_bind(queue=qname, exchange=ename) + def unbind_queue(self, ssn, qname, ename): + ssn.exchange_unbind(queue=qname, exchange=ename) + def delivery_properties(self, ssn): + return ssn.delivery_properties(routing_key="spud") + self.generic_dynamic_bounce_test(Params()) + + def test_dynamic_headers_bounce(self): + """ Bounce the connection between federated Headers Exchanges. + """ + class Params: + def exchange_type(self): return "headers" + def bind_queue(self, ssn, qname, ename): + ssn.exchange_bind(queue=qname, exchange=ename, + binding_key="spud", arguments={'x-match':'any', 'class':'first'}) + def unbind_queue(self, ssn, qname, ename): + ssn.exchange_unbind(queue=qname, exchange=ename, binding_key="spud") + def delivery_properties(self, ssn): + return ssn.message_properties(application_headers={'class':'first'}) + ## @todo KAG - re-enable once federation bugs with headers exchanges + ## are fixed. + #self.generic_dynamic_bounce_test(Params()) + return + + + def generic_dynamic_bounce_test(self, params): + """ Verify that a federated broker can maintain a binding to a local + queue using the same key as a remote binding. Destroy and reconnect + the federation link, and verify routes are restored correctly. + See QPID-3170. + Topology: + + Queue1 <---"Key"---B0<==[Federated Exchange]==>B1---"Key"--->Queue2 + """ + session = self.session + + # create the federation + + self.startQmf() + qmf = self.qmf + + self._setup_brokers() + + # create exchange on each broker, and retrieve the corresponding + # management object for that exchange + + exchanges=[] + for _b in self._brokers[0:2]: + _b.client_session.exchange_declare(exchange="fedX", type=params.exchange_type()) + self.assertEqual(_b.client_session.exchange_query(name="fedX").type, + params.exchange_type(), "exchange_declare failed!") + # pull the exchange out of qmf... + retries = 0 + my_exchange = None + timeout = time() + 10 + while my_exchange is None and time() <= timeout: + objs = qmf.getObjects(_broker=_b.qmf_broker, _class="exchange") + for ooo in objs: + if ooo.name == "fedX": + my_exchange = ooo + break + if my_exchange is None: + self.fail("QMF failed to find new exchange!") + exchanges.append(my_exchange) + + # + # on each broker, create a local queue bound to the exchange with the + # same key value. + # + + self._brokers[0].client_session.queue_declare(queue="fedX1", exclusive=True, auto_delete=True) + params.bind_queue(self._brokers[0].client_session, "fedX1", "fedX") + self.subscribe(self._brokers[0].client_session, queue="fedX1", destination="f1") + queue_0 = self._brokers[0].client_session.incoming("f1") + + self._brokers[1].client_session.queue_declare(queue="fedX1", exclusive=True, auto_delete=True) + params.bind_queue(self._brokers[1].client_session, "fedX1", "fedX") + self.subscribe(self._brokers[1].client_session, queue="fedX1", destination="f1") + queue_1 = self._brokers[1].client_session.incoming("f1") + + # now federate the two brokers + + # connect B0 --> B1 + result = self._brokers[1].qmf_object.connect(self._brokers[0].host, + self._brokers[0].port, + False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + # connect B1 --> B0 + result = self._brokers[0].qmf_object.connect(self._brokers[1].host, + self._brokers[1].port, + False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + # for each link, bridge the "fedX" exchanges: + + for _l in qmf.getObjects(_class="link"): + # print("Link=%s:%s %s" % (_l.host, _l.port, str(_l.getBroker()))) + result = _l.bridge(False, # durable + "fedX", # src + "fedX", # dst + "", # key + "", # tag + "", # excludes + False, # srcIsQueue + False, # srcIsLocal + True, # dynamic + 0) # sync + self.assertEqual(result.status, 0) + + # wait for all the inter-broker links to become operational + operational = False + timeout = time() + 10 + while not operational and time() <= timeout: + operational = True + for _l in qmf.getObjects(_class="link"): + #print("Link=%s:%s %s" % (_l.host, _l.port, str(_l.state))) + if _l.state != "Operational": + operational = False + self.failUnless(operational, "inter-broker links failed to become operational.") + + # @todo - There is no way to determine when the bridge objects become + # active. + + # wait until the binding key has propagated to each broker - each + # broker should see 2 bindings (1 local, 1 remote) + + binding_counts = [2, 2] + self.assertEqual(len(binding_counts), len(exchanges), "Update Test!") + for i in range(2): + exchanges[i].update() + timeout = time() + 10 + while exchanges[i].bindingCount < binding_counts[i] and time() <= timeout: + exchanges[i].update() + self.failUnless(exchanges[i].bindingCount == binding_counts[i]) + + # send 10 msgs to B0 + for i in range(1, 11): + # dp = self._brokers[0].client_session.delivery_properties(routing_key=params.routing_key()) + dp = params.delivery_properties(self._brokers[0].client_session) + self._brokers[0].client_session.message_transfer(destination="fedX", message=Message(dp, "Message_trp %d" % i)) + + # get exactly 10 msgs on B0's local queue and B1's queue + for i in range(1, 11): + try: + msg = queue_0.get(timeout=5) + self.assertEqual("Message_trp %d" % i, msg.body) + msg = queue_1.get(timeout=5) + self.assertEqual("Message_trp %d" % i, msg.body) + except Empty: + self.fail("Only got %d msgs - expected 10" % i) + try: + extra = queue_0.get(timeout=1) + self.fail("Got unexpected message in queue_0: " + extra.body) + except Empty: None + + try: + extra = queue_1.get(timeout=1) + self.fail("Got unexpected message in queue_1: " + extra.body) + except Empty: None + + # + # Tear down the bridges between the two exchanges, then wait + # for the bindings to be cleaned up + # + + for _b in qmf.getObjects(_class="bridge"): + result = _b.close() + self.assertEqual(result.status, 0) + + binding_counts = [1, 1] + self.assertEqual(len(binding_counts), len(exchanges), "Update Test!") + for i in range(2): + exchanges[i].update() + timeout = time() + 10 + while exchanges[i].bindingCount != binding_counts[i] and time() <= timeout: + exchanges[i].update() + self.failUnless(exchanges[i].bindingCount == binding_counts[i]) + + # + # restore the bridges between the two exchanges, and wait for the + # bindings to propagate. + # + + for _l in qmf.getObjects(_class="link"): + # print("Link=%s:%s %s" % (_l.host, _l.port, str(_l.getBroker()))) + result = _l.bridge(False, # durable + "fedX", # src + "fedX", # dst + "", # key + "", # tag + "", # excludes + False, # srcIsQueue + False, # srcIsLocal + True, # dynamic + 0) # sync + self.assertEqual(result.status, 0) + + binding_counts = [2, 2] + self.assertEqual(len(binding_counts), len(exchanges), "Update Test!") + for i in range(2): + exchanges[i].update() + timeout = time() + 10 + while exchanges[i].bindingCount != binding_counts[i] and time() <= timeout: + exchanges[i].update() + self.failUnless(exchanges[i].bindingCount == binding_counts[i]) + + # + # verify traffic flows correctly + # + + for i in range(1, 11): + #dp = self._brokers[1].client_session.delivery_properties(routing_key=params.routing_key()) + dp = params.delivery_properties(self._brokers[1].client_session) + self._brokers[1].client_session.message_transfer(destination="fedX", message=Message(dp, "Message_trp %d" % i)) + + # get exactly 10 msgs on B0's queue and B1's queue + for i in range(1, 11): + try: + msg = queue_0.get(timeout=5) + self.assertEqual("Message_trp %d" % i, msg.body) + msg = queue_1.get(timeout=5) + self.assertEqual("Message_trp %d" % i, msg.body) + except Empty: + self.fail("Only got %d msgs - expected 10" % i) + try: + extra = queue_0.get(timeout=1) + self.fail("Got unexpected message in queue_0: " + extra.body) + except Empty: None + + try: + extra = queue_1.get(timeout=1) + self.fail("Got unexpected message in queue_1: " + extra.body) + except Empty: None + + + # + # cleanup + # + params.unbind_queue(self._brokers[0].client_session, "fedX1", "fedX") + self._brokers[0].client_session.message_cancel(destination="f1") + self._brokers[0].client_session.queue_delete(queue="fedX1") + + params.unbind_queue(self._brokers[1].client_session, "fedX1", "fedX") + self._brokers[1].client_session.message_cancel(destination="f1") + self._brokers[1].client_session.queue_delete(queue="fedX1") + + for _b in qmf.getObjects(_class="bridge"): + result = _b.close() + self.assertEqual(result.status, 0) + + for _l in qmf.getObjects(_class="link"): + result = _l.close() + self.assertEqual(result.status, 0) + + for _b in self._brokers[0:2]: + _b.client_session.exchange_delete(exchange="fedX") + + self._teardown_brokers() + + self.verify_cleanup() + + diff --git a/cpp/src/tests/federation_sys.py b/cpp/src/tests/federation_sys.py new file mode 100755 index 0000000000..11590f684e --- /dev/null +++ b/cpp/src/tests/federation_sys.py @@ -0,0 +1,1900 @@ +#!/usr/bin/env python +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from inspect import stack +from qpid import messaging +from qpid.messaging import Message +from qpid.messaging.exceptions import Empty +from qpid.testlib import TestBase010 +from random import randint +from sys import stdout +from time import sleep + + +class Enum(object): + def __init__(self, **entries): + self.__dict__.update(entries) + def __repr__(self): + args = ['%s=%s' % (k, repr(v)) for (k,v) in vars(self).items()] + return 'Enum(%s)' % ', '.join(args) + + +class QmfTestBase010(TestBase010): + + _brokers = [] + _links = [] + _bridges = [] + _alt_exch_ops = Enum(none=0, create=1, delete=2) + + class _Broker(object): + """ + This broker proxy object holds the Qmf proxy to a broker of known address as well as the QMF broker + object, connection and sessions to the broker. + """ + def __init__(self, url): + self.url = url # format: "host:port" + url_parts = url.split(':') + self.host = url_parts[0] + self.port = int(url_parts[1]) + self.qmf_broker = None + self.connection = messaging.Connection.establish(self.url) + self.sessions = [] + def __str__(self): + return "_Broker %s:%s (%d open sessions)" % (self.host, self.port, len(self.sessions)) + def destroy(self, qmf = None): + if qmf is not None: + qmf.delBroker(self.qmf_broker.getBroker()) + for session in self.sessions: + try: # Session may have been closed by broker error + session.close() + except Exception, e: print "WARNING: %s: Unable to close session %s (%s): %s %s" % (self, session, hex(id(session)), type(e), e) + try: # Connection may have been closed by broker error + self.connection.close() + except Exception, e: print "WARNING: %s: Unable to close connection %s (%s): %s %s" % (self, self.connection, hex(id(self.connection)), type(e), e) + def session(self, name, transactional_flag = False): + session = self.connection.session(name, transactional_flag) + self.sessions.append(session) + return session + + def setUp(self): + """ + Called one before each test starts + """ + TestBase010.setUp(self) + self.startQmf(); + + def tearDown(self): + """ + Called once after each test competes. Close all Qmf objects (bridges, links and brokers) + """ + while len(self._bridges): + self._bridges.pop().close() + while len(self._links): + self._links.pop().close() + while len(self._brokers): + b = self._brokers.pop() + if len(self._brokers) <= 1: + b.destroy(None) + else: + b.destroy(self.qmf) + TestBase010.tearDown(self) + self.qmf.close() + + #--- General test utility functions + + def _get_name(self): + """ + Return the name of method which called this method stripped of "test_" prefix. Used for naming + queues and exchanges on a per-test basis. + """ + return stack()[1][3][5:] + + def _get_broker_port(self, key): + """ + Get the port of a broker defined in the environment using -D<key>=portno + """ + return int(self.defines[key]) + + def _get_cluster_ports(self, key): + """ + Get the cluster ports from the parameters of the test which place it in the environment using + -D<key>="port0 port1 ... portN" (space-separated) + """ + ports = [] + ports_str = self.defines[key] + if ports_str: + for p in ports_str.split(): + ports.append(int(p)) + return ports + + def _get_send_address(self, exch_name, queue_name): + """ + Get an address to which to send messages based on the exchange name and queue name, but taking into account + that the exchange name may be "" (the default exchange), in whcih case the format changes slightly. + """ + if len(exch_name) == 0: # Default exchange + return queue_name + return "%s/%s" % (exch_name, queue_name) + + def _get_broker(self, cluster_flag, broker_port_key, cluster_ports_key): + """ + Read the port numbers for pre-started brokers from the environment using keys, then find or create and return + the Qmf broker proxy for the appropriate broker + """ + if cluster_flag: + port = self._get_cluster_ports(cluster_ports_key)[0] # Always use the first node in the cluster + else: + port = self._get_broker_port(broker_port_key) + return self._find_create_broker("localhost:%s" % port) + + def _get_msg_subject(self, topic_key): + """ + Return an appropriate subject for sending a message to a known topic. Return None if there is no topic. + """ + if len(topic_key) == 0: return None + if "*" in topic_key: return topic_key.replace("*", "test") + if "#" in topic_key: return topic_key.replace("#", "multipart.test") + return topic_key + + def _send_msgs(self, session_name, broker, addr, msg_count, msg_content = "Message_%03d", topic_key = "", + msg_durable_flag = False, enq_txn_size = 0): + """ + Send messages to a broker using address addr + """ + send_session = broker.session(session_name, transactional_flag = enq_txn_size > 0) + sender = send_session.sender(addr) + txn_cnt = 0 + for i in range(0, msg_count): + sender.send(Message(msg_content % (i + 1), subject = self._get_msg_subject(topic_key), durable = msg_durable_flag)) + if enq_txn_size > 0: + txn_cnt += 1 + if txn_cnt >= enq_txn_size: + send_session.commit() + txn_cnt = 0 + if enq_txn_size > 0 and txn_cnt > 0: + send_session.commit() + sender.close() + send_session.close() + + def _receive_msgs(self, session_name, broker, addr, msg_count, msg_content = "Message_%03d", deq_txn_size = 0, + timeout = 0): + """ + Receive messages from a broker + """ + receive_session = broker.session(session_name, transactional_flag = deq_txn_size > 0) + receiver = receive_session.receiver(addr) + txn_cnt = 0 + for i in range(0, msg_count): + try: + msg = receiver.fetch(timeout = timeout) + if deq_txn_size > 0: + txn_cnt += 1 + if txn_cnt >= deq_txn_size: + receive_session.commit() + txn_cnt = 0 + receive_session.acknowledge() + except Empty: + if deq_txn_size > 0: receive_session.rollback() + receiver.close() + receive_session.close() + if i == 0: + self.fail("Broker %s queue \"%s\" is empty" % (broker.qmf_broker.getBroker().getUrl(), addr)) + else: + self.fail("Unable to receive message %d from broker %s queue \"%s\"" % (i, broker.qmf_broker.getBroker().getUrl(), addr)) + if msg.content != msg_content % (i + 1): + receiver.close() + receive_session.close() + self.fail("Unexpected message \"%s\", was expecting \"%s\"." % (msg.content, msg_content % (i + 1))) + try: + msg = receiver.fetch(timeout = 0) + if deq_txn_size > 0: receive_session.rollback() + receiver.close() + receive_session.close() + self.fail("Extra message \"%s\" found on broker %s address \"%s\"" % (msg.content, broker.qmf_broker.getBroker().getUrl(), addr)) + except Empty: + pass + if deq_txn_size > 0 and txn_cnt > 0: + receive_session.commit() + receiver.close() + receive_session.close() + + #--- QMF-specific utility functions + + def _get_qmf_property(self, props, key): + """ + Get the value of a named property key kj from a property list [(k0, v0), (k1, v1), ... (kn, vn)]. + """ + for k,v in props: + if k.name == key: + return v + return None + + def _check_qmf_return(self, method_result): + """ + Check the result of a Qmf-defined method call + """ + self.assertTrue(method_result.status == 0, method_result.text) + + def _check_optional_qmf_property(self, qmf_broker, type, qmf_object, key, expected_val, obj_ref_flag): + """ + Optional Qmf properties don't show up in the properties list when they are not specified. Checks for + these property types involve searching the properties list and making sure it is present or not as + expected. + """ + val = self._get_qmf_property(qmf_object.getProperties(), key) + if val is None: + if len(expected_val) > 0: + self.fail("%s %s exists, but has does not have %s property. Expected value: \"%s\"" % + (type, qmf_object.name, key, expected_val)) + else: + if len(expected_val) > 0: + if obj_ref_flag: + obj = self.qmf.getObjects(_objectId = val, _broker = qmf_broker.getBroker()) + self.assertEqual(len(obj), 1, "More than one object with the same objectId: %s" % obj) + val = obj[0].name + self.assertEqual(val, expected_val, "%s %s exists, but has incorrect %s property. Found \"%s\", expected \"%s\"" % + (type, qmf_object.name, key, val, expected_val)) + else: + self.fail("%s %s exists, but has an unexpected %s property \"%s\" set." % (type, qmf_object.name, key, val)) + + #--- Find/create Qmf broker objects + + def _find_qmf_broker(self, url): + """ + Find the Qmf broker object for the given broker URL. The broker must have been previously added to Qmf through + addBroker() + """ + for b in self.qmf.getObjects(_class="broker"): + if b.getBroker().getUrl() == url: + return b + return None + + def _find_create_broker(self, url): + """ + Find a running broker through Qmf. If it does not exist, add it (assuming the broker is already running). + """ + broker = self._Broker(url) + self._brokers.append(broker) + if self.qmf is not None: + qmf_broker = self._find_qmf_broker(broker.url) + if qmf_broker is None: + self.qmf.addBroker(broker.url) + broker.qmf_broker = self._find_qmf_broker(broker.url) + else: + broker.qmf_broker = qmf_broker + return broker + + #--- Find/create/delete exchanges + + def _find_qmf_exchange(self, qmf_broker, name, type, alternate, durable, auto_delete): + """ + Find Qmf exchange object + """ + for e in self.qmf.getObjects(_class="exchange", _broker = qmf_broker.getBroker()): + if e.name == name: + if len(name) == 0 or (len(name) >= 4 and name[:4] == "amq."): return e # skip checks for special exchanges + self.assertEqual(e.type, type, + "Exchange \"%s\" exists, but is of unexpected type %s; expected type %s." % + (name, e.type, type)) + self._check_optional_qmf_property(qmf_broker, "Exchange", e, "altExchange", alternate, True) + self.assertEqual(e.durable, durable, + "Exchange \"%s\" exists, but has incorrect durability. Found durable=%s, expected durable=%s" % + (name, e.durable, durable)) + self.assertEqual(e.autoDelete, auto_delete, + "Exchange \"%s\" exists, but has incorrect auto-delete property. Found %s, expected %s" % + (name, e.autoDelete, auto_delete)) + return e + return None + + def _find_create_qmf_exchange(self, qmf_broker, name, type, alternate, durable, auto_delete, args): + """ + Find Qmf exchange object if exchange exists, create exchange and return its Qmf object if not + """ + e = self._find_qmf_exchange(qmf_broker, name, type, alternate, durable, auto_delete) + if e is not None: return e + # Does not exist, so create it + props = dict({"exchange-type": type, "type": type, "durable": durable, "auto-delete": auto_delete, "alternate-exchange": alternate}, **args) + self._check_qmf_return(qmf_broker.create(type="exchange", name=name, properties=props, strict=True)) + e = self._find_qmf_exchange(qmf_broker, name, type, alternate, durable, auto_delete) + self.assertNotEqual(e, None, "Creation of exchange %s on broker %s failed" % (name, qmf_broker.getBroker().getUrl())) + return e + + def _find_delete_qmf_exchange(self, qmf_broker, name, type, alternate, durable, auto_delete): + """ + Find and delete Qmf exchange object if it exists + """ + e = self._find_qmf_exchange(qmf_broker, name, type, alternate, durable, auto_delete) + if e is not None and not auto_delete: + self._check_qmf_return(qmf_broker.delete(type="exchange", name=name, options={})) + + #--- Find/create/delete queues + + def _find_qmf_queue(self, qmf_broker, name, alternate_exchange, durable, exclusive, auto_delete): + """ + Find a Qmf queue object + """ + for q in self.qmf.getObjects(_class="queue", _broker = qmf_broker.getBroker()): + if q.name == name: + self._check_optional_qmf_property(qmf_broker, "Queue", q, "altExchange", alternate_exchange, True) + self.assertEqual(q.durable, durable, + "Queue \"%s\" exists, but has incorrect durable property. Found %s, expected %s" % + (name, q.durable, durable)) + self.assertEqual(q.exclusive, exclusive, + "Queue \"%s\" exists, but has incorrect exclusive property. Found %s, expected %s" % + (name, q.exclusive, exclusive)) + self.assertEqual(q.autoDelete, auto_delete, + "Queue \"%s\" exists, but has incorrect auto-delete property. Found %s, expected %s" % + (name, q.autoDelete, auto_delete)) + return q + return None + + def _find_create_qmf_queue(self, qmf_broker, name, alternate_exchange, durable, exclusive, auto_delete, args): + """ + Find Qmf queue object if queue exists, create queue and return its Qmf object if not + """ + q = self._find_qmf_queue(qmf_broker, name, alternate_exchange, durable, exclusive, auto_delete) + if q is not None: return q + # Queue does not exist, so create it + props = dict({"durable": durable, "auto-delete": auto_delete, "exclusive": exclusive, "alternate-exchange": alternate_exchange}, **args) + self._check_qmf_return(qmf_broker.create(type="queue", name=name, properties=props, strict=True)) + q = self._find_qmf_queue(qmf_broker, name, alternate_exchange, durable, exclusive, auto_delete) + self.assertNotEqual(q, None, "Creation of queue %s on broker %s failed" % (name, qmf_broker.getBroker().getUrl())) + return q + + def _find_delete_qmf_queue(self, qmf_broker, name, alternate_exchange, durable, exclusive, auto_delete, args): + """ + Find and delete Qmf queue object if it exists + """ + q = self._find_qmf_queue(qmf_broker, name, alternate_exchange, durable, exclusive, auto_delete) + if q is not None and not auto_delete: + self._check_qmf_return(qmf_broker.delete(type="queue", name=name, options={})) + + #--- Find/create/delete bindings (between an exchange and a queue) + + def _find_qmf_binding(self, qmf_broker, qmf_exchange, qmf_queue, binding_key, binding_args): + """ + Find a Qmf binding object + """ + for b in self.qmf.getObjects(_class="binding", _broker = qmf_broker.getBroker()): + if b.exchangeRef == qmf_exchange.getObjectId() and b.queueRef == qmf_queue.getObjectId(): + if qmf_exchange.type != "fanout": # Fanout ignores the binding key, and always returns "" as the key + self.assertEqual(b.bindingKey, binding_key, + "Binding between exchange %s and queue %s exists, but has mismatching binding key: Found %s, expected %s." % + (qmf_exchange.name, qmf_queue.name, b.bindingKey, binding_key)) + self.assertEqual(b.arguments, binding_args, + "Binding between exchange %s and queue %s exists, but has mismatching arguments: Found %s, expected %s" % + (qmf_exchange.name, qmf_queue.name, b.arguments, binding_args)) + return b + return None + + def _find_create_qmf_binding(self, qmf_broker, qmf_exchange, qmf_queue, binding_key, binding_args): + """ + Find Qmf binding object if it exists, create binding and return its Qmf object if not + """ + b = self._find_qmf_binding(qmf_broker, qmf_exchange, qmf_queue, binding_key, binding_args) + if b is not None: return b + # Does not exist, so create it + self._check_qmf_return(qmf_broker.create(type="binding", name="%s/%s/%s" % (qmf_exchange.name, qmf_queue.name, binding_key), properties=binding_args, strict=True)) + b = self._find_qmf_binding(qmf_broker, qmf_exchange, qmf_queue, binding_key, binding_args) + self.assertNotEqual(b, None, "Creation of binding between exchange %s and queue %s with key %s failed" % + (qmf_exchange.name, qmf_queue.name, binding_key)) + return b + + def _find_delete_qmf_binding(self, qmf_broker, qmf_exchange, qmf_queue, binding_key, binding_args): + """ + Find and delete Qmf binding object if it exists + """ + b = self._find_qmf_binding(qmf_broker, qmf_exchange, qmf_queue, binding_key, binding_args) + if b is not None: + if len(qmf_exchange.name) > 0: # not default exchange + self._check_qmf_return(qmf_broker.delete(type="binding", name="%s/%s/%s" % (qmf_exchange.name, qmf_queue.name, binding_key), options={})) + + #--- Find/create a link + + def _find_qmf_link(self, qmf_from_broker_proxy, host, port): + """ + Find a Qmf link object + """ + for l in self.qmf.getObjects(_class="link", _broker=qmf_from_broker_proxy): + if l.host == host and l.port == port: + return l + return None + + def _find_create_qmf_link(self, qmf_from_broker, qmf_to_broker_proxy, link_durable_flag, auth_mechanism, user_id, + password, transport, pause_interval, link_ready_timeout): + """ + Find a Qmf link object if it exists, create it and return its Qmf link object if not + """ + to_broker_host = qmf_to_broker_proxy.host + to_broker_port = qmf_to_broker_proxy.port + l = self._find_qmf_link(qmf_from_broker.getBroker(), to_broker_host, to_broker_port) + if l is not None: return l + # Does not exist, so create it + self._check_qmf_return(qmf_from_broker.connect(to_broker_host, to_broker_port, link_durable_flag, auth_mechanism, user_id, password, transport)) + l = self._find_qmf_link(qmf_from_broker.getBroker(), to_broker_host, to_broker_port) + self.assertNotEqual(l, None, "Creation of link from broker %s to broker %s failed" % + (qmf_from_broker.getBroker().getUrl(), qmf_to_broker_proxy.getUrl())) + self._wait_for_link(l, pause_interval, link_ready_timeout) + return l + + def _wait_for_link(self, link, pause_interval, link_ready_timeout): + """ + Wait for link to become active (state=Operational) + """ + tot_time = 0 + link.update() + if link.state == "": + # Link mgmt updates for the c++ link object are disabled when in a cluster because of inconsistent state: + # one is "Operational", the other "Passive". In this case, wait a bit and hope for the best... + sleep(2*pause_interval) + else: + while link.state != "Operational" and tot_time < link_ready_timeout: + sleep(pause_interval) + tot_time += pause_interval + link.update() + self.assertEqual(link.state, "Operational", "Timeout: Link not operational, state=%s" % link.state) + + #--- Find/create a bridge + + def _find_qmf_bridge(self, qmf_broker_proxy, qmf_link, source, destination, key): + """ + Find a Qmf link object + """ + for b in self.qmf.getObjects(_class="bridge", _broker=qmf_broker_proxy): + if b.linkRef == qmf_link.getObjectId() and b.src == source and b.dest == destination and b.key == key: + return b + return None + + def _find_create_qmf_bridge(self, qmf_broker_proxy, qmf_link, queue_name, exch_name, topic_key, + queue_route_type_flag, bridge_durable_flag): + """ + Find a Qmf bridge object if it exists, create it and return its Qmf object if not + """ + if queue_route_type_flag: + src = queue_name + dest = exch_name + key = "" + else: + src = exch_name + dest = exch_name + if len(topic_key) > 0: + key = topic_key + else: + key = queue_name + b = self._find_qmf_bridge(qmf_broker_proxy, qmf_link, src, dest, key) + if b is not None: + return b + # Does not exist, so create it + self._check_qmf_return(qmf_link.bridge(bridge_durable_flag, src, dest, key, "", "", queue_route_type_flag, False, False, 1)) + b = self._find_qmf_bridge(qmf_broker_proxy, qmf_link, src, dest, key) + self.assertNotEqual(b, None, "Bridge creation failed: src=%s dest=%s key=%s" % (src, dest, key)) + return b + + def _wait_for_bridge(self, bridge, src_broker, dest_broker, exch_name, queue_name, topic_key, pause_interval, + bridge_ready_timeout): + """ + Wait for bridge to become active by sending messages over the bridge at 1 sec intervals until they are + observed at the destination. + """ + tot_time = 0 + active = False + send_session = src_broker.session("tx") + sender = send_session.sender(self._get_send_address(exch_name, queue_name)) + src_receive_session = src_broker.session("src_rx") + src_receiver = src_receive_session.receiver(queue_name) + dest_receive_session = dest_broker.session("dest_rx") + dest_receiver = dest_receive_session.receiver(queue_name) + while not active and tot_time < bridge_ready_timeout: + sender.send(Message("xyz123", subject = self._get_msg_subject(topic_key))) + try: + src_receiver.fetch(timeout = 0) + src_receive_session.acknowledge() + # Keep receiving msgs, as several may have accumulated + while True: + dest_receiver.fetch(timeout = 0) + dest_receive_session.acknowledge() + sleep(1) + active = True + except Empty: + sleep(pause_interval) + tot_time += pause_interval + dest_receiver.close() + dest_receive_session.close() + src_receiver.close() + src_receive_session.close() + sender.close() + send_session.close() + self.assertTrue(active, "Bridge failed to become active after %ds: %s" % (bridge_ready_timeout, bridge)) + + #--- Find/create/delete utility functions + + def _create_and_bind(self, qmf_broker, exchange_args, queue_args, binding_args): + """ + Create a binding between a named exchange and queue on a broker + """ + e = self._find_create_qmf_exchange(qmf_broker, **exchange_args) + q = self._find_create_qmf_queue(qmf_broker, **queue_args) + return self._find_create_qmf_binding(qmf_broker, e, q, **binding_args) + + def _check_alt_exchange(self, qmf_broker, alt_exch_name, alt_exch_type, alt_exch_op): + """ + Check for existence of alternate exchange. Return the Qmf exchange proxy object for the alternate exchange + """ + if len(alt_exch_name) == 0: return None + if alt_exch_op == _alt_exch_ops.create: + return self._find_create_qmf_exchange(qmf_broker=qmf_broker, name=alt_exch_name, type=alt_exch_type, + alternate="", durable=False, auto_delete=False, args={}) + if alt_exch_op == _alt_exch_ops.delete: + return self._find_delete_qmf_exchange(qmf_broker=qmf_broker, name=alt_exch_name, type=alt_exch_type, + alternate="", durable=False, auto_delete=False) + return self._find_qmf_exchange(qmf_broker=qmf_broker, name=alt_exchange_name, type=alt_exchange_type, + alternate="", durable=False, auto_delete=False) + + def _delete_queue_binding(self, qmf_broker, exchange_args, queue_args, binding_args): + """ + Delete a queue and the binding between it and the exchange + """ + e = self._find_qmf_exchange(qmf_broker, exchange_args["name"], exchange_args["type"], exchange_args["alternate"], exchange_args["durable"], exchange_args["auto_delete"]) + q = self._find_qmf_queue(qmf_broker, queue_args["name"], queue_args["alternate_exchange"], queue_args["durable"], queue_args["exclusive"], queue_args["auto_delete"]) + self._find_delete_qmf_binding(qmf_broker, e, q, **binding_args) + self._find_delete_qmf_queue(qmf_broker, **queue_args) + + def _create_route(self, queue_route_type_flag, src_broker, dest_broker, exch_name, queue_name, topic_key, + link_durable_flag, bridge_durable_flag, auth_mechanism, user_id, password, transport, + pause_interval = 1, link_ready_timeout = 20, bridge_ready_timeout = 20): + """ + Create a route from a source broker to a destination broker + """ + l = self._find_create_qmf_link(dest_broker.qmf_broker, src_broker.qmf_broker.getBroker(), link_durable_flag, + auth_mechanism, user_id, password, transport, pause_interval, link_ready_timeout) + self._links.append(l) + b = self._find_create_qmf_bridge(dest_broker.qmf_broker.getBroker(), l, queue_name, exch_name, topic_key, + queue_route_type_flag, bridge_durable_flag) + self._bridges.append(b) + self._wait_for_bridge(b, src_broker, dest_broker, exch_name, queue_name, topic_key, pause_interval, bridge_ready_timeout) + + # Parameterized test - entry point for tests + + def _do_test(self, + test_name, # Name of test + exch_name = "amq.direct", # Remote exchange name + exch_type = "direct", # Remote exchange type + exch_alt_exch = "", # Remote exchange alternate exchange + exch_alt_exch_type = "direct", # Remote exchange alternate exchange type + exch_durable_flag = False, # Remote exchange durability + exch_auto_delete_flag = False, # Remote exchange auto-delete property + exch_x_args = {}, # Remote exchange args + queue_alt_exch = "", # Remote queue alternate exchange + queue_alt_exch_type = "direct", # Remote queue alternate exchange type + queue_durable_flag = False, # Remote queue durability + queue_exclusive_flag = False, # Remote queue exclusive property + queue_auto_delete_flag = False, # Remote queue auto-delete property + queue_x_args = {}, # Remote queue args + binding_durable_flag = False, # Remote binding durability + binding_x_args = {}, # Remote binding args + topic_key = "", # Binding key For remote topic exchanges only + msg_count = 10, # Number of messages to send + msg_durable_flag = False, # Message durability + link_durable_flag = False, # Route link durability + bridge_durable_flag = False, # Route bridge durability + queue_route_type_flag = False, # Route type: false = bridge route, true = queue route + enq_txn_size = 0, # Enqueue transaction size, 0 = no transactions + deq_txn_size = 0, # Dequeue transaction size, 0 = no transactions + local_cluster_flag = False, # Use a node from the local cluster, otherwise use single local broker + remote_cluster_flag = False, # Use a node from the remote cluster, otherwise use single remote broker + alt_exch_op = _alt_exch_ops.create,# Op on alt exch [create (ensure present), delete (ensure not present), none (neither create nor delete)] + auth_mechanism = "", # Authorization mechanism for linked broker + user_id = "", # User ID for authorization on linked broker + password = "", # Password for authorization on linked broker + transport = "tcp" # Transport for route to linked broker + ): + """ + Parameterized federation test. Sets up a federated link between a source broker and a destination broker and + checks that messages correctly pass over the link to the destination. Where appropriate (non-queue-routes), also + checks for the presence of messages on the source broker. + + In these tests, the concept is to create a LOCAL broker, then create a link to a REMOTE broker using federation. + In other words, the messages sent to the LOCAL broker will be replicated on the REMOTE broker, and tests are + performed on the REMOTE broker to check that the required messages are present. In the case of regular routes, + the LOCAL broker will also retain the messages, and a similar test is performed on this broker. + + TODO: There are several items to improve here: + 1. _do_test() is rather general. Rather create a version for each exchange type and test the exchange/queue + interaction in more detail based on the exchange type + 2. Add a headers and an xml exchange type + 3. Restructure the tests to start and stop brokers and clusters directly rather than relying on previously + started brokers. Then persistence can be checked by stopping and restarting the brokers/clusters. In particular, + test the persistence of links and bridges, both of which take a persistence flag. + 4. Test the behavior of the alternate exchanges when messages are sourced through a link. Also check behavior + when the alternate exchange is not present or is deleted after the reference is made. + 5. Test special queue types (eg LVQ) + """ + local_broker = self._get_broker(local_cluster_flag, "local-port", "local-cluster-ports") + remote_broker = self._get_broker(remote_cluster_flag, "remote-port", "remote-cluster-ports") + + # Check alternate exchanges exist (and create them if not) on both local and remote brokers + self._check_alt_exchange(local_broker.qmf_broker, exch_alt_exch, exch_alt_exch_type, alt_exch_op) + self._check_alt_exchange(local_broker.qmf_broker, queue_alt_exch, queue_alt_exch_type, alt_exch_op) + self._check_alt_exchange(remote_broker.qmf_broker, exch_alt_exch, exch_alt_exch_type, alt_exch_op) + self._check_alt_exchange(remote_broker.qmf_broker, queue_alt_exch, queue_alt_exch_type, alt_exch_op) + + queue_name = "queue_%s" % test_name + exchange_args = {"name": exch_name, "type": exch_type, "alternate": exch_alt_exch, + "durable": exch_durable_flag, "auto_delete": exch_auto_delete_flag, "args": exch_x_args} + queue_args = {"name": queue_name, "alternate_exchange": queue_alt_exch, "durable": queue_durable_flag, + "exclusive": queue_exclusive_flag, "auto_delete": queue_auto_delete_flag, "args": queue_x_args} + binding_args = {"binding_args": binding_x_args} + if exch_type == "topic": + self.assertTrue(len(topic_key) > 0, "Topic exchange selected, but no topic key was set.") + binding_args["binding_key"] = topic_key + elif exch_type == "direct": + binding_args["binding_key"] = queue_name + else: + binding_args["binding_key"] = "" + self._create_and_bind(qmf_broker=local_broker.qmf_broker, exchange_args=exchange_args, queue_args=queue_args, binding_args=binding_args) + self._create_and_bind(qmf_broker=remote_broker.qmf_broker, exchange_args=exchange_args, queue_args=queue_args, binding_args=binding_args) + self._create_route(queue_route_type_flag, local_broker, remote_broker, exch_name, queue_name, topic_key, + link_durable_flag, bridge_durable_flag, auth_mechanism, user_id, password, transport) + + self._send_msgs("send_session", local_broker, addr = self._get_send_address(exch_name, queue_name), + msg_count = msg_count, topic_key = topic_key, msg_durable_flag = msg_durable_flag, enq_txn_size = enq_txn_size) + if not queue_route_type_flag: + self._receive_msgs("local_receive_session", local_broker, addr = queue_name, msg_count = msg_count, deq_txn_size = deq_txn_size) + self._receive_msgs("remote_receive_session", remote_broker, addr = queue_name, msg_count = msg_count, deq_txn_size = deq_txn_size, timeout = 5) + + # Clean up + self._delete_queue_binding(qmf_broker=local_broker.qmf_broker, exchange_args=exchange_args, queue_args=queue_args, binding_args=binding_args) + self._delete_queue_binding(qmf_broker=remote_broker.qmf_broker, exchange_args=exchange_args, queue_args=queue_args, binding_args=binding_args) + +class A_ShortTests(QmfTestBase010): + + def test_route_defaultExch(self): + self._do_test(self._get_name()) + + def test_queueRoute_defaultExch(self): + self._do_test(self._get_name(), queue_route_type_flag=True) + + +class A_LongTests(QmfTestBase010): + + def test_route_amqDirectExch(self): + self._do_test(self._get_name(), exch_name="amq.direct") + + def test_queueRoute_amqDirectExch(self): + self._do_test(self._get_name(), exch_name="amq.direct", queue_route_type_flag=True) + + + def test_route_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange") + + def test_queueRoute_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_route_type_flag=True) + + + def test_route_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout") + + def test_queueRoute_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_route_type_flag=True) + + + def test_route_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#") + + def test_queueRoute_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_route_type_flag=True) + + +class B_ShortTransactionTests(QmfTestBase010): + + def test_txEnq01_route_defaultExch(self): + self._do_test(self._get_name(), enq_txn_size=1) + + def test_txEnq01_queueRoute_defaultExch(self): + self._do_test(self._get_name(), queue_route_type_flag=True, enq_txn_size=1) + + def test_txEnq01_txDeq01_route_defaultExch(self): + self._do_test(self._get_name(), enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_queueRoute_defaultExch(self): + self._do_test(self._get_name(), queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1) + + +class B_LongTransactionTests(QmfTestBase010): + + def test_txEnq10_route_defaultExch(self): + self._do_test(self._get_name(), enq_txn_size=10, msg_count = 103) + + def test_txEnq10_queueRoute_defaultExch(self): + self._do_test(self._get_name(), queue_route_type_flag=True, enq_txn_size=10, msg_count = 103) + + + + + def test_txEnq01_route_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", enq_txn_size=1) + + def test_txEnq01_queueRoute_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_route_type_flag=True, enq_txn_size=1) + + def test_txEnq10_route_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", enq_txn_size=10, msg_count = 103) + + def test_txEnq10_queueRoute_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_route_type_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq01_txDeq01_route_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_queueRoute_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1) + + + def test_txEnq01_route_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", enq_txn_size=1) + + def test_txEnq01_queueRoute_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_route_type_flag=True, enq_txn_size=1) + + def test_txEnq10_route_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", enq_txn_size=10, msg_count = 103) + + def test_txEnq10_queueRoute_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_route_type_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq01_txDeq01_route_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_queueRoute_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1) + + + def test_txEnq01_route_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", enq_txn_size=1) + + def test_txEnq01_queueRoute_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_route_type_flag=True, enq_txn_size=1) + + def test_txEnq10_route_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", enq_txn_size=10, msg_count = 103) + + def test_txEnq10_queueRoute_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_route_type_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq01_txDeq01_route_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_queueRoute_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1) + + +class C_ShortClusterTests(QmfTestBase010): + + def test_locCluster_route_defaultExch(self): + self._do_test(self._get_name(), local_cluster_flag=True) + + def test_locCluster_queueRoute_defaultExch(self): + self._do_test(self._get_name(), queue_route_type_flag=True, local_cluster_flag=True) + + def test_remCluster_route_defaultExch(self): + self._do_test(self._get_name(), remote_cluster_flag=True) + + def test_remCluster_queueRoute_defaultExch(self): + self._do_test(self._get_name(), queue_route_type_flag=True, remote_cluster_flag=True) + + def test_locCluster_remCluster_route_defaultExch(self): + self._do_test(self._get_name(), local_cluster_flag=True, remote_cluster_flag=True) + + def test_locCluster_remCluster_queueRoute_defaultExch(self): + self._do_test(self._get_name(), queue_route_type_flag=True, local_cluster_flag=True, remote_cluster_flag=True) + + +class C_LongClusterTests(QmfTestBase010): + + def test_locCluster_route_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", local_cluster_flag=True) + + def test_locCluster_queueRoute_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_route_type_flag=True, local_cluster_flag=True) + + def test_remCluster_route_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", remote_cluster_flag=True) + + def test_remCluster_queueRoute_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_route_type_flag=True, remote_cluster_flag=True) + + def test_locCluster_remCluster_route_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", local_cluster_flag=True, remote_cluster_flag=True) + + def test_locCluster_remCluster_queueRoute_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_route_type_flag=True, local_cluster_flag=True, remote_cluster_flag=True) + + + def test_locCluster_route_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", local_cluster_flag=True) + + def test_locCluster_queueRoute_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_route_type_flag=True, local_cluster_flag=True) + + def test_remCluster_route_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", remote_cluster_flag=True) + + def test_remCluster_queueRoute_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_route_type_flag=True, remote_cluster_flag=True) + + def test_locCluster_remCluster_route_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", local_cluster_flag=True, remote_cluster_flag=True) + + def test_locCluster_remCluster_queueRoute_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_route_type_flag=True, local_cluster_flag=True, remote_cluster_flag=True) + + + def test_locCluster_route_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", local_cluster_flag=True) + + def test_locCluster_queueRoute_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_route_type_flag=True, local_cluster_flag=True) + + def test_remCluster_route_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", remote_cluster_flag=True) + + def test_remCluster_queueRoute_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_route_type_flag=True, remote_cluster_flag=True) + + def test_locCluster_remCluster_route_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", local_cluster_flag=True, remote_cluster_flag=True) + + def test_locCluster_remCluster_queueRoute_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_route_type_flag=True, local_cluster_flag=True, remote_cluster_flag=True) + + +class D_ShortClusterTransactionTests(QmfTestBase010): + + def test_txEnq01_locCluster_route_defaultExch(self): + self._do_test(self._get_name(), enq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_locCluster_queueRoute_defaultExch(self): + self._do_test(self._get_name(), queue_route_type_flag=True, enq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_route_defaultExch(self): + self._do_test(self._get_name(), enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_queueRoute_defaultExch(self): + self._do_test(self._get_name(), queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_remCluster_route_defaultExch(self): + self._do_test(self._get_name(), enq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_remCluster_queueRoute_defaultExch(self): + self._do_test(self._get_name(), queue_route_type_flag=True, enq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_remCluster_route_defaultExch(self): + self._do_test(self._get_name(), enq_txn_size=1, deq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_remCluster_queueRoute_defaultExch(self): + self._do_test(self._get_name(), queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_locCluster_remCluster_route_defaultExch(self): + self._do_test(self._get_name(), enq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_locCluster_remCluster_queueRoute_defaultExch(self): + self._do_test(self._get_name(), queue_route_type_flag=True, enq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_remCluster_route_defaultExch(self): + self._do_test(self._get_name(), enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_remCluster_queueRoute_defaultExch(self): + self._do_test(self._get_name(), queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + +class D_LongClusterTransactionTests(QmfTestBase010): + + def test_txEnq10_locCluster_route_defaultExch(self): + self._do_test(self._get_name(), enq_txn_size=10, msg_count = 103, local_cluster_flag=True) + + def test_txEnq10_locCluster_queueRoute_defaultExch(self): + self._do_test(self._get_name(), queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True) + + def test_txEnq10_remCluster_route_defaultExch(self): + self._do_test(self._get_name(), enq_txn_size=10, msg_count = 103, remote_cluster_flag=True) + + def test_txEnq10_remCluster_queueRoute_defaultExch(self): + self._do_test(self._get_name(), queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, remote_cluster_flag=True) + + def test_txEnq10_locCluster_remCluster_route_defaultExch(self): + self._do_test(self._get_name(), enq_txn_size=10, msg_count = 103, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq10_locCluster_remCluster_queueRoute_defaultExch(self): + self._do_test(self._get_name(), queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True, remote_cluster_flag=True) + + + def test_txEnq01_locCluster_route_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", enq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_locCluster_queueRoute_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_route_type_flag=True, enq_txn_size=1, local_cluster_flag=True) + + def test_txEnq10_locCluster_route_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", enq_txn_size=10, msg_count = 103, local_cluster_flag=True) + + def test_txEnq10_locCluster_queueRoute_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_route_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_queueRoute_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_remCluster_route_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", enq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_remCluster_queueRoute_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_route_type_flag=True, enq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq10_remCluster_route_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", enq_txn_size=10, msg_count = 103, remote_cluster_flag=True) + + def test_txEnq10_remCluster_queueRoute_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_remCluster_route_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", enq_txn_size=1, deq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_remCluster_queueRoute_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_locCluster_remCluster_route_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", enq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_locCluster_remCluster_queueRoute_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_route_type_flag=True, enq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq10_locCluster_remCluster_route_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", enq_txn_size=10, msg_count = 103, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq10_locCluster_remCluster_queueRoute_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_remCluster_route_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_remCluster_queueRoute_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + + def test_txEnq01_locCluster_route_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", enq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_locCluster_queueRoute_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_route_type_flag=True, enq_txn_size=1, local_cluster_flag=True) + + def test_txEnq10_locCluster_route_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", enq_txn_size=10, msg_count = 103, local_cluster_flag=True) + + def test_txEnq10_locCluster_queueRoute_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_route_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_queueRoute_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_remCluster_route_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", enq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_remCluster_queueRoute_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_route_type_flag=True, enq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq10_remCluster_route_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", enq_txn_size=10, msg_count = 103, remote_cluster_flag=True) + + def test_txEnq10_remCluster_queueRoute_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_remCluster_route_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", enq_txn_size=1, deq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_remCluster_queueRoute_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_locCluster_remCluster_route_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", enq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_locCluster_remCluster_queueRoute_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_route_type_flag=True, enq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq10_locCluster_remCluster_route_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", enq_txn_size=10, msg_count = 103, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq10_locCluster_remCluster_queueRoute_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_remCluster_route_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_remCluster_queueRoute_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + + def test_txEnq01_locCluster_route_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", enq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_locCluster_queueRoute_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_route_type_flag=True, enq_txn_size=1, local_cluster_flag=True) + + def test_txEnq10_locCluster_route_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", enq_txn_size=10, msg_count = 103, local_cluster_flag=True) + + def test_txEnq10_locCluster_queueRoute_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_route_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_queueRoute_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_remCluster_route_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", enq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_remCluster_queueRoute_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_route_type_flag=True, enq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq10_remCluster_route_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", enq_txn_size=10, msg_count = 103, remote_cluster_flag=True) + + def test_txEnq10_remCluster_queueRoute_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_remCluster_route_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", enq_txn_size=1, deq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_remCluster_queueRoute_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_locCluster_remCluster_route_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", enq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_locCluster_remCluster_queueRoute_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_route_type_flag=True, enq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq10_locCluster_remCluster_route_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", enq_txn_size=10, msg_count = 103, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq10_locCluster_remCluster_queueRoute_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_remCluster_route_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_remCluster_queueRoute_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + +class E_ShortPersistenceTests(QmfTestBase010): + + def test_route_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True) + + def test_route_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True) + + def test_queueRoute_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, queue_route_type_flag=True) + + def test_queueRoute_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True) + + +class E_LongPersistenceTests(QmfTestBase010): + + + def test_route_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True) + + def test_route_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True) + + def test_queueRoute_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, queue_route_type_flag=True) + + def test_queueRoute_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True) + + + def test_route_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True) + + def test_route_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True) + + def test_queueRoute_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, queue_route_type_flag=True) + + def test_queueRoute_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True) + + + def test_route_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True) + + def test_route_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True) + + def test_queueRoute_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, queue_route_type_flag=True) + + def test_queueRoute_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True) + + +class F_ShortPersistenceTransactionTests(QmfTestBase010): + + def test_txEnq01_route_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, enq_txn_size=1) + + def test_txEnq01_route_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1) + + def test_txEnq01_queueRoute_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1) + + def test_txEnq01_queueRoute_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1) + + def test_txEnq01_txDeq01_route_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_route_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_queueRoute_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_queueRoute_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1) + + +class F_LongPersistenceTransactionTests(QmfTestBase010): + + def test_txEnq10_route_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq10_route_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq10_queueRoute_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq10_queueRoute_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103) + + + + + def test_txEnq01_route_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, enq_txn_size=1) + + def test_txEnq01_route_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1) + + def test_txEnq01_queueRoute_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1) + + def test_txEnq01_queueRoute_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1) + + def test_txEnq10_route_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq10_route_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq10_queueRoute_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq10_queueRoute_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq01_txDeq01_route_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_route_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_queueRoute_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_queueRoute_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1) + + + def test_txEnq01_route_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, enq_txn_size=1) + + def test_txEnq01_route_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1) + + def test_txEnq01_queueRoute_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1) + + def test_txEnq01_queueRoute_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1) + + def test_txEnq10_route_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq10_route_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq10_queueRoute_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq10_queueRoute_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq01_txDeq01_route_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_route_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_queueRoute_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_queueRoute_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1) + + + def test_txEnq01_route_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, enq_txn_size=1) + + def test_txEnq01_route_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1) + + def test_txEnq01_queueRoute_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1) + + def test_txEnq01_queueRoute_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1) + + def test_txEnq10_route_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq10_route_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq10_queueRoute_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq10_queueRoute_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq01_txDeq01_route_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_route_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_queueRoute_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_queueRoute_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1) + + +class G_ShortPersistenceClusterTests(QmfTestBase010): + + def test_locCluster_route_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, local_cluster_flag=True) + + def test_locCluster_route_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, local_cluster_flag=True) + + def test_locCluster_queueRoute_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, queue_route_type_flag=True, local_cluster_flag=True) + + def test_locCluster_queueRoute_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, local_cluster_flag=True) + + def test_remCluster_route_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, remote_cluster_flag=True) + + def test_remCluster_route_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, remote_cluster_flag=True) + + def test_remCluster_queueRoute_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, queue_route_type_flag=True, remote_cluster_flag=True) + + def test_remCluster_queueRoute_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, remote_cluster_flag=True) + + def test_locCluster_remCluster_route_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, local_cluster_flag=True, remote_cluster_flag=True) + + def test_locCluster_remCluster_route_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, local_cluster_flag=True, remote_cluster_flag=True) + + def test_locCluster_remCluster_queueRoute_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, queue_route_type_flag=True, local_cluster_flag=True, remote_cluster_flag=True) + + def test_locCluster_remCluster_queueRoute_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, local_cluster_flag=True, remote_cluster_flag=True) + + +class G_LongPersistenceClusterTests(QmfTestBase010): + + + + def test_locCluster_route_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, local_cluster_flag=True) + + def test_locCluster_route_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, local_cluster_flag=True) + + def test_locCluster_queueRoute_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, queue_route_type_flag=True, local_cluster_flag=True) + + def test_locCluster_queueRoute_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, local_cluster_flag=True) + + def test_remCluster_route_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, remote_cluster_flag=True) + + def test_remCluster_route_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, remote_cluster_flag=True) + + def test_remCluster_queueRoute_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, queue_route_type_flag=True, remote_cluster_flag=True) + + def test_remCluster_queueRoute_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, remote_cluster_flag=True) + + def test_locCluster_remCluster_route_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, local_cluster_flag=True, remote_cluster_flag=True) + + def test_locCluster_remCluster_route_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, local_cluster_flag=True, remote_cluster_flag=True) + + def test_locCluster_remCluster_queueRoute_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, queue_route_type_flag=True, local_cluster_flag=True, remote_cluster_flag=True) + + def test_locCluster_remCluster_queueRoute_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, local_cluster_flag=True, remote_cluster_flag=True) + + + def test_locCluster_route_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, local_cluster_flag=True) + + def test_locCluster_route_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, local_cluster_flag=True) + + def test_locCluster_queueRoute_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, queue_route_type_flag=True, local_cluster_flag=True) + + def test_locCluster_queueRoute_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, local_cluster_flag=True) + + def test_remCluster_route_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, remote_cluster_flag=True) + + def test_remCluster_route_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, remote_cluster_flag=True) + + def test_remCluster_queueRoute_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, queue_route_type_flag=True, remote_cluster_flag=True) + + def test_remCluster_queueRoute_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, remote_cluster_flag=True) + + def test_locCluster_remCluster_route_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, local_cluster_flag=True, remote_cluster_flag=True) + + def test_locCluster_remCluster_route_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, local_cluster_flag=True, remote_cluster_flag=True) + + def test_locCluster_remCluster_queueRoute_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, queue_route_type_flag=True, local_cluster_flag=True, remote_cluster_flag=True) + + def test_locCluster_remCluster_queueRoute_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, local_cluster_flag=True, remote_cluster_flag=True) + + + def test_locCluster_route_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, local_cluster_flag=True) + + def test_locCluster_route_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, local_cluster_flag=True) + + def test_locCluster_queueRoute_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, queue_route_type_flag=True, local_cluster_flag=True) + + def test_locCluster_queueRoute_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, local_cluster_flag=True) + + def test_remCluster_route_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, remote_cluster_flag=True) + + def test_remCluster_route_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, remote_cluster_flag=True) + + def test_remCluster_queueRoute_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, queue_route_type_flag=True, remote_cluster_flag=True) + + def test_remCluster_queueRoute_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, remote_cluster_flag=True) + + def test_locCluster_remCluster_route_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, local_cluster_flag=True, remote_cluster_flag=True) + + def test_locCluster_remCluster_route_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, local_cluster_flag=True, remote_cluster_flag=True) + + def test_locCluster_remCluster_queueRoute_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, queue_route_type_flag=True, local_cluster_flag=True, remote_cluster_flag=True) + + def test_locCluster_remCluster_queueRoute_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, local_cluster_flag=True, remote_cluster_flag=True) + + +class H_ShortPersistenceClusterTransactionTests(QmfTestBase010): + + def test_txEnq01_locCluster_route_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, enq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_locCluster_route_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_locCluster_queueRoute_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_locCluster_queueRoute_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_route_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_route_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_queueRoute_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_queueRoute_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_remCluster_route_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, enq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_remCluster_route_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_remCluster_queueRoute_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_remCluster_queueRoute_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_remCluster_route_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_remCluster_route_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_remCluster_queueRoute_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_remCluster_queueRoute_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_locCluster_remCluster_route_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, enq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_locCluster_remCluster_route_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_locCluster_remCluster_queueRoute_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_locCluster_remCluster_queueRoute_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_remCluster_route_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_remCluster_route_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_remCluster_queueRoute_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_remCluster_queueRoute_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + +class H_LongPersistenceClusterTransactionTests(QmfTestBase010): + + def test_txEnq10_locCluster_route_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True) + + def test_txEnq10_locCluster_route_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True) + + def test_txEnq10_locCluster_queueRoute_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True) + + def test_txEnq10_locCluster_queueRoute_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True) + + def test_txEnq10_remCluster_route_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, enq_txn_size=10, msg_count = 103, remote_cluster_flag=True) + + def test_txEnq10_remCluster_route_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=10, msg_count = 103, remote_cluster_flag=True) + + def test_txEnq10_remCluster_queueRoute_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, remote_cluster_flag=True) + + def test_txEnq10_remCluster_queueRoute_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, remote_cluster_flag=True) + + def test_txEnq10_locCluster_remCluster_route_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq10_locCluster_remCluster_route_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq10_locCluster_remCluster_queueRoute_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq10_locCluster_remCluster_queueRoute_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True, remote_cluster_flag=True) + + + + + def test_txEnq01_locCluster_route_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, enq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_locCluster_route_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_locCluster_queueRoute_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_locCluster_queueRoute_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, local_cluster_flag=True) + + def test_txEnq10_locCluster_route_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True) + + def test_txEnq10_locCluster_route_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True) + + def test_txEnq10_locCluster_queueRoute_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True) + + def test_txEnq10_locCluster_queueRoute_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_route_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_route_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_queueRoute_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_queueRoute_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_remCluster_route_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, enq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_remCluster_route_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_remCluster_queueRoute_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_remCluster_queueRoute_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq10_remCluster_route_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, enq_txn_size=10, msg_count = 103, remote_cluster_flag=True) + + def test_txEnq10_remCluster_route_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=10, msg_count = 103, remote_cluster_flag=True) + + def test_txEnq10_remCluster_queueRoute_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, remote_cluster_flag=True) + + def test_txEnq10_remCluster_queueRoute_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_remCluster_route_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_remCluster_route_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_remCluster_queueRoute_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_remCluster_queueRoute_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_locCluster_remCluster_route_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, enq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_locCluster_remCluster_route_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_locCluster_remCluster_queueRoute_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_locCluster_remCluster_queueRoute_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq10_locCluster_remCluster_route_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq10_locCluster_remCluster_route_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq10_locCluster_remCluster_queueRoute_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq10_locCluster_remCluster_queueRoute_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_remCluster_route_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_remCluster_route_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_remCluster_queueRoute_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_remCluster_queueRoute_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + + def test_txEnq01_locCluster_route_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, enq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_locCluster_route_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_locCluster_queueRoute_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_locCluster_queueRoute_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, local_cluster_flag=True) + + def test_txEnq10_locCluster_route_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True) + + def test_txEnq10_locCluster_route_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True) + + def test_txEnq10_locCluster_queueRoute_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True) + + def test_txEnq10_locCluster_queueRoute_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_route_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_route_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_queueRoute_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_queueRoute_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_remCluster_route_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, enq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_remCluster_route_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_remCluster_queueRoute_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_remCluster_queueRoute_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq10_remCluster_route_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, enq_txn_size=10, msg_count = 103, remote_cluster_flag=True) + + def test_txEnq10_remCluster_route_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=10, msg_count = 103, remote_cluster_flag=True) + + def test_txEnq10_remCluster_queueRoute_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, remote_cluster_flag=True) + + def test_txEnq10_remCluster_queueRoute_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_remCluster_route_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_remCluster_route_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_remCluster_queueRoute_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_remCluster_queueRoute_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_locCluster_remCluster_route_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, enq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_locCluster_remCluster_route_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_locCluster_remCluster_queueRoute_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_locCluster_remCluster_queueRoute_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq10_locCluster_remCluster_route_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq10_locCluster_remCluster_route_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq10_locCluster_remCluster_queueRoute_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq10_locCluster_remCluster_queueRoute_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_remCluster_route_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_remCluster_route_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_remCluster_queueRoute_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_remCluster_queueRoute_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + + def test_txEnq01_locCluster_route_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, enq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_locCluster_route_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_locCluster_queueRoute_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_locCluster_queueRoute_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, local_cluster_flag=True) + + def test_txEnq10_locCluster_route_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True) + + def test_txEnq10_locCluster_route_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True) + + def test_txEnq10_locCluster_queueRoute_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True) + + def test_txEnq10_locCluster_queueRoute_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_route_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_route_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_queueRoute_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_queueRoute_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True) + + def test_txEnq01_remCluster_route_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, enq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_remCluster_route_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_remCluster_queueRoute_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_remCluster_queueRoute_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq10_remCluster_route_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, enq_txn_size=10, msg_count = 103, remote_cluster_flag=True) + + def test_txEnq10_remCluster_route_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=10, msg_count = 103, remote_cluster_flag=True) + + def test_txEnq10_remCluster_queueRoute_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, remote_cluster_flag=True) + + def test_txEnq10_remCluster_queueRoute_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_remCluster_route_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_remCluster_route_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_remCluster_queueRoute_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_remCluster_queueRoute_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, remote_cluster_flag=True) + + def test_txEnq01_locCluster_remCluster_route_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, enq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_locCluster_remCluster_route_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_locCluster_remCluster_queueRoute_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_locCluster_remCluster_queueRoute_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq10_locCluster_remCluster_route_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq10_locCluster_remCluster_route_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq10_locCluster_remCluster_queueRoute_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq10_locCluster_remCluster_queueRoute_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_remCluster_route_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_remCluster_route_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_remCluster_queueRoute_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + + def test_txEnq01_txDeq01_locCluster_remCluster_queueRoute_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1, local_cluster_flag=True, remote_cluster_flag=True) + diff --git a/cpp/src/tests/ipv6_test b/cpp/src/tests/ipv6_test new file mode 100755 index 0000000000..d75d50fd0a --- /dev/null +++ b/cpp/src/tests/ipv6_test @@ -0,0 +1,150 @@ +#!/bin/bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Run a simple test over IPv6 +source ./test_env.sh + +CONFIG=$(dirname $0)/config.null +TEST_HOSTNAME=::1 +COUNT=10 + +trap cleanup EXIT + +error() { echo $*; exit 1; } + +# Don't need --no-module-dir or --no-data-dir as they are set as env vars in test_env.sh +COMMON_OPTS="--daemon --auth no --config $CONFIG" + +# Record all broker ports started +unset PORTS +declare -a PORTS + +# Start new brokers: +# $1 must be integer +# $2 = extra opts +# Append used ports to PORTS variable +start_brokers() { + local -a ports + for (( i=0; $i<$1; i++)) do + ports[$i]=$($QPIDD_EXEC --port 0 $COMMON_OPTS $2) + done + PORTS=( ${PORTS[@]} ${ports[@]} ) +} + +stop_brokers() { + for port in "${PORTS[@]}"; + do + $QPIDD_EXEC -qp $port + done + PORTS=() +} + +cleanup() { + stop_brokers +} + +start_brokers 1 +PORT=${PORTS[0]} +echo "Started IPv6 smoke perftest on broker port $PORT" + +## Test connection via connection settings +./qpid-perftest --count ${COUNT} --port ${PORT} -b $TEST_HOSTNAME --summary + +## Test connection with a URL +URL="amqp:[$TEST_HOSTNAME]:$PORT" + +./qpid-send -b $URL --content-string=hello -a "foo;{create:always}" +MSG=`./qpid-receive -b $URL -a "foo;{create:always}" --messages 1` +test "$MSG" = "hello" || { echo "receive failed '$MSG' != 'hello'"; exit 1; } + +stop_brokers + +# Federation smoke test follows + +# Start 2 brokers + +# In a distribution, the python tools will be absent. +if [ ! -f $QPID_CONFIG_EXEC ] || [ ! -f $QPID_ROUTE_EXEC ] ; then + echo "python tools absent - skipping federation test." +else + + start_brokers 2 + echo "Started Federated brokers on ports ${PORTS[*]}" + # Make broker urls + BROKER0="[::1]:${PORTS[0]}" + BROKER1="[::1]:${PORTS[1]}" + TEST_QUEUE=ipv6-fed-test + + $QPID_CONFIG_EXEC -a $BROKER0 add queue $TEST_QUEUE + $QPID_CONFIG_EXEC -a $BROKER1 add queue $TEST_QUEUE + $QPID_ROUTE_EXEC dynamic add $BROKER1 $BROKER0 amq.direct + $QPID_CONFIG_EXEC -a $BROKER1 bind amq.direct $TEST_QUEUE $TEST_QUEUE + $QPID_ROUTE_EXEC route map $BROKER1 + + ./datagen --count 100 | tee rdata-in | + ./qpid-send -b amqp:$BROKER0 -a amq.direct/$TEST_QUEUE --content-stdin + ./qpid-receive -b amqp:$BROKER1 -a $TEST_QUEUE --print-content yes -m 0 > rdata-out + + cmp rdata-in rdata-out || { echo "Federated data over IPv6 does not compare"; exit 1; } + + stop_brokers + rm rdata-in rdata-out +fi + +# Cluster smoke test follows +test -z $CLUSTER_LIB && exit 0 # Exit if cluster not supported. + +## Test failover in a cluster using IPv6 only +. $srcdir/ais_check # Will exit if clustering not enabled. + +pick_port() { + # We need a fixed port to set --cluster-url. Use qpidd to pick a free port. + # Note this method is racy + PICK=$($QPIDD_EXEC -dp0) + $QPIDD_EXEC -qp $PICK + echo $PICK +} + +ssl_cluster_broker() { # $1 = port + $QPIDD_EXEC $COMMON_OPTS --load-module $CLUSTER_LIB --cluster-name ipv6_test.$HOSTNAME.$$ --cluster-url amqp:[$TEST_HOSTNAME]:$1 --port $1 + # Wait for broker to be ready + ./qpid-ping -b $TEST_HOSTNAME -qp $1 || { echo "Cannot connect to broker on $1"; exit 1; } + echo "Running IPv6 cluster broker on port $1" +} + +PORT1=`pick_port`; ssl_cluster_broker $PORT1 +PORT2=`pick_port`; ssl_cluster_broker $PORT2 + +# Pipe receive output to uniq to remove duplicates +./qpid-receive --connection-options "{reconnect:true, reconnect-timeout:5}" --failover-updates -b amqp:[$TEST_HOSTNAME]:$PORT1 -a "foo;{create:always}" -f | uniq > ssl_test_receive.tmp & + +./qpid-send -b amqp:[$TEST_HOSTNAME]:$PORT2 --content-string=one -a "foo;{create:always}" + +$QPIDD_EXEC -qp $PORT1 # Kill broker 1 receiver should fail-over. +./qpid-send -b amqp:[$TEST_HOSTNAME]:$PORT2 --content-string=two -a "foo;{create:always}" --send-eos 1 +wait # Wait for qpid-receive +{ echo one; echo two; } > ssl_test_receive.cmp +diff ssl_test_receive.tmp ssl_test_receive.cmp || { echo "Failover failed"; exit 1; } + +$QPIDD_EXEC -qp $PORT2 + +rm -f ssl_test_receive.* + diff --git a/cpp/src/tests/msg_group_test.cpp b/cpp/src/tests/msg_group_test.cpp new file mode 100644 index 0000000000..6b9d09b89a --- /dev/null +++ b/cpp/src/tests/msg_group_test.cpp @@ -0,0 +1,618 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <qpid/messaging/Address.h> +#include <qpid/messaging/Connection.h> +#include <qpid/messaging/Receiver.h> +#include <qpid/messaging/Sender.h> +#include <qpid/messaging/Session.h> +#include <qpid/messaging/Message.h> +#include <qpid/messaging/FailoverUpdates.h> +#include <qpid/Options.h> +#include <qpid/log/Logger.h> +#include <qpid/log/Options.h> +#include "qpid/log/Statement.h" +#include "qpid/sys/Time.h" +#include "qpid/sys/Runnable.h" +#include "qpid/sys/Thread.h" +#include "qpid/sys/SystemInfo.h" + +#include <iostream> +#include <memory> +#include <stdlib.h> + +using namespace qpid::messaging; +using namespace qpid::types; +using namespace std; + +namespace qpid { +namespace tests { + +struct Options : public qpid::Options +{ + bool help; + std::string url; + std::string address; + std::string connectionOptions; + uint messages; + uint capacity; + uint ackFrequency; + bool failoverUpdates; + qpid::log::Options log; + uint senders; + uint receivers; + uint groupSize; + bool printReport; + std::string groupKey; + bool durable; + bool allowDuplicates; + bool randomizeSize; + bool stickyConsumer; + uint timeout; + uint interleave; + std::string prefix; + uint sendRate; + + Options(const std::string& argv0=std::string()) + : qpid::Options("Options"), + help(false), + url("amqp:tcp:127.0.0.1"), + messages(10000), + capacity(1000), + ackFrequency(100), + failoverUpdates(false), + log(argv0), + senders(2), + receivers(2), + groupSize(10), + printReport(false), + groupKey("qpid.no_group"), + durable(false), + allowDuplicates(false), + randomizeSize(false), + stickyConsumer(false), + timeout(10), + interleave(1), + sendRate(0) + { + addOptions() + ("ack-frequency", qpid::optValue(ackFrequency, "N"), "Ack frequency (0 implies none of the messages will get accepted)") + ("address,a", qpid::optValue(address, "ADDRESS"), "address to send and receive from") + ("allow-duplicates", qpid::optValue(allowDuplicates), "Ignore the delivery of duplicated messages") + ("broker,b", qpid::optValue(url, "URL"), "url of broker to connect to") + ("capacity", qpid::optValue(capacity, "N"), "Pre-fetch window (0 implies no pre-fetch)") + ("connection-options", qpid::optValue(connectionOptions, "OPTIONS"), "options for the connection") + ("durable", qpid::optValue(durable, "yes|no"), "Mark messages as durable.") + ("failover-updates", qpid::optValue(failoverUpdates), "Listen for membership updates distributed via amq.failover") + ("group-key", qpid::optValue(groupKey, "KEY"), "Key of the message header containing the group identifier.") + ("group-prefix", qpid::optValue(prefix, "STRING"), "Add 'prefix' to the start of all generated group identifiers.") + ("group-size", qpid::optValue(groupSize, "N"), "Number of messages per a group.") + ("interleave", qpid::optValue(interleave, "N"), "Simultaineously interleave messages from N different groups.") + ("messages,m", qpid::optValue(messages, "N"), "Number of messages to send per each sender.") + ("receivers,r", qpid::optValue(receivers, "N"), "Number of message consumers.") + ("randomize-group-size", qpid::optValue(randomizeSize), "Randomize the number of messages per group to [1...group-size].") + ("send-rate", qpid::optValue(sendRate,"N"), "Send at rate of N messages/second. 0 means send as fast as possible.") + ("senders,s", qpid::optValue(senders, "N"), "Number of message producers.") + ("sticky-consumers", qpid::optValue(stickyConsumer), "If set, verify that all messages in a group are consumed by the same client [TBD].") + ("timeout", qpid::optValue(timeout, "N"), "Fail with a stall error should all consumers remain idle for timeout seconds.") + ("print-report", qpid::optValue(printReport), "Dump message group statistics to stdout.") + ("help", qpid::optValue(help), "print this usage statement"); + add(log); + //("check-redelivered", qpid::optValue(checkRedelivered), "Fails with exception if a duplicate is not marked as redelivered (only relevant when ignore-duplicates is selected)") + //("tx", qpid::optValue(tx, "N"), "batch size for transactions (0 implies transaction are not used)") + //("rollback-frequency", qpid::optValue(rollbackFrequency, "N"), "rollback frequency (0 implies no transaction will be rolledback)") + } + + bool parse(int argc, char** argv) + { + try { + qpid::Options::parse(argc, argv); + if (address.empty()) throw qpid::Exception("Address must be specified!"); + qpid::log::Logger::instance().configure(log); + if (help) { + std::ostringstream msg; + std::cout << msg << *this << std::endl << std::endl + << "Verifies the behavior of grouped messages." << std::endl; + return false; + } else { + return true; + } + } catch (const std::exception& e) { + std::cerr << *this << std::endl << std::endl << e.what() << std::endl; + return false; + } + } +}; + +const string EOS("eos"); +const string SN("sn"); + + +// class that monitors group state across all publishers and consumers. tracks the next +// expected sequence for each group, and total messages consumed. +class GroupChecker +{ + qpid::sys::Mutex lock; + + const uint totalMsgs; + uint totalMsgsConsumed; + uint totalMsgsPublished; + bool allowDuplicates; + uint duplicateMsgs; + + typedef std::map<std::string, uint> SequenceMap; + SequenceMap sequenceMap; + + // Statistics - for each group, store the names of all clients that consumed messages + // from that group, and the number of messages consumed per client. + typedef std::map<std::string, uint> ClientCounter; + typedef std::map<std::string, ClientCounter> GroupStatistics; + GroupStatistics statistics; + +public: + + GroupChecker( uint t, bool d ) : + totalMsgs(t), totalMsgsConsumed(0), totalMsgsPublished(0), allowDuplicates(d), + duplicateMsgs(0) {} + + bool checkSequence( const std::string& groupId, + uint sequence, const std::string& client ) + { + qpid::sys::Mutex::ScopedLock l(lock); + + QPID_LOG(debug, "Client " << client << " has received " << groupId << ":" << sequence); + + GroupStatistics::iterator gs = statistics.find(groupId); + if (gs == statistics.end()) { + statistics[groupId][client] = 1; + } else { + gs->second[client]++; + } + // now verify + SequenceMap::iterator s = sequenceMap.find(groupId); + if (s == sequenceMap.end()) { + QPID_LOG(debug, "Client " << client << " thinks this is the first message from group " << groupId << ":" << sequence); + // if duplication allowed, it is possible that the last msg(s) of an old sequence are redelivered on reconnect. + // in this case, set the sequence from the first msg. + sequenceMap[groupId] = (allowDuplicates) ? sequence : 0; + s = sequenceMap.find(groupId); + } else if (sequence < s->second) { + duplicateMsgs++; + QPID_LOG(debug, "Client " << client << " thinks this message is a duplicate! " << groupId << ":" << sequence); + return allowDuplicates; + } + totalMsgsConsumed++; + return sequence == s->second++; + } + + void sendingSequence( const std::string& groupId, + uint sequence, bool eos, + const std::string& client ) + { + qpid::sys::Mutex::ScopedLock l(lock); + ++totalMsgsPublished; + + QPID_LOG(debug, "Client " << client << " sending " << groupId << ":" << sequence << + ((eos) ? " (last)" : "")); + } + + bool eraseGroup( const std::string& groupId, const std::string& name ) + { + qpid::sys::Mutex::ScopedLock l(lock); + QPID_LOG(debug, "Deleting group " << groupId << " (by client " << name << ")"); + return sequenceMap.erase( groupId ) == 1; + } + + uint getNextExpectedSequence( const std::string& groupId ) + { + qpid::sys::Mutex::ScopedLock l(lock); + return sequenceMap[groupId]; + } + + bool allMsgsConsumed() // true when done processing msgs + { + qpid::sys::Mutex::ScopedLock l(lock); + return (totalMsgsPublished >= totalMsgs) && + (totalMsgsConsumed >= totalMsgsPublished) && + sequenceMap.size() == 0; + } + + uint getConsumedTotal() + { + qpid::sys::Mutex::ScopedLock l(lock); + return totalMsgsConsumed; + } + + uint getPublishedTotal() + { + qpid::sys::Mutex::ScopedLock l(lock); + return totalMsgsPublished; + } + + ostream& print(ostream& out) + { + qpid::sys::Mutex::ScopedLock l(lock); + out << "Total Published: " << totalMsgsPublished << ", Total Consumed: " << totalMsgsConsumed << + ", Duplicates detected: " << duplicateMsgs << std::endl; + out << "Total Groups: " << statistics.size() << std::endl; + unsigned long consumers = 0; + for (GroupStatistics::iterator gs = statistics.begin(); gs != statistics.end(); ++gs) { + out << " GroupId: " << gs->first; + consumers += gs->second.size(); // # of consumers that processed this group + if (gs->second.size() == 1) + out << " completely consumed by a single client." << std::endl; + else + out << " consumed by " << gs->second.size() << " different clients." << std::endl; + + for (ClientCounter::iterator cc = gs->second.begin(); cc != gs->second.end(); ++cc) { + out << " Client: " << cc->first << " consumed " << cc->second << " messages from the group." << std::endl; + } + } + out << "Average # of consumers per group: " << ((statistics.size() != 0) ? (double(consumers)/statistics.size()) : 0) << std::endl; + return out; + } +}; + + +namespace { + // rand() is not thread safe. Create a singleton obj to hold a lock while calling + // rand() so it can be called safely by multiple concurrent clients. + class Randomizer { + qpid::sys::Mutex lock; + public: + uint operator()(uint max) { + qpid::sys::Mutex::ScopedLock l(lock); + return (rand() % max) + 1; + } + }; + + static Randomizer randomizer; +} + + +// tag each generated message with a group identifer +// +class GroupGenerator { + + const std::string groupPrefix; + const uint groupSize; + const bool randomizeSize; + const uint interleave; + + uint groupSuffix; + uint total; + + struct GroupState { + std::string id; + const uint size; + uint count; + GroupState( const std::string& i, const uint s ) + : id(i), size(s), count(0) {} + }; + typedef std::list<GroupState> GroupList; + GroupList groups; + GroupList::iterator current; + + // add a new group identifier to the list + void newGroup() { + std::ostringstream groupId(groupPrefix, ios_base::out|ios_base::ate); + groupId << std::string(":") << groupSuffix++; + uint size = (randomizeSize) ? randomizer(groupSize) : groupSize; + QPID_LOG(trace, "New group: GROUPID=[" << groupId.str() << "] size=" << size << " this=" << this); + GroupState group( groupId.str(), size ); + groups.push_back( group ); + } + +public: + GroupGenerator( const std::string& prefix, + const uint t, + const uint size, + const bool randomize, + const uint i) + : groupPrefix(prefix), groupSize(size), + randomizeSize(randomize), interleave(i), groupSuffix(0), total(t) + { + QPID_LOG(trace, "New group generator: PREFIX=[" << prefix << "] total=" << total << " size=" << size << " rand=" << randomize << " interleave=" << interleave << " this=" << this); + for (uint i = 0; i < 1 || i < interleave; ++i) { + newGroup(); + } + current = groups.begin(); + } + + bool genGroup(std::string& groupId, uint& seq, bool& eos) + { + if (!total) return false; + --total; + if (current == groups.end()) + current = groups.begin(); + groupId = current->id; + seq = current->count++; + if (current->count == current->size) { + QPID_LOG(trace, "Last msg for " << current->id << ", " << current->count << " this=" << this); + eos = true; + if (total >= interleave) { // need a new group to replace this one + newGroup(); + groups.erase(current++); + } else ++current; + } else { + ++current; + eos = total < interleave; // mark eos on the last message of each group + } + QPID_LOG(trace, "SENDING GROUPID=[" << groupId << "] seq=" << seq << " eos=" << eos << " this=" << this); + return true; + } +}; + + + +class Client : public qpid::sys::Runnable +{ +public: + typedef boost::shared_ptr<Client> shared_ptr; + enum State {ACTIVE, DONE, FAILURE}; + Client( const std::string& n, const Options& o ) : name(n), opts(o), state(ACTIVE), stopped(false) {} + virtual ~Client() {} + State getState() { return state; } + void testFailed( const std::string& reason ) { state = FAILURE; error << "Client '" << name << "' failed: " << reason; } + void clientDone() { if (state == ACTIVE) state = DONE; } + qpid::sys::Thread& getThread() { return thread; } + const std::string getErrorMsg() { return error.str(); } + void stop() {stopped = true;} + const std::string& getName() { return name; } + +protected: + const std::string name; + const Options& opts; + qpid::sys::Thread thread; + ostringstream error; + State state; + bool stopped; +}; + + +class Consumer : public Client +{ + GroupChecker& checker; + +public: + Consumer(const std::string& n, const Options& o, GroupChecker& c ) : Client(n, o), checker(c) {}; + virtual ~Consumer() {}; + + void run() + { + Connection connection; + try { + connection = Connection(opts.url, opts.connectionOptions); + connection.open(); + std::auto_ptr<FailoverUpdates> updates(opts.failoverUpdates ? new FailoverUpdates(connection) : 0); + Session session = connection.createSession(); + Receiver receiver = session.createReceiver(opts.address); + receiver.setCapacity(opts.capacity); + Message msg; + uint count = 0; + + while (!stopped) { + if (receiver.fetch(msg, Duration::SECOND)) { // msg retrieved + qpid::types::Variant::Map& properties = msg.getProperties(); + std::string groupId = properties[opts.groupKey]; + uint groupSeq = properties[SN]; + bool eof = properties[EOS]; + + QPID_LOG(trace, "RECVING GROUPID=[" << groupId << "] seq=" << groupSeq << " eos=" << eof << " name=" << name); + + qpid::sys::usleep(10); + + if (!checker.checkSequence( groupId, groupSeq, name )) { + ostringstream msg; + msg << "Check sequence failed. Group=" << groupId << " rcvd seq=" << groupSeq << " expected=" << checker.getNextExpectedSequence( groupId ); + testFailed( msg.str() ); + break; + } else if (eof) { + if (!checker.eraseGroup( groupId, name )) { + ostringstream msg; + msg << "Erase group failed. Group=" << groupId << " rcvd seq=" << groupSeq; + testFailed( msg.str() ); + break; + } + } + + ++count; + if (opts.ackFrequency && (count % opts.ackFrequency == 0)) { + session.acknowledge(); + } + // Clear out message properties & content for next iteration. + msg = Message(); // TODO aconway 2010-12-01: should be done by fetch + } else if (checker.allMsgsConsumed()) // timed out, nothing else to do? + break; + } + session.acknowledge(); + session.close(); + connection.close(); + } catch(const std::exception& error) { + ostringstream msg; + msg << "consumer error: " << error.what(); + testFailed( msg.str() ); + connection.close(); + } + clientDone(); + QPID_LOG(trace, "Consuming client " << name << " completed."); + } +}; + + + +class Producer : public Client +{ + GroupChecker& checker; + GroupGenerator generator; + +public: + Producer(const std::string& n, const Options& o, GroupChecker& c) + : Client(n, o), checker(c), + generator( n, o.messages, o.groupSize, o.randomizeSize, o.interleave ) + {}; + virtual ~Producer() {}; + + void run() + { + Connection connection; + try { + connection = Connection(opts.url, opts.connectionOptions); + connection.open(); + std::auto_ptr<FailoverUpdates> updates(opts.failoverUpdates ? new FailoverUpdates(connection) : 0); + Session session = connection.createSession(); + Sender sender = session.createSender(opts.address); + if (opts.capacity) sender.setCapacity(opts.capacity); + Message msg; + msg.setDurable(opts.durable); + std::string groupId; + uint seq; + bool eos; + uint sent = 0; + + qpid::sys::AbsTime start = qpid::sys::now(); + int64_t interval = 0; + if (opts.sendRate) interval = qpid::sys::TIME_SEC/opts.sendRate; + + while (!stopped && generator.genGroup(groupId, seq, eos)) { + msg.getProperties()[opts.groupKey] = groupId; + msg.getProperties()[SN] = seq; + msg.getProperties()[EOS] = eos; + checker.sendingSequence( groupId, seq, eos, name ); + + sender.send(msg); + ++sent; + + if (opts.sendRate) { + qpid::sys::AbsTime waitTill(start, sent*interval); + int64_t delay = qpid::sys::Duration(qpid::sys::now(), waitTill); + if (delay > 0) qpid::sys::usleep(delay/qpid::sys::TIME_USEC); + } + } + session.sync(); + session.close(); + connection.close(); + } catch(const std::exception& error) { + ostringstream msg; + msg << "producer '" << name << "' error: " << error.what(); + testFailed(msg.str()); + connection.close(); + } + clientDone(); + QPID_LOG(trace, "Producing client " << name << " completed."); + } +}; + + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char ** argv) +{ + int status = 0; + try { + Options opts; + if (opts.parse(argc, argv)) { + + GroupChecker state( opts.senders * opts.messages, + opts.allowDuplicates); + std::vector<Client::shared_ptr> clients; + + if (opts.randomizeSize) srand((unsigned int)qpid::sys::SystemInfo::getProcessId()); + + // fire off the producers && consumers + for (size_t j = 0; j < opts.senders; ++j) { + ostringstream name; + name << opts.prefix << "P_" << j; + clients.push_back(Client::shared_ptr(new Producer( name.str(), opts, state ))); + clients.back()->getThread() = qpid::sys::Thread(*clients.back()); + } + for (size_t j = 0; j < opts.receivers; ++j) { + ostringstream name; + name << opts.prefix << "C_" << j; + clients.push_back(Client::shared_ptr(new Consumer( name.str(), opts, state ))); + clients.back()->getThread() = qpid::sys::Thread(*clients.back()); + } + + // wait for all pubs/subs to finish.... or for consumers to fail or stall. + uint stalledTime = 0; + bool done; + bool clientFailed = false; + do { + uint lastCount = state.getConsumedTotal(); + qpid::sys::usleep( 1000000 ); + + // check each client for status + done = true; + for (std::vector<Client::shared_ptr>::iterator i = clients.begin(); + i != clients.end(); ++i) { + QPID_LOG(debug, "Client " << (*i)->getName() << " state=" << (*i)->getState()); + if ((*i)->getState() == Client::FAILURE) { + QPID_LOG(error, argv[0] << ": test failed with client error: " << (*i)->getErrorMsg()); + clientFailed = true; + done = true; + break; // exit test. + } else if ((*i)->getState() != Client::DONE) { + done = false; + } + } + + if (!done) { + // check that consumers are still receiving messages + if (lastCount == state.getConsumedTotal()) + stalledTime++; + else { + lastCount = state.getConsumedTotal(); + stalledTime = 0; + } + } + + QPID_LOG(debug, "Consumed to date = " << state.getConsumedTotal() << + " Published to date = " << state.getPublishedTotal() << + " total=" << opts.senders * opts.messages ); + + } while (!done && stalledTime < opts.timeout); + + if (clientFailed) { + status = 1; + } else if (stalledTime >= opts.timeout) { + QPID_LOG(error, argv[0] << ": test failed due to stalled consumer." ); + status = 2; + } + + // Wait for started threads. + for (std::vector<Client::shared_ptr>::iterator i = clients.begin(); + i != clients.end(); ++i) { + (*i)->stop(); + (*i)->getThread().join(); + } + + if (opts.printReport && !status) state.print(std::cout); + } else status = 4; + } catch(const std::exception& error) { + QPID_LOG(error, argv[0] << ": " << error.what()); + status = 3; + } + QPID_LOG(trace, "TEST DONE [" << status << "]"); + + return status; +} diff --git a/cpp/src/tests/python_tests b/cpp/src/tests/python_tests index e367004a71..0216b5ca7b 100755 --- a/cpp/src/tests/python_tests +++ b/cpp/src/tests/python_tests @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # # Licensed to the Apache Software Foundation (ASF) under one diff --git a/cpp/src/tests/qpid-cluster-benchmark b/cpp/src/tests/qpid-cluster-benchmark index 4408e63866..ff787a46dd 100755 --- a/cpp/src/tests/qpid-cluster-benchmark +++ b/cpp/src/tests/qpid-cluster-benchmark @@ -7,9 +7,9 @@ # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -19,21 +19,40 @@ # # Benchmark script for comparing cluster performance. -#PORT=":5555" -BROKER=`echo $HOSTS | awk '{print $1}'` # Single broker -BROKERS=`echo $HOSTS | sed "s/\>/$PORT/g;s/ /,/g"` # Broker URL list -COUNT=100000 -RATE=20000 # Rate to throttle senders for latency results -run_test() { echo $*; "$@"; echo; echo; echo; } -# Thruput, unshared queue -run_test qpid-cpp-benchmark --repeat 10 -b $BROKER --no-timestamp -m $COUNT +# Default values +PORT="5672" +COUNT=10000 +FLOW=100 # Flow control limit on queue depth for latency. +REPEAT=10 +QUEUES=4 +CLIENTS=3 -# Latency -run_test qpid-cpp-benchmark --repeat 10 -b $BROKER --connection-options '{tcp-nodelay:true}' -m `expr $COUNT / 2` --send-rate $RATE +while getopts "p:c:f:r:t:b:q:c" opt; do + case $opt in + p) PORT=$OPTARG;; + c) COUNT=$OPTARG;; + f) FLOW=$OPTARG;; + r) REPEAT=$OPTARG;; + s) SCALE=$OPTARG;; + b) BROKERS=$OPTARG;; + q) QUEUES=$OPTARG;; + c) CLIENTS=$OPTARG;; + *) echo "Unknown option"; exit 1;; + esac +done + +BROKERS=${BROKERS:-$(echo $HOSTS | sed "s/\>/:$PORT/g;s/ /,/g")} # Broker URL list +BROKER=`echo $BROKERS | awk -F, '{print $1}'` # First broker + +run_test() { echo $*; shift; "$@"; echo; echo; echo; } # Multiple pubs/subs connect via multiple brokers (active-active) -run_test qpid-cpp-benchmark --repeat 10 -b $BROKERS --no-timestamp --summarize -s10 -r10 -m `expr $COUNT / 10` +run_test "multi-host-thruput" qpid-cpp-benchmark --repeat $REPEAT -b $BROKERS --no-timestamp --summarize -q$QUEUES -s$CLIENTS -r$CLIENTS -m $COUNT # Multiple pubs/subs connect via single broker (active-passive) -run_test qpid-cpp-benchmark --repeat 10 -b $BROKER --no-timestamp --summarize -s10 -r10 -m `expr $COUNT / 10` +run_test "single-host-thruput" qpid-cpp-benchmark --repeat $REPEAT -b $BROKER --no-timestamp --summarize -q$QUEUES -s$CLIENTS -r$CLIENTS -m $COUNT + +# Latency +run_test "latency" qpid-cpp-benchmark --repeat $REPEAT -b $BROKER --connection-options '{tcp-nodelay:true}' -m $COUNT --flow-control $FLOW + diff --git a/cpp/src/tests/qpid-cpp-benchmark b/cpp/src/tests/qpid-cpp-benchmark index 1f77226b4d..300d34774f 100755 --- a/cpp/src/tests/qpid-cpp-benchmark +++ b/cpp/src/tests/qpid-cpp-benchmark @@ -77,6 +77,20 @@ def ssh_command(host, command): """Convert command into an ssh command on host with quoting""" return ["ssh", host] + [posix_quote(arg) for arg in command] +class Clients: + def __init__(self): self.clients=[] + + def add(self, client): + self.clients.append(client) + return client + + def kill(self): + for c in self.clients: + try: c.kill() + except: pass + +clients = Clients() + def start_receive(queue, index, opts, ready_queue, broker, host): address_opts=["create:receiver"] + opts.receive_option if opts.durable: address_opts += ["node:{durable:true}"] @@ -101,7 +115,7 @@ def start_receive(queue, index, opts, ready_queue, broker, host): if opts.connection_options: command += ["--connection-options",opts.connection_options] if host: command = ssh_command(host, command) - return Popen(command, stdout=PIPE) + return clients.add(Popen(command, stdout=PIPE)) def start_send(queue, opts, broker, host): address="%s;{%s}"%(queue,",".join(opts.send_option)) @@ -122,7 +136,7 @@ def start_send(queue, opts, broker, host): if opts.connection_options: command += ["--connection-options",opts.connection_options] if host: command = ssh_command(host, command) - return Popen(command, stdout=PIPE) + return clients.add(Popen(command, stdout=PIPE)) def first_line(p): out,err=p.communicate() @@ -133,7 +147,11 @@ def delete_queues(queues, broker): c = qpid.messaging.Connection(broker) c.open() for q in queues: - try: s = c.session().sender("%s;{delete:always}"%(q)) + try: + s = c.session() + snd = s.sender("%s;{delete:always}"%(q)) + snd.close() + s.sync() except qpid.messaging.exceptions.NotFound: pass # Ignore "no such queue" c.close() @@ -145,7 +163,6 @@ def print_header(timestamp): def parse(parser, lines): # Parse sender/receiver output for l in lines: fn_val = zip(parser, l) - return [map(lambda p: p[0](p[1]), zip(parser,line.split())) for line in lines] def parse_senders(senders): @@ -156,11 +173,12 @@ def parse_receivers(receivers): def print_data(send_stats, recv_stats): for send,recv in map(None, send_stats, recv_stats): - if send: print send[0], + line="" + if send: line += "%d"%send[0] if recv: - print "\t\t%d"%recv[0], - if len(recv) == 4: print "\t%.2f\t%.2f\t%.2f"%tuple(recv[1:]), - print + line += "\t\t%d"%recv[0] + if len(recv) == 4: line += "\t%.2f\t%.2f\t%.2f"%tuple(recv[1:]) + print line def print_summary(send_stats, recv_stats): def avg(s): sum(s) / len(s) @@ -184,11 +202,11 @@ class ReadyReceiver: self.receiver = self.connection.session().receiver( "%s;{create:receiver,delete:receiver,node:{durable:false}}"%(queue)) self.receiver.session.sync() - self.timeout=2 + self.timeout=10 def wait(self, receivers): try: - for i in xrange(len(receivers)): self.receiver.fetch(self.timeout) + for i in receivers: self.receiver.fetch(self.timeout) self.connection.close() except qpid.messaging.Empty: for r in receivers: @@ -197,7 +215,8 @@ class ReadyReceiver: raise Exception("Receiver error: %s"%(out)) raise Exception("Timed out waiting for receivers to be ready") -def flatten(l): return sum(map(lambda s: s.split(","), l),[]) +def flatten(l): + return sum(map(lambda s: re.split(re.compile("\s*,\s*|\s+"), s), l), []) class RoundRobin: def __init__(self,items): @@ -221,20 +240,22 @@ def main(): receive_out = "" ready_queue="%s-ready"%(opts.queue_name) queues = ["%s-%s"%(opts.queue_name, i) for i in xrange(opts.queues)] - for i in xrange(opts.repeat): - delete_queues(queues, opts.broker[0]) - ready_receiver = ReadyReceiver(ready_queue, opts.broker[0]) - receivers = [start_receive(q, j, opts, ready_queue, brokers.next(), client_hosts.next()) - for q in queues for j in xrange(opts.receivers)] - ready_receiver.wait(filter(None, receivers)) # Wait for receivers to be ready. - senders = [start_send(q, opts,brokers.next(), client_hosts.next()) - for q in queues for j in xrange(opts.senders)] - if opts.report_header and i == 0: print_header(opts.timestamp) - send_stats=parse_senders(senders) - recv_stats=parse_receivers(receivers) - if opts.summarize: print_summary(send_stats, recv_stats) - else: print_data(send_stats, recv_stats) - delete_queues(queues, opts.broker[0]) + try: + for i in xrange(opts.repeat): + delete_queues(queues, opts.broker[0]) + ready_receiver = ReadyReceiver(ready_queue, opts.broker[0]) + receivers = [start_receive(q, j, opts, ready_queue, brokers.next(), client_hosts.next()) + for q in queues for j in xrange(opts.receivers)] + ready_receiver.wait(filter(None, receivers)) # Wait for receivers to be ready. + senders = [start_send(q, opts,brokers.next(), client_hosts.next()) + for q in queues for j in xrange(opts.senders)] + if opts.report_header and i == 0: print_header(opts.timestamp) + send_stats=parse_senders(senders) + recv_stats=parse_receivers(receivers) + if opts.summarize: print_summary(send_stats, recv_stats) + else: print_data(send_stats, recv_stats) + delete_queues(queues, opts.broker[0]) + finally: clients.kill() # No strays if __name__ == "__main__": main() diff --git a/cpp/src/tests/qpid-ctrl b/cpp/src/tests/qpid-ctrl index 7b46c190fb..4246c57898 100755 --- a/cpp/src/tests/qpid-ctrl +++ b/cpp/src/tests/qpid-ctrl @@ -92,7 +92,10 @@ try: arguments = {} for a in args: name, val = nameval(a) - arguments[name] = val + if val[0] == '{' or val[0] == '[': + arguments[name] = eval(val) + else: + arguments[name] = val content = { "_object_id": {"_object_name": object_name}, "_method_name": method_name, diff --git a/cpp/src/tests/qpid-perftest.cpp b/cpp/src/tests/qpid-perftest.cpp index 4d7b563c8c..664f0cf877 100644 --- a/cpp/src/tests/qpid-perftest.cpp +++ b/cpp/src/tests/qpid-perftest.cpp @@ -173,7 +173,7 @@ struct Opts : public TestOptions { if (count % subs) { count += subs - (count % subs); cout << "WARNING: Adjusted --count to " << count - << " the nearest multiple of --nsubs" << endl; + << " the next multiple of --nsubs" << endl; } totalPubs = pubs*qt; totalSubs = subs*qt; @@ -396,7 +396,7 @@ struct Controller : public Client { void run() { // Controller try { // Wait for subscribers to be ready. - process(opts.totalSubs, fqn("sub_ready"), bind(expect, _1, "ready")); + process(opts.totalSubs, fqn("sub_ready"), boost::bind(expect, _1, "ready")); LocalQueue pubDone; LocalQueue subDone; @@ -413,7 +413,7 @@ struct Controller : public Client { AbsTime start=now(); send(opts.totalPubs, fqn("pub_start"), "start"); // Start publishers if (j) { - send(opts.totalPubs, fqn("sub_iteration"), "next"); // Start subscribers on next iteration + send(opts.totalSubs, fqn("sub_iteration"), "next"); // Start subscribers on next iteration } Stats pubRates; @@ -423,8 +423,10 @@ struct Controller : public Client { process(opts.totalSubs, subDone, fqn("sub_done"), boost::ref(subRates)); AbsTime end=now(); - double time=secs(start, end); + if (time <= 0.0) { + throw Exception("ERROR: Test completed in zero seconds. Try again with a larger message count."); + } double txrate=opts.transfers/time; double mbytes=(txrate*opts.size)/(1024*1024); @@ -508,10 +510,11 @@ struct PublishThread : public Client { } SubscriptionManager subs(session); LocalQueue lq; - subs.setFlowControl(1, SubscriptionManager::UNLIMITED, true); - subs.subscribe(lq, fqn("pub_start")); + subs.setFlowControl(0, SubscriptionManager::UNLIMITED, false); + Subscription cs = subs.subscribe(lq, fqn("pub_start")); for (size_t j = 0; j < opts.iterations; ++j) { + cs.grantMessageCredit(1); expect(lq.pop().getData(), "start"); AbsTime start=now(); for (size_t i=0; i<opts.count; i++) { @@ -543,6 +546,9 @@ struct PublishThread : public Client { if (opts.confirm) session.sync(); AbsTime end=now(); double time=secs(start,end); + if (time <= 0.0) { + throw Exception("ERROR: Test completed in zero seconds. Try again with a larger message count."); + } // Send result to controller. Message report(lexical_cast<string>(opts.count/time), fqn("pub_done")); @@ -638,7 +644,9 @@ struct SubscribeThread : public Client { // // For now verify order only for a single publisher. size_t offset = opts.uniqueData ? 5 /*marker is 'data:'*/ : 0; - size_t n = *reinterpret_cast<const size_t*>(msg.getData().data() + offset); + size_t n; + memcpy (&n, reinterpret_cast<const char*>(msg.getData().data() + offset), + sizeof(n)); if (opts.pubs == 1) { if (opts.subs == 1 || opts.mode == FANOUT) verify(n==expect, "==", expect, n); else verify(n>=expect, ">=", expect, n); diff --git a/cpp/src/tests/qpid-receive.cpp b/cpp/src/tests/qpid-receive.cpp index 012d544a2e..9c713e872a 100644 --- a/cpp/src/tests/qpid-receive.cpp +++ b/cpp/src/tests/qpid-receive.cpp @@ -53,6 +53,7 @@ struct Options : public qpid::Options bool forever; uint messages; bool ignoreDuplicates; + bool checkRedelivered; uint capacity; uint ackFrequency; uint tx; @@ -75,6 +76,7 @@ struct Options : public qpid::Options forever(false), messages(0), ignoreDuplicates(false), + checkRedelivered(false), capacity(1000), ackFrequency(100), tx(0), @@ -92,10 +94,11 @@ struct Options : public qpid::Options ("broker,b", qpid::optValue(url, "URL"), "url of broker to connect to") ("address,a", qpid::optValue(address, "ADDRESS"), "address to receive from") ("connection-options", qpid::optValue(connectionOptions, "OPTIONS"), "options for the connection") - ("timeout,t", qpid::optValue(timeout, "TIMEOUT"), "timeout in seconds to wait before exiting") + ("timeout", qpid::optValue(timeout, "TIMEOUT"), "timeout in seconds to wait before exiting") ("forever,f", qpid::optValue(forever), "ignore timeout and wait forever") ("messages,m", qpid::optValue(messages, "N"), "Number of messages to receive; 0 means receive indefinitely") ("ignore-duplicates", qpid::optValue(ignoreDuplicates), "Detect and ignore duplicates (by checking 'sn' header)") + ("check-redelivered", qpid::optValue(checkRedelivered), "Fails with exception if a duplicate is not marked as redelivered (only relevant when ignore-duplicates is selected)") ("capacity", qpid::optValue(capacity, "N"), "Pre-fetch window (0 implies no pre-fetch)") ("ack-frequency", qpid::optValue(ackFrequency, "N"), "Ack frequency (0 implies none of the messages will get accepted)") ("tx", qpid::optValue(tx, "N"), "batch size for transactions (0 implies transaction are not used)") @@ -216,6 +219,8 @@ int main(int argc, char ** argv) std::cout << msg.getContent() << std::endl;//TODO: handle map or list messages if (opts.messages && count >= opts.messages) done = true; } + } else if (opts.checkRedelivered && !msg.getRedelivered()) { + throw qpid::Exception("duplicate sequence number received, message not marked as redelivered!"); } if (opts.tx && (count % opts.tx == 0)) { if (opts.rollbackFrequency && (++txCount % opts.rollbackFrequency == 0)) { @@ -257,7 +262,7 @@ int main(int argc, char ** argv) return 0; } } catch(const std::exception& error) { - std::cerr << "Failure: " << error.what() << std::endl; + std::cerr << "qpid-receive: " << error.what() << std::endl; connection.close(); return 1; } diff --git a/cpp/src/tests/qpid-send.cpp b/cpp/src/tests/qpid-send.cpp index 6a7e7838ce..b1213a484f 100644 --- a/cpp/src/tests/qpid-send.cpp +++ b/cpp/src/tests/qpid-send.cpp @@ -28,6 +28,7 @@ #include <qpid/messaging/FailoverUpdates.h> #include <qpid/sys/Time.h> #include <qpid/sys/Monitor.h> +#include <qpid/sys/SystemInfo.h> #include "TestOptions.h" #include "Statistics.h" @@ -76,6 +77,11 @@ struct Options : public qpid::Options uint flowControl; bool sequence; bool timestamp; + std::string groupKey; + std::string groupPrefix; + uint groupSize; + bool groupRandSize; + uint groupInterleave; Options(const std::string& argv0=std::string()) : qpid::Options("Options"), @@ -100,19 +106,23 @@ struct Options : public qpid::Options sendRate(0), flowControl(0), sequence(true), - timestamp(true) + timestamp(true), + groupPrefix("GROUP-"), + groupSize(10), + groupRandSize(false), + groupInterleave(1) { addOptions() ("broker,b", qpid::optValue(url, "URL"), "url of broker to connect to") - ("address,a", qpid::optValue(address, "ADDRESS"), "address to drain from") + ("address,a", qpid::optValue(address, "ADDRESS"), "address to send to") ("connection-options", qpid::optValue(connectionOptions, "OPTIONS"), "options for the connection") ("messages,m", qpid::optValue(messages, "N"), "stop after N messages have been sent, 0 means no limit") ("id,i", qpid::optValue(id, "ID"), "use the supplied id instead of generating one") ("reply-to", qpid::optValue(replyto, "REPLY-TO"), "specify reply-to address") ("send-eos", qpid::optValue(sendEos, "N"), "Send N EOS messages to mark end of input") ("durable", qpid::optValue(durable, "yes|no"), "Mark messages as durable.") - ("ttl", qpid::optValue(ttl, "msecs"), "Time-to-live for messages, in milliseconds") - ("priority", qpid::optValue(priority, "PRIORITY"), "Priority for messages (higher value implies higher priority)") + ("ttl", qpid::optValue(ttl, "msecs"), "Time-to-live for messages, in milliseconds") + ("priority", qpid::optValue(priority, "PRIORITY"), "Priority for messages (higher value implies higher priority)") ("property,P", qpid::optValue(properties, "NAME=VALUE"), "specify message property") ("correlation-id", qpid::optValue(correlationid, "ID"), "correlation-id for message") ("user-id", qpid::optValue(userid, "USERID"), "userid for message") @@ -131,6 +141,11 @@ struct Options : public qpid::Options ("flow-control", qpid::optValue(flowControl,"N"), "Do end to end flow control to limit queue depth to 2*N. 0 means no flow control.") ("sequence", qpid::optValue(sequence, "yes|no"), "Add a sequence number messages property (required for duplicate/lost message detection)") ("timestamp", qpid::optValue(timestamp, "yes|no"), "Add a time stamp messages property (required for latency measurement)") + ("group-key", qpid::optValue(groupKey, "KEY"), "Generate groups of messages using message header 'KEY' to hold the group identifier") + ("group-prefix", qpid::optValue(groupPrefix, "STRING"), "Generate group identifers with 'STRING' prefix (if group-key specified)") + ("group-size", qpid::optValue(groupSize, "N"), "Number of messages per a group (if group-key specified)") + ("group-randomize-size", qpid::optValue(groupRandSize), "Randomize the number of messages per group to [1...group-size] (if group-key specified)") + ("group-interleave", qpid::optValue(groupInterleave, "N"), "Simultaineously interleave messages from N different groups (if group-key specified)") ("help", qpid::optValue(help), "print this usage statement"); add(log); } @@ -252,6 +267,68 @@ class MapContentGenerator : public ContentGenerator { const Options& opts; }; +// tag each generated message with a group identifer +// +class GroupGenerator { +public: + GroupGenerator(const std::string& key, + const std::string& prefix, + const uint size, + const bool randomize, + const uint interleave) + : groupKey(key), groupPrefix(prefix), groupSize(size), + randomizeSize(randomize), groupSuffix(0) + { + if (randomize) srand((unsigned int)qpid::sys::SystemInfo::getProcessId()); + + for (uint i = 0; i < 1 || i < interleave; ++i) { + newGroup(); + } + current = groups.begin(); + } + + void setGroupInfo(Message &msg) + { + if (current == groups.end()) + current = groups.begin(); + msg.getProperties()[groupKey] = current->id; + // std::cout << "SENDING GROUPID=[" << current->id << "]" << std::endl; + if (++(current->count) == current->size) { + newGroup(); + groups.erase(current++); + } else + ++current; + } + + private: + const std::string& groupKey; + const std::string& groupPrefix; + const uint groupSize; + const bool randomizeSize; + + uint groupSuffix; + + struct GroupState { + std::string id; + const uint size; + uint count; + GroupState( const std::string& i, const uint s ) + : id(i), size(s), count(0) {} + }; + typedef std::list<GroupState> GroupList; + GroupList groups; + GroupList::iterator current; + + void newGroup() { + std::ostringstream groupId(groupPrefix, ios_base::out|ios_base::ate); + groupId << groupSuffix++; + uint size = (randomizeSize) ? (rand() % groupSize) + 1 : groupSize; + // std::cout << "New group: GROUPID=[" << groupId.str() << "] size=" << size << std::endl; + GroupState group( groupId.str(), size ); + groups.push_back( group ); + } +}; + int main(int argc, char ** argv) { Connection connection; @@ -296,6 +373,14 @@ int main(int argc, char ** argv) else contentGen.reset(new FixedContentGenerator(opts.contentString)); + std::auto_ptr<GroupGenerator> groupGen; + if (!opts.groupKey.empty()) + groupGen.reset(new GroupGenerator(opts.groupKey, + opts.groupPrefix, + opts.groupSize, + opts.groupRandSize, + opts.groupInterleave)); + qpid::sys::AbsTime start = qpid::sys::now(); int64_t interval = 0; if (opts.sendRate) interval = qpid::sys::TIME_SEC/opts.sendRate; @@ -312,9 +397,6 @@ int main(int argc, char ** argv) ++sent; if (opts.sequence) msg.getProperties()[SN] = sent; - if (opts.timestamp) - msg.getProperties()[TS] = int64_t( - qpid::sys::Duration(qpid::sys::EPOCH, qpid::sys::now())); if (opts.flowControl) { if ((sent % opts.flowControl) == 0) { msg.setReplyTo(flowControlAddress); @@ -323,6 +405,12 @@ int main(int argc, char ** argv) else msg.setReplyTo(Address()); // Clear the reply address. } + if (groupGen.get()) + groupGen->setGroupInfo(msg); + + if (opts.timestamp) + msg.getProperties()[TS] = int64_t( + qpid::sys::Duration(qpid::sys::EPOCH, qpid::sys::now())); sender.send(msg); reporter.message(msg); @@ -368,7 +456,7 @@ int main(int argc, char ** argv) return 0; } } catch(const std::exception& error) { - std::cout << "Failed: " << error.what() << std::endl; + std::cerr << "qpid-send: " << error.what() << std::endl; connection.close(); return 1; } diff --git a/cpp/src/tests/qrsh.cpp b/cpp/src/tests/qrsh.cpp deleted file mode 100644 index 0cb52b6b05..0000000000 --- a/cpp/src/tests/qrsh.cpp +++ /dev/null @@ -1,169 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -#include <qpid/client/Connection.h> -#include <qpid/client/Session.h> -#include <qpid/client/AsyncSession.h> -#include <qpid/client/Message.h> -#include <qpid/client/MessageListener.h> -#include <qpid/client/SubscriptionManager.h> - -#include <stdio.h> -#include <cstdlib> -#include <iostream> - -#include <sstream> - -using namespace qpid::client; -using namespace qpid::framing; - -using namespace std; - -namespace qpid { -namespace tests { - -class ResponseListener : public MessageListener -{ - public : - - int exitCode; - - ResponseListener ( SubscriptionManager & subscriptions ) - : exitCode(-1), - subscriptions ( subscriptions ) - { - } - - virtual void - received ( Message & message ) - { - char first_word[1000]; - sscanf ( message.getData().c_str(), "%s", first_word ); - - if ( ! strcmp ( first_word, "wait_response" ) ) - { - // If we receive a message here, parse out the exit code. - sscanf ( message.getData().c_str(), "%*s%d", & exitCode ); - subscriptions.cancel(message.getDestination()); - } - else - if ( ! strcmp ( first_word, "get_response" ) ) - { - // The remainder of the message is the file we requested. - fprintf ( stdout, - "%s", - message.getData().c_str() + strlen("get_response" ) - ); - subscriptions.cancel(message.getDestination()); - } - } - - - private : - - SubscriptionManager & subscriptions; -}; - -}} // namespace qpid::tests - -using namespace qpid::tests; - -/* - * argv[1] host - * argv[2] port - * argv[3] server name - * argv[4] command name - * argv[5..N] args to the command - */ -int -main ( int argc, char ** argv ) -{ - const char* host = argv[1]; - int port = atoi(argv[2]); - - - Connection connection; - - try - { - connection.open ( host, port ); - Session session = connection.newSession ( ); - - // Make a queue and bind it to fanout. - string myQueue = session.getId().getName(); - - session.queueDeclare ( arg::queue=myQueue, - arg::exclusive=true, - arg::autoDelete=true - ); - - session.exchangeBind ( arg::exchange="amq.fanout", - arg::queue=myQueue, - arg::bindingKey="my-key" - ); - - // Get ready to listen for the wait-response. - // or maybe a get-response. - // ( Although this may not be one of those types - // of command, get ready anyway. - SubscriptionManager subscriptions ( session ); - ResponseListener responseListener ( subscriptions ); - subscriptions.subscribe ( responseListener, myQueue ); - - bool response_command = false; - if(! strcmp("exec_wait", argv[4] )) - response_command = true; - else - if(! strcmp("exited", argv[4] )) - response_command = true; - else - if(! strcmp("get", argv[4] )) - response_command = true; - - // Send the payload message. - // Skip "qrsh host_name port" - Message message; - stringstream ss; - for ( int i = 3; i < argc; ++ i ) - ss << argv[i] << ' '; - - message.setData ( ss.str() ); - - session.messageTransfer(arg::content=message, - arg::destination="amq.fanout"); - - if ( response_command ) - subscriptions.run(); - - session.close(); - connection.close(); - return responseListener.exitCode; - } - catch ( exception const & e) - { - cerr << e.what() << endl; - } - - return 1; -} - - - diff --git a/cpp/src/tests/qrsh_run.cpp b/cpp/src/tests/qrsh_run.cpp deleted file mode 100644 index cfdd0cef80..0000000000 --- a/cpp/src/tests/qrsh_run.cpp +++ /dev/null @@ -1,321 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -#include <iostream> -#include <sstream> -#include <stdio.h> -#include <stdlib.h> -#include <unistd.h> -#include <string.h> -#include <fcntl.h> -#include <errno.h> - -#include <sys/stat.h> -#include <sys/types.h> -#include <sys/wait.h> - - -using namespace std; - - - -int -main ( int argc, char ** argv ) -{ - int exit_code = -1; - int fd[2]; - int my_pid = getpid(); - int child_pid; - - pipe(fd); - - char const * root_dir = argv[1]; // This arg is prepended by qrsh_server. - char const * child_name = argv[2]; // This arg comes from qrsh. - char const * child_path = argv[3]; // This arg comes from qrsh. - - // This is the problem.. - fprintf ( stderr, "MDEBUG qrsh_run: root_dir: |%s|\n", root_dir ); - fprintf ( stderr, "MDEBUG qrsh_run: child_name: |%s|\n", child_name ); - fprintf ( stderr, "MDEBUG qrsh_run: child_path: |%s|\n", child_path ); - - /* - * A named child is one for whom we will create a directory and - * store information. There are some magic names that are not - * real symbolic names -- but are instead the names of actions. - */ - - bool named_child = true; - - if ( ! strcmp ( child_name, "exec" ) ) - named_child = false; - else - if ( ! strcmp ( child_name, "exec_wait" ) ) - named_child = false; - else - if ( ! strcmp ( child_name, "exited" ) ) - named_child = false; - else - named_child = true; - - stringstream child_dir_name; - - if ( named_child ) - { - child_dir_name << root_dir - << '/' - << child_name; - - /* - * Make the child directory before forking, or there is - * a race in which the child might be trying to make its - * stdout and stderr files while we are tring to make - * the directory. - */ - if ( -1 == mkdir ( child_dir_name.str().c_str(), 0777 ) ) - { - fprintf ( stderr, - "qrsh_run error: Can't mkdir |%s|\n", - child_dir_name.str().c_str() - ); - exit ( 1 ); - } - - } - else - /* - * If this is an 'exited' command that means we are - * waiting for a pre-existing child. - */ - if ( ! strcmp ( child_name, "exited" ) ) - { - int wait_pid = atoi(child_path); - - // Find the child's symbolic name. - stringstream pid_to_name_file_name; - pid_to_name_file_name << root_dir - << '/' - << wait_pid; - FILE * fp = fopen ( pid_to_name_file_name.str().c_str(), "r" ); - if (! fp) - { - fprintf ( stderr, - "qrsh_run %d error: Can't open pid2name file |%s|.\n", - my_pid, - pid_to_name_file_name.str().c_str() - ); - exit(1); - } - char symbolic_name[1000]; - strcpy ( symbolic_name, "qrsh_no_name" ); - fscanf ( fp, "%s", symbolic_name ); - fclose ( fp ); - - // Make the name of the child's exit code file. - stringstream exit_code_file_name; - exit_code_file_name << root_dir - << '/' - << symbolic_name - << "/exit_code"; - - struct stat stat_buf; - int file_does_not_exist = stat ( exit_code_file_name.str().c_str(), & stat_buf ); - - /* - * If the result of stat is zero, the file exists, which means that - * the command has exited. The question we are being asked here is - * "has it exited yet?" - */ - if ( ! file_does_not_exist ) - return 1; - else - if ( errno == ENOENT ) - return 0; - else - return 2 ; - } - - - // We are not waiting on a pre-wxiting child: we have a - // new child to create. - - child_pid = fork(); - - if ( child_pid == 0 ) - { - // This code is executed in the child process. - - // If it's a *named* child, then redirect its stdout and stderr. - if ( named_child ) - { - stringstream stdout_path, - stderr_path; - - // Redirect the child's stdout. ----------------- - stdout_path << root_dir - << '/' - << child_name - << '/' - << "stdout"; - - int redirected_stdout = open ( stdout_path.str().c_str(), - O_WRONLY|O_CREAT|O_TRUNC, - S_IRWXU|S_IRWXG|S_IRWXO - ); - if ( redirected_stdout < 0 ) - { - perror ( "qrsh_run: error opening redirected_stdout: " ); - fprintf ( stderr, "stdout path: |%s|\n", stdout_path.str().c_str() ); - exit ( 1 ); - } - if ( -1 == dup2 ( redirected_stdout, 1 ) ) - { - perror ( "qrsh_run: dup2 (stdout) error: " ); - exit(1); - } - - // Redirect the child's stderr. ----------------- - stderr_path << root_dir - << '/' - << child_name - << '/' - << "stderr"; - - int redirected_stderr = open ( stderr_path.str().c_str(), - O_WRONLY|O_CREAT|O_TRUNC, - S_IRWXU|S_IRWXG|S_IRWXO - ); - if ( redirected_stderr < 0 ) - { - perror ( "qrsh_run: error opening redirected_stderr: " ); - fprintf ( stderr, "stderr path: |%s|\n", stderr_path.str().c_str() ); - exit ( 1 ); - } - if(-1 == dup2 ( redirected_stderr, 2 ) ) - { - perror ( "qrsh_run: dup2 (stderr) error: " ); - exit(1); - } - } - - fprintf ( stderr, "MDEBUG ------------- qrsh_run argv -------------\n" ); - for ( int i = 0; i < argc; ++ i ) - fprintf ( stderr, "MDEBUG argv[%d] : |%s|\n", i, argv[i] ); - - execv ( child_path, argv + 2 ); - perror ( "qrsh_run: execv error: " ); - fprintf ( stderr, "on path |%s|\n", child_path ); - exit ( 1 ); - } - else - { - // This code is executed in the parent process. - - if ( named_child ) - { - // Write the name-to-pid mapping. - stringstream pid_file_name; - pid_file_name << child_dir_name.str() - << "/pid"; - - FILE * fp; - if ( ! (fp = fopen ( pid_file_name.str().c_str(), "w") ) ) - { - fprintf ( stderr, - "qrsh_run %d error: Can't open file |%s|\n", - my_pid, - pid_file_name.str().c_str() - ); - exit(1); - } - fprintf ( fp, "%d\n", child_pid ); - fclose ( fp ); - - - // Write the pid-to-name mapping. - stringstream name_to_pid_file_name; - name_to_pid_file_name << root_dir - << '/' - << child_pid; - if(! (fp = fopen ( name_to_pid_file_name.str().c_str(), "w"))) - { - fprintf ( stderr, - "qrsh_run %d error: Can't open file |%s|\n", - my_pid, - name_to_pid_file_name.str().c_str() - ); - exit(1); - } - fprintf ( fp, "%s\n", child_name ); - fclose(fp); - } - - pid_t awaited_pid; - while ( 0 == (awaited_pid = waitpid ( child_pid, & exit_code, WNOHANG)) ) - { - fprintf ( stderr, - "qrsh_run %d info: parent: waiting for child %d...\n", - my_pid, - child_pid - ); - sleep(1); - } - - if ( -1 == awaited_pid ) - { - fprintf ( stderr, "qrsh_run error awaiting child!\n" ); - exit ( 1 ); - } - - /* - * Write the exit code. - */ - exit_code >>= 8; - - if ( named_child ) - { - if ( child_pid == awaited_pid ) - { - stringstream exit_code_file_name; - exit_code_file_name << child_dir_name.str() - << "/exit_code"; - - FILE * fp; - if ( ! (fp = fopen ( exit_code_file_name.str().c_str(), "w") ) ) - { - fprintf ( stderr, - "qrsh_run error: Can't open file |%s|\n", - exit_code_file_name.str().c_str() - ); - exit(1); - } - fprintf ( fp, "%d\n", exit_code ); - fclose ( fp ); - } - } - } - - fprintf ( stderr, "MDEBUG qrsh_run returning exit code %d\n", exit_code ); - return exit_code; -} - - - - diff --git a/cpp/src/tests/qrsh_server.cpp b/cpp/src/tests/qrsh_server.cpp deleted file mode 100644 index 782f1e6c7c..0000000000 --- a/cpp/src/tests/qrsh_server.cpp +++ /dev/null @@ -1,1068 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -#include <stdio.h> -#include <set> -#include <string> -#include <sstream> -#include <unistd.h> -#include <cstdlib> -#include <iostream> -#include <map> -#include <dirent.h> - -#include <sys/stat.h> -#include <sys/types.h> -#include <sys/wait.h> - -#include <qpid/client/Connection.h> -#include <qpid/client/Session.h> -#include <qpid/client/AsyncSession.h> -#include <qpid/client/Message.h> -#include <qpid/client/MessageListener.h> -#include <qpid/client/SubscriptionManager.h> - - -using namespace qpid::client; -using namespace qpid::framing; -using namespace std; - - -namespace qpid { -namespace tests { - -int -mrand ( int max_desired_val ) -{ - double zero_to_one = (double) rand() / (double) RAND_MAX; - return (int) (zero_to_one * (double) max_desired_val); -} - - - -char * -file2str ( char const * file_name ) -{ - FILE * fp = fopen ( file_name, "r" ); - if(! fp) - { - fprintf ( stderr, "file2str error: can't open file |%s|.\n", file_name ); - return 0; - } - - fseek ( fp, 0, SEEK_END ); - size_t file_len = (size_t) ftell ( fp ); - rewind ( fp ); - char * content = (char *) malloc ( file_len + 1 ); - - if ( ! content ) - { - fprintf ( stderr, - "file2str error: can't malloc %d bytes.\n", - (int)file_len - ); - return 0; - } - - size_t items_read = fread ( content, file_len, 1, fp ); - - if ( 1 != items_read ) - { - fprintf ( stderr, "file2str error: read failed.\n" ); - free ( content ); - return 0; - } - - fclose ( fp ); - content[file_len] = 0; - - return content; -} - - - - - -class QrshServer : public MessageListener -{ - public: - - QrshServer ( SubscriptionManager & subscriptions, - char const * name, - char const * qrsh_run_path, - char const * host, - int port - ); - - virtual void received ( Message & message); - - - private: - - set<string> all_server_names; - - stringstream data_dir; - - SubscriptionManager & subscriptions; - - // Is this message addressed to me? - bool myMessage ( Message const & message ); - - /* ---------------------------------------------- - * Special Commands - * These are commands that the qrsh_server executes - * directly, rather than through a child process - * instance of qrsh_run. - */ - void runCommand ( Message const & message ); - void execute ( Message const & message ); - void wait ( Message const & message ); - void exited ( Message const & message ); - void get ( Message const & message ); - void rememberIntroduction ( Message const & message ); - void getStraw ( Message const & message ); - void addAlias ( Message const & message ); - - void start ( ); - void sayHello ( ); - void sayName ( ); - // end Special Commands ------------------------ - - - void saveCommand ( Message const & message ); - - void send ( string const & content ); - - void drawStraws ( ); - void getNames ( ); - void runSavedCommand ( ); - - char ** getArgs ( char const * s ); - bool isProcessName ( char const * s ); - int string_countWords ( char const * s ); - char const * skipWord ( char const * s ); - - - void string_replaceAll ( string & str, - string & target, - string & replacement - ); - - - string name, - qrsh_run_path, - host; - - vector<string *> aliases; - - int port; - - map < char *, int > abstract_name_map; - - set < string > myFellowBrokers; - - bool saidHello; - - Message savedCommand; - - vector < int > straws; - int myStraw; - -}; - - - -QrshServer::QrshServer ( SubscriptionManager & subs, - char const * name, - char const * qrsh_run_path, - char const * host, - int port - ) - : subscriptions ( subs ), - name ( name ), - qrsh_run_path ( qrsh_run_path ), - host ( host ), - port ( port ), - saidHello ( false ), - myStraw ( 0 ) -{ - data_dir << "/tmp/qrsh_" - << getpid(); - - if(mkdir ( data_dir.str().c_str(), 0777 ) ) - { - fprintf ( stderr, - "QrshServer::QrshServer error: can't mkdir |%s|\n", - data_dir.str().c_str() - ); - exit ( 1 ); - } -} - - - -void -QrshServer::saveCommand ( Message const & message ) -{ - savedCommand = message; -} - - - -void -QrshServer::runSavedCommand ( ) -{ - runCommand ( savedCommand ); -} - - - -void -QrshServer::start ( ) -{ - stringstream announcement_data; - announcement_data << "hello_my_name_is " - << name; - - send ( announcement_data.str() ); - - saidHello = true; -} - - - - -void -QrshServer::send ( string const & content ) -{ - try - { - Message message; - message.setData ( content ); - - Connection connection; - connection.open ( host, port ); - Session session = connection.newSession ( ); - session.messageTransfer ( arg::content = message, - arg::destination = "amq.fanout" - ); - session.close(); - connection.close(); - } - catch ( exception const & e ) - { - fprintf ( stderr, "QrshServer::send error: |%s|\n", e.what() ); - } -} - - - - -void -QrshServer::sayHello ( ) -{ - if ( saidHello ) - return; - - stringstream ss; - - ss << "hello_my_name_is " - << name; - - send ( ss.str() ); - saidHello = true; -} - - - -void -QrshServer::sayName ( ) -{ - fprintf ( stderr, "My name is: |%s|\n", name.c_str() ); -} - - - - -void -QrshServer::drawStraws ( ) -{ - myStraw = mrand ( 1000000000 ); - stringstream ss; - ss << "straw " - << name - << ' ' - << myStraw; - send ( ss.str() ); -} - - - -void -QrshServer::getStraw ( Message const & message ) -{ - int straw; - - char brokerName[1000]; - sscanf ( message.getData().c_str(), "%*s%s", brokerName ); - - if ( ! strcmp ( brokerName, name.c_str() ) ) - return; - - sscanf ( message.getData().c_str(), "%*s%*s%d", & straw ); - straws.push_back ( straw ); - - bool i_win = true; - int ties = 0; - - if ( straws.size() >= myFellowBrokers.size() ) - { - // All votes are in! Let's see if I win! - for ( unsigned int i = 0; i < straws.size(); ++ i ) - { - if ( straws[i] == myStraw ) - ++ ties; - else - if ( straws[i] > myStraw ) - { - i_win = false; - break; - } - } - - if ( i_win && (ties <= 0) ) - { - myStraw = 0; - straws.clear(); - runSavedCommand ( ); - } - else - if ( i_win && (ties > 0) ) - { - fprintf ( stderr, "MDEBUG oh no! drawStraws error: server %s tied with straw %d!\n", name.c_str(), straw ); - } - } -} - - - - -/* - * "APB" command (all-points-bullitens (commands that are not addressed - * specifically to any server)) are handled directly, here. - * Because if I return simply "true", the normal command processing code - * will misinterpret the command. - */ -bool -QrshServer::myMessage ( Message const & message ) -{ - int const maxlen = 100; - char head[maxlen]; - char first_word [ maxlen + 1 ]; - strncpy ( head, message.getData().c_str(), maxlen ); - sscanf ( head, "%s", first_word ); - - if ( ! strcmp ( name.c_str(), first_word ) ) - { - return true; - } - else - { - // Is the given name one of my aliases? - char possibleAlias[1000]; - if(1 == sscanf ( message.getData().c_str(), "%s", possibleAlias )) - { - for ( unsigned int i = 0; i < aliases.size(); ++ i ) - { - - if ( ! strcmp ( possibleAlias, aliases[i]->c_str() )) - { - return true; - } - } - } - } - - if ( ! strcmp ( first_word, "hello_my_name_is" ) ) - { - rememberIntroduction ( message ); - sayHello ( ); - return false; - } - else - if ( ! strcmp ( first_word, "straw" ) ) - { - getStraw ( message ); - return false; - } - else - if ( ! strcmp ( first_word, "all" ) ) - { - return true; - } - else - if ( ! strcmp ( first_word, "any" ) ) - { - straws.clear(); - usleep ( 200000 ); - saveCommand ( message ); - drawStraws ( ); - return false; - } - else - return false; -} - - - - -void -QrshServer::rememberIntroduction ( Message const & message ) -{ - char brokerName [ 1000 ]; - sscanf ( message.getData().c_str(), "%*s%s", brokerName ); - - if ( strcmp ( brokerName, name.c_str() ) ) - myFellowBrokers.insert ( string ( brokerName ) ); -} - - - - -void -QrshServer::addAlias ( Message const & message ) -{ - char alias[1000]; - sscanf ( message.getData().c_str(), "%*s%*s%s", alias ); - aliases.push_back ( new string(alias) ); -} - - - - -void -QrshServer::getNames ( ) -{ - abstract_name_map.clear(); - - DIR * dir = opendir ( data_dir.str().c_str() ); - - if ( ! dir ) - { - fprintf ( stderr, - "QrshServer::getNames error: could not open dir |%s|.\n", - data_dir.str().c_str() - ); - return; - } - - struct dirent * file; - while ( (file = readdir ( dir ) ) ) - { - if ( '.' != file->d_name[0] ) - { - stringstream pid_file_name; - pid_file_name << data_dir.str() - << '/' - << file->d_name - << "/pid"; - - int pid = 0; - FILE * fp; - if ( (fp = fopen ( pid_file_name.str().c_str(), "r" ) ) ) - { - fscanf ( fp, "%d", & pid ); - fclose ( fp ); - abstract_name_map.insert(pair<char*, int>(strdup(file->d_name), pid)); - } - else - { - /* - * Fail silently. The non-existence of this file - * is not necessarily an error. - */ - } - } - } - closedir ( dir ); -} - - - -void -QrshServer::string_replaceAll ( string & str, - string & target, - string & replacement - ) -{ - int target_size = target.size(); - int found_pos = 0; - - while ( 0 <= (found_pos = str.find ( target ) ) ) - str.replace ( found_pos, target_size, replacement ); -} - - - - -bool -QrshServer::isProcessName ( char const * str ) -{ - getNames(); - map<char *, int>::iterator it; - for ( it = abstract_name_map.begin(); it != abstract_name_map.end(); ++ it ) - { - if ( ! strcmp ( str, it->first ) ) - return true; - } - - return false; -} - - - - - -int -QrshServer::string_countWords ( char const * s1 ) -{ - int count = 0; - char const * s2 = s1 + 1; - - if ( ! isspace(* s1) ) - { - ++ count; - } - - for ( ; * s2; ++ s1, ++ s2 ) - { - // count space-to-word transitions. - if ( isspace(*s1) && (! isspace(*s2)) ) - ++ count; - } - - return count; -} - - - - -void -QrshServer::execute ( Message const & message ) -{ - // First, gather all the symbolic names we know. - getNames(); - - // Now make a copy of the command, that I can alter. - string command ( message.getData() ); - - - // Replace each occurrence of every abstract name with its pid. - char pid_str[100]; - map<char *, int>::iterator it; - for ( it = abstract_name_map.begin(); it != abstract_name_map.end(); ++ it ) - { - sprintf ( pid_str, "%d", it->second ); - string target ( it->first ), - replacement ( pid_str ); - string_replaceAll ( command, target, replacement ); - } - - - char const * truncated_command = skipWord(skipWord(command.c_str())); - - if ( truncated_command ) - system ( truncated_command ); -} - - - - - -void -QrshServer::get ( Message const & request_message ) -{ - char * file_content; - - /* - * Get the contents of the requested file. - */ - char file_or_process_name[1000]; - sscanf ( request_message.getData().c_str(), "%*s%*s%s", file_or_process_name ); - - if ( isProcessName ( file_or_process_name ) ) - { - stringstream desired_file_name; - desired_file_name << data_dir.str() - << '/' - << file_or_process_name - << '/'; - char requested_output_stream[1000]; - if(1 != sscanf ( request_message.getData().c_str(), - "%*s%*s%*s%s", - requested_output_stream - ) - ) - { - fprintf ( stderr, - "QrshServer::get error: Can't read requested data file name from this message: |%s|\n", - request_message.getData().c_str() - ); - return; - } - desired_file_name << requested_output_stream; - file_content = file2str ( desired_file_name.str().c_str() ); - } - else - { - file_content = file2str ( file_or_process_name ); - } - - stringstream reply_data ; - reply_data << "get_response " - << file_content; - /* - * Send a response-message to the server who is waiting. - */ - send ( reply_data.str() ); -} - - - - - - -void -QrshServer::exited ( Message const & message ) -{ - int exit_code = -1; - - // First, gather all the symbolic names we know. - getNames(); - - // Now make a copy of the command, that I can alter. - string edited_command ( message.getData() ); - - // Replace each occurrence of every abstract name with its pid. - char pid_str[100]; - map<char *, int>::iterator it; - for ( it = abstract_name_map.begin(); it != abstract_name_map.end(); ++ it ) - { - sprintf ( pid_str, "%d", it->second ); - string target ( it->first ), - replacement ( pid_str ); - string_replaceAll ( edited_command, target, replacement ); - } - - // Skip the service name. That is not used by the child. - char const * truncated_command = skipWord(edited_command.c_str()); - - if ( truncated_command ) - { - stringstream ss; - ss << qrsh_run_path - << ' ' - << data_dir.str() - << ' ' - << truncated_command; - - int child_pid; - if ( ! (child_pid = fork() ) ) - { - // This is the child. - - char ** argv = getArgs ( ss.str().c_str() ); - execv ( qrsh_run_path.c_str(), argv ); - - perror ( "qrsh_server: execv error: " ); - exit ( 1 ); - } - else - { - // This is the parent. - pid_t awaited_pid; - while ( 0 == (awaited_pid = waitpid ( child_pid, & exit_code, WNOHANG)) ) - { - fprintf ( stderr, "qrsh_server info: parent: waiting for child...\n" ); - sleep(1); - } - - if ( -1 == awaited_pid ) - { - fprintf ( stderr, "qrsh_server error awaiting child!\n" ); - exit ( 1 ); - } - - exit_code >>= 8; - - stringstream data; - data << "wait_response " - << exit_code; - - send ( data.str() ); - } - } -} - - - - -void -QrshServer::wait ( Message const & message ) -{ - bool pre_existing = false; - if ( 3 == string_countWords ( message.getData().c_str() ) ) - { - // The first word is the name of this service. - // The second word is "exec_wait". - // The third word is the symbolic name of the command to wait for. - // The fact that there are exactly three words means that this - // must be a command that has already been named and started -- - // we just need to find its pid and wait on it. - pre_existing = true; - } - - - int exit_code = -1; - - // First, gather all the symbolic names we know. - getNames(); - - // Now make a copy of the command, that I can alter. - string edited_command ( message.getData() ); - - // Replace each occurrence of every abstract name with its pid. - char pid_str[100]; - map<char *, int>::iterator it; - for ( it = abstract_name_map.begin(); it != abstract_name_map.end(); ++ it ) - { - sprintf ( pid_str, "%d", it->second ); - string target ( it->first ), - replacement ( pid_str ); - string_replaceAll ( edited_command, target, replacement ); - } - - // Skip the service name. That is not used by the child. - char const * truncated_command = skipWord(edited_command.c_str()); - - if ( truncated_command ) - { - stringstream ss; - ss << qrsh_run_path - << ' ' - << data_dir.str() - << ' ' - << truncated_command; - - int child_pid; - if ( ! (child_pid = fork() ) ) - { - // This is the child. - - char ** argv = getArgs ( ss.str().c_str() ); - execv ( qrsh_run_path.c_str(), argv ); - - perror ( "qrsh_server: execv error: " ); - exit ( 1 ); - } - else - { - // This is the parent. - pid_t awaited_pid; - while ( 0 == (awaited_pid = waitpid ( child_pid, & exit_code, WNOHANG)) ) - { - fprintf ( stderr, "qrsh_server info: parent: waiting for child...\n" ); - sleep(1); - } - - if ( -1 == awaited_pid ) - { - fprintf ( stderr, "qrsh_server error awaiting child!\n" ); - exit ( 1 ); - } - } - - exit_code >>= 8; - - stringstream data; - data << "wait_response " - << exit_code; - - send ( data.str() ); - } -} - - - - - -char const * -QrshServer::skipWord ( char const * s ) -{ - if(! (s && *s) ) - return 0; - - // skip past initial white space - while ( isspace(*s) ) - { - ++ s; - if(! *s) - return 0; - } - - // skip past first word - while ( ! isspace(*s) ) - { - ++ s; - if(! *s) - return 0; - } - - return s; -} - - - - - -char ** -QrshServer::getArgs ( char const * str ) -{ - char const * s = str; - - char ** argv = 0; - vector<int> start_positions, - lengths; - - int pos = 0; - int arg_len = 0; - - int n_args = 0; - while ( 1 ) - { - // advance over whitespace. - while ( isspace ( *s ) ) - { - ++ s; ++ pos; - if(! *s) - { - goto done; - } - } - - ++ n_args; - start_positions.push_back ( pos ); - arg_len = 0; - - // advance over non-whitespace. - while ( ! isspace ( *s ) ) - { - ++ s; ++ pos; ++ arg_len; - if(! *s) - { - lengths.push_back ( arg_len ); - arg_len = 0; - goto done; - } - } - - lengths.push_back ( arg_len ); - arg_len = 0; - } - - done: - - if ( arg_len > 0 ) - lengths.push_back ( arg_len ); - - // Alloc the array. - argv = (char **) malloc ( sizeof(char *) * ( n_args + 1 ) ); - argv[n_args] = 0; // mull-term the array. - - for ( int i = 0; i < n_args; ++ i ) - { - argv[i] = ( char *) malloc ( lengths[i] + 1 ); - strncpy ( argv[i], - str + start_positions[i], - lengths[i] - ); - argv[i][lengths[i]] = 0; - } - - return argv; -} - - - -void -QrshServer::runCommand ( Message const & message ) -{ - char const * s = message.getData().c_str(); - - /* - * Skip the first word, which is this server's name. - */ - while ( isspace(*s) ) // go to start of first word. - ++ s; - - while ( ! isspace(*s) ) // go to end of first word. - ++ s; - - while ( isspace(*s) ) // go to start of second word. - ++ s; - - char command_name[1000]; - sscanf ( s, "%s", command_name ); - - if ( ! strcmp ( "get", command_name ) ) - { - get ( message ); - } - else - if ( ! strcmp ( "exited", command_name ) ) - { - exited ( message ); - } - else - if ( ! strcmp ( "exec_wait", command_name ) ) - { - wait ( message ); - } - else - if ( ! strcmp ( "exec", command_name ) ) - { - execute ( message ); - } - else - if ( ! strcmp ( "start", command_name ) ) - { - start ( ); - } - else - if ( ! strcmp ( "alias", command_name ) ) - { - addAlias ( message ); - } - else - if ( ! strcmp ( "sayName", command_name ) ) - { - sayName ( ); - } - else - { - /* - * If the command is not any of the "special" commands - * above, then it's a "normal" command. - * That means we run it with a child process instance of - * qrsh_run, which will save all its data in the qrsh dir. - */ - stringstream ss; - ss << qrsh_run_path - << ' ' - << data_dir.str() - << ' ' - << s; - - if ( ! fork() ) - { - char ** argv = getArgs ( ss.str().c_str() ); - execv ( qrsh_run_path.c_str(), argv ); - perror ( "qrsh_server: execv error: " ); - } - } -} - - - -void -QrshServer::received ( Message & message ) -{ - if ( myMessage ( message ) ) - runCommand ( message ); -} - - - -}} // namespace qpid::tests - -using namespace qpid::tests; - -/* - * fixme mick Mon Aug 3 10:29:26 EDT 2009 - * argv[1] server name - * argv[2] qrsh exe path - * argv[3] host - * argv[4] port - */ -int -main ( int /*argc*/, char** argv ) -{ - const char* host = argv[3]; - int port = atoi(argv[4]); - Connection connection; - Message msg; - - srand ( getpid() ); - - try - { - connection.open ( host, port ); - Session session = connection.newSession(); - - - // Declare queues. - string myQueue = session.getId().getName(); - session.queueDeclare ( arg::queue=myQueue, - arg::exclusive=true, - arg::autoDelete=true); - - session.exchangeBind ( arg::exchange="amq.fanout", - arg::queue=myQueue, - arg::bindingKey="my-key"); - - // Create a server and subscribe it to my queue. - SubscriptionManager subscriptions ( session ); - QrshServer server ( subscriptions, - argv[1], // server name - argv[2], // qrsh exe path - host, - port - ); - subscriptions.subscribe ( server, myQueue ); - - // Receive messages until the subscription is cancelled - // by QrshServer::received() - subscriptions.run(); - - connection.close(); - } - catch(const exception& error) - { - cout << error.what() << endl; - return 1; - } - - return 0; -} - - - - diff --git a/cpp/src/tests/qrsh_utils/10_all b/cpp/src/tests/qrsh_utils/10_all deleted file mode 100755 index 7b486ea672..0000000000 --- a/cpp/src/tests/qrsh_utils/10_all +++ /dev/null @@ -1,30 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -#! /bin/bash - -echo "Asking all servers to say their names... " -qrsh 127.0.0.1 5813 \ - all sayName - - - - diff --git a/cpp/src/tests/qrsh_utils/1_remote_run b/cpp/src/tests/qrsh_utils/1_remote_run deleted file mode 100755 index 5b9b307bba..0000000000 --- a/cpp/src/tests/qrsh_utils/1_remote_run +++ /dev/null @@ -1,26 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -#! /bin/bash - - -./qrsh 127.0.0.1 5813 \ - mrg23 command_1 /home/mick/redhat/qrsh/qrsh_run/my_command foo bar baz diff --git a/cpp/src/tests/qrsh_utils/2_forever b/cpp/src/tests/qrsh_utils/2_forever deleted file mode 100755 index 5528b0e4d8..0000000000 --- a/cpp/src/tests/qrsh_utils/2_forever +++ /dev/null @@ -1,26 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -#! /bin/bash - - -./qrsh 127.0.0.1 5813 \ - mrg23 command_2 /home/mick/redhat/qrsh/qrsh_run/forever foo bar baz diff --git a/cpp/src/tests/qrsh_utils/3_kill_it b/cpp/src/tests/qrsh_utils/3_kill_it deleted file mode 100755 index afc7a03c9d..0000000000 --- a/cpp/src/tests/qrsh_utils/3_kill_it +++ /dev/null @@ -1,27 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -#! /bin/bash - -echo "Killing command 2... " -./qrsh 127.0.0.1 5813 \ - mrg23 exec kill -9 command_2 - diff --git a/cpp/src/tests/qrsh_utils/4_wait_for_it b/cpp/src/tests/qrsh_utils/4_wait_for_it deleted file mode 100755 index a4dc0da1ce..0000000000 --- a/cpp/src/tests/qrsh_utils/4_wait_for_it +++ /dev/null @@ -1,26 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -#! /bin/bash - -./qrsh 127.0.0.1 5813 \ - mrg23 exec_wait /home/mick/redhat/qrsh/qrsh_run/my_command foo bar baz -echo "my_command returned an exit code of $?" diff --git a/cpp/src/tests/qrsh_utils/5_exited b/cpp/src/tests/qrsh_utils/5_exited deleted file mode 100755 index 4fec1dcc79..0000000000 --- a/cpp/src/tests/qrsh_utils/5_exited +++ /dev/null @@ -1,64 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -#! /bin/bash - -path=/home/mick/redhat/qrsh/qrsh_run - -echo "Running command_3 ..." -./qrsh 127.0.0.1 5813 \ - mrg23 command_3 $path/my_command foo bar baz - -echo "Now I do some other stuff..." -sleep 1 -echo "And then some more stuff..." -sleep 1 -echo "and so on..." -sleep 1 - -echo "Now I'm waiting for command_3 ..." -./qrsh 127.0.0.1 5813 \ - mrg23 exited command_3 -echo "has command_3 exited: $? ." -sleep 5 - -./qrsh 127.0.0.1 5813 \ - mrg23 exited command_3 -echo "has command_3 exited: $? ." -sleep 5 - -./qrsh 127.0.0.1 5813 \ - mrg23 exited command_3 -echo "has command_3 exited: $? ." -sleep 5 - -./qrsh 127.0.0.1 5813 \ - mrg23 exited command_3 -echo "has command_3 exited: $? ." -sleep 5 - -./qrsh 127.0.0.1 5813 \ - mrg23 exited command_3 -echo "has command_3 exited: $? ." -sleep 5 - - - diff --git a/cpp/src/tests/qrsh_utils/7_get_output b/cpp/src/tests/qrsh_utils/7_get_output deleted file mode 100755 index 59911089ec..0000000000 --- a/cpp/src/tests/qrsh_utils/7_get_output +++ /dev/null @@ -1,44 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -#! /bin/bash - -echo "Run a command..." -./qrsh 127.0.0.1 5813 \ - mrg23 command_4 /home/mick/redhat/qrsh/qrsh_run/my_command foo bar baz - -echo "Wait for a while..." -sleep 20 - -echo "Get stderr output:" -echo "------------- begin stderr ---------------" -./qrsh 127.0.0.1 5813 \ - mrg23 get command_4 stderr -echo "------------- end stderr ---------------" -echo " " -echo " " -echo " " -echo "Get stdout output:" -echo "------------- begin stdout ---------------" -./qrsh 127.0.0.1 5813 \ - mrg23 get command_4 stdout -echo "------------- end stdout ---------------" - diff --git a/cpp/src/tests/qrsh_utils/8_any b/cpp/src/tests/qrsh_utils/8_any deleted file mode 100755 index 2a922ea0e0..0000000000 --- a/cpp/src/tests/qrsh_utils/8_any +++ /dev/null @@ -1,43 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -#! /bin/bash - -echo "asking any server to say his name ..." -./qrsh 127.0.0.1 5813 \ - any sayName -sleep 1 -echo "asking any server to say his name ..." -./qrsh 127.0.0.1 5813 \ - any sayName -sleep 1 -echo "asking any server to say his name ..." -./qrsh 127.0.0.1 5813 \ - any sayName -sleep 1 -echo "asking any server to say his name ..." -./qrsh 127.0.0.1 5813 \ - any sayName -sleep 1 - - - - diff --git a/cpp/src/tests/qrsh_utils/9_alias b/cpp/src/tests/qrsh_utils/9_alias deleted file mode 100755 index a4cfdfdf9a..0000000000 --- a/cpp/src/tests/qrsh_utils/9_alias +++ /dev/null @@ -1,38 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -#! /bin/bash - -# Make a group of two of the servers, using "alias", -# and send the group a command. - -qrsh 127.0.0.1 5813 \ - mrg22 alias group_1 -qrsh 127.0.0.1 5813 \ - mrg23 alias group_1 - -echo "Asking group_1 to say their names... " -qrsh 127.0.0.1 5813 \ - group_1 sayName - - - - diff --git a/cpp/src/tests/qrsh_utils/qrsh_forever.cpp b/cpp/src/tests/qrsh_utils/qrsh_forever.cpp deleted file mode 100644 index 191a9bca11..0000000000 --- a/cpp/src/tests/qrsh_utils/qrsh_forever.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - - -#include <stdio.h> -#include <unistd.h> - - - -main ( int argc, char ** argv ) -{ - fprintf ( stderr, "Hello, I am the Forever Example Child!\n"); - fprintf ( stderr, "my %d arguments are:\n", argc - 1 ); - - int i; - for ( i = 1; i < argc; ++ i ) - fprintf ( stderr, "arg %d: |%s|\n", i, argv[i] ); - - for ( i = 0; i >= 0; ++ i ) - { - fprintf ( stderr, "child sleeping forever %d ...\n" , i); - sleep ( 1 ); - } - - fprintf ( stderr, "child exiting with code 12.\n" ); - - return 12; -} - - - - diff --git a/cpp/src/tests/qrsh_utils/qsh_doc.txt b/cpp/src/tests/qrsh_utils/qsh_doc.txt deleted file mode 100644 index ad5990b38b..0000000000 --- a/cpp/src/tests/qrsh_utils/qsh_doc.txt +++ /dev/null @@ -1,309 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -############################################## - qrsh: a Qpid-based remote shell utility - - Last updated: 3 Aug 09 Mick Goulish -############################################## - - - -============================= -Overview -============================= - - You're writing a multi-box test, and you want to write a - shell script in which you start processes on other boxes - and kill them (or send arbitrary signals to them). - - But ssh doesn't let you signal them, and bash isn't the - greatest language in the world for creating data structures - (like you need to associate the PIDs with box names and - executable names.) - - Qsh is a utility implemented on Qpid that you can use from - within your bash script, or any other scripting language. - With it, you can: - - 1. run any executable on any box in your cluster. - - 2. don't worry about PIDs and box-names. You associate - your own abstract names with the executable instances, - and then use those names in the rest of your script. - I.e. "broker_1" "sender_3" etc. - - 3. Launch the executable and wait until it returns, and - get its exit code. - - 4. Launch your executable and do other stuff, then come - back later and see if it has exited. - - 5. Get whatever it sent to stdout or stderr. - - 6. Get the contents of any other file. - - 7. send a command to all your boxes at once - - 8. send a command to a randomly selected box. - - 9. define groups of boxes, and send a command simultaneously - to all boxes in a given group. - - - - -============================= -Using It -============================= - - 1. You need to run a Qpid broker. - - 2. You start a Qpid client ( which is called a qrsh_server ) - on all the boxes you care about. And you give them all - names like "mrg13", "mrg14" etc. The names can be anything - you want, but I've always used one qrsh_server per box, - and given it the box name. ( However, you can run two on - one box, they won't collide. ) - - 3. After you start all servers, send a "start" command to any - one of them: - - 4. The qrsh_servers use the fanout exchange to talk to each - other. - - 5. In your script, you run an executable called "qrsh". It knows - how to talk to the servers, do what you want, and retrieve - the data you want. - - - example start script: (this does 4 servers on the same box) - ------------------------------------------------------------- - - echo "Starting server mrg22 ..." - ./qrsh_server mrg22 ./qrsh_run 127.0.0.1 5813 & - - echo "Starting server mrg23 ..." - ./qrsh_server mrg23 ./qrsh_run 127.0.0.1 5813 & - - echo "Starting server mrg24 ..." - ./qrsh_server mrg24 ./qrsh_run 127.0.0.1 5813 & - - echo "Starting server mrg25 ..." - ./qrsh_server mrg25 ./qrsh_run 127.0.0.1 5813 & - - echo "Issuing start command..." - sleep 2 - ./qrsh 127.0.0.1 5813 mrg22 start - sleep 1 - - echo "Ready." - - # end of script. - - - - - - -============================= -Qrsh Syntax -============================= - - qrsh host port server_name command_name arg* - - - "host" and "port" specify the Qpid server to connect to. - - "server_name" can be anything you want. I always use the name - of the box that the server is running on. - - "command_name" is the name that you choose to assign to - the process you are running. Each process that you decide - to name must have a unique name within this script. - - Or it could be a reserved command name, that Qsh - interprets in a special way. - - Reserved command names are: - - exec - exec_wait - exited - get - - "exec" means "interpret the rest of the command line as a - command to be executed by the designated server. - - "exec_wait" means same as "exec", but wait for the command - to terminate, and return its exit code. - - "exited" -- you provide 1 arg, which is an abstract - process name. qrsh returns 1 if that process has exited, - else 0. - - "get" -- you provide one arg which is a path. qrsh returns - (by printing to stdout) the contents of that file. - - "arg*" is zero or more arguments. They are interpreted - differently depending on whether you are using one of - the above reserved command names, or making up your own - abstract name for a command. - - - - -============================= -Examples -============================= - - 1. Run a process on a remote box. - - qrsh mrg23 command_1 /usr/sbin/whatever foo bar baz - - Returns immediately. - - - - 2. Kill a process that you started earlier: - - qrsh mrg23 exec kill -9 command_1 - - After the word "exec" put any command line you want. - The server you're sending this to will replace all abstract - names in the command with process IDs. ( In this example, - just the word "command_1" will be replaced. ) Then it will - execute the command. - - - - 3. Execute a command, and wait for it to finish - - qrsh mrg23 exec_wait command_name args - - - - 4. Check on whether a command you issude earlier has exited. - - ./qrsh mrg23 exited command_3 - - Returns 1 if it has exited, else 0. - - - - 5. Get the contents of a file from the remote system: - - ./qrsh mrg23 get /tmp/foo - - Prints the contents to stdout. - - - - 6. Send a command to all servers at once: - - # This example causes them all to print thir names to stderr. - ./qrsh all sayName - - - 7. Define a group of servers and send a command to that group. - - #! /bin/bash - - # Make a group of two of the servers, using "alias", - # and send the group a command. - - qrsh 127.0.0.1 5813 \ - mrg22 alias group_1 - - qrsh 127.0.0.1 5813 \ - mrg23 alias group_1 - - echo "Asking group_1 to say their names... " - qrsh 127.0.0.1 5813 \ - group_1 sayName - - # end of script. - - - - - 8. Execute a command and get its stdout and stderr contents. - - #! /bin/bash - - echo "Run a command..." - ./qrsh 127.0.0.1 5813 \ - mrg23 command_4 my_command foo bar baz - - echo "Wait for a while..." - sleep 10 - - echo "Get stderr output:" - echo "------------- begin stderr ---------------" - ./qrsh 127.0.0.1 5813 \ - mrg23 get command_4 stderr - echo "------------- end stderr ---------------" - echo " " - - echo " " - echo "Get stdout output:" - echo "------------- begin stdout ---------------" - ./qrsh 127.0.0.1 5813 \ - mrg23 get command_4 stdout - echo "------------- end stdout ---------------" - - # end of script. - - - - - 9. Send a command to one of your servers, selected - at random. - - #! /bin/bash - - # I do it multiple times here, so I can see - # that it really is selecting randomly. - - echo "asking any server to say his name ..." - ./qrsh 127.0.0.1 5813 \ - any sayName - sleep 1 - - echo "asking any server to say his name ..." - ./qrsh 127.0.0.1 5813 \ - any sayName - sleep 1 - - echo "asking any server to say his name ..." - ./qrsh 127.0.0.1 5813 \ - any sayName - sleep 1 - - echo "asking any server to say his name ..." - ./qrsh 127.0.0.1 5813 \ - any sayName - - # end of script. - - - - diff --git a/cpp/src/tests/queue_flow_limit_tests.py b/cpp/src/tests/queue_flow_limit_tests.py new file mode 100644 index 0000000000..dec7cfb3af --- /dev/null +++ b/cpp/src/tests/queue_flow_limit_tests.py @@ -0,0 +1,371 @@ +#!/usr/bin/env python +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import sys +from qpid.testlib import TestBase010 +from qpid import datatypes, messaging +from qpid.messaging import Message, Empty +from threading import Thread, Lock +from logging import getLogger +from time import sleep, time +from os import environ, popen + +class QueueFlowLimitTests(TestBase010): + + def __getattr__(self, name): + if name == "assertGreater": + return lambda a, b: self.failUnless(a > b) + else: + raise AttributeError + + def _create_queue(self, name, + stop_count=None, resume_count=None, + stop_size=None, resume_size=None, + max_size=None, max_count=None): + """ Create a queue with the given flow settings via the queue.declare + command. + """ + args={} + if (stop_count is not None): + args["qpid.flow_stop_count"] = stop_count; + if (resume_count is not None): + args["qpid.flow_resume_count"] = resume_count; + if (stop_size is not None): + args["qpid.flow_stop_size"] = stop_size; + if (resume_size is not None): + args["qpid.flow_resume_size"] = resume_size; + if (max_size is not None): + args["qpid.max_size"] = max_size; + if (max_count is not None): + args["qpid.max_count"] = max_count; + + + self.session.queue_declare(queue=name, arguments=args) + + qs = self.qmf.getObjects(_class="queue") + for i in qs: + if i.name == name: + # verify flow settings + if (stop_count is not None): + self.assertEqual(i.arguments.get("qpid.flow_stop_count"), stop_count) + if (resume_count is not None): + self.assertEqual(i.arguments.get("qpid.flow_resume_count"), resume_count) + if (stop_size is not None): + self.assertEqual(i.arguments.get("qpid.flow_stop_size"), stop_size) + if (resume_size is not None): + self.assertEqual(i.arguments.get("qpid.flow_resume_size"), resume_size) + if (max_size is not None): + self.assertEqual(i.arguments.get("qpid.max_size"), max_size) + if (max_count is not None): + self.assertEqual(i.arguments.get("qpid.max_count"), max_count) + self.failIf(i.flowStopped) + return i.getObjectId() + self.fail("Unable to create queue '%s'" % name) + return None + + + def _delete_queue(self, name): + """ Delete a named queue + """ + self.session.queue_delete(queue=name) + + + def _start_qpid_send(self, queue, count, content="X", capacity=100): + """ Use the qpid-send client to generate traffic to a queue. + """ + command = "qpid-send" + \ + " -b" + " %s:%s" % (self.broker.host, self.broker.port) \ + + " -a " + str(queue) \ + + " --messages " + str(count) \ + + " --content-string " + str(content) \ + + " --capacity " + str(capacity) + return popen(command) + + def _start_qpid_receive(self, queue, count, timeout=5): + """ Use the qpid-receive client to consume from a queue. + Note well: prints one line of text to stdout for each consumed msg. + """ + command = "qpid-receive" + \ + " -b " + "%s:%s" % (self.broker.host, self.broker.port) \ + + " -a " + str(queue) \ + + " --messages " + str(count) \ + + " --timeout " + str(timeout) \ + + " --print-content yes" + return popen(command) + + def test_qpid_config_cmd(self): + """ Test the qpid-config command's ability to configure a queue's flow + control thresholds. + """ + tool = environ.get("QPID_CONFIG_EXEC") + if tool: + command = tool + \ + " --broker-addr=%s:%s " % (self.broker.host, self.broker.port) \ + + "add queue test01 --flow-stop-count=999" \ + + " --flow-resume-count=55 --flow-stop-size=5000000" \ + + " --flow-resume-size=100000" + cmd = popen(command) + rc = cmd.close() + self.assertEqual(rc, None) + + # now verify the settings + self.startQmf(); + qs = self.qmf.getObjects(_class="queue") + for i in qs: + if i.name == "test01": + self.assertEqual(i.arguments.get("qpid.flow_stop_count"), 999) + self.assertEqual(i.arguments.get("qpid.flow_resume_count"), 55) + self.assertEqual(i.arguments.get("qpid.flow_stop_size"), 5000000) + self.assertEqual(i.arguments.get("qpid.flow_resume_size"), 100000) + self.failIf(i.flowStopped) + break; + self.assertEqual(i.name, "test01") + self._delete_queue("test01") + + + def test_flow_count(self): + """ Create a queue with count-based flow limit. Spawn several + producers which will exceed the limit. Verify limit exceeded. Consume + all messages. Verify flow control released. + """ + self.startQmf(); + oid = self._create_queue("test-q", stop_count=373, resume_count=229) + self.assertEqual(self.qmf.getObjects(_objectId=oid)[0].flowStoppedCount, 0) + + sndr1 = self._start_qpid_send("test-q", count=1213, content="XXX", capacity=50); + sndr2 = self._start_qpid_send("test-q", count=797, content="Y", capacity=13); + sndr3 = self._start_qpid_send("test-q", count=331, content="ZZZZZ", capacity=149); + totalMsgs = 1213 + 797 + 331 + + # wait until flow control is active + deadline = time() + 10 + while (not self.qmf.getObjects(_objectId=oid)[0].flowStopped) and \ + time() < deadline: + pass + self.failUnless(self.qmf.getObjects(_objectId=oid)[0].flowStopped) + depth = self.qmf.getObjects(_objectId=oid)[0].msgDepth + self.assertGreater(depth, 373) + + # now wait until the enqueues stop happening - ensure that + # not all msgs have been sent (senders are blocked) + sleep(1) + newDepth = self.qmf.getObjects(_objectId=oid)[0].msgDepth + while depth != newDepth: + depth = newDepth; + sleep(1) + newDepth = self.qmf.getObjects(_objectId=oid)[0].msgDepth + self.assertGreater(totalMsgs, depth) + + # drain the queue + rcvr = self._start_qpid_receive("test-q", + count=totalMsgs) + count = 0; + x = rcvr.readline() # prints a line for each received msg + while x: + count += 1; + x = rcvr.readline() + + sndr1.close(); + sndr2.close(); + sndr3.close(); + rcvr.close(); + + self.assertEqual(count, totalMsgs) + self.failIf(self.qmf.getObjects(_objectId=oid)[0].flowStopped) + self.failUnless(self.qmf.getObjects(_objectId=oid)[0].flowStoppedCount) + + self._delete_queue("test-q") + + + def test_flow_size(self): + """ Create a queue with size-based flow limit. Spawn several + producers which will exceed the limit. Verify limit exceeded. Consume + all messages. Verify flow control released. + """ + self.startQmf(); + oid = self._create_queue("test-q", stop_size=351133, resume_size=251143) + + sndr1 = self._start_qpid_send("test-q", count=1699, content="X"*439, capacity=53); + sndr2 = self._start_qpid_send("test-q", count=1129, content="Y"*631, capacity=13); + sndr3 = self._start_qpid_send("test-q", count=881, content="Z"*823, capacity=149); + totalMsgs = 1699 + 1129 + 881 + + # wait until flow control is active + deadline = time() + 10 + while (not self.qmf.getObjects(_objectId=oid)[0].flowStopped) and \ + time() < deadline: + pass + self.failUnless(self.qmf.getObjects(_objectId=oid)[0].flowStopped) + self.assertGreater(self.qmf.getObjects(_objectId=oid)[0].byteDepth, 351133) + + # now wait until the enqueues stop happening - ensure that + # not all msgs have been sent (senders are blocked) + depth = self.qmf.getObjects(_objectId=oid)[0].msgDepth + sleep(1) + newDepth = self.qmf.getObjects(_objectId=oid)[0].msgDepth + while depth != newDepth: + depth = newDepth; + sleep(1) + newDepth = self.qmf.getObjects(_objectId=oid)[0].msgDepth + self.assertGreater(totalMsgs, depth) + + # drain the queue + rcvr = self._start_qpid_receive("test-q", + count=totalMsgs) + count = 0; + x = rcvr.readline() # prints a line for each received msg + while x: + count += 1; + x = rcvr.readline() + + sndr1.close(); + sndr2.close(); + sndr3.close(); + rcvr.close(); + + self.assertEqual(count, totalMsgs) + self.failIf(self.qmf.getObjects(_objectId=oid)[0].flowStopped) + + self._delete_queue("test-q") + + + def verify_limit(self, testq): + """ run a limit check against the testq object + """ + + testq.mgmt = self.qmf.getObjects(_objectId=testq.oid)[0] + + # fill up the queue, waiting until flow control is active + sndr1 = self._start_qpid_send(testq.mgmt.name, count=testq.sendCount, content=testq.content) + deadline = time() + 10 + while (not testq.mgmt.flowStopped) and time() < deadline: + testq.mgmt.update() + + self.failUnless(testq.verifyStopped()) + + # now consume enough messages to drop below the flow resume point, and + # verify flow control is released. + rcvr = self._start_qpid_receive(testq.mgmt.name, count=testq.consumeCount) + rcvr.readlines() # prints a line for each received msg + rcvr.close(); + + # we should now be below the resume threshold + self.failUnless(testq.verifyResumed()) + + self._delete_queue(testq.mgmt.name) + sndr1.close(); + + + def test_default_flow_count(self): + """ Create a queue with count-based size limit, and verify the computed + thresholds using the broker's default ratios. + """ + class TestQ: + def __init__(self, oid): + # Use the broker-wide default flow thresholds of 80%/70% (see + # run_queue_flow_limit_tests) to base the thresholds off the + # queue's max_count configuration parameter + # max_count == 1000 -> stop == 800, resume == 700 + self.oid = oid + self.sendCount = 1000 + self.consumeCount = 301 # (send - resume) + 1 to reenable flow + self.content = "X" + def verifyStopped(self): + self.mgmt.update() + return self.mgmt.flowStopped and (self.mgmt.msgDepth > 800) + def verifyResumed(self): + self.mgmt.update() + return (not self.mgmt.flowStopped) and (self.mgmt.msgDepth < 700) + + self.startQmf(); + oid = self._create_queue("test-X", max_count=1000) + self.verify_limit(TestQ(oid)) + + + def test_default_flow_size(self): + """ Create a queue with byte-based size limit, and verify the computed + thresholds using the broker's default ratios. + """ + class TestQ: + def __init__(self, oid): + # Use the broker-wide default flow thresholds of 80%/70% (see + # run_queue_flow_limit_tests) to base the thresholds off the + # queue's max_count configuration parameter + # max_size == 10000 -> stop == 8000 bytes, resume == 7000 bytes + self.oid = oid + self.sendCount = 2000 + self.consumeCount = 601 # (send - resume) + 1 to reenable flow + self.content = "XXXXX" # 5 bytes per message sent. + def verifyStopped(self): + self.mgmt.update() + return self.mgmt.flowStopped and (self.mgmt.byteDepth > 8000) + def verifyResumed(self): + self.mgmt.update() + return (not self.mgmt.flowStopped) and (self.mgmt.byteDepth < 7000) + + self.startQmf(); + oid = self._create_queue("test-Y", max_size=10000) + self.verify_limit(TestQ(oid)) + + + def test_blocked_queue_delete(self): + """ Verify that blocked senders are unblocked when a queue that is flow + controlled is deleted. + """ + + class BlockedSender(Thread): + def __init__(self, tester, queue, count, capacity=10): + self.tester = tester + self.queue = queue + self.count = count + self.capacity = capacity + Thread.__init__(self) + self.done = False + self.start() + def run(self): + # spawn qpid-send + p = self.tester._start_qpid_send(self.queue, + self.count, + self.capacity) + p.close() # waits for qpid-send to complete + self.done = True + + self.startQmf(); + oid = self._create_queue("kill-q", stop_size=10, resume_size=2) + q = self.qmf.getObjects(_objectId=oid)[0] + self.failIf(q.flowStopped) + + sender = BlockedSender(self, "kill-q", count=100) + # wait for flow control + deadline = time() + 10 + while (not q.flowStopped) and time() < deadline: + q.update() + + self.failUnless(q.flowStopped) + self.failIf(sender.done) # sender blocked + + self._delete_queue("kill-q") + sender.join(5) + self.failIf(sender.isAlive()) + self.failUnless(sender.done) + + + + diff --git a/cpp/src/tests/replication_test b/cpp/src/tests/replication_test index 691fd20b0c..8c37568875 100755 --- a/cpp/src/tests/replication_test +++ b/cpp/src/tests/replication_test @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # # Licensed to the Apache Software Foundation (ASF) under one diff --git a/cpp/src/tests/run_acl_tests b/cpp/src/tests/run_acl_tests index aff13408ed..41f41e20e1 100755 --- a/cpp/src/tests/run_acl_tests +++ b/cpp/src/tests/run_acl_tests @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # # Licensed to the Apache Software Foundation (ASF) under one diff --git a/cpp/src/tests/run_cli_tests b/cpp/src/tests/run_cli_tests index 3f1388b9f5..ec5c71b646 100755 --- a/cpp/src/tests/run_cli_tests +++ b/cpp/src/tests/run_cli_tests @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # # Licensed to the Apache Software Foundation (ASF) under one @@ -70,7 +70,8 @@ stop_brokers() { if test -d ${PYTHON_DIR} ; then start_brokers echo "Running CLI tests using brokers on ports $LOCAL_PORT $REMOTE_PORT" - $QPID_PYTHON_TEST -m cli_tests -b localhost:$LOCAL_PORT -Dremote-port=$REMOTE_PORT -Dcli-dir=$CLI_DIR $targs $@ + PYTHON_TESTS=${PYTHON_TESTS:-$*} + $QPID_PYTHON_TEST -m cli_tests -b localhost:$LOCAL_PORT -Dremote-port=$REMOTE_PORT -Dcli-dir=$CLI_DIR $targs $PYTHON_TESTS $@ RETCODE=$? stop_brokers if test x$RETCODE != x0; then diff --git a/cpp/src/tests/run_federation_sys_tests b/cpp/src/tests/run_federation_sys_tests new file mode 100755 index 0000000000..f5f772d72e --- /dev/null +++ b/cpp/src/tests/run_federation_sys_tests @@ -0,0 +1,97 @@ +#!/bin/bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Run the federation system tests. + +source ./test_env.sh + +MODULENAME=federation_sys + +# Test for clustering +ps -u root | grep 'aisexec\|corosync' > /dev/null +if (( $? == 0 )); then + CLUSTERING_ENABLED=1 +else + echo "WARNING: No clustering detected; tests using it will be ignored." +fi + +# Test for long test +if [[ "$1" == "LONG_TEST" ]]; then + USE_LONG_TEST=1 + shift # get rid of this param so it is not treated as a test name +fi + +trap stop_brokers INT TERM QUIT + +SKIPTESTS="-i federation_sys.E_* -i federation_sys.F_* -i federation_sys.G_* -i federation_sys.H_*" +if [ -z ${USE_LONG_TEST} ]; then + SKIPTESTS="-i federation_sys.A_Long* -i federation_sys.B_Long* ${SKIPTESTS}" +fi +echo "WARNING: Tests using persistence will be ignored." +if [ -z ${CLUSTERING_ENABLED} ]; then + SKIPTESTS="${SKIPTESTS} -i federation_sys.C_* -i federation_sys.D_*" +elif [ -z ${USE_LONG_TEST} ]; then + SKIPTESTS="${SKIPTESTS} -i federation_sys.C_Long* -i federation_sys.D_Long*" +fi + +start_brokers() { + start_broker() { + ${QPIDD_EXEC} --daemon --port 0 --auth no --no-data-dir $1 > qpidd.port + PORT=`cat qpidd.port` + eval "$2=${PORT}" + } + start_broker "" LOCAL_PORT + start_broker "" REMOTE_PORT + if [ -n "${CLUSTERING_ENABLED}" ]; then + start_broker "--load-module ${CLUSTER_LIB} --cluster-name test-cluster-1" CLUSTER_C1_1 + start_broker "--load-module ${CLUSTER_LIB} --cluster-name test-cluster-1" CLUSTER_C1_2 + start_broker "--load-module ${CLUSTER_LIB} --cluster-name test-cluster-2" CLUSTER_C2_1 + start_broker "--load-module ${CLUSTER_LIB} --cluster-name test-cluster-2" CLUSTER_C2_2 + fi + rm qpidd.port +} + +stop_brokers() { + ${QPIDD_EXEC} -q --port ${LOCAL_PORT} + ${QPIDD_EXEC} -q --port ${REMOTE_PORT} + if [ -n "${CLUSTERING_ENABLED}" ]; then + ${QPID_CLUSTER_EXEC} --all-stop --force localhost:${CLUSTER_C1_1} + ${QPID_CLUSTER_EXEC} --all-stop --force localhost:${CLUSTER_C2_1} + fi +} + +if test -d ${PYTHON_DIR} ; then + start_brokers + if [ -z ${CLUSTERING_ENABLED} ]; then + echo "Running federation tests using brokers on local port ${LOCAL_PORT}, remote port ${REMOTE_PORT} (NOTE: clustering is DISABLED)" + else + echo "Running federation tests using brokers on local port ${LOCAL_PORT}, remote port ${REMOTE_PORT}, local cluster nodes ${CLUSTER_C1_1} ${CLUSTER_C1_2}, remote cluster nodes ${CLUSTER_C2_1} ${CLUSTER_C2_2}" + fi + if [ -z ${USE_LONG_TEST} ]; then + echo "NOTE: To run a full set of federation system tests, use \"make check-long\". To test with persistence, run the store version of this script." + fi + ${QPID_PYTHON_TEST} -m ${MODULENAME} ${SKIPTESTS} -b localhost:${REMOTE_PORT} -Dlocal-port=${LOCAL_PORT} -Dremote-port=${REMOTE_PORT} -Dlocal-cluster-ports="${CLUSTER_C1_1} ${CLUSTER_C1_2}" -Dremote-cluster-ports="${CLUSTER_C2_1} ${CLUSTER_C2_2}" $@ + RETCODE=$? + stop_brokers + if test x${RETCODE} != x0; then + echo "FAIL federation tests"; exit 1; + fi +fi diff --git a/cpp/src/tests/run_federation_tests b/cpp/src/tests/run_federation_tests index 4be27a2e85..14af4807ba 100755 --- a/cpp/src/tests/run_federation_tests +++ b/cpp/src/tests/run_federation_tests @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # # Licensed to the Apache Software Foundation (ASF) under one @@ -55,7 +55,7 @@ stop_brokers() { if test -d ${PYTHON_DIR} ; then start_brokers echo "Running federation tests using brokers on ports $LOCAL_PORT $REMOTE_PORT $REMOTE_B1 $REMOTE_B2" - $QPID_PYTHON_TEST -m federation $SKIPTESTS -b localhost:$LOCAL_PORT -Dremote-port=$REMOTE_PORT -Dextra-brokers="$REMOTE_B1 $REMOTE_B2" $@ + $QPID_PYTHON_TEST -m federation "$SKIPTESTS" -b localhost:$LOCAL_PORT -Dremote-port=$REMOTE_PORT -Dextra-brokers="$REMOTE_B1 $REMOTE_B2" $@ RETCODE=$? stop_brokers if test x$RETCODE != x0; then diff --git a/cpp/src/tests/run_header_test b/cpp/src/tests/run_header_test index 07658343e7..34008132cc 100755 --- a/cpp/src/tests/run_header_test +++ b/cpp/src/tests/run_header_test @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # # Licensed to the Apache Software Foundation (ASF) under one diff --git a/cpp/src/tests/run_long_federation_sys_tests b/cpp/src/tests/run_long_federation_sys_tests new file mode 100644 index 0000000000..69dc08d11c --- /dev/null +++ b/cpp/src/tests/run_long_federation_sys_tests @@ -0,0 +1,24 @@ +#! /bin/bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Run the federation system tests (long version). + +./run_federation_sys_tests LONG_TEST $@ diff --git a/cpp/src/tests/run_msg_group_tests b/cpp/src/tests/run_msg_group_tests new file mode 100755 index 0000000000..8423022521 --- /dev/null +++ b/cpp/src/tests/run_msg_group_tests @@ -0,0 +1,66 @@ +#!/bin/bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +#script to run a sequence of message group queue tests via make + +#setup path to find qpid-config and msg_group_test progs +source ./test_env.sh + +export PATH=$PWD:$srcdir:$PYTHON_COMMANDS:$PATH + +#set port to connect to via env var +test -s qpidd.port && QPID_PORT=`cat qpidd.port` + +#trap cleanup INT TERM QUIT + +QUEUE_NAME="group-queue" +GROUP_KEY="My-Group-Id" + +BROKER_URL="${QPID_BROKER:-localhost}:${QPID_PORT:-5672}" + +run_test() { + $@ +} + +##set -x + +declare -i i=0 +declare -a tests +tests=("qpid-config -a $BROKER_URL add queue $QUEUE_NAME --group-header=${GROUP_KEY} --shared-groups" + "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 103 --group-size 13 --receivers 2 --senders 3 --capacity 3 --ack-frequency 7 --randomize-group-size --interleave 3" + "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 103 --group-size 13 --receivers 2 --senders 3 --capacity 7 --ack-frequency 7 --randomize-group-size" + "qpid-config -a $BROKER_URL add queue ${QUEUE_NAME}-two --group-header=${GROUP_KEY} --shared-groups" + "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 103 --group-size 13 --receivers 2 --senders 3 --capacity 7 --ack-frequency 3 --randomize-group-size" + "msg_group_test -b $BROKER_URL -a ${QUEUE_NAME}-two --group-key $GROUP_KEY --messages 103 --group-size 13 --receivers 2 --senders 3 --capacity 3 --ack-frequency 7 --randomize-group-size --interleave 5" + "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 59 --group-size 5 --receivers 2 --senders 3 --capacity 1 --ack-frequency 3 --randomize-group-size" + "qpid-config -a $BROKER_URL del queue ${QUEUE_NAME}-two --force" + "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 59 --group-size 3 --receivers 2 --senders 3 --capacity 1 --ack-frequency 1 --randomize-group-size" + "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 211 --group-size 13 --receivers 2 --senders 3 --capacity 47 --ack-frequency 79 --interleave 53" + "qpid-config -a $BROKER_URL del queue $QUEUE_NAME --force") + +while [ -n "${tests[i]}" ]; do + run_test ${tests[i]} + RETCODE=$? + if test x$RETCODE != x0; then + echo "FAILED message group test. Failed command: \"${tests[i]}\""; + exit 1; + fi + i+=1 +done diff --git a/cpp/src/tests/run_msg_group_tests_soak b/cpp/src/tests/run_msg_group_tests_soak new file mode 100755 index 0000000000..5231f74755 --- /dev/null +++ b/cpp/src/tests/run_msg_group_tests_soak @@ -0,0 +1,60 @@ +#!/bin/bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +#script to run a sequence of long-running message group tests via make + +#setup path to find qpid-config and msg_group_test test progs +source ./test_env.sh + +export PATH=$PWD:$srcdir:$PYTHON_COMMANDS:$PATH + +#set port to connect to via env var +test -s qpidd.port && QPID_PORT=`cat qpidd.port` + +#trap cleanup INT TERM QUIT + +QUEUE_NAME="group-queue" +GROUP_KEY="My-Group-Id" + +BROKER_URL="${QPID_BROKER:-localhost}:${QPID_PORT:-5672}" + +run_test() { + $@ +} + +##set -x + +declare -i i=0 +declare -a tests +tests=("qpid-config -a $BROKER_URL add queue $QUEUE_NAME --group-header=${GROUP_KEY} --shared-groups" + "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 10007 --receivers 3 --senders 5 --group-size 211 --randomize-group-size --capacity 47 --ack-frequency 97" + "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 10007 --receivers 3 --senders 5 --group-size 211 --randomize-group-size --capacity 79 --ack-frequency 79" + "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 10007 --receivers 3 --senders 5 --group-size 211 --randomize-group-size --capacity 97 --ack-frequency 47" + "qpid-config -a $BROKER_URL del queue $QUEUE_NAME --force") + +while [ -n "${tests[i]}" ]; do + run_test ${tests[i]} + RETCODE=$? + if test x$RETCODE != x0; then + echo "FAILED message group test. Failed command: \"${tests[i]}\""; + exit 1; + fi + i+=1 +done diff --git a/cpp/src/tests/run_queue_flow_limit_tests b/cpp/src/tests/run_queue_flow_limit_tests new file mode 100755 index 0000000000..f921cf5e7e --- /dev/null +++ b/cpp/src/tests/run_queue_flow_limit_tests @@ -0,0 +1,57 @@ +#!/bin/sh + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Run tests against Queue producer flow control. + +source ./test_env.sh +test -d $PYTHON_DIR || { echo "Skipping queue flow control tests, no python dir."; exit 0; } + +LOG_FILE=queue_flow_limit_test.log +PORT="" + +trap stop_broker INT TERM QUIT + +error() { + echo $* + exit 1; +} + +start_broker() { + # Note: if you change the DEFAULT_THRESHOLDS, you will need to update queue_flow_limit_tests.py + DEFAULT_THRESHOLDS="--default-flow-stop-threshold=80 --default-flow-resume-threshold=70" + rm -rf $LOG_FILE + PORT=$($QPIDD_EXEC $DEFAULT_THRESHOLDS --auth=no --no-module-dir --daemon --port=0 -t --log-to-file $LOG_FILE) || error "Could not start broker" +} + +stop_broker() { + test -n "$PORT" && $QPIDD_EXEC --no-module-dir --quit --port $PORT +} + +start_broker +echo "Running Queue flow limit tests using broker on port $PORT" +$QPID_PYTHON_TEST -m queue_flow_limit_tests $SKIPTESTS -b localhost:$PORT $@ +RETCODE=$? +stop_broker +if test x$RETCODE != x0; then + echo "FAIL queue flow limit tests"; exit 1; +fi +rm -rf $LOG_FILE + diff --git a/cpp/src/tests/run_store_tests.ps1 b/cpp/src/tests/run_store_tests.ps1 index 76b46737f0..b2f0b1ccd8 100644 --- a/cpp/src/tests/run_store_tests.ps1 +++ b/cpp/src/tests/run_store_tests.ps1 @@ -111,7 +111,7 @@ Invoke-Expression "$prog --quit --port $env:QPID_PORT" | Write-Output # Test 2... store.py starts/stops/restarts its own brokers $tests = "*" -$env:PYTHONPATH="$PYTHON_DIR;$srcdir" +$env:PYTHONPATH="$PYTHON_DIR;$QMF_LIB;$srcdir" $env:QPIDD_EXEC="$prog" $env:STORE_LIB="$store_dir\store$suffix.dll" if ($test_store -eq "MSSQL") { diff --git a/cpp/src/tests/run_test b/cpp/src/tests/run_test index 4b227621bc..6ec1fd892b 100755 --- a/cpp/src/tests/run_test +++ b/cpp/src/tests/run_test @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file diff --git a/cpp/src/tests/sasl.mk b/cpp/src/tests/sasl.mk index 5b8419f604..69b24c3f8a 100644 --- a/cpp/src/tests/sasl.mk +++ b/cpp/src/tests/sasl.mk @@ -30,9 +30,21 @@ check_PROGRAMS+=sasl_version sasl_version_SOURCES=sasl_version.cpp sasl_version_LDADD=$(lib_client) -TESTS += run_cluster_authentication_test sasl_fed sasl_fed_ex +TESTS += run_cluster_authentication_test sasl_fed sasl_fed_ex_dynamic sasl_fed_ex_link sasl_fed_ex_queue sasl_fed_ex_route sasl_fed_ex_route_cluster sasl_fed_ex_link_cluster sasl_fed_ex_queue_cluster sasl_fed_ex_dynamic_cluster sasl_no_dir LONG_TESTS += run_cluster_authentication_soak -EXTRA_DIST += run_cluster_authentication_test sasl_fed sasl_fed_ex run_cluster_authentication_soak +EXTRA_DIST += run_cluster_authentication_test \ + sasl_fed \ + sasl_fed_ex \ + run_cluster_authentication_soak \ + sasl_fed_ex_dynamic \ + sasl_fed_ex_link \ + sasl_fed_ex_queue \ + sasl_fed_ex_route \ + sasl_fed_ex_dynamic_cluster \ + sasl_fed_ex_link_cluster \ + sasl_fed_ex_queue_cluster \ + sasl_fed_ex_route_cluster \ + sasl_no_dir endif # HAVE_SASL diff --git a/cpp/src/tests/sasl_fed b/cpp/src/tests/sasl_fed index 9845a20838..884c44177c 100755 --- a/cpp/src/tests/sasl_fed +++ b/cpp/src/tests/sasl_fed @@ -123,7 +123,7 @@ n_messages=100 #-------------------------------------------------- #echo " Sending 100 messages to $broker_1_port " #-------------------------------------------------- -$builddir/datagen --count $n_messages | $SENDER_EXEC --username zag --password zag --exchange $EXCHANGE_NAME --routing-key $ROUTING_KEY --port $broker_1_port +$builddir/datagen --count $n_messages | $SENDER_EXEC --mechanism DIGEST-MD5 --username zag --password zag --exchange $EXCHANGE_NAME --routing-key $ROUTING_KEY --port $broker_1_port sleep 5 diff --git a/cpp/src/tests/sasl_fed_ex b/cpp/src/tests/sasl_fed_ex index 0740650d6c..716a806874 100755 --- a/cpp/src/tests/sasl_fed_ex +++ b/cpp/src/tests/sasl_fed_ex @@ -19,22 +19,52 @@ # under the License. # - #=============================================================================== -# This test creates a federated link between two brokers using SASL security. +# These tests create federated links between two brokers using SASL security. # The SASL mechanism used is EXTERNAL, which is satisfied by SSL # transport-layer security. #=============================================================================== source ./test_env.sh +script_name=`basename $0` + +if [ $# -lt 1 ] || [ $# -gt 2 ] +then + echo + # These are the four different ways of creating links ( or routes+links ) + # that the qpid-route command provides. + echo "Usage: ${script_name} dynamic|link|queue|route [cluster]" + echo + exit 1 +fi + +# Has the user told us to do clustering ? ----------- +clustering_flag= +if [ $# -eq "2" ] && [ "$2" == "cluster" ]; then + clustering_flag=true +fi + +qpid_route_method=$1 + +# Debugging print. -------------------------- +debug= +function print { + if [ "$debug" ]; then + echo "${script_name}: $1" + fi +} + +print "=========== start sasl_fed_ex $* ============" + + # This minimum value corresponds to sasl version 2.1.22 minimum_sasl_version=131350 sasl_version=`$QPID_TEST_EXEC_DIR/sasl_version` -# This test is necessary becasue this sasl version is the first one that permits +# This test is necessary because this sasl version is the first one that permits # redirection of the sasl config file path. if [ "$sasl_version" -lt "$minimum_sasl_version" ]; then echo "sasl_fed: must have sasl version 2.1.22 or greater. ( Integer value: $minimum_sasl_version ) Version is: $sasl_version" @@ -60,6 +90,7 @@ create_certs() { delete_certs() { if [[ -e ${CERT_DIR} ]] ; then + print "removing cert dir ${CERT_DIR}" rm -rf ${CERT_DIR} fi } @@ -72,22 +103,40 @@ if [[ !(-x $CERTUTIL) ]] ; then fi delete_certs -create_certs || error "Could not create test certificate" - +create_certs 2> /dev/null +if [ ! $? ]; then + error "Could not create test certificate" + exit 1 +fi -sasl_config_file=$builddir/sasl_config +sasl_config_dir=$builddir/sasl_config -my_random_number=$RANDOM -tmp_root=/tmp/sasl_fed_$my_random_number +tmp_root=${builddir}/sasl_fed_ex_temp +print "results dir is ${tmp_root}" +rm -rf ${tmp_root} mkdir -p $tmp_root SRC_SSL_PORT=6667 DST_SSL_PORT=6666 +SRC_SSL_PORT_2=6668 +DST_SSL_PORT_2=6669 + SRC_TCP_PORT=5801 DST_TCP_PORT=5807 -SSL_LIB=../.libs/ssl.so +SRC_TCP_PORT_2=5802 +DST_TCP_PORT_2=5803 + +CLUSTER_NAME_SUFFIX=`hostname | tr '.' ' ' | awk '{print $1}'` +CLUSTER_1_NAME=sasl_fed_ex_cluster_1_${CLUSTER_NAME_SUFFIX} +CLUSTER_2_NAME=sasl_fed_ex_cluster_2_${CLUSTER_NAME_SUFFIX} + +print "CLUSTER_1_NAME == ${CLUSTER_1_NAME}" +print "CLUSTER_2_NAME == ${CLUSTER_2_NAME}" + +SSL_LIB=${moduledir}/ssl.so +CLUSTER_LIB=${moduledir}/cluster.so export QPID_SSL_CERT_NAME=${TEST_HOSTNAME} @@ -116,52 +165,112 @@ export QPID_SSL_CERT_NAME=${TEST_HOSTNAME} # 5. DST pulls messages off the temp queue on SRC to itself. # +COMMON_BROKER_OPTIONS=" \ + --ssl-sasl-no-dict \ + --sasl-config=$sasl_config_dir \ + --ssl-require-client-authentication \ + --auth yes \ + --ssl-cert-db $CERT_DIR \ + --ssl-cert-password-file $CERT_PW_FILE \ + --ssl-cert-name $TEST_HOSTNAME \ + --no-data-dir \ + --no-module-dir \ + --load-module ${SSL_LIB} \ + --mgmt-enable=yes \ + --log-enable info+ \ + --log-source yes \ + --daemon " + + +function start_brokers { + if [ $1 ]; then + # clustered ---------------------------------------- + print "Starting SRC cluster" + + print " src broker 1" + $QPIDD_EXEC \ + --port=${SRC_TCP_PORT} \ + --ssl-port ${SRC_SSL_PORT} \ + ${COMMON_BROKER_OPTIONS} \ + --load-module ${CLUSTER_LIB} \ + --cluster-name ${CLUSTER_1_NAME} \ + --log-to-file $tmp_root/qpidd_src.log 2> /dev/null + + broker_ports[0]=${SRC_TCP_PORT} + + print " src broker 2" + $QPIDD_EXEC \ + --port=${SRC_TCP_PORT_2} \ + --ssl-port ${SRC_SSL_PORT_2} \ + ${COMMON_BROKER_OPTIONS} \ + --load-module ${CLUSTER_LIB} \ + --cluster-name ${CLUSTER_1_NAME} \ + --log-to-file $tmp_root/qpidd_src_2.log 2> /dev/null + + broker_ports[1]=${SRC_TCP_PORT_2} + + + print "Starting DST cluster" + + print " dst broker 1" + $QPIDD_EXEC \ + --port=${DST_TCP_PORT} \ + --ssl-port ${DST_SSL_PORT} \ + ${COMMON_BROKER_OPTIONS} \ + --load-module ${CLUSTER_LIB} \ + --cluster-name ${CLUSTER_2_NAME} \ + --log-to-file $tmp_root/qpidd_dst.log 2> /dev/null + + broker_ports[2]=${DST_TCP_PORT} + + print " dst broker 2" + $QPIDD_EXEC \ + --port=${DST_TCP_PORT_2} \ + --ssl-port ${DST_SSL_PORT_2} \ + ${COMMON_BROKER_OPTIONS} \ + --load-module ${CLUSTER_LIB} \ + --cluster-name ${CLUSTER_2_NAME} \ + --log-to-file $tmp_root/qpidd_dst_2.log 2> /dev/null + + broker_ports[3]=${DST_TCP_PORT_2} + + else + # vanilla brokers -------------------------------- + print "Starting SRC broker" + $QPIDD_EXEC \ + --port=${SRC_TCP_PORT} \ + --ssl-port ${SRC_SSL_PORT} \ + ${COMMON_BROKER_OPTIONS} \ + --log-to-file $tmp_root/qpidd_src.log 2> /dev/null + + broker_ports[0]=${SRC_TCP_PORT} + + print "Starting DST broker" + $QPIDD_EXEC \ + --port=${DST_TCP_PORT} \ + --ssl-port ${DST_SSL_PORT} \ + ${COMMON_BROKER_OPTIONS} \ + --log-to-file $tmp_root/qpidd_dst.log 2> /dev/null + + broker_ports[1]=${DST_TCP_PORT} + fi +} + +function halt_brokers { + n_brokers=${#broker_ports[@]} + print "Halting ${n_brokers} brokers." + for i in $(seq 0 $((${n_brokers} - 1))) + do + halt_port=${broker_ports[$i]} + print "Halting broker $i on port ${halt_port}" + $QPIDD_EXEC --port ${halt_port} --quit + done + +} -#echo "-----------------------" -#echo "Starting SRC broker" -#echo "-----------------------" -$QPIDD_EXEC \ - --port=${SRC_TCP_PORT} \ - --ssl-port ${SRC_SSL_PORT} \ - --ssl-sasl-no-dict \ - --sasl-config=$sasl_config_file \ - --ssl-require-client-authentication \ - --auth yes \ - --ssl-cert-db $CERT_DIR \ - --ssl-cert-password-file $CERT_PW_FILE \ - --ssl-cert-name $TEST_HOSTNAME \ - --no-data-dir \ - --no-module-dir \ - --load-module ${SSL_LIB} \ - --mgmt-enable=yes \ - --log-enable info+ \ - --log-source yes \ - --daemon \ - --log-to-file $tmp_root/qpidd_src.log 2> /dev/null - - -#echo "-----------------------" -#echo "Starting DST broker" -#echo "-----------------------" -$QPIDD_EXEC \ - --port=${DST_TCP_PORT} \ - --ssl-port ${DST_SSL_PORT} \ - --ssl-cert-db $CERT_DIR \ - --ssl-cert-password-file $CERT_PW_FILE \ - --ssl-cert-name $TEST_HOSTNAME \ - --ssl-sasl-no-dict \ - --ssl-require-client-authentication \ - --sasl-config=$sasl_config_file \ - --no-data-dir \ - --no-module-dir \ - --load-module ${SSL_LIB} \ - --mgmt-enable=yes \ - --log-enable info+ \ - --log-source yes \ - --daemon \ - $COMMON_BROKER_OPTIONS \ - --log-to-file $tmp_root/qpidd_dst.log 2> /dev/null + +start_brokers $clustering_flag # I am not randomizing these names, because this test creates its own brokers. @@ -170,76 +279,83 @@ ROUTING_KEY=sasl_fed_queue EXCHANGE_NAME=sasl_fedex -#echo "-----------------------" -#echo "add exchanges" -#echo "-----------------------" +print "add exchanges" $QPID_CONFIG_EXEC -a localhost:${SRC_TCP_PORT} add exchange direct $EXCHANGE_NAME $QPID_CONFIG_EXEC -a localhost:${DST_TCP_PORT} add exchange direct $EXCHANGE_NAME -#echo "-----------------------" -#echo "add queues" -#echo "-----------------------" +print "add queues" $QPID_CONFIG_EXEC -a localhost:${SRC_TCP_PORT} add queue $QUEUE_NAME $QPID_CONFIG_EXEC -a localhost:${DST_TCP_PORT} add queue $QUEUE_NAME -#echo "-----------------------" -#echo "create bindings" -#echo "-----------------------" +print "create bindings" $QPID_CONFIG_EXEC -a localhost:${SRC_TCP_PORT} bind $EXCHANGE_NAME $QUEUE_NAME $ROUTING_KEY $QPID_CONFIG_EXEC -a localhost:${DST_TCP_PORT} bind $EXCHANGE_NAME $QUEUE_NAME $ROUTING_KEY -#echo "-----------------------" -#echo "qpid-route route add" -#echo "-----------------------" +# # NOTE: The SRC broker *must* be referred to as $TEST_HOSTNAME, and not as "localhost". # It must be referred to by the exact string given as the Common Name (CN) in the cert, # which was created in the function create_certs, above. -$QPID_ROUTE_EXEC route add localhost:${DST_TCP_PORT} $TEST_HOSTNAME:${SRC_SSL_PORT} -t ssl $EXCHANGE_NAME $ROUTING_KEY "" "" EXTERNAL -#echo "-----------------------" -#echo "view the route :" -#echo "-----------------------" -#$PYTHON_COMMANDS/qpid-route route list localhost:${DST_TCP_PORT} - -# I don't know how to avoid this sleep yet. It has to come after route-creation. -sleep 5 -n_messages=100 -./datagen --count ${n_messages} | ./sender --broker localhost --port ${SRC_TCP_PORT} --exchange ${EXCHANGE_NAME} --routing-key ${ROUTING_KEY} --mechanism ANONYMOUS +#---------------------------------------------------------------- +# Use qpid-route to create the link, or the link+route, depending +# on which of its several methods was requested. +#---------------------------------------------------------------- +if [ ${qpid_route_method} == "dynamic" ]; then + print "dynamic add" + $QPID_ROUTE_EXEC -t ssl dynamic add localhost:${DST_TCP_PORT} $TEST_HOSTNAME:${SRC_SSL_PORT} $EXCHANGE_NAME "" "" EXTERNAL +elif [ ${qpid_route_method} == "link" ]; then + print "link add" + $QPID_ROUTE_EXEC -t ssl link add localhost:${DST_TCP_PORT} $TEST_HOSTNAME:${SRC_SSL_PORT} EXTERNAL +elif [ ${qpid_route_method} == "queue" ]; then + print "queue add" + $QPID_ROUTE_EXEC -t ssl queue add localhost:${DST_TCP_PORT} $TEST_HOSTNAME:${SRC_SSL_PORT} $EXCHANGE_NAME $ROUTING_KEY EXTERNAL +elif [ ${qpid_route_method} == "route" ]; then + print "route add" + $QPID_ROUTE_EXEC -t ssl route add localhost:${DST_TCP_PORT} $TEST_HOSTNAME:${SRC_SSL_PORT} $EXCHANGE_NAME $ROUTING_KEY "" "" EXTERNAL +else + echo "unknown method: |${qpid_route_method}|" + echo " choices are: dynamic|link|queue|route " + halt_brokers + exit 1 +fi -#echo "-----------------------" -#echo "Examine DST Broker" -#echo "-----------------------" -dst_message_count=`qpid-stat -q localhost:${DST_TCP_PORT} | grep sasl_fed_queue | awk '{print $2}'` +# I don't know how to avoid this sleep yet. It has to come after route-creation +# to avoid false negatives. +sleep 5 +# This should work the same whether or not we are running a clustered test. +# In the case of clustered tests, the status is not printed by qpid_route. +# So in either case, I will look only at the transport field, which should be "ssl". +print "check the link" +link_status=$($QPID_ROUTE_EXEC link list localhost:${DST_TCP_PORT} | tail -1 | awk '{print $3}') -#echo "-----------------------" -#echo "Asking brokers to quit." -#echo "-----------------------" -$QPIDD_EXEC --port ${SRC_TCP_PORT} --quit -$QPIDD_EXEC --port ${DST_TCP_PORT} --quit +halt_brokers +sleep 1 -#echo "-----------------------" -#echo "Removing temporary directory $tmp_root" -#echo "-----------------------" -rm -rf $tmp_root +if [ ! ${link_status} ]; then + print "link_status is empty" + print "result: fail" + exit 2 +fi -if [ "$dst_message_count" -eq "$n_messages" ]; then - #echo "good: |$dst_message_count| == |$n_messages|" +if [ ${link_status} == "ssl" ]; then + print "result: good" + # Only remove the tmp_root on success, to permit debugging. + print "Removing temporary directory $tmp_root" + rm -rf $tmp_root exit 0 -else - #echo "not ideal: |$dst_message_count| != |$n_messages|" - exit 1 fi - - +print "link_status has a bad value: ${link_status}" +print "result: fail" +exit 3 diff --git a/cpp/src/tests/sasl_fed_ex_dynamic b/cpp/src/tests/sasl_fed_ex_dynamic new file mode 100755 index 0000000000..c20b8d69a0 --- /dev/null +++ b/cpp/src/tests/sasl_fed_ex_dynamic @@ -0,0 +1,27 @@ +#! /bin/bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + +source ./test_env.sh + +${srcdir}/sasl_fed_ex dynamic + + diff --git a/cpp/src/tests/sasl_fed_ex_dynamic_cluster b/cpp/src/tests/sasl_fed_ex_dynamic_cluster new file mode 100755 index 0000000000..b0cceccecb --- /dev/null +++ b/cpp/src/tests/sasl_fed_ex_dynamic_cluster @@ -0,0 +1,28 @@ +#! /bin/bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + +source ./test_env.sh +source $srcdir/ais_check + +with_ais_group ${srcdir}/sasl_fed_ex dynamic cluster + + diff --git a/cpp/src/tests/sasl_fed_ex_link b/cpp/src/tests/sasl_fed_ex_link new file mode 100755 index 0000000000..7b232d4874 --- /dev/null +++ b/cpp/src/tests/sasl_fed_ex_link @@ -0,0 +1,27 @@ +#! /bin/bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + +source ./test_env.sh + +${srcdir}/sasl_fed_ex link + + diff --git a/cpp/src/tests/sasl_fed_ex_link_cluster b/cpp/src/tests/sasl_fed_ex_link_cluster new file mode 100755 index 0000000000..4139300b12 --- /dev/null +++ b/cpp/src/tests/sasl_fed_ex_link_cluster @@ -0,0 +1,28 @@ +#! /bin/bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + +source ./test_env.sh +source $srcdir/ais_check + +with_ais_group ${srcdir}/sasl_fed_ex link cluster + + diff --git a/cpp/src/tests/sasl_fed_ex_queue b/cpp/src/tests/sasl_fed_ex_queue new file mode 100755 index 0000000000..be0c10cf63 --- /dev/null +++ b/cpp/src/tests/sasl_fed_ex_queue @@ -0,0 +1,27 @@ +#! /bin/bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + +source ./test_env.sh + +${srcdir}/sasl_fed_ex queue + + diff --git a/cpp/src/tests/sasl_fed_ex_queue_cluster b/cpp/src/tests/sasl_fed_ex_queue_cluster new file mode 100755 index 0000000000..f251420e08 --- /dev/null +++ b/cpp/src/tests/sasl_fed_ex_queue_cluster @@ -0,0 +1,28 @@ +#! /bin/bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + +source ./test_env.sh +source ${srcdir}/ais_check + +with_ais_group ${srcdir}/sasl_fed_ex queue cluster + + diff --git a/cpp/src/tests/sasl_fed_ex_route b/cpp/src/tests/sasl_fed_ex_route new file mode 100755 index 0000000000..dd5c4f3cac --- /dev/null +++ b/cpp/src/tests/sasl_fed_ex_route @@ -0,0 +1,27 @@ +#! /bin/bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + +source ./test_env.sh + +${srcdir}/sasl_fed_ex route + + diff --git a/cpp/src/tests/sasl_fed_ex_route_cluster b/cpp/src/tests/sasl_fed_ex_route_cluster new file mode 100755 index 0000000000..a5d1542def --- /dev/null +++ b/cpp/src/tests/sasl_fed_ex_route_cluster @@ -0,0 +1,28 @@ +#! /bin/bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + +source ./test_env.sh +source ${srcdir}/ais_check + +with_ais_group ${srcdir}/sasl_fed_ex route cluster + + diff --git a/cpp/src/tests/sasl_no_dir b/cpp/src/tests/sasl_no_dir new file mode 100755 index 0000000000..15a36014bb --- /dev/null +++ b/cpp/src/tests/sasl_no_dir @@ -0,0 +1,218 @@ +#! /bin/bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +source ./test_env.sh + +script_name=`basename $0` + +# This minimum value corresponds to sasl version 2.1.22 +minimum_sasl_version=131350 + +sasl_version=`$QPID_TEST_EXEC_DIR/sasl_version` + +# This test is necessary because this sasl version is the first one that permits +# redirection of the sasl config file path. +if [ "$sasl_version" -lt "$minimum_sasl_version" ]; then + echo "sasl_fed: must have sasl version 2.1.22 or greater. ( Integer value: $minimum_sasl_version ) Version is: $sasl_version" + exit 0 +fi + + +sasl_config_dir=$builddir/sasl_config + + +# Debugging print. -------------------------- +debug= +function print { + if [ "$debug" ]; then + echo "${script_name}: $1" + fi +} + + +my_random_number=$RANDOM +tmp_root=/tmp/sasl_fed_$my_random_number +mkdir -p $tmp_root + + +LOG_FILE=$tmp_root/qpidd.log + +# If you want to see this test fail, just comment out this 'mv' command. +print "Moving sasl configuration dir." +mv ${sasl_config_dir} ${sasl_config_dir}- + + +#-------------------------------------------------- +print " Starting broker" +#-------------------------------------------------- +$QPIDD_EXEC \ + -p 0 \ + --no-data-dir \ + --auth=yes \ + --mgmt-enable=yes \ + --log-enable info+ \ + --log-source yes \ + --log-to-file ${LOG_FILE} \ + --sasl-config=$sasl_config_dir \ + -d 2> /dev/null 1> $tmp_root/broker_port + + + +# If it works right, the output will look something like this: ( two lines long ) +# Daemon startup failed: SASL: sasl_set_path failed: no such directory: /home/mick/trunk/qpid/cpp/src/tests/sasl_config (qpid/broker/SaslAuthenticator.cpp:112) +# 2011-10-13 14:07:00 critical qpidd.cpp:83: Unexpected error: Daemon startup failed: SASL: sasl_set_path failed: no such directory: /home/mick/trunk/qpid/cpp/src/tests/sasl_config (qpid/broker/SaslAuthenticator.cpp:112) + +result=`cat ${LOG_FILE} | grep "sasl_set_path failed: no such directory" | wc -l ` + +#-------------------------------------------------- +print "Restore the Sasl config dir to its original place." +#-------------------------------------------------- +mv ${sasl_config_dir}- ${sasl_config_dir} + +if [ "2" -eq ${result} ]; then + print "result: success" + rm -rf $tmp_root + exit 0 +fi + + +# If this test fails, the broker is still alive. +# Kill it. +broker_port=`cat $tmp_root/broker_port` +#-------------------------------------------------- +print "Asking broker to quit." +#-------------------------------------------------- +$QPIDD_EXEC --port $broker_port --quit + +rm -rf $tmp_root + +print "result: fail" +exit 1 + +#! /bin/bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +source ./test_env.sh + +script_name=`basename $0` + +# This minimum value corresponds to sasl version 2.1.22 +minimum_sasl_version=131350 + +sasl_version=`$QPID_TEST_EXEC_DIR/sasl_version` + +# This test is necessary because this sasl version is the first one that permits +# redirection of the sasl config file path. +if [ "$sasl_version" -lt "$minimum_sasl_version" ]; then + echo "sasl_fed: must have sasl version 2.1.22 or greater. ( Integer value: $minimum_sasl_version ) Version is: $sasl_version" + exit 0 +fi + + +sasl_config_dir=$builddir/sasl_config + + +# Debugging print. -------------------------- +debug= +function print { + if [ "$debug" ]; then + echo "${script_name}: $1" + fi +} + + +my_random_number=$RANDOM +tmp_root=/tmp/sasl_fed_$my_random_number +mkdir -p $tmp_root + + +LOG_FILE=$tmp_root/qpidd.log + +# If you want to see this test fail, just comment out this 'mv' command. +print "Moving sasl configuration dir." +mv ${sasl_config_dir} ${sasl_config_dir}- + + +#-------------------------------------------------- +print " Starting broker" +#-------------------------------------------------- +$QPIDD_EXEC \ + -p 0 \ + --no-data-dir \ + --auth=yes \ + --mgmt-enable=yes \ + --log-enable info+ \ + --log-source yes \ + --log-to-file ${LOG_FILE} \ + --sasl-config=$sasl_config_dir \ + -d 2> /dev/null 1> $tmp_root/broker_port + + + +# If it works right, the output will look something like this: ( two lines long ) +# Daemon startup failed: SASL: sasl_set_path failed: no such directory: /home/mick/trunk/qpid/cpp/src/tests/sasl_config (qpid/broker/SaslAuthenticator.cpp:112) +# 2011-10-13 14:07:00 critical qpidd.cpp:83: Unexpected error: Daemon startup failed: SASL: sasl_set_path failed: no such directory: /home/mick/trunk/qpid/cpp/src/tests/sasl_config (qpid/broker/SaslAuthenticator.cpp:112) + +result=`cat ${LOG_FILE} | grep "sasl_set_path failed: no such directory" | wc -l ` + +#-------------------------------------------------- +print "Restore the Sasl config dir to its original place." +#-------------------------------------------------- +mv ${sasl_config_dir}- ${sasl_config_dir} + +if [ "2" -eq ${result} ]; then + print "result: success" + rm -rf $tmp_root + exit 0 +fi + + +# If this test fails, the broker is still alive. +# Kill it. +broker_port=`cat $tmp_root/broker_port` +#-------------------------------------------------- +print "Asking broker to quit." +#-------------------------------------------------- +$QPIDD_EXEC --port $broker_port --quit + +rm -rf $tmp_root + +print "result: fail" +exit 1 + diff --git a/cpp/src/tests/sasl_test_setup.sh b/cpp/src/tests/sasl_test_setup.sh index 6395ba6ec3..3e69c0f02b 100755 --- a/cpp/src/tests/sasl_test_setup.sh +++ b/cpp/src/tests/sasl_test_setup.sh @@ -30,6 +30,7 @@ pwcheck_method: auxprop auxprop_plugin: sasldb sasldb_path: $PWD/sasl_config/qpidd.sasldb sql_select: dummy select +mech_list: ANONYMOUS PLAIN DIGEST-MD5 EXTERNAL EOF # Populate temporary sasl db. diff --git a/cpp/src/tests/sender.cpp b/cpp/src/tests/sender.cpp index 9850e851da..063b5e87dc 100644 --- a/cpp/src/tests/sender.cpp +++ b/cpp/src/tests/sender.cpp @@ -120,7 +120,7 @@ void Sender::execute(AsyncSession& session, bool isRetry) string data; while (getline(std::cin, data)) { message.setData(data); - message.getHeaders().setInt("SN", ++sent); + //message.getHeaders().setInt("SN", ++sent); string matchKey; if (lvqMatchValues && getline(lvqMatchValues, matchKey)) { message.getHeaders().setString(QueueOptions::strLVQMatchProperty, matchKey); diff --git a/cpp/src/tests/ssl_test b/cpp/src/tests/ssl_test index 04584f169d..6c056f4288 100755 --- a/cpp/src/tests/ssl_test +++ b/cpp/src/tests/ssl_test @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # # Licensed to the Apache Software Foundation (ASF) under one @@ -47,9 +47,13 @@ delete_certs() { fi } -COMMON_OPTS="--daemon --no-data-dir --no-module-dir --auth no --config $CONFIG --load-module $SSL_LIB --ssl-cert-db $CERT_DIR --ssl-cert-password-file $CERT_PW_FILE --ssl-cert-name $TEST_HOSTNAME --require-encryption" +COMMON_OPTS="--daemon --no-data-dir --no-module-dir --config $CONFIG --load-module $SSL_LIB --ssl-cert-db $CERT_DIR --ssl-cert-password-file $CERT_PW_FILE --ssl-cert-name $TEST_HOSTNAME" start_broker() { # $1 = extra opts - ../qpidd --transport ssl --port 0 --ssl-port 0 $COMMON_OPTS $1; + ../qpidd --transport ssl --port 0 --ssl-port 0 $COMMON_OPTS --require-encryption --auth no $1; +} + +start_authenticating_broker() { + ../qpidd --transport ssl --port 0 --ssl-port 0 $COMMON_OPTS --require-encryption --ssl-sasl-no-dict --ssl-require-client-authentication --auth yes; } stop_brokers() { @@ -64,6 +68,13 @@ cleanup() { delete_certs } +pick_port() { + # We need a fixed port to set --cluster-url. Use qpidd to pick a free port. + PICK=`../qpidd --no-module-dir -dp0` + ../qpidd --no-module-dir -qp $PICK + echo $PICK +} + CERTUTIL=$(type -p certutil) if [[ !(-x $CERTUTIL) ]] ; then echo "No certutil, skipping ssl test"; @@ -93,7 +104,7 @@ test "$MSG" = "hello" || { echo "receive failed '$MSG' != 'hello'"; exit 1; } #### Client Authentication tests -PORT2=`start_broker --ssl-require-client-authentication` || error "Could not start broker" +PORT2=`start_authenticating_broker` || error "Could not start broker" echo "Running SSL client authentication test on port $PORT2" URL=amqp:ssl:$TEST_HOSTNAME:$PORT2 @@ -109,19 +120,22 @@ test "$MSG3" = "" || { echo "receive succeeded without valid ssl cert '$MSG3' != stop_brokers +#Test multiplexed connection where SSL and plain TCP are served by the same port +PORT=`pick_port`; ../qpidd --port $PORT --ssl-port $PORT $COMMON_OPTS --transport ssl --auth no +echo "Running multiplexed SSL/TCP test on $PORT" + +./qpid-perftest --count ${COUNT} --port ${PORT} -P ssl -b $TEST_HOSTNAME --summary || { echo "SSL on multiplexed connection failed!"; exit 1; } +./qpid-perftest --count ${COUNT} --port ${PORT} -P tcp -b $TEST_HOSTNAME --summary || { echo "Plain TCP on multiplexed connection failed!"; exit 1; } + +stop_brokers + test -z $CLUSTER_LIB && exit 0 # Exit if cluster not supported. ## Test failover in a cluster using SSL only . $srcdir/ais_check # Will exit if clustering not enabled. -pick_port() { - # We need a fixed port to set --cluster-url. Use qpidd to pick a free port. - PICK=`../qpidd --no-module-dir -dp0` - ../qpidd --no-module-dir -qp $PICK - echo $PICK -} ssl_cluster_broker() { # $1 = port - ../qpidd $COMMON_OPTS --load-module $CLUSTER_LIB --cluster-name ssl_test.$HOSTNAME.$$ --cluster-url amqp:ssl:$TEST_HOSTNAME:$1 --port 0 --ssl-port $1 --transport ssl > /dev/null + ../qpidd $COMMON_OPTS --require-encryption --auth no --load-module $CLUSTER_LIB --cluster-name ssl_test.$HOSTNAME.$$ --cluster-url amqp:ssl:$TEST_HOSTNAME:$1 --port 0 --ssl-port $1 --transport ssl > /dev/null # Wait for broker to be ready qpid-ping -Pssl -b $TEST_HOSTNAME -qp $1 || { echo "Cannot connect to broker on $1"; exit 1; } echo "Running SSL cluster broker on port $1" diff --git a/cpp/src/tests/windows/DisableWin32ErrorWindows.cpp b/cpp/src/tests/windows/DisableWin32ErrorWindows.cpp index a0b665db73..024f20b147 100644 --- a/cpp/src/tests/windows/DisableWin32ErrorWindows.cpp +++ b/cpp/src/tests/windows/DisableWin32ErrorWindows.cpp @@ -26,7 +26,9 @@ // include this file with the executable being built. If the default // behaviors are desired, don't include this file in the build. +#if defined(_MSC_VER) #include <crtdbg.h> +#endif #include <windows.h> #include <iostream> @@ -53,12 +55,14 @@ static redirect_errors_to_stderr block; redirect_errors_to_stderr::redirect_errors_to_stderr() { +#if defined(_MSC_VER) _CrtSetReportMode (_CRT_WARN, _CRTDBG_MODE_FILE); _CrtSetReportFile (_CRT_WARN, _CRTDBG_FILE_STDERR); _CrtSetReportMode (_CRT_ERROR, _CRTDBG_MODE_FILE); _CrtSetReportFile (_CRT_ERROR, _CRTDBG_FILE_STDERR); _CrtSetReportMode (_CRT_ASSERT, _CRTDBG_MODE_FILE); _CrtSetReportFile (_CRT_ASSERT, _CRTDBG_FILE_STDERR); +#endif // Prevent the system from displaying the critical-error-handler // and can't-open-file message boxes. diff --git a/cpp/src/windows/QpiddBroker.cpp b/cpp/src/windows/QpiddBroker.cpp index 884cdc366b..42ba97bdb1 100644 --- a/cpp/src/windows/QpiddBroker.cpp +++ b/cpp/src/windows/QpiddBroker.cpp @@ -147,7 +147,7 @@ NamedSharedMemory<T>::NamedSharedMemory(const std::string& n) : name(n), memory(NULL), data(0) -{}; +{} template <typename T> NamedSharedMemory<T>::~NamedSharedMemory() { @@ -155,7 +155,7 @@ NamedSharedMemory<T>::~NamedSharedMemory() { ::UnmapViewOfFile(data); if (memory != NULL) ::CloseHandle(memory); -}; +} template <typename T> T& NamedSharedMemory<T>::create() { diff --git a/cpp/src/windows/resources/template-resource.rc b/cpp/src/windows/resources/template-resource.rc index 725d1c9391..8ca0a90890 100644 --- a/cpp/src/windows/resources/template-resource.rc +++ b/cpp/src/windows/resources/template-resource.rc @@ -24,7 +24,7 @@ //
// Generated from the TEXTINCLUDE 2 resource.
//
-#include "afxres.h"
+#include "windows.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
diff --git a/cpp/src/xml.mk b/cpp/src/xml.mk index 0d700fcc03..baf3803647 100644 --- a/cpp/src/xml.mk +++ b/cpp/src/xml.mk @@ -16,7 +16,7 @@ # specific language governing permissions and limitations # under the License. # -dmodule_LTLIBRARIES += xml.la +dmoduleexec_LTLIBRARIES += xml.la xml_la_SOURCES = \ qpid/xml/XmlExchange.cpp \ |