diff options
Diffstat (limited to 'qpid/cpp/src')
1010 files changed, 151792 insertions, 0 deletions
diff --git a/qpid/cpp/src/CMakeLists.txt b/qpid/cpp/src/CMakeLists.txt new file mode 100644 index 0000000000..0fe2d7e4d0 --- /dev/null +++ b/qpid/cpp/src/CMakeLists.txt @@ -0,0 +1,1305 @@ +# +# 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. +# + +# Option to require building optional plugins +foreach (r ${REQUIRE}) + set(${r}_force ON) + message(STATUS "Forcing ${r} to ${${r}_force}") +endforeach(r) + +include(CheckFunctionExists) +include(CheckIncludeFileCXX) +include(CheckIncludeFiles) +include(CheckIncludeFileCXX) +include(CheckLibraryExists) +include(CheckSymbolExists) +include(FindBoost) +include(FindDoxygen) + +#set (CMAKE_VERBOSE_MAKEFILE ON) # for debugging + +# +# Set up installation of .pdb files if the compiler is Visual Studio +# +# Sample: install_pdb (qpidcommon ${QPID_COMPONENT_COMMON}) +# +MACRO (install_pdb theLibrary theComponent) + if (MSVC) + get_target_property(library_dll ${theLibrary} LOCATION) + string(REPLACE .dll .pdb library_pdb ${library_dll}) + string(REPLACE $(OutDir) \${CMAKE_INSTALL_CONFIG_NAME} library_pdb ${library_pdb}) + string(REPLACE .pdb d.pdb libraryd_pdb ${library_pdb}) + #message(STATUS "_pdb: ${library_pdb}, ${libraryd_pdb}") + install (PROGRAMS + ${library_pdb} + DESTINATION ${QPID_INSTALL_LIBDIR}/ReleasePDB + COMPONENT ${theComponent} + OPTIONAL + CONFIGURATIONS Release|MinSizeRel) + install (PROGRAMS + ${library_pdb} + DESTINATION ${QPID_INSTALL_LIBDIR}/ReleasePDB + COMPONENT ${theComponent} + CONFIGURATIONS RelWithDebInfo) + install (PROGRAMS + ${libraryd_pdb} + DESTINATION ${QPID_INSTALL_LIBDIR}/DebugPDB + COMPONENT ${theComponent} + CONFIGURATIONS Debug) + endif (MSVC) +ENDMACRO (install_pdb) + +# +# inherit_value - if the symbol is undefined then set it to the given value. +# Set flag to indicate this symbol was defined here. +# +MACRO (inherit_value theSymbol theValue) + if (NOT DEFINED ${theSymbol}) + set (${theSymbol} ${theValue}) + # message ("Set symbol '${theSymbol}' to value '${theValue}'") + set (${theSymbol}_inherited = "true") + endif (NOT DEFINED ${theSymbol}) +ENDMACRO (inherit_value) + +# +# If compiler is Visual Studio then create a "version resource" for the project. +# Use this call to override CPACK and file global settings but not file per-project settings. +# Two groups of four version numbers specify "file" and "product" versions separately. +# +# Sample: add_msvc_version_full (qmfengine library dll 1 0 0 1 1 0 0 1) +# +MACRO (add_msvc_version_full verProject verProjectType verProjectFileExt verFN1 verFN2 verFN3 verFN4 verPN1 verPN2 verPN3 verPN4) + if (MSVC) + # Create project-specific version strings + inherit_value ("winver_${verProject}_FileVersionBinary" "${verFN1},${verFN2},${verFN3},${verFN4}") + inherit_value ("winver_${verProject}_ProductVersionBinary" "${verPN1},${verPN2},${verPN3},${verPN4}") + inherit_value ("winver_${verProject}_FileVersionString" "${verFN1}, ${verFN2}, ${verFN3}, ${verFN4}") + inherit_value ("winver_${verProject}_ProductVersionString" "${verPN1}, ${verPN2}, ${verPN3}, ${verPN4}") + inherit_value ("winver_${verProject}_FileDescription" "${winver_PACKAGE_NAME}-${verProject} ${verProjectType}") + inherit_value ("winver_${verProject}_LegalCopyright" "${winver_LEGAL_COPYRIGHT}") + 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}") + set ("winverFileVersionString" "${winver_${verProject}_FileVersionString}") + set ("winverProductVersionString" "${winver_${verProject}_ProductVersionString}") + set ("winverFileDescription" "${winver_${verProject}_FileDescription}") + set ("winverLegalCopyright" "${winver_${verProject}_LegalCopyright}") + set ("winverInternalName" "${winver_${verProject}_InternalName}") + set ("winverOriginalFilename" "${winver_${verProject}_OriginalFilename}") + set ("winverProductName" "${winver_${verProject}_ProductName}") + + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/windows/resources/template-resource.rc + ${CMAKE_CURRENT_BINARY_DIR}/windows/resources/${verProject}-resource.rc) + set (${verProject}_SOURCES + ${${verProject}_SOURCES} + ${CMAKE_CURRENT_BINARY_DIR}/windows/resources/${verProject}-resource.rc + ) + endif (MSVC) +ENDMACRO (add_msvc_version_full) + +# +# If compiler is Visual Studio then create a "version resource" for the project. +# Use this call to accept file override version settings or +# inherited CPACK_PACKAGE_VERSION version settings. +# +# Sample: add_msvc_version (qpidcommon library dll) +# +MACRO (add_msvc_version verProject verProjectType verProjectFileExt) + if (MSVC) + add_msvc_version_full (${verProject} + ${verProjectType} + ${verProjectFileExt} + ${winver_FILE_VERSION_N1} + ${winver_FILE_VERSION_N2} + ${winver_FILE_VERSION_N3} + ${winver_FILE_VERSION_N4} + ${winver_PRODUCT_VERSION_N1} + ${winver_PRODUCT_VERSION_N2} + ${winver_PRODUCT_VERSION_N3} + ${winver_PRODUCT_VERSION_N4}) + endif (MSVC) +ENDMACRO (add_msvc_version) + + +# +# Install optional windows version settings. Override variables are specified in a file. +# +include (./CMakeWinVersions.cmake OPTIONAL) + +# +# Inherit global windows version settings from CPACK settings. +# +inherit_value ("winver_PACKAGE_NAME" "${CPACK_PACKAGE_NAME}") +inherit_value ("winver_DESCRIPTION_SUMMARY" "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}") +inherit_value ("winver_FILE_VERSION_N1" "${CPACK_PACKAGE_VERSION_MAJOR}") +inherit_value ("winver_FILE_VERSION_N2" "${CPACK_PACKAGE_VERSION_MINOR}") +inherit_value ("winver_FILE_VERSION_N3" "${CPACK_PACKAGE_VERSION_PATCH}") +inherit_value ("winver_FILE_VERSION_N4" "1") +inherit_value ("winver_PRODUCT_VERSION_N1" "${winver_FILE_VERSION_N1}") +inherit_value ("winver_PRODUCT_VERSION_N2" "${winver_FILE_VERSION_N2}") +inherit_value ("winver_PRODUCT_VERSION_N3" "${winver_FILE_VERSION_N3}") +inherit_value ("winver_PRODUCT_VERSION_N4" "${winver_FILE_VERSION_N4}") +inherit_value ("winver_LEGAL_COPYRIGHT" "") + + +# check if we generate source as part of the build +# - rubygen generates the amqp spec and clustering +# - managementgen generates the broker management code +# +# rubygen subdir is excluded from stable distributions +# If the main AMQP spec is present, then check if ruby and python are +# present, and if any sources have changed, forcing a re-gen of source code. + +set(AMQP_SPEC_DIR ${qpid-cpp_SOURCE_DIR}/../specs) +set(AMQP_SPEC ${AMQP_SPEC_DIR}/amqp.0-10-qpid-errata.xml) +if (EXISTS ${AMQP_SPEC}) + include(FindRuby) + include(FindPythonInterp) + if (NOT RUBY_EXECUTABLE) + message(FATAL_ERROR "Can't locate ruby, needed to generate source files.") + endif (NOT RUBY_EXECUTABLE) + if (NOT PYTHON_EXECUTABLE) + message(FATAL_ERROR "Can't locate python, needed to generate source files.") + endif (NOT PYTHON_EXECUTABLE) + + set(specs ${AMQP_SPEC} ${qpid-cpp_SOURCE_DIR}/xml/cluster.xml) + set(regen_amqp OFF) + set(rgen_dir ${qpid-cpp_SOURCE_DIR}/rubygen) + file(GLOB_RECURSE rgen_progs ${rgen_dir}/*.rb) + # If any of the specs, or any of the sources used to generate code, change + # then regenerate the sources. + foreach (spec_file ${specs} ${rgen_progs}) + if (${spec_file} IS_NEWER_THAN ${CMAKE_CURRENT_BINARY_DIR}/rubygen.cmake) + set(regen_amqp ON) + endif (${spec_file} IS_NEWER_THAN ${CMAKE_CURRENT_BINARY_DIR}/rubygen.cmake) + endforeach (spec_file ${specs}) + if (regen_amqp) + message(STATUS "Regenerating AMQP protocol sources") +execute_process(COMMAND ${RUBY_EXECUTABLE} -I ${rgen_dir} ${rgen_dir}/generate ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/../include ${specs} all rubygen.cmake + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + else (regen_amqp) + message(STATUS "No need to generate AMQP protocol sources") + endif (regen_amqp) + + set(mgmt_specs ${AMQP_SPEC_DIR}/management-schema.xml + ${CMAKE_CURRENT_SOURCE_DIR}/qpid/acl/management-schema.xml + ${CMAKE_CURRENT_SOURCE_DIR}/qpid/cluster/management-schema.xml) + set(mgen_dir ${qpid-cpp_SOURCE_DIR}/managementgen) + set(regen_mgmt OFF) + foreach (spec_file ${mgmt_specs}) + if (${spec_file} IS_NEWER_THAN ${CMAKE_CURRENT_BINARY_DIR}/managementgen.cmake) + message(STATUS "${spec_file} is newer") + set(regen_mgmt ON) + endif (${spec_file} IS_NEWER_THAN ${CMAKE_CURRENT_BINARY_DIR}/managementgen.cmake) + endforeach (spec_file ${mgmt_specs}) + if (regen_mgmt) + message(STATUS "Regenerating Qpid Management Framework sources") +execute_process(COMMAND ${PYTHON_EXECUTABLE} ${mgen_dir}/qmf-gen -c managementgen.cmake -b -q -o ${CMAKE_CURRENT_BINARY_DIR}/qmf ${mgmt_specs} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + else (regen_mgmt) + message(STATUS "No need to generate Qpid Management Framework sources") + endif (regen_mgmt) + + # Pull in the names of the generated files, i.e. ${rgen_framing_srcs} + include (${CMAKE_CURRENT_BINARY_DIR}/rubygen.cmake) + include (${CMAKE_CURRENT_BINARY_DIR}/managementgen.cmake) + +else (EXISTS ${AMQP_SPEC}) + message(STATUS "No AMQP spec... presume generated sources are included") + set(QPID_GENERATED_HEADERS_IN_SOURCE ON) + include (rubygen.cmake) + include (managementgen.cmake) +endif (EXISTS ${AMQP_SPEC}) + +find_program(HELP2MAN help2man DOC "Location of the help2man program") +option(GEN_MANPAGES "Use help2man to generate man pages" ON) +if (GEN_MANPAGES AND NOT HELP2MAN) + message(STATUS "Can't locate the help2man command; man pages will not be generated") + set (GEN_MANPAGES OFF) +endif (GEN_MANPAGES AND NOT HELP2MAN) + +# FindDoxygen module tries to locate doxygen and Graphviz dot +set (docs_default ON) +if (NOT DOXYGEN_EXECUTABLE) + set (docs_default OFF) +endif (NOT DOXYGEN_EXECUTABLE) +option(GEN_DOXYGEN "Use doxygen to generate user documentation" ${docs_default}) +if (GEN_DOXYGEN AND NOT DOXYGEN_EXECUTABLE) + message(STATUS "Can't locate the doxygen command; user documentation will not be generated") + set (GEN_DOXYGEN OFF) +endif (GEN_DOXYGEN AND NOT DOXYGEN_EXECUTABLE) + +find_program(VALGRIND valgrind DOC "Location of the valgrind program") +option(ENABLE_VALGRIND "Use valgrind to detect run-time problems" ON) +if (ENABLE_VALGRIND AND NOT VALGRIND) + message(STATUS "Can't locate the valgrind command; no run-time error detection") +endif (ENABLE_VALGRIND AND NOT VALGRIND) + +if (CMAKE_COMPILER_IS_GNUCXX) + set (COMPILER_FLAGS "") + # Warnings: Enable as many as possible, keep the code clean. Please + # do not disable warnings or remove -Werror without discussing on + # qpid-dev list. + # + # The following warnings are deliberately omitted, they warn on valid code. + # -Wunreachable-code -Wpadded -Winline + # -Wshadow - warns about boost headers. + set (WARNING_FLAGS + "-Werror -pedantic -Wall -Wextra -Wno-shadow -Wpointer-arith -Wcast-qual -Wcast-align -Wno-long-long -Wvolatile-register-var -Winvalid-pch -Wno-system-headers -Woverloaded-virtual") +endif (CMAKE_COMPILER_IS_GNUCXX) + +if (CMAKE_CXX_COMPILER_ID STREQUAL SunPro) + set (COMPILER_FLAGS "-library=stlport4 -mt") + set (WARNING_FLAGS "+w2") +endif (CMAKE_CXX_COMPILER_ID STREQUAL SunPro) + +option(ENABLE_WARNINGS "Enable lots of compiler warnings (recommended)" ON) +if (NOT ENABLE_WARNINGS) + set (WARNING_FLAGS "") +endif (NOT ENABLE_WARNINGS) + +set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COMPILER_FLAGS} ${WARNING_FLAGS}") + +# Expand a bit from the basic Find_Boost; be specific about what's needed. +# TODO: Not all these libs are needed everywhere: +# Linux only uses filesystem program_options unit_test_framework +# (which itself uses regex). +# Boost.system is sometimes needed; it's handled separately, below. +find_package(Boost 1.33 REQUIRED + COMPONENTS filesystem program_options date_time thread + regex unit_test_framework) +if(NOT Boost_FOUND) + message(FATAL_ERROR "Boost C++ libraries not found. Please install or try setting BOOST_ROOT") +endif(NOT Boost_FOUND) + +# Boost.system was introduced at Boost 1.35; it's needed secondarily by other +# Boost libs Qpid needs, so be sure it's there. +if (Boost_VERSION GREATER 103499) + find_package(Boost COMPONENTS system) + + # Allow for cmake pre 2.6 and boost post 1.35 + if (NOT Boost_SYSTEM_LIBRARY) + set(Boost_SYSTEM_LIBRARY boost_system) + endif (NOT Boost_SYSTEM_LIBRARY) +endif (Boost_VERSION GREATER 103499) + +# Versions of cmake pre 2.6 don't set the Boost_*_LIBRARY variables correctly +# these values are correct for Linux +if (NOT Boost_PROGRAM_OPTIONS_LIBRARY) + set(Boost_PROGRAM_OPTIONS_LIBRARY boost_program_options) +endif (NOT Boost_PROGRAM_OPTIONS_LIBRARY) + +if (NOT Boost_FILESYSTEM_LIBRARY) + set(Boost_FILESYSTEM_LIBRARY boost_filesystem) +endif (NOT Boost_FILESYSTEM_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) + +if (NOT Boost_REGEX_LIBRARY) + set(Boost_REGEX_LIBRARY boost_regex) +endif (NOT Boost_REGEX_LIBRARY) + +# The Windows install also wants the Boost DLLs, libs and headers that the +# release is built with. The DLLs enable everything to run, and the headers +# and libs ensure that users building Qpid C++ client programs can compile +# (the C++ API still exposes Boost headers, but hopefully this will be fixed +# in the future). +# +# On Windows you can pick whether the static or dynamic versions of the libs +# are used; allow this choice to the user. Since we also install the Boost +# DLLs that are needed for the Windows package, none are needed for the +# static link case; else drop them into the install. Do this all first, since +# Boost on Windows can use automatic linking to pick up the correct +# Boost libs based on compile-time touching of the headers. Since we don't +# really need to add them to the link lines, set the names to blanks. +if (MSVC) + install (PROGRAMS + ${Boost_DATE_TIME_LIBRARY_DEBUG} ${Boost_DATE_TIME_LIBRARY_RELEASE} + ${Boost_FILESYSTEM_LIBRARY_DEBUG} ${Boost_FILESYSTEM_LIBRARY_RELEASE} + ${Boost_PROGRAM_OPTIONS_LIBRARY_DEBUG} ${Boost_PROGRAM_OPTIONS_LIBRARY_RELEASE} + ${Boost_REGEX_LIBRARY_DEBUG} ${Boost_REGEX_LIBRARY_RELEASE} + ${Boost_THREAD_LIBRARY_DEBUG} ${Boost_THREAD_LIBRARY_RELEASE} + DESTINATION ${QPID_INSTALL_LIBDIR}/boost + COMPONENT ${QPID_COMPONENT_COMMON}) + + if (NOT Boost_VERSION LESS 103500) + install (PROGRAMS + ${Boost_SYSTEM_LIBRARY_DEBUG} ${Boost_SYSTEM_LIBRARY_RELEASE} + DESTINATION ${QPID_INSTALL_LIBDIR}/boost + COMPONENT ${QPID_COMPONENT_COMMON}) + endif (NOT Boost_VERSION LESS 103500) + + option(QPID_LINK_BOOST_DYNAMIC "Link with dynamic Boost libs (OFF to link static)" ON) + if (QPID_LINK_BOOST_DYNAMIC) + add_definitions( /D BOOST_ALL_DYN_LINK) + string (REPLACE .lib .dll + _boost_date_time_debug ${Boost_DATE_TIME_LIBRARY_DEBUG}) + string (REPLACE .lib .dll + _boost_date_time_release ${Boost_DATE_TIME_LIBRARY_RELEASE}) + string (REPLACE .lib .dll + _boost_filesystem_debug ${Boost_FILESYSTEM_LIBRARY_DEBUG}) + string (REPLACE .lib .dll + _boost_filesystem_release ${Boost_FILESYSTEM_LIBRARY_RELEASE}) + string (REPLACE .lib .dll + _boost_program_options_debug ${Boost_PROGRAM_OPTIONS_LIBRARY_DEBUG}) + string (REPLACE .lib .dll + _boost_program_options_release ${Boost_PROGRAM_OPTIONS_LIBRARY_RELEASE}) + string (REPLACE .lib .dll + _boost_regex_debug ${Boost_REGEX_LIBRARY_DEBUG}) + string (REPLACE .lib .dll + _boost_regex_release ${Boost_REGEX_LIBRARY_RELEASE}) + string (REPLACE .lib .dll + _boost_thread_debug ${Boost_THREAD_LIBRARY_DEBUG}) + string (REPLACE .lib .dll + _boost_thread_release ${Boost_THREAD_LIBRARY_RELEASE}) + # Boost 1.35 added the system library, which gets indirectly linked in + # via other Boost libs. So, if building with Boost 1.35 or later, also + # include system in the Windows install package. + if (NOT Boost_VERSION LESS 103500) + string (REPLACE boost_thread boost_system + _boost_system_debug ${_boost_thread_debug}) + string (REPLACE boost_thread boost_system + _boost_system_release ${_boost_thread_release}) + endif (NOT Boost_VERSION LESS 103500) + install (PROGRAMS + ${_boost_date_time_debug} ${_boost_date_time_release} + ${_boost_filesystem_debug} ${_boost_filesystem_release} + ${_boost_program_options_debug} ${_boost_program_options_release} + ${_boost_regex_debug} ${_boost_regex_release} + ${_boost_system_debug} ${_boost_system_release} + ${_boost_thread_debug} ${_boost_thread_release} + DESTINATION ${QPID_INSTALL_LIBDIR}/boost + COMPONENT ${QPID_COMPONENT_COMMON}) + endif (QPID_LINK_BOOST_DYNAMIC) + + # Need the boost headers regardless of which way the libs go. Try to + # weed out what we don't need, else it's giant and unnecessary. + install (DIRECTORY ${Boost_INCLUDE_DIR}/boost + DESTINATION ${QPID_INSTALL_INCLUDEDIR} + COMPONENT ${QPID_COMPONENT_CLIENT_INCLUDE} + PATTERN "accumulators/*" EXCLUDE + PATTERN "algorithm/*" EXCLUDE + PATTERN "archive/*" EXCLUDE + PATTERN "asio*" EXCLUDE + PATTERN "bimap*" EXCLUDE + PATTERN "circular_buffer*" EXCLUDE + PATTERN "concept*" EXCLUDE + PATTERN "dynamic_bitset*" EXCLUDE + PATTERN "flyweight*" EXCLUDE + PATTERN "fusion/*" EXCLUDE + PATTERN "gil/*" EXCLUDE + PATTERN "graph/*" EXCLUDE + PATTERN "interprocess/*" EXCLUDE + PATTERN "lambda/*" EXCLUDE + PATTERN "logic/*" EXCLUDE + PATTERN "math*" EXCLUDE + PATTERN "mpi*" EXCLUDE + PATTERN "multi_*" EXCLUDE + PATTERN "numeric/*" EXCLUDE + PATTERN "pending/*" EXCLUDE + PATTERN "pool/*" EXCLUDE + PATTERN "property_map/*" EXCLUDE + PATTERN "proto/*" EXCLUDE + PATTERN "random*" EXCLUDE + PATTERN "signals*" EXCLUDE + PATTERN "spirit*" EXCLUDE + PATTERN "statechart/*" EXCLUDE + PATTERN "units/*" EXCLUDE + PATTERN "unordered*" EXCLUDE + PATTERN "wave*" EXCLUDE + PATTERN "xpressive/*" EXCLUDE) + + set(Boost_DATE_TIME_LIBRARY "") + set(Boost_THREAD_LIBRARY "") + set(Boost_PROGRAM_OPTIONS_LIBRARY "") + set(Boost_FILESYSTEM_LIBRARY "") + set(Boost_UNIT_TEST_FRAMEWORK_LIBRARY "") + set(Boost_REGEX_LIBRARY "") + include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/windows/resources ) +endif (MSVC) + +include_directories( ${Boost_INCLUDE_DIR} ) + +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ) +include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../include ) +include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( ${CMAKE_CURRENT_BINARY_DIR}/../include ) + +link_directories( ${Boost_LIBRARY_DIRS} ) + +CHECK_LIBRARY_EXISTS (rt clock_gettime "" CLOCK_GETTIME_IN_RT) +if (NOT CLOCK_GETTIME_IN_RT) + CHECK_FUNCTION_EXISTS (clock_gettime QPID_HAS_CLOCK_GETTIME) +else (NOT CLOCK_GETTIME_IN_RT) + set(CMAKE_REQUIRED_LIBS ${CMAKE_REQUIRED_LIBS} rt) + set(QPID_HAS_CLOCK_GETTIME YES CACHE BOOL "Platform has clock_gettime") +endif (NOT CLOCK_GETTIME_IN_RT) + +# See if Cyrus SASL is desired and available +CHECK_LIBRARY_EXISTS (sasl2 sasl_checkpass "" HAVE_SASL) +CHECK_INCLUDE_FILES (sasl/sasl.h HAVE_SASL_H) + +set (sasl_default ${sasl_force}) +if (HAVE_SASL AND HAVE_SASL_H) + set (sasl_default ON) +endif (HAVE_SASL AND HAVE_SASL_H) + +option(BUILD_SASL "Build with Cyrus SASL support" ${sasl_default}) +if (BUILD_SASL) + if (NOT HAVE_SASL) + message(FATAL_ERROR "Cyrus SASL support requested but libsasl2 not found") + endif (NOT HAVE_SASL) + if (NOT HAVE_SASL_H) + message(FATAL_ERROR "Cyrus SASL support requested but sasl.h not found") + endif (NOT HAVE_SASL_H) + + set(BROKER_SASL_NAME "qpidd" CACHE STRING "SASL app name for the qpid broker") + set(qpidcommon_sasl_source + qpid/sys/cyrus/CyrusSecurityLayer.h + qpid/sys/cyrus/CyrusSecurityLayer.cpp + ) + set(qpidcommon_sasl_lib sasl2) +endif (BUILD_SASL) + +# See if XML Exchange is desired and prerequisites are available +CHECK_LIBRARY_EXISTS (xerces-c _init "" HAVE_XERCES) +CHECK_INCLUDE_FILE_CXX (xercesc/framework/MemBufInputSource.hpp HAVE_XERCES_H) +CHECK_INCLUDE_FILE_CXX (xqilla/xqilla-simple.hpp HAVE_XQILLA_H) +CHECK_INCLUDE_FILE_CXX (xqilla/ast/XQEffectiveBooleanValue.hpp HAVE_XQ_EBV) + +set (xml_default ${xml_force}) +if (CMAKE_SYSTEM_NAME STREQUAL Windows) +else (CMAKE_SYSTEM_NAME STREQUAL Windows) + if (HAVE_XERCES AND HAVE_XERCES_H) + if (HAVE_XQILLA_H) + set (xml_default ON) + endif (HAVE_XQILLA_H) + endif (HAVE_XERCES AND HAVE_XERCES_H) +endif (CMAKE_SYSTEM_NAME STREQUAL Windows) + +option(BUILD_XML "Build with XML Exchange" ${xml_default}) +if (BUILD_XML) + if (NOT HAVE_XERCES) + message(FATAL_ERROR "XML Exchange support requested but xerces-c library not found") + endif (NOT HAVE_XERCES) + if (NOT HAVE_XERCES_H) + message(FATAL_ERROR "XML Exchange support requested but Xerces-C headers not found") + endif (NOT HAVE_XERCES_H) + if (NOT HAVE_XQILLA_H) + message(FATAL_ERROR "XML Exchange support requested but XQilla headers not found") + endif (NOT HAVE_XQILLA_H) + + if (HAVE_XQ_EBV) + add_definitions(-DXQ_EFFECTIVE_BOOLEAN_VALUE_HPP) + endif (HAVE_XQ_EBV) + + add_library (xml MODULE + qpid/xml/XmlExchange.cpp + qpid/xml/XmlExchange.h + qpid/xml/XmlExchangePlugin.cpp) + set_target_properties (xml PROPERTIES PREFIX "") + target_link_libraries (xml xerces-c xqilla qpidbroker pthread) + if (CMAKE_COMPILER_IS_GNUCXX) + set_target_properties (xml PROPERTIES + PREFIX "" + LINK_FLAGS -Wl,--no-undefined) + endif (CMAKE_COMPILER_IS_GNUCXX) + install (TARGETS xml + DESTINATION ${QPIDD_MODULE_DIR} + COMPONENT ${QPID_COMPONENT_BROKER}) + + set(xml_tests XmlClientSessionTest) + +endif (BUILD_XML) + +# Build the ACL plugin +set (acl_default ON) +option(BUILD_ACL "Build ACL enforcement broker plugin" ${acl_default}) +if (BUILD_ACL) + set (acl_SOURCES + qpid/acl/Acl.cpp + qpid/acl/Acl.h + qpid/acl/AclData.cpp + qpid/acl/AclData.h + qpid/acl/AclPlugin.cpp + qpid/acl/AclReader.cpp + qpid/acl/AclReader.h + qpid/acl/AclValidator.cpp + qpid/acl/AclValidator.h + ) + # Windows builds the ACL code into the qpidbroker library; see QPID-1842 + # for history and rationale. If this is changed, remove the acl_SOURCES from + # the qpidbroker platform-specific source list. + if (NOT CMAKE_SYSTEM_NAME STREQUAL Windows) + add_library (acl MODULE ${acl_SOURCES}) + set_target_properties (acl PROPERTIES PREFIX "") + target_link_libraries (acl qpidbroker ${Boost_PROGRAM_OPTIONS_LIBRARY}) + if (CMAKE_COMPILER_IS_GNUCXX) + set_target_properties (acl PROPERTIES + PREFIX "" + LINK_FLAGS -Wl,--no-undefined) + endif (CMAKE_COMPILER_IS_GNUCXX) + install (TARGETS acl + DESTINATION ${QPIDD_MODULE_DIR} + COMPONENT ${QPID_COMPONENT_BROKER}) + endif (NOT CMAKE_SYSTEM_NAME STREQUAL Windows) +endif (BUILD_ACL) + +# Check for optional cluster support requirements +include (cluster.cmake) + +# Check for optional RDMA support requirements +include (rdma.cmake) + +# Check for optional SSL support requirements +include (ssl.cmake) + +# Check for syslog capabilities not present on all systems +check_symbol_exists (LOG_AUTHPRIV "sys/syslog.h" HAVE_LOG_AUTHPRIV) +check_symbol_exists (LOG_FTP "sys/syslog.h" HAVE_LOG_FTP) + +if (CMAKE_SYSTEM_NAME STREQUAL Windows) + if (MSVC) + add_definitions( + /D "_CRT_NONSTDC_NO_WARNINGS" + /D "NOMINMAX" + /D "WIN32_LEAN_AND_MEAN" + /wd4244 + /wd4800 + /wd4355 + ) + if (MSVC80) + add_definitions(/D "_WIN32_WINNT=0x0501") + endif (MSVC80) + + # set the RelWithDebInfo compile/link switches to equal Release + set (CMAKE_CXX_FLAGS_RELWITHDEBINFO "/MD /O2 /Ob2 /D NDEBUG") + set (CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "/debug /INCREMENTAL:NO") + + if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/../bindings/qpid/dotnet/src) + # Set the windows version for the .NET Binding cpp project + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../bindings/qpid/dotnet/src/org.apache.qpid.messaging.template.rc + ${CMAKE_CURRENT_BINARY_DIR}/windows/resources/org.apache.qpid.messaging.rc) + # Set the windows version for the .NET Binding sessionreceiver project + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../bindings/qpid/dotnet/src/sessionreceiver/properties/sessionreceiver-AssemblyInfo-template.cs + ${CMAKE_CURRENT_BINARY_DIR}/windows/generated_src/sessionreceiver-AssemblyInfo.cs) + endif (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/../bindings/qpid/dotnet/src) + endif (MSVC) + + set (qpidtypes_platform_SOURCES + qpid/sys/windows/uuid.cpp + ) + set (qpidtypes_platform_LIBS + rpcrt4 + ) + + set (qpidcommon_platform_SOURCES + qpid/log/windows/SinkOptions.cpp + qpid/sys/windows/AsynchIO.cpp + qpid/sys/windows/FileSysDir.cpp + qpid/sys/windows/IocpPoller.cpp + qpid/sys/windows/IOHandle.cpp + qpid/sys/windows/LockFile.cpp + qpid/sys/windows/PipeHandle.cpp + qpid/sys/windows/PollableCondition.cpp + qpid/sys/windows/Shlib.cpp + qpid/sys/windows/Socket.cpp + qpid/sys/windows/SocketAddress.cpp + qpid/sys/windows/StrError.cpp + qpid/sys/windows/SystemInfo.cpp + qpid/sys/windows/Thread.cpp + qpid/sys/windows/Time.cpp + qpid/client/windows/SaslFactory.cpp + ${sslcommon_windows_SOURCES} + ) + + set (qpidcommon_platform_LIBS + ${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 + ${acl_SOURCES} + ${sslbroker_windows_SOURCES} + ) + set (qpidbroker_platform_LIBS + ${windows_ssl_libs} ${windows_ssl_server_libs} + ) + set (qpidclient_platform_SOURCES + ${sslclient_windows_SOURCES} + ) + set (qpidclient_platform_LIBS + ${windows_ssl_libs} + ) + + set (qpidd_platform_SOURCES + windows/QpiddBroker.cpp + ) + + set (qpidmessaging_platform_SOURCES + qpid/messaging/HandleInstantiator.cpp + ) + +else (CMAKE_SYSTEM_NAME STREQUAL Windows) + + # POSIX (Non-Windows) platforms have a lot of overlap in sources; the only + # major difference is the poller module. + if (CMAKE_SYSTEM_NAME STREQUAL Linux) + set (qpid_poller_module + qpid/sys/epoll/EpollPoller.cpp + qpid/sys/posix/SystemInfo.cpp + ) + add_definitions(-pthread) + set (CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} -pthread) + endif (CMAKE_SYSTEM_NAME STREQUAL Linux) + + if (CMAKE_SYSTEM_NAME STREQUAL SunOS) + set (qpid_poller_module + qpid/sys/solaris/ECFPoller.cpp + qpid/sys/solaris/SystemInfo.cpp + ) + endif (CMAKE_SYSTEM_NAME STREQUAL SunOS) + + set (qpidtypes_platform_SOURCES) + set (qpidtypes_platform_LIBS + uuid + ) + + set (qpidcommon_platform_SOURCES + qpid/sys/posix/AsynchIO.cpp + qpid/sys/posix/Fork.cpp + qpid/sys/posix/FileSysDir.cpp + qpid/sys/posix/IOHandle.cpp + qpid/sys/posix/LockFile.cpp + qpid/sys/posix/Mutex.cpp + qpid/sys/posix/PipeHandle.cpp + qpid/sys/posix/PollableCondition.cpp + qpid/sys/posix/Shlib.cpp + qpid/log/posix/SinkOptions.cpp + qpid/sys/posix/Socket.cpp + qpid/sys/posix/SocketAddress.cpp + qpid/sys/posix/StrError.cpp + qpid/sys/posix/Thread.cpp + qpid/sys/posix/Time.cpp + qpid/SaslFactory.cpp + + ${qpid_poller_module} + ) + set (qpidcommon_platform_LIBS + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${CMAKE_DL_LIBS} + ) + + set (qpidbroker_platform_SOURCES + qpid/broker/Daemon.cpp + qpid/broker/SaslAuthenticator.cpp + qpid/broker/SignalHandler.h + qpid/broker/SignalHandler.cpp + qpid/broker/posix/BrokerDefaults.cpp + ) + + set (qpidclient_platform_SOURCES + ) + + set (qpidd_platform_SOURCES + posix/QpiddBroker.cpp + ) + + set (qpidmessaging_platform_SOURCES + ) +endif (CMAKE_SYSTEM_NAME STREQUAL Windows) + +set (qpidcommon_SOURCES + ${rgen_framing_srcs} + ${qpidcommon_platform_SOURCES} + ${qpidcommon_sasl_source} + qpid/assert.cpp + qpid/Address.cpp + qpid/DataDir.cpp + qpid/Exception.cpp + qpid/Modules.cpp + qpid/Options.cpp + qpid/Plugin.cpp + qpid/RefCountedBuffer.cpp + qpid/SessionState.cpp + qpid/SessionId.cpp + qpid/StringUtils.cpp + qpid/Url.cpp + qpid/amqp_0_10/SessionHandler.cpp + qpid/framing/AccumulatedAck.cpp + qpid/framing/AMQBody.cpp + qpid/framing/AMQMethodBody.cpp + qpid/framing/AMQContentBody.cpp + qpid/framing/AMQFrame.cpp + qpid/framing/AMQHeaderBody.cpp + qpid/framing/AMQHeartbeatBody.cpp + qpid/framing/Array.cpp + qpid/framing/BodyHandler.cpp + qpid/framing/Buffer.cpp + qpid/framing/Endian.cpp + qpid/framing/FieldTable.cpp + qpid/framing/FieldValue.cpp + qpid/framing/FrameSet.cpp + qpid/framing/FrameDecoder.cpp + qpid/framing/List.cpp + qpid/framing/ProtocolInitiation.cpp + qpid/framing/ProtocolVersion.cpp + qpid/framing/SendContent.cpp + qpid/framing/SequenceNumber.cpp + qpid/framing/SequenceNumberSet.cpp + qpid/framing/SequenceSet.cpp + qpid/framing/Proxy.cpp + qpid/framing/Uuid.cpp + qpid/framing/TransferContent.cpp + qpid/log/Logger.cpp + qpid/log/Options.cpp + qpid/log/OstreamOutput.cpp + qpid/log/Selector.cpp + qpid/log/Statement.cpp + qpid/management/Buffer.cpp + qpid/management/ConnectionSettings.cpp + qpid/management/Mutex.cpp + qpid/management/Manageable.cpp + qpid/management/ManagementObject.cpp + qpid/sys/AggregateOutput.cpp + qpid/sys/AsynchIOHandler.cpp + qpid/sys/ClusterSafe.cpp + qpid/sys/Dispatcher.cpp + qpid/sys/DispatchHandle.cpp + qpid/sys/Runnable.cpp + qpid/sys/Shlib.cpp + qpid/sys/Timer.cpp + qpid/sys/TimerWarnings.cpp + qpid/amqp_0_10/Codecs.cpp +) +add_msvc_version (qpidcommon library dll) + +add_library (qpidcommon SHARED ${qpidcommon_SOURCES}) +if (CLOCK_GETTIME_IN_RT) + set (qpidcommon_platform_LIBS ${qpidcommon_platform_LIBS} rt) +endif (CLOCK_GETTIME_IN_RT) +target_link_libraries (qpidcommon qpidtypes + ${qpidcommon_platform_LIBS} + ${qpidcommon_sasl_lib}) +set_target_properties (qpidcommon PROPERTIES + VERSION ${qpidc_version}) +install (TARGETS qpidcommon + DESTINATION ${QPID_INSTALL_LIBDIR} + COMPONENT ${QPID_COMPONENT_COMMON}) +install_pdb (qpidcommon ${QPID_COMPONENT_COMMON}) + +set(qpidtypes_SOURCES + qpid/types/Exception.cpp + qpid/types/Uuid.cpp + qpid/types/Variant.cpp + ${qpidtypes_platform_SOURCES} +) +add_msvc_version (qpidtypes library dll) +add_library(qpidtypes SHARED ${qpidtypes_SOURCES}) +target_link_libraries(qpidtypes ${qpidtypes_platform_LIBS}) +set_target_properties (qpidtypes PROPERTIES VERSION ${qpidc_version}) +install(TARGETS qpidtypes + DESTINATION ${QPID_INSTALL_LIBDIR} + COMPONENT ${QPID_COMPONENT_COMMON}) +install_pdb (qpidtypes ${QPID_COMPONENT_COMMON}) + +set (qpidclient_SOURCES + ${rgen_client_srcs} + ${qpidclient_platform_SOURCES} + qpid/client/Bounds.cpp + qpid/client/Completion.cpp + qpid/client/Connection.cpp + qpid/client/ConnectionHandler.cpp + qpid/client/ConnectionImpl.cpp + qpid/client/ConnectionSettings.cpp + qpid/client/Connector.cpp + qpid/client/Demux.cpp + qpid/client/Dispatcher.cpp + qpid/client/FailoverManager.cpp + qpid/client/FailoverListener.cpp + qpid/client/Future.cpp + qpid/client/FutureCompletion.cpp + qpid/client/FutureResult.cpp + qpid/client/LoadPlugins.cpp + qpid/client/LocalQueue.cpp + qpid/client/LocalQueueImpl.cpp + qpid/client/Message.cpp + qpid/client/MessageImpl.cpp + qpid/client/MessageListener.cpp + qpid/client/MessageReplayTracker.cpp + qpid/client/QueueOptions.cpp + qpid/client/Results.cpp + qpid/client/SessionBase_0_10.cpp + qpid/client/SessionBase_0_10Access.h + qpid/client/ConnectionAccess.h + qpid/client/SessionImpl.cpp + qpid/client/StateManager.cpp + qpid/client/Subscription.cpp + qpid/client/SubscriptionImpl.cpp + qpid/client/SubscriptionManager.cpp + qpid/client/SubscriptionManagerImpl.cpp + qpid/client/TCPConnector.cpp +) +add_msvc_version (qpidclient library dll) + +add_library (qpidclient SHARED ${qpidclient_SOURCES}) +target_link_libraries (qpidclient qpidcommon ${qpidclient_platform_LIBS}) +set_target_properties (qpidclient PROPERTIES VERSION ${qpidc_version}) +install (TARGETS qpidclient + DESTINATION ${QPID_INSTALL_LIBDIR} + COMPONENT ${QPID_COMPONENT_CLIENT}) +install (DIRECTORY ../include/qpid + DESTINATION ${QPID_INSTALL_INCLUDEDIR} + COMPONENT ${QPID_COMPONENT_CLIENT_INCLUDE} + PATTERN ".svn" EXCLUDE) +install_pdb (qpidclient ${QPID_COMPONENT_CLIENT}) + + +set (qpidmessaging_SOURCES + ${qpidmessaging_platform_SOURCES} + qpid/messaging/Address.cpp + qpid/messaging/AddressParser.h + qpid/messaging/AddressParser.cpp + qpid/messaging/Connection.cpp + qpid/messaging/ConnectionImpl.h + qpid/messaging/Duration.cpp + qpid/messaging/exceptions.cpp + qpid/messaging/Message.cpp + qpid/messaging/MessageImpl.h + qpid/messaging/MessageImpl.cpp + qpid/messaging/Receiver.cpp + qpid/messaging/ReceiverImpl.h + qpid/messaging/Session.cpp + qpid/messaging/SessionImpl.h + qpid/messaging/Sender.cpp + qpid/messaging/SenderImpl.h + qpid/messaging/FailoverUpdates.cpp + qpid/client/amqp0_10/AcceptTracker.h + qpid/client/amqp0_10/AcceptTracker.cpp + qpid/client/amqp0_10/AddressResolution.h + qpid/client/amqp0_10/AddressResolution.cpp + qpid/client/amqp0_10/ConnectionImpl.h + qpid/client/amqp0_10/ConnectionImpl.cpp + qpid/client/amqp0_10/IncomingMessages.h + qpid/client/amqp0_10/IncomingMessages.cpp + qpid/client/amqp0_10/MessageSink.h + qpid/client/amqp0_10/MessageSource.h + qpid/client/amqp0_10/OutgoingMessage.h + qpid/client/amqp0_10/OutgoingMessage.cpp + qpid/client/amqp0_10/ReceiverImpl.h + qpid/client/amqp0_10/ReceiverImpl.cpp + 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 +) +add_msvc_version (qpidmessaging library dll) + +add_library (qpidmessaging SHARED ${qpidmessaging_SOURCES}) +target_link_libraries (qpidmessaging qpidclient) +set_target_properties (qpidmessaging PROPERTIES VERSION ${qpidc_version}) +install (TARGETS qpidmessaging + DESTINATION ${QPID_INSTALL_LIBDIR} + COMPONENT ${QPID_COMPONENT_CLIENT}) +install_pdb (qpidmessaging ${QPID_COMPONENT_CLIENT}) + +# Released source artifacts from Apache have the generated headers included in +# the source tree, not the binary tree. So don't attempt to grab them when +# they're not supposed to be there. +if (NOT QPID_GENERATED_HEADERS_IN_SOURCE) + install (DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/../include/qpid + DESTINATION ${QPID_INSTALL_INCLUDEDIR} + COMPONENT ${QPID_COMPONENT_CLIENT_INCLUDE}) +endif (NOT QPID_GENERATED_HEADERS_IN_SOURCE) + + +if (_MSC_VER) + # 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) + if (EXISTS ${qpidxarm_SOURCES}) + add_msvc_version (qpidxarm library dll) + add_library (qpidxarm SHARED ${qpidxarm_SOURCES}) + target_link_libraries (qpidxarm qpidclient qpidcommon) + install (TARGETS qpidxarm + DESTINATION ${QPID_INSTALL_LIBDIR} + COMPONENT ${QPID_COMPONENT_CLIENT}) + install_pdb (qpidxarm ${QPID_COMPONENT_CLIENT}) + endif (EXISTS ${qpidxarm_SOURCES}) +endif (_MSC_VER) + +set (qpidbroker_SOURCES + ${mgen_broker_cpp} + ${qpidbroker_platform_SOURCES} + qpid/amqp_0_10/Connection.h + qpid/amqp_0_10/Connection.cpp + qpid/broker/Broker.cpp + qpid/broker/Exchange.cpp + qpid/broker/ExpiryPolicy.cpp + qpid/broker/Fairshare.cpp + qpid/broker/LegacyLVQ.cpp + qpid/broker/MessageDeque.cpp + qpid/broker/MessageMap.cpp + qpid/broker/PriorityQueue.cpp + qpid/broker/Queue.cpp + qpid/broker/QueueCleaner.cpp + qpid/broker/QueueListeners.cpp + qpid/broker/PersistableMessage.cpp + qpid/broker/Bridge.cpp + qpid/broker/Connection.cpp + qpid/broker/ConnectionHandler.cpp + qpid/broker/ConnectionFactory.cpp + qpid/broker/DeliverableMessage.cpp + qpid/broker/DeliveryRecord.cpp + qpid/broker/DirectExchange.cpp + qpid/broker/DtxAck.cpp + qpid/broker/DtxBuffer.cpp + qpid/broker/DtxManager.cpp + qpid/broker/DtxTimeout.cpp + qpid/broker/DtxWorkRecord.cpp + qpid/broker/ExchangeRegistry.cpp + qpid/broker/FanOutExchange.cpp + qpid/broker/HeadersExchange.cpp + qpid/broker/Link.cpp + qpid/broker/LinkRegistry.cpp + qpid/broker/Message.cpp + qpid/broker/MessageAdapter.cpp + qpid/broker/MessageBuilder.cpp + qpid/broker/MessageStoreModule.cpp + qpid/broker/NameGenerator.cpp + qpid/broker/NullMessageStore.cpp + qpid/broker/QueueBindings.cpp + qpid/broker/QueueEvents.cpp + qpid/broker/QueuePolicy.cpp + qpid/broker/QueueRegistry.cpp + qpid/broker/QueueFlowLimit.cpp + qpid/broker/RateTracker.cpp + qpid/broker/RecoveryManagerImpl.cpp + qpid/broker/RecoveredEnqueue.cpp + qpid/broker/RecoveredDequeue.cpp + qpid/broker/RetryList.cpp + qpid/broker/SecureConnection.cpp + qpid/broker/SecureConnectionFactory.cpp + qpid/broker/SemanticState.h + qpid/broker/SemanticState.cpp + qpid/broker/SessionAdapter.cpp + qpid/broker/SessionState.h + qpid/broker/SessionState.cpp + qpid/broker/SessionManager.h + qpid/broker/SessionManager.cpp + qpid/broker/SessionContext.h + qpid/broker/SessionHandler.h + qpid/broker/SessionHandler.cpp + qpid/broker/System.cpp + qpid/broker/ThresholdAlerts.cpp + qpid/broker/TopicExchange.cpp + qpid/broker/TxAccept.cpp + qpid/broker/TxBuffer.cpp + qpid/broker/TxPublish.cpp + qpid/broker/Vhost.cpp + qpid/management/ManagementAgent.cpp + qpid/management/ManagementDirectExchange.cpp + qpid/management/ManagementTopicExchange.cpp + qpid/sys/TCPIOPlugin.cpp +) +add_msvc_version (qpidbroker library dll) +add_library (qpidbroker SHARED ${qpidbroker_SOURCES}) +target_link_libraries (qpidbroker qpidcommon ${qpidbroker_platform_LIBS}) +set_target_properties (qpidbroker PROPERTIES VERSION ${qpidc_version}) +if (MSVC) + set_target_properties (qpidbroker PROPERTIES COMPILE_FLAGS /wd4290) +endif (MSVC) +install (TARGETS qpidbroker + DESTINATION ${QPID_INSTALL_LIBDIR} + COMPONENT ${QPID_COMPONENT_BROKER}) +install_pdb (qpidbroker ${QPID_COMPONENT_BROKER}) + + +set (qpidd_SOURCES + ${qpidd_platform_SOURCES} + qpidd.cpp + qpidd.h +) +add_msvc_version (qpidd application exe) +add_executable (qpidd ${qpidd_SOURCES}) +target_link_libraries (qpidd qpidbroker qpidcommon ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY}) +install (TARGETS qpidd RUNTIME + DESTINATION ${QPID_INSTALL_BINDIR} + COMPONENT ${QPID_COMPONENT_BROKER}) +if (CPACK_GENERATOR STREQUAL "NSIS") + set (CPACK_NSIS_MENU_LINKS + "qpidd" "Start Qpid Broker") +endif (CPACK_GENERATOR STREQUAL "NSIS") + +# QMF library +# Library Version Information (CURRENT.REVISION.AGE): +# +# CURRENT => API/ABI version. Bump this if the interface changes +# 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 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) +set_target_properties (qmf PROPERTIES + VERSION ${qmf_version}) +install (TARGETS qmf OPTIONAL + DESTINATION ${QPID_INSTALL_LIBDIR} + 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/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/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 + qmf/engine/BrokerProxyImpl.h + qmf/engine/ConnectionSettingsImpl.cpp + qmf/engine/ConnectionSettingsImpl.h + qmf/engine/ConsoleImpl.cpp + qmf/engine/ConsoleImpl.h + qmf/engine/EventImpl.cpp + qmf/engine/EventImpl.h + qmf/engine/MessageImpl.cpp + qmf/engine/MessageImpl.h + qmf/engine/ObjectIdImpl.cpp + qmf/engine/ObjectIdImpl.h + qmf/engine/ObjectImpl.cpp + qmf/engine/ObjectImpl.h + qmf/engine/Protocol.cpp + qmf/engine/Protocol.h + qmf/engine/QueryImpl.cpp + qmf/engine/QueryImpl.h + qmf/engine/SequenceManager.cpp + qmf/engine/SequenceManager.h + qmf/engine/SchemaImpl.cpp + qmf/engine/SchemaImpl.h + qmf/engine/ValueImpl.cpp + qmf/engine/ValueImpl.h + ) +if (NOT WIN32) + list(APPEND qmfengine_SOURCES qmf/engine/ResilientConnection.cpp) +endif (NOT WIN32) +add_msvc_version (qmfengine library dll) + +add_library (qmfengine SHARED ${qmfengine_SOURCES}) +target_link_libraries (qmfengine qpidclient) +set_target_properties (qmfengine PROPERTIES + VERSION ${qmfengine_version}) +install (TARGETS qmfengine OPTIONAL + DESTINATION ${QPID_INSTALL_LIBDIR} + COMPONENT ${QPID_COMPONENT_QMF}) +install_pdb (qmfengine ${QPID_COMPONENT_QMF}) + +# QMF console library +#module_hdr += \ +# qpid/console/Agent.h \ +# qpid/console/Broker.h \ +# qpid/console/ClassKey.h \ +# qpid/console/ConsoleImportExport.h \ +# qpid/console/ConsoleListener.h \ +# qpid/console/Event.h \ +# qpid/console/Object.h \ +# qpid/console/ObjectId.h \ +# qpid/console/Package.h \ +# qpid/console/Schema.h \ +# qpid/console/SequenceManager.h \ +# qpid/console/SessionManager.h \ +# qpid/console/Value.h +set (qmfconsole_SOURCES + ../include/qpid/console/Agent.h + ../include/qpid/console/Broker.h + ../include/qpid/console/ClassKey.h + ../include/qpid/console/ConsoleImportExport.h + ../include/qpid/console/ConsoleListener.h + ../include/qpid/console/Event.h + ../include/qpid/console/Object.h + ../include/qpid/console/ObjectId.h + ../include/qpid/console/Package.h + ../include/qpid/console/Schema.h + ../include/qpid/console/SequenceManager.h + ../include/qpid/console/SessionManager.h + ../include/qpid/console/Value.h + qpid/console/Agent.cpp + qpid/console/Broker.cpp + qpid/console/ClassKey.cpp + qpid/console/Event.cpp + qpid/console/Object.cpp + qpid/console/ObjectId.cpp + qpid/console/Package.cpp + qpid/console/Schema.cpp + qpid/console/SequenceManager.cpp + qpid/console/SessionManager.cpp + qpid/console/Value.cpp + ) +add_msvc_version (qmfconsole library dll) +add_library (qmfconsole SHARED ${qmfconsole_SOURCES}) +target_link_libraries (qmfconsole qpidclient) +set_target_properties (qmfconsole PROPERTIES + VERSION ${qpidc_version}) +install (TARGETS qmfconsole + DESTINATION ${QPID_INSTALL_LIBDIR} + COMPONENT ${QPID_COMPONENT_QMF}) +install_pdb (qmfconsole ${QPID_COMPONENT_QMF}) + +# A queue event listener plugin that creates messages on a replication +# queue corresponding to enqueue and dequeue events: +set (replicating_listener_SOURCES + qpid/replication/constants.h + qpid/replication/ReplicatingEventListener.cpp + qpid/replication/ReplicatingEventListener.h + ) +add_msvc_version (replicating_listener library dll) +add_library (replicating_listener MODULE ${replicating_listener_SOURCES}) +target_link_libraries (replicating_listener qpidbroker ${Boost_PROGRAM_OPTIONS_LIBRARY}) +set_target_properties (replicating_listener PROPERTIES PREFIX "") +if (CMAKE_COMPILER_IS_GNUCXX) + set_target_properties(replicating_listener PROPERTIES + LINK_FLAGS -Wl,--no-undefined) +endif (CMAKE_COMPILER_IS_GNUCXX) +install (TARGETS replicating_listener + DESTINATION ${QPIDD_MODULE_DIR} + COMPONENT ${QPID_COMPONENT_BROKER}) + +# A custom exchange plugin that allows an exchange to be created that +# can process the messages from a replication queue (populated on the +# source system by the replicating listener plugin above) and take the +# corresponding action on the local queues +set (replication_exchange_SOURCES + qpid/replication/constants.h + qpid/replication/ReplicationExchange.cpp + qpid/replication/ReplicationExchange.h + ) +add_msvc_version (replication_exchange library dll) +add_library (replication_exchange MODULE ${replication_exchange_SOURCES}) +target_link_libraries (replication_exchange qpidbroker) +set_target_properties (replication_exchange PROPERTIES PREFIX "") +if (CMAKE_COMPILER_IS_GNUCXX) + set_target_properties(replication_exchange PROPERTIES + LINK_FLAGS -Wl,--no-undefined) +endif (CMAKE_COMPILER_IS_GNUCXX) +install (TARGETS replication_exchange + DESTINATION ${QPIDD_MODULE_DIR} + COMPONENT ${QPID_COMPONENT_BROKER}) + +# This is only really needed until all the trunk builds (Linux, UNIX, Windows) +# are all on cmake only. This is because cmake builds always have a config.h +# file whereas older builds only have config.h on autoconf-generated builds. +add_definitions(-DHAVE_CONFIG_H) + +add_definitions(-DBOOST_FILESYSTEM_VERSION=2) + +# Now create the config file from all the info learned above. +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake + ${CMAKE_CURRENT_BINARY_DIR}/config.h) +add_subdirectory(qpid/store) +add_subdirectory(tests) diff --git a/qpid/cpp/src/CMakeWinVersions.cmake b/qpid/cpp/src/CMakeWinVersions.cmake new file mode 100644 index 0000000000..0bac7cab47 --- /dev/null +++ b/qpid/cpp/src/CMakeWinVersions.cmake @@ -0,0 +1,57 @@ +#
+# 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.
+#
+
+#
+# Versions settings overrides for Windows dll/exe file version resource.
+# These values are compiled into the dll and exe files.
+#
+# The settings override precedence from lowest to highest:
+# 1. CPACK settings from cpp/CMakeLists.txt
+# 2. Global settings from this file
+# 3. Command line version number (only) from add_msvc_version_full call
+# 4. Per-project settings from this file
+#
+
+#
+# Specification of global settings for all projects.
+#
+# set ("winver_PACKAGE_NAME" "qpid-cpp")
+# set ("winver_DESCRIPTION_SUMMARY" "Apache Qpid C++")
+# set ("winver_FILE_VERSION_N1" "0")
+# 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" "11")
+# set ("winver_PRODUCT_VERSION_N3" "0")
+# set ("winver_PRODUCT_VERSION_N4" "0")
+# set ("winver_LEGAL_COPYRIGHT" "")
+
+#
+# Specification of per-project settings:
+#
+# 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")
+# set ("winver_${projectName}_OriginalFilename" "qpidcommon.dll")
+# set ("winver_${projectName}_ProductName" "Apache Qpid C++")
diff --git a/qpid/cpp/src/Makefile.am b/qpid/cpp/src/Makefile.am new file mode 100644 index 0000000000..15021cc68b --- /dev/null +++ b/qpid/cpp/src/Makefile.am @@ -0,0 +1,896 @@ +# +# 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. +# + +SUBDIRS = . tests + +# The Windows-only sources are not compiled using this Makefile, but +# are listed here to ensure they're included in releases. They are built +# using Visual Studio solutions/projects. +windows_dist = \ + qpid/client/windows/SaslFactory.cpp \ + qpid/client/windows/SslConnector.cpp \ + qpid/log/windows/SinkOptions.cpp \ + qpid/log/windows/SinkOptions.h \ + ../include/qpid/sys/windows/check.h \ + qpid/sys/windows/AsynchIO.cpp \ + qpid/sys/windows/AsynchIoResult.h \ + ../include/qpid/sys/windows/Condition.h \ + qpid/sys/windows/FileSysDir.cpp \ + ../include/qpid/sys/windows/IntegerTypes.h \ + qpid/sys/windows/IocpPoller.cpp \ + qpid/sys/windows/IOHandle.cpp \ + qpid/sys/windows/IoHandlePrivate.h \ + qpid/sys/windows/LockFile.cpp \ + qpid/sys/windows/PollableCondition.cpp \ + qpid/sys/windows/PipeHandle.cpp \ + ../include/qpid/sys/windows/Mutex.h \ + qpid/sys/windows/Shlib.cpp \ + qpid/sys/windows/SocketAddress.cpp \ + qpid/sys/windows/Socket.cpp \ + qpid/sys/windows/SslAsynchIO.cpp \ + qpid/sys/windows/SslAsynchIO.h \ + qpid/sys/windows/StrError.cpp \ + qpid/sys/windows/SystemInfo.cpp \ + qpid/sys/windows/Thread.cpp \ + qpid/sys/windows/Time.cpp \ + ../include/qpid/sys/windows/Time.h \ + qpid/sys/windows/uuid.cpp \ + qpid/sys/windows/uuid.h \ + windows/QpiddBroker.cpp \ + qpid/broker/windows/BrokerDefaults.cpp \ + qpid/broker/windows/SaslAuthenticator.cpp \ + qpid/broker/windows/SslProtocolFactory.cpp \ + qpid/messaging/HandleInstantiator.cpp \ + windows/resources/template-resource.rc \ + windows/resources/version-resource.h \ + windows/resources/qpid-icon.ico + +EXTRA_DIST= $(platform_dist) $(rgen_srcs) $(windows_dist) + +# Define variables that are be appended to by this file and included .mk files. +nobase_include_HEADERS = +libqpidcommon_la_SOURCES = + +## Generated code + +# Note: generated soure and makefiles included in distribution so a +# distribution can be built without code generation tools and XML +# sources. + +# This phony target is needed by generated makefile fragments: +force: + +if GENERATE + +# AMQP_FINAL_XML is defined in ../configure.ac +amqp_0_10_xml=@AMQP_FINAL_XML@ +specs=$(amqp_0_10_xml) $(top_srcdir)/xml/cluster.xml + +# Ruby generator. +rgen_dir=$(top_srcdir)/rubygen +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_generator): + +# The CMake version is needed for dist +$(srcdir)/rubygen.cmake: $(rgen_generator) $(specs) + $(rgen_cmd) $(srcdir)/rubygen.cmake + +# Management generator. +mgen_dir=$(top_srcdir)/managementgen +mgen_cmd=$(mgen_dir)/qmf-gen -m $(srcdir)/managementgen.mk \ + -c $(srcdir)/managementgen.cmake -q -b -o qmf \ + $(top_srcdir)/../specs/management-schema.xml \ + $(srcdir)/qpid/acl/management-schema.xml \ + $(srcdir)/qpid/cluster/management-schema.xml + +$(srcdir)/managementgen.mk $(mgen_broker_cpp) $(dist_qpid_management_HEADERS): mgen.timestamp +mgen.timestamp: $(mgen_generator) + $(mgen_cmd); touch $@ +$(mgen_generator): + +endif # GENERATE + +include $(srcdir)/rubygen.mk +include $(srcdir)/managementgen.mk + +## Compiler flags +AM_CXXFLAGS = $(WARNING_CFLAGS) +INCLUDES = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(srcdir) -I=$(builddir) + +# +# Destination for intalled programs and tests defined here +# +qpidexecdir = $(libexecdir)/qpid +AM_CXXFLAGS += -DQPID_LIBEXEC_DIR=\"$(qpidexecdir)\" +qpidexec_PROGRAMS = +qpidexec_SCRIPTS = +qpidtestdir = $(qpidexecdir)/tests +qpidtest_PROGRAMS = +qpidtest_SCRIPTS = +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=\"$(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 \ + libqpidcommon.la + +posix_qpidd_src = posix/QpiddBroker.cpp + +sbin_PROGRAMS = qpidd +qpidd_SOURCES = qpidd.cpp qpidd.h $(posix_qpidd_src) + +## Platform specific code. + +# Posix-specific code +libqpidcommon_la_SOURCES += \ + qpid/log/posix/SinkOptions.cpp \ + qpid/sys/posix/IOHandle.cpp \ + qpid/sys/posix/Socket.cpp \ + qpid/sys/posix/SocketAddress.cpp \ + qpid/sys/posix/AsynchIO.cpp \ + qpid/sys/posix/FileSysDir.cpp \ + qpid/sys/posix/LockFile.cpp \ + qpid/sys/posix/Time.cpp \ + qpid/sys/posix/Thread.cpp \ + qpid/sys/posix/Shlib.cpp \ + qpid/sys/posix/Mutex.cpp \ + qpid/sys/posix/Fork.cpp \ + qpid/sys/posix/StrError.cpp \ + qpid/sys/posix/PollableCondition.cpp \ + qpid/sys/posix/PidFile.h \ + qpid/sys/posix/PipeHandle.cpp \ + qpid/log/posix/SinkOptions.h \ + qpid/sys/posix/Fork.h + +nobase_include_HEADERS += \ + ../include/qpid/sys/posix/Condition.h \ + ../include/qpid/sys/posix/IntegerTypes.h \ + ../include/qpid/sys/posix/Mutex.h \ + ../include/qpid/sys/posix/PrivatePosix.h \ + ../include/qpid/sys/posix/Time.h \ + ../include/qpid/sys/posix/check.h + +if HAVE_EPOLL + poller = qpid/sys/epoll/EpollPoller.cpp +endif + +if HAVE_ECF + poller = qpid/sys/solaris/ECFPoller.cpp +endif + +if SUNOS + systeminfo = qpid/sys/solaris/SystemInfo.cpp +else + systeminfo = qpid/sys/posix/SystemInfo.cpp +endif + +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 + +# Definitions for client and daemon plugins +PLUGINLDFLAGS=-no-undefined -module -avoid-version +confdir=$(sysconfdir)/qpid +dmoduleexecdir=$(libdir)/qpid/daemon +cmoduleexecdir=$(libdir)/qpid/client +dmoduleexec_LTLIBRARIES = +cmoduleexec_LTLIBRARIES = + +include cluster.mk +include acl.mk +include qmf.mk +include qmfc.mk +if HAVE_XML +include xml.mk +endif +include replication.mk + +if RDMA + +# RDMA (Infiniband) protocol code +librdmawrap_la_SOURCES = \ + qpid/sys/rdma/rdma_exception.h \ + qpid/sys/rdma/rdma_factories.cpp \ + qpid/sys/rdma/rdma_factories.h \ + qpid/sys/rdma/RdmaIO.cpp \ + qpid/sys/rdma/RdmaIO.h \ + qpid/sys/rdma/rdma_wrap.cpp \ + qpid/sys/rdma/rdma_wrap.h +librdmawrap_la_LIBADD = \ + libqpidcommon.la \ + -lrdmacm \ + -libverbs +librdmawrap_la_CXXFLAGS = \ + $(AM_CXXFLAGS) -Wno-missing-field-initializers +lib_LTLIBRARIES += \ + librdmawrap.la +RDMAWRAP_VERSION_INFO = 2:0:0 +librdmawrap_la_LDFLAGS = -version-info $(RDMAWRAP_VERSION_INFO) -no-undefined + +rdma_la_SOURCES = \ + qpid/sys/RdmaIOPlugin.cpp +rdma_la_LIBADD = \ + libqpidbroker.la \ + librdmawrap.la \ + -libverbs +rdma_la_LDFLAGS = $(PLUGINLDFLAGS) +rdma_la_CXXFLAGS = \ + $(AM_CXXFLAGS) -Wno-missing-field-initializers +dmoduleexec_LTLIBRARIES += \ + rdma.la + +rdmaconnector_la_SOURCES = \ + qpid/client/RdmaConnector.cpp +rdmaconnector_la_LIBADD = \ + libqpidclient.la \ + librdmawrap.la \ + -libverbs +rdmaconnector_la_LDFLAGS = $(PLUGINLDFLAGS) +rdmaconnector_la_CXXFLAGS = \ + $(AM_CXXFLAGS) -Wno-missing-field-initializers +cmoduleexec_LTLIBRARIES += \ + rdmaconnector.la + +# RDMA test/sample programs +noinst_PROGRAMS = RdmaServer RdmaClient +RdmaServer_SOURCES = qpid/sys/rdma/RdmaServer.cpp +RdmaServer_LDADD = \ + librdmawrap.la libqpidcommon.la +RdmaClient_SOURCES = qpid/sys/rdma/RdmaClient.cpp +RdmaClient_CXXFLAGS = \ + $(AM_CXXFLAGS) -Wno-missing-field-initializers +RdmaClient_LDADD = \ + librdmawrap.la libqpidcommon.la + +endif + +if SSL +include ssl.mk +endif + +EXTRA_DIST +=\ + CMakeLists.txt \ + cluster.cmake \ + config.h.cmake \ + rdma.cmake \ + ssl.cmake \ + managementgen.cmake \ + rubygen.cmake \ + $(rgen_amqp_0_10_srcs) \ + qpid/amqp_0_10/apply.h \ + qpid/amqp_0_10/built_in_types.h \ + qpid/amqp_0_10/complex_types.cpp \ + qpid/amqp_0_10/Array.h \ + qpid/amqp_0_10/Array.cpp \ + qpid/amqp_0_10/Body.h \ + qpid/amqp_0_10/Command.h \ + qpid/amqp_0_10/CommmandPacker.h \ + qpid/amqp_0_10/Control.h \ + qpid/amqp_0_10/Header.h \ + qpid/amqp_0_10/Header.cpp \ + qpid/amqp_0_10/FrameHeader.h \ + qpid/amqp_0_10/FrameHeader.cpp \ + qpid/amqp_0_10/Holder.h \ + qpid/amqp_0_10/Codec.h \ + qpid/amqp_0_10/Packer.h \ + qpid/amqp_0_10/Decimal.h \ + qpid/amqp_0_10/SerializableString.h \ + qpid/amqp_0_10/Map.h \ + qpid/amqp_0_10/Map.cpp \ + qpid/amqp_0_10/Struct.h \ + qpid/amqp_0_10/Struct32.h \ + qpid/amqp_0_10/Struct32.cpp \ + qpid/amqp_0_10/Unit.h \ + qpid/amqp_0_10/Unit.cpp \ + qpid/amqp_0_10/UnitHandler.h \ + qpid/amqp_0_10/UnknownType.h \ + qpid/amqp_0_10/UnknownType.cpp \ + qpid/amqp_0_10/UnknownStruct.h \ + qpid/amqp_0_10/UnknownStruct.cpp \ + qpid/store + +libqpidcommon_la_LIBADD = \ + libqpidtypes.la \ + -lboost_program_options \ + -lboost_filesystem \ + -luuid \ + $(LIB_DLOPEN) \ + $(LIB_CLOCK_GETTIME) + +libqpidcommon_la_SOURCES += \ + $(rgen_framing_srcs) \ + $(platform_src) \ + qpid/Address.cpp \ + qpid/DataDir.cpp \ + qpid/DataDir.h \ + qpid/DisableExceptionLogging.h \ + qpid/Exception.cpp \ + qpid/Modules.cpp \ + qpid/Modules.h \ + qpid/Options.cpp \ + qpid/Plugin.cpp \ + qpid/Plugin.h \ + qpid/RefCounted.h \ + qpid/RefCountedBuffer.cpp \ + qpid/RefCountedBuffer.h \ + qpid/BufferRef.h \ + qpid/Sasl.h \ + qpid/SaslFactory.cpp \ + qpid/SaslFactory.h \ + qpid/Serializer.h \ + qpid/SessionId.cpp \ + qpid/SessionState.cpp \ + qpid/SessionState.h \ + qpid/SessionState.h \ + qpid/SharedObject.h \ + qpid/StringUtils.cpp \ + qpid/StringUtils.h \ + qpid/Url.cpp \ + qpid/Version.h \ + qpid/amqp_0_10/Exception.h \ + qpid/amqp_0_10/SessionHandler.cpp \ + qpid/amqp_0_10/SessionHandler.h \ + qpid/amqp_0_10/apply.h \ + qpid/assert.cpp qpid/assert.h \ + qpid/assert.h \ + qpid/framing/AMQBody.cpp \ + qpid/framing/AMQBody.h \ + qpid/framing/AMQCommandControlBody.h \ + qpid/framing/AMQContentBody.cpp \ + qpid/framing/AMQContentBody.h \ + qpid/framing/AMQDataBlock.h \ + qpid/framing/AMQFrame.cpp \ + qpid/framing/AMQFrame.h \ + qpid/framing/AMQHeaderBody.cpp \ + qpid/framing/AMQHeaderBody.h \ + qpid/framing/AMQHeartbeatBody.cpp \ + qpid/framing/AMQHeartbeatBody.h \ + qpid/framing/AMQMethodBody.cpp \ + qpid/framing/AMQMethodBody.h \ + qpid/framing/AMQP_HighestVersion.h \ + qpid/framing/AMQP_HighestVersion.h \ + qpid/framing/AccumulatedAck.cpp \ + qpid/framing/AccumulatedAck.h \ + qpid/framing/Array.cpp \ + qpid/framing/BodyFactory.h \ + qpid/framing/BodyHandler.cpp \ + qpid/framing/BodyHandler.h \ + qpid/framing/Buffer.cpp \ + qpid/framing/ResizableBuffer.h \ + qpid/framing/ChannelHandler.h \ + qpid/framing/Endian.cpp \ + qpid/framing/Endian.h \ + qpid/framing/FieldTable.cpp \ + qpid/framing/FieldValue.cpp \ + qpid/framing/FrameDecoder.cpp \ + qpid/framing/FrameDecoder.h \ + qpid/framing/FrameDefaultVisitor.h \ + qpid/framing/FrameHandler.h \ + qpid/framing/FrameSet.cpp \ + qpid/framing/FrameSet.h \ + qpid/framing/Handler.h \ + qpid/framing/HeaderProperties.h \ + qpid/framing/InitiationHandler.h \ + qpid/framing/InputHandler.h \ + qpid/framing/Invoker.h \ + qpid/framing/IsInSequenceSet.h \ + qpid/framing/List.cpp \ + qpid/framing/MethodBodyFactory.h \ + qpid/framing/MethodContent.h \ + qpid/framing/ModelMethod.h \ + qpid/framing/OutputHandler.h \ + qpid/framing/ProtocolInitiation.cpp \ + qpid/framing/ProtocolInitiation.h \ + qpid/framing/ProtocolVersion.cpp \ + qpid/framing/Proxy.cpp \ + qpid/framing/Proxy.h \ + qpid/framing/SendContent.cpp \ + qpid/framing/SendContent.h \ + qpid/framing/SequenceNumber.cpp \ + qpid/framing/SequenceNumberSet.cpp \ + qpid/framing/SequenceNumberSet.h \ + qpid/framing/SequenceSet.cpp \ + qpid/framing/TransferContent.cpp \ + qpid/framing/TransferContent.h \ + qpid/framing/TypeFilter.h \ + qpid/framing/Uuid.cpp \ + qpid/framing/Visitor.h \ + qpid/framing/amqp_framing.h \ + qpid/framing/frame_functors.h \ + qpid/framing/variant.h \ + qpid/log/Helpers.h \ + qpid/log/Logger.cpp \ + qpid/log/Options.cpp \ + qpid/log/OstreamOutput.cpp \ + qpid/log/OstreamOutput.h \ + qpid/log/Selector.cpp \ + qpid/log/Statement.cpp \ + qpid/management/Buffer.cpp \ + qpid/management/ConnectionSettings.cpp \ + qpid/management/Manageable.cpp \ + qpid/management/ManagementObject.cpp \ + qpid/management/Mutex.cpp \ + qpid/memory.h \ + qpid/pointer_to_other.h \ + qpid/ptr_map.h \ + qpid/sys/AggregateOutput.cpp \ + qpid/sys/AggregateOutput.h \ + qpid/sys/AsynchIO.h \ + qpid/sys/AsynchIOHandler.cpp \ + qpid/sys/AsynchIOHandler.h \ + qpid/sys/AtomicCount.h \ + qpid/sys/AtomicValue.h \ + qpid/sys/AtomicValue_gcc.h \ + qpid/sys/AtomicValue_mutex.h \ + qpid/sys/BlockingQueue.h \ + qpid/sys/ClusterSafe.h \ + qpid/sys/ClusterSafe.cpp \ + qpid/sys/Codec.h \ + qpid/sys/ConnectionCodec.h \ + qpid/sys/ConnectionInputHandler.h \ + qpid/sys/ConnectionInputHandlerFactory.h \ + qpid/sys/ConnectionOutputHandler.h \ + qpid/sys/ConnectionOutputHandlerPtr.h \ + qpid/sys/CopyOnWriteArray.h \ + qpid/sys/DeletionManager.h \ + qpid/sys/DispatchHandle.cpp \ + qpid/sys/DispatchHandle.h \ + qpid/sys/Dispatcher.cpp \ + qpid/sys/Dispatcher.h \ + qpid/sys/FileSysDir.h \ + qpid/sys/Fork.h \ + qpid/sys/LockFile.h \ + qpid/sys/LockPtr.h \ + qpid/sys/OutputControl.h \ + qpid/sys/OutputTask.h \ + qpid/sys/PipeHandle.h \ + qpid/sys/PollableCondition.h \ + qpid/sys/PollableQueue.h \ + qpid/sys/Poller.h \ + qpid/sys/ProtocolFactory.h \ + qpid/sys/Runnable.cpp \ + qpid/sys/ScopedIncrement.h \ + qpid/sys/SecurityLayer.h \ + qpid/sys/SecuritySettings.h \ + qpid/sys/Semaphore.h \ + qpid/sys/Shlib.cpp \ + qpid/sys/Shlib.h \ + qpid/sys/ShutdownHandler.h \ + qpid/sys/Socket.h \ + qpid/sys/SocketAddress.h \ + qpid/sys/StateMonitor.h \ + qpid/sys/TimeoutHandler.h \ + qpid/sys/Timer.cpp \ + qpid/sys/Timer.h \ + qpid/sys/TimerWarnings.cpp \ + qpid/sys/TimerWarnings.h \ + qpid/sys/Waitable.h \ + qpid/sys/alloca.h \ + qpid/sys/uuid.h \ + qpid/amqp_0_10/Codecs.cpp + +if HAVE_SASL +libqpidcommon_la_SOURCES += qpid/sys/cyrus/CyrusSecurityLayer.h +libqpidcommon_la_SOURCES += qpid/sys/cyrus/CyrusSecurityLayer.cpp +libqpidcommon_la_LIBADD += -lsasl2 +endif + +QPIDCOMMON_VERSION_INFO = 2:0:0 +libqpidcommon_la_LDFLAGS=-version-info $(QPIDCOMMON_VERSION_INFO) + +libqpidbroker_la_LIBADD = libqpidcommon.la +libqpidbroker_la_SOURCES = \ + $(mgen_broker_cpp) \ + $(posix_broker_src) \ + qpid/amqp_0_10/Connection.cpp \ + qpid/amqp_0_10/Connection.h \ + qpid/broker/AclModule.h \ + qpid/broker/Bridge.cpp \ + qpid/broker/Bridge.h \ + qpid/broker/Broker.cpp \ + qpid/broker/Broker.h \ + qpid/broker/BrokerImportExport.h \ + qpid/broker/Connection.cpp \ + qpid/broker/Connection.h \ + qpid/broker/ConnectionFactory.cpp \ + qpid/broker/ConnectionFactory.h \ + qpid/broker/ConnectionHandler.cpp \ + qpid/broker/ConnectionHandler.h \ + qpid/broker/ConnectionState.h \ + qpid/broker/ConnectionToken.h \ + qpid/broker/Consumer.h \ + qpid/broker/Daemon.cpp \ + qpid/broker/Daemon.h \ + qpid/broker/Deliverable.h \ + qpid/broker/DeliverableMessage.cpp \ + qpid/broker/DeliverableMessage.h \ + qpid/broker/DeliveryAdapter.h \ + qpid/broker/DeliveryId.h \ + qpid/broker/DeliveryRecord.cpp \ + qpid/broker/DeliveryRecord.h \ + qpid/broker/DirectExchange.cpp \ + qpid/broker/DirectExchange.h \ + qpid/broker/DtxAck.cpp \ + qpid/broker/DtxAck.h \ + qpid/broker/DtxBuffer.cpp \ + qpid/broker/DtxBuffer.h \ + qpid/broker/DtxManager.cpp \ + qpid/broker/DtxManager.h \ + qpid/broker/DtxTimeout.cpp \ + qpid/broker/DtxTimeout.h \ + qpid/broker/DtxWorkRecord.cpp \ + qpid/broker/DtxWorkRecord.h \ + qpid/broker/Exchange.cpp \ + qpid/broker/Exchange.h \ + qpid/broker/ExchangeRegistry.cpp \ + qpid/broker/ExchangeRegistry.h \ + qpid/broker/ExpiryPolicy.cpp \ + qpid/broker/ExpiryPolicy.h \ + qpid/broker/Fairshare.h \ + qpid/broker/Fairshare.cpp \ + qpid/broker/FanOutExchange.cpp \ + qpid/broker/FanOutExchange.h \ + qpid/broker/FedOps.h \ + qpid/broker/HandlerImpl.h \ + qpid/broker/HeadersExchange.cpp \ + qpid/broker/HeadersExchange.h \ + qpid/broker/AsyncCompletion.h \ + qpid/broker/LegacyLVQ.h \ + qpid/broker/LegacyLVQ.cpp \ + qpid/broker/Link.cpp \ + qpid/broker/Link.h \ + qpid/broker/LinkRegistry.cpp \ + qpid/broker/LinkRegistry.h \ + qpid/broker/Message.cpp \ + qpid/broker/Message.h \ + qpid/broker/MessageAdapter.cpp \ + qpid/broker/MessageAdapter.h \ + qpid/broker/MessageBuilder.cpp \ + qpid/broker/MessageBuilder.h \ + qpid/broker/MessageDeque.h \ + qpid/broker/MessageDeque.cpp \ + qpid/broker/MessageMap.h \ + qpid/broker/MessageMap.cpp \ + qpid/broker/Messages.h \ + qpid/broker/MessageStore.h \ + qpid/broker/MessageStoreModule.cpp \ + qpid/broker/MessageStoreModule.h \ + qpid/broker/PriorityQueue.h \ + qpid/broker/PriorityQueue.cpp \ + qpid/broker/NameGenerator.cpp \ + qpid/broker/NameGenerator.h \ + qpid/broker/NullMessageStore.cpp \ + qpid/broker/NullMessageStore.h \ + qpid/broker/OwnershipToken.h \ + qpid/broker/Persistable.h \ + qpid/broker/PersistableConfig.h \ + qpid/broker/PersistableExchange.h \ + qpid/broker/PersistableMessage.cpp \ + qpid/broker/PersistableMessage.h \ + qpid/broker/PersistableQueue.h \ + qpid/broker/Queue.cpp \ + qpid/broker/Queue.h \ + qpid/broker/QueueBindings.cpp \ + qpid/broker/QueueBindings.h \ + qpid/broker/QueueCleaner.cpp \ + qpid/broker/QueueCleaner.h \ + qpid/broker/QueueEvents.cpp \ + qpid/broker/QueueEvents.h \ + qpid/broker/QueueListeners.cpp \ + qpid/broker/QueueListeners.h \ + qpid/broker/QueueObserver.h \ + qpid/broker/QueuePolicy.cpp \ + qpid/broker/QueuePolicy.h \ + 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 \ + qpid/broker/RecoverableQueue.h \ + qpid/broker/RecoverableTransaction.h \ + qpid/broker/RecoveredDequeue.cpp \ + qpid/broker/RecoveredDequeue.h \ + qpid/broker/RecoveredEnqueue.cpp \ + qpid/broker/RecoveredEnqueue.h \ + qpid/broker/RecoveryManager.h \ + qpid/broker/RecoveryManagerImpl.cpp \ + qpid/broker/RecoveryManagerImpl.h \ + qpid/broker/RetryList.cpp \ + qpid/broker/RetryList.h \ + qpid/broker/SaslAuthenticator.cpp \ + qpid/broker/SaslAuthenticator.h \ + qpid/broker/SecureConnection.cpp \ + qpid/broker/SecureConnection.h \ + qpid/broker/SecureConnectionFactory.cpp \ + qpid/broker/SecureConnectionFactory.h \ + qpid/broker/SemanticState.cpp \ + qpid/broker/SemanticState.h \ + qpid/broker/SessionAdapter.cpp \ + qpid/broker/SessionAdapter.h \ + qpid/broker/SessionAdapter.h \ + qpid/broker/SessionContext.h \ + qpid/broker/SessionHandler.cpp \ + qpid/broker/SessionHandler.h \ + qpid/broker/SessionManager.cpp \ + qpid/broker/SessionManager.h \ + qpid/broker/SessionManager.h \ + qpid/broker/SessionOutputException.h \ + qpid/broker/SessionState.cpp \ + qpid/broker/SessionState.h \ + qpid/broker/SignalHandler.cpp \ + qpid/broker/SignalHandler.h \ + qpid/broker/System.cpp \ + qpid/broker/System.h \ + qpid/broker/ThresholdAlerts.cpp \ + qpid/broker/ThresholdAlerts.h \ + qpid/broker/TopicExchange.cpp \ + qpid/broker/TopicExchange.h \ + qpid/broker/TransactionalStore.h \ + qpid/broker/TxAccept.cpp \ + qpid/broker/TxAccept.h \ + qpid/broker/TxBuffer.cpp \ + qpid/broker/TxBuffer.h \ + qpid/broker/TxOp.h \ + qpid/broker/TxOpVisitor.h \ + qpid/broker/TxPublish.cpp \ + qpid/broker/TxPublish.h \ + qpid/broker/Vhost.cpp \ + qpid/broker/Vhost.h \ + qpid/management/ManagementAgent.cpp \ + qpid/management/ManagementAgent.h \ + qpid/management/ManagementDirectExchange.cpp \ + qpid/management/ManagementDirectExchange.h \ + qpid/management/ManagementTopicExchange.cpp \ + qpid/management/ManagementTopicExchange.h \ + qpid/sys/TCPIOPlugin.cpp + +QPIDBROKER_VERSION_INFO = 2:0:0 +libqpidbroker_la_LDFLAGS = -version-info $(QPIDBROKER_VERSION_INFO) + +libqpidclient_la_LIBADD = libqpidcommon.la -luuid + +libqpidclient_la_SOURCES = \ + $(rgen_client_srcs) \ + qpid/client/Bounds.cpp \ + qpid/client/Bounds.h \ + qpid/client/ChainableFrameHandler.h \ + qpid/client/Completion.cpp \ + qpid/client/CompletionImpl.h \ + qpid/client/Connection.cpp \ + qpid/client/ConnectionAccess.h \ + qpid/client/ConnectionHandler.cpp \ + qpid/client/ConnectionHandler.h \ + qpid/client/ConnectionImpl.cpp \ + qpid/client/ConnectionImpl.h \ + qpid/client/ConnectionSettings.cpp \ + qpid/client/Connector.cpp \ + qpid/client/Connector.h \ + qpid/client/Demux.cpp \ + qpid/client/Demux.h \ + qpid/client/Dispatcher.cpp \ + qpid/client/Dispatcher.h \ + qpid/client/Execution.h \ + qpid/client/FailoverListener.cpp \ + qpid/client/FailoverManager.cpp \ + qpid/client/Future.cpp \ + qpid/client/FutureCompletion.cpp \ + qpid/client/FutureResult.cpp \ + qpid/client/LoadPlugins.h \ + qpid/client/LoadPlugins.cpp \ + qpid/client/LocalQueue.cpp \ + qpid/client/LocalQueueImpl.cpp \ + qpid/client/LocalQueueImpl.h \ + qpid/client/Message.cpp \ + qpid/client/MessageImpl.cpp \ + qpid/client/MessageImpl.h \ + qpid/client/MessageListener.cpp \ + qpid/client/MessageReplayTracker.cpp \ + qpid/client/PrivateImplRef.h \ + qpid/client/QueueOptions.cpp \ + qpid/client/Results.cpp \ + qpid/client/Results.h \ + qpid/client/SessionBase_0_10.cpp \ + qpid/client/SessionBase_0_10Access.h \ + qpid/client/SessionImpl.cpp \ + qpid/client/SessionImpl.h \ + qpid/client/StateManager.cpp \ + qpid/client/StateManager.h \ + qpid/client/Subscription.cpp \ + qpid/client/SubscriptionImpl.cpp \ + qpid/client/SubscriptionImpl.h \ + qpid/client/SubscriptionManager.cpp \ + qpid/client/SubscriptionManagerImpl.cpp \ + qpid/client/SubscriptionManagerImpl.h \ + qpid/client/TCPConnector.cpp \ + qpid/client/TCPConnector.h + +QPIDCLIENT_VERSION_INFO = 2:0:0 +libqpidclient_la_LDFLAGS = -version-info $(QPIDCLIENT_VERSION_INFO) + +libqpidtypes_la_LIBADD= -luuid +libqpidtypes_la_SOURCES= \ + qpid/types/Exception.cpp \ + qpid/types/Uuid.cpp \ + qpid/types/Variant.cpp \ + ../include/qpid/types/ImportExport.h + +QPIDTYPES_VERSION_INFO = 1:0:0 +libqpidtypes_la_LDFLAGS = -version-info $(QPIDTYPES_VERSION_INFO) + +libqpidmessaging_la_LIBADD = libqpidclient.la libqpidtypes.la +libqpidmessaging_la_SOURCES = \ + qpid/messaging/Address.cpp \ + qpid/messaging/AddressParser.h \ + qpid/messaging/AddressParser.cpp \ + qpid/messaging/Connection.cpp \ + qpid/messaging/Duration.cpp \ + qpid/messaging/exceptions.cpp \ + qpid/messaging/Message.cpp \ + qpid/messaging/MessageImpl.h \ + qpid/messaging/MessageImpl.cpp \ + qpid/messaging/PrivateImplRef.h \ + qpid/messaging/Sender.cpp \ + qpid/messaging/Receiver.cpp \ + qpid/messaging/Session.cpp \ + qpid/messaging/ConnectionImpl.h \ + qpid/messaging/SenderImpl.h \ + qpid/messaging/ReceiverImpl.h \ + qpid/messaging/SessionImpl.h \ + qpid/messaging/FailoverUpdates.cpp \ + qpid/client/amqp0_10/AcceptTracker.h \ + qpid/client/amqp0_10/AcceptTracker.cpp \ + qpid/client/amqp0_10/AddressResolution.h \ + qpid/client/amqp0_10/AddressResolution.cpp \ + qpid/client/amqp0_10/ConnectionImpl.h \ + qpid/client/amqp0_10/ConnectionImpl.cpp \ + qpid/client/amqp0_10/IncomingMessages.h \ + qpid/client/amqp0_10/IncomingMessages.cpp \ + qpid/client/amqp0_10/MessageSink.h \ + qpid/client/amqp0_10/MessageSource.h \ + qpid/client/amqp0_10/OutgoingMessage.h \ + qpid/client/amqp0_10/OutgoingMessage.cpp \ + qpid/client/amqp0_10/ReceiverImpl.h \ + qpid/client/amqp0_10/ReceiverImpl.cpp \ + 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 + +QPIDMESSAGING_VERSION_INFO = 2:0:0 +libqpidmessaging_la_LDFLAGS = -version-info $(QPIDMESSAGING_VERSION_INFO) + +# NOTE: only public header files (which should be in ../include) +# should go in this list. Private headers should go in the SOURCES +# list for one of the libraries or executables that includes it. + +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 \ + ../include/qpid/Options.h \ + ../include/qpid/RangeSet.h \ + ../include/qpid/SessionId.h \ + ../include/qpid/Url.h \ + ../include/qpid/amqp_0_10/Codecs.h \ + ../include/qpid/client/AsyncSession.h \ + ../include/qpid/client/ClientImportExport.h \ + ../include/qpid/client/Completion.h \ + ../include/qpid/client/Connection.h \ + ../include/qpid/client/ConnectionSettings.h \ + ../include/qpid/client/FailoverListener.h \ + ../include/qpid/client/FailoverManager.h \ + ../include/qpid/client/FlowControl.h \ + ../include/qpid/client/Future.h \ + ../include/qpid/client/FutureCompletion.h \ + ../include/qpid/client/FutureResult.h \ + ../include/qpid/client/Handle.h \ + ../include/qpid/client/LocalQueue.h \ + ../include/qpid/client/Message.h \ + ../include/qpid/client/MessageListener.h \ + ../include/qpid/client/MessageReplayTracker.h \ + ../include/qpid/client/QueueOptions.h \ + ../include/qpid/client/Session.h \ + ../include/qpid/client/SessionBase_0_10.h \ + ../include/qpid/client/Subscription.h \ + ../include/qpid/client/SubscriptionManager.h \ + ../include/qpid/client/SubscriptionSettings.h \ + ../include/qpid/client/TypedResult.h \ + ../include/qpid/framing/Array.h \ + ../include/qpid/framing/Buffer.h \ + ../include/qpid/framing/FieldTable.h \ + ../include/qpid/framing/FieldValue.h \ + ../include/qpid/framing/List.h \ + ../include/qpid/framing/ProtocolVersion.h \ + ../include/qpid/framing/SequenceNumber.h \ + ../include/qpid/framing/SequenceSet.h \ + ../include/qpid/framing/StructHelper.h \ + ../include/qpid/framing/Uuid.h \ + ../include/qpid/framing/amqp_types.h \ + ../include/qpid/framing/amqp_types_full.h \ + ../include/qpid/log/Logger.h \ + ../include/qpid/log/Options.h \ + ../include/qpid/log/Selector.h \ + ../include/qpid/log/SinkOptions.h \ + ../include/qpid/log/Statement.h \ + ../include/qpid/management/Args.h \ + ../include/qpid/management/Buffer.h \ + ../include/qpid/management/ConnectionSettings.h \ + ../include/qpid/management/Manageable.h \ + ../include/qpid/management/ManagementEvent.h \ + ../include/qpid/management/ManagementObject.h \ + ../include/qpid/management/Mutex.h \ + ../include/qpid/sys/Condition.h \ + ../include/qpid/sys/ExceptionHolder.h \ + ../include/qpid/sys/IOHandle.h \ + ../include/qpid/sys/IntegerTypes.h \ + ../include/qpid/sys/Monitor.h \ + ../include/qpid/sys/Mutex.h \ + ../include/qpid/sys/Runnable.h \ + ../include/qpid/sys/StrError.h \ + ../include/qpid/sys/SystemInfo.h \ + ../include/qpid/sys/Thread.h \ + ../include/qpid/sys/Time.h \ + ../include/qpid/messaging/Address.h \ + ../include/qpid/messaging/Connection.h \ + ../include/qpid/messaging/Duration.h \ + ../include/qpid/messaging/exceptions.h \ + ../include/qpid/messaging/Handle.h \ + ../include/qpid/messaging/ImportExport.h \ + ../include/qpid/messaging/Message.h \ + ../include/qpid/messaging/Receiver.h \ + ../include/qpid/messaging/Sender.h \ + ../include/qpid/messaging/Session.h \ + ../include/qpid/messaging/FailoverUpdates.h \ + ../include/qpid/types/Exception.h \ + ../include/qpid/types/Uuid.h \ + ../include/qpid/types/Variant.h \ + ../include/qpid/types/ImportExport.h + +# Create the default data directory +install-data-local: + $(mkinstalldirs) $(DESTDIR)/$(localstatedir)/lib/qpidd + +# Support for pkg-config +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = qpid.pc diff --git a/qpid/cpp/src/acl.mk b/qpid/cpp/src/acl.mk new file mode 100644 index 0000000000..b8e2ff0e13 --- /dev/null +++ b/qpid/cpp/src/acl.mk @@ -0,0 +1,41 @@ +# +# 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. +# +# +# acl library makefile fragment, to be included in Makefile.am +# +dmoduleexec_LTLIBRARIES += acl.la + +acl_la_SOURCES = \ + qpid/acl/Acl.cpp \ + qpid/acl/Acl.h \ + qpid/acl/AclData.cpp \ + qpid/acl/AclData.h \ + qpid/acl/AclPlugin.cpp \ + qpid/acl/AclReader.cpp \ + qpid/acl/AclReader.h \ + qpid/acl/AclValidator.cpp \ + qpid/acl/AclValidator.h + +acl_la_LIBADD = libqpidbroker.la +if SUNOS + acl_la_LIBADD += libqmfagent.la libqmfconsole.la libqpidcommon.la -lboost_program_options $(SUNCC_RUNTIME_LIBS) +endif + +acl_la_LDFLAGS = $(PLUGINLDFLAGS) + diff --git a/qpid/cpp/src/cluster.cmake b/qpid/cpp/src/cluster.cmake new file mode 100644 index 0000000000..a389f8f13f --- /dev/null +++ b/qpid/cpp/src/cluster.cmake @@ -0,0 +1,168 @@ +# +# 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. +# +# +# Cluster library CMake fragment, to be included in CMakeLists.txt +# + +# Optional cluster support. Requires CPG; if building it, can optionally +# include CMAN support as well. + +include(CheckIncludeFiles) +include(CheckLibraryExists) + +set(LIBCPG_PATH /usr/lib/openais /usr/lib64/openais /usr/lib/corosync /usr/lib64/corosync CACHE STRING "Default locations for libcpg (cluster library)" ) +find_library(LIBCPG cpg ${LIBCPG_PATH}) +if (LIBCPG) + CHECK_LIBRARY_EXISTS (${LIBCPG} cpg_local_get "" HAVE_LIBCPG) + CHECK_INCLUDE_FILES (openais/cpg.h HAVE_OPENAIS_CPG_H) + CHECK_INCLUDE_FILES (corosync/cpg.h HAVE_COROSYNC_CPG_H) +endif (LIBCPG) + +set (cluster_default ${cluster_force}) +if (CMAKE_SYSTEM_NAME STREQUAL Windows) +else (CMAKE_SYSTEM_NAME STREQUAL Windows) + if (HAVE_LIBCPG) + if (HAVE_OPENAIS_CPG_H OR HAVE_COROSYNC_CPG_H) + set (cluster_default ON) + endif (HAVE_OPENAIS_CPG_H OR HAVE_COROSYNC_CPG_H) + endif (HAVE_LIBCPG) +endif (CMAKE_SYSTEM_NAME STREQUAL Windows) + +option(BUILD_CLUSTER "Build with CPG support for clustering" ${cluster_default}) +if (BUILD_CLUSTER) + + if (NOT HAVE_LIBCPG) + message(FATAL_ERROR "libcpg not found, install openais-devel or corosync-devel") + endif (NOT HAVE_LIBCPG) + if (NOT HAVE_OPENAIS_CPG_H AND NOT HAVE_COROSYNC_CPG_H) + message(FATAL_ERROR "cpg.h not found, install openais-devel or corosync-devel") + endif (NOT HAVE_OPENAIS_CPG_H AND NOT HAVE_COROSYNC_CPG_H) + + CHECK_LIBRARY_EXISTS (cman cman_is_quorate "" HAVE_LIBCMAN) + CHECK_INCLUDE_FILES (libcman.h HAVE_LIBCMAN_H) + + set(cluster_quorum_default ${cluster_quorum_force}) + if (HAVE_LIBCMAN AND HAVE_LIBCMAN_H) + set(cluster_quorum_default ON) + endif (HAVE_LIBCMAN AND HAVE_LIBCMAN_H) + + option(BUILD_CLUSTER_QUORUM "Include libcman quorum service integration" ${cluster_quorum_default}) + if (BUILD_CLUSTER_QUORUM) + if (NOT HAVE_LIBCMAN) + message(FATAL_ERROR "libcman not found, install cman-devel or cmanlib-devel") + endif (NOT HAVE_LIBCMAN) + if (NOT HAVE_LIBCMAN_H) + message(FATAL_ERROR "libcman.h not found, install cman-devel or cmanlib-devel") + endif (NOT HAVE_LIBCMAN_H) + + set (CMAN_SOURCES qpid/cluster/Quorum_cman.h qpid/cluster/Quorum_cman.cpp) + set (CMAN_LIB cman) + else (BUILD_CLUSTER_QUORUM) + set (CMAN_SOURCES qpid/cluster/Quorum_null.h) + endif (BUILD_CLUSTER_QUORUM) + + set (cluster_SOURCES + ${CMAN_SOURCES} + qpid/cluster/Cluster.cpp + qpid/cluster/Cluster.h + qpid/cluster/ClusterTimer.cpp + qpid/cluster/ClusterTimer.h + qpid/cluster/Decoder.cpp + qpid/cluster/Decoder.h + qpid/cluster/PollableQueue.h + qpid/cluster/ClusterMap.cpp + qpid/cluster/ClusterMap.h + qpid/cluster/ClusterPlugin.cpp + qpid/cluster/ClusterSettings.h + qpid/cluster/Connection.cpp + qpid/cluster/Connection.h + qpid/cluster/ConnectionCodec.cpp + qpid/cluster/ConnectionCodec.h + qpid/cluster/Cpg.cpp + qpid/cluster/Cpg.h + qpid/cluster/Dispatchable.h + qpid/cluster/UpdateClient.cpp + qpid/cluster/UpdateClient.h + qpid/cluster/RetractClient.cpp + qpid/cluster/RetractClient.h + qpid/cluster/ErrorCheck.cpp + qpid/cluster/ErrorCheck.h + qpid/cluster/Event.cpp + qpid/cluster/Event.h + qpid/cluster/EventFrame.h + qpid/cluster/EventFrame.cpp + qpid/cluster/ExpiryPolicy.h + qpid/cluster/ExpiryPolicy.cpp + qpid/cluster/FailoverExchange.cpp + qpid/cluster/FailoverExchange.h + qpid/cluster/UpdateExchange.cpp + qpid/cluster/UpdateExchange.h + qpid/cluster/UpdateReceiver.h + qpid/cluster/LockedConnectionMap.h + qpid/cluster/Multicaster.cpp + qpid/cluster/Multicaster.h + qpid/cluster/McastFrameHandler.h + qpid/cluster/NoOpConnectionOutputHandler.h + qpid/cluster/Numbering.h + qpid/cluster/OutputInterceptor.cpp + qpid/cluster/OutputInterceptor.h + qpid/cluster/PollerDispatch.cpp + qpid/cluster/PollerDispatch.h + qpid/cluster/ProxyInputHandler.h + qpid/cluster/Quorum.h + qpid/cluster/InitialStatusMap.h + qpid/cluster/InitialStatusMap.cpp + qpid/cluster/MemberSet.h + qpid/cluster/MemberSet.cpp + qpid/cluster/types.h + qpid/cluster/SecureConnectionFactory.h + qpid/cluster/SecureConnectionFactory.cpp + qpid/cluster/StoreStatus.h + qpid/cluster/StoreStatus.cpp + qpid/cluster/UpdateDataExchange.h + qpid/cluster/UpdateDataExchange.cpp + ) + + add_library (cluster MODULE ${cluster_SOURCES}) + target_link_libraries (cluster ${LIBCPG} ${CMAN_LIB} qpidbroker qpidclient ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY}) + set_target_properties (cluster PROPERTIES PREFIX "") + + # Create a second shared library for linking with test executables, + # cmake will not allow a module to be linked with an executable. + add_library (cluster_shared SHARED ${cluster_SOURCES}) + target_link_libraries (cluster_shared ${LIBCPG} ${CMAN_LIB} qpidbroker qpidclient ${Boost_FILESYSTEM_LIBRARY}) + + if (CMAKE_COMPILER_IS_GNUCXX) + set_target_properties(cluster PROPERTIES + LINK_FLAGS "-Wl,--no-undefined -pthread") + endif (CMAKE_COMPILER_IS_GNUCXX) + + install (TARGETS cluster + DESTINATION ${QPIDD_MODULE_DIR} + COMPONENT ${QPID_COMPONENT_BROKER}) + + add_library (watchdog MODULE qpid/cluster/WatchDogPlugin.cpp) + set_target_properties (watchdog PROPERTIES PREFIX "") + + add_executable(qpidd_watchdog qpid/cluster/qpidd_watchdog.cpp) + +endif (BUILD_CLUSTER) + +# Distribute all sources. +#EXTRA_DIST += qpid/cluster/Quorum_cman.h qpid/cluster/Quorum_cman.cpp qpid/cluster/Quorum_null.h diff --git a/qpid/cpp/src/cluster.mk b/qpid/cpp/src/cluster.mk new file mode 100644 index 0000000000..3ce4ce25b3 --- /dev/null +++ b/qpid/cpp/src/cluster.mk @@ -0,0 +1,113 @@ +# +# 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. +# +# +# Cluster library makefile fragment, to be included in Makefile.am +# + +# Optional CMAN support + +# Distribute all sources. +EXTRA_DIST += qpid/cluster/Quorum_cman.h qpid/cluster/Quorum_cman.cpp qpid/cluster/Quorum_null.h + +if HAVE_LIBCMAN +CMAN_SOURCES = qpid/cluster/Quorum_cman.h qpid/cluster/Quorum_cman.cpp +libcman = -lcman +else +CMAN_SOURCES = qpid/cluster/Quorum_null.h +endif + +if HAVE_LIBCPG + +dmoduleexec_LTLIBRARIES += cluster.la + +cluster_la_SOURCES = \ + $(CMAN_SOURCES) \ + qpid/cluster/Cluster.cpp \ + qpid/cluster/Cluster.h \ + qpid/cluster/ClusterTimer.cpp \ + qpid/cluster/ClusterTimer.h \ + qpid/cluster/Decoder.cpp \ + qpid/cluster/Decoder.h \ + qpid/cluster/PollableQueue.h \ + qpid/cluster/ClusterMap.cpp \ + qpid/cluster/ClusterMap.h \ + qpid/cluster/ClusterPlugin.cpp \ + qpid/cluster/ClusterSettings.h \ + qpid/cluster/Connection.cpp \ + qpid/cluster/Connection.h \ + qpid/cluster/ConnectionCodec.cpp \ + qpid/cluster/ConnectionCodec.h \ + qpid/cluster/Cpg.cpp \ + qpid/cluster/Cpg.h \ + qpid/cluster/Dispatchable.h \ + qpid/cluster/UpdateClient.cpp \ + qpid/cluster/UpdateClient.h \ + qpid/cluster/RetractClient.cpp \ + qpid/cluster/RetractClient.h \ + qpid/cluster/ErrorCheck.cpp \ + qpid/cluster/ErrorCheck.h \ + qpid/cluster/Event.cpp \ + qpid/cluster/Event.h \ + qpid/cluster/EventFrame.h \ + qpid/cluster/EventFrame.cpp \ + qpid/cluster/ExpiryPolicy.h \ + qpid/cluster/ExpiryPolicy.cpp \ + qpid/cluster/FailoverExchange.cpp \ + qpid/cluster/FailoverExchange.h \ + qpid/cluster/UpdateExchange.h \ + qpid/cluster/UpdateExchange.cpp \ + qpid/cluster/UpdateReceiver.h \ + qpid/cluster/LockedConnectionMap.h \ + qpid/cluster/Multicaster.cpp \ + qpid/cluster/Multicaster.h \ + qpid/cluster/McastFrameHandler.h \ + qpid/cluster/NoOpConnectionOutputHandler.h \ + qpid/cluster/Numbering.h \ + qpid/cluster/OutputInterceptor.cpp \ + qpid/cluster/OutputInterceptor.h \ + qpid/cluster/PollerDispatch.cpp \ + qpid/cluster/PollerDispatch.h \ + qpid/cluster/ProxyInputHandler.h \ + qpid/cluster/Quorum.h \ + qpid/cluster/InitialStatusMap.h \ + qpid/cluster/InitialStatusMap.cpp \ + qpid/cluster/MemberSet.h \ + qpid/cluster/MemberSet.cpp \ + qpid/cluster/types.h \ + qpid/cluster/SecureConnectionFactory.h \ + qpid/cluster/SecureConnectionFactory.cpp \ + qpid/cluster/StoreStatus.h \ + qpid/cluster/StoreStatus.cpp \ + qpid/cluster/UpdateDataExchange.h \ + qpid/cluster/UpdateDataExchange.cpp + +cluster_la_LIBADD= -lcpg $(libcman) libqpidbroker.la libqpidclient.la +cluster_la_CXXFLAGS = $(AM_CXXFLAGS) -fno-strict-aliasing +cluster_la_LDFLAGS = $(PLUGINLDFLAGS) + +# The watchdog plugin and helper executable +dmoduleexec_LTLIBRARIES += watchdog.la +watchdog_la_SOURCES = qpid/cluster/WatchDogPlugin.cpp +watchdog_la_LIBADD = libqpidbroker.la +watchdog_la_LDFLAGS = $(PLUGINLDFLAGS) + +qpidexec_PROGRAMS += qpidd_watchdog +qpidd_watchdog_SOURCES = qpid/cluster/qpidd_watchdog.cpp + +endif # HAVE_LIBCPG diff --git a/qpid/cpp/src/config.h.cmake b/qpid/cpp/src/config.h.cmake new file mode 100644 index 0000000000..2bb84c6e47 --- /dev/null +++ b/qpid/cpp/src/config.h.cmake @@ -0,0 +1,66 @@ +/* + * + * 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. + * + */ + +/* + * This file is automatically generated and will be overwritten by the + * next CMake invocation. + */ + +#ifndef QPID_CONFIG_H +#define QPID_CONFIG_H + +// PACKAGE_NAME and PACKAGE_VERSION are carry-overs from the autoconf world. +// They tend to cause confusion and problems when mixing headers from multiple +// autoconf-configured packages, so it's best to remove these in favor of +// Qpid-specific names as soon as the autoconf stuff is removed. +#define PACKAGE_NAME "${CMAKE_PROJECT_NAME}" +#define PACKAGE_VERSION "${qpidc_version}" + +#cmakedefine QPIDC_CONF_FILE "${QPIDC_CONF_FILE}" +#cmakedefine QPIDD_CONF_FILE "${QPIDD_CONF_FILE}" + +#cmakedefine QPIDC_MODULE_DIR "${QPIDC_MODULE_DIR}" +#cmakedefine QPIDD_MODULE_DIR "${QPIDD_MODULE_DIR}" + +#cmakedefine QPID_LIBEXEC_DIR "${QPID_LIBEXEC_DIR}" + +#define QPID_SHLIB_PREFIX "${CMAKE_SHARED_LIBRARY_PREFIX}" +#define QPID_MODULE_PREFIX +#cmakedefine QPID_DEBUG_POSTFIX "${QPID_DEBUG_POSTFIX}" +#if defined(QPID_DEBUG_POSTFIX) && defined (_DEBUG) +# define QPID_SHLIB_POSTFIX QPID_DEBUG_POSTFIX +# define QPID_MODULE_POSTFIX QPID_DEBUG_POSTFIX +#else +# define QPID_SHLIB_POSTFIX +# define QPID_MODULE_POSTFIX +#endif +#define QPID_SHLIB_SUFFIX "${CMAKE_SHARED_LIBRARY_SUFFIX}" +#define QPID_MODULE_SUFFIX "${CMAKE_SHARED_LIBRARY_SUFFIX}" + +#cmakedefine QPID_HAS_CLOCK_GETTIME + +#cmakedefine BROKER_SASL_NAME "${BROKER_SASL_NAME}" +#cmakedefine HAVE_SASL ${HAVE_SASL} + +#cmakedefine HAVE_OPENAIS_CPG_H ${HAVE_OPENAIS_CPG_H} +#cmakedefine HAVE_COROSYNC_CPG_H ${HAVE_COROSYNC_CPG_H} +#cmakedefine HAVE_LIBCMAN_H ${HAVE_LIBCMAN_H} +#cmakedefine HAVE_LOG_AUTHPRIV +#cmakedefine HAVE_LOG_FTP + +#endif /* QPID_CONFIG_H */ diff --git a/qpid/cpp/src/generate.sh b/qpid/cpp/src/generate.sh new file mode 100755 index 0000000000..581a45ff7f --- /dev/null +++ b/qpid/cpp/src/generate.sh @@ -0,0 +1,67 @@ +# !/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. +# + +# Generate code from AMQP specification. +# specs and gentools_dir are set by Makefile +# +set -e + +test -z "$JAVA" && JAVA=java ; +test -z "$JAVAC" && JAVAC=javac ; + +srcdir=`dirname $0` +checkspecs() { + for s in $specs; do test -f $s || return 1; done + return 0 +} + +# Can we generate code? +if { test -d $gentools_dir && checkspecs && + which $JAVA && which $JAVAC; } > /dev/null; +then + echo "Generating code." + mkdir -p gen/qpid/framing + ( cd $gentools_dir/src && $JAVAC `find -name '*.java' -print` ; ) + $JAVA -cp $gentools_dir/src org.apache.qpid.gentools.Main \ + -c -o gen/qpid/framing -t $gentools_dir/templ.cpp $specs + GENERATED=yes +fi + +# Print a Makefile variable assignment. +make_assign() { + echo -n "$1 = "; shift + prefix=$1; shift + for f in $*; do echo "\\" ; echo -n " $prefix$f "; done + echo +} + +# Generate a Makefile fragment +( + make_assign "generated_cpp" "" `find gen -name '*.cpp' -print` + make_assign "generated_h" "" `find gen -name '*.h' -print` + if test x$GENERATED = xyes; then + make_assign "generator" "" $specs \ + `find ../gentools \( -name '*.java' -o -name '*.tmpl' \) -print` + fi +) > generate.mk-t +mv generate.mk-t $srcdir/generate.mk + + + diff --git a/qpid/cpp/src/posix/QpiddBroker.cpp b/qpid/cpp/src/posix/QpiddBroker.cpp new file mode 100644 index 0000000000..879935462e --- /dev/null +++ b/qpid/cpp/src/posix/QpiddBroker.cpp @@ -0,0 +1,190 @@ +/* + * 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 "config.h" +#include "qpidd.h" +#include "qpid/Exception.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/Daemon.h" +#include "qpid/broker/SignalHandler.h" +#include "qpid/log/Logger.h" + +#include <iostream> +#include <signal.h> +#include <unistd.h> +#include <sys/utsname.h> + +using namespace std; +using namespace qpid; +using qpid::broker::Broker; +using qpid::broker::Daemon; + +BootstrapOptions::BootstrapOptions(const char* argv0) + : qpid::Options("Options"), + common("", QPIDD_CONF_FILE), + module(QPIDD_MODULE_DIR), + log(argv0) +{ + add(common); + add(module); + add(log); +} + +namespace { +const std::string TCP = "tcp"; +} + +struct DaemonOptions : public qpid::Options { + bool daemon; + bool quit; + bool check; + int wait; + std::string piddir; + std::string transport; + + DaemonOptions() : qpid::Options("Daemon options"), daemon(false), quit(false), check(false), wait(600), transport(TCP) + { + char *home = ::getenv("HOME"); + + if (home == 0) + piddir += "/tmp"; + else + piddir += home; + piddir += "/.qpidd"; + + addOptions() + ("daemon,d", optValue(daemon), "Run as a daemon. Logs to syslog by default in this mode.") + ("transport", optValue(transport, "TRANSPORT"), "The transport for which to return the port") + ("pid-dir", optValue(piddir, "DIR"), "Directory where port-specific PID file is stored") + ("wait,w", optValue(wait, "SECONDS"), "Sets the maximum wait time to initialize the daemon. If the daemon fails to initialize, prints an error and returns 1") + ("check,c", optValue(check), "Prints the daemon's process ID to stdout and returns 0 if the daemon is running, otherwise returns 1") + ("quit,q", optValue(quit), "Tells the daemon to shut down"); + } +}; + +struct QpiddPosixOptions : public QpiddOptionsPrivate { + DaemonOptions daemon; + QpiddOptions *parent; + + QpiddPosixOptions(QpiddOptions *parent_) : parent(parent_) { + parent->add(daemon); + } +}; + +QpiddOptions::QpiddOptions(const char* argv0) + : qpid::Options("Options"), + common("", QPIDD_CONF_FILE), + module(QPIDD_MODULE_DIR), + log(argv0) +{ + add(common); + add(module); + add(broker); + add(log); + + platform.reset(new QpiddPosixOptions(this)); + qpid::Plugin::addOptions(*this); +} + +void QpiddOptions::usage() const { + cout << "Usage: qpidd [OPTIONS]" << endl << endl << *this << endl; +} + +// Set the broker pointer on the signal handler, then reset at end of scope. +// This is to ensure that the signal handler doesn't keep a broker +// reference after main() has returned. +// +struct ScopedSetBroker { + ScopedSetBroker(const boost::intrusive_ptr<Broker>& broker) { + qpid::broker::SignalHandler::setBroker(broker.get()); + } + ~ScopedSetBroker() { qpid::broker::SignalHandler::setBroker(0); } +}; + +struct QpiddDaemon : public Daemon { + QpiddPosixOptions *options; + + QpiddDaemon(std::string pidDir, QpiddPosixOptions *opts) + : Daemon(pidDir), options(opts) {} + + /** Code for parent process */ + void parent() { + uint16_t port = wait(options->daemon.wait); + if (options->parent->broker.port == 0 || options->daemon.transport != TCP) + cout << port << endl; + } + + /** Code for forked child process */ + void child() { + boost::intrusive_ptr<Broker> brokerPtr(new Broker(options->parent->broker)); + ScopedSetBroker ssb(brokerPtr); + brokerPtr->accept(); + uint16_t port=brokerPtr->getPort(options->daemon.transport); + ready(port); // Notify parent. + brokerPtr->run(); + } +}; + +int QpiddBroker::execute (QpiddOptions *options) { + // Options that affect a running daemon. + QpiddPosixOptions *myOptions = + static_cast<QpiddPosixOptions *>(options->platform.get()); + if (myOptions == 0) + throw Exception("Internal error obtaining platform options"); + + if (myOptions->daemon.check || myOptions->daemon.quit) { + pid_t pid = Daemon::getPid(myOptions->daemon.piddir, + options->broker.port); + if (pid < 0) + return 1; + if (myOptions->daemon.check) + cout << pid << endl; + if (myOptions->daemon.quit) { + if (kill(pid, SIGINT) < 0) + throw Exception("Failed to stop daemon: " + qpid::sys::strError(errno)); + // Wait for the process to die before returning + int retry=10000; // Try up to 10 seconds + while (kill(pid,0) == 0 && --retry) + sys::usleep(1000); + if (retry == 0) + throw Exception("Gave up waiting for daemon process to exit"); + } + return 0; + } + + // Starting the broker. + if (myOptions->daemon.daemon) { + // For daemon mode replace default stderr with syslog. + options->log.sinkOptions->detached(); + qpid::log::Logger::instance().configure(options->log); + // Fork the daemon + QpiddDaemon d(myOptions->daemon.piddir, myOptions); + d.fork(); // Broker is stared in QpiddDaemon::child() + } + else { // Non-daemon broker. + 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; + brokerPtr->run(); + } + return 0; +} diff --git a/qpid/cpp/src/prof b/qpid/cpp/src/prof new file mode 100755 index 0000000000..acfbaff2d4 --- /dev/null +++ b/qpid/cpp/src/prof @@ -0,0 +1,39 @@ +#!/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. +# +# + + +rm /var/lib/oprofile/oprofiled.log + +opcontrol --reset +opcontrol --setup --no-vmlinux --separate=library +opcontrol --start +# -- Do stuff here -- +./qpidd +# -- End of stuff -- +opcontrol --stop +opcontrol --dump +opcontrol --shutdown +opreport -l ./.libs/lt-qpidd > stats.txt +opannotate --source --output-dir=qpidd-prof ./.libs/lt-qpidd + +# clear the relusts +#opcontrol --reset diff --git a/qpid/cpp/src/qmf.mk b/qpid/cpp/src/qmf.mk new file mode 100644 index 0000000000..f3462f1a93 --- /dev/null +++ b/qpid/cpp/src/qmf.mk @@ -0,0 +1,167 @@ +# +# 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. +# + +# +# qmf library makefile fragment, to be included in Makefile.am +# +lib_LTLIBRARIES += \ + libqmf.la \ + libqmfengine.la \ + libqmf2.la + +# +# Public headers for the QMF API +# +QMF_API = \ + ../include/qpid/agent/ManagementAgent.h \ + ../include/qpid/agent/QmfAgentImportExport.h + +# +# Public headers for the QMF2 API +# +QMF2_API = \ + ../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/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 + + +# +# Public headers for the QMF Engine API +# +QMF_ENGINE_API = \ + ../include/qmf/engine/Agent.h \ + ../include/qmf/engine/ConnectionSettings.h \ + ../include/qmf/engine/Console.h \ + ../include/qmf/engine/Event.h \ + ../include/qmf/engine/Message.h \ + ../include/qmf/engine/Object.h \ + ../include/qmf/engine/ObjectId.h \ + ../include/qmf/engine/QmfEngineImportExport.h \ + ../include/qmf/engine/Query.h \ + ../include/qmf/engine/ResilientConnection.h \ + ../include/qmf/engine/Schema.h \ + ../include/qmf/engine/Typecode.h \ + ../include/qmf/engine/Value.h + +# Public header files +nobase_include_HEADERS += \ + $(QMF_API) \ + $(QMF_ENGINE_API) \ + $(QMF2_API) + +libqmf_la_SOURCES = \ + $(QMF_API) \ + qpid/agent/ManagementAgentImpl.cpp \ + qpid/agent/ManagementAgentImpl.h + +libqmf2_la_SOURCES = \ + $(QMF2_API) \ + 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/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 + +libqmfengine_la_SOURCES = \ + $(QMF_ENGINE_API) \ + qmf/engine/Agent.cpp \ + qmf/engine/BrokerProxyImpl.cpp \ + qmf/engine/BrokerProxyImpl.h \ + qmf/engine/ConnectionSettingsImpl.cpp \ + qmf/engine/ConnectionSettingsImpl.h \ + qmf/engine/ConsoleImpl.cpp \ + qmf/engine/ConsoleImpl.h \ + qmf/engine/EventImpl.cpp \ + qmf/engine/EventImpl.h \ + qmf/engine/MessageImpl.cpp \ + qmf/engine/MessageImpl.h \ + qmf/engine/ObjectIdImpl.cpp \ + qmf/engine/ObjectIdImpl.h \ + qmf/engine/ObjectImpl.cpp \ + qmf/engine/ObjectImpl.h \ + qmf/engine/Protocol.cpp \ + qmf/engine/Protocol.h \ + qmf/engine/QueryImpl.cpp \ + qmf/engine/QueryImpl.h \ + qmf/engine/ResilientConnection.cpp \ + qmf/engine/SequenceManager.cpp \ + qmf/engine/SequenceManager.h \ + qmf/engine/SchemaImpl.cpp \ + qmf/engine/SchemaImpl.h \ + qmf/engine/ValueImpl.cpp \ + qmf/engine/ValueImpl.h + +libqmf_la_LIBADD = libqmfengine.la +libqmf2_la_LIBADD = libqpidmessaging.la libqpidtypes.la +libqmfengine_la_LIBADD = libqpidclient.la + +QMF_VERSION_INFO = 1:0:0 +QMF2_VERSION_INFO = 1:0:0 +QMFENGINE_VERSION_INFO = 1:1:0 + +libqmf_la_LDFLAGS = -version-info $(QMF_VERSION_INFO) +libqmf2_la_LDFLAGS = -version-info $(QMF2_VERSION_INFO) +libqmfengine_la_LDFLAGS = -version-info $(QMFENGINE_VERSION_INFO) diff --git a/qpid/cpp/src/qmf/Agent.cpp b/qpid/cpp/src/qmf/Agent.cpp new file mode 100644 index 0000000000..915f2a1c88 --- /dev/null +++ b/qpid/cpp/src/qmf/Agent.cpp @@ -0,0 +1,657 @@ +/* + * + * 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/AgentImpl.h" +#include "qmf/PrivateImplRef.h" +#include "qmf/ConsoleEventImpl.h" +#include "qmf/ConsoleSession.h" +#include "qmf/DataImpl.h" +#include "qmf/Query.h" +#include "qmf/SchemaImpl.h" +#include "qmf/agentCapability.h" +#include "qmf/constants.h" +#include "qpid/messaging/Sender.h" +#include "qpid/messaging/AddressParser.h" +#include "qpid/management/Buffer.h" +#include "qpid/log/Statement.h" +#include <boost/lexical_cast.hpp> + +using qpid::types::Variant; +using qpid::messaging::Duration; +using qpid::messaging::Message; +using qpid::messaging::Sender; +using namespace std; +using namespace qmf; + +typedef PrivateImplRef<Agent> PI; + +Agent::Agent(AgentImpl* impl) { PI::ctor(*this, impl); } +Agent::Agent(const Agent& s) : qmf::Handle<AgentImpl>() { PI::copy(*this, s); } +Agent::~Agent() { PI::dtor(*this); } +Agent& Agent::operator=(const Agent& s) { return PI::assign(*this, s); } +string Agent::getName() const { return isValid() ? impl->getName() : ""; } +uint32_t Agent::getEpoch() const { return isValid() ? impl->getEpoch() : 0; } +string Agent::getVendor() const { return isValid() ? impl->getVendor() : ""; } +string Agent::getProduct() const { return isValid() ? impl->getProduct() : ""; } +string Agent::getInstance() const { return isValid() ? impl->getInstance() : ""; } +const Variant& Agent::getAttribute(const string& k) const { return impl->getAttribute(k); } +const Variant::Map& Agent::getAttributes() const { return impl->getAttributes(); } +ConsoleEvent Agent::querySchema(Duration t) { return impl->querySchema(t); } +uint32_t Agent::querySchemaAsync() { return impl->querySchemaAsync(); } +ConsoleEvent Agent::query(const Query& q, Duration t) { return impl->query(q, t); } +ConsoleEvent Agent::query(const string& q, Duration t) { return impl->query(q, t); } +uint32_t Agent::queryAsync(const Query& q) { return impl->queryAsync(q); } +uint32_t Agent::queryAsync(const string& q) { return impl->queryAsync(q); } +ConsoleEvent Agent::callMethod(const string& m, const Variant::Map& a, const DataAddr& d, Duration t) { return impl->callMethod(m, a, d, t); } +uint32_t Agent::callMethodAsync(const string& m, const Variant::Map& a, const DataAddr& d) { return impl->callMethodAsync(m, a, d); } +uint32_t Agent::getPackageCount() const { return impl->getPackageCount(); } +const string& Agent::getPackage(uint32_t i) const { return impl->getPackage(i); } +uint32_t Agent::getSchemaIdCount(const string& p) const { return impl->getSchemaIdCount(p); } +SchemaId Agent::getSchemaId(const string& p, uint32_t i) const { return impl->getSchemaId(p, i); } +Schema Agent::getSchema(const SchemaId& s, Duration t) { return impl->getSchema(s, t); } + + + +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) +{ +} + +void AgentImpl::setAttribute(const std::string& k, const qpid::types::Variant& v) +{ + attributes[k] = v; + if (k == "qmf.agent_capability") + try { + capability = v.asUint32(); + } catch (std::exception&) {} + if (k == "_direct_subject") + try { + directSubject = v.asString(); + sender = session.topicSender; + } catch (std::exception&) {} +} + +const Variant& AgentImpl::getAttribute(const string& k) const +{ + Variant::Map::const_iterator iter = attributes.find(k); + if (iter == attributes.end()) + throw KeyNotFound(k); + return iter->second; +} + + +ConsoleEvent AgentImpl::query(const Query& query, Duration timeout) +{ + boost::shared_ptr<SyncContext> context(new SyncContext()); + uint32_t correlator; + ConsoleEvent result; + + { + qpid::sys::Mutex::ScopedLock l(lock); + correlator = nextCorrelator++; + contextMap[correlator] = context; + } + try { + sendQuery(query, correlator); + { + uint64_t milliseconds = timeout.getMilliseconds(); + qpid::sys::Mutex::ScopedLock cl(context->lock); + if (!context->response.isValid() || !context->response.isFinal()) + context->cond.wait(context->lock, + qpid::sys::AbsTime(qpid::sys::now(), + qpid::sys::Duration(milliseconds * qpid::sys::TIME_MSEC))); + if (context->response.isValid() && + ((context->response.getType() == CONSOLE_QUERY_RESPONSE && context->response.isFinal()) || + (context->response.getType() == CONSOLE_EXCEPTION))) + result = context->response; + else { + auto_ptr<ConsoleEventImpl> impl(new ConsoleEventImpl(CONSOLE_EXCEPTION)); + Data exception(new DataImpl()); + exception.setProperty("error_text", "Timed out waiting for the agent to respond"); + impl->addData(exception); + result = ConsoleEvent(impl.release()); + } + } + } catch (qpid::types::Exception&) { + } + + { + qpid::sys::Mutex::ScopedLock l(lock); + contextMap.erase(correlator); + } + + return result; +} + + +ConsoleEvent AgentImpl::query(const string& text, Duration timeout) +{ + return query(stringToQuery(text), timeout); +} + + +uint32_t AgentImpl::queryAsync(const Query& query) +{ + uint32_t correlator; + + { + qpid::sys::Mutex::ScopedLock l(lock); + correlator = nextCorrelator++; + } + + sendQuery(query, correlator); + return correlator; +} + + +uint32_t AgentImpl::queryAsync(const string& text) +{ + return queryAsync(stringToQuery(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; + ConsoleEvent result; + + { + qpid::sys::Mutex::ScopedLock l(lock); + correlator = nextCorrelator++; + contextMap[correlator] = context; + } + try { + sendMethod(method, args, addr, correlator); + { + uint64_t milliseconds = timeout.getMilliseconds(); + qpid::sys::Mutex::ScopedLock cl(context->lock); + if (!context->response.isValid()) + context->cond.wait(context->lock, + qpid::sys::AbsTime(qpid::sys::now(), + qpid::sys::Duration(milliseconds * qpid::sys::TIME_MSEC))); + if (context->response.isValid()) + result = context->response; + else { + auto_ptr<ConsoleEventImpl> impl(new ConsoleEventImpl(CONSOLE_EXCEPTION)); + Data exception(new DataImpl()); + exception.setProperty("error_text", "Timed out waiting for the agent to respond"); + impl->addData(exception); + result = ConsoleEvent(impl.release()); + } + } + } catch (qpid::types::Exception&) { + } + + { + qpid::sys::Mutex::ScopedLock l(lock); + contextMap.erase(correlator); + } + + return result; +} + + +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++; + } + + sendMethod(method, args, addr, correlator); + return correlator; +} + + +uint32_t AgentImpl::getPackageCount() const +{ + qpid::sys::Mutex::ScopedLock l(lock); + + // + // Populate the package set. + // + for (set<SchemaId>::const_iterator iter = schemaIdSet.begin(); iter != schemaIdSet.end(); iter++) + packageSet.insert(iter->getPackageName()); + + return packageSet.size(); +} + + +const string& AgentImpl::getPackage(uint32_t idx) const +{ + qpid::sys::Mutex::ScopedLock l(lock); + uint32_t count(0); + for (set<string>::const_iterator iter = packageSet.begin(); iter != packageSet.end(); iter++) { + if (idx == count) + return *iter; + count++; + } + throw IndexOutOfRange(); +} + + +uint32_t AgentImpl::getSchemaIdCount(const string& pname) const +{ + qpid::sys::Mutex::ScopedLock l(lock); + uint32_t count(0); + for (set<SchemaId>::const_iterator iter = schemaIdSet.begin(); iter != schemaIdSet.end(); iter++) + if (iter->getPackageName() == pname) + count++; + return count; +} + + +SchemaId AgentImpl::getSchemaId(const string& pname, uint32_t idx) const +{ + qpid::sys::Mutex::ScopedLock l(lock); + uint32_t count(0); + for (set<SchemaId>::const_iterator iter = schemaIdSet.begin(); iter != schemaIdSet.end(); iter++) { + if (iter->getPackageName() == pname) { + if (idx == count) + return *iter; + count++; + } + } + throw IndexOutOfRange(); +} + + +Schema AgentImpl::getSchema(const SchemaId& id, Duration timeout) +{ + if (!schemaCache->haveSchema(id)) + // + // The desired schema is not in the cache. We need to asynchronously query the remote + // agent for the information. The call to schemaCache->getSchema will block waiting for + // the response to be received. + // + sendSchemaRequest(id); + + return schemaCache->getSchema(id, timeout); +} + + +void AgentImpl::handleException(const Variant::Map& content, const Message& msg) +{ + const string& cid(msg.getCorrelationId()); + Variant::Map::const_iterator aIter; + uint32_t correlator; + boost::shared_ptr<SyncContext> context; + + try { correlator = boost::lexical_cast<uint32_t>(cid); } + catch(const boost::bad_lexical_cast&) { correlator = 0; } + + { + qpid::sys::Mutex::ScopedLock l(lock); + map<uint32_t, boost::shared_ptr<SyncContext> >::iterator iter = contextMap.find(correlator); + if (iter != contextMap.end()) + context = iter->second; + } + + if (context.get() != 0) { + // + // This exception is associated with a synchronous request. + // + qpid::sys::Mutex::ScopedLock cl(context->lock); + context->response = ConsoleEvent(new ConsoleEventImpl(CONSOLE_EXCEPTION)); + ConsoleEventImplAccess::get(context->response).addData(new DataImpl(content, this)); + ConsoleEventImplAccess::get(context->response).setAgent(this); + context->cond.notify(); + } else { + // + // This exception is associated with an asynchronous request. + // + auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_EXCEPTION)); + eventImpl->setCorrelator(correlator); + eventImpl->setAgent(this); + eventImpl->addData(new DataImpl(content, this)); + session.enqueueEvent(eventImpl.release()); + } +} + + +void AgentImpl::handleMethodResponse(const Variant::Map& response, const Message& msg) +{ + const string& cid(msg.getCorrelationId()); + Variant::Map::const_iterator aIter; + Variant::Map argMap; + uint32_t correlator; + boost::shared_ptr<SyncContext> context; + + QPID_LOG(trace, "RCVD MethodResponse cid=" << cid << " map=" << response); + + aIter = response.find("_arguments"); + if (aIter != response.end()) + argMap = aIter->second.asMap(); + + try { correlator = boost::lexical_cast<uint32_t>(cid); } + catch(const boost::bad_lexical_cast&) { correlator = 0; } + + { + qpid::sys::Mutex::ScopedLock l(lock); + map<uint32_t, boost::shared_ptr<SyncContext> >::iterator iter = contextMap.find(correlator); + if (iter != contextMap.end()) + context = iter->second; + } + + if (context.get() != 0) { + // + // This response is associated with a synchronous request. + // + qpid::sys::Mutex::ScopedLock cl(context->lock); + context->response = ConsoleEvent(new ConsoleEventImpl(CONSOLE_METHOD_RESPONSE)); + ConsoleEventImplAccess::get(context->response).setArguments(argMap); + ConsoleEventImplAccess::get(context->response).setAgent(this); + context->cond.notify(); + } else { + // + // This response is associated with an asynchronous request. + // + auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_METHOD_RESPONSE)); + eventImpl->setCorrelator(correlator); + eventImpl->setAgent(this); + eventImpl->setArguments(argMap); + session.enqueueEvent(eventImpl.release()); + } +} + + +void AgentImpl::handleDataIndication(const Variant::List& list, const Message& msg) +{ + Variant::Map::const_iterator aIter; + const Variant::Map& props(msg.getProperties()); + boost::shared_ptr<SyncContext> context; + + aIter = props.find("qmf.content"); + if (aIter == props.end()) + return; + + string content_type(aIter->second.asString()); + if (content_type != "_event") + return; + + for (Variant::List::const_iterator lIter = list.begin(); lIter != list.end(); lIter++) { + const Variant::Map& eventMap(lIter->asMap()); + Data data(new DataImpl(eventMap, this)); + int severity(SEV_NOTICE); + uint64_t timestamp(0); + + aIter = eventMap.find("_severity"); + if (aIter != eventMap.end()) + severity = int(aIter->second.asInt8()); + + aIter = eventMap.find("_timestamp"); + if (aIter != eventMap.end()) + timestamp = aIter->second.asUint64(); + + auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_EVENT)); + eventImpl->setAgent(this); + eventImpl->addData(data); + eventImpl->setSeverity(severity); + eventImpl->setTimestamp(timestamp); + if (data.hasSchema()) + learnSchemaId(data.getSchemaId()); + session.enqueueEvent(eventImpl.release()); + } +} + + +void AgentImpl::handleQueryResponse(const Variant::List& list, const Message& msg) +{ + const string& cid(msg.getCorrelationId()); + Variant::Map::const_iterator aIter; + const Variant::Map& props(msg.getProperties()); + uint32_t correlator; + bool final(false); + boost::shared_ptr<SyncContext> context; + + aIter = props.find("partial"); + if (aIter == props.end()) + final = true; + + aIter = props.find("qmf.content"); + if (aIter == props.end()) + return; + + string content_type(aIter->second.asString()); + if (content_type != "_schema" && content_type != "_schema_id" && content_type != "_data") + return; + + try { correlator = boost::lexical_cast<uint32_t>(cid); } + catch(const boost::bad_lexical_cast&) { correlator = 0; } + + { + qpid::sys::Mutex::ScopedLock l(lock); + map<uint32_t, boost::shared_ptr<SyncContext> >::iterator iter = contextMap.find(correlator); + if (iter != contextMap.end()) + context = iter->second; + } + + if (context.get() != 0) { + // + // This response is associated with a synchronous request. + // + qpid::sys::Mutex::ScopedLock cl(context->lock); + if (!context->response.isValid()) + context->response = ConsoleEvent(new ConsoleEventImpl(CONSOLE_QUERY_RESPONSE)); + + if (content_type == "_data") + for (Variant::List::const_iterator lIter = list.begin(); lIter != list.end(); lIter++) { + Data data(new DataImpl(lIter->asMap(), this)); + ConsoleEventImplAccess::get(context->response).addData(data); + if (data.hasSchema()) + learnSchemaId(data.getSchemaId()); + } + else if (content_type == "_schema_id") + for (Variant::List::const_iterator lIter = list.begin(); lIter != list.end(); lIter++) { + SchemaId schemaId(new SchemaIdImpl(lIter->asMap())); + ConsoleEventImplAccess::get(context->response).addSchemaId(schemaId); + learnSchemaId(schemaId); + } + else if (content_type == "_schema") + for (Variant::List::const_iterator lIter = list.begin(); lIter != list.end(); lIter++) { + Schema schema(new SchemaImpl(lIter->asMap())); + schemaCache->declareSchema(schema); + } + + if (final) { + ConsoleEventImplAccess::get(context->response).setFinal(); + ConsoleEventImplAccess::get(context->response).setAgent(this); + context->cond.notify(); + } + } else { + // + // This response is associated with an asynchronous request. + // + auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_QUERY_RESPONSE)); + eventImpl->setCorrelator(correlator); + eventImpl->setAgent(this); + + if (content_type == "_data") + for (Variant::List::const_iterator lIter = list.begin(); lIter != list.end(); lIter++) { + Data data(new DataImpl(lIter->asMap(), this)); + eventImpl->addData(data); + if (data.hasSchema()) + learnSchemaId(data.getSchemaId()); + } + else if (content_type == "_schema_id") + for (Variant::List::const_iterator lIter = list.begin(); lIter != list.end(); lIter++) { + SchemaId schemaId(new SchemaIdImpl(lIter->asMap())); + eventImpl->addSchemaId(schemaId); + learnSchemaId(schemaId); + } + else if (content_type == "_schema") + for (Variant::List::const_iterator lIter = list.begin(); lIter != list.end(); lIter++) { + Schema schema(new SchemaImpl(lIter->asMap())); + schemaCache->declareSchema(schema); + } + + if (final) + eventImpl->setFinal(); + if (content_type != "_schema") + session.enqueueEvent(eventImpl.release()); + } +} + + +Query AgentImpl::stringToQuery(const std::string& text) +{ + qpid::messaging::AddressParser parser(text); + Variant::Map map; + Variant::Map::const_iterator iter; + string className; + string packageName; + + parser.parseMap(map); + + iter = map.find("class"); + if (iter != map.end()) + className = iter->second.asString(); + + iter = map.find("package"); + if (iter != map.end()) + packageName = iter->second.asString(); + + Query query(QUERY_OBJECT, className, packageName); + + iter = map.find("where"); + if (iter != map.end()) + query.setPredicate(iter->second.asList()); + + return query; +} + + +void AgentImpl::sendQuery(const Query& query, uint32_t correlator) +{ + Message msg; + Variant::Map map; + Variant::Map& headers(msg.getProperties()); + + headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_REQUEST; + headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_QUERY_REQUEST; + headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF; + + msg.setReplyTo(session.replyAddress); + msg.setCorrelationId(boost::lexical_cast<string>(correlator)); + msg.setSubject(directSubject); + string userId(session.connection.getAuthenticatedUsername()); + if (!userId.empty()) + msg.setUserId(userId); + encode(QueryImplAccess::get(query).asMap(), msg); + if (sender.isValid()) { + sender.send(msg); + QPID_LOG(trace, "SENT QueryRequest to=" << sender.getName() << "/" << directSubject << " cid=" << correlator); + } +} + + +void AgentImpl::sendMethod(const string& method, const Variant::Map& args, const DataAddr& addr, uint32_t correlator) +{ + Message msg; + Variant::Map map; + Variant::Map& headers(msg.getProperties()); + + headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_REQUEST; + headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_METHOD_REQUEST; + headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF; + + map["_method_name"] = method; + map["_object_id"] = addr.asMap(); + map["_arguments"] = args; + + msg.setReplyTo(session.replyAddress); + msg.setCorrelationId(boost::lexical_cast<string>(correlator)); + msg.setSubject(directSubject); + string userId(session.connection.getAuthenticatedUsername()); + if (!userId.empty()) + msg.setUserId(userId); + encode(map, msg); + if (sender.isValid()) { + sender.send(msg); + QPID_LOG(trace, "SENT MethodRequest method=" << method << " to=" << sender.getName() << "/" << directSubject << " content=" << map << " cid=" << correlator); + } +} + +void AgentImpl::sendSchemaRequest(const SchemaId& id) +{ + uint32_t correlator; + + { + qpid::sys::Mutex::ScopedLock l(lock); + correlator = nextCorrelator++; + } + + if (capability >= AGENT_CAPABILITY_V2_SCHEMA) { + Query query(QUERY_SCHEMA, id); + sendQuery(query, correlator); + return; + } + +#define RAW_BUFFER_SIZE 1024 + char rawBuffer[RAW_BUFFER_SIZE]; + qpid::management::Buffer buffer(rawBuffer, RAW_BUFFER_SIZE); + + buffer.putOctet('A'); + buffer.putOctet('M'); + buffer.putOctet('2'); + buffer.putOctet('S'); + buffer.putLong(correlator); + buffer.putShortString(id.getPackageName()); + buffer.putShortString(id.getName()); + buffer.putBin128(id.getHash().data()); + + string content(rawBuffer, buffer.getPosition()); + + Message msg; + msg.setReplyTo(session.replyAddress); + msg.setContent(content); + msg.setSubject(directSubject); + string userId(session.connection.getAuthenticatedUsername()); + if (!userId.empty()) + msg.setUserId(userId); + if (sender.isValid()) { + sender.send(msg); + QPID_LOG(trace, "SENT V1SchemaRequest to=" << sender.getName() << "/" << directSubject); + } +} + + +void AgentImpl::learnSchemaId(const SchemaId& id) +{ + schemaCache->declareSchemaId(id); + schemaIdSet.insert(id); +} + + +AgentImpl& AgentImplAccess::get(Agent& item) +{ + return *item.impl; +} + + +const AgentImpl& AgentImplAccess::get(const Agent& item) +{ + return *item.impl; +} diff --git a/qpid/cpp/src/qmf/AgentEvent.cpp b/qpid/cpp/src/qmf/AgentEvent.cpp new file mode 100644 index 0000000000..2dc24ecac1 --- /dev/null +++ b/qpid/cpp/src/qmf/AgentEvent.cpp @@ -0,0 +1,85 @@ +/* + * + * 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/AgentEventImpl.h" +#include "qmf/PrivateImplRef.h" +#include "qmf/SchemaImpl.h" + +using namespace std; +using namespace qmf; +using qpid::types::Variant; + +typedef PrivateImplRef<AgentEvent> PI; + +AgentEvent::AgentEvent(AgentEventImpl* impl) { PI::ctor(*this, impl); } +AgentEvent::AgentEvent(const AgentEvent& s) : qmf::Handle<AgentEventImpl>() { PI::copy(*this, s); } +AgentEvent::~AgentEvent() { PI::dtor(*this); } +AgentEvent& AgentEvent::operator=(const AgentEvent& s) { return PI::assign(*this, s); } + +AgentEventCode AgentEvent::getType() const { return impl->getType(); } +const string& AgentEvent::getUserId() const { return impl->getUserId(); } +Query AgentEvent::getQuery() const { return impl->getQuery(); } +bool AgentEvent::hasDataAddr() const { return impl->hasDataAddr(); } +DataAddr AgentEvent::getDataAddr() const { return impl->getDataAddr(); } +const string& AgentEvent::getMethodName() const { return impl->getMethodName(); } +qpid::types::Variant::Map& AgentEvent::getArguments() { return impl->getArguments(); } +qpid::types::Variant::Map& AgentEvent::getArgumentSubtypes() { return impl->getArgumentSubtypes(); } +void AgentEvent::addReturnArgument(const std::string& k, const qpid::types::Variant& v, const std::string& s) { impl->addReturnArgument(k, v, s); } + +uint32_t AgentEventImpl::enqueueData(const Data& data) +{ + qpid::sys::Mutex::ScopedLock l(lock); + dataQueue.push(data); + return dataQueue.size(); +} + + +Data AgentEventImpl::dequeueData() +{ + qpid::sys::Mutex::ScopedLock l(lock); + if (dataQueue.empty()) + return Data(); + Data data(dataQueue.front()); + dataQueue.pop(); + return data; +} + + +void AgentEventImpl::addReturnArgument(const string& key, const Variant& val, const string& subtype) +{ + if (schema.isValid() && !SchemaImplAccess::get(schema).isValidMethodOutArg(methodName, key, val)) + throw QmfException("Output argument is unknown or the type is incompatible"); + outArguments[key] = val; + if (!subtype.empty()) + outArgumentSubtypes[key] = subtype; +} + + +AgentEventImpl& AgentEventImplAccess::get(AgentEvent& item) +{ + return *item.impl; +} + + +const AgentEventImpl& AgentEventImplAccess::get(const AgentEvent& item) +{ + return *item.impl; +} diff --git a/qpid/cpp/src/qmf/AgentEventImpl.h b/qpid/cpp/src/qmf/AgentEventImpl.h new file mode 100644 index 0000000000..1ecb41775a --- /dev/null +++ b/qpid/cpp/src/qmf/AgentEventImpl.h @@ -0,0 +1,96 @@ +#ifndef _QMF_AGENT_EVENT_IMPL_H_ +#define _QMF_AGENT_EVENT_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 "qpid/sys/Mutex.h" +#include "qpid/sys/IntegerTypes.h" +#include "qpid/messaging/Address.h" +#include "qmf/AgentEvent.h" +#include "qmf/Query.h" +#include "qmf/DataAddr.h" +#include "qmf/Data.h" +#include "qmf/Schema.h" +#include <queue> + +namespace qmf { + class AgentEventImpl : public virtual qpid::RefCounted { + public: + // + // Impl-only methods + // + AgentEventImpl(AgentEventCode e) : eventType(e) {} + void setUserId(const std::string& u) { userId = u; } + void setQuery(const Query& q) { query = q; } + void setDataAddr(const DataAddr& d) { dataAddr = d; } + void setMethodName(const std::string& m) { methodName = m; } + void setArguments(const qpid::types::Variant::Map& a) { arguments = a; } + void setArgumentSubtypes(const qpid::types::Variant::Map& a) { argumentSubtypes = a; } + void setReplyTo(const qpid::messaging::Address& r) { replyTo = r; } + void setSchema(const Schema& s) { schema = s; } + const qpid::messaging::Address& getReplyTo() { return replyTo; } + void setCorrelationId(const std::string& c) { correlationId = c; } + const std::string& getCorrelationId() { return correlationId; } + const qpid::types::Variant::Map& getReturnArguments() const { return outArguments; } + const qpid::types::Variant::Map& getReturnArgumentSubtypes() const { return outArgumentSubtypes; } + uint32_t enqueueData(const Data&); + Data dequeueData(); + + // + // Methods from API handle + // + AgentEventCode getType() const { return eventType; } + const std::string& getUserId() const { return userId; } + Query getQuery() const { return query; } + bool hasDataAddr() const { return dataAddr.isValid(); } + DataAddr getDataAddr() const { return dataAddr; } + const std::string& getMethodName() const { return methodName; } + qpid::types::Variant::Map& getArguments() { return arguments; } + qpid::types::Variant::Map& getArgumentSubtypes() { return argumentSubtypes; } + void addReturnArgument(const std::string&, const qpid::types::Variant&, const std::string&); + + private: + const AgentEventCode eventType; + std::string userId; + qpid::messaging::Address replyTo; + std::string correlationId; + Query query; + DataAddr dataAddr; + Schema schema; + std::string methodName; + qpid::types::Variant::Map arguments; + qpid::types::Variant::Map argumentSubtypes; + qpid::types::Variant::Map outArguments; + qpid::types::Variant::Map outArgumentSubtypes; + + qpid::sys::Mutex lock; + std::queue<Data> dataQueue; + }; + + struct AgentEventImplAccess + { + static AgentEventImpl& get(AgentEvent&); + static const AgentEventImpl& get(const AgentEvent&); + }; +} + +#endif diff --git a/qpid/cpp/src/qmf/AgentImpl.h b/qpid/cpp/src/qmf/AgentImpl.h new file mode 100644 index 0000000000..7fa4f4373a --- /dev/null +++ b/qpid/cpp/src/qmf/AgentImpl.h @@ -0,0 +1,123 @@ +#ifndef _QMF_AGENT_IMPL_H_ +#define _QMF_AGENT_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/Agent.h" +#include "qmf/ConsoleEventImpl.h" +#include "qmf/ConsoleSessionImpl.h" +#include "qmf/QueryImpl.h" +#include "qmf/SchemaCache.h" +#include "qpid/messaging/Session.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/Sender.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Condition.h" +#include <boost/shared_ptr.hpp> +#include <map> +#include <set> + +namespace qmf { + class AgentImpl : public virtual qpid::RefCounted { + public: + // + // Impl-only methods + // + AgentImpl(const std::string& n, uint32_t e, ConsoleSessionImpl& s); + void setAttribute(const std::string& k, const qpid::types::Variant& v); + void setAttribute(const std::string& k, const std::string& v) { attributes[k] = v; } + void touch() { touched = true; } + uint32_t age() { untouchedCount = touched ? 0 : untouchedCount + 1; touched = false; return untouchedCount; } + uint32_t getCapability() const { return capability; } + void handleException(const qpid::types::Variant::Map&, const qpid::messaging::Message&); + void handleMethodResponse(const qpid::types::Variant::Map&, const qpid::messaging::Message&); + void handleDataIndication(const qpid::types::Variant::List&, const qpid::messaging::Message&); + void handleQueryResponse(const qpid::types::Variant::List&, const qpid::messaging::Message&); + + // + // Methods from API handle + // + const std::string& getName() const { return name; } + uint32_t getEpoch() const { return epoch; } + void setEpoch(uint32_t e) { epoch = e; } + std::string getVendor() const { return getAttribute("_vendor").asString(); } + std::string getProduct() const { return getAttribute("_product").asString(); } + std::string getInstance() const { return getAttribute("_instance").asString(); } + const qpid::types::Variant& getAttribute(const std::string& k) const; + const qpid::types::Variant::Map& getAttributes() const { return attributes; } + + ConsoleEvent querySchema(qpid::messaging::Duration t) { return query(Query(QUERY_SCHEMA_ID), t); } + uint32_t querySchemaAsync() { return queryAsync(Query(QUERY_SCHEMA_ID)); } + + ConsoleEvent query(const Query& q, qpid::messaging::Duration t); + ConsoleEvent query(const std::string& q, qpid::messaging::Duration t); + uint32_t queryAsync(const Query& q); + uint32_t queryAsync(const std::string& q); + + ConsoleEvent callMethod(const std::string& m, const qpid::types::Variant::Map& a, const DataAddr&, qpid::messaging::Duration t); + uint32_t callMethodAsync(const std::string& m, const qpid::types::Variant::Map& a, const DataAddr&); + + uint32_t getPackageCount() const; + const std::string& getPackage(uint32_t i) const; + uint32_t getSchemaIdCount(const std::string& p) const; + SchemaId getSchemaId(const std::string& p, uint32_t i) const; + Schema getSchema(const SchemaId& s, qpid::messaging::Duration t); + + private: + struct SyncContext { + qpid::sys::Mutex lock; + qpid::sys::Condition cond; + ConsoleEvent response; + }; + + mutable qpid::sys::Mutex lock; + std::string name; + std::string directSubject; + uint32_t epoch; + ConsoleSessionImpl& session; + bool touched; + uint32_t untouchedCount; + 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; + std::set<SchemaId, SchemaIdCompare> schemaIdSet; + + Query stringToQuery(const std::string&); + void sendQuery(const Query&, uint32_t); + void sendSchemaIdQuery(uint32_t); + void sendMethod(const std::string&, const qpid::types::Variant::Map&, const DataAddr&, uint32_t); + void sendSchemaRequest(const SchemaId&); + void learnSchemaId(const SchemaId&); + }; + + struct AgentImplAccess + { + static AgentImpl& get(Agent&); + static const AgentImpl& get(const Agent&); + }; +} + +#endif diff --git a/qpid/cpp/src/qmf/AgentSession.cpp b/qpid/cpp/src/qmf/AgentSession.cpp new file mode 100644 index 0000000000..71d369325f --- /dev/null +++ b/qpid/cpp/src/qmf/AgentSession.cpp @@ -0,0 +1,1072 @@ +/* + * + * 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/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); + int pendingEvents() 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; + 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; + +AgentSession::AgentSession(AgentSessionImpl* impl) { PI::ctor(*this, impl); } +AgentSession::AgentSession(const AgentSession& s) : qmf::Handle<AgentSessionImpl>() { PI::copy(*this, s); } +AgentSession::~AgentSession() { PI::dtor(*this); } +AgentSession& AgentSession::operator=(const AgentSession& s) { return PI::assign(*this, s); } + +AgentSession::AgentSession(Connection& c, const string& o) { PI::ctor(*this, new AgentSessionImpl(c, o)); } +void AgentSession::setDomain(const string& d) { impl->setDomain(d); } +void AgentSession::setVendor(const string& v) { impl->setVendor(v); } +void AgentSession::setProduct(const string& p) { impl->setProduct(p); } +void AgentSession::setInstance(const string& i) { impl->setInstance(i); } +void AgentSession::setAttribute(const string& k, const qpid::types::Variant& v) { impl->setAttribute(k, v); } +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); } +void AgentSession::authAccept(AgentEvent& e) { impl->authAccept(e); } +void AgentSession::authReject(AgentEvent& e, const string& m) { impl->authReject(e, m); } +void AgentSession::raiseException(AgentEvent& e, const string& s) { impl->raiseException(e, s); } +void AgentSession::raiseException(AgentEvent& e, const Data& d) { impl->raiseException(e, d); } +void AgentSession::response(AgentEvent& e, const Data& d) { impl->response(e, d); } +void AgentSession::complete(AgentEvent& e) { impl->complete(e); } +void AgentSession::methodSuccess(AgentEvent& e) { impl->methodSuccess(e); } +void AgentSession::raiseEvent(const Data& d) { impl->raiseEvent(d); } +void AgentSession::raiseEvent(const Data& d, int s) { impl->raiseEvent(d, s); } + +//======================================================================================== +// Impl Method Bodies +//======================================================================================== + +AgentSessionImpl::AgentSessionImpl(Connection& c, const string& options) : + connection(c), domain("default"), opened(false), 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), + schemaUpdateTime(uint64_t(qpid::sys::Duration(qpid::sys::EPOCH, qpid::sys::now()))) +{ + // + // Set Agent Capability Level + // + attributes["qmf.agent_capability"] = AGENT_CAPABILITY_0_8; + + if (!options.empty()) { + qpid::messaging::AddressParser parser(options); + Variant::Map optMap; + Variant::Map::const_iterator iter; + + parser.parseMap(optMap); + + iter = optMap.find("domain"); + if (iter != optMap.end()) + domain = iter->second.asString(); + + iter = optMap.find("interval"); + if (iter != optMap.end()) { + interval = iter->second.asUint32(); + if (interval < 1) + interval = 1; + } + + iter = optMap.find("external"); + if (iter != optMap.end()) + externalStorage = iter->second.asBool(); + + iter = optMap.find("allow-queries"); + if (iter != optMap.end()) + autoAllowQueries = iter->second.asBool(); + + iter = optMap.find("allow-methods"); + if (iter != optMap.end()) + autoAllowMethods = iter->second.asBool(); + + iter = optMap.find("max-subscriptions"); + if (iter != optMap.end()) + maxSubscriptions = iter->second.asUint32(); + + iter = optMap.find("min-sub-interval"); + if (iter != optMap.end()) + minSubInterval = iter->second.asUint32(); + + iter = optMap.find("sub-lifetime"); + if (iter != optMap.end()) + subLifetime = iter->second.asUint32(); + + iter = optMap.find("public-events"); + if (iter != optMap.end()) + publicEvents = iter->second.asBool(); + + iter = optMap.find("listen-on-direct"); + if (iter != optMap.end()) + listenOnDirect = iter->second.asBool(); + + iter = optMap.find("strict-security"); + if (iter != optMap.end()) + strictSecurity = iter->second.asBool(); + } +} + + +AgentSessionImpl::~AgentSessionImpl() +{ + if (opened) + close(); +} + + +void AgentSessionImpl::open() +{ + if (opened) + throw QmfException("The session is already open"); + + const string addrArgs(";{create:never,node:{type:topic}}"); + const string routableAddr("direct-agent.route." + qpid::types::Uuid(true).str()); + attributes["_direct_subject"] = routableAddr; + + // Establish messaging addresses + setAgentName(); + directBase = "qmf." + domain + ".direct"; + topicBase = "qmf." + domain + ".topic"; + + // Create AMQP session, receivers, and senders + session = connection.createSession(); + Receiver directRx; + Receiver routableDirectRx = session.createReceiver(topicBase + "/" + routableAddr + addrArgs); + Receiver topicRx = session.createReceiver(topicBase + "/console.#" + addrArgs); + + if (listenOnDirect && !strictSecurity) { + directRx = session.createReceiver(directBase + "/" + agentName + addrArgs); + directRx.setCapacity(64); + } + + routableDirectRx.setCapacity(64); + topicRx.setCapacity(64); + + if (!strictSecurity) + directSender = session.createSender(directBase + addrArgs); + topicSender = session.createSender(topicBase + addrArgs); + + // Start the receiver thread + threadCanceled = false; + opened = true; + thread = new qpid::sys::Thread(*this); + + // Send an initial agent heartbeat message + sendHeartbeat(); +} + + +void AgentSessionImpl::close() +{ + if (!opened) + return; + + // Stop and join the receiver thread + threadCanceled = true; + thread->join(); + delete thread; + + // Close the AMQP session + session.close(); + opened = false; +} + + +bool AgentSessionImpl::nextEvent(AgentEvent& event, Duration timeout) +{ + uint64_t milliseconds = timeout.getMilliseconds(); + qpid::sys::Mutex::ScopedLock l(lock); + + if (eventQueue.empty() && milliseconds > 0) + cond.wait(lock, qpid::sys::AbsTime(qpid::sys::now(), + qpid::sys::Duration(milliseconds * qpid::sys::TIME_MSEC))); + + if (!eventQueue.empty()) { + event = eventQueue.front(); + eventQueue.pop(); + return true; + } + + return false; +} + + +int AgentSessionImpl::pendingEvents() const +{ + qpid::sys::Mutex::ScopedLock l(lock); + return eventQueue.size(); +} + + +void AgentSessionImpl::registerSchema(Schema& schema) +{ + if (!schema.isFinalized()) + schema.finalize(); + const SchemaId& schemaId(schema.getSchemaId()); + + qpid::sys::Mutex::ScopedLock l(lock); + schemata[schemaId] = schema; + schemaIndex[schemaId] = DataIndex(); + + // + // Get the news out at the next periodic interval that there is new schema information. + // + schemaUpdateTime = uint64_t(qpid::sys::Duration(qpid::sys::EPOCH, qpid::sys::now())); + forceHeartbeat = true; +} + + +DataAddr AgentSessionImpl::addData(Data& data, const string& name, bool persistent) +{ + if (externalStorage) + throw QmfException("addData() must not be called when the 'external' option is enabled."); + + string dataName; + if (name.empty()) + dataName = qpid::types::Uuid(true).str(); + else + dataName = name; + + DataAddr addr(dataName, agentName, persistent ? 0 : bootSequence); + data.setAddr(addr); + + { + qpid::sys::Mutex::ScopedLock l(lock); + DataIndex::const_iterator iter = globalIndex.find(addr); + if (iter != globalIndex.end()) + throw QmfException("Duplicate Data Address"); + + globalIndex[addr] = data; + if (data.hasSchema()) + schemaIndex[data.getSchemaId()][addr] = data; + } + + // + // TODO: Survey active subscriptions to see if they need to hear about this new data. + // + + return addr; +} + + +void AgentSessionImpl::delData(const DataAddr& addr) +{ + { + qpid::sys::Mutex::ScopedLock l(lock); + DataIndex::iterator iter = globalIndex.find(addr); + if (iter == globalIndex.end()) + return; + if (iter->second.hasSchema()) { + const SchemaId& schemaId(iter->second.getSchemaId()); + schemaIndex[schemaId].erase(addr); + } + globalIndex.erase(iter); + } + + // + // TODO: Survey active subscriptions to see if they need to hear about this deleted data. + // +} + + +void AgentSessionImpl::authAccept(AgentEvent& authEvent) +{ + auto_ptr<AgentEventImpl> eventImpl(new AgentEventImpl(AGENT_QUERY)); + eventImpl->setQuery(authEvent.getQuery()); + eventImpl->setUserId(authEvent.getUserId()); + eventImpl->setReplyTo(AgentEventImplAccess::get(authEvent).getReplyTo()); + eventImpl->setCorrelationId(AgentEventImplAccess::get(authEvent).getCorrelationId()); + AgentEvent event(eventImpl.release()); + + if (externalStorage) { + enqueueEvent(event); + return; + } + + const Query& query(authEvent.getQuery()); + if (query.getDataAddr().isValid()) { + { + qpid::sys::Mutex::ScopedLock l(lock); + DataIndex::const_iterator iter = globalIndex.find(query.getDataAddr()); + if (iter != globalIndex.end()) + response(event, iter->second); + } + complete(event); + return; + } + + if (query.getSchemaId().isValid()) { + { + qpid::sys::Mutex::ScopedLock l(lock); + map<SchemaId, DataIndex>::const_iterator iter = schemaIndex.find(query.getSchemaId()); + if (iter != schemaIndex.end()) + for (DataIndex::const_iterator dIter = iter->second.begin(); dIter != iter->second.end(); dIter++) + if (query.matchesPredicate(dIter->second.getProperties())) + response(event, dIter->second); + } + complete(event); + return; + } + + raiseException(event, "Query is Invalid"); +} + + +void AgentSessionImpl::authReject(AgentEvent& event, const string& error) +{ + raiseException(event, "Action Forbidden - " + error); +} + + +void AgentSessionImpl::raiseException(AgentEvent& event, const string& error) +{ + Data exception(new DataImpl()); + exception.setProperty("error_text", error); + raiseException(event, exception); +} + + +void AgentSessionImpl::raiseException(AgentEvent& event, const Data& data) +{ + Message msg; + Variant::Map map; + Variant::Map& headers(msg.getProperties()); + + headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_RESPONSE; + headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_EXCEPTION; + headers[protocol::HEADER_KEY_CONTENT] = protocol::HEADER_CONTENT_DATA; + headers[protocol::HEADER_KEY_AGENT] = agentName; + headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF; + + AgentEventImpl& eventImpl(AgentEventImplAccess::get(event)); + const DataImpl& dataImpl(DataImplAccess::get(data)); + + msg.setCorrelationId(eventImpl.getCorrelationId()); + encode(dataImpl.asMap(), msg); + send(msg, eventImpl.getReplyTo()); + + QPID_LOG(trace, "SENT Exception to=" << eventImpl.getReplyTo()); +} + + +void AgentSessionImpl::response(AgentEvent& event, const Data& data) +{ + AgentEventImpl& impl(AgentEventImplAccess::get(event)); + uint32_t count = impl.enqueueData(data); + if (count >= 8) + flushResponses(event, false); +} + + +void AgentSessionImpl::complete(AgentEvent& event) +{ + flushResponses(event, true); +} + + +void AgentSessionImpl::methodSuccess(AgentEvent& event) +{ + Message msg; + Variant::Map map; + Variant::Map& headers(msg.getProperties()); + + headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_RESPONSE; + headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_METHOD_RESPONSE; + headers[protocol::HEADER_KEY_AGENT] = agentName; + headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF; + + AgentEventImpl& eventImpl(AgentEventImplAccess::get(event)); + + const Variant::Map& outArgs(eventImpl.getReturnArguments()); + const Variant::Map& outSubtypes(eventImpl.getReturnArgumentSubtypes()); + + map["_arguments"] = outArgs; + if (!outSubtypes.empty()) + map["_subtypes"] = outSubtypes; + + msg.setCorrelationId(eventImpl.getCorrelationId()); + encode(map, msg); + send(msg, eventImpl.getReplyTo()); + + QPID_LOG(trace, "SENT MethodResponse to=" << eventImpl.getReplyTo()); +} + + +void AgentSessionImpl::raiseEvent(const Data& data) +{ + int severity(SEV_NOTICE); + if (data.hasSchema()) { + const Schema& schema(DataImplAccess::get(data).getSchema()); + if (schema.isValid()) + severity = schema.getDefaultSeverity(); + } + + raiseEvent(data, severity); +} + + +void AgentSessionImpl::raiseEvent(const Data& data, int severity) +{ + Message msg; + Variant::Map map; + Variant::Map& headers(msg.getProperties()); + string subject("agent.ind.event"); + + if (data.hasSchema()) { + const SchemaId& schemaId(data.getSchemaId()); + if (schemaId.getType() != SCHEMA_TYPE_EVENT) + throw QmfException("Cannot call raiseEvent on data that is not an Event"); + subject = subject + "." + schemaId.getPackageName() + "." + schemaId.getName(); + } + + if (severity < SEV_EMERG || severity > SEV_DEBUG) + throw QmfException("Invalid severity value"); + + headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_INDICATION; + headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_DATA_INDICATION; + headers[protocol::HEADER_KEY_CONTENT] = protocol::HEADER_CONTENT_EVENT; + headers[protocol::HEADER_KEY_AGENT] = agentName; + headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF; + msg.setSubject(subject); + + Variant::List list; + Variant::Map dataAsMap(DataImplAccess::get(data).asMap()); + dataAsMap["_severity"] = severity; + dataAsMap["_timestamp"] = uint64_t(qpid::sys::Duration(qpid::sys::EPOCH, qpid::sys::now())); + list.push_back(dataAsMap); + encode(list, msg); + topicSender.send(msg); + + QPID_LOG(trace, "SENT EventIndication to=" << topicSender.getName() << "/" << subject); +} + + +void AgentSessionImpl::checkOpen() +{ + if (opened) + throw QmfException("Operation must be performed before calling open()"); +} + + +void AgentSessionImpl::enqueueEvent(const AgentEvent& event) +{ + qpid::sys::Mutex::ScopedLock l(lock); + bool notify = eventQueue.empty(); + eventQueue.push(event); + if (notify) + cond.notify(); +} + + +void AgentSessionImpl::setAgentName() +{ + Variant::Map::iterator iter; + string vendor; + string product; + string instance; + + iter = attributes.find("_vendor"); + if (iter == attributes.end()) + attributes["_vendor"] = vendor; + else + vendor = iter->second.asString(); + + iter = attributes.find("_product"); + if (iter == attributes.end()) + attributes["_product"] = product; + else + product = iter->second.asString(); + + iter = attributes.find("_instance"); + if (iter == attributes.end()) { + instance = qpid::types::Uuid(true).str(); + attributes["_instance"] = instance; + } else + instance = iter->second.asString(); + + agentName = vendor + ":" + product + ":" + instance; + attributes["_name"] = agentName; +} + + +void AgentSessionImpl::handleLocateRequest(const Variant::List& predicate, const Message& msg) +{ + QPID_LOG(trace, "RCVD AgentLocateRequest from=" << msg.getReplyTo()); + + if (!predicate.empty()) { + Query agentQuery(QUERY_OBJECT); + agentQuery.setPredicate(predicate); + if (!agentQuery.matchesPredicate(attributes)) { + QPID_LOG(trace, "AgentLocate predicate does not match this agent, ignoring"); + return; + } + } + + Message reply; + Variant::Map map; + Variant::Map& headers(reply.getProperties()); + + headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_INDICATION; + headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_AGENT_LOCATE_RESPONSE; + headers[protocol::HEADER_KEY_AGENT] = agentName; + headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF; + + map["_values"] = attributes; + map["_values"].asMap()[protocol::AGENT_ATTR_TIMESTAMP] = uint64_t(qpid::sys::Duration(qpid::sys::EPOCH, qpid::sys::now())); + map["_values"].asMap()[protocol::AGENT_ATTR_HEARTBEAT_INTERVAL] = interval; + map["_values"].asMap()[protocol::AGENT_ATTR_EPOCH] = bootSequence; + map["_values"].asMap()[protocol::AGENT_ATTR_SCHEMA_UPDATED_TIMESTAMP] = schemaUpdateTime; + + encode(map, reply); + send(reply, msg.getReplyTo()); + QPID_LOG(trace, "SENT AgentLocateResponse to=" << msg.getReplyTo()); +} + + +void AgentSessionImpl::handleMethodRequest(const Variant::Map& content, const Message& msg) +{ + QPID_LOG(trace, "RCVD MethodRequest map=" << content << " from=" << msg.getReplyTo() << " cid=" << msg.getCorrelationId()); + + // + // Construct an AgentEvent to be sent to the application. + // + auto_ptr<AgentEventImpl> eventImpl(new AgentEventImpl(AGENT_METHOD)); + eventImpl->setUserId(msg.getUserId()); + eventImpl->setReplyTo(msg.getReplyTo()); + eventImpl->setCorrelationId(msg.getCorrelationId()); + + Variant::Map::const_iterator iter; + + iter = content.find("_method_name"); + if (iter == content.end()) { + AgentEvent event(eventImpl.release()); + raiseException(event, "Malformed MethodRequest: missing _method_name field"); + return; + } + eventImpl->setMethodName(iter->second.asString()); + + iter = content.find("_arguments"); + if (iter != content.end()) + eventImpl->setArguments(iter->second.asMap()); + + iter = content.find("_subtypes"); + if (iter != content.end()) + eventImpl->setArgumentSubtypes(iter->second.asMap()); + + iter = content.find("_object_id"); + if (iter != content.end()) { + DataAddr addr(new DataAddrImpl(iter->second.asMap())); + eventImpl->setDataAddr(addr); + DataIndex::const_iterator iter(globalIndex.find(addr)); + if (iter == globalIndex.end()) { + AgentEvent event(eventImpl.release()); + raiseException(event, "No data object found with the specified address"); + return; + } + + const Schema& schema(DataImplAccess::get(iter->second).getSchema()); + if (schema.isValid()) { + eventImpl->setSchema(schema); + for (Variant::Map::const_iterator aIter = eventImpl->getArguments().begin(); + aIter != eventImpl->getArguments().end(); aIter++) { + const Schema& schema(DataImplAccess::get(iter->second).getSchema()); + if (!SchemaImplAccess::get(schema).isValidMethodInArg(eventImpl->getMethodName(), aIter->first, aIter->second)) { + AgentEvent event(eventImpl.release()); + raiseException(event, "Invalid argument: " + aIter->first); + return; + } + } + } + } + + enqueueEvent(AgentEvent(eventImpl.release())); +} + + +void AgentSessionImpl::handleQueryRequest(const Variant::Map& content, const Message& msg) +{ + QPID_LOG(trace, "RCVD QueryRequest query=" << content << " from=" << msg.getReplyTo() << " cid=" << msg.getCorrelationId()); + + // + // Construct an AgentEvent to be sent to the application or directly handled by the agent. + // + auto_ptr<QueryImpl> queryImpl(new QueryImpl(content)); + auto_ptr<AgentEventImpl> eventImpl(new AgentEventImpl(AGENT_AUTH_QUERY)); + eventImpl->setUserId(msg.getUserId()); + eventImpl->setReplyTo(msg.getReplyTo()); + eventImpl->setCorrelationId(msg.getCorrelationId()); + eventImpl->setQuery(queryImpl.release()); + AgentEvent ae(eventImpl.release()); + + if (ae.getQuery().getTarget() == QUERY_SCHEMA_ID || ae.getQuery().getTarget() == QUERY_SCHEMA) { + handleSchemaRequest(ae); + return; + } + + if (autoAllowQueries) + authAccept(ae); + else + enqueueEvent(ae); +} + + +void AgentSessionImpl::handleSchemaRequest(AgentEvent& event) +{ + SchemaMap::const_iterator iter; + string error; + const Query& query(event.getQuery()); + + Message msg; + Variant::List content; + Variant::Map map; + Variant::Map& headers(msg.getProperties()); + + headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_RESPONSE; + headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_QUERY_RESPONSE; + headers[protocol::HEADER_KEY_AGENT] = agentName; + headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF; + + { + qpid::sys::Mutex::ScopedLock l(lock); + if (query.getTarget() == QUERY_SCHEMA_ID) { + headers[protocol::HEADER_KEY_CONTENT] = "_schema_id"; + for (iter = schemata.begin(); iter != schemata.end(); iter++) + content.push_back(SchemaIdImplAccess::get(iter->first).asMap()); + } else if (query.getSchemaId().isValid()) { + headers[protocol::HEADER_KEY_CONTENT] = "_schema"; + iter = schemata.find(query.getSchemaId()); + if (iter != schemata.end()) + content.push_back(SchemaImplAccess::get(iter->second).asMap()); + } else { + error = "Invalid Schema Query: Requests for SCHEMA must supply a valid schema ID."; + } + } + + if (!error.empty()) { + raiseException(event, error); + return; + } + + AgentEventImpl& eventImpl(AgentEventImplAccess::get(event)); + + msg.setCorrelationId(eventImpl.getCorrelationId()); + encode(content, msg); + send(msg, eventImpl.getReplyTo()); + + QPID_LOG(trace, "SENT QueryResponse(Schema) to=" << eventImpl.getReplyTo()); +} + + +void AgentSessionImpl::handleV1SchemaRequest(qpid::management::Buffer& buffer, uint32_t seq, const Message& msg) +{ + string packageName; + string className; + uint8_t hashBits[16]; + + buffer.getShortString(packageName); + buffer.getShortString(className); + buffer.getBin128(hashBits); + + QPID_LOG(trace, "RCVD QMFv1 SchemaRequest for " << packageName << ":" << className); + + qpid::types::Uuid hash(hashBits); + map<SchemaId, Schema>::const_iterator iter; + string replyContent; + + SchemaId dataId(SCHEMA_TYPE_DATA, packageName, className); + dataId.setHash(hash); + + { + qpid::sys::Mutex::ScopedLock l(lock); + iter = schemata.find(dataId); + if (iter != schemata.end()) + replyContent = SchemaImplAccess::get(iter->second).asV1Content(seq); + else { + SchemaId eventId(SCHEMA_TYPE_EVENT, packageName, className); + eventId.setHash(hash); + iter = schemata.find(dataId); + if (iter != schemata.end()) + replyContent = SchemaImplAccess::get(iter->second).asV1Content(seq); + else + return; + } + } + + Message reply; + Variant::Map& headers(reply.getProperties()); + + headers[protocol::HEADER_KEY_AGENT] = agentName; + reply.setContent(replyContent); + + send(reply, msg.getReplyTo()); + QPID_LOG(trace, "SENT QMFv1 SchemaResponse to=" << msg.getReplyTo()); +} + + +void AgentSessionImpl::dispatch(Message msg) +{ + const Variant::Map& properties(msg.getProperties()); + Variant::Map::const_iterator iter; + + // + // If strict-security is enabled, make sure that reply-to address complies with the + // strict-security addressing pattern (i.e. start with 'qmf.<domain>.topic/direct-console.'). + // + if (strictSecurity && msg.getReplyTo()) { + if (msg.getReplyTo().getName() != topicBase || msg.getReplyTo().getSubject().find("direct-console.") != 0) { + QPID_LOG(warning, "Reply-to violates strict-security policy: " << msg.getReplyTo().str()); + return; + } + } + + iter = properties.find(protocol::HEADER_KEY_APP_ID); + if (iter != properties.end() && iter->second.asString() == protocol::HEADER_APP_ID_QMF) { + // + // Dispatch a QMFv2 formatted message + // + iter = properties.find(protocol::HEADER_KEY_OPCODE); + if (iter == properties.end()) { + QPID_LOG(trace, "Message received with no 'qmf.opcode' header"); + return; + } + + const string& opcode = iter->second.asString(); + + if (msg.getContentType() == "amqp/list") { + Variant::List content; + decode(msg, content); + + if (opcode == protocol::HEADER_OPCODE_AGENT_LOCATE_REQUEST) handleLocateRequest(content, msg); + else { + QPID_LOG(trace, "Unexpected QMFv2 opcode with 'amqp/list' content: " << opcode); + } + + } else if (msg.getContentType() == "amqp/map") { + Variant::Map content; + decode(msg, content); + + if (opcode == protocol::HEADER_OPCODE_METHOD_REQUEST) handleMethodRequest(content, msg); + else if (opcode == protocol::HEADER_OPCODE_QUERY_REQUEST) handleQueryRequest(content, msg); + else { + QPID_LOG(trace, "Unexpected QMFv2 opcode with 'amqp/map' content: " << opcode); + } + } else { + QPID_LOG(trace, "Unexpected QMFv2 content type. Expected amqp/list or amqp/map"); + } + + } else { + // + // Dispatch a QMFv1 formatted message + // + const string& body(msg.getContent()); + if (body.size() < 8) + return; + qpid::management::Buffer buffer(const_cast<char*>(body.c_str()), body.size()); + + if (buffer.getOctet() != 'A') return; + if (buffer.getOctet() != 'M') return; + if (buffer.getOctet() != '2') return; + char v1Opcode(buffer.getOctet()); + uint32_t seq(buffer.getLong()); + + if (v1Opcode == 'S') handleV1SchemaRequest(buffer, seq, msg); + else { + QPID_LOG(trace, "Unknown or Unsupported QMFv1 opcode: " << v1Opcode); + } + } +} + + +void AgentSessionImpl::sendHeartbeat() +{ + Message msg; + Variant::Map map; + Variant::Map& headers(msg.getProperties()); + std::stringstream address; + + address << "agent.ind.heartbeat"; + + // append .<vendor>.<product> to address key if present. + Variant::Map::const_iterator v; + if ((v = attributes.find("_vendor")) != attributes.end() && !v->second.getString().empty()) { + address << "." << v->second.getString(); + if ((v = attributes.find("_product")) != attributes.end() && !v->second.getString().empty()) { + address << "." << v->second.getString(); + } + } + + headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_INDICATION; + headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_AGENT_HEARTBEAT_INDICATION; + headers[protocol::HEADER_KEY_AGENT] = agentName; + headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF; + msg.setSubject(address.str()); + + map["_values"] = attributes; + map["_values"].asMap()[protocol::AGENT_ATTR_TIMESTAMP] = uint64_t(qpid::sys::Duration(qpid::sys::EPOCH, qpid::sys::now())); + map["_values"].asMap()[protocol::AGENT_ATTR_HEARTBEAT_INTERVAL] = interval; + map["_values"].asMap()[protocol::AGENT_ATTR_EPOCH] = bootSequence; + map["_values"].asMap()[protocol::AGENT_ATTR_SCHEMA_UPDATED_TIMESTAMP] = schemaUpdateTime; + + encode(map, msg); + topicSender.send(msg); + QPID_LOG(trace, "SENT AgentHeartbeat name=" << agentName); +} + + +void AgentSessionImpl::send(Message msg, const Address& to) +{ + Sender sender; + + if (strictSecurity && to.getName() != topicBase) { + QPID_LOG(warning, "Address violates strict-security policy: " << to); + return; + } + + if (to.getName() == directBase) { + msg.setSubject(to.getSubject()); + sender = directSender; + } else if (to.getName() == topicBase) { + msg.setSubject(to.getSubject()); + sender = topicSender; + } else + sender = session.createSender(to); + + sender.send(msg); +} + + +void AgentSessionImpl::flushResponses(AgentEvent& event, bool final) +{ + Message msg; + Variant::Map map; + Variant::Map& headers(msg.getProperties()); + + headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_RESPONSE; + headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_QUERY_RESPONSE; + headers[protocol::HEADER_KEY_CONTENT] = protocol::HEADER_CONTENT_DATA; + headers[protocol::HEADER_KEY_AGENT] = agentName; + headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF; + if (!final) + headers[protocol::HEADER_KEY_PARTIAL] = Variant(); + + Variant::List body; + AgentEventImpl& eventImpl(AgentEventImplAccess::get(event)); + Data data(eventImpl.dequeueData()); + while (data.isValid()) { + DataImpl& dataImpl(DataImplAccess::get(data)); + body.push_back(dataImpl.asMap()); + data = eventImpl.dequeueData(); + } + + msg.setCorrelationId(eventImpl.getCorrelationId()); + encode(body, msg); + send(msg, eventImpl.getReplyTo()); + + QPID_LOG(trace, "SENT QueryResponse to=" << eventImpl.getReplyTo()); +} + + +void AgentSessionImpl::periodicProcessing(uint64_t seconds) +{ + // + // The granularity of this timer is seconds. Don't waste time looking for work if + // it's been less than a second since we last visited. + // + if (seconds == lastVisit) + return; + //uint64_t thisInterval(seconds - lastVisit); + lastVisit = seconds; + + // + // First time through, set lastHeartbeat to the current time. + // + if (lastHeartbeat == 0) + lastHeartbeat = seconds; + + // + // If the hearbeat interval has elapsed, send a heartbeat. + // + if (forceHeartbeat || (seconds - lastHeartbeat >= interval)) { + lastHeartbeat = seconds; + forceHeartbeat = false; + sendHeartbeat(); + } + + // + // TODO: process any active subscriptions on their intervals. + // +} + + +void AgentSessionImpl::run() +{ + QPID_LOG(debug, "AgentSession thread started for agent " << agentName); + + try { + while (!threadCanceled) { + 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); + if (threadCanceled) + break; + if (valid) { + try { + dispatch(rx.fetch()); + } catch (qpid::types::Exception& e) { + QPID_LOG(error, "Exception caught in message dispatch: " << e.what()); + } + session.acknowledge(); + } + } + } catch (qpid::types::Exception& e) { + QPID_LOG(error, "Exception caught in message thread - exiting: " << e.what()); + enqueueEvent(AgentEvent(new AgentEventImpl(AGENT_THREAD_FAILED))); + } + + QPID_LOG(debug, "AgentSession thread exiting for agent " << agentName); +} + diff --git a/qpid/cpp/src/qmf/AgentSubscription.cpp b/qpid/cpp/src/qmf/AgentSubscription.cpp new file mode 100644 index 0000000000..4dc5cb74a4 --- /dev/null +++ b/qpid/cpp/src/qmf/AgentSubscription.cpp @@ -0,0 +1,51 @@ +/* + * + * 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/AgentSubscription.h" + +using namespace qmf; + +AgentSubscription::AgentSubscription(uint64_t _id, uint64_t _interval, uint64_t _life, + const std::string& _replyTo, const std::string& _cid, Query _query) : + id(_id), interval(_interval), lifetime(_life), timeSincePublish(0), timeSinceKeepalive(0), + replyTo(_replyTo), cid(_cid), query(_query) +{ +} + + +AgentSubscription::~AgentSubscription() +{ +} + + +bool AgentSubscription::tick(uint64_t seconds) +{ + timeSinceKeepalive += seconds; + if (timeSinceKeepalive >= lifetime) + return false; + + timeSincePublish += seconds; + if (timeSincePublish >= interval) { + } + + return true; +} + diff --git a/qpid/cpp/src/qmf/AgentSubscription.h b/qpid/cpp/src/qmf/AgentSubscription.h new file mode 100644 index 0000000000..01e8f43e9f --- /dev/null +++ b/qpid/cpp/src/qmf/AgentSubscription.h @@ -0,0 +1,52 @@ +#ifndef _QMF_AGENT_SUBSCRIPTION_H_ +#define _QMF_AGENT_SUBSCRIPTION_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/IntegerTypes.h" +#include "qpid/types/Variant.h" +#include "qmf/Query.h" +#include "qmf/Data.h" +#include <boost/shared_ptr.hpp> + +namespace qmf { + class AgentSubscription { + public: + AgentSubscription(uint64_t _id, uint64_t _interval, uint64_t _life, + const std::string& _replyTo, const std::string& _cid, Query _query); + ~AgentSubscription(); + bool tick(uint64_t seconds); + void keepalive() { timeSinceKeepalive = 0; } + + private: + uint64_t id; + uint64_t interval; + uint64_t lifetime; + uint64_t timeSincePublish; + uint64_t timeSinceKeepalive; + const std::string replyTo; + const std::string cid; + Query query; + }; + +} + +#endif diff --git a/qpid/cpp/src/qmf/ConsoleEvent.cpp b/qpid/cpp/src/qmf/ConsoleEvent.cpp new file mode 100644 index 0000000000..b2a5e321c7 --- /dev/null +++ b/qpid/cpp/src/qmf/ConsoleEvent.cpp @@ -0,0 +1,82 @@ +/* + * + * 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/ConsoleEventImpl.h" +#include "qmf/PrivateImplRef.h" +#include "qmf/exceptions.h" + +using namespace std; +using namespace qmf; +using qpid::types::Variant; + +typedef PrivateImplRef<ConsoleEvent> PI; + +ConsoleEvent::ConsoleEvent(ConsoleEventImpl* impl) { PI::ctor(*this, impl); } +ConsoleEvent::ConsoleEvent(const ConsoleEvent& s) : qmf::Handle<ConsoleEventImpl>() { PI::copy(*this, s); } +ConsoleEvent::~ConsoleEvent() { PI::dtor(*this); } +ConsoleEvent& ConsoleEvent::operator=(const ConsoleEvent& s) { return PI::assign(*this, s); } + +ConsoleEventCode ConsoleEvent::getType() const { return impl->getType(); } +uint32_t ConsoleEvent::getCorrelator() const { return impl->getCorrelator(); } +Agent ConsoleEvent::getAgent() const { return impl->getAgent(); } +AgentDelReason ConsoleEvent::getAgentDelReason() const { return impl->getAgentDelReason(); } +uint32_t ConsoleEvent::getSchemaIdCount() const { return impl->getSchemaIdCount(); } +SchemaId ConsoleEvent::getSchemaId(uint32_t i) const { return impl->getSchemaId(i); } +uint32_t ConsoleEvent::getDataCount() const { return impl->getDataCount(); } +Data ConsoleEvent::getData(uint32_t i) const { return impl->getData(i); } +bool ConsoleEvent::isFinal() const { return impl->isFinal(); } +const Variant::Map& ConsoleEvent::getArguments() const { return impl->getArguments(); } +int ConsoleEvent::getSeverity() const { return impl->getSeverity(); } +uint64_t ConsoleEvent::getTimestamp() const { return impl->getTimestamp(); } + + +SchemaId ConsoleEventImpl::getSchemaId(uint32_t i) const +{ + uint32_t count = 0; + for (list<SchemaId>::const_iterator iter = newSchemaIds.begin(); iter != newSchemaIds.end(); iter++) { + if (count++ == i) + return *iter; + } + throw IndexOutOfRange(); +} + + +Data ConsoleEventImpl::getData(uint32_t i) const +{ + uint32_t count = 0; + for (list<Data>::const_iterator iter = dataList.begin(); iter != dataList.end(); iter++) { + if (count++ == i) + return *iter; + } + throw IndexOutOfRange(); +} + + +ConsoleEventImpl& ConsoleEventImplAccess::get(ConsoleEvent& item) +{ + return *item.impl; +} + + +const ConsoleEventImpl& ConsoleEventImplAccess::get(const ConsoleEvent& item) +{ + return *item.impl; +} diff --git a/qpid/cpp/src/qmf/ConsoleEventImpl.h b/qpid/cpp/src/qmf/ConsoleEventImpl.h new file mode 100644 index 0000000000..9843971456 --- /dev/null +++ b/qpid/cpp/src/qmf/ConsoleEventImpl.h @@ -0,0 +1,84 @@ +#ifndef _QMF_CONSOLE_EVENT_IMPL_H_ +#define _QMF_CONSOLE_EVENT_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/ConsoleEvent.h" +#include "qmf/Agent.h" +#include "qmf/Data.h" +#include "qpid/types/Variant.h" +#include <list> + +namespace qmf { + class ConsoleEventImpl : public virtual qpid::RefCounted { + public: + // + // Impl-only methods + // + ConsoleEventImpl(ConsoleEventCode e, AgentDelReason r = AGENT_DEL_AGED) : + eventType(e), delReason(r), correlator(0), final(false) {} + void setCorrelator(uint32_t c) { correlator = c; } + void setAgent(const Agent& a) { agent = a; } + void addData(const Data& d) { dataList.push_back(Data(d)); } + void addSchemaId(const SchemaId& s) { newSchemaIds.push_back(SchemaId(s)); } + void setFinal() { final = true; } + void setArguments(const qpid::types::Variant::Map& a) { arguments = a; } + void setSeverity(int s) { severity = s; } + void setTimestamp(uint64_t t) { timestamp = t; } + + // + // Methods from API handle + // + ConsoleEventCode getType() const { return eventType; } + uint32_t getCorrelator() const { return correlator; } + Agent getAgent() const { return agent; } + AgentDelReason getAgentDelReason() const { return delReason; } + uint32_t getSchemaIdCount() const { return newSchemaIds.size(); } + SchemaId getSchemaId(uint32_t) const; + uint32_t getDataCount() const { return dataList.size(); } + Data getData(uint32_t i) const; + bool isFinal() const { return final; } + const qpid::types::Variant::Map& getArguments() const { return arguments; } + int getSeverity() const { return severity; } + uint64_t getTimestamp() const { return timestamp; } + + private: + const ConsoleEventCode eventType; + const AgentDelReason delReason; + uint32_t correlator; + Agent agent; + bool final; + std::list<Data> dataList; + std::list<SchemaId> newSchemaIds; + qpid::types::Variant::Map arguments; + int severity; + uint64_t timestamp; + }; + + struct ConsoleEventImplAccess + { + static ConsoleEventImpl& get(ConsoleEvent&); + static const ConsoleEventImpl& get(const ConsoleEvent&); + }; +} + +#endif diff --git a/qpid/cpp/src/qmf/ConsoleSession.cpp b/qpid/cpp/src/qmf/ConsoleSession.cpp new file mode 100644 index 0000000000..7b839930e1 --- /dev/null +++ b/qpid/cpp/src/qmf/ConsoleSession.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 "qmf/PrivateImplRef.h" +#include "qmf/ConsoleSessionImpl.h" +#include "qmf/AgentImpl.h" +#include "qmf/SchemaId.h" +#include "qmf/SchemaImpl.h" +#include "qmf/ConsoleEventImpl.h" +#include "qmf/constants.h" +#include "qpid/log/Statement.h" +#include "qpid/messaging/AddressParser.h" +#include "qpid/messaging/Sender.h" +#include "qpid/messaging/Receiver.h" + +using namespace std; +using namespace qmf; +using qpid::messaging::Address; +using qpid::messaging::Connection; +using qpid::messaging::Receiver; +using qpid::messaging::Sender; +using qpid::messaging::Duration; +using qpid::messaging::Message; +using qpid::types::Variant; + +typedef qmf::PrivateImplRef<ConsoleSession> PI; + +ConsoleSession::ConsoleSession(ConsoleSessionImpl* impl) { PI::ctor(*this, impl); } +ConsoleSession::ConsoleSession(const ConsoleSession& s) : qmf::Handle<ConsoleSessionImpl>() { PI::copy(*this, s); } +ConsoleSession::~ConsoleSession() { PI::dtor(*this); } +ConsoleSession& ConsoleSession::operator=(const ConsoleSession& s) { return PI::assign(*this, s); } + +ConsoleSession::ConsoleSession(Connection& c, const string& o) { PI::ctor(*this, new ConsoleSessionImpl(c, o)); } +void ConsoleSession::setDomain(const string& d) { impl->setDomain(d); } +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(); } +Subscription ConsoleSession::subscribe(const Query& q, const string& f, const string& o) { return impl->subscribe(q, f, o); } +Subscription ConsoleSession::subscribe(const string& q, const string& f, const string& o) { return impl->subscribe(q, f, o); } + +//======================================================================================== +// Impl Method Bodies +//======================================================================================== + +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()) +{ + if (!options.empty()) { + qpid::messaging::AddressParser parser(options); + Variant::Map optMap; + Variant::Map::const_iterator iter; + + parser.parseMap(optMap); + + iter = optMap.find("domain"); + if (iter != optMap.end()) + domain = iter->second.asString(); + + iter = optMap.find("max-agent-age"); + if (iter != optMap.end()) + maxAgentAgeMinutes = iter->second.asUint32(); + + iter = optMap.find("listen-on-direct"); + if (iter != optMap.end()) + listenOnDirect = iter->second.asBool(); + + iter = optMap.find("strict-security"); + if (iter != optMap.end()) + strictSecurity = iter->second.asBool(); + } +} + + +ConsoleSessionImpl::~ConsoleSessionImpl() +{ + if (opened) + close(); +} + + +void ConsoleSessionImpl::setAgentFilter(const string& predicate) +{ + agentQuery = Query(QUERY_OBJECT, predicate); + + // + // Purge the agent list of any agents that don't match the filter. + // + { + qpid::sys::Mutex::ScopedLock l(lock); + map<string, Agent> toDelete; + for (map<string, Agent>::iterator iter = agents.begin(); iter != agents.end(); iter++) + if (!agentQuery.matchesPredicate(iter->second.getAttributes())) { + toDelete[iter->first] = iter->second; + if (iter->second.getName() == connectedBrokerAgent.getName()) + connectedBrokerInAgentList = false; + } + + for (map<string, Agent>::iterator iter = toDelete.begin(); iter != toDelete.end(); iter++) { + agents.erase(iter->first); + auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_AGENT_DEL, AGENT_DEL_FILTER)); + eventImpl->setAgent(iter->second); + enqueueEventLH(eventImpl.release()); + } + + if (!connectedBrokerInAgentList && connectedBrokerAgent.isValid() && + agentQuery.matchesPredicate(connectedBrokerAgent.getAttributes())) { + agents[connectedBrokerAgent.getName()] = connectedBrokerAgent; + connectedBrokerInAgentList = true; + + // + // Enqueue a notification of the new agent. + // + auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_AGENT_ADD)); + eventImpl->setAgent(connectedBrokerAgent); + enqueueEventLH(ConsoleEvent(eventImpl.release())); + } + } + + // + // Broadcast an agent locate request with our new criteria. + // + if (opened) + sendAgentLocate(); +} + + +void ConsoleSessionImpl::open() +{ + if (opened) + throw QmfException("The session is already open"); + + // Establish messaging addresses + directBase = "qmf." + domain + ".direct"; + topicBase = "qmf." + domain + ".topic"; + + string myKey("direct-console." + qpid::types::Uuid(true).str()); + + replyAddress = Address(topicBase + "/" + myKey + ";{node:{type:topic}}"); + + // Create AMQP session, receivers, and senders + session = connection.createSession(); + Receiver directRx = session.createReceiver(replyAddress); + Receiver topicRx = session.createReceiver(topicBase + "/agent.#"); // TODO: be more discriminating + if (!strictSecurity) { + Receiver legacyRx = session.createReceiver("amq.direct/" + myKey + ";{node:{type:topic}}"); + legacyRx.setCapacity(64); + directSender = session.createSender(directBase + ";{create:never,node:{type:topic}}"); + directSender.setCapacity(128); + } + + directRx.setCapacity(64); + topicRx.setCapacity(128); + + topicSender = session.createSender(topicBase + ";{create:never,node:{type:topic}}"); + + topicSender.setCapacity(128); + + // Start the receiver thread + threadCanceled = false; + 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() +{ + if (!opened) + throw QmfException("The session is already closed"); + + // Stop and join the receiver thread + threadCanceled = true; + thread->join(); + delete thread; + + // Close the AMQP session + session.close(); + opened = false; +} + + +bool ConsoleSessionImpl::nextEvent(ConsoleEvent& event, Duration timeout) +{ + uint64_t milliseconds = timeout.getMilliseconds(); + qpid::sys::Mutex::ScopedLock l(lock); + + if (eventQueue.empty() && milliseconds > 0) + cond.wait(lock, qpid::sys::AbsTime(qpid::sys::now(), + qpid::sys::Duration(milliseconds * qpid::sys::TIME_MSEC))); + + if (!eventQueue.empty()) { + event = eventQueue.front(); + eventQueue.pop(); + return true; + } + + return false; +} + + +int ConsoleSessionImpl::pendingEvents() const +{ + qpid::sys::Mutex::ScopedLock l(lock); + return eventQueue.size(); +} + + +uint32_t ConsoleSessionImpl::getAgentCount() const +{ + qpid::sys::Mutex::ScopedLock l(lock); + return agents.size(); +} + + +Agent ConsoleSessionImpl::getAgent(uint32_t i) const +{ + qpid::sys::Mutex::ScopedLock l(lock); + uint32_t count = 0; + for (map<string, Agent>::const_iterator iter = agents.begin(); iter != agents.end(); iter++) + if (count++ == i) + return iter->second; + throw IndexOutOfRange(); +} + + +Subscription ConsoleSessionImpl::subscribe(const Query&, const string&, const string&) +{ + return Subscription(); +} + + +Subscription ConsoleSessionImpl::subscribe(const string&, const string&, const string&) +{ + return Subscription(); +} + + +void ConsoleSessionImpl::enqueueEvent(const ConsoleEvent& event) +{ + qpid::sys::Mutex::ScopedLock l(lock); + enqueueEventLH(event); +} + + +void ConsoleSessionImpl::enqueueEventLH(const ConsoleEvent& event) +{ + bool notify = eventQueue.empty(); + eventQueue.push(event); + if (notify) + cond.notify(); +} + + +void ConsoleSessionImpl::dispatch(Message msg) +{ + const Variant::Map& properties(msg.getProperties()); + Variant::Map::const_iterator iter; + Variant::Map::const_iterator oiter; + + oiter = properties.find(protocol::HEADER_KEY_OPCODE); + iter = properties.find(protocol::HEADER_KEY_APP_ID); + if (iter == properties.end()) + iter = properties.find("app_id"); + if (iter != properties.end() && iter->second.asString() == protocol::HEADER_APP_ID_QMF && oiter != properties.end()) { + // + // Dispatch a QMFv2 formatted message + // + const string& opcode = oiter->second.asString(); + + iter = properties.find(protocol::HEADER_KEY_AGENT); + if (iter == properties.end()) { + QPID_LOG(trace, "Message received with no 'qmf.agent' header"); + return; + } + const string& agentName = iter->second.asString(); + + Agent agent; + { + qpid::sys::Mutex::ScopedLock l(lock); + map<string, Agent>::iterator aIter = agents.find(agentName); + if (aIter != agents.end()) { + agent = aIter->second; + AgentImplAccess::get(agent).touch(); + } + } + + if (msg.getContentType() == "amqp/map" && + (opcode == protocol::HEADER_OPCODE_AGENT_HEARTBEAT_INDICATION || opcode == protocol::HEADER_OPCODE_AGENT_LOCATE_RESPONSE)) { + // + // This is the one case where it's ok (necessary actually) to receive a QMFv2 + // message from an unknown agent (how else are they going to get known?) + // + Variant::Map content; + decode(msg, content); + handleAgentUpdate(agentName, content, msg); + return; + } + + if (!agent.isValid()) + return; + + AgentImpl& agentImpl(AgentImplAccess::get(agent)); + + if (msg.getContentType() == "amqp/map") { + Variant::Map content; + decode(msg, content); + + if (opcode == protocol::HEADER_OPCODE_EXCEPTION) agentImpl.handleException(content, msg); + else if (opcode == protocol::HEADER_OPCODE_METHOD_RESPONSE) agentImpl.handleMethodResponse(content, msg); + else + QPID_LOG(error, "Received a map-formatted QMFv2 message with opcode=" << opcode); + + return; + } + + if (msg.getContentType() == "amqp/list") { + Variant::List content; + decode(msg, content); + + if (opcode == protocol::HEADER_OPCODE_QUERY_RESPONSE) agentImpl.handleQueryResponse(content, msg); + else if (opcode == protocol::HEADER_OPCODE_DATA_INDICATION) agentImpl.handleDataIndication(content, msg); + else + QPID_LOG(error, "Received a list-formatted QMFv2 message with opcode=" << opcode); + + return; + } + } else { + // + // Dispatch a QMFv1 formatted message + // + const string& body(msg.getContent()); + if (body.size() < 8) + return; + qpid::management::Buffer buffer(const_cast<char*>(body.c_str()), body.size()); + + if (buffer.getOctet() != 'A') return; + if (buffer.getOctet() != 'M') return; + if (buffer.getOctet() != '2') return; + char v1Opcode(buffer.getOctet()); + uint32_t seq(buffer.getLong()); + + if (v1Opcode == 's') handleV1SchemaResponse(buffer, seq, msg); + else { + QPID_LOG(trace, "Unknown or Unsupported QMFv1 opcode: " << v1Opcode); + } + } +} + + +void ConsoleSessionImpl::sendBrokerLocate() +{ + Message msg; + Variant::Map& headers(msg.getProperties()); + + headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_REQUEST; + headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_AGENT_LOCATE_REQUEST; + headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF; + + msg.setReplyTo(replyAddress); + msg.setCorrelationId("broker-locate"); + msg.setSubject("broker"); + + Sender sender = session.createSender(directBase + ";{create:never,node:{type:topic}}"); + sender.send(msg); + sender.close(); + + QPID_LOG(trace, "SENT AgentLocate to broker"); +} + + +void ConsoleSessionImpl::sendAgentLocate() +{ + Message msg; + Variant::Map& headers(msg.getProperties()); + static const string subject("console.request.agent_locate"); + + headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_REQUEST; + headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_AGENT_LOCATE_REQUEST; + headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF; + + msg.setReplyTo(replyAddress); + msg.setCorrelationId("agent-locate"); + msg.setSubject(subject); + encode(agentQuery.getPredicate(), msg); + + topicSender.send(msg); + + QPID_LOG(trace, "SENT AgentLocate to=" << topicSender.getName() << "/" << subject); +} + + +void ConsoleSessionImpl::handleAgentUpdate(const string& agentName, const Variant::Map& content, const Message& msg) +{ + Variant::Map::const_iterator iter; + Agent agent; + uint32_t epoch(0); + string cid(msg.getCorrelationId()); + + iter = content.find("_values"); + if (iter == content.end()) + return; + 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()) + epoch = iter->second.asUint32(); + + if (cid == "broker-locate") { + qpid::sys::Mutex::ScopedLock l(lock); + auto_ptr<AgentImpl> impl(new AgentImpl(agentName, epoch, *this)); + for (iter = attrs.begin(); iter != attrs.end(); iter++) + if (iter->first != protocol::AGENT_ATTR_EPOCH) + impl->setAttribute(iter->first, iter->second); + agent = Agent(impl.release()); + connectedBrokerAgent = agent; + if (!agentQuery || agentQuery.matchesPredicate(attrs)) { + connectedBrokerInAgentList = true; + agents[agentName] = agent; + + // + // Enqueue a notification of the new agent. + // + auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_AGENT_ADD)); + eventImpl->setAgent(agent); + enqueueEventLH(ConsoleEvent(eventImpl.release())); + } + return; + } + + // + // Check this agent against the agent filter. Exit if it doesn't match. + // (only if this isn't the connected broker agent) + // + if (agentQuery && (!agentQuery.matchesPredicate(attrs))) + return; + + QPID_LOG(trace, "RCVD AgentHeartbeat from an agent matching our filter: " << agentName); + + { + qpid::sys::Mutex::ScopedLock l(lock); + map<string, Agent>::iterator aIter = agents.find(agentName); + if (aIter == agents.end()) { + // + // This is a new agent. We have no current record of its existence. + // + auto_ptr<AgentImpl> impl(new AgentImpl(agentName, epoch, *this)); + for (iter = attrs.begin(); iter != attrs.end(); iter++) + if (iter->first != protocol::AGENT_ATTR_EPOCH) + impl->setAttribute(iter->first, iter->second); + agent = Agent(impl.release()); + agents[agentName] = agent; + + // + // Enqueue a notification of the new agent. + // + auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_AGENT_ADD)); + eventImpl->setAgent(agent); + enqueueEventLH(ConsoleEvent(eventImpl.release())); + } else { + // + // This is a refresh of an agent we are already tracking. + // + bool detectedRestart(false); + agent = aIter->second; + AgentImpl& impl(AgentImplAccess::get(agent)); + impl.touch(); + if (impl.getEpoch() != epoch) { + // + // The agent has restarted since the last time we heard from it. + // Enqueue a notification. + // + impl.setEpoch(epoch); + auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_AGENT_RESTART)); + eventImpl->setAgent(agent); + enqueueEventLH(ConsoleEvent(eventImpl.release())); + detectedRestart = true; + } + + iter = attrs.find(protocol::AGENT_ATTR_SCHEMA_UPDATED_TIMESTAMP); + if (iter != attrs.end()) { + uint64_t ts(iter->second.asUint64()); + if (ts > impl.getAttribute(protocol::AGENT_ATTR_SCHEMA_UPDATED_TIMESTAMP).asUint64()) { + // + // The agent has added new schema entries since we last heard from it. + // Update the attribute and, if this doesn't accompany a restart, enqueue a notification. + // + if (!detectedRestart) { + auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_AGENT_SCHEMA_UPDATE)); + eventImpl->setAgent(agent); + enqueueEventLH(ConsoleEvent(eventImpl.release())); + } + impl.setAttribute(protocol::AGENT_ATTR_SCHEMA_UPDATED_TIMESTAMP, iter->second); + } + } + } + } +} + + +void ConsoleSessionImpl::handleV1SchemaResponse(qpid::management::Buffer& buffer, uint32_t, const Message&) +{ + QPID_LOG(trace, "RCVD V1SchemaResponse"); + Schema schema(new SchemaImpl(buffer)); + schemaCache->declareSchema(schema); +} + + +void ConsoleSessionImpl::periodicProcessing(uint64_t seconds) +{ + // + // The granularity of this timer is seconds. Don't waste time looking for work if + // it's been less than a second since we last visited. + // + if (seconds == lastVisit) + return; + lastVisit = seconds; + + // + // Handle the aging of agent records + // + if (lastAgePass == 0) + lastAgePass = seconds; + if (seconds - lastAgePass >= 60) { + lastAgePass = seconds; + map<string, Agent> toDelete; + qpid::sys::Mutex::ScopedLock l(lock); + + for (map<string, Agent>::iterator iter = agents.begin(); iter != agents.end(); iter++) + if ((iter->second.getName() != connectedBrokerAgent.getName()) && + (AgentImplAccess::get(iter->second).age() > maxAgentAgeMinutes)) + toDelete[iter->first] = iter->second; + + for (map<string, Agent>::iterator iter = toDelete.begin(); iter != toDelete.end(); iter++) { + agents.erase(iter->first); + auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_AGENT_DEL, AGENT_DEL_AGED)); + eventImpl->setAgent(iter->second); + enqueueEventLH(eventImpl.release()); + } + } +} + + +void ConsoleSessionImpl::run() +{ + QPID_LOG(debug, "ConsoleSession thread started"); + + try { + while (!threadCanceled) { + 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); + if (threadCanceled) + break; + if (valid) { + try { + dispatch(rx.fetch()); + } catch (qpid::types::Exception& e) { + QPID_LOG(error, "Exception caught in message dispatch: " << e.what()); + } + session.acknowledge(); + } + } + } catch (qpid::types::Exception& e) { + QPID_LOG(error, "Exception caught in message thread - exiting: " << e.what()); + enqueueEvent(ConsoleEvent(new ConsoleEventImpl(CONSOLE_THREAD_FAILED))); + } + + QPID_LOG(debug, "ConsoleSession thread exiting"); +} + diff --git a/qpid/cpp/src/qmf/ConsoleSessionImpl.h b/qpid/cpp/src/qmf/ConsoleSessionImpl.h new file mode 100644 index 0000000000..411b3f016a --- /dev/null +++ b/qpid/cpp/src/qmf/ConsoleSessionImpl.h @@ -0,0 +1,108 @@ +#ifndef _QMF_CONSOLE_SESSION_IMPL_H_ +#define _QMF_CONSOLE_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/ConsoleSession.h" +#include "qmf/AgentImpl.h" +#include "qmf/SchemaId.h" +#include "qmf/Schema.h" +#include "qmf/ConsoleEventImpl.h" +#include "qmf/SchemaCache.h" +#include "qmf/Query.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/Message.h" +#include "qpid/messaging/Connection.h" +#include "qpid/messaging/Session.h" +#include "qpid/messaging/Sender.h" +#include "qpid/messaging/Address.h" +#include "qpid/management/Buffer.h" +#include "qpid/types/Variant.h" +#include <map> +#include <queue> + +namespace qmf { + class ConsoleSessionImpl : public virtual qpid::RefCounted, public qpid::sys::Runnable { + public: + ~ConsoleSessionImpl(); + + // + // Methods from API handle + // + ConsoleSessionImpl(qpid::messaging::Connection& c, const std::string& o); + void setDomain(const std::string& d) { domain = d; } + void setAgentFilter(const std::string& f); + void open(); + void close(); + bool nextEvent(ConsoleEvent& e, qpid::messaging::Duration t); + int pendingEvents() const; + uint32_t getAgentCount() const; + Agent getAgent(uint32_t i) const; + Agent getConnectedBrokerAgent() const { return connectedBrokerAgent; } + Subscription subscribe(const Query&, const std::string& agentFilter, const std::string& options); + Subscription subscribe(const std::string&, const std::string& agentFilter, const std::string& options); + + protected: + mutable qpid::sys::Mutex lock; + qpid::sys::Condition cond; + qpid::messaging::Connection connection; + qpid::messaging::Session session; + qpid::messaging::Sender directSender; + qpid::messaging::Sender topicSender; + std::string domain; + uint32_t maxAgentAgeMinutes; + bool listenOnDirect; + bool strictSecurity; + Query agentQuery; + bool opened; + std::queue<ConsoleEvent> eventQueue; + qpid::sys::Thread* thread; + bool threadCanceled; + uint64_t lastVisit; + uint64_t lastAgePass; + std::map<std::string, Agent> agents; + Agent connectedBrokerAgent; + bool connectedBrokerInAgentList; + qpid::messaging::Address replyAddress; + std::string directBase; + std::string topicBase; + boost::shared_ptr<SchemaCache> schemaCache; + + void enqueueEvent(const ConsoleEvent&); + void enqueueEventLH(const ConsoleEvent&); + void dispatch(qpid::messaging::Message); + void sendBrokerLocate(); + void sendAgentLocate(); + 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 run(); + + friend class AgentImpl; + }; +} + +#endif diff --git a/qpid/cpp/src/qmf/Data.cpp b/qpid/cpp/src/qmf/Data.cpp new file mode 100644 index 0000000000..c503bab445 --- /dev/null +++ b/qpid/cpp/src/qmf/Data.cpp @@ -0,0 +1,130 @@ +/* + * + * 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/DataImpl.h" +#include "qmf/DataAddrImpl.h" +#include "qmf/SchemaImpl.h" +#include "qmf/SchemaIdImpl.h" +#include "qmf/PrivateImplRef.h" +#include "qmf/SchemaProperty.h" + +using namespace std; +using namespace qmf; +using qpid::types::Variant; + +typedef PrivateImplRef<Data> PI; + +Data::Data(DataImpl* impl) { PI::ctor(*this, impl); } +Data::Data(const Data& s) : qmf::Handle<DataImpl>() { PI::copy(*this, s); } +Data::~Data() { PI::dtor(*this); } +Data& Data::operator=(const Data& s) { return PI::assign(*this, s); } + +Data::Data(const Schema& s) { PI::ctor(*this, new DataImpl(s)); } +void Data::setAddr(const DataAddr& a) { impl->setAddr(a); } +void Data::setProperty(const string& k, const qpid::types::Variant& v) { impl->setProperty(k, v); } +void Data::overwriteProperties(const qpid::types::Variant::Map& m) { impl->overwriteProperties(m); } +bool Data::hasSchema() const { return impl->hasSchema(); } +bool Data::hasAddr() const { return impl->hasAddr(); } +const SchemaId& Data::getSchemaId() const { return impl->getSchemaId(); } +const DataAddr& Data::getAddr() const { return impl->getAddr(); } +const Variant& Data::getProperty(const string& k) const { return impl->getProperty(k); } +const Variant::Map& Data::getProperties() const { return impl->getProperties(); } +bool Data::hasAgent() const { return impl->hasAgent(); } +const Agent& Data::getAgent() const { return impl->getAgent(); } + + +void DataImpl::overwriteProperties(const Variant::Map& m) { + for (Variant::Map::const_iterator iter = m.begin(); iter != m.end(); iter++) + properties[iter->first] = iter->second; +} + +const Variant& DataImpl::getProperty(const string& k) const { + Variant::Map::const_iterator iter = properties.find(k); + if (iter == properties.end()) + throw KeyNotFound(k); + return iter->second; +} + + +DataImpl::DataImpl(const qpid::types::Variant::Map& map, const Agent& a) +{ + Variant::Map::const_iterator iter; + + agent = a; + + iter = map.find("_values"); + if (iter != map.end()) + properties = iter->second.asMap(); + + iter = map.find("_object_id"); + if (iter != map.end()) + dataAddr = DataAddr(new DataAddrImpl(iter->second.asMap())); + + iter = map.find("_schema_id"); + if (iter != map.end()) + schemaId = SchemaId(new SchemaIdImpl(iter->second.asMap())); +} + + +Variant::Map DataImpl::asMap() const +{ + Variant::Map result; + + result["_values"] = properties; + + if (hasAddr()) { + const DataAddrImpl& aImpl(DataAddrImplAccess::get(getAddr())); + result["_object_id"] = aImpl.asMap(); + } + + if (hasSchema()) { + const SchemaIdImpl& sImpl(SchemaIdImplAccess::get(getSchemaId())); + result["_schema_id"] = sImpl.asMap(); + } + + return result; +} + + +void DataImpl::setProperty(const std::string& k, const qpid::types::Variant& v) +{ + if (schema.isValid()) { + // + // If we have a valid schema, make sure that the property is included in the + // schema and that the variant type is compatible with the schema type. + // + if (!SchemaImplAccess::get(schema).isValidProperty(k, v)) + throw QmfException("Property '" + k + "' either not in the schema or value is of incompatible type"); + } + properties[k] = v; +} + + +DataImpl& DataImplAccess::get(Data& item) +{ + return *item.impl; +} + + +const DataImpl& DataImplAccess::get(const Data& item) +{ + return *item.impl; +} diff --git a/qpid/cpp/src/qmf/DataAddr.cpp b/qpid/cpp/src/qmf/DataAddr.cpp new file mode 100644 index 0000000000..fb51d5787f --- /dev/null +++ b/qpid/cpp/src/qmf/DataAddr.cpp @@ -0,0 +1,106 @@ +/* + * + * 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/DataAddrImpl.h" +#include "qmf/PrivateImplRef.h" +#include "qmf/DataAddr.h" +#include <iostream> + +using namespace std; +using namespace qmf; +using qpid::types::Variant; + +typedef PrivateImplRef<DataAddr> PI; + +DataAddr::DataAddr(DataAddrImpl* impl) { PI::ctor(*this, impl); } +DataAddr::DataAddr(const DataAddr& s) : qmf::Handle<DataAddrImpl>() { PI::copy(*this, s); } +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) { 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)); } +const string& DataAddr::getName() const { return impl->getName(); } +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) +{ + return + agentName == other.agentName && + name == other.name && + agentEpoch == other.agentEpoch; +} + + +bool DataAddrImpl::operator<(const DataAddrImpl& other) +{ + if (agentName < other.agentName) return true; + if (agentName > other.agentName) return false; + if (name < other.name) return true; + if (name > other.name) return false; + return agentEpoch < other.agentEpoch; +} + + +DataAddrImpl::DataAddrImpl(const Variant::Map& map) +{ + Variant::Map::const_iterator iter; + + iter = map.find("_agent_name"); + if (iter != map.end()) + agentName = iter->second.asString(); + + iter = map.find("_object_name"); + if (iter != map.end()) + name = iter->second.asString(); + + iter = map.find("_agent_epoch"); + if (iter != map.end()) + agentEpoch = (uint32_t) iter->second.asUint64(); +} + + +Variant::Map DataAddrImpl::asMap() const +{ + Variant::Map result; + + result["_agent_name"] = agentName; + result["_object_name"] = name; + if (agentEpoch > 0) + result["_agent_epoch"] = agentEpoch; + return result; +} + + +DataAddrImpl& DataAddrImplAccess::get(DataAddr& item) +{ + return *item.impl; +} + + +const DataAddrImpl& DataAddrImplAccess::get(const DataAddr& item) +{ + return *item.impl; +} diff --git a/qpid/cpp/src/qmf/DataAddrImpl.h b/qpid/cpp/src/qmf/DataAddrImpl.h new file mode 100644 index 0000000000..3f9cae9453 --- /dev/null +++ b/qpid/cpp/src/qmf/DataAddrImpl.h @@ -0,0 +1,73 @@ +#ifndef _QMF_DATA_ADDR_IMPL_H_ +#define _QMF_DATA_ADDR_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 "qpid/sys/IntegerTypes.h" +#include "qpid/types/Variant.h" +#include "qmf/DataAddr.h" + +namespace qmf { + class DataAddrImpl : public virtual qpid::RefCounted { + public: + // + // Impl-only methods + // + void setName(const std::string& n) { name = n; } + void setAgent(const std::string& n, uint32_t e=0) { agentName = n; agentEpoch = e; } + + // + // Methods from API handle + // + bool operator==(const DataAddrImpl&); + bool operator<(const DataAddrImpl&); + 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) {} + const std::string& getName() const { return name; } + const std::string& getAgentName() const { return agentName; } + uint32_t getAgentEpoch() const { return agentEpoch; } + qpid::types::Variant::Map asMap() const; + + private: + std::string agentName; + std::string name; + uint32_t agentEpoch; + }; + + struct DataAddrImplAccess + { + static DataAddrImpl& get(DataAddr&); + static const DataAddrImpl& get(const DataAddr&); + }; + + struct DataAddrCompare { + bool operator() (const DataAddr& lhs, const DataAddr& rhs) const + { + if (lhs.getName() != rhs.getName()) + return lhs.getName() < rhs.getName(); + return lhs.getAgentName() < rhs.getAgentName(); + } + }; +} + +#endif diff --git a/qpid/cpp/src/qmf/DataImpl.h b/qpid/cpp/src/qmf/DataImpl.h new file mode 100644 index 0000000000..4ac3197da0 --- /dev/null +++ b/qpid/cpp/src/qmf/DataImpl.h @@ -0,0 +1,84 @@ +#ifndef _QMF_DATA_IMPL_H_ +#define _QMF_DATA_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/Data.h" +#include "qmf/SchemaId.h" +#include "qmf/Schema.h" +#include "qmf/DataAddr.h" +#include "qmf/Agent.h" +#include "qmf/AgentSubscription.h" +#include "qpid/types/Variant.h" + +namespace qmf { + class DataImpl : public virtual qpid::RefCounted { + public: + // + // Public impl-only methods + // + DataImpl(const qpid::types::Variant::Map&, const Agent&); + qpid::types::Variant::Map asMap() const; + DataImpl() {} + void addSubscription(boost::shared_ptr<AgentSubscription>); + void delSubscription(uint64_t); + qpid::types::Variant::Map publishSubscription(uint64_t); + const Schema& getSchema() const { return schema; } + + // + // Methods from API handle + // + DataImpl(const Schema& s) : schema(s) {} + void setAddr(const DataAddr& a) { dataAddr = a; } + void setProperty(const std::string& k, const qpid::types::Variant& v); + void overwriteProperties(const qpid::types::Variant::Map& m); + bool hasSchema() const { return schemaId.isValid() || schema.isValid(); } + bool hasAddr() const { return dataAddr.isValid(); } + const SchemaId& getSchemaId() const { if (schema.isValid()) return schema.getSchemaId(); else return schemaId; } + const DataAddr& getAddr() const { return dataAddr; } + const qpid::types::Variant& getProperty(const std::string& k) const; + const qpid::types::Variant::Map& getProperties() const { return properties; } + bool hasAgent() const { return agent.isValid(); } + const Agent& getAgent() const { return agent; } + + private: + struct Subscr { + boost::shared_ptr<AgentSubscription> subscription; + qpid::types::Variant::Map deltas; + }; + std::map<uint64_t, boost::shared_ptr<Subscr> > subscriptions; + + SchemaId schemaId; + Schema schema; + DataAddr dataAddr; + qpid::types::Variant::Map properties; + Agent agent; + }; + + struct DataImplAccess + { + static DataImpl& get(Data&); + static const DataImpl& get(const Data&); + }; +} + +#endif diff --git a/qpid/cpp/src/qmf/Expression.cpp b/qpid/cpp/src/qmf/Expression.cpp new file mode 100644 index 0000000000..7d48678c15 --- /dev/null +++ b/qpid/cpp/src/qmf/Expression.cpp @@ -0,0 +1,441 @@ +/* + * + * 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/exceptions.h" +#include "qmf/Expression.h" +#include <iostream> + +using namespace std; +using namespace qmf; +using namespace qpid::types; + +Expression::Expression(const Variant::List& expr) +{ + static int level(0); + level++; + Variant::List::const_iterator iter(expr.begin()); + string op(iter->asString()); + iter++; + + if (op == "not") logicalOp = LOGICAL_NOT; + else if (op == "and") logicalOp = LOGICAL_AND; + else if (op == "or") logicalOp = LOGICAL_OR; + else { + logicalOp = LOGICAL_ID; + if (op == "eq") boolOp = BOOL_EQ; + else if (op == "ne") boolOp = BOOL_NE; + else if (op == "lt") boolOp = BOOL_LT; + else if (op == "le") boolOp = BOOL_LE; + else if (op == "gt") boolOp = BOOL_GT; + else if (op == "ge") boolOp = BOOL_GE; + else if (op == "re_match") boolOp = BOOL_RE_MATCH; + else if (op == "exists") boolOp = BOOL_EXISTS; + else if (op == "true") boolOp = BOOL_TRUE; + else if (op == "false") boolOp = BOOL_FALSE; + else + throw QmfException("Invalid operator in predicate expression"); + } + + if (logicalOp == LOGICAL_ID) { + switch (boolOp) { + case BOOL_EQ: + case BOOL_NE: + case BOOL_LT: + case BOOL_LE: + case BOOL_GT: + case BOOL_GE: + case BOOL_RE_MATCH: + // + // Binary operator: get two operands. + // + operandCount = 2; + break; + + case BOOL_EXISTS: + // + // Unary operator: get one operand. + // + operandCount = 1; + break; + + case BOOL_TRUE: + case BOOL_FALSE: + // + // Literal operator: no operands. + // + operandCount = 0; + break; + } + + for (int idx = 0; idx < operandCount; idx++) { + if (iter == expr.end()) + throw QmfException("Too few operands for operation: " + op); + if (iter->getType() == VAR_STRING) { + quoted[idx] = false; + operands[idx] = *iter; + } else if (iter->getType() == VAR_LIST) { + const Variant::List& sublist(iter->asList()); + Variant::List::const_iterator subIter(sublist.begin()); + if (subIter != sublist.end() && subIter->asString() == "quote") { + quoted[idx] = true; + subIter++; + if (subIter != sublist.end()) { + operands[idx] = *subIter; + subIter++; + if (subIter != sublist.end()) + throw QmfException("Extra tokens at end of 'quote'"); + } + } else + throw QmfException("Expected '[quote, <token>]'"); + } else + throw QmfException("Expected string or list as operand for: " + op); + iter++; + } + + if (iter != expr.end()) + throw QmfException("Too many operands for operation: " + op); + + } else { + // + // This is a logical expression, collect sub-expressions + // + while (iter != expr.end()) { + if (iter->getType() != VAR_LIST) + throw QmfException("Operands of " + op + " must be lists"); + expressionList.push_back(boost::shared_ptr<Expression>(new Expression(iter->asList()))); + iter++; + } + } + level--; +} + + +bool Expression::evaluate(const Variant::Map& data) const +{ + list<boost::shared_ptr<Expression> >::const_iterator iter; + + switch (logicalOp) { + case LOGICAL_ID: + return boolEval(data); + + case LOGICAL_NOT: + for (iter = expressionList.begin(); iter != expressionList.end(); iter++) + if ((*iter)->evaluate(data)) + return false; + return true; + + case LOGICAL_AND: + for (iter = expressionList.begin(); iter != expressionList.end(); iter++) + if (!(*iter)->evaluate(data)) + return false; + return true; + + case LOGICAL_OR: + for (iter = expressionList.begin(); iter != expressionList.end(); iter++) + if ((*iter)->evaluate(data)) + return true; + return false; + } + + return false; +} + + +bool Expression::boolEval(const Variant::Map& data) const +{ + Variant val[2]; + bool exists[2]; + + for (int idx = 0; idx < operandCount; idx++) { + if (quoted[idx]) { + exists[idx] = true; + val[idx] = operands[idx]; + } else { + Variant::Map::const_iterator mIter(data.find(operands[idx].asString())); + if (mIter == data.end()) { + exists[idx] = false; + } else { + exists[idx] = true; + val[idx] = mIter->second; + } + } + } + + switch (boolOp) { + case BOOL_EQ: return (exists[0] && exists[1] && (val[0].asString() == val[1].asString())); + case BOOL_NE: return (exists[0] && exists[1] && (val[0].asString() != val[1].asString())); + case BOOL_LT: return (exists[0] && exists[1] && lessThan(val[0], val[1])); + case BOOL_LE: return (exists[0] && exists[1] && lessEqual(val[0], val[1])); + case BOOL_GT: return (exists[0] && exists[1] && greaterThan(val[0], val[1])); + case BOOL_GE: return (exists[0] && exists[1] && greaterEqual(val[0], val[1])); + case BOOL_RE_MATCH: return false; // TODO + case BOOL_EXISTS: return exists[0]; + case BOOL_TRUE: return true; + case BOOL_FALSE: return false; + } + + return false; +} + +bool Expression::lessThan(const Variant& left, const Variant& right) const +{ + switch (left.getType()) { + case VAR_UINT8: case VAR_UINT16: case VAR_UINT32: case VAR_UINT64: + case VAR_INT8: case VAR_INT16: case VAR_INT32: case VAR_INT64: + switch (right.getType()) { + case VAR_UINT8: case VAR_UINT16: case VAR_UINT32: case VAR_UINT64: + case VAR_INT8: case VAR_INT16: case VAR_INT32: case VAR_INT64: + return left.asInt64() < right.asInt64(); + case VAR_STRING: + try { + return left.asInt64() < right.asInt64(); + } catch (std::exception&) {} + break; + default: + break; + } + break; + + case VAR_FLOAT: case VAR_DOUBLE: + switch (right.getType()) { + case VAR_FLOAT: case VAR_DOUBLE: + return left.asDouble() < right.asDouble(); + case VAR_STRING: + try { + return left.asDouble() < right.asDouble(); + } catch (std::exception&) {} + break; + default: + break; + } + break; + + case VAR_STRING: + switch (right.getType()) { + case VAR_UINT8: case VAR_UINT16: case VAR_UINT32: case VAR_UINT64: + case VAR_INT8: case VAR_INT16: case VAR_INT32: case VAR_INT64: + try { + return left.asInt64() < right.asInt64(); + } catch (std::exception&) {} + break; + + case VAR_FLOAT: case VAR_DOUBLE: + try { + return left.asDouble() < right.asDouble(); + } catch (std::exception&) {} + break; + + case VAR_STRING: + return left.asString() < right.asString(); + default: + break; + } + default: + break; + } + + return false; +} + + +bool Expression::lessEqual(const Variant& left, const Variant& right) const +{ + switch (left.getType()) { + case VAR_UINT8: case VAR_UINT16: case VAR_UINT32: case VAR_UINT64: + case VAR_INT8: case VAR_INT16: case VAR_INT32: case VAR_INT64: + switch (right.getType()) { + case VAR_UINT8: case VAR_UINT16: case VAR_UINT32: case VAR_UINT64: + case VAR_INT8: case VAR_INT16: case VAR_INT32: case VAR_INT64: + return left.asInt64() <= right.asInt64(); + case VAR_STRING: + try { + return left.asInt64() <= right.asInt64(); + } catch (std::exception&) {} + break; + default: + break; + } + break; + + case VAR_FLOAT: case VAR_DOUBLE: + switch (right.getType()) { + case VAR_FLOAT: case VAR_DOUBLE: + return left.asDouble() <= right.asDouble(); + case VAR_STRING: + try { + return left.asDouble() <= right.asDouble(); + } catch (std::exception&) {} + break; + default: + break; + } + break; + + case VAR_STRING: + switch (right.getType()) { + case VAR_UINT8: case VAR_UINT16: case VAR_UINT32: case VAR_UINT64: + case VAR_INT8: case VAR_INT16: case VAR_INT32: case VAR_INT64: + try { + return left.asInt64() <= right.asInt64(); + } catch (std::exception&) {} + break; + + case VAR_FLOAT: case VAR_DOUBLE: + try { + return left.asDouble() <= right.asDouble(); + } catch (std::exception&) {} + break; + + case VAR_STRING: + return left.asString() <= right.asString(); + default: + break; + } + default: + break; + } + + return false; +} + + +bool Expression::greaterThan(const Variant& left, const Variant& right) const +{ + switch (left.getType()) { + case VAR_UINT8: case VAR_UINT16: case VAR_UINT32: case VAR_UINT64: + case VAR_INT8: case VAR_INT16: case VAR_INT32: case VAR_INT64: + switch (right.getType()) { + case VAR_UINT8: case VAR_UINT16: case VAR_UINT32: case VAR_UINT64: + case VAR_INT8: case VAR_INT16: case VAR_INT32: case VAR_INT64: + return left.asInt64() > right.asInt64(); + case VAR_STRING: + try { + return left.asInt64() > right.asInt64(); + } catch (std::exception&) {} + break; + default: + break; + } + break; + + case VAR_FLOAT: case VAR_DOUBLE: + switch (right.getType()) { + case VAR_FLOAT: case VAR_DOUBLE: + return left.asDouble() > right.asDouble(); + case VAR_STRING: + try { + return left.asDouble() > right.asDouble(); + } catch (std::exception&) {} + break; + default: + break; + } + break; + + case VAR_STRING: + switch (right.getType()) { + case VAR_UINT8: case VAR_UINT16: case VAR_UINT32: case VAR_UINT64: + case VAR_INT8: case VAR_INT16: case VAR_INT32: case VAR_INT64: + try { + return left.asInt64() > right.asInt64(); + } catch (std::exception&) {} + break; + + case VAR_FLOAT: case VAR_DOUBLE: + try { + return left.asDouble() > right.asDouble(); + } catch (std::exception&) {} + break; + + case VAR_STRING: + return left.asString() > right.asString(); + default: + break; + } + default: + break; + } + + return false; +} + + +bool Expression::greaterEqual(const Variant& left, const Variant& right) const +{ + switch (left.getType()) { + case VAR_UINT8: case VAR_UINT16: case VAR_UINT32: case VAR_UINT64: + case VAR_INT8: case VAR_INT16: case VAR_INT32: case VAR_INT64: + switch (right.getType()) { + case VAR_UINT8: case VAR_UINT16: case VAR_UINT32: case VAR_UINT64: + case VAR_INT8: case VAR_INT16: case VAR_INT32: case VAR_INT64: + return left.asInt64() >= right.asInt64(); + case VAR_STRING: + try { + return left.asInt64() >= right.asInt64(); + } catch (std::exception&) {} + break; + default: + break; + } + break; + + case VAR_FLOAT: case VAR_DOUBLE: + switch (right.getType()) { + case VAR_FLOAT: case VAR_DOUBLE: + return left.asDouble() >= right.asDouble(); + case VAR_STRING: + try { + return left.asDouble() >= right.asDouble(); + } catch (std::exception&) {} + break; + default: + break; + } + break; + + case VAR_STRING: + switch (right.getType()) { + case VAR_UINT8: case VAR_UINT16: case VAR_UINT32: case VAR_UINT64: + case VAR_INT8: case VAR_INT16: case VAR_INT32: case VAR_INT64: + try { + return left.asInt64() >= right.asInt64(); + } catch (std::exception&) {} + break; + + case VAR_FLOAT: case VAR_DOUBLE: + try { + return left.asDouble() >= right.asDouble(); + } catch (std::exception&) {} + break; + + case VAR_STRING: + return left.asString() >= right.asString(); + default: + break; + } + default: + break; + } + + return false; +} + + diff --git a/qpid/cpp/src/qmf/Expression.h b/qpid/cpp/src/qmf/Expression.h new file mode 100644 index 0000000000..6fbfdbc4ba --- /dev/null +++ b/qpid/cpp/src/qmf/Expression.h @@ -0,0 +1,73 @@ +#ifndef _QMF_EXPRESSION_H_ +#define _QMF_EXPRESSION_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/types/Variant.h" +#include <string> +#include <list> +#include <boost/shared_ptr.hpp> + +namespace qmf { + + enum LogicalOp { + LOGICAL_ID = 1, + LOGICAL_NOT = 2, + LOGICAL_AND = 3, + LOGICAL_OR = 4 + }; + + enum BooleanOp { + BOOL_EQ = 1, + BOOL_NE = 2, + BOOL_LT = 3, + BOOL_LE = 4, + BOOL_GT = 5, + BOOL_GE = 6, + BOOL_RE_MATCH = 7, + BOOL_EXISTS = 8, + BOOL_TRUE = 9, + BOOL_FALSE = 10 + }; + + class Expression { + public: + Expression(const qpid::types::Variant::List& expr); + bool evaluate(const qpid::types::Variant::Map& data) const; + private: + LogicalOp logicalOp; + BooleanOp boolOp; + int operandCount; + qpid::types::Variant operands[2]; + bool quoted[2]; + std::list<boost::shared_ptr<Expression> > expressionList; + + bool boolEval(const qpid::types::Variant::Map& data) const; + bool lessThan(const qpid::types::Variant& left, const qpid::types::Variant& right) const; + bool lessEqual(const qpid::types::Variant& left, const qpid::types::Variant& right) const; + bool greaterThan(const qpid::types::Variant& left, const qpid::types::Variant& right) const; + bool greaterEqual(const qpid::types::Variant& left, const qpid::types::Variant& right) const; + }; + +} + +#endif + diff --git a/qpid/cpp/src/qmf/Hash.cpp b/qpid/cpp/src/qmf/Hash.cpp new file mode 100644 index 0000000000..86738dda2f --- /dev/null +++ b/qpid/cpp/src/qmf/Hash.cpp @@ -0,0 +1,45 @@ +/* + * + * 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/Hash.h" + +using namespace qmf; + +Hash::Hash() +{ + data[0] = 0x5A5A5A5A5A5A5A5ALL; + data[1] = 0x5A5A5A5A5A5A5A5ALL; +} + +void Hash::update(const char* s, uint32_t len) +{ + uint64_t* first = &data[0]; + uint64_t* second = &data[1]; + + for (uint32_t idx = 0; idx < len; idx++) { + uint64_t recycle = ((*second & 0xff00000000000000LL) >> 56); + *second = *second << 8; + *second |= ((*first & 0xFF00000000000000LL) >> 56); + *first = *first << 8; + *first = *first + (uint64_t) s[idx] + recycle; + } +} + diff --git a/qpid/cpp/src/qmf/Hash.h b/qpid/cpp/src/qmf/Hash.h new file mode 100644 index 0000000000..e1eff84117 --- /dev/null +++ b/qpid/cpp/src/qmf/Hash.h @@ -0,0 +1,44 @@ +#ifndef QMF_HASH_H +#define QMF_HASH_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/IntegerTypes.h" +#include "qpid/types/Uuid.h" +#include <string> + +namespace qmf { + class Hash { + public: + Hash(); + qpid::types::Uuid asUuid() const { return qpid::types::Uuid((unsigned char*) data); } + void update(const char* s, uint32_t len); + void update(uint8_t v) { update((char*) &v, sizeof(v)); } + void update(uint32_t v) { update((char*) &v, sizeof(v)); } + void update(const std::string& v) { update(const_cast<char*>(v.c_str()), v.size()); } + void update(bool v) { update(uint8_t(v ? 1 : 0)); } + + private: + uint64_t data[2]; + }; +} + +#endif diff --git a/qpid/cpp/src/qmf/PrivateImplRef.h b/qpid/cpp/src/qmf/PrivateImplRef.h new file mode 100644 index 0000000000..960cbb2e09 --- /dev/null +++ b/qpid/cpp/src/qmf/PrivateImplRef.h @@ -0,0 +1,93 @@ +#ifndef QMF_PRIVATEIMPL_H +#define QMF_PRIVATEIMPL_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/ImportExport.h" +#include "qpid/RefCounted.h" +#include <boost/intrusive_ptr.hpp> + +namespace qmf { + +/** + * Helper class to implement a class with a private, reference counted + * implementation and reference semantics. + * + * Such classes are used in the public API to hide implementation, they + * should. Example of use: + * + * === Foo.h + * + * template <class T> PrivateImplRef; + * class FooImpl; + * + * Foo : public Handle<FooImpl> { + * public: + * Foo(FooImpl* = 0); + * Foo(const Foo&); + * ~Foo(); + * Foo& operator=(const Foo&); + * + * int fooDo(); // and other Foo functions... + * + * private: + * typedef FooImpl Impl; + * Impl* impl; + * friend class PrivateImplRef<Foo>; + * + * === Foo.cpp + * + * typedef PrivateImplRef<Foo> PI; + * Foo::Foo(FooImpl* p) { PI::ctor(*this, p); } + * Foo::Foo(const Foo& c) : Handle<FooImpl>() { PI::copy(*this, c); } + * Foo::~Foo() { PI::dtor(*this); } + * Foo& Foo::operator=(const Foo& c) { return PI::assign(*this, c); } + * + * int foo::fooDo() { return impl->fooDo(); } + * + */ +template <class T> class PrivateImplRef { + public: + typedef typename T::Impl Impl; + typedef boost::intrusive_ptr<Impl> intrusive_ptr; + + /** Get the implementation pointer from a handle */ + static intrusive_ptr get(const T& t) { return intrusive_ptr(t.impl); } + + /** Set the implementation pointer in a handle */ + static void set(T& t, const intrusive_ptr& p) { + if (t.impl == p) return; + if (t.impl) boost::intrusive_ptr_release(t.impl); + t.impl = p.get(); + if (t.impl) boost::intrusive_ptr_add_ref(t.impl); + } + + // Helper functions to implement the ctor, dtor, copy, assign + static void ctor(T& t, Impl* p) { t.impl = p; if (p) boost::intrusive_ptr_add_ref(p); } + static void copy(T& t, const T& x) { if (&t == &x) return; t.impl = 0; assign(t, x); } + static void dtor(T& t) { if(t.impl) boost::intrusive_ptr_release(t.impl); } + static T& assign(T& t, const T& x) { set(t, get(x)); return t;} +}; + +} // namespace qmf + +#endif /*!QMF_PRIVATEIMPL_H*/ diff --git a/qpid/cpp/src/qmf/Query.cpp b/qpid/cpp/src/qmf/Query.cpp new file mode 100644 index 0000000000..ee8ca38e59 --- /dev/null +++ b/qpid/cpp/src/qmf/Query.cpp @@ -0,0 +1,153 @@ +/* + * + * 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/PrivateImplRef.h" +#include "qmf/exceptions.h" +#include "qmf/QueryImpl.h" +#include "qmf/DataAddrImpl.h" +#include "qmf/SchemaIdImpl.h" +#include "qpid/messaging/AddressParser.h" + +using namespace std; +using namespace qmf; +using qpid::types::Variant; + +typedef PrivateImplRef<Query> PI; + +Query::Query(QueryImpl* impl) { PI::ctor(*this, impl); } +Query::Query(const Query& s) : qmf::Handle<QueryImpl>() { PI::copy(*this, s); } +Query::~Query() { PI::dtor(*this); } +Query& Query::operator=(const Query& s) { return PI::assign(*this, s); } + +Query::Query(QueryTarget t, const string& pr) { PI::ctor(*this, new QueryImpl(t, pr)); } +Query::Query(QueryTarget t, const string& c, const string& p, const string& pr) { PI::ctor(*this, new QueryImpl(t, c, p, pr)); } +Query::Query(QueryTarget t, const SchemaId& s, const string& pr) { PI::ctor(*this, new QueryImpl(t, s, pr)); } +Query::Query(const DataAddr& a) { PI::ctor(*this, new QueryImpl(a)); } + +QueryTarget Query::getTarget() const { return impl->getTarget(); } +const DataAddr& Query::getDataAddr() const { return impl->getDataAddr(); } +const SchemaId& Query::getSchemaId() const { return impl->getSchemaId(); } +void Query::setPredicate(const Variant::List& pr) { impl->setPredicate(pr); } +const Variant::List& Query::getPredicate() const { return impl->getPredicate(); } +bool Query::matchesPredicate(const qpid::types::Variant::Map& map) const { return impl->matchesPredicate(map); } + + +QueryImpl::QueryImpl(const Variant::Map& map) : predicateCompiled(false) +{ + Variant::Map::const_iterator iter; + + iter = map.find("_what"); + if (iter == map.end()) + throw QmfException("Query missing _what element"); + + const string& targetString(iter->second.asString()); + if (targetString == "OBJECT") target = QUERY_OBJECT; + else if (targetString == "OBJECT_ID") target = QUERY_OBJECT_ID; + else if (targetString == "SCHEMA") target = QUERY_SCHEMA; + else if (targetString == "SCHEMA_ID") target = QUERY_SCHEMA_ID; + else + throw QmfException("Query with invalid _what value: " + targetString); + + iter = map.find("_object_id"); + if (iter != map.end()) { + auto_ptr<DataAddrImpl> addrImpl(new DataAddrImpl(iter->second.asMap())); + dataAddr = DataAddr(addrImpl.release()); + } + + iter = map.find("_schema_id"); + if (iter != map.end()) { + auto_ptr<SchemaIdImpl> sidImpl(new SchemaIdImpl(iter->second.asMap())); + schemaId = SchemaId(sidImpl.release()); + } + + iter = map.find("_where"); + if (iter != map.end()) + predicate = iter->second.asList(); +} + + +Variant::Map QueryImpl::asMap() const +{ + Variant::Map map; + string targetString; + + switch (target) { + case QUERY_OBJECT : targetString = "OBJECT"; break; + case QUERY_OBJECT_ID : targetString = "OBJECT_ID"; break; + case QUERY_SCHEMA : targetString = "SCHEMA"; break; + case QUERY_SCHEMA_ID : targetString = "SCHEMA_ID"; break; + } + + map["_what"] = targetString; + + if (dataAddr.isValid()) + map["_object_id"] = DataAddrImplAccess::get(dataAddr).asMap(); + + if (schemaId.isValid()) + map["_schema_id"] = SchemaIdImplAccess::get(schemaId).asMap(); + + if (!predicate.empty()) + map["_where"] = predicate; + + return map; +} + + +bool QueryImpl::matchesPredicate(const qpid::types::Variant::Map& data) const +{ + if (predicate.empty()) + return true; + + if (!predicateCompiled) { + expression.reset(new Expression(predicate)); + predicateCompiled = true; + } + + return expression->evaluate(data); +} + + +void QueryImpl::parsePredicate(const string& pred) +{ + if (pred.empty()) + return; + + if (pred[0] == '[') { + // + // Parse this as an AddressParser list. + // + qpid::messaging::AddressParser parser(pred); + parser.parseList(predicate); + } else + throw QmfException("Invalid predicate format"); +} + + +QueryImpl& QueryImplAccess::get(Query& item) +{ + return *item.impl; +} + + +const QueryImpl& QueryImplAccess::get(const Query& item) +{ + return *item.impl; +} diff --git a/qpid/cpp/src/qmf/QueryImpl.h b/qpid/cpp/src/qmf/QueryImpl.h new file mode 100644 index 0000000000..27ec427684 --- /dev/null +++ b/qpid/cpp/src/qmf/QueryImpl.h @@ -0,0 +1,77 @@ +#ifndef _QMF_QUERY_IMPL_H_ +#define _QMF_QUERY_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/Query.h" +#include "qmf/DataAddr.h" +#include "qmf/SchemaId.h" +#include "qmf/Expression.h" +#include "qpid/types/Variant.h" +#include <boost/shared_ptr.hpp> + +namespace qmf { + class QueryImpl : public virtual qpid::RefCounted { + public: + // + // Public impl-only methods + // + QueryImpl(const qpid::types::Variant::Map&); + qpid::types::Variant::Map asMap() const; + + // + // Methods from API handle + // + QueryImpl(QueryTarget t, const std::string& pr) : target(t), predicateCompiled(false) { parsePredicate(pr); } + QueryImpl(QueryTarget t, const std::string& c, const std::string& p, const std::string& pr) : + target(t), schemaId(SCHEMA_TYPE_DATA, p, c), predicateCompiled(false) { parsePredicate(pr); } + QueryImpl(QueryTarget t, const SchemaId& s, const std::string& pr) : + target(t), schemaId(s), predicateCompiled(false) { parsePredicate(pr); } + QueryImpl(const DataAddr& a) : target(QUERY_OBJECT), dataAddr(a), predicateCompiled(false) {} + + QueryTarget getTarget() const { return target; } + const DataAddr& getDataAddr() const { return dataAddr; } + const SchemaId& getSchemaId() const { return schemaId; } + void setPredicate(const qpid::types::Variant::List& pr) { predicate = pr; } + const qpid::types::Variant::List& getPredicate() const { return predicate; } + bool matchesPredicate(const qpid::types::Variant::Map& map) const; + + private: + QueryTarget target; + SchemaId schemaId; + DataAddr dataAddr; + qpid::types::Variant::List predicate; + mutable bool predicateCompiled; + mutable boost::shared_ptr<Expression> expression; + + void parsePredicate(const std::string& s); + }; + + struct QueryImplAccess + { + static QueryImpl& get(Query&); + static const QueryImpl& get(const Query&); + }; +} + +#endif + diff --git a/qpid/cpp/src/qmf/Schema.cpp b/qpid/cpp/src/qmf/Schema.cpp new file mode 100644 index 0000000000..872aad724c --- /dev/null +++ b/qpid/cpp/src/qmf/Schema.cpp @@ -0,0 +1,358 @@ +/* + * + * 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/SchemaImpl.h" +#include "qmf/PrivateImplRef.h" +#include "qmf/exceptions.h" +#include "qmf/SchemaTypes.h" +#include "qmf/SchemaIdImpl.h" +#include "qmf/SchemaPropertyImpl.h" +#include "qmf/SchemaMethodImpl.h" +#include "qmf/Hash.h" +#include "qpid/log/Statement.h" +#include "qpid/management/Buffer.h" +#include <list> + +using namespace std; +using qpid::types::Variant; +using namespace qmf; + +typedef PrivateImplRef<Schema> PI; + +Schema::Schema(SchemaImpl* impl) { PI::ctor(*this, impl); } +Schema::Schema(const Schema& s) : qmf::Handle<SchemaImpl>() { PI::copy(*this, s); } +Schema::~Schema() { PI::dtor(*this); } +Schema& Schema::operator=(const Schema& s) { return PI::assign(*this, s); } + +Schema::Schema(int t, const string& p, const string& c) { PI::ctor(*this, new SchemaImpl(t, p, c)); } +const SchemaId& Schema::getSchemaId() const { return impl->getSchemaId(); } +void Schema::finalize() { impl->finalize(); } +bool Schema::isFinalized() const { return impl->isFinalized(); } +void Schema::addProperty(const SchemaProperty& p) { impl->addProperty(p); } +void Schema::addMethod(const SchemaMethod& m) { impl->addMethod(m); } +void Schema::setDesc(const string& d) { impl->setDesc(d); } +const string& Schema::getDesc() const { return impl->getDesc(); } +void Schema::setDefaultSeverity(int s) { impl->setDefaultSeverity(s); } +int Schema::getDefaultSeverity() const { return impl->getDefaultSeverity(); } +uint32_t Schema::getPropertyCount() const { return impl->getPropertyCount(); } +SchemaProperty Schema::getProperty(uint32_t i) const { return impl->getProperty(i); } +uint32_t Schema::getMethodCount() const { return impl->getMethodCount(); } +SchemaMethod Schema::getMethod(uint32_t i) const { return impl->getMethod(i); } + +//======================================================================================== +// Impl Method Bodies +//======================================================================================== + +SchemaImpl::SchemaImpl(const Variant::Map& map) : finalized(false) +{ + Variant::Map::const_iterator iter; + Variant::List::const_iterator lIter; + + iter = map.find("_schema_id"); + if (iter == map.end()) + throw QmfException("Schema map missing _schema_id element"); + schemaId = SchemaId(new SchemaIdImpl(iter->second.asMap())); + + iter = map.find("_desc"); + if (iter != map.end()) + description = iter->second.asString(); + + iter = map.find("_default_severity"); + if (iter != map.end()) + defaultSeverity = int(iter->second.asUint32()); + + iter = map.find("_properties"); + if (iter != map.end()) { + const Variant::List& props(iter->second.asList()); + for (lIter = props.begin(); lIter != props.end(); lIter++) + addProperty(SchemaProperty(new SchemaPropertyImpl(lIter->asMap()))); + } + + iter = map.find("_methods"); + if (iter != map.end()) { + const Variant::List& meths(iter->second.asList()); + for (lIter = meths.begin(); lIter != meths.end(); lIter++) + addMethod(SchemaMethod(new SchemaMethodImpl(lIter->asMap()))); + } + + finalized = true; +} + + +Variant::Map SchemaImpl::asMap() const +{ + Variant::Map map; + Variant::List propList; + Variant::List methList; + + checkNotFinal(); + + map["_schema_id"] = SchemaIdImplAccess::get(schemaId).asMap(); + if (!description.empty()) + map["_desc"] = description; + if (schemaId.getType() == SCHEMA_TYPE_EVENT) + map["_default_severity"] = uint32_t(defaultSeverity); + + for (list<SchemaProperty>::const_iterator pIter = properties.begin(); pIter != properties.end(); pIter++) + propList.push_back(SchemaPropertyImplAccess::get(*pIter).asMap()); + + for (list<SchemaMethod>::const_iterator mIter = methods.begin(); mIter != methods.end(); mIter++) + methList.push_back(SchemaMethodImplAccess::get(*mIter).asMap()); + + map["_properties"] = propList; + map["_methods"] = methList; + return map; +} + + +SchemaImpl::SchemaImpl(qpid::management::Buffer& buffer) : finalized(false) +{ + int schemaType; + string packageName; + string className; + uint8_t hash[16]; + + schemaType = int(buffer.getOctet()); + buffer.getShortString(packageName); + buffer.getShortString(className); + buffer.getBin128(hash); + schemaId = SchemaId(schemaType, packageName, className); + schemaId.setHash(qpid::types::Uuid(hash)); + + if (schemaType == SCHEMA_TYPE_DATA) { + uint16_t propCount(buffer.getShort()); + uint16_t statCount(buffer.getShort()); + uint16_t methCount(buffer.getShort()); + for (uint16_t idx = 0; idx < propCount + statCount; idx++) + addProperty(new SchemaPropertyImpl(buffer)); + for (uint16_t idx = 0; idx < methCount; idx++) + addMethod(new SchemaMethodImpl(buffer)); + } + + finalized = true; +} + + +string SchemaImpl::asV1Content(uint32_t sequence) const +{ +#define RAW_BUF_SIZE 65536 + char rawBuf[RAW_BUF_SIZE]; + qpid::management::Buffer buffer(rawBuf, RAW_BUF_SIZE); + + // + // Encode the QMFv1 Header + // + buffer.putOctet('A'); + buffer.putOctet('M'); + buffer.putOctet('2'); + buffer.putOctet('s'); + buffer.putLong(sequence); + + // + // Encode the common schema information + // + buffer.putOctet(uint8_t(schemaId.getType())); + buffer.putShortString(schemaId.getPackageName()); + buffer.putShortString(schemaId.getName()); + buffer.putBin128(schemaId.getHash().data()); + + if (schemaId.getType() == SCHEMA_TYPE_DATA) { + buffer.putShort(properties.size()); + buffer.putShort(0); + buffer.putShort(methods.size()); + for (list<SchemaProperty>::const_iterator pIter = properties.begin(); pIter != properties.end(); pIter++) + SchemaPropertyImplAccess::get(*pIter).encodeV1(buffer, false, false); + for (list<SchemaMethod>::const_iterator mIter = methods.begin(); mIter != methods.end(); mIter++) + SchemaMethodImplAccess::get(*mIter).encodeV1(buffer); + } else { + buffer.putShort(properties.size()); + for (list<SchemaProperty>::const_iterator pIter = properties.begin(); pIter != properties.end(); pIter++) + SchemaPropertyImplAccess::get(*pIter).encodeV1(buffer, true, false); + } + + return string(rawBuf, buffer.getPosition()); +} + + +bool SchemaImpl::isValidProperty(const std::string& k, const Variant& v) const +{ + for (list<SchemaProperty>::const_iterator iter = properties.begin(); iter != properties.end(); iter++) + if (iter->getName() == k) + return (isCompatibleType(iter->getType(), v.getType())); + return false; +} + + +bool SchemaImpl::isValidMethodInArg(const std::string& m, const std::string& k, const Variant& v) const +{ + for (list<SchemaMethod>::const_iterator mIter = methods.begin(); mIter != methods.end(); mIter++) { + if (mIter->getName() == m) { + uint32_t count(mIter->getArgumentCount()); + for (uint32_t i = 0; i < count; i++) { + const SchemaProperty prop(mIter->getArgument(i)); + if (prop.getName() == k) { + if (prop.getDirection() == DIR_IN || prop.getDirection() == DIR_IN_OUT) + return (isCompatibleType(prop.getType(), v.getType())); + else + return false; + } + } + } + } + return false; +} + + +bool SchemaImpl::isValidMethodOutArg(const std::string& m, const std::string& k, const Variant& v) const +{ + for (list<SchemaMethod>::const_iterator mIter = methods.begin(); mIter != methods.end(); mIter++) { + if (mIter->getName() == m) { + uint32_t count(mIter->getArgumentCount()); + for (uint32_t i = 0; i < count; i++) { + const SchemaProperty prop(mIter->getArgument(i)); + if (prop.getName() == k) { + if (prop.getDirection() == DIR_OUT || prop.getDirection() == DIR_IN_OUT) + return (isCompatibleType(prop.getType(), v.getType())); + else + return false; + } + } + } + } + return false; +} + + +void SchemaImpl::finalize() +{ + Hash hash; + + hash.update((uint8_t) schemaId.getType()); + hash.update(schemaId.getPackageName()); + hash.update(schemaId.getName()); + + for (list<SchemaProperty>::const_iterator pIter = properties.begin(); pIter != properties.end(); pIter++) + SchemaPropertyImplAccess::get(*pIter).updateHash(hash); + for (list<SchemaMethod>::const_iterator mIter = methods.begin(); mIter != methods.end(); mIter++) + SchemaMethodImplAccess::get(*mIter).updateHash(hash); + + schemaId.setHash(hash.asUuid()); + QPID_LOG(debug, "Schema Finalized: " << schemaId.getPackageName() << ":" << schemaId.getName() << ":" << + schemaId.getHash()); + + finalized = true; +} + + +SchemaProperty SchemaImpl::getProperty(uint32_t i) const +{ + uint32_t count = 0; + for (list<SchemaProperty>::const_iterator iter = properties.begin(); iter != properties.end(); iter++) + if (count++ == i) + return *iter; + throw IndexOutOfRange(); +} + + +SchemaMethod SchemaImpl::getMethod(uint32_t i) const +{ + uint32_t count = 0; + for (list<SchemaMethod>::const_iterator iter = methods.begin(); iter != methods.end(); iter++) + if (count++ == i) + return *iter; + throw IndexOutOfRange(); +} + +void SchemaImpl::checkFinal() const +{ + if (finalized) + throw QmfException("Modification of a finalized schema is forbidden"); +} + + +void SchemaImpl::checkNotFinal() const +{ + if (!finalized) + throw QmfException("Schema is not yet finalized/registered"); +} + + +bool SchemaImpl::isCompatibleType(int qmfType, qpid::types::VariantType qpidType) const +{ + bool typeValid(false); + + switch (qpidType) { + case qpid::types::VAR_VOID: + if (qmfType == SCHEMA_DATA_VOID) + typeValid = true; + break; + case qpid::types::VAR_BOOL: + if (qmfType == SCHEMA_DATA_BOOL) + typeValid = true; + break; + case qpid::types::VAR_UINT8: + case qpid::types::VAR_UINT16: + case qpid::types::VAR_UINT32: + case qpid::types::VAR_UINT64: + case qpid::types::VAR_INT8: + case qpid::types::VAR_INT16: + case qpid::types::VAR_INT32: + case qpid::types::VAR_INT64: + if (qmfType == SCHEMA_DATA_INT) + typeValid = true; + break; + case qpid::types::VAR_FLOAT: + case qpid::types::VAR_DOUBLE: + if (qmfType == SCHEMA_DATA_FLOAT) + typeValid = true; + break; + case qpid::types::VAR_STRING: + if (qmfType == SCHEMA_DATA_STRING) + typeValid = true; + break; + case qpid::types::VAR_MAP: + if (qmfType == SCHEMA_DATA_MAP) + typeValid = true; + break; + case qpid::types::VAR_LIST: + if (qmfType == SCHEMA_DATA_LIST) + typeValid = true; + break; + case qpid::types::VAR_UUID: + if (qmfType == SCHEMA_DATA_UUID) + typeValid = true; + break; + } + + return typeValid; +} + + +SchemaImpl& SchemaImplAccess::get(Schema& item) +{ + return *item.impl; +} + + +const SchemaImpl& SchemaImplAccess::get(const Schema& item) +{ + return *item.impl; +} diff --git a/qpid/cpp/src/qmf/SchemaCache.cpp b/qpid/cpp/src/qmf/SchemaCache.cpp new file mode 100644 index 0000000000..74ca4044fd --- /dev/null +++ b/qpid/cpp/src/qmf/SchemaCache.cpp @@ -0,0 +1,91 @@ +/* + * + * 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/SchemaCache.h" +#include "qmf/exceptions.h" + +using namespace std; +using namespace qmf; + +bool SchemaCache::declareSchemaId(const SchemaId& id) +{ + qpid::sys::Mutex::ScopedLock l(lock); + SchemaMap::const_iterator iter = schemata.find(id); + if (iter == schemata.end()) { + schemata[id] = Schema(); + return false; + } + return true; +} + + +void SchemaCache::declareSchema(const Schema& schema) +{ + qpid::sys::Mutex::ScopedLock l(lock); + SchemaMap::const_iterator iter = schemata.find(schema.getSchemaId()); + if (iter == schemata.end() || !iter->second.isValid()) { + schemata[schema.getSchemaId()] = schema; + + // + // If there are any threads blocking in SchemaCache::getSchema waiting for + // this schema, unblock them all now. + // + CondMap::iterator cIter = conditions.find(schema.getSchemaId()); + if (cIter != conditions.end()) + cIter->second->notifyAll(); + } +} + + +bool SchemaCache::haveSchema(const SchemaId& id) const +{ + qpid::sys::Mutex::ScopedLock l(lock); + SchemaMap::const_iterator iter = schemata.find(id); + return iter != schemata.end() && iter->second.isValid(); +} + + +const Schema& SchemaCache::getSchema(const SchemaId& id, qpid::messaging::Duration timeout) const +{ + qpid::sys::Mutex::ScopedLock l(lock); + SchemaMap::const_iterator iter = schemata.find(id); + if (iter != schemata.end() && iter->second.isValid()) + return iter->second; + + // + // The desired schema is not in the cache. Assume that the caller knows this and has + // sent a schema request to the remote agent and now wishes to wait until the schema + // information arrives. + // + CondMap::iterator cIter = conditions.find(id); + if (cIter == conditions.end()) + conditions[id] = boost::shared_ptr<qpid::sys::Condition>(new qpid::sys::Condition()); + + uint64_t milliseconds = timeout.getMilliseconds(); + conditions[id]->wait(lock, qpid::sys::AbsTime(qpid::sys::now(), + qpid::sys::Duration(milliseconds * qpid::sys::TIME_MSEC))); + iter = schemata.find(id); + if (iter != schemata.end() && iter->second.isValid()) + return iter->second; + + throw QmfException("Schema lookup timed out"); +} + diff --git a/qpid/cpp/src/qmf/SchemaCache.h b/qpid/cpp/src/qmf/SchemaCache.h new file mode 100644 index 0000000000..a1f104233f --- /dev/null +++ b/qpid/cpp/src/qmf/SchemaCache.h @@ -0,0 +1,56 @@ +#ifndef QMF_SCHEMA_CACHE_H +#define QMF_SCHEMA_CACHE_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/SchemaIdImpl.h" +#include "qmf/Schema.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Condition.h" +#include "qpid/messaging/Duration.h" +#include <string> +#include <map> +#include <boost/shared_ptr.hpp> + +namespace qmf { + + class SchemaCache { + public: + SchemaCache() {} + ~SchemaCache() {} + + bool declareSchemaId(const SchemaId&); + void declareSchema(const Schema&); + bool haveSchema(const SchemaId&) const; + const Schema& getSchema(const SchemaId&, qpid::messaging::Duration) const; + + private: + mutable qpid::sys::Mutex lock; + typedef std::map<SchemaId, Schema, SchemaIdCompare> SchemaMap; + typedef std::map<SchemaId, boost::shared_ptr<qpid::sys::Condition>, SchemaIdCompare> CondMap; + SchemaMap schemata; + mutable CondMap conditions; + }; + +} + +#endif + diff --git a/qpid/cpp/src/qmf/SchemaId.cpp b/qpid/cpp/src/qmf/SchemaId.cpp new file mode 100644 index 0000000000..25fa9915ae --- /dev/null +++ b/qpid/cpp/src/qmf/SchemaId.cpp @@ -0,0 +1,96 @@ +/* + * + * 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/SchemaIdImpl.h" +#include "qmf/PrivateImplRef.h" + +using namespace std; +using namespace qmf; +using qpid::types::Variant; + +typedef PrivateImplRef<SchemaId> PI; + +SchemaId::SchemaId(SchemaIdImpl* impl) { PI::ctor(*this, impl); } +SchemaId::SchemaId(const SchemaId& s) : qmf::Handle<SchemaIdImpl>() { PI::copy(*this, s); } +SchemaId::~SchemaId() { PI::dtor(*this); } +SchemaId& SchemaId::operator=(const SchemaId& s) { return PI::assign(*this, s); } + +SchemaId::SchemaId(int t, const string& p, const string& n) { PI::ctor(*this, new SchemaIdImpl(t, p, n)); } +void SchemaId::setHash(const qpid::types::Uuid& h) { impl->setHash(h); } +int SchemaId::getType() const { return impl->getType(); } +const string& SchemaId::getPackageName() const { return impl->getPackageName(); } +const string& SchemaId::getName() const { return impl->getName(); } +const qpid::types::Uuid& SchemaId::getHash() const { return impl->getHash(); } + + +SchemaIdImpl::SchemaIdImpl(const Variant::Map& map) +{ + Variant::Map::const_iterator iter; + + iter = map.find("_package_name"); + if (iter != map.end()) + package = iter->second.asString(); + + iter = map.find("_class_name"); + if (iter != map.end()) + name = iter->second.asString(); + + iter = map.find("_type"); + if (iter != map.end()) { + const string& stype = iter->second.asString(); + if (stype == "_data") + sType = SCHEMA_TYPE_DATA; + else if (stype == "_event") + sType = SCHEMA_TYPE_EVENT; + } + + iter = map.find("_hash"); + if (iter != map.end()) + hash = iter->second.asUuid(); +} + + +Variant::Map SchemaIdImpl::asMap() const +{ + Variant::Map result; + + result["_package_name"] = package; + result["_class_name"] = name; + if (sType == SCHEMA_TYPE_DATA) + result["_type"] = "_data"; + else + result["_type"] = "_event"; + if (!hash.isNull()) + result["_hash"] = hash; + return result; +} + + +SchemaIdImpl& SchemaIdImplAccess::get(SchemaId& item) +{ + return *item.impl; +} + + +const SchemaIdImpl& SchemaIdImplAccess::get(const SchemaId& item) +{ + return *item.impl; +} diff --git a/qpid/cpp/src/qmf/SchemaIdImpl.h b/qpid/cpp/src/qmf/SchemaIdImpl.h new file mode 100644 index 0000000000..ae1a3d8d3b --- /dev/null +++ b/qpid/cpp/src/qmf/SchemaIdImpl.h @@ -0,0 +1,83 @@ +#ifndef _QMF_SCHEMA_ID_IMPL_H_ +#define _QMF_SCHEMA_ID_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/SchemaId.h" +#include "qpid/types/Variant.h" +#include "qpid/types/Uuid.h" +#include <string> + +namespace qmf { + class SchemaIdImpl : public virtual qpid::RefCounted { + public: + // + // Public impl-only methods + // + SchemaIdImpl(const qpid::types::Variant::Map&); + qpid::types::Variant::Map asMap() const; + + // + // Methods from API handle + // + SchemaIdImpl(int t, const std::string& p, const std::string& n) : sType(t), package(p), name(n) {} + void setHash(const qpid::types::Uuid& h) { hash = h; } + int getType() const { return sType; } + const std::string& getPackageName() const { return package; } + const std::string& getName() const { return name; } + const qpid::types::Uuid& getHash() const { return hash; } + + private: + int sType; + std::string package; + std::string name; + qpid::types::Uuid hash; + }; + + struct SchemaIdImplAccess + { + static SchemaIdImpl& get(SchemaId&); + static const SchemaIdImpl& get(const SchemaId&); + }; + + struct SchemaIdCompare { + bool operator() (const SchemaId& lhs, const SchemaId& rhs) const + { + if (lhs.getName() != rhs.getName()) + return lhs.getName() < rhs.getName(); + if (lhs.getPackageName() != rhs.getPackageName()) + return lhs.getPackageName() < rhs.getPackageName(); + return lhs.getHash() < rhs.getHash(); + } + }; + + struct SchemaIdCompareNoHash { + bool operator() (const SchemaId& lhs, const SchemaId& rhs) const + { + if (lhs.getName() != rhs.getName()) + return lhs.getName() < rhs.getName(); + return lhs.getPackageName() < rhs.getPackageName(); + } + }; +} + +#endif diff --git a/qpid/cpp/src/qmf/SchemaImpl.h b/qpid/cpp/src/qmf/SchemaImpl.h new file mode 100644 index 0000000000..1c88f87808 --- /dev/null +++ b/qpid/cpp/src/qmf/SchemaImpl.h @@ -0,0 +1,95 @@ +#ifndef _QMF_SCHEMAIMPL_H_ +#define _QMF_SCHEMAIMPL_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/SchemaTypes.h" +#include "qmf/SchemaId.h" +#include "qmf/Schema.h" +#include "qmf/SchemaProperty.h" +#include "qmf/SchemaMethod.h" +#include <list> + +namespace qpid { +namespace management { + class Buffer; +}} + +namespace qmf { + class SchemaImpl : public virtual qpid::RefCounted { + public: + // + // Impl-only public methods + // + SchemaImpl(const qpid::types::Variant::Map& m); + qpid::types::Variant::Map asMap() const; + SchemaImpl(qpid::management::Buffer& v1Buffer); + std::string asV1Content(uint32_t sequence) const; + bool isValidProperty(const std::string& k, const qpid::types::Variant& v) const; + bool isValidMethodInArg(const std::string& m, const std::string& k, const qpid::types::Variant& v) const; + bool isValidMethodOutArg(const std::string& m, const std::string& k, const qpid::types::Variant& v) const; + + // + // Methods from API handle + // + SchemaImpl(int t, const std::string& p, const std::string& c) : schemaId(t, p, c), finalized(false) {} + const SchemaId& getSchemaId() const { checkNotFinal(); return schemaId; } + + void finalize(); + bool isFinalized() const { return finalized; } + void addProperty(const SchemaProperty& p) { checkFinal(); properties.push_back(p); } + void addMethod(const SchemaMethod& m) { checkFinal(); methods.push_back(m); } + + void setDesc(const std::string& d) { description = d; } + const std::string& getDesc() const { return description; } + + void setDefaultSeverity(int s) { checkFinal(); defaultSeverity = s; } + int getDefaultSeverity() const { return defaultSeverity; } + + uint32_t getPropertyCount() const { return properties.size(); } + SchemaProperty getProperty(uint32_t i) const; + + uint32_t getMethodCount() const { return methods.size(); } + SchemaMethod getMethod(uint32_t i) const; + private: + SchemaId schemaId; + int defaultSeverity; + std::string description; + bool finalized; + std::list<SchemaProperty> properties; + std::list<SchemaMethod> methods; + + void checkFinal() const; + void checkNotFinal() const; + bool isCompatibleType(int qmfType, qpid::types::VariantType qpidType) const; + }; + + struct SchemaImplAccess + { + static SchemaImpl& get(Schema&); + static const SchemaImpl& get(const Schema&); + }; +} + +#endif diff --git a/qpid/cpp/src/qmf/SchemaMethod.cpp b/qpid/cpp/src/qmf/SchemaMethod.cpp new file mode 100644 index 0000000000..e267878238 --- /dev/null +++ b/qpid/cpp/src/qmf/SchemaMethod.cpp @@ -0,0 +1,186 @@ +/* + * + * 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/SchemaMethodImpl.h" +#include "qmf/PrivateImplRef.h" +#include "qmf/exceptions.h" +#include "qmf/Hash.h" +#include "qpid/messaging/AddressParser.h" +#include "qpid/management/Buffer.h" + +using namespace std; +using qpid::types::Variant; +using namespace qmf; + +typedef PrivateImplRef<SchemaMethod> PI; + +SchemaMethod::SchemaMethod(SchemaMethodImpl* impl) { PI::ctor(*this, impl); } +SchemaMethod::SchemaMethod(const SchemaMethod& s) : qmf::Handle<SchemaMethodImpl>() { PI::copy(*this, s); } +SchemaMethod::~SchemaMethod() { PI::dtor(*this); } +SchemaMethod& SchemaMethod::operator=(const SchemaMethod& s) { return PI::assign(*this, s); } + +SchemaMethod::SchemaMethod(const string& n, const string& o) { PI::ctor(*this, new SchemaMethodImpl(n, o)); } +void SchemaMethod::setDesc(const string& d) { impl->setDesc(d); } +void SchemaMethod::addArgument(const SchemaProperty& p) { impl->addArgument(p); } +const string& SchemaMethod::getName() const { return impl->getName(); } +const string& SchemaMethod::getDesc() const { return impl->getDesc(); } +uint32_t SchemaMethod::getArgumentCount() const { return impl->getArgumentCount(); } +SchemaProperty SchemaMethod::getArgument(uint32_t i) const { return impl->getArgument(i); } + +//======================================================================================== +// Impl Method Bodies +//======================================================================================== + +SchemaMethodImpl::SchemaMethodImpl(const string& n, const string& options) : name(n) +{ + if (!options.empty()) { + qpid::messaging::AddressParser parser = qpid::messaging::AddressParser(options); + Variant::Map optMap; + Variant::Map::iterator iter; + + parser.parseMap(optMap); + iter = optMap.find("desc"); + if (iter != optMap.end()) { + desc = iter->second.asString(); + optMap.erase(iter); + } + + if (!optMap.empty()) + throw QmfException("Unrecognized option: " + optMap.begin()->first); + } +} + + +SchemaMethodImpl::SchemaMethodImpl(const qpid::types::Variant::Map& map) +{ + Variant::Map::const_iterator iter; + Variant::List::const_iterator lIter; + + iter = map.find("_name"); + if (iter == map.end()) + throw QmfException("SchemaMethod without a _name element"); + name = iter->second.asString(); + + iter = map.find("_desc"); + if (iter != map.end()) + desc = iter->second.asString(); + + iter = map.find("_arguments"); + if (iter != map.end()) { + const Variant::List& argList(iter->second.asList()); + for (lIter = argList.begin(); lIter != argList.end(); lIter++) + addArgument(SchemaProperty(new SchemaPropertyImpl(lIter->asMap()))); + } +} + + +Variant::Map SchemaMethodImpl::asMap() const +{ + Variant::Map map; + Variant::List argList; + + map["_name"] = name; + + if (!desc.empty()) + map["_desc"] = desc; + + for (list<SchemaProperty>::const_iterator iter = arguments.begin(); iter != arguments.end(); iter++) + argList.push_back(SchemaPropertyImplAccess::get(*iter).asMap()); + map["_arguments"] = argList; + + return map; +} + + +SchemaMethodImpl::SchemaMethodImpl(qpid::management::Buffer& buffer) +{ + Variant::Map::const_iterator iter; + Variant::Map argMap; + + buffer.getMap(argMap); + + iter = argMap.find("name"); + if (iter == argMap.end()) + throw QmfException("Received V1 Method without a name"); + name = iter->second.asString(); + + iter = argMap.find("desc"); + if (iter != argMap.end()) + desc = iter->second.asString(); + + iter = argMap.find("argCount"); + if (iter == argMap.end()) + throw QmfException("Received V1 Method without argCount"); + + int64_t count = iter->second.asInt64(); + for (int idx = 0; idx < count; idx++) { + SchemaProperty arg(new SchemaPropertyImpl(buffer)); + addArgument(arg); + } +} + + +SchemaProperty SchemaMethodImpl::getArgument(uint32_t i) const +{ + uint32_t count = 0; + for (list<SchemaProperty>::const_iterator iter = arguments.begin(); iter != arguments.end(); iter++) + if (count++ == i) + return *iter; + + throw IndexOutOfRange(); +} + + +void SchemaMethodImpl::updateHash(Hash& hash) const +{ + hash.update(name); + hash.update(desc); + for (list<SchemaProperty>::const_iterator iter = arguments.begin(); iter != arguments.end(); iter++) + SchemaPropertyImplAccess::get(*iter).updateHash(hash); +} + + +void SchemaMethodImpl::encodeV1(qpid::management::Buffer& buffer) const +{ + Variant::Map map; + + map["name"] = name; + map["argCount"] = (uint64_t) arguments.size(); + if (!desc.empty()) + map["desc"] = desc; + + buffer.putMap(map); + + for (list<SchemaProperty>::const_iterator iter = arguments.begin(); iter != arguments.end(); iter++) + SchemaPropertyImplAccess::get(*iter).encodeV1(buffer, true, true); +} + + +SchemaMethodImpl& SchemaMethodImplAccess::get(SchemaMethod& item) +{ + return *item.impl; +} + + +const SchemaMethodImpl& SchemaMethodImplAccess::get(const SchemaMethod& item) +{ + return *item.impl; +} diff --git a/qpid/cpp/src/qmf/SchemaMethodImpl.h b/qpid/cpp/src/qmf/SchemaMethodImpl.h new file mode 100644 index 0000000000..930d48509c --- /dev/null +++ b/qpid/cpp/src/qmf/SchemaMethodImpl.h @@ -0,0 +1,75 @@ +#ifndef _QMF_SCHEMA_METHOD_IMPL_H_ +#define _QMF_SCHEMA_METHOD_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/SchemaTypes.h" +#include "qmf/SchemaMethod.h" +#include "qmf/SchemaPropertyImpl.h" +#include "qpid/management/Buffer.h" +#include <list> +#include <string> + +namespace qpid { +namespace management { + class Buffer; +}} + +namespace qmf { + class Hash; + class SchemaMethodImpl : public virtual qpid::RefCounted { + public: + // + // Public impl-only methods + // + SchemaMethodImpl(const qpid::types::Variant::Map& m); + SchemaMethodImpl(qpid::management::Buffer& v1Buffer); + qpid::types::Variant::Map asMap() const; + void updateHash(Hash&) const; + void encodeV1(qpid::management::Buffer&) const; + + // + // Methods from API handle + // + SchemaMethodImpl(const std::string& n, const std::string& options); + + void setDesc(const std::string& d) { desc = d; } + void addArgument(const SchemaProperty& p) { arguments.push_back(p); } + const std::string& getName() const { return name; } + const std::string& getDesc() const { return desc; } + uint32_t getArgumentCount() const { return arguments.size(); } + SchemaProperty getArgument(uint32_t i) const; + + private: + std::string name; + std::string desc; + std::list<SchemaProperty> arguments; + }; + + struct SchemaMethodImplAccess + { + static SchemaMethodImpl& get(SchemaMethod&); + static const SchemaMethodImpl& get(const SchemaMethod&); + }; +} + +#endif diff --git a/qpid/cpp/src/qmf/SchemaProperty.cpp b/qpid/cpp/src/qmf/SchemaProperty.cpp new file mode 100644 index 0000000000..106127261b --- /dev/null +++ b/qpid/cpp/src/qmf/SchemaProperty.cpp @@ -0,0 +1,434 @@ +/* + * + * 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/SchemaPropertyImpl.h" +#include "qmf/PrivateImplRef.h" +#include "qmf/exceptions.h" +#include "qmf/SchemaTypes.h" +#include "qmf/SchemaProperty.h" +#include "qmf/Hash.h" +#include "qpid/messaging/AddressParser.h" +#include <list> +#include <iostream> + +using namespace std; +using qpid::types::Variant; +using namespace qmf; + +typedef PrivateImplRef<SchemaProperty> PI; + +SchemaProperty::SchemaProperty(SchemaPropertyImpl* impl) { PI::ctor(*this, impl); } +SchemaProperty::SchemaProperty(const SchemaProperty& s) : qmf::Handle<SchemaPropertyImpl>() { PI::copy(*this, s); } +SchemaProperty::~SchemaProperty() { PI::dtor(*this); } +SchemaProperty& SchemaProperty::operator=(const SchemaProperty& s) { return PI::assign(*this, s); } + +SchemaProperty::SchemaProperty(const string& n, int t, const string& o) { PI::ctor(*this, new SchemaPropertyImpl(n, t, o)); } + +void SchemaProperty::setAccess(int a) { impl->setAccess(a); } +void SchemaProperty::setIndex(bool i) { impl->setIndex(i); } +void SchemaProperty::setOptional(bool o) { impl->setOptional(o); } +void SchemaProperty::setUnit(const string& u) { impl->setUnit(u); } +void SchemaProperty::setDesc(const string& d) { impl->setDesc(d); } +void SchemaProperty::setSubtype(const string& s) { impl->setSubtype(s); } +void SchemaProperty::setDirection(int d) { impl->setDirection(d); } + +const string& SchemaProperty::getName() const { return impl->getName(); } +int SchemaProperty::getType() const { return impl->getType(); } +int SchemaProperty::getAccess() const { return impl->getAccess(); } +bool SchemaProperty::isIndex() const { return impl->isIndex(); } +bool SchemaProperty::isOptional() const { return impl->isOptional(); } +const string& SchemaProperty::getUnit() const { return impl->getUnit(); } +const string& SchemaProperty::getDesc() const { return impl->getDesc(); } +const string& SchemaProperty::getSubtype() const { return impl->getSubtype(); } +int SchemaProperty::getDirection() const { return impl->getDirection(); } + +//======================================================================================== +// Impl Method Bodies +//======================================================================================== + +SchemaPropertyImpl::SchemaPropertyImpl(const string& n, int t, const string options) : + name(n), dataType(t), access(ACCESS_READ_ONLY), index(false), + optional(false), direction(DIR_IN) +{ + if (!options.empty()) { + qpid::messaging::AddressParser parser = qpid::messaging::AddressParser(options); + Variant::Map optMap; + Variant::Map::iterator iter; + + parser.parseMap(optMap); + + iter = optMap.find("access"); + if (iter != optMap.end()) { + const string& v(iter->second.asString()); + if (v == "RC") access = ACCESS_READ_CREATE; + else if (v == "RO") access = ACCESS_READ_ONLY; + else if (v == "RW") access = ACCESS_READ_WRITE; + else + throw QmfException("Invalid value for 'access' option. Expected RC, RO, or RW"); + optMap.erase(iter); + } + + iter = optMap.find("index"); + if (iter != optMap.end()) { + index = iter->second.asBool(); + optMap.erase(iter); + } + + iter = optMap.find("optional"); + if (iter != optMap.end()) { + optional = iter->second.asBool(); + optMap.erase(iter); + } + + iter = optMap.find("unit"); + if (iter != optMap.end()) { + unit = iter->second.asString(); + optMap.erase(iter); + } + + iter = optMap.find("desc"); + if (iter != optMap.end()) { + desc = iter->second.asString(); + optMap.erase(iter); + } + + iter = optMap.find("subtype"); + if (iter != optMap.end()) { + subtype = iter->second.asString(); + optMap.erase(iter); + } + + iter = optMap.find("dir"); + if (iter != optMap.end()) { + const string& v(iter->second.asString()); + if (v == "IN") direction = DIR_IN; + else if (v == "OUT") direction = DIR_OUT; + else if (v == "INOUT") direction = DIR_IN_OUT; + else + throw QmfException("Invalid value for 'dir' option. Expected IN, OUT, or INOUT"); + optMap.erase(iter); + } + + if (!optMap.empty()) + throw QmfException("Unexpected option: " + optMap.begin()->first); + } +} + + +SchemaPropertyImpl::SchemaPropertyImpl(const Variant::Map& map) : + access(ACCESS_READ_ONLY), index(false), optional(false), direction(DIR_IN) +{ + Variant::Map::const_iterator iter; + + iter = map.find("_name"); + if (iter == map.end()) + throw QmfException("SchemaProperty without a _name element"); + name = iter->second.asString(); + + iter = map.find("_type"); + if (iter == map.end()) + throw QmfException("SchemaProperty without a _type element"); + const string& ts(iter->second.asString()); + if (ts == "TYPE_VOID") dataType = SCHEMA_DATA_VOID; + else if (ts == "TYPE_BOOL") dataType = SCHEMA_DATA_BOOL; + else if (ts == "TYPE_INT") dataType = SCHEMA_DATA_INT; + else if (ts == "TYPE_FLOAT") dataType = SCHEMA_DATA_FLOAT; + else if (ts == "TYPE_STRING") dataType = SCHEMA_DATA_STRING; + else if (ts == "TYPE_MAP") dataType = SCHEMA_DATA_MAP; + else if (ts == "TYPE_LIST") dataType = SCHEMA_DATA_LIST; + else if (ts == "TYPE_UUID") dataType = SCHEMA_DATA_UUID; + else + throw QmfException("SchemaProperty with an invalid type code: " + ts); + + iter = map.find("_access"); + if (iter != map.end()) { + const string& as(iter->second.asString()); + if (as == "RO") access = ACCESS_READ_ONLY; + else if (as == "RC") access = ACCESS_READ_CREATE; + else if (as == "RW") access = ACCESS_READ_WRITE; + else + throw QmfException("SchemaProperty with an invalid access code: " + as); + } + + iter = map.find("_unit"); + if (iter != map.end()) + unit = iter->second.asString(); + + iter = map.find("_dir"); + if (iter != map.end()) { + const string& ds(iter->second.asString()); + if (ds == "I") direction = DIR_IN; + else if (ds == "O") direction = DIR_OUT; + else if (ds == "IO") direction = DIR_IN_OUT; + else + throw QmfException("SchemaProperty with an invalid direction code: " + ds); + } + + iter = map.find("_desc"); + if (iter != map.end()) + desc = iter->second.asString(); + + iter = map.find("_index"); + if (iter != map.end()) + index = iter->second.asBool(); + + iter = map.find("_subtype"); + if (iter != map.end()) + subtype = iter->second.asString(); +} + + +Variant::Map SchemaPropertyImpl::asMap() const +{ + Variant::Map map; + string ts; + + map["_name"] = name; + + switch (dataType) { + case SCHEMA_DATA_VOID: ts = "TYPE_VOID"; break; + case SCHEMA_DATA_BOOL: ts = "TYPE_BOOL"; break; + case SCHEMA_DATA_INT: ts = "TYPE_INT"; break; + case SCHEMA_DATA_FLOAT: ts = "TYPE_FLOAT"; break; + case SCHEMA_DATA_STRING: ts = "TYPE_STRING"; break; + case SCHEMA_DATA_MAP: ts = "TYPE_MAP"; break; + case SCHEMA_DATA_LIST: ts = "TYPE_LIST"; break; + case SCHEMA_DATA_UUID: ts = "TYPE_UUID"; break; + } + map["_type"] = ts; + + switch (access) { + case ACCESS_READ_ONLY: ts = "RO"; break; + case ACCESS_READ_CREATE: ts = "RC"; break; + case ACCESS_READ_WRITE: ts = "RW"; break; + } + map["_access"] = ts; + + if (!unit.empty()) + map["_unit"] = unit; + + switch (direction) { + case DIR_IN: ts = "I"; break; + case DIR_OUT: ts = "O"; break; + case DIR_IN_OUT: ts = "IO"; break; + } + map["_dir"] = ts; + + if (!desc.empty()) + map["_desc"] = desc; + + if (index) + map["_index"] = true; + + if (!subtype.empty()) + map["_subtype"] = subtype; + + return map; +} + + +SchemaPropertyImpl::SchemaPropertyImpl(qpid::management::Buffer& buffer) : + access(ACCESS_READ_ONLY), index(false), optional(false), direction(DIR_IN) +{ + Variant::Map::const_iterator iter; + Variant::Map pmap; + + buffer.getMap(pmap); + iter = pmap.find("name"); + if (iter == pmap.end()) + throw QmfException("Received V1 Schema property without a name"); + name = iter->second.asString(); + + iter = pmap.find("type"); + if (iter == pmap.end()) + throw QmfException("Received V1 Schema property without a type"); + fromV1TypeCode(iter->second.asInt8()); + + iter = pmap.find("unit"); + if (iter != pmap.end()) + unit = iter->second.asString(); + + iter = pmap.find("desc"); + if (iter != pmap.end()) + desc = iter->second.asString(); + + iter = pmap.find("access"); + if (iter != pmap.end()) { + int8_t val = iter->second.asInt8(); + if (val < 1 || val > 3) + throw QmfException("Received V1 Schema property with invalid 'access' code"); + access = val; + } + + iter = pmap.find("index"); + if (iter != pmap.end()) + index = iter->second.asInt64() != 0; + + iter = pmap.find("optional"); + if (iter != pmap.end()) + optional = iter->second.asInt64() != 0; + + iter = pmap.find("dir"); + if (iter != pmap.end()) { + string dirStr(iter->second.asString()); + if (dirStr == "I") direction = DIR_IN; + else if (dirStr == "O") direction = DIR_OUT; + else if (dirStr == "IO") direction = DIR_IN_OUT; + else + throw QmfException("Received V1 Schema property with invalid 'dir' code"); + } +} + + +void SchemaPropertyImpl::updateHash(Hash& hash) const +{ + hash.update(name); + hash.update((uint8_t) dataType); + hash.update(subtype); + hash.update((uint8_t) access); + hash.update(index); + hash.update(optional); + hash.update(unit); + hash.update(desc); + hash.update((uint8_t) direction); +} + + +void SchemaPropertyImpl::encodeV1(qpid::management::Buffer& buffer, bool isArg, bool isMethodArg) const +{ + Variant::Map pmap; + + pmap["name"] = name; + pmap["type"] = v1TypeCode(); + if (!unit.empty()) + pmap["unit"] = unit; + if (!desc.empty()) + pmap["desc"] = desc; + if (!isArg) { + pmap["access"] = access; + pmap["index"] = index ? 1 : 0; + pmap["optional"] = optional ? 1 : 0; + } else { + if (isMethodArg) { + string dirStr; + switch (direction) { + case DIR_IN : dirStr = "I"; break; + case DIR_OUT : dirStr = "O"; break; + case DIR_IN_OUT : dirStr = "IO"; break; + } + pmap["dir"] = dirStr; + } + } + + buffer.putMap(pmap); +} + + +uint8_t SchemaPropertyImpl::v1TypeCode() const +{ + switch (dataType) { + case SCHEMA_DATA_VOID: return 1; + case SCHEMA_DATA_BOOL: return 11; + case SCHEMA_DATA_INT: + if (subtype == "timestamp") return 8; + if (subtype == "duration") return 9; + return 19; + case SCHEMA_DATA_FLOAT: return 13; + case SCHEMA_DATA_STRING: return 7; + case SCHEMA_DATA_LIST: return 21; + case SCHEMA_DATA_UUID: return 14; + case SCHEMA_DATA_MAP: + if (subtype == "reference") return 10; + if (subtype == "data") return 20; + return 15; + } + + return 1; +} + +void SchemaPropertyImpl::fromV1TypeCode(int8_t code) +{ + switch (code) { + case 1: // U8 + case 2: // U16 + case 3: // U32 + case 4: // U64 + dataType = SCHEMA_DATA_INT; + break; + case 6: // SSTR + case 7: // LSTR + dataType = SCHEMA_DATA_STRING; + break; + case 8: // ABSTIME + dataType = SCHEMA_DATA_INT; + subtype = "timestamp"; + break; + case 9: // DELTATIME + dataType = SCHEMA_DATA_INT; + subtype = "duration"; + break; + case 10: // REF + dataType = SCHEMA_DATA_MAP; + subtype = "reference"; + break; + case 11: // BOOL + dataType = SCHEMA_DATA_BOOL; + break; + case 12: // FLOAT + case 13: // DOUBLE + dataType = SCHEMA_DATA_FLOAT; + break; + case 14: // UUID + dataType = SCHEMA_DATA_UUID; + break; + case 15: // FTABLE + dataType = SCHEMA_DATA_MAP; + break; + case 16: // S8 + case 17: // S16 + case 18: // S32 + case 19: // S64 + dataType = SCHEMA_DATA_INT; + break; + case 20: // OBJECT + dataType = SCHEMA_DATA_MAP; + subtype = "data"; + break; + case 21: // LIST + case 22: // ARRAY + dataType = SCHEMA_DATA_LIST; + break; + default: + throw QmfException("Received V1 schema with an unknown data type"); + } +} + + +SchemaPropertyImpl& SchemaPropertyImplAccess::get(SchemaProperty& item) +{ + return *item.impl; +} + + +const SchemaPropertyImpl& SchemaPropertyImplAccess::get(const SchemaProperty& item) +{ + return *item.impl; +} diff --git a/qpid/cpp/src/qmf/SchemaPropertyImpl.h b/qpid/cpp/src/qmf/SchemaPropertyImpl.h new file mode 100644 index 0000000000..cdfc29066f --- /dev/null +++ b/qpid/cpp/src/qmf/SchemaPropertyImpl.h @@ -0,0 +1,93 @@ +#ifndef _QMF_SCHEMA_PROPERTY_IMPL_H_ +#define _QMF_SCHEMA_PROPERTY_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/SchemaTypes.h" +#include "qmf/SchemaProperty.h" +#include "qpid/types/Variant.h" +#include "qpid/management/Buffer.h" + +namespace qpid { +namespace management { + class Buffer; +}} + +namespace qmf { + class Hash; + class SchemaPropertyImpl : public virtual qpid::RefCounted { + public: + // + // Public impl-only methods + // + SchemaPropertyImpl(const qpid::types::Variant::Map& m); + SchemaPropertyImpl(qpid::management::Buffer& v1Buffer); + qpid::types::Variant::Map asMap() const; + void updateHash(Hash&) const; + void encodeV1(qpid::management::Buffer&, bool isArg, bool isMethodArg) const; + + // + // Methods from API handle + // + SchemaPropertyImpl(const std::string& n, int t, const std::string o); + void setAccess(int a) { access = a; } + void setIndex(bool i) { index = i; } + void setOptional(bool o) { optional = o; } + void setUnit(const std::string& u) { unit = u; } + void setDesc(const std::string& d) { desc = d; } + void setSubtype(const std::string& s) { subtype = s; } + void setDirection(int d) { direction = d; } + + const std::string& getName() const { return name; } + int getType() const { return dataType; } + int getAccess() const { return access; } + bool isIndex() const { return index; } + bool isOptional() const { return optional; } + const std::string& getUnit() const { return unit; } + const std::string& getDesc() const { return desc; } + const std::string& getSubtype() const { return subtype; } + int getDirection() const { return direction; } + + private: + std::string name; + int dataType; + std::string subtype; + int access; + bool index; + bool optional; + std::string unit; + std::string desc; + int direction; + + uint8_t v1TypeCode() const; + void fromV1TypeCode(int8_t); + }; + + struct SchemaPropertyImplAccess + { + static SchemaPropertyImpl& get(SchemaProperty&); + static const SchemaPropertyImpl& get(const SchemaProperty&); + }; +} + +#endif diff --git a/qpid/cpp/src/qmf/Subscription.cpp b/qpid/cpp/src/qmf/Subscription.cpp new file mode 100644 index 0000000000..73afc8c79d --- /dev/null +++ b/qpid/cpp/src/qmf/Subscription.cpp @@ -0,0 +1,88 @@ +/* + * + * 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/PrivateImplRef.h" +#include "qmf/exceptions.h" +#include "qmf/SubscriptionImpl.h" +#include "qmf/DataImpl.h" + +using namespace std; +using namespace qmf; +using qpid::types::Variant; + +typedef PrivateImplRef<Subscription> PI; + +Subscription::Subscription(SubscriptionImpl* impl) { PI::ctor(*this, impl); } +Subscription::Subscription(const Subscription& s) : qmf::Handle<SubscriptionImpl>() { PI::copy(*this, s); } +Subscription::~Subscription() { PI::dtor(*this); } +Subscription& Subscription::operator=(const Subscription& s) { return PI::assign(*this, s); } + +void Subscription::cancel() { impl->cancel(); } +bool Subscription::isActive() const { return impl->isActive(); } +void Subscription::lock() { impl->lock(); } +void Subscription::unlock() { impl->unlock(); } +uint32_t Subscription::getDataCount() const { return impl->getDataCount(); } +Data Subscription::getData(uint32_t i) const { return impl->getData(i); } + + +void SubscriptionImpl::cancel() +{ +} + + +bool SubscriptionImpl::isActive() const +{ + return false; +} + + +void SubscriptionImpl::lock() +{ +} + + +void SubscriptionImpl::unlock() +{ +} + + +uint32_t SubscriptionImpl::getDataCount() const +{ + return 0; +} + + +Data SubscriptionImpl::getData(uint32_t) const +{ + return Data(); +} + + +SubscriptionImpl& SubscriptionImplAccess::get(Subscription& item) +{ + return *item.impl; +} + + +const SubscriptionImpl& SubscriptionImplAccess::get(const Subscription& item) +{ + return *item.impl; +} diff --git a/qpid/cpp/src/qmf/SubscriptionImpl.h b/qpid/cpp/src/qmf/SubscriptionImpl.h new file mode 100644 index 0000000000..053e3cd00e --- /dev/null +++ b/qpid/cpp/src/qmf/SubscriptionImpl.h @@ -0,0 +1,57 @@ +#ifndef _QMF_SUBSCRIPTION_IMPL_H_ +#define _QMF_SUBSCRIPTION_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/Subscription.h" + +namespace qmf { + class SubscriptionImpl : public virtual qpid::RefCounted { + public: + // + // Public impl-only methods + // + SubscriptionImpl(int p) : placeholder(p) {} + ~SubscriptionImpl(); + + // + // Methods from API handle + // + void cancel(); + bool isActive() const; + void lock(); + void unlock(); + uint32_t getDataCount() const; + Data getData(uint32_t) const; + + private: + int placeholder; + }; + + struct SubscriptionImplAccess + { + static SubscriptionImpl& get(Subscription&); + static const SubscriptionImpl& get(const Subscription&); + }; +} + +#endif diff --git a/qpid/cpp/src/qmf/agentCapability.h b/qpid/cpp/src/qmf/agentCapability.h new file mode 100644 index 0000000000..6a3f6f8534 --- /dev/null +++ b/qpid/cpp/src/qmf/agentCapability.h @@ -0,0 +1,39 @@ +#ifndef QMF_AGENT_CAPABILITY_H +#define QMF_AGENT_CAPABILITY_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. + * + */ + +namespace qmf { + + /** + * Legacy (Qpid 0.7 C++ Agent, 0.7 Broker Agent) capabilities + */ + const uint32_t AGENT_CAPABILITY_LEGACY = 0; + + /** + * Qpid 0.8 QMFv2 capabilities + */ + const uint32_t AGENT_CAPABILITY_0_8 = 1; + const uint32_t AGENT_CAPABILITY_V2_SCHEMA = 1; + const uint32_t AGENT_CAPABILITY_AGENT_PREDICATE = 1; +} + +#endif diff --git a/qpid/cpp/src/qmf/constants.cpp b/qpid/cpp/src/qmf/constants.cpp new file mode 100644 index 0000000000..6e2fd935a9 --- /dev/null +++ b/qpid/cpp/src/qmf/constants.cpp @@ -0,0 +1,77 @@ +/* + * + * 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 "constants.h" + +using namespace std; +using namespace qmf; + +/** + * Header key strings + */ +const string protocol::HEADER_KEY_APP_ID = "x-amqp-0-10.app-id"; +const string protocol::HEADER_KEY_METHOD = "method"; +const string protocol::HEADER_KEY_OPCODE = "qmf.opcode"; +const string protocol::HEADER_KEY_AGENT = "qmf.agent"; +const string protocol::HEADER_KEY_CONTENT = "qmf.content"; +const string protocol::HEADER_KEY_PARTIAL = "partial"; + +/** + * Header values per-key + */ +const string protocol::HEADER_APP_ID_QMF = "qmf2"; + +const string protocol::HEADER_METHOD_REQUEST = "request"; +const string protocol::HEADER_METHOD_RESPONSE = "response"; +const string protocol::HEADER_METHOD_INDICATION = "indication"; + +const string protocol::HEADER_OPCODE_EXCEPTION = "_exception"; +const string protocol::HEADER_OPCODE_AGENT_LOCATE_REQUEST = "_agent_locate_request"; +const string protocol::HEADER_OPCODE_AGENT_LOCATE_RESPONSE = "_agent_locate_response"; +const string protocol::HEADER_OPCODE_AGENT_HEARTBEAT_INDICATION = "_agent_heartbeat_indication"; +const string protocol::HEADER_OPCODE_QUERY_REQUEST = "_query_request"; +const string protocol::HEADER_OPCODE_QUERY_RESPONSE = "_query_response"; +const string protocol::HEADER_OPCODE_SUBSCRIBE_REQUEST = "_subscribe_request"; +const string protocol::HEADER_OPCODE_SUBSCRIBE_RESPONSE = "_subscribe_response"; +const string protocol::HEADER_OPCODE_SUBSCRIBE_CANCEL_INDICATION = "_subscribe_cancel_indication"; +const string protocol::HEADER_OPCODE_SUBSCRIBE_REFRESH_INDICATION = "_subscribe_refresh_indication"; +const string protocol::HEADER_OPCODE_DATA_INDICATION = "_data_indication"; +const string protocol::HEADER_OPCODE_METHOD_REQUEST = "_method_request"; +const string protocol::HEADER_OPCODE_METHOD_RESPONSE = "_method_response"; + +const string protocol::HEADER_CONTENT_SCHEMA_ID = "_schema_id"; +const string protocol::HEADER_CONTENT_SCHEMA_CLASS = "_schema_class"; +const string protocol::HEADER_CONTENT_OBJECT_ID = "_object_id"; +const string protocol::HEADER_CONTENT_DATA = "_data"; +const string protocol::HEADER_CONTENT_EVENT = "_event"; +const string protocol::HEADER_CONTENT_QUERY = "_query"; + +/** + * Keywords for Agent attributes + */ +const string protocol::AGENT_ATTR_VENDOR = "_vendor"; +const string protocol::AGENT_ATTR_PRODUCT = "_product"; +const string protocol::AGENT_ATTR_INSTANCE = "_instance"; +const string protocol::AGENT_ATTR_NAME = "_name"; +const string protocol::AGENT_ATTR_TIMESTAMP = "_timestamp"; +const string protocol::AGENT_ATTR_HEARTBEAT_INTERVAL = "_heartbeat_interval"; +const string protocol::AGENT_ATTR_EPOCH = "_epoch"; +const string protocol::AGENT_ATTR_SCHEMA_UPDATED_TIMESTAMP = "_schema_updated"; diff --git a/qpid/cpp/src/qmf/constants.h b/qpid/cpp/src/qmf/constants.h new file mode 100644 index 0000000000..79beaaf1ca --- /dev/null +++ b/qpid/cpp/src/qmf/constants.h @@ -0,0 +1,83 @@ +#ifndef QMF_CONSTANTS_H +#define QMF_CONSTANTS_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 <string> + +namespace qmf { + + struct protocol { + /** + * Header key strings + */ + static const std::string HEADER_KEY_APP_ID; + static const std::string HEADER_KEY_METHOD; + static const std::string HEADER_KEY_OPCODE; + static const std::string HEADER_KEY_AGENT; + static const std::string HEADER_KEY_CONTENT; + static const std::string HEADER_KEY_PARTIAL; + + /** + * Header values per-key + */ + static const std::string HEADER_APP_ID_QMF; + + static const std::string HEADER_METHOD_REQUEST; + static const std::string HEADER_METHOD_RESPONSE; + static const std::string HEADER_METHOD_INDICATION; + + static const std::string HEADER_OPCODE_EXCEPTION; + static const std::string HEADER_OPCODE_AGENT_LOCATE_REQUEST; + static const std::string HEADER_OPCODE_AGENT_LOCATE_RESPONSE; + static const std::string HEADER_OPCODE_AGENT_HEARTBEAT_INDICATION; + static const std::string HEADER_OPCODE_QUERY_REQUEST; + static const std::string HEADER_OPCODE_QUERY_RESPONSE; + static const std::string HEADER_OPCODE_SUBSCRIBE_REQUEST; + static const std::string HEADER_OPCODE_SUBSCRIBE_RESPONSE; + static const std::string HEADER_OPCODE_SUBSCRIBE_CANCEL_INDICATION; + static const std::string HEADER_OPCODE_SUBSCRIBE_REFRESH_INDICATION; + static const std::string HEADER_OPCODE_DATA_INDICATION; + static const std::string HEADER_OPCODE_METHOD_REQUEST; + static const std::string HEADER_OPCODE_METHOD_RESPONSE; + + static const std::string HEADER_CONTENT_SCHEMA_ID; + static const std::string HEADER_CONTENT_SCHEMA_CLASS; + static const std::string HEADER_CONTENT_OBJECT_ID; + static const std::string HEADER_CONTENT_DATA; + static const std::string HEADER_CONTENT_EVENT; + static const std::string HEADER_CONTENT_QUERY; + + /** + * Keywords for Agent attributes + */ + static const std::string AGENT_ATTR_VENDOR; + static const std::string AGENT_ATTR_PRODUCT; + static const std::string AGENT_ATTR_INSTANCE; + static const std::string AGENT_ATTR_NAME; + static const std::string AGENT_ATTR_TIMESTAMP; + static const std::string AGENT_ATTR_HEARTBEAT_INTERVAL; + static const std::string AGENT_ATTR_EPOCH; + static const std::string AGENT_ATTR_SCHEMA_UPDATED_TIMESTAMP; + }; +} + +#endif diff --git a/qpid/cpp/src/qmf/engine/Agent.cpp b/qpid/cpp/src/qmf/engine/Agent.cpp new file mode 100644 index 0000000000..1f08dded94 --- /dev/null +++ b/qpid/cpp/src/qmf/engine/Agent.cpp @@ -0,0 +1,915 @@ +/* + * 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/engine/Agent.h" +#include "qmf/engine/MessageImpl.h" +#include "qmf/engine/SchemaImpl.h" +#include "qmf/engine/Typecode.h" +#include "qmf/engine/EventImpl.h" +#include "qmf/engine/ObjectImpl.h" +#include "qmf/engine/ObjectIdImpl.h" +#include "qmf/engine/QueryImpl.h" +#include "qmf/engine/ValueImpl.h" +#include "qmf/engine/Protocol.h" +#include <qpid/framing/Buffer.h> +#include <qpid/framing/Uuid.h> +#include <qpid/framing/FieldTable.h> +#include <qpid/framing/FieldValue.h> +#include <qpid/sys/Mutex.h> +#include <qpid/log/Statement.h> +#include <qpid/sys/Time.h> +#include <string.h> +#include <string> +#include <deque> +#include <map> +#include <iostream> +#include <fstream> +#include <boost/shared_ptr.hpp> +#include <boost/noncopyable.hpp> + +using namespace std; +using namespace qmf::engine; +using namespace qpid::framing; +using namespace qpid::sys; + +namespace qmf { +namespace engine { + + struct AgentEventImpl { + typedef boost::shared_ptr<AgentEventImpl> Ptr; + AgentEvent::EventKind kind; + uint32_t sequence; + string authUserId; + string authToken; + string name; + Object* object; + boost::shared_ptr<ObjectId> objectId; + boost::shared_ptr<Query> query; + boost::shared_ptr<Value> arguments; + string exchange; + string bindingKey; + const SchemaObjectClass* objectClass; + + AgentEventImpl(AgentEvent::EventKind k) : + kind(k), sequence(0), object(0), objectClass(0) {} + ~AgentEventImpl() {} + AgentEvent copy(); + }; + + struct AgentQueryContext { + typedef boost::shared_ptr<AgentQueryContext> Ptr; + uint32_t sequence; + string exchange; + string key; + const SchemaMethod* schemaMethod; + AgentQueryContext() : schemaMethod(0) {} + }; + + class AgentImpl : public boost::noncopyable { + public: + AgentImpl(char* label, bool internalStore); + ~AgentImpl(); + + void setStoreDir(const char* path); + void setTransferDir(const char* path); + void handleRcvMessage(Message& message); + bool getXmtMessage(Message& item) const; + void popXmt(); + bool getEvent(AgentEvent& event) const; + void popEvent(); + void newSession(); + void startProtocol(); + void heartbeat(); + void methodResponse(uint32_t sequence, uint32_t status, char* text, const Value& arguments); + void queryResponse(uint32_t sequence, Object& object, bool prop, bool stat); + void queryComplete(uint32_t sequence); + void registerClass(SchemaObjectClass* cls); + void registerClass(SchemaEventClass* cls); + const ObjectId* addObject(Object& obj, uint64_t persistId); + const ObjectId* allocObjectId(uint64_t persistId); + const ObjectId* allocObjectId(uint32_t persistIdLo, uint32_t persistIdHi); + void raiseEvent(Event& event); + + private: + mutable Mutex lock; + Mutex addLock; + string label; + string queueName; + string storeDir; + string transferDir; + bool internalStore; + uint64_t nextTransientId; + Uuid systemId; + uint32_t requestedBrokerBank; + uint32_t requestedAgentBank; + uint32_t assignedBrokerBank; + uint32_t assignedAgentBank; + AgentAttachment attachment; + uint16_t bootSequence; + uint64_t nextObjectId; + uint32_t nextContextNum; + deque<AgentEventImpl::Ptr> eventQueue; + deque<MessageImpl::Ptr> xmtQueue; + map<uint32_t, AgentQueryContext::Ptr> contextMap; + bool attachComplete; + + static const char* QMF_EXCHANGE; + static const char* DIR_EXCHANGE; + static const char* BROKER_KEY; + static const uint32_t MERR_UNKNOWN_METHOD = 2; + static const uint32_t MERR_UNKNOWN_PACKAGE = 8; + static const uint32_t MERR_UNKNOWN_CLASS = 9; + static const uint32_t MERR_INTERNAL_ERROR = 10; +# define MA_BUFFER_SIZE 65536 + char outputBuffer[MA_BUFFER_SIZE]; + + struct AgentClassKey { + string name; + uint8_t hash[16]; + AgentClassKey(const string& n, const uint8_t* h) : name(n) { + memcpy(hash, h, 16); + } + AgentClassKey(Buffer& buffer) { + buffer.getShortString(name); + buffer.getBin128(hash); + } + string repr() { + return name; + } + }; + + struct AgentClassKeyComp { + bool operator() (const AgentClassKey& lhs, const AgentClassKey& rhs) const + { + if (lhs.name != rhs.name) + return lhs.name < rhs.name; + else + for (int i = 0; i < 16; i++) + if (lhs.hash[i] != rhs.hash[i]) + return lhs.hash[i] < rhs.hash[i]; + return false; + } + }; + + typedef map<AgentClassKey, SchemaObjectClass*, AgentClassKeyComp> ObjectClassMap; + typedef map<AgentClassKey, SchemaEventClass*, AgentClassKeyComp> EventClassMap; + + struct ClassMaps { + ObjectClassMap objectClasses; + EventClassMap eventClasses; + }; + + map<string, ClassMaps> packages; + + AgentEventImpl::Ptr eventDeclareQueue(const string& queueName); + AgentEventImpl::Ptr eventBind(const string& exchange, const string& queue, const string& key); + AgentEventImpl::Ptr eventSetupComplete(); + AgentEventImpl::Ptr eventQuery(uint32_t num, const string& userId, const string& package, const string& cls, + boost::shared_ptr<ObjectId> oid); + AgentEventImpl::Ptr eventMethod(uint32_t num, const string& userId, const string& method, + boost::shared_ptr<ObjectId> oid, boost::shared_ptr<Value> argMap, + const SchemaObjectClass* objectClass); + void sendBufferLH(Buffer& buf, const string& destination, const string& routingKey); + + void sendPackageIndicationLH(const string& packageName); + void sendClassIndicationLH(ClassKind kind, const string& packageName, const AgentClassKey& key); + void sendCommandCompleteLH(const string& exchange, const string& key, uint32_t seq, + uint32_t code = 0, const string& text = "OK"); + void sendMethodErrorLH(uint32_t sequence, const string& key, uint32_t code, const string& text=""); + void handleAttachResponse(Buffer& inBuffer); + void handlePackageRequest(Buffer& inBuffer); + void handleClassQuery(Buffer& inBuffer); + void handleSchemaRequest(Buffer& inBuffer, uint32_t sequence, + const string& replyToExchange, const string& replyToKey); + void handleGetQuery(Buffer& inBuffer, uint32_t sequence, const string& replyTo, const string& userId); + void handleMethodRequest(Buffer& inBuffer, uint32_t sequence, const string& replyTo, const string& userId); + void handleConsoleAddedIndication(); + }; +} +} + +const char* AgentImpl::QMF_EXCHANGE = "qpid.management"; +const char* AgentImpl::DIR_EXCHANGE = "amq.direct"; +const char* AgentImpl::BROKER_KEY = "broker"; + +#define STRING_REF(s) {if (!s.empty()) item.s = const_cast<char*>(s.c_str());} + +AgentEvent AgentEventImpl::copy() +{ + AgentEvent item; + + ::memset(&item, 0, sizeof(AgentEvent)); + item.kind = kind; + item.sequence = sequence; + item.object = object; + item.objectId = objectId.get(); + item.query = query.get(); + item.arguments = arguments.get(); + item.objectClass = objectClass; + + STRING_REF(authUserId); + STRING_REF(authToken); + STRING_REF(name); + STRING_REF(exchange); + STRING_REF(bindingKey); + + return item; +} + +AgentImpl::AgentImpl(char* _label, bool i) : + label(_label), queueName("qmfa-"), internalStore(i), nextTransientId(1), + requestedBrokerBank(0), requestedAgentBank(0), + assignedBrokerBank(0), assignedAgentBank(0), + bootSequence(1), nextObjectId(1), nextContextNum(1), attachComplete(false) +{ + queueName += Uuid(true).str(); +} + +AgentImpl::~AgentImpl() +{ +} + +void AgentImpl::setStoreDir(const char* path) +{ + Mutex::ScopedLock _lock(lock); + if (path) + storeDir = path; + else + storeDir.clear(); +} + +void AgentImpl::setTransferDir(const char* path) +{ + Mutex::ScopedLock _lock(lock); + if (path) + transferDir = path; + else + transferDir.clear(); +} + +void AgentImpl::handleRcvMessage(Message& message) +{ + Buffer inBuffer(message.body, message.length); + uint8_t opcode; + uint32_t sequence; + string replyToExchange(message.replyExchange ? message.replyExchange : ""); + string replyToKey(message.replyKey ? message.replyKey : ""); + string userId(message.userId ? message.userId : ""); + + while (Protocol::checkHeader(inBuffer, &opcode, &sequence)) { + if (opcode == Protocol::OP_ATTACH_RESPONSE) handleAttachResponse(inBuffer); + else if (opcode == Protocol::OP_SCHEMA_REQUEST) handleSchemaRequest(inBuffer, sequence, replyToExchange, replyToKey); + else if (opcode == Protocol::OP_CONSOLE_ADDED_INDICATION) handleConsoleAddedIndication(); + else if (opcode == Protocol::OP_GET_QUERY) handleGetQuery(inBuffer, sequence, replyToKey, userId); + else if (opcode == Protocol::OP_METHOD_REQUEST) handleMethodRequest(inBuffer, sequence, replyToKey, userId); + else { + QPID_LOG(error, "AgentImpl::handleRcvMessage invalid opcode=" << opcode); + break; + } + } +} + +bool AgentImpl::getXmtMessage(Message& item) const +{ + Mutex::ScopedLock _lock(lock); + if (xmtQueue.empty()) + return false; + item = xmtQueue.front()->copy(); + return true; +} + +void AgentImpl::popXmt() +{ + Mutex::ScopedLock _lock(lock); + if (!xmtQueue.empty()) + xmtQueue.pop_front(); +} + +bool AgentImpl::getEvent(AgentEvent& event) const +{ + Mutex::ScopedLock _lock(lock); + if (eventQueue.empty()) + return false; + event = eventQueue.front()->copy(); + return true; +} + +void AgentImpl::popEvent() +{ + Mutex::ScopedLock _lock(lock); + if (!eventQueue.empty()) + eventQueue.pop_front(); +} + +void AgentImpl::newSession() +{ + Mutex::ScopedLock _lock(lock); + eventQueue.clear(); + xmtQueue.clear(); + eventQueue.push_back(eventDeclareQueue(queueName)); + eventQueue.push_back(eventBind("amq.direct", queueName, queueName)); + eventQueue.push_back(eventSetupComplete()); +} + +void AgentImpl::startProtocol() +{ + Mutex::ScopedLock _lock(lock); + char rawbuffer[512]; + Buffer buffer(rawbuffer, 512); + + Protocol::encodeHeader(buffer, Protocol::OP_ATTACH_REQUEST); + buffer.putShortString(label); + systemId.encode(buffer); + buffer.putLong(requestedBrokerBank); + buffer.putLong(requestedAgentBank); + sendBufferLH(buffer, QMF_EXCHANGE, BROKER_KEY); + QPID_LOG(trace, "SENT AttachRequest: reqBroker=" << requestedBrokerBank << + " reqAgent=" << requestedAgentBank); +} + +void AgentImpl::heartbeat() +{ + Mutex::ScopedLock _lock(lock); + Buffer buffer(outputBuffer, MA_BUFFER_SIZE); + + Protocol::encodeHeader(buffer, Protocol::OP_HEARTBEAT_INDICATION); + buffer.putLongLong(uint64_t(Duration(EPOCH, now()))); + stringstream key; + key << "console.heartbeat." << assignedBrokerBank << "." << assignedAgentBank; + sendBufferLH(buffer, QMF_EXCHANGE, key.str()); + QPID_LOG(trace, "SENT HeartbeatIndication"); +} + +void AgentImpl::methodResponse(uint32_t sequence, uint32_t status, char* text, const Value& argMap) +{ + Mutex::ScopedLock _lock(lock); + map<uint32_t, AgentQueryContext::Ptr>::iterator iter = contextMap.find(sequence); + if (iter == contextMap.end()) + return; + AgentQueryContext::Ptr context = iter->second; + contextMap.erase(iter); + + char* buf(outputBuffer); + uint32_t bufLen(114 + strlen(text)); // header(8) + status(4) + mstring(2 + size) + margin(100) + bool allocated(false); + + if (status == 0) { + for (vector<const SchemaArgument*>::const_iterator aIter = context->schemaMethod->impl->arguments.begin(); + aIter != context->schemaMethod->impl->arguments.end(); aIter++) { + const SchemaArgument* schemaArg = *aIter; + if (schemaArg->getDirection() == DIR_OUT || schemaArg->getDirection() == DIR_IN_OUT) { + if (argMap.keyInMap(schemaArg->getName())) { + const Value* val = argMap.byKey(schemaArg->getName()); + bufLen += val->impl->encodedSize(); + } else { + Value val(schemaArg->getType()); + bufLen += val.impl->encodedSize(); + } + } + } + } + + if (bufLen > MA_BUFFER_SIZE) { + buf = (char*) malloc(bufLen); + allocated = true; + } + + Buffer buffer(buf, bufLen); + Protocol::encodeHeader(buffer, Protocol::OP_METHOD_RESPONSE, context->sequence); + buffer.putLong(status); + buffer.putMediumString(text); + if (status == 0) { + for (vector<const SchemaArgument*>::const_iterator aIter = context->schemaMethod->impl->arguments.begin(); + aIter != context->schemaMethod->impl->arguments.end(); aIter++) { + const SchemaArgument* schemaArg = *aIter; + if (schemaArg->getDirection() == DIR_OUT || schemaArg->getDirection() == DIR_IN_OUT) { + if (argMap.keyInMap(schemaArg->getName())) { + const Value* val = argMap.byKey(schemaArg->getName()); + val->impl->encode(buffer); + } else { + Value val(schemaArg->getType()); + val.impl->encode(buffer); + } + } + } + } + sendBufferLH(buffer, context->exchange, context->key); + if (allocated) + free(buf); + QPID_LOG(trace, "SENT MethodResponse seq=" << context->sequence << " status=" << status << " text=" << text); +} + +void AgentImpl::queryResponse(uint32_t sequence, Object& object, bool prop, bool stat) +{ + Mutex::ScopedLock _lock(lock); + map<uint32_t, AgentQueryContext::Ptr>::iterator iter = contextMap.find(sequence); + if (iter == contextMap.end()) + return; + AgentQueryContext::Ptr context = iter->second; + + Buffer buffer(outputBuffer, MA_BUFFER_SIZE); + Protocol::encodeHeader(buffer, Protocol::OP_OBJECT_INDICATION, context->sequence); + + object.impl->encodeSchemaKey(buffer); + object.impl->encodeManagedObjectData(buffer); + if (prop) + object.impl->encodeProperties(buffer); + if (stat) + object.impl->encodeStatistics(buffer); + + sendBufferLH(buffer, context->exchange, context->key); + QPID_LOG(trace, "SENT ContentIndication seq=" << context->sequence); +} + +void AgentImpl::queryComplete(uint32_t sequence) +{ + Mutex::ScopedLock _lock(lock); + map<uint32_t, AgentQueryContext::Ptr>::iterator iter = contextMap.find(sequence); + if (iter == contextMap.end()) + return; + + AgentQueryContext::Ptr context = iter->second; + contextMap.erase(iter); + sendCommandCompleteLH(context->exchange, context->key, context->sequence, 0, "OK"); +} + +void AgentImpl::registerClass(SchemaObjectClass* cls) +{ + Mutex::ScopedLock _lock(lock); + bool newPackage = false; + + map<string, ClassMaps>::iterator iter = packages.find(cls->getClassKey()->getPackageName()); + if (iter == packages.end()) { + packages[cls->getClassKey()->getPackageName()] = ClassMaps(); + iter = packages.find(cls->getClassKey()->getPackageName()); + newPackage = true; + } + + AgentClassKey key(cls->getClassKey()->getClassName(), cls->getClassKey()->getHash()); + iter->second.objectClasses[key] = cls; + + // Indicate this new schema if connected. + + if (attachComplete) { + + if (newPackage) { + sendPackageIndicationLH(iter->first); + } + sendClassIndicationLH(CLASS_OBJECT, iter->first, key); + } +} + +void AgentImpl::registerClass(SchemaEventClass* cls) +{ + Mutex::ScopedLock _lock(lock); + bool newPackage = false; + + map<string, ClassMaps>::iterator iter = packages.find(cls->getClassKey()->getPackageName()); + if (iter == packages.end()) { + packages[cls->getClassKey()->getPackageName()] = ClassMaps(); + iter = packages.find(cls->getClassKey()->getPackageName()); + newPackage = true; + } + + AgentClassKey key(cls->getClassKey()->getClassName(), cls->getClassKey()->getHash()); + iter->second.eventClasses[key] = cls; + + // Indicate this new schema if connected. + + if (attachComplete) { + + if (newPackage) { + sendPackageIndicationLH(iter->first); + } + sendClassIndicationLH(CLASS_EVENT, iter->first, key); + } +} + +const ObjectId* AgentImpl::addObject(Object&, uint64_t) +{ + Mutex::ScopedLock _lock(lock); + return 0; +} + +const ObjectId* AgentImpl::allocObjectId(uint64_t persistId) +{ + Mutex::ScopedLock _lock(lock); + uint16_t sequence = persistId ? 0 : bootSequence; + uint64_t objectNum = persistId ? persistId : nextObjectId++; + + ObjectId* oid = ObjectIdImpl::factory(&attachment, 0, sequence, objectNum); + return oid; +} + +const ObjectId* AgentImpl::allocObjectId(uint32_t persistIdLo, uint32_t persistIdHi) +{ + return allocObjectId(((uint64_t) persistIdHi) << 32 | (uint64_t) persistIdLo); +} + +void AgentImpl::raiseEvent(Event& event) +{ + Mutex::ScopedLock _lock(lock); + Buffer buffer(outputBuffer, MA_BUFFER_SIZE); + Protocol::encodeHeader(buffer, Protocol::OP_EVENT_INDICATION); + + event.impl->encodeSchemaKey(buffer); + buffer.putLongLong(uint64_t(Duration(EPOCH, now()))); + event.impl->encode(buffer); + string key(event.impl->getRoutingKey(assignedBrokerBank, assignedAgentBank)); + + sendBufferLH(buffer, QMF_EXCHANGE, key); + QPID_LOG(trace, "SENT EventIndication"); +} + +AgentEventImpl::Ptr AgentImpl::eventDeclareQueue(const string& name) +{ + AgentEventImpl::Ptr event(new AgentEventImpl(AgentEvent::DECLARE_QUEUE)); + event->name = name; + + return event; +} + +AgentEventImpl::Ptr AgentImpl::eventBind(const string& exchange, const string& queue, + const string& key) +{ + AgentEventImpl::Ptr event(new AgentEventImpl(AgentEvent::BIND)); + event->name = queue; + event->exchange = exchange; + event->bindingKey = key; + + return event; +} + +AgentEventImpl::Ptr AgentImpl::eventSetupComplete() +{ + AgentEventImpl::Ptr event(new AgentEventImpl(AgentEvent::SETUP_COMPLETE)); + return event; +} + +AgentEventImpl::Ptr AgentImpl::eventQuery(uint32_t num, const string& userId, const string& package, + const string& cls, boost::shared_ptr<ObjectId> oid) +{ + AgentEventImpl::Ptr event(new AgentEventImpl(AgentEvent::GET_QUERY)); + event->sequence = num; + event->authUserId = userId; + if (oid.get()) + event->query.reset(new Query(oid.get())); + else + event->query.reset(new Query(cls.c_str(), package.c_str())); + return event; +} + +AgentEventImpl::Ptr AgentImpl::eventMethod(uint32_t num, const string& userId, const string& method, + boost::shared_ptr<ObjectId> oid, boost::shared_ptr<Value> argMap, + const SchemaObjectClass* objectClass) +{ + AgentEventImpl::Ptr event(new AgentEventImpl(AgentEvent::METHOD_CALL)); + event->sequence = num; + event->authUserId = userId; + event->name = method; + event->objectId = oid; + event->arguments = argMap; + event->objectClass = objectClass; + return event; +} + +void AgentImpl::sendBufferLH(Buffer& buf, const string& destination, const string& routingKey) +{ + uint32_t length = buf.getPosition(); + MessageImpl::Ptr message(new MessageImpl); + + buf.reset(); + buf.getRawData(message->body, length); + message->destination = destination; + message->routingKey = routingKey; + message->replyExchange = "amq.direct"; + message->replyKey = queueName; + + xmtQueue.push_back(message); +} + +void AgentImpl::sendPackageIndicationLH(const string& packageName) +{ + Buffer buffer(outputBuffer, MA_BUFFER_SIZE); + Protocol::encodeHeader(buffer, Protocol::OP_PACKAGE_INDICATION); + buffer.putShortString(packageName); + sendBufferLH(buffer, QMF_EXCHANGE, BROKER_KEY); + QPID_LOG(trace, "SENT PackageIndication: package_name=" << packageName); +} + +void AgentImpl::sendClassIndicationLH(ClassKind kind, const string& packageName, const AgentClassKey& key) +{ + Buffer buffer(outputBuffer, MA_BUFFER_SIZE); + Protocol::encodeHeader(buffer, Protocol::OP_CLASS_INDICATION); + buffer.putOctet((int) kind); + buffer.putShortString(packageName); + buffer.putShortString(key.name); + buffer.putBin128(const_cast<uint8_t*>(key.hash)); // const_cast needed for older Qpid libraries + sendBufferLH(buffer, QMF_EXCHANGE, BROKER_KEY); + QPID_LOG(trace, "SENT ClassIndication: package_name=" << packageName << " class_name=" << key.name); +} + +void AgentImpl::sendCommandCompleteLH(const string& exchange, const string& replyToKey, + uint32_t sequence, uint32_t code, const string& text) +{ + Buffer buffer(outputBuffer, MA_BUFFER_SIZE); + Protocol::encodeHeader(buffer, Protocol::OP_COMMAND_COMPLETE, sequence); + buffer.putLong(code); + buffer.putShortString(text); + sendBufferLH(buffer, exchange, replyToKey); + QPID_LOG(trace, "SENT CommandComplete: seq=" << sequence << " code=" << code << " text=" << text); +} + +void AgentImpl::sendMethodErrorLH(uint32_t sequence, const string& key, uint32_t code, const string& text) +{ + Buffer buffer(outputBuffer, MA_BUFFER_SIZE); + Protocol::encodeHeader(buffer, Protocol::OP_METHOD_RESPONSE, sequence); + buffer.putLong(code); + + string fulltext; + switch (code) { + case MERR_UNKNOWN_PACKAGE: fulltext = "Unknown Package"; break; + case MERR_UNKNOWN_CLASS: fulltext = "Unknown Class"; break; + case MERR_UNKNOWN_METHOD: fulltext = "Unknown Method"; break; + case MERR_INTERNAL_ERROR: fulltext = "Internal Error"; break; + default: fulltext = "Unspecified Error"; break; + } + + if (!text.empty()) { + fulltext += " ("; + fulltext += text; + fulltext += ")"; + } + + buffer.putMediumString(fulltext); + sendBufferLH(buffer, DIR_EXCHANGE, key); + QPID_LOG(trace, "SENT MethodResponse: errorCode=" << code << " text=" << fulltext); +} + +void AgentImpl::handleAttachResponse(Buffer& inBuffer) +{ + Mutex::ScopedLock _lock(lock); + + assignedBrokerBank = inBuffer.getLong(); + assignedAgentBank = inBuffer.getLong(); + + QPID_LOG(trace, "RCVD AttachResponse: broker=" << assignedBrokerBank << " agent=" << assignedAgentBank); + + if ((assignedBrokerBank != requestedBrokerBank) || + (assignedAgentBank != requestedAgentBank)) { + if (requestedAgentBank == 0) { + QPID_LOG(notice, "Initial object-id bank assigned: " << assignedBrokerBank << "." << + assignedAgentBank); + } else { + QPID_LOG(warning, "Collision in object-id! New bank assigned: " << assignedBrokerBank << + "." << assignedAgentBank); + } + //storeData(); // TODO + requestedBrokerBank = assignedBrokerBank; + requestedAgentBank = assignedAgentBank; + } + + attachment.setBanks(assignedBrokerBank, assignedAgentBank); + + // Bind to qpid.management to receive commands + stringstream key; + key << "agent." << assignedBrokerBank << "." << assignedAgentBank; + eventQueue.push_back(eventBind(QMF_EXCHANGE, queueName, key.str())); + + // Send package indications for all local packages + for (map<string, ClassMaps>::iterator pIter = packages.begin(); + pIter != packages.end(); + pIter++) { + sendPackageIndicationLH(pIter->first); + + // Send class indications for all local classes + ClassMaps cMap = pIter->second; + for (ObjectClassMap::iterator cIter = cMap.objectClasses.begin(); + cIter != cMap.objectClasses.end(); cIter++) + sendClassIndicationLH(CLASS_OBJECT, pIter->first, cIter->first); + for (EventClassMap::iterator cIter = cMap.eventClasses.begin(); + cIter != cMap.eventClasses.end(); cIter++) + sendClassIndicationLH(CLASS_EVENT, pIter->first, cIter->first); + } + + attachComplete = true; +} + +void AgentImpl::handlePackageRequest(Buffer&) +{ + Mutex::ScopedLock _lock(lock); +} + +void AgentImpl::handleClassQuery(Buffer&) +{ + Mutex::ScopedLock _lock(lock); +} + +void AgentImpl::handleSchemaRequest(Buffer& inBuffer, uint32_t sequence, + const string& replyExchange, const string& replyKey) +{ + Mutex::ScopedLock _lock(lock); + string rExchange(replyExchange); + string rKey(replyKey); + string packageName; + inBuffer.getShortString(packageName); + AgentClassKey key(inBuffer); + + if (rExchange.empty()) + rExchange = QMF_EXCHANGE; + if (rKey.empty()) + rKey = BROKER_KEY; + + QPID_LOG(trace, "RCVD SchemaRequest: package=" << packageName << " class=" << key.name); + + map<string, ClassMaps>::iterator pIter = packages.find(packageName); + if (pIter == packages.end()) { + sendCommandCompleteLH(rExchange, rKey, sequence, 1, "package not found"); + return; + } + + ClassMaps cMap = pIter->second; + ObjectClassMap::iterator ocIter = cMap.objectClasses.find(key); + if (ocIter != cMap.objectClasses.end()) { + SchemaObjectClass* oImpl = ocIter->second; + Buffer buffer(outputBuffer, MA_BUFFER_SIZE); + Protocol::encodeHeader(buffer, Protocol::OP_SCHEMA_RESPONSE, sequence); + oImpl->impl->encode(buffer); + sendBufferLH(buffer, rExchange, rKey); + QPID_LOG(trace, "SENT SchemaResponse: (object) package=" << packageName << " class=" << key.name); + return; + } + + EventClassMap::iterator ecIter = cMap.eventClasses.find(key); + if (ecIter != cMap.eventClasses.end()) { + SchemaEventClass* eImpl = ecIter->second; + Buffer buffer(outputBuffer, MA_BUFFER_SIZE); + Protocol::encodeHeader(buffer, Protocol::OP_SCHEMA_RESPONSE, sequence); + eImpl->impl->encode(buffer); + sendBufferLH(buffer, rExchange, rKey); + QPID_LOG(trace, "SENT SchemaResponse: (event) package=" << packageName << " class=" << key.name); + return; + } + + sendCommandCompleteLH(rExchange, rKey, sequence, 1, "class not found"); +} + +void AgentImpl::handleGetQuery(Buffer& inBuffer, uint32_t sequence, const string& replyTo, const string& userId) +{ + Mutex::ScopedLock _lock(lock); + FieldTable ft; + FieldTable::ValuePtr value; + map<string, ClassMaps>::const_iterator pIter = packages.end(); + string pname; + string cname; + string oidRepr; + boost::shared_ptr<ObjectId> oid; + + ft.decode(inBuffer); + + QPID_LOG(trace, "RCVD GetQuery: seq=" << sequence << " map=" << ft); + + value = ft.get("_package"); + if (value.get() && value->convertsTo<string>()) { + pname = value->get<string>(); + pIter = packages.find(pname); + if (pIter == packages.end()) { + sendCommandCompleteLH(DIR_EXCHANGE, replyTo, sequence); + return; + } + } + + value = ft.get("_class"); + if (value.get() && value->convertsTo<string>()) { + cname = value->get<string>(); + // TODO - check for validity of class (in package or any package) + if (pIter == packages.end()) { + } else { + + } + } + + value = ft.get("_objectid"); + if (value.get() && value->convertsTo<string>()) { + oidRepr = value->get<string>(); + oid.reset(new ObjectId()); + oid->impl->fromString(oidRepr); + } + + AgentQueryContext::Ptr context(new AgentQueryContext); + uint32_t contextNum = nextContextNum++; + context->sequence = sequence; + context->exchange = DIR_EXCHANGE; + context->key = replyTo; + contextMap[contextNum] = context; + + eventQueue.push_back(eventQuery(contextNum, userId, pname, cname, oid)); +} + +void AgentImpl::handleMethodRequest(Buffer& buffer, uint32_t sequence, const string& replyTo, const string& userId) +{ + Mutex::ScopedLock _lock(lock); + string pname; + string method; + boost::shared_ptr<ObjectId> oid(ObjectIdImpl::factory(buffer)); + buffer.getShortString(pname); + AgentClassKey classKey(buffer); + buffer.getShortString(method); + + QPID_LOG(trace, "RCVD MethodRequest seq=" << sequence << " method=" << method); + + map<string, ClassMaps>::const_iterator pIter = packages.find(pname); + if (pIter == packages.end()) { + sendMethodErrorLH(sequence, replyTo, MERR_UNKNOWN_PACKAGE, pname); + return; + } + + ObjectClassMap::const_iterator cIter = pIter->second.objectClasses.find(classKey); + if (cIter == pIter->second.objectClasses.end()) { + sendMethodErrorLH(sequence, replyTo, MERR_UNKNOWN_CLASS, classKey.repr()); + return; + } + + const SchemaObjectClass* schema = cIter->second; + vector<const SchemaMethod*>::const_iterator mIter = schema->impl->methods.begin(); + for (; mIter != schema->impl->methods.end(); mIter++) { + if ((*mIter)->getName() == method) + break; + } + + if (mIter == schema->impl->methods.end()) { + sendMethodErrorLH(sequence, replyTo, MERR_UNKNOWN_METHOD, method); + return; + } + + const SchemaMethod* schemaMethod = *mIter; + boost::shared_ptr<Value> argMap(new Value(TYPE_MAP)); + Value* value; + for (vector<const SchemaArgument*>::const_iterator aIter = schemaMethod->impl->arguments.begin(); + aIter != schemaMethod->impl->arguments.end(); aIter++) { + const SchemaArgument* schemaArg = *aIter; + if (schemaArg->getDirection() == DIR_IN || schemaArg->getDirection() == DIR_IN_OUT) + value = ValueImpl::factory(schemaArg->getType(), buffer); + else + value = ValueImpl::factory(schemaArg->getType()); + argMap->insert(schemaArg->getName(), value); + } + + AgentQueryContext::Ptr context(new AgentQueryContext); + uint32_t contextNum = nextContextNum++; + context->sequence = sequence; + context->exchange = DIR_EXCHANGE; + context->key = replyTo; + context->schemaMethod = schemaMethod; + contextMap[contextNum] = context; + + eventQueue.push_back(eventMethod(contextNum, userId, method, oid, argMap, schema)); +} + +void AgentImpl::handleConsoleAddedIndication() +{ + Mutex::ScopedLock _lock(lock); +} + +//================================================================== +// Wrappers +//================================================================== + +Agent::Agent(char* label, bool internalStore) { impl = new AgentImpl(label, internalStore); } +Agent::~Agent() { delete impl; } +void Agent::setStoreDir(const char* path) { impl->setStoreDir(path); } +void Agent::setTransferDir(const char* path) { impl->setTransferDir(path); } +void Agent::handleRcvMessage(Message& message) { impl->handleRcvMessage(message); } +bool Agent::getXmtMessage(Message& item) const { return impl->getXmtMessage(item); } +void Agent::popXmt() { impl->popXmt(); } +bool Agent::getEvent(AgentEvent& event) const { return impl->getEvent(event); } +void Agent::popEvent() { impl->popEvent(); } +void Agent::newSession() { impl->newSession(); } +void Agent::startProtocol() { impl->startProtocol(); } +void Agent::heartbeat() { impl->heartbeat(); } +void Agent::methodResponse(uint32_t sequence, uint32_t status, char* text, const Value& arguments) { impl->methodResponse(sequence, status, text, arguments); } +void Agent::queryResponse(uint32_t sequence, Object& object, bool prop, bool stat) { impl->queryResponse(sequence, object, prop, stat); } +void Agent::queryComplete(uint32_t sequence) { impl->queryComplete(sequence); } +void Agent::registerClass(SchemaObjectClass* cls) { impl->registerClass(cls); } +void Agent::registerClass(SchemaEventClass* cls) { impl->registerClass(cls); } +const ObjectId* Agent::addObject(Object& obj, uint64_t persistId) { return impl->addObject(obj, persistId); } +const ObjectId* Agent::allocObjectId(uint64_t persistId) { return impl->allocObjectId(persistId); } +const ObjectId* Agent::allocObjectId(uint32_t persistIdLo, uint32_t persistIdHi) { return impl->allocObjectId(persistIdLo, persistIdHi); } +void Agent::raiseEvent(Event& event) { impl->raiseEvent(event); } + diff --git a/qpid/cpp/src/qmf/engine/BrokerProxyImpl.cpp b/qpid/cpp/src/qmf/engine/BrokerProxyImpl.cpp new file mode 100644 index 0000000000..5fc71979fd --- /dev/null +++ b/qpid/cpp/src/qmf/engine/BrokerProxyImpl.cpp @@ -0,0 +1,827 @@ +/* + * 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/engine/BrokerProxyImpl.h" +#include "qmf/engine/ConsoleImpl.h" +#include "qmf/engine/Protocol.h" +#include "qpid/Address.h" +#include "qpid/sys/SystemInfo.h" +#include <qpid/log/Statement.h> +#include <qpid/StringUtils.h> +#include <string.h> +#include <iostream> +#include <fstream> + +using namespace std; +using namespace qmf::engine; +using namespace qpid::framing; +using namespace qpid::sys; + +namespace { + const char* QMF_EXCHANGE = "qpid.management"; + const char* DIR_EXCHANGE = "amq.direct"; + const char* BROKER_KEY = "broker"; + const char* BROKER_PACKAGE = "org.apache.qpid.broker"; + const char* AGENT_CLASS = "agent"; + const char* BROKER_AGENT_KEY = "agent.1.0"; +} + +const Object* QueryResponseImpl::getObject(uint32_t idx) const +{ + vector<ObjectPtr>::const_iterator iter = results.begin(); + + while (idx > 0) { + if (iter == results.end()) + return 0; + iter++; + idx--; + } + + return iter->get(); +} + +#define STRING_REF(s) {if (!s.empty()) item.s = const_cast<char*>(s.c_str());} + +BrokerEvent BrokerEventImpl::copy() +{ + BrokerEvent item; + + ::memset(&item, 0, sizeof(BrokerEvent)); + item.kind = kind; + + STRING_REF(name); + STRING_REF(exchange); + STRING_REF(bindingKey); + item.context = context; + item.queryResponse = queryResponse.get(); + item.methodResponse = methodResponse.get(); + + return item; +} + +BrokerProxyImpl::BrokerProxyImpl(BrokerProxy& pub, Console& _console) : publicObject(pub), console(_console) +{ + stringstream qn; + qpid::Address addr; + + SystemInfo::getLocalHostname(addr); + qn << "qmfc-" << SystemInfo::getProcessName() << "-" << addr << "-" << SystemInfo::getProcessId(); + queueName = qn.str(); + + seqMgr.setUnsolicitedContext(SequenceContext::Ptr(new StaticContext(*this))); +} + +void BrokerProxyImpl::sessionOpened(SessionHandle& /*sh*/) +{ + Mutex::ScopedLock _lock(lock); + agentList.clear(); + eventQueue.clear(); + xmtQueue.clear(); + eventQueue.push_back(eventDeclareQueue(queueName)); + eventQueue.push_back(eventBind(DIR_EXCHANGE, queueName, queueName)); + eventQueue.push_back(eventSetupComplete()); + + // TODO: Store session handle +} + +void BrokerProxyImpl::sessionClosed() +{ + Mutex::ScopedLock _lock(lock); + agentList.clear(); + eventQueue.clear(); + xmtQueue.clear(); +} + +void BrokerProxyImpl::startProtocol() +{ + AgentProxyPtr agent(AgentProxyImpl::factory(console, publicObject, 0, "Agent embedded in broker")); + { + Mutex::ScopedLock _lock(lock); + char rawbuffer[512]; + Buffer buffer(rawbuffer, 512); + + agentList[0] = agent; + + requestsOutstanding = 1; + topicBound = false; + uint32_t sequence(seqMgr.reserve()); + Protocol::encodeHeader(buffer, Protocol::OP_BROKER_REQUEST, sequence); + sendBufferLH(buffer, QMF_EXCHANGE, BROKER_KEY); + QPID_LOG(trace, "SENT BrokerRequest seq=" << sequence); + } + + console.impl->eventAgentAdded(agent); +} + +void BrokerProxyImpl::sendBufferLH(Buffer& buf, const string& destination, const string& routingKey) +{ + uint32_t length = buf.getPosition(); + MessageImpl::Ptr message(new MessageImpl); + + buf.reset(); + buf.getRawData(message->body, length); + message->destination = destination; + message->routingKey = routingKey; + message->replyExchange = DIR_EXCHANGE; + message->replyKey = queueName; + + xmtQueue.push_back(message); +} + +void BrokerProxyImpl::handleRcvMessage(Message& message) +{ + Buffer inBuffer(message.body, message.length); + uint8_t opcode; + uint32_t sequence; + + while (Protocol::checkHeader(inBuffer, &opcode, &sequence)) + seqMgr.dispatch(opcode, sequence, message.routingKey ? string(message.routingKey) : string(), inBuffer); +} + +bool BrokerProxyImpl::getXmtMessage(Message& item) const +{ + Mutex::ScopedLock _lock(lock); + if (xmtQueue.empty()) + return false; + item = xmtQueue.front()->copy(); + return true; +} + +void BrokerProxyImpl::popXmt() +{ + Mutex::ScopedLock _lock(lock); + if (!xmtQueue.empty()) + xmtQueue.pop_front(); +} + +bool BrokerProxyImpl::getEvent(BrokerEvent& event) const +{ + Mutex::ScopedLock _lock(lock); + if (eventQueue.empty()) + return false; + event = eventQueue.front()->copy(); + return true; +} + +void BrokerProxyImpl::popEvent() +{ + Mutex::ScopedLock _lock(lock); + if (!eventQueue.empty()) + eventQueue.pop_front(); +} + +uint32_t BrokerProxyImpl::agentCount() const +{ + Mutex::ScopedLock _lock(lock); + return agentList.size(); +} + +const AgentProxy* BrokerProxyImpl::getAgent(uint32_t idx) const +{ + Mutex::ScopedLock _lock(lock); + for (map<uint32_t, AgentProxyPtr>::const_iterator iter = agentList.begin(); + iter != agentList.end(); iter++) + if (idx-- == 0) + return iter->second.get(); + return 0; +} + +void BrokerProxyImpl::sendQuery(const Query& query, void* context, const AgentProxy* agent) +{ + SequenceContext::Ptr queryContext(new QueryContext(*this, context)); + Mutex::ScopedLock _lock(lock); + bool sent = false; + if (agent != 0) { + if (sendGetRequestLH(queryContext, query, agent)) + sent = true; + } else { + // TODO (optimization) only send queries to agents that have the requested class+package + for (map<uint32_t, AgentProxyPtr>::const_iterator iter = agentList.begin(); + iter != agentList.end(); iter++) { + if (sendGetRequestLH(queryContext, query, iter->second.get())) + sent = true; + } + } + + if (!sent) { + queryContext->reserve(); + queryContext->release(); + } +} + +bool BrokerProxyImpl::sendGetRequestLH(SequenceContext::Ptr queryContext, const Query& query, const AgentProxy* agent) +{ + if (query.impl->singleAgent()) { + if (query.impl->agentBank() != agent->getAgentBank()) + return false; + } + stringstream key; + Buffer outBuffer(outputBuffer, MA_BUFFER_SIZE); + uint32_t sequence(seqMgr.reserve(queryContext)); + agent->impl->addSequence(sequence); + + Protocol::encodeHeader(outBuffer, Protocol::OP_GET_QUERY, sequence); + query.impl->encode(outBuffer); + key << "agent.1." << agent->impl->agentBank; + sendBufferLH(outBuffer, QMF_EXCHANGE, key.str()); + QPID_LOG(trace, "SENT GetQuery seq=" << sequence << " key=" << key.str()); + return true; +} + +string BrokerProxyImpl::encodeMethodArguments(const SchemaMethod* schema, const Value* argmap, Buffer& buffer) +{ + int argCount = schema->getArgumentCount(); + + if (argmap == 0 || !argmap->isMap()) + return string("Arguments must be in a map value"); + + for (int aIdx = 0; aIdx < argCount; aIdx++) { + const SchemaArgument* arg(schema->getArgument(aIdx)); + if (arg->getDirection() == DIR_IN || arg->getDirection() == DIR_IN_OUT) { + if (argmap->keyInMap(arg->getName())) { + const Value* argVal(argmap->byKey(arg->getName())); + if (argVal->getType() != arg->getType()) + return string("Argument is the wrong type: ") + arg->getName(); + argVal->impl->encode(buffer); + } else { + Value defaultValue(arg->getType()); + defaultValue.impl->encode(buffer); + } + } + } + + return string(); +} + +string BrokerProxyImpl::encodedSizeMethodArguments(const SchemaMethod* schema, const Value* argmap, uint32_t& size) +{ + int argCount = schema->getArgumentCount(); + + if (argmap == 0 || !argmap->isMap()) + return string("Arguments must be in a map value"); + + for (int aIdx = 0; aIdx < argCount; aIdx++) { + const SchemaArgument* arg(schema->getArgument(aIdx)); + if (arg->getDirection() == DIR_IN || arg->getDirection() == DIR_IN_OUT) { + if (argmap->keyInMap(arg->getName())) { + const Value* argVal(argmap->byKey(arg->getName())); + if (argVal->getType() != arg->getType()) + return string("Argument is the wrong type: ") + arg->getName(); + size += argVal->impl->encodedSize(); + } else { + Value defaultValue(arg->getType()); + size += defaultValue.impl->encodedSize(); + } + } + } + + return string(); +} + +void BrokerProxyImpl::sendMethodRequest(ObjectId* oid, const SchemaObjectClass* cls, + const string& methodName, const Value* args, void* userContext) +{ + int methodCount = cls->getMethodCount(); + int idx; + for (idx = 0; idx < methodCount; idx++) { + const SchemaMethod* method = cls->getMethod(idx); + if (string(method->getName()) == methodName) { + Mutex::ScopedLock _lock(lock); + SequenceContext::Ptr methodContext(new MethodContext(*this, userContext, method)); + stringstream key; + char* buf(outputBuffer); + uint32_t bufLen(1024); + bool allocated(false); + + string argErrorString = encodedSizeMethodArguments(method, args, bufLen); + if (!argErrorString.empty()) { + MethodResponsePtr argError(MethodResponseImpl::factory(1, argErrorString)); + eventQueue.push_back(eventMethodResponse(userContext, argError)); + return; + } + + if (bufLen > MA_BUFFER_SIZE) { + buf = (char*) malloc(bufLen); + allocated = true; + } + + Buffer outBuffer(buf, bufLen); + uint32_t sequence(seqMgr.reserve(methodContext)); + + Protocol::encodeHeader(outBuffer, Protocol::OP_METHOD_REQUEST, sequence); + oid->impl->encode(outBuffer); + cls->getClassKey()->impl->encode(outBuffer); + outBuffer.putShortString(methodName); + + encodeMethodArguments(method, args, outBuffer); + key << "agent.1." << oid->impl->getAgentBank(); + sendBufferLH(outBuffer, QMF_EXCHANGE, key.str()); + QPID_LOG(trace, "SENT MethodRequest seq=" << sequence << " method=" << methodName << " key=" << key.str()); + + if (allocated) + free(buf); + + return; + } + } + + MethodResponsePtr error(MethodResponseImpl::factory(1, string("Unknown method: ") + methodName)); + Mutex::ScopedLock _lock(lock); + eventQueue.push_back(eventMethodResponse(userContext, error)); +} + +void BrokerProxyImpl::addBinding(const string& exchange, const string& key) +{ + Mutex::ScopedLock _lock(lock); + eventQueue.push_back(eventBind(exchange, queueName, key)); +} + +BrokerEventImpl::Ptr BrokerProxyImpl::eventDeclareQueue(const string& queueName) +{ + BrokerEventImpl::Ptr event(new BrokerEventImpl(BrokerEvent::DECLARE_QUEUE)); + event->name = queueName; + return event; +} + +BrokerEventImpl::Ptr BrokerProxyImpl::eventBind(const string& exchange, const string& queue, const string& key) +{ + BrokerEventImpl::Ptr event(new BrokerEventImpl(BrokerEvent::BIND)); + event->name = queue; + event->exchange = exchange; + event->bindingKey = key; + + return event; +} + +BrokerEventImpl::Ptr BrokerProxyImpl::eventSetupComplete() +{ + BrokerEventImpl::Ptr event(new BrokerEventImpl(BrokerEvent::SETUP_COMPLETE)); + return event; +} + +BrokerEventImpl::Ptr BrokerProxyImpl::eventStable() +{ + QPID_LOG(trace, "Console Link to Broker Stable"); + BrokerEventImpl::Ptr event(new BrokerEventImpl(BrokerEvent::STABLE)); + return event; +} + +BrokerEventImpl::Ptr BrokerProxyImpl::eventQueryComplete(void* context, QueryResponsePtr response) +{ + BrokerEventImpl::Ptr event(new BrokerEventImpl(BrokerEvent::QUERY_COMPLETE)); + event->context = context; + event->queryResponse = response; + return event; +} + +BrokerEventImpl::Ptr BrokerProxyImpl::eventMethodResponse(void* context, MethodResponsePtr response) +{ + BrokerEventImpl::Ptr event(new BrokerEventImpl(BrokerEvent::METHOD_RESPONSE)); + event->context = context; + event->methodResponse = response; + return event; +} + +void BrokerProxyImpl::handleBrokerResponse(Buffer& inBuffer, uint32_t seq) +{ + brokerId.decode(inBuffer); + QPID_LOG(trace, "RCVD BrokerResponse seq=" << seq << " brokerId=" << brokerId); + Mutex::ScopedLock _lock(lock); + Buffer outBuffer(outputBuffer, MA_BUFFER_SIZE); + uint32_t sequence(seqMgr.reserve()); + incOutstandingLH(); + Protocol::encodeHeader(outBuffer, Protocol::OP_PACKAGE_REQUEST, sequence); + sendBufferLH(outBuffer, QMF_EXCHANGE, BROKER_KEY); + QPID_LOG(trace, "SENT PackageRequest seq=" << sequence); +} + +void BrokerProxyImpl::handlePackageIndication(Buffer& inBuffer, uint32_t seq) +{ + string package; + + inBuffer.getShortString(package); + QPID_LOG(trace, "RCVD PackageIndication seq=" << seq << " package=" << package); + console.impl->learnPackage(package); + + Mutex::ScopedLock _lock(lock); + Buffer outBuffer(outputBuffer, MA_BUFFER_SIZE); + uint32_t sequence(seqMgr.reserve()); + incOutstandingLH(); + Protocol::encodeHeader(outBuffer, Protocol::OP_CLASS_QUERY, sequence); + outBuffer.putShortString(package); + sendBufferLH(outBuffer, QMF_EXCHANGE, BROKER_KEY); + QPID_LOG(trace, "SENT ClassQuery seq=" << sequence << " package=" << package); +} + +void BrokerProxyImpl::handleCommandComplete(Buffer& inBuffer, uint32_t seq) +{ + string text; + uint32_t code = inBuffer.getLong(); + inBuffer.getShortString(text); + QPID_LOG(trace, "RCVD CommandComplete seq=" << seq << " code=" << code << " text=" << text); +} + +void BrokerProxyImpl::handleClassIndication(Buffer& inBuffer, uint32_t seq) +{ + uint8_t kind = inBuffer.getOctet(); + auto_ptr<SchemaClassKey> classKey(SchemaClassKeyImpl::factory(inBuffer)); + + QPID_LOG(trace, "RCVD ClassIndication seq=" << seq << " kind=" << (int) kind << " key=" << classKey->impl->str()); + + if (!console.impl->haveClass(classKey.get())) { + Mutex::ScopedLock _lock(lock); + incOutstandingLH(); + Buffer outBuffer(outputBuffer, MA_BUFFER_SIZE); + uint32_t sequence(seqMgr.reserve()); + Protocol::encodeHeader(outBuffer, Protocol::OP_SCHEMA_REQUEST, sequence); + classKey->impl->encode(outBuffer); + sendBufferLH(outBuffer, QMF_EXCHANGE, BROKER_KEY); + QPID_LOG(trace, "SENT SchemaRequest seq=" << sequence <<" key=" << classKey->impl->str()); + } +} + +MethodResponsePtr BrokerProxyImpl::handleMethodResponse(Buffer& inBuffer, uint32_t seq, const SchemaMethod* schema) +{ + MethodResponsePtr response(MethodResponseImpl::factory(inBuffer, schema)); + + QPID_LOG(trace, "RCVD MethodResponse seq=" << seq << " status=" << response->getStatus() << " text=" << + response->getException()->asString()); + + return response; +} + +void BrokerProxyImpl::handleHeartbeatIndication(Buffer& inBuffer, uint32_t seq, const string& routingKey) +{ + vector<string> tokens = qpid::split(routingKey, "."); + uint32_t agentBank; + uint64_t timestamp; + + if (routingKey.empty() || tokens.size() != 4) + agentBank = 0; + else + agentBank = ::atoi(tokens[3].c_str()); + + timestamp = inBuffer.getLongLong(); + map<uint32_t, AgentProxyPtr>::const_iterator iter = agentList.find(agentBank); + if (iter != agentList.end()) { + console.impl->eventAgentHeartbeat(iter->second, timestamp); + } + QPID_LOG(trace, "RCVD HeartbeatIndication seq=" << seq << " agentBank=" << agentBank); +} + +void BrokerProxyImpl::handleEventIndication(Buffer& inBuffer, uint32_t seq) +{ + auto_ptr<SchemaClassKey> classKey(SchemaClassKeyImpl::factory(inBuffer)); + const SchemaEventClass *schema = console.impl->getEventClass(classKey.get()); + if (schema == 0) { + QPID_LOG(trace, "No Schema Found for EventIndication. seq=" << seq << " key=" << classKey->impl->str()); + return; + } + + EventPtr eptr(EventImpl::factory(schema, inBuffer)); + + console.impl->eventEventReceived(eptr); + QPID_LOG(trace, "RCVD EventIndication seq=" << seq << " key=" << classKey->impl->str()); +} + +void BrokerProxyImpl::handleSchemaResponse(Buffer& inBuffer, uint32_t seq) +{ + SchemaObjectClass* oClassPtr; + SchemaEventClass* eClassPtr; + uint8_t kind = inBuffer.getOctet(); + const SchemaClassKey* key; + if (kind == CLASS_OBJECT) { + oClassPtr = SchemaObjectClassImpl::factory(inBuffer); + console.impl->learnClass(oClassPtr); + key = oClassPtr->getClassKey(); + QPID_LOG(trace, "RCVD SchemaResponse seq=" << seq << " kind=object key=" << key->impl->str()); + + // + // If we have just learned about the org.apache.qpid.broker:agent class, send a get + // request for the current list of agents so we can have it on-hand before we declare + // this session "stable". + // + if (key->impl->getClassName() == AGENT_CLASS && key->impl->getPackageName() == BROKER_PACKAGE) { + Mutex::ScopedLock _lock(lock); + incOutstandingLH(); + Buffer outBuffer(outputBuffer, MA_BUFFER_SIZE); + uint32_t sequence(seqMgr.reserve()); + Protocol::encodeHeader(outBuffer, Protocol::OP_GET_QUERY, sequence); + FieldTable ft; + ft.setString("_class", AGENT_CLASS); + ft.setString("_package", BROKER_PACKAGE); + ft.encode(outBuffer); + sendBufferLH(outBuffer, QMF_EXCHANGE, BROKER_AGENT_KEY); + QPID_LOG(trace, "SENT GetQuery seq=" << sequence << " key=" << BROKER_AGENT_KEY); + } + } else if (kind == CLASS_EVENT) { + eClassPtr = SchemaEventClassImpl::factory(inBuffer); + console.impl->learnClass(eClassPtr); + key = eClassPtr->getClassKey(); + QPID_LOG(trace, "RCVD SchemaResponse seq=" << seq << " kind=event key=" << key->impl->str()); + } + else { + QPID_LOG(error, "BrokerProxyImpl::handleSchemaResponse received unknown class kind: " << (int) kind); + } +} + +ObjectPtr BrokerProxyImpl::handleObjectIndication(Buffer& inBuffer, uint32_t seq, bool prop, bool stat) +{ + auto_ptr<SchemaClassKey> classKey(SchemaClassKeyImpl::factory(inBuffer)); + QPID_LOG(trace, "RCVD ObjectIndication seq=" << seq << " key=" << classKey->impl->str()); + + SchemaObjectClass* schema = console.impl->getSchema(classKey.get()); + if (schema == 0) { + QPID_LOG(trace, "No Schema Found for ObjectIndication. seq=" << seq << " key=" << classKey->impl->str()); + return ObjectPtr(); + } + + ObjectPtr optr(ObjectImpl::factory(schema, this, inBuffer, prop, stat, true)); + if (prop && classKey->impl->getPackageName() == BROKER_PACKAGE && classKey->impl->getClassName() == AGENT_CLASS) { + // + // We've intercepted information about a remote agent... update the agent list accordingly + // + updateAgentList(optr); + } + return optr; +} + +void BrokerProxyImpl::updateAgentList(ObjectPtr obj) +{ + Value* value = obj->getValue("agentBank"); + Mutex::ScopedLock _lock(lock); + if (value != 0 && value->isUint()) { + uint32_t agentBank = value->asUint(); + if (obj->isDeleted()) { + map<uint32_t, AgentProxyPtr>::iterator iter = agentList.find(agentBank); + if (iter != agentList.end()) { + AgentProxyPtr agent(iter->second); + console.impl->eventAgentDeleted(agent); + agentList.erase(agentBank); + QPID_LOG(trace, "Agent at bank " << agentBank << " removed from agent list"); + + // + // Release all sequence numbers for requests in-flight to this agent. + // Since the agent is no longer connected, these requests would not + // otherwise complete. + // + agent->impl->releaseInFlight(seqMgr); + } + } else { + Value* str = obj->getValue("label"); + string label; + if (str != 0 && str->isString()) + label = str->asString(); + map<uint32_t, AgentProxyPtr>::const_iterator iter = agentList.find(agentBank); + if (iter == agentList.end()) { + AgentProxyPtr agent(AgentProxyImpl::factory(console, publicObject, agentBank, label)); + agentList[agentBank] = agent; + console.impl->eventAgentAdded(agent); + QPID_LOG(trace, "Agent '" << label << "' found at bank " << agentBank); + } + } + } +} + +void BrokerProxyImpl::incOutstandingLH() +{ + requestsOutstanding++; +} + +void BrokerProxyImpl::decOutstanding() +{ + Mutex::ScopedLock _lock(lock); + requestsOutstanding--; + if (requestsOutstanding == 0 && !topicBound) { + topicBound = true; + for (vector<pair<string, string> >::const_iterator iter = console.impl->bindingList.begin(); + iter != console.impl->bindingList.end(); iter++) { + string exchange(iter->first.empty() ? QMF_EXCHANGE : iter->first); + string key(iter->second); + eventQueue.push_back(eventBind(exchange, queueName, key)); + } + eventQueue.push_back(eventStable()); + } +} + +MethodResponseImpl::MethodResponseImpl(const MethodResponseImpl& from) : + status(from.status), schema(from.schema) +{ + if (from.exception.get()) + exception.reset(new Value(*(from.exception))); + if (from.arguments.get()) + arguments.reset(new Value(*(from.arguments))); +} + +MethodResponseImpl::MethodResponseImpl(Buffer& buf, const SchemaMethod* s) : schema(s) +{ + string text; + + status = buf.getLong(); + buf.getMediumString(text); + exception.reset(new Value(TYPE_LSTR)); + exception->setString(text.c_str()); + + if (status != 0) + return; + + arguments.reset(new Value(TYPE_MAP)); + int argCount(schema->getArgumentCount()); + for (int idx = 0; idx < argCount; idx++) { + const SchemaArgument* arg = schema->getArgument(idx); + if (arg->getDirection() == DIR_OUT || arg->getDirection() == DIR_IN_OUT) { + Value* value(ValueImpl::factory(arg->getType(), buf)); + arguments->insert(arg->getName(), value); + } + } +} + +MethodResponseImpl::MethodResponseImpl(uint32_t s, const string& text) : schema(0) +{ + status = s; + exception.reset(new Value(TYPE_LSTR)); + exception->setString(text.c_str()); +} + +MethodResponse* MethodResponseImpl::factory(Buffer& buf, const SchemaMethod* schema) +{ + MethodResponseImpl* impl(new MethodResponseImpl(buf, schema)); + return new MethodResponse(impl); +} + +MethodResponse* MethodResponseImpl::factory(uint32_t status, const std::string& text) +{ + MethodResponseImpl* impl(new MethodResponseImpl(status, text)); + return new MethodResponse(impl); +} + +bool StaticContext::handleMessage(uint8_t opcode, uint32_t sequence, const string& routingKey, Buffer& buffer) +{ + ObjectPtr object; + bool completeContext = false; + + if (opcode == Protocol::OP_BROKER_RESPONSE) { + broker.handleBrokerResponse(buffer, sequence); + completeContext = true; + } + else if (opcode == Protocol::OP_COMMAND_COMPLETE) { + broker.handleCommandComplete(buffer, sequence); + completeContext = true; + } + else if (opcode == Protocol::OP_SCHEMA_RESPONSE) { + broker.handleSchemaResponse(buffer, sequence); + completeContext = true; + } + else if (opcode == Protocol::OP_PACKAGE_INDICATION) + broker.handlePackageIndication(buffer, sequence); + else if (opcode == Protocol::OP_CLASS_INDICATION) + broker.handleClassIndication(buffer, sequence); + else if (opcode == Protocol::OP_HEARTBEAT_INDICATION) + broker.handleHeartbeatIndication(buffer, sequence, routingKey); + else if (opcode == Protocol::OP_EVENT_INDICATION) + broker.handleEventIndication(buffer, sequence); + else if (opcode == Protocol::OP_PROPERTY_INDICATION) { + object = broker.handleObjectIndication(buffer, sequence, true, false); + broker.console.impl->eventObjectUpdate(object, true, false); + } + else if (opcode == Protocol::OP_STATISTIC_INDICATION) { + object = broker.handleObjectIndication(buffer, sequence, false, true); + broker.console.impl->eventObjectUpdate(object, false, true); + } + else if (opcode == Protocol::OP_OBJECT_INDICATION) { + object = broker.handleObjectIndication(buffer, sequence, true, true); + broker.console.impl->eventObjectUpdate(object, true, true); + } + else { + QPID_LOG(trace, "StaticContext::handleMessage invalid opcode: " << opcode); + completeContext = true; + } + + return completeContext; +} + +void QueryContext::reserve() +{ + Mutex::ScopedLock _lock(lock); + requestsOutstanding++; +} + +void QueryContext::release() +{ + { + Mutex::ScopedLock _lock(lock); + if (--requestsOutstanding > 0) + return; + } + + Mutex::ScopedLock _block(broker.lock); + broker.eventQueue.push_back(broker.eventQueryComplete(userContext, queryResponse)); +} + +bool QueryContext::handleMessage(uint8_t opcode, uint32_t sequence, const string& /*routingKey*/, Buffer& buffer) +{ + bool completeContext = false; + ObjectPtr object; + + if (opcode == Protocol::OP_COMMAND_COMPLETE) { + broker.handleCommandComplete(buffer, sequence); + completeContext = true; + + // + // Visit each agent and remove the sequence from that agent's in-flight list. + // This could be made more efficient because only one agent will have this sequence + // in its list. + // + map<uint32_t, AgentProxyPtr> copy; + { + Mutex::ScopedLock _block(broker.lock); + copy = broker.agentList; + } + for (map<uint32_t, AgentProxyPtr>::iterator iter = copy.begin(); iter != copy.end(); iter++) + iter->second->impl->delSequence(sequence); + } + else if (opcode == Protocol::OP_OBJECT_INDICATION) { + object = broker.handleObjectIndication(buffer, sequence, true, true); + if (object.get() != 0) + queryResponse->impl->results.push_back(object); + } + else { + QPID_LOG(trace, "QueryContext::handleMessage invalid opcode: " << opcode); + completeContext = true; + } + + return completeContext; +} + +void MethodContext::release() +{ + Mutex::ScopedLock _block(broker.lock); + broker.eventQueue.push_back(broker.eventMethodResponse(userContext, methodResponse)); +} + +bool MethodContext::handleMessage(uint8_t opcode, uint32_t sequence, const string& /*routingKey*/, Buffer& buffer) +{ + if (opcode == Protocol::OP_METHOD_RESPONSE) + methodResponse = broker.handleMethodResponse(buffer, sequence, schema); + else + QPID_LOG(trace, "QueryContext::handleMessage invalid opcode: " << opcode); + + return true; +} + + +//================================================================== +// Wrappers +//================================================================== + +AgentProxy::AgentProxy(AgentProxyImpl* i) : impl(i) {} +AgentProxy::AgentProxy(const AgentProxy& from) : impl(new AgentProxyImpl(*(from.impl))) {} +AgentProxy::~AgentProxy() { delete impl; } +const char* AgentProxy::getLabel() const { return impl->getLabel().c_str(); } +uint32_t AgentProxy::getBrokerBank() const { return impl->getBrokerBank(); } +uint32_t AgentProxy::getAgentBank() const { return impl->getAgentBank(); } + +BrokerProxy::BrokerProxy(Console& console) : impl(new BrokerProxyImpl(*this, console)) {} +BrokerProxy::~BrokerProxy() { delete impl; } +void BrokerProxy::sessionOpened(SessionHandle& sh) { impl->sessionOpened(sh); } +void BrokerProxy::sessionClosed() { impl->sessionClosed(); } +void BrokerProxy::startProtocol() { impl->startProtocol(); } +void BrokerProxy::handleRcvMessage(Message& message) { impl->handleRcvMessage(message); } +bool BrokerProxy::getXmtMessage(Message& item) const { return impl->getXmtMessage(item); } +void BrokerProxy::popXmt() { impl->popXmt(); } +bool BrokerProxy::getEvent(BrokerEvent& event) const { return impl->getEvent(event); } +void BrokerProxy::popEvent() { impl->popEvent(); } +uint32_t BrokerProxy::agentCount() const { return impl->agentCount(); } +const AgentProxy* BrokerProxy::getAgent(uint32_t idx) const { return impl->getAgent(idx); } +void BrokerProxy::sendQuery(const Query& query, void* context, const AgentProxy* agent) { impl->sendQuery(query, context, agent); } + +MethodResponse::MethodResponse(const MethodResponse& from) : impl(new MethodResponseImpl(*(from.impl))) {} +MethodResponse::MethodResponse(MethodResponseImpl* i) : impl(i) {} +MethodResponse::~MethodResponse() {} +uint32_t MethodResponse::getStatus() const { return impl->getStatus(); } +const Value* MethodResponse::getException() const { return impl->getException(); } +const Value* MethodResponse::getArgs() const { return impl->getArgs(); } + +QueryResponse::QueryResponse(QueryResponseImpl* i) : impl(i) {} +QueryResponse::~QueryResponse() {} +uint32_t QueryResponse::getStatus() const { return impl->getStatus(); } +const Value* QueryResponse::getException() const { return impl->getException(); } +uint32_t QueryResponse::getObjectCount() const { return impl->getObjectCount(); } +const Object* QueryResponse::getObject(uint32_t idx) const { return impl->getObject(idx); } + diff --git a/qpid/cpp/src/qmf/engine/BrokerProxyImpl.h b/qpid/cpp/src/qmf/engine/BrokerProxyImpl.h new file mode 100644 index 0000000000..0542b67dbb --- /dev/null +++ b/qpid/cpp/src/qmf/engine/BrokerProxyImpl.h @@ -0,0 +1,241 @@ +#ifndef _QmfEngineBrokerProxyImpl_ +#define _QmfEngineBrokerProxyImpl_ + +/* + * 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/engine/Console.h" +#include "qmf/engine/ObjectImpl.h" +#include "qmf/engine/EventImpl.h" +#include "qmf/engine/SchemaImpl.h" +#include "qmf/engine/ValueImpl.h" +#include "qmf/engine/QueryImpl.h" +#include "qmf/engine/SequenceManager.h" +#include "qmf/engine/MessageImpl.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/Uuid.h" +#include "qpid/sys/Mutex.h" +#include "boost/shared_ptr.hpp" +#include "boost/noncopyable.hpp" +#include <memory> +#include <string> +#include <deque> +#include <map> +#include <set> +#include <vector> + +namespace qmf { +namespace engine { + + typedef boost::shared_ptr<MethodResponse> MethodResponsePtr; + struct MethodResponseImpl { + uint32_t status; + const SchemaMethod* schema; + std::auto_ptr<Value> exception; + std::auto_ptr<Value> arguments; + + MethodResponseImpl(const MethodResponseImpl& from); + MethodResponseImpl(qpid::framing::Buffer& buf, const SchemaMethod* schema); + MethodResponseImpl(uint32_t status, const std::string& text); + static MethodResponse* factory(qpid::framing::Buffer& buf, const SchemaMethod* schema); + static MethodResponse* factory(uint32_t status, const std::string& text); + ~MethodResponseImpl() {} + uint32_t getStatus() const { return status; } + const Value* getException() const { return exception.get(); } + const Value* getArgs() const { return arguments.get(); } + }; + + typedef boost::shared_ptr<QueryResponse> QueryResponsePtr; + struct QueryResponseImpl { + uint32_t status; + std::auto_ptr<Value> exception; + std::vector<ObjectPtr> results; + + QueryResponseImpl() : status(0) {} + static QueryResponse* factory() { + QueryResponseImpl* impl(new QueryResponseImpl()); + return new QueryResponse(impl); + } + ~QueryResponseImpl() {} + uint32_t getStatus() const { return status; } + const Value* getException() const { return exception.get(); } + uint32_t getObjectCount() const { return results.size(); } + const Object* getObject(uint32_t idx) const; + }; + + struct BrokerEventImpl { + typedef boost::shared_ptr<BrokerEventImpl> Ptr; + BrokerEvent::EventKind kind; + std::string name; + std::string exchange; + std::string bindingKey; + void* context; + QueryResponsePtr queryResponse; + MethodResponsePtr methodResponse; + + BrokerEventImpl(BrokerEvent::EventKind k) : kind(k), context(0) {} + ~BrokerEventImpl() {} + BrokerEvent copy(); + }; + + typedef boost::shared_ptr<AgentProxy> AgentProxyPtr; + struct AgentProxyImpl { + Console& console; + BrokerProxy& broker; + uint32_t agentBank; + std::string label; + std::set<uint32_t> inFlightSequences; + + AgentProxyImpl(Console& c, BrokerProxy& b, uint32_t ab, const std::string& l) : console(c), broker(b), agentBank(ab), label(l) {} + static AgentProxy* factory(Console& c, BrokerProxy& b, uint32_t ab, const std::string& l) { + AgentProxyImpl* impl(new AgentProxyImpl(c, b, ab, l)); + return new AgentProxy(impl); + } + ~AgentProxyImpl() {} + const std::string& getLabel() const { return label; } + uint32_t getBrokerBank() const { return 1; } + uint32_t getAgentBank() const { return agentBank; } + void addSequence(uint32_t seq) { inFlightSequences.insert(seq); } + void delSequence(uint32_t seq) { inFlightSequences.erase(seq); } + void releaseInFlight(SequenceManager& seqMgr) { + for (std::set<uint32_t>::iterator iter = inFlightSequences.begin(); iter != inFlightSequences.end(); iter++) + seqMgr.release(*iter); + inFlightSequences.clear(); + } + }; + + class BrokerProxyImpl : public boost::noncopyable { + public: + BrokerProxyImpl(BrokerProxy& pub, Console& _console); + ~BrokerProxyImpl() {} + + void sessionOpened(SessionHandle& sh); + void sessionClosed(); + void startProtocol(); + + void sendBufferLH(qpid::framing::Buffer& buf, const std::string& destination, const std::string& routingKey); + void handleRcvMessage(Message& message); + bool getXmtMessage(Message& item) const; + void popXmt(); + + bool getEvent(BrokerEvent& event) const; + void popEvent(); + + uint32_t agentCount() const; + const AgentProxy* getAgent(uint32_t idx) const; + void sendQuery(const Query& query, void* context, const AgentProxy* agent); + bool sendGetRequestLH(SequenceContext::Ptr queryContext, const Query& query, const AgentProxy* agent); + std::string encodeMethodArguments(const SchemaMethod* schema, const Value* args, qpid::framing::Buffer& buffer); + std::string encodedSizeMethodArguments(const SchemaMethod* schema, const Value* args, uint32_t& size); + void sendMethodRequest(ObjectId* oid, const SchemaObjectClass* cls, const std::string& method, const Value* args, void* context); + + void addBinding(const std::string& exchange, const std::string& key); + void staticRelease() { decOutstanding(); } + + private: + friend struct StaticContext; + friend struct QueryContext; + friend struct MethodContext; + BrokerProxy& publicObject; + mutable qpid::sys::Mutex lock; + Console& console; + std::string queueName; + qpid::framing::Uuid brokerId; + SequenceManager seqMgr; + uint32_t requestsOutstanding; + bool topicBound; + std::map<uint32_t, AgentProxyPtr> agentList; + std::deque<MessageImpl::Ptr> xmtQueue; + std::deque<BrokerEventImpl::Ptr> eventQueue; + +# define MA_BUFFER_SIZE 65536 + char outputBuffer[MA_BUFFER_SIZE]; + + BrokerEventImpl::Ptr eventDeclareQueue(const std::string& queueName); + BrokerEventImpl::Ptr eventBind(const std::string& exchange, const std::string& queue, const std::string& key); + BrokerEventImpl::Ptr eventSetupComplete(); + BrokerEventImpl::Ptr eventStable(); + BrokerEventImpl::Ptr eventQueryComplete(void* context, QueryResponsePtr response); + BrokerEventImpl::Ptr eventMethodResponse(void* context, MethodResponsePtr response); + + void handleBrokerResponse(qpid::framing::Buffer& inBuffer, uint32_t seq); + void handlePackageIndication(qpid::framing::Buffer& inBuffer, uint32_t seq); + void handleCommandComplete(qpid::framing::Buffer& inBuffer, uint32_t seq); + void handleClassIndication(qpid::framing::Buffer& inBuffer, uint32_t seq); + MethodResponsePtr handleMethodResponse(qpid::framing::Buffer& inBuffer, uint32_t seq, const SchemaMethod* schema); + void handleHeartbeatIndication(qpid::framing::Buffer& inBuffer, uint32_t seq, const std::string& routingKey); + void handleEventIndication(qpid::framing::Buffer& inBuffer, uint32_t seq); + void handleSchemaResponse(qpid::framing::Buffer& inBuffer, uint32_t seq); + ObjectPtr handleObjectIndication(qpid::framing::Buffer& inBuffer, uint32_t seq, bool prop, bool stat); + void updateAgentList(ObjectPtr obj); + void incOutstandingLH(); + void decOutstanding(); + }; + + // + // StaticContext is used to handle: + // + // 1) Responses to console-level requests (for schema info, etc.) + // 2) Unsolicited messages from agents (events, published updates, etc.) + // + struct StaticContext : public SequenceContext { + StaticContext(BrokerProxyImpl& b) : broker(b) {} + virtual ~StaticContext() {} + void reserve() {} + void release() { broker.staticRelease(); } + bool handleMessage(uint8_t opcode, uint32_t sequence, const std::string& routingKey, qpid::framing::Buffer& buffer); + BrokerProxyImpl& broker; + }; + + // + // QueryContext is used to track and handle responses associated with a single Get Query + // + struct QueryContext : public SequenceContext { + QueryContext(BrokerProxyImpl& b, void* u) : + broker(b), userContext(u), requestsOutstanding(0), queryResponse(QueryResponseImpl::factory()) {} + virtual ~QueryContext() {} + void reserve(); + void release(); + bool handleMessage(uint8_t opcode, uint32_t sequence, const std::string& routingKey, qpid::framing::Buffer& buffer); + + mutable qpid::sys::Mutex lock; + BrokerProxyImpl& broker; + void* userContext; + uint32_t requestsOutstanding; + QueryResponsePtr queryResponse; + }; + + struct MethodContext : public SequenceContext { + MethodContext(BrokerProxyImpl& b, void* u, const SchemaMethod* s) : broker(b), userContext(u), schema(s) {} + virtual ~MethodContext() {} + void reserve() {} + void release(); + bool handleMessage(uint8_t opcode, uint32_t sequence, const std::string& routingKey, qpid::framing::Buffer& buffer); + + BrokerProxyImpl& broker; + void* userContext; + const SchemaMethod* schema; + MethodResponsePtr methodResponse; + }; + +} +} + +#endif + diff --git a/qpid/cpp/src/qmf/engine/ConnectionSettingsImpl.cpp b/qpid/cpp/src/qmf/engine/ConnectionSettingsImpl.cpp new file mode 100644 index 0000000000..22a65f28ca --- /dev/null +++ b/qpid/cpp/src/qmf/engine/ConnectionSettingsImpl.cpp @@ -0,0 +1,278 @@ +/* + * 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/engine/ConnectionSettingsImpl.h" +#include "qmf/engine/Typecode.h" + +using namespace std; +using namespace qmf::engine; +using namespace qpid; + +const string attrProtocol("protocol"); +const string attrHost("host"); +const string attrPort("port"); +const string attrVirtualhost("virtualhost"); +const string attrUsername("username"); +const string attrPassword("password"); +const string attrMechanism("mechanism"); +const string attrLocale("locale"); +const string attrHeartbeat("heartbeat"); +const string attrMaxChannels("maxChannels"); +const string attrMaxFrameSize("maxFrameSize"); +const string attrBounds("bounds"); +const string attrTcpNoDelay("tcpNoDelay"); +const string attrService("service"); +const string attrMinSsf("minSsf"); +const string attrMaxSsf("maxSsf"); +const string attrRetryDelayMin("retryDelayMin"); +const string attrRetryDelayMax("retryDelayMax"); +const string attrRetryDelayFactor("retryDelayFactor"); +const string attrSendUserId("sendUserId"); + +ConnectionSettingsImpl::ConnectionSettingsImpl() : + retryDelayMin(1), retryDelayMax(64), retryDelayFactor(2), sendUserId(true) +{ +} + +ConnectionSettingsImpl::ConnectionSettingsImpl(const string& /*url*/) : + retryDelayMin(1), retryDelayMax(64), retryDelayFactor(2), sendUserId(true) +{ + // TODO: Parse the URL +} + +bool ConnectionSettingsImpl::setAttr(const string& key, const Value& value) +{ + if (key == attrProtocol) clientSettings.protocol = value.asString(); + else if (key == attrHost) clientSettings.host = value.asString(); + else if (key == attrPort) clientSettings.port = value.asUint(); + else if (key == attrVirtualhost) clientSettings.virtualhost = value.asString(); + else if (key == attrUsername) clientSettings.username = value.asString(); + else if (key == attrPassword) clientSettings.password = value.asString(); + else if (key == attrMechanism) clientSettings.mechanism = value.asString(); + else if (key == attrLocale) clientSettings.locale = value.asString(); + else if (key == attrHeartbeat) clientSettings.heartbeat = value.asUint(); + else if (key == attrMaxChannels) clientSettings.maxChannels = value.asUint(); + else if (key == attrMaxFrameSize) clientSettings.maxFrameSize = value.asUint(); + else if (key == attrBounds) clientSettings.bounds = value.asUint(); + else if (key == attrTcpNoDelay) clientSettings.tcpNoDelay = value.asBool(); + else if (key == attrService) clientSettings.service = value.asString(); + else if (key == attrMinSsf) clientSettings.minSsf = value.asUint(); + else if (key == attrMaxSsf) clientSettings.maxSsf = value.asUint(); + + else if (key == attrRetryDelayMin) retryDelayMin = value.asUint(); + else if (key == attrRetryDelayMax) retryDelayMax = value.asUint(); + else if (key == attrRetryDelayFactor) retryDelayFactor = value.asUint(); + else if (key == attrSendUserId) sendUserId = value.asBool(); + else + return false; + return true; +} + +Value ConnectionSettingsImpl::getAttr(const string& key) const +{ + Value strval(TYPE_LSTR); + Value intval(TYPE_UINT32); + Value boolval(TYPE_BOOL); + + if (key == attrProtocol) { + strval.setString(clientSettings.protocol.c_str()); + return strval; + } + + if (key == attrHost) { + strval.setString(clientSettings.host.c_str()); + return strval; + } + + if (key == attrPort) { + intval.setUint(clientSettings.port); + return intval; + } + + if (key == attrVirtualhost) { + strval.setString(clientSettings.virtualhost.c_str()); + return strval; + } + + if (key == attrUsername) { + strval.setString(clientSettings.username.c_str()); + return strval; + } + + if (key == attrPassword) { + strval.setString(clientSettings.password.c_str()); + return strval; + } + + if (key == attrMechanism) { + strval.setString(clientSettings.mechanism.c_str()); + return strval; + } + + if (key == attrLocale) { + strval.setString(clientSettings.locale.c_str()); + return strval; + } + + if (key == attrHeartbeat) { + intval.setUint(clientSettings.heartbeat); + return intval; + } + + if (key == attrMaxChannels) { + intval.setUint(clientSettings.maxChannels); + return intval; + } + + if (key == attrMaxFrameSize) { + intval.setUint(clientSettings.maxFrameSize); + return intval; + } + + if (key == attrBounds) { + intval.setUint(clientSettings.bounds); + return intval; + } + + if (key == attrTcpNoDelay) { + boolval.setBool(clientSettings.tcpNoDelay); + return boolval; + } + + if (key == attrService) { + strval.setString(clientSettings.service.c_str()); + return strval; + } + + if (key == attrMinSsf) { + intval.setUint(clientSettings.minSsf); + return intval; + } + + if (key == attrMaxSsf) { + intval.setUint(clientSettings.maxSsf); + return intval; + } + + if (key == attrRetryDelayMin) { + intval.setUint(retryDelayMin); + return intval; + } + + if (key == attrRetryDelayMax) { + intval.setUint(retryDelayMax); + return intval; + } + + if (key == attrRetryDelayFactor) { + intval.setUint(retryDelayFactor); + return intval; + } + + if (key == attrSendUserId) { + boolval.setBool(sendUserId); + return boolval; + } + + return strval; +} + +const string& ConnectionSettingsImpl::getAttrString() const +{ + // TODO: build and return attribute string + return attrString; +} + +void ConnectionSettingsImpl::transportTcp(uint16_t port) +{ + clientSettings.protocol = "tcp"; + clientSettings.port = port; +} + +void ConnectionSettingsImpl::transportSsl(uint16_t port) +{ + clientSettings.protocol = "ssl"; + clientSettings.port = port; +} + +void ConnectionSettingsImpl::transportRdma(uint16_t port) +{ + clientSettings.protocol = "rdma"; + clientSettings.port = port; +} + +void ConnectionSettingsImpl::authAnonymous(const string& username) +{ + clientSettings.mechanism = "ANONYMOUS"; + clientSettings.username = username; +} + +void ConnectionSettingsImpl::authPlain(const string& username, const string& password) +{ + clientSettings.mechanism = "PLAIN"; + clientSettings.username = username; + clientSettings.password = password; +} + +void ConnectionSettingsImpl::authGssapi(const string& serviceName, uint32_t minSsf, uint32_t maxSsf) +{ + clientSettings.mechanism = "GSSAPI"; + clientSettings.service = serviceName; + clientSettings.minSsf = minSsf; + clientSettings.maxSsf = maxSsf; +} + +void ConnectionSettingsImpl::setRetry(int delayMin, int delayMax, int delayFactor) +{ + retryDelayMin = delayMin; + retryDelayMax = delayMax; + retryDelayFactor = delayFactor; +} + +const client::ConnectionSettings& ConnectionSettingsImpl::getClientSettings() const +{ + return clientSettings; +} + +void ConnectionSettingsImpl::getRetrySettings(int* min, int* max, int* factor) const +{ + *min = retryDelayMin; + *max = retryDelayMax; + *factor = retryDelayFactor; +} + +//================================================================== +// Wrappers +//================================================================== + +ConnectionSettings::ConnectionSettings(const ConnectionSettings& from) { impl = new ConnectionSettingsImpl(*from.impl); } +ConnectionSettings::ConnectionSettings() { impl = new ConnectionSettingsImpl(); } +ConnectionSettings::ConnectionSettings(const char* url) { impl = new ConnectionSettingsImpl(url); } +ConnectionSettings::~ConnectionSettings() { delete impl; } +bool ConnectionSettings::setAttr(const char* key, const Value& value) { return impl->setAttr(key, value); } +Value ConnectionSettings::getAttr(const char* key) const { return impl->getAttr(key); } +const char* ConnectionSettings::getAttrString() const { return impl->getAttrString().c_str(); } +void ConnectionSettings::transportTcp(uint16_t port) { impl->transportTcp(port); } +void ConnectionSettings::transportSsl(uint16_t port) { impl->transportSsl(port); } +void ConnectionSettings::transportRdma(uint16_t port) { impl->transportRdma(port); } +void ConnectionSettings::authAnonymous(const char* username) { impl->authAnonymous(username); } +void ConnectionSettings::authPlain(const char* username, const char* password) { impl->authPlain(username, password); } +void ConnectionSettings::authGssapi(const char* serviceName, uint32_t minSsf, uint32_t maxSsf) { impl->authGssapi(serviceName, minSsf, maxSsf); } +void ConnectionSettings::setRetry(int delayMin, int delayMax, int delayFactor) { impl->setRetry(delayMin, delayMax, delayFactor); } + diff --git a/qpid/cpp/src/qmf/engine/ConnectionSettingsImpl.h b/qpid/cpp/src/qmf/engine/ConnectionSettingsImpl.h new file mode 100644 index 0000000000..98bf87868b --- /dev/null +++ b/qpid/cpp/src/qmf/engine/ConnectionSettingsImpl.h @@ -0,0 +1,63 @@ +#ifndef _QmfEngineConnectionSettingsImpl_ +#define _QmfEngineConnectionSettingsImpl_ + +/* + * 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/engine/ConnectionSettings.h" +#include "qmf/engine/Value.h" +#include "qpid/client/ConnectionSettings.h" +#include <string> +#include <map> + +namespace qmf { +namespace engine { + + class ConnectionSettingsImpl { + qpid::client::ConnectionSettings clientSettings; + mutable std::string attrString; + int retryDelayMin; + int retryDelayMax; + int retryDelayFactor; + bool sendUserId; + + public: + ConnectionSettingsImpl(); + ConnectionSettingsImpl(const std::string& url); + ~ConnectionSettingsImpl() {} + bool setAttr(const std::string& key, const Value& value); + Value getAttr(const std::string& key) const; + const std::string& getAttrString() const; + void transportTcp(uint16_t port); + void transportSsl(uint16_t port); + void transportRdma(uint16_t port); + void authAnonymous(const std::string& username); + void authPlain(const std::string& username, const std::string& password); + void authGssapi(const std::string& serviceName, uint32_t minSsf, uint32_t maxSsf); + void setRetry(int delayMin, int delayMax, int delayFactor); + + const qpid::client::ConnectionSettings& getClientSettings() const; + void getRetrySettings(int* delayMin, int* delayMax, int* delayFactor) const; + bool getSendUserId() const { return sendUserId; } + }; + +} +} + +#endif diff --git a/qpid/cpp/src/qmf/engine/ConsoleImpl.cpp b/qpid/cpp/src/qmf/engine/ConsoleImpl.cpp new file mode 100644 index 0000000000..4a5da31bdc --- /dev/null +++ b/qpid/cpp/src/qmf/engine/ConsoleImpl.cpp @@ -0,0 +1,458 @@ +/* + * 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/engine/ConsoleImpl.h" +#include "qmf/engine/MessageImpl.h" +#include "qmf/engine/SchemaImpl.h" +#include "qmf/engine/Typecode.h" +#include "qmf/engine/ObjectImpl.h" +#include "qmf/engine/ObjectIdImpl.h" +#include "qmf/engine/QueryImpl.h" +#include "qmf/engine/ValueImpl.h" +#include "qmf/engine/Protocol.h" +#include "qmf/engine/SequenceManager.h" +#include "qmf/engine/BrokerProxyImpl.h" +#include <qpid/framing/Buffer.h> +#include <qpid/framing/Uuid.h> +#include <qpid/framing/FieldTable.h> +#include <qpid/framing/FieldValue.h> +#include <qpid/log/Statement.h> +#include <qpid/sys/Time.h> +#include <qpid/sys/SystemInfo.h> +#include <string.h> +#include <iostream> +#include <fstream> + +using namespace std; +using namespace qmf::engine; +using namespace qpid::framing; +using namespace qpid::sys; + +namespace { + const char* QMF_EXCHANGE = "qpid.management"; +} + +#define STRING_REF(s) {if (!s.empty()) item.s = const_cast<char*>(s.c_str());} + +ConsoleEvent ConsoleEventImpl::copy() +{ + ConsoleEvent item; + + ::memset(&item, 0, sizeof(ConsoleEvent)); + item.kind = kind; + item.agent = agent.get(); + item.classKey = classKey; + item.object = object.get(); + item.context = context; + item.event = event.get(); + item.timestamp = timestamp; + item.hasProps = hasProps; + item.hasStats = hasStats; + + STRING_REF(name); + + return item; +} + +ConsoleImpl::ConsoleImpl(const ConsoleSettings& s) : settings(s) +{ + bindingList.push_back(pair<string, string>(string(), "schema.#")); + if (settings.rcvObjects && settings.rcvEvents && settings.rcvHeartbeats && !settings.userBindings) { + bindingList.push_back(pair<string, string>(string(), "console.#")); + } else { + if (settings.rcvObjects && !settings.userBindings) + bindingList.push_back(pair<string, string>(string(), "console.obj.#")); + else + bindingList.push_back(pair<string, string>(string(), "console.obj.*.*.org.apache.qpid.broker.agent")); + if (settings.rcvEvents) + bindingList.push_back(pair<string, string>(string(), "console.event.#")); + if (settings.rcvHeartbeats) + bindingList.push_back(pair<string, string>(string(), "console.heartbeat.#")); + } +} + +ConsoleImpl::~ConsoleImpl() +{ + // This function intentionally left blank. +} + +bool ConsoleImpl::getEvent(ConsoleEvent& event) const +{ + Mutex::ScopedLock _lock(lock); + if (eventQueue.empty()) + return false; + event = eventQueue.front()->copy(); + return true; +} + +void ConsoleImpl::popEvent() +{ + Mutex::ScopedLock _lock(lock); + if (!eventQueue.empty()) + eventQueue.pop_front(); +} + +void ConsoleImpl::addConnection(BrokerProxy& broker, void* /*context*/) +{ + Mutex::ScopedLock _lock(lock); + brokerList.push_back(broker.impl); +} + +void ConsoleImpl::delConnection(BrokerProxy& broker) +{ + Mutex::ScopedLock _lock(lock); + for (vector<BrokerProxyImpl*>::iterator iter = brokerList.begin(); + iter != brokerList.end(); iter++) + if (*iter == broker.impl) { + brokerList.erase(iter); + break; + } +} + +uint32_t ConsoleImpl::packageCount() const +{ + Mutex::ScopedLock _lock(lock); + return packages.size(); +} + +const string& ConsoleImpl::getPackageName(uint32_t idx) const +{ + const static string empty; + + Mutex::ScopedLock _lock(lock); + if (idx >= packages.size()) + return empty; + + PackageList::const_iterator iter = packages.begin(); + for (uint32_t i = 0; i < idx; i++) iter++; + return iter->first; +} + +uint32_t ConsoleImpl::classCount(const char* packageName) const +{ + Mutex::ScopedLock _lock(lock); + PackageList::const_iterator pIter = packages.find(packageName); + if (pIter == packages.end()) + return 0; + + const ObjectClassList& oList = pIter->second.first; + const EventClassList& eList = pIter->second.second; + + return oList.size() + eList.size(); +} + +const SchemaClassKey* ConsoleImpl::getClass(const char* packageName, uint32_t idx) const +{ + Mutex::ScopedLock _lock(lock); + PackageList::const_iterator pIter = packages.find(packageName); + if (pIter == packages.end()) + return 0; + + const ObjectClassList& oList = pIter->second.first; + const EventClassList& eList = pIter->second.second; + uint32_t count = 0; + + for (ObjectClassList::const_iterator oIter = oList.begin(); + oIter != oList.end(); oIter++) { + if (count == idx) + return oIter->second->getClassKey(); + count++; + } + + for (EventClassList::const_iterator eIter = eList.begin(); + eIter != eList.end(); eIter++) { + if (count == idx) + return eIter->second->getClassKey(); + count++; + } + + return 0; +} + +ClassKind ConsoleImpl::getClassKind(const SchemaClassKey* key) const +{ + Mutex::ScopedLock _lock(lock); + PackageList::const_iterator pIter = packages.find(key->getPackageName()); + if (pIter == packages.end()) + return CLASS_OBJECT; + + const EventClassList& eList = pIter->second.second; + if (eList.find(key) != eList.end()) + return CLASS_EVENT; + return CLASS_OBJECT; +} + +const SchemaObjectClass* ConsoleImpl::getObjectClass(const SchemaClassKey* key) const +{ + Mutex::ScopedLock _lock(lock); + PackageList::const_iterator pIter = packages.find(key->getPackageName()); + if (pIter == packages.end()) + return 0; + + const ObjectClassList& oList = pIter->second.first; + ObjectClassList::const_iterator iter = oList.find(key); + if (iter == oList.end()) + return 0; + return iter->second; +} + +const SchemaEventClass* ConsoleImpl::getEventClass(const SchemaClassKey* key) const +{ + Mutex::ScopedLock _lock(lock); + PackageList::const_iterator pIter = packages.find(key->getPackageName()); + if (pIter == packages.end()) + return 0; + + const EventClassList& eList = pIter->second.second; + EventClassList::const_iterator iter = eList.find(key); + if (iter == eList.end()) + return 0; + return iter->second; +} + +void ConsoleImpl::bindPackage(const char* packageName) +{ + stringstream key; + key << "console.obj.*.*." << packageName << ".#"; + Mutex::ScopedLock _lock(lock); + bindingList.push_back(pair<string, string>(string(), key.str())); + for (vector<BrokerProxyImpl*>::iterator iter = brokerList.begin(); + iter != brokerList.end(); iter++) + (*iter)->addBinding(QMF_EXCHANGE, key.str()); +} + +void ConsoleImpl::bindClass(const SchemaClassKey* classKey) +{ + stringstream key; + key << "console.obj.*.*." << classKey->getPackageName() << "." << classKey->getClassName() << ".#"; + Mutex::ScopedLock _lock(lock); + bindingList.push_back(pair<string, string>(string(), key.str())); + for (vector<BrokerProxyImpl*>::iterator iter = brokerList.begin(); + iter != brokerList.end(); iter++) + (*iter)->addBinding(QMF_EXCHANGE, key.str()); +} + +void ConsoleImpl::bindClass(const char* packageName, const char* className) +{ + stringstream key; + key << "console.obj.*.*." << packageName << "." << className << ".#"; + Mutex::ScopedLock _lock(lock); + bindingList.push_back(pair<string, string>(string(), key.str())); + for (vector<BrokerProxyImpl*>::iterator iter = brokerList.begin(); + iter != brokerList.end(); iter++) + (*iter)->addBinding(QMF_EXCHANGE, key.str()); +} + + +void ConsoleImpl::bindEvent(const SchemaClassKey* classKey) +{ + bindEvent(classKey->getPackageName(), classKey->getClassName()); +} + +void ConsoleImpl::bindEvent(const char* packageName, const char* eventName) +{ + if (!settings.userBindings) throw qpid::Exception("Console not configured for userBindings."); + if (settings.rcvEvents) throw qpid::Exception("Console already configured to receive all events."); + + stringstream key; + key << "console.event.*.*." << packageName; + if (eventName && *eventName) { + key << "." << eventName << ".#"; + } else { + key << ".#"; + } + + Mutex::ScopedLock _lock(lock); + bindingList.push_back(pair<string, string>(string(), key.str())); + for (vector<BrokerProxyImpl*>::iterator iter = brokerList.begin(); + iter != brokerList.end(); iter++) + (*iter)->addBinding(QMF_EXCHANGE, key.str()); +} + +/* +void ConsoleImpl::startSync(const Query& query, void* context, SyncQuery& sync) +{ +} + +void ConsoleImpl::touchSync(SyncQuery& sync) +{ +} + +void ConsoleImpl::endSync(SyncQuery& sync) +{ +} +*/ + +void ConsoleImpl::learnPackage(const string& packageName) +{ + Mutex::ScopedLock _lock(lock); + if (packages.find(packageName) == packages.end()) { + packages.insert(pair<string, pair<ObjectClassList, EventClassList> > + (packageName, pair<ObjectClassList, EventClassList>(ObjectClassList(), EventClassList()))); + eventNewPackage(packageName); + } +} + +void ConsoleImpl::learnClass(SchemaObjectClass* cls) +{ + Mutex::ScopedLock _lock(lock); + const SchemaClassKey* key = cls->getClassKey(); + PackageList::iterator pIter = packages.find(key->getPackageName()); + if (pIter == packages.end()) + return; + + ObjectClassList& list = pIter->second.first; + if (list.find(key) == list.end()) { + list[key] = cls; + eventNewClass(key); + } +} + +void ConsoleImpl::learnClass(SchemaEventClass* cls) +{ + Mutex::ScopedLock _lock(lock); + const SchemaClassKey* key = cls->getClassKey(); + PackageList::iterator pIter = packages.find(key->getPackageName()); + if (pIter == packages.end()) + return; + + EventClassList& list = pIter->second.second; + if (list.find(key) == list.end()) { + list[key] = cls; + eventNewClass(key); + } +} + +bool ConsoleImpl::haveClass(const SchemaClassKey* key) const +{ + Mutex::ScopedLock _lock(lock); + PackageList::const_iterator pIter = packages.find(key->getPackageName()); + if (pIter == packages.end()) + return false; + + const ObjectClassList& oList = pIter->second.first; + const EventClassList& eList = pIter->second.second; + + return oList.find(key) != oList.end() || eList.find(key) != eList.end(); +} + +SchemaObjectClass* ConsoleImpl::getSchema(const SchemaClassKey* key) const +{ + Mutex::ScopedLock _lock(lock); + PackageList::const_iterator pIter = packages.find(key->getPackageName()); + if (pIter == packages.end()) + return 0; + + const ObjectClassList& oList = pIter->second.first; + ObjectClassList::const_iterator iter = oList.find(key); + if (iter == oList.end()) + return 0; + + return iter->second; +} + +void ConsoleImpl::eventAgentAdded(boost::shared_ptr<AgentProxy> agent) +{ + ConsoleEventImpl::Ptr event(new ConsoleEventImpl(ConsoleEvent::AGENT_ADDED)); + event->agent = agent; + Mutex::ScopedLock _lock(lock); + eventQueue.push_back(event); +} + +void ConsoleImpl::eventAgentDeleted(boost::shared_ptr<AgentProxy> agent) +{ + ConsoleEventImpl::Ptr event(new ConsoleEventImpl(ConsoleEvent::AGENT_DELETED)); + event->agent = agent; + Mutex::ScopedLock _lock(lock); + eventQueue.push_back(event); +} + +void ConsoleImpl::eventNewPackage(const string& packageName) +{ + ConsoleEventImpl::Ptr event(new ConsoleEventImpl(ConsoleEvent::NEW_PACKAGE)); + event->name = packageName; + Mutex::ScopedLock _lock(lock); + eventQueue.push_back(event); +} + +void ConsoleImpl::eventNewClass(const SchemaClassKey* key) +{ + ConsoleEventImpl::Ptr event(new ConsoleEventImpl(ConsoleEvent::NEW_CLASS)); + event->classKey = key; + Mutex::ScopedLock _lock(lock); + eventQueue.push_back(event); +} + +void ConsoleImpl::eventObjectUpdate(ObjectPtr object, bool prop, bool stat) +{ + ConsoleEventImpl::Ptr event(new ConsoleEventImpl(ConsoleEvent::OBJECT_UPDATE)); + event->object = object; + event->hasProps = prop; + event->hasStats = stat; + Mutex::ScopedLock _lock(lock); + eventQueue.push_back(event); +} + +void ConsoleImpl::eventAgentHeartbeat(boost::shared_ptr<AgentProxy> agent, uint64_t timestamp) +{ + ConsoleEventImpl::Ptr event(new ConsoleEventImpl(ConsoleEvent::AGENT_HEARTBEAT)); + event->agent = agent; + event->timestamp = timestamp; + Mutex::ScopedLock _lock(lock); + eventQueue.push_back(event); +} + + +void ConsoleImpl::eventEventReceived(EventPtr event) +{ + ConsoleEventImpl::Ptr console_event(new ConsoleEventImpl(ConsoleEvent::EVENT_RECEIVED)); + console_event->event = event; + Mutex::ScopedLock _lock(lock); + eventQueue.push_back(console_event); +} + +//================================================================== +// Wrappers +//================================================================== + +Console::Console(const ConsoleSettings& settings) : impl(new ConsoleImpl(settings)) {} +Console::~Console() { delete impl; } +bool Console::getEvent(ConsoleEvent& event) const { return impl->getEvent(event); } +void Console::popEvent() { impl->popEvent(); } +void Console::addConnection(BrokerProxy& broker, void* context) { impl->addConnection(broker, context); } +void Console::delConnection(BrokerProxy& broker) { impl->delConnection(broker); } +uint32_t Console::packageCount() const { return impl->packageCount(); } +const char* Console::getPackageName(uint32_t idx) const { return impl->getPackageName(idx).c_str(); } +uint32_t Console::classCount(const char* packageName) const { return impl->classCount(packageName); } +const SchemaClassKey* Console::getClass(const char* packageName, uint32_t idx) const { return impl->getClass(packageName, idx); } +ClassKind Console::getClassKind(const SchemaClassKey* key) const { return impl->getClassKind(key); } +const SchemaObjectClass* Console::getObjectClass(const SchemaClassKey* key) const { return impl->getObjectClass(key); } +const SchemaEventClass* Console::getEventClass(const SchemaClassKey* key) const { return impl->getEventClass(key); } +void Console::bindPackage(const char* packageName) { impl->bindPackage(packageName); } +void Console::bindClass(const SchemaClassKey* key) { impl->bindClass(key); } +void Console::bindClass(const char* packageName, const char* className) { impl->bindClass(packageName, className); } + +void Console::bindEvent(const SchemaClassKey *key) { impl->bindEvent(key); } +void Console::bindEvent(const char* packageName, const char* eventName) { impl->bindEvent(packageName, eventName); } + +//void Console::startSync(const Query& query, void* context, SyncQuery& sync) { impl->startSync(query, context, sync); } +//void Console::touchSync(SyncQuery& sync) { impl->touchSync(sync); } +//void Console::endSync(SyncQuery& sync) { impl->endSync(sync); } + + diff --git a/qpid/cpp/src/qmf/engine/ConsoleImpl.h b/qpid/cpp/src/qmf/engine/ConsoleImpl.h new file mode 100644 index 0000000000..0c27fdabcd --- /dev/null +++ b/qpid/cpp/src/qmf/engine/ConsoleImpl.h @@ -0,0 +1,148 @@ +#ifndef _QmfEngineConsoleEngineImpl_ +#define _QmfEngineConsoleEngineImpl_ + +/* + * 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/engine/Console.h" +#include "qmf/engine/MessageImpl.h" +#include "qmf/engine/SchemaImpl.h" +#include "qmf/engine/Typecode.h" +#include "qmf/engine/ObjectImpl.h" +#include "qmf/engine/ObjectIdImpl.h" +#include "qmf/engine/QueryImpl.h" +#include "qmf/engine/ValueImpl.h" +#include "qmf/engine/Protocol.h" +#include "qmf/engine/SequenceManager.h" +#include "qmf/engine/BrokerProxyImpl.h" +#include <qpid/framing/Buffer.h> +#include <qpid/framing/Uuid.h> +#include <qpid/framing/FieldTable.h> +#include <qpid/framing/FieldValue.h> +#include <qpid/sys/Mutex.h> +#include <qpid/sys/Time.h> +#include <qpid/sys/SystemInfo.h> +#include <string.h> +#include <string> +#include <deque> +#include <map> +#include <vector> +#include <iostream> +#include <fstream> +#include <boost/shared_ptr.hpp> +#include <boost/noncopyable.hpp> + +namespace qmf { +namespace engine { + + struct ConsoleEventImpl { + typedef boost::shared_ptr<ConsoleEventImpl> Ptr; + ConsoleEvent::EventKind kind; + boost::shared_ptr<AgentProxy> agent; + std::string name; + const SchemaClassKey* classKey; + boost::shared_ptr<Object> object; + void* context; + boost::shared_ptr<Event> event; + uint64_t timestamp; + bool hasProps; + bool hasStats; + + ConsoleEventImpl(ConsoleEvent::EventKind k) : + kind(k), classKey(0), context(0), timestamp(0) {} + ~ConsoleEventImpl() {} + ConsoleEvent copy(); + }; + + class ConsoleImpl : public boost::noncopyable { + public: + ConsoleImpl(const ConsoleSettings& settings = ConsoleSettings()); + ~ConsoleImpl(); + + bool getEvent(ConsoleEvent& event) const; + void popEvent(); + + void addConnection(BrokerProxy& broker, void* context); + void delConnection(BrokerProxy& broker); + + uint32_t packageCount() const; + const std::string& getPackageName(uint32_t idx) const; + + uint32_t classCount(const char* packageName) const; + const SchemaClassKey* getClass(const char* packageName, uint32_t idx) const; + + ClassKind getClassKind(const SchemaClassKey* key) const; + const SchemaObjectClass* getObjectClass(const SchemaClassKey* key) const; + const SchemaEventClass* getEventClass(const SchemaClassKey* key) const; + + void bindPackage(const char* packageName); + void bindClass(const SchemaClassKey* key); + void bindClass(const char* packageName, const char* className); + void bindEvent(const SchemaClassKey* key); + void bindEvent(const char* packageName, const char* eventName); + + /* + void startSync(const Query& query, void* context, SyncQuery& sync); + void touchSync(SyncQuery& sync); + void endSync(SyncQuery& sync); + */ + + private: + friend class BrokerProxyImpl; + friend struct StaticContext; + const ConsoleSettings& settings; + mutable qpid::sys::Mutex lock; + std::deque<ConsoleEventImpl::Ptr> eventQueue; + std::vector<BrokerProxyImpl*> brokerList; + std::vector<std::pair<std::string, std::string> > bindingList; // exchange/key (empty exchange => QMF_EXCHANGE) + + // Declare a compare class for the class maps that compares the dereferenced + // class key pointers. The default behavior would be to compare the pointer + // addresses themselves. + struct KeyCompare { + bool operator()(const SchemaClassKey* left, const SchemaClassKey* right) const { + return *left < *right; + } + }; + + typedef std::map<const SchemaClassKey*, SchemaObjectClass*, KeyCompare> ObjectClassList; + typedef std::map<const SchemaClassKey*, SchemaEventClass*, KeyCompare> EventClassList; + typedef std::map<std::string, std::pair<ObjectClassList, EventClassList> > PackageList; + + PackageList packages; + + void learnPackage(const std::string& packageName); + void learnClass(SchemaObjectClass* cls); + void learnClass(SchemaEventClass* cls); + bool haveClass(const SchemaClassKey* key) const; + SchemaObjectClass* getSchema(const SchemaClassKey* key) const; + + void eventAgentAdded(boost::shared_ptr<AgentProxy> agent); + void eventAgentDeleted(boost::shared_ptr<AgentProxy> agent); + void eventNewPackage(const std::string& packageName); + void eventNewClass(const SchemaClassKey* key); + void eventObjectUpdate(ObjectPtr object, bool prop, bool stat); + void eventAgentHeartbeat(boost::shared_ptr<AgentProxy> agent, uint64_t timestamp); + void eventEventReceived(boost::shared_ptr<Event> event); + }; +} +} + +#endif + diff --git a/qpid/cpp/src/qmf/engine/EventImpl.cpp b/qpid/cpp/src/qmf/engine/EventImpl.cpp new file mode 100644 index 0000000000..4b034e8e83 --- /dev/null +++ b/qpid/cpp/src/qmf/engine/EventImpl.cpp @@ -0,0 +1,120 @@ +/* + * 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/engine/EventImpl.h> +#include <qmf/engine/ValueImpl.h> + +#include <sstream> + +using namespace std; +using namespace qmf::engine; +using qpid::framing::Buffer; + +EventImpl::EventImpl(const SchemaEventClass* type) : eventClass(type), timestamp(0), severity(0) +{ + int argCount = eventClass->getArgumentCount(); + int idx; + + for (idx = 0; idx < argCount; idx++) { + const SchemaArgument* arg = eventClass->getArgument(idx); + arguments[arg->getName()] = ValuePtr(new Value(arg->getType())); + } +} + + +EventImpl::EventImpl(const SchemaEventClass* type, Buffer& buffer) : + eventClass(type), timestamp(0), severity(0) +{ + int argCount = eventClass->getArgumentCount(); + int idx; + + timestamp = buffer.getLongLong(); + severity = buffer.getOctet(); + + for (idx = 0; idx < argCount; idx++) + { + const SchemaArgument *arg = eventClass->getArgument(idx); + Value* pval = ValueImpl::factory(arg->getType(), buffer); + arguments[arg->getName()] = ValuePtr(pval); + } +} + + +Event* EventImpl::factory(const SchemaEventClass* type, Buffer& buffer) +{ + EventImpl* impl(new EventImpl(type, buffer)); + return new Event(impl); +} + + +Value* EventImpl::getValue(const char* key) const +{ + map<string, ValuePtr>::const_iterator iter; + + iter = arguments.find(key); + if (iter != arguments.end()) + return iter->second.get(); + + return 0; +} + + +void EventImpl::encodeSchemaKey(Buffer& buffer) const +{ + buffer.putShortString(eventClass->getClassKey()->getPackageName()); + buffer.putShortString(eventClass->getClassKey()->getClassName()); + buffer.putBin128(const_cast<uint8_t*>(eventClass->getClassKey()->getHash())); +} + + +void EventImpl::encode(Buffer& buffer) const +{ + buffer.putOctet((uint8_t) eventClass->getSeverity()); + + int argCount = eventClass->getArgumentCount(); + for (int idx = 0; idx < argCount; idx++) { + const SchemaArgument* arg = eventClass->getArgument(idx); + ValuePtr value = arguments[arg->getName()]; + value->impl->encode(buffer); + } +} + + +string EventImpl::getRoutingKey(uint32_t brokerBank, uint32_t agentBank) const +{ + stringstream key; + + key << "console.event." << brokerBank << "." << agentBank << "." << + eventClass->getClassKey()->getPackageName() << "." << + eventClass->getClassKey()->getClassName(); + return key.str(); +} + + +//================================================================== +// Wrappers +//================================================================== + +Event::Event(const SchemaEventClass* type) : impl(new EventImpl(type)) {} +Event::Event(EventImpl* i) : impl(i) {} +Event::Event(const Event& from) : impl(new EventImpl(*(from.impl))) {} +Event::~Event() { delete impl; } +const SchemaEventClass* Event::getClass() const { return impl->getClass(); } +Value* Event::getValue(const char* key) const { return impl->getValue(key); } + diff --git a/qpid/cpp/src/qmf/engine/EventImpl.h b/qpid/cpp/src/qmf/engine/EventImpl.h new file mode 100644 index 0000000000..4046e71ef9 --- /dev/null +++ b/qpid/cpp/src/qmf/engine/EventImpl.h @@ -0,0 +1,57 @@ +#ifndef _QmfEngineEventImpl_ +#define _QmfEngineEventImpl_ + +/* + * 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/engine/Event.h> +#include <qmf/engine/Schema.h> +#include <qpid/framing/Buffer.h> +#include <boost/shared_ptr.hpp> +#include <map> + +namespace qmf { +namespace engine { + + typedef boost::shared_ptr<Event> EventPtr; + + struct EventImpl { + typedef boost::shared_ptr<Value> ValuePtr; + const SchemaEventClass* eventClass; + uint64_t timestamp; + uint8_t severity; + mutable std::map<std::string, ValuePtr> arguments; + + EventImpl(const SchemaEventClass* type); + EventImpl(const SchemaEventClass* type, qpid::framing::Buffer& buffer); + static Event* factory(const SchemaEventClass* type, qpid::framing::Buffer& buffer); + + const SchemaEventClass* getClass() const { return eventClass; } + Value* getValue(const char* key) const; + + void encodeSchemaKey(qpid::framing::Buffer& buffer) const; + void encode(qpid::framing::Buffer& buffer) const; + std::string getRoutingKey(uint32_t brokerBank, uint32_t agentBank) const; + }; + +} +} + +#endif + diff --git a/qpid/cpp/src/qmf/engine/MessageImpl.cpp b/qpid/cpp/src/qmf/engine/MessageImpl.cpp new file mode 100644 index 0000000000..0047d3eb9d --- /dev/null +++ b/qpid/cpp/src/qmf/engine/MessageImpl.cpp @@ -0,0 +1,43 @@ +/* + * 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/engine/MessageImpl.h" +#include <string.h> + +using namespace std; +using namespace qmf::engine; + +#define STRING_REF(s) {if (!s.empty()) item.s = const_cast<char*>(s.c_str());} + +Message MessageImpl::copy() +{ + Message item; + + ::memset(&item, 0, sizeof(Message)); + item.body = const_cast<char*>(body.c_str()); + item.length = body.length(); + STRING_REF(destination); + STRING_REF(routingKey); + STRING_REF(replyExchange); + STRING_REF(replyKey); + STRING_REF(userId); + + return item; +} + diff --git a/qpid/cpp/src/qmf/engine/MessageImpl.h b/qpid/cpp/src/qmf/engine/MessageImpl.h new file mode 100644 index 0000000000..b91291d2e4 --- /dev/null +++ b/qpid/cpp/src/qmf/engine/MessageImpl.h @@ -0,0 +1,44 @@ +#ifndef _QmfEngineMessageImpl_ +#define _QmfEngineMessageImpl_ + +/* + * 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/engine/Message.h" +#include <string> +#include <boost/shared_ptr.hpp> + +namespace qmf { +namespace engine { + + struct MessageImpl { + typedef boost::shared_ptr<MessageImpl> Ptr; + std::string body; + std::string destination; + std::string routingKey; + std::string replyExchange; + std::string replyKey; + std::string userId; + + Message copy(); + }; +} +} + +#endif diff --git a/qpid/cpp/src/qmf/engine/ObjectIdImpl.cpp b/qpid/cpp/src/qmf/engine/ObjectIdImpl.cpp new file mode 100644 index 0000000000..9216f7bac0 --- /dev/null +++ b/qpid/cpp/src/qmf/engine/ObjectIdImpl.cpp @@ -0,0 +1,210 @@ +/* + * 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/engine/ObjectIdImpl.h" +#include <stdlib.h> +#include <sstream> + +using namespace std; +using namespace qmf::engine; +using qpid::framing::Buffer; + +void AgentAttachment::setBanks(uint32_t broker, uint32_t agent) +{ + first = + ((uint64_t) (broker & 0x000fffff)) << 28 | + ((uint64_t) (agent & 0x0fffffff)); +} + +ObjectIdImpl::ObjectIdImpl(Buffer& buffer) : agent(0) +{ + decode(buffer); +} + +ObjectIdImpl::ObjectIdImpl(AgentAttachment* a, uint8_t flags, uint16_t seq, uint64_t object) : agent(a) +{ + first = + ((uint64_t) (flags & 0x0f)) << 60 | + ((uint64_t) (seq & 0x0fff)) << 48; + second = object; +} + +ObjectId* ObjectIdImpl::factory(Buffer& buffer) +{ + ObjectIdImpl* impl(new ObjectIdImpl(buffer)); + return new ObjectId(impl); +} + +ObjectId* ObjectIdImpl::factory(AgentAttachment* agent, uint8_t flags, uint16_t seq, uint64_t object) +{ + ObjectIdImpl* impl(new ObjectIdImpl(agent, flags, seq, object)); + return new ObjectId(impl); +} + +void ObjectIdImpl::decode(Buffer& buffer) +{ + first = buffer.getLongLong(); + second = buffer.getLongLong(); +} + +void ObjectIdImpl::encode(Buffer& buffer) const +{ + if (agent == 0) + buffer.putLongLong(first); + else + buffer.putLongLong(first | agent->first); + buffer.putLongLong(second); +} + +void ObjectIdImpl::fromString(const std::string& repr) +{ +#define FIELDS 5 +#if defined (_WIN32) && !defined (atoll) +# define atoll(X) _atoi64(X) +#endif + + std::string copy(repr.c_str()); + char* cText; + char* field[FIELDS]; + bool atFieldStart = true; + int idx = 0; + + cText = const_cast<char*>(copy.c_str()); + for (char* cursor = cText; *cursor; cursor++) { + if (atFieldStart) { + if (idx >= FIELDS) + return; // TODO error + field[idx++] = cursor; + atFieldStart = false; + } else { + if (*cursor == '-') { + *cursor = '\0'; + atFieldStart = true; + } + } + } + + if (idx != FIELDS) + return; // TODO error + + first = (atoll(field[0]) << 60) + + (atoll(field[1]) << 48) + + (atoll(field[2]) << 28) + + atoll(field[3]); + second = atoll(field[4]); + agent = 0; +} + +const string& ObjectIdImpl::asString() const +{ + stringstream val; + + val << (int) getFlags() << "-" << getSequence() << "-" << getBrokerBank() << "-" << + getAgentBank() << "-" << getObjectNum(); + repr = val.str(); + return repr; +} + +#define ACTUAL_FIRST (agent == 0 ? first : first | agent->first) +#define ACTUAL_OTHER (other.agent == 0 ? other.first : other.first | other.agent->first) + +uint8_t ObjectIdImpl::getFlags() const +{ + return (ACTUAL_FIRST & 0xF000000000000000LL) >> 60; +} + +uint16_t ObjectIdImpl::getSequence() const +{ + return (ACTUAL_FIRST & 0x0FFF000000000000LL) >> 48; +} + +uint32_t ObjectIdImpl::getBrokerBank() const +{ + return (ACTUAL_FIRST & 0x0000FFFFF0000000LL) >> 28; +} + +uint32_t ObjectIdImpl::getAgentBank() const +{ + return ACTUAL_FIRST & 0x000000000FFFFFFFLL; +} + +uint64_t ObjectIdImpl::getObjectNum() const +{ + return second; +} + +uint32_t ObjectIdImpl::getObjectNumHi() const +{ + return (uint32_t) (second >> 32); +} + +uint32_t ObjectIdImpl::getObjectNumLo() const +{ + return (uint32_t) (second & 0x00000000FFFFFFFFLL); +} + +bool ObjectIdImpl::operator==(const ObjectIdImpl& other) const +{ + return ACTUAL_FIRST == ACTUAL_OTHER && second == other.second; +} + +bool ObjectIdImpl::operator<(const ObjectIdImpl& other) const +{ + return (ACTUAL_FIRST < ACTUAL_OTHER) || ((ACTUAL_FIRST == ACTUAL_OTHER) && (second < other.second)); +} + +bool ObjectIdImpl::operator>(const ObjectIdImpl& other) const +{ + return (ACTUAL_FIRST > ACTUAL_OTHER) || ((ACTUAL_FIRST == ACTUAL_OTHER) && (second > other.second)); +} + + +//================================================================== +// Wrappers +//================================================================== + +ObjectId::ObjectId() : impl(new ObjectIdImpl()) {} +ObjectId::ObjectId(const ObjectId& from) : impl(new ObjectIdImpl(*(from.impl))) {} +ObjectId::ObjectId(ObjectIdImpl* i) : impl(i) {} +ObjectId::~ObjectId() { delete impl; } +uint64_t ObjectId::getObjectNum() const { return impl->getObjectNum(); } +uint32_t ObjectId::getObjectNumHi() const { return impl->getObjectNumHi(); } +uint32_t ObjectId::getObjectNumLo() const { return impl->getObjectNumLo(); } +bool ObjectId::isDurable() const { return impl->isDurable(); } +const char* ObjectId::str() const { return impl->asString().c_str(); } +uint8_t ObjectId::getFlags() const { return impl->getFlags(); } +uint16_t ObjectId::getSequence() const { return impl->getSequence(); } +uint32_t ObjectId::getBrokerBank() const { return impl->getBrokerBank(); } +uint32_t ObjectId::getAgentBank() const { return impl->getAgentBank(); } +bool ObjectId::operator==(const ObjectId& other) const { return *impl == *other.impl; } +bool ObjectId::operator<(const ObjectId& other) const { return *impl < *other.impl; } +bool ObjectId::operator>(const ObjectId& other) const { return *impl > *other.impl; } +bool ObjectId::operator<=(const ObjectId& other) const { return !(*impl > *other.impl); } +bool ObjectId::operator>=(const ObjectId& other) const { return !(*impl < *other.impl); } +ObjectId& ObjectId::operator=(const ObjectId& other) { + ObjectIdImpl *old; + if (this != &other) { + old = impl; + impl = new ObjectIdImpl(*(other.impl)); + if (old) + delete old; + } + return *this; +} + diff --git a/qpid/cpp/src/qmf/engine/ObjectIdImpl.h b/qpid/cpp/src/qmf/engine/ObjectIdImpl.h new file mode 100644 index 0000000000..d70c8efff4 --- /dev/null +++ b/qpid/cpp/src/qmf/engine/ObjectIdImpl.h @@ -0,0 +1,72 @@ +#ifndef _QmfEngineObjectIdImpl_ +#define _QmfEngineObjectIdImpl_ + +/* + * 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/engine/ObjectId.h> +#include <qpid/framing/Buffer.h> + +namespace qmf { +namespace engine { + + struct AgentAttachment { + uint64_t first; + + AgentAttachment() : first(0) {} + void setBanks(uint32_t broker, uint32_t bank); + uint64_t getFirst() const { return first; } + }; + + struct ObjectIdImpl { + AgentAttachment* agent; + uint64_t first; + uint64_t second; + mutable std::string repr; + + ObjectIdImpl() : agent(0), first(0), second(0) {} + ObjectIdImpl(qpid::framing::Buffer& buffer); + ObjectIdImpl(AgentAttachment* agent, uint8_t flags, uint16_t seq, uint64_t object); + + static ObjectId* factory(qpid::framing::Buffer& buffer); + static ObjectId* factory(AgentAttachment* agent, uint8_t flags, uint16_t seq, uint64_t object); + + void decode(qpid::framing::Buffer& buffer); + void encode(qpid::framing::Buffer& buffer) const; + void fromString(const std::string& repr); + const std::string& asString() const; + uint8_t getFlags() const; + uint16_t getSequence() const; + uint32_t getBrokerBank() const; + uint32_t getAgentBank() const; + uint64_t getObjectNum() const; + uint32_t getObjectNumHi() const; + uint32_t getObjectNumLo() const; + bool isDurable() const { return getSequence() == 0; } + void setValue(uint64_t f, uint64_t s) { first = f; second = s; agent = 0; } + + bool operator==(const ObjectIdImpl& other) const; + bool operator<(const ObjectIdImpl& other) const; + bool operator>(const ObjectIdImpl& other) const; + }; +} +} + +#endif + diff --git a/qpid/cpp/src/qmf/engine/ObjectImpl.cpp b/qpid/cpp/src/qmf/engine/ObjectImpl.cpp new file mode 100644 index 0000000000..45925cb804 --- /dev/null +++ b/qpid/cpp/src/qmf/engine/ObjectImpl.cpp @@ -0,0 +1,232 @@ +/* + * 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/engine/ObjectImpl.h" +#include "qmf/engine/ValueImpl.h" +#include "qmf/engine/BrokerProxyImpl.h" +#include <qpid/sys/Time.h> + +using namespace std; +using namespace qmf::engine; +using namespace qpid::sys; +using qpid::framing::Buffer; + +ObjectImpl::ObjectImpl(const SchemaObjectClass* type) : objectClass(type), broker(0), createTime(uint64_t(Duration(EPOCH, now()))), destroyTime(0), lastUpdatedTime(createTime) +{ + int propCount = objectClass->getPropertyCount(); + int statCount = objectClass->getStatisticCount(); + int idx; + + for (idx = 0; idx < propCount; idx++) { + const SchemaProperty* prop = objectClass->getProperty(idx); + properties[prop->getName()] = ValuePtr(new Value(prop->getType())); + } + + for (idx = 0; idx < statCount; idx++) { + const SchemaStatistic* stat = objectClass->getStatistic(idx); + statistics[stat->getName()] = ValuePtr(new Value(stat->getType())); + } +} + +ObjectImpl::ObjectImpl(const SchemaObjectClass* type, BrokerProxyImpl* b, Buffer& buffer, bool prop, bool stat, bool managed) : + objectClass(type), broker(b), createTime(0), destroyTime(0), lastUpdatedTime(0) +{ + int idx; + + if (managed) { + lastUpdatedTime = buffer.getLongLong(); + createTime = buffer.getLongLong(); + destroyTime = buffer.getLongLong(); + objectId.reset(ObjectIdImpl::factory(buffer)); + } + + if (prop) { + int propCount = objectClass->getPropertyCount(); + set<string> excludes; + parsePresenceMasks(buffer, excludes); + for (idx = 0; idx < propCount; idx++) { + const SchemaProperty* prop = objectClass->getProperty(idx); + if (excludes.count(prop->getName()) != 0) { + properties[prop->getName()] = ValuePtr(new Value(prop->getType())); + } else { + Value* pval = ValueImpl::factory(prop->getType(), buffer); + properties[prop->getName()] = ValuePtr(pval); + } + } + } + + if (stat) { + int statCount = objectClass->getStatisticCount(); + for (idx = 0; idx < statCount; idx++) { + const SchemaStatistic* stat = objectClass->getStatistic(idx); + Value* sval = ValueImpl::factory(stat->getType(), buffer); + statistics[stat->getName()] = ValuePtr(sval); + } + } +} + +Object* ObjectImpl::factory(const SchemaObjectClass* type, BrokerProxyImpl* b, Buffer& buffer, bool prop, bool stat, bool managed) +{ + ObjectImpl* impl(new ObjectImpl(type, b, buffer, prop, stat, managed)); + return new Object(impl); +} + +ObjectImpl::~ObjectImpl() +{ +} + +void ObjectImpl::destroy() +{ + destroyTime = uint64_t(Duration(EPOCH, now())); + // TODO - flag deletion +} + +Value* ObjectImpl::getValue(const string& key) const +{ + map<string, ValuePtr>::const_iterator iter; + + iter = properties.find(key); + if (iter != properties.end()) + return iter->second.get(); + + iter = statistics.find(key); + if (iter != statistics.end()) + return iter->second.get(); + + return 0; +} + +void ObjectImpl::invokeMethod(const string& methodName, const Value* inArgs, void* context) const +{ + if (broker != 0 && objectId.get() != 0) + broker->sendMethodRequest(objectId.get(), objectClass, methodName, inArgs, context); +} + +void ObjectImpl::merge(const Object& from) +{ + for (map<string, ValuePtr>::const_iterator piter = from.impl->properties.begin(); + piter != from.impl->properties.end(); piter++) + properties[piter->first] = piter->second; + for (map<string, ValuePtr>::const_iterator siter = from.impl->statistics.begin(); + siter != from.impl->statistics.end(); siter++) + statistics[siter->first] = siter->second; +} + +void ObjectImpl::parsePresenceMasks(Buffer& buffer, set<string>& excludeList) +{ + int propCount = objectClass->getPropertyCount(); + excludeList.clear(); + uint8_t bit = 0; + uint8_t mask = 0; + + for (int idx = 0; idx < propCount; idx++) { + const SchemaProperty* prop = objectClass->getProperty(idx); + if (prop->isOptional()) { + if (bit == 0) { + mask = buffer.getOctet(); + bit = 1; + } + if ((mask & bit) == 0) + excludeList.insert(string(prop->getName())); + if (bit == 0x80) + bit = 0; + else + bit = bit << 1; + } + } +} + +void ObjectImpl::encodeSchemaKey(qpid::framing::Buffer& buffer) const +{ + buffer.putShortString(objectClass->getClassKey()->getPackageName()); + buffer.putShortString(objectClass->getClassKey()->getClassName()); + buffer.putBin128(const_cast<uint8_t*>(objectClass->getClassKey()->getHash())); +} + +void ObjectImpl::encodeManagedObjectData(qpid::framing::Buffer& buffer) const +{ + buffer.putLongLong(lastUpdatedTime); + buffer.putLongLong(createTime); + buffer.putLongLong(destroyTime); + objectId->impl->encode(buffer); +} + +void ObjectImpl::encodeProperties(qpid::framing::Buffer& buffer) const +{ + int propCount = objectClass->getPropertyCount(); + uint8_t bit = 0; + uint8_t mask = 0; + ValuePtr value; + + for (int idx = 0; idx < propCount; idx++) { + const SchemaProperty* prop = objectClass->getProperty(idx); + if (prop->isOptional()) { + value = properties[prop->getName()]; + if (bit == 0) + bit = 1; + if (!value->isNull()) + mask |= bit; + if (bit == 0x80) { + buffer.putOctet(mask); + bit = 0; + mask = 0; + } else + bit = bit << 1; + } + } + if (bit != 0) { + buffer.putOctet(mask); + } + + for (int idx = 0; idx < propCount; idx++) { + const SchemaProperty* prop = objectClass->getProperty(idx); + value = properties[prop->getName()]; + if (!prop->isOptional() || !value->isNull()) { + value->impl->encode(buffer); + } + } +} + +void ObjectImpl::encodeStatistics(qpid::framing::Buffer& buffer) const +{ + int statCount = objectClass->getStatisticCount(); + for (int idx = 0; idx < statCount; idx++) { + const SchemaStatistic* stat = objectClass->getStatistic(idx); + ValuePtr value = statistics[stat->getName()]; + value->impl->encode(buffer); + } +} + +//================================================================== +// Wrappers +//================================================================== + +Object::Object(const SchemaObjectClass* type) : impl(new ObjectImpl(type)) {} +Object::Object(ObjectImpl* i) : impl(i) {} +Object::Object(const Object& from) : impl(new ObjectImpl(*(from.impl))) {} +Object::~Object() { delete impl; } +void Object::destroy() { impl->destroy(); } +const ObjectId* Object::getObjectId() const { return impl->getObjectId(); } +void Object::setObjectId(ObjectId* oid) { impl->setObjectId(oid); } +const SchemaObjectClass* Object::getClass() const { return impl->getClass(); } +Value* Object::getValue(const char* key) const { return impl->getValue(key); } +void Object::invokeMethod(const char* m, const Value* a, void* c) const { impl->invokeMethod(m, a, c); } +bool Object::isDeleted() const { return impl->isDeleted(); } +void Object::merge(const Object& from) { impl->merge(from); } + diff --git a/qpid/cpp/src/qmf/engine/ObjectImpl.h b/qpid/cpp/src/qmf/engine/ObjectImpl.h new file mode 100644 index 0000000000..6f25867004 --- /dev/null +++ b/qpid/cpp/src/qmf/engine/ObjectImpl.h @@ -0,0 +1,76 @@ +#ifndef _QmfEngineObjectImpl_ +#define _QmfEngineObjectImpl_ + +/* + * 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/engine/Object.h> +#include <qmf/engine/ObjectIdImpl.h> +#include <map> +#include <set> +#include <string> +#include <qpid/framing/Buffer.h> +#include <boost/shared_ptr.hpp> +#include <qpid/sys/Mutex.h> + +namespace qmf { +namespace engine { + + class BrokerProxyImpl; + + typedef boost::shared_ptr<Object> ObjectPtr; + + struct ObjectImpl { + typedef boost::shared_ptr<Value> ValuePtr; + const SchemaObjectClass* objectClass; + BrokerProxyImpl* broker; + boost::shared_ptr<ObjectId> objectId; + uint64_t createTime; + uint64_t destroyTime; + uint64_t lastUpdatedTime; + mutable std::map<std::string, ValuePtr> properties; + mutable std::map<std::string, ValuePtr> statistics; + + ObjectImpl(const SchemaObjectClass* type); + ObjectImpl(const SchemaObjectClass* type, BrokerProxyImpl* b, qpid::framing::Buffer& buffer, + bool prop, bool stat, bool managed); + static Object* factory(const SchemaObjectClass* type, BrokerProxyImpl* b, qpid::framing::Buffer& buffer, + bool prop, bool stat, bool managed); + ~ObjectImpl(); + + void destroy(); + const ObjectId* getObjectId() const { return objectId.get(); } + void setObjectId(ObjectId* oid) { objectId.reset(new ObjectId(*oid)); } + const SchemaObjectClass* getClass() const { return objectClass; } + Value* getValue(const std::string& key) const; + void invokeMethod(const std::string& methodName, const Value* inArgs, void* context) const; + bool isDeleted() const { return destroyTime != 0; } + void merge(const Object& from); + + void parsePresenceMasks(qpid::framing::Buffer& buffer, std::set<std::string>& excludeList); + void encodeSchemaKey(qpid::framing::Buffer& buffer) const; + void encodeManagedObjectData(qpid::framing::Buffer& buffer) const; + void encodeProperties(qpid::framing::Buffer& buffer) const; + void encodeStatistics(qpid::framing::Buffer& buffer) const; + }; +} +} + +#endif + diff --git a/qpid/cpp/src/qmf/engine/Protocol.cpp b/qpid/cpp/src/qmf/engine/Protocol.cpp new file mode 100644 index 0000000000..9e5f490604 --- /dev/null +++ b/qpid/cpp/src/qmf/engine/Protocol.cpp @@ -0,0 +1,52 @@ +/* + * 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/engine/Protocol.h" +#include "qpid/framing/Buffer.h" + +using namespace std; +using namespace qmf::engine; +using namespace qpid::framing; + + +bool Protocol::checkHeader(Buffer& buf, uint8_t *opcode, uint32_t *seq) +{ + if (buf.available() < 8) + return false; + + uint8_t h1 = buf.getOctet(); + uint8_t h2 = buf.getOctet(); + uint8_t h3 = buf.getOctet(); + + *opcode = buf.getOctet(); + *seq = buf.getLong(); + + return h1 == 'A' && h2 == 'M' && h3 == '2'; +} + +void Protocol::encodeHeader(qpid::framing::Buffer& buf, uint8_t opcode, uint32_t seq) +{ + buf.putOctet('A'); + buf.putOctet('M'); + buf.putOctet('2'); + buf.putOctet(opcode); + buf.putLong (seq); +} + + diff --git a/qpid/cpp/src/qmf/engine/Protocol.h b/qpid/cpp/src/qmf/engine/Protocol.h new file mode 100644 index 0000000000..1cdfa60c84 --- /dev/null +++ b/qpid/cpp/src/qmf/engine/Protocol.h @@ -0,0 +1,69 @@ +#ifndef _QmfEngineProtocol_ +#define _QmfEngineProtocol_ + +/* + * 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/IntegerTypes.h> + +namespace qpid { + namespace framing { + class Buffer; + } +} + +namespace qmf { +namespace engine { + + class Protocol { + public: + static bool checkHeader(qpid::framing::Buffer& buf, uint8_t *opcode, uint32_t *seq); + static void encodeHeader(qpid::framing::Buffer& buf, uint8_t opcode, uint32_t seq = 0); + + const static uint8_t OP_ATTACH_REQUEST = 'A'; + const static uint8_t OP_ATTACH_RESPONSE = 'a'; + + const static uint8_t OP_BROKER_REQUEST = 'B'; + const static uint8_t OP_BROKER_RESPONSE = 'b'; + + const static uint8_t OP_CONSOLE_ADDED_INDICATION = 'x'; + const static uint8_t OP_COMMAND_COMPLETE = 'z'; + const static uint8_t OP_HEARTBEAT_INDICATION = 'h'; + + const static uint8_t OP_PACKAGE_REQUEST = 'P'; + const static uint8_t OP_PACKAGE_INDICATION = 'p'; + const static uint8_t OP_CLASS_QUERY = 'Q'; + const static uint8_t OP_CLASS_INDICATION = 'q'; + const static uint8_t OP_SCHEMA_REQUEST = 'S'; + const static uint8_t OP_SCHEMA_RESPONSE = 's'; + + const static uint8_t OP_METHOD_REQUEST = 'M'; + const static uint8_t OP_METHOD_RESPONSE = 'm'; + const static uint8_t OP_GET_QUERY = 'G'; + const static uint8_t OP_OBJECT_INDICATION = 'g'; + const static uint8_t OP_PROPERTY_INDICATION = 'c'; + const static uint8_t OP_STATISTIC_INDICATION = 'i'; + const static uint8_t OP_EVENT_INDICATION = 'e'; + }; + +} +} + +#endif + diff --git a/qpid/cpp/src/qmf/engine/QueryImpl.cpp b/qpid/cpp/src/qmf/engine/QueryImpl.cpp new file mode 100644 index 0000000000..6f2beeee87 --- /dev/null +++ b/qpid/cpp/src/qmf/engine/QueryImpl.cpp @@ -0,0 +1,103 @@ +/* + * 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/engine/QueryImpl.h" +#include "qmf/engine/ObjectIdImpl.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/FieldTable.h" + +using namespace std; +using namespace qmf::engine; +using namespace qpid::framing; + +bool QueryElementImpl::evaluate(const Object* /*object*/) const +{ + // TODO: Implement this + return false; +} + +bool QueryExpressionImpl::evaluate(const Object* /*object*/) const +{ + // TODO: Implement this + return false; +} + +QueryImpl::QueryImpl(Buffer& buffer) +{ + FieldTable ft; + ft.decode(buffer); + // TODO +} + +Query* QueryImpl::factory(Buffer& buffer) +{ + QueryImpl* impl(new QueryImpl(buffer)); + return new Query(impl); +} + +void QueryImpl::encode(Buffer& buffer) const +{ + FieldTable ft; + + if (oid.get() != 0) { + ft.setString("_objectid", oid->impl->asString()); + } else { + if (!packageName.empty()) + ft.setString("_package", packageName); + ft.setString("_class", className); + } + + ft.encode(buffer); +} + + +//================================================================== +// Wrappers +//================================================================== + +QueryElement::QueryElement(const char* attrName, const Value* value, ValueOper oper) : impl(new QueryElementImpl(attrName, value, oper)) {} +QueryElement::QueryElement(QueryElementImpl* i) : impl(i) {} +QueryElement::~QueryElement() { delete impl; } +bool QueryElement::evaluate(const Object* object) const { return impl->evaluate(object); } + +QueryExpression::QueryExpression(ExprOper oper, const QueryOperand* operand1, const QueryOperand* operand2) : impl(new QueryExpressionImpl(oper, operand1, operand2)) {} +QueryExpression::QueryExpression(QueryExpressionImpl* i) : impl(i) {} +QueryExpression::~QueryExpression() { delete impl; } +bool QueryExpression::evaluate(const Object* object) const { return impl->evaluate(object); } + +Query::Query(const char* className, const char* packageName) : impl(new QueryImpl(className, packageName)) {} +Query::Query(const SchemaClassKey* key) : impl(new QueryImpl(key)) {} +Query::Query(const ObjectId* oid) : impl(new QueryImpl(oid)) {} +Query::Query(QueryImpl* i) : impl(i) {} +Query::Query(const Query& from) : impl(new QueryImpl(*(from.impl))) {} +Query::~Query() { delete impl; } +void Query::setSelect(const QueryOperand* criterion) { impl->setSelect(criterion); } +void Query::setLimit(uint32_t maxResults) { impl->setLimit(maxResults); } +void Query::setOrderBy(const char* attrName, bool decreasing) { impl->setOrderBy(attrName, decreasing); } +const char* Query::getPackage() const { return impl->getPackage().c_str(); } +const char* Query::getClass() const { return impl->getClass().c_str(); } +const ObjectId* Query::getObjectId() const { return impl->getObjectId(); } +bool Query::haveSelect() const { return impl->haveSelect(); } +bool Query::haveLimit() const { return impl->haveLimit(); } +bool Query::haveOrderBy() const { return impl->haveOrderBy(); } +const QueryOperand* Query::getSelect() const { return impl->getSelect(); } +uint32_t Query::getLimit() const { return impl->getLimit(); } +const char* Query::getOrderBy() const { return impl->getOrderBy().c_str(); } +bool Query::getDecreasing() const { return impl->getDecreasing(); } + diff --git a/qpid/cpp/src/qmf/engine/QueryImpl.h b/qpid/cpp/src/qmf/engine/QueryImpl.h new file mode 100644 index 0000000000..8ebe0d932f --- /dev/null +++ b/qpid/cpp/src/qmf/engine/QueryImpl.h @@ -0,0 +1,102 @@ +#ifndef _QmfEngineQueryImpl_ +#define _QmfEngineQueryImpl_ + +/* + * 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/engine/Query.h" +#include "qmf/engine/Schema.h" +#include <string> +#include <boost/shared_ptr.hpp> + +namespace qpid { + namespace framing { + class Buffer; + } +} + +namespace qmf { +namespace engine { + + struct QueryElementImpl { + QueryElementImpl(const std::string& a, const Value* v, ValueOper o) : attrName(a), value(v), oper(o) {} + ~QueryElementImpl() {} + bool evaluate(const Object* object) const; + + std::string attrName; + const Value* value; + ValueOper oper; + }; + + struct QueryExpressionImpl { + QueryExpressionImpl(ExprOper o, const QueryOperand* operand1, const QueryOperand* operand2) : oper(o), left(operand1), right(operand2) {} + ~QueryExpressionImpl() {} + bool evaluate(const Object* object) const; + + ExprOper oper; + const QueryOperand* left; + const QueryOperand* right; + }; + + struct QueryImpl { + // Constructors mapped to public + QueryImpl(const std::string& c, const std::string& p) : packageName(p), className(c), select(0), resultLimit(0) {} + QueryImpl(const SchemaClassKey* key) : packageName(key->getPackageName()), className(key->getClassName()), select(0), resultLimit(0) {} + QueryImpl(const ObjectId* oid) : oid(new ObjectId(*oid)), select(0), resultLimit(0) {} + + // Factory constructors + QueryImpl(qpid::framing::Buffer& buffer); + + ~QueryImpl() {}; + static Query* factory(qpid::framing::Buffer& buffer); + + void setSelect(const QueryOperand* criterion) { select = criterion; } + void setLimit(uint32_t maxResults) { resultLimit = maxResults; } + void setOrderBy(const std::string& attrName, bool decreasing) { + orderBy = attrName; orderDecreasing = decreasing; + } + + const std::string& getPackage() const { return packageName; } + const std::string& getClass() const { return className; } + const ObjectId* getObjectId() const { return oid.get(); } + + bool haveSelect() const { return select != 0; } + bool haveLimit() const { return resultLimit > 0; } + bool haveOrderBy() const { return !orderBy.empty(); } + const QueryOperand* getSelect() const { return select; } + uint32_t getLimit() const { return resultLimit; } + const std::string& getOrderBy() const { return orderBy; } + bool getDecreasing() const { return orderDecreasing; } + + void encode(qpid::framing::Buffer& buffer) const; + bool singleAgent() const { return oid.get() != 0; } + uint32_t agentBank() const { return singleAgent() ? oid->getAgentBank() : 0; } + + std::string packageName; + std::string className; + boost::shared_ptr<ObjectId> oid; + const QueryOperand* select; + uint32_t resultLimit; + std::string orderBy; + bool orderDecreasing; + }; +} +} + +#endif diff --git a/qpid/cpp/src/qmf/engine/ResilientConnection.cpp b/qpid/cpp/src/qmf/engine/ResilientConnection.cpp new file mode 100644 index 0000000000..ab65b8d768 --- /dev/null +++ b/qpid/cpp/src/qmf/engine/ResilientConnection.cpp @@ -0,0 +1,514 @@ +/* + * 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/engine/ResilientConnection.h" +#include "qmf/engine/MessageImpl.h" +#include "qmf/engine/ConnectionSettingsImpl.h" +#include <qpid/client/Connection.h> +#include <qpid/client/Session.h> +#include <qpid/client/MessageListener.h> +#include <qpid/client/SubscriptionManager.h> +#include <qpid/client/Message.h> +#include <qpid/sys/Thread.h> +#include <qpid/sys/Runnable.h> +#include <qpid/sys/Mutex.h> +#include <qpid/sys/Condition.h> +#include <qpid/sys/Time.h> +#include <qpid/log/Statement.h> +#include <qpid/RefCounted.h> +#include <boost/bind.hpp> +#include <string> +#include <deque> +#include <vector> +#include <set> +#include <boost/intrusive_ptr.hpp> +#include <boost/noncopyable.hpp> +#include <unistd.h> +#include <fcntl.h> + +using namespace std; +using namespace qmf::engine; +using namespace qpid; +using qpid::sys::Mutex; + +namespace qmf { +namespace engine { + struct ResilientConnectionEventImpl { + ResilientConnectionEvent::EventKind kind; + void* sessionContext; + string errorText; + MessageImpl message; + + ResilientConnectionEventImpl(ResilientConnectionEvent::EventKind k, + const MessageImpl& m = MessageImpl()) : + kind(k), sessionContext(0), message(m) {} + ResilientConnectionEvent copy(); + }; + + struct RCSession : public client::MessageListener, public qpid::sys::Runnable, public qpid::RefCounted { + typedef boost::intrusive_ptr<RCSession> Ptr; + ResilientConnectionImpl& connImpl; + string name; + client::Connection& connection; + client::Session session; + client::SubscriptionManager* subscriptions; + string userId; + void* userContext; + vector<string> dests; + qpid::sys::Thread thread; + + RCSession(ResilientConnectionImpl& ci, const string& n, client::Connection& c, void* uc); + ~RCSession(); + void received(client::Message& msg); + void run(); + void stop(); + }; + + class ResilientConnectionImpl : public qpid::sys::Runnable, public boost::noncopyable { + public: + ResilientConnectionImpl(const ConnectionSettings& settings); + ~ResilientConnectionImpl(); + + bool isConnected() const; + bool getEvent(ResilientConnectionEvent& event); + void popEvent(); + bool createSession(const char* name, void* sessionContext, SessionHandle& handle); + void destroySession(SessionHandle handle); + void sendMessage(SessionHandle handle, qmf::engine::Message& message); + void declareQueue(SessionHandle handle, char* queue); + void deleteQueue(SessionHandle handle, char* queue); + void bind(SessionHandle handle, char* exchange, char* queue, char* key); + void unbind(SessionHandle handle, char* exchange, char* queue, char* key); + void setNotifyFd(int fd); + void notify(); + + void run(); + void failure(); + void sessionClosed(RCSession* sess); + + void EnqueueEvent(ResilientConnectionEvent::EventKind kind, + void* sessionContext = 0, + const MessageImpl& message = MessageImpl(), + const string& errorText = ""); + + private: + int notifyFd; + bool connected; + bool shutdown; + string lastError; + const ConnectionSettings settings; + client::Connection connection; + mutable qpid::sys::Mutex lock; + int delayMin; + int delayMax; + int delayFactor; + qpid::sys::Condition cond; + deque<ResilientConnectionEventImpl> eventQueue; + set<RCSession::Ptr> sessions; + qpid::sys::Thread connThread; + }; +} +} + +ResilientConnectionEvent ResilientConnectionEventImpl::copy() +{ + ResilientConnectionEvent item; + + ::memset(&item, 0, sizeof(ResilientConnectionEvent)); + item.kind = kind; + item.sessionContext = sessionContext; + item.message = message.copy(); + item.errorText = const_cast<char*>(errorText.c_str()); + + return item; +} + +RCSession::RCSession(ResilientConnectionImpl& ci, const string& n, client::Connection& c, void* uc) : + connImpl(ci), name(n), connection(c), session(connection.newSession(name)), + subscriptions(new client::SubscriptionManager(session)), userContext(uc), thread(*this) +{ + const qpid::client::ConnectionSettings& operSettings = connection.getNegotiatedSettings(); + userId = operSettings.username; +} + +RCSession::~RCSession() +{ + subscriptions->stop(); + thread.join(); + session.close(); + delete subscriptions; +} + +void RCSession::run() +{ + try { + subscriptions->run(); + } catch (exception& /*e*/) { + connImpl.sessionClosed(this); + } +} + +void RCSession::stop() +{ + subscriptions->stop(); +} + +void RCSession::received(client::Message& msg) +{ + MessageImpl qmsg; + qmsg.body = msg.getData(); + + qpid::framing::DeliveryProperties dp = msg.getDeliveryProperties(); + if (dp.hasRoutingKey()) { + qmsg.routingKey = dp.getRoutingKey(); + } + + qpid::framing::MessageProperties mp = msg.getMessageProperties(); + if (mp.hasReplyTo()) { + const qpid::framing::ReplyTo& rt = mp.getReplyTo(); + qmsg.replyExchange = rt.getExchange(); + qmsg.replyKey = rt.getRoutingKey(); + } + + if (mp.hasUserId()) { + qmsg.userId = mp.getUserId(); + } + + connImpl.EnqueueEvent(ResilientConnectionEvent::RECV, userContext, qmsg); +} + +ResilientConnectionImpl::ResilientConnectionImpl(const ConnectionSettings& _settings) : + notifyFd(-1), connected(false), shutdown(false), settings(_settings), delayMin(1), connThread(*this) +{ + connection.registerFailureCallback(boost::bind(&ResilientConnectionImpl::failure, this)); + settings.impl->getRetrySettings(&delayMin, &delayMax, &delayFactor); +} + +ResilientConnectionImpl::~ResilientConnectionImpl() +{ + shutdown = true; + connected = false; + cond.notify(); + connThread.join(); + connection.close(); +} + +bool ResilientConnectionImpl::isConnected() const +{ + Mutex::ScopedLock _lock(lock); + return connected; +} + +bool ResilientConnectionImpl::getEvent(ResilientConnectionEvent& event) +{ + Mutex::ScopedLock _lock(lock); + if (eventQueue.empty()) + return false; + event = eventQueue.front().copy(); + return true; +} + +void ResilientConnectionImpl::popEvent() +{ + Mutex::ScopedLock _lock(lock); + if (!eventQueue.empty()) + eventQueue.pop_front(); +} + +bool ResilientConnectionImpl::createSession(const char* name, void* sessionContext, + SessionHandle& handle) +{ + Mutex::ScopedLock _lock(lock); + if (!connected) + return false; + + RCSession::Ptr sess = RCSession::Ptr(new RCSession(*this, name, connection, sessionContext)); + + handle.impl = (void*) sess.get(); + sessions.insert(sess); + + return true; +} + +void ResilientConnectionImpl::destroySession(SessionHandle handle) +{ + Mutex::ScopedLock _lock(lock); + RCSession::Ptr sess = RCSession::Ptr((RCSession*) handle.impl); + set<RCSession::Ptr>::iterator iter = sessions.find(sess); + if (iter != sessions.end()) { + for (vector<string>::iterator dIter = sess->dests.begin(); dIter != sess->dests.end(); dIter++) + sess->subscriptions->cancel(dIter->c_str()); + sess->subscriptions->stop(); + sess->subscriptions->wait(); + + sessions.erase(iter); + return; + } +} + +void ResilientConnectionImpl::sendMessage(SessionHandle handle, qmf::engine::Message& message) +{ + Mutex::ScopedLock _lock(lock); + RCSession::Ptr sess = RCSession::Ptr((RCSession*) handle.impl); + set<RCSession::Ptr>::iterator iter = sessions.find(sess); + qpid::client::Message msg; + string data(message.body, message.length); + msg.getDeliveryProperties().setRoutingKey(message.routingKey); + msg.getMessageProperties().setReplyTo(qpid::framing::ReplyTo(message.replyExchange, message.replyKey)); + if (settings.impl->getSendUserId()) + msg.getMessageProperties().setUserId(sess->userId); + msg.setData(data); + + try { + sess->session.messageTransfer(client::arg::content=msg, client::arg::destination=message.destination); + } catch(exception& e) { + QPID_LOG(error, "Session Exception during message-transfer: " << e.what()); + sessions.erase(iter); + EnqueueEvent(ResilientConnectionEvent::SESSION_CLOSED, (*iter)->userContext); + } +} + +void ResilientConnectionImpl::declareQueue(SessionHandle handle, char* queue) +{ + Mutex::ScopedLock _lock(lock); + RCSession* sess = (RCSession*) handle.impl; + + sess->session.queueDeclare(client::arg::queue=queue, client::arg::autoDelete=true, client::arg::exclusive=true); + sess->subscriptions->setAcceptMode(client::ACCEPT_MODE_NONE); + sess->subscriptions->setAcquireMode(client::ACQUIRE_MODE_PRE_ACQUIRED); + sess->subscriptions->subscribe(*sess, queue, queue); + sess->subscriptions->setFlowControl(queue, client::FlowControl::unlimited()); + sess->dests.push_back(string(queue)); +} + +void ResilientConnectionImpl::deleteQueue(SessionHandle handle, char* queue) +{ + Mutex::ScopedLock _lock(lock); + RCSession* sess = (RCSession*) handle.impl; + + sess->session.queueDelete(client::arg::queue=queue); + for (vector<string>::iterator iter = sess->dests.begin(); + iter != sess->dests.end(); iter++) + if (*iter == queue) { + sess->subscriptions->cancel(queue); + sess->dests.erase(iter); + break; + } +} + +void ResilientConnectionImpl::bind(SessionHandle handle, + char* exchange, char* queue, char* key) +{ + Mutex::ScopedLock _lock(lock); + RCSession* sess = (RCSession*) handle.impl; + + sess->session.exchangeBind(client::arg::exchange=exchange, client::arg::queue=queue, client::arg::bindingKey=key); +} + +void ResilientConnectionImpl::unbind(SessionHandle handle, + char* exchange, char* queue, char* key) +{ + Mutex::ScopedLock _lock(lock); + RCSession* sess = (RCSession*) handle.impl; + + sess->session.exchangeUnbind(client::arg::exchange=exchange, client::arg::queue=queue, client::arg::bindingKey=key); +} + +void ResilientConnectionImpl::notify() +{ + if (notifyFd != -1) + { + int unused_ret; //Suppress warnings about ignoring return value. + unused_ret = ::write(notifyFd, ".", 1); + } +} + + +void ResilientConnectionImpl::setNotifyFd(int fd) +{ + notifyFd = fd; + if (notifyFd > 0) { + int original = fcntl(notifyFd, F_GETFL); + fcntl(notifyFd, F_SETFL, O_NONBLOCK | original); + } +} + +void ResilientConnectionImpl::run() +{ + int delay(delayMin); + + while (true) { + try { + QPID_LOG(trace, "Trying to open connection..."); + connection.open(settings.impl->getClientSettings()); + { + Mutex::ScopedLock _lock(lock); + connected = true; + EnqueueEvent(ResilientConnectionEvent::CONNECTED); + + while (connected) + cond.wait(lock); + delay = delayMin; + + while (!sessions.empty()) { + set<RCSession::Ptr>::iterator iter = sessions.begin(); + RCSession::Ptr sess = *iter; + sessions.erase(iter); + EnqueueEvent(ResilientConnectionEvent::SESSION_CLOSED, sess->userContext); + Mutex::ScopedUnlock _u(lock); + sess->stop(); + + // Nullify the intrusive pointer within the scoped unlock, otherwise, + // the reference is held until overwritted above (under lock) which causes + // the session destructor to be called with the lock held. + sess = 0; + } + + EnqueueEvent(ResilientConnectionEvent::DISCONNECTED); + + if (shutdown) + return; + } + connection.close(); + } catch (exception &e) { + QPID_LOG(debug, "connection.open exception: " << e.what()); + Mutex::ScopedLock _lock(lock); + lastError = e.what(); + if (delay < delayMax) + delay *= delayFactor; + } + + ::qpid::sys::sleep(delay); + } +} + +void ResilientConnectionImpl::failure() +{ + Mutex::ScopedLock _lock(lock); + + connected = false; + lastError = "Closed by Peer"; + cond.notify(); +} + +void ResilientConnectionImpl::sessionClosed(RCSession*) +{ + Mutex::ScopedLock _lock(lock); + connected = false; + lastError = "Closed due to Session failure"; + cond.notify(); +} + +void ResilientConnectionImpl::EnqueueEvent(ResilientConnectionEvent::EventKind kind, + void* sessionContext, + const MessageImpl& message, + const string& errorText) +{ + { + Mutex::ScopedLock _lock(lock); + ResilientConnectionEventImpl event(kind, message); + + event.sessionContext = sessionContext; + event.errorText = errorText; + + eventQueue.push_back(event); + } + + if (notifyFd != -1) + { + int unused_ret; //Suppress warnings about ignoring return value. + unused_ret = ::write(notifyFd, ".", 1); + } +} + + +//================================================================== +// Wrappers +//================================================================== + +ResilientConnection::ResilientConnection(const ConnectionSettings& settings) +{ + impl = new ResilientConnectionImpl(settings); +} + +ResilientConnection::~ResilientConnection() +{ + delete impl; +} + +bool ResilientConnection::isConnected() const +{ + return impl->isConnected(); +} + +bool ResilientConnection::getEvent(ResilientConnectionEvent& event) +{ + return impl->getEvent(event); +} + +void ResilientConnection::popEvent() +{ + impl->popEvent(); +} + +bool ResilientConnection::createSession(const char* name, void* sessionContext, SessionHandle& handle) +{ + return impl->createSession(name, sessionContext, handle); +} + +void ResilientConnection::destroySession(SessionHandle handle) +{ + impl->destroySession(handle); +} + +void ResilientConnection::sendMessage(SessionHandle handle, qmf::engine::Message& message) +{ + impl->sendMessage(handle, message); +} + +void ResilientConnection::declareQueue(SessionHandle handle, char* queue) +{ + impl->declareQueue(handle, queue); +} + +void ResilientConnection::deleteQueue(SessionHandle handle, char* queue) +{ + impl->deleteQueue(handle, queue); +} + +void ResilientConnection::bind(SessionHandle handle, char* exchange, char* queue, char* key) +{ + impl->bind(handle, exchange, queue, key); +} + +void ResilientConnection::unbind(SessionHandle handle, char* exchange, char* queue, char* key) +{ + impl->unbind(handle, exchange, queue, key); +} + +void ResilientConnection::setNotifyFd(int fd) +{ + impl->setNotifyFd(fd); +} + +void ResilientConnection::notify() +{ + impl->notify(); +} + diff --git a/qpid/cpp/src/qmf/engine/SchemaImpl.cpp b/qpid/cpp/src/qmf/engine/SchemaImpl.cpp new file mode 100644 index 0000000000..f75663e131 --- /dev/null +++ b/qpid/cpp/src/qmf/engine/SchemaImpl.cpp @@ -0,0 +1,614 @@ +/* + * 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/engine/SchemaImpl.h" +#include <qpid/framing/Buffer.h> +#include <qpid/framing/FieldTable.h> +#include <qpid/framing/Uuid.h> +#include <string.h> +#include <string> +#include <vector> +#include <sstream> + +using namespace std; +using namespace qmf::engine; +using qpid::framing::Buffer; +using qpid::framing::FieldTable; +using qpid::framing::Uuid; + +SchemaHash::SchemaHash() +{ + for (int idx = 0; idx < 16; idx++) + hash[idx] = 0x5A; +} + +void SchemaHash::encode(Buffer& buffer) const +{ + buffer.putBin128(hash); +} + +void SchemaHash::decode(Buffer& buffer) +{ + buffer.getBin128(hash); +} + +void SchemaHash::update(uint8_t data) +{ + update((char*) &data, 1); +} + +void SchemaHash::update(const char* data, uint32_t len) +{ + union h { + uint8_t b[16]; + uint64_t q[2]; + }* h = reinterpret_cast<union h*>(&hash[0]); + uint64_t* first = &h->q[0]; + uint64_t* second = &h->q[1]; + for (uint32_t idx = 0; idx < len; idx++) { + *first = *first ^ (uint64_t) data[idx]; + *second = *second << 1; + *second |= ((*first & 0x8000000000000000LL) >> 63); + *first = *first << 1; + *first = *first ^ *second; + } +} + +bool SchemaHash::operator==(const SchemaHash& other) const +{ + return ::memcmp(&hash, &other.hash, 16) == 0; +} + +bool SchemaHash::operator<(const SchemaHash& other) const +{ + return ::memcmp(&hash, &other.hash, 16) < 0; +} + +bool SchemaHash::operator>(const SchemaHash& other) const +{ + return ::memcmp(&hash, &other.hash, 16) > 0; +} + +SchemaArgumentImpl::SchemaArgumentImpl(Buffer& buffer) +{ + FieldTable map; + map.decode(buffer); + + name = map.getAsString("name"); + typecode = (Typecode) map.getAsInt("type"); + unit = map.getAsString("unit"); + description = map.getAsString("desc"); + + dir = DIR_IN; + string dstr(map.getAsString("dir")); + if (dstr == "O") + dir = DIR_OUT; + else if (dstr == "IO") + dir = DIR_IN_OUT; +} + +SchemaArgument* SchemaArgumentImpl::factory(Buffer& buffer) +{ + SchemaArgumentImpl* impl(new SchemaArgumentImpl(buffer)); + return new SchemaArgument(impl); +} + +void SchemaArgumentImpl::encode(Buffer& buffer) const +{ + FieldTable map; + + map.setString("name", name); + map.setInt("type", (int) typecode); + if (dir == DIR_IN) + map.setString("dir", "I"); + else if (dir == DIR_OUT) + map.setString("dir", "O"); + else + map.setString("dir", "IO"); + if (!unit.empty()) + map.setString("unit", unit); + if (!description.empty()) + map.setString("desc", description); + + map.encode(buffer); +} + +void SchemaArgumentImpl::updateHash(SchemaHash& hash) const +{ + hash.update(name); + hash.update(typecode); + hash.update(dir); + hash.update(unit); + hash.update(description); +} + +SchemaMethodImpl::SchemaMethodImpl(Buffer& buffer) +{ + FieldTable map; + int argCount; + + map.decode(buffer); + name = map.getAsString("name"); + argCount = map.getAsInt("argCount"); + description = map.getAsString("desc"); + + for (int idx = 0; idx < argCount; idx++) { + SchemaArgument* arg = SchemaArgumentImpl::factory(buffer); + addArgument(arg); + } +} + +SchemaMethod* SchemaMethodImpl::factory(Buffer& buffer) +{ + SchemaMethodImpl* impl(new SchemaMethodImpl(buffer)); + return new SchemaMethod(impl); +} + +void SchemaMethodImpl::encode(Buffer& buffer) const +{ + FieldTable map; + + map.setString("name", name); + map.setInt("argCount", arguments.size()); + if (!description.empty()) + map.setString("desc", description); + map.encode(buffer); + + for (vector<const SchemaArgument*>::const_iterator iter = arguments.begin(); + iter != arguments.end(); iter++) + (*iter)->impl->encode(buffer); +} + +void SchemaMethodImpl::addArgument(const SchemaArgument* argument) +{ + arguments.push_back(argument); +} + +const SchemaArgument* SchemaMethodImpl::getArgument(int idx) const +{ + int count = 0; + for (vector<const SchemaArgument*>::const_iterator iter = arguments.begin(); + iter != arguments.end(); iter++, count++) + if (idx == count) + return (*iter); + return 0; +} + +void SchemaMethodImpl::updateHash(SchemaHash& hash) const +{ + hash.update(name); + hash.update(description); + for (vector<const SchemaArgument*>::const_iterator iter = arguments.begin(); + iter != arguments.end(); iter++) + (*iter)->impl->updateHash(hash); +} + +SchemaPropertyImpl::SchemaPropertyImpl(Buffer& buffer) +{ + FieldTable map; + map.decode(buffer); + + name = map.getAsString("name"); + typecode = (Typecode) map.getAsInt("type"); + access = (Access) map.getAsInt("access"); + index = map.getAsInt("index") != 0; + optional = map.getAsInt("optional") != 0; + unit = map.getAsString("unit"); + description = map.getAsString("desc"); +} + +SchemaProperty* SchemaPropertyImpl::factory(Buffer& buffer) +{ + SchemaPropertyImpl* impl(new SchemaPropertyImpl(buffer)); + return new SchemaProperty(impl); +} + +void SchemaPropertyImpl::encode(Buffer& buffer) const +{ + FieldTable map; + + map.setString("name", name); + map.setInt("type", (int) typecode); + map.setInt("access", (int) access); + map.setInt("index", index ? 1 : 0); + map.setInt("optional", optional ? 1 : 0); + if (!unit.empty()) + map.setString("unit", unit); + if (!description.empty()) + map.setString("desc", description); + + map.encode(buffer); +} + +void SchemaPropertyImpl::updateHash(SchemaHash& hash) const +{ + hash.update(name); + hash.update(typecode); + hash.update(access); + hash.update(index); + hash.update(optional); + hash.update(unit); + hash.update(description); +} + +SchemaStatisticImpl::SchemaStatisticImpl(Buffer& buffer) +{ + FieldTable map; + map.decode(buffer); + + name = map.getAsString("name"); + typecode = (Typecode) map.getAsInt("type"); + unit = map.getAsString("unit"); + description = map.getAsString("desc"); +} + +SchemaStatistic* SchemaStatisticImpl::factory(Buffer& buffer) +{ + SchemaStatisticImpl* impl(new SchemaStatisticImpl(buffer)); + return new SchemaStatistic(impl); +} + +void SchemaStatisticImpl::encode(Buffer& buffer) const +{ + FieldTable map; + + map.setString("name", name); + map.setInt("type", (int) typecode); + if (!unit.empty()) + map.setString("unit", unit); + if (!description.empty()) + map.setString("desc", description); + + map.encode(buffer); +} + +void SchemaStatisticImpl::updateHash(SchemaHash& hash) const +{ + hash.update(name); + hash.update(typecode); + hash.update(unit); + hash.update(description); +} + +SchemaClassKeyImpl::SchemaClassKeyImpl(const string& p, const string& n, const SchemaHash& h) : package(p), name(n), hash(h) {} + +SchemaClassKeyImpl::SchemaClassKeyImpl(Buffer& buffer) : package(packageContainer), name(nameContainer), hash(hashContainer) +{ + buffer.getShortString(packageContainer); + buffer.getShortString(nameContainer); + hashContainer.decode(buffer); +} + +SchemaClassKey* SchemaClassKeyImpl::factory(const string& package, const string& name, const SchemaHash& hash) +{ + SchemaClassKeyImpl* impl(new SchemaClassKeyImpl(package, name, hash)); + return new SchemaClassKey(impl); +} + +SchemaClassKey* SchemaClassKeyImpl::factory(Buffer& buffer) +{ + SchemaClassKeyImpl* impl(new SchemaClassKeyImpl(buffer)); + return new SchemaClassKey(impl); +} + +void SchemaClassKeyImpl::encode(Buffer& buffer) const +{ + buffer.putShortString(package); + buffer.putShortString(name); + hash.encode(buffer); +} + +bool SchemaClassKeyImpl::operator==(const SchemaClassKeyImpl& other) const +{ + return package == other.package && + name == other.name && + hash == other.hash; +} + +bool SchemaClassKeyImpl::operator<(const SchemaClassKeyImpl& other) const +{ + if (package < other.package) return true; + if (package > other.package) return false; + if (name < other.name) return true; + if (name > other.name) return false; + return hash < other.hash; +} + +const string& SchemaClassKeyImpl::str() const +{ + Uuid printableHash(hash.get()); + stringstream str; + str << package << ":" << name << "(" << printableHash << ")"; + repr = str.str(); + return repr; +} + +SchemaObjectClassImpl::SchemaObjectClassImpl(Buffer& buffer) : hasHash(true), classKey(SchemaClassKeyImpl::factory(package, name, hash)) +{ + buffer.getShortString(package); + buffer.getShortString(name); + hash.decode(buffer); + + uint16_t propCount = buffer.getShort(); + uint16_t statCount = buffer.getShort(); + uint16_t methodCount = buffer.getShort(); + + for (uint16_t idx = 0; idx < propCount; idx++) { + const SchemaProperty* property = SchemaPropertyImpl::factory(buffer); + addProperty(property); + } + + for (uint16_t idx = 0; idx < statCount; idx++) { + const SchemaStatistic* statistic = SchemaStatisticImpl::factory(buffer); + addStatistic(statistic); + } + + for (uint16_t idx = 0; idx < methodCount; idx++) { + SchemaMethod* method = SchemaMethodImpl::factory(buffer); + addMethod(method); + } +} + +SchemaObjectClass* SchemaObjectClassImpl::factory(Buffer& buffer) +{ + SchemaObjectClassImpl* impl(new SchemaObjectClassImpl(buffer)); + return new SchemaObjectClass(impl); +} + +void SchemaObjectClassImpl::encode(Buffer& buffer) const +{ + buffer.putOctet((uint8_t) CLASS_OBJECT); + buffer.putShortString(package); + buffer.putShortString(name); + hash.encode(buffer); + //buffer.putOctet(0); // No parent class + buffer.putShort((uint16_t) properties.size()); + buffer.putShort((uint16_t) statistics.size()); + buffer.putShort((uint16_t) methods.size()); + + for (vector<const SchemaProperty*>::const_iterator iter = properties.begin(); + iter != properties.end(); iter++) + (*iter)->impl->encode(buffer); + for (vector<const SchemaStatistic*>::const_iterator iter = statistics.begin(); + iter != statistics.end(); iter++) + (*iter)->impl->encode(buffer); + for (vector<const SchemaMethod*>::const_iterator iter = methods.begin(); + iter != methods.end(); iter++) + (*iter)->impl->encode(buffer); +} + +const SchemaClassKey* SchemaObjectClassImpl::getClassKey() const +{ + if (!hasHash) { + hasHash = true; + hash.update(package); + hash.update(name); + for (vector<const SchemaProperty*>::const_iterator iter = properties.begin(); + iter != properties.end(); iter++) + (*iter)->impl->updateHash(hash); + for (vector<const SchemaStatistic*>::const_iterator iter = statistics.begin(); + iter != statistics.end(); iter++) + (*iter)->impl->updateHash(hash); + for (vector<const SchemaMethod*>::const_iterator iter = methods.begin(); + iter != methods.end(); iter++) + (*iter)->impl->updateHash(hash); + } + + return classKey.get(); +} + +void SchemaObjectClassImpl::addProperty(const SchemaProperty* property) +{ + properties.push_back(property); +} + +void SchemaObjectClassImpl::addStatistic(const SchemaStatistic* statistic) +{ + statistics.push_back(statistic); +} + +void SchemaObjectClassImpl::addMethod(const SchemaMethod* method) +{ + methods.push_back(method); +} + +const SchemaProperty* SchemaObjectClassImpl::getProperty(int idx) const +{ + int count = 0; + for (vector<const SchemaProperty*>::const_iterator iter = properties.begin(); + iter != properties.end(); iter++, count++) + if (idx == count) + return *iter; + return 0; +} + +const SchemaStatistic* SchemaObjectClassImpl::getStatistic(int idx) const +{ + int count = 0; + for (vector<const SchemaStatistic*>::const_iterator iter = statistics.begin(); + iter != statistics.end(); iter++, count++) + if (idx == count) + return *iter; + return 0; +} + +const SchemaMethod* SchemaObjectClassImpl::getMethod(int idx) const +{ + int count = 0; + for (vector<const SchemaMethod*>::const_iterator iter = methods.begin(); + iter != methods.end(); iter++, count++) + if (idx == count) + return *iter; + return 0; +} + +SchemaEventClassImpl::SchemaEventClassImpl(Buffer& buffer) : hasHash(true), classKey(SchemaClassKeyImpl::factory(package, name, hash)) +{ + buffer.getShortString(package); + buffer.getShortString(name); + hash.decode(buffer); + + uint16_t argCount = buffer.getShort(); + + for (uint16_t idx = 0; idx < argCount; idx++) { + SchemaArgument* argument = SchemaArgumentImpl::factory(buffer); + addArgument(argument); + } +} + +SchemaEventClass* SchemaEventClassImpl::factory(Buffer& buffer) +{ + SchemaEventClassImpl* impl(new SchemaEventClassImpl(buffer)); + return new SchemaEventClass(impl); +} + +void SchemaEventClassImpl::encode(Buffer& buffer) const +{ + buffer.putOctet((uint8_t) CLASS_EVENT); + buffer.putShortString(package); + buffer.putShortString(name); + hash.encode(buffer); + buffer.putShort((uint16_t) arguments.size()); + + for (vector<const SchemaArgument*>::const_iterator iter = arguments.begin(); + iter != arguments.end(); iter++) + (*iter)->impl->encode(buffer); +} + +const SchemaClassKey* SchemaEventClassImpl::getClassKey() const +{ + if (!hasHash) { + hasHash = true; + hash.update(package); + hash.update(name); + for (vector<const SchemaArgument*>::const_iterator iter = arguments.begin(); + iter != arguments.end(); iter++) + (*iter)->impl->updateHash(hash); + } + return classKey.get(); +} + +void SchemaEventClassImpl::addArgument(const SchemaArgument* argument) +{ + arguments.push_back(argument); +} + +const SchemaArgument* SchemaEventClassImpl::getArgument(int idx) const +{ + int count = 0; + for (vector<const SchemaArgument*>::const_iterator iter = arguments.begin(); + iter != arguments.end(); iter++, count++) + if (idx == count) + return (*iter); + return 0; +} + + +//================================================================== +// Wrappers +//================================================================== + +SchemaArgument::SchemaArgument(const char* name, Typecode typecode) { impl = new SchemaArgumentImpl(name, typecode); } +SchemaArgument::SchemaArgument(SchemaArgumentImpl* i) : impl(i) {} +SchemaArgument::SchemaArgument(const SchemaArgument& from) : impl(new SchemaArgumentImpl(*(from.impl))) {} +SchemaArgument::~SchemaArgument() { delete impl; } +void SchemaArgument::setDirection(Direction dir) { impl->setDirection(dir); } +void SchemaArgument::setUnit(const char* val) { impl->setUnit(val); } +void SchemaArgument::setDesc(const char* desc) { impl->setDesc(desc); } +const char* SchemaArgument::getName() const { return impl->getName().c_str(); } +Typecode SchemaArgument::getType() const { return impl->getType(); } +Direction SchemaArgument::getDirection() const { return impl->getDirection(); } +const char* SchemaArgument::getUnit() const { return impl->getUnit().c_str(); } +const char* SchemaArgument::getDesc() const { return impl->getDesc().c_str(); } + +SchemaMethod::SchemaMethod(const char* name) : impl(new SchemaMethodImpl(name)) {} +SchemaMethod::SchemaMethod(SchemaMethodImpl* i) : impl(i) {} +SchemaMethod::SchemaMethod(const SchemaMethod& from) : impl(new SchemaMethodImpl(*(from.impl))) {} +SchemaMethod::~SchemaMethod() { delete impl; } +void SchemaMethod::addArgument(const SchemaArgument* argument) { impl->addArgument(argument); } +void SchemaMethod::setDesc(const char* desc) { impl->setDesc(desc); } +const char* SchemaMethod::getName() const { return impl->getName().c_str(); } +const char* SchemaMethod::getDesc() const { return impl->getDesc().c_str(); } +int SchemaMethod::getArgumentCount() const { return impl->getArgumentCount(); } +const SchemaArgument* SchemaMethod::getArgument(int idx) const { return impl->getArgument(idx); } + +SchemaProperty::SchemaProperty(const char* name, Typecode typecode) : impl(new SchemaPropertyImpl(name, typecode)) {} +SchemaProperty::SchemaProperty(SchemaPropertyImpl* i) : impl(i) {} +SchemaProperty::SchemaProperty(const SchemaProperty& from) : impl(new SchemaPropertyImpl(*(from.impl))) {} +SchemaProperty::~SchemaProperty() { delete impl; } +void SchemaProperty::setAccess(Access access) { impl->setAccess(access); } +void SchemaProperty::setIndex(bool val) { impl->setIndex(val); } +void SchemaProperty::setOptional(bool val) { impl->setOptional(val); } +void SchemaProperty::setUnit(const char* val) { impl->setUnit(val); } +void SchemaProperty::setDesc(const char* desc) { impl->setDesc(desc); } +const char* SchemaProperty::getName() const { return impl->getName().c_str(); } +Typecode SchemaProperty::getType() const { return impl->getType(); } +Access SchemaProperty::getAccess() const { return impl->getAccess(); } +bool SchemaProperty::isIndex() const { return impl->isIndex(); } +bool SchemaProperty::isOptional() const { return impl->isOptional(); } +const char* SchemaProperty::getUnit() const { return impl->getUnit().c_str(); } +const char* SchemaProperty::getDesc() const { return impl->getDesc().c_str(); } + +SchemaStatistic::SchemaStatistic(const char* name, Typecode typecode) : impl(new SchemaStatisticImpl(name, typecode)) {} +SchemaStatistic::SchemaStatistic(SchemaStatisticImpl* i) : impl(i) {} +SchemaStatistic::SchemaStatistic(const SchemaStatistic& from) : impl(new SchemaStatisticImpl(*(from.impl))) {} +SchemaStatistic::~SchemaStatistic() { delete impl; } +void SchemaStatistic::setUnit(const char* val) { impl->setUnit(val); } +void SchemaStatistic::setDesc(const char* desc) { impl->setDesc(desc); } +const char* SchemaStatistic::getName() const { return impl->getName().c_str(); } +Typecode SchemaStatistic::getType() const { return impl->getType(); } +const char* SchemaStatistic::getUnit() const { return impl->getUnit().c_str(); } +const char* SchemaStatistic::getDesc() const { return impl->getDesc().c_str(); } + +SchemaClassKey::SchemaClassKey(SchemaClassKeyImpl* i) : impl(i) {} +SchemaClassKey::SchemaClassKey(const SchemaClassKey& from) : impl(new SchemaClassKeyImpl(*(from.impl))) {} +SchemaClassKey::~SchemaClassKey() { delete impl; } +const char* SchemaClassKey::getPackageName() const { return impl->getPackageName().c_str(); } +const char* SchemaClassKey::getClassName() const { return impl->getClassName().c_str(); } +const uint8_t* SchemaClassKey::getHash() const { return impl->getHash(); } +const char* SchemaClassKey::asString() const { return impl->str().c_str(); } +bool SchemaClassKey::operator==(const SchemaClassKey& other) const { return *impl == *(other.impl); } +bool SchemaClassKey::operator<(const SchemaClassKey& other) const { return *impl < *(other.impl); } + +SchemaObjectClass::SchemaObjectClass(const char* package, const char* name) : impl(new SchemaObjectClassImpl(package, name)) {} +SchemaObjectClass::SchemaObjectClass(SchemaObjectClassImpl* i) : impl(i) {} +SchemaObjectClass::SchemaObjectClass(const SchemaObjectClass& from) : impl(new SchemaObjectClassImpl(*(from.impl))) {} +SchemaObjectClass::~SchemaObjectClass() { delete impl; } +void SchemaObjectClass::addProperty(const SchemaProperty* property) { impl->addProperty(property); } +void SchemaObjectClass::addStatistic(const SchemaStatistic* statistic) { impl->addStatistic(statistic); } +void SchemaObjectClass::addMethod(const SchemaMethod* method) { impl->addMethod(method); } +const SchemaClassKey* SchemaObjectClass::getClassKey() const { return impl->getClassKey(); } +int SchemaObjectClass::getPropertyCount() const { return impl->getPropertyCount(); } +int SchemaObjectClass::getStatisticCount() const { return impl->getStatisticCount(); } +int SchemaObjectClass::getMethodCount() const { return impl->getMethodCount(); } +const SchemaProperty* SchemaObjectClass::getProperty(int idx) const { return impl->getProperty(idx); } +const SchemaStatistic* SchemaObjectClass::getStatistic(int idx) const { return impl->getStatistic(idx); } +const SchemaMethod* SchemaObjectClass::getMethod(int idx) const { return impl->getMethod(idx); } + +SchemaEventClass::SchemaEventClass(const char* package, const char* name, Severity s) : impl(new SchemaEventClassImpl(package, name, s)) {} +SchemaEventClass::SchemaEventClass(SchemaEventClassImpl* i) : impl(i) {} +SchemaEventClass::SchemaEventClass(const SchemaEventClass& from) : impl(new SchemaEventClassImpl(*(from.impl))) {} +SchemaEventClass::~SchemaEventClass() { delete impl; } +void SchemaEventClass::addArgument(const SchemaArgument* argument) { impl->addArgument(argument); } +void SchemaEventClass::setDesc(const char* desc) { impl->setDesc(desc); } +const SchemaClassKey* SchemaEventClass::getClassKey() const { return impl->getClassKey(); } +Severity SchemaEventClass::getSeverity() const { return impl->getSeverity(); } +int SchemaEventClass::getArgumentCount() const { return impl->getArgumentCount(); } +const SchemaArgument* SchemaEventClass::getArgument(int idx) const { return impl->getArgument(idx); } + diff --git a/qpid/cpp/src/qmf/engine/SchemaImpl.h b/qpid/cpp/src/qmf/engine/SchemaImpl.h new file mode 100644 index 0000000000..8b079a5ec6 --- /dev/null +++ b/qpid/cpp/src/qmf/engine/SchemaImpl.h @@ -0,0 +1,227 @@ +#ifndef _QmfEngineSchemaImpl_ +#define _QmfEngineSchemaImpl_ + +/* + * 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/engine/Schema.h" +#include "qpid/framing/Buffer.h" + +#include <string> +#include <vector> +#include <memory> + +namespace qmf { +namespace engine { + + // TODO: Destructors for schema classes + // TODO: Add "frozen" attribute for schema classes so they can't be modified after + // they've been registered. + + class SchemaHash { + uint8_t hash[16]; + public: + SchemaHash(); + void encode(qpid::framing::Buffer& buffer) const; + void decode(qpid::framing::Buffer& buffer); + void update(const char* data, uint32_t len); + void update(uint8_t data); + void update(const std::string& data) { update(data.c_str(), data.size()); } + void update(Typecode t) { update((uint8_t) t); } + 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; } + bool operator==(const SchemaHash& other) const; + bool operator<(const SchemaHash& other) const; + bool operator>(const SchemaHash& other) const; + }; + + struct SchemaArgumentImpl { + std::string name; + Typecode typecode; + Direction dir; + std::string unit; + std::string description; + + SchemaArgumentImpl(const char* n, Typecode t) : name(n), typecode(t), dir(DIR_IN) {} + SchemaArgumentImpl(qpid::framing::Buffer& buffer); + static SchemaArgument* factory(qpid::framing::Buffer& buffer); + void encode(qpid::framing::Buffer& buffer) const; + void setDirection(Direction d) { dir = d; } + void setUnit(const char* val) { unit = val; } + void setDesc(const char* desc) { description = desc; } + const std::string& getName() const { return name; } + Typecode getType() const { return typecode; } + Direction getDirection() const { return dir; } + const std::string& getUnit() const { return unit; } + const std::string& getDesc() const { return description; } + void updateHash(SchemaHash& hash) const; + }; + + struct SchemaMethodImpl { + std::string name; + std::string description; + std::vector<const SchemaArgument*> arguments; + + SchemaMethodImpl(const char* n) : name(n) {} + SchemaMethodImpl(qpid::framing::Buffer& buffer); + static SchemaMethod* factory(qpid::framing::Buffer& buffer); + void encode(qpid::framing::Buffer& buffer) const; + void addArgument(const SchemaArgument* argument); + void setDesc(const char* desc) { description = desc; } + const std::string& getName() const { return name; } + const std::string& getDesc() const { return description; } + int getArgumentCount() const { return arguments.size(); } + const SchemaArgument* getArgument(int idx) const; + void updateHash(SchemaHash& hash) const; + }; + + struct SchemaPropertyImpl { + std::string name; + Typecode typecode; + Access access; + bool index; + bool optional; + std::string unit; + std::string description; + + SchemaPropertyImpl(const char* n, Typecode t) : name(n), typecode(t), access(ACCESS_READ_ONLY), index(false), optional(false) {} + SchemaPropertyImpl(qpid::framing::Buffer& buffer); + static SchemaProperty* factory(qpid::framing::Buffer& buffer); + void encode(qpid::framing::Buffer& buffer) const; + void setAccess(Access a) { access = a; } + void setIndex(bool val) { index = val; } + void setOptional(bool val) { optional = val; } + void setUnit(const char* val) { unit = val; } + void setDesc(const char* desc) { description = desc; } + const std::string& getName() const { return name; } + Typecode getType() const { return typecode; } + Access getAccess() const { return access; } + bool isIndex() const { return index; } + bool isOptional() const { return optional; } + const std::string& getUnit() const { return unit; } + const std::string& getDesc() const { return description; } + void updateHash(SchemaHash& hash) const; + }; + + struct SchemaStatisticImpl { + std::string name; + Typecode typecode; + std::string unit; + std::string description; + + SchemaStatisticImpl(const char* n, Typecode t) : name(n), typecode(t) {} + SchemaStatisticImpl(qpid::framing::Buffer& buffer); + static SchemaStatistic* factory(qpid::framing::Buffer& buffer); + void encode(qpid::framing::Buffer& buffer) const; + void setUnit(const char* val) { unit = val; } + void setDesc(const char* desc) { description = desc; } + const std::string& getName() const { return name; } + Typecode getType() const { return typecode; } + const std::string& getUnit() const { return unit; } + const std::string& getDesc() const { return description; } + void updateHash(SchemaHash& hash) const; + }; + + struct SchemaClassKeyImpl { + const std::string& package; + const std::string& name; + const SchemaHash& hash; + mutable std::string repr; + + // The *Container elements are only used if there isn't an external place to + // store these values. + std::string packageContainer; + std::string nameContainer; + SchemaHash hashContainer; + + SchemaClassKeyImpl(const std::string& package, const std::string& name, const SchemaHash& hash); + SchemaClassKeyImpl(qpid::framing::Buffer& buffer); + static SchemaClassKey* factory(const std::string& package, const std::string& name, const SchemaHash& hash); + static SchemaClassKey* factory(qpid::framing::Buffer& buffer); + + const std::string& getPackageName() const { return package; } + const std::string& getClassName() const { return name; } + const uint8_t* getHash() const { return hash.get(); } + + void encode(qpid::framing::Buffer& buffer) const; + bool operator==(const SchemaClassKeyImpl& other) const; + bool operator<(const SchemaClassKeyImpl& other) const; + const std::string& str() const; + }; + + struct SchemaObjectClassImpl { + std::string package; + std::string name; + mutable SchemaHash hash; + mutable bool hasHash; + std::auto_ptr<SchemaClassKey> classKey; + std::vector<const SchemaProperty*> properties; + std::vector<const SchemaStatistic*> statistics; + std::vector<const SchemaMethod*> methods; + + SchemaObjectClassImpl(const char* p, const char* n) : + package(p), name(n), hasHash(false), classKey(SchemaClassKeyImpl::factory(package, name, hash)) {} + SchemaObjectClassImpl(qpid::framing::Buffer& buffer); + static SchemaObjectClass* factory(qpid::framing::Buffer& buffer); + + void encode(qpid::framing::Buffer& buffer) const; + void addProperty(const SchemaProperty* property); + void addStatistic(const SchemaStatistic* statistic); + void addMethod(const SchemaMethod* method); + + const SchemaClassKey* getClassKey() const; + int getPropertyCount() const { return properties.size(); } + int getStatisticCount() const { return statistics.size(); } + int getMethodCount() const { return methods.size(); } + const SchemaProperty* getProperty(int idx) const; + const SchemaStatistic* getStatistic(int idx) const; + const SchemaMethod* getMethod(int idx) const; + }; + + struct SchemaEventClassImpl { + std::string package; + std::string name; + mutable SchemaHash hash; + mutable bool hasHash; + std::auto_ptr<SchemaClassKey> classKey; + std::string description; + Severity severity; + std::vector<const SchemaArgument*> arguments; + + SchemaEventClassImpl(const char* p, const char* n, Severity sev) : + package(p), name(n), hasHash(false), classKey(SchemaClassKeyImpl::factory(package, name, hash)), severity(sev) {} + SchemaEventClassImpl(qpid::framing::Buffer& buffer); + static SchemaEventClass* factory(qpid::framing::Buffer& buffer); + + void encode(qpid::framing::Buffer& buffer) const; + void addArgument(const SchemaArgument* argument); + void setDesc(const char* desc) { description = desc; } + + const SchemaClassKey* getClassKey() const; + Severity getSeverity() const { return severity; } + int getArgumentCount() const { return arguments.size(); } + const SchemaArgument* getArgument(int idx) const; + }; +} +} + +#endif + diff --git a/qpid/cpp/src/qmf/engine/SequenceManager.cpp b/qpid/cpp/src/qmf/engine/SequenceManager.cpp new file mode 100644 index 0000000000..4a4644a8b9 --- /dev/null +++ b/qpid/cpp/src/qmf/engine/SequenceManager.cpp @@ -0,0 +1,96 @@ +/* + * 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/engine/SequenceManager.h" + +using namespace std; +using namespace qmf::engine; +using namespace qpid::sys; + +SequenceManager::SequenceManager() : nextSequence(1) {} + +void SequenceManager::setUnsolicitedContext(SequenceContext::Ptr ctx) +{ + unsolicitedContext = ctx; +} + +uint32_t SequenceManager::reserve(SequenceContext::Ptr ctx) +{ + Mutex::ScopedLock _lock(lock); + if (ctx.get() == 0) + ctx = unsolicitedContext; + uint32_t seq = nextSequence; + while (contextMap.find(seq) != contextMap.end()) + seq = seq < 0xFFFFFFFF ? seq + 1 : 1; + nextSequence = seq < 0xFFFFFFFF ? seq + 1 : 1; + contextMap[seq] = ctx; + ctx->reserve(); + return seq; +} + +void SequenceManager::release(uint32_t sequence) +{ + Mutex::ScopedLock _lock(lock); + + if (sequence == 0) { + if (unsolicitedContext.get() != 0) + unsolicitedContext->release(); + return; + } + + map<uint32_t, SequenceContext::Ptr>::iterator iter = contextMap.find(sequence); + if (iter != contextMap.end()) { + if (iter->second != 0) + iter->second->release(); + contextMap.erase(iter); + } +} + +void SequenceManager::releaseAll() +{ + Mutex::ScopedLock _lock(lock); + contextMap.clear(); +} + +void SequenceManager::dispatch(uint8_t opcode, uint32_t sequence, const string& routingKey, qpid::framing::Buffer& buffer) +{ + Mutex::ScopedLock _lock(lock); + bool done; + + if (sequence == 0) { + if (unsolicitedContext.get() != 0) { + done = unsolicitedContext->handleMessage(opcode, sequence, routingKey, buffer); + if (done) + unsolicitedContext->release(); + } + return; + } + + map<uint32_t, SequenceContext::Ptr>::iterator iter = contextMap.find(sequence); + if (iter != contextMap.end()) { + if (iter->second != 0) { + done = iter->second->handleMessage(opcode, sequence, routingKey, buffer); + if (done) { + iter->second->release(); + contextMap.erase(iter); + } + } + } +} + diff --git a/qpid/cpp/src/qmf/engine/SequenceManager.h b/qpid/cpp/src/qmf/engine/SequenceManager.h new file mode 100644 index 0000000000..9e47e38610 --- /dev/null +++ b/qpid/cpp/src/qmf/engine/SequenceManager.h @@ -0,0 +1,68 @@ +#ifndef _QmfEngineSequenceManager_ +#define _QmfEngineSequenceManager_ + +/* + * 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/Mutex.h" +#include <boost/shared_ptr.hpp> +#include <map> + +namespace qpid { + namespace framing { + class Buffer; + } +} + +namespace qmf { +namespace engine { + + class SequenceContext { + public: + typedef boost::shared_ptr<SequenceContext> Ptr; + SequenceContext() {} + virtual ~SequenceContext() {} + + virtual void reserve() = 0; + virtual bool handleMessage(uint8_t opcode, uint32_t sequence, const std::string& routingKey, qpid::framing::Buffer& buffer) = 0; + virtual void release() = 0; + }; + + class SequenceManager { + public: + SequenceManager(); + + void setUnsolicitedContext(SequenceContext::Ptr ctx); + uint32_t reserve(SequenceContext::Ptr ctx = SequenceContext::Ptr()); + void release(uint32_t sequence); + void releaseAll(); + void dispatch(uint8_t opcode, uint32_t sequence, const std::string& routingKey, qpid::framing::Buffer& buffer); + + private: + mutable qpid::sys::Mutex lock; + uint32_t nextSequence; + SequenceContext::Ptr unsolicitedContext; + std::map<uint32_t, SequenceContext::Ptr> contextMap; + }; + +} +} + +#endif + diff --git a/qpid/cpp/src/qmf/engine/ValueImpl.cpp b/qpid/cpp/src/qmf/engine/ValueImpl.cpp new file mode 100644 index 0000000000..f9ebbf5028 --- /dev/null +++ b/qpid/cpp/src/qmf/engine/ValueImpl.cpp @@ -0,0 +1,571 @@ +/* + * 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/engine/ValueImpl.h" +#include <qpid/framing/FieldValue.h> +#include <qpid/framing/FieldTable.h> +#include <qpid/framing/List.h> +#include <qpid/log/Statement.h> + +using namespace std; +using namespace qmf::engine; +//using qpid::framing::Buffer; +//using qpid::framing::FieldTable; +//using qpid::framing::FieldValue; +using namespace qpid::framing; + +ValueImpl::ValueImpl(Typecode t, Buffer& buf) : typecode(t) +{ + uint64_t first; + uint64_t second; + FieldTable ft; + List fl; + + switch (typecode) { + case TYPE_UINT8 : value.u32 = (uint32_t) buf.getOctet(); break; + case TYPE_UINT16 : value.u32 = (uint32_t) buf.getShort(); break; + case TYPE_UINT32 : value.u32 = (uint32_t) buf.getLong(); break; + case TYPE_UINT64 : value.u64 = buf.getLongLong(); break; + case TYPE_SSTR : buf.getShortString(stringVal); break; + case TYPE_LSTR : buf.getMediumString(stringVal); break; + case TYPE_ABSTIME : value.s64 = buf.getLongLong(); break; + case TYPE_DELTATIME : value.u64 = buf.getLongLong(); break; + case TYPE_BOOL : value.boolVal = (buf.getOctet() != 0); break; + case TYPE_FLOAT : value.floatVal = buf.getFloat(); break; + case TYPE_DOUBLE : value.doubleVal = buf.getDouble(); break; + case TYPE_INT8 : value.s32 = (int32_t) ((int8_t) buf.getOctet()); break; + case TYPE_INT16 : value.s32 = (int32_t) ((int16_t) buf.getShort()); break; + case TYPE_INT32 : value.s32 = (int32_t) buf.getLong(); break; + case TYPE_INT64 : value.s64 = buf.getLongLong(); break; + case TYPE_UUID : buf.getBin128(value.uuidVal); break; + case TYPE_REF: + first = buf.getLongLong(); + second = buf.getLongLong(); + refVal.impl->setValue(first, second); + break; + + case TYPE_MAP: + ft.decode(buf); + initMap(ft); + break; + + case TYPE_LIST: + fl.decode(buf); + initList(fl); + break; + + case TYPE_ARRAY: + case TYPE_OBJECT: + default: + break; + } +} + +ValueImpl::ValueImpl(Typecode t, Typecode at) : typecode(t), valid(false), arrayTypecode(at) +{ +} + +ValueImpl::ValueImpl(Typecode t) : typecode(t) +{ + ::memset(&value, 0, sizeof(value)); +} + +Value* ValueImpl::factory(Typecode t, Buffer& b) +{ + ValueImpl* impl(new ValueImpl(t, b)); + return new Value(impl); +} + +Value* ValueImpl::factory(Typecode t) +{ + ValueImpl* impl(new ValueImpl(t)); + return new Value(impl); +} + +ValueImpl::~ValueImpl() +{ +} + +void ValueImpl::initMap(const FieldTable& ft) +{ + for (FieldTable::ValueMap::const_iterator iter = ft.begin(); + iter != ft.end(); iter++) { + const string& name(iter->first); + const FieldValue& fvalue(*iter->second); + uint8_t amqType = fvalue.getType(); + + if (amqType == 0x32) { + Value* subval(new Value(TYPE_UINT64)); + subval->setUint64(fvalue.get<int64_t>()); + insert(name.c_str(), subval); + } else if ((amqType & 0xCF) == 0x02) { + Value* subval(new Value(TYPE_UINT32)); + switch (amqType) { + case 0x02 : subval->setUint(fvalue.get<int>()); break; + case 0x12 : subval->setUint(fvalue.get<int>()); break; + case 0x22 : subval->setUint(fvalue.get<int>()); break; + } + insert(name.c_str(), subval); + } else if (amqType == 0x31) { // int64 + Value* subval(new Value(TYPE_INT64)); + subval->setInt64(fvalue.get<int64_t>()); + insert(name.c_str(), subval); + } else if ((amqType & 0xCF) == 0x01) { // 0x01:int8, 0x11:int16, 0x21:int21 + Value* subval(new Value(TYPE_INT32)); + subval->setInt((int32_t)fvalue.get<int>()); + insert(name.c_str(), subval); + } else if (amqType == 0x85 || amqType == 0x95) { + Value* subval(new Value(TYPE_LSTR)); + subval->setString(fvalue.get<string>().c_str()); + insert(name.c_str(), subval); + } else if (amqType == 0x23 || amqType == 0x33) { + Value* subval(new Value(TYPE_DOUBLE)); + subval->setDouble(fvalue.get<double>()); + insert(name.c_str(), subval); + } else if (amqType == 0xa8) { + FieldTable subFt; + bool valid = qpid::framing::getEncodedValue<FieldTable>(iter->second, subFt); + if (valid) { + Value* subval(new Value(TYPE_MAP)); + subval->impl->initMap(subFt); + insert(name.c_str(), subval); + } + } else if (amqType == 0xa9) { + List subList; + bool valid = qpid::framing::getEncodedValue<List>(iter->second, subList); + if (valid) { + Value* subval(new Value(TYPE_LIST)); + subval->impl->initList(subList); + insert(name.c_str(), subval); + } + } else if (amqType == 0x08) { + Value* subval(new Value(TYPE_BOOL)); + subval->setBool(fvalue.get<int>() ? true : false); + insert(name.c_str(), subval); + } else { + QPID_LOG(error, "Unable to decode unsupported AMQP typecode=" << amqType << " map index=" << name); + } + } +} + +void ValueImpl::mapToFieldTable(FieldTable& ft) const +{ + FieldTable subFt; + + for (map<string, Value>::const_iterator iter = mapVal.begin(); + iter != mapVal.end(); iter++) { + const string& name(iter->first); + const Value& subval(iter->second); + + switch (subval.getType()) { + case TYPE_UINT8: + case TYPE_UINT16: + case TYPE_UINT32: + ft.setUInt64(name, (uint64_t) subval.asUint()); + break; + case TYPE_UINT64: + case TYPE_DELTATIME: + ft.setUInt64(name, subval.asUint64()); + break; + case TYPE_SSTR: + case TYPE_LSTR: + ft.setString(name, subval.asString()); + break; + case TYPE_INT64: + case TYPE_ABSTIME: + ft.setInt64(name, subval.asInt64()); + break; + case TYPE_BOOL: + ft.set(name, FieldTable::ValuePtr(new BoolValue(subval.asBool()))); + break; + case TYPE_FLOAT: + ft.setFloat(name, subval.asFloat()); + break; + case TYPE_DOUBLE: + ft.setDouble(name, subval.asDouble()); + break; + case TYPE_INT8: + case TYPE_INT16: + case TYPE_INT32: + ft.setInt(name, subval.asInt()); + break; + case TYPE_MAP: + subFt.clear(); + subval.impl->mapToFieldTable(subFt); + ft.setTable(name, subFt); + break; + case TYPE_LIST: + { + List subList; + subval.impl->listToFramingList(subList); + ft.set(name, + ::qpid::framing::FieldTable::ValuePtr( + new ListValue( + subList))); + } break; + case TYPE_ARRAY: + case TYPE_OBJECT: + case TYPE_UUID: + case TYPE_REF: + default: + break; + } + } + } + + +void ValueImpl::initList(const List& fl) +{ + for (List::const_iterator iter = fl.begin(); + iter != fl.end(); iter++) { + const FieldValue& fvalue(*iter->get()); + uint8_t amqType = fvalue.getType(); + + if (amqType == 0x32) { + Value* subval(new Value(TYPE_UINT64)); + subval->setUint64(fvalue.get<int64_t>()); + appendToList(subval); + } else if ((amqType & 0xCF) == 0x02) { + Value* subval(new Value(TYPE_UINT32)); + switch (amqType) { + case 0x02 : subval->setUint(fvalue.get<int>()); break; // uint8 + case 0x12 : subval->setUint(fvalue.get<int>()); break; // uint16 + case 0x22 : subval->setUint(fvalue.get<int>()); break; // uint32 + } + appendToList(subval); + } else if (amqType == 0x31) { // int64 + Value* subval(new Value(TYPE_INT64)); + subval->setInt64(fvalue.get<int64_t>()); + appendToList(subval); + } else if ((amqType & 0xCF) == 0x01) { // 0x01:int8, 0x11:int16, 0x21:int32 + Value* subval(new Value(TYPE_INT32)); + subval->setInt((int32_t)fvalue.get<int>()); + appendToList(subval); + } else if (amqType == 0x85 || amqType == 0x95) { + Value* subval(new Value(TYPE_LSTR)); + subval->setString(fvalue.get<string>().c_str()); + appendToList(subval); + } else if (amqType == 0x23 || amqType == 0x33) { + Value* subval(new Value(TYPE_DOUBLE)); + subval->setDouble(fvalue.get<double>()); + appendToList(subval); + } else if (amqType == 0xa8) { + FieldTable subFt; + bool valid = qpid::framing::getEncodedValue<FieldTable>(*iter, subFt); + if (valid) { + Value* subval(new Value(TYPE_MAP)); + subval->impl->initMap(subFt); + appendToList(subval); + } + } else if (amqType == 0xa9) { + List subList; + bool valid = qpid::framing::getEncodedValue<List>(*iter, subList); + if (valid) { + Value *subVal(new Value(TYPE_LIST)); + subVal->impl->initList(subList); + appendToList(subVal); + } + } else if (amqType == 0x08) { + Value* subval(new Value(TYPE_BOOL)); + subval->setBool(fvalue.get<int>() ? true : false); + appendToList(subval); + } else { + QPID_LOG(error, "Unable to decode unsupported AMQP typecode =" << amqType); + } + } +} + +void ValueImpl::listToFramingList(List& fl) const +{ + for (vector<Value>::const_iterator iter = vectorVal.begin(); + iter != vectorVal.end(); iter++) { + const Value& subval(*iter); + + switch (subval.getType()) { + case TYPE_UINT8: + case TYPE_UINT16: + case TYPE_UINT32: + fl.push_back(List::ValuePtr(new Unsigned64Value((uint64_t) subval.asUint()))); + break; + case TYPE_UINT64: + case TYPE_DELTATIME: + fl.push_back(List::ValuePtr(new Unsigned64Value(subval.asUint64()))); + break; + case TYPE_SSTR: + case TYPE_LSTR: + fl.push_back(List::ValuePtr(new Str16Value(subval.asString()))); + break; + case TYPE_INT64: + case TYPE_ABSTIME: + fl.push_back(List::ValuePtr(new Integer64Value(subval.asInt64()))); + break; + case TYPE_BOOL: + fl.push_back(List::ValuePtr(new BoolValue(subval.asBool() ? 1 : 0))); + break; + case TYPE_FLOAT: + fl.push_back(List::ValuePtr(new FloatValue(subval.asFloat()))); + break; + case TYPE_DOUBLE: + fl.push_back(List::ValuePtr(new DoubleValue(subval.asDouble()))); + break; + case TYPE_INT8: + case TYPE_INT16: + case TYPE_INT32: + fl.push_back(List::ValuePtr(new IntegerValue(subval.asInt()))); + break; + case TYPE_MAP: + { + FieldTable subFt; + subval.impl->mapToFieldTable(subFt); + fl.push_back(List::ValuePtr(new FieldTableValue(subFt))); + + } break; + case TYPE_LIST: + { + List subList; + subval.impl->listToFramingList(subList); + fl.push_back(List::ValuePtr(new ListValue(subList))); + } break; + + case TYPE_ARRAY: + case TYPE_OBJECT: + case TYPE_UUID: + case TYPE_REF: + default: + break; + } + } + } + + + +void ValueImpl::encode(Buffer& buf) const +{ + FieldTable ft; + List fl; + + switch (typecode) { + case TYPE_UINT8 : buf.putOctet((uint8_t) value.u32); break; + case TYPE_UINT16 : buf.putShort((uint16_t) value.u32); break; + case TYPE_UINT32 : buf.putLong(value.u32); break; + case TYPE_UINT64 : buf.putLongLong(value.u64); break; + case TYPE_SSTR : buf.putShortString(stringVal); break; + case TYPE_LSTR : buf.putMediumString(stringVal); break; + case TYPE_ABSTIME : buf.putLongLong(value.s64); break; + case TYPE_DELTATIME : buf.putLongLong(value.u64); break; + case TYPE_BOOL : buf.putOctet(value.boolVal ? 1 : 0); break; + case TYPE_FLOAT : buf.putFloat(value.floatVal); break; + case TYPE_DOUBLE : buf.putDouble(value.doubleVal); break; + case TYPE_INT8 : buf.putOctet((uint8_t) value.s32); break; + case TYPE_INT16 : buf.putShort((uint16_t) value.s32); break; + case TYPE_INT32 : buf.putLong(value.s32); break; + case TYPE_INT64 : buf.putLongLong(value.s64); break; + case TYPE_UUID : buf.putBin128(value.uuidVal); break; + case TYPE_REF : refVal.impl->encode(buf); break; + case TYPE_MAP: + mapToFieldTable(ft); + ft.encode(buf); + break; + case TYPE_LIST: + listToFramingList(fl); + fl.encode(buf); + break; + + case TYPE_ARRAY: + case TYPE_OBJECT: + default: + break; + } +} + +uint32_t ValueImpl::encodedSize() const +{ + FieldTable ft; + List fl; + + switch (typecode) { + case TYPE_UINT8 : + case TYPE_BOOL : + case TYPE_INT8 : return 1; + + case TYPE_UINT16 : + case TYPE_INT16 : return 2; + + case TYPE_UINT32 : + case TYPE_INT32 : + case TYPE_FLOAT : return 4; + + case TYPE_UINT64 : + case TYPE_INT64 : + case TYPE_DOUBLE : + case TYPE_ABSTIME : + case TYPE_DELTATIME : return 8; + + case TYPE_UUID : + case TYPE_REF : return 16; + + case TYPE_SSTR : return 1 + stringVal.size(); + case TYPE_LSTR : return 2 + stringVal.size(); + case TYPE_MAP: + mapToFieldTable(ft); + return ft.encodedSize(); + + case TYPE_LIST: + listToFramingList(fl); + return fl.encodedSize(); + + case TYPE_ARRAY: + case TYPE_OBJECT: + default: + break; + } + + return 0; +} + +bool ValueImpl::keyInMap(const char* key) const +{ + return typecode == TYPE_MAP && mapVal.count(key) > 0; +} + +Value* ValueImpl::byKey(const char* key) +{ + if (keyInMap(key)) { + map<string, Value>::iterator iter = mapVal.find(key); + if (iter != mapVal.end()) + return &iter->second; + } + return 0; +} + +const Value* ValueImpl::byKey(const char* key) const +{ + if (keyInMap(key)) { + map<string, Value>::const_iterator iter = mapVal.find(key); + if (iter != mapVal.end()) + return &iter->second; + } + return 0; +} + +void ValueImpl::deleteKey(const char* key) +{ + mapVal.erase(key); +} + +void ValueImpl::insert(const char* key, Value* val) +{ + pair<string, Value> entry(key, *val); + mapVal.insert(entry); +} + +const char* ValueImpl::key(uint32_t idx) const +{ + map<string, Value>::const_iterator iter = mapVal.begin(); + for (uint32_t i = 0; i < idx; i++) { + if (iter == mapVal.end()) + break; + iter++; + } + + if (iter == mapVal.end()) + return 0; + else + return iter->first.c_str(); +} + +Value* ValueImpl::arrayItem(uint32_t) +{ + return 0; +} + +void ValueImpl::appendToArray(Value*) +{ +} + +void ValueImpl::deleteArrayItem(uint32_t) +{ +} + + +//================================================================== +// Wrappers +//================================================================== + +Value::Value(const Value& from) : impl(new ValueImpl(*(from.impl))) {} +Value::Value(Typecode t, Typecode at) : impl(new ValueImpl(t, at)) {} +Value::Value(ValueImpl* i) : impl(i) {} +Value::~Value() { delete impl;} + +Typecode Value::getType() const { return impl->getType(); } +bool Value::isNull() const { return impl->isNull(); } +void Value::setNull() { impl->setNull(); } +bool Value::isObjectId() const { return impl->isObjectId(); } +const ObjectId& Value::asObjectId() const { return impl->asObjectId(); } +void Value::setObjectId(const ObjectId& oid) { impl->setObjectId(oid); } +bool Value::isUint() const { return impl->isUint(); } +uint32_t Value::asUint() const { return impl->asUint(); } +void Value::setUint(uint32_t val) { impl->setUint(val); } +bool Value::isInt() const { return impl->isInt(); } +int32_t Value::asInt() const { return impl->asInt(); } +void Value::setInt(int32_t val) { impl->setInt(val); } +bool Value::isUint64() const { return impl->isUint64(); } +uint64_t Value::asUint64() const { return impl->asUint64(); } +void Value::setUint64(uint64_t val) { impl->setUint64(val); } +bool Value::isInt64() const { return impl->isInt64(); } +int64_t Value::asInt64() const { return impl->asInt64(); } +void Value::setInt64(int64_t val) { impl->setInt64(val); } +bool Value::isString() const { return impl->isString(); } +const char* Value::asString() const { return impl->asString(); } +void Value::setString(const char* val) { impl->setString(val); } +bool Value::isBool() const { return impl->isBool(); } +bool Value::asBool() const { return impl->asBool(); } +void Value::setBool(bool val) { impl->setBool(val); } +bool Value::isFloat() const { return impl->isFloat(); } +float Value::asFloat() const { return impl->asFloat(); } +void Value::setFloat(float val) { impl->setFloat(val); } +bool Value::isDouble() const { return impl->isDouble(); } +double Value::asDouble() const { return impl->asDouble(); } +void Value::setDouble(double val) { impl->setDouble(val); } +bool Value::isUuid() const { return impl->isUuid(); } +const uint8_t* Value::asUuid() const { return impl->asUuid(); } +void Value::setUuid(const uint8_t* val) { impl->setUuid(val); } +bool Value::isObject() const { return impl->isObject(); } +const Object* Value::asObject() const { return impl->asObject(); } +void Value::setObject(Object* val) { impl->setObject(val); } +bool Value::isMap() const { return impl->isMap(); } +bool Value::keyInMap(const char* key) const { return impl->keyInMap(key); } +Value* Value::byKey(const char* key) { return impl->byKey(key); } +const Value* Value::byKey(const char* key) const { return impl->byKey(key); } +void Value::deleteKey(const char* key) { impl->deleteKey(key); } +void Value::insert(const char* key, Value* val) { impl->insert(key, val); } +uint32_t Value::keyCount() const { return impl->keyCount(); } +const char* Value::key(uint32_t idx) const { return impl->key(idx); } +bool Value::isList() const { return impl->isList(); } +uint32_t Value::listItemCount() const { return impl->listItemCount(); } +Value* Value::listItem(uint32_t idx) { return impl->listItem(idx); } +void Value::appendToList(Value* val) { impl->appendToList(val); } +void Value::deleteListItem(uint32_t idx) { impl->deleteListItem(idx); } +bool Value::isArray() const { return impl->isArray(); } +Typecode Value::arrayType() const { return impl->arrayType(); } +uint32_t Value::arrayItemCount() const { return impl->arrayItemCount(); } +Value* Value::arrayItem(uint32_t idx) { return impl->arrayItem(idx); } +void Value::appendToArray(Value* val) { impl->appendToArray(val); } +void Value::deleteArrayItem(uint32_t idx) { impl->deleteArrayItem(idx); } + diff --git a/qpid/cpp/src/qmf/engine/ValueImpl.h b/qpid/cpp/src/qmf/engine/ValueImpl.h new file mode 100644 index 0000000000..8de8c5329f --- /dev/null +++ b/qpid/cpp/src/qmf/engine/ValueImpl.h @@ -0,0 +1,166 @@ +#ifndef _QmfEngineValueImpl_ +#define _QmfEngineValueImpl_ + +/* + * 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/engine/Value.h> +#include <qmf/engine/ObjectIdImpl.h> +#include <qmf/engine/Object.h> +#include <qpid/framing/Buffer.h> +#include <string> +#include <string.h> +#include <map> +#include <vector> +#include <boost/shared_ptr.hpp> + +namespace qpid { +namespace framing { + class FieldTable; + class List; +} +} + +namespace qmf { +namespace engine { + + // TODO: set valid flag on all value settors + // TODO: add a modified flag and accessors + + struct ValueImpl { + const Typecode typecode; + bool valid; + + ObjectId refVal; + std::string stringVal; + std::auto_ptr<Object> objectVal; + std::map<std::string, Value> mapVal; + std::vector<Value> vectorVal; + Typecode arrayTypecode; + + union { + uint32_t u32; + uint64_t u64; + int32_t s32; + int64_t s64; + bool boolVal; + float floatVal; + double doubleVal; + uint8_t uuidVal[16]; + } value; + + ValueImpl(const ValueImpl& from) : + typecode(from.typecode), valid(from.valid), refVal(from.refVal), stringVal(from.stringVal), + objectVal(from.objectVal.get() ? new Object(*(from.objectVal)) : 0), + mapVal(from.mapVal), vectorVal(from.vectorVal), arrayTypecode(from.arrayTypecode), + value(from.value) {} + + ValueImpl(Typecode t, Typecode at); + ValueImpl(Typecode t, qpid::framing::Buffer& b); + ValueImpl(Typecode t); + static Value* factory(Typecode t, qpid::framing::Buffer& b); + static Value* factory(Typecode t); + ~ValueImpl(); + + void encode(qpid::framing::Buffer& b) const; + uint32_t encodedSize() const; + + Typecode getType() const { return typecode; } + bool isNull() const { return !valid; } + void setNull() { valid = false; } + + bool isObjectId() const { return typecode == TYPE_REF; } + const ObjectId& asObjectId() const { return refVal; } + void setObjectId(const ObjectId& o) { refVal = o; } // TODO + + bool isUint() const { return typecode >= TYPE_UINT8 && typecode <= TYPE_UINT32; } + uint32_t asUint() const { return value.u32; } + void setUint(uint32_t val) { value.u32 = val; } + + bool isInt() const { return typecode >= TYPE_INT8 && typecode <= TYPE_INT32; } + int32_t asInt() const { return value.s32; } + void setInt(int32_t val) { value.s32 = val; } + + bool isUint64() const { return typecode == TYPE_UINT64 || typecode == TYPE_DELTATIME; } + uint64_t asUint64() const { return value.u64; } + void setUint64(uint64_t val) { value.u64 = val; } + + bool isInt64() const { return typecode == TYPE_INT64 || typecode == TYPE_ABSTIME; } + int64_t asInt64() const { return value.s64; } + void setInt64(int64_t val) { value.s64 = val; } + + bool isString() const { return typecode == TYPE_SSTR || typecode == TYPE_LSTR; } + const char* asString() const { return stringVal.c_str(); } + void setString(const char* val) { stringVal = val; } + + bool isBool() const { return typecode == TYPE_BOOL; } + bool asBool() const { return value.boolVal; } + void setBool(bool val) { value.boolVal = val; } + + bool isFloat() const { return typecode == TYPE_FLOAT; } + float asFloat() const { return value.floatVal; } + void setFloat(float val) { value.floatVal = val; } + + bool isDouble() const { return typecode == TYPE_DOUBLE; } + double asDouble() const { return value.doubleVal; } + void setDouble(double val) { value.doubleVal = val; } + + bool isUuid() const { return typecode == TYPE_UUID; } + const uint8_t* asUuid() const { return value.uuidVal; } + void setUuid(const uint8_t* val) { ::memcpy(value.uuidVal, val, 16); } + + bool isObject() const { return typecode == TYPE_OBJECT; } + Object* asObject() const { return objectVal.get(); } + void setObject(Object* val) { objectVal.reset(val); } + + bool isMap() const { return typecode == TYPE_MAP; } + bool keyInMap(const char* key) const; + Value* byKey(const char* key); + const Value* byKey(const char* key) const; + void deleteKey(const char* key); + void insert(const char* key, Value* val); + uint32_t keyCount() const { return mapVal.size(); } + const char* key(uint32_t idx) const; + + bool isList() const { return typecode == TYPE_LIST; } + uint32_t listItemCount() const { return vectorVal.size(); } + Value* listItem(uint32_t idx) { return idx < listItemCount() ? &vectorVal[idx] : 0; } + const Value* listItem(uint32_t idx) const { return idx < listItemCount() ? &vectorVal[idx] : 0; } + void appendToList(Value* val) { vectorVal.push_back(*val); } + void deleteListItem(uint32_t idx) { if (idx < listItemCount()) vectorVal.erase(vectorVal.begin()+idx); } + + bool isArray() const { return typecode == TYPE_ARRAY; } + Typecode arrayType() const { return arrayTypecode; } + uint32_t arrayItemCount() const { return 0; } + Value* arrayItem(uint32_t idx); + void appendToArray(Value* val); + void deleteArrayItem(uint32_t idx); + + private: + void mapToFieldTable(qpid::framing::FieldTable& ft) const; + void initMap(const qpid::framing::FieldTable& ft); + + void listToFramingList(qpid::framing::List& fl) const; + void initList(const qpid::framing::List& fl); + }; +} +} + +#endif + diff --git a/qpid/cpp/src/qmf/exceptions.cpp b/qpid/cpp/src/qmf/exceptions.cpp new file mode 100644 index 0000000000..be212f62f7 --- /dev/null +++ b/qpid/cpp/src/qmf/exceptions.cpp @@ -0,0 +1,37 @@ +/* + * + * 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/exceptions.h" + +namespace qmf { + + QmfException::QmfException(const std::string& msg) : qpid::types::Exception(msg) {} + QmfException::~QmfException() throw() {} + + KeyNotFound::KeyNotFound(const std::string& msg) : QmfException("Key Not Found: " + msg) {} + KeyNotFound::~KeyNotFound() throw() {} + + IndexOutOfRange::IndexOutOfRange() : QmfException("Index out-of-range") {} + IndexOutOfRange::~IndexOutOfRange() throw() {} + + OperationTimedOut::OperationTimedOut() : QmfException("Timeout Expired") {} + OperationTimedOut::~OperationTimedOut() throw() {} +} + diff --git a/qpid/cpp/src/qmfc.mk b/qpid/cpp/src/qmfc.mk new file mode 100644 index 0000000000..e445a538a1 --- /dev/null +++ b/qpid/cpp/src/qmfc.mk @@ -0,0 +1,57 @@ +# +# 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. +# + +# +# qmf console library makefile fragment, to be included in Makefile.am +# +lib_LTLIBRARIES += libqmfconsole.la + +# Public header files. +nobase_include_HEADERS += \ + ../include/qpid/console/Agent.h \ + ../include/qpid/console/Broker.h \ + ../include/qpid/console/ClassKey.h \ + ../include/qpid/console/ConsoleImportExport.h \ + ../include/qpid/console/ConsoleListener.h \ + ../include/qpid/console/Event.h \ + ../include/qpid/console/Object.h \ + ../include/qpid/console/ObjectId.h \ + ../include/qpid/console/Package.h \ + ../include/qpid/console/Schema.h \ + ../include/qpid/console/SequenceManager.h \ + ../include/qpid/console/SessionManager.h \ + ../include/qpid/console/Value.h + +libqmfconsole_la_SOURCES = \ + qpid/console/Agent.cpp \ + qpid/console/Broker.cpp \ + qpid/console/ClassKey.cpp \ + qpid/console/Event.cpp \ + qpid/console/Object.cpp \ + qpid/console/ObjectId.cpp \ + qpid/console/Package.cpp \ + qpid/console/Schema.cpp \ + qpid/console/SequenceManager.cpp \ + qpid/console/SessionManager.cpp \ + qpid/console/Value.cpp + +libqmfconsole_la_LIBADD = libqpidclient.la + +QMFCONSOLE_VERSION_INFO = 2:0:0 +libqmfconsole_la_LDFLAGS = -version-info $(QMFCONSOLE_VERSION_INFO) diff --git a/qpid/cpp/src/qpid.pc.in b/qpid/cpp/src/qpid.pc.in new file mode 100644 index 0000000000..87d368f20c --- /dev/null +++ b/qpid/cpp/src/qpid.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: qpid +Version: @VERSION@ +Description: Qpid C++ client library +Requires: +Libs: -L${libdir} -lqpidmessaging @LIBS@ +Cflags: -I${includedir} diff --git a/qpid/cpp/src/qpid/Address.cpp b/qpid/cpp/src/qpid/Address.cpp new file mode 100644 index 0000000000..e2b2dfbcdf --- /dev/null +++ b/qpid/cpp/src/qpid/Address.cpp @@ -0,0 +1,38 @@ +/* + * + * 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. + * + */ + +#include "qpid/Address.h" +#include "qpid/client/ConnectionSettings.h" + +#include <ostream> + +using namespace std; + +namespace qpid { + +const string Address::TCP("tcp"); + +ostream& operator<<(ostream& os, const Address& a) { + return os << a.protocol << ":" << a.host << ":" << a.port; +} + +bool operator==(const Address& x, const Address& y) { + return y.protocol==x.protocol && y.host==x.host && y.port == x.port; +} + +} // namespace qpid diff --git a/qpid/cpp/src/qpid/BufferRef.h b/qpid/cpp/src/qpid/BufferRef.h new file mode 100644 index 0000000000..bfe1f9ebaa --- /dev/null +++ b/qpid/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/qpid/cpp/src/qpid/DataDir.cpp b/qpid/cpp/src/qpid/DataDir.cpp new file mode 100644 index 0000000000..ad732052ab --- /dev/null +++ b/qpid/cpp/src/qpid/DataDir.cpp @@ -0,0 +1,50 @@ +/* + * 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/Exception.h" +#include "qpid/DataDir.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/FileSysDir.h" +#include "qpid/sys/LockFile.h" + +namespace qpid { + +DataDir::DataDir (std::string path) : + enabled (!path.empty ()), + dirPath (path) +{ + if (!enabled) + { + QPID_LOG (info, "No data directory - Disabling persistent configuration"); + return; + } + + sys::FileSysDir dir(dirPath); + if (!dir.exists()) + dir.mkdir(); + std::string lockFileName(path); + lockFileName += "/lock"; + lockFile = std::auto_ptr<sys::LockFile>(new sys::LockFile(lockFileName, true)); +} + +DataDir::~DataDir () {} + +} // namespace qpid + diff --git a/qpid/cpp/src/qpid/DataDir.h b/qpid/cpp/src/qpid/DataDir.h new file mode 100644 index 0000000000..828299f3ba --- /dev/null +++ b/qpid/cpp/src/qpid/DataDir.h @@ -0,0 +1,54 @@ +#ifndef QPID_DATADIR_H +#define QPID_DATADIR_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 <string> +#include <memory> +#include "qpid/CommonImportExport.h" + +namespace qpid { + + namespace sys { + class LockFile; + } + +/** + * DataDir class. + */ +class DataDir +{ + const bool enabled; + const std::string dirPath; + std::auto_ptr<qpid::sys::LockFile> lockFile; + + public: + + QPID_COMMON_EXTERN DataDir (std::string path); + QPID_COMMON_EXTERN ~DataDir (); + + bool isEnabled() { return enabled; } + const std::string& getPath() { return dirPath; } +}; + +} // namespace qpid + +#endif /*!QPID_DATADIR_H*/ diff --git a/qpid/cpp/src/qpid/DisableExceptionLogging.h b/qpid/cpp/src/qpid/DisableExceptionLogging.h new file mode 100644 index 0000000000..04a9240513 --- /dev/null +++ b/qpid/cpp/src/qpid/DisableExceptionLogging.h @@ -0,0 +1,39 @@ +#ifndef QPID_DISABLEEXCEPTIONLOGGING_H +#define QPID_DISABLEEXCEPTIONLOGGING_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/CommonImportExport.h" + +namespace qpid { + +/** + * Temporarily disable logging in qpid::Exception constructor. + * Used by log::Logger to avoid logging exceptions during Logger construction. + */ +struct DisableExceptionLogging +{ + QPID_COMMON_EXTERN DisableExceptionLogging(); + QPID_COMMON_EXTERN ~DisableExceptionLogging(); +}; +} // namespace qpid + +#endif /*!QPID_DISABLEEXCEPTIONLOGGING_H*/ diff --git a/qpid/cpp/src/qpid/Exception.cpp b/qpid/cpp/src/qpid/Exception.cpp new file mode 100644 index 0000000000..a6696f06e1 --- /dev/null +++ b/qpid/cpp/src/qpid/Exception.cpp @@ -0,0 +1,67 @@ +/* + * + * 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/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); +} + +Exception::~Exception() throw() {} + +std::string Exception::getPrefix() const { return ""; } + +std::string Exception::getMessage() const { return message; } + +const char* Exception::what() const throw() { + // Construct the what string the first time it is needed. + if (whatStr.empty()) { + whatStr = getPrefix(); + if (!whatStr.empty()) whatStr += ": "; + whatStr += message; + } + return whatStr.c_str(); +} + +ClosedException::ClosedException(const std::string& msg) + : Exception(msg) {} + +std::string ClosedException::getPrefix() const { return "Closed"; } + +} // namespace qpid diff --git a/qpid/cpp/src/qpid/Modules.cpp b/qpid/cpp/src/qpid/Modules.cpp new file mode 100644 index 0000000000..727e05d212 --- /dev/null +++ b/qpid/cpp/src/qpid/Modules.cpp @@ -0,0 +1,97 @@ +/* + * + * 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 "config.h" +#include "qpid/Modules.h" +#include "qpid/Exception.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Shlib.h" + +#include <boost/filesystem/operations.hpp> +#include <boost/filesystem/path.hpp> + +namespace fs=boost::filesystem; + +namespace { + +// CMake sets QPID_MODULE_SUFFIX; Autoconf doesn't, so assume Linux .so +#ifndef QPID_MODULE_SUFFIX +#define QPID_MODULE_SUFFIX ".so" +#endif + +inline std::string& suffix() { + static std::string s(QPID_MODULE_SUFFIX); + return s; +} + +bool isShlibName(const std::string& name) { + return name.find (suffix()) == name.length() - suffix().length(); +} + +} + +namespace qpid { + +ModuleOptions::ModuleOptions(const std::string& defaultModuleDir) + : qpid::Options("Module options"), loadDir(defaultModuleDir), noLoad(false) +{ + addOptions() + ("module-dir", optValue(loadDir, "DIR"), "Load all shareable modules in this directory") + ("load-module", optValue(load, "FILE"), "Specifies additional module(s) to be loaded") + ("no-module-dir", optValue(noLoad), "Don't load modules from module directory"); +} + +void tryShlib(const char* libname_, bool noThrow) { + std::string libname(libname_); + if (!isShlibName(libname)) libname += suffix(); + try { + sys::Shlib shlib(libname); + } + catch (const std::exception& /*e*/) { + if (!noThrow) + throw; + } +} + +void loadModuleDir (std::string dirname, bool isDefault) +{ + fs::path dirPath (dirname, fs::native); + + if (!fs::exists (dirPath)) + { + if (isDefault) + return; + throw Exception ("Directory not found: " + dirname); + } + if (!fs::is_directory(dirPath)) + { + throw Exception ("Invalid value for module-dir: " + dirname + " is not a directory"); + } + + fs::directory_iterator endItr; + for (fs::directory_iterator itr (dirPath); itr != endItr; ++itr) + { + if (!fs::is_directory(*itr) && isShlibName(itr->string())) + tryShlib (itr->string().data(), true); + } +} + +} // namespace qpid diff --git a/qpid/cpp/src/qpid/Modules.h b/qpid/cpp/src/qpid/Modules.h new file mode 100644 index 0000000000..159dd156c1 --- /dev/null +++ b/qpid/cpp/src/qpid/Modules.h @@ -0,0 +1,44 @@ +#ifndef QPID_MODULES_H +#define QPID_MODULES_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/Options.h" +#include <string> +#include <vector> +#include "qpid/CommonImportExport.h" + +namespace qpid { + +struct ModuleOptions : public qpid::Options { + std::string loadDir; + std::vector<std::string> load; + bool noLoad; + QPID_COMMON_EXTERN ModuleOptions(const std::string& defaultModuleDir); +}; + +QPID_COMMON_EXTERN void tryShlib(const char* libname, bool noThrow); +QPID_COMMON_EXTERN void loadModuleDir (std::string dirname, bool isDefault); + +} // namespace qpid + +#endif /*!QPID_MODULES_H*/ diff --git a/qpid/cpp/src/qpid/Options.cpp b/qpid/cpp/src/qpid/Options.cpp new file mode 100644 index 0000000000..4b13e349f5 --- /dev/null +++ b/qpid/cpp/src/qpid/Options.cpp @@ -0,0 +1,200 @@ +/* + * + * 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. + * + */ + +#include "qpid/Options.h" +#include "qpid/Exception.h" + +#include <boost/bind.hpp> + +#include <fstream> +#include <algorithm> +#include <iostream> + +namespace qpid { + +using namespace std; + + +namespace { + +struct EnvOptMapper { + static bool matchChar(char env, char opt) { + return (env==toupper(opt)) || (strchr("-.", opt) && env=='_'); + } + + static bool matchStr(const string& env, boost::shared_ptr<po::option_description> desc) { + return desc->long_name().size() == env.size() && + std::equal(env.begin(), env.end(), desc->long_name().begin(), &matchChar); + } + + static bool matchCase(const string& env, boost::shared_ptr<po::option_description> desc) { + return env == desc->long_name(); + } + + EnvOptMapper(const Options& o) : opts(o) {} + + string operator()(const string& envVar) { + static const std::string prefix("QPID_"); + if (envVar.substr(0, prefix.size()) == prefix) { + string env = envVar.substr(prefix.size()); + 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(); + } + return string(); + } + + + bool + isComment ( string const & str ) + { + size_t i = str.find_first_not_of ( " \t" ); + + if ( i == string::npos ) + return true; + + return str[i] == '#'; + } + + + string configFileLine (string& line) { + + if ( isComment ( line ) ) + return string(); + + size_t pos = line.find ('='); + if (pos == string::npos) + return string(); + string key = line.substr (0, pos); +#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(matchCase, key, _1)); + if (i != opts.options().end()) + return string (line) + "\n"; + else + return string(); +#else + // Use 'count' to see if this option exists. Using 'find' will SEGV or hang + // if the option has not been defined yet. + if ( opts.count(key.c_str()) > 0 ) + return string ( line ) + "\n"; + else + return string ( ); +#endif + } + + const Options& opts; +}; + +} +std::string prettyArg(const std::string& name, const std::string& value) { + return value.empty() ? name+" " : name+" ("+value+") "; +} + +Options::Options(const string& name) : + po::options_description(name) +{ +} + + + + + +void Options::parse(int argc, char const* const* argv, const std::string& configFile, bool allowUnknown) +{ + string defaultConfigFile = configFile; // May be changed by env/cmdline + string parsing; + try { + po::variables_map vm; + parsing="command line options"; + if (argc > 0 && argv != 0) { + if (allowUnknown) { + // 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)). + options(*this).allow_unregistered(); + po::parsed_options opts = clp.run(); + po::parsed_options filtopts = clp.run(); + filtopts.options.clear (); + for (std::vector< po::basic_option<char> >::iterator i = opts.options.begin(); + i != opts.options.end(); i++) + if (!i->unregistered) + filtopts.options.push_back (*i); + po::store(filtopts, vm); + + } + else + po::store(po::parse_command_line(argc, const_cast<char**>(argv), *this), vm); + } + parsing="environment variables"; + po::store(po::parse_environment(*this, EnvOptMapper(*this)), vm); + po::notify(vm); // configFile may be updated from arg/env options. + if (!configFile.empty()) { + parsing="configuration file "+configFile; + ifstream conf(configFile.c_str()); + if (conf.good()) { + // Remove this hack when we get a stable version of boost that + // can allow unregistered options in config files. + EnvOptMapper mapper(*this); + stringstream filtered; + + while (!conf.eof()) { + string line; + getline (conf, line); + filtered << mapper.configFileLine (line); + } + + po::store(po::parse_config_file(filtered, *this), vm); + // End of hack + } + else { + // No error if default configfile is missing/unreadable + // but complain for non-default config file. + if (configFile != defaultConfigFile) + throw Exception("cannot read configuration file " + +configFile); + } + } + po::notify(vm); + } + catch (const std::exception& e) { + ostringstream msg; + msg << "Error in " << parsing << ": " << e.what() << endl; +#if (BOOST_VERSION >= 103300) + if (find_nothrow("help", false)) + msg << "Use --help to see valid options" << endl; +#endif + throw Exception(msg.str()); + } +} + +CommonOptions::CommonOptions(const string& name, const string& configfile) + : Options(name), config(configfile) +{ + addOptions() + ("help,h", optValue(help), "Displays the help message") + ("version,v", optValue(version), "Displays version information") + ("config", optValue(config, "FILE"), "Reads configuration from FILE"); +} + + +} // namespace qpid + diff --git a/qpid/cpp/src/qpid/Plugin.cpp b/qpid/cpp/src/qpid/Plugin.cpp new file mode 100644 index 0000000000..196b5c2333 --- /dev/null +++ b/qpid/cpp/src/qpid/Plugin.cpp @@ -0,0 +1,94 @@ +/* + * 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/Plugin.h" +#include "qpid/Options.h" +#include <boost/bind.hpp> +#include <algorithm> + +namespace qpid { + +namespace { + +Plugin::Plugins& thePlugins() { + // This is a single threaded singleton implementation so + // it is important to be sure that the first use of this + // singleton is when the program is still single threaded + static Plugin::Plugins plugins; + return plugins; +} + +void invoke(boost::function<void()> f) { f(); } + +} // namespace + +Plugin::Target::~Target() { finalize(); } + +void Plugin::Target::finalize() { + std::for_each(finalizers.begin(), finalizers.end(), invoke); + finalizers.clear(); +} + +void Plugin::Target::addFinalizer(const boost::function<void()>& f) { + finalizers.push_back(f); +} + +namespace { +bool initBefore(const Plugin* a, const Plugin* b) { + return a->initOrder() < b->initOrder(); +} +} + +Plugin::Plugin() { + // Register myself. + thePlugins().push_back(this); + std::sort(thePlugins().begin(), thePlugins().end(), &initBefore); +} + +Plugin::~Plugin() {} + +Options* Plugin::getOptions() { return 0; } + +const Plugin::Plugins& Plugin::getPlugins() { return thePlugins(); } + +namespace { +template <class F> void each_plugin(const F& f) { + std::for_each(Plugin::getPlugins().begin(), Plugin::getPlugins().end(), f); +} +} + +void Plugin::addOptions(Options& opts) { + for (Plugins::const_iterator i = getPlugins().begin(); i != getPlugins().end(); ++i) { + if ((*i)->getOptions()) + opts.add(*(*i)->getOptions()); + } +} + +int Plugin::initOrder() const { return DEFAULT_INIT_ORDER; } + +void Plugin::earlyInitAll(Target& t) { + each_plugin(boost::bind(&Plugin::earlyInitialize, _1, boost::ref(t))); +} + +void Plugin::initializeAll(Target& t) { + each_plugin(boost::bind(&Plugin::initialize, _1, boost::ref(t))); +} + +} // namespace qpid diff --git a/qpid/cpp/src/qpid/Plugin.h b/qpid/cpp/src/qpid/Plugin.h new file mode 100644 index 0000000000..4e057872b9 --- /dev/null +++ b/qpid/cpp/src/qpid/Plugin.h @@ -0,0 +1,129 @@ +#ifndef QPID_PLUGIN_H +#define QPID_PLUGIN_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 <boost/noncopyable.hpp> +#include <boost/function.hpp> +#include <vector> +#include "qpid/CommonImportExport.h" + +/**@file Generic plug-in framework. */ + +namespace qpid { +struct Options; + +/** + * Plug-in base class. + */ +class Plugin : private boost::noncopyable { + public: + typedef std::vector<Plugin*> Plugins; + /** Default value returned by initOrder() */ + static const int DEFAULT_INIT_ORDER=1000; + + /** + * Base interface for targets that can receive plug-ins. + * Also allows plug-ins to attach a a function to be called + * when the target is 'finalized'. + */ + class Target : private boost::noncopyable + { + public: + /** Calls finalize() if not already called. */ + QPID_COMMON_EXTERN virtual ~Target(); + + /** Run all the finalizers */ + QPID_COMMON_EXTERN void finalize(); + + /** Add a function to run when finalize() is called */ + QPID_COMMON_EXTERN void addFinalizer(const boost::function<void()>&); + + private: + std::vector<boost::function<void()> > finalizers; + }; + + /** + * Constructor registers the plug-in to appear in getPlugins(). + * + * A concrete Plugin is instantiated as a global or static + * member variable in a library so it is registered during + * initialization when the library is loaded. + */ + QPID_COMMON_EXTERN Plugin(); + + QPID_COMMON_EXTERN virtual ~Plugin(); + + /** + * Configuration options for the plugin. + * Then will be updated during option parsing by the host program. + * + * @return An options group or 0 for no options. Default returns 0. + * Plugin retains ownership of return value. + */ + QPID_COMMON_EXTERN virtual Options* getOptions(); + + /** + * Initialize Plugin functionality on a Target, called before + * initializing the target. + * + * Plugins should ignore targets they don't recognize. + * + * Called before the target itself is initialized. + */ + virtual void earlyInitialize(Target&) = 0; + + /** + * Initialize Plugin functionality on a Target. Called after + * initializing the target. + * + * Plugins should ignore targets they don't recognize. + * + * Called after the target is fully initialized. + */ + virtual void initialize(Target&) = 0; + + /** + * Initialization order. If a plugin does not override this, it + * returns DEFAULT_INIT_ORDER. Plugins that need to be initialized + * earlier/later than normal can override initOrder to return + * a lower/higher value than DEFAULT_INIT_ORDER. + */ + QPID_COMMON_EXTERN virtual int initOrder() const; + + /** List of registered Plugin objects. + * Caller must not delete plugin pointers. + */ + QPID_COMMON_EXTERN static const Plugins& getPlugins(); + + /** Call earlyInitialize() on all registered plugins */ + QPID_COMMON_EXTERN static void earlyInitAll(Target&); + + /** Call initialize() on all registered plugins */ + QPID_COMMON_EXTERN static void initializeAll(Target&); + + /** For each registered plugin, add plugin.getOptions() to opts. */ + QPID_COMMON_EXTERN static void addOptions(Options& opts); +}; + +} // namespace qpid + +#endif /*!QPID_PLUGIN_H*/ diff --git a/qpid/cpp/src/qpid/RefCounted.h b/qpid/cpp/src/qpid/RefCounted.h new file mode 100644 index 0000000000..f9e0107103 --- /dev/null +++ b/qpid/cpp/src/qpid/RefCounted.h @@ -0,0 +1,63 @@ +#ifndef QPID_REFCOUNTED_H +#define QPID_REFCOUNTED_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 <boost/utility.hpp> +#include <boost/detail/atomic_count.hpp> + +namespace qpid { + +/** + * Reference-counted base class. + * Note: this class isn't copyable - you must copy the intrusive_ptr that points + * to the class that has mixed this in not the class itself (as that would sidestep + * the reference counting) + */ +class RefCounted : boost::noncopyable { + mutable boost::detail::atomic_count count; + +public: + RefCounted() : count(0) {} + void addRef() const { ++count; } + void release() const { if (--count==0) released(); } + long refCount() { return count; } + +protected: + virtual ~RefCounted() {}; + // Allow subclasses to over-ride behavior when refcount reaches 0. + virtual void released() const { delete this; } +}; + + +} // namespace qpid + +// intrusive_ptr support. +namespace boost { +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(); } +} + + +#endif /*!QPID_REFCOUNTED_H*/ diff --git a/qpid/cpp/src/qpid/RefCountedBuffer.cpp b/qpid/cpp/src/qpid/RefCountedBuffer.cpp new file mode 100644 index 0000000000..40d620f7ad --- /dev/null +++ b/qpid/cpp/src/qpid/RefCountedBuffer.cpp @@ -0,0 +1,43 @@ +/* + * + * 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/RefCountedBuffer.h" +#include <new> + +namespace qpid { + +void RefCountedBuffer::released() const { + this->~RefCountedBuffer(); + ::delete[] reinterpret_cast<const char*>(this); +} + +BufferRef RefCountedBuffer::create(size_t n) { + char* store=::new char[n+sizeof(RefCountedBuffer)]; + new(store) RefCountedBuffer; + char* start = store+sizeof(RefCountedBuffer); + return BufferRef( + boost::intrusive_ptr<RefCounted>(reinterpret_cast<RefCountedBuffer*>(store)), + start, start+n); +} + +} // namespace qpid + + diff --git a/qpid/cpp/src/qpid/RefCountedBuffer.h b/qpid/cpp/src/qpid/RefCountedBuffer.h new file mode 100644 index 0000000000..f0ea86130b --- /dev/null +++ b/qpid/cpp/src/qpid/RefCountedBuffer.h @@ -0,0 +1,44 @@ +#ifndef QPID_REFCOUNTEDBUFFER_H +#define QPID_REFCOUNTEDBUFFER_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 <qpid/BufferRef.h> + +namespace qpid { + +/** + * Reference-counted byte buffer. No alignment guarantees. + */ +class RefCountedBuffer : public RefCounted { + public: + /** Create a reference counted buffer of size n */ + static BufferRef create(size_t n); + + protected: + void released() const; +}; + +} // namespace qpid + +#endif /*!QPID_REFCOUNTEDBUFFER_H*/ diff --git a/qpid/cpp/src/qpid/Sasl.h b/qpid/cpp/src/qpid/Sasl.h new file mode 100644 index 0000000000..9a9d61b037 --- /dev/null +++ b/qpid/cpp/src/qpid/Sasl.h @@ -0,0 +1,60 @@ +#ifndef QPID_SASL_H +#define QPID_SASL_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 <memory> +#include <string> +#include "qpid/sys/IntegerTypes.h" + +namespace qpid { + +namespace sys { +class SecurityLayer; +struct SecuritySettings; +} + +/** + * Interface to SASL support. This class is implemented by platform-specific + * SASL providers. + */ +class Sasl +{ + public: + /** + * Start SASL negotiation with the broker. + * + * @param mechanisms Comma-separated list of the SASL mechanism the + * 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 std::string step(const std::string& challenge) = 0; + virtual std::string getMechanism() = 0; + virtual std::string getUserId() = 0; + virtual std::auto_ptr<qpid::sys::SecurityLayer> getSecurityLayer(uint16_t maxFrameSize) = 0; + virtual ~Sasl() {} +}; +} // namespace qpid + +#endif /*!QPID_SASL_H*/ diff --git a/qpid/cpp/src/qpid/SaslFactory.cpp b/qpid/cpp/src/qpid/SaslFactory.cpp new file mode 100644 index 0000000000..f117404028 --- /dev/null +++ b/qpid/cpp/src/qpid/SaslFactory.cpp @@ -0,0 +1,429 @@ +/* + * + * 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//SaslFactory.h" +#include <map> +#include <string.h> + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifndef HAVE_SASL + +namespace qpid { + +//Null implementation + +SaslFactory::SaslFactory() {} + +SaslFactory::~SaslFactory() {} + +SaslFactory& SaslFactory::getInstance() +{ + qpid::sys::Mutex::ScopedLock l(lock); + if (!instance.get()) { + instance = std::auto_ptr<SaslFactory>(new SaslFactory()); + } + return *instance; +} + +std::auto_ptr<Sasl> SaslFactory::create( const std::string &, const std::string &, const std::string &, const std::string &, int, int, bool ) +{ + return std::auto_ptr<Sasl>(); +} + +qpid::sys::Mutex SaslFactory::lock; +std::auto_ptr<SaslFactory> SaslFactory::instance; + +} // namespace qpid + +#else + +#include "qpid/Exception.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/sys/SecurityLayer.h" +#include "qpid/sys/SecuritySettings.h" +#include "qpid/sys/cyrus/CyrusSecurityLayer.h" +#include "qpid/log/Statement.h" +#include <sasl/sasl.h> +#include <strings.h> + +namespace qpid { + +using qpid::sys::SecurityLayer; +using qpid::sys::SecuritySettings; +using qpid::sys::cyrus::CyrusSecurityLayer; +using qpid::framing::InternalErrorException; + +const size_t MAX_LOGIN_LENGTH = 50; + +struct CyrusSaslSettings +{ + CyrusSaslSettings ( ) : + username ( std::string(0) ), + password ( std::string(0) ), + service ( std::string(0) ), + host ( std::string(0) ), + minSsf ( 0 ), + maxSsf ( 0 ) + { + } + + CyrusSaslSettings ( const std::string & user, const std::string & password, const std::string & service, const std::string & host, int minSsf, int maxSsf ) : + username(user), + password(password), + service(service), + host(host), + minSsf(minSsf), + maxSsf(maxSsf) + { + } + + std::string username, + password, + service, + host; + + int minSsf, + maxSsf; +}; + + +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); + std::string step(const std::string& challenge); + std::string getMechanism(); + std::string getUserId(); + std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize); + private: + sasl_conn_t* conn; + sasl_callback_t callbacks[5];//realm, user, authname, password, end-of-list + CyrusSaslSettings settings; + std::string input; + std::string mechanism; + char login[MAX_LOGIN_LENGTH]; + + /* In some contexts, like running in the broker or as a daemon, console + * interaction is impossible. In those cases, we will treat the attempt + * to interact as an error. */ + bool allowInteraction; + void interact(sasl_interact_t* client_interact); +}; + +//sasl callback functions +int getUserFromSettings(void *context, int id, const char **result, unsigned *len); +int getPasswordFromSettings(sasl_conn_t *conn, void *context, int id, sasl_secret_t **psecret); +typedef int CallbackProc(); + +qpid::sys::Mutex SaslFactory::lock; +std::auto_ptr<SaslFactory> SaslFactory::instance; + +SaslFactory::SaslFactory() +{ + sasl_callback_t* callbacks = 0; + int result = sasl_client_init(callbacks); + if (result != SASL_OK) { + throw InternalErrorException(QPID_MSG("Sasl error: " << sasl_errstring(result, 0, 0))); + } +} + +SaslFactory::~SaslFactory() +{ + sasl_done(); +} + +SaslFactory& SaslFactory::getInstance() +{ + qpid::sys::Mutex::ScopedLock l(lock); + if (!instance.get()) { + instance = std::auto_ptr<SaslFactory>(new SaslFactory()); + } + return *instance; +} + +std::auto_ptr<Sasl> SaslFactory::create(const std::string & username, const std::string & password, const std::string & serviceName, const std::string & hostName, int minSsf, int maxSsf, bool allowInteraction) +{ + std::auto_ptr<Sasl> sasl(new CyrusSasl(username, password, serviceName, hostName, minSsf, maxSsf, allowInteraction)); + return sasl; +} + +CyrusSasl::CyrusSasl(const std::string & username, const std::string & password, const std::string & serviceName, const std::string & hostName, int minSsf, int maxSsf, bool allowInteraction) + : conn(0), settings(username, password, serviceName, hostName, minSsf, maxSsf), allowInteraction(allowInteraction) +{ + size_t i = 0; + + callbacks[i].id = SASL_CB_GETREALM; + callbacks[i].proc = 0; + callbacks[i++].context = 0; + + if (!settings.username.empty()) { + 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_LIST_END; + callbacks[i].proc = 0; + callbacks[i++].context = 0; +} + +CyrusSasl::~CyrusSasl() +{ + if (conn) { + sasl_dispose(&conn); + } +} + +namespace { + const std::string SSL("ssl"); +} + +std::string CyrusSasl::start(const std::string& mechanisms, const SecuritySettings* externalSettings) +{ + QPID_LOG(debug, "CyrusSasl::start(" << mechanisms << ")"); + int result = sasl_client_new(settings.service.c_str(), + settings.host.c_str(), + 0, 0, /* Local and remote IP address strings */ + callbacks, + 0, /* security flags */ + &conn); + + if (result != SASL_OK) throw InternalErrorException(QPID_MSG("Sasl error: " << sasl_errdetail(conn))); + + sasl_security_properties_t secprops; + + if (externalSettings) { + sasl_ssf_t external_ssf = (sasl_ssf_t) externalSettings->ssf; + if (external_ssf) { + int result = sasl_setprop(conn, SASL_SSF_EXTERNAL, &external_ssf); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL error: unable to set external SSF: " << result)); + } + QPID_LOG(debug, "external SSF detected and set to " << external_ssf); + } + if (externalSettings->authid.size()) { + const char* external_authid = externalSettings->authid.c_str(); + result = sasl_setprop(conn, SASL_AUTH_EXTERNAL, external_authid); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL error: unable to set external auth: " << result)); + } + QPID_LOG(debug, "external auth detected and set to " << external_authid); + } + } + + secprops.min_ssf = settings.minSsf; + secprops.max_ssf = settings.maxSsf; + secprops.maxbufsize = 65535; + + QPID_LOG(debug, "min_ssf: " << secprops.min_ssf << ", max_ssf: " << secprops.max_ssf); + + secprops.property_names = 0; + secprops.property_values = 0; + secprops.security_flags = 0;//TODO: provide means for application to configure these + + result = sasl_setprop(conn, SASL_SEC_PROPS, &secprops); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL error: " << sasl_errdetail(conn))); + } + + sasl_interact_t* client_interact = 0; + const char *out = 0; + unsigned outlen = 0; + const char *chosenMechanism = 0; + + do { + result = sasl_client_start(conn, + mechanisms.c_str(), + &client_interact, + &out, + &outlen, + &chosenMechanism); + + if (result == SASL_INTERACT) { + interact(client_interact); + } + } while (result == SASL_INTERACT); + + if (result != SASL_CONTINUE && result != SASL_OK) { + throw InternalErrorException(QPID_MSG("Sasl error: " << sasl_errdetail(conn))); + } + + mechanism = std::string(chosenMechanism); + QPID_LOG(debug, "CyrusSasl::start(" << mechanisms << "): selected " + << mechanism << " response: '" << std::string(out, outlen) << "'"); + return std::string(out, outlen); +} + +std::string CyrusSasl::step(const std::string& challenge) +{ + sasl_interact_t* client_interact = 0; + const char *out = 0; + unsigned outlen = 0; + int result = 0; + do { + result = sasl_client_step(conn, /* our context */ + challenge.data(), /* the data from the server */ + challenge.size(), /* it's length */ + &client_interact, /* this should be + unallocated and NULL */ + &out, /* filled in on success */ + &outlen); /* filled in on success */ + + if (result == SASL_INTERACT) { + interact(client_interact); + } + } while (result == SASL_INTERACT); + + std::string response; + if (result == SASL_CONTINUE || result == SASL_OK) response = std::string(out, outlen); + else if (result != SASL_OK) { + throw InternalErrorException(QPID_MSG("Sasl error: " << sasl_errdetail(conn))); + } + QPID_LOG(debug, "CyrusSasl::step(" << challenge << "): " << response); + return response; +} + +std::string CyrusSasl::getMechanism() +{ + return mechanism; +} + +std::string CyrusSasl::getUserId() +{ + int propResult; + const void* operName; + + propResult = sasl_getprop(conn, SASL_USERNAME, &operName); + if (propResult == SASL_OK) + return std::string((const char*) operName); + + return std::string(); +} + +void CyrusSasl::interact(sasl_interact_t* client_interact) +{ + + /* + In some context console interaction cannot be allowed, such + as when this code run as part of a broker, or as a some other + daemon. In those cases we will treat the attempt to + */ + if ( ! allowInteraction ) { + throw InternalErrorException("interaction disallowed"); + } + + if (client_interact->id == SASL_CB_PASS) { + char* password = getpass(client_interact->prompt); + input = std::string(password); + client_interact->result = input.data(); + client_interact->len = input.size(); + } else { + std::cout << client_interact->prompt; + if (client_interact->defresult) std::cout << " (" << client_interact->defresult << ")"; + std::cout << ": "; + if (std::cin >> input) { + client_interact->result = input.data(); + client_interact->len = input.size(); + } + } + +} + +std::auto_ptr<SecurityLayer> CyrusSasl::getSecurityLayer(uint16_t maxFrameSize) +{ + const void* value(0); + int result = sasl_getprop(conn, SASL_SSF, &value); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL error: " << sasl_errdetail(conn))); + } + uint ssf = *(reinterpret_cast<const unsigned*>(value)); + std::auto_ptr<SecurityLayer> securityLayer; + if (ssf) { + QPID_LOG(info, "Installing security layer, SSF: "<< ssf); + securityLayer = std::auto_ptr<SecurityLayer>(new CyrusSecurityLayer(conn, maxFrameSize)); + } + return securityLayer; +} + +int getUserFromSettings(void* context, int /*id*/, const char** result, unsigned* /*len*/) +{ + if (context) { + *result = ((CyrusSaslSettings*) context)->username.c_str(); + QPID_LOG(debug, "getUserFromSettings(): " << (*result)); + return SASL_OK; + } else { + return SASL_FAIL; + } +} + +namespace { +// Global map of secrets allocated for SASL connections via callback +// to getPasswordFromSettings. Ensures secrets are freed. +class SecretsMap { + typedef std::map<sasl_conn_t*, void*> Map; + Map map; + public: + void keep(sasl_conn_t* conn, void* secret) { + Map::iterator i = map.find(conn); + if (i != map.end()) free(i->second); + map[conn] = secret; + } + + ~SecretsMap() { + for (Map::iterator i = map.begin(); i != map.end(); ++i) + free(i->second); + } +}; +SecretsMap getPasswordFromSettingsSecrets; +} + +int getPasswordFromSettings(sasl_conn_t* conn, void* context, int /*id*/, sasl_secret_t** psecret) +{ + if (context) { + size_t length = ((CyrusSaslSettings*) context)->password.size(); + sasl_secret_t* secret = (sasl_secret_t*) malloc(sizeof(sasl_secret_t) + length); + getPasswordFromSettingsSecrets.keep(conn, secret); + secret->len = length; + memcpy(secret->data, ((CyrusSaslSettings*) context)->password.data(), length); + *psecret = secret; + return SASL_OK; + } else { + return SASL_FAIL; + } +} + +} // namespace qpid + +#endif diff --git a/qpid/cpp/src/qpid/SaslFactory.h b/qpid/cpp/src/qpid/SaslFactory.h new file mode 100644 index 0000000000..8554597147 --- /dev/null +++ b/qpid/cpp/src/qpid/SaslFactory.h @@ -0,0 +1,47 @@ +#ifndef QPID_SASLFACTORY_H +#define QPID_SASLFACTORY_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/Sasl.h" +#include "qpid/sys/Mutex.h" +#include <memory> + +namespace qpid { + +/** + * Factory for instances of the Sasl interface through which Sasl + * support is provided to a ConnectionHandler. + */ +class SaslFactory +{ + public: + QPID_COMMON_EXTERN std::auto_ptr<Sasl> create(const std::string & userName, const std::string & password, const std::string & serviceName, const std::string & hostName, int minSsf, int maxSsf, bool allowInteraction=true ); + QPID_COMMON_EXTERN static SaslFactory& getInstance(); + QPID_COMMON_EXTERN ~SaslFactory(); + private: + SaslFactory(); + static qpid::sys::Mutex lock; + static std::auto_ptr<SaslFactory> instance; +}; +} // namespace qpid + +#endif /*!QPID_SASLFACTORY_H*/ diff --git a/qpid/cpp/src/qpid/Serializer.h b/qpid/cpp/src/qpid/Serializer.h new file mode 100644 index 0000000000..a8ded9f5e0 --- /dev/null +++ b/qpid/cpp/src/qpid/Serializer.h @@ -0,0 +1,197 @@ +#ifndef QPID_SERIALIZER_H +#define QPID_SERIALIZER_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 <limits> +#include <algorithm> +#include "qpid/Exception.h" // FIXME aconway 2008-04-03: proper exception class. + +namespace qpid { + +/** + * Overload for types that do not provide a serialize() member. + * It should retrun a wrapper holding a reference to t that implements + * serialize() + */ +template <class T> T& serializable(T& t) { return t; } + +/** Serialize std::pair */ +template <class T, class U> struct SerializablePair { + std::pair<T,U>& value; + SerializablePair(std::pair<T,U>& x) : value(x) {} + template <class S> void serialize(S& s) { s(value.first)(value.second); } +}; + +template <class T, class U> +SerializablePair<T,U> serializable(std::pair<T,U>& p) { + return SerializablePair<T,U>(p); +} + +/** + * Base class for all serializers. + * Derived serializers inherit from either Encoder or Decoder. + * Serializers can be used as functors or static_visitors. + */ +template <class Derived> class Serializer { + public: + /** Temporarily set a lower relative limit on the serializer */ + class ScopedLimit { + public: + ScopedLimit(Serializer& s, size_t l) + : serializer(s), save(serializer.setLimit(l)) {} + + ~ScopedLimit() { serializer.setAbsLimit(save); } + + private: + Serializer& serializer; + size_t save; + }; + + static size_t maxLimit() { return std::numeric_limits<size_t>::max(); } + + Serializer() : bytes(0), limit(maxLimit()) {} + + typedef Derived& result_type; // unary functor requirement. + + /** Wrapper functor to pass serializer functors by reference. */ + template <class S> struct Ref { + typedef typename S::result_type result_type; + S& s; + Ref(S& ss) : s(ss) {} + template <class T> result_type operator()(T& x) { return s(x); } + template <class T> result_type operator()(const T& x) { return s(x); } + }; + + /** Reference wrapper to pass serializers by reference, + * e.g. to std:: functions that take functors. + */ + template <class S> static Ref<S> ref(S& s) { return Ref<S>(s); } + + /** Generic rule to serialize an iterator range */ + template <class Iter> Derived& operator()(Iter begin, Iter end) { + std::for_each(begin, end, ref(this->self())); + return self(); + } + + /** Set limit relative to current position. + * @return old absolute limit. + */ + size_t setLimit(size_t n) { + size_t l=limit; + limit = bytes+n; + return l; + } + + /** Get the max number of bytes that can be processed under the + * current limit. + */ + size_t bytesRemaining() const { + return limit - bytes; + } + /** Set absolute limit. */ + void setAbsLimit(size_t n) { + limit = n; + if (bytes > limit) + throw Exception("Framing error: data overrun"); // FIXME aconway 2008-04-03: proper exception. + } + + protected: + Derived& self() { return *static_cast<Derived*>(this); } + void addBytes(size_t n) { + size_t newBytes=bytes+n; + if (newBytes > limit) + throw Exception("Framing error: data overrun"); // FIXME aconway 2008-04-03: proper exception. + bytes = newBytes; + } + + private: + void checkLimit() { + } + + size_t bytes; // how many bytes serialized. + size_t limit; // bytes may not exceed this limit. +}; + +/** + * Base class for encoders, provides generic encode functions. + * + * A derived encoder must provide operator(const T&) to encode all + * primitive types T. + */ +template <class Derived> class EncoderBase : public Serializer<Derived> { + public: + using Serializer<Derived>::operator(); + using Serializer<Derived>::self; + + /** Default op() for non-primitive types. */ + template <class T> Derived& operator()(const T& t) { + serializable(const_cast<T&>(t)).serialize(self()); return self(); + } + + /** Split serialize() into encode()/decode() */ + template <class T> Derived& split(const T& t) { + t.encode(self()); return self(); + } +}; + +/** + * Base class for decoders, provides generic decode functions. + * + * A derived encoder must provide operator(T&) to encode all + * primitive types T. + */ +template <class Derived> class DecoderBase : public Serializer<Derived> { + public: + using Serializer<Derived>::operator(); + using Serializer<Derived>::self; + + /** Default op() for non-primitive types. */ + template <class T> Derived& operator()(T& t) { + + serializable(t).serialize(self()); return self(); + } + + /** Split serialize() into encode()/decode() */ + template <class T> Derived& split(T& t) { + t.decode(self()); return self(); + } +}; + +/** Serialize a type by converting it to/from another type. + * To serialize type Foo by converting to/from type Bar create + * a serializable() overload like this: + * + * SerializeAs<Foo,Bar> serializable(Foo& t) { return SerializeAs<Foo,Bar>(t); } + */ +template <class Type, class AsType> +struct SerializeAs { + Type& value; + SerializeAs(Type & t) : value(t) {} + template <class S> void serialize(S& s) { s.split(*this); } + template <class S> void encode(S& s) const { s(AsType(value)); } + template <class S> void decode(S& s) { AsType x; s(x); value=Type(x); } +}; + +} // namespace qpid + +#endif /*!QPID_SERIALIZER_H*/ diff --git a/qpid/cpp/src/qpid/SessionId.cpp b/qpid/cpp/src/qpid/SessionId.cpp new file mode 100644 index 0000000000..c7e83f83d7 --- /dev/null +++ b/qpid/cpp/src/qpid/SessionId.cpp @@ -0,0 +1,47 @@ +/* + * + * 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/SessionId.h" +#include <sstream> + +namespace qpid { + +SessionId::SessionId(const std::string& u, const std::string& n) : userId(u), name(n) {} + +bool SessionId::operator<(const SessionId& id) const { + return userId < id.userId || (userId == id.userId && name < id.name); +} + +bool SessionId::operator==(const SessionId& id) const { + return id.name == name && id.userId == userId; +} + +std::ostream& operator<<(std::ostream& o, const SessionId& id) { + return o << id.getUserId() << "." << id.getName(); +} + +std::string SessionId::str() const { + std::ostringstream o; + o << *this; + return o.str(); +} + +} // namespace qpid diff --git a/qpid/cpp/src/qpid/SessionState.cpp b/qpid/cpp/src/qpid/SessionState.cpp new file mode 100644 index 0000000000..e5019604d2 --- /dev/null +++ b/qpid/cpp/src/qpid/SessionState.cpp @@ -0,0 +1,287 @@ +/* + * + * 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/SessionState.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/AMQMethodBody.h" +#include "qpid/framing/enum.h" +#include "qpid/log/Statement.h" +#include <boost/bind.hpp> +#include <numeric> + +namespace qpid { +using framing::AMQFrame; +using framing::NotImplementedException; +using framing::InvalidArgumentException; +using framing::IllegalStateException; +using framing::ResourceLimitExceededException; +using framing::InternalErrorException; +using framing::FramingErrorException; + +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; +} +} // namespace + +SessionPoint::SessionPoint(SequenceNumber c, uint64_t o) : command(c), offset(o) {} + +// TODO aconway 2008-05-22: Do complete frame sequence validity check here, +// currently duplicated betwen broker and client session impl. +// +void SessionPoint::advance(const AMQFrame& f) { + if (isControl(f)) return; // Ignore controls. + if (f.isFirstSegment() && f.isFirstFrame()) { + if (offset != 0) + throw FramingErrorException(QPID_MSG("Unexpected command start frame.")); + if (!isCommand(f)) + throw FramingErrorException( + QPID_MSG("Command start frame has invalid type" << f.getBody()->type())); + if (f.isLastSegment() && f.isLastFrame()) + ++command; // Single-frame command. + else + offset += f.encodedSize(); + } + else { // continuation frame for partial command + if (offset == 0) + throw FramingErrorException(QPID_MSG("Unexpected command continuation frame.")); + if (f.isLastSegment() && f.isLastFrame()) { + ++command; + offset = 0; + } + else { + // TODO aconway 2008-04-24: if we go to support for partial + // command replay, then it may be better to record the unframed + // data size in a command point rather than the framed size so + // that the relationship of fragment offsets to the replay + // list can be computed more easily. + // + offset += f.encodedSize(); + } + } +} + +bool SessionPoint::operator<(const SessionPoint& x) const { + return command < x.command || (command == x.command && offset < x.offset); +} + +bool SessionPoint::operator==(const SessionPoint& x) const { + return command == x.command && offset == x.offset; +} + + +SessionState::SendState::SendState() : unflushedSize(), replaySize(), bytesSinceKnownCompleted() {} + +SessionState::ReceiveState::ReceiveState() : bytesSinceKnownCompleted() {} + +uint32_t SessionState::getTimeout() const { return timeout; } +void SessionState::setTimeout(uint32_t seconds) { timeout = seconds; } + +SessionPoint SessionState::senderGetCommandPoint() { return sender.sendPoint; } +SequenceSet SessionState::senderGetIncomplete() const { return sender.incomplete; } +SessionPoint SessionState::senderGetReplayPoint() const { return sender.replayPoint; } + +SessionState::ReplayRange SessionState::senderExpected(const SessionPoint& expect) { + if (expect < sender.replayPoint || sender.sendPoint < expect) + throw InvalidArgumentException(QPID_MSG(getId() << ": expected command-point out of range.")); + QPID_LOG(debug, getId() << ": sender expected point moved to " << expect); + ReplayList::iterator i = sender.replayList.begin(); + SessionPoint p = sender.replayPoint; + while (i != sender.replayList.end() && p.command < expect.command) + p.advance(*i++); + assert(p.command == expect.command); + return boost::make_iterator_range(i, sender.replayList.end()); +} + +void SessionState::senderRecord(const AMQFrame& f) { + if (isControl(f)) return; // Ignore control frames. + QPID_LOG(trace, getId() << ": sent cmd " << sender.sendPoint.command << ": " << *f.getBody()); + + stateful = true; + if (timeout) sender.replayList.push_back(f); + sender.unflushedSize += f.encodedSize(); + sender.bytesSinceKnownCompleted += f.encodedSize(); + sender.replaySize += f.encodedSize(); + sender.incomplete += sender.sendPoint.command; + sender.sendPoint.advance(f); + if (config.replayHardLimit && config.replayHardLimit < sender.replaySize) + throw ResourceLimitExceededException("Replay buffer exceeeded hard limit"); +} + +static const uint32_t SPONTANEOUS_REQUEST_INTERVAL = 65536; + +bool SessionState::senderNeedFlush() const { + return (sender.sendPoint.command % SPONTANEOUS_REQUEST_INTERVAL == 0) || + (config.replayFlushLimit && sender.unflushedSize >= config.replayFlushLimit); +} + +void SessionState::senderRecordFlush() { + sender.flushPoint = sender.sendPoint; + sender.unflushedSize = 0; +} + +bool SessionState::senderNeedKnownCompleted() const { + return config.replayFlushLimit && sender.bytesSinceKnownCompleted >= config.replayFlushLimit; +} + +void SessionState::senderRecordKnownCompleted() { + sender.bytesSinceKnownCompleted = 0; +} + +void SessionState::senderConfirmed(const SessionPoint& confirmed) { + if (confirmed > sender.sendPoint) + throw InvalidArgumentException(QPID_MSG(getId() << ": confirmed < " << confirmed << " but only sent < " << sender.sendPoint)); + QPID_LOG(debug, getId() << ": sender confirmed point moved to " << confirmed); + ReplayList::iterator i = sender.replayList.begin(); + while (i != sender.replayList.end() && sender.replayPoint.command < confirmed.command) { + sender.replayPoint.advance(*i); + assert(sender.replayPoint <= sender.sendPoint); + sender.replaySize -= i->encodedSize(); + if (sender.replayPoint > sender.flushPoint) + sender.unflushedSize -= i->encodedSize(); + ++i; + } + if (sender.replayPoint > sender.flushPoint) + sender.flushPoint = sender.replayPoint; + sender.replayList.erase(sender.replayList.begin(), i); + assert(sender.replayPoint.offset == 0); +} + +void SessionState::senderCompleted(const SequenceSet& commands) { + if (commands.empty()) return; + QPID_LOG(debug, getId() << ": sender marked completed: " << commands); + sender.incomplete -= commands; + // Completion implies confirmation but we don't handle out-of-order + // confirmation, so confirm up to the end of the first contiguous range of commands. + senderConfirmed(SessionPoint(commands.rangesBegin()->end())); +} + +void SessionState::receiverSetCommandPoint(const SessionPoint& point) { + if (hasState() && point > receiver.received) + throw InvalidArgumentException(QPID_MSG(getId() << ": Command-point out of range.")); + QPID_LOG(debug, getId() << ": receiver command-point set to: " << point); + receiver.expected = point; + if (receiver.expected > receiver.received) + receiver.received = receiver.expected; +} + +bool SessionState::receiverRecord(const AMQFrame& f) { + if (receiverTrackingDisabled) return true; //Very nasty hack for push bridges + if (isControl(f)) return true; // Ignore control frames. + stateful = true; + receiver.expected.advance(f); + receiver.bytesSinceKnownCompleted += f.encodedSize(); + bool firstTime = receiver.expected > receiver.received; + if (firstTime) { + receiver.received = receiver.expected; + receiver.incomplete += receiverGetCurrent(); + } + QPID_LOG(trace, getId() << ": recv cmd " << receiverGetCurrent() << ": " << *f.getBody()); + if (!firstTime) QPID_LOG(trace, "Ignoring duplicate frame."); + return firstTime; +} + +void SessionState::receiverCompleted(SequenceNumber command, bool cumulative) { + if (receiverTrackingDisabled) return; //Very nasty hack for push bridges + assert(receiver.incomplete.contains(command)); // Internal error to complete command twice. + SequenceNumber first =cumulative ? receiver.incomplete.front() : command; + SequenceNumber last = command; + receiver.unknownCompleted.add(first, last); + receiver.incomplete.remove(first, last); + QPID_LOG(debug, getId() << ": receiver marked completed: " << command + << " incomplete: " << receiver.incomplete + << " unknown-completed: " << receiver.unknownCompleted); +} + +void SessionState::receiverKnownCompleted(const SequenceSet& commands) { + if (!commands.empty() && commands.back() > receiver.received.command) + throw InvalidArgumentException(QPID_MSG(getId() << ": Known-completed has invalid commands.")); + receiver.bytesSinceKnownCompleted=0; + receiver.unknownCompleted -= commands; + QPID_LOG(debug, getId() << ": receiver known completed: " << commands << " unknown: " << receiver.unknownCompleted); +} + +bool SessionState::receiverNeedKnownCompleted() const { + return (receiver.expected.command % SPONTANEOUS_REQUEST_INTERVAL == 0) || + (config.replayFlushLimit && receiver.bytesSinceKnownCompleted >= config.replayFlushLimit); +} + +const SessionPoint& SessionState::receiverGetExpected() const { return receiver.expected; } +const SessionPoint& SessionState::receiverGetReceived() const { return receiver.received; } +const SequenceSet& SessionState::receiverGetUnknownComplete() const { return receiver.unknownCompleted; } +const SequenceSet& SessionState::receiverGetIncomplete() const { return receiver.incomplete; } + +SequenceNumber SessionState::receiverGetCurrent() const { + SequenceNumber current = receiver.expected.command; + if (receiver.expected.offset == 0) + --current; + return current; +} + +SessionState::Configuration::Configuration(size_t flush, size_t hard) : + replayFlushLimit(flush), replayHardLimit(hard) {} + +SessionState::SessionState(const SessionId& i, const Configuration& c) + : id(i), timeout(0), config(c), stateful(false), receiverTrackingDisabled(false) +{ + QPID_LOG(debug, "SessionState::SessionState " << id << ": " << this); +} + +bool SessionState::hasState() const { return stateful; } + +SessionState::~SessionState() {} + +std::ostream& operator<<(std::ostream& o, const SessionPoint& p) { + return o << "(" << p.command.getValue() << "+" << p.offset << ")"; +} + +void SessionState::setState( + const SequenceNumber& replayStart, + const SequenceNumber& sendCommandPoint, + const SequenceSet& sentIncomplete, + const SequenceNumber& expected, + const SequenceNumber& received, + const SequenceSet& unknownCompleted, + const SequenceSet& receivedIncomplete +) +{ + sender.replayPoint = replayStart; + sender.flushPoint = sendCommandPoint; + sender.sendPoint = sendCommandPoint; + sender.unflushedSize = 0; + sender.replaySize = 0; // Replay list will be updated separately. + sender.incomplete = sentIncomplete; + sender.bytesSinceKnownCompleted = 0; + + receiver.expected = expected; + receiver.received = received; + receiver.unknownCompleted = unknownCompleted; + receiver.incomplete = receivedIncomplete; + receiver.bytesSinceKnownCompleted = 0; +} + +void SessionState::disableReceiverTracking() { receiverTrackingDisabled = true; } +void SessionState::enableReceiverTracking() { receiverTrackingDisabled = false; } + +} // namespace qpid diff --git a/qpid/cpp/src/qpid/SessionState.h b/qpid/cpp/src/qpid/SessionState.h new file mode 100644 index 0000000000..02853b1143 --- /dev/null +++ b/qpid/cpp/src/qpid/SessionState.h @@ -0,0 +1,235 @@ +#ifndef QPID_SESSIONSTATE_H +#define QPID_SESSIONSTATE_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/SessionId.h> +#include <qpid/framing/SequenceNumber.h> +#include <qpid/framing/SequenceSet.h> +#include <qpid/framing/AMQFrame.h> +#include <qpid/framing/FrameHandler.h> +#include <boost/operators.hpp> +#include <boost/range/iterator_range.hpp> +#include <vector> +#include <iosfwd> +#include <qpid/CommonImportExport.h> + +namespace qpid { +using framing::SequenceNumber; +using framing::SequenceSet; + +/** A point in the session. Points to command id + offset */ +struct SessionPoint : boost::totally_ordered1<SessionPoint> { + QPID_COMMON_EXTERN SessionPoint(SequenceNumber command = 0, uint64_t offset = 0); + + SequenceNumber command; + uint64_t offset; + + /** Advance past frame f */ + QPID_COMMON_EXTERN void advance(const framing::AMQFrame& f); + + QPID_COMMON_EXTERN bool operator<(const SessionPoint&) const; + QPID_COMMON_EXTERN bool operator==(const SessionPoint&) const; +}; + +QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream&, const SessionPoint&); + +/** + * Support for session idempotence barrier and resume as defined in + * AMQP 0-10. + * + * We only issue/use contiguous confirmations, out-of-order confirmation + * is ignored. Out of order completion is fully supported. + * + * Raises NotImplemented if the command point is set greater than the + * max currently received command data, either explicitly via + * session.command-point or implicitly via session.gap. + * + * Partial replay is not supported, replay always begins on a command + * boundary, and we never confirm partial commands. + * + * The SessionPoint data structure does store offsets so this class + * could be extended to support partial replay without + * source-incompatbile API changes. + */ +class SessionState { + typedef std::vector<framing::AMQFrame> ReplayList; + + public: + + typedef boost::iterator_range<ReplayList::iterator> ReplayRange; + + struct Configuration { + QPID_COMMON_EXTERN Configuration(size_t flush=1024*1024, size_t hard=0); + size_t replayFlushLimit; // Flush when the replay list >= N bytes. 0 disables. + size_t replayHardLimit; // Kill session if replay list > N bytes. 0 disables. + }; + + QPID_COMMON_EXTERN SessionState(const SessionId& =SessionId(), const Configuration& =Configuration()); + + QPID_COMMON_EXTERN virtual ~SessionState(); + + bool hasState() const; + + const SessionId& getId() const { return id; } + + QPID_COMMON_EXTERN virtual uint32_t getTimeout() const; + QPID_COMMON_EXTERN virtual void setTimeout(uint32_t seconds); + + bool operator==(const SessionId& other) const { return id == other; } + bool operator==(const SessionState& other) const { return id == other.id; } + + // ==== Functions for sender state. + + /** Record frame f for replay. Should not be called during replay. */ + QPID_COMMON_EXTERN virtual void senderRecord(const framing::AMQFrame& f); + + /** @return true if we should send flush for confirmed and completed commands. */ + QPID_COMMON_EXTERN virtual bool senderNeedFlush() const; + + /** Called when flush for confirmed and completed commands is sent to peer. */ + QPID_COMMON_EXTERN virtual void senderRecordFlush(); + + /** True if we should reply to the next incoming completed command */ + QPID_COMMON_EXTERN virtual bool senderNeedKnownCompleted() const; + + /** Called when knownCompleted is sent to peer. */ + QPID_COMMON_EXTERN virtual void senderRecordKnownCompleted(); + + /** Called when the peer confirms up to comfirmed. */ + QPID_COMMON_EXTERN virtual void senderConfirmed(const SessionPoint& confirmed); + + /** Called when the peer indicates commands completed */ + QPID_COMMON_EXTERN virtual void senderCompleted(const SequenceSet& commands); + + /** Point from which the next new (not replayed) data will be sent. */ + QPID_COMMON_EXTERN virtual SessionPoint senderGetCommandPoint(); + + /** Set of outstanding incomplete commands */ + QPID_COMMON_EXTERN virtual SequenceSet senderGetIncomplete() const; + + /** Point from which we can replay. */ + QPID_COMMON_EXTERN virtual SessionPoint senderGetReplayPoint() const; + + /** Peer expecting commands from this point. + *@return Range of frames to be replayed. + */ + QPID_COMMON_EXTERN virtual ReplayRange senderExpected(const SessionPoint& expected); + + // ==== Functions for receiver state + + /** Set the command point. */ + QPID_COMMON_EXTERN virtual void receiverSetCommandPoint(const SessionPoint& point); + + /** Returns true if frame should be be processed, false if it is a duplicate. */ + QPID_COMMON_EXTERN virtual bool receiverRecord(const framing::AMQFrame& f); + + /** Command completed locally */ + QPID_COMMON_EXTERN virtual void receiverCompleted(SequenceNumber command, bool cumulative=false); + + /** Peer has indicated commands are known completed */ + QPID_COMMON_EXTERN virtual void receiverKnownCompleted(const SequenceSet& commands); + + /** True if the next completed control should set the timely-reply argument + * to request a knonw-completed response. + */ + QPID_COMMON_EXTERN virtual bool receiverNeedKnownCompleted() const; + + /** Get the incoming command point */ + QPID_COMMON_EXTERN virtual const SessionPoint& receiverGetExpected() const; + + /** Get the received high-water-mark, may be > getExpected() during replay */ + QPID_COMMON_EXTERN virtual const SessionPoint& receiverGetReceived() const; + + /** Completed received commands that the peer may not know about. */ + QPID_COMMON_EXTERN virtual const SequenceSet& receiverGetUnknownComplete() const; + + /** Incomplete received commands. */ + QPID_COMMON_EXTERN virtual const SequenceSet& receiverGetIncomplete() const; + + /** ID of the command currently being handled. */ + QPID_COMMON_EXTERN virtual SequenceNumber receiverGetCurrent() const; + + /** Set the state variables, used to create a session that will resume + * from some previously established point. + */ + QPID_COMMON_EXTERN virtual void setState( + const SequenceNumber& replayStart, + const SequenceNumber& sendCommandPoint, + const SequenceSet& sentIncomplete, + const SequenceNumber& expected, + const SequenceNumber& received, + const SequenceSet& unknownCompleted, + const SequenceSet& receivedIncomplete + ); + + /** + * So called 'push' bridges work by faking a subscribe request + * (and the accompanying flows etc) to the local broker to initiate + * the outflow of messages for the bridge. + * + * As the peer doesn't send these it cannot include them in its + * session state. To keep the session state on either side of the + * bridge in sync, this hack allows the tracking of state for + * received messages to be disabled for the faked commands and + * subsequently re-enabled. + */ + QPID_COMMON_EXTERN void disableReceiverTracking(); + QPID_COMMON_EXTERN void enableReceiverTracking(); + + private: + + struct SendState { + SendState(); + // invariant: replayPoint <= flushPoint <= sendPoint + SessionPoint replayPoint; // Can replay from this point + SessionPoint flushPoint; // Point of last flush + SessionPoint sendPoint; // Send from this point + ReplayList replayList; // Starts from replayPoint. + size_t unflushedSize; // Un-flushed bytes in replay list. + size_t replaySize; // Total bytes in replay list. + SequenceSet incomplete; // Commands sent and not yet completed. + size_t bytesSinceKnownCompleted; // Bytes sent since we last issued a knownCompleted. + } sender; + + struct ReceiveState { + ReceiveState(); + SessionPoint expected; // Expected from here + SessionPoint received; // Received to here. Invariant: expected <= received. + SequenceSet unknownCompleted; // Received & completed, may not not known-complete by peer. + SequenceSet incomplete; // Incomplete received commands. + size_t bytesSinceKnownCompleted; // Bytes sent since we last issued a knownCompleted. + } receiver; + + SessionId id; + uint32_t timeout; + Configuration config; + bool stateful; + bool receiverTrackingDisabled;//very nasty hack for 'push' bridges +}; + +inline bool operator==(const SessionId& id, const SessionState& s) { return s == id; } + +} // namespace qpid + + +#endif /*!QPID_SESSIONSTATE_H*/ diff --git a/qpid/cpp/src/qpid/SharedObject.h b/qpid/cpp/src/qpid/SharedObject.h new file mode 100644 index 0000000000..852a036ab9 --- /dev/null +++ b/qpid/cpp/src/qpid/SharedObject.h @@ -0,0 +1,55 @@ +#ifndef _SharedObject_ +#define _SharedObject_ + +/* + * + * 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/shared_ptr.hpp> +#include <boost/noncopyable.hpp> + +namespace qpid { + /** + * Template to enforce shared object conventions. + * Shared object classes should inherit : public qpid::SharedObject + * That ensures Foo: + * - has typedef boost::shared_ptr<T> shared_ptr + * - has virtual destructor + * - is boost::noncopyable (no default copy or assign) + * - has a protected default constructor. + * + * Shared objects should not have public constructors. + * Make constructors protected and provide public statc create() + * functions that return a shared_ptr. + */ + template <class T> + class SharedObject : private boost::noncopyable + { + public: + typedef boost::shared_ptr<T> shared_ptr; + + virtual ~SharedObject() {}; + + protected: + SharedObject() {} + }; +} + +#endif /*!_SharedObject_*/ diff --git a/qpid/cpp/src/qpid/StringUtils.cpp b/qpid/cpp/src/qpid/StringUtils.cpp new file mode 100644 index 0000000000..c436441c56 --- /dev/null +++ b/qpid/cpp/src/qpid/StringUtils.cpp @@ -0,0 +1,50 @@ +/* + * + * 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/StringUtils.h" + +namespace qpid { + +using std::string; +using std::vector; + +void split(vector<string>& out, const string& in, const string& delims) +{ + string::size_type start = in.find_first_not_of(delims); + if (start == string::npos) return; + + string::size_type end = in.find_first_of(delims, start); + while (end != string::npos) { + out.push_back(in.substr(start, end - start)); + start = in.find_first_not_of(delims, end); + if (start == string::npos) return; + end = in.find_first_of(delims, start); + } + out.push_back(in.substr(start)); +} + +vector<string> split(const string& in, const string& delims) +{ + vector<string> out; + split(out, in, delims); + return out; +} + +} // namespace qpid diff --git a/qpid/cpp/src/qpid/StringUtils.h b/qpid/cpp/src/qpid/StringUtils.h new file mode 100644 index 0000000000..4130fae017 --- /dev/null +++ b/qpid/cpp/src/qpid/StringUtils.h @@ -0,0 +1,45 @@ +#ifndef QPID_STRINGUTILS_H +#define QPID_STRINGUTILS_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/CommonImportExport.h" + +#include <string> +#include <vector> + +namespace qpid { + +/** + * Split 'in' into words using delimiters in 'delims' and put + * resulting strings into 'out' vector. + */ +QPID_COMMON_EXTERN void split(std::vector<std::string>& out, const std::string& in, const std::string& delims); +/** + * Split 'in' into words using delimiters in 'delims' and return the + * resulting strings in a vector. + */ +QPID_COMMON_EXTERN std::vector<std::string> split(const std::string& in, const std::string& delims); + +} // namespace qpid + +#endif /*!QPID_STRINGUTILS_H*/ diff --git a/qpid/cpp/src/qpid/Url.cpp b/qpid/cpp/src/qpid/Url.cpp new file mode 100644 index 0000000000..ab796f4642 --- /dev/null +++ b/qpid/cpp/src/qpid/Url.cpp @@ -0,0 +1,265 @@ +/* + * + * 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. + * + */ + +#include "qpid/Url.h" +#include "qpid/Exception.h" +#include "qpid/Msg.h" +#include "qpid/sys/SystemInfo.h" +#include "qpid/sys/StrError.h" +#include "qpid/client/Connector.h" +#include "qpid/sys/Mutex.h" +#include <boost/lexical_cast.hpp> + +#include <algorithm> +#include <vector> +#include <string> + +#include <string.h> + +using namespace std; +using boost::lexical_cast; + +namespace qpid { + +class ProtocolTags { + public: + bool find(const string& tag) { + sys::Mutex::ScopedLock l(lock); + return std::find(tags.begin(), tags.end(), tag) != tags.end(); + } + + void add(const string& tag) { + sys::Mutex::ScopedLock l(lock); + if (std::find(tags.begin(), tags.end(), tag) == tags.end()) + tags.push_back(tag); + } + + static ProtocolTags& instance() { + /** First call must be made while program is still single threaded. + * This will be the case since tags are registered in static initializers. + */ + static ProtocolTags tags; + return tags; + } + + private: + sys::Mutex lock; + vector<string> tags; +}; + +Url::Invalid::Invalid(const string& s) : Exception(s) {} + +Url Url::getHostNameUrl(uint16_t port) { + Address address("tcp", std::string(), port); + if (!sys::SystemInfo::getLocalHostname(address)) + throw Url::Invalid(QPID_MSG("Cannot get host name: " << qpid::sys::strError(errno))); + return Url(address); +} + +Url Url::getIpAddressesUrl(uint16_t port) { + Url url; + sys::SystemInfo::getLocalIpAddresses(port, url); + return url; +} + +string Url::str() const { + if (cache.empty() && !this->empty()) { + ostringstream os; + os << *this; + cache = os.str(); + } + return cache; +} + +ostream& operator<<(ostream& os, const Url& url) { + os << "amqp:"; + if (!url.getUser().empty()) os << url.getUser(); + if (!url.getPass().empty()) os << "/" << url.getPass(); + if (!(url.getUser().empty() && url.getPass().empty())) os << "@"; + Url::const_iterator i = url.begin(); + if (i!=url.end()) { + os << *i++; + while (i != url.end()) + os << "," << *i++; + } + return os; +} + +static const std::string TCP = "tcp"; + +/** Simple recursive-descent parser for this grammar: +url = ["amqp:"][ user ["/" password] "@" ] protocol_addr *("," protocol_addr) +protocol_addr = tcp_addr / rmda_addr / ssl_addr / .. others plug-in +tcp_addr = ["tcp:"] host [":" port] +rdma_addr = "rdma:" host [":" port] +ssl_addr = "ssl:" host [":" port] +*/ +class UrlParser { + public: + UrlParser(Url& u, const char* s) : url(u), text(s), end(s+strlen(s)), i(s) {} + bool parse() { + literal("amqp:"); // Optional + userPass(); // Optional + return list(&UrlParser::protocolAddr, &UrlParser::comma) && i == end; + } + + private: + typedef bool (UrlParser::*Rule)(); + + bool userPass() { + const char* at = std::find(i, end, '@'); + if (at == end) return false; + const char* slash = std::find(i, at, '/'); + url.setUser(string(i, slash)); + const char* pass = (slash == at) ? slash : slash+1; + url.setPass(string(pass, at)); + i = at+1; + return true; + } + + bool comma() { return literal(","); } + + bool protocolAddr() { + Address addr(Address::TCP, "", Address::AMQP_PORT); // Set up defaults + protocolTag(addr.protocol); // Optional + bool ok = (host(addr.host) && + (literal(":") ? port(addr.port) : true)); + if (ok) url.push_back(addr); + return ok; + } + + bool protocolTag(string& result) { + const char* j = std::find(i,end,':'); + if (j != end) { + string tag(i,j); + if (ProtocolTags::instance().find(tag)) { + i = j+1; + result = tag; + return true; + } + } + 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. + // + bool host(string& h) { + const char* start=i; + while (unreserved() || pctEncoded()) + ; + if (start == i) return false;//host is required + else h.assign(start, i); + return true; + } + + bool unreserved() { return (::isalnum(*i) || ::strchr("-._~", *i)) && advance(); } + + bool pctEncoded() { return literal("%") && hexDigit() && hexDigit(); } + + bool hexDigit() { return i < end && ::strchr("01234567890abcdefABCDEF", *i) && advance(); } + + bool port(uint16_t& p) { return decimalInt(p); } + + template <class IntType> bool decimalInt(IntType& n) { + const char* start = i; + while (decDigit()) + ; + try { + n = lexical_cast<IntType>(string(start, i)); + return true; + } catch(...) { return false; } + } + + bool decDigit() { return i < end && ::isdigit(*i) && advance(); } + + bool literal(const char* s) { + int n = ::strlen(s); + if (n <= end-i && equal(s, s+n, i)) return advance(n); + return false; + }; + + bool noop() { return true; } + + /** List of item, separated by separator, with min & max bounds. */ + bool list(Rule item, Rule separator, size_t min=0, size_t max=UNLIMITED) { + assert(max > 0); + assert(max >= min); + if (!(this->*item)()) return min == 0; // Empty list. + size_t n = 1; + while (n < max && i < end) { + if (!(this->*separator)()) break; + if (i == end || !(this->*item)()) return false; // Separator with no item. + ++n; + } + return n >= min; + } + + /** List of items with no separator */ + bool list(Rule item, size_t min=0, size_t max=UNLIMITED) { return list(item, &UrlParser::noop, min, max); } + + bool advance(size_t n=1) { + if (i+n > end) return false; + i += n; + return true; + } + + static const size_t UNLIMITED = size_t(~1); + static const std::string LOCALHOST; + + Url& url; + const char* text; + const char* end; + const char* i; +}; + +const string UrlParser::LOCALHOST("127.0.0.1"); + +void Url::parse(const char* url) { + parseNoThrow(url); + if (empty()) + throw Url::Invalid(QPID_MSG("Invalid URL: " << url)); +} + +void Url::parseNoThrow(const char* url) { + cache.clear(); + if (!UrlParser(*this, url).parse()) + clear(); +} + +void Url::throwIfEmpty() const { + if (empty()) + throw Url::Invalid("URL contains no addresses"); +} + +std::string Url::getUser() const { return user; } +std::string Url::getPass() const { return pass; } +void Url::setUser(const std::string& s) { user = s; } +void Url::setPass(const std::string& s) { pass = s; } + +std::istream& operator>>(std::istream& is, Url& url) { + std::string s; + is >> s; + url.parse(s); + return is; +} + +void Url::addProtocol(const std::string& tag) { ProtocolTags::instance().add(tag); } + +} // namespace qpid diff --git a/qpid/cpp/src/qpid/Version.h b/qpid/cpp/src/qpid/Version.h new file mode 100755 index 0000000000..b4805a3757 --- /dev/null +++ b/qpid/cpp/src/qpid/Version.h @@ -0,0 +1,40 @@ +#ifndef QPID_VERSION_H +#define QPID_VERSION_H + +/* + * + * 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. + * + */ + +#include <string> + +#ifdef HAVE_CONFIG_H +# include "config.h" +#else +# error "config.h not generated" +#endif + +namespace qpid { + const std::string product = PACKAGE_NAME; + const std::string version = PACKAGE_VERSION; +# if HAVE_SASL + const std::string saslName = BROKER_SASL_NAME; +# else + const std::string saslName = "qpidd-no-sasl"; +# endif +} + +#endif /*!QPID_VERSION_H*/ diff --git a/qpid/cpp/src/qpid/acl/Acl.cpp b/qpid/cpp/src/qpid/acl/Acl.cpp new file mode 100644 index 0000000000..4b3dda7962 --- /dev/null +++ b/qpid/cpp/src/qpid/acl/Acl.cpp @@ -0,0 +1,191 @@ +/* + * + * 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. + * + */ + +#include "qpid/acl/Acl.h" +#include "qpid/acl/AclData.h" +#include "qpid/acl/AclValidator.h" +#include "qpid/sys/Mutex.h" + +#include "qpid/broker/Broker.h" +#include "qpid/Plugin.h" +#include "qpid/Options.h" +#include "qpid/log/Logger.h" +#include "qpid/types/Variant.h" +#include "qmf/org/apache/qpid/acl/Package.h" +#include "qmf/org/apache/qpid/acl/EventAllow.h" +#include "qmf/org/apache/qpid/acl/EventDeny.h" +#include "qmf/org/apache/qpid/acl/EventFileLoaded.h" +#include "qmf/org/apache/qpid/acl/EventFileLoadFailed.h" + +#include <map> + +#include <boost/shared_ptr.hpp> +#include <boost/utility/in_place_factory.hpp> + +using namespace std; +using namespace qpid::acl; +using qpid::broker::Broker; +using namespace qpid::sys; +using qpid::management::ManagementAgent; +using qpid::management::ManagementObject; +using qpid::management::Manageable; +using qpid::management::Args; +namespace _qmf = qmf::org::apache::qpid::acl; + +Acl::Acl (AclValues& av, Broker& b): aclValues(av), broker(&b), transferAcl(false), mgmtObject(0) +{ + + agent = broker->getManagementAgent(); + + if (agent != 0){ + _qmf::Package packageInit(agent); + mgmtObject = new _qmf::Acl (agent, this, broker); + agent->addObject (mgmtObject); + } + std::string errorString; + if (!readAclFile(errorString)){ + throw Exception("Could not read ACL file " + errorString); + if (mgmtObject!=0) mgmtObject->set_enforcingAcl(0); + } + QPID_LOG(info, "ACL Plugin loaded"); + if (mgmtObject!=0) mgmtObject->set_enforcingAcl(1); +} + + bool Acl::authorise(const std::string& id, const Action& action, const ObjectType& objType, const std::string& name, std::map<Property, std::string>* params) + { + boost::shared_ptr<AclData> dataLocal; + { + Mutex::ScopedLock locker(dataLock); + dataLocal = data; //rcu copy + } + + // add real ACL check here... + AclResult aclreslt = dataLocal->lookup(id,action,objType,name,params); + + + return result(aclreslt, id, action, objType, name); + } + + bool Acl::authorise(const std::string& id, const Action& action, const ObjectType& objType, const std::string& ExchangeName, const std::string& RoutingKey) + { + boost::shared_ptr<AclData> dataLocal; + { + Mutex::ScopedLock locker(dataLock); + dataLocal = data; //rcu copy + } + + // only use dataLocal here... + AclResult aclreslt = dataLocal->lookup(id,action,objType,ExchangeName,RoutingKey); + + return result(aclreslt, id, action, objType, ExchangeName); + } + + + bool Acl::result(const AclResult& aclreslt, const std::string& id, const Action& action, const ObjectType& objType, const std::string& name) + { + switch (aclreslt) + { + case ALLOWLOG: + QPID_LOG(info, "ACL Allow id:" << id <<" action:" << AclHelper::getActionStr(action) << + " ObjectType:" << AclHelper::getObjectTypeStr(objType) << " Name:" << name ); + agent->raiseEvent(_qmf::EventAllow(id, AclHelper::getActionStr(action), + AclHelper::getObjectTypeStr(objType), + name, types::Variant::Map())); + case ALLOW: + return true; + case DENY: + if (mgmtObject!=0) mgmtObject->inc_aclDenyCount(); + return false; + case DENYLOG: + if (mgmtObject!=0) mgmtObject->inc_aclDenyCount(); + default: + QPID_LOG(info, "ACL Deny id:" << id << " action:" << AclHelper::getActionStr(action) << " ObjectType:" << AclHelper::getObjectTypeStr(objType) << " Name:" << name); + agent->raiseEvent(_qmf::EventDeny(id, AclHelper::getActionStr(action), + AclHelper::getObjectTypeStr(objType), + name, types::Variant::Map())); + return false; + } + return false; + } + + bool Acl::readAclFile(std::string& errorText) + { + // only set transferAcl = true if a rule implies the use of ACL on transfer, else keep false for performance reasons. + return readAclFile(aclValues.aclFile, errorText); + } + + bool Acl::readAclFile(std::string& aclFile, std::string& errorText) { + boost::shared_ptr<AclData> d(new AclData); + AclReader ar; + if (ar.read(aclFile, d)){ + agent->raiseEvent(_qmf::EventFileLoadFailed("", ar.getError())); + errorText = ar.getError(); + QPID_LOG(error,ar.getError()); + return false; + } + + AclValidator validator; + validator.validate(d); + + { + Mutex::ScopedLock locker(dataLock); + data = d; + } + transferAcl = data->transferAcl; // any transfer ACL + + if (data->transferAcl){ + QPID_LOG(debug,"Transfer ACL is Enabled!"); + } + + data->aclSource = aclFile; + if (mgmtObject!=0){ + mgmtObject->set_transferAcl(transferAcl?1:0); + mgmtObject->set_policyFile(aclFile); + sys::AbsTime now = sys::AbsTime::now(); + int64_t ns = sys::Duration(sys::EPOCH, now); + mgmtObject->set_lastAclLoad(ns); + agent->raiseEvent(_qmf::EventFileLoaded("")); + } + return true; + } + + Acl::~Acl(){} + + ManagementObject* Acl::GetManagementObject(void) const + { + return (ManagementObject*) mgmtObject; + } + + Manageable::status_t Acl::ManagementMethod (uint32_t methodId, Args& /*args*/, string& text) + { + Manageable::status_t status = Manageable::STATUS_UNKNOWN_METHOD; + QPID_LOG (debug, "Queue::ManagementMethod [id=" << methodId << "]"); + + switch (methodId) + { + case _qmf::Acl::METHOD_RELOADACLFILE : + readAclFile(text); + if (text.empty()) + status = Manageable::STATUS_OK; + else + status = Manageable::STATUS_USER; + break; + } + + return status; +} diff --git a/qpid/cpp/src/qpid/acl/Acl.h b/qpid/cpp/src/qpid/acl/Acl.h new file mode 100644 index 0000000000..77f43838de --- /dev/null +++ b/qpid/cpp/src/qpid/acl/Acl.h @@ -0,0 +1,86 @@ +#ifndef QPID_ACL_ACL_H +#define QPID_ACL_ACL_H + + +/* + * + * 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. + * + */ + + + +#include "qpid/acl/AclReader.h" +#include "qpid/RefCounted.h" +#include "qpid/broker/AclModule.h" +#include "qpid/management/Manageable.h" +#include "qpid/management/ManagementAgent.h" +#include "qmf/org/apache/qpid/acl/Acl.h" +#include "qpid/sys/Mutex.h" + +#include <map> +#include <string> + + +namespace qpid { +namespace broker { +class Broker; +} + +namespace acl { + +struct AclValues { + std::string aclFile; +}; + + +class Acl : public broker::AclModule, public RefCounted, public management::Manageable +{ + +private: + acl::AclValues aclValues; + broker::Broker* broker; + bool transferAcl; + boost::shared_ptr<AclData> data; + qmf::org::apache::qpid::acl::Acl* mgmtObject; // mgnt owns lifecycle + qpid::management::ManagementAgent* agent; + mutable qpid::sys::Mutex dataLock; + +public: + Acl (AclValues& av, broker::Broker& b); + + void initialize(); + + inline virtual bool doTransferAcl() {return transferAcl;}; + + // create specilied authorise methods for cases that need faster matching as needed. + virtual bool authorise(const std::string& id, const Action& action, const ObjectType& objType, const std::string& name, std::map<Property, std::string>* params=0); + virtual bool authorise(const std::string& id, const Action& action, const ObjectType& objType, const std::string& ExchangeName,const std::string& RoutingKey); + + virtual ~Acl(); +private: + bool result(const AclResult& aclreslt, const std::string& id, const Action& action, const ObjectType& objType, const std::string& name); + bool readAclFile(std::string& errorText); + bool readAclFile(std::string& aclFile, std::string& errorText); + virtual qpid::management::ManagementObject* GetManagementObject(void) const; + virtual management::Manageable::status_t ManagementMethod (uint32_t methodId, management::Args& args, std::string& text); + +}; + + + +}} // namespace qpid::acl + +#endif // QPID_ACL_ACL_H diff --git a/qpid/cpp/src/qpid/acl/AclData.cpp b/qpid/cpp/src/qpid/acl/AclData.cpp new file mode 100644 index 0000000000..658529b270 --- /dev/null +++ b/qpid/cpp/src/qpid/acl/AclData.cpp @@ -0,0 +1,261 @@ +/* + * + * 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. + * + */ + +#include "qpid/acl/AclData.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/IntegerTypes.h" +#include <boost/lexical_cast.hpp> + +namespace qpid { +namespace acl { + +AclData::AclData():decisionMode(qpid::acl::DENY),transferAcl(false),aclSource("UNKNOWN") +{ + for (unsigned int cnt=0; cnt< qpid::acl::ACTIONSIZE; cnt++){ + actionList[cnt]=0; + } + +} + +void AclData::clear () +{ + for (unsigned int cnt=0; cnt< qpid::acl::ACTIONSIZE; cnt++){ + if (actionList[cnt]){ + for (unsigned int cnt1=0; cnt1< qpid::acl::OBJECTSIZE; cnt1++) + delete actionList[cnt][cnt1]; + } + delete[] actionList[cnt]; + } + +} + +bool AclData::matchProp(const std::string & src, const std::string& src1) +{ + // allow wildcard on the end of strings... + if (src.data()[src.size()-1]=='*') { + return (src.compare(0, src.size()-1, src1, 0,src.size()-1 ) == 0); + } else { + return (src.compare(src1)==0) ; + } +} + +AclResult AclData::lookup(const std::string& id, const Action& action, const ObjectType& objType, + const std::string& name, std::map<Property, std::string>* params) { + + QPID_LOG(debug, "ACL: Lookup for id:" << id << " action:" << AclHelper::getActionStr((Action) action) + << " objectType:" << AclHelper::getObjectTypeStr((ObjectType) objType) << " name:" << name + << " with params " << AclHelper::propertyMapToString(params)); + + AclResult aclresult = decisionMode; + if (actionList[action] && actionList[action][objType]) { + AclData::actObjItr itrRule = actionList[action][objType]->find(id); + if (itrRule == actionList[action][objType]->end()) + itrRule = actionList[action][objType]->find("*"); + + if (itrRule != actionList[action][objType]->end()) { + + QPID_LOG(debug, "ACL: checking the following rules for : " << itrRule->first ); + + //loop the vector + for (ruleSetItr i = itrRule->second.begin(); i < itrRule->second.end(); i++) { + QPID_LOG(debug, "ACL: checking rule " << i->toString()); + // loop the names looking for match + bool match = true; + for (propertyMapItr pMItr = i->props.begin(); (pMItr != i->props.end()) && match; pMItr++) { + //match name is exists first + if (pMItr->first == acl::PROP_NAME) { + if (matchProp(pMItr->second, name)){ + QPID_LOG(debug, "ACL: name '" << name << "' matched with name '" + << pMItr->second << "' given in the rule"); + }else{ + match = false; + QPID_LOG(debug, "ACL: name '" << name << "' didn't match with name '" + << pMItr->second << "' given in the rule"); + } + } else if (params) { //match pMItr against params + propertyMapItr paramItr = params->find(pMItr->first); + if (paramItr == params->end()) { + match = false; + QPID_LOG(debug, "ACL: the given parameter map in lookup doesn't contain the property '" + << AclHelper::getPropertyStr(pMItr->first) << "'"); + }else if ( pMItr->first == acl::PROP_MAXQUEUECOUNT || pMItr->first == acl::PROP_MAXQUEUESIZE ) { + if ( pMItr->first == paramItr->first ) { + + uint64_t aclMax = 0; + uint64_t paramMax = 0; + + try{ + aclMax = boost::lexical_cast<uint64_t>(pMItr->second); + }catch(const boost::bad_lexical_cast&){ + match = false; + QPID_LOG(error,"Error evaluating rule. " << + "Illegal value given in ACL source <" << aclSource << + "> for property '" << + AclHelper::getPropertyStr(pMItr->first) << "' : " << + boost::lexical_cast<std::string>(pMItr->second)); + break; + } + + try{ + paramMax = boost::lexical_cast<uint64_t>(paramItr->second); + }catch(const boost::bad_lexical_cast&){ + match = false; + QPID_LOG(error,"Error evaluating rule. " << + "Illegal value given in lookup for property '" << + AclHelper::getPropertyStr(pMItr->first) << "' : " << + boost::lexical_cast<std::string>(paramItr->second)); + break; + } + + QPID_LOG(debug, "ACL: Numeric comparison for property " << + AclHelper::getPropertyStr(paramItr->first) << + " (value given in lookup = " << + boost::lexical_cast<std::string>(paramItr->second) << + ", value give in rule = " << + boost::lexical_cast<std::string>(pMItr->second) << " )"); + + if (( aclMax ) && ( paramMax == 0 || paramMax > aclMax)){ + match = decisionMode == qpid::acl::ALLOW ; + QPID_LOG(debug, "ACL: Limit exceeded and match=" << + (match ? "true": "false") << + " as decision mode is " << AclHelper::getAclResultStr(decisionMode)); + } + } + }else if (matchProp(pMItr->second, paramItr->second)) { + QPID_LOG(debug, "ACL: the pair(" + << AclHelper::getPropertyStr(paramItr->first) << "," << paramItr->second + << ") given in lookup matched the pair(" + << AclHelper::getPropertyStr(pMItr->first) << "," << pMItr->second << ") given in the rule"); + } else { + QPID_LOG(debug, "ACL: the pair(" + << AclHelper::getPropertyStr(paramItr->first) << "," << paramItr->second + << ") given in lookup doesn't match the pair(" + << AclHelper::getPropertyStr(pMItr->first) << "," << pMItr->second << ") given in the rule"); + match = false; + } + } + } + if (match) + { + aclresult = getACLResult(i->logOnly, i->log); + QPID_LOG(debug,"Successful match, the decision is:" << AclHelper::getAclResultStr(aclresult)); + return aclresult; + } + } + } + } + + QPID_LOG(debug,"No successful match, defaulting to the decision mode " << AclHelper::getAclResultStr(aclresult)); + return aclresult; +} + +AclResult AclData::lookup(const std::string& id, const Action& action, const ObjectType& objType, const std::string& /*Exchange*/ name, const std::string& RoutingKey) +{ + + QPID_LOG(debug, "ACL: Lookup for id:" << id << " action:" << AclHelper::getActionStr((Action) action) + << " objectType:" << AclHelper::getObjectTypeStr((ObjectType) objType) << " exchange name:" << name + << " with routing key " << RoutingKey); + + AclResult aclresult = decisionMode; + + if (actionList[action] && actionList[action][objType]){ + AclData::actObjItr itrRule = actionList[action][objType]->find(id); + + if (itrRule == actionList[action][objType]->end()) + itrRule = actionList[action][objType]->find("*"); + + if (itrRule != actionList[action][objType]->end() ) { + + QPID_LOG(debug, "ACL: checking the following rules for : " << itrRule->first ); + + //loop the vector + for (ruleSetItr i=itrRule->second.begin(); i<itrRule->second.end(); i++) { + QPID_LOG(debug, "ACL: checking rule " << i->toString()); + + // loop the names looking for match + bool match =true; + for (propertyMapItr pMItr = i->props.begin(); (pMItr != i->props.end()) && match; pMItr++) + { + //match name is exists first + if (pMItr->first == acl::PROP_NAME){ + if (matchProp(pMItr->second, name)){ + QPID_LOG(debug, "ACL: name '" << name << "' matched with name '" + << pMItr->second << "' given in the rule"); + + }else{ + match= false; + QPID_LOG(debug, "ACL: name '" << name << "' didn't match with name '" + << pMItr->second << "' given in the rule"); + } + }else if (pMItr->first == acl::PROP_ROUTINGKEY){ + if (matchProp(pMItr->second, RoutingKey)){ + QPID_LOG(debug, "ACL: name '" << name << "' matched with routing_key '" + << pMItr->second << "' given in the rule"); + }else{ + match= false; + QPID_LOG(debug, "ACL: name '" << name << "' didn't match with routing_key '" + << pMItr->second << "' given in the rule"); + } + } + } + if (match){ + aclresult = getACLResult(i->logOnly, i->log); + QPID_LOG(debug,"Successful match, the decision is:" << AclHelper::getAclResultStr(aclresult)); + return aclresult; + } + } + } + } + QPID_LOG(debug,"No successful match, defaulting to the decision mode " << AclHelper::getAclResultStr(aclresult)); + return aclresult; + +} + + +AclResult AclData::getACLResult(bool logOnly, bool log) +{ + switch (decisionMode) + { + case qpid::acl::ALLOWLOG: + case qpid::acl::ALLOW: + if (logOnly) return qpid::acl::ALLOWLOG; + if (log) + return qpid::acl::DENYLOG; + else + return qpid::acl::DENY; + + + case qpid::acl::DENYLOG: + case qpid::acl::DENY: + if (logOnly) return qpid::acl::DENYLOG; + if (log) + return qpid::acl::ALLOWLOG; + else + return qpid::acl::ALLOW; + } + + QPID_LOG(error, "ACL Decision Failed, setting DENY"); + return qpid::acl::DENY; +} + +AclData::~AclData() +{ + clear(); +} + +}} diff --git a/qpid/cpp/src/qpid/acl/AclData.h b/qpid/cpp/src/qpid/acl/AclData.h new file mode 100644 index 0000000000..efd3b60145 --- /dev/null +++ b/qpid/cpp/src/qpid/acl/AclData.h @@ -0,0 +1,84 @@ +#ifndef QPID_ACL_ACLDATA_H +#define QPID_ACL_ACLDATA_H + + +/* + * + * 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. + * + */ + +#include "qpid/broker/AclModule.h" +#include <vector> +#include <sstream> + +namespace qpid { +namespace acl { + +class AclData { + + +public: + + typedef std::map<qpid::acl::Property, std::string> propertyMap; + typedef propertyMap::const_iterator propertyMapItr; + struct rule { + + bool log; + bool logOnly; // this is a rule is to log only + + // key value map + //?? + propertyMap props; + + + rule (propertyMap& p):log(false),logOnly(false),props(p) {}; + + std::string toString () const { + std::ostringstream ruleStr; + ruleStr << "[log=" << log << ", logOnly=" << logOnly << " props{"; + for (propertyMapItr pMItr = props.begin(); pMItr != props.end(); pMItr++) { + ruleStr << " " << AclHelper::getPropertyStr((Property) pMItr-> first) << "=" << pMItr->second; + } + ruleStr << " }]"; + return ruleStr.str(); + } + }; + typedef std::vector<rule> ruleSet; + typedef ruleSet::const_iterator ruleSetItr; + typedef std::map<std::string, ruleSet > actionObject; // user + typedef actionObject::iterator actObjItr; + typedef actionObject* aclAction; + + // Action*[] -> Object*[] -> map<user -> set<Rule> > + aclAction* actionList[qpid::acl::ACTIONSIZE]; + qpid::acl::AclResult decisionMode; // determines if the rule set is a deny or allow mode. + bool transferAcl; + std::string aclSource; + + AclResult lookup(const std::string& id, const Action& action, const ObjectType& objType, const std::string& name, std::map<Property, std::string>* params=0); + AclResult lookup(const std::string& id, const Action& action, const ObjectType& objType, const std::string& ExchangeName, const std::string& RoutingKey); + AclResult getACLResult(bool logOnly, bool log); + + bool matchProp(const std::string & src, const std::string& src1); + void clear (); + + AclData(); + virtual ~AclData(); +}; + +}} // namespace qpid::acl + +#endif // QPID_ACL_ACLDATA_H diff --git a/qpid/cpp/src/qpid/acl/AclPlugin.cpp b/qpid/cpp/src/qpid/acl/AclPlugin.cpp new file mode 100644 index 0000000000..e4d721ea44 --- /dev/null +++ b/qpid/cpp/src/qpid/acl/AclPlugin.cpp @@ -0,0 +1,96 @@ +/* + * + * 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. + * + */ + +#include <sstream> +#include "qpid/acl/Acl.h" +#include "qpid/broker/Broker.h" +#include "qpid/Plugin.h" +#include "qpid/Options.h" +#include "qpid/log/Statement.h" + +#include <boost/shared_ptr.hpp> +#include <boost/utility/in_place_factory.hpp> + +namespace qpid { +namespace acl { + +using namespace std; + +/** Note separating options from values to work around boost version differences. + * Old boost takes a reference to options objects, but new boost makes a copy. + * New boost allows a shared_ptr but that's not compatible with old boost. + */ +struct AclOptions : public Options { + AclValues& values; + + AclOptions(AclValues& v) : Options("ACL Options"), values(v) { + addOptions() + ("acl-file", optValue(values.aclFile, "FILE"), "The policy file to load from, loaded from data dir"); + } +}; + +struct AclPlugin : public Plugin { + + AclValues values; + AclOptions options; + boost::intrusive_ptr<Acl> acl; + + AclPlugin() : options(values) {} + + Options* getOptions() { return &options; } + + void init(broker::Broker& b) { + if (values.aclFile.empty()){ + QPID_LOG(info, "Policy file not specified. ACL Disabled, no ACL checking being done!"); + return; + } + + if (acl) throw Exception("ACL plugin cannot be initialized twice in one process."); + + if (values.aclFile.at(0) != '/' && !b.getDataDir().getPath().empty()) { + std::ostringstream oss; + oss << b.getDataDir().getPath() << "/" << values.aclFile; + values.aclFile = oss.str(); + } + + acl = new Acl(values, b); + b.setAcl(acl.get()); + b.addFinalizer(boost::bind(&AclPlugin::shutdown, this)); + } + + template <class T> bool init(Plugin::Target& target) { + T* t = dynamic_cast<T*>(&target); + if (t) init(*t); + return t; + } + + void earlyInitialize(Plugin::Target&) {} + + void initialize(Plugin::Target& target) { + init<broker::Broker>(target); + } + + void shutdown() { acl = 0; } +}; + +static AclPlugin instance; // Static initialization. + +// For test purposes. +boost::intrusive_ptr<Acl> getGlobalAcl() { return instance.acl; } + +}} // namespace qpid::acl diff --git a/qpid/cpp/src/qpid/acl/AclReader.cpp b/qpid/cpp/src/qpid/acl/AclReader.cpp new file mode 100644 index 0000000000..31c69e69b5 --- /dev/null +++ b/qpid/cpp/src/qpid/acl/AclReader.cpp @@ -0,0 +1,581 @@ +/* + * + * 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. + * + */ + +#include "qpid/acl/AclReader.h" + +#include <cctype> +#include <cstring> +#include <fstream> +#include <sstream> +#include "qpid/log/Statement.h" +#include "qpid/Exception.h" + +#include <iomanip> // degug +#include <iostream> // debug + +#define ACL_FORMAT_ERR_LOG_PREFIX "ACL format error: " << fileName << ":" << lineNumber << ": " + +namespace qpid { +namespace acl { + +AclReader::aclRule::aclRule(const AclResult r, const std::string n, const groupMap& groups) : res(r), actionAll(true), objStatus(NONE) { + processName(n, groups); +} +AclReader::aclRule::aclRule(const AclResult r, const std::string n, const groupMap& groups, const Action a) : res(r), actionAll(false), action(a), objStatus(NONE) { + processName(n, groups); +} + +void AclReader::aclRule::setObjectType(const ObjectType o) { + objStatus = VALUE; + object = o; +} + +void AclReader::aclRule::setObjectTypeAll() { + objStatus = ALL; +} + +bool AclReader::aclRule::addProperty(const Property p, const std::string v) { + return props.insert(propNvPair(p, v)).second; +} + +bool AclReader::aclRule::validate(const AclHelper::objectMapPtr& /*validationMap*/) { + // TODO - invalid rules won't ever be called in real life... + return true; +} + +// Debug aid +std::string AclReader::aclRule::toString() { + std::ostringstream oss; + oss << AclHelper::getAclResultStr(res) << " ["; + for (nsCitr itr = names.begin(); itr != names.end(); itr++) { + if (itr != names.begin()) oss << ", "; + oss << *itr; + } + oss << "]"; + if (actionAll) { + oss << " *"; + } else { + oss << " " << AclHelper::getActionStr(action); + } + if (objStatus == ALL) { + oss << " *"; + } else if (objStatus == VALUE) { + oss << " " << AclHelper::getObjectTypeStr(object); + } + for (pmCitr i=props.begin(); i!=props.end(); i++) { + oss << " " << AclHelper::getPropertyStr(i->first) << "=" << i->second; + } + return oss.str(); +} + +void AclReader::loadDecisionData(boost::shared_ptr<AclData> d) { + d->clear(); + QPID_LOG(debug, "ACL Load Rules"); + int cnt = rules.size(); + bool foundmode = false; + + for (rlCitr i = rules.end(); cnt; cnt--) { + i--; + QPID_LOG(debug, "ACL Processing " << std::setfill(' ') << std::setw(2) + << cnt << " " << (*i)->toString()); + + if (!foundmode && (*i)->actionAll && (*i)->names.size() == 1 + && (*((*i)->names.begin())).compare("*") == 0) { + d->decisionMode = (*i)->res; + QPID_LOG(debug, "ACL FoundMode " + << AclHelper::getAclResultStr(d->decisionMode)); + foundmode = true; + } else { + AclData::rule rule((*i)->props); + bool addrule = true; + + switch ((*i)->res) { + case qpid::acl::ALLOWLOG: + rule.log = true; + if (d->decisionMode == qpid::acl::ALLOW || + d->decisionMode == qpid::acl::ALLOWLOG) + rule.logOnly = true; + break; + case qpid::acl::ALLOW: + if (d->decisionMode == qpid::acl::ALLOW || + d->decisionMode == qpid::acl::ALLOWLOG) + addrule = false; + break; + case qpid::acl::DENYLOG: + rule.log = true; + if (d->decisionMode == qpid::acl::DENY || + d->decisionMode == qpid::acl::DENYLOG) + rule.logOnly = true; + break; + case qpid::acl::DENY: + if (d->decisionMode == qpid::acl::DENY || + d->decisionMode == qpid::acl::DENYLOG) + addrule = false; + break; + default: + throw Exception("Invalid ACL Result loading rules."); + } + + // Action -> Object -> map<user -> set<Rule> > + if (addrule) { + std::ostringstream actionstr; + for (int acnt = ((*i)->actionAll ? 0 : (*i)->action); + acnt < acl::ACTIONSIZE; + (*i)->actionAll ? acnt++ : acnt = acl::ACTIONSIZE) { + + if (acnt == acl::ACT_PUBLISH) + d->transferAcl = true; // we have transfer ACL + + actionstr << AclHelper::getActionStr((Action) acnt) << ","; + + //find the Action, create if not exist + if (d->actionList[acnt] == NULL) { + d->actionList[acnt] = + new AclData::aclAction[qpid::acl::OBJECTSIZE]; + for (int j = 0; j < qpid::acl::OBJECTSIZE; j++) + d->actionList[acnt][j] = NULL; + } + + // optimize this loop to limit to valid options only!! + for (int ocnt = ((*i)->objStatus != aclRule::VALUE ? 0 + : (*i)->object); + ocnt < acl::OBJECTSIZE; + (*i)->objStatus != aclRule::VALUE ? ocnt++ : ocnt = acl::OBJECTSIZE) { + + //find the Object, create if not exist + if (d->actionList[acnt][ocnt] == NULL) + d->actionList[acnt][ocnt] = + new AclData::actionObject; + + // add users and Rule to object set + bool allNames = false; + // check to see if names.begin is '*' + if ((*(*i)->names.begin()).compare("*") == 0) + allNames = true; + + for (nsCitr itr = (allNames ? names.begin() + : (*i)->names.begin()); + itr != (allNames ? names.end() : (*i)->names.end()); + itr++) { + + AclData::actObjItr itrRule = + d->actionList[acnt][ocnt]->find(*itr); + + if (itrRule == d->actionList[acnt][ocnt]->end()) { + AclData::ruleSet rSet; + rSet.push_back(rule); + d->actionList[acnt][ocnt]->insert + (make_pair(std::string(*itr), rSet)); + } else { + // TODO add code to check for dead rules + // allow peter create queue name=tmp <-- dead rule!! + // allow peter create queue + + itrRule->second.push_back(rule); + } + } + + } + } + + std::ostringstream objstr; + for (int ocnt = ((*i)->objStatus != aclRule::VALUE ? 0 : (*i)->object); + ocnt < acl::OBJECTSIZE; + (*i)->objStatus != aclRule::VALUE ? ocnt++ : ocnt = acl::OBJECTSIZE) { + objstr << AclHelper::getObjectTypeStr((ObjectType) ocnt) << ","; + } + + bool allNames = ((*(*i)->names.begin()).compare("*") == 0); + std::ostringstream userstr; + for (nsCitr itr = (allNames ? names.begin() : (*i)->names.begin()); + itr != (allNames ? names.end() : (*i)->names.end()); + itr++) { + userstr << *itr << ","; + } + + QPID_LOG(debug, "ACL: Adding actions {" << + actionstr.str().substr(0,actionstr.str().length()-1) + << "} to objects {" << + objstr.str().substr(0,objstr.str().length()-1) + << "} with props " << + AclHelper::propertyMapToString(&rule.props) + << " for users {" << + userstr.str().substr(0,userstr.str().length()-1) + << "}" ); + } else { + QPID_LOG(debug, "ACL Skipping based on Mode:" + << AclHelper::getAclResultStr(d->decisionMode)); + } + } + + } + +} + + +void AclReader::aclRule::processName(const std::string& name, const groupMap& groups) { + if (name.compare("all") == 0) { + names.insert("*"); + } else { + gmCitr itr = groups.find(name); + if (itr == groups.end()) { + names.insert(name); + } else { + names.insert(itr->second->begin(), itr->second->end()); + } + } +} + +AclReader::AclReader() : lineNumber(0), contFlag(false), validationMap(new AclHelper::objectMap) { + AclHelper::loadValidationMap(validationMap); + names.insert("*"); +} + +AclReader::~AclReader() {} + +std::string AclReader::getError() { + return errorStream.str(); +} + +int AclReader::read(const std::string& fn, boost::shared_ptr<AclData> d) { + fileName = fn; + lineNumber = 0; + char buff[1024]; + std::ifstream ifs(fn.c_str(), std::ios_base::in); + if (!ifs.good()) { + errorStream << "Unable to open ACL file \"" << fn << "\": eof=" << (ifs.eof()?"T":"F") << "; fail=" << (ifs.fail()?"T":"F") << "; bad=" << (ifs.bad()?"T":"F"); + return -1; + } + try { + bool err = false; + while (ifs.good()) { + ifs.getline(buff, 1024); + lineNumber++; + if (std::strlen(buff) > 0 && buff[0] != '#') // Ignore blank lines and comments + err |= !processLine(buff); + } + if (!ifs.eof()) + { + errorStream << "Unable to read ACL file \"" << fn << "\": eof=" << (ifs.eof()?"T":"F") << "; fail=" << (ifs.fail()?"T":"F") << "; bad=" << (ifs.bad()?"T":"F"); + ifs.close(); + return -2; + } + ifs.close(); + if (err) return -3; + QPID_LOG(notice, "Read ACL file \"" << fn << "\""); + } catch (const std::exception& e) { + errorStream << "Unable to read ACL file \"" << fn << "\": " << e.what(); + ifs.close(); + return -4; + } catch (...) { + errorStream << "Unable to read ACL file \"" << fn << "\": Unknown exception"; + ifs.close(); + return -5; + } + printNames(); + printRules(); + loadDecisionData(d); + + return 0; +} + +bool AclReader::processLine(char* line) { + bool ret = false; + std::vector<std::string> toks; + + // Check for continuation + char* contCharPtr = std::strrchr(line, '\\'); + bool cont = contCharPtr != 0; + if (cont) *contCharPtr = 0; + + int numToks = tokenize(line, toks); + + if (cont && numToks == 0){ + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line \"" << lineNumber << "\" contains an illegal extension."; + return false; + } + + if (numToks && (toks[0].compare("group") == 0 || contFlag)) { + ret = processGroupLine(toks, cont); + } else if (numToks && toks[0].compare("acl") == 0) { + ret = processAclLine(toks); + } else { + // Check for whitespace only line, ignore these + bool ws = true; + for (unsigned i=0; i<std::strlen(line) && ws; i++) { + if (!std::isspace(line[i])) ws = false; + } + if (ws) { + ret = true; + } else { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Non-continuation line must start with \"group\" or \"acl\"."; + ret = false; + } + } + contFlag = cont; + return ret; +} + +int AclReader::tokenize(char* line, std::vector<std::string>& toks) { + const char* tokChars = " \t\n\f\v\r"; + int cnt = 0; + char* cp = std::strtok(line, tokChars); + while (cp != 0) { + toks.push_back(std::string(cp)); + cnt++; + cp = std::strtok(0, tokChars); + } + return cnt; +} + +// Return true if the line is successfully processed without errors +// If cont is true, then groupName must be set to the continuation group name +bool AclReader::processGroupLine(tokList& toks, const bool cont) { + const unsigned toksSize = toks.size(); + + if (contFlag) { + gmCitr citr = groups.find(groupName); + for (unsigned i = 0; i < toksSize; i++) { + if (!isValidUserName(toks[i])) return false; + addName(toks[i], citr->second); + } + } else { + const unsigned minimumSize = (cont ? 2 : 3); + if (toksSize < minimumSize) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Insufficient tokens for group definition."; + return false; + } + if (!isValidGroupName(toks[1])) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Group name \"" << toks[1] << "\" contains illegal characters."; + return false; + } + gmCitr citr = addGroup(toks[1]); + if (citr == groups.end()) return false; + for (unsigned i = 2; i < toksSize; i++) { + if (!isValidUserName(toks[i])) return false; + addName(toks[i], citr->second); + } + } + return true; +} + +// Return true if sucessfully added group +AclReader::gmCitr AclReader::addGroup(const std::string& newGroupName) { + gmCitr citr = groups.find(newGroupName); + if (citr != groups.end()) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Duplicate group name \"" << newGroupName << "\"."; + return groups.end(); + } + groupPair p(newGroupName, nameSetPtr(new nameSet)); + gmRes res = groups.insert(p); + assert(res.second); + groupName = newGroupName; + return res.first; +} + +void AclReader::addName(const std::string& name, nameSetPtr groupNameSet) { + gmCitr citr = groups.find(name); + if (citr != groups.end() && citr->first != name){ + // This is a previously defined group: add all the names in that group to this group + groupNameSet->insert(citr->second->begin(), citr->second->end()); + } else { + // Not a known group name + groupNameSet->insert(name); + addName(name); + } +} + +void AclReader::addName(const std::string& name) { + names.insert(name); +} + +// Debug aid +void AclReader::printNames() const { + QPID_LOG(debug, "Group list: " << groups.size() << " groups found:" ); + std::string tmp; + for (gmCitr i=groups.begin(); i!= groups.end(); i++) { + tmp += " \""; + tmp += i->first; + tmp += "\":"; + for (nsCitr j=i->second->begin(); j!=i->second->end(); j++) { + tmp += " "; + tmp += *j; + } + QPID_LOG(debug, tmp); + tmp.clear(); + } + QPID_LOG(debug, "Name list: " << names.size() << " names found:" ); + tmp.clear(); + for (nsCitr k=names.begin(); k!=names.end(); k++) { + tmp += " "; + tmp += *k; + } + QPID_LOG(debug, tmp); +} + +bool AclReader::processAclLine(tokList& toks) { + const unsigned toksSize = toks.size(); + if (toksSize < 4) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Insufficient tokens for acl definition."; + return false; + } + + AclResult res; + try { + res = AclHelper::getAclResult(toks[1]); + } catch (...) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Unknown ACL permission \"" << toks[1] << "\"."; + return false; + } + + bool actionAllFlag = toks[3].compare("all") == 0; + bool userAllFlag = toks[2].compare("all") == 0; + Action action; + if (actionAllFlag) { + + if (userAllFlag && toksSize > 4) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Tokens found after action \"all\"."; + return false; + } + action = ACT_CONSUME; // dummy; compiler must initialize action for this code path + } else { + try { + action = AclHelper::getAction(toks[3]); + } catch (...) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Unknown action \"" << toks[3] << "\"."; + return false; + } + } + + // Create rule obj; then add object (if any) and properties (if any) + aclRulePtr rule; + if (actionAllFlag) { + rule.reset(new aclRule(res, toks[2], groups)); + } else { + rule.reset(new aclRule(res, toks[2], groups, action)); + } + + if (toksSize >= 5) { // object name-value pair + if (toks[4].compare("all") == 0) { + rule->setObjectTypeAll(); + } else { + try { + rule->setObjectType(AclHelper::getObjectType(toks[4])); + } catch (...) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Unknown object \"" << toks[4] << "\"."; + return false; + } + } + } + + if (toksSize >= 6) { // property name-value pair(s) + for (unsigned i=5; i<toksSize; i++) { + nvPair propNvp = splitNameValuePair(toks[i]); + if (propNvp.second.size() == 0) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + <<", Badly formed property name-value pair \"" + << propNvp.first << "\". (Must be name=value)"; + return false; + } + Property prop; + try { + prop = AclHelper::getProperty(propNvp.first); + } catch (...) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Unknown property \"" << propNvp.first << "\"."; + return false; + } + rule->addProperty(prop, propNvp.second); + } + } + // Check if name (toks[2]) is group; if not, add as name of individual + if (toks[2].compare("all") != 0) { + if (groups.find(toks[2]) == groups.end()) { + addName(toks[2]); + } + } + + // If rule validates, add to rule list + if (!rule->validate(validationMap)) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Invalid object/action/property combination."; + return false; + } + rules.push_back(rule); + + return true; +} + +// Debug aid +void AclReader::printRules() const { + QPID_LOG(debug, "Rule list: " << rules.size() << " ACL rules found:"); + int cnt = 0; + for (rlCitr i=rules.begin(); i<rules.end(); i++,cnt++) { + QPID_LOG(debug, " " << std::setfill(' ') << std::setw(2) << cnt << " " << (*i)->toString()); + } +} + +// Static function +// Return true if the name is well-formed (ie contains legal characters) +bool AclReader::isValidGroupName(const std::string& name) { + for (unsigned i=0; i<name.size(); i++) { + const char ch = name.at(i); + if (!std::isalnum(ch) && ch != '-' && ch != '_') return false; + } + return true; +} + +// Static function +// Split name-value pair around '=' char of the form "name=value" +AclReader::nvPair AclReader::splitNameValuePair(const std::string& nvpString) { + std::size_t pos = nvpString.find("="); + if (pos == std::string::npos || pos == nvpString.size() - 1) { + return nvPair(nvpString, ""); + } + return nvPair(nvpString.substr(0, pos), nvpString.substr(pos+1)); +} + +// Returns true if a username has the name@realm format +bool AclReader::isValidUserName(const std::string& name){ + size_t pos = name.find('@'); + if ( pos == std::string::npos || pos == name.length() -1){ + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Username '" << name << "' must contain a realm"; + return false; + } + for (unsigned i=0; i<name.size(); i++) { + const char ch = name.at(i); + if (!std::isalnum(ch) && ch != '-' && ch != '_' && ch != '@' && ch != '.' && ch != '/'){ + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Username \"" << name << "\" contains illegal characters."; + return false; + } + } + return true; +} + +}} // namespace qpid::acl diff --git a/qpid/cpp/src/qpid/acl/AclReader.h b/qpid/cpp/src/qpid/acl/AclReader.h new file mode 100644 index 0000000000..62c6f38f37 --- /dev/null +++ b/qpid/cpp/src/qpid/acl/AclReader.h @@ -0,0 +1,118 @@ +#ifndef QPID_ACL_ACLREADER_H +#define QPID_ACL_ACLREADER_H + + +/* + * + * 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. + * + */ + +#include <boost/shared_ptr.hpp> +#include <map> +#include <set> +#include <string> +#include <vector> +#include <sstream> +#include "qpid/acl/AclData.h" +#include "qpid/broker/AclModule.h" + +namespace qpid { +namespace acl { + +class AclReader { + typedef std::set<std::string> nameSet; + typedef nameSet::const_iterator nsCitr; + typedef boost::shared_ptr<nameSet> nameSetPtr; + + typedef std::pair<std::string, nameSetPtr> groupPair; + typedef std::map<std::string, nameSetPtr> groupMap; + typedef groupMap::const_iterator gmCitr; + typedef std::pair<gmCitr, bool> gmRes; + + typedef std::pair<Property, std::string> propNvPair; + typedef std::map<Property, std::string> propMap; + typedef propMap::const_iterator pmCitr; + + class aclRule { + public: + enum objectStatus {NONE, VALUE, ALL}; + AclResult res; + nameSet names; + bool actionAll; // True if action is set to keyword "all" + Action action; // Ignored if action is set to keyword "all" + objectStatus objStatus; + ObjectType object; // Ignored for all status values except VALUE + propMap props; + public: + aclRule(const AclResult r, const std::string n, const groupMap& groups); // action = "all" + aclRule(const AclResult r, const std::string n, const groupMap& groups, const Action a); + void setObjectType(const ObjectType o); + void setObjectTypeAll(); + bool addProperty(const Property p, const std::string v); + bool validate(const AclHelper::objectMapPtr& validationMap); + std::string toString(); // debug aid + private: + void processName(const std::string& name, const groupMap& groups); + }; + typedef boost::shared_ptr<aclRule> aclRulePtr; + typedef std::vector<aclRulePtr> ruleList; + typedef ruleList::const_iterator rlCitr; + + typedef std::vector<std::string> tokList; + typedef tokList::const_iterator tlCitr; + + typedef std::set<std::string> keywordSet; + typedef keywordSet::const_iterator ksCitr; + typedef std::pair<std::string, std::string> nvPair; // Name-Value pair + + std::string fileName; + int lineNumber; + bool contFlag; + std::string groupName; + nameSet names; + groupMap groups; + ruleList rules; + AclHelper::objectMapPtr validationMap; + std::ostringstream errorStream; + + public: + AclReader(); + virtual ~AclReader(); + int read(const std::string& fn, boost::shared_ptr<AclData> d); + std::string getError(); + + private: + bool processLine(char* line); + void loadDecisionData( boost::shared_ptr<AclData> d); + int tokenize(char* line, tokList& toks); + + bool processGroupLine(tokList& toks, const bool cont); + gmCitr addGroup(const std::string& groupName); + void addName(const std::string& name, nameSetPtr groupNameSet); + void addName(const std::string& name); + void printNames() const; // debug aid + + bool processAclLine(tokList& toks); + void printRules() const; // debug aid + bool isValidUserName(const std::string& name); + + static bool isValidGroupName(const std::string& name); + static nvPair splitNameValuePair(const std::string& nvpString); +}; + +}} // namespace qpid::acl + +#endif // QPID_ACL_ACLREADER_H diff --git a/qpid/cpp/src/qpid/acl/AclValidator.cpp b/qpid/cpp/src/qpid/acl/AclValidator.cpp new file mode 100644 index 0000000000..57b68e520a --- /dev/null +++ b/qpid/cpp/src/qpid/acl/AclValidator.cpp @@ -0,0 +1,150 @@ +/* + * + * 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. + * + */ + +#include "qpid/acl/AclValidator.h" +#include "qpid/acl/AclData.h" +#include "qpid/Exception.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/IntegerTypes.h" +#include <boost/lexical_cast.hpp> +#include <boost/bind.hpp> +#include <numeric> +#include <sstream> + +namespace qpid { +namespace acl { + +AclValidator::IntPropertyType::IntPropertyType(int64_t i,int64_t j) : min(i), max(j){ +} + +bool AclValidator::IntPropertyType::validate(const std::string& val) { + int64_t v; + try + { + v = boost::lexical_cast<int64_t>(val); + }catch(const boost::bad_lexical_cast&){ + return 0; + } + + if (v < min || v >= max){ + return 0; + }else{ + return 1; + } +} + +std::string AclValidator::IntPropertyType::allowedValues() { + return "values should be between " + + boost::lexical_cast<std::string>(min) + " and " + + boost::lexical_cast<std::string>(max); +} + +AclValidator::EnumPropertyType::EnumPropertyType(std::vector<std::string>& allowed): values(allowed){ +} + +bool AclValidator::EnumPropertyType::validate(const std::string& val) { + for (std::vector<std::string>::iterator itr = values.begin(); itr != values.end(); ++itr ){ + if (val.compare(*itr) == 0){ + return 1; + } + } + + return 0; +} + +std::string AclValidator::EnumPropertyType::allowedValues() { + std::ostringstream oss; + oss << "possible values are one of { "; + for (std::vector<std::string>::iterator itr = values.begin(); itr != values.end(); itr++ ){ + oss << "'" << *itr << "' "; + } + oss << "}"; + return oss.str(); +} + +AclValidator::AclValidator(){ + validators.insert(Validator(acl::PROP_MAXQUEUESIZE, + boost::shared_ptr<PropertyType>( + new IntPropertyType(0,std::numeric_limits<int64_t>::max())) + ) + ); + + validators.insert(Validator(acl::PROP_MAXQUEUECOUNT, + boost::shared_ptr<PropertyType>( + new IntPropertyType(0,std::numeric_limits<int64_t>::max())) + ) + ); + + std::string policyTypes[] = {"ring", "ring_strict", "flow_to_disk", "reject"}; + std::vector<std::string> v(policyTypes, policyTypes + sizeof(policyTypes) / sizeof(std::string)); + validators.insert(Validator(acl::PROP_POLICYTYPE, + boost::shared_ptr<PropertyType>(new EnumPropertyType(v)) + ) + ); + +} + +AclValidator::~AclValidator(){ +} + +/* Iterate through the data model and validate the parameters. */ +void AclValidator::validate(boost::shared_ptr<AclData> d) { + + for (unsigned int cnt=0; cnt< qpid::acl::ACTIONSIZE; cnt++){ + + if (d->actionList[cnt]){ + + for (unsigned int cnt1=0; cnt1< qpid::acl::OBJECTSIZE; cnt1++){ + + if (d->actionList[cnt][cnt1]){ + + std::for_each(d->actionList[cnt][cnt1]->begin(), + d->actionList[cnt][cnt1]->end(), + boost::bind(&AclValidator::validateRuleSet, this, _1)); + }//if + }//for + }//if + }//for +} + +void AclValidator::validateRuleSet(std::pair<const std::string, qpid::acl::AclData::ruleSet>& rules){ + std::for_each(rules.second.begin(), + rules.second.end(), + boost::bind(&AclValidator::validateRule, this, _1)); +} + +void AclValidator::validateRule(qpid::acl::AclData::rule& rule){ + std::for_each(rule.props.begin(), + rule.props.end(), + boost::bind(&AclValidator::validateProperty, this, _1)); +} + +void AclValidator::validateProperty(std::pair<const qpid::acl::Property, std::string>& prop){ + ValidatorItr itr = validators.find(prop.first); + if (itr != validators.end()){ + QPID_LOG(debug,"Found validator for property " << itr->second->allowedValues()); + + if (!itr->second->validate(prop.second)){ + throw Exception( prop.second + " is not a valid value for '" + + AclHelper::getPropertyStr(prop.first) + "', " + + itr->second->allowedValues()); + } + } +} + +}} diff --git a/qpid/cpp/src/qpid/acl/AclValidator.h b/qpid/cpp/src/qpid/acl/AclValidator.h new file mode 100644 index 0000000000..966e5d326b --- /dev/null +++ b/qpid/cpp/src/qpid/acl/AclValidator.h @@ -0,0 +1,83 @@ +#ifndef QPID_ACL_ACLVALIDATOR_H +#define QPID_ACL_ACLVALIDATOR_H + + +/* + * + * 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. + * + */ + +#include "qpid/broker/AclModule.h" +#include "qpid/acl/AclData.h" +#include "qpid/sys/IntegerTypes.h" +#include <boost/shared_ptr.hpp> +#include <vector> +#include <sstream> + +namespace qpid { +namespace acl { + +class AclValidator { + + /* Base Property */ + class PropertyType{ + + public: + virtual ~PropertyType(){}; + virtual bool validate(const std::string& val)=0; + virtual std::string allowedValues()=0; + }; + + class IntPropertyType : public PropertyType{ + int64_t min; + int64_t max; + + public: + IntPropertyType(int64_t min,int64_t max); + virtual ~IntPropertyType (){}; + virtual bool validate(const std::string& val); + virtual std::string allowedValues(); + }; + + class EnumPropertyType : public PropertyType{ + std::vector<std::string> values; + + public: + EnumPropertyType(std::vector<std::string>& allowed); + virtual ~EnumPropertyType (){}; + virtual bool validate(const std::string& val); + virtual std::string allowedValues(); + }; + + typedef std::pair<acl::Property,boost::shared_ptr<PropertyType> > Validator; + typedef std::map<acl::Property,boost::shared_ptr<PropertyType> > ValidatorMap; + typedef ValidatorMap::iterator ValidatorItr; + + ValidatorMap validators; + +public: + + void validateRuleSet(std::pair<const std::string, qpid::acl::AclData::ruleSet>& rules); + void validateRule(qpid::acl::AclData::rule& rule); + void validateProperty(std::pair<const qpid::acl::Property, std::string>& prop); + void validate(boost::shared_ptr<AclData> d); + AclValidator(); + ~AclValidator(); +}; + +}} // namespace qpid::acl + +#endif // QPID_ACL_ACLVALIDATOR_H diff --git a/qpid/cpp/src/qpid/acl/management-schema.xml b/qpid/cpp/src/qpid/acl/management-schema.xml new file mode 100644 index 0000000000..7f48a9be34 --- /dev/null +++ b/qpid/cpp/src/qpid/acl/management-schema.xml @@ -0,0 +1,44 @@ +<schema package="org.apache.qpid.acl"> + +<!-- + * Copyright (c) 2008 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. +--> + + <class name="Acl"> + <property name="brokerRef" type="objId" references="org.apache.qpid.broker:Broker" access="RO" index="y" parentRef="y"/> + <property name="policyFile" type="lstr" access="RO" desc="Name of the policy file"/> + <property name="enforcingAcl" type="bool" access="RO" desc="Currently Enforcing ACL"/> + <property name="transferAcl" type="bool" access="RO" desc="Any transfer ACL rules in force"/> + <property name="lastAclLoad" type="absTime" access="RO" desc="Timestamp of last successful load of ACL"/> + <statistic name="aclDenyCount" type="count64" unit="request" desc="Number of ACL requests denied"/> + + <method name="reloadACLFile" desc="Reload the ACL file"/> + </class> + + <eventArguments> + <arg name="action" type="sstr"/> + <arg name="arguments" type="map"/> + <arg name="objectName" type="sstr"/> + <arg name="objectType" type="sstr"/> + <arg name="reason" type="lstr"/> + <arg name="userId" type="sstr"/> + </eventArguments> + + <event name="allow" sev="inform" args="userId, action, objectType, objectName, arguments"/> + <event name="deny" sev="notice" args="userId, action, objectType, objectName, arguments"/> + <event name="fileLoaded" sev="inform" args="userId"/> + <event name="fileLoadFailed" sev="error" args="userId, reason"/> + +</schema> diff --git a/qpid/cpp/src/qpid/agent/ManagementAgentImpl.cpp b/qpid/cpp/src/qpid/agent/ManagementAgentImpl.cpp new file mode 100644 index 0000000000..633401ef5b --- /dev/null +++ b/qpid/cpp/src/qpid/agent/ManagementAgentImpl.cpp @@ -0,0 +1,1390 @@ + +// +// 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/management/Manageable.h" +#include "qpid/management/ManagementObject.h" +#include "qpid/log/Statement.h" +#include "qpid/agent/ManagementAgentImpl.h" +#include "qpid/amqp_0_10/Codecs.h" +#include <list> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#include <iostream> +#include <fstream> +#include <boost/lexical_cast.hpp> + +using namespace qpid::client; +using namespace qpid::framing; +using namespace qpid::management; +using namespace qpid::sys; +using namespace std; +using std::stringstream; +using std::ofstream; +using std::ifstream; +using std::string; +using std::endl; +using qpid::types::Variant; +using qpid::amqp_0_10::MapCodec; +using qpid::amqp_0_10::ListCodec; + +namespace { + qpid::sys::Mutex lock; + bool disabled = false; + ManagementAgent* agent = 0; + int refCount = 0; + + const string defaultVendorName("vendor"); + const string defaultProductName("product"); + + // Create a valid binding key substring by + // replacing all '.' chars with '_' + const string keyifyNameStr(const string& name) + { + string n2 = name; + + size_t pos = n2.find('.'); + while (pos != n2.npos) { + n2.replace(pos, 1, "_"); + pos = n2.find('.', pos); + } + return n2; + } +} + +ManagementAgent::Singleton::Singleton(bool disableManagement) +{ + sys::Mutex::ScopedLock _lock(lock); + if (disableManagement && !disabled) { + disabled = true; + assert(refCount == 0); // can't disable after agent has been allocated + } + if (refCount == 0 && !disabled) + agent = new ManagementAgentImpl(); + refCount++; +} + +ManagementAgent::Singleton::~Singleton() +{ + sys::Mutex::ScopedLock _lock(lock); + refCount--; + if (refCount == 0 && !disabled) { + delete agent; + agent = 0; + } +} + +ManagementAgent* ManagementAgent::Singleton::getInstance() +{ + return agent; +} + +const string ManagementAgentImpl::storeMagicNumber("MA02"); + +ManagementAgentImpl::ManagementAgentImpl() : + interval(10), extThread(false), pipeHandle(0), notifyCallback(0), notifyContext(0), + notifyable(0), inCallback(false), + initialized(false), connected(false), useMapMsg(false), lastFailure("never connected"), + topicExchange("qmf.default.topic"), directExchange("qmf.default.direct"), + schemaTimestamp(Duration(EPOCH, now())), + publishAllData(true), requestedBrokerBank(0), requestedAgentBank(0), + assignedBrokerBank(0), assignedAgentBank(0), bootSequence(0), + maxV2ReplyObjs(10), // KAG todo: make this a tuneable parameter + connThreadBody(*this), connThread(connThreadBody), + pubThreadBody(*this), pubThread(pubThreadBody) +{ +} + +ManagementAgentImpl::~ManagementAgentImpl() +{ + // shutdown & cleanup all threads + connThreadBody.close(); + pubThreadBody.close(); + + connThread.join(); + pubThread.join(); + + if (pipeHandle) { + delete pipeHandle; + pipeHandle = 0; + } +} + +void ManagementAgentImpl::setName(const string& vendor, const string& product, const string& instance) +{ + if (vendor.find(':') != vendor.npos) { + throw Exception("vendor string cannot contain a ':' character."); + } + if (product.find(':') != product.npos) { + throw Exception("product string cannot contain a ':' character."); + } + + attrMap["_vendor"] = vendor; + attrMap["_product"] = product; + if (!instance.empty()) { + attrMap["_instance"] = instance; + } +} + + +void ManagementAgentImpl::getName(string& vendor, string& product, string& instance) +{ + vendor = std::string(attrMap["_vendor"]); + product = std::string(attrMap["_product"]); + instance = std::string(attrMap["_instance"]); +} + + +const std::string& ManagementAgentImpl::getAddress() +{ + return name_address; +} + + +void ManagementAgentImpl::init(const string& brokerHost, + uint16_t brokerPort, + uint16_t intervalSeconds, + bool useExternalThread, + const string& _storeFile, + const string& uid, + const string& pwd, + const string& mech, + const string& proto) +{ + management::ConnectionSettings settings; + settings.protocol = proto; + settings.host = brokerHost; + settings.port = brokerPort; + settings.username = uid; + settings.password = pwd; + settings.mechanism = mech; + settings.heartbeat = 10; + init(settings, intervalSeconds, useExternalThread, _storeFile); +} + +void ManagementAgentImpl::init(const qpid::management::ConnectionSettings& settings, + uint16_t intervalSeconds, + bool useExternalThread, + const string& _storeFile) +{ + std::string cfgVendor, cfgProduct, cfgInstance; + + interval = intervalSeconds; + extThread = useExternalThread; + storeFile = _storeFile; + nextObjectId = 1; + + // + // Convert from management::ConnectionSettings to client::ConnectionSettings + // + connectionSettings.protocol = settings.protocol; + connectionSettings.host = settings.host; + connectionSettings.port = settings.port; + connectionSettings.virtualhost = settings.virtualhost; + connectionSettings.username = settings.username; + connectionSettings.password = settings.password; + connectionSettings.mechanism = settings.mechanism; + connectionSettings.locale = settings.locale; + connectionSettings.heartbeat = settings.heartbeat; + connectionSettings.maxChannels = settings.maxChannels; + connectionSettings.maxFrameSize = settings.maxFrameSize; + connectionSettings.bounds = settings.bounds; + connectionSettings.tcpNoDelay = settings.tcpNoDelay; + connectionSettings.service = settings.service; + connectionSettings.minSsf = settings.minSsf; + connectionSettings.maxSsf = settings.maxSsf; + + retrieveData(cfgVendor, cfgProduct, cfgInstance); + + bootSequence++; + if ((bootSequence & 0xF000) != 0) + bootSequence = 1; + + // setup the agent's name. The name may be set via a call to setName(). If setName() + // has not been called, the name can be read from the configuration file. If there is + // no name in the configuration file, a unique default name is provided. + if (attrMap.empty()) { + // setName() never called by application, so use names retrieved from config, otherwise defaults. + setName(cfgVendor.empty() ? defaultVendorName : cfgVendor, + cfgProduct.empty() ? defaultProductName : cfgProduct, + cfgInstance.empty() ? qpid::types::Uuid(true).str() : cfgInstance); + } else if (attrMap.find("_instance") == attrMap.end()) { + // setName() called, but instance was not specified, use config or generate a uuid + setName(attrMap["_vendor"].asString(), attrMap["_product"].asString(), + cfgInstance.empty() ? qpid::types::Uuid(true).str() : cfgInstance); + } + + name_address = attrMap["_vendor"].asString() + ":" + attrMap["_product"].asString() + ":" + attrMap["_instance"].asString(); + vendorNameKey = keyifyNameStr(attrMap["_vendor"].asString()); + productNameKey = keyifyNameStr(attrMap["_product"].asString()); + instanceNameKey = keyifyNameStr(attrMap["_instance"].asString()); + attrMap["_name"] = name_address; + + storeData(true); + + QPID_LOG(info, "QMF Agent Initialized: broker=" << settings.host << ":" << settings.port << + " interval=" << intervalSeconds << " storeFile=" << _storeFile << " name=" << name_address); + + initialized = true; +} + +void ManagementAgentImpl::registerClass(const string& packageName, + const string& className, + uint8_t* md5Sum, + ManagementObject::writeSchemaCall_t schemaCall) +{ + sys::Mutex::ScopedLock lock(agentLock); + PackageMap::iterator pIter = findOrAddPackage(packageName); + addClassLocal(ManagementItem::CLASS_KIND_TABLE, pIter, className, md5Sum, schemaCall); +} + +void ManagementAgentImpl::registerEvent(const string& packageName, + const string& eventName, + uint8_t* md5Sum, + ManagementObject::writeSchemaCall_t schemaCall) +{ + sys::Mutex::ScopedLock lock(agentLock); + PackageMap::iterator pIter = findOrAddPackage(packageName); + addClassLocal(ManagementItem::CLASS_KIND_EVENT, pIter, eventName, md5Sum, schemaCall); +} + +// old-style add object: 64bit id - deprecated +ObjectId ManagementAgentImpl::addObject(ManagementObject* object, + uint64_t persistId) +{ + std::string key; + if (persistId) { + key = boost::lexical_cast<std::string>(persistId); + } + return addObject(object, key, persistId != 0); +} + + +// new style add object - use this approach! +ObjectId ManagementAgentImpl::addObject(ManagementObject* object, + const std::string& key, + bool persistent) +{ + sys::Mutex::ScopedLock lock(addLock); + + uint16_t sequence = persistent ? 0 : bootSequence; + + ObjectId objectId(&attachment, 0, sequence); + if (key.empty()) + objectId.setV2Key(*object); // let object generate the key + else + objectId.setV2Key(key); + objectId.setAgentName(name_address); + + object->setObjectId(objectId); + newManagementObjects[objectId] = boost::shared_ptr<ManagementObject>(object); + return objectId; +} + + +void ManagementAgentImpl::raiseEvent(const ManagementEvent& event, severity_t severity) +{ + static const std::string severityStr[] = { + "emerg", "alert", "crit", "error", "warn", + "note", "info", "debug" + }; + string content; + stringstream key; + Variant::Map headers; + + { + 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; + + Variant::List list; + list.push_back(map_); + ListCodec::encode(list, content); + } + + connThreadBody.sendBuffer(content, "", headers, topicExchange, key.str(), "amqp/list"); +} + +uint32_t ManagementAgentImpl::pollCallbacks(uint32_t callLimit) +{ + sys::Mutex::ScopedLock lock(agentLock); + + if (inCallback) { + QPID_LOG(critical, "pollCallbacks invoked from the agent's thread!"); + return 0; + } + + for (uint32_t idx = 0; callLimit == 0 || idx < callLimit; idx++) { + if (methodQueue.empty()) + break; + + QueuedMethod* item = methodQueue.front(); + methodQueue.pop_front(); + { + sys::Mutex::ScopedUnlock unlock(agentLock); + invokeMethodRequest(item->body, item->cid, item->replyToExchange, item->replyToKey, item->userId); + delete item; + } + } + + if (pipeHandle != 0) { + char rbuf[100]; + while (pipeHandle->read(rbuf, 100) > 0) ; // Consume all signaling bytes + } + return methodQueue.size(); +} + +int ManagementAgentImpl::getSignalFd() +{ + if (extThread) { + if (pipeHandle == 0) + pipeHandle = new PipeHandle(true); + return pipeHandle->getReadHandle(); + } + + return -1; +} + +void ManagementAgentImpl::setSignalCallback(cb_t callback, void* context) +{ + sys::Mutex::ScopedLock lock(agentLock); + notifyCallback = callback; + notifyContext = context; +} + +void ManagementAgentImpl::setSignalCallback(Notifyable& _notifyable) +{ + sys::Mutex::ScopedLock lock(agentLock); + notifyable = &_notifyable; +} + +void ManagementAgentImpl::startProtocol() +{ + sendHeartbeat(); + { + sys::Mutex::ScopedLock lock(agentLock); + publishAllData = true; + } +} + +void ManagementAgentImpl::storeData(bool requested) +{ + if (!storeFile.empty()) { + ofstream outFile(storeFile.c_str()); + uint32_t brokerBankToWrite = requested ? requestedBrokerBank : assignedBrokerBank; + uint32_t agentBankToWrite = requested ? requestedAgentBank : assignedAgentBank; + + if (outFile.good()) { + outFile << storeMagicNumber << " " << brokerBankToWrite << " " << + agentBankToWrite << " " << bootSequence << endl; + + if (attrMap.find("_vendor") != attrMap.end()) + outFile << "vendor=" << attrMap["_vendor"] << endl; + if (attrMap.find("_product") != attrMap.end()) + outFile << "product=" << attrMap["_product"] << endl; + if (attrMap.find("_instance") != attrMap.end()) + outFile << "instance=" << attrMap["_instance"] << endl; + + outFile.close(); + } + } +} + +void ManagementAgentImpl::retrieveData(std::string& vendor, std::string& product, std::string& inst) +{ + vendor.clear(); + product.clear(); + inst.clear(); + + if (!storeFile.empty()) { + ifstream inFile(storeFile.c_str()); + string mn; + + if (inFile.good()) { + inFile >> mn; + if (mn == storeMagicNumber) { + std::string inText; + + inFile >> requestedBrokerBank; + inFile >> requestedAgentBank; + inFile >> bootSequence; + + while (inFile.good()) { + std::getline(inFile, inText); + if (!inText.compare(0, 7, "vendor=")) { + vendor = inText.substr(7); + QPID_LOG(debug, "read vendor name [" << vendor << "] from configuration file."); + } else if (!inText.compare(0, 8, "product=")) { + product = inText.substr(8); + QPID_LOG(debug, "read product name [" << product << "] from configuration file."); + } else if (!inText.compare(0, 9, "instance=")) { + inst = inText.substr(9); + QPID_LOG(debug, "read instance name [" << inst << "] from configuration file."); + } + } + } + inFile.close(); + } + } +} + +void ManagementAgentImpl::sendHeartbeat() +{ + static const string addr_key_base("agent.ind.heartbeat."); + + Variant::Map map; + Variant::Map headers; + string content; + std::stringstream addr_key; + + addr_key << addr_key_base << vendorNameKey + << "." << productNameKey + << "." << instanceNameKey; + + headers["method"] = "indication"; + headers["qmf.opcode"] = "_agent_heartbeat_indication"; + headers["qmf.agent"] = name_address; + + getHeartbeatContent(map); + MapCodec::encode(map, content); + + // Set TTL (in msecs) on outgoing heartbeat indications based on the interval + // time to prevent stale heartbeats from getting to the consoles. + + connThreadBody.sendBuffer(content, "", headers, topicExchange, addr_key.str(), + "amqp/map", interval * 2 * 1000); + + QPID_LOG(trace, "SENT AgentHeartbeat name=" << name_address); +} + +void ManagementAgentImpl::sendException(const string& rte, const string& rtk, const string& cid, + const string& text, uint32_t code) +{ + Variant::Map map; + Variant::Map headers; + Variant::Map values; + string content; + + headers["method"] = "indication"; + headers["qmf.opcode"] = "_exception"; + headers["qmf.agent"] = name_address; + + values["error_code"] = code; + values["error_text"] = text; + map["_values"] = values; + + MapCodec::encode(map, content); + connThreadBody.sendBuffer(content, cid, headers, rte, rtk); + + QPID_LOG(trace, "SENT Exception code=" << code <<" text=" << text); +} + +void ManagementAgentImpl::handleSchemaRequest(Buffer& inBuffer, uint32_t sequence, const string& rte, const string& rtk) +{ + 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); + inBuffer.getBin128(key.hash); + + QPID_LOG(trace, "RCVD SchemaRequest: 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() +{ + sys::Mutex::ScopedLock lock(agentLock); + publishAllData = true; + + QPID_LOG(trace, "RCVD ConsoleAddedInd"); +} + +void ManagementAgentImpl::invokeMethodRequest(const string& body, const string& cid, const string& rte, const string& rtk, const string& userId) +{ + string methodName; + bool failed = false; + Variant::Map inMap; + Variant::Map outMap; + Variant::Map::const_iterator oid, mid; + string content; + + MapCodec::decode(body, inMap); + + if ((oid = inMap.find("_object_id")) == inMap.end() || + (mid = inMap.find("_method_name")) == inMap.end()) { + sendException(rte, rtk, cid, Manageable::StatusText(Manageable::STATUS_PARAMETER_INVALID), + Manageable::STATUS_PARAMETER_INVALID); + failed = true; + } else { + string methodName; + ObjectId objId; + Variant::Map inArgs; + Variant::Map callMap; + + try { + // conversions will throw if input is invalid. + objId = ObjectId(oid->second.asMap()); + methodName = mid->second.getString(); + + mid = inMap.find("_arguments"); + if (mid != inMap.end()) { + inArgs = (mid->second).asMap(); + } + + QPID_LOG(trace, "Invoking Method: name=" << methodName << " args=" << inArgs); + + boost::shared_ptr<ManagementObject> oPtr; + { + sys::Mutex::ScopedLock lock(agentLock); + ObjectMap::iterator iter = managementObjects.find(objId); + if (iter != managementObjects.end() && !iter->second->isDeleted()) + oPtr = iter->second; + } + + if (oPtr.get() == 0) { + sendException(rte, rtk, cid, Manageable::StatusText(Manageable::STATUS_UNKNOWN_OBJECT), + Manageable::STATUS_UNKNOWN_OBJECT); + failed = true; + } else { + oPtr->doMethod(methodName, inArgs, callMap, userId); + + if (callMap["_status_code"].asUint32() == 0) { + outMap["_arguments"] = Variant::Map(); + for (Variant::Map::const_iterator iter = callMap.begin(); + iter != callMap.end(); iter++) + if (iter->first != "_status_code" && iter->first != "_status_text") + outMap["_arguments"].asMap()[iter->first] = iter->second; + } else { + sendException(rte, rtk, cid, callMap["_status_text"], callMap["_status_code"]); + failed = true; + } + } + + } catch(types::InvalidConversion& e) { + sendException(rte, rtk, cid, e.what(), Manageable::STATUS_EXCEPTION); + failed = true; + } + } + + if (!failed) { + Variant::Map headers; + headers["method"] = "response"; + headers["qmf.agent"] = name_address; + headers["qmf.opcode"] = "_method_response"; + QPID_LOG(trace, "SENT MethodResponse map=" << outMap); + MapCodec::encode(outMap, content); + connThreadBody.sendBuffer(content, cid, headers, rte, rtk); + } +} + +void ManagementAgentImpl::handleGetQuery(const string& body, const string& cid, const string& rte, const string& rtk) +{ + moveNewObjectsLH(); + + Variant::Map inMap; + Variant::Map::const_iterator i; + Variant::Map headers; + + MapCodec::decode(body, inMap); + QPID_LOG(trace, "RCVD GetQuery: map=" << inMap << " cid=" << cid); + + headers["method"] = "response"; + headers["qmf.opcode"] = "_query_response"; + headers["qmf.agent"] = name_address; + headers["partial"] = Variant(); + + Variant::List list_; + Variant::Map map_; + Variant::Map values; + Variant::Map oidMap; + string content; + + /* + * Unpack the _what element of the query. Currently we only support OBJECT queries. + */ + i = inMap.find("_what"); + if (i == inMap.end()) { + sendException(rte, rtk, cid, "_what element missing in Query"); + return; + } + + if (i->second.getType() != qpid::types::VAR_STRING) { + sendException(rte, rtk, cid, "_what element is not a string"); + return; + } + + if (i->second.asString() == "OBJECT") { + headers["qmf.content"] = "_data"; + /* + * Unpack the _object_id element of the query if it is present. If it is present, find that one + * object and return it. If it is not present, send a class-based result. + */ + i = inMap.find("_object_id"); + if (i != inMap.end() && i->second.getType() == qpid::types::VAR_MAP) { + ObjectId objId(i->second.asMap()); + boost::shared_ptr<ManagementObject> object; + + { + sys::Mutex::ScopedLock lock(agentLock); + ObjectMap::iterator iter = managementObjects.find(objId); + if (iter != managementObjects.end()) + object = iter->second; + } + + if (object.get() != 0) { + if (object->getConfigChanged() || object->getInstChanged()) + object->setUpdateTime(); + + object->mapEncodeValues(values, true, true); // write both stats and properties + objId.mapEncode(oidMap); + map_["_values"] = values; + map_["_object_id"] = oidMap; + object->writeTimestamps(map_); + map_["_schema_id"] = mapEncodeSchemaId(object->getPackageName(), + object->getClassName(), + object->getMd5Sum()); + list_.push_back(map_); + headers.erase("partial"); + + ListCodec::encode(list_, content); + connThreadBody.sendBuffer(content, cid, headers, rte, rtk, "amqp/list"); + QPID_LOG(trace, "SENT QueryResponse (query by object_id) to=" << rte << "/" << rtk); + return; + } + } else { // match using schema_id, if supplied + + string className; + string packageName; + + i = inMap.find("_schema_id"); + if (i != inMap.end() && i->second.getType() == qpid::types::VAR_MAP) { + const Variant::Map& schemaIdMap(i->second.asMap()); + + Variant::Map::const_iterator s_iter = schemaIdMap.find("_class_name"); + if (s_iter != schemaIdMap.end() && s_iter->second.getType() == qpid::types::VAR_STRING) + className = s_iter->second.asString(); + + s_iter = schemaIdMap.find("_package_name"); + if (s_iter != schemaIdMap.end() && s_iter->second.getType() == qpid::types::VAR_STRING) + packageName = s_iter->second.asString(); + + typedef list<boost::shared_ptr<ManagementObject> > StageList; + StageList staging; + + { + sys::Mutex::ScopedLock lock(agentLock); + for (ObjectMap::iterator iter = managementObjects.begin(); + iter != managementObjects.end(); + iter++) { + ManagementObject* object = iter->second.get(); + if (object->getClassName() == className && + (packageName.empty() || object->getPackageName() == packageName)) + staging.push_back(iter->second); + } + } + + unsigned int objCount = 0; + for (StageList::iterator iter = staging.begin(); iter != staging.end(); iter++) { + ManagementObject* object = iter->get(); + if (object->getClassName() == className && + (packageName.empty() || object->getPackageName() == packageName)) { + + values.clear(); + oidMap.clear(); + map_.clear(); + + if (object->getConfigChanged() || object->getInstChanged()) + object->setUpdateTime(); + + object->mapEncodeValues(values, true, true); // write both stats and properties + object->getObjectId().mapEncode(oidMap); + map_["_values"] = values; + map_["_object_id"] = oidMap; + object->writeTimestamps(map_); + map_["_schema_id"] = mapEncodeSchemaId(object->getPackageName(), + object->getClassName(), + object->getMd5Sum()); + list_.push_back(map_); + + if (++objCount >= maxV2ReplyObjs) { + objCount = 0; + ListCodec::encode(list_, content); + connThreadBody.sendBuffer(content, cid, headers, rte, rtk, "amqp/list"); + QPID_LOG(trace, "SENT QueryResponse (query by schema_id) to=" << rte << "/" << rtk); + content.clear(); + list_.clear(); + } + } + } + } + } + + // Send last "non-partial" message to indicate CommandComplete + headers.erase("partial"); + ListCodec::encode(list_, content); + connThreadBody.sendBuffer(content, cid, headers, rte, rtk, "amqp/list"); + QPID_LOG(trace, "SENT QueryResponse (last message, no 'partial' indicator) to=" << rte << "/" << rtk); + + } else if (i->second.asString() == "SCHEMA_ID") { + headers["qmf.content"] = "_schema_id"; + /** + * @todo - support for a predicate. For now, send a list of all known schema class keys. + */ + for (PackageMap::iterator pIter = packages.begin(); + pIter != packages.end(); pIter++) { + for (ClassMap::iterator cIter = pIter->second.begin(); + cIter != pIter->second.end(); cIter++) { + + list_.push_back(mapEncodeSchemaId( pIter->first, + cIter->first.name, + cIter->first.hash, + cIter->second.kind )); + } + } + + headers.erase("partial"); + ListCodec::encode(list_, content); + connThreadBody.sendBuffer(content, cid, headers, rte, rtk, "amqp/list"); + QPID_LOG(trace, "SENT QueryResponse (SchemaId) to=" << rte << "/" << rtk); + + } else { + // Unknown query target + sendException(rte, rtk, cid, "Query for _what => '" + i->second.asString() + "' not supported"); + } +} + +void ManagementAgentImpl::handleLocateRequest(const string&, const string& cid, const string& rte, const string& rtk) +{ + QPID_LOG(trace, "RCVD AgentLocateRequest"); + + Variant::Map map; + Variant::Map headers; + string content; + + headers["method"] = "indication"; + headers["qmf.opcode"] = "_agent_locate_response"; + headers["qmf.agent"] = name_address; + + getHeartbeatContent(map); + MapCodec::encode(map, content); + connThreadBody.sendBuffer(content, cid, headers, rte, rtk); + + QPID_LOG(trace, "SENT AgentLocateResponse replyTo=" << rte << "/" << rtk); + + { + sys::Mutex::ScopedLock lock(agentLock); + publishAllData = true; + } +} + +void ManagementAgentImpl::handleMethodRequest(const string& body, const string& cid, const string& rte, const string& rtk, const string& userId) +{ + if (extThread) { + sys::Mutex::ScopedLock lock(agentLock); + + methodQueue.push_back(new QueuedMethod(cid, rte, rtk, body, userId)); + if (pipeHandle != 0) { + pipeHandle->write("X", 1); + } else if (notifyable != 0) { + inCallback = true; + { + sys::Mutex::ScopedUnlock unlock(agentLock); + notifyable->notify(); + } + inCallback = false; + } else if (notifyCallback != 0) { + inCallback = true; + { + sys::Mutex::ScopedUnlock unlock(agentLock); + notifyCallback(notifyContext); + } + inCallback = false; + } + } else { + invokeMethodRequest(body, cid, rte, rtk, userId); + } + + QPID_LOG(trace, "RCVD MethodRequest"); +} + +void ManagementAgentImpl::received(Message& msg) +{ + string replyToExchange; + string replyToKey; + framing::MessageProperties mp = msg.getMessageProperties(); + if (mp.hasReplyTo()) { + const framing::ReplyTo& rt = mp.getReplyTo(); + replyToExchange = rt.getExchange(); + replyToKey = rt.getRoutingKey(); + } + + string userId; + if (mp.hasUserId()) + userId = mp.getUserId(); + + if (mp.hasAppId() && mp.getAppId() == "qmf2") + { + string opcode = mp.getApplicationHeaders().getAsString("qmf.opcode"); + string cid = msg.getMessageProperties().getCorrelationId(); + + if (opcode == "_agent_locate_request") handleLocateRequest(msg.getData(), cid, replyToExchange, replyToKey); + else if (opcode == "_method_request") handleMethodRequest(msg.getData(), cid, replyToExchange, replyToKey, userId); + else if (opcode == "_query_request") handleGetQuery(msg.getData(), cid, replyToExchange, replyToKey); + else { + QPID_LOG(warning, "Support for QMF V2 Opcode [" << opcode << "] TBD!!!"); + } + return; + } + + // old preV2 binary messages + + uint32_t sequence; + string data = msg.getData(); + Buffer inBuffer(const_cast<char*>(data.c_str()), data.size()); + uint8_t opcode; + + + if (checkHeader(inBuffer, &opcode, &sequence)) + { + if (opcode == 'S') handleSchemaRequest(inBuffer, sequence, replyToExchange, replyToKey); + else if (opcode == 'x') handleConsoleAddedIndication(); + else + QPID_LOG(warning, "Ignoring old-format QMF Request! opcode=" << char(opcode)); + } +} + + +void ManagementAgentImpl::encodeHeader(Buffer& buf, uint8_t opcode, uint32_t seq) +{ + buf.putOctet('A'); + buf.putOctet('M'); + buf.putOctet('2'); + buf.putOctet(opcode); + buf.putLong (seq); +} + +Variant::Map ManagementAgentImpl::mapEncodeSchemaId(const string& pname, + const string& cname, + const uint8_t *md5Sum, + uint8_t type) +{ + Variant::Map map_; + + map_["_package_name"] = pname; + map_["_class_name"] = cname; + map_["_hash"] = types::Uuid(md5Sum); + if (type == ManagementItem::CLASS_KIND_EVENT) + map_["_type"] = "_event"; + else + map_["_type"] = "_data"; + + return map_; +} + + +bool ManagementAgentImpl::checkHeader(Buffer& buf, uint8_t *opcode, uint32_t *seq) +{ + if (buf.getSize() < 8) + return false; + + uint8_t h1 = buf.getOctet(); + uint8_t h2 = buf.getOctet(); + uint8_t h3 = buf.getOctet(); + + *opcode = buf.getOctet(); + *seq = buf.getLong(); + + return h1 == 'A' && h2 == 'M' && h3 == '2'; +} + +ManagementAgentImpl::PackageMap::iterator ManagementAgentImpl::findOrAddPackage(const string& name) +{ + PackageMap::iterator pIter = packages.find(name); + if (pIter != packages.end()) + return pIter; + + // No such package found, create a new map entry. + pair<PackageMap::iterator, bool> result = + packages.insert(pair<string, ClassMap>(name, ClassMap())); + + return result.first; +} + +void ManagementAgentImpl::moveNewObjectsLH() +{ + sys::Mutex::ScopedLock lock(addLock); + for (ObjectMap::iterator iter = newManagementObjects.begin(); + iter != newManagementObjects.end(); + iter++) + managementObjects[iter->first] = iter->second; + newManagementObjects.clear(); +} + +void ManagementAgentImpl::addClassLocal(uint8_t classKind, + PackageMap::iterator pIter, + const string& className, + uint8_t* md5Sum, + ManagementObject::writeSchemaCall_t schemaCall) +{ + SchemaClassKey key; + ClassMap& cMap = pIter->second; + + key.name = className; + memcpy(&key.hash, md5Sum, 16); + + ClassMap::iterator cIter = cMap.find(key); + if (cIter != cMap.end()) + return; + + // No such class found, create a new class with local information. + cMap.insert(pair<SchemaClassKey, SchemaClass>(key, SchemaClass(schemaCall, classKind))); + schemaTimestamp = Duration(EPOCH, now()); + QPID_LOG(trace, "Updated schema timestamp, now=" << uint64_t(schemaTimestamp)); +} + +void ManagementAgentImpl::encodePackageIndication(Buffer& buf, + PackageMap::iterator pIter) +{ + buf.putShortString((*pIter).first); + + QPID_LOG(trace, "SENT PackageInd: package=" << (*pIter).first); +} + +void ManagementAgentImpl::encodeClassIndication(Buffer& buf, + PackageMap::iterator pIter, + ClassMap::iterator cIter) +{ + SchemaClassKey key = (*cIter).first; + + buf.putOctet((*cIter).second.kind); + buf.putShortString((*pIter).first); + buf.putShortString(key.name); + buf.putBin128(key.hash); + + 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."; + list<ObjectId> deleteList; + list<boost::shared_ptr<MessageItem> > message_list; + + sendHeartbeat(); + + { + sys::Mutex::ScopedLock lock(agentLock); + + if (!connected) + return; + + 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; + + // + // 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_); + + 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 (!list_.empty()) { + boost::shared_ptr<MessageItem> item(new MessageItem(headers, addr_key.str())); + ListCodec::encode(list_, item->content); + message_list.push_back(item); + } + } + + // 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"); + } +} + + +void ManagementAgentImpl::getHeartbeatContent(qpid::types::Variant::Map& map) +{ + map["_values"] = attrMap; + map["_values"].asMap()["_timestamp"] = uint64_t(Duration(EPOCH, now())); + map["_values"].asMap()["_heartbeat_interval"] = interval; + map["_values"].asMap()["_epoch"] = bootSequence; + map["_values"].asMap()["_schema_updated"] = uint64_t(schemaTimestamp); +} + +void ManagementAgentImpl::ConnectionThread::run() +{ + static const int delayMin(1); + static const int delayMax(128); + static const int delayFactor(2); + int delay(delayMin); + string dest("qmfagent"); + ConnectionThread::shared_ptr tmp; + + sessionId.generate(); + queueName << "qmfagent-" << sessionId; + + while (true) { + try { + if (agent.initialized) { + QPID_LOG(debug, "QMF Agent attempting to connect to the broker..."); + connection.open(agent.connectionSettings); + session = connection.newSession(queueName.str()); + subscriptions.reset(new client::SubscriptionManager(session)); + + session.queueDeclare(arg::queue=queueName.str(), arg::autoDelete=true, + arg::exclusive=true); + session.exchangeBind(arg::exchange="amq.direct", arg::queue=queueName.str(), + arg::bindingKey=queueName.str()); + session.exchangeBind(arg::exchange=agent.directExchange, arg::queue=queueName.str(), + arg::bindingKey=agent.name_address); + session.exchangeBind(arg::exchange=agent.topicExchange, arg::queue=queueName.str(), + arg::bindingKey="console.#"); + + subscriptions->subscribe(agent, queueName.str(), dest); + QPID_LOG(info, "Connection established with broker"); + { + sys::Mutex::ScopedLock _lock(connLock); + if (shutdown) + return; + operational = true; + agent.connected = true; + agent.startProtocol(); + try { + sys::Mutex::ScopedUnlock _unlock(connLock); + subscriptions->run(); + } catch (exception) {} + + QPID_LOG(warning, "Connection to the broker has been lost"); + + operational = false; + agent.connected = false; + tmp = subscriptions; + subscriptions.reset(); + } + tmp.reset(); // frees the subscription outside the lock + delay = delayMin; + connection.close(); + } + } catch (exception &e) { + if (delay < delayMax) + delay *= delayFactor; + QPID_LOG(debug, "Connection failed: exception=" << e.what()); + } + + { + // sleep for "delay" seconds, but peridically check if the + // agent is shutting down so we don't hang for up to delayMax + // seconds during agent shutdown + sys::Mutex::ScopedLock _lock(connLock); + if (shutdown) + return; + sleeping = true; + int totalSleep = 0; + do { + sys::Mutex::ScopedUnlock _unlock(connLock); + ::sleep(delayMin); + totalSleep += delayMin; + } while (totalSleep < delay && !shutdown); + sleeping = false; + if (shutdown) + return; + } + } +} + +ManagementAgentImpl::ConnectionThread::~ConnectionThread() +{ +} + +void ManagementAgentImpl::ConnectionThread::sendBuffer(Buffer& buf, + uint32_t length, + const string& exchange, + const string& routingKey) +{ + Message msg; + string data; + + buf.getRawData(data, length); + msg.setData(data); + sendMessage(msg, exchange, routingKey); +} + + + +void ManagementAgentImpl::ConnectionThread::sendBuffer(const string& data, + const string& cid, + const Variant::Map headers, + const string& exchange, + const string& routingKey, + const string& contentType, + uint64_t ttl_msec) +{ + Message msg; + Variant::Map::const_iterator i; + + if (!cid.empty()) + msg.getMessageProperties().setCorrelationId(cid); + + if (!contentType.empty()) + msg.getMessageProperties().setContentType(contentType); + + if (ttl_msec) + msg.getDeliveryProperties().setTtl(ttl_msec); + + for (i = headers.begin(); i != headers.end(); ++i) { + msg.getHeaders().setString(i->first, i->second.asString()); + } + + msg.setData(data); + sendMessage(msg, exchange, routingKey); +} + + + + + +void ManagementAgentImpl::ConnectionThread::sendMessage(Message msg, + const string& exchange, + const string& routingKey) +{ + ConnectionThread::shared_ptr s; + { + sys::Mutex::ScopedLock _lock(connLock); + if (!operational) + return; + s = subscriptions; + } + + msg.getDeliveryProperties().setRoutingKey(routingKey); + msg.getMessageProperties().setReplyTo(ReplyTo("amq.direct", queueName.str())); + msg.getMessageProperties().getApplicationHeaders().setString("qmf.agent", agent.name_address); + msg.getMessageProperties().setAppId("qmf2"); + try { + session.messageTransfer(arg::content=msg, arg::destination=exchange); + } catch(exception& e) { + QPID_LOG(error, "Exception caught in sendMessage: " << e.what()); + // Bounce the connection + if (s) + s->stop(); + } +} + + + +void ManagementAgentImpl::ConnectionThread::bindToBank(uint32_t brokerBank, uint32_t agentBank) +{ + stringstream key; + key << "agent." << brokerBank << "." << agentBank; + session.exchangeBind(arg::exchange="qpid.management", arg::queue=queueName.str(), + arg::bindingKey=key.str()); +} + +void ManagementAgentImpl::ConnectionThread::close() +{ + ConnectionThread::shared_ptr s; + { + sys::Mutex::ScopedLock _lock(connLock); + shutdown = true; + s = subscriptions; + } + if (s) + s->stop(); +} + +bool ManagementAgentImpl::ConnectionThread::isSleeping() const +{ + sys::Mutex::ScopedLock _lock(connLock); + return sleeping; +} + + +void ManagementAgentImpl::PublishThread::run() +{ + uint16_t totalSleep; + + while (!shutdown) { + agent.periodicProcessing(); + totalSleep = 0; + while (totalSleep++ < agent.getInterval() && !shutdown) { + ::sleep(1); + } + } +} diff --git a/qpid/cpp/src/qpid/agent/ManagementAgentImpl.h b/qpid/cpp/src/qpid/agent/ManagementAgentImpl.h new file mode 100644 index 0000000000..bf340777d1 --- /dev/null +++ b/qpid/cpp/src/qpid/agent/ManagementAgentImpl.h @@ -0,0 +1,298 @@ +#ifndef _qpid_agent_ManagementAgentImpl_ +#define _qpid_agent_ManagementAgentImpl_ + +// +// 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/agent/ManagementAgent.h" +#include "qpid/client/Connection.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/client/Session.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/client/Message.h" +#include "qpid/client/MessageListener.h" +#include "qpid/sys/Thread.h" +#include "qpid/sys/Runnable.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/PipeHandle.h" +#include "qpid/sys/Time.h" +#include "qpid/framing/Uuid.h" +#include <iostream> +#include <sstream> +#include <deque> + +namespace qpid { +namespace management { + +class ManagementAgentImpl : public ManagementAgent, public client::MessageListener +{ + public: + + ManagementAgentImpl(); + virtual ~ManagementAgentImpl(); + + // + // Methods from ManagementAgent + // + int getMaxThreads() { return 1; } + void setName(const std::string& vendor, + const std::string& product, + const std::string& instance=""); + void getName(std::string& vendor, std::string& product, std::string& instance); + const std::string& getAddress(); + void init(const std::string& brokerHost = "localhost", + uint16_t brokerPort = 5672, + uint16_t intervalSeconds = 10, + bool useExternalThread = false, + const std::string& storeFile = "", + const std::string& uid = "guest", + const std::string& pwd = "guest", + const std::string& mech = "PLAIN", + const std::string& proto = "tcp"); + void init(const management::ConnectionSettings& settings, + uint16_t intervalSeconds = 10, + bool useExternalThread = false, + const std::string& storeFile = ""); + bool isConnected() { return connected; } + std::string& getLastFailure() { return lastFailure; } + void registerClass(const std::string& packageName, + const std::string& className, + uint8_t* md5Sum, + management::ManagementObject::writeSchemaCall_t schemaCall); + void registerEvent(const std::string& packageName, + const std::string& eventName, + uint8_t* md5Sum, + management::ManagementObject::writeSchemaCall_t schemaCall); + ObjectId addObject(management::ManagementObject* objectPtr, uint64_t persistId = 0); + ObjectId addObject(management::ManagementObject* objectPtr, const std::string& key, + bool persistent); + void raiseEvent(const management::ManagementEvent& event, severity_t severity = SEV_DEFAULT); + uint32_t pollCallbacks(uint32_t callLimit = 0); + int getSignalFd(); + void setSignalCallback(cb_t callback, void* context); + void setSignalCallback(Notifyable& n); + + uint16_t getInterval() { return interval; } + void periodicProcessing(); + + // these next are here to support the hot-wiring of state between clustered brokers + uint64_t getNextObjectId(void) { return nextObjectId; } + void setNextObjectId(uint64_t o) { nextObjectId = o; } + + uint16_t getBootSequence(void) { return bootSequence; } + void setBootSequence(uint16_t b) { bootSequence = b; } + + private: + + struct SchemaClassKey { + std::string name; + uint8_t hash[16]; + }; + + struct SchemaClassKeyComp { + bool operator() (const SchemaClassKey& lhs, const SchemaClassKey& rhs) const + { + if (lhs.name != rhs.name) + return lhs.name < rhs.name; + else + for (int i = 0; i < 16; i++) + if (lhs.hash[i] != rhs.hash[i]) + return lhs.hash[i] < rhs.hash[i]; + return false; + } + }; + + struct SchemaClass { + management::ManagementObject::writeSchemaCall_t writeSchemaCall; + uint8_t kind; + + SchemaClass(const management::ManagementObject::writeSchemaCall_t call, + const uint8_t _kind) : writeSchemaCall(call), kind(_kind) {} + }; + + struct QueuedMethod { + QueuedMethod(const std::string& _cid, const std::string& _rte, const std::string& _rtk, const std::string& _body, const std::string& _uid) : + cid(_cid), replyToExchange(_rte), replyToKey(_rtk), body(_body), userId(_uid) {} + + std::string cid; + std::string replyToExchange; + std::string replyToKey; + std::string body; + std::string userId; + }; + + typedef std::deque<QueuedMethod*> MethodQueue; + typedef std::map<SchemaClassKey, SchemaClass, SchemaClassKeyComp> ClassMap; + typedef std::map<std::string, ClassMap> PackageMap; + + PackageMap packages; + AgentAttachment attachment; + + typedef std::map<ObjectId, boost::shared_ptr<ManagementObject> > ObjectMap; + + ObjectMap managementObjects; + ObjectMap newManagementObjects; + MethodQueue methodQueue; + + void received (client::Message& msg); + + qpid::types::Variant::Map attrMap; + std::string name_address; + std::string vendorNameKey; // vendor name with "." --> "_" + std::string productNameKey; // product name with "." --> "_" + std::string instanceNameKey; // agent instance with "." --> "_" + uint16_t interval; + bool extThread; + sys::PipeHandle* pipeHandle; + uint64_t nextObjectId; + cb_t notifyCallback; + void* notifyContext; + Notifyable* notifyable; + bool inCallback; + std::string storeFile; + sys::Mutex agentLock; + sys::Mutex addLock; + framing::Uuid systemId; + client::ConnectionSettings connectionSettings; + bool initialized; + bool connected; + bool useMapMsg; + std::string lastFailure; + std::string topicExchange; + std::string directExchange; + qpid::sys::Duration schemaTimestamp; + + bool publishAllData; + uint32_t requestedBrokerBank; + uint32_t requestedAgentBank; + uint32_t assignedBrokerBank; + uint32_t assignedAgentBank; + uint16_t bootSequence; + + // Maximum # of objects allowed in a single V2 response + // message. + uint32_t maxV2ReplyObjs; + + static const uint8_t DEBUG_OFF = 0; + static const uint8_t DEBUG_CONN = 1; + static const uint8_t DEBUG_PROTO = 2; + static const uint8_t DEBUG_PUBLISH = 3; + +# define MA_BUFFER_SIZE 65536 + char outputBuffer[MA_BUFFER_SIZE]; + char eventBuffer[MA_BUFFER_SIZE]; + + friend class ConnectionThread; + class ConnectionThread : public sys::Runnable + { + typedef boost::shared_ptr<client::SubscriptionManager> shared_ptr; + + bool operational; + ManagementAgentImpl& agent; + framing::Uuid sessionId; + client::Connection connection; + client::Session session; + ConnectionThread::shared_ptr subscriptions; + std::stringstream queueName; + mutable sys::Mutex connLock; + bool shutdown; + bool sleeping; + void run(); + public: + ConnectionThread(ManagementAgentImpl& _agent) : + operational(false), agent(_agent), + shutdown(false), sleeping(false) {} + ~ConnectionThread(); + void sendBuffer(qpid::framing::Buffer& buf, + uint32_t length, + const std::string& exchange, + const std::string& routingKey); + void sendBuffer(const std::string& data, + const std::string& cid, + const qpid::types::Variant::Map headers, + const std::string& exchange, + const std::string& routingKey, + const std::string& contentType="amqp/map", + uint64_t ttl_msec=0); + void sendMessage(qpid::client::Message msg, + const std::string& exchange, + const std::string& routingKey); + void bindToBank(uint32_t brokerBank, uint32_t agentBank); + void close(); + bool isSleeping() const; + }; + + class PublishThread : public sys::Runnable + { + ManagementAgentImpl& agent; + void run(); + bool shutdown; + public: + PublishThread(ManagementAgentImpl& _agent) : + agent(_agent), shutdown(false) {} + void close() { shutdown = true; } + }; + + ConnectionThread connThreadBody; + sys::Thread connThread; + PublishThread pubThreadBody; + sys::Thread pubThread; + + static const std::string storeMagicNumber; + + void startProtocol(); + void storeData(bool requested=false); + void retrieveData(std::string& vendor, std::string& product, std::string& inst); + PackageMap::iterator findOrAddPackage(const std::string& name); + void moveNewObjectsLH(); + void addClassLocal (uint8_t classKind, + PackageMap::iterator pIter, + const std::string& className, + uint8_t* md5Sum, + management::ManagementObject::writeSchemaCall_t schemaCall); + void encodePackageIndication (framing::Buffer& buf, + PackageMap::iterator pIter); + void encodeClassIndication (framing::Buffer& buf, + PackageMap::iterator pIter, + ClassMap::iterator cIter); + void encodeHeader (framing::Buffer& buf, uint8_t opcode, uint32_t seq = 0); + qpid::types::Variant::Map mapEncodeSchemaId(const std::string& pname, + const std::string& cname, + const uint8_t *md5Sum, + uint8_t type=ManagementItem::CLASS_KIND_TABLE); + bool checkHeader (framing::Buffer& buf, uint8_t *opcode, uint32_t *seq); + void sendHeartbeat(); + void sendException(const std::string& replyToExchange, const std::string& replyToKey, const std::string& cid, + const std::string& text, uint32_t code=1); + void handlePackageRequest (qpid::framing::Buffer& inBuffer); + void handleClassQuery (qpid::framing::Buffer& inBuffer); + void handleSchemaRequest (qpid::framing::Buffer& inBuffer, uint32_t sequence, const std::string& rte, const std::string& rtk); + void invokeMethodRequest (const std::string& body, const std::string& cid, const std::string& rte, const std::string& rtk, const std::string& userId); + + void handleGetQuery (const std::string& body, const std::string& cid, const std::string& rte, const std::string& rtk); + void handleLocateRequest (const std::string& body, const std::string& sequence, const std::string& rte, const std::string& rtk); + void handleMethodRequest (const std::string& body, const std::string& sequence, const std::string& rte, const std::string& rtk, const std::string& userId); + void handleConsoleAddedIndication(); + void getHeartbeatContent (qpid::types::Variant::Map& map); +}; + +}} + +#endif /*!_qpid_agent_ManagementAgentImpl_*/ diff --git a/qpid/cpp/src/qpid/amqp_0_10/Array.cpp b/qpid/cpp/src/qpid/amqp_0_10/Array.cpp new file mode 100644 index 0000000000..2ee47546f2 --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/Array.cpp @@ -0,0 +1,34 @@ +/* + * + * 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/amqp_0_10/Array.h" + +namespace qpid { +namespace amqp_0_10 { + +std::ostream& operator<<(std::ostream& o, const Array& a) { + std::ostream_iterator<UnknownType> i(o, " "); + o << "Array<" << typeName(a.getType()) << "["; + std::copy(a.begin(), a.end(), i); + o << "]"; + return o; +} + +}} // namespace qpid::amqp_0_10 diff --git a/qpid/cpp/src/qpid/amqp_0_10/Array.h b/qpid/cpp/src/qpid/amqp_0_10/Array.h new file mode 100644 index 0000000000..6e8a419df7 --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/Array.h @@ -0,0 +1,124 @@ +#ifndef QPID_AMQP_0_10_ARRAY_H +#define QPID_AMQP_0_10_ARRAY_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/amqp_0_10/TypeForCode.h" +#include "qpid/amqp_0_10/CodeForType.h" +#include "qpid/amqp_0_10/UnknownType.h" +#include "qpid/amqp_0_10/exceptions.h" +#include "qpid/amqp_0_10/Codec.h" +#include <vector> +#include <ostream> + +namespace qpid { +namespace amqp_0_10 { + +template <class T> class ArrayDomain : public std::vector<T> { + public: + template <class S> void serialize(S& s) { s.split(*this); } + + template <class S> void encode(S& s) const { + s(contentSize())(CodeForType<T>::value)(uint32_t(this->size())); + s(this->begin(), this->end()); + } + + void encode(Codec::Size& s) const { s.raw(0, contentSize() + 4/*size*/); } + + template <class S> void decode(S& s) { + uint32_t size; uint8_t type; uint32_t count; + s(size); + typename S::ScopedLimit l(s, size); + s(type); + if (type != CodeForType<T>::value) + throw InvalidArgumentException(QPID_MSG("Array domain expected type " << CodeForType<T>::value << " but found " << type)); + s(count); + this->resize(count); + s(this->begin(), this->end()); + } + + private: + uint32_t contentSize() const { + return Codec::size(this->begin(), this->end()) + sizeof(uint32_t) /*count*/ + sizeof(uint8_t) /*type*/; + } +}; + +template <class T> +std::ostream& operator<<(std::ostream& o, const ArrayDomain<T>& ad) { + std::ostream_iterator<T> i(o, " "); + o << "Array<" << typeName(CodeForType<T>::value) << ">["; + std::copy(ad.begin(), ad.end(), i); + o << "]"; + return o; +} + +/** A non-domain array is represented as and array of UnknownType. + * Special case templat. + */ +template<> class ArrayDomain<UnknownType> : public std::vector<UnknownType> { + public: + ArrayDomain(uint8_t type_=0) : type(type_) {} + + template <class S> void serialize(S& s) { s.split(*this); } + + template <class S> void encode(S& s) const { + s(contentSize())(type)(uint32_t(this->size())); + s(this->begin(), this->end()); + } + + void encode(Codec::Size& s) const { s.raw(0, contentSize() + 4/*size*/); } + + template <class S> void decode(S& s) { + uint32_t size; uint32_t count; + s(size); + typename S::ScopedLimit l(s, size); + s(type)(count); + this->clear(); + this->resize(count, UnknownType(type)); + s(this->begin(), this->end()); + } + + uint8_t getType() const { return type; } + + private: + uint32_t contentSize() const { + return Codec::size(this->begin(), this->end()) + sizeof(uint32_t) /*count*/ + sizeof(uint8_t) /*type*/; + } + uint8_t type; +}; + +std::ostream& operator<<(std::ostream& o, const Array& a); + +// FIXME aconway 2008-04-08: hack to supress encoding of +// command-fragments and in-doubt as there is a problem with the spec +// (command-fragments does not have a one byte type code.) +namespace session { class CommandFragment; } +namespace dtx { class Xid; } + +template <> struct ArrayDomain<session::CommandFragment> : public Void {}; +template <> struct ArrayDomain<dtx::Xid> : public Void {}; +inline std::ostream& operator<<(std::ostream& o, const ArrayDomain<session::CommandFragment>&) { return o; } +inline std::ostream& operator<<(std::ostream& o, const ArrayDomain<dtx::Xid>&) { return o; } + +}} // namespace qpid::amqp_0_10 + +#endif /*!QPID_AMQP_0_10_ARRAY_H*/ diff --git a/qpid/cpp/src/qpid/amqp_0_10/Body.h b/qpid/cpp/src/qpid/amqp_0_10/Body.h new file mode 100644 index 0000000000..c96931551c --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/Body.h @@ -0,0 +1,55 @@ +#ifndef QPID_AMQP_0_10_BODY_H +#define QPID_AMQP_0_10_BODY_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 <string> +#include <ostream> + +namespace qpid { +namespace amqp_0_10 { + +/** Holds data from a body frame. */ +class Body { + public: + Body() {} + Body(size_t size_) : str(size_, '\0') {} + Body(const char* data_, size_t size_) : str(data_, size_) {} + + size_t size() const { return str.size(); }; + const char* data() const { return str.data(); } + char* data() { return const_cast<char*>(str.data()); } + + template <class S> void serialize(S& s) { s.raw(data(), size()); } + + private: + std::string str; + + friend std::ostream& operator<<(std::ostream&, const Body&); +}; + +inline std::ostream& operator<<(std::ostream& o, const Body& b) { + return o << b.str.substr(0, 16) << "... (" << b.size() << ")"; +} + +}} // namespace qpid::amqp_0_10 + +#endif /*!QPID_AMQP_0_10_BODY_H*/ diff --git a/qpid/cpp/src/qpid/amqp_0_10/Codec.h b/qpid/cpp/src/qpid/amqp_0_10/Codec.h new file mode 100644 index 0000000000..fd006a348e --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/Codec.h @@ -0,0 +1,213 @@ +#ifndef QPID_AMQP_0_10_CODEC_H +#define QPID_AMQP_0_10_CODEC_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/amqp_0_10/built_in_types.h" +#include "qpid/Serializer.h" +#include <boost/type_traits/is_integral.hpp> +#include <boost/type_traits/is_float.hpp> +#include <boost/type_traits/is_arithmetic.hpp> +#include <boost/detail/endian.hpp> +#include <boost/static_assert.hpp> +#include <iterator> + +namespace qpid { +namespace amqp_0_10 { + +template <class T> void reverse(T& t) { + char*p =reinterpret_cast<char*>(&t); + std::reverse(p, p+sizeof(T)); +} + +#ifdef BOOST_LITTLE_ENDIAN +template <class T> void bigEndian(T& t) { reverse(t); } +template <class T> void littleEndian(T&) {} +#else +template <class T> void littleEndian(T& t) { reverse(t); } +template <class T> void bigEndian(T&) {} +#endif + +/** + * AMQP 0-10 encoding and decoding. + */ +struct Codec { + /** Encode to an output byte iterator */ + template <class OutIter> + class Encoder : public EncoderBase<Encoder<OutIter> > + { + public: + typedef EncoderBase<Encoder<OutIter> > Base; + typedef OutIter Iterator; + + Encoder(OutIter o, size_t limit=Base::maxLimit()) : out(o) { + this->setLimit(limit); + } + + using EncoderBase<Encoder<OutIter> >::operator(); + + Encoder& operator()(bool x) { raw(x); return *this;} + Encoder& operator()(char x) { raw(x); return *this; } + Encoder& operator()(int8_t x) { raw(x); return *this; } + Encoder& operator()(uint8_t x) { raw(x); return *this; } + + Encoder& operator()(int16_t x) { return networkByteOrder(x); } + Encoder& operator()(int32_t x) { return networkByteOrder(x); } + Encoder& operator()(int64_t x) { return networkByteOrder(x); } + + Encoder& operator()(uint16_t x) { return networkByteOrder(x); } + Encoder& operator()(uint32_t x) { return networkByteOrder(x); } + Encoder& operator()(uint64_t x) { return networkByteOrder(x); } + + Encoder& operator()(float x) { return networkByteOrder(x); } + Encoder& operator()(double x) { return networkByteOrder(x); } + + void raw(const void* p, size_t n) { + this->addBytes(n); + out = std::copy((const char*)p, (const char*)p+n, out); + } + + void raw(char b) { this->addBytes(1); *out++=b; } + + template <class T> Encoder& littleEnd(T x) { + littleEndian(x); raw(&x, sizeof(x)); return *this; + } + + OutIter pos() const { return out; } + + private: + + template <class T> Encoder& networkByteOrder(T x) { + bigEndian(x); raw(&x, sizeof(x)); return *this; + } + + OutIter out; + }; + + template <class InIter> + class Decoder : public DecoderBase<Decoder<InIter> > { + public: + typedef DecoderBase<Decoder<InIter> > Base; + typedef InIter Iterator; + + Decoder(InIter i, size_t limit=Base::maxLimit()) : in(i) { + this->setLimit(limit); + } + + using DecoderBase<Decoder<InIter> >::operator(); + + // FIXME aconway 2008-03-10: wrong encoding, need packing support + Decoder& operator()(bool& x) { raw((char&)x); return *this; } + + Decoder& operator()(char& x) { raw((char&)x); return *this; } + Decoder& operator()(int8_t& x) { raw((char&)x); return *this; } + Decoder& operator()(uint8_t& x) { raw((char&)x); return *this; } + + Decoder& operator()(int16_t& x) { return networkByteOrder(x); } + Decoder& operator()(int32_t& x) { return networkByteOrder(x); } + Decoder& operator()(int64_t& x) { return networkByteOrder(x); } + + Decoder& operator()(uint16_t& x) { return networkByteOrder(x); } + Decoder& operator()(uint32_t& x) { return networkByteOrder(x); } + Decoder& operator()(uint64_t& x) { return networkByteOrder(x); } + + Decoder& operator()(float& x) { return networkByteOrder(x); } + Decoder& operator()(double& x) { return networkByteOrder(x); } + + void raw(void *p, size_t n) { + this->addBytes(n); + std::copy(in, in+n, (char*)p); + std::advance(in, n); + } + + void raw(char &b) { this->addBytes(1); b=*in++; } + + template <class T> Decoder& littleEnd(T& x) { + raw(&x, sizeof(x)); littleEndian(x); return *this; + } + + InIter pos() const { return in; } + + private: + + template <class T> Decoder& networkByteOrder(T& x) { + raw(&x, sizeof(x)); bigEndian(x); return *this; + } + + InIter in; + }; + + + class Size : public EncoderBase<Size> { + public: + Size() : size(0) {} + + operator size_t() const { return size; } + + using EncoderBase<Size>::operator(); + + // FIXME aconway 2008-03-10: wrong encoding, need packing support + Size& operator()(bool x) { size += sizeof(x); return *this; } + + Size& operator()(char x) { size += sizeof(x); return *this; } + Size& operator()(int8_t x) { size += sizeof(x); return *this; } + Size& operator()(uint8_t x) { size += sizeof(x); return *this; } + + Size& operator()(int16_t x) { size += sizeof(x); return *this; } + Size& operator()(int32_t x) { size += sizeof(x); return *this; } + Size& operator()(int64_t x) { size += sizeof(x); return *this; } + + Size& operator()(uint16_t x) { size += sizeof(x); return *this; } + Size& operator()(uint32_t x) { size += sizeof(x); return *this; } + Size& operator()(uint64_t x) { size += sizeof(x); return *this; } + + Size& operator()(float x) { size += sizeof(x); return *this; } + Size& operator()(double x) { size += sizeof(x); return *this; } + + // FIXME aconway 2008-04-03: optimize op()(Iter,Iter) + // for Iter with fixed-size value_type: + // distance(begin,end)*sizeof(value_type) + + void raw(const void*, size_t n){ size += n; } + + template <class T> Size& littleEnd(T) { size+= sizeof(T); return *this; } + + private: + size_t size; + }; + + // FIXME aconway 2008-03-11: rename to encoder(), decoder() + template <class InIter> static Decoder<InIter> decode(const InIter &i) { + return Decoder<InIter>(i); + } + + template <class OutIter> static Encoder<OutIter> encode(OutIter i) { + return Encoder<OutIter>(i); + } + + template <class T> static size_t size(const T& x) { return Size()(x); } + template <class Iter> static size_t size(const Iter& a, const Iter& z) { return Size()(a,z); } +}; + +}} // namespace qpid::amqp_0_10 + +#endif /*!QPID_AMQP_0_10_CODEC_H*/ diff --git a/qpid/cpp/src/qpid/amqp_0_10/Codecs.cpp b/qpid/cpp/src/qpid/amqp_0_10/Codecs.cpp new file mode 100644 index 0000000000..b976a5d09b --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/Codecs.cpp @@ -0,0 +1,347 @@ +/* + * + * 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/amqp_0_10/Codecs.h" +#include "qpid/framing/Array.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/framing/FieldValue.h" +#include "qpid/framing/List.h" +#include "qpid/log/Statement.h" +#include <algorithm> +#include <functional> +#include <limits> + +using namespace qpid::framing; +using namespace qpid::types; + +namespace qpid { +namespace amqp_0_10 { + +namespace { +const std::string iso885915("iso-8859-15"); +const std::string utf8("utf8"); +const std::string utf16("utf16"); +const std::string binary("binary"); +const std::string amqp0_10_binary("amqp0-10:binary"); +const std::string amqp0_10_bit("amqp0-10:bit"); +const std::string amqp0_10_datetime("amqp0-10:datetime"); +const std::string amqp0_10_struct("amqp0-10:struct"); +} + +template <class T, class U, class F> void convert(const T& from, U& to, F f) +{ + std::transform(from.begin(), from.end(), std::inserter(to, to.begin()), f); +} + +Variant::Map::value_type toVariantMapEntry(const FieldTable::value_type& in); +FieldTable::value_type toFieldTableEntry(const Variant::Map::value_type& in); +Variant toVariant(boost::shared_ptr<FieldValue> in); +boost::shared_ptr<FieldValue> toFieldValue(const Variant& in); + +template <class T, class U, class F> void translate(boost::shared_ptr<FieldValue> in, U& u, F f) +{ + T t; + getEncodedValue<T>(in, t); + convert(t, u, f); +} + +template <class T, class U, class F> T* toFieldValueCollection(const U& u, F f) +{ + typename T::ValueType t; + convert(u, t, f); + return new T(t); +} + +FieldTableValue* toFieldTableValue(const Variant::Map& map) +{ + FieldTable ft; + convert(map, ft, &toFieldTableEntry); + return new FieldTableValue(ft); +} + +ListValue* toListValue(const Variant::List& list) +{ + List l; + convert(list, l, &toFieldValue); + return new ListValue(l); +} + +void setEncodingFor(Variant& out, uint8_t code) +{ + switch(code){ + case 0x80: + case 0x90: + case 0xa0: + out.setEncoding(amqp0_10_binary); + break; + case 0x84: + case 0x94: + out.setEncoding(iso885915); + break; + case 0x85: + case 0x95: + out.setEncoding(utf8); + break; + case 0x86: + case 0x96: + out.setEncoding(utf16); + break; + case 0xab: + out.setEncoding(amqp0_10_struct); + break; + default: + //do nothing + break; + } +} + +qpid::types::Uuid getUuid(FieldValue& value) +{ + unsigned char data[16]; + value.getFixedWidthValue<16>(data); + return qpid::types::Uuid(data); +} + +Variant toVariant(boost::shared_ptr<FieldValue> in) +{ + Variant out; + //based on AMQP 0-10 typecode, pick most appropriate variant type + switch (in->getType()) { + //Fixed Width types: + case 0x01: out.setEncoding(amqp0_10_binary); + 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>()); 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; + case 0x20: out.setEncoding(amqp0_10_binary); + case 0x21: out = in->getIntegerValue<int32_t, 4>(); break; + case 0x22: out = in->getIntegerValue<uint32_t, 4>(); break; + case 0x23: out = in->get<float>(); break; + case 0x27: break; //TODO: utf-32 char + case 0x30: out.setEncoding(amqp0_10_binary); + case 0x31: out = in->getIntegerValue<int64_t, 8>(); break; + case 0x38: out.setEncoding(amqp0_10_datetime); //treat datetime as uint64_t, but set encoding + case 0x32: out = in->getIntegerValue<uint64_t, 8>(); break; + case 0x33: out = in->get<double>(); break; + + case 0x48: out = getUuid(*in); 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: + case 0x84: + case 0x85: + case 0x86: + case 0x90: + case 0x94: + case 0x95: + case 0x96: + case 0xa0: + case 0xab: + out = in->get<std::string>(); + setEncodingFor(out, in->getType()); + break; + + case 0xa8: + out = Variant::Map(); + translate<FieldTable>(in, out.asMap(), &toVariantMapEntry); + break; + + case 0xa9: + out = Variant::List(); + translate<List>(in, out.asList(), &toVariant); + break; + case 0xaa: //convert amqp0-10 array into variant list + out = Variant::List(); + translate<Array>(in, out.asList(), &toVariant); + break; + + default: + //error? + break; + } + return out; +} + +boost::shared_ptr<FieldValue> convertString(const std::string& value, const std::string& encoding) +{ + bool large = value.size() > std::numeric_limits<uint16_t>::max(); + if (encoding.empty() || encoding == amqp0_10_binary || encoding == binary) { + if (large) { + return boost::shared_ptr<FieldValue>(new Var32Value(value, 0xa0)); + } else { + return boost::shared_ptr<FieldValue>(new Var16Value(value, 0x90)); + } + } else if (encoding == utf8) { + if (!large) + return boost::shared_ptr<FieldValue>(new Str16Value(value)); + throw Exception(QPID_MSG("Could not encode utf8 character string - too long (" << value.size() << " bytes)")); + } else if (encoding == utf16) { + if (!large) + return boost::shared_ptr<FieldValue>(new Var16Value(value, 0x96)); + throw Exception(QPID_MSG("Could not encode utf16 character string - too long (" << value.size() << " bytes)")); + } else if (encoding == iso885915) { + if (!large) + return boost::shared_ptr<FieldValue>(new Var16Value(value, 0x94)); + throw Exception(QPID_MSG("Could not encode iso-8859-15 character string - too long (" << value.size() << " bytes)")); + } else { + // the encoding was not recognised + QPID_LOG(warning, "Unknown byte encoding: [" << encoding << "], encoding as vbin32."); + return boost::shared_ptr<FieldValue>(new Var32Value(value, 0xa0)); + } +} + +boost::shared_ptr<FieldValue> toFieldValue(const Variant& in) +{ + boost::shared_ptr<FieldValue> out; + switch (in.getType()) { + case VAR_VOID: out = boost::shared_ptr<FieldValue>(new VoidValue()); break; + case VAR_BOOL: out = boost::shared_ptr<FieldValue>(new BoolValue(in.asBool())); break; + case VAR_UINT8: out = boost::shared_ptr<FieldValue>(new Unsigned8Value(in.asUint8())); break; + case VAR_UINT16: out = boost::shared_ptr<FieldValue>(new Unsigned16Value(in.asUint16())); break; + case VAR_UINT32: out = boost::shared_ptr<FieldValue>(new Unsigned32Value(in.asUint32())); break; + case VAR_UINT64: out = boost::shared_ptr<FieldValue>(new Unsigned64Value(in.asUint64())); break; + case VAR_INT8: out = boost::shared_ptr<FieldValue>(new Integer8Value(in.asInt8())); break; + case VAR_INT16: out = boost::shared_ptr<FieldValue>(new Integer16Value(in.asInt16())); break; + case VAR_INT32: out = boost::shared_ptr<FieldValue>(new Integer32Value(in.asInt32())); break; + case VAR_INT64: out = boost::shared_ptr<FieldValue>(new Integer64Value(in.asInt64())); break; + case VAR_FLOAT: out = boost::shared_ptr<FieldValue>(new FloatValue(in.asFloat())); break; + case VAR_DOUBLE: out = boost::shared_ptr<FieldValue>(new DoubleValue(in.asDouble())); break; + case VAR_STRING: out = convertString(in.asString(), in.getEncoding()); break; + case VAR_UUID: out = boost::shared_ptr<FieldValue>(new UuidValue(in.asUuid().data())); break; + case VAR_MAP: + out = boost::shared_ptr<FieldValue>(toFieldTableValue(in.asMap())); + break; + case VAR_LIST: + out = boost::shared_ptr<FieldValue>(toListValue(in.asList())); + break; + } + return out; +} + +Variant::Map::value_type toVariantMapEntry(const FieldTable::value_type& in) +{ + return Variant::Map::value_type(in.first, toVariant(in.second)); +} + +FieldTable::value_type toFieldTableEntry(const Variant::Map::value_type& in) +{ + return FieldTable::value_type(in.first, toFieldValue(in.second)); +} + +struct EncodeBuffer +{ + char* data; + Buffer buffer; + + EncodeBuffer(size_t size) : data(new char[size]), buffer(data, size) {} + ~EncodeBuffer() { delete[] data; } + + template <class T> void encode(T& t) { t.encode(buffer); } + + void getData(std::string& s) { + s.assign(data, buffer.getSize()); + } +}; + +struct DecodeBuffer +{ + Buffer buffer; + + DecodeBuffer(const std::string& s) : buffer(const_cast<char*>(s.data()), s.size()) {} + + template <class T> void decode(T& t) { t.decode(buffer); } + +}; + +template <class T, class U, class F> void _encode(const U& value, std::string& data, F f) +{ + T t; + convert(value, t, f); + EncodeBuffer buffer(t.encodedSize()); + buffer.encode(t); + buffer.getData(data); +} + +template <class T, class U, class F> void _decode(const std::string& data, U& value, F f) +{ + T t; + DecodeBuffer buffer(data); + buffer.decode(t); + convert(t, value, f); +} + +void MapCodec::encode(const Variant::Map& value, std::string& data) +{ + _encode<FieldTable>(value, data, &toFieldTableEntry); +} + +void MapCodec::decode(const std::string& data, Variant::Map& value) +{ + _decode<FieldTable>(data, value, &toVariantMapEntry); +} + +size_t MapCodec::encodedSize(const Variant::Map& value) +{ + std::string encoded; + encode(value, encoded); + return encoded.size(); +} + +void ListCodec::encode(const Variant::List& value, std::string& data) +{ + _encode<List>(value, data, &toFieldValue); +} + +void ListCodec::decode(const std::string& data, Variant::List& value) +{ + _decode<List>(data, value, &toVariant); +} + +size_t ListCodec::encodedSize(const Variant::List& value) +{ + std::string encoded; + encode(value, encoded); + return encoded.size(); +} + +void translate(const Variant::Map& from, FieldTable& to) +{ + convert(from, to, &toFieldTableEntry); +} + +void translate(const FieldTable& from, Variant::Map& to) +{ + convert(from, to, &toVariantMapEntry); +} + +const std::string ListCodec::contentType("amqp/list"); +const std::string MapCodec::contentType("amqp/map"); + +}} // namespace qpid::amqp_0_10 diff --git a/qpid/cpp/src/qpid/amqp_0_10/Command.h b/qpid/cpp/src/qpid/amqp_0_10/Command.h new file mode 100644 index 0000000000..b1d3607a84 --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/Command.h @@ -0,0 +1,62 @@ +#ifndef QPID_AMQP_0_10_COMMAND_H +#define QPID_AMQP_0_10_COMMAND_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/amqp_0_10/Control.h" +#include "qpid/amqp_0_10/structs.h" + +namespace qpid { +namespace amqp_0_10 { + +struct CommandVisitor; +struct ConstCommandVisitor; +struct CommandHolder; +struct Command + : public Action, + public Visitable<CommandVisitor, ConstCommandVisitor, CommandHolder> +{ + using Action::getCommand; + Command* getCommand() { return this; } + uint8_t getCode() const; + uint8_t getClassCode() const; + const char* getName() const; + const char* getClassName() const; + + session::Header sessionHeader; +}; + +std::ostream& operator<<(std::ostream&, const Command&); + +template <class T> +struct CommandPacker : Packer<T> { + CommandPacker(T& t) : Packer<T>(t) {} + + template <class S> void serialize(S& s) { + s(this->data.sessionHeader); + Packer<T>::serialize(s); + } +}; + +}} // namespace qpid::amqp_0_10 + +#endif /*!QPID_AMQP_0_10_COMMAND_H*/ diff --git a/qpid/cpp/src/qpid/amqp_0_10/CommmandPacker.h b/qpid/cpp/src/qpid/amqp_0_10/CommmandPacker.h new file mode 100644 index 0000000000..51ebfe8186 --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/CommmandPacker.h @@ -0,0 +1,60 @@ +#ifndef QPID_AMQP_0_10_COMMMANDPACKER_H +#define QPID_AMQP_0_10_COMMMANDPACKER_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/amqp_0_10/structs.h" + +namespace qpid { +namespace amqp_0_10 { + +/** + * Packer for commands - serialize session.header before pack bits. + */ +template <class T> +class CommmandPacker : public Packer<T> +{ + public: + CommmandPacker(T& t) : Packer<T>(t) {} + template <class S> void serialize(S& s) { s.split(*this); } + + template <class S> void encode(S& s) const { + s.sessionHeader( + Packer<T>::encode(s); + } + + template <class S> void decode(S& s) { + Bits bits; + s.littleEnd(bits); + PackedDecoder<S, Bits> decode(s, bits); + data.serialize(decode); + } + + + protected: + T& data; + + +}; +}} // namespace qpid::amqp_0_10 + +#endif /*!QPID_AMQP_0_10_COMMMANDPACKER_H*/ diff --git a/qpid/cpp/src/qpid/amqp_0_10/Connection.cpp b/qpid/cpp/src/qpid/amqp_0_10/Connection.cpp new file mode 100644 index 0000000000..bf2e7d5713 --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/Connection.cpp @@ -0,0 +1,153 @@ +/* + * + * 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/amqp_0_10/Connection.h" +#include "qpid/log/Statement.h" +#include "qpid/amqp_0_10/exceptions.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/ProtocolInitiation.h" + +namespace qpid { +namespace amqp_0_10 { + +using sys::Mutex; + +Connection::Connection(sys::OutputControl& o, const std::string& id, bool _isClient) + : pushClosed(false), popClosed(false), output(o), identifier(id), initialized(false), + isClient(_isClient), buffered(0), version(0,10) +{} + +void Connection::setInputHandler(std::auto_ptr<sys::ConnectionInputHandler> c) { + connection = c; +} + +size_t Connection::decode(const char* buffer, size_t size) { + framing::Buffer in(const_cast<char*>(buffer), size); + if (isClient && !initialized) { + //read in protocol header + framing::ProtocolInitiation pi; + if (pi.decode(in)) { + if(!(pi==version)) + throw Exception(QPID_MSG("Unsupported version: " << pi + << " supported version " << version)); + QPID_LOG(trace, "RECV " << identifier << " INIT(" << pi << ")"); + } + initialized = true; + } + framing::AMQFrame frame; + while(frame.decode(in)) { + QPID_LOG(trace, "RECV [" << identifier << "]: " << frame); + connection->received(frame); + } + return in.getPosition(); +} + +bool Connection::canEncode() { + Mutex::ScopedLock l(frameQueueLock); + if (!popClosed) { + Mutex::ScopedUnlock u(frameQueueLock); + connection->doOutput(); + } + return !popClosed && ((!isClient && !initialized) || !frameQueue.empty()); +} + +bool Connection::isClosed() const { + Mutex::ScopedLock l(frameQueueLock); + return pushClosed && popClosed; +} + +size_t Connection::encode(const char* buffer, size_t size) { + { // Swap frameQueue data into workQueue to avoid holding lock while we encode. + Mutex::ScopedLock l(frameQueueLock); + if (popClosed) return 0; // Can't pop any more frames. + assert(workQueue.empty()); + workQueue.swap(frameQueue); + } + framing::Buffer out(const_cast<char*>(buffer), size); + if (!isClient && !initialized) { + framing::ProtocolInitiation pi(getVersion()); + pi.encode(out); + initialized = true; + QPID_LOG(trace, "SENT " << identifier << " INIT(" << pi << ")"); + } + size_t frameSize=0; + size_t encoded=0; + while (!workQueue.empty() && ((frameSize=workQueue.front().encodedSize()) <= out.available())) { + workQueue.front().encode(out); + QPID_LOG(trace, "SENT [" << identifier << "]: " << workQueue.front()); + workQueue.pop_front(); + encoded += frameSize; + if (workQueue.empty() && out.available() > 0) connection->doOutput(); + } + assert(workQueue.empty() || workQueue.front().encodedSize() <= size); + if (!workQueue.empty() && workQueue.front().encodedSize() > size) + throw InternalErrorException(QPID_MSG("Frame too large for buffer.")); + { + Mutex::ScopedLock l(frameQueueLock); + buffered -= encoded; + // Put back any frames we did not encode. + frameQueue.insert(frameQueue.begin(), workQueue.begin(), workQueue.end()); + workQueue.clear(); + if (frameQueue.empty() && pushClosed) + popClosed = true; + } + return out.getPosition(); +} + +void Connection::abort() { output.abort(); } +void Connection::activateOutput() { output.activateOutput(); } +void Connection::giveReadCredit(int32_t credit) { output.giveReadCredit(credit); } + +void Connection::close() { + // No more frames can be pushed onto the queue. + // Frames aleady on the queue can be popped. + Mutex::ScopedLock l(frameQueueLock); + pushClosed = true; +} + +void Connection::closed() { + connection->closed(); +} + +void Connection::send(framing::AMQFrame& f) { + { + Mutex::ScopedLock l(frameQueueLock); + if (!pushClosed) + frameQueue.push_back(f); + buffered += f.encodedSize(); + } + activateOutput(); +} + +framing::ProtocolVersion Connection::getVersion() const { + return version; +} + +void Connection::setVersion(const framing::ProtocolVersion& v) { + version = v; +} + +size_t Connection::getBuffered() const { + Mutex::ScopedLock l(frameQueueLock); + return buffered; +} + +}} // namespace qpid::amqp_0_10 diff --git a/qpid/cpp/src/qpid/amqp_0_10/Connection.h b/qpid/cpp/src/qpid/amqp_0_10/Connection.h new file mode 100644 index 0000000000..995d824796 --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/Connection.h @@ -0,0 +1,83 @@ +#ifndef QPID_AMQP_0_10_CONNECTION_H +#define QPID_AMQP_0_10_CONNECTION_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/framing/AMQFrame.h" +#include "qpid/sys/ConnectionCodec.h" +#include "qpid/sys/ConnectionInputHandler.h" +#include "qpid/sys/ConnectionOutputHandler.h" +#include "qpid/sys/Mutex.h" +#include "qpid/broker/BrokerImportExport.h" +#include <boost/intrusive_ptr.hpp> +#include <memory> +#include <deque> + +namespace qpid { + +namespace sys { +class ConnectionInputHandlerFactory; +} + +namespace amqp_0_10 { + +class Connection : public sys::ConnectionCodec, + public sys::ConnectionOutputHandler +{ + typedef std::deque<framing::AMQFrame> FrameQueue; + + FrameQueue frameQueue; + FrameQueue workQueue; + bool pushClosed, popClosed; + mutable sys::Mutex frameQueueLock; + sys::OutputControl& output; + std::auto_ptr<sys::ConnectionInputHandler> connection; + std::string identifier; + bool initialized; + bool isClient; + size_t buffered; + framing::ProtocolVersion version; + + public: + QPID_BROKER_EXTERN Connection(sys::OutputControl&, const std::string& id, bool isClient); + QPID_BROKER_EXTERN void setInputHandler(std::auto_ptr<sys::ConnectionInputHandler> c); + size_t decode(const char* buffer, size_t size); + size_t encode(const char* buffer, size_t size); + bool isClosed() const; + bool canEncode(); + void abort(); + void activateOutput(); + void giveReadCredit(int32_t); + void closed(); // connection closed by peer. + void close(); // closing from this end. + void send(framing::AMQFrame&); + framing::ProtocolVersion getVersion() const; + size_t getBuffered() const; + + /** Used by cluster code to set a special version on "update" connections. */ + // FIXME aconway 2009-07-30: find a cleaner mechanism for this. + void setVersion(const framing::ProtocolVersion&); +}; + +}} // namespace qpid::amqp_0_10 + +#endif /*!QPID_AMQP_0_10_CONNECTION_H*/ diff --git a/qpid/cpp/src/qpid/amqp_0_10/Control.h b/qpid/cpp/src/qpid/amqp_0_10/Control.h new file mode 100644 index 0000000000..ce188ae6d8 --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/Control.h @@ -0,0 +1,70 @@ +#ifndef QPID_AMQP_0_10_CONTROL_H +#define QPID_AMQP_0_10_CONTROL_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/amqp_0_10/Struct.h" + +namespace qpid { +namespace amqp_0_10 { + +struct Command; +struct Control; + +struct Action { // Base for commands & controls + virtual ~Action() {} + virtual Command* getCommand() { return 0; } + virtual Control* getControl() { return 0; } + + virtual const Command* getCommand() const { + return const_cast<Action*>(this)->getCommand(); + } + virtual const Control* getControl() const { + return const_cast<Action*>(this)->getControl(); + } + static const uint8_t SIZE=0; + static const uint8_t PACK=2; +}; + +struct ControlVisitor; +struct ConstControlVisitor; +struct ControlHolder; +struct Control + : public Action, + public Visitable<ControlVisitor, ConstControlVisitor, ControlHolder> +{ + using Action::getControl; + Control* getControl() { return this; } + uint8_t getCode() const; + uint8_t getClassCode() const; + const char* getName() const; + const char* getClassName() const; +}; +std::ostream& operator<<(std::ostream&, const Control&); + +template <SegmentType E> struct ActionType; +template <> struct ActionType<CONTROL> { typedef Control type; }; +template <> struct ActionType<COMMAND> { typedef Command type; }; + +}} // namespace qpid::amqp_0_10 + +#endif /*!QPID_AMQP_0_10_CONTROL_H*/ diff --git a/qpid/cpp/src/qpid/amqp_0_10/Decimal.h b/qpid/cpp/src/qpid/amqp_0_10/Decimal.h new file mode 100644 index 0000000000..50fc457c76 --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/Decimal.h @@ -0,0 +1,51 @@ +#ifndef TESTS_DECIMAL_H +#define TESTS_DECIMAL_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 <ostream> + +namespace qpid { +namespace amqp_0_10 { + +template <class E, class M> struct Decimal { + E exponent; + M mantissa; + + Decimal(E exp=0, M man=0) : exponent(exp), mantissa(man) {} + + bool operator==(const Decimal& d) const { + return exponent == d.exponent && mantissa == d.mantissa; + } + + // TODO aconway 2008-02-20: We could provide arithmetic operators + // if anybody really cares about this type. + + template <class S> void serialize(S& s) { s(exponent)(mantissa); } +}; + +template<class E, class M> +inline std::ostream& operator<<(std::ostream& o, const Decimal<E,M>& d) { + return o << "Decimal{" << d.mantissa << "/10^" << (int)d.exponent << "}"; +} +}} + +#endif /*!TESTS_DECIMAL_H*/ diff --git a/qpid/cpp/src/qpid/amqp_0_10/Exception.h b/qpid/cpp/src/qpid/amqp_0_10/Exception.h new file mode 100644 index 0000000000..6d526c1706 --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/Exception.h @@ -0,0 +1,96 @@ +#ifndef QPID_AMQP_0_10_EXCEPTION_H +#define QPID_AMQP_0_10_EXCEPTION_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/Exception.h" +#include "qpid/amqp_0_10/specification_fwd.h" + +namespace qpid { +namespace amqp_0_10 { + +/** + * Raised when the connection is unexpectedly closed. Sessions with + * non-0 timeout may be available for re-attachment on another connection. + */ +struct ConnectionException : public qpid::Exception { + // FIXME aconway 2008-04-04: Merge qpid::ConnectionException + // into this when the old code is removed. + typedef connection::CloseCode Code; + ConnectionException(Code c, const std::string m) + : qpid::Exception(m), code(c) {} + Code code; +}; + +/** + * Raised when a session is unexpectedly detached for any reason, or + * if an attempt is made to use a session that is not attached. + */ +struct SessionException : public qpid::Exception { + // FIXME aconway 2008-04-04: should not have a code at this level. + // Leave in place till old preview code is gone. + SessionException(int /*code*/, const std::string& msg) : qpid::Exception(msg) {} +}; + +/** Raised when the state of a session has been destroyed */ +struct SessionDestroyedException : public SessionException { + // FIXME aconway 2008-04-04: should not have a code at this level. + // Leave in place till old preview code is gone. + SessionDestroyedException(int code, const std::string& msg) : SessionException(code, msg){} +}; + +/** Raised when a session is destroyed due to an execution.exception */ +struct SessionAbortedException : public SessionDestroyedException { + typedef execution::ErrorCode Code; + SessionAbortedException(Code c, const std::string m) + : SessionDestroyedException(c, m), code(c) {} + Code code; +}; + +/** + * Raised when a session with 0 timeout is unexpectedly detached + * and therefore expires and is destroyed. + */ +struct SessionExpiredException : public SessionDestroyedException { + typedef session::DetachCode Code; + SessionExpiredException(Code c, const std::string m) + : SessionDestroyedException(c, m), code(c) {} + Code code; +}; + +/** + * Raised when a session with non-0 timeout is unexpectedly detached + * or if an attempt is made to use a session that is not attached. + * + * The session is not necessarily destroyed, it may be possible to + * re-attach. + */ +struct SessionDetachedException : public SessionException { + typedef session::DetachCode Code; + SessionDetachedException(Code c, const std::string m) + : SessionException(c, m), code(c) {} + Code code; +}; + +}} // namespace qpid::amqp_0_10 + +#endif /*!QPID_AMQP_0_10_EXCEPTION_H*/ diff --git a/qpid/cpp/src/qpid/amqp_0_10/FrameHeader.cpp b/qpid/cpp/src/qpid/amqp_0_10/FrameHeader.cpp new file mode 100644 index 0000000000..371e3c1bcb --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/FrameHeader.cpp @@ -0,0 +1,50 @@ +/* + * + * 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/amqp_0_10/FrameHeader.h" +#include <ios> +#include <iomanip> +#include <ostream> + +using namespace std; + +namespace qpid { +namespace amqp_0_10 { + +bool FrameHeader::operator==(const FrameHeader& x) const { + return flags == x.flags && + type == x.type && + size == x.size && + track == x.track && + channel == x.channel; +} + +std::ostream& operator<<(std::ostream& o, const FrameHeader& f) { + std::ios::fmtflags saveFlags = o.flags(); + return o << "Frame[" + << "flags=" << std::hex << std::showbase << int(f.getFlags()) << std::setiosflags(saveFlags) + << " type=" << f.getType() + << " size=" << f.getSize() + << " track=" << int(f.getTrack()) + << " channel=" << f.getChannel() + << "]"; +} + +}} // namespace qpid::amqp_0_10 diff --git a/qpid/cpp/src/qpid/amqp_0_10/FrameHeader.h b/qpid/cpp/src/qpid/amqp_0_10/FrameHeader.h new file mode 100644 index 0000000000..b2f0619f9b --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/FrameHeader.h @@ -0,0 +1,90 @@ +#ifndef QPID_AMQP_0_10_FRAMEHEADER_H +#define QPID_AMQP_0_10_FRAMEHEADER_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/amqp_0_10/built_in_types.h" +#include <boost/shared_array.hpp> +#include <string.h> +#include <assert.h> +#include <iosfwd> + +namespace qpid { +namespace amqp_0_10 { + +enum FrameFlags { FIRST_SEGMENT=8, LAST_SEGMENT=4, FIRST_FRAME=2, LAST_FRAME=1 }; + +class FrameHeader { + public: + static const size_t SIZE=12; + static uint8_t trackFor(SegmentType type) { return type == 0 ? 0 : 1; } + + FrameHeader(uint8_t flags_=0, SegmentType type_=SegmentType(), uint16_t size_=0, uint8_t track_=0, uint16_t channel_=0) + : flags(flags_), type(type_), size(size_), track(track_), channel(channel_) + {} + + uint8_t getFlags() const { return flags; } + SegmentType getType() const { return type; } + /** @return size total size of of frame, including frame header. */ + uint16_t getSize() const { return size; } + /** @return size of frame data, excluding frame header. */ + uint16_t getDataSize() const { return size - SIZE; } + uint8_t getTrack() const { return track; } + uint16_t getChannel() const { return channel; } + + void setFlags(uint8_t flags_) { flags=flags_; } + /** Also sets the track. There is no setTrack() */ + void setType(SegmentType type_) { type=type_; track=trackFor(type); } + /** @param size total size of of frame, including frame header. */ + void setSize(uint16_t size_) { size = size_; } + /** @param size size of frame data, excluding frame header. */ + void setDataSize(uint16_t size_) { size = size_+SIZE; } + void setChannel(uint8_t channel_) { channel=channel_; } + + bool allFlags(uint8_t f) const { return (flags & f) == f; } + bool anyFlags(uint8_t f) const { return (flags & f); } + + void raiseFlags(uint8_t f) { flags |= f; } + void clearFlags(uint8_t f) { flags &= ~f; } + + bool isComplete() const { return allFlags(FIRST_FRAME | LAST_FRAME); } + + bool operator==(const FrameHeader&) const; + + template <class S> void serialize(S& s) { + uint8_t pad8=0; uint32_t pad32=0; + s(flags)(type)(size)(pad8)(track)(channel)(pad32); + } + + private: + uint8_t flags; + SegmentType type; + uint16_t size; + uint8_t track; + uint16_t channel; +}; + +std::ostream& operator<<(std::ostream&, const FrameHeader&); + +}} // namespace qpid::amqp_0_10 + +#endif /*!QPID_AMQP_0_10_FRAMEHEADER_H*/ diff --git a/qpid/cpp/src/qpid/amqp_0_10/Header.cpp b/qpid/cpp/src/qpid/amqp_0_10/Header.cpp new file mode 100644 index 0000000000..d83814e969 --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/Header.cpp @@ -0,0 +1,34 @@ +/* + * + * 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/amqp_0_10/Header.h" + +namespace qpid { +namespace amqp_0_10 { + +std::ostream& operator<<(std::ostream& o, const Header& h) { + o << "Header["; + std::ostream_iterator<Struct32> i(o, " "); + std::copy(h.begin(), h.end(), i); + o << "]"; + return o; +} + +}} // namespace qpid::amqp_0_10 diff --git a/qpid/cpp/src/qpid/amqp_0_10/Header.h b/qpid/cpp/src/qpid/amqp_0_10/Header.h new file mode 100644 index 0000000000..0ce6ad9135 --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/Header.h @@ -0,0 +1,53 @@ +#ifndef QPID_AMQP_0_10_HEADER_H +#define QPID_AMQP_0_10_HEADER_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/amqp_0_10/built_in_types.h" +#include "qpid/amqp_0_10/Struct32.h" +#include <vector> +#include <ostream> + +namespace qpid { +namespace amqp_0_10 { + +class Header : public std::vector<Struct32> { + public: + Header() {} + + template <class S> void serialize(S& s) { s.split(*this); } + template <class S> void encode(S& s) const { s(this->begin(), this->end()); } + template <class S> void decode(S& s); +}; + +template <class S> void Header::decode(S& s) { + this->clear(); + while (s.bytesRemaining() > 0) { + this->push_back(Struct32()); + s(this->back()); + } +} + +std::ostream& operator<<(std::ostream& o, const Header&); + +}} // namespace qpid::amqp_0_10 + +#endif /*!QPID_AMQP_0_10_HEADER_H*/ diff --git a/qpid/cpp/src/qpid/amqp_0_10/Holder.h b/qpid/cpp/src/qpid/amqp_0_10/Holder.h new file mode 100644 index 0000000000..605d2e0ed5 --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/Holder.h @@ -0,0 +1,103 @@ +#ifndef QPID_AMQP_0_10_HOLDER_H +#define QPID_AMQP_0_10_HOLDER_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/framing/Blob.h" +#include "qpid/amqp_0_10/apply.h" + +namespace qpid { +namespace amqp_0_10 { + +using framing::in_place; + +template <class Invokable> struct InvokeVisitor { + typedef void result_type; + Invokable& target; + InvokeVisitor(Invokable& i) : target(i) {} + + template <class Action> + void operator()(const Action& action) { action.invoke(target); } +}; + +template <class DerivedHolder, class BaseHeld, size_t Size> +class Holder : public framing::Blob<Size, BaseHeld> { + typedef framing::Blob<Size, BaseHeld> Base; + + public: + + Holder() {} + template <class T> explicit Holder(const T& value) : Base(value) {} + + using Base::operator=; + Holder& operator=(const BaseHeld& rhs); + + uint8_t getCode() const { return this->get()->getCode(); } + uint8_t getClassCode() const { return this->get()->getClassCode(); } + + template <class Invokable> void invoke(Invokable& i) const { + InvokeVisitor<Invokable> v(i); + apply(v, *this->get()); + } + + template <class S> void encode(S& s) const { + s(getClassCode())(getCode()); + } + + template <class S> void decode(S& s) { + uint8_t code, classCode; + s(classCode)(code); + static_cast<DerivedHolder*>(this)->set(classCode, code); + } + + template <class S> void serialize(S& s) { + s.split(*this); + qpid::amqp_0_10::apply(s, *this->get()); + } + + template <class T> T* getIf() { + return (getClassCode()==T::CLASS_CODE && getCode()==T::CODE) ? static_cast<T*>(this->get()) : 0; + } + + template <class T> const T* getIf() const { + return (getClassCode()==T::CLASS_CODE && getCode()==T::CODE) ? static_cast<T*>(this->get()) : 0; + } + + private: + struct Assign : public ApplyFunctor<void> { + Holder& holder; + Assign(Holder& x) : holder(x) {} + template <class T> void operator()(const T& rhs) { holder=rhs; } + }; +}; + +template <class D, class B, size_t S> +Holder<D,B,S>& Holder<D,B,S>::operator=(const B& rhs) { + Assign assign(*this); + qpid::amqp_0_10::apply(assign, rhs); + return *this; +} + + + +}} // namespace qpid::amqp_0_10 + +#endif /*!QPID_AMQP_0_10_HOLDER_H*/ diff --git a/qpid/cpp/src/qpid/amqp_0_10/Map.cpp b/qpid/cpp/src/qpid/amqp_0_10/Map.cpp new file mode 100644 index 0000000000..af3b302d25 --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/Map.cpp @@ -0,0 +1,66 @@ +/* + * + * 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/amqp_0_10/Map.h" +#include "qpid/amqp_0_10/Struct32.h" +#include "qpid/amqp_0_10/Array.h" +#include <ostream> + +namespace qpid { +namespace amqp_0_10 { + +MapValue::MapValue() : code(codeFor(uint8_t(0))), blob(in_place<uint8_t>(0)) {} + +MapValue::MapValue(const MapValue& x) : code(x.code), blob(x.blob) {} + +bool MapValue::operator==(const MapValue& x) const { + return code == x.code; // FIXME aconway 2008-04-01: incomplete +} + +struct OstreamVisitor : public MapValue::Visitor<std::ostream&> { + std::ostream& out; + OstreamVisitor(std::ostream& o) : out(o) {} + template <class T> std::ostream& operator()(const T& t) { + return out << t; + } +}; + +std::ostream& operator<<(std::ostream& o, const MapValue& m) { + o << typeName(m.getCode()) << ":"; + const_cast<MapValue&>(m).apply_visitor(OstreamVisitor(o)); + return o; +} + +std::ostream& operator<<(std::ostream& o, const Map::value_type& v) { + return o << v.first << "=" << v.second; +} +std::ostream& operator<<(std::ostream& o, const Map& map) { + o << "map["; + std::ostream_iterator<Map::value_type> i(o, " "); + std::copy(map.begin(), map.end(), i); + return o << "]"; +} + +uint32_t Map::contentSize() const { + // FIXME aconway 2008-04-03: preview to 0-10 mapping: +4 for count. + return /*4 +*/ Codec::Size()(begin(), end()); +} + +}} // namespace qpid::amqp_0_10 diff --git a/qpid/cpp/src/qpid/amqp_0_10/Map.h b/qpid/cpp/src/qpid/amqp_0_10/Map.h new file mode 100644 index 0000000000..4093b1a0aa --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/Map.h @@ -0,0 +1,188 @@ +#ifndef QPID_AMQP_0_10_MAP_H +#define QPID_AMQP_0_10_MAP_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 ang + * "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/Exception.h" +#include "qpid/amqp_0_10/built_in_types.h" +#include "qpid/amqp_0_10/UnknownType.h" +#include "qpid/amqp_0_10/CodeForType.h" +#include "qpid/amqp_0_10/TypeForCode.h" +#include "qpid/amqp_0_10/Codec.h" +#include "qpid/framing/Blob.h" +#include <map> +#include <string> +#include <iosfwd> + +namespace qpid { +namespace amqp_0_10 { + +class Map; + +class MapValue { + public: + struct BadTypeException : public Exception {}; + + template <class R> struct Visitor { typedef R result_type; }; + + MapValue(); + MapValue(const MapValue& x); + template <class T> explicit MapValue(const T& t); + template <class T> MapValue& operator=(const T& t); + + template <class T> T* get(); + template <class T> const T* get() const; + + template <class V> typename V::result_type apply_visitor(V&); + template <class V> typename V::result_type apply_visitor(const V&); + + uint8_t getCode() const { return code; } + + bool operator==(const MapValue&) const; + + template <class S> void serialize(S& s) { s(code); s.split(*this); } + template <class S> void encode(S& s) const { + const_cast<MapValue*>(this)->apply_visitor(s); + } + template <class S> void decode(S& s) { + DecodeVisitor<S> dv(blob, s); + qpid::amqp_0_10::apply_visitor(dv, code); + } + + + private: + // TODO aconway 2008-04-15: Estimate required size, we will get a + // compile error from static_assert in Blob.h if the estimate is too + // low. We can't use sizeof() directly because #include Struct32.h + // creates a circular dependency. Needs a better solution. + static const size_t SIZE=256; + typedef framing::Blob<SIZE> Blob; + + template <class V> struct VisitVisitor; + template <class T> struct GetVisitor; + template <class D> struct DecodeVisitor; + + uint8_t code; + Blob blob; +}; + +class Map : public std::map<Str8, MapValue> { + public: + template <class S> void serialize(S& s) { s.split(*this); } + template <class S> void encode(S& s) const; + // Shortcut calculation for size. + void encode(Codec::Size& s) const { s.raw(0, contentSize() + 4/*size*/); } + + template <class S> void decode(S& s); + + private: + uint32_t contentSize() const; +}; + +std::ostream& operator<<(std::ostream&, const MapValue&); +std::ostream& operator<<(std::ostream&, const Map::value_type&); +std::ostream& operator<<(std::ostream&, const Map&); + +using framing::in_place; + +template <class T> MapValue::MapValue(const T& t) : code(codeFor(t)), blob(in_place<t>()) {} + +template <class T> MapValue& MapValue::operator=(const T& t) { + code=codeFor(t); + blob=t; + return *this; +} + +template <class V> struct MapValue::VisitVisitor { + typedef typename V::result_type result_type; + V& visitor; + Blob& blob; + VisitVisitor(V& v, Blob& b) : visitor(v), blob(b) {} + + template <class T> result_type operator()(T*) { + return visitor(*reinterpret_cast<T*>(blob.get())); + } +}; + +template <class V> typename V::result_type MapValue::apply_visitor(V& v) { + VisitVisitor<V> visitor(v, blob); + return qpid::amqp_0_10::apply_visitor(visitor, code); +} + +template <class R> struct MapValue::GetVisitor { + typedef R* result_type; + const MapValue::Blob& blob; + + GetVisitor(const MapValue::Blob& b) : blob(b) {} + + R* operator()(R& r) { return &r; } + template <class T> R* operator()(T&) { return 0; } +}; + +template <class D> struct MapValue::DecodeVisitor { + typedef void result_type; + MapValue::Blob& blob; + D& decoder; + DecodeVisitor(Blob& b, D& d) : blob(b), decoder(d) {} + + template <class T> void operator()(T*) { + T t; + decoder(t); + blob = t; + } +}; + +template <class T> T* MapValue::get() { return apply_visitor(GetVisitor<T>(blob)); } +template <class T> const T* MapValue::get() const { return apply_visitor(GetVisitor<const T>()); } + +template <class V> typename V::result_type MapValue::apply_visitor(const V& v) { + return apply_visitor(const_cast<V&>(v)); +} + +template <class S> void Map::encode(S& s) const { + // FIXME aconway 2008-04-03: replace preview mapping with 0-10 mapping: + // s(contentSize())(uint32_t(size())); // size, count + s(contentSize()); + for (const_iterator i = begin(); i != end(); ++i) + s(i->first)(i->second); // key (type value) +} + +template <class S> void Map::decode(S& s) { + uint32_t decodedSize /*, count*/; + // FIXME aconway 2008-04-03: replace preview mapping with 0-10 mapping: + // s(contentSize())(uint32_t(size())); // size, count + // s(decodedSize)(count); + s(decodedSize); + typename S::ScopedLimit l(s, decodedSize); // Make sure we don't overrun. + // FIXME aconway 2008-04-03: replace preview with 0-10: + // for ( ; count > 0; --count) { + while (s.bytesRemaining() > 0) { + key_type k; MapValue v; + s(k)(v); + insert(value_type(k,v)); + } +} + + +}} // namespace qpid::amqp_0_10 + +#endif /*!QPID_AMQP_0_10_MAP_H*/ diff --git a/qpid/cpp/src/qpid/amqp_0_10/Packer.h b/qpid/cpp/src/qpid/amqp_0_10/Packer.h new file mode 100644 index 0000000000..c38e3a7efa --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/Packer.h @@ -0,0 +1,195 @@ +#ifndef QPID_PACKER_H +#define QPID_PACKER_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 <boost/optional.hpp> +#include <boost/none.hpp> +#include "qpid/amqp_0_10/built_in_types.h" + +namespace qpid { +namespace amqp_0_10 { + +/** Serialization for optional values */ +template <class T> struct SerializableOptional { + boost::optional<T>& optional; + SerializableOptional(boost::optional<T>& x) : optional(x) {} + template <class S> void serialize(S& s) { + if (optional) + s(*optional); + } +}; + +}} + + +namespace boost { // For argument dependent lookup. + +template <class T> +qpid::amqp_0_10::SerializableOptional<T> serializable(boost::optional<T>& x) { + return qpid::amqp_0_10::SerializableOptional<T>(x); +} + +} // namespace boost + +namespace qpid { +namespace amqp_0_10 { + +/** "Encoder" that encodes a struct as a set of bit flags + * for all non-empty members. + */ +class PackBits { + public: + PackBits() : bit(1), bits(0) {} + + void setBit(bool b) { if (b) bits |= bit; bit <<= 1; } + uint32_t getBits() { return bits; } + + /** The bit is always set for non-optional values. */ + template <class T> + PackBits& operator()(const T&) { setBit(1); return *this; } + + /** For optional values the bit is set if the value is present. */ + template <class T> PackBits& operator()(const boost::optional<T>& opt) { + setBit(opt); return *this; + } + + /** Bits are special optional values */ + PackBits& operator()(Bit b) { setBit(b); return *this; } + + private: + uint32_t bit; + uint32_t bits; +}; + +/** Bit mask to encode a packable struct */ +template<class T> uint32_t packBits(const T& t) { + PackBits pack; + const_cast<T&>(t).serialize(pack); + return pack.getBits(); +} + +/** Decode members enabled by Bits */ +template <class Decoder, class Bits> +class PackedDecoder { + public: + PackedDecoder(Decoder& d, Bits b) : decode(d), bits(b) {} + + template <class T> PackedDecoder& operator()(T& t) { + if (bits & 1) + decode(t); + else + t = T(); + // FIXME aconway 2008-04-10: When we have all optionals + // represented by boost::optional the line above should be: + // throw CommandInvalidException("A required value was omitted."); + bits >>= 1; + return *this; + } + + template <class T> PackedDecoder& operator()(boost::optional<T>& opt) { + if (bits & 1) { + opt = T(); + decode(*opt); + } + else + opt = boost::none; + bits >>= 1; + return *this; + } + + private: + Decoder& decode; + Bits bits; +}; + +/** Metafunction to compute type to contain pack bits. */ +template <int Bytes> struct UintOfSize; +template <> struct UintOfSize<1> { typedef uint8_t type; }; +template <> struct UintOfSize<2> { typedef uint16_t type; }; +template <> struct UintOfSize<4> { typedef uint32_t type; }; + +/** + * Helper to serialize packed structs. + */ +template <class T> class Packer +{ + public: + typedef typename UintOfSize<T::PACK>::type Bits; + + Packer(T& t) : data(t) {} + + template <class S> void serialize(S& s) { s.split(*this); } + + template <class S> void encode(S& s) const { + Bits bits = packBits(data); + s.littleEnd(bits); + data.serialize(s); + } + + template <class S> void decode(S& s) { + Bits bits; + s.littleEnd(bits); + PackedDecoder<S, Bits> decode(s, bits); + data.serialize(decode); + } + + + protected: + T& data; +}; + +template <class T, uint8_t=T::SIZE> struct SizedPacker : public Packer<T> { + typedef typename UintOfSize<T::SIZE>::type Size; + + SizedPacker(T& t) : Packer<T>(t) {} + + template <class S> void serialize(S& s) { + s.split(*this); + } + + template <class S> void encode(S& s) const { + Codec::Size sizer; + this->data.serialize(sizer); + Size size=size_t(sizer)+T::PACK; // Size with pack bits. + s(size); + Packer<T>::encode(s); + } + + template <class S> void decode(S& s) { + Size size; + s(size); + typename S::ScopedLimit l(s, size); + Packer<T>::decode(s); + } + +}; + +template <class T> struct SizedPacker<T,0> : public Packer<T> { + SizedPacker(T& t) : Packer<T>(t) {} +}; + +}} // namespace qpid::amqp_0_10 + + + +#endif /*!QPID_PACKER_H*/ diff --git a/qpid/cpp/src/qpid/amqp_0_10/SerializableString.h b/qpid/cpp/src/qpid/amqp_0_10/SerializableString.h new file mode 100644 index 0000000000..485b7ca6a8 --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/SerializableString.h @@ -0,0 +1,62 @@ +#ifndef QPID_AMQP_0_10_SERIALIZABLESTRING_H +#define QPID_AMQP_0_10_SERIALIZABLESTRING_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. + * + */ +namespace qpid { +namespace amqp_0_10 { + +/** Template for length-prefixed strings/arrays. + * Unique parameter allows creation of distinct SerializableString + * types with the smae T/SizeType + */ +template <class T, class SizeType, int Unique=0> +struct SerializableString : public std::basic_string<T> { + SerializableString() {} + template <class U> SerializableString(const U& u) : std::basic_string<T>(u) {} + template <class I> SerializableString(const I& i, const I& j) : std::basic_string<T>(i,j) {} + + using std::basic_string<T>::operator=; + + template <class S> void serialize(S& s) { s.split(*this); } + + template <class S> void encode(S& s) const { + s(SizeType(this->size()))(this->begin(), this->end()); + } + + template <class S> void decode(S& s) { + SizeType newSize; + s(newSize); + this->resize(newSize); + s(this->begin(), this->end()); + } +}; + +// TODO aconway 2008-02-29: separate ostream ops +template <class T, class SizeType> +std::ostream& operator<<(std::ostream& o, const SerializableString<T,SizeType>& s) { + const std::basic_string<T> str(s); + return o << str.c_str(); // TODO aconway 2008-02-29: why doesn't o<<str work? +} + +}} // namespace qpid::amqp_0_10 + +#endif /*!QPID_AMQP_0_10_SERIALIZABLESTRING_H*/ diff --git a/qpid/cpp/src/qpid/amqp_0_10/SessionHandler.cpp b/qpid/cpp/src/qpid/amqp_0_10/SessionHandler.cpp new file mode 100644 index 0000000000..97281a8d8c --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/SessionHandler.cpp @@ -0,0 +1,332 @@ +/* + * 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/amqp_0_10/SessionHandler.h" +#include "qpid/SessionState.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/AllInvoker.h" +#include "qpid/framing/enum.h" +#include "qpid/log/Statement.h" + + +#include <boost/bind.hpp> + +namespace qpid { +namespace amqp_0_10 { +using namespace framing; +using namespace std; + +void SessionHandler::checkAttached() { + if (!getState()) + throw NotAttachedException(QPID_MSG("Channel " << channel.get() << " is not attached")); +} + +SessionHandler::SessionHandler(FrameHandler* out, ChannelId ch) + : channel(ch, out), peer(channel), + awaitingDetached(false), + sendReady(), receiveReady() {} + +SessionHandler::~SessionHandler() {} + +namespace { +bool isSessionControl(AMQMethodBody* m) { + return m && m->amqpClassId() == SESSION_CLASS_ID; +} + +session::DetachCode convert(uint8_t code) { + switch(code) { + case 0: return session::DETACH_CODE_NORMAL; + case 1: return session::DETACH_CODE_SESSION_BUSY; + case 2: return session::DETACH_CODE_TRANSPORT_BUSY; + case 3: return session::DETACH_CODE_NOT_ATTACHED; + case 4: default: return session::DETACH_CODE_UNKNOWN_IDS; + } +} + +} // namespace + +void SessionHandler::invoke(const AMQMethodBody& m) { + framing::invoke(*this, m); +} + +void SessionHandler::handleIn(AMQFrame& f) { + // Note on channel states: a channel is attached if session != 0 + AMQMethodBody* m = f.getBody()->getMethod(); + try { + // Ignore all but detach controls while awaiting detach + if (awaitingDetached) { + if (!isSessionControl(m)) return; + if (m->amqpMethodId() != SESSION_DETACH_METHOD_ID && + m->amqpMethodId() != SESSION_DETACHED_METHOD_ID) + return; + } + if (isSessionControl(m)) { + invoke(*m); + } + else { + // Drop frames if we are detached. + if (!getState()) return; + if (!receiveReady) + throw IllegalStateException(QPID_MSG(getState()->getId() << ": Not ready to receive data")); + if (!getState()->receiverRecord(f)) + return; // Ignore duplicates. + if (getState()->receiverNeedKnownCompleted()) + sendCompletion(); + getInHandler()->handle(f); + } + } + catch(const SessionException& e) { + QPID_LOG(error, "Execution exception: " << e.what()); + executionException(e.code, e.what()); // Let subclass handle this first. + framing::AMQP_AllProxy::Execution execution(channel); + AMQMethodBody* m = f.getMethod(); + SequenceNumber commandId; + if (getState()) commandId = getState()->receiverGetCurrent(); + execution.exception(e.code, commandId, m ? m->amqpClassId() : 0, m ? m->amqpMethodId() : 0, 0, e.what(), FieldTable()); + detaching(); + sendDetach(); + } + catch(const ChannelException& e){ + QPID_LOG(error, "Channel exception: " << e.what()); + channelException(e.code, e.what()); // Let subclass handle this first. + peer.detached(name, e.code); + } + catch(const ConnectionException& e) { + QPID_LOG(error, "Connection exception: " << e.what()); + connectionException(e.code, e.getMessage()); + } + catch(const std::exception& e) { + QPID_LOG(error, "Unexpected exception: " << e.what()); + connectionException(connection::CLOSE_CODE_FRAMING_ERROR, e.what()); + } +} + +void SessionHandler::handleException(const qpid::SessionException& e) +{ + QPID_LOG(error, "Execution exception (during output): " << e.what()); + executionException(e.code, e.what()); // Let subclass handle this first. + framing::AMQP_AllProxy::Execution execution(channel); + execution.exception(e.code, 0, 0, 0, 0, e.what(), FieldTable()); + detaching(); + sendDetach(); +} + +namespace { +bool isCommand(const AMQFrame& f) { + return f.getMethod() && f.getMethod()->type() == framing::SEGMENT_TYPE_COMMAND; +} +} // namespace + +void SessionHandler::handleOut(AMQFrame& f) { + checkAttached(); + if (!sendReady) + throw IllegalStateException(QPID_MSG(getState()->getId() << ": Not ready to send data")); + getState()->senderRecord(f); + if (isCommand(f) && getState()->senderNeedFlush()) { + peer.flush(false, false, true); + getState()->senderRecordFlush(); + } + channel.handle(f); +} + +void SessionHandler::attach(const std::string& name_, bool force) { + // Save the name for possible session-busy exception. Session-busy + // can be thrown before we have attached the handler to a valid + // SessionState, and in that case we need the name to send peer.detached + name = name_; + if (getState() && name == getState()->getId().getName()) + return; // Idempotent + if (getState()) + throw TransportBusyException( + QPID_MSG("Channel " << channel.get() << " already attached to " << getState()->getId())); + setState(name, force); + QPID_LOG(debug, "Attached channel " << channel.get() << " to " << getState()->getId()); + peer.attached(name); + if (getState()->hasState()) + peer.flush(true, true, true); + else + sendCommandPoint(getState()->senderGetCommandPoint()); +} + +#define CHECK_NAME(NAME, MSG) do { \ + checkAttached(); \ + if (NAME != getState()->getId().getName()) \ + throw InvalidArgumentException( \ + QPID_MSG(MSG << ": incorrect session name: " << NAME \ + << ", expecting: " << getState()->getId().getName())); \ + } while(0) + + +void SessionHandler::attached(const std::string& name) { + CHECK_NAME(name, "session.attached"); +} + +void SessionHandler::detach(const std::string& name) { + CHECK_NAME(name, "session.detach"); + peer.detached(name, session::DETACH_CODE_NORMAL); + handleDetach(); +} + +void SessionHandler::detached(const std::string& name, uint8_t code) { + CHECK_NAME(name, "session.detached"); + awaitingDetached = false; + if (code != session::DETACH_CODE_NORMAL) + channelException(convert(code), "session.detached from peer."); + else { + handleDetach(); + } +} + +void SessionHandler::handleDetach() { + sendReady = receiveReady = false; +} + +void SessionHandler::requestTimeout(uint32_t t) { + checkAttached(); + getState()->setTimeout(t); + peer.timeout(getState()->getTimeout()); +} + +void SessionHandler::timeout(uint32_t t) { + checkAttached(); + getState()->setTimeout(t); +} + +void SessionHandler::commandPoint(const SequenceNumber& id, uint64_t offset) { + checkAttached(); + getState()->receiverSetCommandPoint(SessionPoint(id, offset)); + if (!receiveReady) { + receiveReady = true; + readyToReceive(); + } +} + +void SessionHandler::expected(const SequenceSet& commands, const Array& /*fragments*/) { + checkAttached(); + if (getState()->hasState()) { // Replay + if (commands.empty()) throw IllegalStateException( + QPID_MSG(getState()->getId() << ": has state but client is attaching as new session.")); + // TODO aconway 2008-05-12: support replay of partial commands. + // Here we always round down to the last command boundary. + SessionPoint expectedPoint = commands.empty() ? SequenceNumber(0) : SessionPoint(commands.front(),0); + SessionState::ReplayRange replay = getState()->senderExpected(expectedPoint); + sendCommandPoint(expectedPoint); + std::for_each(replay.begin(), replay.end(), out); // replay + } + else + sendCommandPoint(getState()->senderGetCommandPoint()); +} + +void SessionHandler::confirmed(const SequenceSet& commands, const Array& /*fragments*/) { + checkAttached(); + // Ignore non-contiguous confirmations. + if (!commands.empty() && commands.front() >= getState()->senderGetReplayPoint()) + getState()->senderConfirmed(commands.rangesBegin()->last()); +} + +void SessionHandler::completed(const SequenceSet& commands, bool timelyReply) { + checkAttached(); + getState()->senderCompleted(commands); + if (getState()->senderNeedKnownCompleted() || timelyReply) { + peer.knownCompleted(commands); + getState()->senderRecordKnownCompleted(); + } +} + +void SessionHandler::knownCompleted(const SequenceSet& commands) { + checkAttached(); + getState()->receiverKnownCompleted(commands); +} + +void SessionHandler::flush(bool expected, bool confirmed, bool completed) { + checkAttached(); + if (expected) { + SequenceSet expectSet; + if (getState()->hasState()) + expectSet.add(getState()->receiverGetExpected().command); + peer.expected(expectSet, Array()); + } + if (confirmed) { + SequenceSet confirmSet; + if (!getState()->receiverGetUnknownComplete().empty()) + confirmSet.add(getState()->receiverGetUnknownComplete().front(), + getState()->receiverGetReceived().command); + peer.confirmed(confirmSet, Array()); + } + if (completed) + peer.completed(getState()->receiverGetUnknownComplete(), true); +} + +void SessionHandler::gap(const SequenceSet& /*commands*/) { + throw NotImplementedException("session.gap not supported"); +} + +void SessionHandler::sendDetach() +{ + checkAttached(); + awaitingDetached = true; + peer.detach(getState()->getId().getName()); +} + +void SessionHandler::sendCompletion() { + checkAttached(); + const SequenceSet& c = getState()->receiverGetUnknownComplete(); + peer.completed(c, getState()->receiverNeedKnownCompleted()); +} + +void SessionHandler::sendAttach(bool force) { + QPID_LOG(debug, "SessionHandler::sendAttach attach id=" << getState()->getId()); + peer.attach(getState()->getId().getName(), force); + if (getState()->hasState()) + peer.flush(true, true, true); + else + sendCommandPoint(getState()->senderGetCommandPoint()); +} + +void SessionHandler::sendCommandPoint(const SessionPoint& point) { + peer.commandPoint(point.command, point.offset); + if (!sendReady) { + sendReady = true; + readyToSend(); + } +} + +void SessionHandler::markReadyToSend() { + if (!sendReady) { + sendReady = true; + } +} + +void SessionHandler::sendTimeout(uint32_t t) { + checkAttached(); + peer.requestTimeout(t); +} + +void SessionHandler::sendFlush() { + peer.flush(false, true, true); +} + +bool SessionHandler::ready() const { + return sendReady && receiveReady; +} + + +}} // namespace qpid::broker diff --git a/qpid/cpp/src/qpid/amqp_0_10/SessionHandler.h b/qpid/cpp/src/qpid/amqp_0_10/SessionHandler.h new file mode 100644 index 0000000000..b5b0fe5ee0 --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/SessionHandler.h @@ -0,0 +1,118 @@ +#ifndef QPID_AMQP_0_10_SESSIONHANDLER_H +#define QPID_AMQP_0_10_SESSIONHANDLER_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/framing/ChannelHandler.h" +#include "qpid/framing/AMQP_AllProxy.h" +#include "qpid/framing/AMQP_AllOperations.h" +#include "qpid/SessionState.h" +#include "qpid/CommonImportExport.h" + +namespace qpid { + +struct SessionException; + +namespace amqp_0_10 { + +/** + * Base SessionHandler with logic common to both client and broker. + * + * A SessionHandler is associated with a channel and can be attached + * to a session state. + */ + +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); + QPID_COMMON_EXTERN ~SessionHandler(); + + void setChannel(uint16_t ch) { channel = ch; } + uint16_t getChannel() const { return channel.get(); } + + void setOutHandler(framing::FrameHandler& h) { channel.next = &h; } + + virtual SessionState* getState() = 0; + virtual framing::FrameHandler* getInHandler() = 0; + + // Non-protocol methods, called locally to initiate some action. + QPID_COMMON_EXTERN void sendDetach(); + QPID_COMMON_EXTERN void sendCompletion(); + QPID_COMMON_EXTERN void sendAttach(bool force); + QPID_COMMON_EXTERN void sendTimeout(uint32_t t); + QPID_COMMON_EXTERN void sendFlush(); + QPID_COMMON_EXTERN void markReadyToSend();//TODO: only needed for inter-broker bridge; cleanup + QPID_COMMON_EXTERN void handleException(const qpid::SessionException& e); + + /** True if the handler is ready to send and receive */ + bool ready() const; + + // Protocol methods + QPID_COMMON_EXTERN void attach(const std::string& name, bool force); + QPID_COMMON_EXTERN void attached(const std::string& name); + QPID_COMMON_EXTERN void detach(const std::string& name); + QPID_COMMON_EXTERN void detached(const std::string& name, uint8_t code); + + QPID_COMMON_EXTERN void requestTimeout(uint32_t t); + QPID_COMMON_EXTERN void timeout(uint32_t t); + + QPID_COMMON_EXTERN void commandPoint(const framing::SequenceNumber& id, uint64_t offset); + QPID_COMMON_EXTERN void expected(const framing::SequenceSet& commands, const framing::Array& fragments); + QPID_COMMON_EXTERN void confirmed(const framing::SequenceSet& commands,const framing::Array& fragments); + QPID_COMMON_EXTERN void completed(const framing::SequenceSet& commands, bool timelyReply); + QPID_COMMON_EXTERN void knownCompleted(const framing::SequenceSet& commands); + QPID_COMMON_EXTERN void flush(bool expected, bool confirmed, bool completed); + QPID_COMMON_EXTERN void gap(const framing::SequenceSet& commands); + + protected: + QPID_COMMON_EXTERN virtual void invoke(const framing::AMQMethodBody& m); + + virtual void setState(const std::string& sessionName, bool force) = 0; + virtual void connectionException(framing::connection::CloseCode code, const std::string& msg) = 0; + virtual void channelException(framing::session::DetachCode, const std::string& msg) = 0; + virtual void executionException(framing::execution::ErrorCode, const std::string& msg) = 0; + virtual void detaching() = 0; + + // Notification of events + virtual void readyToSend() {} + virtual void readyToReceive() {} + + QPID_COMMON_EXTERN virtual void handleDetach(); + QPID_COMMON_EXTERN virtual void handleIn(framing::AMQFrame&); + QPID_COMMON_EXTERN virtual void handleOut(framing::AMQFrame&); + + framing::ChannelHandler channel; + + private: + void checkAttached(); + void sendCommandPoint(const SessionPoint&); + + framing::AMQP_AllProxy::Session peer; + std::string name; + bool awaitingDetached; + bool sendReady, receiveReady; +}; +}} // namespace qpid::amqp_0_10 + +#endif /*!QPID_AMQP_0_10_SESSIONHANDLER_H*/ diff --git a/qpid/cpp/src/qpid/amqp_0_10/Struct.h b/qpid/cpp/src/qpid/amqp_0_10/Struct.h new file mode 100644 index 0000000000..29ece84f6e --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/Struct.h @@ -0,0 +1,60 @@ +#ifndef QPID_AMQP_0_10_STRUCT_H +#define QPID_AMQP_0_10_STRUCT_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/amqp_0_10/built_in_types.h" +#include <iosfwd> + +namespace qpid { +namespace amqp_0_10 { + +// Base classes for complex types. + +template <class V, class CV, class H> struct Visitable { + typedef V Visitor; + typedef CV ConstVisitor; + typedef H Holder; + + virtual ~Visitable() {} + virtual void accept(Visitor&) = 0; + virtual void accept(ConstVisitor&) const = 0; +}; + + +// Note: only coded structs inherit from Struct. +struct StructVisitor; +struct ConstStructVisitor; +struct StructHolder; +struct Struct + : public Visitable<StructVisitor, ConstStructVisitor, StructHolder> +{ + uint8_t getCode() const; + uint8_t getPack() const; + uint8_t getSize() const; + uint8_t getClassCode() const; +}; +std::ostream& operator<<(std::ostream&, const Struct&); + +}} // namespace qpid::amqp_0_10 + +#endif /*!QPID_AMQP_0_10_STRUCT_H*/ diff --git a/qpid/cpp/src/qpid/amqp_0_10/Struct32.cpp b/qpid/cpp/src/qpid/amqp_0_10/Struct32.cpp new file mode 100644 index 0000000000..2d38c09c21 --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/Struct32.cpp @@ -0,0 +1,36 @@ +/* + * 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/amqp_0_10/Struct32.h" + +namespace qpid { +namespace amqp_0_10 { + +Struct32::Struct32() { + // FIXME aconway 2008-04-16: this is only here to force a valid + // default-constructed Struct32 for serialize tests, clean up. + *this = in_place<message::MessageResumeResult>(); +} + +std::ostream& operator<<(std::ostream& o, const Struct32& s) { + return o << static_cast<const StructHolder&>(s); +} + +}} // namespace qpid::amqp_0_10 diff --git a/qpid/cpp/src/qpid/amqp_0_10/Struct32.h b/qpid/cpp/src/qpid/amqp_0_10/Struct32.h new file mode 100644 index 0000000000..2ed73e0b4c --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/Struct32.h @@ -0,0 +1,64 @@ +#ifndef QPID_AMQP_0_10_STRUCT32_H +#define QPID_AMQP_0_10_STRUCT32_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/amqp_0_10/StructHolder.h" + +namespace qpid { +namespace amqp_0_10 { + +class Struct32 : public StructHolder +{ + public: + Struct32(); + + template <class T> explicit Struct32(const T& t) : StructHolder(t) {} + + template <class S> void serialize(S& s) { s.split(*this); } + + using StructHolder::operator=; + + template <class S> void encode(S& s) const { + s(contentSize()); + const_cast<Struct32*>(this)->StructHolder::serialize(s); + } + + template <class S> void decode(S& s) { + uint32_t contentSz; + s(contentSz); + typename S::ScopedLimit l(s, contentSz); + StructHolder::serialize(s); + } + + private: + uint32_t contentSize() const { + return Codec::size(static_cast<const StructHolder&>(*this)); + } + +}; + +std::ostream& operator<<(std::ostream&, const Struct32&); + +}} // namespace qpid::amqp_0_10 + +#endif /*!QPID_AMQP_0_10_STRUCT32_H*/ diff --git a/qpid/cpp/src/qpid/amqp_0_10/Unit.cpp b/qpid/cpp/src/qpid/amqp_0_10/Unit.cpp new file mode 100644 index 0000000000..381de76dcc --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/Unit.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 "qpid/amqp_0_10/Unit.h" +#include "qpid/amqp_0_10/Codec.h" + +namespace qpid { +namespace amqp_0_10 { + +void Unit::updateVariant() { + switch (header.getType()) { + case CONTROL: variant=ControlHolder(); break; + case COMMAND: variant=CommandHolder(); break; + case HEADER: variant=Header(); break; + case BODY: variant=Body(header.getDataSize()); break; + default: assert(0); // FIXME aconway 2008-04-14: exception? + } +} + +struct GetTypeVisitor : public boost::static_visitor<SegmentType> { + SegmentType operator()(const CommandHolder& ) const { return COMMAND; } + SegmentType operator()(const ControlHolder& ) const { return CONTROL; } + SegmentType operator()(const Header& ) const { return HEADER; } + SegmentType operator()(const Body&) const { return BODY; } +}; + +struct GetFlagsVisitor : public boost::static_visitor<uint8_t> { + uint8_t operator()(const CommandHolder& ) const { return FIRST_FRAME|LAST_FRAME|FIRST_SEGMENT; } + uint8_t operator()(const ControlHolder& ) const { return FIRST_FRAME|LAST_FRAME|FIRST_SEGMENT; } + uint8_t operator()(const Header& ) const { return FIRST_FRAME|LAST_FRAME; } + uint8_t operator()(const Body&) const { return 0; } +}; + +void Unit::updateHeader(uint8_t flags) { + GetFlagsVisitor flagger; + header.setFlags(flags | variant.apply_visitor(flagger)); + GetTypeVisitor getter; + header.setType(variant.apply_visitor(getter)); + header.setDataSize(Codec::size(*this)); + // track automatically set from type. + // no channel specified at this point. +} + +std::ostream& operator<<(std::ostream& o, const Unit& u) { + return o << u.getHeader() << " " << u.variant.type().name() << "[" << u.variant << "]"; +} + +}} // namespace qpid::amqp_0_10 diff --git a/qpid/cpp/src/qpid/amqp_0_10/Unit.h b/qpid/cpp/src/qpid/amqp_0_10/Unit.h new file mode 100644 index 0000000000..0229e07419 --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/Unit.h @@ -0,0 +1,82 @@ +#ifndef QPID_AMQP_0_10_UNIT_H +#define QPID_AMQP_0_10_UNIT_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/amqp_0_10/ControlHolder.h" +#include "qpid/amqp_0_10/CommandHolder.h" +#include "qpid/amqp_0_10/Header.h" +#include "qpid/amqp_0_10/Body.h" +#include "qpid/amqp_0_10/FrameHeader.h" + +#include <boost/variant.hpp> +#include <ostream> + +namespace qpid { +namespace amqp_0_10 { + +/** + * A Unit contains a frame header and associated value. + * For all types except BODY the frame header is for a complete segment. + */ +class Unit { + public: + explicit Unit(const FrameHeader& h=FrameHeader()) : header(h) { updateVariant(); } + + /** + *@param flags: is ORed with the required flags for type T. + */ + template <class T> + explicit Unit(const T& t, uint8_t flags=0) : variant(t) { updateHeader(flags); } + + void setHeader(FrameHeader& h) { header = h; updateVariant(); } + const FrameHeader& getHeader() const { return header; } + + template<class T> const T* get() const { return boost::get<T>(&variant); } + template<class T> T* get() { return boost::get<T>(&variant); } + template<class T> Unit& operator=(const T& t) { variant=t; return *this; } + + template <class V> typename V::result_type applyVisitor(V& v) const { + variant.apply_visitor(v); + } + + template <class S> void serialize(S& s) { variant.apply_visitor(s); s.split(*this); } + template <class S> void encode(S&) const {} + template <class S> void decode(S&) { updateHeader(header.getFlags()); } + + private: + typedef boost::variant<ControlHolder, CommandHolder, Header, Body> Variant; + + void updateHeader(uint8_t flags); + void updateVariant(); + + Variant variant; + FrameHeader header; + + friend std::ostream& operator<<(std::ostream& o, const Unit& u); +}; + +std::ostream& operator<<(std::ostream& o, const Unit& u); + +}} // namespace qpid::amqp_0_10 + +#endif /*!QPID_AMQP_0_10_UNIT_H*/ diff --git a/qpid/cpp/src/qpid/amqp_0_10/UnitHandler.h b/qpid/cpp/src/qpid/amqp_0_10/UnitHandler.h new file mode 100644 index 0000000000..93a8ce573a --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/UnitHandler.h @@ -0,0 +1,35 @@ +#ifndef QPID_AMQP_0_10_UNITHANDLER_H +#define QPID_AMQP_0_10_UNITHANDLER_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/framing/Handler.h" + +namespace qpid { +namespace amqp_0_10 { + +class Unit; +typedef framing::Handler<const Unit&> UnitHandler; + +}} // namespace qpid::amqp_0_10 + +#endif /*!QPID_AMQP_0_10_UNITHANDLER_H*/ diff --git a/qpid/cpp/src/qpid/amqp_0_10/UnknownStruct.cpp b/qpid/cpp/src/qpid/amqp_0_10/UnknownStruct.cpp new file mode 100644 index 0000000000..35445054c9 --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/UnknownStruct.cpp @@ -0,0 +1,34 @@ +/* + * + * 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/amqp_0_10/StructVisitor.h" +#include "qpid/amqp_0_10/UnknownStruct.h" + +namespace qpid { +namespace amqp_0_10 { + +void UnknownStruct::accept(Visitor& v) { v.visit(*this); } +void UnknownStruct::accept(ConstVisitor& v) const { v.visit(*this); } +std::ostream& operator<<(std::ostream& o, const UnknownStruct& u) { + return o << "UnknownStruct[class=" << u.getClassCode() << " code=" << u.getCode() << "]"; +} + +}} // namespace qpid::amqp_0_10 diff --git a/qpid/cpp/src/qpid/amqp_0_10/UnknownStruct.h b/qpid/cpp/src/qpid/amqp_0_10/UnknownStruct.h new file mode 100644 index 0000000000..1c66d8e6af --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/UnknownStruct.h @@ -0,0 +1,55 @@ +#ifndef QPID_AMQP_0_10_UNKNOWNSTRUCT_H +#define QPID_AMQP_0_10_UNKNOWNSTRUCT_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/amqp_0_10/Struct.h" +#include <string> + +namespace qpid { +namespace amqp_0_10 { + +class UnknownStruct : public Struct { + public: + static const uint8_t SIZE=4; + static const uint8_t PACK=2; + + template <class S> void serialize(S& s) { s.split(*this); s(data.begin(), data.end()); } + template <class S> void encode(S&) const { } + template <class S> void decode(S& s) { data.resize(s.bytesRemaining()); } + + UnknownStruct(uint8_t cc=0, uint8_t c=0) : classCode(cc), code(c) {} + void accept(Visitor&); + void accept(ConstVisitor&) const; + + uint8_t getClassCode() const { return classCode; } + uint8_t getCode() const { return code; } + + private: + uint8_t classCode, code; + std::string data; +}; + +std::ostream& operator<<(std::ostream&, const UnknownStruct&); + +}} // namespace qpid::amqp_0_10 + +#endif /*!QPID_AMQP_0_10_UNKNOWNSTRUCT_H*/ diff --git a/qpid/cpp/src/qpid/amqp_0_10/UnknownType.cpp b/qpid/cpp/src/qpid/amqp_0_10/UnknownType.cpp new file mode 100644 index 0000000000..cd45dd76db --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/UnknownType.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 "qpid/amqp_0_10/UnknownType.h" +#include <boost/range/iterator_range.hpp> +#include <ostream> + +namespace qpid { +namespace amqp_0_10 { + +UnknownType::Width UnknownType::WidthTable[16] = { + { 1, 0 }, + { 2, 0 }, + { 4, 0 }, + { 8, 0 }, + { 16, 0 }, + { 32, 0 }, + { 64, 0 }, + { 128, 0 }, + { 0, 1 }, + { 0, 2 }, + { 0, 4 }, + { -1, -1 }, // Invalid + { 5, 0 }, + { 9, 0 }, + { -1, -1 }, // Invalid + { 0, 0 } +}; + +int UnknownType::fixed() const { return WidthTable[code>>4].fixed; } +int UnknownType::variable() const { return WidthTable[code>>4].variable; } +UnknownType::UnknownType(uint8_t c) : code(c) { data.resize(fixed()); } + +std::ostream& operator<<(std::ostream& o, const UnknownType& u) { + return o << boost::make_iterator_range(u.begin(), u.end()) << std::endl; +} + +}} // namespace qpid::amqp_0_10 + diff --git a/qpid/cpp/src/qpid/amqp_0_10/UnknownType.h b/qpid/cpp/src/qpid/amqp_0_10/UnknownType.h new file mode 100644 index 0000000000..77498871b3 --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/UnknownType.h @@ -0,0 +1,87 @@ +#ifndef QPID_AMQP_0_10_UNKNOWNTYPE_H +#define QPID_AMQP_0_10_UNKNOWNTYPE_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/IntegerTypes.h" +#include <vector> +#include <iosfwd> + +namespace qpid { +namespace amqp_0_10 { + +/** Encode/decode an unknown type based on typecode. */ +class UnknownType { + public: + UnknownType(uint8_t code=0); + uint8_t getCode() const { return code; } + /** Size of fixed type or 0 if not fixed/0-length. -1 invalid */ + int fixed() const; + /** Bytes in size type for variable width. -1 invalid */ + int variable() const; + + typedef std::vector<char>::const_iterator const_iterator; + const_iterator begin() const { return data.begin(); } + const_iterator end() const { return data.end(); } + size_t size() const { return data.size(); } + + template <class S> void serialize(S& s) { s.split(*this); } + template <class S> void encode(S& s) const; + template <class S> void decode(S& s); + + private: + uint8_t code; + struct Width { int fixed; int variable; }; + static Width WidthTable[16]; + + std::vector<char> data; +}; + +template <class S> void UnknownType::encode(S& s) const { + switch (variable()) { + case 0: break; + case 1: s(uint8_t(data.size())); break; + case 2: s(uint16_t(data.size())); break; + case 4: s(uint32_t(data.size())); break; + } + s(data.begin(), data.end()); +} + +template <class S> void UnknownType::decode(S& s) { + uint32_t s8; + uint32_t s16; + uint32_t s32; + switch (variable()) { + case 0: break; + case 1: s(s8); data.resize(s8); break; + case 2: s(s16); data.resize(s16); break; + case 4: s(s32); data.resize(s32); break; + } + s(data.begin(), data.end()); +} + +inline uint8_t codeFor(const UnknownType& u) { return u.getCode(); } + +std::ostream& operator<<(std::ostream&, const UnknownType&); + +}} // namespace qpid::amqp_0_10 + +#endif /*!QPID_AMQP_0_10_UNKNOWNTYPE_H*/ diff --git a/qpid/cpp/src/qpid/amqp_0_10/apply.h b/qpid/cpp/src/qpid/amqp_0_10/apply.h new file mode 100644 index 0000000000..f32b3482ef --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/apply.h @@ -0,0 +1,86 @@ +#ifndef QPID_AMQP_0_10_APPLY_H +#define QPID_AMQP_0_10_APPLY_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 <boost/optional.hpp> + +namespace qpid { +namespace amqp_0_10 { + +template <class F, class R=typename F::result_type> struct FunctionAndResult { + F* functor; + boost::optional<R> result; + + FunctionAndResult() : functor(0) {} + template <class T> void invoke(T& t) { result=(*functor)(t); } + template <class T> void invoke(const T& t) { result=(*functor)(t); } + R getResult() { return *result; } +}; + +// void result is special case. +template <class F> struct FunctionAndResult<F, void> { + F* functor; + + FunctionAndResult() : functor(0) {} + template <class T> void invoke(T& t) { (*functor)(t); } + void getResult() {} +}; + +// Metafunction returning correct abstract visitor for Visitable type. +template <class Visitable> struct VisitorType { + typedef typename Visitable::Visitor type; +}; +template <class Visitable> struct VisitorType<const Visitable> { + typedef typename Visitable::ConstVisitor type; +}; + +template <class Visitor, class F> +struct ApplyVisitorBase : public Visitor, public FunctionAndResult<F> {}; + +// Specialize for each visitor type +template <class Visitable, class F> struct ApplyVisitor; + +/** Apply a functor to a visitable object. + * The functor can have operator() overloads for each visitable type + * and/or templated operator(). + */ +template <class F, class Visitable> +typename F::result_type apply(F& functor, Visitable& visitable) { + ApplyVisitor<typename VisitorType<Visitable>::type, F> visitor; + visitor.functor=&functor; + visitable.accept(visitor); + return visitor.getResult(); +} + +template <class F, class Visitable> +typename F::result_type apply(const F& functor, Visitable& visitable) { + ApplyVisitor<typename VisitorType<Visitable>::type, const F> visitor; + visitor.functor=&functor; + visitable.accept(visitor); + return visitor.getResult(); +} + +template <class R> struct ApplyFunctor { typedef R result_type; }; + +}} // namespace qpid::amqp_0_10 + +#endif /*!QPID_AMQP_0_10_APPLY_H*/ diff --git a/qpid/cpp/src/qpid/amqp_0_10/built_in_types.h b/qpid/cpp/src/qpid/amqp_0_10/built_in_types.h new file mode 100644 index 0000000000..e95d1cf3e9 --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/built_in_types.h @@ -0,0 +1,171 @@ +#ifndef QPID_AMQP_0_10_BUILT_IN_TYPES_H +#define QPID_AMQP_0_10_BUILT_IN_TYPES_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/Serializer.h" +#include "qpid/framing/Uuid.h" +#include "qpid/sys/IntegerTypes.h" +#include "qpid/sys/Time.h" +#include "qpid/amqp_0_10/Decimal.h" +#include "qpid/amqp_0_10/SerializableString.h" +#include <boost/array.hpp> +#include <boost/range/iterator_range.hpp> +#include <string> +#include <ostream> +#include <vector> + +/**@file Mapping from built-in AMQP types to C++ types */ + +namespace qpid { + +namespace framing { +class SequenceNumber; +class SequenceSet; +} + +namespace amqp_0_10 { + +/** Wrapper that behaves like type T but is a distinct type for + * overloading purposes. Unique allows multiple distinc wrappers. + */ +template <class T, int Unique=0> struct Wrapper { + T value; + Wrapper() {} + Wrapper(const T& x) : value(x) {} + Wrapper& operator=(const T& x) { value=x; return *this; } + operator T&() { return value; } + operator const T&() const { return value; } + template <class S> void serialize(S& s) { s(value); } +}; + +template<class T> +inline std::ostream& operator<<(std::ostream& o, const Wrapper<T>& w) { + return o << w.value; +} + +/** Void type */ +struct Void { template <class S> void serialize(S&) {} }; +inline std::ostream& operator<<(std::ostream& o, const Void&) { return o; } + +/** Bit is a presence indicator - an optional value with no encoding. */ +struct Bit : public Wrapper<bool> { + Bit(bool b=false) : Wrapper<bool>(b) {} + using Wrapper<bool>::operator=; + template <class S> void serialize(S& s) { s.split(*this); } + template <class S> void encode(S&) const { } + template <class S> void decode(S&) { *this = true; } +}; + +inline std::ostream& operator<<(std::ostream& o, const Bit& b) { + return o << bool(b); +} + +// Fixed size types +typedef bool Boolean; +typedef char Char; +typedef int8_t Int8; +typedef int16_t Int16; +typedef int32_t Int32; +typedef int64_t Int64; +typedef uint8_t Uint8; +typedef uint16_t Uint16; +typedef uint32_t Uint32; +typedef uint64_t Uint64; +typedef Wrapper<uint32_t> CharUtf32; + +template <size_t N> struct Bin : public boost::array<char, N> { + template <class S> void serialize(S& s) { s.raw(this->begin(), this->size()); } +}; + +template <size_t N> std::ostream& operator<<(std::ostream& o, const Bin<N>& b) { + return o << boost::make_iterator_range(b.begin(), b.end()); +} + +template <> struct Bin<1> : public boost::array<char, 1> { + Bin(char c=0) { this->front() = c; } + operator char() { return this->front(); } + template <class S> void serialize(S& s) { s(front()); } +}; + +typedef Bin<1> Bin8; +typedef Bin<128> Bin1024; +typedef Bin<16> Bin128; +typedef Bin<2> Bin16; +typedef Bin<32> Bin256; +typedef Bin<4> Bin32; +typedef Bin<5> Bin40; +typedef Bin<64> Bin512; +typedef Bin<8> Bin64; +typedef Bin<9> Bin72; + +typedef double Double; +typedef float Float; +typedef framing::SequenceNumber SequenceNo; +using framing::Uuid; +typedef sys::AbsTime Datetime; + +typedef Decimal<Uint8, Int32> Dec32; +typedef Decimal<Uint8, Int64> Dec64; + +// Variable width types + +typedef SerializableString<Uint8, Uint8> Vbin8; +typedef SerializableString<char, Uint8, 1> Str8Latin; +typedef SerializableString<char, Uint8> Str8; +typedef SerializableString<Uint16, Uint8> Str8Utf16; + +typedef SerializableString<Uint8, Uint16> Vbin16; +typedef SerializableString<char, Uint16, 1> Str16Latin; +typedef SerializableString<char, Uint16> Str16; +typedef SerializableString<Uint16, Uint16> Str16Utf16; + +typedef SerializableString<Uint8, Uint32> Vbin32; + +typedef framing::SequenceSet SequenceSet; + +// Forward declare class types. +class Map; +class Struct32; +class UnknownType; + +template <class T> struct ArrayDomain; +typedef ArrayDomain<UnknownType> Array; + +// FIXME aconway 2008-04-08: TODO +struct ByteRanges { template <class S> void serialize(S&) {} }; +struct List { template <class S> void serialize(S&) {} }; + +// FIXME aconway 2008-03-10: dummy ostream operators +inline std::ostream& operator<<(std::ostream& o, const ByteRanges&) { return o; } +inline std::ostream& operator<<(std::ostream& o, const SequenceSet&) { return o; } +inline std::ostream& operator<<(std::ostream& o, const List&) { return o; } + +enum SegmentType { CONTROL, COMMAND, HEADER, BODY }; + +inline SerializeAs<SegmentType, uint8_t> serializable(SegmentType& st) { + return SerializeAs<SegmentType, uint8_t>(st); +} + + +}} // namespace qpid::amqp_0_10 + +#endif diff --git a/qpid/cpp/src/qpid/amqp_0_10/complex_types.cpp b/qpid/cpp/src/qpid/amqp_0_10/complex_types.cpp new file mode 100644 index 0000000000..656d363ba6 --- /dev/null +++ b/qpid/cpp/src/qpid/amqp_0_10/complex_types.cpp @@ -0,0 +1,84 @@ +/* + * + * 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/amqp_0_10/UnknownStruct.h" +#include "qpid/amqp_0_10/ApplyCommand.h" +#include "qpid/amqp_0_10/ApplyControl.h" +#include "qpid/amqp_0_10/ApplyStruct.h" +#include "qpid/amqp_0_10/apply.h" +#include <iostream> + +namespace qpid { +namespace amqp_0_10 { +// Functors for getting static values from a visitable base type. + +#define QPID_STATIC_VALUE_GETTER(NAME, TYPE, VALUE) \ + struct NAME : public ApplyFunctor<TYPE> { \ + template <class T> TYPE operator()(const T&) const { return T::VALUE; }\ + } + +QPID_STATIC_VALUE_GETTER(GetCode, uint8_t, CODE); +QPID_STATIC_VALUE_GETTER(GetSize, uint8_t, SIZE); +QPID_STATIC_VALUE_GETTER(GetPack, uint8_t, PACK); +QPID_STATIC_VALUE_GETTER(GetClassCode, uint8_t, CLASS_CODE); +QPID_STATIC_VALUE_GETTER(GetName, const char*, NAME); +QPID_STATIC_VALUE_GETTER(GetClassName, const char*, CLASS_NAME); + + +uint8_t Command::getCode() const { return apply(GetCode(), *this); } +uint8_t Command::getClassCode() const { return apply(GetClassCode(), *this); } +const char* Command::getName() const { return apply(GetName(), *this); } +const char* Command::getClassName() const { return apply(GetClassName(), *this); } + +uint8_t Control::getCode() const { return apply(GetCode(), *this); } +uint8_t Control::getClassCode() const { return apply(GetClassCode(), *this); } +const char* Control::getName() const { return apply(GetName(), *this); } +const char* Control::getClassName() const { return apply(GetClassName(), *this); } + +// Special cases for UnknownStruct +struct GetStructCode : public GetCode { + using GetCode::operator(); + uint8_t operator()(const UnknownStruct& u) const { return u.getCode(); } +}; + +struct GetStructClassCode : public GetClassCode { + using GetClassCode::operator(); + uint8_t operator()(const UnknownStruct& u) const { return u.getClassCode(); } +}; + +uint8_t Struct::getCode() const { return apply(GetStructCode(), *this); } +uint8_t Struct::getClassCode() const { return apply(GetStructClassCode(), *this); } +uint8_t Struct::getPack() const { return apply(GetPack(), *this); } +uint8_t Struct::getSize() const { return apply(GetSize(), *this); } + +struct PrintVisitor { + typedef std::ostream& result_type; + std::ostream& out; + PrintVisitor(std::ostream& o) : out(o) {} + template <class T> result_type operator()(const T& t) const { return out << t; } +}; + +std::ostream& operator<<(std::ostream& o, const Command& x) { return apply(PrintVisitor(o), x); } +std::ostream& operator<<(std::ostream& o, const Control& x) { return apply(PrintVisitor(o), x); } +std::ostream& operator<<(std::ostream& o, const Struct& x) { return apply(PrintVisitor(o), x); } + +}} // namespace qpid::amqp_0_10 + diff --git a/qpid/cpp/src/qpid/assert.cpp b/qpid/cpp/src/qpid/assert.cpp new file mode 100644 index 0000000000..801bfa6ae5 --- /dev/null +++ b/qpid/cpp/src/qpid/assert.cpp @@ -0,0 +1,47 @@ +#ifndef QPID_ASSERT_CPP +#define QPID_ASSERT_CPP + +/* + * + * 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 <iostream> +#include "qpid/framing/reply_exceptions.h" +#include "qpid/log/Statement.h" +#include <stdlib.h> + +namespace qpid { + +void assert_fail(char const * expr, char const * function, char const * file, long line) { + std::ostringstream msg; + msg << "Assertion failed: " << expr << " in function " << function + << "(" << file << ":" << line << ")"; + QPID_LOG(critical, msg.str()); +#ifdef NDEBUG + throw framing::InternalErrorException(msg.str()); +#else + std::cerr << msg.str() << std::endl; + abort(); +#endif +} + +} // namespace qpid + +#endif /*!QPID_ASSERT_CPP*/ diff --git a/qpid/cpp/src/qpid/assert.h b/qpid/cpp/src/qpid/assert.h new file mode 100644 index 0000000000..49e7c5355d --- /dev/null +++ b/qpid/cpp/src/qpid/assert.h @@ -0,0 +1,38 @@ +#ifndef QPID_ASSERT_H +#define QPID_ASSERT_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 <boost/current_function.hpp> + +/** + * Abort if !expr in debug mode, throw an exception if NDEBUG is set. + */ +#define QPID_ASSERT(expr) ((expr) ? static_cast<void>(0) : ::qpid::assert_fail(#expr, BOOST_CURRENT_FUNCTION, __FILE__, __LINE__)) + +namespace qpid { + +void assert_fail(char const * expr, char const * function, char const * file, long line); + +} // namespace qpid + +#endif /*!QPID_ASSERT_H*/ diff --git a/qpid/cpp/src/qpid/broker/AclModule.h b/qpid/cpp/src/qpid/broker/AclModule.h new file mode 100644 index 0000000000..2f4f7eaacc --- /dev/null +++ b/qpid/cpp/src/qpid/broker/AclModule.h @@ -0,0 +1,281 @@ +#ifndef QPID_ACLMODULE_ACL_H +#define QPID_ACLMODULE_ACL_H + + +/* + * + * 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. + * + */ + + +#include "qpid/RefCounted.h" +#include <boost/shared_ptr.hpp> +#include <map> +#include <set> +#include <string> +#include <sstream> + +namespace qpid { + +namespace acl { + +enum ObjectType {OBJ_QUEUE, OBJ_EXCHANGE, OBJ_BROKER, OBJ_LINK, + OBJ_METHOD, OBJECTSIZE}; // OBJECTSIZE must be last in list +enum Action {ACT_CONSUME, ACT_PUBLISH, ACT_CREATE, ACT_ACCESS, ACT_BIND, + ACT_UNBIND, ACT_DELETE, ACT_PURGE, ACT_UPDATE, + ACTIONSIZE}; // ACTIONSIZE must be last in list +enum Property {PROP_NAME, PROP_DURABLE, PROP_OWNER, PROP_ROUTINGKEY, + PROP_PASSIVE, PROP_AUTODELETE, PROP_EXCLUSIVE, PROP_TYPE, + PROP_ALTERNATE, PROP_QUEUENAME, PROP_SCHEMAPACKAGE, + PROP_SCHEMACLASS, PROP_POLICYTYPE, PROP_MAXQUEUESIZE, + PROP_MAXQUEUECOUNT}; +enum AclResult {ALLOW, ALLOWLOG, DENY, DENYLOG}; + +} // namespace acl + +namespace broker { + + +class AclModule +{ + +public: + + // effienty turn off ACL on message transfer. + virtual bool doTransferAcl()=0; + + virtual bool authorise(const std::string& id, const acl::Action& action, const acl::ObjectType& objType, const std::string& name, + std::map<acl::Property, std::string>* params=0)=0; + virtual bool authorise(const std::string& id, const acl::Action& action, const acl::ObjectType& objType, const std::string& ExchangeName, + const std::string& RoutingKey)=0; + // create specilied authorise methods for cases that need faster matching as needed. + + virtual ~AclModule() {}; +}; + +} // namespace broker + +namespace acl { + +class AclHelper { + private: + AclHelper(){} + public: + static inline ObjectType getObjectType(const std::string& str) { + if (str.compare("queue") == 0) return OBJ_QUEUE; + if (str.compare("exchange") == 0) return OBJ_EXCHANGE; + if (str.compare("broker") == 0) return OBJ_BROKER; + if (str.compare("link") == 0) return OBJ_LINK; + if (str.compare("method") == 0) return OBJ_METHOD; + throw str; + } + static inline std::string getObjectTypeStr(const ObjectType o) { + switch (o) { + case OBJ_QUEUE: return "queue"; + case OBJ_EXCHANGE: return "exchange"; + case OBJ_BROKER: return "broker"; + case OBJ_LINK: return "link"; + case OBJ_METHOD: return "method"; + default: assert(false); // should never get here + } + return ""; + } + static inline Action getAction(const std::string& str) { + if (str.compare("consume") == 0) return ACT_CONSUME; + if (str.compare("publish") == 0) return ACT_PUBLISH; + if (str.compare("create") == 0) return ACT_CREATE; + if (str.compare("access") == 0) return ACT_ACCESS; + if (str.compare("bind") == 0) return ACT_BIND; + if (str.compare("unbind") == 0) return ACT_UNBIND; + if (str.compare("delete") == 0) return ACT_DELETE; + if (str.compare("purge") == 0) return ACT_PURGE; + if (str.compare("update") == 0) return ACT_UPDATE; + throw str; + } + static inline std::string getActionStr(const Action a) { + switch (a) { + case ACT_CONSUME: return "consume"; + case ACT_PUBLISH: return "publish"; + case ACT_CREATE: return "create"; + case ACT_ACCESS: return "access"; + case ACT_BIND: return "bind"; + case ACT_UNBIND: return "unbind"; + case ACT_DELETE: return "delete"; + case ACT_PURGE: return "purge"; + case ACT_UPDATE: return "update"; + default: assert(false); // should never get here + } + return ""; + } + static inline Property getProperty(const std::string& str) { + if (str.compare("name") == 0) return PROP_NAME; + if (str.compare("durable") == 0) return PROP_DURABLE; + if (str.compare("owner") == 0) return PROP_OWNER; + if (str.compare("routingkey") == 0) return PROP_ROUTINGKEY; + if (str.compare("passive") == 0) return PROP_PASSIVE; + if (str.compare("autodelete") == 0) return PROP_AUTODELETE; + if (str.compare("exclusive") == 0) return PROP_EXCLUSIVE; + if (str.compare("type") == 0) return PROP_TYPE; + if (str.compare("alternate") == 0) return PROP_ALTERNATE; + if (str.compare("queuename") == 0) return PROP_QUEUENAME; + if (str.compare("schemapackage") == 0) return PROP_SCHEMAPACKAGE; + if (str.compare("schemaclass") == 0) return PROP_SCHEMACLASS; + if (str.compare("policytype") == 0) return PROP_POLICYTYPE; + if (str.compare("maxqueuesize") == 0) return PROP_MAXQUEUESIZE; + if (str.compare("maxqueuecount") == 0) return PROP_MAXQUEUECOUNT; + throw str; + } + static inline std::string getPropertyStr(const Property p) { + switch (p) { + case PROP_NAME: return "name"; + case PROP_DURABLE: return "durable"; + case PROP_OWNER: return "owner"; + case PROP_ROUTINGKEY: return "routingkey"; + case PROP_PASSIVE: return "passive"; + case PROP_AUTODELETE: return "autodelete"; + case PROP_EXCLUSIVE: return "exclusive"; + case PROP_TYPE: return "type"; + case PROP_ALTERNATE: return "alternate"; + case PROP_QUEUENAME: return "queuename"; + case PROP_SCHEMAPACKAGE: return "schemapackage"; + case PROP_SCHEMACLASS: return "schemaclass"; + case PROP_POLICYTYPE: return "policytype"; + case PROP_MAXQUEUESIZE: return "maxqueuesize"; + case PROP_MAXQUEUECOUNT: return "maxqueuecount"; + default: assert(false); // should never get here + } + return ""; + } + static inline AclResult getAclResult(const std::string& str) { + if (str.compare("allow") == 0) return ALLOW; + if (str.compare("allow-log") == 0) return ALLOWLOG; + if (str.compare("deny") == 0) return DENY; + if (str.compare("deny-log") == 0) return DENYLOG; + throw str; + } + static inline std::string getAclResultStr(const AclResult r) { + switch (r) { + case ALLOW: return "allow"; + case ALLOWLOG: return "allow-log"; + case DENY: return "deny"; + case DENYLOG: return "deny-log"; + default: assert(false); // should never get here + } + return ""; + } + + typedef std::set<Property> propSet; + typedef boost::shared_ptr<propSet> propSetPtr; + typedef std::pair<Action, propSetPtr> actionPair; + typedef std::map<Action, propSetPtr> actionMap; + typedef boost::shared_ptr<actionMap> actionMapPtr; + typedef std::pair<ObjectType, actionMapPtr> objectPair; + typedef std::map<ObjectType, actionMapPtr> objectMap; + typedef objectMap::const_iterator omCitr; + typedef boost::shared_ptr<objectMap> objectMapPtr; + typedef std::map<Property, std::string> propMap; + typedef propMap::const_iterator propMapItr; + + // This map contains the legal combinations of object/action/properties found in an ACL file + static void loadValidationMap(objectMapPtr& map) { + if (!map.get()) return; + map->clear(); + propSetPtr p0; // empty ptr, used for no properties + + // == Exchanges == + + propSetPtr p1(new propSet); + p1->insert(PROP_TYPE); + p1->insert(PROP_ALTERNATE); + p1->insert(PROP_PASSIVE); + p1->insert(PROP_DURABLE); + + propSetPtr p2(new propSet); + p2->insert(PROP_ROUTINGKEY); + + propSetPtr p3(new propSet); + p3->insert(PROP_QUEUENAME); + p3->insert(PROP_ROUTINGKEY); + + actionMapPtr a0(new actionMap); + a0->insert(actionPair(ACT_CREATE, p1)); + a0->insert(actionPair(ACT_DELETE, p0)); + a0->insert(actionPair(ACT_ACCESS, p0)); + a0->insert(actionPair(ACT_BIND, p2)); + a0->insert(actionPair(ACT_UNBIND, p2)); + a0->insert(actionPair(ACT_ACCESS, p3)); + a0->insert(actionPair(ACT_PUBLISH, p0)); + + map->insert(objectPair(OBJ_EXCHANGE, a0)); + + // == Queues == + + propSetPtr p4(new propSet); + p4->insert(PROP_ALTERNATE); + p4->insert(PROP_PASSIVE); + p4->insert(PROP_DURABLE); + p4->insert(PROP_EXCLUSIVE); + p4->insert(PROP_AUTODELETE); + p4->insert(PROP_POLICYTYPE); + p4->insert(PROP_MAXQUEUESIZE); + p4->insert(PROP_MAXQUEUECOUNT); + + actionMapPtr a1(new actionMap); + a1->insert(actionPair(ACT_ACCESS, p0)); + a1->insert(actionPair(ACT_CREATE, p4)); + a1->insert(actionPair(ACT_PURGE, p0)); + a1->insert(actionPair(ACT_DELETE, p0)); + a1->insert(actionPair(ACT_CONSUME, p0)); + + map->insert(objectPair(OBJ_QUEUE, a1)); + + // == Links == + + actionMapPtr a2(new actionMap); + a2->insert(actionPair(ACT_CREATE, p0)); + + map->insert(objectPair(OBJ_LINK, a2)); + + // == Method == + + propSetPtr p5(new propSet); + p5->insert(PROP_SCHEMAPACKAGE); + p5->insert(PROP_SCHEMACLASS); + + actionMapPtr a4(new actionMap); + a4->insert(actionPair(ACT_ACCESS, p5)); + + map->insert(objectPair(OBJ_METHOD, a4)); + } + + static std::string propertyMapToString(const std::map<Property, std::string>* params) { + std::ostringstream ss; + ss << "{"; + if (params) + { + for (propMapItr pMItr = params->begin(); pMItr != params->end(); pMItr++) { + ss << " " << getPropertyStr((Property) pMItr-> first) << "=" << pMItr->second; + } + } + ss << " }"; + return ss.str(); + } +}; + + +}} // namespace qpid::acl + +#endif // QPID_ACLMODULE_ACL_H diff --git a/qpid/cpp/src/qpid/broker/AsyncCompletion.h b/qpid/cpp/src/qpid/broker/AsyncCompletion.h new file mode 100644 index 0000000000..fef994438f --- /dev/null +++ b/qpid/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/qpid/cpp/src/qpid/broker/Bridge.cpp b/qpid/cpp/src/qpid/broker/Bridge.cpp new file mode 100644 index 0000000000..7fbbf4e2c4 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/Bridge.cpp @@ -0,0 +1,323 @@ +/* + * + * 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/Bridge.h" +#include "qpid/broker/FedOps.h" +#include "qpid/broker/ConnectionState.h" +#include "qpid/broker/Connection.h" +#include "qpid/broker/Link.h" +#include "qpid/broker/LinkRegistry.h" +#include "qpid/broker/SessionState.h" + +#include "qpid/management/ManagementAgent.h" +#include "qpid/framing/Uuid.h" +#include "qpid/log/Statement.h" +#include <iostream> + +using qpid::framing::FieldTable; +using qpid::framing::Uuid; +using qpid::framing::Buffer; +using qpid::management::ManagementAgent; +using std::string; +namespace _qmf = qmf::org::apache::qpid::broker; + +namespace qpid { +namespace broker { + +void Bridge::PushHandler::handle(framing::AMQFrame& frame) +{ + conn->received(frame); +} + +Bridge::Bridge(Link* _link, framing::ChannelId _id, CancellationListener l, + const _qmf::ArgsLinkBridge& _args) : + link(_link), id(_id), args(_args), mgmtObject(0), + listener(l), name(Uuid(true).str()), queueName("bridge_queue_"), persistenceId(0) +{ + std::stringstream title; + title << id << "_" << link->getBroker()->getFederationTag(); + queueName += title.str(); + ManagementAgent* agent = link->getBroker()->getManagementAgent(); + if (agent != 0) { + mgmtObject = new _qmf::Bridge + (agent, this, link, id, args.i_durable, args.i_src, args.i_dest, + args.i_key, args.i_srcIsQueue, args.i_srcIsLocal, + args.i_tag, args.i_excludes, args.i_dynamic, args.i_sync); + agent->addObject(mgmtObject); + } + QPID_LOG(debug, "Bridge created from " << args.i_src << " to " << args.i_dest); +} + +Bridge::~Bridge() +{ + mgmtObject->resourceDestroy(); +} + +void Bridge::create(Connection& c) +{ + connState = &c; + conn = &c; + FieldTable options; + if (args.i_sync) options.setInt("qpid.sync_frequency", args.i_sync); + SessionHandler& sessionHandler = c.getChannel(id); + if (args.i_srcIsLocal) { + if (args.i_dynamic) + throw Exception("Dynamic routing not supported for push routes"); + // Point the bridging commands at the local connection handler + pushHandler.reset(new PushHandler(&c)); + channelHandler.reset(new framing::ChannelHandler(id, pushHandler.get())); + + session.reset(new framing::AMQP_ServerProxy::Session(*channelHandler)); + peer.reset(new framing::AMQP_ServerProxy(*channelHandler)); + + session->attach(name, false); + session->commandPoint(0,0); + } else { + sessionHandler.attachAs(name); + // Point the bridging commands at the remote peer broker + peer.reset(new framing::AMQP_ServerProxy(sessionHandler.out)); + } + + if (args.i_srcIsLocal) sessionHandler.getSession()->disableReceiverTracking(); + if (args.i_srcIsQueue) { + peer->getMessage().subscribe(args.i_src, args.i_dest, args.i_sync ? 0 : 1, 0, false, "", 0, options); + peer->getMessage().flow(args.i_dest, 0, 0xFFFFFFFF); + peer->getMessage().flow(args.i_dest, 1, 0xFFFFFFFF); + QPID_LOG(debug, "Activated route from queue " << args.i_src << " to " << args.i_dest); + } else { + FieldTable queueSettings; + + if (args.i_tag.size()) { + queueSettings.setString("qpid.trace.id", args.i_tag); + } else { + const string& peerTag = c.getFederationPeerTag(); + if (peerTag.size()) + queueSettings.setString("qpid.trace.id", peerTag); + } + + if (args.i_excludes.size()) { + queueSettings.setString("qpid.trace.exclude", args.i_excludes); + } else { + const string& localTag = link->getBroker()->getFederationTag(); + if (localTag.size()) + queueSettings.setString("qpid.trace.exclude", localTag); + } + + bool durable = false;//should this be an arg, or would we use srcIsQueue for durable queues? + bool autoDelete = !durable;//auto delete transient queues? + peer->getQueue().declare(queueName, "", false, durable, true, autoDelete, queueSettings); + if (!args.i_dynamic) + peer->getExchange().bind(queueName, args.i_src, args.i_key, FieldTable()); + peer->getMessage().subscribe(queueName, args.i_dest, 1, 0, false, "", 0, FieldTable()); + peer->getMessage().flow(args.i_dest, 0, 0xFFFFFFFF); + peer->getMessage().flow(args.i_dest, 1, 0xFFFFFFFF); + + if (args.i_dynamic) { + Exchange::shared_ptr exchange = link->getBroker()->getExchanges().get(args.i_src); + if (exchange.get() == 0) + throw Exception("Exchange not found for dynamic route"); + exchange->registerDynamicBridge(this); + QPID_LOG(debug, "Activated dynamic route for exchange " << args.i_src); + } else { + QPID_LOG(debug, "Activated static route from exchange " << args.i_src << " to " << args.i_dest); + } + } + if (args.i_srcIsLocal) sessionHandler.getSession()->enableReceiverTracking(); +} + +void Bridge::cancel(Connection&) +{ + if (resetProxy()) { + peer->getMessage().cancel(args.i_dest); + peer->getSession().detach(name); + } +} + +void Bridge::closed() +{ + if (args.i_dynamic) { + Exchange::shared_ptr exchange = link->getBroker()->getExchanges().get(args.i_src); + if (exchange.get() != 0) + exchange->removeDynamicBridge(this); + } +} + +void Bridge::destroy() +{ + listener(this); +} + +void Bridge::setPersistenceId(uint64_t pId) const +{ + persistenceId = pId; +} + +const string& Bridge::getName() const +{ + return name; +} + +Bridge::shared_ptr Bridge::decode(LinkRegistry& links, Buffer& buffer) +{ + string host; + uint16_t port; + string src; + string dest; + string key; + string id; + string excludes; + + buffer.getShortString(host); + port = buffer.getShort(); + bool durable(buffer.getOctet()); + buffer.getShortString(src); + buffer.getShortString(dest); + buffer.getShortString(key); + bool is_queue(buffer.getOctet()); + bool is_local(buffer.getOctet()); + buffer.getShortString(id); + buffer.getShortString(excludes); + bool dynamic(buffer.getOctet()); + uint16_t sync = buffer.getShort(); + + return links.declare(host, port, durable, src, dest, key, + is_queue, is_local, id, excludes, dynamic, sync).first; +} + +void Bridge::encode(Buffer& buffer) const +{ + buffer.putShortString(string("bridge")); + buffer.putShortString(link->getHost()); + buffer.putShort(link->getPort()); + buffer.putOctet(args.i_durable ? 1 : 0); + buffer.putShortString(args.i_src); + buffer.putShortString(args.i_dest); + buffer.putShortString(args.i_key); + buffer.putOctet(args.i_srcIsQueue ? 1 : 0); + buffer.putOctet(args.i_srcIsLocal ? 1 : 0); + buffer.putShortString(args.i_tag); + buffer.putShortString(args.i_excludes); + buffer.putOctet(args.i_dynamic ? 1 : 0); + buffer.putShort(args.i_sync); +} + +uint32_t Bridge::encodedSize() const +{ + return link->getHost().size() + 1 // short-string (host) + + 7 // short-string ("bridge") + + 2 // port + + 1 // durable + + args.i_src.size() + 1 + + args.i_dest.size() + 1 + + args.i_key.size() + 1 + + 1 // srcIsQueue + + 1 // srcIsLocal + + args.i_tag.size() + 1 + + args.i_excludes.size() + 1 + + 1 // dynamic + + 2; // sync +} + +management::ManagementObject* Bridge::GetManagementObject (void) const +{ + return (management::ManagementObject*) mgmtObject; +} + +management::Manageable::status_t Bridge::ManagementMethod(uint32_t methodId, + management::Args& /*args*/, + string&) +{ + if (methodId == _qmf::Bridge::METHOD_CLOSE) { + //notify that we are closed + destroy(); + return management::Manageable::STATUS_OK; + } else { + return management::Manageable::STATUS_UNKNOWN_METHOD; + } +} + +void Bridge::propagateBinding(const string& key, const string& tagList, + const string& op, const string& origin, + qpid::framing::FieldTable* extra_args) +{ + const string& localTag = link->getBroker()->getFederationTag(); + const string& peerTag = connState->getFederationPeerTag(); + + if (tagList.find(peerTag) == tagList.npos) { + FieldTable bindArgs; + if (extra_args) { + for (qpid::framing::FieldTable::ValueMap::iterator i=extra_args->begin(); i != extra_args->end(); ++i) { + bindArgs.insert((*i)); + } + } + string newTagList(tagList + string(tagList.empty() ? "" : ",") + localTag); + + bindArgs.setString(qpidFedOp, op); + bindArgs.setString(qpidFedTags, newTagList); + if (origin.empty()) + bindArgs.setString(qpidFedOrigin, localTag); + else + bindArgs.setString(qpidFedOrigin, origin); + + conn->requestIOProcessing(boost::bind(&Bridge::ioThreadPropagateBinding, this, + queueName, args.i_src, key, bindArgs)); + } +} + +void Bridge::sendReorigin() +{ + FieldTable bindArgs; + + bindArgs.setString(qpidFedOp, fedOpReorigin); + bindArgs.setString(qpidFedTags, link->getBroker()->getFederationTag()); + + conn->requestIOProcessing(boost::bind(&Bridge::ioThreadPropagateBinding, this, + queueName, args.i_src, args.i_key, bindArgs)); +} +bool Bridge::resetProxy() +{ + SessionHandler& sessionHandler = conn->getChannel(id); + if (!sessionHandler.getSession()) peer.reset(); + else peer.reset(new framing::AMQP_ServerProxy(sessionHandler.out)); + return peer.get(); +} + +void Bridge::ioThreadPropagateBinding(const string& queue, const string& exchange, const string& key, FieldTable args) +{ + if (resetProxy()) { + peer->getExchange().bind(queue, exchange, key, args); + } else { + QPID_LOG(error, "Cannot propagate binding for dynamic bridge as session has been detached, deleting dynamic bridge"); + destroy(); + } +} + +bool Bridge::containsLocalTag(const string& tagList) const +{ + const string& localTag = link->getBroker()->getFederationTag(); + return (tagList.find(localTag) != tagList.npos); +} + +const string& Bridge::getLocalTag() const +{ + return link->getBroker()->getFederationTag(); +} + +}} diff --git a/qpid/cpp/src/qpid/broker/Bridge.h b/qpid/cpp/src/qpid/broker/Bridge.h new file mode 100644 index 0000000000..a846254c57 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/Bridge.h @@ -0,0 +1,111 @@ +/* + * + * 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 _Bridge_ +#define _Bridge_ + +#include "qpid/broker/PersistableConfig.h" +#include "qpid/framing/AMQP_ServerProxy.h" +#include "qpid/framing/ChannelHandler.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/FrameHandler.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/management/Manageable.h" +#include "qpid/broker/Exchange.h" +#include "qmf/org/apache/qpid/broker/ArgsLinkBridge.h" +#include "qmf/org/apache/qpid/broker/Bridge.h" + +#include <boost/function.hpp> +#include <memory> + +namespace qpid { +namespace broker { + +class Connection; +class ConnectionState; +class Link; +class LinkRegistry; + +class Bridge : public PersistableConfig, public management::Manageable, public Exchange::DynamicBridge +{ +public: + typedef boost::shared_ptr<Bridge> shared_ptr; + typedef boost::function<void(Bridge*)> CancellationListener; + + Bridge(Link* link, framing::ChannelId id, CancellationListener l, + const qmf::org::apache::qpid::broker::ArgsLinkBridge& args); + ~Bridge(); + + void create(Connection& c); + void cancel(Connection& c); + void closed(); + void destroy(); + bool isDurable() { return args.i_durable; } + + management::ManagementObject* GetManagementObject() const; + management::Manageable::status_t ManagementMethod(uint32_t methodId, + management::Args& args, + std::string& text); + + // PersistableConfig: + void setPersistenceId(uint64_t id) const; + uint64_t getPersistenceId() const { return persistenceId; } + uint32_t encodedSize() const; + void encode(framing::Buffer& buffer) const; + const std::string& getName() const; + static Bridge::shared_ptr decode(LinkRegistry& links, framing::Buffer& buffer); + + // Exchange::DynamicBridge methods + void propagateBinding(const std::string& key, const std::string& tagList, const std::string& op, const std::string& origin, qpid::framing::FieldTable* extra_args=0); + void sendReorigin(); + void ioThreadPropagateBinding(const std::string& queue, const std::string& exchange, const std::string& key, framing::FieldTable args); + bool containsLocalTag(const std::string& tagList) const; + const std::string& getLocalTag() const; + +private: + struct PushHandler : framing::FrameHandler { + PushHandler(Connection* c) { conn = c; } + void handle(framing::AMQFrame& frame); + Connection* conn; + }; + + std::auto_ptr<PushHandler> pushHandler; + std::auto_ptr<framing::ChannelHandler> channelHandler; + std::auto_ptr<framing::AMQP_ServerProxy::Session> session; + std::auto_ptr<framing::AMQP_ServerProxy> peer; + + Link* link; + framing::ChannelId id; + qmf::org::apache::qpid::broker::ArgsLinkBridge args; + qmf::org::apache::qpid::broker::Bridge* mgmtObject; + CancellationListener listener; + std::string name; + std::string queueName; + mutable uint64_t persistenceId; + ConnectionState* connState; + Connection* conn; + + bool resetProxy(); +}; + + +}} + +#endif diff --git a/qpid/cpp/src/qpid/broker/Broker.cpp b/qpid/cpp/src/qpid/broker/Broker.cpp new file mode 100644 index 0000000000..ca3be5b567 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/Broker.cpp @@ -0,0 +1,967 @@ +/* + * + * 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/Broker.h" +#include "qpid/broker/ConnectionState.h" +#include "qpid/broker/DirectExchange.h" +#include "qpid/broker/FanOutExchange.h" +#include "qpid/broker/HeadersExchange.h" +#include "qpid/broker/MessageStoreModule.h" +#include "qpid/broker/NullMessageStore.h" +#include "qpid/broker/RecoveryManagerImpl.h" +#include "qpid/broker/SaslAuthenticator.h" +#include "qpid/broker/SecureConnectionFactory.h" +#include "qpid/broker/TopicExchange.h" +#include "qpid/broker/Link.h" +#include "qpid/broker/ExpiryPolicy.h" +#include "qpid/broker/QueueFlowLimit.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/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/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" +#include "qpid/log/Options.h" +#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" +#include "qpid/sys/Dispatcher.h" +#include "qpid/sys/Thread.h" +#include "qpid/sys/Time.h" +#include "qpid/sys/ConnectionInputHandler.h" +#include "qpid/sys/ConnectionInputHandlerFactory.h" +#include "qpid/sys/TimeoutHandler.h" +#include "qpid/sys/SystemInfo.h" +#include "qpid/Address.h" +#include "qpid/StringUtils.h" +#include "qpid/Url.h" +#include "qpid/Version.h" + +#include <boost/bind.hpp> +#include <boost/format.hpp> + +#include <iostream> +#include <memory> + +using qpid::sys::ProtocolFactory; +using qpid::sys::Poller; +using qpid::sys::Dispatcher; +using qpid::sys::Thread; +using qpid::framing::FrameHandler; +using qpid::framing::ChannelId; +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; + +namespace qpid { +namespace broker { + +Broker::Options::Options(const std::string& name) : + qpid::Options(name), + noDataDir(0), + port(DEFAULT_PORT), + workerThreads(5), + maxConnections(500), + connectionBacklog(10), + enableMgmt(1), + mgmtPubInterval(10), + queueCleanInterval(60*10),//10 minutes + auth(SaslAuthenticator::available()), + realm("QPID"), + replayFlushLimit(0), + replayHardLimit(0), + queueLimit(100*1048576/*100M default limit*/), + tcpNoDelay(false), + requireEncrypted(false), + maxSessionRate(0), + asyncQueueEvents(false), // Must be false in a cluster. + qmf2Support(true), + qmf1Support(true), + queueFlowStopRatio(80), + queueFlowResumeRatio(70), + queueThresholdEventRatio(80) +{ + int c = sys::SystemInfo::concurrency(); + workerThreads=c+1; + std::string home = getHome(); + + if (home.length() == 0) + dataDir += DEFAULT_DATA_DIR_LOCATION; + else + dataDir += home; + dataDir += DEFAULT_DATA_DIR_NAME; + + addOptions() + ("data-dir", optValue(dataDir,"DIR"), "Directory to contain persistent data generated by the broker") + ("no-data-dir", optValue(noDataDir), "Don't use a data directory. No persistent configuration will be loaded or stored") + ("port,p", optValue(port,"PORT"), "Tells the broker to listen on PORT") + ("worker-threads", optValue(workerThreads, "N"), "Sets the broker thread pool size") + ("max-connections", optValue(maxConnections, "N"), "Sets the maximum allowed connections") + ("connection-backlog", optValue(connectionBacklog, "N"), "Sets the connection backlog limit for the server socket") + ("mgmt-enable,m", optValue(enableMgmt,"yes|no"), "Enable Management") + ("mgmt-qmf2", optValue(qmf2Support,"yes|no"), "Enable broadcast of management information over QMF v2") + ("mgmt-qmf1", optValue(qmf1Support,"yes|no"), "Enable broadcast of management information over QMF v1") + ("mgmt-pub-interval", optValue(mgmtPubInterval, "SECONDS"), "Management Publish Interval") + ("queue-purge-interval", optValue(queueCleanInterval, "SECONDS"), + "Interval between attempts to purge any expired messages from queues") + ("auth", optValue(auth, "yes|no"), "Enable authentication, if disabled all incoming connections will be trusted") + ("realm", optValue(realm, "REALM"), "Use the given realm when performing authentication") + ("default-queue-limit", optValue(queueLimit, "BYTES"), "Default maximum size for queues (in bytes)") + ("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, "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") + ("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"); +} + +const std::string empty; +const std::string amq_direct("amq.direct"); +const std::string amq_topic("amq.topic"); +const std::string amq_fanout("amq.fanout"); +const std::string amq_match("amq.match"); +const std::string qpid_management("qpid.management"); +const std::string knownHostsNone("none"); + +Broker::Broker(const Broker::Options& conf) : + poller(new Poller), + config(conf), + managementAgent(conf.enableMgmt ? new ManagementAgent(conf.qmf1Support, + conf.qmf2Support) + : 0), + store(new NullMessageStore), + acl(0), + dataDir(conf.noDataDir ? std::string() : conf.dataDir), + queues(this), + exchanges(this), + links(this), + factory(new SecureConnectionFactory(*this)), + dtxManager(timer), + sessionManager( + qpid::SessionState::Configuration( + conf.replayFlushLimit*1024, // convert kb to bytes. + conf.replayHardLimit*1024), + *this), + queueCleaner(queues, timer), + queueEvents(poller,!conf.asyncQueueEvents), + recovery(true), + inCluster(false), + clusterUpdatee(false), + expiryPolicy(new ExpiryPolicy), + connectionCounter(conf.maxConnections), + getKnownBrokers(boost::bind(&Broker::getKnownBrokersImpl, this)), + deferDelivery(boost::bind(&Broker::deferDeliveryImpl, this, _1, _2)) +{ + try { + if (conf.enableMgmt) { + QPID_LOG(info, "Management enabled"); + managementAgent->configure(dataDir.isEnabled() ? dataDir.getPath() : string(), + conf.mgmtPubInterval, this, conf.workerThreads + 3); + managementAgent->setName("apache.org", "qpidd"); + _qmf::Package packageInitializer(managementAgent.get()); + + System* system = new System (dataDir.isEnabled() ? dataDir.getPath() : string(), this); + systemObject = System::shared_ptr(system); + + mgmtObject = new _qmf::Broker(managementAgent.get(), this, system, "amqp-broker"); + mgmtObject->set_systemRef(system->GetManagementObject()->getObjectId()); + mgmtObject->set_port(conf.port); + mgmtObject->set_workerThreads(conf.workerThreads); + mgmtObject->set_maxConns(conf.maxConnections); + mgmtObject->set_connBacklog(conf.connectionBacklog); + mgmtObject->set_mgmtPubInterval(conf.mgmtPubInterval); + mgmtObject->set_version(qpid::version); + if (dataDir.isEnabled()) + mgmtObject->set_dataDir(dataDir.getPath()); + else + mgmtObject->clr_dataDir(); + + managementAgent->addObject(mgmtObject, 0, true); + + // Since there is currently no support for virtual hosts, a placeholder object + // representing the implied single virtual host is added here to keep the + // management schema correct. + Vhost* vhost = new Vhost(this, this); + vhostObject = Vhost::shared_ptr(vhost); + framing::Uuid uuid(managementAgent->getUuid()); + federationTag = uuid.str(); + vhostObject->setFederationTag(federationTag); + + queues.setParent(vhost); + exchanges.setParent(vhost); + links.setParent(vhost); + } else { + // Management is disabled so there is no broker management ID. + // Create a unique uuid to use as the federation tag. + framing::Uuid uuid(true); + federationTag = uuid.str(); + } + + QueuePolicy::setDefaultMaxSize(conf.queueLimit); + + // Early-Initialize plugins + Plugin::earlyInitAll(*this); + + QueueFlowLimit::setDefaults(conf.queueLimit, conf.queueFlowStopRatio, conf.queueFlowResumeRatio); + + // If no plugin store module registered itself, set up the null store. + if (NullMessageStore::isNullStore(store.get())) + setStore(); + + exchanges.declare(empty, DirectExchange::typeName); // Default exchange. + + if (store.get() != 0) { + // The cluster plug-in will setRecovery(false) on all but the first + // broker to join a cluster. + if (getRecovery()) { + RecoveryManagerImpl recoverer(queues, exchanges, links, dtxManager); + store->recover(recoverer); + } + else { + QPID_LOG(notice, "Cluster recovery: recovered journal data discarded and journal files pushed down"); + store->truncateInit(true); // save old files in subdir + } + } + + //ensure standard exchanges exist (done after recovery from store) + declareStandardExchange(amq_direct, DirectExchange::typeName); + declareStandardExchange(amq_topic, TopicExchange::typeName); + declareStandardExchange(amq_fanout, FanOutExchange::typeName); + declareStandardExchange(amq_match, HeadersExchange::typeName); + + if(conf.enableMgmt) { + exchanges.declare(qpid_management, ManagementTopicExchange::typeName); + Exchange::shared_ptr mExchange = exchanges.get(qpid_management); + Exchange::shared_ptr dExchange = exchanges.get(amq_direct); + managementAgent->setExchange(mExchange, dExchange); + boost::dynamic_pointer_cast<ManagementTopicExchange>(mExchange)->setManagmentAgent(managementAgent.get(), 1); + + std::string qmfTopic("qmf.default.topic"); + std::string qmfDirect("qmf.default.direct"); + + std::pair<Exchange::shared_ptr, bool> topicPair(exchanges.declare(qmfTopic, ManagementTopicExchange::typeName)); + std::pair<Exchange::shared_ptr, bool> directPair(exchanges.declare(qmfDirect, ManagementDirectExchange::typeName)); + + boost::dynamic_pointer_cast<ManagementDirectExchange>(directPair.first)->setManagmentAgent(managementAgent.get(), 2); + boost::dynamic_pointer_cast<ManagementTopicExchange>(topicPair.first)->setManagmentAgent(managementAgent.get(), 2); + + managementAgent->setExchangeV2(topicPair.first, directPair.first); + } + else + QPID_LOG(info, "Management not enabled"); + + /** + * SASL setup, can fail and terminate startup + */ + if (conf.auth) { + SaslAuthenticator::init(qpid::saslName, conf.saslConfigPath); + QPID_LOG(info, "SASL enabled"); + } else { + QPID_LOG(notice, "SASL disabled: No Authentication Performed"); + } + + // Initialize plugins + Plugin::initializeAll(*this); + + if (managementAgent.get()) managementAgent->pluginsInitialized(); + + if (conf.queueCleanInterval) { + queueCleaner.start(conf.queueCleanInterval * qpid::sys::TIME_SEC); + } + + //initialize known broker urls (TODO: add support for urls for other transports (SSL, RDMA)): + if (conf.knownHosts.empty()) { + boost::shared_ptr<ProtocolFactory> factory = getProtocolFactory(TCP_TRANSPORT); + if (factory) { + knownBrokers.push_back ( qpid::Url::getIpAddressesUrl ( factory->getPort() ) ); + } + } else if (conf.knownHosts != knownHostsNone) { + knownBrokers.push_back(Url(conf.knownHosts)); + } + } catch (const std::exception& /*e*/) { + finalize(); + throw; + } +} + +void Broker::declareStandardExchange(const std::string& name, const std::string& type) +{ + bool storeEnabled = store.get() != NULL; + std::pair<Exchange::shared_ptr, bool> status = exchanges.declare(name, type, storeEnabled); + if (status.second && storeEnabled) { + store->create(*status.first, framing::FieldTable ()); + } +} + + +boost::intrusive_ptr<Broker> Broker::create(int16_t port) +{ + Options config; + config.port=port; + return create(config); +} + +boost::intrusive_ptr<Broker> Broker::create(const Options& opts) +{ + return boost::intrusive_ptr<Broker>(new Broker(opts)); +} + +void Broker::setStore (boost::shared_ptr<MessageStore>& _store) +{ + store.reset(new MessageStoreModule (_store)); + setStore(); +} + +void Broker::setStore () { + queues.setStore (store.get()); + dtxManager.setStore (store.get()); + links.setStore (store.get()); +} + +void Broker::run() { + if (config.workerThreads > 0) { + QPID_LOG(notice, "Broker running"); + 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(); + } + } else { + throw Exception((boost::format("Invalid value for worker-threads: %1%") % config.workerThreads).str()); + } +} + +void Broker::shutdown() { + // NB: this function must be async-signal safe, it must not + // call any function that is not async-signal safe. + // Any unsafe shutdown actions should be done in the destructor. + poller->shutdown(); +} + +Broker::~Broker() { + shutdown(); + queueEvents.shutdown(); + finalize(); // Finalize any plugins. + if (config.auth) + SaslAuthenticator::fini(); + timer.stop(); + QPID_LOG(notice, "Shut down"); +} + +ManagementObject* Broker::GetManagementObject(void) const +{ + return (ManagementObject*) mgmtObject; +} + +Manageable* Broker::GetVhostObject(void) const +{ + return vhostObject.get(); +} + +Manageable::status_t Broker::ManagementMethod (uint32_t methodId, + Args& args, + string&) +{ + Manageable::status_t status = Manageable::STATUS_UNKNOWN_METHOD; + + switch (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 + << ")"); + status = Manageable::STATUS_OK; + break; + case _qmf::Broker::METHOD_CONNECT : { + _qmf::ArgsBrokerConnect& hp= + dynamic_cast<_qmf::ArgsBrokerConnect&>(args); + + QPID_LOG (debug, "Broker::connect()"); + string transport = hp.i_transport.empty() ? TCP_TRANSPORT : hp.i_transport; + if (!getProtocolFactory(transport)) { + QPID_LOG(error, "Transport '" << transport << "' not supported"); + return Manageable::STATUS_NOT_IMPLEMENTED; + } + std::pair<Link::shared_ptr, bool> response = + links.declare (hp.i_host, hp.i_port, transport, hp.i_durable, + hp.i_authMechanism, hp.i_username, hp.i_password); + if (hp.i_durable && response.second) + store->create(*response.first); + status = Manageable::STATUS_OK; + break; + } + case _qmf::Broker::METHOD_QUEUEMOVEMESSAGES : { + _qmf::ArgsBrokerQueueMoveMessages& moveArgs= + dynamic_cast<_qmf::ArgsBrokerQueueMoveMessages&>(args); + QPID_LOG (debug, "Broker::queueMoveMessages()"); + if (queueMoveMessages(moveArgs.i_srcQueue, moveArgs.i_destQueue, moveArgs.i_qty)) + status = Manageable::STATUS_OK; + else + return Manageable::STATUS_PARAMETER_INVALID; + break; + } + case _qmf::Broker::METHOD_SETLOGLEVEL : + setLogLevel(dynamic_cast<_qmf::ArgsBrokerSetLogLevel&>(args).i_level); + QPID_LOG (debug, "Broker::setLogLevel()"); + status = Manageable::STATUS_OK; + break; + case _qmf::Broker::METHOD_GETLOGLEVEL : + dynamic_cast<_qmf::ArgsBrokerGetLogLevel&>(args).o_level = getLogLevel(); + 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; + } + default: + QPID_LOG (debug, "Broker ManagementMethod not implemented: id=" << methodId << "]"); + status = Manageable::STATUS_NOT_IMPLEMENTED; + break; + } + + 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 _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); + } + +} + +void Broker::setLogLevel(const std::string& level) +{ + QPID_LOG(notice, "Changing log level to " << level); + std::vector<std::string> selectors; + split(selectors, level, ", "); + qpid::log::Logger::instance().reconfigure(selectors); +} + +std::string Broker::getLogLevel() +{ + std::string level; + 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; + } + return level; +} + +boost::shared_ptr<ProtocolFactory> Broker::getProtocolFactory(const std::string& name) const { + ProtocolFactoryMap::const_iterator i + = name.empty() ? protocolFactories.begin() : protocolFactories.find(name); + if (i == protocolFactories.end()) return boost::shared_ptr<ProtocolFactory>(); + else return i->second; +} + +uint16_t Broker::getPort(const std::string& name) const { + boost::shared_ptr<ProtocolFactory> factory = getProtocolFactory(name); + if (factory) { + return factory->getPort(); + } else { + throw NoSuchTransportException(QPID_MSG("No such transport: '" << name << "'")); + } +} + +void Broker::registerProtocolFactory(const std::string& name, ProtocolFactory::shared_ptr protocolFactory) { + protocolFactories[name] = protocolFactory; + Url::addProtocol(name); +} + +void Broker::accept() { + for (ProtocolFactoryMap::const_iterator i = protocolFactories.begin(); i != protocolFactories.end(); i++) { + i->second->accept(poller, factory.get()); + } +} + +void Broker::connect( + const std::string& host, const std::string& port, const std::string& transport, + boost::function2<void, int, std::string> failed, + sys::ConnectionCodec::Factory* f) +{ + boost::shared_ptr<ProtocolFactory> pf = getProtocolFactory(transport); + if (pf) pf->connect(poller, host, port, f ? f : factory.get(), failed); + else throw NoSuchTransportException(QPID_MSG("Unsupported transport type: " << transport)); +} + +void Broker::connect( + const Url& url, + boost::function2<void, int, std::string> failed, + sys::ConnectionCodec::Factory* f) +{ + url.throwIfEmpty(); + const Address& addr=url[0]; + 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) +{ + Queue::shared_ptr src_queue = queues.find(srcQueue); + if (!src_queue) + return 0; + Queue::shared_ptr dest_queue = queues.find(destQueue); + if (!dest_queue) + return 0; + + return src_queue->move(dest_queue, qty); +} + + +boost::shared_ptr<sys::Poller> Broker::getPoller() { return poller; } + +std::vector<Url> +Broker::getKnownBrokersImpl() +{ + return knownBrokers; +} + +bool Broker::deferDeliveryImpl(const std::string& , + const boost::intrusive_ptr<Message>& ) +{ return false; } + +void Broker::setClusterTimer(std::auto_ptr<sys::Timer> t) { + clusterTimer = t; +} + +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)); + } + + 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)); + } + + 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)); + } + + 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 (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/qpid/cpp/src/qpid/broker/Broker.h b/qpid/cpp/src/qpid/broker/Broker.h new file mode 100644 index 0000000000..40f7b6273f --- /dev/null +++ b/qpid/cpp/src/qpid/broker/Broker.h @@ -0,0 +1,351 @@ +#ifndef _Broker_ +#define _Broker_ + +/* + * + * 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/BrokerImportExport.h" +#include "qpid/broker/ConnectionFactory.h" +#include "qpid/broker/ConnectionToken.h" +#include "qpid/broker/DirectExchange.h" +#include "qpid/broker/DtxManager.h" +#include "qpid/broker/ExchangeRegistry.h" +#include "qpid/broker/MessageStore.h" +#include "qpid/broker/QueueRegistry.h" +#include "qpid/broker/LinkRegistry.h" +#include "qpid/broker/SessionManager.h" +#include "qpid/broker/QueueCleaner.h" +#include "qpid/broker/QueueEvents.h" +#include "qpid/broker/Vhost.h" +#include "qpid/broker/System.h" +#include "qpid/broker/ExpiryPolicy.h" +#include "qpid/management/Manageable.h" +#include "qpid/management/ManagementAgent.h" +#include "qmf/org/apache/qpid/broker/Broker.h" +#include "qmf/org/apache/qpid/broker/ArgsBrokerConnect.h" +#include "qpid/Options.h" +#include "qpid/Plugin.h" +#include "qpid/DataDir.h" +#include "qpid/framing/FrameHandler.h" +#include "qpid/framing/OutputHandler.h" +#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" + +#include <boost/intrusive_ptr.hpp> +#include <string> +#include <vector> + +namespace qpid { + +namespace sys { + class ProtocolFactory; + class Poller; +} + +struct Url; + +namespace broker { + +class ConnectionState; +class ExpiryPolicy; +class Message; + +static const uint16_t DEFAULT_PORT=5672; + +struct NoSuchTransportException : qpid::Exception +{ + NoSuchTransportException(const std::string& s) : Exception(s) {} + virtual ~NoSuchTransportException() throw() {} +}; + +/** + * A broker instance. + */ +class Broker : public sys::Runnable, public Plugin::Target, + public management::Manageable, + public RefCounted +{ +public: + + struct Options : public qpid::Options { + static const std::string DEFAULT_DATA_DIR_LOCATION; + static const std::string DEFAULT_DATA_DIR_NAME; + + QPID_BROKER_EXTERN Options(const std::string& name="Broker Options"); + + bool noDataDir; + std::string dataDir; + uint16_t port; + int workerThreads; + int maxConnections; + int connectionBacklog; + bool enableMgmt; + uint16_t mgmtPubInterval; + uint16_t queueCleanInterval; + bool auth; + std::string realm; + size_t replayFlushLimit; + size_t replayHardLimit; + uint queueLimit; + bool tcpNoDelay; + bool requireEncrypted; + std::string knownHosts; + std::string saslConfigPath; + uint32_t maxSessionRate; + bool asyncQueueEvents; + bool qmf2Support; + bool qmf1Support; + uint queueFlowStopRatio; // producer flow control: on + uint queueFlowResumeRatio; // producer flow control: off + uint16_t queueThresholdEventRatio; + + 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); + connectionCount++; + } + void dec_connectionCount() { + sys::ScopedLock<sys::Mutex> l(connectionCountLock); + connectionCount--; + } + bool allowConnection() { + sys::ScopedLock<sys::Mutex> l(connectionCountLock); + return (maxConnections <= connectionCount); + } + }; + + private: + typedef std::map<std::string, boost::shared_ptr<sys::ProtocolFactory> > ProtocolFactoryMap; + + void declareStandardExchange(const std::string& name, const std::string& type); + 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); + + boost::shared_ptr<sys::Poller> poller; + sys::Timer timer; + std::auto_ptr<sys::Timer> clusterTimer; + Options config; + std::auto_ptr<management::ManagementAgent> managementAgent; + ProtocolFactoryMap protocolFactories; + std::auto_ptr<MessageStore> store; + AclModule* acl; + DataDir dataDir; + + QueueRegistry queues; + ExchangeRegistry exchanges; + LinkRegistry links; + boost::shared_ptr<sys::ConnectionCodec::Factory> factory; + DtxManager dtxManager; + SessionManager sessionManager; + qmf::org::apache::qpid::broker::Broker* mgmtObject; + Vhost::shared_ptr vhostObject; + System::shared_ptr systemObject; + QueueCleaner queueCleaner; + QueueEvents queueEvents; + std::vector<Url> knownBrokers; + std::vector<Url> getKnownBrokersImpl(); + bool deferDeliveryImpl(const std::string& queue, + const boost::intrusive_ptr<Message>& msg); + std::string federationTag; + bool recovery; + bool inCluster, clusterUpdatee; + boost::intrusive_ptr<ExpiryPolicy> expiryPolicy; + ConnectionCounter connectionCounter; + + public: + virtual ~Broker(); + + QPID_BROKER_EXTERN Broker(const Options& configuration); + static QPID_BROKER_EXTERN boost::intrusive_ptr<Broker> create(const Options& configuration); + static QPID_BROKER_EXTERN boost::intrusive_ptr<Broker> create(int16_t port = DEFAULT_PORT); + + /** + * Return listening port. If called before bind this is + * the configured port. If called after it is the actual + * port, which will be different if the configured port is + * 0. + */ + virtual uint16_t getPort(const std::string& name) const; + + /** + * Run the broker. Implements Runnable::run() so the broker + * can be run in a separate thread. + */ + virtual void run(); + + /** Shut down the broker */ + virtual void shutdown(); + + QPID_BROKER_EXTERN void setStore (boost::shared_ptr<MessageStore>& store); + MessageStore& getStore() { return *store; } + void setAcl (AclModule* _acl) {acl = _acl;} + AclModule* getAcl() { return acl; } + QueueRegistry& getQueues() { return queues; } + ExchangeRegistry& getExchanges() { return exchanges; } + LinkRegistry& getLinks() { return links; } + DtxManager& getDtxManager() { return dtxManager; } + DataDir& getDataDir() { return dataDir; } + Options& getOptions() { return config; } + QueueEvents& getQueueEvents() { return queueEvents; } + + void setExpiryPolicy(const boost::intrusive_ptr<ExpiryPolicy>& e) { expiryPolicy = e; } + boost::intrusive_ptr<ExpiryPolicy> getExpiryPolicy() { return expiryPolicy; } + + SessionManager& getSessionManager() { return sessionManager; } + const std::string& getFederationTag() const { return federationTag; } + + management::ManagementObject* GetManagementObject (void) const; + management::Manageable* GetVhostObject (void) const; + management::Manageable::status_t ManagementMethod (uint32_t methodId, + management::Args& args, + std::string& text); + + /** Add to the broker's protocolFactorys */ + void registerProtocolFactory(const std::string& name, boost::shared_ptr<sys::ProtocolFactory>); + + /** Accept connections */ + QPID_BROKER_EXTERN void accept(); + + /** Create a connection to another broker. */ + 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); + /** Create a connection to another broker. */ + void connect(const Url& url, + boost::function2<void, int, std::string> failed, + sys::ConnectionCodec::Factory* =0); + + /** Move messages from one queue to another. + A zero quantity means to move all messages + */ + uint32_t queueMoveMessages( const std::string& srcQueue, + const std::string& destQueue, + uint32_t qty); + + boost::shared_ptr<sys::ProtocolFactory> getProtocolFactory(const std::string& name = TCP_TRANSPORT) const; + + /** Expose poller so plugins can register their descriptors. */ + boost::shared_ptr<sys::Poller> getPoller(); + + boost::shared_ptr<sys::ConnectionCodec::Factory> getConnectionFactory() { return factory; } + void setConnectionFactory(boost::shared_ptr<sys::ConnectionCodec::Factory> f) { factory = f; } + + /** Timer for local tasks affecting only this broker */ + sys::Timer& getTimer() { return timer; } + + /** Timer for tasks that must be synchronized if we are in a cluster */ + sys::Timer& getClusterTimer() { return clusterTimer.get() ? *clusterTimer : timer; } + void setClusterTimer(std::auto_ptr<sys::Timer>); + + boost::function<std::vector<Url> ()> getKnownBrokers; + + static QPID_BROKER_EXTERN const std::string TCP_TRANSPORT; + + void setRecovery(bool set) { recovery = set; } + bool getRecovery() const { return recovery; } + + /** 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;} + + /** + * Never true in a stand-alone broker. In a cluster, return true + * to defer delivery of messages deliveredg in a cluster-unsafe + * context. + *@return true if delivery of a message should be deferred. + */ + boost::function<bool (const std::string& queue, + const boost::intrusive_ptr<Message>& msg)> deferDelivery; + + bool isAuthenticating ( ) { return config.auth; } + + 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); +}; + +}} + +#endif /*!_Broker_*/ diff --git a/qpid/cpp/src/qpid/broker/BrokerImportExport.h b/qpid/cpp/src/qpid/broker/BrokerImportExport.h new file mode 100644 index 0000000000..ee05788063 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/BrokerImportExport.h @@ -0,0 +1,42 @@ +#ifndef QPID_BROKER_IMPORT_EXPORT_H +#define QPID_BROKER_IMPORT_EXPORT_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. + */ + +#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 +# define QPID_BROKER_CLASS_EXTERN +# define QPID_BROKER_INLINE_EXTERN +#endif + +#endif diff --git a/qpid/cpp/src/qpid/broker/Connection.cpp b/qpid/cpp/src/qpid/broker/Connection.cpp new file mode 100644 index 0000000000..c07e63e68c --- /dev/null +++ b/qpid/cpp/src/qpid/broker/Connection.cpp @@ -0,0 +1,487 @@ +/* + * + * 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/Connection.h" +#include "qpid/broker/SessionOutputException.h" +#include "qpid/broker/SessionState.h" +#include "qpid/broker/Bridge.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/Queue.h" +#include "qpid/sys/SecuritySettings.h" +#include "qpid/sys/ClusterSafe.h" + +#include "qpid/log/Statement.h" +#include "qpid/ptr_map.h" +#include "qpid/framing/AMQP_ClientProxy.h" +#include "qpid/framing/enum.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qmf/org/apache/qpid/broker/EventClientConnect.h" +#include "qmf/org/apache/qpid/broker/EventClientDisconnect.h" + +#include <boost/bind.hpp> +#include <boost/ptr_container/ptr_vector.hpp> + +#include <algorithm> +#include <iostream> +#include <assert.h> + + + +using namespace qpid::sys; +using namespace qpid::framing; +using qpid::ptr_map_ptr; +using qpid::management::ManagementAgent; +using qpid::management::ManagementObject; +using qpid::management::Manageable; +using qpid::management::Args; +namespace _qmf = qmf::org::apache::qpid::broker; + +namespace qpid { +namespace broker { + +struct ConnectionTimeoutTask : public sys::TimerTask { + sys::Timer& timer; + Connection& connection; + + ConnectionTimeoutTask(uint16_t hb, sys::Timer& t, Connection& c) : + TimerTask(Duration(hb*2*TIME_SEC),"ConnectionTimeout"), + timer(t), + connection(c) + {} + + void touch() { + restart(); + } + + void fire() { + // If we get here then we've not received any traffic in the timeout period + // Schedule closing the connection for the io thread + QPID_LOG(error, "Connection " << connection.getMgmtId() + << " timed out: closing"); + connection.abort(); + } +}; + +Connection::Connection(ConnectionOutputHandler* out_, + Broker& broker_, const + std::string& mgmtId_, + const qpid::sys::SecuritySettings& external, + bool isLink_, + uint64_t objectId_, + bool shadow_, + bool delayManagement) : + ConnectionState(out_, broker_), + securitySettings(external), + adapter(*this, isLink_, shadow_), + isLink(isLink_), + mgmtClosing(false), + mgmtId(mgmtId_), + mgmtObject(0), + links(broker_.getLinks()), + agent(0), + timer(broker_.getTimer()), + errorListener(0), + objectId(objectId_), + shadow(shadow_), + outboundTracker(*this) +{ + outboundTracker.wrap(out); + if (isLink) + links.notifyConnection(mgmtId, this); + // In a cluster, allow adding the management object to be delayed. + if (!delayManagement) addManagementObject(); + if (!isShadow()) broker.getConnectionCounter().inc_connectionCount(); +} + +void Connection::addManagementObject() { + assert(agent == 0); + assert(mgmtObject == 0); + Manageable* parent = broker.GetVhostObject(); + if (parent != 0) { + agent = broker.getManagementAgent(); + if (agent != 0) { + // TODO set last bool true if system connection + mgmtObject = new _qmf::Connection(agent, this, parent, mgmtId, !isLink, false); + mgmtObject->set_shadow(shadow); + agent->addObject(mgmtObject, objectId); + } + ConnectionState::setUrl(mgmtId); + } +} + +void Connection::requestIOProcessing(boost::function0<void> callback) +{ + ScopedLock<Mutex> l(ioCallbackLock); + ioCallbacks.push(callback); + out.activateOutput(); +} + +Connection::~Connection() +{ + if (mgmtObject != 0) { + mgmtObject->resourceDestroy(); + // In a cluster, Connections destroyed during shutdown are in + // a cluster-unsafe context. Don't raise an event in that case. + if (!isLink && isClusterSafe()) + agent->raiseEvent(_qmf::EventClientDisconnect(mgmtId, ConnectionState::getUserId())); + } + if (isLink) + links.notifyClosed(mgmtId); + + if (heartbeatTimer) + heartbeatTimer->cancel(); + if (timeoutTimer) + timeoutTimer->cancel(); + + if (!isShadow()) broker.getConnectionCounter().dec_connectionCount(); +} + +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."); + } + + if (isLink) //i.e. we are acting as the client to another broker + recordFromServer(frame); + else + recordFromClient(frame); +} + +void Connection::sent(const framing::AMQFrame& frame) +{ + if (isLink) //i.e. we are acting as the client to another broker + recordFromClient(frame); + else + recordFromServer(frame); +} + +bool isMessage(const AMQMethodBody* method) +{ + return method && method->isA<qpid::framing::MessageTransferBody>(); +} + +void Connection::recordFromServer(const framing::AMQFrame& frame) +{ + // Don't record management stats in cluster-unsafe contexts + if (mgmtObject != 0 && isClusterSafe()) + { + mgmtObject->inc_framesToClient(); + mgmtObject->inc_bytesToClient(frame.encodedSize()); + if (isMessage(frame.getMethod())) { + mgmtObject->inc_msgsToClient(); + } + } +} + +void Connection::recordFromClient(const framing::AMQFrame& frame) +{ + // Don't record management stats in cluster-unsafe contexts + if (mgmtObject != 0 && isClusterSafe()) + { + mgmtObject->inc_framesFromClient(); + mgmtObject->inc_bytesFromClient(frame.encodedSize()); + if (isMessage(frame.getMethod())) { + mgmtObject->inc_msgsFromClient(); + } + } +} + +string Connection::getAuthMechanism() +{ + if (!isLink) + return string("ANONYMOUS"); + + return links.getAuthMechanism(mgmtId); +} + +string Connection::getUsername ( ) +{ + if (!isLink) + return string("anonymous"); + + return links.getUsername(mgmtId); +} + +string Connection::getPassword ( ) +{ + if (!isLink) + return string(""); + + return links.getPassword(mgmtId); +} + +string Connection::getHost ( ) +{ + if (!isLink) + return string(""); + + return links.getHost(mgmtId); +} + +uint16_t Connection::getPort ( ) +{ + if (!isLink) + return 0; + + return links.getPort(mgmtId); +} + +string Connection::getAuthCredentials() +{ + if (!isLink) + return string(); + + if (mgmtObject != 0) + { + if (links.getAuthMechanism(mgmtId) == "ANONYMOUS") + mgmtObject->set_authIdentity("anonymous"); + else + mgmtObject->set_authIdentity(links.getAuthIdentity(mgmtId)); + } + + return links.getAuthCredentials(mgmtId); +} + +void Connection::notifyConnectionForced(const string& text) +{ + if (isLink) + links.notifyConnectionForced(mgmtId, text); +} + +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 (!broker.isInCluster()) raiseConnectEvent(); +} + +void Connection::raiseConnectEvent() { + if (mgmtObject != 0) { + mgmtObject->set_authIdentity(userId); + agent->raiseEvent(_qmf::EventClientConnect(mgmtId, userId)); + } +} + +void Connection::setFederationLink(bool b) +{ + ConnectionState::setFederationLink(b); + if (mgmtObject != 0) + mgmtObject->set_federationLink(b); +} + +void Connection::close(connection::CloseCode code, const string& text) +{ + QPID_LOG_IF(error, code != connection::CLOSE_CODE_NORMAL, "Connection " << mgmtId << " closed by error: " << text << "(" << code << ")"); + if (heartbeatTimer) + heartbeatTimer->cancel(); + if (timeoutTimer) + timeoutTimer->cancel(); + adapter.close(code, text); + //make sure we delete dangling pointers from outputTasks before deleting sessions + outputTasks.removeAll(); + channels.clear(); + getOutput().close(); +} + +// Send a close to the client but keep the channels. Used by cluster. +void Connection::sendClose() { + if (heartbeatTimer) + heartbeatTimer->cancel(); + if (timeoutTimer) + timeoutTimer->cancel(); + adapter.close(connection::CLOSE_CODE_NORMAL, "OK"); + getOutput().close(); +} + +void Connection::idleOut(){} + +void Connection::idleIn(){} + +void Connection::closed(){ // Physically closed, suspend open sessions. + if (heartbeatTimer) + heartbeatTimer->cancel(); + if (timeoutTimer) + timeoutTimer->cancel(); + try { + while (!channels.empty()) + ptr_map_ptr(channels.begin())->handleDetach(); + } 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 { + doIoCallbacks(); + if (mgmtClosing) { + closed(); + close(connection::CLOSE_CODE_CONNECTION_FORCED, "Closed by Management Request"); + } else { + //then do other output as needed: + return outputTasks.doOutput(); + } + }catch(const SessionOutputException& e){ + getChannel(e.channel).handleException(e); + return true; + }catch(ConnectionException& e){ + close(e.code, e.getMessage()); + }catch(std::exception& e){ + close(connection::CLOSE_CODE_CONNECTION_FORCED, e.what()); + } + return false; +} + +void Connection::sendHeartbeat() { + adapter.heartbeat(); +} + +void Connection::closeChannel(uint16_t id) { + ChannelMap::iterator i = channels.find(id); + if (i != channels.end()) channels.erase(i); +} + +SessionHandler& Connection::getChannel(ChannelId id) { + ChannelMap::iterator i=channels.find(id); + if (i == channels.end()) { + i = channels.insert(id, new SessionHandler(*this, id)).first; + } + return *ptr_map_ptr(i); +} + +ManagementObject* Connection::GetManagementObject(void) const +{ + return (ManagementObject*) mgmtObject; +} + +Manageable::status_t Connection::ManagementMethod(uint32_t methodId, Args&, string&) +{ + Manageable::status_t status = Manageable::STATUS_UNKNOWN_METHOD; + + QPID_LOG(debug, "Connection::ManagementMethod [id=" << methodId << "]"); + + switch (methodId) + { + case _qmf::Connection::METHOD_CLOSE : + mgmtClosing = true; + if (mgmtObject != 0) mgmtObject->set_closing(1); + out.activateOutput(); + status = Manageable::STATUS_OK; + break; + } + + return status; +} + +void Connection::setSecureConnection(SecureConnection* s) +{ + adapter.setSecureConnection(s); +} + +struct ConnectionHeartbeatTask : public sys::TimerTask { + sys::Timer& timer; + Connection& connection; + ConnectionHeartbeatTask(uint16_t hb, sys::Timer& t, Connection& c) : + TimerTask(Duration(hb*TIME_SEC), "ConnectionHeartbeat"), + timer(t), + connection(c) + {} + + void fire() { + // Setup next firing + setupNextFire(); + timer.add(this); + + // Send Heartbeat + connection.sendHeartbeat(); + } +}; + +void Connection::abort() +{ + // Make sure that we don't try to send a heartbeat as we're + // aborting the connection + if (heartbeatTimer) + heartbeatTimer->cancel(); + + out.abort(); +} + +void Connection::setHeartbeatInterval(uint16_t heartbeat) +{ + setHeartbeat(heartbeat); + if (heartbeat > 0 && !isShadow()) { + heartbeatTimer = new ConnectionHeartbeatTask(heartbeat, timer, *this); + timer.add(heartbeatTimer); + timeoutTimer = new ConnectionTimeoutTask(heartbeat, timer, *this); + timer.add(timeoutTimer); + } +} + +void Connection::restartTimeout() +{ + if (timeoutTimer) + timeoutTimer->touch(); +} + +bool Connection::isOpen() { return adapter.isOpen(); } + +Connection::OutboundFrameTracker::OutboundFrameTracker(Connection& _con) : con(_con), next(0) {} +void Connection::OutboundFrameTracker::close() { next->close(); } +size_t Connection::OutboundFrameTracker::getBuffered() const { return next->getBuffered(); } +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); + con.sent(f); +} +void Connection::OutboundFrameTracker::wrap(sys::ConnectionOutputHandlerPtr& p) +{ + next = p.get(); + p.set(this); +} + +}} diff --git a/qpid/cpp/src/qpid/broker/Connection.h b/qpid/cpp/src/qpid/broker/Connection.h new file mode 100644 index 0000000000..8f1aa701ef --- /dev/null +++ b/qpid/cpp/src/qpid/broker/Connection.h @@ -0,0 +1,216 @@ +#ifndef QPID_BROKER_CONNECTION_H +#define QPID_BROKER_CONNECTION_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 <memory> +#include <sstream> +#include <vector> +#include <queue> + +#include <boost/ptr_container/ptr_map.hpp> + +#include "qpid/broker/ConnectionHandler.h" +#include "qpid/broker/ConnectionState.h" +#include "qpid/broker/SessionHandler.h" +#include "qmf/org/apache/qpid/broker/Connection.h" +#include "qpid/Exception.h" +#include "qpid/RefCounted.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/AMQP_ClientProxy.h" +#include "qpid/framing/AMQP_ServerOperations.h" +#include "qpid/framing/ProtocolVersion.h" +#include "qpid/management/ManagementAgent.h" +#include "qpid/management/Manageable.h" +#include "qpid/ptr_map.h" +#include "qpid/sys/AggregateOutput.h" +#include "qpid/sys/ConnectionInputHandler.h" +#include "qpid/sys/ConnectionOutputHandler.h" +#include "qpid/sys/SecuritySettings.h" +#include "qpid/sys/Socket.h" +#include "qpid/sys/TimeoutHandler.h" +#include "qpid/sys/Mutex.h" + +#include <boost/ptr_container/ptr_map.hpp> +#include <boost/bind.hpp> + +#include <algorithm> + +namespace qpid { +namespace broker { + +class Broker; +class LinkRegistry; +class SecureConnection; +struct ConnectionTimeoutTask; + +class Connection : public sys::ConnectionInputHandler, + public ConnectionState, + public RefCounted +{ + public: + /** + * Listener that can be registered with a Connection to be informed of errors. + */ + class ErrorListener + { + public: + virtual ~ErrorListener() {} + virtual void sessionError(uint16_t channel, const std::string&) = 0; + virtual void connectionError(const std::string&) = 0; + }; + + Connection(sys::ConnectionOutputHandler* out, + Broker& broker, + const std::string& mgmtId, + const qpid::sys::SecuritySettings&, + bool isLink = false, + uint64_t objectId = 0, + bool shadow=false, + bool delayManagement = false); + + ~Connection (); + + /** Get the SessionHandler for channel. Create if it does not already exist */ + SessionHandler& getChannel(framing::ChannelId channel); + + /** Close the connection */ + void close(framing::connection::CloseCode code, const std::string& text); + + // ConnectionInputHandler methods + void received(framing::AMQFrame& frame); + void idleOut(); + void idleIn(); + bool doOutput(); + void closed(); + + void closeChannel(framing::ChannelId channel); + + // Manageable entry points + management::ManagementObject* GetManagementObject (void) const; + management::Manageable::status_t + ManagementMethod (uint32_t methodId, management::Args& args, std::string&); + + void requestIOProcessing (boost::function0<void>); + void recordFromServer (const framing::AMQFrame& frame); + void recordFromClient (const framing::AMQFrame& frame); + std::string getAuthMechanism(); + std::string getAuthCredentials(); + std::string getUsername(); + std::string getPassword(); + std::string getHost(); + uint16_t getPort(); + void notifyConnectionForced(const std::string& text); + void setUserId(const std::string& uid); + void raiseConnectEvent(); + 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); + /** Connection does not delete the listener. 0 resets. */ + void setErrorListener(ErrorListener* l) { errorListener=l; } + ErrorListener* getErrorListener() { return errorListener; } + + void setHeartbeatInterval(uint16_t heartbeat); + void sendHeartbeat(); + void restartTimeout(); + void abort(); + + template <class F> void eachSessionHandler(F f) { + for (ChannelMap::iterator i = channels.begin(); i != channels.end(); ++i) + f(*ptr_map_ptr(i)); + } + + void sendClose(); + void setSecureConnection(SecureConnection* secured); + + /** True if this is a shadow connection in a cluster. */ + bool isShadow() { return shadow; } + + // Used by cluster to update connection status + sys::AggregateOutput& getOutputTasks() { return outputTasks; } + + /** Cluster delays adding management object in the constructor then calls this. */ + 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; + + ChannelMap channels; + qpid::sys::SecuritySettings securitySettings; + ConnectionHandler adapter; + const bool isLink; + bool mgmtClosing; + const std::string mgmtId; + sys::Mutex ioCallbackLock; + std::queue<boost::function0<void> > ioCallbacks; + qmf::org::apache::qpid::broker::Connection* mgmtObject; + LinkRegistry& links; + management::ManagementAgent* agent; + sys::Timer& timer; + boost::intrusive_ptr<sys::TimerTask> heartbeatTimer; + boost::intrusive_ptr<ConnectionTimeoutTask> timeoutTimer; + ErrorListener* errorListener; + uint64_t objectId; + bool shadow; + /** + * Chained ConnectionOutputHandler that allows outgoing frames to be + * tracked (for updating mgmt stats). + */ + class OutboundFrameTracker : public sys::ConnectionOutputHandler + { + public: + OutboundFrameTracker(Connection&); + void close(); + size_t getBuffered() const; + void abort(); + void activateOutput(); + void giveReadCredit(int32_t credit); + void send(framing::AMQFrame&); + void wrap(sys::ConnectionOutputHandlerPtr&); + private: + Connection& con; + sys::ConnectionOutputHandler* next; + }; + OutboundFrameTracker outboundTracker; + + + void sent(const framing::AMQFrame& f); + public: + qmf::org::apache::qpid::broker::Connection* getMgmtObject() { return mgmtObject; } +}; + +}} + +#endif /*!QPID_BROKER_CONNECTION_H*/ diff --git a/qpid/cpp/src/qpid/broker/ConnectionFactory.cpp b/qpid/cpp/src/qpid/broker/ConnectionFactory.cpp new file mode 100644 index 0000000000..9e0020812b --- /dev/null +++ b/qpid/cpp/src/qpid/broker/ConnectionFactory.cpp @@ -0,0 +1,66 @@ +/* + * + * 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/ConnectionFactory.h" +#include "qpid/framing/ProtocolVersion.h" +#include "qpid/amqp_0_10/Connection.h" +#include "qpid/broker/Connection.h" +#include "qpid/sys/SecuritySettings.h" +#include "qpid/log/Statement.h" + +namespace qpid { +namespace broker { + +using framing::ProtocolVersion; +using qpid::sys::SecuritySettings; +typedef std::auto_ptr<amqp_0_10::Connection> ConnectionPtr; +typedef std::auto_ptr<sys::ConnectionInputHandler> InputPtr; + +ConnectionFactory::ConnectionFactory(Broker& b) : broker(b) {} + +ConnectionFactory::~ConnectionFactory() {} + +sys::ConnectionCodec* +ConnectionFactory::create(ProtocolVersion v, sys::OutputControl& out, const std::string& id, + const SecuritySettings& external) { + if (broker.getConnectionCounter().allowConnection()) + { + QPID_LOG(error, "Client max connection count limit exceeded: " << broker.getOptions().maxConnections << " connection refused"); + return 0; + } + if (v == ProtocolVersion(0, 10)) { + ConnectionPtr c(new amqp_0_10::Connection(out, id, false)); + c->setInputHandler(InputPtr(new broker::Connection(c.get(), broker, id, external, false))); + return c.release(); + } + return 0; +} + +sys::ConnectionCodec* +ConnectionFactory::create(sys::OutputControl& out, const std::string& id, + const SecuritySettings& external) { + // used to create connections from one broker to another + ConnectionPtr c(new amqp_0_10::Connection(out, id, true)); + c->setInputHandler(InputPtr(new broker::Connection(c.get(), broker, id, external, true))); + return c.release(); +} + + +}} // namespace qpid::broker diff --git a/qpid/cpp/src/qpid/broker/ConnectionFactory.h b/qpid/cpp/src/qpid/broker/ConnectionFactory.h new file mode 100644 index 0000000000..7c1a9a08e1 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/ConnectionFactory.h @@ -0,0 +1,51 @@ +/* + * + * 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 _ConnectionFactory_ +#define _ConnectionFactory_ + +#include "qpid/sys/ConnectionCodec.h" + +namespace qpid { +namespace broker { +class Broker; + +class ConnectionFactory : public sys::ConnectionCodec::Factory +{ + public: + ConnectionFactory(Broker& b); + + virtual ~ConnectionFactory(); + + sys::ConnectionCodec* + create(framing::ProtocolVersion, sys::OutputControl&, const std::string& id, + const qpid::sys::SecuritySettings&); + + sys::ConnectionCodec* + create(sys::OutputControl&, const std::string& id, const qpid::sys::SecuritySettings&); + + private: + Broker& broker; +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/ConnectionHandler.cpp b/qpid/cpp/src/qpid/broker/ConnectionHandler.cpp new file mode 100644 index 0000000000..3f97e5b9de --- /dev/null +++ b/qpid/cpp/src/qpid/broker/ConnectionHandler.cpp @@ -0,0 +1,364 @@ + +/* + * + * 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/SaslFactory.h" +#include "qpid/broker/ConnectionHandler.h" +#include "qpid/broker/Connection.h" +#include "qpid/broker/SecureConnection.h" +#include "qpid/Url.h" +#include "qpid/framing/AllInvoker.h" +#include "qpid/framing/enum.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/SecurityLayer.h" +#include "qpid/broker/AclModule.h" +#include "qmf/org/apache/qpid/broker/EventClientConnectFail.h" + +using namespace qpid; +using namespace qpid::broker; +using namespace qpid::framing; +using qpid::sys::SecurityLayer; +namespace _qmf = qmf::org::apache::qpid::broker; + +namespace +{ +const std::string ANONYMOUS = "ANONYMOUS"; +const std::string PLAIN = "PLAIN"; +const std::string en_US = "en_US"; +const std::string QPID_FED_LINK = "qpid.fed_link"; +const std::string QPID_FED_TAG = "qpid.federation_tag"; +const std::string SESSION_FLOW_CONTROL("qpid.session_flow"); +const std::string CLIENT_PROCESS_NAME("qpid.client_process"); +const std::string CLIENT_PID("qpid.client_pid"); +const std::string CLIENT_PPID("qpid.client_ppid"); +const int SESSION_FLOW_CONTROL_VER = 1; +const std::string SPACE(" "); +} + +void ConnectionHandler::close(connection::CloseCode code, const string& text) +{ + handler->proxy.close(code, text); +} + +void ConnectionHandler::heartbeat() +{ + handler->proxy.heartbeat(); +} + +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)) { + handler->connection.getChannel(frame.getChannel()).in(frame); + } + }catch(ConnectionException& e){ + if (errorListener) errorListener->connectionError(e.what()); + handler->proxy.close(e.code, e.what()); + }catch(std::exception& e){ + if (errorListener) errorListener->connectionError(e.what()); + handler->proxy.close(541/*internal error*/, e.what()); + } +} + +void ConnectionHandler::setSecureConnection(SecureConnection* secured) +{ + handler->secured = secured; +} + +ConnectionHandler::ConnectionHandler(Connection& connection, bool isClient, bool isShadow) : handler(new Handler(connection, isClient, isShadow)) {} + +ConnectionHandler::Handler::Handler(Connection& c, bool isClient, bool isShadow) : + proxy(c.getOutput()), + connection(c), serverMode(!isClient), acl(0), secured(0), + isOpen(false) +{ + if (serverMode) { + + acl = connection.getBroker().getAcl(); + + FieldTable properties; + Array mechanisms(0x95); + + properties.setString(QPID_FED_TAG, connection.getBroker().getFederationTag()); + + authenticator = SaslAuthenticator::createAuthenticator(c, isShadow); + authenticator->getMechanisms(mechanisms); + + Array locales(0x95); + boost::shared_ptr<FieldValue> l(new Str16Value(en_US)); + locales.add(l); + proxy.start(properties, mechanisms, locales); + + } + + maxFrameSize = (64 * 1024) - 1; +} + + +ConnectionHandler::Handler::~Handler() {} + + +void ConnectionHandler::Handler::startOk(const framing::FieldTable& clientProperties, + const string& mechanism, + const string& response, + const string& /*locale*/) +{ + try { + authenticator->start(mechanism, response); + } catch (std::exception& /*e*/) { + management::ManagementAgent* agent = connection.getAgent(); + if (agent) { + string error; + string uid; + authenticator->getError(error); + authenticator->getUid(uid); + agent->raiseEvent(_qmf::EventClientConnectFail(connection.getMgmtId(), uid, error)); + } + throw; + } + connection.setFederationLink(clientProperties.get(QPID_FED_LINK)); + connection.setFederationPeerTag(clientProperties.getAsString(QPID_FED_TAG)); + if (connection.isFederationLink()) { + 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; + } + QPID_LOG(info, "Connection is a federation link"); + } + if (clientProperties.getAsInt(SESSION_FLOW_CONTROL) == SESSION_FLOW_CONTROL_VER) { + connection.setClientThrottling(); + } + + if (connection.getMgmtObject() != 0) { + string procName = clientProperties.getAsString(CLIENT_PROCESS_NAME); + uint32_t pid = clientProperties.getAsInt(CLIENT_PID); + uint32_t ppid = clientProperties.getAsInt(CLIENT_PPID); + + if (!procName.empty()) + connection.getMgmtObject()->set_remoteProcessName(procName); + if (pid != 0) + connection.getMgmtObject()->set_remotePid(pid); + if (ppid != 0) + connection.getMgmtObject()->set_remoteParentPid(ppid); + } +} + +void ConnectionHandler::Handler::secureOk(const string& response) +{ + try { + authenticator->step(response); + } catch (std::exception& /*e*/) { + management::ManagementAgent* agent = connection.getAgent(); + if (agent) { + string error; + string uid; + authenticator->getError(error); + authenticator->getUid(uid); + agent->raiseEvent(_qmf::EventClientConnectFail(connection.getMgmtId(), uid, error)); + } + throw; + } +} + +void ConnectionHandler::Handler::tuneOk(uint16_t /*channelmax*/, + uint16_t framemax, uint16_t heartbeat) +{ + connection.setFrameMax(framemax); + connection.setHeartbeatInterval(heartbeat); +} + +void ConnectionHandler::Handler::open(const string& /*virtualHost*/, + const framing::Array& /*capabilities*/, bool /*insist*/) +{ + std::vector<Url> urls = connection.broker.getKnownBrokers(); + framing::Array array(0x95); // str16 array + for (std::vector<Url>::iterator i = urls.begin(); i < urls.end(); ++i) + array.add(boost::shared_ptr<Str16Value>(new Str16Value(i->str()))); + + //install security layer if one has been negotiated: + if (secured) { + std::auto_ptr<SecurityLayer> sl = authenticator->getSecurityLayer(connection.getFrameMax()); + if (sl.get()) secured->activateSecurityLayer(sl); + } + + isOpen = true; + proxy.openOk(array); +} + + +void ConnectionHandler::Handler::close(uint16_t replyCode, const string& replyText) +{ + if (replyCode != 200) { + QPID_LOG(warning, "Client closed connection with " << replyCode << ": " << replyText); + } + + if (replyCode == framing::connection::CLOSE_CODE_CONNECTION_FORCED) + connection.notifyConnectionForced(replyText); + + proxy.closeOk(); + connection.getOutput().close(); +} + +void ConnectionHandler::Handler::closeOk(){ + connection.getOutput().close(); +} + +void ConnectionHandler::Handler::heartbeat(){ + // For general case, do nothing - the purpose of heartbeats is + // just to make sure that there is some traffic on the connection + // within the heart beat interval, we check for the traffic and + // don't need to do anything in response to heartbeats. The + // exception is when we are in fact the client to another broker + // (i.e. an inter-broker link), in which case we echo the + // heartbeat back to the peer + if (!serverMode) proxy.heartbeat(); +} + +void ConnectionHandler::Handler::start(const FieldTable& serverProperties, + const framing::Array& supportedMechanisms, + const framing::Array& /*locales*/) +{ + string requestedMechanism = connection.getAuthMechanism(); + + std::string username = connection.getUsername(); + + std::string password = connection.getPassword(); + std::string host = connection.getHost(); + std::string service("qpidd"); + + if ( connection.getBroker().isAuthenticating() ) { + sasl = SaslFactory::getInstance().create( username, + password, + service, + host, + 0, // TODO -- mgoulish Fri Sep 24 2010 + 256, + false ); // disallow interaction + } + std::string supportedMechanismsList; + bool requestedMechanismIsSupported = false; + Array::const_iterator i; + + /* + If no specific mechanism has been requested, just make + a list of all of them, and assert that the one the caller + requested is there. ( If *any* are supported! ) + */ + if ( requestedMechanism.empty() ) { + for ( i = supportedMechanisms.begin(); i != supportedMechanisms.end(); ++i) { + 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. + */ + for ( i = supportedMechanisms.begin(); i != supportedMechanisms.end(); ++i) { + string currentMechanism = (*i)->get<std::string>(); + + if ( requestedMechanism == currentMechanism ) { + requestedMechanismIsSupported = true; + supportedMechanismsList = currentMechanism + SPACE + supportedMechanismsList; + } else { + if (i != supportedMechanisms.begin()) + supportedMechanismsList += SPACE; + supportedMechanismsList += currentMechanism; + } + } + } + + connection.setFederationPeerTag(serverProperties.getAsString(QPID_FED_TAG)); + + FieldTable ft; + ft.setInt(QPID_FED_LINK,1); + ft.setString(QPID_FED_TAG, connection.getBroker().getFederationTag()); + + 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 ); + } + else { + response = ((char)0) + username + ((char)0) + password; + proxy.startOk ( ft, requestedMechanism, response, en_US ); + } + +} + +void ConnectionHandler::Handler::secure(const string& challenge ) +{ + if (sasl.get()) { + string response = sasl->step(challenge); + proxy.secureOk(response); + } + else { + proxy.secureOk(""); + } +} + +void ConnectionHandler::Handler::tune(uint16_t channelMax, + uint16_t maxFrameSizeProposed, + uint16_t /*heartbeatMin*/, + uint16_t heartbeatMax) +{ + maxFrameSize = std::min(maxFrameSize, maxFrameSizeProposed); + connection.setFrameMax(maxFrameSize); + + connection.setHeartbeat(heartbeatMax); + proxy.tuneOk(channelMax, maxFrameSize, heartbeatMax); + proxy.open("/", Array(), true); +} + +void ConnectionHandler::Handler::openOk(const framing::Array& knownHosts) +{ + for (Array::ValueVector::const_iterator i = knownHosts.begin(); i != knownHosts.end(); ++i) { + Url url((*i)->get<std::string>()); + connection.getKnownHosts().push_back(url); + } + + if (sasl.get()) { + std::auto_ptr<qpid::sys::SecurityLayer> securityLayer = sasl->getSecurityLayer(maxFrameSize); + + if ( securityLayer.get() ) { + secured->activateSecurityLayer(securityLayer, true); + } + + saslUserId = sasl->getUserId(); + } + + isOpen = true; +} + +void ConnectionHandler::Handler::redirect(const string& /*host*/, const framing::Array& /*knownHosts*/) +{ + +} diff --git a/qpid/cpp/src/qpid/broker/ConnectionHandler.h b/qpid/cpp/src/qpid/broker/ConnectionHandler.h new file mode 100644 index 0000000000..b32167669e --- /dev/null +++ b/qpid/cpp/src/qpid/broker/ConnectionHandler.h @@ -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. + * + */ +#ifndef _ConnectionAdapter_ +#define _ConnectionAdapter_ + +#include <memory> +#include "qpid/Sasl.h" +#include "qpid/broker/SaslAuthenticator.h" +#include "qpid/framing/amqp_types.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/AMQP_AllOperations.h" +#include "qpid/framing/AMQP_AllProxy.h" +#include "qpid/framing/enum.h" +#include "qpid/framing/FrameHandler.h" +#include "qpid/framing/ProtocolInitiation.h" +#include "qpid/framing/ProtocolVersion.h" +#include "qpid/Exception.h" +#include "qpid/broker/AclModule.h" +#include "qpid/sys/SecurityLayer.h" + + +namespace qpid { + +namespace sys { +struct SecuritySettings; +} + + +namespace broker { + +class Connection; +class SecureConnection; + +class ConnectionHandler : public framing::FrameHandler +{ + struct Handler : public framing::AMQP_AllOperations::ConnectionHandler + { + framing::AMQP_AllProxy::Connection proxy; + 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::FieldTable& clientProperties, + const std::string& mechanism, const std::string& response, + const std::string& locale); + void secureOk(const std::string& response); + void tuneOk(uint16_t channelMax, uint16_t frameMax, uint16_t heartbeat); + void heartbeat(); + void open(const std::string& virtualHost, + const framing::Array& capabilities, bool insist); + void close(uint16_t replyCode, const std::string& replyText); + void closeOk(); + + void start(const qpid::framing::FieldTable& serverProperties, + const framing::Array& mechanisms, + const framing::Array& locales); + + void secure(const std::string& challenge); + + void tune(uint16_t channelMax, + uint16_t frameMax, + uint16_t heartbeatMin, + uint16_t heartbeatMax); + + void openOk(const framing::Array& knownHosts); + + void redirect(const std::string& host, const framing::Array& knownHosts); + + std::auto_ptr<Sasl> sasl; + typedef boost::function<const qpid::sys::SecuritySettings*()> GetSecuritySettings; + std::string saslUserId; + uint16_t maxFrameSize; + }; + std::auto_ptr<Handler> handler; + + + public: + ConnectionHandler(Connection& connection, bool isClient, bool isShadow=false ); + void close(framing::connection::CloseCode code, const std::string& text); + void heartbeat(); + void handle(framing::AMQFrame& frame); + void setSecureConnection(SecureConnection* secured); + bool isOpen() { return handler->isOpen; } +}; + + +}} + +#endif diff --git a/qpid/cpp/src/qpid/broker/ConnectionState.h b/qpid/cpp/src/qpid/broker/ConnectionState.h new file mode 100644 index 0000000000..9c31a931d8 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/ConnectionState.h @@ -0,0 +1,117 @@ +/* + * + * 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 _ConnectionState_ +#define _ConnectionState_ + +#include <vector> + +#include "qpid/sys/AggregateOutput.h" +#include "qpid/sys/ConnectionOutputHandlerPtr.h" +#include "qpid/framing/ProtocolVersion.h" +#include "qpid/management/Manageable.h" +#include "qpid/Url.h" +#include "qpid/broker/Broker.h" + +namespace qpid { +namespace broker { + +class ConnectionState : public ConnectionToken, public management::Manageable +{ + protected: + sys::ConnectionOutputHandlerPtr out; + + public: + ConnectionState(qpid::sys::ConnectionOutputHandler* o, Broker& b) : + out(o), + broker(b), + outputTasks(out), + framemax(65535), + heartbeat(0), + heartbeatmax(120), + federationLink(true), + clientSupportsThrottling(false), + clusterOrderOut(0) + {} + + virtual ~ConnectionState () {} + + uint32_t getFrameMax() const { return framemax; } + uint16_t getHeartbeat() const { return heartbeat; } + uint16_t getHeartbeatMax() const { return heartbeatmax; } + + void setFrameMax(uint32_t fm) { framemax = std::max(fm, (uint32_t) 4096); } + void setHeartbeat(uint16_t hb) { heartbeat = hb; } + void setHeartbeatMax(uint16_t hbm) { heartbeatmax = hbm; } + + virtual void setUserId(const std::string& uid) { userId = uid; } + const std::string& getUserId() const { return userId; } + + 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 setFederationPeerTag(const std::string& tag) { federationPeerTag = std::string(tag); } + const std::string& getFederationPeerTag() const { return federationPeerTag; } + std::vector<Url>& getKnownHosts() { return knownHosts; } + + void setClientThrottling(bool set=true) { clientSupportsThrottling = set; } + bool getClientThrottling() const { return clientSupportsThrottling; } + + Broker& getBroker() { return broker; } + + Broker& broker; + + //contained output tasks + sys::AggregateOutput outputTasks; + + sys::ConnectionOutputHandler& getOutput() { return out; } + framing::ProtocolVersion getVersion() const { return version; } + void setOutputHandler(qpid::sys::ConnectionOutputHandler* o) { out.set(o); } + + /** + * If the broker is part of a cluster, this is a handler provided + * by cluster code. It ensures consistent ordering of commands + * that are sent based on criteria that are not predictably + * ordered cluster-wide, e.g. a timer firing. + */ + framing::FrameHandler* getClusterOrderOutput() { return clusterOrderOut; } + void setClusterOrderOutput(framing::FrameHandler& fh) { clusterOrderOut = &fh; } + + virtual void requestIOProcessing (boost::function0<void>) = 0; + + protected: + framing::ProtocolVersion version; + uint32_t framemax; + uint16_t heartbeat; + uint16_t heartbeatmax; + std::string userId; + std::string url; + bool federationLink; + std::string federationPeerTag; + std::vector<Url> knownHosts; + bool clientSupportsThrottling; + framing::FrameHandler* clusterOrderOut; +}; + +}} + +#endif diff --git a/qpid/cpp/src/qpid/broker/ConnectionToken.h b/qpid/cpp/src/qpid/broker/ConnectionToken.h new file mode 100644 index 0000000000..9b40383c80 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/ConnectionToken.h @@ -0,0 +1,40 @@ +/* + * + * 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 _ConnectionToken_ +#define _ConnectionToken_ + +#include "qpid/broker/OwnershipToken.h" +namespace qpid { + namespace broker { + /** + * An empty interface allowing opaque implementations of some + * form of token to identify a connection. + */ + class ConnectionToken : public OwnershipToken { + public: + virtual bool isLocal(const ConnectionToken* t) const { return this == t; } + virtual ~ConnectionToken(){} + }; + } +} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/Consumer.h b/qpid/cpp/src/qpid/broker/Consumer.h new file mode 100644 index 0000000000..317338a8ad --- /dev/null +++ b/qpid/cpp/src/qpid/broker/Consumer.h @@ -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. + * + */ +#ifndef _Consumer_ +#define _Consumer_ + +#include "qpid/broker/Message.h" +#include "qpid/broker/QueuedMessage.h" +#include "qpid/broker/OwnershipToken.h" + +namespace qpid { +namespace broker { + +class Queue; +class QueueListeners; + +class Consumer { + const bool acquires; + // inListeners allows QueueListeners to efficiently track if this instance is registered + // for notifications without having to search its containers + bool inListeners; + public: + typedef boost::shared_ptr<Consumer> shared_ptr; + + framing::SequenceNumber position; + + Consumer(bool preAcquires = true) : acquires(preAcquires), inListeners(false) {} + bool preAcquires() const { return acquires; } + 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; +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/Daemon.cpp b/qpid/cpp/src/qpid/broker/Daemon.cpp new file mode 100644 index 0000000000..b30e5f18cb --- /dev/null +++ b/qpid/cpp/src/qpid/broker/Daemon.cpp @@ -0,0 +1,219 @@ +/* + * + * 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. + * + */ + +/* + * TODO: Note this is really a Posix specific implementation and so should be + * refactored together with windows/QpiddBroker into a more coherent daemon driver/ + * platform specific split + */ +#include "qpid/broker/Daemon.h" +#include "qpid/log/Statement.h" +#include "qpid/Exception.h" +#include "qpid/sys/posix/PidFile.h" + +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +namespace qpid { +namespace broker { + +using namespace std; +using qpid::sys::PidFile; + +Daemon::Daemon(std::string _pidDir) : pidDir(_pidDir) { + struct stat s; + pid = -1; + pipeFds[0] = pipeFds[1] = -1; + + if (::stat(pidDir.c_str(), &s)) { + if (errno == ENOENT) { + if (::mkdir(pidDir.c_str(), 0755)) + throw Exception ("Can't create PID directory: " + pidDir); + } + else + throw Exception ("PID directory not found: " + pidDir); + } +} + +string Daemon::pidFile(string pidDir, uint16_t port) { + ostringstream path; + path << pidDir << "/qpidd." << port << ".pid"; + return path.str(); +} + +/* + * Rewritten using low-level IO, for compatibility + * with earlier Boost versions, i.e. 103200. + */ +void Daemon::fork() +{ + if(::pipe(pipeFds) < 0) throw ErrnoException("Can't create pipe"); + if ((pid = ::fork()) < 0) throw ErrnoException("Daemon fork failed"); + if (pid == 0) { // Child + try { + QPID_LOG(debug, "Forked daemon child process"); + + // File descriptors + if(::close(pipeFds[0])<0) throw ErrnoException("Cannot close read pipe"); + if(::close(0)<0) throw ErrnoException("Cannot close stdin"); + if(::close(1)<0) throw ErrnoException("Cannot close stdout"); + if(::close(2)<0) throw ErrnoException("Cannot close stderr"); + int fd=::open("/dev/null",O_RDWR); // stdin + if(fd != 0) throw ErrnoException("Cannot re-open stdin"); + if(::dup(fd)<0) throw ErrnoException("Cannot re-open stdout"); + if(::dup(fd)<0) throw ErrnoException("Cannot re-open stderror"); + + // Misc + if(setsid()<0) throw ErrnoException("Cannot set session ID"); + if(chdir(pidDir.c_str()) < 0) throw ErrnoException("Cannot change directory to "+pidDir); + umask(027); + + // Child behavior + child(); + } + 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)); + + std::string pipeFailureMessage = e.what(); + unused_ret = write ( pipeFds[1], + pipeFailureMessage.c_str(), + strlen(pipeFailureMessage.c_str()) + ); + } + } + else { // Parent + close(pipeFds[1]); // Write side. + parent(); + } +} + +Daemon::~Daemon() { + if (!lockFile.empty()) + unlink(lockFile.c_str()); +} + +uint16_t Daemon::wait(int timeout) { // parent waits for child. + try { + errno = 0; + struct timeval tv; + tv.tv_sec = timeout; + tv.tv_usec = 0; + + /* + * Rewritten using low-level IO, for compatibility + * with earlier Boost versions, i.e. 103200. + */ + fd_set fds; + FD_ZERO(&fds); + FD_SET(pipeFds[0], &fds); + int n=select(FD_SETSIZE, &fds, 0, 0, &tv); + if(n==0) throw Exception("Timed out waiting for daemon (If store recovery is in progress, use longer wait time)"); + if(n<0) throw ErrnoException("Error waiting for daemon"); + uint16_t port = 0; + /* + * Read the child's port number from the pipe. + */ + int desired_read = sizeof(uint16_t); + if ( desired_read > ::read(pipeFds[0], & port, desired_read) ) + throw Exception("Cannot read from child process."); + + /* + * If the port number is 0, the child has put an error message + * on the pipe. Get it and throw it. + */ + if ( 0 == port ) { + // Skip whitespace + char c = ' '; + while ( isspace(c) ) { + if ( 1 > ::read(pipeFds[0], &c, 1) ) + throw Exception("Child port == 0, and no error message on pipe."); + } + + // Get Message + string errmsg; + do { + errmsg += c; + } while (::read(pipeFds[0], &c, 1)); + throw Exception("Daemon startup failed"+ + (errmsg.empty() ? string(".") : ": " + errmsg)); + } + return port; + } + catch (const std::exception& e) { + // Print directly to cerr. The caller will catch and log the + // exception, but in the case of a daemon parent process we + // also need to be sure the error goes to stderr. A + // dameon's logging configuration normally does not log to + // stderr. + std::cerr << e.what() << endl; + throw; + } +} + + +/* + * When the child is ready, it writes its pid to the + * lockfile and its port number on the pipe back to + * its parent process. This indicates that the + * child has successfully daemonized. When the parent + * hears the good news, it ill exit. + */ +void Daemon::ready(uint16_t port) { // child + lockFile = pidFile(pidDir, port); + PidFile lf(lockFile, true); + + /* + * Write the PID to the lockfile. + */ + lf.writePid(); + + /* + * Write the port number to the parent. + */ + int desired_write = sizeof(uint16_t); + if ( desired_write > ::write(pipeFds[1], & port, desired_write) ) { + throw Exception("Error writing to parent." ); + } + + QPID_LOG(debug, "Daemon ready on port: " << port); +} + +/* + * The parent process reads the child's pid + * from the lockfile. + */ +pid_t Daemon::getPid(string _pidDir, uint16_t port) { + string name = pidFile(_pidDir, port); + PidFile lf(name, false); + pid_t pid = lf.readPid(); + if (kill(pid, 0) < 0 && errno != EPERM) { + unlink(name.c_str()); + throw Exception("Removing stale lock file "+name); + } + return pid; +} + + +}} // namespace qpid::broker diff --git a/qpid/cpp/src/qpid/broker/Daemon.h b/qpid/cpp/src/qpid/broker/Daemon.h new file mode 100644 index 0000000000..a9cd98bce2 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/Daemon.h @@ -0,0 +1,84 @@ +#ifndef _broker_Daemon_h +#define _broker_Daemon_h + +/* + * + * 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. + * + */ + +#include "qpid/sys/IntegerTypes.h" +#include <boost/scoped_ptr.hpp> +#include <boost/function.hpp> +#include <boost/noncopyable.hpp> +#include <string> + + +namespace qpid { +namespace broker { + +/** + * Tools for forking and managing a daemon process. + * NB: Only one Daemon instance is allowed in a process. + */ +class Daemon : private boost::noncopyable +{ + public: + /** Check daemon is running on port, throw exception if not */ + static pid_t getPid(std::string pidDir, uint16_t port); + + Daemon(std::string pidDir); + + virtual ~Daemon(); + + /** + * Fork a daemon process. + * Call parent() in the parent process, child() in the child. + */ + void fork(); + + protected: + + /** Called in parent process */ + virtual void parent() = 0; + + /** Called in child process */ + virtual void child() = 0; + + /** Call from parent(): wait for child to indicate it is ready. + * @timeout in seconds to wait for response. + * @return port passed by child to ready(). + */ + uint16_t wait(int timeout); + + /** Call from child(): Notify the parent we are ready and write the + * PID file. + *@param port returned by parent call to wait(). + */ + void ready(uint16_t port); + + private: + static std::string pidFile(std::string pidDir, uint16_t port); + + pid_t pid; + int pipeFds[2]; + int lockFileFd; + std::string lockFile; + std::string pidDir; +}; + +}} // namespace qpid::broker + +#endif /*!_broker_Daemon_h*/ diff --git a/qpid/cpp/src/qpid/broker/Deliverable.h b/qpid/cpp/src/qpid/broker/Deliverable.h new file mode 100644 index 0000000000..ffb5a77bca --- /dev/null +++ b/qpid/cpp/src/qpid/broker/Deliverable.h @@ -0,0 +1,43 @@ +/* + * + * 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 _Deliverable_ +#define _Deliverable_ + +#include "qpid/broker/Message.h" + +namespace qpid { + namespace broker { + class Deliverable{ + public: + bool delivered; + Deliverable() : delivered(false) {} + + virtual Message& getMessage() = 0; + + virtual void deliverTo(const boost::shared_ptr<Queue>& queue) = 0; + virtual uint64_t contentSize() { return 0; } + virtual ~Deliverable(){} + }; + } +} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/DeliverableMessage.cpp b/qpid/cpp/src/qpid/broker/DeliverableMessage.cpp new file mode 100644 index 0000000000..3ebb12461c --- /dev/null +++ b/qpid/cpp/src/qpid/broker/DeliverableMessage.cpp @@ -0,0 +1,45 @@ +/* + * + * 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/DeliverableMessage.h" +#include "qpid/broker/Queue.h" + +using namespace qpid::broker; + +DeliverableMessage::DeliverableMessage(const boost::intrusive_ptr<Message>& _msg) : msg(_msg) +{ +} + +void DeliverableMessage::deliverTo(const boost::shared_ptr<Queue>& queue) +{ + queue->deliver(msg); + delivered = true; +} + +Message& DeliverableMessage::getMessage() +{ + return *msg; +} + +uint64_t DeliverableMessage::contentSize () +{ + return msg->contentSize (); +} diff --git a/qpid/cpp/src/qpid/broker/DeliverableMessage.h b/qpid/cpp/src/qpid/broker/DeliverableMessage.h new file mode 100644 index 0000000000..c8d21001eb --- /dev/null +++ b/qpid/cpp/src/qpid/broker/DeliverableMessage.h @@ -0,0 +1,45 @@ +/* + * + * 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 _DeliverableMessage_ +#define _DeliverableMessage_ + +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/Deliverable.h" +#include "qpid/broker/Message.h" + +#include <boost/intrusive_ptr.hpp> + +namespace qpid { + namespace broker { + class QPID_BROKER_CLASS_EXTERN DeliverableMessage : public Deliverable{ + boost::intrusive_ptr<Message> msg; + public: + QPID_BROKER_EXTERN DeliverableMessage(const boost::intrusive_ptr<Message>& msg); + QPID_BROKER_EXTERN virtual void deliverTo(const boost::shared_ptr<Queue>& queue); + QPID_BROKER_EXTERN Message& getMessage(); + QPID_BROKER_EXTERN uint64_t contentSize(); + virtual ~DeliverableMessage(){} + }; + } +} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/DeliveryAdapter.h b/qpid/cpp/src/qpid/broker/DeliveryAdapter.h new file mode 100644 index 0000000000..b0bec60890 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/DeliveryAdapter.h @@ -0,0 +1,53 @@ +/* + * + * 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 _DeliveryAdapter_ +#define _DeliveryAdapter_ + +#include "qpid/broker/DeliveryId.h" +#include "qpid/broker/Message.h" +#include "qpid/framing/amqp_types.h" + +namespace qpid { +namespace broker { + +class DeliveryRecord; + +/** + * The intention behind this interface is to separate the generic + * handling of some form of message delivery to clients that is + * contained in the version independent Channel class from the + * details required for a particular situation or + * version. i.e. where the existing adapters allow (through + * supporting the generated interface for a version of the + * protocol) inputs of a channel to be adapted to the version + * independent part, this does the same for the outputs. + */ +class DeliveryAdapter +{ + public: + virtual void deliver(DeliveryRecord&, bool sync) = 0; + virtual ~DeliveryAdapter(){} +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/DeliveryId.h b/qpid/cpp/src/qpid/broker/DeliveryId.h new file mode 100644 index 0000000000..05b19f032e --- /dev/null +++ b/qpid/cpp/src/qpid/broker/DeliveryId.h @@ -0,0 +1,35 @@ +/* + * + * 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 _DeliveryId_ +#define _DeliveryId_ + +#include "qpid/framing/SequenceNumber.h" +#include "qpid/framing/SequenceNumberSet.h" + +namespace qpid { +namespace broker { + + typedef framing::SequenceNumber DeliveryId; + typedef framing::SequenceNumberSet DeliveryIds; +}} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/DeliveryRecord.cpp b/qpid/cpp/src/qpid/broker/DeliveryRecord.cpp new file mode 100644 index 0000000000..58dcc6d7c7 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/DeliveryRecord.cpp @@ -0,0 +1,196 @@ +/* + * + * 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/DeliveryRecord.h" +#include "qpid/broker/DeliverableMessage.h" +#include "qpid/broker/SemanticState.h" +#include "qpid/broker/Exchange.h" +#include "qpid/broker/Queue.h" +#include "qpid/log/Statement.h" +#include "qpid/framing/FrameHandler.h" +#include "qpid/framing/MessageTransferBody.h" + +using namespace qpid; +using namespace qpid::broker; +using std::string; + +DeliveryRecord::DeliveryRecord(const QueuedMessage& _msg, + const Queue::shared_ptr& _queue, + const std::string& _tag, + bool _acquired, + bool accepted, + bool _windowing, + uint32_t _credit) : msg(_msg), + queue(_queue), + tag(_tag), + acquired(_acquired), + acceptExpected(!accepted), + cancelled(false), + completed(false), + ended(accepted && acquired), + windowing(_windowing), + credit(msg.payload ? msg.payload->getRequiredCredit() : _credit) +{} + +bool DeliveryRecord::setEnded() +{ + ended = true; + //reset msg pointer, don't need to hold on to it anymore + msg.payload = boost::intrusive_ptr<Message>(); + QPID_LOG(debug, "DeliveryRecord::setEnded() id=" << id); + return isRedundant(); +} + +void DeliveryRecord::redeliver(SemanticState* const session) { + if (!ended) { + if(cancelled){ + //if subscription was cancelled, requeue it (waiting for + //final confirmation for AMQP WG on this case) + requeue(); + }else{ + msg.payload->redeliver();//mark as redelivered + session->deliver(*this, false); + } + } +} + +void DeliveryRecord::deliver(framing::FrameHandler& h, DeliveryId deliveryId, uint16_t framesize) +{ + id = deliveryId; + if (msg.payload->getRedelivered()){ + msg.payload->getProperties<framing::DeliveryProperties>()->setRedelivered(true); + } + msg.payload->adjustTtl(); + + framing::AMQFrame method((framing::MessageTransferBody(framing::ProtocolVersion(), tag, acceptExpected ? 0 : 1, acquired ? 0 : 1))); + method.setEof(false); + h.handle(method); + msg.payload->sendHeader(h, framesize); + msg.payload->sendContent(*queue, h, framesize); +} + +void DeliveryRecord::requeue() const +{ + if (acquired && !ended) { + msg.payload->redeliver(); + queue->requeue(msg); + } +} + +void DeliveryRecord::release(bool setRedelivered) +{ + if (acquired && !ended) { + if (setRedelivered) msg.payload->redeliver(); + queue->requeue(msg); + acquired = false; + setEnded(); + } else { + QPID_LOG(debug, "Ignoring release for " << id << " acquired=" << acquired << ", ended =" << ended); + } +} + +void DeliveryRecord::complete() { + completed = true; +} + +bool DeliveryRecord::accept(TransactionContext* ctxt) { + if (acquired && !ended) { + queue->dequeue(ctxt, msg); + setEnded(); + QPID_LOG(debug, "Accepted " << id); + } + return isRedundant(); +} + +void DeliveryRecord::dequeue(TransactionContext* ctxt) const{ + if (acquired && !ended) { + queue->dequeue(ctxt, msg); + } +} + +void DeliveryRecord::committed() const{ + queue->dequeueCommitted(msg); +} + +void DeliveryRecord::reject() +{ + 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(); + } +} + +uint32_t DeliveryRecord::getCredit() const +{ + return credit; +} + +void DeliveryRecord::acquire(DeliveryIds& results) { + if (queue->acquire(msg)) { + acquired = true; + results.push_back(id); + if (!acceptExpected) { + if (ended) { QPID_LOG(error, "Can't dequeue ended message"); } + else { queue->dequeue(0, msg); setEnded(); } + } + } else { + QPID_LOG(info, "Message already acquired " << id.getValue()); + } +} + +void DeliveryRecord::cancel(const std::string& cancelledTag) +{ + if (tag == cancelledTag) + cancelled = true; +} + +AckRange DeliveryRecord::findRange(DeliveryRecords& records, DeliveryId first, DeliveryId last) +{ + DeliveryRecords::iterator start = lower_bound(records.begin(), records.end(), first); + // Find end - position it just after the last record in range + DeliveryRecords::iterator end = lower_bound(records.begin(), records.end(), last); + if (end != records.end() && end->getId() == last) ++end; + return AckRange(start, end); +} + + +namespace qpid { +namespace broker { + +std::ostream& operator<<(std::ostream& out, const DeliveryRecord& r) +{ + out << "{" << "id=" << r.id.getValue(); + out << ", tag=" << r.tag << "}"; + out << ", queue=" << r.queue->getName() << "}"; + return out; +} + + +}} diff --git a/qpid/cpp/src/qpid/broker/DeliveryRecord.h b/qpid/cpp/src/qpid/broker/DeliveryRecord.h new file mode 100644 index 0000000000..d388ba94be --- /dev/null +++ b/qpid/cpp/src/qpid/broker/DeliveryRecord.h @@ -0,0 +1,143 @@ +#ifndef QPID_BROKER_DELIVERYRECORD_H +#define QPID_BROKER_DELIVERYRECORD_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 <algorithm> +#include <deque> +#include <vector> +#include <ostream> +#include "qpid/framing/SequenceSet.h" +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/QueuedMessage.h" +#include "qpid/broker/DeliveryId.h" +#include "qpid/broker/Message.h" + +namespace qpid { +namespace broker { + +class TransactionContext; +class SemanticState; +struct AckRange; + +/** + * Record of a delivery for which an ack is outstanding. + */ +class DeliveryRecord +{ + QueuedMessage msg; + mutable boost::shared_ptr<Queue> queue; + std::string tag; + DeliveryId id; + bool acquired : 1; + bool acceptExpected : 1; + bool cancelled : 1; + bool completed : 1; + bool ended : 1; + bool windowing : 1; + + /** + * Record required credit on construction as the pointer to the + * message may be reset once we no longer need to deliver it + * (e.g. when it is accepted), but we will still need to be able + * to reallocate credit when it is completed (which could happen + * after that). + */ + uint32_t credit; + + public: + QPID_BROKER_EXTERN DeliveryRecord(const QueuedMessage& msg, + const boost::shared_ptr<Queue>& queue, + const std::string& tag, + bool acquired, + bool accepted, + bool windowing, + uint32_t credit=0 // Only used if msg is empty. + ); + + bool coveredBy(const framing::SequenceSet* const range) const { return range->contains(id); } + + void dequeue(TransactionContext* ctxt = 0) const; + void requeue() const; + void release(bool setRedelivered); + void reject(); + void cancel(const std::string& tag); + void redeliver(SemanticState* const); + void acquire(DeliveryIds& results); + void complete(); + bool accept(TransactionContext* ctxt); // Returns isRedundant() + bool setEnded(); // Returns isRedundant() + void committed() const; + + bool isAcquired() const { return acquired; } + bool isComplete() const { return completed; } + bool isRedundant() const { return ended && (!windowing || completed); } + bool isCancelled() const { return cancelled; } + bool isAccepted() const { return !acceptExpected; } + bool isEnded() const { return ended; } + bool isWindowing() const { return windowing; } + + uint32_t getCredit() const; + const std::string& getTag() const { return tag; } + + void deliver(framing::FrameHandler& h, DeliveryId deliveryId, uint16_t framesize); + void setId(DeliveryId _id) { id = _id; } + + typedef std::deque<DeliveryRecord> DeliveryRecords; + static AckRange findRange(DeliveryRecords& records, DeliveryId first, DeliveryId last); + const QueuedMessage& getMessage() const { return msg; } + framing::SequenceNumber getId() const { return id; } + boost::shared_ptr<Queue> getQueue() const { return queue; } + + friend std::ostream& operator<<(std::ostream&, const DeliveryRecord&); +}; + +inline bool operator<(const DeliveryRecord& a, const DeliveryRecord& b) { return a.getId() < b.getId(); } +inline bool operator<(const framing::SequenceNumber& a, const DeliveryRecord& b) { return a < b.getId(); } +inline bool operator<(const DeliveryRecord& a, const framing::SequenceNumber& b) { return a.getId() < b; } + +struct AcquireFunctor +{ + DeliveryIds& results; + + AcquireFunctor(DeliveryIds& _results) : results(_results) {} + + void operator()(DeliveryRecord& record) + { + record.acquire(results); + } +}; + +typedef DeliveryRecord::DeliveryRecords DeliveryRecords; + +struct AckRange +{ + DeliveryRecords::iterator start; + DeliveryRecords::iterator end; + AckRange(DeliveryRecords::iterator _start, DeliveryRecords::iterator _end) : start(_start), end(_end) {} +}; + +} +} + + +#endif /*!QPID_BROKER_DELIVERYRECORD_H*/ diff --git a/qpid/cpp/src/qpid/broker/DirectExchange.cpp b/qpid/cpp/src/qpid/broker/DirectExchange.cpp new file mode 100644 index 0000000000..060f80f60d --- /dev/null +++ b/qpid/cpp/src/qpid/broker/DirectExchange.cpp @@ -0,0 +1,194 @@ +/* + * + * 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/log/Statement.h" +#include "qpid/broker/FedOps.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/DirectExchange.h" +#include <iostream> + +using namespace qpid::broker; +using namespace qpid::framing; +using namespace qpid::sys; +using qpid::management::Manageable; +namespace _qmf = qmf::org::apache::qpid::broker; + +namespace +{ + const std::string qpidExclusiveBinding("qpid.exclusive-binding"); +} + +DirectExchange::DirectExchange(const string& _name, Manageable* _parent, Broker* b) : Exchange(_name, _parent, b) +{ + if (mgmtExchange != 0) + mgmtExchange->set_type(typeName); +} + +DirectExchange::DirectExchange(const string& _name, bool _durable, + const FieldTable& _args, Manageable* _parent, Broker* b) : + Exchange(_name, _durable, _args, _parent, b) +{ + if (mgmtExchange != 0) + mgmtExchange->set_type(typeName); +} + +bool DirectExchange::bind(Queue::shared_ptr queue, const string& routingKey, const FieldTable* args) +{ + string fedOp(fedOpBind); + string fedTags; + string fedOrigin; + bool exclusiveBinding = false; + if (args) { + fedOp = args->getAsString(qpidFedOp); + fedTags = args->getAsString(qpidFedTags); + fedOrigin = args->getAsString(qpidFedOrigin); + exclusiveBinding = args->get(qpidExclusiveBinding); // only direct exchanges take exclusive bindings + } + + bool propagate = false; + + if (args == 0 || fedOp.empty() || fedOp == fedOpBind) { + Mutex::ScopedLock l(lock); + Binding::shared_ptr b(new Binding(routingKey, queue, this, FieldTable(), fedOrigin)); + BoundKey& bk = bindings[routingKey]; + if (exclusiveBinding) bk.queues.clear(); + + QPID_LOG(debug, "Bind key [" << routingKey << "] to queue " << queue->getName() + << " (origin=" << fedOrigin << ")"); + + if (bk.queues.add_unless(b, MatchQueue(queue))) { + b->startManagement(); + propagate = bk.fedBinding.addOrigin(queue->getName(), fedOrigin); + if (mgmtExchange != 0) { + mgmtExchange->inc_bindingCount(); + } + } else { + // queue already present - still need to track fedOrigin + bk.fedBinding.addOrigin(queue->getName(), fedOrigin); + return false; + } + } else if (fedOp == fedOpUnbind) { + Mutex::ScopedLock l(lock); + BoundKey& bk = bindings[routingKey]; + + QPID_LOG(debug, "Bind - fedOpUnbind key [" << routingKey << "] queue " << queue->getName() + << " (origin=" << fedOrigin << ")" << " (count=" << bk.fedBinding.count() << ")"); + + propagate = bk.fedBinding.delOrigin(queue->getName(), fedOrigin); + if (bk.fedBinding.countFedBindings(queue->getName()) == 0) + unbind(queue, routingKey, args); + + } 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 + * released + */ + std::vector<std::string> keys2prop; + { + Mutex::ScopedLock l(lock); + for (Bindings::iterator iter = bindings.begin(); + iter != bindings.end(); iter++) { + const BoundKey& bk = iter->second; + if (bk.fedBinding.hasLocal()) { + keys2prop.push_back(iter->first); + } + } + } /* lock dropped */ + for (std::vector<std::string>::const_iterator key = keys2prop.begin(); + key != keys2prop.end(); key++) { + propagateFedOp( *key, string(), fedOpBind, string()); + } + } + + routeIVE(); + if (propagate) + propagateFedOp(routingKey, fedTags, fedOp, fedOrigin); + return true; +} + +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, "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(queue->getName(), fedOrigin); + if (mgmtExchange != 0) { + mgmtExchange->dec_bindingCount(); + } + } else { + return false; + } + } + + // If I delete my local binding, propagate this unbind to any upstream brokers + if (propagate) + propagateFedOp(routingKey, string(), fedOpUnbind, string()); + return true; +} + +void DirectExchange::route(Deliverable& msg, const string& routingKey, const FieldTable* /*args*/) +{ + PreRoute pr(msg, this); + ConstBindingList b; + { + Mutex::ScopedLock l(lock); + b = bindings[routingKey].queues.snapshot(); + } + doRoute(msg, b); +} + + +bool DirectExchange::isBound(Queue::shared_ptr queue, const string* const routingKey, const FieldTable* const) +{ + Mutex::ScopedLock l(lock); + if (routingKey) { + Bindings::iterator i = bindings.find(*routingKey); + + if (i == bindings.end()) + return false; + if (!queue) + return true; + + Queues::ConstPtr p = i->second.queues.snapshot(); + return p && std::find_if(p->begin(), p->end(), MatchQueue(queue)) != p->end(); + } else if (!queue) { + //if no queue or routing key is specified, just report whether any bindings exist + return bindings.size() > 0; + } else { + for (Bindings::iterator i = bindings.begin(); i != bindings.end(); i++) { + Queues::ConstPtr p = i->second.queues.snapshot(); + if (p && std::find_if(p->begin(), p->end(), MatchQueue(queue)) != p->end()) return true; + } + return false; + } + + return false; +} + +DirectExchange::~DirectExchange() {} + +const std::string DirectExchange::typeName("direct"); diff --git a/qpid/cpp/src/qpid/broker/DirectExchange.h b/qpid/cpp/src/qpid/broker/DirectExchange.h new file mode 100644 index 0000000000..a6f9cf91af --- /dev/null +++ b/qpid/cpp/src/qpid/broker/DirectExchange.h @@ -0,0 +1,74 @@ +/* + * + * 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 _DirectExchange_ +#define _DirectExchange_ + +#include <map> +#include <vector> +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/Exchange.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/sys/CopyOnWriteArray.h" +#include "qpid/sys/Mutex.h" + +namespace qpid { +namespace broker { +class DirectExchange : public virtual Exchange { + typedef qpid::sys::CopyOnWriteArray<Binding::shared_ptr> Queues; + struct BoundKey { + Queues queues; + FedBinding fedBinding; + }; + typedef std::map<std::string, BoundKey> Bindings; + Bindings bindings; + qpid::sys::Mutex lock; + +public: + static const std::string typeName; + + QPID_BROKER_EXTERN DirectExchange(const std::string& name, + management::Manageable* parent = 0, Broker* broker = 0); + QPID_BROKER_EXTERN DirectExchange(const std::string& _name, + bool _durable, + const qpid::framing::FieldTable& _args, + management::Manageable* parent = 0, Broker* broker = 0); + + virtual std::string getType() const { return typeName; } + + QPID_BROKER_EXTERN virtual bool bind(boost::shared_ptr<Queue> queue, + const std::string& routingKey, + const qpid::framing::FieldTable* args); + virtual bool unbind(boost::shared_ptr<Queue> queue, const std::string& routingKey, const qpid::framing::FieldTable* args); + QPID_BROKER_EXTERN virtual void route(Deliverable& msg, + const std::string& routingKey, + const qpid::framing::FieldTable* args); + QPID_BROKER_EXTERN virtual bool isBound(boost::shared_ptr<Queue> queue, + const std::string* const routingKey, + const qpid::framing::FieldTable* const args); + + QPID_BROKER_EXTERN virtual ~DirectExchange(); + + virtual bool supportsDynamicBinding() { return true; } +}; + +}} + +#endif diff --git a/qpid/cpp/src/qpid/broker/DtxAck.cpp b/qpid/cpp/src/qpid/broker/DtxAck.cpp new file mode 100644 index 0000000000..bca3f90bbe --- /dev/null +++ b/qpid/cpp/src/qpid/broker/DtxAck.cpp @@ -0,0 +1,73 @@ +/* + * + * 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/DtxAck.h" +#include "qpid/log/Statement.h" + +using std::bind1st; +using std::bind2nd; +using std::mem_fun_ref; +using namespace qpid::broker; + +DtxAck::DtxAck(const qpid::framing::SequenceSet& acked, DeliveryRecords& unacked) +{ + remove_copy_if(unacked.begin(), unacked.end(), inserter(pending, pending.end()), + not1(bind2nd(mem_fun_ref(&DeliveryRecord::coveredBy), &acked))); +} + +bool DtxAck::prepare(TransactionContext* ctxt) throw() +{ + try{ + //record dequeue in the store + for (DeliveryRecords::iterator i = pending.begin(); i != pending.end(); i++) { + i->dequeue(ctxt); + } + return true; + }catch(...){ + QPID_LOG(error, "Failed to prepare"); + return false; + } +} + +void DtxAck::commit() throw() +{ + try { + for_each(pending.begin(), pending.end(), mem_fun_ref(&DeliveryRecord::committed)); + pending.clear(); + } catch (const std::exception& e) { + QPID_LOG(error, "Failed to commit: " << e.what()); + } catch(...) { + QPID_LOG(error, "Failed to commit (unknown error)"); + } + +} + +void DtxAck::rollback() throw() +{ + try { + for_each(pending.begin(), pending.end(), mem_fun_ref(&DeliveryRecord::requeue)); + pending.clear(); + } catch (const std::exception& e) { + QPID_LOG(error, "Failed to complete rollback: " << e.what()); + } catch(...) { + QPID_LOG(error, "Failed to complete rollback (unknown error)"); + } + +} diff --git a/qpid/cpp/src/qpid/broker/DtxAck.h b/qpid/cpp/src/qpid/broker/DtxAck.h new file mode 100644 index 0000000000..166147e58d --- /dev/null +++ b/qpid/cpp/src/qpid/broker/DtxAck.h @@ -0,0 +1,48 @@ +/* + * + * 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 _DtxAck_ +#define _DtxAck_ + +#include <algorithm> +#include <functional> +#include <list> +#include "qpid/framing/SequenceSet.h" +#include "qpid/broker/DeliveryRecord.h" +#include "qpid/broker/TxOp.h" + +namespace qpid { + 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); } + }; + } +} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/DtxBuffer.cpp b/qpid/cpp/src/qpid/broker/DtxBuffer.cpp new file mode 100644 index 0000000000..f1b8169cf7 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/DtxBuffer.cpp @@ -0,0 +1,83 @@ +/* + * + * 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/DtxBuffer.h" + +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() {} + +void DtxBuffer::markEnded() +{ + Mutex::ScopedLock locker(lock); + ended = true; +} + +bool DtxBuffer::isEnded() +{ + Mutex::ScopedLock locker(lock); + return ended; +} + +void DtxBuffer::setSuspended(bool isSuspended) +{ + suspended = isSuspended; +} + +bool DtxBuffer::isSuspended() +{ + return suspended; +} + +void DtxBuffer::fail() +{ + Mutex::ScopedLock locker(lock); + rollback(); + failed = true; + ended = true; +} + +bool DtxBuffer::isRollbackOnly() +{ + Mutex::ScopedLock locker(lock); + return failed; +} + +const std::string& DtxBuffer::getXid() +{ + return xid; +} + +void DtxBuffer::timedout() +{ + Mutex::ScopedLock locker(lock); + expired = true; + fail(); +} + +bool DtxBuffer::isExpired() +{ + Mutex::ScopedLock locker(lock); + return expired; +} diff --git a/qpid/cpp/src/qpid/broker/DtxBuffer.h b/qpid/cpp/src/qpid/broker/DtxBuffer.h new file mode 100644 index 0000000000..1511cb032f --- /dev/null +++ b/qpid/cpp/src/qpid/broker/DtxBuffer.h @@ -0,0 +1,57 @@ +/* + * + * 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 _DtxBuffer_ +#define _DtxBuffer_ + +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/TxBuffer.h" +#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; + + 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(); + }; + } +} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/DtxManager.cpp b/qpid/cpp/src/qpid/broker/DtxManager.cpp new file mode 100644 index 0000000000..3caa41c3f4 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/DtxManager.cpp @@ -0,0 +1,171 @@ +/* + * + * 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/DtxManager.h" +#include "qpid/broker/DtxTimeout.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Timer.h" +#include "qpid/ptr_map.h" + +#include <boost/format.hpp> +#include <iostream> + +using boost::intrusive_ptr; +using qpid::sys::Mutex; +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() {} + +void DtxManager::start(const std::string& xid, DtxBuffer::shared_ptr ops) +{ + createWork(xid)->add(ops); +} + +void DtxManager::join(const std::string& xid, DtxBuffer::shared_ptr ops) +{ + getWork(xid)->add(ops); +} + +void DtxManager::recover(const std::string& xid, std::auto_ptr<TPCTransactionContext> txn, DtxBuffer::shared_ptr ops) +{ + createWork(xid)->recover(txn, ops); +} + +bool DtxManager::prepare(const std::string& xid) +{ + QPID_LOG(debug, "preparing: " << xid); + try { + return getWork(xid)->prepare(); + } catch (DtxTimeoutException& e) { + remove(xid); + throw e; + } +} + +bool DtxManager::commit(const std::string& xid, bool onePhase) +{ + QPID_LOG(debug, "committing: " << xid); + try { + bool result = getWork(xid)->commit(onePhase); + remove(xid); + return result; + } catch (DtxTimeoutException& e) { + remove(xid); + throw e; + } +} + +void DtxManager::rollback(const std::string& xid) +{ + QPID_LOG(debug, "rolling back: " << xid); + try { + getWork(xid)->rollback(); + remove(xid); + } catch (DtxTimeoutException& e) { + remove(xid); + throw e; + } +} + +DtxWorkRecord* DtxManager::getWork(const std::string& xid) +{ + Mutex::ScopedLock locker(lock); + WorkMap::iterator i = work.find(xid); + if (i == work.end()) { + throw NotFoundException(QPID_MSG("Unrecognised xid " << xid)); + } + return ptr_map_ptr(i); +} + +void DtxManager::remove(const std::string& xid) +{ + Mutex::ScopedLock locker(lock); + WorkMap::iterator i = work.find(xid); + if (i == work.end()) { + throw NotFoundException(QPID_MSG("Unrecognised xid " << xid)); + } else { + work.erase(i); + } +} + +DtxWorkRecord* DtxManager::createWork(std::string xid) +{ + 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); + } +} + +void DtxManager::setTimeout(const std::string& xid, uint32_t secs) +{ + DtxWorkRecord* record = getWork(xid); + intrusive_ptr<DtxTimeout> timeout = record->getTimeout(); + if (timeout.get()) { + if (timeout->timeout == secs) return;//no need to do anything further if timeout hasn't changed + timeout->cancel(); + } + timeout = intrusive_ptr<DtxTimeout>(new DtxTimeout(secs, *this, xid)); + record->setTimeout(timeout); + timer.add(timeout); +} + +uint32_t DtxManager::getTimeout(const std::string& xid) +{ + intrusive_ptr<DtxTimeout> timeout = getWork(xid)->getTimeout(); + return !timeout ? 0 : timeout->timeout; +} + +void DtxManager::timedout(const std::string& xid) +{ + Mutex::ScopedLock locker(lock); + WorkMap::iterator i = work.find(xid); + if (i == work.end()) { + QPID_LOG(warning, "Transaction timeout failed: no record for xid"); + } else { + ptr_map_ptr(i)->timedout(); + //TODO: do we want to have a timed task to cleanup, or can we rely on an explicit completion? + //timer.add(intrusive_ptr<TimerTask>(new DtxCleanup(60*30/*30 mins*/, *this, 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() +{ + try { + mgr.remove(xid); + } catch (ConnectionException& /*e*/) { + //assume it was explicitly cleaned up after a call to prepare, commit or rollback + } +} + +void DtxManager::setStore (TransactionalStore* _store) +{ + store = _store; +} diff --git a/qpid/cpp/src/qpid/broker/DtxManager.h b/qpid/cpp/src/qpid/broker/DtxManager.h new file mode 100644 index 0000000000..680b62eeb2 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/DtxManager.h @@ -0,0 +1,74 @@ +/* + * + * 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 _DtxManager_ +#define _DtxManager_ + +#include <boost/ptr_container/ptr_map.hpp> +#include "qpid/broker/DtxBuffer.h" +#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" + +namespace qpid { +namespace broker { + +class DtxManager{ + typedef boost::ptr_map<std::string, DtxWorkRecord> WorkMap; + + struct DtxCleanup : public sys::TimerTask + { + 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; + + void remove(const std::string& xid); + DtxWorkRecord* getWork(const std::string& xid); + DtxWorkRecord* createWork(std::string xid); + +public: + DtxManager(qpid::sys::Timer&); + ~DtxManager(); + void start(const std::string& xid, DtxBuffer::shared_ptr work); + void join(const std::string& xid, DtxBuffer::shared_ptr work); + void recover(const std::string& xid, std::auto_ptr<TPCTransactionContext> txn, DtxBuffer::shared_ptr work); + bool prepare(const std::string& xid); + bool commit(const std::string& xid, bool onePhase); + void rollback(const std::string& xid); + void setTimeout(const std::string& xid, uint32_t secs); + uint32_t getTimeout(const std::string& xid); + void timedout(const std::string& xid); + void setStore(TransactionalStore* store); +}; + +} +} + +#endif diff --git a/qpid/cpp/src/qpid/broker/DtxTimeout.cpp b/qpid/cpp/src/qpid/broker/DtxTimeout.cpp new file mode 100644 index 0000000000..c4c52ec40a --- /dev/null +++ b/qpid/cpp/src/qpid/broker/DtxTimeout.cpp @@ -0,0 +1,35 @@ +/* + * + * 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/DtxTimeout.h" +#include "qpid/broker/DtxManager.h" +#include "qpid/sys/Time.h" + +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) +{ +} + +void DtxTimeout::fire() +{ + mgr.timedout(xid); +} diff --git a/qpid/cpp/src/qpid/broker/DtxTimeout.h b/qpid/cpp/src/qpid/broker/DtxTimeout.h new file mode 100644 index 0000000000..680a210e4f --- /dev/null +++ b/qpid/cpp/src/qpid/broker/DtxTimeout.h @@ -0,0 +1,48 @@ +/* + * + * 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 _DtxTimeout_ +#define _DtxTimeout_ + +#include "qpid/Exception.h" +#include "qpid/sys/Timer.h" + +namespace qpid { +namespace broker { + +class DtxManager; + +struct DtxTimeoutException : public Exception {}; + +struct DtxTimeout : public sys::TimerTask +{ + const uint32_t timeout; + DtxManager& mgr; + const std::string xid; + + DtxTimeout(uint32_t timeout, DtxManager& mgr, const std::string& xid); + void fire(); +}; + +} +} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/DtxWorkRecord.cpp b/qpid/cpp/src/qpid/broker/DtxWorkRecord.cpp new file mode 100644 index 0000000000..9f33e698db --- /dev/null +++ b/qpid/cpp/src/qpid/broker/DtxWorkRecord.cpp @@ -0,0 +1,177 @@ +/* + * + * 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/DtxWorkRecord.h" +#include "qpid/framing/reply_exceptions.h" +#include <boost/format.hpp> +#include <boost/mem_fn.hpp> +using boost::mem_fn; +using qpid::sys::Mutex; + +using namespace qpid::broker; +using namespace qpid::framing; + +DtxWorkRecord::DtxWorkRecord(const std::string& _xid, TransactionalStore* const _store) : + xid(_xid), store(_store), completed(false), rolledback(false), prepared(false), expired(false) {} + +DtxWorkRecord::~DtxWorkRecord() +{ + if (timeout.get()) { + timeout->cancel(); + } +} + +bool DtxWorkRecord::prepare() +{ + Mutex::ScopedLock locker(lock); + if (check()) { + txn = store->begin(xid); + if (prepare(txn.get())) { + store->prepare(*txn); + prepared = true; + } else { + abort(); + //TODO: this should probably be flagged as internal error + } + } else { + //some part of the work has been marked rollback only + abort(); + } + return prepared; +} + +bool DtxWorkRecord::prepare(TransactionContext* _txn) +{ + bool succeeded(true); + for (Work::iterator i = work.begin(); succeeded && i != work.end(); i++) { + succeeded = (*i)->prepare(_txn); + } + return succeeded; +} + +bool DtxWorkRecord::commit(bool onePhase) +{ + Mutex::ScopedLock locker(lock); + if (check()) { + if (prepared) { + //already prepared i.e. 2pc + if (onePhase) { + throw IllegalStateException(QPID_MSG("Branch with xid " << xid << " has been prepared, one-phase option not valid!")); + } + + 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!")); + } + std::auto_ptr<TransactionContext> localtxn = store->begin(); + if (prepare(localtxn.get())) { + store->commit(*localtxn); + std::for_each(work.begin(), work.end(), mem_fn(&TxBuffer::commit)); + return true; + } else { + store->abort(*localtxn); + abort(); + //TODO: this should probably be flagged as internal error + return false; + } + } + } else { + //some part of the work has been marked rollback only + abort(); + return false; + } +} + +void DtxWorkRecord::rollback() +{ + Mutex::ScopedLock locker(lock); + check(); + abort(); +} + +void DtxWorkRecord::add(DtxBuffer::shared_ptr ops) +{ + Mutex::ScopedLock locker(lock); + if (expired) { + throw DtxTimeoutException(); + } + if (completed) { + throw CommandInvalidException(QPID_MSG("Branch with xid " << xid << " has been completed!")); + } + work.push_back(ops); +} + +bool DtxWorkRecord::check() +{ + if (expired) { + throw DtxTimeoutException(); + } + if (!completed) { + //iterate through all DtxBuffers and ensure they are all ended + for (Work::iterator i = work.begin(); i != work.end(); i++) { + if (!(*i)->isEnded()) { + throw IllegalStateException(QPID_MSG("Branch with xid " << xid << " not completed!")); + } else if ((*i)->isRollbackOnly()) { + rolledback = true; + } + } + completed = true; + } + return !rolledback; +} + +void DtxWorkRecord::abort() +{ + if (txn.get()) { + store->abort(*txn); + txn.reset(); + } + std::for_each(work.begin(), work.end(), mem_fn(&TxBuffer::rollback)); +} + +void DtxWorkRecord::recover(std::auto_ptr<TPCTransactionContext> _txn, DtxBuffer::shared_ptr ops) +{ + add(ops); + txn = _txn; + ops->markEnded(); + completed = true; + prepared = true; +} + +void DtxWorkRecord::timedout() +{ + Mutex::ScopedLock locker(lock); + expired = true; + rolledback = true; + if (!completed) { + for (Work::iterator i = work.begin(); i != work.end(); i++) { + if (!(*i)->isEnded()) { + (*i)->timedout(); + } + } + } + abort(); +} diff --git a/qpid/cpp/src/qpid/broker/DtxWorkRecord.h b/qpid/cpp/src/qpid/broker/DtxWorkRecord.h new file mode 100644 index 0000000000..aec2d2aed4 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/DtxWorkRecord.h @@ -0,0 +1,81 @@ +/* + * + * 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 _DtxWorkRecord_ +#define _DtxWorkRecord_ + +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/DtxBuffer.h" +#include "qpid/broker/DtxTimeout.h" +#include "qpid/broker/TransactionalStore.h" + +#include "qpid/framing/amqp_types.h" +#include "qpid/sys/Mutex.h" + +#include <algorithm> +#include <functional> +#include <vector> + +#include <boost/intrusive_ptr.hpp> + +namespace qpid { +namespace broker { + +/** + * Represents the work done under a particular distributed transaction + * across potentially multiple channels. Identified by a xid. Allows + * that work to be prepared, committed and rolled-back. + */ +class DtxWorkRecord +{ + typedef std::vector<DtxBuffer::shared_ptr> Work; + + const std::string xid; + TransactionalStore* const store; + bool completed; + bool rolledback; + bool prepared; + bool expired; + boost::intrusive_ptr<DtxTimeout> timeout; + Work work; + std::auto_ptr<TPCTransactionContext> txn; + qpid::sys::Mutex lock; + + bool check(); + void abort(); + bool prepare(TransactionContext* txn); +public: + QPID_BROKER_EXTERN DtxWorkRecord(const std::string& xid, + TransactionalStore* const store); + QPID_BROKER_EXTERN ~DtxWorkRecord(); + QPID_BROKER_EXTERN bool prepare(); + QPID_BROKER_EXTERN bool commit(bool onePhase); + QPID_BROKER_EXTERN void rollback(); + QPID_BROKER_EXTERN void add(DtxBuffer::shared_ptr ops); + void recover(std::auto_ptr<TPCTransactionContext> txn, DtxBuffer::shared_ptr ops); + void timedout(); + void setTimeout(boost::intrusive_ptr<DtxTimeout> t) { timeout = t; } + boost::intrusive_ptr<DtxTimeout> getTimeout() { return timeout; } +}; + +} +} + +#endif diff --git a/qpid/cpp/src/qpid/broker/Exchange.cpp b/qpid/cpp/src/qpid/broker/Exchange.cpp new file mode 100644 index 0000000000..622cc81002 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/Exchange.cpp @@ -0,0 +1,403 @@ +/* + * + * 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/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/Queue.h" +#include "qpid/framing/MessageProperties.h" +#include "qpid/framing/reply_exceptions.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; +using qpid::framing::Buffer; +using qpid::framing::FieldTable; +using qpid::sys::Mutex; +using qpid::management::ManagementAgent; +using qpid::management::ManagementObject; +using qpid::management::Manageable; +using qpid::management::Args; +namespace _qmf = qmf::org::apache::qpid::broker; + +namespace +{ + const std::string qpidMsgSequence("qpid.msg_sequence"); + const std::string qpidSequenceCounter("qpid.sequence_counter"); + const std::string qpidIVE("qpid.ive"); + const std::string QPID_MANAGEMENT("qpid.management"); +} + + +Exchange::PreRoute::PreRoute(Deliverable& msg, Exchange* _p):parent(_p) { + if (parent){ + if (parent->sequence || parent->ive) parent->sequenceLock.lock(); + + if (parent->sequence){ + parent->sequenceNo++; + msg.getMessage().getProperties<MessageProperties>()->getApplicationHeaders().setInt64(qpidMsgSequence,parent->sequenceNo); + } + if (parent->ive) { + parent->lastMsg = &( msg.getMessage()); + } + } +} + +Exchange::PreRoute::~PreRoute(){ + if (parent && (parent->sequence || parent->ive)){ + parent->sequenceLock.unlock(); + } +} + +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; + + if (b.get()) { + // Block the content release if the message is transient AND there is more than one binding + if (!msg.getMessage().isPersistent() && b->size() > 1) { + 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++) { + 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) + { + mgmtExchange->inc_msgReceives (); + mgmtExchange->inc_byteReceives (msg.contentSize ()); + if (count == 0) + { + //QPID_LOG(warning, "Exchange " << getName() << " could not route message; no matching binding found"); + mgmtExchange->inc_msgDrops (); + mgmtExchange->inc_byteDrops (msg.contentSize ()); + } + else + { + mgmtExchange->inc_msgRoutes (count); + mgmtExchange->inc_byteRoutes (count * msg.contentSize ()); + } + } +} + +void Exchange::routeIVE(){ + if (ive && lastMsg.get()){ + DeliverableMessage dmsg(lastMsg); + route(dmsg, lastMsg->getRoutingKey(), lastMsg->getApplicationHeaders()); + } +} + + +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), destroyed(false) +{ + if (parent != 0 && broker != 0) + { + ManagementAgent* agent = broker->getManagementAgent(); + if (agent != 0) + { + mgmtExchange = new _qmf::Exchange (agent, this, parent, _name); + mgmtExchange->set_durable(durable); + mgmtExchange->set_autoDelete(false); + agent->addObject(mgmtExchange, 0, durable); + } + } +} + +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), destroyed(false) +{ + if (parent != 0 && broker != 0) + { + ManagementAgent* agent = broker->getManagementAgent(); + if (agent != 0) + { + mgmtExchange = new _qmf::Exchange (agent, this, parent, _name); + mgmtExchange->set_durable(durable); + mgmtExchange->set_autoDelete(false); + mgmtExchange->set_arguments(ManagementAgent::toMap(args)); + agent->addObject(mgmtExchange, 0, durable); + } + } + + sequence = _args.get(qpidMsgSequence); + if (sequence) { + QPID_LOG(debug, "Configured exchange " << _name << " with Msg sequencing"); + args.setInt64(std::string(qpidSequenceCounter), sequenceNo); + } + + ive = _args.get(qpidIVE); + 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 () +{ + if (mgmtExchange != 0) + mgmtExchange->resourceDestroy (); +} + +void Exchange::setAlternate(Exchange::shared_ptr _alternate) +{ + alternate = _alternate; + if (mgmtExchange != 0) { + if (alternate.get() != 0) + mgmtExchange->set_altExchange(alternate->GetManagementObject()->getObjectId()); + else + mgmtExchange->clr_altExchange(); + } +} + +void Exchange::setPersistenceId(uint64_t id) const +{ + persistenceId = id; +} + +Exchange::shared_ptr Exchange::decode(ExchangeRegistry& exchanges, Buffer& buffer) +{ + string name; + string type; + string altName; + FieldTable args; + + buffer.getShortString(name); + bool durable(buffer.getOctet()); + buffer.getShortString(type); + buffer.get(args); + // For backwards compatibility on restoring exchanges from before the alt-exchange update, perform check + if (buffer.available()) + buffer.getShortString(altName); + + try { + Exchange::shared_ptr exch = exchanges.declare(name, type, durable, args).first; + exch->sequenceNo = args.getAsInt64(qpidSequenceCounter); + exch->alternateName.assign(altName); + return exch; + } catch (const UnknownExchangeTypeException&) { + QPID_LOG(warning, "Could not create exchange " << name << "; type " << type << " is not recognised"); + return Exchange::shared_ptr(); + } +} + +void Exchange::encode(Buffer& buffer) const +{ + buffer.putShortString(name); + buffer.putOctet(durable); + buffer.putShortString(getType()); + if (args.isSet(qpidSequenceCounter)) + args.setInt64(std::string(qpidSequenceCounter),sequenceNo); + buffer.put(args); + buffer.putShortString(alternate.get() ? alternate->getName() : string("")); +} + +uint32_t Exchange::encodedSize() const +{ + return name.size() + 1/*short string size*/ + + 1 /*durable*/ + + getType().size() + 1/*short string size*/ + + (alternate.get() ? alternate->getName().size() : 0) + 1/*short string size*/ + + args.encodedSize(); +} + +void Exchange::recoveryComplete(ExchangeRegistry& exchanges) +{ + if (!alternateName.empty()) { + try { + Exchange::shared_ptr ae = exchanges.get(alternateName); + setAlternate(ae); + } catch (const NotFoundException&) { + QPID_LOG(warning, "Could not set alternate exchange \"" << alternateName << "\": does not exist."); + } + } +} + +ManagementObject* Exchange::GetManagementObject (void) const +{ + return (ManagementObject*) mgmtExchange; +} + +void Exchange::registerDynamicBridge(DynamicBridge* db) +{ + if (!supportsDynamicBinding()) + throw Exception("Exchange type does not support dynamic binding"); + + { + Mutex::ScopedLock l(bridgeLock); + for (std::vector<DynamicBridge*>::iterator iter = bridgeVector.begin(); + iter != bridgeVector.end(); iter++) + (*iter)->sendReorigin(); + + bridgeVector.push_back(db); + } + + FieldTable args; + args.setString(qpidFedOp, fedOpReorigin); + bind(Queue::shared_ptr(), string(), &args); +} + +void Exchange::removeDynamicBridge(DynamicBridge* db) +{ + Mutex::ScopedLock l(bridgeLock); + for (std::vector<DynamicBridge*>::iterator iter = bridgeVector.begin(); + iter != bridgeVector.end(); iter++) + if (*iter == db) { + bridgeVector.erase(iter); + break; + } +} + +void Exchange::handleHelloRequest() +{ +} + +void Exchange::propagateFedOp(const string& routingKey, const string& tags, const string& op, const string& origin, qpid::framing::FieldTable* extra_args) +{ + Mutex::ScopedLock l(bridgeLock); + string myOp(op.empty() ? fedOpBind : op); + + for (std::vector<DynamicBridge*>::iterator iter = bridgeVector.begin(); + iter != bridgeVector.end(); iter++) + (*iter)->propagateBinding(routingKey, tags, op, origin, extra_args); +} + +Exchange::Binding::Binding(const string& _key, Queue::shared_ptr _queue, Exchange* _parent, + FieldTable _args, const string& _origin) + : parent(_parent), queue(_queue), key(_key), args(_args), origin(_origin), mgmtBinding(0) +{ +} + +Exchange::Binding::~Binding () +{ + if (mgmtBinding != 0) { + ManagementObject* mo = queue->GetManagementObject(); + if (mo != 0) + static_cast<_qmf::Queue*>(mo)->dec_bindingCount(); + mgmtBinding->resourceDestroy (); + } +} + +void Exchange::Binding::startManagement() +{ + if (parent != 0) + { + Broker* broker = parent->getBroker(); + if (broker != 0) { + ManagementAgent* agent = broker->getManagementAgent(); + if (agent != 0) { + ManagementObject* mo = queue->GetManagementObject(); + if (mo != 0) { + management::ObjectId queueId = mo->getObjectId(); + + mgmtBinding = new _qmf::Binding + (agent, this, (Manageable*) parent, queueId, key, ManagementAgent::toMap(args)); + if (!origin.empty()) + mgmtBinding->set_origin(origin); + agent->addObject(mgmtBinding); + static_cast<_qmf::Queue*>(mo)->inc_bindingCount(); + } + } + } + } +} + +ManagementObject* Exchange::Binding::GetManagementObject () const +{ + return (ManagementObject*) mgmtBinding; +} + +Exchange::MatchQueue::MatchQueue(Queue::shared_ptr q) : queue(q) {} + +bool Exchange::MatchQueue::operator()(Exchange::Binding::shared_ptr b) +{ + return b->queue == queue; +} + +void Exchange::setProperties(const boost::intrusive_ptr<Message>& msg) { + msg->getProperties<DeliveryProperties>()->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/qpid/cpp/src/qpid/broker/Exchange.h b/qpid/cpp/src/qpid/broker/Exchange.h new file mode 100644 index 0000000000..b12af9a1dd --- /dev/null +++ b/qpid/cpp/src/qpid/broker/Exchange.h @@ -0,0 +1,248 @@ +#ifndef _broker_Exchange_h +#define _broker_Exchange_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 <boost/shared_ptr.hpp> +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/Deliverable.h" +#include "qpid/broker/MessageStore.h" +#include "qpid/broker/PersistableExchange.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/sys/Mutex.h" +#include "qpid/management/Manageable.h" +#include "qmf/org/apache/qpid/broker/Exchange.h" +#include "qmf/org/apache/qpid/broker/Binding.h" + +namespace qpid { +namespace broker { + +class Broker; +class ExchangeRegistry; + +class QPID_BROKER_CLASS_EXTERN Exchange : public PersistableExchange, public management::Manageable { +public: + struct Binding : public management::Manageable { + typedef boost::shared_ptr<Binding> shared_ptr; + typedef std::vector<Binding::shared_ptr> vector; + + Exchange* parent; + boost::shared_ptr<Queue> queue; + const std::string key; + const framing::FieldTable args; + std::string origin; + qmf::org::apache::qpid::broker::Binding* mgmtBinding; + + Binding(const std::string& key, boost::shared_ptr<Queue> queue, Exchange* parent = 0, + framing::FieldTable args = framing::FieldTable(), const std::string& origin = std::string()); + ~Binding(); + void startManagement(); + management::ManagementObject* GetManagementObject() const; + }; + +private: + const std::string name; + const bool durable; + std::string alternateName; + boost::shared_ptr<Exchange> alternate; + uint32_t alternateUsers; + mutable uint64_t persistenceId; + +protected: + mutable qpid::framing::FieldTable args; + bool sequence; + mutable qpid::sys::Mutex sequenceLock; + int64_t sequenceNo; + bool ive; + boost::intrusive_ptr<Message> lastMsg; + + class PreRoute{ + public: + PreRoute(Deliverable& msg, Exchange* _p); + ~PreRoute(); + 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; + MatchQueue(boost::shared_ptr<Queue> q); + bool operator()(Exchange::Binding::shared_ptr b); + }; + + /** A FedBinding keeps track of information that Federation needs + to know when to propagate changes. + + Dynamic federation needs to know which exchanges have at least + one local binding. The bindings on these exchanges need to be + propagated. + + Federated binds and unbinds need to know which federation + origins are associated with the bindings for each queue. When + origins are added or deleted, the corresponding bindings need + to be propagated. + + fedBindings[queueName] contains the origins associated with + the given queue. + */ + + class FedBinding { + uint32_t localBindings; + + typedef std::set<std::string> originSet; + std::map<std::string, originSet> fedBindings; + + public: + FedBinding() : localBindings(0) {} + bool hasLocal() const { return localBindings != 0; } + + /** Returns true if propagation is needed. */ + bool addOrigin(const std::string& queueName, const std::string& origin) { + if (origin.empty()) { + localBindings++; + return localBindings == 1; + } + fedBindings[queueName].insert(origin); + return true; + } + + /** Returns true if propagation is needed. */ + bool delOrigin(const std::string& queueName, const std::string& origin){ + 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() { + return localBindings + fedBindings.size(); + } + + uint32_t countFedBindings(const std::string& queueName) { + // 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; + } + }; + + qmf::org::apache::qpid::broker::Exchange* mgmtExchange; + +public: + typedef boost::shared_ptr<Exchange> shared_ptr; + + QPID_BROKER_EXTERN explicit Exchange(const std::string& name, management::Manageable* parent = 0, + 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_INLINE_EXTERN virtual ~Exchange(); + + const std::string& getName() const { return name; } + bool isDurable() { return durable; } + qpid::framing::FieldTable& getArgs() { return args; } + + Exchange::shared_ptr getAlternate() { return alternate; } + void setAlternate(Exchange::shared_ptr _alternate); + void incAlternateUsers() { alternateUsers++; } + void decAlternateUsers() { alternateUsers--; } + bool inUseAsAlternate() { return alternateUsers > 0; } + + virtual std::string getType() const = 0; + + /** + * bind() is used for two distinct purposes: + * + * 1. To create a binding, in the conventional sense + * + * 2. As a vehicle for any FedOp, currently including federated + * binding, federated unbinding, federated reorigin. + * + */ + + virtual bool bind(boost::shared_ptr<Queue> queue, const std::string& routingKey, const qpid::framing::FieldTable* args) = 0; + virtual bool unbind(boost::shared_ptr<Queue> queue, const std::string& routingKey, const qpid::framing::FieldTable* args) = 0; + 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; } + QPID_BROKER_EXTERN uint32_t encodedSize() const; + QPID_BROKER_EXTERN virtual void encode(framing::Buffer& buffer) const; + + static QPID_BROKER_EXTERN Exchange::shared_ptr decode(ExchangeRegistry& exchanges, framing::Buffer& buffer); + + // Manageable entry points + QPID_BROKER_EXTERN management::ManagementObject* GetManagementObject(void) const; + + // Federation hooks + class DynamicBridge { + public: + virtual ~DynamicBridge() {} + virtual void propagateBinding(const std::string& key, const std::string& tagList, const std::string& op, const std::string& origin, qpid::framing::FieldTable* extra_args=0) = 0; + virtual void sendReorigin() = 0; + virtual bool containsLocalTag(const std::string& tagList) const = 0; + virtual const std::string& getLocalTag() const = 0; + }; + + void registerDynamicBridge(DynamicBridge* db); + void removeDynamicBridge(DynamicBridge* db); + virtual bool supportsDynamicBinding() { return false; } + Broker* getBroker() const { return broker; } + /** + * Notify exchange that recovery has completed. + */ + 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, + qpid::framing::FieldTable* extra_args=0); +}; + +}} + +#endif /*!_broker_Exchange.cpp_h*/ diff --git a/qpid/cpp/src/qpid/broker/ExchangeRegistry.cpp b/qpid/cpp/src/qpid/broker/ExchangeRegistry.cpp new file mode 100644 index 0000000000..1c8d26c4f7 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/ExchangeRegistry.cpp @@ -0,0 +1,116 @@ +/* + * + * 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/ExchangeRegistry.h" +#include "qpid/broker/DirectExchange.h" +#include "qpid/broker/FanOutExchange.h" +#include "qpid/broker/HeadersExchange.h" +#include "qpid/broker/TopicExchange.h" +#include "qpid/management/ManagementDirectExchange.h" +#include "qpid/management/ManagementTopicExchange.h" +#include "qpid/framing/reply_exceptions.h" + +using namespace qpid::broker; +using namespace qpid::sys; +using std::pair; +using std::string; +using qpid::framing::FieldTable; + +pair<Exchange::shared_ptr, bool> ExchangeRegistry::declare(const string& name, const string& type){ + + return declare(name, type, false, FieldTable()); +} + +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); + if (i == exchanges.end()) { + Exchange::shared_ptr exchange; + + if (type == TopicExchange::typeName){ + exchange = Exchange::shared_ptr(new TopicExchange(name, durable, args, parent, broker)); + }else if(type == DirectExchange::typeName){ + exchange = Exchange::shared_ptr(new DirectExchange(name, durable, args, parent, broker)); + }else if(type == FanOutExchange::typeName){ + exchange = Exchange::shared_ptr(new FanOutExchange(name, durable, args, parent, broker)); + }else if (type == HeadersExchange::typeName) { + exchange = Exchange::shared_ptr(new HeadersExchange(name, durable, args, parent, broker)); + }else if (type == ManagementDirectExchange::typeName) { + exchange = Exchange::shared_ptr(new ManagementDirectExchange(name, durable, args, parent, broker)); + }else if (type == ManagementTopicExchange::typeName) { + exchange = Exchange::shared_ptr(new ManagementTopicExchange(name, durable, args, parent, broker)); + }else{ + FunctionMap::iterator i = factory.find(type); + if (i == factory.end()) { + throw UnknownExchangeTypeException(); + } else { + exchange = i->second(name, durable, args, parent, broker); + } + } + exchanges[name] = exchange; + return std::pair<Exchange::shared_ptr, bool>(exchange, true); + } else { + return std::pair<Exchange::shared_ptr, bool>(i->second, false); + } +} + +void ExchangeRegistry::destroy(const string& name){ + if (name.empty() || + (name.find("amq.") == 0 && + (name == "amq.direct" || name == "amq.fanout" || name == "amq.topic" || name == "amq.match")) || + name == "qpid.management") + throw framing::NotAllowedException(QPID_MSG("Cannot delete default exchange: '" << name << "'")); + RWlock::ScopedWlock locker(lock); + ExchangeMap::iterator i = exchanges.find(name); + if (i != exchanges.end()) { + i->second->destroy(); + exchanges.erase(i); + } +} + +Exchange::shared_ptr ExchangeRegistry::get(const string& name){ + RWlock::ScopedRlock locker(lock); + ExchangeMap::iterator i = exchanges.find(name); + if (i == exchanges.end()) + throw framing::NotFoundException(QPID_MSG("Exchange not found: " << name)); + return i->second; +} + +bool ExchangeRegistry::registerExchange(const Exchange::shared_ptr& ex) { + return exchanges.insert(ExchangeMap::value_type(ex->getName(), ex)).second; +} + +void ExchangeRegistry::registerType(const std::string& type, FactoryFunction f) +{ + factory[type] = f; +} + + +namespace +{ +const std::string empty; +} + +Exchange::shared_ptr ExchangeRegistry::getDefault() +{ + return get(empty); +} diff --git a/qpid/cpp/src/qpid/broker/ExchangeRegistry.h b/qpid/cpp/src/qpid/broker/ExchangeRegistry.h new file mode 100644 index 0000000000..2b75a8f3cf --- /dev/null +++ b/qpid/cpp/src/qpid/broker/ExchangeRegistry.h @@ -0,0 +1,93 @@ +#ifndef _broker_ExchangeRegistry_h +#define _broker_ExchangeRegistry_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/BrokerImportExport.h" +#include "qpid/broker/Exchange.h" +#include "qpid/broker/MessageStore.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/sys/Monitor.h" +#include "qpid/management/Manageable.h" + +#include <boost/function.hpp> +#include <boost/bind.hpp> + +#include <algorithm> +#include <map> + +namespace qpid { +namespace broker { + +struct UnknownExchangeTypeException{}; + +class ExchangeRegistry{ + public: + typedef boost::function5<Exchange::shared_ptr, const std::string&, + bool, const qpid::framing::FieldTable&, qpid::management::Manageable*, qpid::broker::Broker*> FactoryFunction; + + ExchangeRegistry (Broker* b = 0) : parent(0), broker(b) {} + QPID_BROKER_EXTERN std::pair<Exchange::shared_ptr, bool> declare + (const std::string& name, const std::string& type); + QPID_BROKER_EXTERN std::pair<Exchange::shared_ptr, bool> declare + (const std::string& name, + const std::string& type, + bool durable, + const qpid::framing::FieldTable& args = framing::FieldTable()); + QPID_BROKER_EXTERN void destroy(const std::string& name); + QPID_BROKER_EXTERN Exchange::shared_ptr get(const std::string& name); + Exchange::shared_ptr getDefault(); + + /** + * Register the manageable parent for declared exchanges + */ + void setParent (management::Manageable* _parent) { parent = _parent; } + + /** Register an exchange instance. + *@return true if registered, false if exchange with same name is already registered. + */ + bool registerExchange(const Exchange::shared_ptr&); + + QPID_BROKER_EXTERN void registerType(const std::string& type, FactoryFunction); + + /** Call f for each exchange in the registry. */ + template <class F> void eachExchange(F f) const { + qpid::sys::RWlock::ScopedRlock l(lock); + for (ExchangeMap::const_iterator i = exchanges.begin(); i != exchanges.end(); ++i) + f(i->second); + } + + private: + typedef std::map<std::string, Exchange::shared_ptr> ExchangeMap; + typedef std::map<std::string, FactoryFunction > FunctionMap; + + ExchangeMap exchanges; + FunctionMap factory; + mutable qpid::sys::RWlock lock; + management::Manageable* parent; + Broker* broker; +}; + +}} // namespace qpid::broker + + +#endif /*!_broker_ExchangeRegistry_h*/ diff --git a/qpid/cpp/src/qpid/broker/ExpiryPolicy.cpp b/qpid/cpp/src/qpid/broker/ExpiryPolicy.cpp new file mode 100644 index 0000000000..64a12d918a --- /dev/null +++ b/qpid/cpp/src/qpid/broker/ExpiryPolicy.cpp @@ -0,0 +1,38 @@ +/* + * + * 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/ExpiryPolicy.h" +#include "qpid/broker/Message.h" +#include "qpid/sys/Time.h" + +namespace qpid { +namespace broker { + +ExpiryPolicy::~ExpiryPolicy() {} + +void ExpiryPolicy::willExpire(Message&) {} + +bool ExpiryPolicy::hasExpired(Message& m) { + return m.getExpiration() < sys::AbsTime::now(); +} + +void ExpiryPolicy::forget(Message&) {} + +}} // namespace qpid::broker diff --git a/qpid/cpp/src/qpid/broker/ExpiryPolicy.h b/qpid/cpp/src/qpid/broker/ExpiryPolicy.h new file mode 100644 index 0000000000..a723eb0aa8 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/ExpiryPolicy.h @@ -0,0 +1,46 @@ +#ifndef QPID_BROKER_EXPIRYPOLICY_H +#define QPID_BROKER_EXPIRYPOLICY_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 "qpid/broker/BrokerImportExport.h" + +namespace qpid { +namespace broker { + +class Message; + +/** + * Default expiry policy. + */ +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&); +}; +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_EXPIRYPOLICY_H*/ diff --git a/qpid/cpp/src/qpid/broker/Fairshare.cpp b/qpid/cpp/src/qpid/broker/Fairshare.cpp new file mode 100644 index 0000000000..17270ffd8d --- /dev/null +++ b/qpid/cpp/src/qpid/broker/Fairshare.cpp @@ -0,0 +1,186 @@ +/* + * + * 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/Fairshare.h" +#include "qpid/broker/QueuedMessage.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/log/Statement.h" +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/assign/list_of.hpp> + +namespace qpid { +namespace broker { + +Fairshare::Fairshare(size_t levels, uint limit) : + PriorityQueue(levels), + limits(levels, limit), priority(levels-1), count(0) {} + + +void Fairshare::setLimit(size_t level, uint limit) +{ + limits[level] = limit; +} + +bool Fairshare::limitReached() +{ + uint l = limits[priority]; + return l && ++count > l; +} + +uint Fairshare::currentLevel() +{ + if (limitReached()) { + return nextLevel(); + } else { + return priority; + } +} + +uint Fairshare::nextLevel() +{ + count = 1; + if (priority) --priority; + else priority = levels-1; + return priority; +} + +bool Fairshare::isNull() +{ + for (int i = 0; i < levels; i++) if (limits[i]) return false; + return true; +} + +bool Fairshare::getState(uint& p, uint& c) const +{ + p = priority; + c = count; + return true; +} + +bool Fairshare::setState(uint p, uint c) +{ + priority = p; + count = c; + return true; +} + +bool Fairshare::findFrontLevel(uint& p, PriorityLevels& messages) +{ + const uint start = p = currentLevel(); + do { + if (!messages[p].empty()) return true; + } while ((p = nextLevel()) != start); + return false; +} + + + +bool Fairshare::getState(const Messages& m, uint& priority, uint& count) +{ + const Fairshare* fairshare = dynamic_cast<const Fairshare*>(&m); + return fairshare && fairshare->getState(priority, count); +} + +bool Fairshare::setState(Messages& m, uint priority, uint count) +{ + Fairshare* fairshare = dynamic_cast<Fairshare*>(&m); + return fairshare && fairshare->setState(priority, count); +} + +int getIntegerSetting(const qpid::framing::FieldTable& settings, const std::vector<std::string>& keys) +{ + 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); + } catch(const boost::bad_lexical_cast&) { + QPID_LOG(warning, "Ignoring invalid integer value for " << *i << ": " << s); + return 0; + } + } else { + QPID_LOG(warning, "Ignoring invalid integer value for " << *i << ": " << *v); + return 0; + } +} + +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) +{ + 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, list_of<std::string>("qpid.priorities")("x-qpid-priorities"), 1, 100); + if (levels) { + 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; +} + +}} // namespace qpid::broker diff --git a/qpid/cpp/src/qpid/broker/Fairshare.h b/qpid/cpp/src/qpid/broker/Fairshare.h new file mode 100644 index 0000000000..1b25721e0c --- /dev/null +++ b/qpid/cpp/src/qpid/broker/Fairshare.h @@ -0,0 +1,61 @@ +#ifndef QPID_BROKER_FAIRSHARE_H +#define QPID_BROKER_FAIRSHARE_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/PriorityQueue.h" + +namespace qpid { +namespace framing { +class FieldTable; +} +namespace broker { + +/** + * Modifies a basic prioirty queue by limiting the number of messages + * from each priority level that are dispatched before allowing + * dispatch from the next level. + */ +class Fairshare : public PriorityQueue +{ + public: + Fairshare(size_t levels, uint limit); + 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 limitReached(); + bool findFrontLevel(uint& p, PriorityLevels&); +}; +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_FAIRSHARE_H*/ diff --git a/qpid/cpp/src/qpid/broker/FanOutExchange.cpp b/qpid/cpp/src/qpid/broker/FanOutExchange.cpp new file mode 100644 index 0000000000..5879fa0892 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/FanOutExchange.cpp @@ -0,0 +1,119 @@ +/* + * + * 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/log/Statement.h" +#include "qpid/broker/FanOutExchange.h" +#include "qpid/broker/FedOps.h" +#include <algorithm> + +using namespace qpid::broker; +using namespace qpid::framing; +using namespace qpid::sys; +namespace _qmf = qmf::org::apache::qpid::broker; + +FanOutExchange::FanOutExchange(const std::string& _name, Manageable* _parent, Broker* b) : + Exchange(_name, _parent, b) +{ + if (mgmtExchange != 0) + mgmtExchange->set_type (typeName); +} + +FanOutExchange::FanOutExchange(const std::string& _name, bool _durable, + const FieldTable& _args, Manageable* _parent, Broker* b) : + Exchange(_name, _durable, _args, _parent, b) +{ + if (mgmtExchange != 0) + mgmtExchange->set_type (typeName); +} + +bool FanOutExchange::bind(Queue::shared_ptr queue, const string& /*key*/, const FieldTable* args) +{ + string fedOp(args ? args->getAsString(qpidFedOp) : fedOpBind); + string fedTags(args ? args->getAsString(qpidFedTags) : ""); + string fedOrigin(args ? args->getAsString(qpidFedOrigin) : ""); + bool propagate = false; + + if (args == 0 || fedOp.empty() || fedOp == fedOpBind) { + Binding::shared_ptr binding (new Binding ("", queue, this, FieldTable(), fedOrigin)); + if (bindings.add_unless(binding, MatchQueue(queue))) { + binding->startManagement(); + propagate = fedBinding.addOrigin(queue->getName(), fedOrigin); + if (mgmtExchange != 0) { + mgmtExchange->inc_bindingCount(); + } + } else { + // queue already present - still need to track fedOrigin + fedBinding.addOrigin(queue->getName(), fedOrigin); + return false; + } + } else if (fedOp == fedOpUnbind) { + propagate = fedBinding.delOrigin(queue->getName(), fedOrigin); + if (fedBinding.countFedBindings(queue->getName()) == 0) + unbind(queue, "", args); + } else if (fedOp == fedOpReorigin) { + if (fedBinding.hasLocal()) { + propagateFedOp(string(), string(), fedOpBind, string()); + } + } + + routeIVE(); + if (propagate) + propagateFedOp(string(), fedTags, fedOp, fedOrigin); + return true; +} + +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(queue->getName(), fedOrigin); + if (mgmtExchange != 0) { + mgmtExchange->dec_bindingCount(); + } + } else { + return false; + } + + if (propagate) + propagateFedOp(string(), string(), fedOpUnbind, string()); + return true; +} + +void FanOutExchange::route(Deliverable& msg, const string& /*routingKey*/, const FieldTable* /*args*/) +{ + PreRoute pr(msg, this); + doRoute(msg, bindings.snapshot()); +} + +bool FanOutExchange::isBound(Queue::shared_ptr queue, const string* const, const FieldTable* const) +{ + BindingsArray::ConstPtr ptr = bindings.snapshot(); + return ptr && std::find_if(ptr->begin(), ptr->end(), MatchQueue(queue)) != ptr->end(); +} + + +FanOutExchange::~FanOutExchange() {} + +const std::string FanOutExchange::typeName("fanout"); diff --git a/qpid/cpp/src/qpid/broker/FanOutExchange.h b/qpid/cpp/src/qpid/broker/FanOutExchange.h new file mode 100644 index 0000000000..1a7d486796 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/FanOutExchange.h @@ -0,0 +1,74 @@ +/* + * + * 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 _FanOutExchange_ +#define _FanOutExchange_ + +#include <map> +#include <vector> +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/Exchange.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/sys/CopyOnWriteArray.h" +#include "qpid/broker/Queue.h" + +namespace qpid { +namespace broker { + +class FanOutExchange : public virtual Exchange { + typedef qpid::sys::CopyOnWriteArray<Binding::shared_ptr> BindingsArray; + BindingsArray bindings; + FedBinding fedBinding; + public: + static const std::string typeName; + + QPID_BROKER_EXTERN FanOutExchange(const std::string& name, + management::Manageable* parent = 0, Broker* broker = 0); + QPID_BROKER_EXTERN FanOutExchange(const std::string& _name, + bool _durable, + const qpid::framing::FieldTable& _args, + management::Manageable* parent = 0, Broker* broker = 0); + + virtual std::string getType() const { return typeName; } + + QPID_BROKER_EXTERN virtual bool bind(Queue::shared_ptr queue, + const std::string& routingKey, + const qpid::framing::FieldTable* args); + + virtual bool unbind(Queue::shared_ptr queue, const std::string& routingKey, const qpid::framing::FieldTable* args); + + QPID_BROKER_EXTERN virtual void route(Deliverable& msg, + const std::string& routingKey, + const qpid::framing::FieldTable* args); + + QPID_BROKER_EXTERN virtual bool isBound(Queue::shared_ptr queue, + const std::string* const routingKey, + const qpid::framing::FieldTable* const args); + + QPID_BROKER_EXTERN virtual ~FanOutExchange(); + virtual bool supportsDynamicBinding() { return true; } +}; + +} +} + + + +#endif diff --git a/qpid/cpp/src/qpid/broker/FedOps.h b/qpid/cpp/src/qpid/broker/FedOps.h new file mode 100644 index 0000000000..dc4a38e244 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/FedOps.h @@ -0,0 +1,38 @@ +/* + * + * 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. + * + */ + +/* + * Strings used to identify federated operations and operands. + */ + +namespace +{ + const std::string qpidFedOp("qpid.fed.op"); // a federation primitive + const std::string qpidFedTags("qpid.fed.tags"); // a unique id for a broker + const std::string qpidFedOrigin("qpid.fed.origin"); // the tag of the broker on which a propagated binding originated + + // Operands for qpidFedOp - each identifies a federation primitive + + const std::string fedOpBind("B"); + const std::string fedOpUnbind("U"); + const std::string fedOpReorigin("R"); + const std::string fedOpHello("H"); +} diff --git a/qpid/cpp/src/qpid/broker/HandlerImpl.h b/qpid/cpp/src/qpid/broker/HandlerImpl.h new file mode 100644 index 0000000000..aae636e818 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/HandlerImpl.h @@ -0,0 +1,53 @@ +#ifndef _broker_HandlerImpl_h +#define _broker_HandlerImpl_h + +/* + * + * 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. + * + */ + +#include "qpid/broker/SemanticState.h" +#include "qpid/broker/SessionContext.h" +#include "qpid/broker/ConnectionState.h" + +namespace qpid { +namespace broker { + +class Broker; + +/** + * Base template for protocol handler implementations. + * Provides convenience methods for getting common session objects. + */ +class HandlerImpl { + protected: + SemanticState& state; + SessionContext& session; + + HandlerImpl(SemanticState& s) : state(s), session(s.getSession()) {} + + framing::AMQP_ClientProxy& getProxy() { return session.getProxy(); } + ConnectionState& getConnection() { return session.getConnection(); } + Broker& getBroker() { return session.getConnection().getBroker(); } +}; + +}} // namespace qpid::broker + + + +#endif /*!_broker_HandlerImpl_h*/ + + diff --git a/qpid/cpp/src/qpid/broker/HeadersExchange.cpp b/qpid/cpp/src/qpid/broker/HeadersExchange.cpp new file mode 100644 index 0000000000..abcaa5f69d --- /dev/null +++ b/qpid/cpp/src/qpid/broker/HeadersExchange.cpp @@ -0,0 +1,341 @@ +/* + * + * 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/HeadersExchange.h" +#include "qpid/framing/FieldValue.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/log/Statement.h" +#include <algorithm> + + +using namespace qpid::broker; +using namespace qpid::framing; +using namespace qpid::sys; +namespace _qmf = qmf::org::apache::qpid::broker; + +// TODO aconway 2006-09-20: More efficient matching algorithm. +// The current search algorithm really sucks. +// Fieldtables are heavy, maybe use shared_ptr to do handle-body. + +using namespace qpid::broker; + +namespace { + const std::string x_match("x-match"); + // possible values for x-match + const std::string all("all"); + const std::string any("any"); + const std::string empty; + + // federation related args and values + const std::string qpidFedOp("qpid.fed.op"); + const std::string qpidFedTags("qpid.fed.tags"); + const std::string qpidFedOrigin("qpid.fed.origin"); + + const std::string fedOpBind("B"); + const std::string fedOpUnbind("U"); + const std::string fedOpReorigin("R"); + const std::string fedOpHello("H"); +} + +HeadersExchange::HeadersExchange(const string& _name, Manageable* _parent, Broker* b) : + Exchange(_name, _parent, b) +{ + if (mgmtExchange != 0) + mgmtExchange->set_type (typeName); +} + +HeadersExchange::HeadersExchange(const std::string& _name, bool _durable, + const FieldTable& _args, Manageable* _parent, Broker* b) : + Exchange(_name, _durable, _args, _parent, b) +{ + if (mgmtExchange != 0) + mgmtExchange->set_type (typeName); +} + +std::string HeadersExchange::getMatch(const FieldTable* args) +{ + if (!args) { + throw InternalErrorException(QPID_MSG("No arguments given.")); + } + FieldTable::ValuePtr what = args->get(x_match); + if (!what) { + return empty; + } + if (!what->convertsTo<std::string>()) { + throw InternalErrorException(QPID_MSG("Invalid x-match binding format to headers exchange. Must be a string [\"all\" or \"any\"]")); + } + return what->get<std::string>(); +} + +bool HeadersExchange::bind(Queue::shared_ptr queue, const string& bindingKey, const FieldTable* args) +{ + string fedOp(fedOpBind); + string fedTags; + string fedOrigin; + if (args) { + fedOp = args->getAsString(qpidFedOp); + fedTags = args->getAsString(qpidFedTags); + fedOrigin = args->getAsString(qpidFedOrigin); + } + + bool propagate = false; + + // The federation args get propagated directly, so we need to identify + // the non federation args in case a federated propagate is needed + FieldTable extra_args; + getNonFedArgs(args, extra_args); + + if (fedOp.empty() || fedOp == fedOpBind) { + // x-match arg MUST be present for a bind call + std::string x_match_value = getMatch(args); + + if (x_match_value != all && x_match_value != any) { + throw InternalErrorException(QPID_MSG("Invalid or missing x-match value binding to headers exchange. Must be a string [\"all\" or \"any\"]")); + } + + { + Mutex::ScopedLock l(lock); + Binding::shared_ptr binding (new Binding (bindingKey, queue, this, *args)); + BoundKey bk(binding); + if (bindings.add_unless(bk, MatchArgs(queue, args))) { + binding->startManagement(); + propagate = bk.fedBinding.addOrigin(queue->getName(), fedOrigin); + if (mgmtExchange != 0) { + mgmtExchange->inc_bindingCount(); + } + } else { + bk.fedBinding.addOrigin(queue->getName(), fedOrigin); + return false; + } + } // lock dropped + + } else if (fedOp == fedOpUnbind) { + Mutex::ScopedLock l(lock); + + FedUnbindModifier modifier(queue->getName(), fedOrigin); + bindings.modify_if(MatchKey(queue, bindingKey), modifier); + propagate = modifier.shouldPropagate; + if (modifier.shouldUnbind) { + unbind(queue, bindingKey, args); + } + + } else if (fedOp == fedOpReorigin) { + Bindings::ConstPtr p = bindings.snapshot(); + if (p.get()) + { + Mutex::ScopedLock l(lock); + for (std::vector<BoundKey>::const_iterator i = p->begin(); i != p->end(); ++i) + { + if ((*i).fedBinding.hasLocal()) { + propagateFedOp( (*i).binding->key, string(), fedOpBind, string()); + } + } + } + } + routeIVE(); + if (propagate) { + FieldTable * prop_args = (extra_args.count() != 0 ? &extra_args : 0); + propagateFedOp(bindingKey, fedTags, fedOp, fedOrigin, prop_args); + } + + return true; +} + +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(queue->getName(), fedOrigin); + MatchKey match_key(queue, bindingKey); + bindings.modify_if(match_key, modifier); + propagate = modifier.shouldPropagate; + if (modifier.shouldUnbind) { + if (bindings.remove_if(match_key)) { + if (mgmtExchange != 0) { + mgmtExchange->dec_bindingCount(); + } + } else { + return false; + } + } + } + + if (propagate) { + propagateFedOp(bindingKey, string(), fedOpUnbind, string()); + } + return true; +} + + +void HeadersExchange::route(Deliverable& msg, const string& /*routingKey*/, const FieldTable* args) +{ + if (!args) { + //can't match if there were no headers passed in + if (mgmtExchange != 0) { + mgmtExchange->inc_msgReceives(); + mgmtExchange->inc_byteReceives(msg.contentSize()); + mgmtExchange->inc_msgDrops(); + mgmtExchange->inc_byteDrops(msg.contentSize()); + } + return; + } + + PreRoute pr(msg, this); + + BindingList b(new std::vector<boost::shared_ptr<qpid::broker::Exchange::Binding> >); + Bindings::ConstPtr p = bindings.snapshot(); + if (p.get()) { + for (std::vector<BoundKey>::const_iterator i = p->begin(); i != p->end(); ++i) { + if (match((*i).binding->args, *args)) { + b->push_back((*i).binding); + } + } + } + doRoute(msg, b); +} + + +bool HeadersExchange::isBound(Queue::shared_ptr queue, const string* const, const FieldTable* const args) +{ + Bindings::ConstPtr p = bindings.snapshot(); + if (p.get()){ + for (std::vector<BoundKey>::const_iterator i = p->begin(); i != p->end(); ++i) { + if ( (!args || equal((*i).binding->args, *args)) && (!queue || (*i).binding->queue == queue)) { + return true; + } + } + } + return false; +} + +void HeadersExchange::getNonFedArgs(const FieldTable* args, FieldTable& nonFedArgs) +{ + if (!args) + { + return; + } + + for (qpid::framing::FieldTable::ValueMap::const_iterator i=args->begin(); i != args->end(); ++i) + { + const string & name(i->first); + if (name == qpidFedOp || + name == qpidFedTags || + name == qpidFedOrigin) + { + continue; + } + nonFedArgs.insert((*i)); + } +} + +HeadersExchange::~HeadersExchange() {} + +const std::string HeadersExchange::typeName("headers"); + +namespace +{ + + bool match_values(const FieldValue& bind, const FieldValue& msg) { + return bind.getType() == 0xf0 || bind == msg; + } + +} + + +bool HeadersExchange::match(const FieldTable& bind, const FieldTable& msg) { + typedef FieldTable::ValueMap Map; + std::string what = getMatch(&bind); + if (what == all) { + for (Map::const_iterator i = bind.begin(); + i != bind.end(); + ++i) + { + if (i->first != x_match) + { + Map::const_iterator j = msg.find(i->first); + if (j == msg.end()) return false; + if (!match_values(*(i->second), *(j->second))) return false; + } + } + return true; + } else if (what == any) { + for (Map::const_iterator i = bind.begin(); + i != bind.end(); + ++i) + { + if (i->first != x_match) + { + Map::const_iterator j = msg.find(i->first); + if (j != msg.end()) { + if (match_values(*(i->second), *(j->second))) return true; + } + } + } + return false; + } else { + return false; + } +} + +bool HeadersExchange::equal(const FieldTable& a, const FieldTable& b) { + typedef FieldTable::ValueMap Map; + for (Map::const_iterator i = a.begin(); + i != a.end(); + ++i) + { + Map::const_iterator j = b.find(i->first); + if (j == b.end()) return false; + if (!match_values(*(i->second), *(j->second))) return false; + } + return true; +} + +//--------- +HeadersExchange::MatchArgs::MatchArgs(Queue::shared_ptr q, const qpid::framing::FieldTable* a) : queue(q), args(a) {} + +bool HeadersExchange::MatchArgs::operator()(BoundKey & bk) +{ + return bk.binding->queue == queue && bk.binding->args == *args; +} + +//--------- +HeadersExchange::MatchKey::MatchKey(Queue::shared_ptr q, const std::string& k) : queue(q), key(k) {} + +bool HeadersExchange::MatchKey::operator()(BoundKey & bk) +{ + return bk.binding->queue == queue && bk.binding->key == key; +} + +//---------- +HeadersExchange::FedUnbindModifier::FedUnbindModifier(const string& queueName, const string& origin) : queueName(queueName), fedOrigin(origin), shouldUnbind(false), shouldPropagate(false) {} +HeadersExchange::FedUnbindModifier::FedUnbindModifier() : shouldUnbind(false), shouldPropagate(false) {} + +bool HeadersExchange::FedUnbindModifier::operator()(BoundKey & bk) +{ + shouldPropagate = bk.fedBinding.delOrigin(queueName, fedOrigin); + if (bk.fedBinding.countFedBindings(queueName) == 0) + { + shouldUnbind = true; + } + return true; +} + diff --git a/qpid/cpp/src/qpid/broker/HeadersExchange.h b/qpid/cpp/src/qpid/broker/HeadersExchange.h new file mode 100644 index 0000000000..3b939d6851 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/HeadersExchange.h @@ -0,0 +1,122 @@ +/* + * + * 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 _HeadersExchange_ +#define _HeadersExchange_ + +#include <vector> +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/Exchange.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/sys/CopyOnWriteArray.h" +#include "qpid/sys/Mutex.h" +#include "qpid/broker/Queue.h" + +namespace qpid { +namespace broker { + + +class HeadersExchange : public virtual Exchange { + + struct BoundKey + { + Binding::shared_ptr binding; + FedBinding fedBinding; + BoundKey(Binding::shared_ptr binding_) : binding(binding_) {} + }; + + struct MatchArgs + { + const Queue::shared_ptr queue; + const qpid::framing::FieldTable* args; + MatchArgs(Queue::shared_ptr q, const qpid::framing::FieldTable* a); + bool operator()(BoundKey & bk); + }; + + struct MatchKey + { + const Queue::shared_ptr queue; + const std::string& key; + MatchKey(Queue::shared_ptr q, const std::string& k); + bool operator()(BoundKey & bk); + }; + + struct FedUnbindModifier + { + std::string queueName; + std::string fedOrigin; + bool shouldUnbind; + bool shouldPropagate; + FedUnbindModifier(); + FedUnbindModifier(const std::string& queueName, const std::string& origin); + bool operator()(BoundKey & bk); + }; + + typedef qpid::sys::CopyOnWriteArray<BoundKey> Bindings; + + Bindings bindings; + qpid::sys::Mutex lock; + + static std::string getMatch(const framing::FieldTable* args); + + protected: + void getNonFedArgs(const framing::FieldTable* args, + framing::FieldTable& nonFedArgs); + + public: + static const std::string typeName; + + QPID_BROKER_EXTERN HeadersExchange(const std::string& name, + management::Manageable* parent = 0, Broker* broker = 0); + QPID_BROKER_EXTERN HeadersExchange(const std::string& _name, + bool _durable, + const qpid::framing::FieldTable& _args, + management::Manageable* parent = 0, Broker* broker = 0); + + virtual std::string getType() const { return typeName; } + + QPID_BROKER_EXTERN virtual bool bind(Queue::shared_ptr queue, + const std::string& routingKey, + const qpid::framing::FieldTable* args); + + virtual bool unbind(Queue::shared_ptr queue, const std::string& routingKey, const qpid::framing::FieldTable* args); + + QPID_BROKER_EXTERN virtual void route(Deliverable& msg, + const std::string& routingKey, + const qpid::framing::FieldTable* args); + + QPID_BROKER_EXTERN virtual bool isBound(Queue::shared_ptr queue, + const std::string* const routingKey, + const qpid::framing::FieldTable* const args); + + QPID_BROKER_EXTERN virtual ~HeadersExchange(); + + virtual bool supportsDynamicBinding() { return true; } + + static QPID_BROKER_EXTERN bool match(const qpid::framing::FieldTable& bindArgs, const qpid::framing::FieldTable& msgArgs); + static bool equal(const qpid::framing::FieldTable& bindArgs, const qpid::framing::FieldTable& msgArgs); +}; + + + +} +} + +#endif diff --git a/qpid/cpp/src/qpid/broker/LegacyLVQ.cpp b/qpid/cpp/src/qpid/broker/LegacyLVQ.cpp new file mode 100644 index 0000000000..a811a86492 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/LegacyLVQ.cpp @@ -0,0 +1,116 @@ +/* + * + * 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/LegacyLVQ.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/QueuedMessage.h" + +namespace qpid { +namespace broker { + +LegacyLVQ::LegacyLVQ(const std::string& k, bool b, Broker* br) : MessageMap(k), noBrowse(b), broker(br) {} + +void LegacyLVQ::setNoBrowse(bool b) +{ + noBrowse = b; +} + +bool LegacyLVQ::remove(const framing::SequenceNumber& position, QueuedMessage& message) +{ + Ordering::iterator i = messages.find(position); + if (i != messages.end() && i->second.payload == message.payload) { + message = i->second; + erase(i); + return true; + } else { + return false; + } +} + +bool LegacyLVQ::next(const framing::SequenceNumber& position, QueuedMessage& message) +{ + if (MessageMap::next(position, message)) { + if (!noBrowse) index.erase(getKey(message)); + return true; + } else { + return false; + } +} + +bool LegacyLVQ::push(const QueuedMessage& added, QueuedMessage& removed) +{ + //Hack to disable LVQ behaviour on cluster update: + if (broker && broker->isClusterUpdatee()) { + messages[added.position] = added; + return false; + } else { + return MessageMap::push(added, removed); + } +} + +const QueuedMessage& LegacyLVQ::replace(const QueuedMessage& original, const QueuedMessage& update) +{ + //add the new message into the original position of the replaced message + Ordering::iterator i = messages.find(original.position); + i->second = update; + i->second.position = original.position; + return i->second; +} + +void LegacyLVQ::removeIf(Predicate p) +{ + //Note: This method is currently called periodically on the timer + //thread to expire messages. In a clustered broker this means that + //the purging does not occur on the cluster event dispatch thread + //and consequently that is not totally ordered w.r.t other events + //(including publication of messages). The cluster does ensure + //that the actual expiration of messages (as distinct from the + //removing of those expired messages from the queue) *is* + //consistently ordered w.r.t. cluster events. This means that + //delivery of messages is in general consistent across the cluster + //inspite of any non-determinism in the triggering of a + //purge. However at present purging a last value queue (of the + //legacy sort) could potentially cause inconsistencies in the + //cluster (as the order w.r.t publications can affect the order in + //which messages appear in the queue). Consequently periodic + //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())) + MessageMap::removeIf(p); +} + +std::auto_ptr<Messages> LegacyLVQ::updateOrReplace(std::auto_ptr<Messages> current, + const std::string& key, bool noBrowse, Broker* broker) +{ + LegacyLVQ* lvq = dynamic_cast<LegacyLVQ*>(current.get()); + if (lvq) { + lvq->setNoBrowse(noBrowse); + return current; + } else { + return std::auto_ptr<Messages>(new LegacyLVQ(key, noBrowse, broker)); + } +} + +}} // namespace qpid::broker diff --git a/qpid/cpp/src/qpid/broker/LegacyLVQ.h b/qpid/cpp/src/qpid/broker/LegacyLVQ.h new file mode 100644 index 0000000000..dd0fd7aaec --- /dev/null +++ b/qpid/cpp/src/qpid/broker/LegacyLVQ.h @@ -0,0 +1,59 @@ +#ifndef QPID_BROKER_LEGACYLVQ_H +#define QPID_BROKER_LEGACYLVQ_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/MessageMap.h" +#include <memory> + +namespace qpid { +namespace broker { +class Broker; + +/** + * This class encapsulates the behaviour of the old style LVQ where a + * message replacing another messages for the given key will use the + * position in the queue of the previous message. This however causes + * problems for browsing. Either browsers stop the coalescing of + * messages by key (default) or they may mis updates (if the no-browse + * option is specified). + */ +class LegacyLVQ : public MessageMap +{ + public: + LegacyLVQ(const std::string& key, bool noBrowse = false, Broker* broker = 0); + bool remove(const framing::SequenceNumber&, QueuedMessage&); + bool next(const framing::SequenceNumber&, QueuedMessage&); + bool push(const QueuedMessage& added, QueuedMessage& removed); + void removeIf(Predicate); + void setNoBrowse(bool); + static std::auto_ptr<Messages> updateOrReplace(std::auto_ptr<Messages> current, + const std::string& key, bool noBrowse, + Broker* broker); + protected: + bool noBrowse; + Broker* broker; + + const QueuedMessage& replace(const QueuedMessage&, const QueuedMessage&); +}; +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_LEGACYLVQ_H*/ diff --git a/qpid/cpp/src/qpid/broker/Link.cpp b/qpid/cpp/src/qpid/broker/Link.cpp new file mode 100644 index 0000000000..9ab4379a69 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/Link.cpp @@ -0,0 +1,474 @@ +/* + * + * 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/Link.h" +#include "qpid/broker/LinkRegistry.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/Connection.h" +#include "qmf/org/apache/qpid/broker/EventBrokerLinkUp.h" +#include "qmf/org/apache/qpid/broker/EventBrokerLinkDown.h" +#include "boost/bind.hpp" +#include "qpid/log/Statement.h" +#include "qpid/framing/enum.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/broker/AclModule.h" + +using namespace qpid::broker; +using qpid::framing::Buffer; +using qpid::framing::FieldTable; +using qpid::framing::UnauthorizedAccessException; +using qpid::framing::connection::CLOSE_CODE_CONNECTION_FORCED; +using qpid::management::ManagementAgent; +using qpid::management::ManagementObject; +using qpid::management::Manageable; +using qpid::management::Args; +using qpid::sys::Mutex; +using std::stringstream; +using std::string; +namespace _qmf = qmf::org::apache::qpid::broker; + +Link::Link(LinkRegistry* _links, + MessageStore* _store, + string& _host, + uint16_t _port, + string& _transport, + bool _durable, + string& _authMechanism, + string& _username, + string& _password, + Broker* _broker, + Manageable* parent) + : 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), + visitCount(0), + currentInterval(1), + closing(false), + updateUrls(false), + channelCounter(1), + connection(0), + agent(0) +{ + if (parent != 0 && broker != 0) + { + agent = broker->getManagementAgent(); + if (agent != 0) + { + mgmtObject = new _qmf::Link(agent, this, parent, _host, _port, _transport, _durable); + agent->addObject(mgmtObject, 0, durable); + } + } + setStateLH(STATE_WAITING); +} + +Link::~Link () +{ + if (state == STATE_OPERATIONAL && connection != 0) + connection->close(CLOSE_CODE_CONNECTION_FORCED, "closed by management"); + + if (mgmtObject != 0) + mgmtObject->resourceDestroy (); +} + +void Link::setStateLH (int newState) +{ + if (newState == state) + return; + + state = newState; + + if (hideManagement()) + return; + + switch (state) + { + case STATE_WAITING : mgmtObject->set_state("Waiting"); break; + case STATE_CONNECTING : mgmtObject->set_state("Connecting"); break; + case STATE_OPERATIONAL : mgmtObject->set_state("Operational"); break; + case STATE_FAILED : mgmtObject->set_state("Failed"); break; + case STATE_CLOSED : mgmtObject->set_state("Closed"); break; + case STATE_PASSIVE : mgmtObject->set_state("Passive"); break; + } +} + +void Link::startConnectionLH () +{ + try { + // 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, 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 (!hideManagement()) + mgmtObject->set_lastError (e.what()); + } +} + +void Link::established () +{ + stringstream addr; + addr << host << ":" << port; + QPID_LOG (info, "Inter-broker link established to " << addr.str()); + + if (!hideManagement() && agent) + agent->raiseEvent(_qmf::EventBrokerLinkUp(addr.str())); + + { + Mutex::ScopedLock mutex(lock); + setStateLH(STATE_OPERATIONAL); + currentInterval = 1; + visitCount = 0; + if (closing) + destroy(); + } +} + +void Link::closed (int, std::string text) +{ + Mutex::ScopedLock mutex(lock); + QPID_LOG (info, "Inter-broker link disconnected from " << host << ":" << port << " " << text); + + connection = 0; + + if (state == STATE_OPERATIONAL) { + stringstream addr; + addr << host << ":" << port; + QPID_LOG (warning, "Inter-broker link disconnected from " << addr.str()); + if (!hideManagement() && agent) + agent->raiseEvent(_qmf::EventBrokerLinkDown(addr.str())); + } + + for (Bridges::iterator i = active.begin(); i != active.end(); i++) { + (*i)->closed(); + created.push_back(*i); + } + active.clear(); + + if (state != STATE_FAILED) + { + setStateLH(STATE_WAITING); + if (!hideManagement()) + mgmtObject->set_lastError (text); + } + + if (closing) + destroy(); +} + +void Link::destroy () +{ + Bridges toDelete; + { + Mutex::ScopedLock mutex(lock); + + QPID_LOG (info, "Inter-broker link to " << host << ":" << port << " removed by management"); + if (connection) + connection->close(CLOSE_CODE_CONNECTION_FORCED, "closed by management"); + + setStateLH(STATE_CLOSED); + + // Move the bridges to be deleted into a local vector so there is no + // corruption of the iterator caused by bridge deletion. + for (Bridges::iterator i = active.begin(); i != active.end(); i++) { + (*i)->closed(); + toDelete.push_back(*i); + } + active.clear(); + + for (Bridges::iterator i = created.begin(); i != created.end(); i++) + toDelete.push_back(*i); + created.clear(); + } + // Now delete all bridges on this link (don't hold the lock for this). + for (Bridges::iterator i = toDelete.begin(); i != toDelete.end(); i++) + (*i)->destroy(); + toDelete.clear(); + links->destroy (host, port); +} + +void Link::add(Bridge::shared_ptr bridge) +{ + Mutex::ScopedLock mutex(lock); + created.push_back (bridge); +} + +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); + break; + } + } + for (Bridges::iterator i = active.begin(); i != active.end(); i++) { + if ((*i).get() == bridge.get()) { + cancellations.push_back(bridge); + bridge->closed(); + active.erase(i); + break; + } + } + } + if (!cancellations.empty()) { + connection->requestIOProcessing (boost::bind(&Link::ioThreadProcessing, this)); + } +} + +void Link::ioThreadProcessing() +{ + Mutex::ScopedLock mutex(lock); + + if (state != STATE_OPERATIONAL) + return; + QPID_LOG(debug, "Link::ioThreadProcessing()"); + + //process any pending creates and/or cancellations + if (!created.empty()) { + for (Bridges::iterator i = created.begin(); i != created.end(); ++i) { + active.push_back(*i); + (*i)->create(*connection); + } + created.clear(); + } + if (!cancellations.empty()) { + for (Bridges::iterator i = cancellations.begin(); i != cancellations.end(); ++i) { + (*i)->cancel(*connection); + } + cancellations.clear(); + } +} + +void Link::setConnection(Connection* c) +{ + Mutex::ScopedLock mutex(lock); + connection = c; + updateUrls = true; +} + +void Link::maintenanceVisit () +{ + Mutex::ScopedLock mutex(lock); + + if (connection && updateUrls) { + urls.reset(connection->getKnownHosts()); + QPID_LOG(debug, "Known hosts for peer of inter-broker link: " << urls); + updateUrls = false; + } + + if (state == STATE_WAITING) + { + visitCount++; + if (visitCount >= currentInterval) + { + visitCount = 0; + //switch host and port to next in url list if possible + if (!tryFailover()) { + currentInterval *= 2; + if (currentInterval > MAX_INTERVAL) + currentInterval = MAX_INTERVAL; + startConnectionLH(); + } + } + } + else if (state == STATE_OPERATIONAL && (!created.empty() || !cancellations.empty()) && connection != 0) + connection->requestIOProcessing (boost::bind(&Link::ioThreadProcessing, this)); +} + +void Link::reconnect(const qpid::Address& a) +{ + Mutex::ScopedLock mutex(lock); + host = a.host; + port = a.port; + transport = a.protocol; + startConnectionLH(); + if (!hideManagement()) { + stringstream errorString; + errorString << "Failed over to " << a; + mgmtObject->set_lastError(errorString.str()); + } +} + +bool Link::tryFailover() +{ + Address 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); + return true; + } else { + return false; + } +} + +// 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); + + return channelCounter++; +} + +void Link::notifyConnectionForced(const string text) +{ + Mutex::ScopedLock mutex(lock); + + setStateLH(STATE_FAILED); + if (!hideManagement()) + mgmtObject->set_lastError(text); +} + +void Link::setPersistenceId(uint64_t id) const +{ + persistenceId = id; +} + +const string& Link::getName() const +{ + return host; +} + +Link::shared_ptr Link::decode(LinkRegistry& links, Buffer& buffer) +{ + string host; + uint16_t port; + string transport; + string authMechanism; + string username; + string password; + + buffer.getShortString(host); + port = buffer.getShort(); + buffer.getShortString(transport); + bool durable(buffer.getOctet()); + buffer.getShortString(authMechanism); + buffer.getShortString(username); + buffer.getShortString(password); + + return links.declare(host, port, transport, durable, authMechanism, username, password).first; +} + +void Link::encode(Buffer& buffer) const +{ + buffer.putShortString(string("link")); + buffer.putShortString(host); + buffer.putShort(port); + buffer.putShortString(transport); + buffer.putOctet(durable ? 1 : 0); + buffer.putShortString(authMechanism); + buffer.putShortString(username); + buffer.putShortString(password); +} + +uint32_t Link::encodedSize() const +{ + return host.size() + 1 // short-string (host) + + 5 // short-string ("link") + + 2 // port + + transport.size() + 1 // short-string(transport) + + 1 // durable + + authMechanism.size() + 1 + + username.size() + 1 + + password.size() + 1; +} + +ManagementObject* Link::GetManagementObject (void) const +{ + return (ManagementObject*) mgmtObject; +} + +Manageable::status_t Link::ManagementMethod (uint32_t op, Args& args, string& text) +{ + switch (op) + { + case _qmf::Link::METHOD_CLOSE : + if (!closing) { + closing = true; + if (state != STATE_CONNECTING && connection) { + //connection can only be closed on the connections own IO processing thread + connection->requestIOProcessing(boost::bind(&Link::destroy, this)); + } + } + return Manageable::STATUS_OK; + + case _qmf::Link::METHOD_BRIDGE : + _qmf::ArgsLinkBridge& iargs = (_qmf::ArgsLinkBridge&) args; + QPID_LOG(debug, "Link::bridge() request received"); + + // Durable bridges are only valid on durable links + if (iargs.i_durable && !durable) { + text = "Can't create a durable route on a non-durable link"; + return Manageable::STATUS_USER; + } + + if (iargs.i_dynamic) { + Exchange::shared_ptr exchange = getBroker()->getExchanges().get(iargs.i_src); + if (exchange.get() == 0) { + text = "Exchange not found"; + return Manageable::STATUS_USER; + } + if (!exchange->supportsDynamicBinding()) { + text = "Exchange type does not support dynamic routing"; + return Manageable::STATUS_USER; + } + } + + std::pair<Bridge::shared_ptr, bool> result = + links->declare (host, port, iargs.i_durable, iargs.i_src, + iargs.i_dest, iargs.i_key, iargs.i_srcIsQueue, + iargs.i_srcIsLocal, iargs.i_tag, iargs.i_excludes, + iargs.i_dynamic, iargs.i_sync); + + if (result.second && iargs.i_durable) + store->create(*result.first); + + return Manageable::STATUS_OK; + } + + return Manageable::STATUS_UNKNOWN_METHOD; +} + +void Link::setPassive(bool passive) +{ + Mutex::ScopedLock mutex(lock); + if (passive) { + setStateLH(STATE_PASSIVE); + } else { + if (state == STATE_PASSIVE) { + setStateLH(STATE_WAITING); + } else { + QPID_LOG(warning, "Ignoring attempt to activate non-passive link"); + } + } +} diff --git a/qpid/cpp/src/qpid/broker/Link.h b/qpid/cpp/src/qpid/broker/Link.h new file mode 100644 index 0000000000..4badd8b3a1 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/Link.h @@ -0,0 +1,145 @@ +#ifndef _broker_Link_h +#define _broker_Link_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 <boost/shared_ptr.hpp> +#include "qpid/broker/MessageStore.h" +#include "qpid/broker/PersistableConfig.h" +#include "qpid/broker/Bridge.h" +#include "qpid/broker/RetryList.h" +#include "qpid/sys/Mutex.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/management/Manageable.h" +#include "qpid/management/ManagementAgent.h" +#include "qmf/org/apache/qpid/broker/Link.h" +#include <boost/ptr_container/ptr_vector.hpp> + +namespace qpid { + namespace broker { + + class LinkRegistry; + class Broker; + class Connection; + + class Link : public PersistableConfig, public management::Manageable { + private: + sys::Mutex lock; + LinkRegistry* links; + MessageStore* store; + std::string host; + uint16_t port; + std::string transport; + bool durable; + std::string authMechanism; + std::string username; + std::string password; + mutable uint64_t persistenceId; + qmf::org::apache::qpid::broker::Link* mgmtObject; + Broker* broker; + int state; + uint32_t visitCount; + uint32_t currentInterval; + bool closing; + RetryList urls; + bool updateUrls; + + typedef std::vector<Bridge::shared_ptr> Bridges; + Bridges created; // Bridges pending creation + Bridges active; // Bridges active + Bridges cancellations; // Bridges pending cancellation + uint channelCounter; + Connection* connection; + management::ManagementAgent* agent; + + static const int STATE_WAITING = 1; + static const int STATE_CONNECTING = 2; + static const int STATE_OPERATIONAL = 3; + static const int STATE_FAILED = 4; + static const int STATE_CLOSED = 5; + static const int STATE_PASSIVE = 6; + + static const uint32_t MAX_INTERVAL = 32; + + void setStateLH (int newState); + void startConnectionLH(); // Start the IO Connection + 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; + + Link(LinkRegistry* links, + MessageStore* store, + std::string& host, + uint16_t port, + std::string& transport, + bool durable, + std::string& authMechanism, + std::string& username, + std::string& password, + Broker* broker, + management::Manageable* parent = 0); + virtual ~Link(); + + std::string getHost() { return host; } + uint16_t getPort() { return port; } + bool isDurable() { return durable; } + void maintenanceVisit (); + uint nextChannel(); + void add(Bridge::shared_ptr); + void cancel(Bridge::shared_ptr); + + void established(); // Called when connection is created + void closed(int, std::string); // Called when connection goes away + void setConnection(Connection*); // Set pointer to the AMQP Connection + void reconnect(const Address&); //called by LinkRegistry + + std::string getAuthMechanism() { return authMechanism; } + std::string getUsername() { return username; } + std::string getPassword() { return password; } + Broker* getBroker() { return broker; } + + 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; + const std::string& getName() const; + + static Link::shared_ptr decode(LinkRegistry& links, framing::Buffer& buffer); + + // Manageable entry points + management::ManagementObject* GetManagementObject(void) const; + management::Manageable::status_t ManagementMethod(uint32_t, management::Args&, std::string&); + + }; + } +} + + +#endif /*!_broker_Link.cpp_h*/ diff --git a/qpid/cpp/src/qpid/broker/LinkRegistry.cpp b/qpid/cpp/src/qpid/broker/LinkRegistry.cpp new file mode 100644 index 0000000000..e9885f5462 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/LinkRegistry.cpp @@ -0,0 +1,399 @@ +/* + * + * 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/LinkRegistry.h" +#include "qpid/broker/Link.h" +#include "qpid/broker/Connection.h" +#include "qpid/log/Statement.h" +#include <iostream> +#include <boost/format.hpp> + +using namespace qpid::broker; +using namespace qpid::sys; +using std::string; +using std::pair; +using std::stringstream; +using boost::intrusive_ptr; +using boost::format; +using boost::str; +namespace _qmf = qmf::org::apache::qpid::broker; + +#define LINK_MAINT_INTERVAL 2 + +// TODO: This constructor is only used by the store unit tests - +// That probably indicates that LinkRegistry isn't correctly +// factored: The persistence element and maintenance element +// should be factored separately +LinkRegistry::LinkRegistry () : + broker(0), timer(0), + parent(0), store(0), passive(false), passiveChanged(false), + realm("") +{ +} + +LinkRegistry::LinkRegistry (Broker* _broker) : + broker(_broker), timer(&broker->getTimer()), + maintenanceTask(new Periodic(*this)), + parent(0), store(0), passive(false), passiveChanged(false), + realm(broker->getOptions().realm) +{ + timer->add(maintenanceTask); +} + +LinkRegistry::~LinkRegistry() +{ + // This test is only necessary if the default constructor above is present + if (maintenanceTask) + maintenanceTask->cancel(); +} + +LinkRegistry::Periodic::Periodic (LinkRegistry& _links) : + TimerTask (Duration (LINK_MAINT_INTERVAL * TIME_SEC),"LinkRegistry"), links(_links) {} + +void LinkRegistry::Periodic::fire () +{ + links.periodicMaintenance (); + setupNextFire(); + links.timer->add(this); +} + +void LinkRegistry::periodicMaintenance () +{ + Mutex::ScopedLock locker(lock); + + linksToDestroy.clear(); + bridgesToDestroy.clear(); + if (passiveChanged) { + if (passive) { QPID_LOG(info, "Passivating links"); } + else { QPID_LOG(info, "Activating links"); } + for (LinkMap::iterator i = links.begin(); i != links.end(); i++) { + i->second->setPassive(passive); + } + passiveChanged = false; + } + for (LinkMap::iterator i = links.begin(); i != links.end(); i++) + i->second->maintenanceVisit(); + //now process any requests for re-addressing + for (AddressMap::iterator i = reMappings.begin(); i != reMappings.end(); i++) + updateAddress(i->first, i->second); + reMappings.clear(); +} + +void LinkRegistry::changeAddress(const qpid::Address& oldAddress, const qpid::Address& newAddress) +{ + //done on periodic maintenance thread; hold changes in separate + //map to avoid modifying the link map that is iterated over + reMappings[createKey(oldAddress)] = newAddress; +} + +bool LinkRegistry::updateAddress(const std::string& oldKey, const qpid::Address& newAddress) +{ + std::string newKey = createKey(newAddress); + if (links.find(newKey) != links.end()) { + QPID_LOG(error, "Attempted to update key from " << oldKey << " to " << newKey << " which is already in use"); + return false; + } else { + LinkMap::iterator i = links.find(oldKey); + if (i == links.end()) { + QPID_LOG(error, "Attempted to update key from " << oldKey << " which does not exist, to " << newKey); + return false; + } else { + links[newKey] = i->second; + i->second->reconnect(newAddress); + links.erase(oldKey); + QPID_LOG(info, "Updated link key from " << oldKey << " to " << newKey); + return true; + } + } +} + +pair<Link::shared_ptr, bool> LinkRegistry::declare(string& host, + uint16_t port, + string& transport, + bool durable, + string& authMechanism, + string& username, + string& password) + +{ + Mutex::ScopedLock locker(lock); + string key = createKey(host, port); + + LinkMap::iterator i = links.find(key); + if (i == links.end()) + { + Link::shared_ptr link; + + link = Link::shared_ptr (new Link (this, store, host, port, transport, durable, + authMechanism, username, password, + broker, parent)); + if (passive) link->setPassive(true); + links[key] = link; + return std::pair<Link::shared_ptr, bool>(link, true); + } + return std::pair<Link::shared_ptr, bool>(i->second, false); +} + +pair<Bridge::shared_ptr, bool> LinkRegistry::declare(std::string& host, + uint16_t port, + bool durable, + std::string& src, + std::string& dest, + std::string& key, + bool isQueue, + bool isLocal, + std::string& tag, + std::string& excludes, + bool dynamic, + uint16_t sync) +{ + Mutex::ScopedLock locker(lock); + QPID_LOG(debug, "Bridge declared " << host << ": " << port << " from " << src << " to " << dest << " (" << key << ")"); + + string linkKey = createKey(host, port); + stringstream keystream; + keystream << linkKey << "!" << src << "!" << dest << "!" << key; + string bridgeKey = keystream.str(); + + LinkMap::iterator l = links.find(linkKey); + if (l == links.end()) + return pair<Bridge::shared_ptr, bool>(Bridge::shared_ptr(), false); + + BridgeMap::iterator b = bridges.find(bridgeKey); + if (b == bridges.end()) + { + _qmf::ArgsLinkBridge args; + Bridge::shared_ptr bridge; + + args.i_durable = durable; + args.i_src = src; + args.i_dest = dest; + args.i_key = key; + args.i_srcIsQueue = isQueue; + args.i_srcIsLocal = isLocal; + args.i_tag = tag; + args.i_excludes = excludes; + args.i_dynamic = dynamic; + args.i_sync = sync; + + bridge = Bridge::shared_ptr + (new Bridge (l->second.get(), l->second->nextChannel(), + boost::bind(&LinkRegistry::destroy, this, + host, port, src, dest, key), args)); + bridges[bridgeKey] = bridge; + l->second->add(bridge); + return std::pair<Bridge::shared_ptr, bool>(bridge, true); + } + return std::pair<Bridge::shared_ptr, bool>(b->second, false); +} + +void LinkRegistry::destroy(const string& host, const uint16_t port) +{ + Mutex::ScopedLock locker(lock); + string key = createKey(host, port); + + LinkMap::iterator i = links.find(key); + if (i != links.end()) + { + if (i->second->isDurable() && store) + store->destroy(*(i->second)); + linksToDestroy[key] = i->second; + links.erase(i); + } +} + +void LinkRegistry::destroy(const std::string& host, + const uint16_t port, + const std::string& src, + const std::string& dest, + const std::string& key) +{ + Mutex::ScopedLock locker(lock); + string linkKey = createKey(host, port); + stringstream keystream; + keystream << linkKey << "!" << src << "!" << dest << "!" << key; + string bridgeKey = keystream.str(); + + LinkMap::iterator l = links.find(linkKey); + if (l == links.end()) + return; + + BridgeMap::iterator b = bridges.find(bridgeKey); + if (b == bridges.end()) + return; + + l->second->cancel(b->second); + if (b->second->isDurable()) + store->destroy(*(b->second)); + bridgesToDestroy[bridgeKey] = b->second; + bridges.erase(b); +} + +void LinkRegistry::setStore (MessageStore* _store) +{ + store = _store; +} + +MessageStore* LinkRegistry::getStore() const { + return store; +} + +Link::shared_ptr LinkRegistry::findLink(const std::string& keyOrMgmtId) +{ + // Convert keyOrMgmtId to a host:port key. + // + // TODO aconway 2011-02-01: centralize code that constructs/parses + // connection management IDs. Currently sys:: protocol factories + // and IO plugins construct the IDs and LinkRegistry parses them. + size_t separator = keyOrMgmtId.find('-'); + if (separator == std::string::npos) separator = 0; + std::string key = keyOrMgmtId.substr(separator+1, std::string::npos); + + Mutex::ScopedLock locker(lock); + LinkMap::iterator l = links.find(key); + if (l != links.end()) return l->second; + else return Link::shared_ptr(); +} + +void LinkRegistry::notifyConnection(const std::string& key, Connection* c) +{ + Link::shared_ptr link = findLink(key); + if (link) { + link->established(); + link->setConnection(c); + c->setUserId(str(format("%1%@%2%") % link->getUsername() % realm)); + } +} + +void LinkRegistry::notifyClosed(const std::string& key) +{ + Link::shared_ptr link = findLink(key); + if (link) { + link->closed(0, "Closed by peer"); + } +} + +void LinkRegistry::notifyConnectionForced(const std::string& key, const std::string& text) +{ + Link::shared_ptr link = findLink(key); + if (link) { + link->notifyConnectionForced(text); + } +} + +std::string LinkRegistry::getAuthMechanism(const std::string& key) +{ + Link::shared_ptr link = findLink(key); + if (link) + return link->getAuthMechanism(); + return string("ANONYMOUS"); +} + +std::string LinkRegistry::getAuthCredentials(const std::string& key) +{ + Link::shared_ptr link = findLink(key); + if (!link) + return string(); + + string result; + result += '\0'; + result += link->getUsername(); + result += '\0'; + result += link->getPassword(); + + return result; +} + +std::string LinkRegistry::getUsername(const std::string& key) +{ + Link::shared_ptr link = findLink(key); + if (!link) + return string(); + + return link->getUsername(); +} + +std::string LinkRegistry::getHost(const std::string& key) +{ + Link::shared_ptr link = findLink(key); + if (!link) + return string(); + + return link->getHost(); +} + +uint16_t LinkRegistry::getPort(const std::string& key) +{ + Link::shared_ptr link = findLink(key); + if (!link) + return 0; + + return link->getPort(); +} + +std::string LinkRegistry::getPassword(const std::string& key) +{ + Link::shared_ptr link = findLink(key); + if (!link) + return string(); + + return link->getPassword(); +} + +std::string LinkRegistry::getAuthIdentity(const std::string& key) +{ + Link::shared_ptr link = findLink(key); + if (!link) + return string(); + + return link->getUsername(); +} + + +std::string LinkRegistry::createKey(const qpid::Address& a) { + // TODO aconway 2010-05-11: key should also include protocol/transport to + // be unique. Requires refactor of LinkRegistry interface. + return createKey(a.host, a.port); +} + +std::string LinkRegistry::createKey(const std::string& host, uint16_t port) { + // TODO aconway 2010-05-11: key should also include protocol/transport to + // be unique. Requires refactor of LinkRegistry interface. + stringstream keystream; + keystream << host << ":" << port; + return keystream.str(); +} + +void LinkRegistry::setPassive(bool p) +{ + Mutex::ScopedLock locker(lock); + passiveChanged = p != passive; + passive = p; + //will activate or passivate links on maintenance visit +} + +void LinkRegistry::eachLink(boost::function<void(boost::shared_ptr<Link>)> f) { + for (LinkMap::iterator i = links.begin(); i != links.end(); ++i) f(i->second); +} + +void LinkRegistry::eachBridge(boost::function<void(boost::shared_ptr<Bridge>)> f) { + for (BridgeMap::iterator i = bridges.begin(); i != bridges.end(); ++i) f(i->second); +} + diff --git a/qpid/cpp/src/qpid/broker/LinkRegistry.h b/qpid/cpp/src/qpid/broker/LinkRegistry.h new file mode 100644 index 0000000000..4c97e4f9d8 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/LinkRegistry.h @@ -0,0 +1,163 @@ +#ifndef _broker_LinkRegistry_h +#define _broker_LinkRegistry_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 <map> +#include "qpid/broker/Bridge.h" +#include "qpid/broker/MessageStore.h" +#include "qpid/Address.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Timer.h" +#include "qpid/management/Manageable.h" +#include <boost/shared_ptr.hpp> +#include <boost/intrusive_ptr.hpp> +#include <boost/function.hpp> + +namespace qpid { +namespace broker { + + class Link; + class Broker; + class Connection; + class LinkRegistry { + + // Declare a timer task to manage the establishment of link connections and the + // re-establishment of lost link connections. + struct Periodic : public sys::TimerTask + { + LinkRegistry& links; + + Periodic(LinkRegistry& links); + virtual ~Periodic() {}; + void fire(); + }; + + typedef std::map<std::string, boost::shared_ptr<Link> > LinkMap; + typedef std::map<std::string, Bridge::shared_ptr> BridgeMap; + typedef std::map<std::string, Address> AddressMap; + + LinkMap links; + LinkMap linksToDestroy; + BridgeMap bridges; + BridgeMap bridgesToDestroy; + AddressMap reMappings; + + qpid::sys::Mutex lock; + Broker* broker; + sys::Timer* timer; + boost::intrusive_ptr<qpid::sys::TimerTask> maintenanceTask; + management::Manageable* parent; + MessageStore* store; + bool passive; + bool passiveChanged; + std::string realm; + + void periodicMaintenance (); + bool updateAddress(const std::string& oldKey, const Address& newAddress); + boost::shared_ptr<Link> findLink(const std::string& key); + static std::string createKey(const Address& address); + static std::string createKey(const std::string& host, uint16_t port); + + public: + LinkRegistry (); // Only used in store tests + LinkRegistry (Broker* _broker); + ~LinkRegistry(); + + std::pair<boost::shared_ptr<Link>, bool> + declare(std::string& host, + uint16_t port, + std::string& transport, + bool durable, + std::string& authMechanism, + std::string& username, + std::string& password); + std::pair<Bridge::shared_ptr, bool> + declare(std::string& host, + uint16_t port, + bool durable, + std::string& src, + std::string& dest, + std::string& key, + bool isQueue, + bool isLocal, + std::string& id, + std::string& excludes, + bool dynamic, + uint16_t sync); + + void destroy(const std::string& host, const uint16_t port); + void destroy(const std::string& host, + const uint16_t port, + const std::string& src, + const std::string& dest, + const std::string& key); + + /** + * Register the manageable parent for declared queues + */ + void setParent (management::Manageable* _parent) { parent = _parent; } + + /** + * Set the store to use. May only be called once. + */ + void setStore (MessageStore*); + + /** + * Return the message store used. + */ + MessageStore* getStore() const; + + void notifyConnection (const std::string& key, Connection* c); + void notifyClosed (const std::string& key); + void notifyConnectionForced (const std::string& key, const std::string& text); + std::string getAuthMechanism (const std::string& key); + std::string getAuthCredentials (const std::string& key); + std::string getAuthIdentity (const std::string& key); + std::string getUsername (const std::string& key); + std::string getPassword (const std::string& key); + std::string getHost (const std::string& key); + uint16_t getPort (const std::string& key); + + /** + * Called by links failing over to new address + */ + void changeAddress(const Address& oldAddress, const Address& newAddress); + /** + * Called to alter passive state. In passive state the links + * and bridges managed by a link registry will be recorded and + * updated but links won't actually establish connections and + * bridges won't therefore pull or push any messages. + */ + void setPassive(bool); + + + /** Iterate over each link in the registry. Used for cluster updates. */ + void eachLink(boost::function<void(boost::shared_ptr<Link>)> f); + /** Iterate over each bridge in the registry. Used for cluster updates. */ + void eachBridge(boost::function<void(boost::shared_ptr< Bridge>)> f); + }; +} +} + + +#endif /*!_broker_LinkRegistry_h*/ diff --git a/qpid/cpp/src/qpid/broker/Message.cpp b/qpid/cpp/src/qpid/broker/Message.cpp new file mode 100644 index 0000000000..763dc55e40 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/Message.cpp @@ -0,0 +1,452 @@ +/* + * + * 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/Message.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/ExchangeRegistry.h" +#include "qpid/broker/ExpiryPolicy.h" +#include "qpid/StringUtils.h" +#include "qpid/framing/frame_functors.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/SendContent.h" +#include "qpid/framing/SequenceNumber.h" +#include "qpid/framing/TypeFilter.h" +#include "qpid/log/Statement.h" + +#include <time.h> + +using boost::intrusive_ptr; +using qpid::sys::AbsTime; +using qpid::sys::Duration; +using qpid::sys::TIME_MSEC; +using qpid::sys::FAR_FUTURE; +using std::string; +using namespace qpid::framing; + +namespace qpid { +namespace broker { + +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), dequeueCallback(0), + inCallback(false), requiredCredit(0), isManagementMessage(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), dequeueCallback(0), + inCallback(false), requiredCredit(0) +{ + setExpiryPolicy(original.expiryPolicy); +} + +Message::~Message() +{ + if (expiryPolicy) + expiryPolicy->forget(*this); +} + +void Message::forcePersistent() +{ + // only set forced bit if we actually need to force. + if (! getAdapter().isPersistent(frames) ){ + forcePersistentPolicy = true; + } +} + +bool Message::isForcedPersistent() +{ + return forcePersistentPolicy; +} + +std::string Message::getRoutingKey() const +{ + return getAdapter().getRoutingKey(frames); +} + +std::string Message::getExchangeName() const +{ + return getAdapter().getExchange(frames); +} + +const boost::shared_ptr<Exchange> Message::getExchange(ExchangeRegistry& registry) const +{ + if (!exchange) { + exchange = registry.get(getExchangeName()); + } + return exchange; +} + +bool Message::isImmediate() const +{ + return getAdapter().isImmediate(frames); +} + +const FieldTable* Message::getApplicationHeaders() const +{ + return getAdapter().getApplicationHeaders(frames); +} + +std::string Message::getAppId() const +{ + return getAdapter().getAppId(frames); +} + +bool Message::isPersistent() const +{ + return (getAdapter().isPersistent(frames) || forcePersistentPolicy); +} + +bool Message::requiresAccept() +{ + return getAdapter().requiresAccept(frames); +} + +uint32_t Message::getRequiredCredit() +{ + sys::Mutex::ScopedLock l(lock); + if (!requiredCredit) { + //add up payload for all header and content frames in the frameset + SumBodySize sum; + frames.map_if(sum, TypeFilter2<HEADER_BODY, CONTENT_BODY>()); + requiredCredit = sum.getSize(); + } + return requiredCredit; +} + +void Message::encode(framing::Buffer& buffer) const +{ + //encode method and header frames + EncodeFrame f1(buffer); + frames.map_if(f1, TypeFilter2<METHOD_BODY, HEADER_BODY>()); + + //then encode the payload of each content frame + framing::EncodeBody f2(buffer); + frames.map_if(f2, TypeFilter<CONTENT_BODY>()); +} + +void Message::encodeContent(framing::Buffer& buffer) const +{ + //encode the payload of each content frame + EncodeBody f2(buffer); + frames.map_if(f2, TypeFilter<CONTENT_BODY>()); +} + +uint32_t Message::encodedSize() const +{ + return encodedHeaderSize() + encodedContentSize(); +} + +uint32_t Message::encodedContentSize() const +{ + return frames.getContentSize(); +} + +uint32_t Message::encodedHeaderSize() const +{ + //add up the size for all method and header frames in the frameset + SumFrameSize sum; + frames.map_if(sum, TypeFilter2<METHOD_BODY, HEADER_BODY>()); + return sum.getSize(); +} + +void Message::decodeHeader(framing::Buffer& buffer) +{ + AMQFrame method; + method.decode(buffer); + frames.append(method); + + AMQFrame header; + header.decode(buffer); + frames.append(header); +} + +void Message::decodeContent(framing::Buffer& buffer) +{ + if (buffer.available()) { + //get the data as a string and set that as the content + //body on a frame then add that frame to the frameset + AMQFrame frame((AMQContentBody())); + frame.castBody<AMQContentBody>()->decode(buffer, buffer.available()); + frame.setFirstSegment(false); + frames.append(frame); + } else { + //adjust header flags + MarkLastSegment f; + frames.map_if(f, TypeFilter<HEADER_BODY>()); + } + //mark content loaded + loaded = true; +} + +// Used for testing only +void Message::tryReleaseContent() +{ + if (checkContentReleasable()) { + releaseContent(); + } +} + +void Message::releaseContent(MessageStore* s) +{ + //deprecated, use setStore(store); releaseContent(); instead + if (!store) setStore(s); + releaseContent(); +} + +void Message::releaseContent() +{ + sys::Mutex::ScopedLock l(lock); + if (store) { + if (!getPersistenceId()) { + intrusive_ptr<PersistableMessage> pmsg(this); + store->stage(pmsg); + staged = true; + } + //ensure required credit is cached before content frames are released + getRequiredCredit(); + //remove any content frames from the frameset + frames.remove(TypeFilter<CONTENT_BODY>()); + setContentReleased(); + } +} + +void Message::destroy() +{ + if (staged) { + if (store) { + store->destroy(*this); + } else { + QPID_LOG(error, "Message content was staged but no store is set so it can't be destroyed"); + } + } +} + +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); + done = data.size() < maxContentSize; + frame.setBof(false); + frame.setEof(true); + QPID_LOG(debug, "loaded frame" << frame); + if (offset > 0) { + frame.setBos(false); + } + if (!done) { + frame.setEos(false); + } else return false; + return true; +} + +void Message::sendContent(const Queue& queue, framing::FrameHandler& out, uint16_t maxFrameSize) const +{ + sys::Mutex::ScopedLock l(lock); + if (isContentReleased() && !frames.isComplete()) { + sys::Mutex::ScopedUnlock u(lock); + 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); + } + } else { + Count c; + frames.map_if(c, TypeFilter<CONTENT_BODY>()); + + SendContent f(out, maxFrameSize, c.getCount()); + frames.map_if(f, TypeFilter<CONTENT_BODY>()); + } +} + +void Message::sendHeader(framing::FrameHandler& out, uint16_t /*maxFrameSize*/) const +{ + sys::Mutex::ScopedLock l(lock); + Relay f(out); + frames.map_if(f, TypeFilter<HEADER_BODY>()); +} + +// TODO aconway 2007-11-09: Obsolete, remove. Was used to cover over +// 0-8/0-9 message differences. +MessageAdapter& Message::getAdapter() const +{ + if (!adapter) { + if(frames.isA<MessageTransferBody>()) { + adapter = &TRANSFER; + } else { + const AMQMethodBody* method = frames.getMethod(); + if (!method) throw Exception("Can't adapt message with no method"); + else throw Exception(QPID_MSG("Can't adapt message based on " << *method)); + } + } + return *adapter; +} + +uint64_t Message::contentSize() const +{ + return frames.getContentSize(); +} + +bool Message::isContentLoaded() const +{ + return loaded; +} + + +namespace +{ +const std::string X_QPID_TRACE("x-qpid.trace"); +} + +bool Message::isExcluded(const std::vector<std::string>& excludes) const +{ + const FieldTable* headers = getApplicationHeaders(); + if (headers) { + std::string traceStr = headers->getAsString(X_QPID_TRACE); + if (traceStr.size()) { + std::vector<std::string> trace = split(traceStr, ", "); + + for (std::vector<std::string>::const_iterator i = excludes.begin(); i != excludes.end(); i++) { + for (std::vector<std::string>::const_iterator j = trace.begin(); j != trace.end(); j++) { + if (*i == *j) { + return true; + } + } + } + } + } + return false; +} + +void Message::addTraceId(const std::string& id) +{ + sys::Mutex::ScopedLock l(lock); + if (isA<MessageTransferBody>()) { + FieldTable& headers = getProperties<MessageProperties>()->getApplicationHeaders(); + std::string trace = headers.getAsString(X_QPID_TRACE); + if (trace.empty()) { + headers.setString(X_QPID_TRACE, id); + } else if (trace.find(id) == std::string::npos) { + trace += ","; + trace += id; + headers.setString(X_QPID_TRACE, trace); + } + } +} + +void Message::setTimestamp(const boost::intrusive_ptr<ExpiryPolicy>& e) +{ + DeliveryProperties* props = getProperties<DeliveryProperties>(); + if (props->getTtl()) { + // AMQP requires setting the expiration property to be posix + // time_t in seconds. TTL is in milliseconds + if (!props->getExpiration()) { + //only set expiration in delivery properties if not already set + time_t now = ::time(0); + props->setExpiration(now + (props->getTtl()/1000)); + } + // Use higher resolution time for the internal expiry calculation. + Duration ttl(std::min(props->getTtl() * TIME_MSEC, (uint64_t) std::numeric_limits<int64_t>::max()));//Prevent overflow + expiration = AbsTime(AbsTime::now(), ttl); + setExpiryPolicy(e); + } +} + +void Message::adjustTtl() +{ + DeliveryProperties* props = getProperties<DeliveryProperties>(); + if (props->getTtl()) { + sys::Mutex::ScopedLock l(lock); + if (expiration < FAR_FUTURE) { + 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 + } + } +} + +void Message::setExpiryPolicy(const boost::intrusive_ptr<ExpiryPolicy>& e) { + expiryPolicy = e; + if (expiryPolicy) + expiryPolicy->willExpire(*this); +} + +bool Message::hasExpired() +{ + return expiryPolicy && expiryPolicy->hasExpired(*this); +} + +namespace { +struct ScopedSet { + sys::Monitor& lock; + bool& flag; + ScopedSet(sys::Monitor& l, bool& f) : lock(l), flag(f) { + sys::Monitor::ScopedLock sl(lock); + flag = true; + } + ~ScopedSet(){ + sys::Monitor::ScopedLock sl(lock); + flag = false; + lock.notifyAll(); + } +}; +} + +void Message::allDequeuesComplete() { + ScopedSet ss(callbackLock, inCallback); + MessageCallback* cb = dequeueCallback; + if (cb && *cb) (*cb)(intrusive_ptr<Message>(this)); +} + +void Message::setDequeueCompleteCallback(MessageCallback& cb) { + sys::Mutex::ScopedLock l(callbackLock); + while (inCallback) callbackLock.wait(); + dequeueCallback = &cb; +} + +void Message::resetDequeueCompleteCallback() { + sys::Mutex::ScopedLock l(callbackLock); + while (inCallback) callbackLock.wait(); + dequeueCallback = 0; +} + +uint8_t Message::getPriority() const { + 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/qpid/cpp/src/qpid/broker/Message.h b/qpid/cpp/src/qpid/broker/Message.h new file mode 100644 index 0000000000..d85ee434db --- /dev/null +++ b/qpid/cpp/src/qpid/broker/Message.h @@ -0,0 +1,196 @@ +#ifndef _broker_Message_h +#define _broker_Message_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/BrokerImportExport.h" +#include "qpid/broker/PersistableMessage.h" +#include "qpid/broker/MessageAdapter.h" +#include "qpid/framing/amqp_types.h" +#include "qpid/sys/Monitor.h" +#include "qpid/sys/Time.h" +#include <boost/function.hpp> +#include <boost/shared_ptr.hpp> +#include <string> +#include <vector> + +namespace qpid { + +namespace framing { +class FieldTable; +class SequenceNumber; +} + +namespace broker { +class ConnectionToken; +class Exchange; +class ExchangeRegistry; +class MessageStore; +class Queue; +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; } + + bool getRedelivered() const { return redelivered; } + void redeliver() { redelivered = true; } + + const ConnectionToken* getPublisher() const { return publisher; } + void setPublisher(ConnectionToken* p) { publisher = p; } + + const framing::SequenceNumber& getCommandId() { return frames.getId(); } + + QPID_BROKER_EXTERN uint64_t contentSize() const; + + QPID_BROKER_EXTERN std::string getRoutingKey() const; + const boost::shared_ptr<Exchange> getExchange(ExchangeRegistry&) const; + QPID_BROKER_EXTERN std::string getExchangeName() const; + 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); + void setExpiryPolicy(const boost::intrusive_ptr<ExpiryPolicy>& e); + bool hasExpired(); + sys::AbsTime getExpiration() const { return expiration; } + 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); + } + + template <class T> const T* getProperties() const { + const qpid::framing::AMQHeaderBody* p = frames.getHeaders(); + return p->get<T>(true); + } + + template <class T> const T* hasProperties() const { + const qpid::framing::AMQHeaderBody* p = frames.getHeaders(); + return p->get<T>(); + } + + template <class T> const T* getMethod() const { + return frames.as<T>(); + } + + template <class T> T* getMethod() { + return frames.as<T>(); + } + + template <class T> bool isA() const { + return frames.isA<T>(); + } + + uint32_t getRequiredCredit(); + + void encode(framing::Buffer& buffer) const; + void encodeContent(framing::Buffer& buffer) const; + + /** + * @returns the size of the buffer needed to encode this + * message in its entirety + */ + uint32_t encodedSize() const; + /** + * @returns the size of the buffer needed to encode the + * 'header' of this message (not just the header frame, + * but other meta data e.g.routing key and exchange) + */ + uint32_t encodedHeaderSize() const; + uint32_t encodedContentSize() const; + + 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 + void destroy(); + + bool getContentFrame(const Queue& queue, framing::AMQFrame& frame, uint16_t maxContentSize, uint64_t offset) const; + QPID_BROKER_EXTERN void sendContent(const Queue& queue, framing::FrameHandler& out, uint16_t maxFrameSize) const; + void sendHeader(framing::FrameHandler& out, uint16_t maxFrameSize) const; + + QPID_BROKER_EXTERN bool isContentLoaded() const; + + bool isExcluded(const std::vector<std::string>& excludes) const; + void addTraceId(const std::string& id); + + 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 allDequeuesComplete(); + + mutable sys::Mutex lock; + framing::FrameSet frames; + mutable boost::shared_ptr<Exchange> exchange; + mutable uint64_t persistenceId; + bool redelivered; + bool loaded; + bool staged; + bool forcePersistentPolicy; // used to force message as durable, via a broker policy + ConnectionToken* publisher; + mutable MessageAdapter* adapter; + qpid::sys::AbsTime expiration; + boost::intrusive_ptr<ExpiryPolicy> expiryPolicy; + + static TransferAdapter TRANSFER; + + mutable boost::intrusive_ptr<Message> empty; + + sys::Monitor callbackLock; + MessageCallback* dequeueCallback; + bool inCallback; + + uint32_t requiredCredit; + bool isManagementMessage; +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/MessageAdapter.cpp b/qpid/cpp/src/qpid/broker/MessageAdapter.cpp new file mode 100644 index 0000000000..0eb4a6fa22 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/MessageAdapter.cpp @@ -0,0 +1,81 @@ +/* + * + * 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/MessageAdapter.h" + +#include "qpid/framing/DeliveryProperties.h" +#include "qpid/framing/MessageProperties.h" +#include "qpid/framing/MessageTransferBody.h" + +namespace { + const std::string empty; +} + +namespace qpid { +namespace broker{ + + std::string TransferAdapter::getRoutingKey(const framing::FrameSet& f) + { + const framing::DeliveryProperties* p = f.getHeaders()->get<framing::DeliveryProperties>(); + return p ? p->getRoutingKey() : empty; + } + + std::string TransferAdapter::getExchange(const framing::FrameSet& f) + { + return f.as<framing::MessageTransferBody>()->getDestination(); + } + + bool TransferAdapter::isImmediate(const framing::FrameSet&) + { + //TODO: delete this, immediate is no longer part of the spec + return false; + } + + const framing::FieldTable* TransferAdapter::getApplicationHeaders(const framing::FrameSet& f) + { + const framing::MessageProperties* p = f.getHeaders()->get<framing::MessageProperties>(); + return p ? &(p->getApplicationHeaders()) : 0; + } + + bool TransferAdapter::isPersistent(const framing::FrameSet& f) + { + const framing::DeliveryProperties* p = f.getHeaders()->get<framing::DeliveryProperties>(); + return p && p->getDeliveryMode() == 2; + } + + bool TransferAdapter::requiresAccept(const framing::FrameSet& f) + { + const framing::MessageTransferBody* b = f.as<framing::MessageTransferBody>(); + return b && b->getAcceptMode() == 0/*EXPLICIT == 0*/; + } + + uint8_t TransferAdapter::getPriority(const framing::FrameSet& f) + { + const framing::DeliveryProperties* p = f.getHeaders()->get<framing::DeliveryProperties>(); + return p ? p->getPriority() : 0; + } + + std::string TransferAdapter::getAppId(const framing::FrameSet& f) + { + const framing::MessageProperties* p = f.getHeaders()->get<framing::MessageProperties>(); + return p ? p->getAppId() : empty; + } +}} diff --git a/qpid/cpp/src/qpid/broker/MessageAdapter.h b/qpid/cpp/src/qpid/broker/MessageAdapter.h new file mode 100644 index 0000000000..df50db4063 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/MessageAdapter.h @@ -0,0 +1,62 @@ +#ifndef _broker_MessageAdapter_h +#define _broker_MessageAdapter_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 <string> +#include "qpid/framing/FieldTable.h" +#include "qpid/framing/FrameSet.h" + +namespace qpid { +namespace broker { + +// TODO aconway 2007-11-09: No longer needed, we only have one type of message. +struct MessageAdapter +{ + virtual ~MessageAdapter() {} + + virtual std::string getRoutingKey(const framing::FrameSet& f) = 0; + virtual std::string getExchange(const framing::FrameSet& f) = 0; + virtual bool isImmediate(const framing::FrameSet& f) = 0; + virtual const framing::FieldTable* getApplicationHeaders(const framing::FrameSet& f) = 0; + virtual bool isPersistent(const framing::FrameSet& f) = 0; + virtual bool requiresAccept(const framing::FrameSet& f) = 0; + virtual uint8_t getPriority(const framing::FrameSet& f) = 0; + virtual std::string getAppId(const framing::FrameSet& f) = 0; +}; + +struct TransferAdapter : MessageAdapter +{ + virtual std::string getRoutingKey(const framing::FrameSet& f); + virtual std::string getExchange(const framing::FrameSet& f); + virtual const framing::FieldTable* getApplicationHeaders(const framing::FrameSet& f); + virtual bool isPersistent(const framing::FrameSet& f); + bool isImmediate(const framing::FrameSet&); + bool requiresAccept(const framing::FrameSet& f); + uint8_t getPriority(const framing::FrameSet& f); + virtual std::string getAppId(const framing::FrameSet& f); +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/MessageBuilder.cpp b/qpid/cpp/src/qpid/broker/MessageBuilder.cpp new file mode 100644 index 0000000000..a6d605c296 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/MessageBuilder.cpp @@ -0,0 +1,114 @@ +/* + * + * 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/MessageBuilder.h" + +#include "qpid/broker/Message.h" +#include "qpid/broker/MessageStore.h" +#include "qpid/broker/NullMessageStore.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/reply_exceptions.h" + +using boost::intrusive_ptr; +using namespace qpid::broker; +using namespace qpid::framing; + +namespace +{ + std::string type_str(uint8_t type); + const std::string QPID_MANAGEMENT("qpid.management"); +} + +MessageBuilder::MessageBuilder(MessageStore* const _store) : + state(DORMANT), store(_store) {} + +void MessageBuilder::handle(AMQFrame& frame) +{ + uint8_t type = frame.getBody()->type(); + switch(state) { + case METHOD: + checkType(METHOD_BODY, type); + state = HEADER; + break; + case HEADER: + if (type == CONTENT_BODY) { + //TODO: rethink how to handle non-existent headers(?)... + //didn't get a header: add in a dummy + AMQFrame header((AMQHeaderBody())); + header.setBof(false); + header.setEof(false); + message->getFrames().append(header); + } else if (type != HEADER_BODY) { + throw CommandInvalidException( + QPID_MSG("Invalid frame sequence for message, expected header or content got " + << type_str(type) << ")")); + } + state = CONTENT; + break; + case CONTENT: + checkType(CONTENT_BODY, type); + break; + default: + throw CommandInvalidException(QPID_MSG("Invalid frame sequence for message (state=" << state << ")")); + } + message->getFrames().append(frame); +} + +void MessageBuilder::end() +{ + message = 0; + state = DORMANT; +} + +void MessageBuilder::start(const SequenceNumber& id) +{ + message = intrusive_ptr<Message>(new Message(id)); + message->setStore(store); + state = METHOD; +} + +namespace { + +const std::string HEADER_BODY_S = "HEADER"; +const std::string METHOD_BODY_S = "METHOD"; +const std::string CONTENT_BODY_S = "CONTENT"; +const std::string HEARTBEAT_BODY_S = "HEARTBEAT"; +const std::string UNKNOWN = "unknown"; + +std::string type_str(uint8_t type) +{ + switch(type) { + case METHOD_BODY: return METHOD_BODY_S; + case HEADER_BODY: return HEADER_BODY_S; + case CONTENT_BODY: return CONTENT_BODY_S; + case HEARTBEAT_BODY: return HEARTBEAT_BODY_S; + } + return UNKNOWN; +} + +} + +void MessageBuilder::checkType(uint8_t expected, uint8_t actual) +{ + if (expected != actual) { + throw CommandInvalidException(QPID_MSG("Invalid frame sequence for message (expected " + << type_str(expected) << " got " << type_str(actual) << ")")); + } +} diff --git a/qpid/cpp/src/qpid/broker/MessageBuilder.h b/qpid/cpp/src/qpid/broker/MessageBuilder.h new file mode 100644 index 0000000000..b99b8efee6 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/MessageBuilder.h @@ -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. + * + */ +#ifndef _MessageBuilder_ +#define _MessageBuilder_ + +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/framing/FrameHandler.h" +#include "qpid/framing/SequenceNumber.h" +#include "qpid/RefCounted.h" + +#include <boost/intrusive_ptr.hpp> + +namespace qpid { + namespace broker { + class Message; + class MessageStore; + + 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); + boost::intrusive_ptr<Message> getMessage() { return message; } + QPID_BROKER_EXTERN void start(const framing::SequenceNumber& id); + void end(); + private: + enum State {DORMANT, METHOD, HEADER, CONTENT}; + State state; + boost::intrusive_ptr<Message> message; + MessageStore* const store; + + void checkType(uint8_t expected, uint8_t actual); + }; + } +} + + +#endif + diff --git a/qpid/cpp/src/qpid/broker/MessageDeque.cpp b/qpid/cpp/src/qpid/broker/MessageDeque.cpp new file mode 100644 index 0000000000..24b8f6f895 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/MessageDeque.cpp @@ -0,0 +1,140 @@ +/* + * + * 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/MessageDeque.h" +#include "qpid/broker/QueuedMessage.h" + +namespace qpid { +namespace broker { + +size_t MessageDeque::size() +{ + return messages.size(); +} + +bool MessageDeque::empty() +{ + return messages.empty(); +} + +void MessageDeque::reinsert(const QueuedMessage& message) +{ + messages.insert(lower_bound(messages.begin(), messages.end(), message), message); +} + +MessageDeque::Deque::iterator MessageDeque::seek(const framing::SequenceNumber& position) +{ + if (!messages.empty()) { + QueuedMessage comp; + comp.position = position; + unsigned long diff = position.getValue() - messages.front().position.getValue(); + long maxEnd = diff < messages.size()? diff : messages.size(); + return lower_bound(messages.begin(),messages.begin()+maxEnd,comp); + } else { + return messages.end(); + } +} + +bool MessageDeque::find(const framing::SequenceNumber& position, QueuedMessage& message, bool remove) +{ + Deque::iterator i = seek(position); + if (i != messages.end() && i->position == position) { + message = *i; + if (remove) messages.erase(i); + return true; + } else { + return false; + } +} + +bool MessageDeque::remove(const framing::SequenceNumber& position, QueuedMessage& message) +{ + return find(position, message, true); +} + +bool MessageDeque::find(const framing::SequenceNumber& position, QueuedMessage& message) +{ + return find(position, message, false); +} + +bool MessageDeque::next(const framing::SequenceNumber& position, QueuedMessage& message) +{ + if (messages.empty()) { + return false; + } else if (position < front().position) { + message = front(); + return true; + } else { + Deque::iterator i = seek(position+1); + if (i != messages.end()) { + message = *i; + return true; + } else { + return false; + } + } +} + +QueuedMessage& MessageDeque::front() +{ + return messages.front(); +} + +void MessageDeque::pop() +{ + if (!messages.empty()) { + messages.pop_front(); + } +} + +bool MessageDeque::pop(QueuedMessage& out) +{ + if (messages.empty()) { + return false; + } else { + out = front(); + messages.pop_front(); + return true; + } +} + +bool MessageDeque::push(const QueuedMessage& added, QueuedMessage& /*not needed*/) +{ + messages.push_back(added); + return false;//adding a message never causes one to be removed for deque +} + +void MessageDeque::foreach(Functor f) +{ + std::for_each(messages.begin(), messages.end(), f); +} + +void MessageDeque::removeIf(Predicate p) +{ + for (Deque::iterator i = messages.begin(); i != messages.end();) { + if (p(*i)) { + i = messages.erase(i); + } else { + ++i; + } + } +} + +}} // namespace qpid::broker diff --git a/qpid/cpp/src/qpid/broker/MessageDeque.h b/qpid/cpp/src/qpid/broker/MessageDeque.h new file mode 100644 index 0000000000..0e1aef2986 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/MessageDeque.h @@ -0,0 +1,62 @@ +#ifndef QPID_BROKER_MESSAGEDEQUE_H +#define QPID_BROKER_MESSAGEDEQUE_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/Messages.h" +#include "qpid/broker/QueuedMessage.h" +#include <deque> + +namespace qpid { +namespace broker { + +/** + * Provides the standard FIFO queue behaviour. + */ +class MessageDeque : public Messages +{ + public: + size_t size(); + bool empty(); + + void reinsert(const QueuedMessage&); + bool remove(const framing::SequenceNumber&, QueuedMessage&); + bool find(const framing::SequenceNumber&, QueuedMessage&); + bool next(const framing::SequenceNumber&, QueuedMessage&); + + QueuedMessage& front(); + void pop(); + bool pop(QueuedMessage&); + bool push(const QueuedMessage& added, QueuedMessage& removed); + + void foreach(Functor); + void removeIf(Predicate); + + private: + typedef std::deque<QueuedMessage> Deque; + Deque messages; + + Deque::iterator seek(const framing::SequenceNumber&); + bool find(const framing::SequenceNumber&, QueuedMessage&, bool remove); +}; +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_MESSAGEDEQUE_H*/ diff --git a/qpid/cpp/src/qpid/broker/MessageMap.cpp b/qpid/cpp/src/qpid/broker/MessageMap.cpp new file mode 100644 index 0000000000..39e23df533 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/MessageMap.cpp @@ -0,0 +1,166 @@ +/* + * + * 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/MessageMap.h" +#include "qpid/broker/QueuedMessage.h" + +namespace qpid { +namespace broker { +namespace { +const std::string EMPTY; +} + +std::string MessageMap::getKey(const QueuedMessage& message) +{ + const framing::FieldTable* ft = message.payload->getApplicationHeaders(); + if (ft) return ft->getAsString(key); + else return EMPTY; +} + +size_t MessageMap::size() +{ + return messages.size(); +} + +bool MessageMap::empty() +{ + return messages.empty(); +} + +void MessageMap::reinsert(const QueuedMessage& message) +{ + std::string key = getKey(message); + Index::iterator i = index.find(key); + if (i == index.end()) { + index[key] = message; + messages[message.position] = message; + } //else message has already been replaced +} + +bool MessageMap::remove(const framing::SequenceNumber& position, QueuedMessage& message) +{ + Ordering::iterator i = messages.find(position); + if (i != messages.end()) { + message = i->second; + erase(i); + return true; + } else { + return false; + } +} + +bool MessageMap::find(const framing::SequenceNumber& position, QueuedMessage& message) +{ + Ordering::iterator i = messages.find(position); + if (i != messages.end()) { + message = i->second; + return true; + } else { + return false; + } +} + +bool MessageMap::next(const framing::SequenceNumber& position, QueuedMessage& message) +{ + if (!messages.empty() && position < front().position) { + message = front(); + return true; + } else { + Ordering::iterator i = messages.lower_bound(position+1); + if (i != messages.end()) { + message = i->second; + return true; + } else { + return false; + } + } +} + +QueuedMessage& MessageMap::front() +{ + return messages.begin()->second; +} + +void MessageMap::pop() +{ + QueuedMessage dummy; + pop(dummy); +} + +bool MessageMap::pop(QueuedMessage& out) +{ + Ordering::iterator i = messages.begin(); + if (i != messages.end()) { + out = i->second; + erase(i); + return true; + } else { + return false; + } +} + +const QueuedMessage& MessageMap::replace(const QueuedMessage& original, const QueuedMessage& update) +{ + messages.erase(original.position); + messages[update.position] = update; + return update; +} + +bool MessageMap::push(const QueuedMessage& added, QueuedMessage& removed) +{ + std::pair<Index::iterator, bool> result = index.insert(Index::value_type(getKey(added), added)); + if (result.second) { + //there was no previous message for this key; nothing needs to + //be removed, just add the message into its correct position + messages[added.position] = added; + return false; + } else { + //there is already a message with that key which needs to be replaced + removed = result.first->second; + result.first->second = replace(result.first->second, added); + return true; + } +} + +void MessageMap::foreach(Functor f) +{ + for (Ordering::iterator i = messages.begin(); i != messages.end(); ++i) { + f(i->second); + } +} + +void MessageMap::removeIf(Predicate p) +{ + for (Ordering::iterator i = messages.begin(); i != messages.end(); i++) { + if (p(i->second)) { + erase(i); + } + } +} + +void MessageMap::erase(Ordering::iterator i) +{ + index.erase(getKey(i->second)); + messages.erase(i); +} + +MessageMap::MessageMap(const std::string& k) : key(k) {} + +}} // namespace qpid::broker diff --git a/qpid/cpp/src/qpid/broker/MessageMap.h b/qpid/cpp/src/qpid/broker/MessageMap.h new file mode 100644 index 0000000000..1128a1d54a --- /dev/null +++ b/qpid/cpp/src/qpid/broker/MessageMap.h @@ -0,0 +1,72 @@ +#ifndef QPID_BROKER_MESSAGEMAP_H +#define QPID_BROKER_MESSAGEMAP_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/Messages.h" +#include "qpid/framing/SequenceNumber.h" +#include <map> +#include <string> + +namespace qpid { +namespace broker { + +/** + * Provides a last value queue behaviour, whereby a messages replace + * any previous message with the same value for a defined property + * (i.e. the key). + */ +class MessageMap : public Messages +{ + public: + MessageMap(const std::string& key); + virtual ~MessageMap() {} + + size_t size(); + bool empty(); + + void reinsert(const QueuedMessage&); + virtual bool remove(const framing::SequenceNumber&, QueuedMessage&); + bool find(const framing::SequenceNumber&, QueuedMessage&); + virtual bool next(const framing::SequenceNumber&, QueuedMessage&); + + QueuedMessage& front(); + void pop(); + bool pop(QueuedMessage&); + virtual bool push(const QueuedMessage& added, QueuedMessage& removed); + + void foreach(Functor); + virtual void removeIf(Predicate); + + protected: + typedef std::map<std::string, QueuedMessage> Index; + typedef std::map<framing::SequenceNumber, QueuedMessage> Ordering; + const std::string key; + Index index; + Ordering messages; + + std::string getKey(const QueuedMessage&); + virtual const QueuedMessage& replace(const QueuedMessage&, const QueuedMessage&); + void erase(Ordering::iterator); +}; +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_MESSAGEMAP_H*/ diff --git a/qpid/cpp/src/qpid/broker/MessageStore.h b/qpid/cpp/src/qpid/broker/MessageStore.h new file mode 100644 index 0000000000..ab0225ef6b --- /dev/null +++ b/qpid/cpp/src/qpid/broker/MessageStore.h @@ -0,0 +1,205 @@ +/* + * + * 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 _MessageStore_ +#define _MessageStore_ + +#include "qpid/broker/PersistableExchange.h" +#include "qpid/broker/PersistableMessage.h" +#include "qpid/broker/PersistableQueue.h" +#include "qpid/broker/PersistableConfig.h" +#include "qpid/broker/RecoveryManager.h" +#include "qpid/broker/TransactionalStore.h" +#include "qpid/framing/FieldTable.h" + +#include <qpid/Options.h> + +#include <boost/shared_ptr.hpp> +#include <boost/intrusive_ptr.hpp> + +namespace qpid { +namespace broker { + +/** + * An abstraction of the persistent storage for messages. (In + * all methods, any pointers/references to queues or messages + * are valid only for the duration of the call). + */ +class MessageStore : public TransactionalStore, public Recoverable { + public: + + /** + * If called after initialization but before recovery, will discard the database + * content and reinitialize as though it were a new installation. If the parameter + * saveStoreContent is true, the content of the store will be saved in such a way + * that the truncate can be reversed. This is used when cluster nodes recover and + * must get their content from a cluster sync rather than directly from the store. + * + * @param saveStoreContent If true, will move content of the store to a backup + * location where they may be restored later if needed. It is + * not necessary to save more than one prior version of the + * store. + */ + virtual void truncateInit(const bool saveStoreContent = false) = 0; + + /** + * Record the existence of a durable queue + */ + virtual void create(PersistableQueue& queue, + const framing::FieldTable& args) = 0; + /** + * Destroy a durable queue + */ + virtual void destroy(PersistableQueue& queue) = 0; + + /** + * Record the existence of a durable exchange + */ + virtual void create(const PersistableExchange& exchange, + const framing::FieldTable& args) = 0; + /** + * Destroy a durable exchange + */ + virtual void destroy(const PersistableExchange& exchange) = 0; + + /** + * Record a binding + */ + virtual void bind(const PersistableExchange& exchange, const PersistableQueue& queue, + const std::string& key, const framing::FieldTable& args) = 0; + + /** + * Forget a binding + */ + virtual void unbind(const PersistableExchange& exchange, const PersistableQueue& queue, + const std::string& key, const framing::FieldTable& args) = 0; + + /** + * Record generic durable configuration + */ + virtual void create(const PersistableConfig& config) = 0; + + /** + * Destroy generic durable configuration + */ + virtual void destroy(const PersistableConfig& config) = 0; + + /** + * Stores a messages before it has been enqueued + * (enqueueing automatically stores the message so this is + * only required if storage is required prior to that + * point). If the message has not yet been stored it will + * store the headers as well as any content passed in. A + * persistence id will be set on the message which can be + * used to load the content or to append to it. + */ + virtual void stage(const boost::intrusive_ptr<PersistableMessage>& msg) = 0; + + /** + * Destroys a previously staged message. This only needs + * to be called if the message is never enqueued. (Once + * enqueued, deletion will be automatic when the message + * is dequeued from all queues it was enqueued onto). + */ + virtual void destroy(PersistableMessage& msg) = 0; + + /** + * Appends content to a previously staged message + */ + virtual void appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg, + const std::string& data) = 0; + + /** + * Loads (a section) of content data for the specified + * message (previously stored through a call to stage or + * enqueue) into data. The offset refers to the content + * only (i.e. an offset of 0 implies that the start of the + * content should be loaded, not the headers or related + * meta-data). + */ + virtual void loadContent(const qpid::broker::PersistableQueue& queue, + const boost::intrusive_ptr<const PersistableMessage>& msg, + std::string& data, uint64_t offset, uint32_t length) = 0; + + /** + * Enqueues a message, storing the message if it has not + * been previously stored and recording that the given + * message is on the given queue. + * + * Note: that this is async so the return of the function does + * not mean the opperation is complete. + * + * @param msg the message to enqueue + * @param queue the name of the queue onto which it is to be enqueued + * @param xid (a pointer to) an identifier of the + * distributed transaction in which the operation takes + * place or null for 'local' transactions + */ + virtual void enqueue(TransactionContext* ctxt, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue) = 0; + + /** + * Dequeues a message, recording that the given message is + * no longer on the given queue and deleting the message + * if it is no longer on any other queue. + * + * Note: that this is async so the return of the function does + * not mean the opperation is complete. + * + * @param msg the message to dequeue + * @param queue the name of the queue from which it is to be dequeued + * @param xid (a pointer to) an identifier of the + * distributed transaction in which the operation takes + * place or null for 'local' transactions + */ + virtual void dequeue(TransactionContext* ctxt, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue) = 0; + + /** + * Flushes all async messages to disk for the specified queue + * + * Note: that this is async so the return of the function does + * not mean the opperation is complete. + * + * @param queue the name of the queue from which it is to be dequeued + */ + virtual void flush(const qpid::broker::PersistableQueue& queue)=0; + + /** + * Returns the number of outstanding AIO's for a given queue + * + * If 0, than all the enqueue / dequeues have been stored + * to disk + * + * @param queue the name of the queue to check for outstanding AIO + */ + virtual uint32_t outstandingQueueAIO(const PersistableQueue& queue) = 0; + + + virtual ~MessageStore(){} +}; + +} +} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/MessageStoreModule.cpp b/qpid/cpp/src/qpid/broker/MessageStoreModule.cpp new file mode 100644 index 0000000000..cd9fd4c933 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/MessageStoreModule.cpp @@ -0,0 +1,180 @@ +/* + * + * 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/MessageStoreModule.h" +#include "qpid/broker/NullMessageStore.h" +#include <iostream> + +// This transfer protects against the unloading of the store lib prior to the handling of the exception +#define TRANSFER_EXCEPTION(fn) try { fn; } catch (std::exception& e) { throw Exception(e.what()); } + +using boost::intrusive_ptr; +using qpid::framing::FieldTable; +using std::string; + +namespace qpid { +namespace broker { + +MessageStoreModule::MessageStoreModule(boost::shared_ptr<MessageStore>& _store) + : store(_store) {} + +MessageStoreModule::~MessageStoreModule() +{ +} + +bool MessageStoreModule::init(const Options*) { return true; } + +void MessageStoreModule::truncateInit(const bool pushDownStoreFiles) +{ + TRANSFER_EXCEPTION(store->truncateInit(pushDownStoreFiles)); +} + +void MessageStoreModule::create(PersistableQueue& queue, const FieldTable& args) +{ + TRANSFER_EXCEPTION(store->create(queue, args)); +} + +void MessageStoreModule::destroy(PersistableQueue& queue) +{ + TRANSFER_EXCEPTION(store->destroy(queue)); +} + +void MessageStoreModule::create(const PersistableExchange& exchange, const FieldTable& args) +{ + TRANSFER_EXCEPTION(store->create(exchange, args)); +} + +void MessageStoreModule::destroy(const PersistableExchange& exchange) +{ + TRANSFER_EXCEPTION(store->destroy(exchange)); +} + +void MessageStoreModule::bind(const PersistableExchange& e, const PersistableQueue& q, + const std::string& k, const framing::FieldTable& a) +{ + TRANSFER_EXCEPTION(store->bind(e, q, k, a)); +} + +void MessageStoreModule::unbind(const PersistableExchange& e, const PersistableQueue& q, + const std::string& k, const framing::FieldTable& a) +{ + TRANSFER_EXCEPTION(store->unbind(e, q, k, a)); +} + +void MessageStoreModule::create(const PersistableConfig& config) +{ + TRANSFER_EXCEPTION(store->create(config)); +} + +void MessageStoreModule::destroy(const PersistableConfig& config) +{ + TRANSFER_EXCEPTION(store->destroy(config)); +} + +void MessageStoreModule::recover(RecoveryManager& registry) +{ + TRANSFER_EXCEPTION(store->recover(registry)); +} + +void MessageStoreModule::stage(const intrusive_ptr<PersistableMessage>& msg) +{ + TRANSFER_EXCEPTION(store->stage(msg)); +} + +void MessageStoreModule::destroy(PersistableMessage& msg) +{ + TRANSFER_EXCEPTION(store->destroy(msg)); +} + +void MessageStoreModule::appendContent(const intrusive_ptr<const PersistableMessage>& msg, + const std::string& data) +{ + TRANSFER_EXCEPTION(store->appendContent(msg, data)); +} + +void MessageStoreModule::loadContent( + const qpid::broker::PersistableQueue& queue, + const intrusive_ptr<const PersistableMessage>& msg, + string& data, uint64_t offset, uint32_t length) +{ + TRANSFER_EXCEPTION(store->loadContent(queue, msg, data, offset, length)); +} + +void MessageStoreModule::enqueue(TransactionContext* ctxt, + const intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue) +{ + TRANSFER_EXCEPTION(store->enqueue(ctxt, msg, queue)); +} + +void MessageStoreModule::dequeue(TransactionContext* ctxt, + const intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue) +{ + TRANSFER_EXCEPTION(store->dequeue(ctxt, msg, queue)); +} + +void MessageStoreModule::flush(const qpid::broker::PersistableQueue& queue) +{ + TRANSFER_EXCEPTION(store->flush(queue)); +} + +uint32_t MessageStoreModule::outstandingQueueAIO(const PersistableQueue& queue) +{ + TRANSFER_EXCEPTION(return store->outstandingQueueAIO(queue)); +} + +std::auto_ptr<TransactionContext> MessageStoreModule::begin() +{ + TRANSFER_EXCEPTION(return store->begin()); +} + +std::auto_ptr<TPCTransactionContext> MessageStoreModule::begin(const std::string& xid) +{ + TRANSFER_EXCEPTION(return store->begin(xid)); +} + +void MessageStoreModule::prepare(TPCTransactionContext& txn) +{ + TRANSFER_EXCEPTION(store->prepare(txn)); +} + +void MessageStoreModule::commit(TransactionContext& ctxt) +{ + TRANSFER_EXCEPTION(store->commit(ctxt)); +} + +void MessageStoreModule::abort(TransactionContext& ctxt) +{ + TRANSFER_EXCEPTION(store->abort(ctxt)); +} + +void MessageStoreModule::collectPreparedXids(std::set<std::string>& xids) +{ + TRANSFER_EXCEPTION(store->collectPreparedXids(xids)); +} + +bool MessageStoreModule::isNull() const +{ + return NullMessageStore::isNullStore(store.get()); +} + +}} // namespace qpid::broker diff --git a/qpid/cpp/src/qpid/broker/MessageStoreModule.h b/qpid/cpp/src/qpid/broker/MessageStoreModule.h new file mode 100644 index 0000000000..56b5a3c1ae --- /dev/null +++ b/qpid/cpp/src/qpid/broker/MessageStoreModule.h @@ -0,0 +1,87 @@ +/* + * + * 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 _MessageStoreModule_ +#define _MessageStoreModule_ + +#include "qpid/broker/MessageStore.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/RecoveryManager.h" + +#include <boost/intrusive_ptr.hpp> +#include <boost/shared_ptr.hpp> + +namespace qpid { +namespace broker { + +/** + * A null implementation of the MessageStore interface + */ +class MessageStoreModule : public MessageStore +{ + boost::shared_ptr<MessageStore> store; + public: + MessageStoreModule(boost::shared_ptr<MessageStore>& store); + + bool init(const Options* options); + void truncateInit(const bool pushDownStoreFiles = false); + std::auto_ptr<TransactionContext> begin(); + std::auto_ptr<TPCTransactionContext> begin(const std::string& xid); + void prepare(TPCTransactionContext& txn); + void commit(TransactionContext& txn); + void abort(TransactionContext& txn); + void collectPreparedXids(std::set<std::string>& xids); + + void create(PersistableQueue& queue, const framing::FieldTable& args); + void destroy(PersistableQueue& queue); + void create(const PersistableExchange& exchange, const framing::FieldTable& args); + void destroy(const PersistableExchange& exchange); + void bind(const PersistableExchange& exchange, const PersistableQueue& queue, + const std::string& key, const framing::FieldTable& args); + void unbind(const PersistableExchange& exchange, const PersistableQueue& queue, + const std::string& key, const framing::FieldTable& args); + void create(const PersistableConfig& config); + void destroy(const PersistableConfig& config); + void recover(RecoveryManager& queues); + void stage(const boost::intrusive_ptr<PersistableMessage>& msg); + void destroy(PersistableMessage& msg); + void appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg, const std::string& data); + void loadContent(const qpid::broker::PersistableQueue& queue, + const boost::intrusive_ptr<const PersistableMessage>& msg, std::string& data, + uint64_t offset, uint32_t length); + + void enqueue(TransactionContext* ctxt, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue); + void dequeue(TransactionContext* ctxt, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue); + uint32_t outstandingQueueAIO(const PersistableQueue& queue); + void flush(const qpid::broker::PersistableQueue& queue); + bool isNull() const; + + ~MessageStoreModule(); +}; + +} +} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/Messages.h b/qpid/cpp/src/qpid/broker/Messages.h new file mode 100644 index 0000000000..0d75417640 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/Messages.h @@ -0,0 +1,117 @@ +#ifndef QPID_BROKER_MESSAGES_H +#define QPID_BROKER_MESSAGES_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 <boost/function.hpp> + +namespace qpid { +namespace framing { +class SequenceNumber; +} +namespace broker { +struct QueuedMessage; + +/** + * This interface abstracts out the access to the messages held for + * delivery by a Queue instance. + */ +class Messages +{ + public: + typedef boost::function1<void, QueuedMessage&> Functor; + typedef boost::function1<bool, QueuedMessage&> Predicate; + + virtual ~Messages() {} + /** + * @return the number of messages available for delivery. + */ + virtual size_t size() = 0; + /** + * @return true if there are no messages for delivery, false otherwise + */ + virtual bool empty() = 0; + + /** + * Re-inserts a message back into its original position - used + * when requeing released messages. + */ + virtual void reinsert(const QueuedMessage&) = 0; + /** + * Remove the message at the specified position, returning true if + * found, false otherwise. The removed message is passed back via + * the second parameter. + */ + virtual bool remove(const framing::SequenceNumber&, QueuedMessage&) = 0; + /** + * Find the message at the specified position, returning true if + * found, false otherwise. The matched message is passed back via + * the second parameter. + */ + virtual bool find(const framing::SequenceNumber&, QueuedMessage&) = 0; + /** + * Return the next message to be given to a browsing subscrption + * that has reached the specified poisition. The next messages is + * passed back via the second parameter. + * + * @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) + * + * @return the next message to be delivered + */ + virtual QueuedMessage& front() = 0; + /** + * Removes the front message + */ + virtual void pop() = 0; + /** + * @return true if there is a mesage to be delivered - in which + * case that message will be returned via the parameter and + * removed - otherwise false. + */ + virtual bool pop(QueuedMessage&) = 0; + /** + * Pushes a message to the back of the 'queue'. For some types of + * queue this may cause another message to be removed; if that is + * the case the method will return true and the removed message + * will be passed out via the second parameter. + */ + virtual bool push(const QueuedMessage& added, QueuedMessage& removed) = 0; + + /** + * Apply the functor to each message held + */ + virtual void foreach(Functor) = 0; + /** + * Remove every message held that for which the specified + * predicate returns true + */ + virtual void removeIf(Predicate) = 0; + private: +}; +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_MESSAGES_H*/ diff --git a/qpid/cpp/src/qpid/broker/NameGenerator.cpp b/qpid/cpp/src/qpid/broker/NameGenerator.cpp new file mode 100644 index 0000000000..e7f193d546 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/NameGenerator.cpp @@ -0,0 +1,32 @@ +/* + * + * 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/NameGenerator.h" +#include <sstream> + +using namespace qpid::broker; + +NameGenerator::NameGenerator(const std::string& _base) : base(_base), counter(1) {} + +std::string NameGenerator::generate(){ + std::stringstream ss; + ss << base << counter++; + return ss.str(); +} diff --git a/qpid/cpp/src/qpid/broker/NameGenerator.h b/qpid/cpp/src/qpid/broker/NameGenerator.h new file mode 100644 index 0000000000..6ea25c9797 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/NameGenerator.h @@ -0,0 +1,39 @@ +/* + * + * 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 _NameGenerator_ +#define _NameGenerator_ + +#include <string> + +namespace qpid { + namespace broker { + class NameGenerator{ + const std::string base; + unsigned int counter; + public: + NameGenerator(const std::string& base); + std::string generate(); + }; + } +} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/NullMessageStore.cpp b/qpid/cpp/src/qpid/broker/NullMessageStore.cpp new file mode 100644 index 0000000000..43f600eaf1 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/NullMessageStore.cpp @@ -0,0 +1,167 @@ +/* + * + * 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/NullMessageStore.h" +#include "qpid/broker/MessageStoreModule.h" +#include "qpid/broker/RecoveryManager.h" +#include "qpid/log/Statement.h" +#include "qpid/framing/reply_exceptions.h" + +#include <iostream> + +using boost::intrusive_ptr; + +namespace qpid{ +namespace broker{ + +const std::string nullxid = ""; + +class SimpleDummyCtxt : public TransactionContext {}; + +class DummyCtxt : public TPCTransactionContext +{ + const std::string xid; +public: + DummyCtxt(const std::string& _xid) : xid(_xid) {} + static std::string getXid(TransactionContext& ctxt) + { + DummyCtxt* c(dynamic_cast<DummyCtxt*>(&ctxt)); + return c ? c->xid : nullxid; + } +}; + +NullMessageStore::NullMessageStore() : nextPersistenceId(1) {} + +bool NullMessageStore::init(const Options* /*options*/) {return true;} + +void NullMessageStore::truncateInit(const bool /*pushDownStoreFiles*/) {} + +void NullMessageStore::create(PersistableQueue& queue, const framing::FieldTable& /*args*/) +{ + queue.setPersistenceId(nextPersistenceId++); +} + +void NullMessageStore::destroy(PersistableQueue&) {} + +void NullMessageStore::create(const PersistableExchange& exchange, const framing::FieldTable& /*args*/) +{ + exchange.setPersistenceId(nextPersistenceId++); +} + +void NullMessageStore::destroy(const PersistableExchange& ) {} + +void NullMessageStore::bind(const PersistableExchange&, const PersistableQueue&, const std::string&, const framing::FieldTable&){} + +void NullMessageStore::unbind(const PersistableExchange&, const PersistableQueue&, const std::string&, const framing::FieldTable&){} + +void NullMessageStore::create(const PersistableConfig& config) +{ + config.setPersistenceId(nextPersistenceId++); +} + +void NullMessageStore::destroy(const PersistableConfig&) {} + +void NullMessageStore::recover(RecoveryManager&) {} + +void NullMessageStore::stage(const intrusive_ptr<PersistableMessage>&) {} + +void NullMessageStore::destroy(PersistableMessage&) {} + +void NullMessageStore::appendContent(const intrusive_ptr<const PersistableMessage>&, const std::string&) {} + +void NullMessageStore::loadContent(const qpid::broker::PersistableQueue&, + const intrusive_ptr<const PersistableMessage>&, + std::string&, uint64_t, uint32_t) +{ + throw qpid::framing::InternalErrorException("Can't load content; persistence not enabled"); +} + +void NullMessageStore::enqueue(TransactionContext*, + const intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue&) +{ + msg->enqueueComplete(); +} + +void NullMessageStore::dequeue(TransactionContext*, + const intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue&) +{ + msg->dequeueComplete(); +} + +void NullMessageStore::flush(const qpid::broker::PersistableQueue&) {} + +uint32_t NullMessageStore::outstandingQueueAIO(const PersistableQueue& ) { + return 0; +} + +std::auto_ptr<TransactionContext> NullMessageStore::begin() +{ + return std::auto_ptr<TransactionContext>(new SimpleDummyCtxt()); +} + +std::auto_ptr<TPCTransactionContext> NullMessageStore::begin(const std::string& xid) +{ + return std::auto_ptr<TPCTransactionContext>(new DummyCtxt(xid)); +} + +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()); +} + +bool NullMessageStore::isNull() const +{ + return true; +} + +bool NullMessageStore::isNullStore(const MessageStore* store) +{ + const MessageStoreModule* wrapper = dynamic_cast<const MessageStoreModule*>(store); + if (wrapper) { + return wrapper->isNull(); + } else { + const NullMessageStore* test = dynamic_cast<const NullMessageStore*>(store); + return test && test->isNull(); + } +} + +}} // namespace qpid::broker diff --git a/qpid/cpp/src/qpid/broker/NullMessageStore.h b/qpid/cpp/src/qpid/broker/NullMessageStore.h new file mode 100644 index 0000000000..c6f402662e --- /dev/null +++ b/qpid/cpp/src/qpid/broker/NullMessageStore.h @@ -0,0 +1,100 @@ +/* + * + * 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 _NullMessageStore_ +#define _NullMessageStore_ + +#include <set> +#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> + +namespace qpid { +namespace broker { + +/** + * A null implementation of the MessageStore interface + */ +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(); + + QPID_BROKER_EXTERN virtual bool init(const Options* options); + QPID_BROKER_EXTERN virtual void truncateInit(const bool pushDownStoreFiles = false); + QPID_BROKER_EXTERN virtual std::auto_ptr<TransactionContext> begin(); + QPID_BROKER_EXTERN virtual std::auto_ptr<TPCTransactionContext> begin(const std::string& xid); + QPID_BROKER_EXTERN virtual void prepare(TPCTransactionContext& txn); + QPID_BROKER_EXTERN virtual void commit(TransactionContext& txn); + QPID_BROKER_EXTERN virtual void abort(TransactionContext& txn); + QPID_BROKER_EXTERN virtual void collectPreparedXids(std::set<std::string>& xids); + + QPID_BROKER_EXTERN virtual void create(PersistableQueue& queue, + const framing::FieldTable& args); + QPID_BROKER_EXTERN virtual void destroy(PersistableQueue& queue); + QPID_BROKER_EXTERN virtual void create(const PersistableExchange& exchange, + const framing::FieldTable& args); + QPID_BROKER_EXTERN virtual void destroy(const PersistableExchange& exchange); + + QPID_BROKER_EXTERN virtual void bind(const PersistableExchange& exchange, + const PersistableQueue& queue, + const std::string& key, + const framing::FieldTable& args); + QPID_BROKER_EXTERN virtual void unbind(const PersistableExchange& exchange, + const PersistableQueue& queue, + const std::string& key, + const framing::FieldTable& args); + QPID_BROKER_EXTERN virtual void create(const PersistableConfig& config); + QPID_BROKER_EXTERN virtual void destroy(const PersistableConfig& config); + QPID_BROKER_EXTERN virtual void recover(RecoveryManager& queues); + QPID_BROKER_EXTERN virtual void stage(const boost::intrusive_ptr<PersistableMessage>& msg); + QPID_BROKER_EXTERN virtual void destroy(PersistableMessage& msg); + QPID_BROKER_EXTERN virtual void appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg, + const std::string& data); + QPID_BROKER_EXTERN virtual void loadContent(const qpid::broker::PersistableQueue& queue, + const boost::intrusive_ptr<const PersistableMessage>& msg, + std::string& data, + uint64_t offset, + uint32_t length); + QPID_BROKER_EXTERN virtual void enqueue(TransactionContext* ctxt, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue); + QPID_BROKER_EXTERN virtual void dequeue(TransactionContext* ctxt, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue); + QPID_BROKER_EXTERN virtual uint32_t outstandingQueueAIO(const PersistableQueue& queue); + QPID_BROKER_EXTERN virtual void flush(const qpid::broker::PersistableQueue& queue); + ~NullMessageStore(){} + + QPID_BROKER_EXTERN virtual bool isNull() const; + static bool isNullStore(const MessageStore*); +}; + +} +} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/OwnershipToken.h b/qpid/cpp/src/qpid/broker/OwnershipToken.h new file mode 100644 index 0000000000..effd2f5b3c --- /dev/null +++ b/qpid/cpp/src/qpid/broker/OwnershipToken.h @@ -0,0 +1,38 @@ +/* + * + * 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 _OwnershipToken_ +#define _OwnershipToken_ + +namespace qpid { +namespace broker { + +class ConnectionToken; + +class OwnershipToken{ +public: + virtual bool isLocal(const ConnectionToken* t) const = 0; + virtual ~OwnershipToken(){} +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/Persistable.h b/qpid/cpp/src/qpid/broker/Persistable.h new file mode 100644 index 0000000000..36499c7a1a --- /dev/null +++ b/qpid/cpp/src/qpid/broker/Persistable.h @@ -0,0 +1,63 @@ +#ifndef _broker_Persistable_h +#define _broker_Persistable_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/framing/amqp_types.h" +#include "qpid/framing/Buffer.h" +#include "qpid/RefCounted.h" + +namespace qpid { +namespace broker { + +/** + * Base class for all persistable objects + */ +class Persistable : public RefCounted +{ +public: + /** + * Allows the store to attach its own identifier to this object + */ + virtual void setPersistenceId(uint64_t id) const = 0; + /** + * Returns any identifier the store may have attached to this + * object + */ + virtual uint64_t getPersistenceId() const = 0; + /** + * Encodes the persistable state of this object into the supplied + * buffer + */ + virtual void encode(framing::Buffer& buffer) const = 0; + /** + * @returns the size of the buffer needed to encode this object + */ + virtual uint32_t encodedSize() const = 0; + + virtual ~Persistable() {}; +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/PersistableConfig.h b/qpid/cpp/src/qpid/broker/PersistableConfig.h new file mode 100644 index 0000000000..8ddb84d129 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/PersistableConfig.h @@ -0,0 +1,45 @@ +#ifndef _broker_PersistableConfig_h +#define _broker_PersistableConfig_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 <string> +#include "qpid/broker/Persistable.h" + +namespace qpid { +namespace broker { + +/** + * The interface used by general-purpose persistable configuration for + * the message store. + */ +class PersistableConfig : public Persistable +{ +public: + virtual const std::string& getName() const = 0; + virtual ~PersistableConfig() {}; +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/PersistableExchange.h b/qpid/cpp/src/qpid/broker/PersistableExchange.h new file mode 100644 index 0000000000..e1a0853247 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/PersistableExchange.h @@ -0,0 +1,45 @@ +#ifndef _broker_PersistableExchange_h +#define _broker_PersistableExchange_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 <string> +#include "qpid/broker/Persistable.h" + +namespace qpid { +namespace broker { + +/** + * The interface exchanges must expose to the MessageStore in order to be + * persistable. + */ +class PersistableExchange : public Persistable +{ +public: + virtual const std::string& getName() const = 0; + virtual ~PersistableExchange() {}; +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/PersistableMessage.cpp b/qpid/cpp/src/qpid/broker/PersistableMessage.cpp new file mode 100644 index 0000000000..7ba28eb293 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/PersistableMessage.cpp @@ -0,0 +1,161 @@ +/* + * + * 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/PersistableMessage.h" +#include "qpid/broker/MessageStore.h" +#include <iostream> + +using namespace qpid::broker; + +namespace qpid { +namespace broker { + +class MessageStore; + +PersistableMessage::~PersistableMessage() {} + +PersistableMessage::PersistableMessage() : + asyncDequeueCounter(0), + store(0) +{} + +void PersistableMessage::flush() +{ + syncList copy; + { + sys::ScopedLock<sys::Mutex> l(storeLock); + if (store) { + copy = synclist; + } else { + return;//early exit as nothing to do + } + } + for (syncList::iterator i = copy.begin(); i != copy.end(); ++i) { + PersistableQueue::shared_ptr q(i->lock()); + if (q) { + q->flush(); + } + } +} + +void PersistableMessage::setContentReleased() +{ + contentReleaseState.released = true; +} + +bool PersistableMessage::isContentReleased() const +{ + return contentReleaseState.released; +} + + +bool PersistableMessage::isStoredOnQueue(PersistableQueue::shared_ptr queue){ + if (store && (queue->getPersistenceId()!=0)) { + for (syncList::iterator i = synclist.begin(); i != synclist.end(); ++i) { + PersistableQueue::shared_ptr q(i->lock()); + if (q && q->getPersistenceId() == queue->getPersistenceId()) return true; + } + } + return false; +} + + +void PersistableMessage::addToSyncList(PersistableQueue::shared_ptr queue, MessageStore* _store) { + if (_store){ + sys::ScopedLock<sys::Mutex> l(storeLock); + store = _store; + boost::weak_ptr<PersistableQueue> q(queue); + synclist.push_back(q); + } +} + +void PersistableMessage::enqueueAsync(PersistableQueue::shared_ptr queue, MessageStore* _store) { + addToSyncList(queue, _store); + enqueueStart(); +} + +bool PersistableMessage::isDequeueComplete() { + sys::ScopedLock<sys::Mutex> l(asyncDequeueLock); + return asyncDequeueCounter == 0; +} + +void PersistableMessage::dequeueComplete() { + bool notify = false; + { + sys::ScopedLock<sys::Mutex> l(asyncDequeueLock); + if (asyncDequeueCounter > 0) { + if (--asyncDequeueCounter == 0) { + notify = true; + } + } + } + if (notify) allDequeuesComplete(); +} + +void PersistableMessage::dequeueAsync(PersistableQueue::shared_ptr queue, MessageStore* _store) { + if (_store){ + sys::ScopedLock<sys::Mutex> l(storeLock); + store = _store; + boost::weak_ptr<PersistableQueue> q(queue); + synclist.push_back(q); + } + dequeueAsync(); +} + +void PersistableMessage::dequeueAsync() { + sys::ScopedLock<sys::Mutex> l(asyncDequeueLock); + asyncDequeueCounter++; +} + +PersistableMessage::ContentReleaseState::ContentReleaseState() : blocked(false), requested(false), released(false) {} + +void PersistableMessage::setStore(MessageStore* s) +{ + store = s; +} + +void PersistableMessage::requestContentRelease() +{ + contentReleaseState.requested = true; +} +void PersistableMessage::blockContentRelease() +{ + contentReleaseState.blocked = true; +} +bool PersistableMessage::checkContentReleasable() +{ + return contentReleaseState.requested && !contentReleaseState.blocked; +} + +bool PersistableMessage::isContentReleaseBlocked() +{ + return contentReleaseState.blocked; +} + +bool PersistableMessage::isContentReleaseRequested() +{ + return contentReleaseState.requested; +} + +}} + + diff --git a/qpid/cpp/src/qpid/broker/PersistableMessage.h b/qpid/cpp/src/qpid/broker/PersistableMessage.h new file mode 100644 index 0000000000..d29c2c45b4 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/PersistableMessage.h @@ -0,0 +1,143 @@ +#ifndef _broker_PersistableMessage_h +#define _broker_PersistableMessage_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 <string> +#include <list> +#include <boost/shared_ptr.hpp> +#include <boost/weak_ptr.hpp> +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/Persistable.h" +#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 { + +class MessageStore; + +/** + * Base class for persistable messages. + */ +class PersistableMessage : public Persistable +{ + typedef std::list< boost::weak_ptr<PersistableQueue> > syncList; + sys::Mutex asyncDequeueLock; + sys::Mutex storeLock; + + /** + * "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. + */ + AsyncCompletion ingressCompletion; + + /** + * Tracks the number of outstanding asynchronous dequeue + * operations. When the message is dequeued asynchronously the + * count is incremented; when that dequeue completes it is + * decremented. Thus when it is 0, there are no outstanding + * dequeues. + */ + int asyncDequeueCounter; + + void dequeueAsync(); + + syncList synclist; + struct ContentReleaseState + { + bool blocked; + bool requested; + bool released; + + ContentReleaseState(); + }; + ContentReleaseState contentReleaseState; + + protected: + /** Called when all dequeues are complete for this message. */ + virtual void allDequeuesComplete() = 0; + + void setContentReleased(); + + MessageStore* store; + + + public: + typedef boost::shared_ptr<PersistableMessage> shared_ptr; + + /** + * @returns the size of the headers when encoded + */ + virtual uint32_t encodedHeaderSize() const = 0; + + virtual ~PersistableMessage(); + + PersistableMessage(); + + void flush(); + + QPID_BROKER_EXTERN bool isContentReleased() const; + + QPID_BROKER_EXTERN void setStore(MessageStore*); + void requestContentRelease(); + void blockContentRelease(); + bool checkContentReleasable(); + bool isContentReleaseBlocked(); + bool isContentReleaseRequested(); + + virtual QPID_BROKER_EXTERN bool isPersistent() const = 0; + + /** 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_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); + + + QPID_BROKER_EXTERN bool isDequeueComplete(); + + QPID_BROKER_EXTERN void dequeueComplete(); + + QPID_BROKER_EXTERN void dequeueAsync(PersistableQueue::shared_ptr queue, + MessageStore* _store); + + bool isStoredOnQueue(PersistableQueue::shared_ptr queue); + + void addToSyncList(PersistableQueue::shared_ptr queue, MessageStore* _store); +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/PersistableQueue.h b/qpid/cpp/src/qpid/broker/PersistableQueue.h new file mode 100644 index 0000000000..655d26bc74 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/PersistableQueue.h @@ -0,0 +1,78 @@ +#ifndef _broker_PersistableQueue_h +#define _broker_PersistableQueue_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 <string> +#include "qpid/broker/Persistable.h" +#include "qpid/management/Manageable.h" +#include <boost/shared_ptr.hpp> + +namespace qpid { +namespace broker { + + +/** +* Empty class to be used by any module that wanted to set an external per queue store into +* persistableQueue +*/ + +class ExternalQueueStore : public management::Manageable +{ +public: + virtual ~ExternalQueueStore() {}; + +}; + + +/** + * The interface queues must expose to the MessageStore in order to be + * persistable. + */ +class PersistableQueue : public Persistable +{ +public: + typedef boost::shared_ptr<PersistableQueue> shared_ptr; + + virtual const std::string& getName() const = 0; + virtual ~PersistableQueue() { + if (externalQueueStore) + delete externalQueueStore; + }; + + virtual void setExternalQueueStore(ExternalQueueStore* inst) = 0; + virtual void flush() = 0; + + inline ExternalQueueStore* getExternalQueueStore() const {return externalQueueStore;}; + + PersistableQueue():externalQueueStore(NULL){ + }; + +protected: + ExternalQueueStore* externalQueueStore; + +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/PriorityQueue.cpp b/qpid/cpp/src/qpid/broker/PriorityQueue.cpp new file mode 100644 index 0000000000..e07e73d323 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/PriorityQueue.cpp @@ -0,0 +1,212 @@ +/* + * + * 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/PriorityQueue.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/QueuedMessage.h" +#include "qpid/framing/reply_exceptions.h" +#include <cmath> + +namespace qpid { +namespace broker { + +PriorityQueue::PriorityQueue(int l) : + levels(l), + messages(levels, Deque()), + frontLevel(0), haveFront(false), cached(false) {} + +size_t PriorityQueue::size() +{ + size_t total(0); + for (int i = 0; i < levels; ++i) { + total += messages[i].size(); + } + return total; +} + +bool PriorityQueue::empty() +{ + for (int i = 0; i < levels; ++i) { + if (!messages[i].empty()) return false; + } + return true; +} + +void PriorityQueue::reinsert(const QueuedMessage& message) +{ + uint p = getPriorityLevel(message); + messages[p].insert(lower_bound(messages[p].begin(), messages[p].end(), message), message); + clearCache(); +} + +bool PriorityQueue::find(const framing::SequenceNumber& position, QueuedMessage& message, bool remove) +{ + QueuedMessage comp; + comp.position = position; + for (int i = 0; i < levels; ++i) { + if (!messages[i].empty()) { + unsigned long diff = position.getValue() - messages[i].front().position.getValue(); + long maxEnd = diff < messages[i].size() ? diff : messages[i].size(); + Deque::iterator l = lower_bound(messages[i].begin(),messages[i].begin()+maxEnd,comp); + if (l != messages[i].end() && l->position == position) { + message = *l; + if (remove) { + messages[i].erase(l); + clearCache(); + } + return true; + } + } + } + return false; +} + +bool PriorityQueue::remove(const framing::SequenceNumber& position, QueuedMessage& message) +{ + return find(position, message, true); +} + +bool PriorityQueue::find(const framing::SequenceNumber& position, QueuedMessage& message) +{ + return find(position, message, false); +} + +bool PriorityQueue::next(const framing::SequenceNumber& position, QueuedMessage& message) +{ + QueuedMessage match; + match.position = position+1; + Deque::iterator lowest; + bool found = false; + for (int i = 0; i < levels; ++i) { + Deque::iterator m = lower_bound(messages[i].begin(), messages[i].end(), match); + if (m != messages[i].end()) { + if (m->position == match.position) { + message = *m; + return true; + } else if (!found || m->position < lowest->position) { + lowest = m; + found = true; + } + } + } + if (found) { + message = *lowest; + } + return found; +} + +QueuedMessage& PriorityQueue::front() +{ + if (checkFront()) { + return messages[frontLevel].front(); + } else { + throw qpid::framing::InternalErrorException(QPID_MSG("No message available")); + } +} + +bool PriorityQueue::pop(QueuedMessage& message) +{ + if (checkFront()) { + message = messages[frontLevel].front(); + messages[frontLevel].pop_front(); + clearCache(); + return true; + } else { + return false; + } +} + +void PriorityQueue::pop() +{ + QueuedMessage dummy; + pop(dummy); +} + +bool PriorityQueue::push(const QueuedMessage& added, QueuedMessage& /*not needed*/) +{ + messages[getPriorityLevel(added)].push_back(added); + clearCache(); + return false;//adding a message never causes one to be removed for deque +} + +void PriorityQueue::foreach(Functor f) +{ + for (int i = 0; i < levels; ++i) { + std::for_each(messages[i].begin(), messages[i].end(), f); + } +} + +void PriorityQueue::removeIf(Predicate p) +{ + for (int priority = 0; priority < levels; ++priority) { + for (Deque::iterator i = messages[priority].begin(); i != messages[priority].end();) { + if (p(*i)) { + i = messages[priority].erase(i); + clearCache(); + } else { + ++i; + } + } + } +} + +uint PriorityQueue::getPriorityLevel(const QueuedMessage& m) const +{ + uint priority = m.payload->getPriority(); + //Use AMQP 0-10 approach to mapping priorities to a fixed level + //(see rule priority-level-implementation) + const uint firstLevel = 5 - uint(std::min(5.0, std::ceil((double) levels/2.0))); + if (priority <= firstLevel) return 0; + return std::min(priority - firstLevel, (uint)levels-1); +} + +void PriorityQueue::clearCache() +{ + cached = false; +} + +bool PriorityQueue::findFrontLevel(uint& l, PriorityLevels& m) +{ + for (int p = levels-1; p >= 0; --p) { + if (!m[p].empty()) { + l = p; + return true; + } + } + return false; +} + +bool PriorityQueue::checkFront() +{ + if (!cached) { + haveFront = findFrontLevel(frontLevel, messages); + cached = true; + } + return haveFront; +} + +uint PriorityQueue::getPriority(const QueuedMessage& message) +{ + const PriorityQueue* queue = dynamic_cast<const PriorityQueue*>(&(message.queue->getMessages())); + if (queue) return queue->getPriorityLevel(message); + else return 0; +} + +}} // namespace qpid::broker diff --git a/qpid/cpp/src/qpid/broker/PriorityQueue.h b/qpid/cpp/src/qpid/broker/PriorityQueue.h new file mode 100644 index 0000000000..4bf9d26a9d --- /dev/null +++ b/qpid/cpp/src/qpid/broker/PriorityQueue.h @@ -0,0 +1,78 @@ +#ifndef QPID_BROKER_PRIORITYQUEUE_H +#define QPID_BROKER_PRIORITYQUEUE_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/Messages.h" +#include "qpid/sys/IntegerTypes.h" +#include <deque> +#include <vector> + +namespace qpid { +namespace broker { + +/** + * Basic priority queue with a configurable number of recognised + * priority levels. This is implemented as a separate deque per + * priority level. Browsing is FIFO not priority order. + */ +class PriorityQueue : public Messages +{ + public: + PriorityQueue(int levels); + virtual ~PriorityQueue() {} + size_t size(); + bool empty(); + + void reinsert(const QueuedMessage&); + bool remove(const framing::SequenceNumber&, QueuedMessage&); + bool find(const framing::SequenceNumber&, QueuedMessage&); + bool next(const framing::SequenceNumber&, QueuedMessage&); + + QueuedMessage& front(); + void pop(); + bool pop(QueuedMessage&); + bool push(const QueuedMessage& added, QueuedMessage& removed); + + void foreach(Functor); + void removeIf(Predicate); + static uint getPriority(const QueuedMessage&); + protected: + typedef std::deque<QueuedMessage> Deque; + typedef std::vector<Deque> PriorityLevels; + virtual bool findFrontLevel(uint& p, PriorityLevels&); + + const int levels; + private: + PriorityLevels messages; + uint frontLevel; + bool haveFront; + bool cached; + + bool find(const framing::SequenceNumber&, QueuedMessage&, bool remove); + uint getPriorityLevel(const QueuedMessage&) const; + void clearCache(); + bool checkFront(); +}; + +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_PRIORITYQUEUE_H*/ diff --git a/qpid/cpp/src/qpid/broker/Queue.cpp b/qpid/cpp/src/qpid/broker/Queue.cpp new file mode 100644 index 0000000000..789ad581f5 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/Queue.cpp @@ -0,0 +1,1225 @@ +/* + * + * 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/Broker.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/QueueEvents.h" +#include "qpid/broker/Exchange.h" +#include "qpid/broker/Fairshare.h" +#include "qpid/broker/DeliverableMessage.h" +#include "qpid/broker/LegacyLVQ.h" +#include "qpid/broker/MessageDeque.h" +#include "qpid/broker/MessageMap.h" +#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/StringUtils.h" +#include "qpid/log/Statement.h" +#include "qpid/management/ManagementAgent.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/sys/ClusterSafe.h" +#include "qpid/sys/Monitor.h" +#include "qpid/sys/Time.h" +#include "qmf/org/apache/qpid/broker/ArgsQueuePurge.h" +#include "qmf/org/apache/qpid/broker/ArgsQueueReroute.h" + +#include <iostream> +#include <algorithm> +#include <functional> + +#include <boost/bind.hpp> +#include <boost/intrusive_ptr.hpp> + + +using namespace qpid::broker; +using namespace qpid::sys; +using namespace qpid::framing; +using qpid::management::ManagementAgent; +using qpid::management::ManagementObject; +using qpid::management::Manageable; +using qpid::management::Args; +using std::for_each; +using std::mem_fun; +namespace _qmf = qmf::org::apache::qpid::broker; + + +namespace +{ +const std::string qpidMaxSize("qpid.max_size"); +const std::string qpidMaxCount("qpid.max_count"); +const std::string qpidNoLocal("no-local"); +const std::string qpidTraceIdentity("qpid.trace.id"); +const std::string qpidTraceExclude("qpid.trace.exclude"); +const std::string qpidLastValueQueueKey("qpid.last_value_queue_key"); +const std::string qpidLastValueQueue("qpid.last_value_queue"); +const std::string qpidLastValueQueueNoBrowse("qpid.last_value_queue_no_browse"); +const std::string qpidPersistLastNode("qpid.persist_last_node"); +const std::string qpidVQMatchProperty("qpid.LVQ_key"); +const std::string qpidQueueEventGeneration("qpid.queue_event_generation"); +const std::string qpidAutoDeleteTimeout("qpid.auto_delete_timeout"); +//following feature is not ready for general use as it doesn't handle +//the case where a message is enqueued on more than one queue well enough: +const std::string qpidInsertSequenceNumbers("qpid.insert_sequence_numbers"); + +const int ENQUEUE_ONLY=1; +const int ENQUEUE_AND_DEQUEUE=2; +} + +Queue::Queue(const string& _name, bool _autodelete, + MessageStore* const _store, + const OwnershipToken* const _owner, + Manageable* parent, + Broker* b) : + + name(_name), + autodelete(_autodelete), + store(_store), + owner(_owner), + consumerCount(0), + exclusive(0), + noLocal(false), + persistLastNode(false), + inLastNodeFailure(false), + messages(new MessageDeque()), + persistenceId(0), + policyExceeded(false), + mgmtObject(0), + eventMode(0), + insertSeqNo(0), + broker(b), + deleted(false), + barrier(*this), + autoDeleteTimeout(0) +{ + if (parent != 0 && broker != 0) { + ManagementAgent* agent = broker->getManagementAgent(); + + if (agent != 0) { + mgmtObject = new _qmf::Queue(agent, this, parent, _name, _store != 0, _autodelete, _owner != 0); + agent->addObject(mgmtObject, 0, store != 0); + } + } +} + +Queue::~Queue() +{ + if (mgmtObject != 0) + mgmtObject->resourceDestroy(); +} + +bool isLocalTo(const OwnershipToken* token, boost::intrusive_ptr<Message>& msg) +{ + return token && token->isLocal(msg->getPublisher()); +} + +bool Queue::isLocal(boost::intrusive_ptr<Message>& msg) +{ + //message is considered local if it was published on the same + //connection as that of the session which declared this queue + //exclusive (owner) or which has an exclusive subscription + //(exclusive) + return noLocal && (isLocalTo(owner, msg) || isLocalTo(exclusive, msg)); +} + +bool Queue::isExcluded(boost::intrusive_ptr<Message>& msg) +{ + return traceExclude.size() && msg->isExcluded(traceExclude); +} + +void Queue::deliver(boost::intrusive_ptr<Message> msg){ + // Check for deferred delivery in a cluster. + if (broker && broker->deferDelivery(name, msg)) + return; + if (msg->isImmediate() && getConsumerCount() == 0) { + if (alternateExchange) { + DeliverableMessage deliverable(msg); + alternateExchange->route(deliverable, msg->getRoutingKey(), msg->getApplicationHeaders()); + } + } else if (isLocal(msg)) { + //drop message + QPID_LOG(info, "Dropping 'local' message from " << getName()); + } else if (isExcluded(msg)) { + //drop message + QPID_LOG(info, "Dropping excluded message from " << getName()); + } else { + enqueue(0, msg); + push(msg); + QPID_LOG(debug, "Message " << msg << " enqueued on " << name); + } +} + +void Queue::recoverPrepared(boost::intrusive_ptr<Message>& msg) +{ + if (policy.get()) policy->recoverEnqueued(msg); +} + +void Queue::recover(boost::intrusive_ptr<Message>& msg){ + if (policy.get()) policy->recoverEnqueued(msg); + + push(msg, true); + if (store){ + // setup synclist for recovered messages, so they don't get re-stored on lastNodeFailure + msg->addToSyncList(shared_from_this(), store); + } + + if (store && (!msg->isContentLoaded() || msg->checkContentReleasable())) { + //content has not been loaded, need to ensure that lazy loading mode is set: + //TODO: find a nicer way to do this + msg->releaseContent(store); + // NOTE: The log message in this section are used for flow-to-disk testing (which checks the log for the + // presence of this message). Do not change this without also checking these tests. + QPID_LOG(debug, "Message id=\"" << msg->getProperties<MessageProperties>()->getMessageId() << "\"; pid=0x" << + std::hex << msg->getPersistenceId() << std::dec << ": Content released after recovery"); + } +} + +void Queue::process(boost::intrusive_ptr<Message>& msg){ + push(msg); + if (mgmtObject != 0){ + mgmtObject->inc_msgTxnEnqueues (); + mgmtObject->inc_byteTxnEnqueues (msg->contentSize ()); + } +} + +void Queue::requeue(const QueuedMessage& msg){ + assertClusterSafe(); + QueueListeners::NotificationSet copy; + { + Mutex::ScopedLock locker(messageLock); + if (!isEnqueued(msg)) return; + messages->reinsert(msg); + listeners.populate(copy); + + // 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() ){ + boost::intrusive_ptr<Message> payload = msg.payload; + enqueue(0, payload); + } + } + } + copy.notify(); +} + +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)) { + QPID_LOG(debug, "Acquired message at " << position << " from " << name); + return true; + } else { + QPID_LOG(debug, "Could not acquire message at " << position << " from " << name << "; no message at that position"); + return false; + } +} + +bool Queue::acquire(const QueuedMessage& msg) { + QueuedMessage copy = msg; + return acquireMessageAt(msg.position, copy); +} + +void Queue::notifyListener() +{ + assertClusterSafe(); + QueueListeners::NotificationSet set; + { + Mutex::ScopedLock locker(messageLock); + if (messages->size()) { + listeners.populate(set); + } + } + set.notify(); +} + +bool Queue::getNextMessage(QueuedMessage& m, Consumer::shared_ptr c) +{ + checkNotDeleted(); + if (c->preAcquires()) { + switch (consumeNextMessage(m, c)) { + case CONSUMED: + return true; + case CANT_CONSUME: + notifyListener();//let someone else try + case NO_MESSAGES: + default: + return false; + } + } else { + return browseNextMessage(m, 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 << "'"); + 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; + } + } else { + //consumer will never want this message + QPID_LOG(debug, "Consumer doesn't want message from '" << name << "'"); + return CANT_CONSUME; + } + } + } +} + + +bool Queue::browseNextMessage(QueuedMessage& m, Consumer::shared_ptr c) +{ + QueuedMessage msg(this); + while (seek(msg, c)) { + if (c->filter(msg.payload) && !msg.payload->hasExpired()) { + if (c->accept(msg.payload)) { + //consumer wants the message + c->position = msg.position; + m = msg; + return true; + } else { + //browser hasn't got enough credit for the message + QPID_LOG(debug, "Browser can't currently accept message from '" << name << "'"); + return false; + } + } else { + //consumer will never want this message, continue seeking + c->position = msg.position; + QPID_LOG(debug, "Browser skipping message from '" << name << "'"); + } + } + return false; +} + +void Queue::removeListener(Consumer::shared_ptr c) +{ + QueueListeners::NotificationSet set; + { + Mutex::ScopedLock locker(messageLock); + listeners.removeListener(c); + if (messages->size()) { + listeners.populate(set); + } + } + set.notify(); +} + +bool Queue::dispatch(Consumer::shared_ptr c) +{ + QueuedMessage msg(this); + if (getNextMessage(msg, c)) { + c->deliver(msg); + return true; + } else { + return false; + } +} + +// 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 { + + Mutex::ScopedLock locker(messageLock); + QueuedMessage msg; + messages->find(pos, msg); + return msg; +} + +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) { + 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(); + } +} + +void Queue::cancel(Consumer::shared_ptr c){ + removeListener(c); + Mutex::ScopedLock locker(consumerLock); + consumerCount--; + if(exclusive) exclusive = 0; + if (mgmtObject != 0) + mgmtObject->dec_consumerCount (); +} + +QueuedMessage Queue::get(){ + Mutex::ScopedLock locker(messageLock); + QueuedMessage msg(this); + messages->pop(msg); + return msg; +} + +bool collect_if_expired(std::deque<QueuedMessage>& expired, QueuedMessage& message) +{ + if (message.payload->hasExpired()) { + expired.push_back(message); + return true; + } else { + return false; + } +} + +void Queue::purgeExpired() +{ + //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) { + std::deque<QueuedMessage> expired; + { + Mutex::ScopedLock locker(messageLock); + messages->removeIf(boost::bind(&collect_if_expired, boost::ref(expired), _1)); + } + for_each(expired.begin(), expired.end(), boost::bind(&Queue::dequeue, this, (TransactionContext*) 0, _1)); + } +} + +/** + * purge - for purging all or some messages on a queue + * depending on the purge_request + * + * purge_request == 0 then purge all messages + * == N then purge N messages from queue + * Sometimes purge_request == 1 to unblock the top of queue + * + * 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. + */ +uint32_t Queue::purge(const uint32_t purge_request, boost::shared_ptr<Exchange> dest) +{ + Mutex::ScopedLock locker(messageLock); + uint32_t purge_count = purge_request; // only comes into play if >0 + std::deque<DeliverableMessage> rerouteQueue; + + 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()) { + 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); + } + 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->routeWithAlternate(msg); + } + + return count; +} + +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 + + 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++; + } + return count; +} + +void Queue::pop() +{ + assertClusterSafe(); + messages->pop(); + ++dequeueTracker; +} + +void Queue::push(boost::intrusive_ptr<Message>& msg, bool isRecovery){ + assertClusterSafe(); + QueueListeners::NotificationSet copy; + QueuedMessage removed; + bool dequeueRequired = false; + { + Mutex::ScopedLock locker(messageLock); + QueuedMessage qm(this, msg, ++sequence); + if (insertSeqNo) msg->getOrInsertHeaders().setInt64(seqNoKey, sequence); + + dequeueRequired = messages->push(qm, removed); + listeners.populate(copy); + enqueued(qm); + } + copy.notify(); + if (dequeueRequired) { + if (isRecovery) { + //can't issue new requests for the store until + //recovery is complete + pendingDequeues.push_back(removed); + } else { + dequeue(0, removed); + } + } +} + +void isEnqueueComplete(uint32_t* result, const QueuedMessage& message) +{ + if (message.payload->isIngressComplete()) (*result)++; +} + +/** function only provided for unit tests, or code not in critical message path */ +uint32_t Queue::getEnqueueCompleteMessageCount() const +{ + Mutex::ScopedLock locker(messageLock); + uint32_t count = 0; + messages->foreach(boost::bind(&isEnqueueComplete, &count, _1)); + return count; +} + +uint32_t Queue::getMessageCount() const +{ + Mutex::ScopedLock locker(messageLock); + return messages->size(); +} + +uint32_t Queue::getConsumerCount() const +{ + Mutex::ScopedLock locker(consumerLock); + return consumerCount; +} + +bool Queue::canAutoDelete() const +{ + Mutex::ScopedLock locker(consumerLock); + return autodelete && !consumerCount && !owner; +} + +void Queue::clearLastNodeFailure() +{ + inLastNodeFailure = false; +} + +void Queue::forcePersistent(QueuedMessage& message) +{ + if(!message.payload->isStoredOnQueue(shared_from_this())) { + message.payload->forcePersistent(); + if (message.payload->isForcedPersistent() ){ + enqueue(0, message.payload); + } + } +} + +void Queue::setLastNodeFailure() +{ + if (persistLastNode){ + Mutex::ScopedLock locker(messageLock); + try { + messages->foreach(boost::bind(&Queue::forcePersistent, this, _1)); + } catch (const std::exception& e) { + // Could not go into last node standing (for example journal not large enough) + QPID_LOG(error, "Unable to fail to last node standing for queue: " << name << " : " << e.what()); + } + inLastNodeFailure = true; + } +} + + +// return true if store exists, +bool Queue::enqueue(TransactionContext* ctxt, boost::intrusive_ptr<Message>& msg, bool suppressPolicyCheck) +{ + ScopedUse u(barrier); + if (!u.acquired) return false; + + if (policy.get() && !suppressPolicyCheck) { + std::deque<QueuedMessage> dequeues; + { + Mutex::ScopedLock locker(messageLock); + policy->tryEnqueue(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)); + } + + 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) { + // 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; + } + if (!store) { + //Messages enqueued on a transient queue should be prevented + //from having their content released as it may not be + //recoverable by these queue for delivery + msg->blockContentRelease(); + } + return false; +} + +void Queue::enqueueAborted(boost::intrusive_ptr<Message> msg) +{ + Mutex::ScopedLock locker(messageLock); + if (policy.get()) policy->enqueueAborted(msg); +} + +// return true if store exists, +bool Queue::dequeue(TransactionContext* ctxt, const QueuedMessage& msg) +{ + ScopedUse u(barrier); + if (!u.acquired) return false; + + { + Mutex::ScopedLock locker(messageLock); + if (!isEnqueued(msg)) return false; + if (!ctxt) { + dequeued(msg); + } + } + // This check prevents messages which have been forced persistent on one queue from dequeuing + // from another on which no forcing has taken place and thus causing a store error. + bool fp = msg.payload->isForcedPersistent(); + if (!fp || (fp && msg.payload->isStoredOnQueue(shared_from_this()))) { + if ((msg.payload->isPersistent() || msg.payload->checkContentReleasable()) && store) { + msg.payload->dequeueAsync(shared_from_this(), store); //increment to async counter -- for message sent to more than one queue + boost::intrusive_ptr<PersistableMessage> pmsg = boost::static_pointer_cast<PersistableMessage>(msg.payload); + store->dequeue(ctxt, pmsg, *this); + return true; + } + } + return false; +} + +void Queue::dequeueCommitted(const QueuedMessage& msg) +{ + Mutex::ScopedLock locker(messageLock); + dequeued(msg); + if (mgmtObject != 0) { + mgmtObject->inc_msgTxnDequeues(); + mgmtObject->inc_byteTxnDequeues(msg.payload->contentSize()); + } +} + +/** + * Removes a message from the in-memory delivery queue as well + * dequeing it from the logical (and persistent if applicable) queue + */ +void Queue::popAndDequeue() +{ + QueuedMessage msg = messages->front(); + pop(); + dequeue(0, msg); +} + +/** + * Updates policy and management when a message has been dequeued, + * expects messageLock to be held + */ +void Queue::dequeued(const QueuedMessage& msg) +{ + if (policy.get()) policy->dequeued(msg); + mgntDeqStats(msg.payload); + for (Observers::const_iterator i = observers.begin(); i != observers.end(); ++i) { + try{ + (*i)->dequeued(msg); + } catch (const std::exception& e) { + QPID_LOG(warning, "Exception on notification of dequeue for queue " << getName() << ": " << e.what()); + } + } +} + + +void Queue::create(const FieldTable& _settings) +{ + settings = _settings; + if (store) { + store->create(*this, _settings); + } + configureImpl(_settings); +} + + +int getIntegerSetting(const qpid::framing::FieldTable& settings, const std::string& key) +{ + qpid::framing::FieldTable::ValuePtr v = settings.get(key); + 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); + } catch(const boost::bad_lexical_cast&) { + QPID_LOG(warning, "Ignoring invalid integer value for " << key << ": " << s); + return 0; + } + } else { + QPID_LOG(warning, "Ignoring invalid integer value for " << key << ": " << *v); + return 0; + } +} + +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 && + (!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()); + } else if (broker && !(broker->getQueueEvents().isSync()) ) { + QPID_LOG(warning, "Flow to disk not valid with async Queue Events:" << getName()); + } + FieldTable copy(_settings); + copy.erase(QueuePolicy::typeKey); + setPolicy(QueuePolicy::createQueuePolicy(getName(), copy)); + } else { + setPolicy(QueuePolicy::createQueuePolicy(getName(), _settings)); + } + if (broker && broker->getManagementAgent()) { + 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); + 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)) { + 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)) { + QPID_LOG(debug, "Configured queue " << getName() << " as Legacy Last Value Queue"); + messages = LegacyLVQ::updateOrReplace(messages, qpidVQMatchProperty, false, broker); + } else { + std::auto_ptr<Messages> m = Fairshare::create(_settings); + if (m.get()) { + messages = m; + QPID_LOG(debug, "Configured queue " << getName() << " as priority queue."); + } + } + + persistLastNode= _settings.get(qpidPersistLastNode); + if (persistLastNode) QPID_LOG(debug, "Configured queue to Persist data if cluster fails to one node for: " << getName()); + + traceId = _settings.getAsString(qpidTraceIdentity); + std::string excludeList = _settings.getAsString(qpidTraceExclude); + if (excludeList.size()) { + split(traceExclude, excludeList, ", "); + } + 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 (mgmtObject != 0) { + mgmtObject->set_arguments(ManagementAgent::toMap(_settings)); + } + + QueueFlowLimit::observe(*this, _settings); +} + +void Queue::destroyed() +{ + unbind(broker->getExchanges()); + if (alternateExchange.get()) { + Mutex::ScopedLock locker(messageLock); + while(!messages->empty()){ + DeliverableMessage msg(messages->front().payload); + alternateExchange->routeWithAlternate(msg); + popAndDequeue(); + } + alternateExchange->decAlternateUsers(); + } + + if (store) { + barrier.destroy(); + store->flush(*this); + store->destroy(*this); + 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() +{ + QueueListeners::ListenerSet set; + { + Mutex::ScopedLock locker(messageLock); + listeners.snapshot(set); + deleted = true; + } + set.notifyAll(); +} + +void Queue::bound(const string& exchange, const string& key, + const FieldTable& args) +{ + bindings.add(exchange, key, args); +} + +void Queue::unbind(ExchangeRegistry& exchanges) +{ + bindings.unbind(exchanges, shared_from_this()); +} + +void Queue::setPolicy(std::auto_ptr<QueuePolicy> _policy) +{ + policy = _policy; +} + +const QueuePolicy* Queue::getPolicy() +{ + return policy.get(); +} + +uint64_t Queue::getPersistenceId() const +{ + return persistenceId; +} + +void Queue::setPersistenceId(uint64_t _persistenceId) const +{ + if (mgmtObject != 0 && persistenceId == 0 && externalQueueStore) + { + ManagementObject* childObj = externalQueueStore->GetManagementObject(); + if (childObj != 0) + childObj->setReference(mgmtObject->getObjectId()); + } + persistenceId = _persistenceId; +} + +void Queue::encode(Buffer& buffer) const +{ + buffer.putShortString(name); + buffer.put(settings); + if (policy.get()) { + buffer.put(*policy); + } + buffer.putShortString(alternateExchange.get() ? alternateExchange->getName() : std::string("")); +} + +uint32_t Queue::encodedSize() const +{ + return name.size() + 1/*short string size octet*/ + + (alternateExchange.get() ? alternateExchange->getName().size() : 0) + 1 /* short string */ + + settings.encodedSize() + + (policy.get() ? (*policy).encodedSize() : 0); +} + +Queue::shared_ptr Queue::restore( QueueRegistry& queues, Buffer& buffer ) +{ + string name; + buffer.getShortString(name); + 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) ); + } + if (buffer.available()) { + string altExch; + buffer.getShortString(altExch); + result.first->alternateExchangeName.assign(altExch); + } + + return result.first; +} + + +void Queue::setAlternateExchange(boost::shared_ptr<Exchange> exchange) +{ + alternateExchange = exchange; + if (mgmtObject) { + if (exchange.get() != 0) + mgmtObject->set_altExchange(exchange->GetManagementObject()->getObjectId()); + else + mgmtObject->clr_altExchange(); + } +} + +boost::shared_ptr<Exchange> Queue::getAlternateExchange() +{ + return alternateExchange; +} + +void tryAutoDeleteImpl(Broker& broker, Queue::shared_ptr queue) +{ + if (broker.getQueues().destroyIf(queue->getName(), + boost::bind(boost::mem_fn(&Queue::canAutoDelete), queue))) { + QPID_LOG(debug, "Auto-deleting " << queue->getName()); + queue->destroyed(); + } +} + +struct AutoDeleteTask : qpid::sys::TimerTask +{ + Broker& broker; + Queue::shared_ptr queue; + + AutoDeleteTask(Broker& b, Queue::shared_ptr q, AbsTime fireTime) + : qpid::sys::TimerTask(fireTime, "DelayedAutoDeletion"), broker(b), queue(q) {} + + void fire() + { + //need to detect case where queue was used after the task was + //created, but then became unused again before the task fired; + //in this case ignore this request as there will have already + //been a later task added + tryAutoDeleteImpl(broker, queue); + } +}; + +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); + QPID_LOG(debug, "Timed auto-delete for " << queue->getName() << " initiated"); + } else { + tryAutoDeleteImpl(broker, queue); + } +} + +bool Queue::isExclusiveOwner(const OwnershipToken* const o) const +{ + Mutex::ScopedLock locker(ownershipLock); + return o == owner; +} + +void Queue::releaseExclusiveOwnership() +{ + Mutex::ScopedLock locker(ownershipLock); + owner = 0; +} + +bool Queue::setExclusiveOwner(const OwnershipToken* const o) +{ + //reset auto deletion timer if necessary + if (autoDeleteTimeout && autoDeleteTask) { + autoDeleteTask->cancel(); + } + Mutex::ScopedLock locker(ownershipLock); + if (owner) { + return false; + } else { + owner = o; + return true; + } +} + +bool Queue::hasExclusiveOwner() const +{ + Mutex::ScopedLock locker(ownershipLock); + return owner != 0; +} + +bool Queue::hasExclusiveConsumer() const +{ + return exclusive; +} + +void Queue::setExternalQueueStore(ExternalQueueStore* inst) { + if (externalQueueStore!=inst && externalQueueStore) + delete externalQueueStore; + externalQueueStore = inst; + + if (inst) { + ManagementObject* childObj = inst->GetManagementObject(); + if (childObj != 0 && mgmtObject != 0) + childObj->setReference(mgmtObject->getObjectId()); + } +} + +ManagementObject* Queue::GetManagementObject (void) const +{ + return (ManagementObject*) mgmtObject; +} + +Manageable::status_t Queue::ManagementMethod (uint32_t methodId, Args& args, string& etext) +{ + Manageable::status_t status = Manageable::STATUS_UNKNOWN_METHOD; + + QPID_LOG (debug, "Queue::ManagementMethod [id=" << methodId << "]"); + + switch (methodId) { + case _qmf::Queue::METHOD_PURGE : + { + _qmf::ArgsQueuePurge& purgeArgs = (_qmf::ArgsQueuePurge&) args; + purge(purgeArgs.i_request); + status = Manageable::STATUS_OK; + } + break; + + case _qmf::Queue::METHOD_REROUTE : + { + _qmf::ArgsQueueReroute& rerouteArgs = (_qmf::ArgsQueueReroute&) args; + boost::shared_ptr<Exchange> dest; + if (rerouteArgs.i_useAltExchange) + dest = alternateExchange; + else { + try { + dest = broker->getExchanges().get(rerouteArgs.i_exchange); + } catch(const std::exception&) { + status = Manageable::STATUS_PARAMETER_INVALID; + etext = "Exchange not found"; + break; + } + } + + purge(rerouteArgs.i_request, dest); + status = Manageable::STATUS_OK; + } + break; + } + + return status; +} + +void Queue::setPosition(SequenceNumber n) { + Mutex::ScopedLock locker(messageLock); + sequence = n; +} + +SequenceNumber Queue::getPosition() { + return sequence; +} + +int Queue::getEventMode() { return eventMode; } + +void Queue::recoveryComplete(ExchangeRegistry& exchanges) +{ + // set the alternate exchange + if (!alternateExchangeName.empty()) { + try { + Exchange::shared_ptr ae = exchanges.get(alternateExchangeName); + setAlternateExchange(ae); + } catch (const NotFoundException&) { + QPID_LOG(warning, "Could not set alternate exchange \"" << alternateExchangeName << "\" on queue \"" << name << "\": exchange does not exist."); + } + } + //process any pending dequeues + for_each(pendingDequeues.begin(), pendingDequeues.end(), boost::bind(&Queue::dequeue, this, (TransactionContext*) 0, _1)); + pendingDequeues.clear(); +} + +void Queue::insertSequenceNumbers(const std::string& key) +{ + seqNoKey = key; + insertSeqNo = !seqNoKey.empty(); + QPID_LOG(debug, "Inserting sequence numbers as " << key); +} + +void Queue::enqueued(const QueuedMessage& m) +{ + for (Observers::iterator i = observers.begin(); i != observers.end(); ++i) { + try { + (*i)->enqueued(m); + } catch (const std::exception& e) { + QPID_LOG(warning, "Exception on notification of enqueue for queue " << getName() << ": " << e.what()); + } + } + if (policy.get()) { + policy->enqueued(m); + } + mgntEnqStats(m.payload); +} + +void Queue::updateEnqueued(const QueuedMessage& m) +{ + if (m.payload) { + boost::intrusive_ptr<Message> payload = m.payload; + enqueue ( 0, payload, true ); + if (policy.get()) { + policy->recoverEnqueued(payload); + } + enqueued(m); + } else { + QPID_LOG(warning, "Queue informed of enqueued message that has no payload"); + } +} + +bool Queue::isEnqueued(const QueuedMessage& msg) +{ + return !policy.get() || policy->isEnqueued(msg); +} + +QueueListeners& Queue::getListeners() { return listeners; } +Messages& Queue::getMessages() { return *messages; } +const Messages& Queue::getMessages() const { return *messages; } + +void Queue::checkNotDeleted() +{ + if (deleted) { + throw ResourceDeletedException(QPID_MSG("Queue " << getName() << " has been deleted.")); + } +} + +void Queue::addObserver(boost::shared_ptr<QueueObserver> observer) +{ + observers.insert(observer); +} + +void Queue::flush() +{ + ScopedUse u(barrier); + 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; +} + + +Queue::UsageBarrier::UsageBarrier(Queue& q) : parent(q), count(0) {} + +bool Queue::UsageBarrier::acquire() +{ + Monitor::ScopedLock l(parent.messageLock); + if (parent.deleted) { + return false; + } else { + ++count; + return true; + } +} + +void Queue::UsageBarrier::release() +{ + Monitor::ScopedLock l(parent.messageLock); + if (--count == 0) parent.messageLock.notifyAll(); +} + +void Queue::UsageBarrier::destroy() +{ + Monitor::ScopedLock l(parent.messageLock); + parent.deleted = true; + while (count) parent.messageLock.wait(); +} diff --git a/qpid/cpp/src/qpid/broker/Queue.h b/qpid/cpp/src/qpid/broker/Queue.h new file mode 100644 index 0000000000..c4f1bcc07e --- /dev/null +++ b/qpid/cpp/src/qpid/broker/Queue.h @@ -0,0 +1,390 @@ +#ifndef _broker_Queue_h +#define _broker_Queue_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/BrokerImportExport.h" +#include "qpid/broker/OwnershipToken.h" +#include "qpid/broker/Consumer.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/Messages.h" +#include "qpid/broker/PersistableQueue.h" +#include "qpid/broker/QueuePolicy.h" +#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/Monitor.h" +#include "qpid/sys/Timer.h" +#include "qpid/management/Manageable.h" +#include "qmf/org/apache/qpid/broker/Queue.h" +#include "qpid/framing/amqp_types.h" + +#include <boost/shared_ptr.hpp> +#include <boost/intrusive_ptr.hpp> +#include <boost/enable_shared_from_this.hpp> + +#include <list> +#include <vector> +#include <memory> +#include <deque> +#include <set> +#include <algorithm> + +namespace qpid { +namespace broker { +class Broker; +class MessageStore; +class QueueEvents; +class QueueRegistry; +class TransactionContext; +class Exchange; + +/** + * The brokers representation of an amqp queue. Messages are + * delivered to a queue from where they can be dispatched to + * registered consumers or be stored until dequeued or until one + * or more consumers registers. + */ +class Queue : public boost::enable_shared_from_this<Queue>, + public PersistableQueue, public management::Manageable { + + struct UsageBarrier + { + Queue& parent; + uint count; + + UsageBarrier(Queue&); + bool acquire(); + void release(); + void destroy(); + }; + + struct ScopedUse + { + UsageBarrier& barrier; + const bool acquired; + 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}; + + + const std::string name; + const bool autodelete; + MessageStore* store; + const OwnershipToken* owner; + uint32_t consumerCount; + OwnershipToken* exclusive; + bool noLocal; + bool persistLastNode; + bool inLastNodeFailure; + std::string traceId; + std::vector<std::string> traceExclude; + QueueListeners listeners; + std::auto_ptr<Messages> messages; + std::deque<QueuedMessage> pendingDequeues;//used to avoid dequeuing during recovery + mutable qpid::sys::Mutex consumerLock; + mutable qpid::sys::Monitor messageLock; + mutable qpid::sys::Mutex ownershipLock; + mutable uint64_t persistenceId; + framing::FieldTable settings; + std::auto_ptr<QueuePolicy> policy; + bool policyExceeded; + QueueBindings bindings; + std::string alternateExchangeName; + boost::shared_ptr<Exchange> alternateExchange; + framing::SequenceNumber sequence; + qmf::org::apache::qpid::broker::Queue* mgmtObject; + RateTracker dequeueTracker; + int eventMode; + Observers observers; + bool insertSeqNo; + std::string seqNoKey; + Broker* broker; + bool deleted; + UsageBarrier barrier; + int autoDeleteTimeout; + boost::intrusive_ptr<qpid::sys::TimerTask> autoDeleteTask; + + 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); + 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(); + void forcePersistent(QueuedMessage& msg); + int getEventMode(); + void configureImpl(const qpid::framing::FieldTable& settings); + + inline void mgntEnqStats(const boost::intrusive_ptr<Message>& msg) + { + if (mgmtObject != 0) { + mgmtObject->inc_msgTotalEnqueues (); + mgmtObject->inc_byteTotalEnqueues (msg->contentSize ()); + if (msg->isPersistent ()) { + mgmtObject->inc_msgPersistEnqueues (); + mgmtObject->inc_bytePersistEnqueues (msg->contentSize ()); + } + } + } + inline void mgntDeqStats(const boost::intrusive_ptr<Message>& msg) + { + if (mgmtObject != 0){ + mgmtObject->inc_msgTotalDequeues (); + mgmtObject->inc_byteTotalDequeues (msg->contentSize()); + if (msg->isPersistent ()){ + mgmtObject->inc_msgPersistDequeues (); + mgmtObject->inc_bytePersistDequeues (msg->contentSize()); + } + } + } + + void checkNotDeleted(); + void notifyDeleted(); + + public: + + typedef boost::shared_ptr<Queue> shared_ptr; + + typedef std::vector<shared_ptr> vector; + + QPID_BROKER_EXTERN Queue(const std::string& name, + bool autodelete = false, + MessageStore* const store = 0, + const OwnershipToken* const owner = 0, + management::Manageable* parent = 0, + Broker* broker = 0); + QPID_BROKER_EXTERN ~Queue(); + + QPID_BROKER_EXTERN bool dispatch(Consumer::shared_ptr); + + /** + * 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); + //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); + QPID_BROKER_EXTERN bool acquireMessageAt(const qpid::framing::SequenceNumber& position, QueuedMessage& message); + + /** + * Delivers a message to the queue. Will record it as + * enqueued if persistent then process it. + */ + QPID_BROKER_EXTERN void deliver(boost::intrusive_ptr<Message> msg); + /** + * Dispatches the messages immediately to a consumer if + * one is available or stores it for later if not. + */ + QPID_BROKER_EXTERN void process(boost::intrusive_ptr<Message>& msg); + /** + * Returns a message to the in-memory queue (due to lack + * of acknowledegement from a receiver). If a consumer is + * available it will be dispatched immediately, else it + * will be returned to the front of the queue. + */ + QPID_BROKER_EXTERN void requeue(const QueuedMessage& msg); + /** + * Used during recovery to add stored messages back to the queue + */ + QPID_BROKER_EXTERN void recover(boost::intrusive_ptr<Message>& msg); + + QPID_BROKER_EXTERN void consume(Consumer::shared_ptr c, + 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(); + + //move qty # of messages to destination Queue destq + uint32_t move(const Queue::shared_ptr destq, uint32_t qty); + + QPID_BROKER_EXTERN uint32_t getMessageCount() const; + QPID_BROKER_EXTERN uint32_t getEnqueueCompleteMessageCount() const; + QPID_BROKER_EXTERN uint32_t getConsumerCount() const; + inline const std::string& getName() const { return name; } + bool isExclusiveOwner(const OwnershipToken* const o) const; + void releaseExclusiveOwnership(); + bool setExclusiveOwner(const OwnershipToken* const o); + bool hasExclusiveConsumer() const; + bool hasExclusiveOwner() const; + inline bool isDurable() const { return store != 0; } + inline const framing::FieldTable& getSettings() const { return settings; } + inline bool isAutoDelete() const { return autodelete; } + bool canAutoDelete() const; + const QueueBindings& getBindings() const { return bindings; } + + /** + * used to take messages from in memory and flush down to disk. + */ + QPID_BROKER_EXTERN void setLastNodeFailure(); + QPID_BROKER_EXTERN void clearLastNodeFailure(); + + bool enqueue(TransactionContext* ctxt, boost::intrusive_ptr<Message>& msg, bool suppressPolicyCheck = false); + void enqueueAborted(boost::intrusive_ptr<Message> msg); + /** + * dequeue from store (only done once messages is acknowledged) + */ + QPID_BROKER_EXTERN bool dequeue(TransactionContext* ctxt, const QueuedMessage &msg); + /** + * Inform the queue that a previous transactional dequeue + * committed. + */ + void dequeueCommitted(const QueuedMessage& msg); + + /** + * 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. + */ + void updateEnqueued(const QueuedMessage& msg); + + /** + * Test whether the specified message (identified by its + * sequence/position), is still enqueued (note this + * doesn't mean it is available for delivery as it may + * have been delievered to a subscriber who has not yet + * accepted it). + */ + bool isEnqueued(const QueuedMessage& msg); + + /** + * Gets the next available message + */ + QPID_BROKER_EXTERN QueuedMessage get(); + + /** Get the message at position pos */ + QPID_BROKER_EXTERN QueuedMessage find(framing::SequenceNumber pos) const; + + const QueuePolicy* getPolicy(); + + void setAlternateExchange(boost::shared_ptr<Exchange> exchange); + boost::shared_ptr<Exchange> getAlternateExchange(); + bool isLocal(boost::intrusive_ptr<Message>& msg); + + //PersistableQueue support: + uint64_t getPersistenceId() const; + void setPersistenceId(uint64_t persistenceId) const; + void encode(framing::Buffer& buffer) const; + uint32_t encodedSize() const; + + /** + * 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); + + // Manageable entry points + management::ManagementObject* GetManagementObject (void) const; + management::Manageable::status_t + ManagementMethod (uint32_t methodId, management::Args& args, std::string& text); + + /** Apply f to each Message on the queue. */ + template <class F> void eachMessage(F f) { + sys::Mutex::ScopedLock l(messageLock); + messages->foreach(f); + } + + /** Apply f to each QueueBinding on the queue */ + template <class F> void eachBinding(F f) { + 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. + */ + QPID_BROKER_EXTERN void setPosition(framing::SequenceNumber pos); + /** return current position sequence number for the next message on the queue. + */ + QPID_BROKER_EXTERN framing::SequenceNumber getPosition(); + void addObserver(boost::shared_ptr<QueueObserver>); + QPID_BROKER_EXTERN void insertSequenceNumbers(const std::string& key); + /** + * Notify queue that recovery has completed. + */ + void recoveryComplete(ExchangeRegistry& exchanges); + + // For cluster update + QueueListeners& getListeners(); + Messages& getMessages(); + const Messages& getMessages() const; + + /** + * Reserve space in policy for an enqueued message that + * has been recovered in the prepared state (dtx only) + */ + void recoverPrepared(boost::intrusive_ptr<Message>& msg); + + void flush(); + + const Broker* getBroker(); +}; +} +} + + +#endif /*!_broker_Queue_h*/ diff --git a/qpid/cpp/src/qpid/broker/QueueBindings.cpp b/qpid/cpp/src/qpid/broker/QueueBindings.cpp new file mode 100644 index 0000000000..60d315acfe --- /dev/null +++ b/qpid/cpp/src/qpid/broker/QueueBindings.cpp @@ -0,0 +1,47 @@ +/* + * + * 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/QueueBindings.h" +#include "qpid/broker/ExchangeRegistry.h" +#include "qpid/framing/reply_exceptions.h" + +using qpid::framing::FieldTable; +using qpid::framing::NotFoundException; +using std::string; +using namespace qpid::broker; + +void QueueBindings::add(const string& exchange, const string& key, const FieldTable& args) +{ + bindings.push_back(QueueBinding(exchange, key, args)); +} + +void QueueBindings::unbind(ExchangeRegistry& exchanges, Queue::shared_ptr queue) +{ + for (Bindings::iterator i = bindings.begin(); i != bindings.end(); i++) { + try { + exchanges.get(i->exchange)->unbind(queue, i->key, &(i->args)); + } catch (const NotFoundException&) {} + } +} + +QueueBinding::QueueBinding(const string& _exchange, const string& _key, const FieldTable& _args) + : exchange(_exchange), key(_key), args(_args) +{} diff --git a/qpid/cpp/src/qpid/broker/QueueBindings.h b/qpid/cpp/src/qpid/broker/QueueBindings.h new file mode 100644 index 0000000000..1b90ba5540 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/QueueBindings.h @@ -0,0 +1,61 @@ +/* + * + * 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 _QueueBindings_ +#define _QueueBindings_ + +#include "qpid/framing/FieldTable.h" +#include <boost/ptr_container/ptr_list.hpp> +#include <boost/shared_ptr.hpp> +#include <algorithm> + +namespace qpid { +namespace broker { + +class ExchangeRegistry; +class Queue; + +struct QueueBinding{ + std::string exchange; + std::string key; + qpid::framing::FieldTable args; + QueueBinding(const std::string& exchange, const std::string& key, const qpid::framing::FieldTable& args); +}; + +class QueueBindings +{ + public: + + /** Apply f to each QueueBinding. */ + template <class F> void eachBinding(F f) const { std::for_each(bindings.begin(), bindings.end(), f); } + + void add(const std::string& exchange, const std::string& key, const qpid::framing::FieldTable& args); + void unbind(ExchangeRegistry& exchanges, boost::shared_ptr<Queue> queue); + + private: + typedef std::vector<QueueBinding> Bindings; + Bindings bindings; +}; + + +}} // namespace qpid::broker + + +#endif diff --git a/qpid/cpp/src/qpid/broker/QueueCleaner.cpp b/qpid/cpp/src/qpid/broker/QueueCleaner.cpp new file mode 100644 index 0000000000..3499ea8a4d --- /dev/null +++ b/qpid/cpp/src/qpid/broker/QueueCleaner.cpp @@ -0,0 +1,74 @@ +/* + * + * 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/QueueCleaner.h" + +#include "qpid/broker/Broker.h" +#include <boost/bind.hpp> + +namespace qpid { +namespace broker { + +QueueCleaner::QueueCleaner(QueueRegistry& q, sys::Timer& t) : queues(q), timer(t) {} + +QueueCleaner::~QueueCleaner() +{ + if (task) task->cancel(); +} + +void QueueCleaner::start(qpid::sys::Duration p) +{ + task = new Task(*this, p); + timer.add(task); +} + +QueueCleaner::Task::Task(QueueCleaner& p, qpid::sys::Duration d) : sys::TimerTask(d,"QueueCleaner"), parent(p) {} + +void QueueCleaner::Task::fire() +{ + parent.fired(); +} + +namespace { +struct CollectQueues +{ + std::vector<Queue::shared_ptr>* queues; + CollectQueues(std::vector<Queue::shared_ptr>* q) : queues(q) {} + void operator()(Queue::shared_ptr q) + { + queues->push_back(q); + } +}; +} + +void QueueCleaner::fired() +{ + //collect copy of list of queues to avoid holding registry lock while we perform purge + std::vector<Queue::shared_ptr> copy; + CollectQueues collect(©); + queues.eachQueue(collect); + std::for_each(copy.begin(), copy.end(), boost::bind(&Queue::purgeExpired, _1)); + task->setupNextFire(); + timer.add(task); +} + + +}} // namespace qpid::broker diff --git a/qpid/cpp/src/qpid/broker/QueueCleaner.h b/qpid/cpp/src/qpid/broker/QueueCleaner.h new file mode 100644 index 0000000000..11c2d180ac --- /dev/null +++ b/qpid/cpp/src/qpid/broker/QueueCleaner.h @@ -0,0 +1,59 @@ +#ifndef QPID_BROKER_QUEUECLEANER_H +#define QPID_BROKER_QUEUECLEANER_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/BrokerImportExport.h" +#include "qpid/sys/Timer.h" + +namespace qpid { +namespace broker { + +class QueueRegistry; +/** + * TimerTask to purge expired messages from queues + */ +class QueueCleaner +{ + public: + QPID_BROKER_EXTERN QueueCleaner(QueueRegistry& queues, sys::Timer& timer); + QPID_BROKER_EXTERN ~QueueCleaner(); + QPID_BROKER_EXTERN void start(qpid::sys::Duration period); + private: + class Task : public sys::TimerTask + { + public: + Task(QueueCleaner& parent, qpid::sys::Duration duration); + void fire(); + private: + QueueCleaner& parent; + }; + + boost::intrusive_ptr<sys::TimerTask> task; + QueueRegistry& queues; + sys::Timer& timer; + + void fired(); +}; +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_QUEUECLEANER_H*/ diff --git a/qpid/cpp/src/qpid/broker/QueueEvents.cpp b/qpid/cpp/src/qpid/broker/QueueEvents.cpp new file mode 100644 index 0000000000..2c540ff1ad --- /dev/null +++ b/qpid/cpp/src/qpid/broker/QueueEvents.cpp @@ -0,0 +1,147 @@ +/* + * + * 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/QueueEvents.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/QueueObserver.h" +#include "qpid/Exception.h" +#include "qpid/log/Statement.h" + +namespace qpid { +namespace broker { + +QueueEvents::QueueEvents(const boost::shared_ptr<sys::Poller>& poller, bool isSync) : + eventQueue(boost::bind(&QueueEvents::handle, this, _1), poller), enabled(true), sync(isSync) +{ + if (!sync) eventQueue.start(); +} + +QueueEvents::~QueueEvents() +{ + if (!sync) eventQueue.stop(); +} + +void QueueEvents::enqueued(const QueuedMessage& m) +{ + if (enabled) { + Event enq(ENQUEUE, m); + if (sync) { + for (Listeners::iterator j = listeners.begin(); j != listeners.end(); j++) + j->second(enq); + } else { + eventQueue.push(enq); + } + } +} + +void QueueEvents::dequeued(const QueuedMessage& m) +{ + if (enabled) { + Event deq(DEQUEUE, m); + if (sync) { + for (Listeners::iterator j = listeners.begin(); j != listeners.end(); j++) + j->second(deq); + } else { + eventQueue.push(Event(DEQUEUE, m)); + } + } +} + +void QueueEvents::registerListener(const std::string& id, const EventListener& listener) +{ + qpid::sys::Mutex::ScopedLock l(lock); + if (listeners.find(id) == listeners.end()) { + listeners[id] = listener; + } else { + throw Exception(QPID_MSG("Event listener already registered for '" << id << "'")); + } +} + +void QueueEvents::unregisterListener(const std::string& id) +{ + qpid::sys::Mutex::ScopedLock l(lock); + if (listeners.find(id) == listeners.end()) { + throw Exception(QPID_MSG("No event listener registered for '" << id << "'")); + } else { + listeners.erase(id); + } +} + +QueueEvents::EventQueue::Batch::const_iterator +QueueEvents::handle(const EventQueue::Batch& events) { + qpid::sys::Mutex::ScopedLock l(lock); + for (EventQueue::Batch::const_iterator i = events.begin(); i != events.end(); ++i) { + for (Listeners::iterator j = listeners.begin(); j != listeners.end(); j++) { + j->second(*i); + } + } + return events.end(); +} + +void QueueEvents::shutdown() +{ + if (!sync && !eventQueue.empty() && !listeners.empty()) eventQueue.shutdown(); +} + +void QueueEvents::enable() +{ + enabled = true; + QPID_LOG(debug, "Queue events enabled"); +} + +void QueueEvents::disable() +{ + enabled = false; + QPID_LOG(debug, "Queue events disabled"); +} + +bool QueueEvents::isSync() +{ + return sync; +} + +class EventGenerator : public QueueObserver +{ + public: + EventGenerator(QueueEvents& mgr, bool enqOnly) : manager(mgr), enqueueOnly(enqOnly) {} + void enqueued(const QueuedMessage& m) + { + manager.enqueued(m); + } + void dequeued(const QueuedMessage& m) + { + if (!enqueueOnly) manager.dequeued(m); + } + private: + QueueEvents& manager; + const bool enqueueOnly; +}; + +void QueueEvents::observe(Queue& queue, bool enqueueOnly) +{ + boost::shared_ptr<QueueObserver> observer(new EventGenerator(*this, enqueueOnly)); + queue.addObserver(observer); +} + + +QueueEvents::Event::Event(EventType t, const QueuedMessage& m) : type(t), msg(m) {} + + +}} // namespace qpid::broker diff --git a/qpid/cpp/src/qpid/broker/QueueEvents.h b/qpid/cpp/src/qpid/broker/QueueEvents.h new file mode 100644 index 0000000000..fcddfe9092 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/QueueEvents.h @@ -0,0 +1,85 @@ +#ifndef QPID_BROKER_QUEUEEVENTS_H +#define QPID_BROKER_QUEUEEVENTS_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/BrokerImportExport.h" +#include "qpid/broker/QueuedMessage.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/PollableQueue.h" +#include <map> +#include <string> +#include <boost/function.hpp> + +namespace qpid { +namespace broker { + +/** + * Event manager for queue events. Allows queues to indicate when + * events have occured; allows listeners to register for notification + * of this. The notification happens asynchronously, in a separate + * thread. + */ +class QueueEvents +{ + public: + enum EventType {ENQUEUE, DEQUEUE}; + + struct Event + { + EventType type; + QueuedMessage msg; + + QPID_BROKER_EXTERN Event(EventType, const QueuedMessage&); + }; + + typedef boost::function<void (Event)> EventListener; + + QPID_BROKER_EXTERN QueueEvents(const boost::shared_ptr<sys::Poller>& poller, bool isSync = false); + QPID_BROKER_EXTERN ~QueueEvents(); + QPID_BROKER_EXTERN void enqueued(const QueuedMessage&); + QPID_BROKER_EXTERN void dequeued(const QueuedMessage&); + QPID_BROKER_EXTERN void registerListener(const std::string& id, + const EventListener&); + QPID_BROKER_EXTERN void unregisterListener(const std::string& id); + void enable(); + void disable(); + void observe(Queue&, bool enqueueOnly); + //process all outstanding events + QPID_BROKER_EXTERN void shutdown(); + QPID_BROKER_EXTERN bool isSync(); + private: + typedef qpid::sys::PollableQueue<Event> EventQueue; + typedef std::map<std::string, EventListener> Listeners; + + EventQueue eventQueue; + Listeners listeners; + volatile bool enabled; + qpid::sys::Mutex lock;//protect listeners from concurrent access + bool sync; + + EventQueue::Batch::const_iterator handle(const EventQueue::Batch& e); + +}; +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_QUEUEEVENTS_H*/ diff --git a/qpid/cpp/src/qpid/broker/QueueFlowLimit.cpp b/qpid/cpp/src/qpid/broker/QueueFlowLimit.cpp new file mode 100644 index 0000000000..b2e2e54bdf --- /dev/null +++ b/qpid/cpp/src/qpid/broker/QueueFlowLimit.cpp @@ -0,0 +1,407 @@ +/* + * + * 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; + 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)); // fyi: msg.payload may be null if msg is delivered & unacked + bool unique; + unique = index.insert(std::pair<framing::SequenceNumber, boost::intrusive_ptr<Message> >(seq, msg.payload)).second; + 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/qpid/cpp/src/qpid/broker/QueueFlowLimit.h b/qpid/cpp/src/qpid/broker/QueueFlowLimit.h new file mode 100644 index 0000000000..c02e479976 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/QueueFlowLimit.h @@ -0,0 +1,129 @@ +/* + * + * 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&); + + /** 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/qpid/cpp/src/qpid/broker/QueueListeners.cpp b/qpid/cpp/src/qpid/broker/QueueListeners.cpp new file mode 100644 index 0000000000..591f4443bb --- /dev/null +++ b/qpid/cpp/src/qpid/broker/QueueListeners.cpp @@ -0,0 +1,98 @@ +/* + * + * 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/QueueListeners.h" +#include <boost/bind.hpp> + +namespace qpid { +namespace broker { + +void QueueListeners::addListener(Consumer::shared_ptr 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->inListeners) { + if (c->acquires) { + remove(consumers, c); + } else { + remove(browsers, c); + } + c->inListeners = false; + } +} + +void QueueListeners::populate(NotificationSet& set) +{ + if (consumers.size()) { + set.consumer = consumers.front(); + consumers.pop_front(); + set.consumer->inListeners = false; + } else { + // 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.push_back(c); +} + +void QueueListeners::remove(Listeners& listeners, Consumer::shared_ptr c) +{ + Listeners::iterator i = std::find(listeners.begin(), listeners.end(), c); + if (i != listeners.end()) listeners.erase(i); +} + +void QueueListeners::NotificationSet::notify() +{ + if (consumer) consumer->notify(); + else std::for_each(browsers.begin(), browsers.end(), boost::mem_fn(&Consumer::notify)); +} + +bool QueueListeners::contains(Consumer::shared_ptr c) const { + return c->inListeners; +} + +void QueueListeners::ListenerSet::notifyAll() +{ + std::for_each(listeners.begin(), listeners.end(), boost::mem_fn(&Consumer::notify)); +} + +void QueueListeners::snapshot(ListenerSet& set) +{ + set.listeners.insert(set.listeners.end(), consumers.begin(), consumers.end()); + set.listeners.insert(set.listeners.end(), browsers.begin(), browsers.end()); +} + +}} // namespace qpid::broker diff --git a/qpid/cpp/src/qpid/broker/QueueListeners.h b/qpid/cpp/src/qpid/broker/QueueListeners.h new file mode 100644 index 0000000000..0659499253 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/QueueListeners.h @@ -0,0 +1,86 @@ +#ifndef QPID_BROKER_QUEUELISTENERS_H +#define QPID_BROKER_QUEUELISTENERS_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/Consumer.h" +#include <deque> + +namespace qpid { +namespace broker { + +/** + * Track and notify components that wish to be notified of messages + * that become available on a queue. + * + * None of the methods defined here are protected by locking. However + * the populate method allows a 'snapshot' to be taken of the + * listeners to be notified. NotificationSet::notify() may then be + * called outside of any lock that protects the QueueListeners + * instance from concurrent access. + */ +class QueueListeners +{ + public: + typedef std::deque<Consumer::shared_ptr> Listeners; + + class NotificationSet + { + public: + void notify(); + private: + Listeners browsers; + Consumer::shared_ptr consumer; + friend class QueueListeners; + }; + + class ListenerSet + { + public: + void notifyAll(); + private: + Listeners listeners; + friend class QueueListeners; + }; + + void addListener(Consumer::shared_ptr); + void removeListener(Consumer::shared_ptr); + void populate(NotificationSet&); + void snapshot(ListenerSet&); + bool contains(Consumer::shared_ptr c) const; + void notifyAll(); + + template <class F> void eachListener(F f) { + std::for_each(browsers.begin(), browsers.end(), f); + std::for_each(consumers.begin(), consumers.end(), f); + } + + private: + Listeners consumers; + Listeners browsers; + + void add(Listeners&, Consumer::shared_ptr); + void remove(Listeners&, Consumer::shared_ptr); + +}; +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_QUEUELISTENERS_H*/ diff --git a/qpid/cpp/src/qpid/broker/QueueObserver.h b/qpid/cpp/src/qpid/broker/QueueObserver.h new file mode 100644 index 0000000000..3ca01c051e --- /dev/null +++ b/qpid/cpp/src/qpid/broker/QueueObserver.h @@ -0,0 +1,42 @@ +#ifndef QPID_BROKER_QUEUEOBSERVER_H +#define QPID_BROKER_QUEUEOBSERVER_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. + * + */ +namespace qpid { +namespace broker { + +struct QueuedMessage; +/** + * Interface for notifying classes who want to act as 'observers' of a + * queue of particular events. + */ +class QueueObserver +{ + public: + virtual ~QueueObserver() {} + virtual void enqueued(const QueuedMessage&) = 0; + virtual void dequeued(const QueuedMessage&) = 0; + private: +}; +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_QUEUEOBSERVER_H*/ diff --git a/qpid/cpp/src/qpid/broker/QueuePolicy.cpp b/qpid/cpp/src/qpid/broker/QueuePolicy.cpp new file mode 100644 index 0000000000..a93a6332fd --- /dev/null +++ b/qpid/cpp/src/qpid/broker/QueuePolicy.cpp @@ -0,0 +1,360 @@ +/* + * + * 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/QueuePolicy.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/PriorityQueue.h" +#include "qpid/Exception.h" +#include "qpid/framing/FieldValue.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/log/Statement.h" +#include <sstream> + +using namespace qpid::broker; +using namespace qpid::framing; + +QueuePolicy::QueuePolicy(const std::string& _name, uint32_t _maxCount, uint64_t _maxSize, const std::string& _type) : + maxCount(_maxCount), maxSize(_maxSize), type(_type), count(0), size(0), policyExceeded(false), name(_name) { + QPID_LOG(info, "Queue \"" << name << "\": Policy created: type=" << type << "; maxCount=" << maxCount << "; maxSize=" << maxSize); +} + +void QueuePolicy::enqueued(uint64_t _size) +{ + if (maxCount) ++count; + if (maxSize) size += _size; +} + +void QueuePolicy::dequeued(uint64_t _size) +{ + if (maxCount) { + if (count > 0) { + --count; + } else { + throw Exception(QPID_MSG("Attempted count underflow on dequeue(" << _size << "): " << *this)); + } + } + if (maxSize) { + if (_size > size) { + throw Exception(QPID_MSG("Attempted size underflow on dequeue(" << _size << "): " << *this)); + } else { + size -= _size; + } + } +} + +bool QueuePolicy::checkLimit(boost::intrusive_ptr<Message> m) +{ + bool sizeExceeded = maxSize && (size + m->contentSize()) > maxSize; + bool countExceeded = maxCount && (count + 1) > maxCount; + bool exceeded = sizeExceeded || countExceeded; + if (exceeded) { + if (!policyExceeded) { + policyExceeded = true; + if (sizeExceeded) QPID_LOG(info, "Queue cumulative message size exceeded policy for " << name); + if (countExceeded) QPID_LOG(info, "Queue message count exceeded policy for " << name); + } + } else { + if (policyExceeded) { + policyExceeded = false; + QPID_LOG(info, "Queue cumulative message size and message count within policy for " << name); + } + } + return !exceeded; +} + +void QueuePolicy::tryEnqueue(boost::intrusive_ptr<Message> m) +{ + if (checkLimit(m)) { + enqueued(m->contentSize()); + } else { + throw ResourceLimitExceededException(QPID_MSG("Policy exceeded on " << name << ", policy: " << *this)); + } +} + +void QueuePolicy::recoverEnqueued(boost::intrusive_ptr<Message> m) +{ + tryEnqueue(m); +} + +void QueuePolicy::enqueueAborted(boost::intrusive_ptr<Message> m) +{ + dequeued(m->contentSize()); +} + +void QueuePolicy::enqueued(const QueuedMessage&) {} + +void QueuePolicy::dequeued(const QueuedMessage& m) +{ + dequeued(m.payload->contentSize()); +} + +bool QueuePolicy::isEnqueued(const QueuedMessage&) +{ + return true; +} + +void QueuePolicy::update(FieldTable& settings) +{ + if (maxCount) settings.setInt(maxCountKey, maxCount); + if (maxSize) settings.setInt(maxSizeKey, maxSize); + settings.setString(typeKey, type); +} + +uint32_t QueuePolicy::getCapacity(const FieldTable& settings, const std::string& key, uint32_t defaultValue) +{ + FieldTable::ValuePtr v = settings.get(key); + + int32_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>(); + 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 && convert.eof()) return result; + } + + throw IllegalArgumentException(QPID_MSG("Cannot convert " << key << " to unsigned integer: " << *v)); +} + +std::string QueuePolicy::getType(const FieldTable& settings) +{ + FieldTable::ValuePtr v = settings.get(typeKey); + if (v && v->convertsTo<std::string>()) { + std::string t = v->get<std::string>(); + std::transform(t.begin(), t.end(), t.begin(), tolower); + if (t == REJECT || t == FLOW_TO_DISK || t == RING || t == RING_STRICT) return t; + } + return REJECT; +} + +void QueuePolicy::setDefaultMaxSize(uint64_t s) +{ + defaultMaxSize = s; +} + +void QueuePolicy::getPendingDequeues(Messages&) {} + + + + +void QueuePolicy::encode(Buffer& buffer) const +{ + buffer.putLong(maxCount); + buffer.putLongLong(maxSize); + buffer.putLong(count); + buffer.putLongLong(size); +} + +void QueuePolicy::decode ( Buffer& buffer ) +{ + maxCount = buffer.getLong(); + maxSize = buffer.getLongLong(); + count = buffer.getLong(); + size = buffer.getLongLong(); +} + + +uint32_t QueuePolicy::encodedSize() const { + return sizeof(uint32_t) + // maxCount + sizeof(uint64_t) + // maxSize + sizeof(uint32_t) + // count + sizeof(uint64_t); // size +} + + + +const std::string QueuePolicy::maxCountKey("qpid.max_count"); +const std::string QueuePolicy::maxSizeKey("qpid.max_size"); +const std::string QueuePolicy::typeKey("qpid.policy_type"); +const std::string QueuePolicy::REJECT("reject"); +const std::string QueuePolicy::FLOW_TO_DISK("flow_to_disk"); +const std::string QueuePolicy::RING("ring"); +const std::string QueuePolicy::RING_STRICT("ring_strict"); +uint64_t QueuePolicy::defaultMaxSize(0); + +FlowToDiskPolicy::FlowToDiskPolicy(const std::string& _name, uint32_t _maxCount, uint64_t _maxSize) : + QueuePolicy(_name, _maxCount, _maxSize, FLOW_TO_DISK) {} + +bool FlowToDiskPolicy::checkLimit(boost::intrusive_ptr<Message> m) +{ + if (!QueuePolicy::checkLimit(m)) m->requestContentRelease(); + return true; +} + +RingQueuePolicy::RingQueuePolicy(const std::string& _name, + uint32_t _maxCount, uint64_t _maxSize, const std::string& _type) : + QueuePolicy(_name, _maxCount, _maxSize, _type), strict(_type == RING_STRICT) {} + +bool before(const QueuedMessage& a, const QueuedMessage& b) +{ + int priorityA = PriorityQueue::getPriority(a); + int priorityB = PriorityQueue::getPriority(b); + if (priorityA == priorityB) return a.position < b.position; + else return priorityA < priorityB; +} + +void RingQueuePolicy::enqueued(const QueuedMessage& m) +{ + //need to insert in correct location based on position + queue.insert(lower_bound(queue.begin(), queue.end(), m, before), m); +} + +void RingQueuePolicy::dequeued(const QueuedMessage& m) +{ + //find and remove m from queue + if (find(m, pendingDequeues, true) || find(m, queue, true)) { + //now update count and size + QueuePolicy::dequeued(m); + } +} + +bool RingQueuePolicy::isEnqueued(const QueuedMessage& m) +{ + //for non-strict ring policy, a message can be replaced (and + //therefore dequeued) before it is accepted or released by + //subscriber; need to detect this + return find(m, pendingDequeues, false) || find(m, queue, false); +} + +bool RingQueuePolicy::checkLimit(boost::intrusive_ptr<Message> m) +{ + + // If the message is bigger than the queue size, give up + if (getMaxSize() && m->contentSize() > getMaxSize()) { + QPID_LOG(debug, "Message too large for ring queue " << name + << " [" << *this << "] " + << ": message size = " << m->contentSize() << " bytes" + << ": max queue size = " << getMaxSize() << " bytes"); + return false; + } + + // if within limits, ok to accept + if (QueuePolicy::checkLimit(m)) return true; + + // At this point, we've exceeded maxSize, maxCount, or both. + // + // If we've exceeded maxCount, we've exceeded it by 1, so + // replacing the first message is sufficient. If we've exceeded + // maxSize, we need to pop enough messages to get the space we + // need. + + unsigned int haveSpace = getMaxSize() - getCurrentQueueSize(); + + do { + QueuedMessage oldest = queue.front(); + + if (oldest.queue->acquire(oldest) || !strict) { + queue.pop_front(); + pendingDequeues.push_back(oldest); + QPID_LOG(debug, "Ring policy triggered in " << name + << ": removed message " << oldest.position << " to make way for new message"); + + haveSpace += oldest.payload->contentSize(); + + } else { + //in strict mode, if oldest message has been delivered (hence + //cannot be acquired) but not yet acked, it should not be + //removed and the attempted enqueue should fail + QPID_LOG(debug, "Ring policy could not be triggered in " << name + << ": oldest message (seq-no=" << oldest.position << ") has been delivered but not yet acknowledged or requeued"); + return false; + } + } while (haveSpace < m->contentSize()); + + + return true; +} + +void RingQueuePolicy::getPendingDequeues(Messages& result) +{ + result = pendingDequeues; +} + +bool RingQueuePolicy::find(const QueuedMessage& m, Messages& q, bool remove) +{ + for (Messages::iterator i = q.begin(); i != q.end(); i++) { + if (i->payload == m.payload) { + if (remove) q.erase(i); + return true; + } + } + return false; +} + +std::auto_ptr<QueuePolicy> QueuePolicy::createQueuePolicy(uint32_t maxCount, uint64_t maxSize, const std::string& type) +{ + return createQueuePolicy("<unspecified>", maxCount, maxSize, type); +} + +std::auto_ptr<QueuePolicy> QueuePolicy::createQueuePolicy(const qpid::framing::FieldTable& settings) +{ + return createQueuePolicy("<unspecified>", settings); +} + +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); + if (maxCount || maxSize) { + return createQueuePolicy(name, maxCount, maxSize, getType(settings)); + } else { + return std::auto_ptr<QueuePolicy>(); + } +} + +std::auto_ptr<QueuePolicy> QueuePolicy::createQueuePolicy(const std::string& name, + uint32_t maxCount, uint64_t maxSize, const std::string& type) +{ + if (type == RING || type == RING_STRICT) { + return std::auto_ptr<QueuePolicy>(new RingQueuePolicy(name, maxCount, maxSize, type)); + } else if (type == FLOW_TO_DISK) { + return std::auto_ptr<QueuePolicy>(new FlowToDiskPolicy(name, maxCount, maxSize)); + } else { + return std::auto_ptr<QueuePolicy>(new QueuePolicy(name, maxCount, maxSize, type)); + } + +} + +namespace qpid { + namespace broker { + +std::ostream& operator<<(std::ostream& out, const QueuePolicy& p) +{ + if (p.maxSize) out << "size: max=" << p.maxSize << ", current=" << p.size; + else out << "size: unlimited"; + out << "; "; + if (p.maxCount) out << "count: max=" << p.maxCount << ", current=" << p.count; + else out << "count: unlimited"; + out << "; type=" << p.type; + return out; +} + + } +} + diff --git a/qpid/cpp/src/qpid/broker/QueuePolicy.h b/qpid/cpp/src/qpid/broker/QueuePolicy.h new file mode 100644 index 0000000000..3cdd63784d --- /dev/null +++ b/qpid/cpp/src/qpid/broker/QueuePolicy.h @@ -0,0 +1,123 @@ +/* + * + * 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 _QueuePolicy_ +#define _QueuePolicy_ + +#include <deque> +#include <iostream> +#include <memory> +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/QueuedMessage.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/sys/AtomicValue.h" +#include "qpid/sys/Mutex.h" + +namespace qpid { +namespace broker { + +class QueuePolicy +{ + static uint64_t defaultMaxSize; + + uint32_t maxCount; + uint64_t maxSize; + const std::string type; + 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; } + + public: + typedef std::deque<QueuedMessage> Messages; + static QPID_BROKER_EXTERN const std::string maxCountKey; + static QPID_BROKER_EXTERN const std::string maxSizeKey; + static QPID_BROKER_EXTERN const std::string typeKey; + static QPID_BROKER_EXTERN const std::string REJECT; + static QPID_BROKER_EXTERN const std::string FLOW_TO_DISK; + static QPID_BROKER_EXTERN const std::string RING; + static QPID_BROKER_EXTERN const std::string RING_STRICT; + + virtual ~QueuePolicy() {} + QPID_BROKER_EXTERN void tryEnqueue(boost::intrusive_ptr<Message> msg); + QPID_BROKER_EXTERN void recoverEnqueued(boost::intrusive_ptr<Message> msg); + QPID_BROKER_EXTERN void enqueueAborted(boost::intrusive_ptr<Message> msg); + virtual void enqueued(const QueuedMessage&); + virtual void dequeued(const QueuedMessage&); + virtual bool isEnqueued(const QueuedMessage&); + QPID_BROKER_EXTERN void update(qpid::framing::FieldTable& settings); + uint32_t getMaxCount() const { return maxCount; } + uint64_t getMaxSize() const { return maxSize; } + void encode(framing::Buffer& buffer) const; + void decode ( framing::Buffer& buffer ); + uint32_t encodedSize() const; + virtual void getPendingDequeues(Messages& result); + + static QPID_BROKER_EXTERN std::auto_ptr<QueuePolicy> createQueuePolicy(const std::string& name, const qpid::framing::FieldTable& settings); + static QPID_BROKER_EXTERN std::auto_ptr<QueuePolicy> createQueuePolicy(const std::string& name, uint32_t maxCount, uint64_t maxSize, const std::string& type = REJECT); + static QPID_BROKER_EXTERN std::auto_ptr<QueuePolicy> createQueuePolicy(const qpid::framing::FieldTable& settings); + static QPID_BROKER_EXTERN std::auto_ptr<QueuePolicy> createQueuePolicy(uint32_t maxCount, uint64_t maxSize, const std::string& type = REJECT); + static std::string getType(const qpid::framing::FieldTable& settings); + static void setDefaultMaxSize(uint64_t); + friend QPID_BROKER_EXTERN std::ostream& operator<<(std::ostream&, + const QueuePolicy&); + protected: + const std::string name; + + QueuePolicy(const std::string& name, uint32_t maxCount, uint64_t maxSize, const std::string& type = REJECT); + + virtual bool checkLimit(boost::intrusive_ptr<Message> msg); + void enqueued(uint64_t size); + void dequeued(uint64_t size); +}; + + +class FlowToDiskPolicy : public QueuePolicy +{ + public: + FlowToDiskPolicy(const std::string& name, uint32_t maxCount, uint64_t maxSize); + bool checkLimit(boost::intrusive_ptr<Message> msg); +}; + +class RingQueuePolicy : public QueuePolicy +{ + public: + RingQueuePolicy(const std::string& name, uint32_t maxCount, uint64_t maxSize, const std::string& type = RING); + void enqueued(const QueuedMessage&); + void dequeued(const QueuedMessage&); + bool isEnqueued(const QueuedMessage&); + bool checkLimit(boost::intrusive_ptr<Message> msg); + void getPendingDequeues(Messages& result); + private: + Messages pendingDequeues; + Messages queue; + const bool strict; + + bool find(const QueuedMessage&, Messages&, bool remove); +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/QueueRegistry.cpp b/qpid/cpp/src/qpid/broker/QueueRegistry.cpp new file mode 100644 index 0000000000..135a3543d9 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/QueueRegistry.cpp @@ -0,0 +1,127 @@ +/* + * + * 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/QueueRegistry.h" +#include "qpid/broker/QueueEvents.h" +#include "qpid/broker/Exchange.h" +#include "qpid/log/Statement.h" +#include <sstream> +#include <assert.h> + +using namespace qpid::broker; +using namespace qpid::sys; +using std::string; + +QueueRegistry::QueueRegistry(Broker* b) : + counter(1), store(0), events(0), parent(0), lastNode(false), broker(b) {} + +QueueRegistry::~QueueRegistry(){} + +std::pair<Queue::shared_ptr, bool> +QueueRegistry::declare(const string& declareName, bool durable, + 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; + assert(!name.empty()); + QueueMap::iterator i = queues.find(name); + + 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(); + + return std::pair<Queue::shared_ptr, bool>(queue, true); + } else { + return std::pair<Queue::shared_ptr, bool>(i->second, false); + } +} + +void QueueRegistry::destroyLH (const string& name){ + queues.erase(name); +} + +void QueueRegistry::destroy (const string& name){ + RWlock::ScopedWlock locker(lock); + destroyLH (name); +} + +Queue::shared_ptr QueueRegistry::find(const string& name){ + RWlock::ScopedRlock locker(lock); + QueueMap::iterator i = queues.find(name); + + if (i == queues.end()) { + return Queue::shared_ptr(); + } else { + return i->second; + } +} + +string QueueRegistry::generateName(){ + string name; + do { + std::stringstream ss; + ss << "tmp_" << counter++; + name = ss.str(); + // Thread safety: Private function, only called with lock held + // so this is OK. + } while(queues.find(name) != queues.end()); + return name; +} + +void QueueRegistry::setStore (MessageStore* _store) +{ + store = _store; +} + +MessageStore* QueueRegistry::getStore() const { + return store; +} + +void QueueRegistry::updateQueueClusterState(bool _lastNode) +{ + RWlock::ScopedRlock locker(lock); + for (QueueMap::iterator i = queues.begin(); i != queues.end(); i++) { + if (_lastNode){ + i->second->setLastNodeFailure(); + } else { + i->second->clearLastNodeFailure(); + } + } + lastNode = _lastNode; +} diff --git a/qpid/cpp/src/qpid/broker/QueueRegistry.h b/qpid/cpp/src/qpid/broker/QueueRegistry.h new file mode 100644 index 0000000000..8a32a64f05 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/QueueRegistry.h @@ -0,0 +1,151 @@ +/* + * + * 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 _QueueRegistry_ +#define _QueueRegistry_ + +#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> +#include <map> + +namespace qpid { +namespace broker { + +class Queue; +class QueueEvents; +class Exchange; +class OwnershipToken; +class Broker; +class MessageStore; + +/** + * A registry of queues indexed by queue name. + * + * Queues are reference counted using shared_ptr to ensure that they + * are deleted when and only when they are no longer in use. + * + */ +class QueueRegistry { + public: + QPID_BROKER_EXTERN QueueRegistry(Broker* b = 0); + QPID_BROKER_EXTERN ~QueueRegistry(); + + /** + * Declare a queue. + * + * @return The queue and a boolean flag which is true if the queue + * was created by this declare call false if it already existed. + */ + QPID_BROKER_EXTERN std::pair<boost::shared_ptr<Queue>, bool> declare( + const std::string& name, + bool durable = false, + bool autodelete = false, + 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. + * + * Note: if the queue is in use it is not actually destroyed until + * all shared_ptrs to it are destroyed. During that time it is + * possible that a new queue with the same name may be + * created. This should not create any problems as the new and + * old queues exist independently. The registry has + * forgotten the old queue so there can be no confusion for + * subsequent calls to find or declare with the same name. + * + */ + QPID_BROKER_EXTERN void destroy(const std::string& name); + template <class Test> bool destroyIf(const std::string& name, Test test) + { + qpid::sys::RWlock::ScopedWlock locker(lock); + if (test()) { + destroyLH (name); + return true; + } else { + return false; + } + } + + /** + * Find the named queue. Return 0 if not found. + */ + QPID_BROKER_EXTERN boost::shared_ptr<Queue> find(const std::string& name); + + /** + * Generate unique queue name. + */ + std::string generateName(); + + /** + * Set the store to use. May only be called once. + */ + void setStore (MessageStore*); + + /** + * Return the message store used. + */ + MessageStore* getStore() const; + + /** + * Register the manageable parent for declared queues + */ + void setParent (management::Manageable* _parent) { parent = _parent; } + + /** Call f for each queue in the registry. */ + template <class F> void eachQueue(F f) const { + qpid::sys::RWlock::ScopedRlock l(lock); + for (QueueMap::const_iterator i = queues.begin(); i != queues.end(); ++i) + f(i->second); + } + + /** + * Change queue mode when cluster size drops to 1 node, expands again + * in practice allows flow queue to disk when last name to be exectuted + */ + void updateQueueClusterState(bool lastNode); + +private: + typedef std::map<std::string, boost::shared_ptr<Queue> > QueueMap; + QueueMap queues; + mutable qpid::sys::RWlock lock; + int counter; + MessageStore* store; + QueueEvents* events; + management::Manageable* parent; + bool lastNode; //used to set mode on queue declare + Broker* broker; + + //destroy impl that assumes lock is already held: + void destroyLH (const std::string& name); +}; + + +}} // namespace qpid::broker + + +#endif diff --git a/qpid/cpp/src/qpid/broker/QueuedMessage.h b/qpid/cpp/src/qpid/broker/QueuedMessage.h new file mode 100644 index 0000000000..35e48b11f3 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/QueuedMessage.h @@ -0,0 +1,48 @@ +/* + * + * 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 _QueuedMessage_ +#define _QueuedMessage_ + +#include "qpid/broker/Message.h" + +namespace qpid { +namespace broker { + +class Queue; + +struct QueuedMessage +{ + boost::intrusive_ptr<Message> payload; + framing::SequenceNumber position; + Queue* queue; + + QueuedMessage() : queue(0) {} + QueuedMessage(Queue* q, boost::intrusive_ptr<Message> msg, framing::SequenceNumber sn) : + payload(msg), position(sn), queue(q) {} + QueuedMessage(Queue* q) : queue(q) {} + +}; + inline bool operator<(const QueuedMessage& a, const QueuedMessage& b) { return a.position < b.position; } + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/RateFlowcontrol.h b/qpid/cpp/src/qpid/broker/RateFlowcontrol.h new file mode 100644 index 0000000000..99f9d2c0c4 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/RateFlowcontrol.h @@ -0,0 +1,105 @@ +#ifndef broker_RateFlowcontrol_h +#define broker_RateFlowcontrol_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" +#include "qpid/sys/IntegerTypes.h" + +#include <algorithm> + +namespace qpid { +namespace broker { + +// Class to keep track of issuing flow control to make sure that the peer doesn't exceed +// a given message rate +// +// Create the object with the target rate +// Then call sendCredit() whenever credit is issued to the peer +// Call receivedMessage() whenever a message is received, it returns the credit to issue. +// +// sentCredit() be sensibly called with a 0 parameter to indicate +// that we sent credit but treat it as if the value was 0 (we may do this at the start of the connection +// to allow our peer to send messages) +// +// receivedMessage() can be called with 0 to indicate that we've not received a message, but +// tell me what credit I can send. +class RateFlowcontrol { + uint32_t rate; // messages per second + uint32_t maxCredit; // max credit issued to client (issued at start) + uint32_t requestedCredit; + qpid::sys::AbsTime creditSent; + +public: + RateFlowcontrol(uint32_t r) : + rate(r), + maxCredit(0), + requestedCredit(0), + creditSent(qpid::sys::FAR_FUTURE) + {} + + uint32_t getRate() const { + return rate; + } + void sentCredit(const qpid::sys::AbsTime& t, uint32_t credit); + uint32_t receivedMessage(const qpid::sys::AbsTime& t, uint32_t msgs); + uint32_t availableCredit(const qpid::sys::AbsTime& t); + bool flowStopped() const; +}; + +inline void RateFlowcontrol::sentCredit(const qpid::sys::AbsTime& t, uint32_t credit) { + // If the client isn't currently requesting credit (ie it's not sent us anything yet) then + // this credit goes to the max credit held by the client (it can't go to reduce credit + // less than 0) + int32_t nextRequestedCredit = requestedCredit - credit; + if ( nextRequestedCredit<0 ) { + requestedCredit = 0; + maxCredit -= nextRequestedCredit; + } else { + requestedCredit = nextRequestedCredit; + } + creditSent = t; +} + +inline uint32_t RateFlowcontrol::availableCredit(const qpid::sys::AbsTime& t) { + qpid::sys::Duration d(creditSent, t); + // Could be -ve before first sentCredit + int64_t toSend = std::min(rate * d / qpid::sys::TIME_SEC, static_cast<int64_t>(requestedCredit)); + return toSend > 0 ? toSend : 0; +} + +inline uint32_t RateFlowcontrol::receivedMessage(const qpid::sys::AbsTime& t, uint32_t msgs) { + requestedCredit +=msgs; + // Don't send credit for every message, only send if more than 0.5s since last credit or + // we've got less than .25 of the max left (heuristic) + return requestedCredit*4 >= maxCredit*3 || qpid::sys::Duration(creditSent, t) >= 500*qpid::sys::TIME_MSEC + ? availableCredit(t) + : 0; +} + +inline bool RateFlowcontrol::flowStopped() const { + return requestedCredit >= maxCredit; +} + +}} + +#endif // broker_RateFlowcontrol_h diff --git a/qpid/cpp/src/qpid/broker/RateTracker.cpp b/qpid/cpp/src/qpid/broker/RateTracker.cpp new file mode 100644 index 0000000000..048349b658 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/RateTracker.cpp @@ -0,0 +1,51 @@ +/* + * + * 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/qpid/cpp/src/qpid/broker/RateTracker.h b/qpid/cpp/src/qpid/broker/RateTracker.h new file mode 100644 index 0000000000..0c20b37312 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/RateTracker.h @@ -0,0 +1,57 @@ +#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/qpid/cpp/src/qpid/broker/RecoverableConfig.h b/qpid/cpp/src/qpid/broker/RecoverableConfig.h new file mode 100644 index 0000000000..838a8582dc --- /dev/null +++ b/qpid/cpp/src/qpid/broker/RecoverableConfig.h @@ -0,0 +1,45 @@ +#ifndef _broker_RecoverableConfig_h +#define _broker_RecoverableConfig_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 <boost/shared_ptr.hpp> + +namespace qpid { +namespace broker { + +/** + * The interface through which configurations are recovered. + */ +class RecoverableConfig +{ +public: + typedef boost::shared_ptr<RecoverableConfig> shared_ptr; + + virtual void setPersistenceId(uint64_t id) = 0; + virtual ~RecoverableConfig() {}; +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/RecoverableExchange.h b/qpid/cpp/src/qpid/broker/RecoverableExchange.h new file mode 100644 index 0000000000..ca6cc1541e --- /dev/null +++ b/qpid/cpp/src/qpid/broker/RecoverableExchange.h @@ -0,0 +1,52 @@ +#ifndef _broker_RecoverableExchange_h +#define _broker_RecoverableExchange_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 <boost/shared_ptr.hpp> +#include "qpid/framing/FieldTable.h" + +namespace qpid { +namespace broker { + +/** + * The interface through which bindings are recovered. + */ +class RecoverableExchange +{ +public: + typedef boost::shared_ptr<RecoverableExchange> shared_ptr; + + virtual void setPersistenceId(uint64_t id) = 0; + /** + * Recover binding. Nb: queue must have been recovered earlier. + */ + virtual void bind(const std::string& queue, + const std::string& routingKey, + qpid::framing::FieldTable& args) = 0; + virtual ~RecoverableExchange() {}; +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/RecoverableMessage.h b/qpid/cpp/src/qpid/broker/RecoverableMessage.h new file mode 100644 index 0000000000..c98857ceb0 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/RecoverableMessage.h @@ -0,0 +1,59 @@ +#ifndef _broker_RecoverableMessage_h +#define _broker_RecoverableMessage_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 <boost/shared_ptr.hpp> +#include "qpid/framing/amqp_types.h" +#include "qpid/framing/Buffer.h" + +namespace qpid { +namespace broker { + +/** + * The interface through which messages are reloaded on recovery. + */ +class RecoverableMessage +{ +public: + typedef boost::shared_ptr<RecoverableMessage> shared_ptr; + virtual void setPersistenceId(uint64_t id) = 0; + virtual void setRedelivered() = 0; + /** + * Used by store to determine whether to load content on recovery + * or let message load its own content as and when it requires it. + * + * @returns true if the content of the message should be loaded + */ + virtual bool loadContent(uint64_t available) = 0; + /** + * Loads the content held in the supplied buffer (may do checking + * of length as necessary) + */ + virtual void decodeContent(framing::Buffer& buffer) = 0; + virtual ~RecoverableMessage() {}; +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/RecoverableQueue.h b/qpid/cpp/src/qpid/broker/RecoverableQueue.h new file mode 100644 index 0000000000..49f05f97a1 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/RecoverableQueue.h @@ -0,0 +1,59 @@ +#ifndef _broker_RecoverableQueue_h +#define _broker_RecoverableQueue_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/RecoverableMessage.h" +#include <boost/shared_ptr.hpp> + +namespace qpid { +namespace broker { + +class ExternalQueueStore; + +/** + * The interface through which messages are added back to queues on + * recovery. + */ +class RecoverableQueue +{ +public: + typedef boost::shared_ptr<RecoverableQueue> shared_ptr; + + virtual void setPersistenceId(uint64_t id) = 0; + virtual uint64_t getPersistenceId() const = 0; + /** + * Used during recovery to add stored messages back to the queue + */ + virtual void recover(RecoverableMessage::shared_ptr msg) = 0; + virtual ~RecoverableQueue() {}; + + virtual const std::string& getName() const = 0; + virtual void setExternalQueueStore(ExternalQueueStore* inst) = 0; + virtual ExternalQueueStore* getExternalQueueStore() const = 0; + +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/RecoverableTransaction.h b/qpid/cpp/src/qpid/broker/RecoverableTransaction.h new file mode 100644 index 0000000000..1b7d94bd1a --- /dev/null +++ b/qpid/cpp/src/qpid/broker/RecoverableTransaction.h @@ -0,0 +1,49 @@ +#ifndef _broker_RecoverableTransaction_h +#define _broker_RecoverableTransaction_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 <boost/shared_ptr.hpp> + +#include "qpid/broker/RecoverableMessage.h" +#include "qpid/broker/RecoverableQueue.h" + +namespace qpid { +namespace broker { + +/** + * The interface through which prepared 2pc transactions are + * recovered. + */ +class RecoverableTransaction +{ +public: + typedef boost::shared_ptr<RecoverableTransaction> shared_ptr; + virtual void enqueue(RecoverableQueue::shared_ptr queue, RecoverableMessage::shared_ptr message) = 0; + virtual void dequeue(RecoverableQueue::shared_ptr queue, RecoverableMessage::shared_ptr message) = 0; + virtual ~RecoverableTransaction() {}; +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/RecoveredDequeue.cpp b/qpid/cpp/src/qpid/broker/RecoveredDequeue.cpp new file mode 100644 index 0000000000..cd6735328f --- /dev/null +++ b/qpid/cpp/src/qpid/broker/RecoveredDequeue.cpp @@ -0,0 +1,48 @@ +/* + * + * 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/RecoveredDequeue.h" + +using boost::intrusive_ptr; +using namespace qpid::broker; + +RecoveredDequeue::RecoveredDequeue(Queue::shared_ptr _queue, intrusive_ptr<Message> _msg) : queue(_queue), msg(_msg) +{ + queue->recoverPrepared(msg); +} + +bool RecoveredDequeue::prepare(TransactionContext*) throw() +{ + //should never be called; transaction has already prepared if an enqueue is recovered + return false; +} + +void RecoveredDequeue::commit() throw() +{ + queue->enqueueAborted(msg); +} + +void RecoveredDequeue::rollback() throw() +{ + queue->process(msg); +} + diff --git a/qpid/cpp/src/qpid/broker/RecoveredDequeue.h b/qpid/cpp/src/qpid/broker/RecoveredDequeue.h new file mode 100644 index 0000000000..66e66f1d5f --- /dev/null +++ b/qpid/cpp/src/qpid/broker/RecoveredDequeue.h @@ -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. + * + */ +#ifndef _RecoveredDequeue_ +#define _RecoveredDequeue_ + +#include "qpid/broker/Deliverable.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/MessageStore.h" +#include "qpid/broker/TxOp.h" + +#include <boost/intrusive_ptr.hpp> + +#include <algorithm> +#include <functional> +#include <list> + +namespace qpid { + namespace broker { + class RecoveredDequeue : public TxOp{ + boost::shared_ptr<Queue> queue; + boost::intrusive_ptr<Message> msg; + + public: + RecoveredDequeue(boost::shared_ptr<Queue> queue, boost::intrusive_ptr<Message> msg); + virtual bool prepare(TransactionContext* ctxt) throw(); + virtual void commit() throw(); + virtual void rollback() throw(); + virtual ~RecoveredDequeue(){} + virtual void accept(TxOpConstVisitor& visitor) const { visitor(*this); } + + boost::shared_ptr<Queue> getQueue() const { return queue; } + boost::intrusive_ptr<Message> getMessage() const { return msg; } + }; + } +} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/RecoveredEnqueue.cpp b/qpid/cpp/src/qpid/broker/RecoveredEnqueue.cpp new file mode 100644 index 0000000000..6d2eaee6c4 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/RecoveredEnqueue.cpp @@ -0,0 +1,45 @@ +/* + * + * 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/RecoveredEnqueue.h" + +using boost::intrusive_ptr; +using namespace qpid::broker; + +RecoveredEnqueue::RecoveredEnqueue(Queue::shared_ptr _queue, intrusive_ptr<Message> _msg) : queue(_queue), msg(_msg) +{ + queue->recoverPrepared(msg); +} + +bool RecoveredEnqueue::prepare(TransactionContext*) throw(){ + //should never be called; transaction has already prepared if an enqueue is recovered + return false; +} + +void RecoveredEnqueue::commit() throw(){ + queue->process(msg); +} + +void RecoveredEnqueue::rollback() throw(){ + queue->enqueueAborted(msg); +} + diff --git a/qpid/cpp/src/qpid/broker/RecoveredEnqueue.h b/qpid/cpp/src/qpid/broker/RecoveredEnqueue.h new file mode 100644 index 0000000000..5f718001d5 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/RecoveredEnqueue.h @@ -0,0 +1,57 @@ +/* + * + * 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 _RecoveredEnqueue_ +#define _RecoveredEnqueue_ + +#include "qpid/broker/Deliverable.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/MessageStore.h" +#include "qpid/broker/TxOp.h" + +#include <boost/intrusive_ptr.hpp> + +#include <algorithm> +#include <functional> +#include <list> + +namespace qpid { +namespace broker { +class RecoveredEnqueue : public TxOp{ + boost::shared_ptr<Queue> queue; + boost::intrusive_ptr<Message> msg; + + public: + RecoveredEnqueue(boost::shared_ptr<Queue> queue, boost::intrusive_ptr<Message> msg); + virtual bool prepare(TransactionContext* ctxt) throw(); + virtual void commit() throw(); + virtual void rollback() throw(); + virtual ~RecoveredEnqueue(){} + virtual void accept(TxOpConstVisitor& visitor) const { visitor(*this); } + + boost::shared_ptr<Queue> getQueue() const { return queue; } + boost::intrusive_ptr<Message> getMessage() const { return msg; } + +}; +} +} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/RecoveryManager.h b/qpid/cpp/src/qpid/broker/RecoveryManager.h new file mode 100644 index 0000000000..2929e92250 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/RecoveryManager.h @@ -0,0 +1,61 @@ +/* + * + * 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 _RecoveryManager_ +#define _RecoveryManager_ + +#include "qpid/broker/RecoverableExchange.h" +#include "qpid/broker/RecoverableQueue.h" +#include "qpid/broker/RecoverableMessage.h" +#include "qpid/broker/RecoverableTransaction.h" +#include "qpid/broker/RecoverableConfig.h" +#include "qpid/broker/TransactionalStore.h" +#include "qpid/framing/Buffer.h" + +namespace qpid { +namespace broker { + +class RecoveryManager{ + public: + virtual ~RecoveryManager(){} + virtual RecoverableExchange::shared_ptr recoverExchange(framing::Buffer& buffer) = 0; + virtual RecoverableQueue::shared_ptr recoverQueue(framing::Buffer& buffer) = 0; + virtual RecoverableMessage::shared_ptr recoverMessage(framing::Buffer& buffer) = 0; + virtual RecoverableTransaction::shared_ptr recoverTransaction(const std::string& xid, + std::auto_ptr<TPCTransactionContext> txn) = 0; + virtual RecoverableConfig::shared_ptr recoverConfig(framing::Buffer& buffer) = 0; + + virtual void recoveryComplete() = 0; +}; + +class Recoverable { + public: + virtual ~Recoverable() {} + + /** + * Request recovery of queue and message state. + */ + virtual void recover(RecoveryManager& recoverer) = 0; +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/RecoveryManagerImpl.cpp b/qpid/cpp/src/qpid/broker/RecoveryManagerImpl.cpp new file mode 100644 index 0000000000..d08409695e --- /dev/null +++ b/qpid/cpp/src/qpid/broker/RecoveryManagerImpl.cpp @@ -0,0 +1,278 @@ +/* + * + * 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/RecoveryManagerImpl.h" + +#include "qpid/broker/Message.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/Link.h" +#include "qpid/broker/Bridge.h" +#include "qpid/broker/RecoveredEnqueue.h" +#include "qpid/broker/RecoveredDequeue.h" +#include "qpid/framing/reply_exceptions.h" + +using boost::dynamic_pointer_cast; +using boost::intrusive_ptr; +using std::string; + +namespace qpid { +namespace broker { + +RecoveryManagerImpl::RecoveryManagerImpl(QueueRegistry& _queues, ExchangeRegistry& _exchanges, LinkRegistry& _links, + DtxManager& _dtxMgr) + : queues(_queues), exchanges(_exchanges), links(_links), dtxMgr(_dtxMgr) {} + +RecoveryManagerImpl::~RecoveryManagerImpl() {} + +class RecoverableMessageImpl : public RecoverableMessage +{ + intrusive_ptr<Message> msg; +public: + RecoverableMessageImpl(const intrusive_ptr<Message>& _msg); + ~RecoverableMessageImpl() {}; + void setPersistenceId(uint64_t id); + void setRedelivered(); + bool loadContent(uint64_t available); + void decodeContent(framing::Buffer& buffer); + void recover(Queue::shared_ptr queue); + void enqueue(DtxBuffer::shared_ptr buffer, Queue::shared_ptr queue); + void dequeue(DtxBuffer::shared_ptr buffer, Queue::shared_ptr queue); +}; + +class RecoverableQueueImpl : public RecoverableQueue +{ + Queue::shared_ptr queue; +public: + RecoverableQueueImpl(const boost::shared_ptr<Queue>& _queue) : queue(_queue) {} + ~RecoverableQueueImpl() {}; + void setPersistenceId(uint64_t id); + uint64_t getPersistenceId() const; + const std::string& getName() const; + void setExternalQueueStore(ExternalQueueStore* inst); + ExternalQueueStore* getExternalQueueStore() const; + void recover(RecoverableMessage::shared_ptr msg); + void enqueue(DtxBuffer::shared_ptr buffer, RecoverableMessage::shared_ptr msg); + void dequeue(DtxBuffer::shared_ptr buffer, RecoverableMessage::shared_ptr msg); +}; + +class RecoverableExchangeImpl : public RecoverableExchange +{ + Exchange::shared_ptr exchange; + QueueRegistry& queues; +public: + RecoverableExchangeImpl(Exchange::shared_ptr _exchange, QueueRegistry& _queues) : exchange(_exchange), queues(_queues) {} + void setPersistenceId(uint64_t id); + void bind(const std::string& queue, const std::string& routingKey, qpid::framing::FieldTable& args); +}; + +class RecoverableConfigImpl : public RecoverableConfig +{ + Link::shared_ptr link; + Bridge::shared_ptr bridge; +public: + RecoverableConfigImpl(Link::shared_ptr _link) : link(_link) {} + RecoverableConfigImpl(Bridge::shared_ptr _bridge) : bridge(_bridge) {} + void setPersistenceId(uint64_t id); +}; + +class RecoverableTransactionImpl : public RecoverableTransaction +{ + DtxBuffer::shared_ptr buffer; +public: + RecoverableTransactionImpl(DtxBuffer::shared_ptr _buffer) : buffer(_buffer) {} + void enqueue(RecoverableQueue::shared_ptr queue, RecoverableMessage::shared_ptr message); + void dequeue(RecoverableQueue::shared_ptr queue, RecoverableMessage::shared_ptr message); +}; + +RecoverableExchange::shared_ptr RecoveryManagerImpl::recoverExchange(framing::Buffer& buffer) +{ + Exchange::shared_ptr e = Exchange::decode(exchanges, buffer); + if (e) { + return RecoverableExchange::shared_ptr(new RecoverableExchangeImpl(e, queues)); + } else { + return RecoverableExchange::shared_ptr(); + } +} + +RecoverableQueue::shared_ptr RecoveryManagerImpl::recoverQueue(framing::Buffer& buffer) +{ + Queue::shared_ptr queue = Queue::restore(queues, buffer); + try { + Exchange::shared_ptr exchange = exchanges.getDefault(); + if (exchange) { + exchange->bind(queue, queue->getName(), 0); + queue->bound(exchange->getName(), queue->getName(), framing::FieldTable()); + } + } catch (const framing::NotFoundException& /*e*/) { + //assume no default exchange has been declared + } + return RecoverableQueue::shared_ptr(new RecoverableQueueImpl(queue)); +} + +RecoverableMessage::shared_ptr RecoveryManagerImpl::recoverMessage(framing::Buffer& buffer) +{ + boost::intrusive_ptr<Message> message(new Message()); + message->decodeHeader(buffer); + return RecoverableMessage::shared_ptr(new RecoverableMessageImpl(message)); +} + +RecoverableTransaction::shared_ptr RecoveryManagerImpl::recoverTransaction(const std::string& xid, + std::auto_ptr<TPCTransactionContext> txn) +{ + DtxBuffer::shared_ptr buffer(new DtxBuffer()); + dtxMgr.recover(xid, txn, buffer); + return RecoverableTransaction::shared_ptr(new RecoverableTransactionImpl(buffer)); +} + +RecoverableConfig::shared_ptr RecoveryManagerImpl::recoverConfig(framing::Buffer& buffer) +{ + string kind; + + buffer.getShortString (kind); + if (kind == "link") + return RecoverableConfig::shared_ptr(new RecoverableConfigImpl(Link::decode (links, buffer))); + else if (kind == "bridge") + return RecoverableConfig::shared_ptr(new RecoverableConfigImpl(Bridge::decode (links, buffer))); + + return RecoverableConfig::shared_ptr(); // TODO: raise an exception instead +} + +void RecoveryManagerImpl::recoveryComplete() +{ + //notify all queues and exchanges + queues.eachQueue(boost::bind(&Queue::recoveryComplete, _1, boost::ref(exchanges))); + exchanges.eachExchange(boost::bind(&Exchange::recoveryComplete, _1, boost::ref(exchanges))); +} + +RecoverableMessageImpl:: RecoverableMessageImpl(const intrusive_ptr<Message>& _msg) : msg(_msg) +{ + if (!msg->isPersistent()) { + msg->forcePersistent(); // set so that message will get dequeued from store. + } +} + +bool RecoverableMessageImpl::loadContent(uint64_t /*available*/) +{ + return true; +} + +void RecoverableMessageImpl::decodeContent(framing::Buffer& buffer) +{ + msg->decodeContent(buffer); +} + +void RecoverableMessageImpl::recover(Queue::shared_ptr queue) +{ + queue->recover(msg); +} + +void RecoverableMessageImpl::setPersistenceId(uint64_t id) +{ + msg->setPersistenceId(id); +} + +void RecoverableMessageImpl::setRedelivered() +{ + msg->redeliver(); +} + +void RecoverableQueueImpl::recover(RecoverableMessage::shared_ptr msg) +{ + dynamic_pointer_cast<RecoverableMessageImpl>(msg)->recover(queue); +} + +void RecoverableQueueImpl::setPersistenceId(uint64_t id) +{ + queue->setPersistenceId(id); +} + +uint64_t RecoverableQueueImpl::getPersistenceId() const +{ + return queue->getPersistenceId(); +} + +const std::string& RecoverableQueueImpl::getName() const +{ + return queue->getName(); +} + +void RecoverableQueueImpl::setExternalQueueStore(ExternalQueueStore* inst) +{ + queue->setExternalQueueStore(inst); +} + +ExternalQueueStore* RecoverableQueueImpl::getExternalQueueStore() const +{ + return queue->getExternalQueueStore(); +} + +void RecoverableExchangeImpl::setPersistenceId(uint64_t id) +{ + exchange->setPersistenceId(id); +} + +void RecoverableConfigImpl::setPersistenceId(uint64_t id) +{ + if (link.get()) + link->setPersistenceId(id); + else if (bridge.get()) + bridge->setPersistenceId(id); +} + +void RecoverableExchangeImpl::bind(const string& queueName, + const string& key, + framing::FieldTable& args) +{ + Queue::shared_ptr queue = queues.find(queueName); + exchange->bind(queue, key, &args); + queue->bound(exchange->getName(), key, args); +} + +void RecoverableMessageImpl::dequeue(DtxBuffer::shared_ptr buffer, Queue::shared_ptr queue) +{ + buffer->enlist(TxOp::shared_ptr(new RecoveredDequeue(queue, msg))); +} + +void RecoverableMessageImpl::enqueue(DtxBuffer::shared_ptr buffer, Queue::shared_ptr queue) +{ + buffer->enlist(TxOp::shared_ptr(new RecoveredEnqueue(queue, msg))); +} + +void RecoverableQueueImpl::dequeue(DtxBuffer::shared_ptr buffer, RecoverableMessage::shared_ptr message) +{ + dynamic_pointer_cast<RecoverableMessageImpl>(message)->dequeue(buffer, queue); +} + +void RecoverableQueueImpl::enqueue(DtxBuffer::shared_ptr buffer, RecoverableMessage::shared_ptr message) +{ + dynamic_pointer_cast<RecoverableMessageImpl>(message)->enqueue(buffer, queue); +} + +void RecoverableTransactionImpl::dequeue(RecoverableQueue::shared_ptr queue, RecoverableMessage::shared_ptr message) +{ + dynamic_pointer_cast<RecoverableQueueImpl>(queue)->dequeue(buffer, message); +} + +void RecoverableTransactionImpl::enqueue(RecoverableQueue::shared_ptr queue, RecoverableMessage::shared_ptr message) +{ + dynamic_pointer_cast<RecoverableQueueImpl>(queue)->enqueue(buffer, message); +} + +}} diff --git a/qpid/cpp/src/qpid/broker/RecoveryManagerImpl.h b/qpid/cpp/src/qpid/broker/RecoveryManagerImpl.h new file mode 100644 index 0000000000..1ad7892b13 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/RecoveryManagerImpl.h @@ -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. + * + */ +#ifndef _RecoveryManagerImpl_ +#define _RecoveryManagerImpl_ + +#include <list> +#include "qpid/broker/DtxManager.h" +#include "qpid/broker/ExchangeRegistry.h" +#include "qpid/broker/QueueRegistry.h" +#include "qpid/broker/LinkRegistry.h" +#include "qpid/broker/RecoveryManager.h" + +namespace qpid { +namespace broker { + + class RecoveryManagerImpl : public RecoveryManager{ + QueueRegistry& queues; + ExchangeRegistry& exchanges; + LinkRegistry& links; + DtxManager& dtxMgr; + public: + RecoveryManagerImpl(QueueRegistry& queues, ExchangeRegistry& exchanges, LinkRegistry& links, + DtxManager& dtxMgr); + ~RecoveryManagerImpl(); + + RecoverableExchange::shared_ptr recoverExchange(framing::Buffer& buffer); + RecoverableQueue::shared_ptr recoverQueue(framing::Buffer& buffer); + RecoverableMessage::shared_ptr recoverMessage(framing::Buffer& buffer); + RecoverableTransaction::shared_ptr recoverTransaction(const std::string& xid, + std::auto_ptr<TPCTransactionContext> txn); + RecoverableConfig::shared_ptr recoverConfig(framing::Buffer& buffer); + void recoveryComplete(); + }; + + +} +} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/RetryList.cpp b/qpid/cpp/src/qpid/broker/RetryList.cpp new file mode 100644 index 0000000000..b0477dd0f7 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/RetryList.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 "qpid/broker/RetryList.h" + +namespace qpid { +namespace broker { + +RetryList::RetryList() : urlIndex(0), addressIndex(0) {} + +void RetryList::reset(const std::vector<Url>& u) +{ + urls = u; + urlIndex = addressIndex = 0;//reset indices +} + +bool RetryList::next(Address& address) +{ + while (urlIndex < urls.size()) { + if (addressIndex < urls[urlIndex].size()) { + address = urls[urlIndex][addressIndex++]; + return true; + } + urlIndex++; + addressIndex = 0; + } + urlIndex = addressIndex = 0;//reset indices + return false; +} + +std::ostream& operator<<(std::ostream& os, const RetryList& l) +{ + for (size_t i = 0; i < l.urls.size(); i++) { + os << l.urls[i] << " "; + } + return os; +} + +}} // namespace qpid::broker diff --git a/qpid/cpp/src/qpid/broker/RetryList.h b/qpid/cpp/src/qpid/broker/RetryList.h new file mode 100644 index 0000000000..242a7d2122 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/RetryList.h @@ -0,0 +1,54 @@ +#ifndef QPID_BROKER_RETRYLIST_H +#define QPID_BROKER_RETRYLIST_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/BrokerImportExport.h" +#include "qpid/Address.h" +#include "qpid/Url.h" + +namespace qpid { +namespace broker { + +/** + * Simple utility for managing a list of urls to try on reconnecting a + * link. Currently only supports TCP urls. + */ +class RetryList +{ + public: + QPID_BROKER_EXTERN RetryList(); + QPID_BROKER_EXTERN void reset(const std::vector<Url>& urls); + QPID_BROKER_EXTERN bool next(Address& address); + private: + std::vector<Url> urls; + size_t urlIndex; + size_t addressIndex; + + friend std::ostream& operator<<(std::ostream& os, const RetryList& l); +}; + +std::ostream& operator<<(std::ostream& os, const RetryList& l); + +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_RETRYLIST_H*/ diff --git a/qpid/cpp/src/qpid/broker/SaslAuthenticator.cpp b/qpid/cpp/src/qpid/broker/SaslAuthenticator.cpp new file mode 100644 index 0000000000..acdb4934d4 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/SaslAuthenticator.cpp @@ -0,0 +1,467 @@ +/* + * + * 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. + * + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "qpid/broker/Connection.h" +#include "qpid/log/Statement.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/sys/SecuritySettings.h" +#include <boost/format.hpp> + +#if HAVE_SASL +#include <sasl/sasl.h> +#include "qpid/sys/cyrus/CyrusSecurityLayer.h" +using qpid::sys::cyrus::CyrusSecurityLayer; +#endif + +using namespace qpid::framing; +using qpid::sys::SecurityLayer; +using qpid::sys::SecuritySettings; +using boost::format; +using boost::str; + + +namespace qpid { +namespace broker { + + + +class NullAuthenticator : public SaslAuthenticator +{ + Connection& connection; + framing::AMQP_ClientProxy::Connection client; + std::string realm; + const bool encrypt; +public: + NullAuthenticator(Connection& connection, bool encrypt); + ~NullAuthenticator(); + void getMechanisms(framing::Array& mechanisms); + void start(const std::string& mechanism, const std::string& response); + void step(const std::string&) {} + std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize); +}; + +#if HAVE_SASL + + + +class CyrusAuthenticator : public SaslAuthenticator +{ + sasl_conn_t *sasl_conn; + Connection& connection; + framing::AMQP_ClientProxy::Connection client; + const bool encrypt; + + void processAuthenticationStep(int code, const char *challenge, unsigned int challenge_len); + bool getUsername(std::string& uid); + +public: + CyrusAuthenticator(Connection& connection, bool encrypt); + ~CyrusAuthenticator(); + void init(); + void getMechanisms(framing::Array& mechanisms); + 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); } + std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize); +}; + +bool SaslAuthenticator::available(void) { + return true; +} + +// Initialize the SASL mechanism; throw if it fails. +void SaslAuthenticator::init(const std::string& saslName, std::string const & saslConfigPath ) +{ + // 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(SASL_OK != code) + throw Exception(QPID_MSG("SASL: sasl_set_path failed [" << code << "] " )); + QPID_LOG(info, "SASL: config path set to " << saslConfigPath ); + } +#endif + + int code = sasl_server_init(NULL, saslName.c_str()); + if (code != SASL_OK) { + // TODO: Figure out who owns the char* returned by + // sasl_errstring, though it probably does not matter much + throw Exception(sasl_errstring(code, NULL, NULL)); + } +} + +void SaslAuthenticator::fini(void) +{ + sasl_done(); +} + +#else + +typedef NullAuthenticator CyrusAuthenticator; + +bool SaslAuthenticator::available(void) { + return false; +} + +void SaslAuthenticator::init(const std::string& /*saslName*/, std::string const & /*saslConfigPath*/ ) +{ + throw Exception("Requested authentication but SASL unavailable"); +} + +void SaslAuthenticator::fini(void) +{ + return; +} + +#endif + +std::auto_ptr<SaslAuthenticator> SaslAuthenticator::createAuthenticator(Connection& c, bool isShadow ) +{ + if (c.getBroker().getOptions().auth) { + if ( isShadow ) + return std::auto_ptr<SaslAuthenticator>(new NullAuthenticator(c, c.getBroker().getOptions().requireEncrypted)); + else + return std::auto_ptr<SaslAuthenticator>(new CyrusAuthenticator(c, c.getBroker().getOptions().requireEncrypted)); + } else { + QPID_LOG(debug, "SASL: No Authentication Performed"); + return std::auto_ptr<SaslAuthenticator>(new NullAuthenticator(c, c.getBroker().getOptions().requireEncrypted)); + } +} + + +NullAuthenticator::NullAuthenticator(Connection& c, bool e) : connection(c), client(c.getOutput()), + realm(c.getBroker().getOptions().realm), encrypt(e) {} +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")));//useful for testing +} + +void NullAuthenticator::start(const string& mechanism, const string& response) +{ + if (encrypt) { +#if HAVE_SASL + // encryption required - check to see if we are running over an + // encrypted SSL connection. + SecuritySettings external = connection.getExternalSecuritySettings(); + sasl_ssf_t external_ssf = (sasl_ssf_t) external.ssf; + if (external_ssf < 1) // < 1 == unencrypted +#endif + { + QPID_LOG(error, "Rejected un-encrypted connection."); + throw ConnectionForcedException("Connection must be encrypted."); + } + } + if (mechanism == "PLAIN") { // Old behavior + if (response.size() > 0) { + string uid; + 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); + } else if (i != string::npos) { + //authorization id is first null delimited field + 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 + i = uid.find(realm); + if (i == string::npos || realm.size() + i < uid.size()) { + uid = str(format("%1%@%2%") % uid % realm); + } + connection.setUserId(uid); + } + } + } else { + connection.setUserId("anonymous"); + } + client.tune(framing::CHANNEL_MAX, connection.getFrameMax(), 0, connection.getHeartbeatMax()); +} + + +std::auto_ptr<SecurityLayer> NullAuthenticator::getSecurityLayer(uint16_t) +{ + std::auto_ptr<SecurityLayer> securityLayer; + return securityLayer; +} + + +#if HAVE_SASL + +CyrusAuthenticator::CyrusAuthenticator(Connection& c, bool _encrypt) : + sasl_conn(0), connection(c), client(c.getOutput()), encrypt(_encrypt) +{ + init(); +} + +void CyrusAuthenticator::init() +{ + /* Next to the service name, which specifies the + * /etc/sasl2/<service name>.conf file to read, the realm is + * currently the most important argument below. When + * performing authentication the user that is authenticating + * will be looked up in a specific realm. If none is given + * then the realm defaults to the hostname, which can cause + * confusion when the daemon is run on different hosts that + * may be logically sharing a realm (aka a user domain). This + * is especially important for SASL PLAIN authentication, + * which cannot specify a realm for the user that is + * authenticating. + */ + int code; + + const char *realm = connection.getBroker().getOptions().realm.c_str(); + code = sasl_server_new(BROKER_SASL_NAME, /* Service name */ + NULL, /* Server FQDN, gethostname() */ + realm, /* Authentication realm */ + NULL, /* Local IP, needed for some mechanism */ + NULL, /* Remote IP, needed for some mechanism */ + NULL, /* Callbacks */ + 0, /* Connection flags */ + &sasl_conn); + + if (SASL_OK != code) { + QPID_LOG(error, "SASL: Connection creation failed: [" << code << "] " << sasl_errdetail(sasl_conn)); + + // TODO: Change this to an exception signaling + // server error, when one is available + throw ConnectionForcedException("Unable to perform authentication"); + } + + sasl_security_properties_t secprops; + + //TODO: should the actual SSF values be configurable here? + secprops.min_ssf = encrypt ? 10: 0; + secprops.max_ssf = 256; + + // If the transport provides encryption, notify the SASL library of + // the key length and set the ssf range to prevent double encryption. + SecuritySettings external = connection.getExternalSecuritySettings(); + QPID_LOG(debug, "External ssf=" << external.ssf << " and auth=" << external.authid); + sasl_ssf_t external_ssf = (sasl_ssf_t) external.ssf; + if (external_ssf) { + int result = sasl_setprop(sasl_conn, SASL_SSF_EXTERNAL, &external_ssf); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL error: unable to set external SSF: " << result)); + } + + secprops.max_ssf = secprops.min_ssf = 0; + } + + QPID_LOG(debug, "min_ssf: " << secprops.min_ssf << + ", max_ssf: " << secprops.max_ssf << + ", external_ssf: " << external_ssf ); + + if (!external.authid.empty()) { + const char* external_authid = external.authid.c_str(); + int result = sasl_setprop(sasl_conn, SASL_AUTH_EXTERNAL, external_authid); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL error: unable to set external auth: " << result)); + } + + QPID_LOG(debug, "external auth detected and set to " << external_authid); + } + + secprops.maxbufsize = 65535; + secprops.property_names = 0; + secprops.property_values = 0; + secprops.security_flags = 0; /* or SASL_SEC_NOANONYMOUS etc as appropriate */ + /* + * The nodict flag restricts SASL authentication mechanisms + * to those that are not susceptible to dictionary attacks. + * They are: + * SRP + * PASSDSS-3DES-1 + * EXTERNAL + */ + if (external.nodict) secprops.security_flags |= SASL_SEC_NODICTIONARY; + int result = sasl_setprop(sasl_conn, SASL_SEC_PROPS, &secprops); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL error: " << result)); + } +} + +CyrusAuthenticator::~CyrusAuthenticator() +{ + if (sasl_conn) { + sasl_dispose(&sasl_conn); + sasl_conn = 0; + } +} + +void CyrusAuthenticator::getError(string& error) +{ + error = string(sasl_errdetail(sasl_conn)); +} + +bool CyrusAuthenticator::getUsername(string& uid) +{ + const void* ptr; + + int code = sasl_getprop(sasl_conn, SASL_USERNAME, &ptr); + if (SASL_OK == code) { + uid = string(const_cast<char*>(static_cast<const char*>(ptr))); + return true; + } else { + QPID_LOG(warning, "Failed to retrieve sasl username"); + return false; + } +} + +void CyrusAuthenticator::getMechanisms(Array& mechanisms) +{ + const char *separator = " "; + const char *list; + unsigned int list_len; + int count; + + int code = sasl_listmech(sasl_conn, NULL, + "", separator, "", + &list, &list_len, + &count); + + if (SASL_OK != code) { + QPID_LOG(info, "SASL: Mechanism listing failed: " << sasl_errdetail(sasl_conn)); + + // TODO: Change this to an exception signaling + // server error, when one is available + throw ConnectionForcedException("Mechanism listing failed"); + } else { + string mechanism; + unsigned int start; + unsigned int end; + + QPID_LOG(info, "SASL: Mechanism list: " << list); + + end = 0; + do { + start = end; + + // Seek to end of next mechanism + while (end < list_len && separator[0] != list[end]) + end++; + + // Record the mechanism + mechanisms.add(boost::shared_ptr<FieldValue>(new Str16Value(string(list, start, end - start)))); + end++; + } while (end < list_len); + } +} + +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); + int code = sasl_server_start(sasl_conn, + mechanism.c_str(), + response.c_str(), response.length(), + &challenge, &challenge_len); + + processAuthenticationStep(code, challenge, challenge_len); +} + +void CyrusAuthenticator::step(const string& response) +{ + const char *challenge; + unsigned int challenge_len; + + int code = sasl_server_step(sasl_conn, + response.c_str(), response.length(), + &challenge, &challenge_len); + + processAuthenticationStep(code, challenge, challenge_len); +} + +void CyrusAuthenticator::processAuthenticationStep(int code, const char *challenge, unsigned int challenge_len) +{ + if (SASL_OK == code) { + std::string uid; + if (!getUsername(uid)) { + // TODO: Change this to an exception signaling + // authentication failure, when one is available + throw ConnectionForcedException("Authenticated username unavailable"); + } + QPID_LOG(info, connection.getMgmtId() << " SASL: Authentication succeeded for: " << uid); + + connection.setUserId(uid); + + client.tune(framing::CHANNEL_MAX, connection.getFrameMax(), 0, connection.getHeartbeatMax()); + } else if (SASL_CONTINUE == code) { + string challenge_str(challenge, challenge_len); + + QPID_LOG(debug, "SASL: sending challenge to client"); + + client.secure(challenge_str); + } else { + std::string uid; + if (!getUsername(uid)) { + QPID_LOG(info, "SASL: Authentication failed (no username available):" << sasl_errdetail(sasl_conn)); + } else { + QPID_LOG(info, "SASL: Authentication failed for " << uid << ":" << sasl_errdetail(sasl_conn)); + } + + // TODO: Change to more specific exceptions, when they are + // available + switch (code) { + case SASL_NOMECH: + throw ConnectionForcedException("Unsupported mechanism"); + break; + case SASL_TRYAGAIN: + throw ConnectionForcedException("Transient failure, try again"); + break; + default: + throw ConnectionForcedException("Authentication failed"); + break; + } + } +} + +std::auto_ptr<SecurityLayer> CyrusAuthenticator::getSecurityLayer(uint16_t maxFrameSize) +{ + + const void* value(0); + int result = sasl_getprop(sasl_conn, SASL_SSF, &value); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL error: " << sasl_errdetail(sasl_conn))); + } + uint ssf = *(reinterpret_cast<const unsigned*>(value)); + std::auto_ptr<SecurityLayer> securityLayer; + if (ssf) { + securityLayer = std::auto_ptr<SecurityLayer>(new CyrusSecurityLayer(sasl_conn, maxFrameSize)); + } + return securityLayer; +} + +#endif + +}} diff --git a/qpid/cpp/src/qpid/broker/SaslAuthenticator.h b/qpid/cpp/src/qpid/broker/SaslAuthenticator.h new file mode 100644 index 0000000000..cfbe1a0cd1 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/SaslAuthenticator.h @@ -0,0 +1,64 @@ +/* + * + * 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 _SaslAuthenticator_ +#define _SaslAuthenticator_ + + +#include "qpid/framing/amqp_types.h" +#include "qpid/framing/AMQP_ClientProxy.h" +#include "qpid/Exception.h" +#include "qpid/sys/SecurityLayer.h" +#include <memory> +#include <vector> +#include <boost/bind.hpp> +#include <boost/function.hpp> + +namespace qpid { +namespace broker { + +class Connection; + +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 step(const std::string& response) = 0; + virtual void getUid(std::string&) {} + virtual bool getUsername(std::string&) { return false; }; + virtual void getError(std::string&) {} + virtual std::auto_ptr<qpid::sys::SecurityLayer> getSecurityLayer(uint16_t maxFrameSize) = 0; + + static bool available(void); + + // Initialize the SASL mechanism; throw if it fails. + static void init(const std::string& saslName, std::string const & saslConfigPath ); + static void fini(void); + + static std::auto_ptr<SaslAuthenticator> createAuthenticator(Connection& connection, bool isShadow); + + virtual void callUserIdCallbacks() { } +}; + +}} + +#endif diff --git a/qpid/cpp/src/qpid/broker/SecureConnection.cpp b/qpid/cpp/src/qpid/broker/SecureConnection.cpp new file mode 100644 index 0000000000..5c1ebf3e8b --- /dev/null +++ b/qpid/cpp/src/qpid/broker/SecureConnection.cpp @@ -0,0 +1,90 @@ +/* + * + * 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/SecureConnection.h" +#include "qpid/sys/SecurityLayer.h" +#include "qpid/framing/reply_exceptions.h" + +namespace qpid { +namespace broker { + +using qpid::sys::SecurityLayer; + +SecureConnection::SecureConnection() : secured(false) {} + +size_t SecureConnection::decode(const char* buffer, size_t size) +{ + if (!secured && securityLayer.get()) { + //security layer comes into effect on first read after its + //activated + secured = true; + } + if (secured) { + return securityLayer->decode(buffer, size); + } else { + return codec->decode(buffer, size); + } +} + +size_t SecureConnection::encode(const char* buffer, size_t size) +{ + if (secured) { + return securityLayer->encode(buffer, size); + } else { + return codec->encode(buffer, size); + } +} + +bool SecureConnection::canEncode() +{ + if (secured) return securityLayer->canEncode(); + else return codec->canEncode(); +} + +void SecureConnection::closed() +{ + codec->closed(); +} + +bool SecureConnection::isClosed() const +{ + return codec->isClosed(); +} + +framing::ProtocolVersion SecureConnection::getVersion() const +{ + return codec->getVersion(); +} + +void SecureConnection:: setCodec(std::auto_ptr<ConnectionCodec> c) +{ + codec = c; +} + +void SecureConnection::activateSecurityLayer(std::auto_ptr<SecurityLayer> sl, bool secureImmediately) +{ + securityLayer = sl; + securityLayer->init(codec.get()); + + if ( secureImmediately ) + secured = true; +} + +}} // namespace qpid::broker diff --git a/qpid/cpp/src/qpid/broker/SecureConnection.h b/qpid/cpp/src/qpid/broker/SecureConnection.h new file mode 100644 index 0000000000..1547faae1e --- /dev/null +++ b/qpid/cpp/src/qpid/broker/SecureConnection.h @@ -0,0 +1,60 @@ +#ifndef QPID_BROKER_SECURECONNECTION_H +#define QPID_BROKER_SECURECONNECTION_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/ConnectionCodec.h" +#include <memory> + +namespace qpid { + +namespace sys { +class SecurityLayer; +} + +namespace broker { + +/** + * A ConnectionCodec 'wrapper' that allows a connection to be + * 'secured' e.g. encrypted based on settings negotiatiated at the + * time of establishment. + */ +class SecureConnection : public qpid::sys::ConnectionCodec +{ + public: + SecureConnection(); + size_t decode(const char* buffer, size_t size); + size_t encode(const char* buffer, size_t size); + bool canEncode(); + void closed(); + bool isClosed() const; + framing::ProtocolVersion getVersion() const; + void setCodec(std::auto_ptr<ConnectionCodec>); + void activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer>, bool secureImmediately=false); + private: + std::auto_ptr<ConnectionCodec> codec; + std::auto_ptr<qpid::sys::SecurityLayer> securityLayer; + bool secured; +}; +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_SECURECONNECTION_H*/ diff --git a/qpid/cpp/src/qpid/broker/SecureConnectionFactory.cpp b/qpid/cpp/src/qpid/broker/SecureConnectionFactory.cpp new file mode 100644 index 0000000000..754b443c22 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/SecureConnectionFactory.cpp @@ -0,0 +1,75 @@ +/* + * + * 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/SecureConnectionFactory.h" +#include "qpid/framing/ProtocolVersion.h" +#include "qpid/amqp_0_10/Connection.h" +#include "qpid/broker/Connection.h" +#include "qpid/broker/SecureConnection.h" +#include "qpid/sys/SecuritySettings.h" +#include "qpid/log/Statement.h" + +namespace qpid { +namespace broker { + +using framing::ProtocolVersion; +using qpid::sys::SecuritySettings; +typedef std::auto_ptr<amqp_0_10::Connection> CodecPtr; +typedef std::auto_ptr<SecureConnection> SecureConnectionPtr; +typedef std::auto_ptr<Connection> ConnectionPtr; +typedef std::auto_ptr<sys::ConnectionInputHandler> InputPtr; + +SecureConnectionFactory::SecureConnectionFactory(Broker& b) : broker(b) {} + +sys::ConnectionCodec* +SecureConnectionFactory::create(ProtocolVersion v, sys::OutputControl& out, const std::string& id, + const SecuritySettings& external) { + if (broker.getConnectionCounter().allowConnection()) + { + QPID_LOG(error, "Client max connection count limit exceeded: " << broker.getOptions().maxConnections << " connection refused"); + return 0; + } + if (v == ProtocolVersion(0, 10)) { + SecureConnectionPtr sc(new SecureConnection()); + CodecPtr c(new amqp_0_10::Connection(out, id, false)); + ConnectionPtr i(new broker::Connection(c.get(), broker, id, external, false)); + i->setSecureConnection(sc.get()); + c->setInputHandler(InputPtr(i.release())); + sc->setCodec(std::auto_ptr<sys::ConnectionCodec>(c)); + return sc.release(); + } + return 0; +} + +sys::ConnectionCodec* +SecureConnectionFactory::create(sys::OutputControl& out, const std::string& id, + const SecuritySettings& external) { + // used to create connections from one broker to another + SecureConnectionPtr sc(new SecureConnection()); + CodecPtr c(new amqp_0_10::Connection(out, id, true)); + ConnectionPtr i(new broker::Connection(c.get(), broker, id, external, true )); + i->setSecureConnection(sc.get()); + c->setInputHandler(InputPtr(i.release())); + sc->setCodec(std::auto_ptr<sys::ConnectionCodec>(c)); + return sc.release(); +} + + +}} // namespace qpid::broker diff --git a/qpid/cpp/src/qpid/broker/SecureConnectionFactory.h b/qpid/cpp/src/qpid/broker/SecureConnectionFactory.h new file mode 100644 index 0000000000..8a04dfcb15 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/SecureConnectionFactory.h @@ -0,0 +1,49 @@ +/* + * + * 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 _SecureConnectionFactory_ +#define _SecureConnectionFactory_ + +#include "qpid/sys/ConnectionCodec.h" + +namespace qpid { +namespace broker { +class Broker; + +class SecureConnectionFactory : public sys::ConnectionCodec::Factory +{ + public: + SecureConnectionFactory(Broker& b); + + sys::ConnectionCodec* + create(framing::ProtocolVersion, sys::OutputControl&, const std::string& id, + const qpid::sys::SecuritySettings&); + + sys::ConnectionCodec* + create(sys::OutputControl&, const std::string& id, const qpid::sys::SecuritySettings&); + + private: + Broker& broker; +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/SemanticState.cpp b/qpid/cpp/src/qpid/broker/SemanticState.cpp new file mode 100644 index 0000000000..ce86253f4a --- /dev/null +++ b/qpid/cpp/src/qpid/broker/SemanticState.cpp @@ -0,0 +1,823 @@ +/* + * + * 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/SessionState.h" +#include "qpid/broker/Connection.h" +#include "qpid/broker/DeliverableMessage.h" +#include "qpid/broker/DtxAck.h" +#include "qpid/broker/DtxTimeout.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/SessionContext.h" +#include "qpid/broker/SessionOutputException.h" +#include "qpid/broker/TxAccept.h" +#include "qpid/broker/TxPublish.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/SequenceSet.h" +#include "qpid/framing/IsInSequenceSet.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/ClusterSafe.h" +#include "qpid/ptr_map.h" +#include "qpid/broker/AclModule.h" + +#include <boost/bind.hpp> +#include <boost/format.hpp> + +#include <iostream> +#include <sstream> +#include <algorithm> +#include <functional> + +#include <assert.h> + +namespace qpid { +namespace broker { + +using namespace std; +using boost::intrusive_ptr; +using boost::bind; +using namespace qpid::broker; +using namespace qpid::framing; +using namespace qpid::sys; +using qpid::ptr_map_ptr; +using qpid::management::ManagementAgent; +using qpid::management::ManagementObject; +using qpid::management::Manageable; +using qpid::management::Args; +namespace _qmf = qmf::org::apache::qpid::broker; + +SemanticState::SemanticState(DeliveryAdapter& da, SessionContext& ss) + : session(ss), + deliveryAdapter(da), + tagGenerator("sgen"), + dtxSelected(false), + authMsg(getSession().getBroker().getOptions().auth && !getSession().getConnection().isFederationLink()), + 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(); +} + +void SemanticState::closed() { + if (!closeComplete) { + //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(); + } + recover(true); + + //now unsubscribe, which may trigger queue deletion and thus + //needs to occur after the requeueing of unacked messages + for (ConsumerImplMap::iterator i = consumers.begin(); i != consumers.end(); i++) { + unsubscribe(i->second); + } + closeComplete = true; + } +} + +bool SemanticState::exists(const string& consumerTag){ + return consumers.find(consumerTag) != consumers.end(); +} + +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)); + queue->consume(c, exclusive);//may throw exception + consumers[tag] = c; +} + +bool SemanticState::cancel(const string& tag) +{ + ConsumerImplMap::iterator i = consumers.find(tag); + if (i != consumers.end()) { + cancel(i->second); + consumers.erase(i); + //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)); + return true; + } else { + return false; + } +} + + +void SemanticState::startTx() +{ + txBuffer = TxBuffer::shared_ptr(new TxBuffer()); +} + +void SemanticState::commit(MessageStore* const store) +{ + if (!txBuffer) throw + CommandInvalidException(QPID_MSG("Session has not been selected for use with transactions")); + + TxOp::shared_ptr txAck(static_cast<TxOp*>(new TxAccept(accumulatedAck, unacked))); + txBuffer->enlist(txAck); + if (txBuffer->commitLocal(store)) { + accumulatedAck.clear(); + } else { + throw InternalErrorException(QPID_MSG("Commit failed")); + } +} + +void SemanticState::rollback() +{ + if (!txBuffer) + throw CommandInvalidException(QPID_MSG("Session has not been selected for use with transactions")); + + txBuffer->rollback(); + accumulatedAck.clear(); +} + +void SemanticState::selectDtx() +{ + dtxSelected = true; +} + +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); + if (join) { + mgr.join(xid, dtxBuffer); + } else { + mgr.start(xid, dtxBuffer); + } +} + +void SemanticState::endDtx(const std::string& xid, bool fail) +{ + if (!dtxBuffer) { + throw IllegalStateException(QPID_MSG("xid " << xid << " not associated with this session")); + } + if (dtxBuffer->getXid() != xid) { + throw CommandInvalidException( + QPID_MSG("xid specified on start was " << dtxBuffer->getXid() << ", but " << xid << " specified on end")); + + } + + txBuffer.reset();//ops on this session no longer transactional + + checkDtxTimeout(); + if (fail) { + dtxBuffer->fail(); + } else { + dtxBuffer->markEnded(); + } + dtxBuffer.reset(); +} + +void SemanticState::suspendDtx(const std::string& xid) +{ + if (dtxBuffer->getXid() != xid) { + throw CommandInvalidException( + QPID_MSG("xid specified on start was " << dtxBuffer->getXid() << ", but " << xid << " specified on suspend")); + } + txBuffer.reset();//ops on this session no longer transactional + + checkDtxTimeout(); + dtxBuffer->setSuspended(true); + suspendedXids[xid] = dtxBuffer; + dtxBuffer.reset(); +} + +void SemanticState::resumeDtx(const std::string& xid) +{ + if (!dtxSelected) { + throw CommandInvalidException(QPID_MSG("Session has not been selected for use with dtx")); + } + + dtxBuffer = suspendedXids[xid]; + if (!dtxBuffer) { + throw CommandInvalidException(QPID_MSG("xid " << xid << " not attached")); + } else { + suspendedXids.erase(xid); + } + + if (dtxBuffer->getXid() != xid) { + throw CommandInvalidException( + QPID_MSG("xid specified on start was " << dtxBuffer->getXid() << ", but " << xid << " specified on resume")); + + } + if (!dtxBuffer->isSuspended()) { + throw CommandInvalidException(QPID_MSG("xid " << xid << " not suspended")); + } + + checkDtxTimeout(); + dtxBuffer->setSuspended(false); + txBuffer = boost::static_pointer_cast<TxBuffer>(dtxBuffer); +} + +void SemanticState::checkDtxTimeout() +{ + if (dtxBuffer->isExpired()) { + dtxBuffer.reset(); + throw DtxTimeoutException(); + } +} + +void SemanticState::record(const DeliveryRecord& delivery) +{ + unacked.push_back(delivery); +} + +const std::string QPID_SYNC_FREQUENCY("qpid.sync_frequency"); + +SemanticState::ConsumerImpl::ConsumerImpl(SemanticState* _parent, + const string& _name, + Queue::shared_ptr _queue, + bool ack, + bool _acquire, + bool _exclusive, + const string& _resumeId, + uint64_t _resumeTtl, + const framing::FieldTable& _arguments + + +) : + Consumer(_acquire), + parent(_parent), + name(_name), + queue(_queue), + ackExpected(ack), + acquire(_acquire), + blocked(true), + windowing(true), + exclusive(_exclusive), + resumeId(_resumeId), + resumeTtl(_resumeTtl), + arguments(_arguments), + msgCredit(0), + byteCredit(0), + notifyEnabled(true), + syncFrequency(_arguments.getAsInt(QPID_SYNC_FREQUENCY)), + deliveryCount(0), + mgmtObject(0) +{ + if (parent != 0 && queue.get() != 0 && queue->GetManagementObject() !=0) + { + 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, + !acquire, ackExpected, exclusive, ManagementAgent::toMap(arguments)); + agent->addObject (mgmtObject); + mgmtObject->set_creditMode("WINDOW"); + } + } +} + +ManagementObject* SemanticState::ConsumerImpl::GetManagementObject (void) const +{ + return (ManagementObject*) mgmtObject; +} + +Manageable::status_t SemanticState::ConsumerImpl::ManagementMethod (uint32_t methodId, Args&, string&) +{ + Manageable::status_t status = Manageable::STATUS_UNKNOWN_METHOD; + + QPID_LOG (debug, "Queue::ManagementMethod [id=" << methodId << "]"); + + return status; +} + + +OwnershipToken* SemanticState::ConsumerImpl::getSession() +{ + return &(parent->session); +} + +bool SemanticState::ConsumerImpl::deliver(QueuedMessage& msg) +{ + assertClusterSafe(); + allocateCredit(msg.payload); + DeliveryRecord record(msg, queue, name, 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 (mgmtObject) { mgmtObject->inc_delivered(); } + return true; +} + +bool SemanticState::ConsumerImpl::filter(intrusive_ptr<Message>) +{ + return true; +} + +bool SemanticState::ConsumerImpl::accept(intrusive_ptr<Message> msg) +{ + assertClusterSafe(); + // FIXME aconway 2009-06-08: if we have byte & message credit but + // 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; +} + +namespace { +struct ConsumerName { + const SemanticState::ConsumerImpl& consumer; + ConsumerName(const SemanticState::ConsumerImpl& ci) : consumer(ci) {} +}; + +ostream& operator<<(ostream& o, const ConsumerName& pc) { + return o << pc.consumer.getName() << " on " + << pc.consumer.getParent().getSession().getSessionId(); +} +} + +void SemanticState::ConsumerImpl::allocateCredit(intrusive_ptr<Message>& msg) +{ + assertClusterSafe(); + uint32_t originalMsgCredit = msgCredit; + uint32_t originalByteCredit = byteCredit; + if (msgCredit != 0xFFFFFFFF) { + msgCredit--; + } + if (byteCredit != 0xFFFFFFFF) { + byteCredit -= msg->getRequiredCredit(); + } + 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) +{ + bool enoughCredit = msgCredit > 0 && + (byteCredit == 0xFFFFFFFF || byteCredit >= msg->getRequiredCredit()); + QPID_LOG(debug, (enoughCredit ? "Sufficient credit for " : "Insufficient credit for ") + << ConsumerName(*this) + << ", have bytes: " << byteCredit << " msgs: " << msgCredit + << ", need " << msg->getRequiredCredit() << " bytes"); + return enoughCredit; +} + +SemanticState::ConsumerImpl::~ConsumerImpl() +{ + if (mgmtObject != 0) + mgmtObject->resourceDestroy (); +} + +void SemanticState::disable(ConsumerImpl::shared_ptr c) +{ + c->disableNotify(); + if (session.isAttached()) + session.getConnection().outputTasks.removeOutputTask(c.get()); +} + +void SemanticState::unsubscribe(ConsumerImpl::shared_ptr c) +{ + Queue::shared_ptr queue = c->getQueue(); + if(queue) { + queue->cancel(c); + if (queue->canAutoDelete() && !queue->hasExclusiveOwner()) { + Queue::tryAutoDelete(session.getBroker(), queue); + } + } +} + +void SemanticState::cancel(ConsumerImpl::shared_ptr c) +{ + disable(c); + unsubscribe(c); +} + +void SemanticState::handle(intrusive_ptr<Message> msg) { + if (txBuffer.get()) { + TxPublish* deliverable(new TxPublish(msg)); + TxOp::shared_ptr op(deliverable); + route(msg, *deliverable); + txBuffer->enlist(op); + } else { + DeliverableMessage deliverable(msg); + route(msg, deliverable); + if (msg->isContentReleaseRequested()) { + // NOTE: The log messages in this section are used for flow-to-disk testing (which checks the log for the + // presence of these messages). Do not change these without also checking these tests. + if (msg->isContentReleaseBlocked()) { + QPID_LOG(debug, "Message id=\"" << msg->getProperties<MessageProperties>()->getMessageId() << "\"; pid=0x" << + std::hex << msg->getPersistenceId() << std::dec << ": Content release blocked"); + } else { + msg->releaseContent(); + QPID_LOG(debug, "Message id=\"" << msg->getProperties<MessageProperties>()->getMessageId() << "\"; pid=0x" << + std::hex << msg->getPersistenceId() << std::dec << ": Content released"); + } + } + } +} + +namespace +{ +const std::string nullstring; +} + +void SemanticState::route(intrusive_ptr<Message> msg, Deliverable& strategy) { + msg->setTimestamp(getSession().getBroker().getExpiryPolicy()); + + std::string exchangeName = msg->getExchangeName(); + 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)); + } + + if (acl && acl->doTransferAcl()) + { + if (!acl->authorise(getSession().getConnection().getUserId(),acl::ACT_PUBLISH,acl::OBJ_EXCHANGE,exchangeName, msg->getRoutingKey() )) + throw UnauthorizedAccessException(QPID_MSG(userID << " cannot publish to " << + exchangeName << " with routing-key " << msg->getRoutingKey())); + } + + cacheExchange->route(strategy, msg->getRoutingKey(), msg->getApplicationHeaders()); + + if (!strategy.delivered) { + //TODO:if discard-unroutable, just drop 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()); + } + if (!strategy.delivered) { + msg->destroy(); + } + } + +} + +void SemanticState::requestDispatch() +{ + for (ConsumerImplMap::iterator i = consumers.begin(); i != consumers.end(); i++) + i->second->requestDispatch(); +} + +void SemanticState::ConsumerImpl::requestDispatch() +{ + assertClusterSafe(); + if (blocked) { + parent->session.getConnection().outputTasks.addOutputTask(this); + parent->session.getConnection().outputTasks.activateOutput(); + blocked = false; + } +} + +bool SemanticState::complete(DeliveryRecord& delivery) +{ + ConsumerImplMap::iterator i = consumers.find(delivery.getTag()); + if (i != consumers.end()) { + i->second->complete(delivery); + } + return delivery.isRedundant(); +} + +void SemanticState::ConsumerImpl::complete(DeliveryRecord& delivery) +{ + if (!delivery.isComplete()) { + delivery.complete(); + if (windowing) { + if (msgCredit != 0xFFFFFFFF) msgCredit++; + if (byteCredit != 0xFFFFFFFF) byteCredit += delivery.getCredit(); + } + } +} + +void SemanticState::recover(bool requeue) +{ + if(requeue){ + //take copy and clear unacked as requeue may result in redelivery to this session + //which will in turn result in additions to unacked + DeliveryRecords copy = unacked; + 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)); + //unconfirmed messages re redelivered and therefore have their + //id adjusted, confirmed messages are not and so the ordering + //w.r.t id is lost + sort(unacked.begin(), unacked.end()); + } +} + +void SemanticState::deliver(DeliveryRecord& msg, bool sync) +{ + return deliveryAdapter.deliver(msg, sync); +} + +SemanticState::ConsumerImpl& SemanticState::find(const std::string& destination) +{ + ConsumerImplMap::iterator i = consumers.find(destination); + if (i == consumers.end()) { + throw NotFoundException(QPID_MSG("Unknown destination " << destination)); + } else { + return *(i->second); + } +} + +void SemanticState::setWindowMode(const std::string& destination) +{ + find(destination).setWindowMode(); +} + +void SemanticState::setCreditMode(const std::string& destination) +{ + find(destination).setCreditMode(); +} + +void SemanticState::addByteCredit(const std::string& destination, uint32_t value) +{ + ConsumerImpl& 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(); +} + +void SemanticState::flush(const std::string& destination) +{ + find(destination).flush(); +} + + +void SemanticState::stop(const std::string& destination) +{ + find(destination).stop(); +} + +void SemanticState::ConsumerImpl::setWindowMode() +{ + assertClusterSafe(); + windowing = true; + if (mgmtObject){ + mgmtObject->set_creditMode("WINDOW"); + } +} + +void SemanticState::ConsumerImpl::setCreditMode() +{ + assertClusterSafe(); + windowing = false; + if (mgmtObject){ + mgmtObject->set_creditMode("CREDIT"); + } +} + +void SemanticState::ConsumerImpl::addByteCredit(uint32_t value) +{ + assertClusterSafe(); + if (byteCredit != 0xFFFFFFFF) { + if (value == 0xFFFFFFFF) byteCredit = value; + else byteCredit += value; + } +} + +void SemanticState::ConsumerImpl::addMessageCredit(uint32_t value) +{ + assertClusterSafe(); + if (msgCredit != 0xFFFFFFFF) { + if (value == 0xFFFFFFFF) msgCredit = value; + else msgCredit += value; + } +} + +bool SemanticState::ConsumerImpl::haveCredit() +{ + if (msgCredit && byteCredit) { + return true; + } else { + blocked = true; + return false; + } +} + +void SemanticState::ConsumerImpl::flush() +{ + while(haveCredit() && queue->dispatch(shared_from_this())) + ; + stop(); +} + +void SemanticState::ConsumerImpl::stop() +{ + assertClusterSafe(); + msgCredit = 0; + byteCredit = 0; +} + +Queue::shared_ptr SemanticState::getQueue(const string& name) const { + Queue::shared_ptr queue; + if (name.empty()) { + throw NotAllowedException(QPID_MSG("No queue name specified.")); + } else { + queue = session.getBroker().getQueues().find(name); + if (!queue) + throw NotFoundException(QPID_MSG("Queue not found: "<<name)); + } + return queue; +} + +AckRange SemanticState::findRange(DeliveryId first, DeliveryId last) +{ + return DeliveryRecord::findRange(unacked, first, last); +} + +void SemanticState::acquire(DeliveryId first, DeliveryId last, DeliveryIds& acquired) +{ + AckRange range = findRange(first, last); + for_each(range.start, range.end, AcquireFunctor(acquired)); +} + +void SemanticState::release(DeliveryId first, DeliveryId last, bool setRedelivered) +{ + AckRange range = findRange(first, last); + //release results in the message being added to the head so want + //to release in reverse order to keep the original transfer order + DeliveryRecords::reverse_iterator start(range.end); + DeliveryRecords::reverse_iterator end(range.start); + for_each(start, end, boost::bind(&DeliveryRecord::release, _1, setRedelivered)); +} + +void SemanticState::reject(DeliveryId first, DeliveryId last) +{ + AckRange range = findRange(first, last); + for_each(range.start, range.end, mem_fun_ref(&DeliveryRecord::reject)); + //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() +{ + try { + return haveCredit() && queue->dispatch(shared_from_this()); + } catch (const SessionException& e) { + throw SessionOutputException(e, parent->session.getChannel()); + } +} + +void SemanticState::ConsumerImpl::enableNotify() +{ + Mutex::ScopedLock l(lock); + assertClusterSafe(); + notifyEnabled = true; +} + +void SemanticState::ConsumerImpl::disableNotify() +{ + Mutex::ScopedLock l(lock); + notifyEnabled = false; +} + +bool SemanticState::ConsumerImpl::isNotifyEnabled() const { + Mutex::ScopedLock l(lock); + return notifyEnabled; +} + +void SemanticState::ConsumerImpl::notify() +{ + Mutex::ScopedLock l(lock); + assertClusterSafe(); + if (notifyEnabled) { + parent->session.getConnection().outputTasks.addOutputTask(this); + parent->session.getConnection().outputTasks.activateOutput(); + } +} + + +// Test that a DeliveryRecord's ID is in a sequence set and some other +// predicate on DeliveryRecord holds. +template <class Predicate> struct IsInSequenceSetAnd { + IsInSequenceSet isInSet; + Predicate predicate; + IsInSequenceSetAnd(const SequenceSet& s, Predicate p) : isInSet(s), predicate(p) {} + bool operator()(DeliveryRecord& dr) { + return isInSet(dr.getId()) && predicate(dr); + } +}; + +template<class Predicate> IsInSequenceSetAnd<Predicate> +isInSequenceSetAnd(const SequenceSet& s, Predicate p) { + return IsInSequenceSetAnd<Predicate>(s,p); +} + +void SemanticState::accepted(const SequenceSet& commands) { + assertClusterSafe(); + if (txBuffer.get()) { + //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); + + //mark the relevant messages as 'ended' in unacked + //if the messages are already completed, they can be + //removed from the record + DeliveryRecords::iterator removed = + remove_if(unacked.begin(), unacked.end(), + isInSequenceSetAnd(commands, + bind(&DeliveryRecord::setEnded, _1))); + unacked.erase(removed, unacked.end()); + } + } else { + DeliveryRecords::iterator removed = + remove_if(unacked.begin(), unacked.end(), + isInSequenceSetAnd(commands, + bind(&DeliveryRecord::accept, _1, + (TransactionContext*) 0))); + unacked.erase(removed, unacked.end()); + } +} + +void SemanticState::completed(const SequenceSet& commands) { + DeliveryRecords::iterator removed = + remove_if(unacked.begin(), unacked.end(), + isInSequenceSetAnd(commands, + bind(&SemanticState::complete, this, _1))); + unacked.erase(removed, unacked.end()); + requestDispatch(); +} + +void SemanticState::attached() +{ + for (ConsumerImplMap::iterator i = consumers.begin(); i != consumers.end(); i++) { + i->second->enableNotify(); + session.getConnection().outputTasks.addOutputTask(i->second.get()); + } + session.getConnection().outputTasks.activateOutput(); +} + +void SemanticState::detached() +{ + for (ConsumerImplMap::iterator i = consumers.begin(); i != consumers.end(); i++) { + i->second->disableNotify(); + session.getConnection().outputTasks.removeOutputTask(i->second.get()); + } +} + +}} // namespace qpid::broker diff --git a/qpid/cpp/src/qpid/broker/SemanticState.h b/qpid/cpp/src/qpid/broker/SemanticState.h new file mode 100644 index 0000000000..8c69d6b89b --- /dev/null +++ b/qpid/cpp/src/qpid/broker/SemanticState.h @@ -0,0 +1,257 @@ +#ifndef QPID_BROKER_SEMANTICSTATE_H +#define QPID_BROKER_SEMANTICSTATE_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/Consumer.h" +#include "qpid/broker/Deliverable.h" +#include "qpid/broker/DeliveryAdapter.h" +#include "qpid/broker/DeliveryRecord.h" +#include "qpid/broker/DtxBuffer.h" +#include "qpid/broker/DtxManager.h" +#include "qpid/broker/NameGenerator.h" +#include "qpid/broker/TxBuffer.h" + +#include "qpid/framing/FrameHandler.h" +#include "qpid/framing/SequenceSet.h" +#include "qpid/framing/Uuid.h" +#include "qpid/sys/AggregateOutput.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/AtomicValue.h" +#include "qpid/broker/AclModule.h" +#include "qmf/org/apache/qpid/broker/Subscription.h" + +#include <list> +#include <map> +#include <vector> + +#include <boost/enable_shared_from_this.hpp> +#include <boost/intrusive_ptr.hpp> +#include <boost/cast.hpp> + +namespace qpid { +namespace broker { + +class SessionContext; + +/** + * + * SemanticState implements the behavior of a Session, especially the + * state of consumers subscribed to queues. The code for ConsumerImpl + * is also in SemanticState.cpp + * + * SemanticState holds the AMQP Execution and Model state of an open + * session, whether attached to a channel or suspended. It is not + * dependent on any specific AMQP version. + * + * 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: + class ConsumerImpl : public Consumer, public sys::OutputTask, + public boost::enable_shared_from_this<ConsumerImpl>, + public management::Manageable + { + 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 exclusive; + std::string resumeId; + uint64_t resumeTtl; + framing::FieldTable arguments; + uint32_t msgCredit; + uint32_t byteCredit; + bool notifyEnabled; + const int syncFrequency; + int deliveryCount; + qmf::org::apache::qpid::broker::Subscription* mgmtObject; + + bool checkCredit(boost::intrusive_ptr<Message>& msg); + void allocateCredit(boost::intrusive_ptr<Message>& msg); + bool haveCredit(); + + public: + typedef boost::shared_ptr<ConsumerImpl> shared_ptr; + + 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); + ~ConsumerImpl(); + OwnershipToken* getSession(); + bool deliver(QueuedMessage& msg); + bool filter(boost::intrusive_ptr<Message> msg); + bool accept(boost::intrusive_ptr<Message> msg); + + void disableNotify(); + void enableNotify(); + void notify(); + bool isNotifyEnabled() const; + + void requestDispatch(); + + void setWindowMode(); + void setCreditMode(); + void addByteCredit(uint32_t value); + void addMessageCredit(uint32_t value); + void flush(); + void stop(); + 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; } + bool isExclusive() const { return exclusive; } + uint32_t getMsgCredit() const { return msgCredit; } + uint32_t getByteCredit() const { return byteCredit; } + std::string getResumeId() const { return resumeId; }; + uint64_t getResumeTtl() const { return resumeTtl; } + const framing::FieldTable& getArguments() const { return arguments; } + + SemanticState& getParent() { return *parent; } + const SemanticState& getParent() const { return *parent; } + // Manageable entry points + management::ManagementObject* GetManagementObject (void) const; + management::Manageable::status_t ManagementMethod (uint32_t methodId, management::Args& args, std::string& text); + }; + + private: + typedef std::map<std::string, ConsumerImpl::shared_ptr> ConsumerImplMap; + typedef std::map<std::string, DtxBuffer::shared_ptr> DtxBufferMap; + + SessionContext& session; + DeliveryAdapter& deliveryAdapter; + ConsumerImplMap consumers; + NameGenerator tagGenerator; + DeliveryRecords unacked; + TxBuffer::shared_ptr txBuffer; + DtxBuffer::shared_ptr dtxBuffer; + bool dtxSelected; + DtxBufferMap suspendedXids; + framing::SequenceSet accumulatedAck; + boost::shared_ptr<Exchange> cacheExchange; + AclModule* acl; + const bool authMsg; + const std::string userID; + const std::string userName; + const bool isDefaultRealm; + bool closeComplete; + + void route(boost::intrusive_ptr<Message> msg, Deliverable& strategy); + void checkDtxTimeout(); + + bool complete(DeliveryRecord&); + AckRange findRange(DeliveryId first, DeliveryId last); + void requestDispatch(); + void cancel(ConsumerImpl::shared_ptr); + void unsubscribe(ConsumerImpl::shared_ptr); + 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); + + /** + * Get named queue, never returns 0. + * @return: named queue + * @exception: ChannelException if no queue of that name is found. + * @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, + bool ackRequired, bool acquire, bool exclusive, + const std::string& resumeId=std::string(), uint64_t resumeTtl=0, + const framing::FieldTable& = framing::FieldTable()); + + bool cancel(const std::string& tag); + + void setWindowMode(const std::string& destination); + void setCreditMode(const std::string& destination); + void addByteCredit(const std::string& destination, uint32_t value); + void addMessageCredit(const std::string& destination, uint32_t value); + void flush(const std::string& destination); + void stop(const std::string& destination); + + void startTx(); + void commit(MessageStore* const store); + void rollback(); + void selectDtx(); + 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 acquire(DeliveryId first, DeliveryId last, DeliveryIds& acquired); + void release(DeliveryId first, DeliveryId last, bool setRedelivered); + void reject(DeliveryId first, DeliveryId last); + void handle(boost::intrusive_ptr<Message> msg); + + void completed(const framing::SequenceSet& commands); + void accepted(const framing::SequenceSet& commands); + + void attached(); + void detached(); + void closed(); + + // Used by cluster to re-create sessions + template <class F> void eachConsumer(F f) { + for(ConsumerImplMap::iterator i = consumers.begin(); i != consumers.end(); ++i) + f(i->second); + } + DeliveryRecords& getUnacked() { return unacked; } + framing::SequenceSet getAccumulatedAck() const { return accumulatedAck; } + TxBuffer::shared_ptr getTxBuffer() const { return txBuffer; } + void setTxBuffer(const TxBuffer::shared_ptr& txb) { txBuffer = txb; } + void setAccumulatedAck(const framing::SequenceSet& s) { accumulatedAck = s; } + void record(const DeliveryRecord& delivery); +}; + +}} // namespace qpid::broker + + + + +#endif /*!QPID_BROKER_SEMANTICSTATE_H*/ diff --git a/qpid/cpp/src/qpid/broker/SessionAdapter.cpp b/qpid/cpp/src/qpid/broker/SessionAdapter.cpp new file mode 100644 index 0000000000..63c4b660b2 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/SessionAdapter.cpp @@ -0,0 +1,693 @@ +/* + * + * 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. + * + */ +#include "qpid/broker/SessionAdapter.h" +#include "qpid/broker/Connection.h" +#include "qpid/broker/Queue.h" +#include "qpid/Exception.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/enum.h" +#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" +#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 "qmf/org/apache/qpid/broker/EventSubscribe.h" +#include "qmf/org/apache/qpid/broker/EventUnsubscribe.h" +#include <boost/format.hpp> +#include <boost/cast.hpp> +#include <boost/bind.hpp> + +namespace qpid { +namespace broker { + +using namespace qpid; +using namespace qpid::framing; +using namespace qpid::framing::dtx; +using namespace qpid::management; +namespace _qmf = qmf::org::apache::qpid::broker; + +typedef std::vector<Queue::shared_ptr> QueueVector; + +SessionAdapter::SessionAdapter(SemanticState& s) : + HandlerImpl(s), + exchangeImpl(s), + queueImpl(s), + messageImpl(s), + executionImpl(s), + txImpl(s), + dtxImpl(s) +{} + +static const std::string _TRUE("true"); +static const std::string _FALSE("false"); + +void SessionAdapter::ExchangeHandlerImpl::declare(const string& exchange, const string& type, + const string& alternateExchange, + bool passive, bool durable, bool /*autoDelete*/, const FieldTable& args){ + + //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{ + 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().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")); + } + }catch(UnknownExchangeTypeException& /*e*/){ + throw NotFoundException(QPID_MSG("Exchange type not implemented: " << type)); + } + } +} + +void SessionAdapter::ExchangeHandlerImpl::checkType(Exchange::shared_ptr exchange, const std::string& type) +{ + if (!type.empty() && exchange->getType() != type) { + throw NotAllowedException(QPID_MSG("Exchange declared to be of type " << exchange->getType() << ", requested " << type)); + } +} + +void SessionAdapter::ExchangeHandlerImpl::checkAlternate(Exchange::shared_ptr exchange, Exchange::shared_ptr alternate) +{ + if (alternate && ((exchange->getAlternate() && alternate != exchange->getAlternate()) + || !exchange->getAlternate())) + throw NotAllowedException(QPID_MSG("Exchange declared with alternate-exchange " + << (exchange->getAlternate() ? exchange->getAlternate()->getName() : "<nonexistent>") + << ", requested " + << alternate->getName())); +} + +void SessionAdapter::ExchangeHandlerImpl::delete_(const string& name, bool /*ifUnused*/) +{ + //TODO: implement if-unused + getBroker().deleteExchange(name, getConnection().getUserId(), getConnection().getUrl()); +} + +ExchangeQueryResult SessionAdapter::ExchangeHandlerImpl::query(const string& name) +{ + AclModule* acl = getBroker().getAcl(); + if (acl) { + if (!acl->authorise(getConnection().getUserId(),acl::ACT_ACCESS,acl::OBJ_EXCHANGE,name,NULL) ) + throw UnauthorizedAccessException(QPID_MSG("ACL denied exchange query request from " << getConnection().getUserId())); + } + + try { + Exchange::shared_ptr exchange(getBroker().getExchanges().get(name)); + return ExchangeQueryResult(exchange->getType(), exchange->isDurable(), false, exchange->getArgs()); + } catch (const NotFoundException& /*e*/) { + return ExchangeQueryResult("", false, true, FieldTable()); + } +} + +void SessionAdapter::ExchangeHandlerImpl::bind(const string& queueName, + const string& exchangeName, const string& routingKey, + const FieldTable& arguments) +{ + getBroker().bind(queueName, exchangeName, routingKey, arguments, + getConnection().getUserId(), getConnection().getUrl()); +} + +void SessionAdapter::ExchangeHandlerImpl::unbind(const string& queueName, + const string& exchangeName, + const string& routingKey) +{ + getBroker().unbind(queueName, exchangeName, routingKey, + getConnection().getUserId(), getConnection().getUrl()); +} + +ExchangeBoundResult SessionAdapter::ExchangeHandlerImpl::bound(const std::string& exchangeName, + const std::string& queueName, + const std::string& key, + const framing::FieldTable& args) +{ + 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, key)); + if (!acl->authorise(getConnection().getUserId(),acl::ACT_ACCESS,acl::OBJ_EXCHANGE,exchangeName,¶ms) ) + throw UnauthorizedAccessException(QPID_MSG("ACL denied exchange bound request from " << getConnection().getUserId())); + } + + Exchange::shared_ptr exchange; + try { + exchange = getBroker().getExchanges().get(exchangeName); + } catch (const NotFoundException&) {} + + Queue::shared_ptr queue; + if (!queueName.empty()) { + queue = getBroker().getQueues().find(queueName); + } + + if (!exchange) { + return ExchangeBoundResult(true, (!queueName.empty() && !queue), false, false, false); + } else if (!queueName.empty() && !queue) { + return ExchangeBoundResult(false, true, false, false, false); + } else if (exchange->isBound(queue, key.empty() ? 0 : &key, args.count() > 0 ? &args : &args)) { + return ExchangeBoundResult(false, false, false, false, false); + } else { + //need to test each specified option individually + bool queueMatched = queueName.empty() || exchange->isBound(queue, 0, 0); + bool keyMatched = key.empty() || exchange->isBound(Queue::shared_ptr(), &key, 0); + bool argsMatched = args.count() == 0 || exchange->isBound(Queue::shared_ptr(), 0, &args); + + return ExchangeBoundResult(false, false, !queueMatched, !keyMatched, !argsMatched); + } +} + +SessionAdapter::QueueHandlerImpl::QueueHandlerImpl(SemanticState& session) : HandlerHelper(session), broker(getBroker()) +{} + + +SessionAdapter::QueueHandlerImpl::~QueueHandlerImpl() +{ + try { + destroyExclusiveQueues(); + } catch (std::exception& e) { + QPID_LOG(error, e.what()); + } +} + +void SessionAdapter::QueueHandlerImpl::destroyExclusiveQueues() +{ + while (!exclusiveQueues.empty()) { + Queue::shared_ptr q(exclusiveQueues.front()); + q->releaseExclusiveOwnership(); + if (q->canAutoDelete()) { + Queue::tryAutoDelete(broker, q); + } + exclusiveQueues.erase(exclusiveQueues.begin()); + } +} + +bool SessionAdapter::QueueHandlerImpl::isLocal(const ConnectionToken* t) const +{ + return session.isLocal(t); +} + + +QueueQueryResult SessionAdapter::QueueHandlerImpl::query(const string& name) +{ + AclModule* acl = getBroker().getAcl(); + if (acl) { + if (!acl->authorise(getConnection().getUserId(),acl::ACT_ACCESS,acl::OBJ_QUEUE,name,NULL) ) + throw UnauthorizedAccessException(QPID_MSG("ACL denied queue query request from " << getConnection().getUserId())); + } + + Queue::shared_ptr queue = session.getBroker().getQueues().find(name); + if (queue) { + + Exchange::shared_ptr alternateExchange = queue->getAlternateExchange(); + + return QueueQueryResult(queue->getName(), + alternateExchange ? alternateExchange->getName() : "", + queue->isDurable(), + queue->hasExclusiveOwner(), + queue->isAutoDelete(), + queue->getSettings(), + queue->getMessageCount(), + queue->getConsumerCount()); + } else { + return QueueQueryResult(); + } +} + +void SessionAdapter::QueueHandlerImpl::declare(const string& name, const string& alternateExchange, + bool passive, bool durable, bool exclusive, + bool autoDelete, const qpid::framing::FieldTable& arguments) +{ + Queue::shared_ptr queue; + if (passive && !name.empty()) { + 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().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 + //handle automatic cleanup: + if (exclusive) { + exclusiveQueues.push_back(queue); + } + } else { + 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")); + } + + } + + 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) + { + if (!acl->authorise(getConnection().getUserId(),acl::ACT_PURGE,acl::OBJ_QUEUE,queue,NULL) ) + throw UnauthorizedAccessException(QPID_MSG("ACL denied queue purge request from " << getConnection().getUserId())); + } + getQueue(queue)->purge(); +} + +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->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 + 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) : + HandlerHelper(s), + releaseRedeliveredOp(boost::bind(&SemanticState::release, &state, _1, _2, true)), + releaseOp(boost::bind(&SemanticState::release, &state, _1, _2, false)), + rejectOp(boost::bind(&SemanticState::reject, &state, _1, _2)) + {} + +// +// Message class method handlers +// + +void SessionAdapter::MessageHandlerImpl::transfer(const string& /*destination*/, + uint8_t /*acceptMode*/, + uint8_t /*acquireMode*/) +{ + //not yet used (content containing assemblies treated differently at present + std::cout << "SessionAdapter::MessageHandlerImpl::transfer() called" << std::endl; +} + +void SessionAdapter::MessageHandlerImpl::release(const SequenceSet& transfers, bool setRedelivered) +{ + transfers.for_each(setRedelivered ? releaseRedeliveredOp : releaseOp); +} + +void +SessionAdapter::MessageHandlerImpl::subscribe(const string& queueName, + const string& destination, + uint8_t acceptMode, + uint8_t acquireMode, + bool exclusive, + const string& resumeId, + uint64_t resumeTtl, + const FieldTable& arguments) +{ + + AclModule* acl = getBroker().getAcl(); + if (acl) + { + if (!acl->authorise(getConnection().getUserId(),acl::ACT_CONSUME,acl::OBJ_QUEUE,queueName,NULL) ) + throw UnauthorizedAccessException(QPID_MSG("ACL denied Queue subscribe request from " << getConnection().getUserId())); + } + + Queue::shared_ptr queue = getQueue(queueName); + if(!destination.empty() && state.exists(destination)) + throw NotAllowedException(QPID_MSG("Consumer tags must be unique")); + + if (queue->hasExclusiveOwner() && !queue->isExclusiveOwner(&session) && acquireMode == 0) + throw ResourceLockedException(QPID_MSG("Cannot subscribe to exclusive queue " + << queue->getName())); + + state.consume(destination, queue, + acceptMode == 0, acquireMode == 0, exclusive, + resumeId, resumeTtl, arguments); + + ManagementAgent* agent = getBroker().getManagementAgent(); + if (agent) + agent->raiseEvent(_qmf::EventSubscribe(getConnection().getUrl(), getConnection().getUserId(), + queueName, destination, exclusive, ManagementAgent::toMap(arguments))); +} + +void +SessionAdapter::MessageHandlerImpl::cancel(const string& destination ) +{ + if (!state.cancel(destination)) { + throw NotFoundException(QPID_MSG("No such subscription: " << destination)); + } + + ManagementAgent* agent = getBroker().getManagementAgent(); + if (agent) + agent->raiseEvent(_qmf::EventUnsubscribe(getConnection().getUrl(), getConnection().getUserId(), destination)); +} + +void +SessionAdapter::MessageHandlerImpl::reject(const SequenceSet& transfers, uint16_t /*code*/, const string& /*text*/ ) +{ + transfers.for_each(rejectOp); +} + +void SessionAdapter::MessageHandlerImpl::flow(const std::string& destination, uint8_t unit, uint32_t value) +{ + if (unit == 0) { + //message + state.addMessageCredit(destination, value); + } else if (unit == 1) { + //bytes + state.addByteCredit(destination, value); + } else { + //unknown + throw InvalidArgumentException(QPID_MSG("Invalid value for unit " << unit)); + } + +} + +void SessionAdapter::MessageHandlerImpl::setFlowMode(const std::string& destination, uint8_t mode) +{ + if (mode == 0) { + //credit + state.setCreditMode(destination); + } else if (mode == 1) { + //window + state.setWindowMode(destination); + } else{ + throw InvalidArgumentException(QPID_MSG("Invalid value for mode " << mode)); + } +} + +void SessionAdapter::MessageHandlerImpl::flush(const std::string& destination) +{ + state.flush(destination); +} + +void SessionAdapter::MessageHandlerImpl::stop(const std::string& destination) +{ + state.stop(destination); +} + +void SessionAdapter::MessageHandlerImpl::accept(const framing::SequenceSet& commands) +{ + state.accepted(commands); +} + +framing::MessageAcquireResult SessionAdapter::MessageHandlerImpl::acquire(const framing::SequenceSet& transfers) +{ + // FIXME aconway 2008-05-12: create SequenceSet directly, no need for intermediate results vector. + SequenceNumberSet results; + RangedOperation f = boost::bind(&SemanticState::acquire, &state, _1, _2, boost::ref(results)); + transfers.for_each(f); + + results = results.condense(); + SequenceSet acquisitions; + RangedOperation g = boost::bind(&SequenceSet::add, &acquisitions, _1, _2); + results.processRanges(g); + + return MessageAcquireResult(acquisitions); +} + +framing::MessageResumeResult SessionAdapter::MessageHandlerImpl::resume(const std::string& /*destination*/, + const std::string& /*resumeId*/) +{ + throw NotImplementedException("resuming transfers not yet supported"); +} + + + +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*/) +{ + //TODO: but currently never used client->server +} + +void SessionAdapter::ExecutionHandlerImpl::exception(uint16_t /*errorCode*/, + const SequenceNumber& /*commandId*/, + uint8_t /*classCode*/, + uint8_t /*commandCode*/, + uint8_t /*fieldIndex*/, + const std::string& /*description*/, + const framing::FieldTable& /*errorInfo*/) +{ + //TODO: again, not really used client->server but may be important + //for inter-broker links +} + + + +void SessionAdapter::TxHandlerImpl::select() +{ + state.startTx(); +} + +void SessionAdapter::TxHandlerImpl::commit() +{ + state.commit(&getBroker().getStore()); +} + +void SessionAdapter::TxHandlerImpl::rollback() +{ + state.rollback(); +} + +std::string SessionAdapter::DtxHandlerImpl::convert(const framing::Xid& xid) +{ + std::string encoded; + encode(xid, encoded); + return encoded; +} + +void SessionAdapter::DtxHandlerImpl::select() +{ + state.selectDtx(); +} + +XaResult SessionAdapter::DtxHandlerImpl::end(const Xid& xid, + bool fail, + bool suspend) +{ + try { + if (fail) { + state.endDtx(convert(xid), true); + if (suspend) { + throw CommandInvalidException(QPID_MSG("End and suspend cannot both be set.")); + } else { + return XaResult(XA_STATUS_XA_RBROLLBACK); + } + } else { + if (suspend) { + state.suspendDtx(convert(xid)); + } else { + state.endDtx(convert(xid), false); + } + return XaResult(XA_STATUS_XA_OK); + } + } catch (const DtxTimeoutException& /*e*/) { + return XaResult(XA_STATUS_XA_RBTIMEOUT); + } +} + +XaResult SessionAdapter::DtxHandlerImpl::start(const Xid& xid, + bool join, + bool resume) +{ + if (join && resume) { + throw CommandInvalidException(QPID_MSG("Join and resume cannot both be set.")); + } + try { + if (resume) { + state.resumeDtx(convert(xid)); + } else { + state.startDtx(convert(xid), getBroker().getDtxManager(), join); + } + return XaResult(XA_STATUS_XA_OK); + } catch (const DtxTimeoutException& /*e*/) { + return XaResult(XA_STATUS_XA_RBTIMEOUT); + } +} + +XaResult SessionAdapter::DtxHandlerImpl::prepare(const Xid& xid) +{ + try { + bool ok = getBroker().getDtxManager().prepare(convert(xid)); + return XaResult(ok ? XA_STATUS_XA_OK : XA_STATUS_XA_RBROLLBACK); + } catch (const DtxTimeoutException& /*e*/) { + return XaResult(XA_STATUS_XA_RBTIMEOUT); + } +} + +XaResult SessionAdapter::DtxHandlerImpl::commit(const Xid& xid, + bool onePhase) +{ + try { + bool ok = getBroker().getDtxManager().commit(convert(xid), onePhase); + return XaResult(ok ? XA_STATUS_XA_OK : XA_STATUS_XA_RBROLLBACK); + } catch (const DtxTimeoutException& /*e*/) { + return XaResult(XA_STATUS_XA_RBTIMEOUT); + } +} + + +XaResult SessionAdapter::DtxHandlerImpl::rollback(const Xid& xid) +{ + try { + getBroker().getDtxManager().rollback(convert(xid)); + return XaResult(XA_STATUS_XA_OK); + } catch (const DtxTimeoutException& /*e*/) { + return XaResult(XA_STATUS_XA_RBTIMEOUT); + } +} + +DtxRecoverResult SessionAdapter::DtxHandlerImpl::recover() +{ + std::set<std::string> xids; + getBroker().getStore().collectPreparedXids(xids); + /* + * create array of long structs + */ + Array indoubt(0xAB); + for (std::set<std::string>::iterator i = xids.begin(); i != xids.end(); i++) { + boost::shared_ptr<FieldValue> xid(new Struct32Value(*i)); + indoubt.add(xid); + } + return DtxRecoverResult(indoubt); +} + +void SessionAdapter::DtxHandlerImpl::forget(const Xid& xid) +{ + //Currently no heuristic completion is supported, so this should never be used. + throw NotImplementedException(QPID_MSG("Forget not implemented. Branch with xid " << xid << " not heuristically completed!")); +} + +DtxGetTimeoutResult SessionAdapter::DtxHandlerImpl::getTimeout(const Xid& xid) +{ + uint32_t timeout = getBroker().getDtxManager().getTimeout(convert(xid)); + return DtxGetTimeoutResult(timeout); +} + + +void SessionAdapter::DtxHandlerImpl::setTimeout(const Xid& xid, + uint32_t timeout) +{ + getBroker().getDtxManager().setTimeout(convert(xid), timeout); +} + + +Queue::shared_ptr SessionAdapter::HandlerHelper::getQueue(const string& name) const { + Queue::shared_ptr queue; + if (name.empty()) { + throw framing::IllegalArgumentException(QPID_MSG("No queue name specified.")); + } else { + queue = session.getBroker().getQueues().find(name); + if (!queue) + throw framing::NotFoundException(QPID_MSG("Queue not found: "<<name)); + } + return queue; +} + +}} // namespace qpid::broker + + diff --git a/qpid/cpp/src/qpid/broker/SessionAdapter.h b/qpid/cpp/src/qpid/broker/SessionAdapter.h new file mode 100644 index 0000000000..8987c4812f --- /dev/null +++ b/qpid/cpp/src/qpid/broker/SessionAdapter.h @@ -0,0 +1,273 @@ +#ifndef _broker_SessionAdapter_h +#define _broker_SessionAdapter_h + +/* + * + * 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. + * + */ + +#include "qpid/broker/HandlerImpl.h" + +#include "qpid/broker/ConnectionToken.h" +#include "qpid/broker/OwnershipToken.h" +#include "qpid/Exception.h" +#include "qpid/framing/AMQP_ServerOperations.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/StructHelper.h" + +#include <algorithm> +#include <vector> +#include <boost/function.hpp> +#include <boost/shared_ptr.hpp> + +namespace qpid { +namespace broker { + +class Channel; +class Connection; +class Broker; +class Queue; + +/** + * SessionAdapter translates protocol-specific AMQP commands for one + * specific version of AMQP into calls on the core broker objects. It + * is a container for a collection of adapters. + * + * Each adapter class provides a client proxy to send methods to the + * peer broker or client. + * + */ + + class SessionAdapter : public HandlerImpl, public framing::AMQP_ServerOperations +{ + public: + SessionAdapter(SemanticState& session); + + framing::ProtocolVersion getVersion() const { return session.getConnection().getVersion();} + + MessageHandler* getMessageHandler(){ return &messageImpl; } + ExchangeHandler* getExchangeHandler(){ return &exchangeImpl; } + QueueHandler* getQueueHandler(){ return &queueImpl; } + ExecutionHandler* getExecutionHandler(){ return &executionImpl; } + TxHandler* getTxHandler(){ return &txImpl; } + DtxHandler* getDtxHandler(){ return &dtxImpl; } + + ConnectionHandler* getConnectionHandler() { throw framing::NotImplementedException("Class not implemented"); } + SessionHandler* getSessionHandler() { throw framing::NotImplementedException("Class not implemented"); } + FileHandler* getFileHandler() { throw framing::NotImplementedException("Class not implemented"); } + StreamHandler* getStreamHandler() { throw framing::NotImplementedException("Class not implemented"); } + + template <class F> void eachExclusiveQueue(F f) + { + queueImpl.eachExclusiveQueue(f); + } + + + private: + //common base for utility methods etc that are specific to this adapter + struct HandlerHelper : public HandlerImpl + { + HandlerHelper(SemanticState& s) : HandlerImpl(s) {} + + boost::shared_ptr<Queue> getQueue(const std::string& name) const; + }; + + + class ExchangeHandlerImpl : + public ExchangeHandler, + public HandlerHelper + { + public: + ExchangeHandlerImpl(SemanticState& session) : HandlerHelper(session) {} + + void declare(const std::string& exchange, const std::string& type, + const std::string& alternateExchange, + bool passive, bool durable, bool autoDelete, + const qpid::framing::FieldTable& arguments); + void delete_(const std::string& exchange, bool ifUnused); + framing::ExchangeQueryResult query(const std::string& name); + void bind(const std::string& queue, + const std::string& exchange, const std::string& routingKey, + const qpid::framing::FieldTable& arguments); + void unbind(const std::string& queue, + const std::string& exchange, + const std::string& routingKey); + framing::ExchangeBoundResult bound(const std::string& exchange, + const std::string& queue, + const std::string& routingKey, + const framing::FieldTable& arguments); + private: + void checkType(boost::shared_ptr<Exchange> exchange, const std::string& type); + + void checkAlternate(boost::shared_ptr<Exchange> exchange, + boost::shared_ptr<Exchange> alternate); + }; + + class QueueHandlerImpl : public QueueHandler, + public HandlerHelper + { + Broker& broker; + std::vector< boost::shared_ptr<Queue> > exclusiveQueues; + + public: + QueueHandlerImpl(SemanticState& session); + ~QueueHandlerImpl(); + + void declare(const std::string& queue, + const std::string& alternateExchange, + bool passive, bool durable, bool exclusive, + bool autoDelete, + const qpid::framing::FieldTable& arguments); + void delete_(const std::string& queue, + bool ifUnused, bool ifEmpty); + void purge(const std::string& queue); + framing::QueueQueryResult query(const std::string& 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); + } + }; + + class MessageHandlerImpl : + public MessageHandler, + public HandlerHelper + { + typedef boost::function<void(DeliveryId, DeliveryId)> RangedOperation; + RangedOperation releaseRedeliveredOp; + RangedOperation releaseOp; + RangedOperation rejectOp; + RangedOperation acceptOp; + + public: + MessageHandlerImpl(SemanticState& session); + void transfer(const std::string& destination, + uint8_t acceptMode, + uint8_t acquireMode); + + void accept(const framing::SequenceSet& commands); + + void reject(const framing::SequenceSet& commands, + uint16_t code, + const std::string& text); + + void release(const framing::SequenceSet& commands, + bool setRedelivered); + + framing::MessageAcquireResult acquire(const framing::SequenceSet&); + + void subscribe(const std::string& queue, + const std::string& destination, + uint8_t acceptMode, + uint8_t acquireMode, + bool exclusive, + const std::string& resumeId, + uint64_t resumeTtl, + const framing::FieldTable& arguments); + + void cancel(const std::string& destination); + + void setFlowMode(const std::string& destination, + uint8_t flowMode); + + void flow(const std::string& destination, + uint8_t unit, + uint32_t value); + + void flush(const std::string& destination); + + void stop(const std::string& destination); + + framing::MessageResumeResult resume(const std::string& destination, + const std::string& resumeId); + + }; + + class ExecutionHandlerImpl : public ExecutionHandler, public HandlerHelper + { + public: + ExecutionHandlerImpl(SemanticState& session) : HandlerHelper(session) {} + + void sync(); + void result(const framing::SequenceNumber& commandId, const std::string& value); + void exception(uint16_t errorCode, + const framing::SequenceNumber& commandId, + uint8_t classCode, + uint8_t commandCode, + uint8_t fieldIndex, + const std::string& description, + const framing::FieldTable& errorInfo); + + }; + + class TxHandlerImpl : public TxHandler, public HandlerHelper + { + public: + TxHandlerImpl(SemanticState& session) : HandlerHelper(session) {} + + void select(); + void commit(); + void rollback(); + }; + + class DtxHandlerImpl : public DtxHandler, public HandlerHelper, private framing::StructHelper + { + std::string convert(const framing::Xid& xid); + + public: + DtxHandlerImpl(SemanticState& session) : HandlerHelper(session) {} + + void select(); + + framing::XaResult start(const framing::Xid& xid, + bool join, + bool resume); + + framing::XaResult end(const framing::Xid& xid, + bool fail, + bool suspend); + + framing::XaResult commit(const framing::Xid& xid, + bool onePhase); + + void forget(const framing::Xid& xid); + + framing::DtxGetTimeoutResult getTimeout(const framing::Xid& xid); + + framing::XaResult prepare(const framing::Xid& xid); + + framing::DtxRecoverResult recover(); + + framing::XaResult rollback(const framing::Xid& xid); + + void setTimeout(const framing::Xid& xid, uint32_t timeout); + }; + + ExchangeHandlerImpl exchangeImpl; + QueueHandlerImpl queueImpl; + MessageHandlerImpl messageImpl; + ExecutionHandlerImpl executionImpl; + TxHandlerImpl txImpl; + DtxHandlerImpl dtxImpl; +}; +}} // namespace qpid::broker + + + +#endif /*!_broker_SessionAdapter_h*/ diff --git a/qpid/cpp/src/qpid/broker/SessionContext.h b/qpid/cpp/src/qpid/broker/SessionContext.h new file mode 100644 index 0000000000..253ce8dcf2 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/SessionContext.h @@ -0,0 +1,56 @@ +#ifndef QPID_BROKER_SESSIONCONTEXT_H +#define QPID_BROKER_SESSIONCONTEXT_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/framing/FrameHandler.h" +#include "qpid/framing/AMQP_ClientProxy.h" +#include "qpid/framing/amqp_types.h" +#include "qpid/sys/OutputControl.h" +#include "qpid/broker/ConnectionState.h" +#include "qpid/broker/OwnershipToken.h" +#include "qpid/SessionId.h" + +#include <boost/noncopyable.hpp> + +namespace qpid { +namespace broker { + +class SessionContext : public OwnershipToken, public sys::OutputControl +{ + public: + virtual ~SessionContext(){} + virtual bool isLocal(const ConnectionToken* t) const = 0; + virtual bool isAttached() const = 0; + virtual ConnectionState& getConnection() = 0; + virtual framing::AMQP_ClientProxy& getProxy() = 0; + virtual Broker& getBroker() = 0; + virtual uint16_t getChannel() const = 0; + virtual const SessionId& getSessionId() const = 0; + virtual void addPendingExecutionSync() = 0; +}; + +}} // namespace qpid::broker + + + +#endif /*!QPID_BROKER_SESSIONCONTEXT_H*/ diff --git a/qpid/cpp/src/qpid/broker/SessionHandler.cpp b/qpid/cpp/src/qpid/broker/SessionHandler.cpp new file mode 100644 index 0000000000..752fa55535 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/SessionHandler.cpp @@ -0,0 +1,120 @@ +/* + * 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/SessionHandler.h" +#include "qpid/broker/SessionState.h" +#include "qpid/broker/Connection.h" +#include "qpid/log/Statement.h" + +#include <boost/bind.hpp> + +namespace qpid { +namespace broker { +using namespace framing; +using namespace std; +using namespace qpid::sys; + +SessionHandler::SessionHandler(Connection& c, ChannelId ch) + : amqp_0_10::SessionHandler(&c.getOutput(), ch), + connection(c), + proxy(out), + clusterOrderProxy(c.getClusterOrderOutput() ? new SetChannelProxy(ch, c.getClusterOrderOutput()) : 0) +{} + +SessionHandler::~SessionHandler() {} + +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); + connection.close(code, msg); +} + +void SessionHandler::channelException(framing::session::DetachCode, const std::string& msg) { + if (connection.getErrorListener()) connection.getErrorListener()->sessionError(getChannel(), msg); +} + +void SessionHandler::executionException(framing::execution::ErrorCode, const std::string& msg) { + if (connection.getErrorListener()) connection.getErrorListener()->sessionError(getChannel(), msg); +} + +ConnectionState& SessionHandler::getConnection() { return connection; } + +const ConnectionState& SessionHandler::getConnection() const { return connection; } + +void SessionHandler::handleDetach() { + amqp_0_10::SessionHandler::handleDetach(); + assert(&connection.getChannel(channel.get()) == this); + if (session.get()) + connection.getBroker().getSessionManager().detach(session); + assert(!session.get()); + connection.closeChannel(channel.get()); +} + +void SessionHandler::setState(const std::string& name, bool force) { + assert(!session.get()); + SessionId id(connection.getUserId(), name); + session = connection.broker.getSessionManager().attach(*this, id, force); +} + +void SessionHandler::detaching() +{ + assert(session.get()); + session->disableOutput(); +} + +FrameHandler* SessionHandler::getInHandler() { return session.get() ? &session->in : 0; } +qpid::SessionState* SessionHandler::getState() { return session.get(); } + +void SessionHandler::readyToSend() { + if (session.get()) session->readyToSend(); +} + +/** + * Used by inter-broker bridges to set up session id and attach + */ +void SessionHandler::attachAs(const std::string& name) +{ + SessionId id(connection.getUserId(), name); + SessionState::Configuration config = connection.broker.getSessionManager().getSessionConfig(); + // Delay creating management object till attached(). In a cluster, + // only the active link broker calls attachAs but all brokers + // receive the subsequent attached() call. + session.reset(new SessionState(connection.getBroker(), *this, id, config, true)); + sendAttach(false); +} + +/** + * TODO: this is a little ugly, fix it; its currently still relied on + * for 'push' bridges + */ +void SessionHandler::attached(const std::string& name) +{ + if (session.get()) { + session->addManagementObject(); // Delayed from attachAs() + amqp_0_10::SessionHandler::attached(name); + } else { + SessionId id(connection.getUserId(), name); + SessionState::Configuration config = connection.broker.getSessionManager().getSessionConfig(); + session.reset(new SessionState(connection.getBroker(), *this, id, config)); + markReadyToSend(); + } +} + +}} // namespace qpid::broker diff --git a/qpid/cpp/src/qpid/broker/SessionHandler.h b/qpid/cpp/src/qpid/broker/SessionHandler.h new file mode 100644 index 0000000000..ca6d6bb193 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/SessionHandler.h @@ -0,0 +1,99 @@ +#ifndef QPID_BROKER_SESSIONHANDLER_H +#define QPID_BROKER_SESSIONHANDLER_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/amqp_0_10/SessionHandler.h" +#include "qpid/framing/AMQP_ClientProxy.h" + +namespace qpid { +class SessionState; + +namespace broker { + +class Connection; +class ConnectionState; +class SessionState; + +/** + * A SessionHandler is associated with each active channel. It + * receives incoming frames, handles session controls and manages the + * association between the channel and a session. + */ +class SessionHandler : public amqp_0_10::SessionHandler { + public: + SessionHandler(Connection&, framing::ChannelId); + ~SessionHandler(); + + /** Get broker::SessionState */ + SessionState* getSession() { return session.get(); } + const SessionState* getSession() const { return session.get(); } + + ConnectionState& getConnection(); + const ConnectionState& getConnection() const; + + framing::AMQP_ClientProxy& getProxy() { return proxy; } + const framing::AMQP_ClientProxy& getProxy() const { return proxy; } + + /** + * If commands are sent based on the local time (e.g. in timers), they don't have + * a well-defined ordering across cluster nodes. + * This proxy is for sending such commands. In a clustered broker it will take steps + * to synchronize command order across the cluster. In a stand-alone broker + * it is just a synonym for getProxy() + */ + framing::AMQP_ClientProxy& getClusterOrderProxy() { + return clusterOrderProxy.get() ? *clusterOrderProxy : proxy; + } + + virtual void handleDetach(); + void attached(const std::string& name);//used by 'pushing' inter-broker bridges + void attachAs(const std::string& name);//used by 'pulling' inter-broker bridges + + protected: + virtual void setState(const std::string& sessionName, bool force); + virtual qpid::SessionState* getState(); + virtual framing::FrameHandler* getInHandler(); + virtual void connectionException(framing::connection::CloseCode code, const std::string& msg); + virtual void channelException(framing::session::DetachCode, const std::string& msg); + virtual void executionException(framing::execution::ErrorCode, const std::string& msg); + virtual void detaching(); + virtual void readyToSend(); + + private: + struct SetChannelProxy : public framing::AMQP_ClientProxy { // Proxy that sets the channel. + framing::ChannelHandler setChannel; + SetChannelProxy(uint16_t ch, framing::FrameHandler* out) + : framing::AMQP_ClientProxy(setChannel), setChannel(ch, out) {} + }; + + Connection& connection; + framing::AMQP_ClientProxy proxy; + std::auto_ptr<SessionState> session; + std::auto_ptr<SetChannelProxy> clusterOrderProxy; +}; + +}} // namespace qpid::broker + + + +#endif /*!QPID_BROKER_SESSIONHANDLER_H*/ diff --git a/qpid/cpp/src/qpid/broker/SessionManager.cpp b/qpid/cpp/src/qpid/broker/SessionManager.cpp new file mode 100644 index 0000000000..8cc58571af --- /dev/null +++ b/qpid/cpp/src/qpid/broker/SessionManager.cpp @@ -0,0 +1,104 @@ +/* + * + * 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/SessionManager.h" +#include "qpid/broker/SessionState.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/log/Statement.h" +#include "qpid/log/Helpers.h" +#include "qpid/memory.h" + +#include <boost/bind.hpp> +#include <boost/range.hpp> + +#include <algorithm> +#include <functional> +#include <ostream> + +namespace qpid { +namespace broker { + +using boost::intrusive_ptr; +using namespace sys; +using namespace framing; + +SessionManager::SessionManager(const SessionState::Configuration& c, Broker& b) + : config(c), broker(b) {} + +SessionManager::~SessionManager() { + detached.clear(); // Must clear before destructor as session dtor will call forget() +} + +std::auto_ptr<SessionState> SessionManager::attach(SessionHandler& h, const SessionId& id, bool/*force*/) { + Mutex::ScopedLock l(lock); + eraseExpired(); // Clean up expired table + std::pair<Attached::iterator, bool> insert = attached.insert(id); + if (!insert.second) + throw SessionBusyException(QPID_MSG("Session already attached: " << id)); + Detached::iterator i = std::find(detached.begin(), detached.end(), id); + std::auto_ptr<SessionState> state; + if (i == detached.end()) + state.reset(new SessionState(broker, h, id, config)); + else { + state.reset(detached.release(i).release()); + state->attach(h); + } + return state; + // FIXME aconway 2008-04-29: implement force +} + +void SessionManager::detach(std::auto_ptr<SessionState> session) { + Mutex::ScopedLock l(lock); + attached.erase(session->getId()); + session->detach(); + if (session->getTimeout() > 0) { + session->expiry = AbsTime(now(),session->getTimeout()*TIME_SEC); + if (session->mgmtObject != 0) + session->mgmtObject->set_expireTime ((uint64_t) Duration (EPOCH, session->expiry)); + detached.push_back(session.release()); // In expiry order + eraseExpired(); +} +} + +void SessionManager::forget(const SessionId& id) { + Mutex::ScopedLock l(lock); + attached.erase(id); +} + +void SessionManager::eraseExpired() { + // Called with lock held. + if (!detached.empty()) { + // This used to use a more elegant invocation of std::lower_bound + // but violated the strict weak ordering rule which Visual Studio + // enforced. See QPID-1424 for more info should you be tempted to + // replace the loop with something more elegant. + AbsTime now = AbsTime::now(); + Detached::iterator keep = detached.begin(); + while ((keep != detached.end()) && ((*keep).expiry < now)) + keep++; + if (detached.begin() != keep) { + QPID_LOG(debug, "Expiring sessions: " << log::formatList(detached.begin(), keep)); + detached.erase(detached.begin(), keep); + } + } +} + +}} // namespace qpid::broker diff --git a/qpid/cpp/src/qpid/broker/SessionManager.h b/qpid/cpp/src/qpid/broker/SessionManager.h new file mode 100644 index 0000000000..db88e7ec10 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/SessionManager.h @@ -0,0 +1,87 @@ +#ifndef QPID_BROKER_SESSIONMANAGER_H +#define QPID_BROKER_SESSIONMANAGER_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/SessionState.h> +#include <qpid/sys/Time.h> +#include <qpid/sys/Mutex.h> +#include <qpid/RefCounted.h> + +#include <set> +#include <vector> +#include <memory> + +#include <boost/noncopyable.hpp> +#include <boost/ptr_container/ptr_vector.hpp> +#include <boost/intrusive_ptr.hpp> + +namespace qpid { +namespace broker { +class Broker; +class SessionState; +class SessionHandler; + +/** + * Create and manage SessionState objects. + */ +class SessionManager : private boost::noncopyable { + public: + SessionManager(const qpid::SessionState::Configuration&, Broker&); + + ~SessionManager(); + + /** Open a new active session, caller takes ownership */ + std::auto_ptr<SessionState> attach(SessionHandler& h, const SessionId& id, bool/*force*/); + + /** Return a detached session to the manager, start the timeout counter. */ + void detach(std::auto_ptr<SessionState>); + + /** Forget about an attached session. Called by SessionState destructor. */ + void forget(const SessionId&); + + Broker& getBroker() const { return broker; } + + const qpid::SessionState::Configuration& getSessionConfig() const { return config; } + + private: + typedef boost::ptr_vector<SessionState> Detached; // Sorted in expiry order. + typedef std::set<SessionId> Attached; + + void eraseExpired(); + + sys::Mutex lock; + Detached detached; + Attached attached; + qpid::SessionState::Configuration config; + Broker& broker; +}; + + + +}} // namespace qpid::broker + + + + + +#endif /*!QPID_BROKER_SESSIONMANAGER_H*/ diff --git a/qpid/cpp/src/qpid/broker/SessionOutputException.h b/qpid/cpp/src/qpid/broker/SessionOutputException.h new file mode 100644 index 0000000000..7c1c5de926 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/SessionOutputException.h @@ -0,0 +1,47 @@ +#ifndef QPID_BROKER_SESSIONOUTPUTEXCEPTION_H +#define QPID_BROKER_SESSIONOUTPUTEXCEPTION_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/Exception.h" + +namespace qpid { +namespace broker { + +/** + * This exception is used to signal 'session' exceptions (aka + * execution exceptions in AMQP 0-10 terms) that occur during output + * processing. It simply allows the channel of the session to be + * specified in addition to the other details. Special treatment is + * required at present because the output processing chain is + * different from that which handles incoming commands (specifically + * AggregateOutput cannot reasonably handle exceptions as it has no + * context). + */ +struct SessionOutputException : qpid::SessionException +{ + const uint16_t channel; + SessionOutputException(const qpid::SessionException& e, uint16_t c) : qpid::SessionException(e.code, e.getMessage()), channel(c) {} +}; +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_SESSIONOUTPUTEXCEPTION_H*/ diff --git a/qpid/cpp/src/qpid/broker/SessionState.cpp b/qpid/cpp/src/qpid/broker/SessionState.cpp new file mode 100644 index 0000000000..119e5732c4 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/SessionState.cpp @@ -0,0 +1,593 @@ +/* + * + * 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/SessionState.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/ConnectionState.h" +#include "qpid/broker/DeliveryRecord.h" +#include "qpid/broker/SessionManager.h" +#include "qpid/broker/SessionHandler.h" +#include "qpid/broker/RateFlowcontrol.h" +#include "qpid/sys/Timer.h" +#include "qpid/framing/AMQContentBody.h" +#include "qpid/framing/AMQHeaderBody.h" +#include "qpid/framing/AMQMethodBody.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/ServerInvoker.h" +#include "qpid/log/Statement.h" +#include "qpid/management/ManagementAgent.h" +#include "qpid/framing/AMQP_ClientProxy.h" + +#include <boost/bind.hpp> +#include <boost/lexical_cast.hpp> + +namespace qpid { +namespace broker { + +using namespace framing; +using sys::Mutex; +using boost::intrusive_ptr; +using qpid::management::ManagementAgent; +using qpid::management::ManagementObject; +using qpid::management::Manageable; +using qpid::management::Args; +using qpid::sys::AbsTime; +//using qpid::sys::Timer; +namespace _qmf = qmf::org::apache::qpid::broker; + +SessionState::SessionState( + Broker& b, SessionHandler& h, const SessionId& id, + const SessionState::Configuration& config, bool delayManagement) + : qpid::SessionState(id, config), + broker(b), handler(&h), + semanticState(*this, *this), + adapter(semanticState), + msgBuilder(&broker.getStore()), + mgmtObject(0), + rateFlowcontrol(0), + asyncCommandCompleter(new AsyncCommandCompleter(this)) +{ + uint32_t maxRate = broker.getOptions().maxSessionRate; + if (maxRate) { + if (handler->getConnection().getClientThrottling()) { + rateFlowcontrol.reset(new RateFlowcontrol(maxRate)); + } else { + QPID_LOG(warning, getId() << ": Unable to flow control client - client doesn't support"); + } + } + if (!delayManagement) addManagementObject(); + attach(h); +} + +void SessionState::addManagementObject() { + if (GetManagementObject()) return; // Already added. + Manageable* parent = broker.GetVhostObject (); + if (parent != 0) { + ManagementAgent* agent = getBroker().getManagementAgent(); + if (agent != 0) { + mgmtObject = new _qmf::Session + (agent, this, parent, getId().getName()); + mgmtObject->set_attached (0); + mgmtObject->set_detachedLifespan (0); + mgmtObject->clr_expireTime(); + if (rateFlowcontrol) + mgmtObject->set_maxClientRate(rateFlowcontrol->getRate()); + agent->addObject(mgmtObject); + } + } +} + +SessionState::~SessionState() { + asyncCommandCompleter->cancel(); + semanticState.closed(); + if (mgmtObject != 0) + mgmtObject->resourceDestroy (); + + if (flowControlTimer) + flowControlTimer->cancel(); +} + +AMQP_ClientProxy& SessionState::getProxy() { + assert(isAttached()); + return handler->getProxy(); +} + +uint16_t SessionState::getChannel() const { + assert(isAttached()); + return handler->getChannel(); +} + +ConnectionState& SessionState::getConnection() { + assert(isAttached()); + return handler->getConnection(); +} + +bool SessionState::isLocal(const ConnectionToken* t) const +{ + return isAttached() && &(handler->getConnection()) == t; +} + +void SessionState::detach() { + QPID_LOG(debug, getId() << ": detached on broker."); + asyncCommandCompleter->detached(); + disableOutput(); + handler = 0; + if (mgmtObject != 0) + mgmtObject->set_attached (0); +} + +void SessionState::disableOutput() +{ + semanticState.detached(); //prevents further activateOutput calls until reattached +} + +void SessionState::attach(SessionHandler& h) { + QPID_LOG(debug, getId() << ": attached on broker."); + handler = &h; + if (mgmtObject != 0) + { + mgmtObject->set_attached (1); + mgmtObject->set_connectionRef (h.getConnection().GetManagementObject()->getObjectId()); + mgmtObject->set_channelId (h.getChannel()); + } + asyncCommandCompleter->attached(); +} + +void SessionState::abort() { + if (isAttached()) + getConnection().outputTasks.abort(); +} + +void SessionState::activateOutput() { + if (isAttached()) + getConnection().outputTasks.activateOutput(); +} + +void SessionState::giveReadCredit(int32_t credit) { + if (isAttached()) + getConnection().outputTasks.giveReadCredit(credit); +} + +ManagementObject* SessionState::GetManagementObject (void) const +{ + return (ManagementObject*) mgmtObject; +} + +Manageable::status_t SessionState::ManagementMethod (uint32_t methodId, + Args& /*args*/, + string& /*text*/) +{ + Manageable::status_t status = Manageable::STATUS_UNKNOWN_METHOD; + + switch (methodId) + { + case _qmf::Session::METHOD_DETACH : + if (handler != 0) { + handler->sendDetach(); + } + status = Manageable::STATUS_OK; + break; + + case _qmf::Session::METHOD_CLOSE : + /* + if (handler != 0) + { + handler->getConnection().closeChannel(handler->getChannel()); + } + status = Manageable::STATUS_OK; + break; + */ + + case _qmf::Session::METHOD_SOLICITACK : + case _qmf::Session::METHOD_RESETLIFESPAN : + status = Manageable::STATUS_NOT_IMPLEMENTED; + break; + } + + return status; +} + +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); + 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() && currentCommandComplete) { + sendAcceptAndCompletion(); + } +} + +struct ScheduledCreditTask : public sys::TimerTask { + sys::Timer& timer; + SessionState& sessionState; + ScheduledCreditTask(const qpid::sys::Duration& d, sys::Timer& t, + SessionState& s) : + TimerTask(d,"ScheduledCredit"), + timer(t), + sessionState(s) + {} + + void fire() { + // This is the best we can currently do to avoid a destruction/fire race + sessionState.getConnection().requestIOProcessing(boost::bind(&ScheduledCreditTask::sendCredit, this)); + } + + void sendCredit() { + if ( !sessionState.processSendCredit(0) ) { + QPID_LOG(warning, sessionState.getId() << ": Reschedule sending credit"); + setupNextFire(); + timer.add(this); + } + } +}; + +void SessionState::handleContent(AMQFrame& frame, const SequenceNumber& id) +{ + if (frame.getBof() && frame.getBos()) //start of frameset + msgBuilder.start(id); + intrusive_ptr<Message> msg(msgBuilder.getMessage()); + msgBuilder.handle(frame); + if (frame.getEof() && frame.getEos()) {//end of frameset + if (frame.getBof()) { + //i.e this is a just a command frame, add a dummy header + AMQFrame header((AMQHeaderBody())); + header.setBof(false); + header.setEof(false); + msg->getFrames().append(header); + } + msg->setPublisher(&getConnection()); + msg->getIngressCompletion().begin(); + semanticState.handle(msg); + msgBuilder.end(); + IncompleteIngressMsgXfer xfer(this, msg); + msg->getIngressCompletion().end(xfer); // allows msg to complete xfer + } + + // Handle producer session flow control + if (rateFlowcontrol && frame.getBof() && frame.getBos()) { + if ( !processSendCredit(1) ) { + QPID_LOG(debug, getId() << ": Schedule sending credit"); + sys::Timer& timer = getBroker().getTimer(); + // Use heuristic for scheduled credit of time for 50 messages, but not longer than 500ms + sys::Duration d = std::min(sys::TIME_SEC * 50 / rateFlowcontrol->getRate(), 500 * sys::TIME_MSEC); + flowControlTimer = new ScheduledCreditTask(d, timer, *this); + timer.add(flowControlTimer); + } + } +} + +bool SessionState::processSendCredit(uint32_t msgs) +{ + qpid::sys::ScopedLock<Mutex> l(rateLock); + // Check for violating flow control + if ( msgs > 0 && rateFlowcontrol->flowStopped() ) { + QPID_LOG(warning, getId() << ": producer throttling violation"); + // TODO: Probably do message.stop("") first time then disconnect + // See comment on getClusterOrderProxy() in .h file + getClusterOrderProxy().getMessage().stop(""); + return true; + } + AbsTime now = AbsTime::now(); + uint32_t sendCredit = rateFlowcontrol->receivedMessage(now, msgs); + if (mgmtObject) mgmtObject->dec_clientCredit(msgs); + if ( sendCredit>0 ) { + QPID_LOG(debug, getId() << ": send producer credit " << sendCredit); + getClusterOrderProxy().getMessage().flow("", 0, sendCredit); + rateFlowcontrol->sentCredit(now, sendCredit); + if (mgmtObject) mgmtObject->inc_clientCredit(sendCredit); + return true; + } else { + return !rateFlowcontrol->flowStopped() ; + } +} + +void SessionState::sendAcceptAndCompletion() +{ + if (!accepted.empty()) { + getProxy().getMessage().accept(accepted); + accepted.clear(); + } + sendCompletion(); +} + +/** 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) +{ + 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) { + sendAcceptAndCompletion(); + } else if (callSendCompletion) { + sendCompletion(); + } +} + +void SessionState::handleIn(AMQFrame& frame) { + SequenceNumber commandId = receiverGetCurrent(); + //TODO: make command handling more uniform, regardless of whether + //commands carry content. + AMQMethodBody* m = frame.getMethod(); + if (m == 0 || m->isContentBearing()) { + handleContent(frame, commandId); + } else if (frame.getBof() && frame.getEof()) { + handleCommand(frame.getMethod(), commandId); + } else { + throw InternalErrorException("Cannot handle multi-frame command segments yet"); + } +} + +void SessionState::handleOut(AMQFrame& frame) { + assert(handler); + handler->out(frame); +} + +void SessionState::deliver(DeliveryRecord& msg, bool sync) +{ + uint32_t maxFrameSize = getConnection().getFrameMax(); + assert(senderGetCommandPoint().offset == 0); + SequenceNumber commandId = senderGetCommandPoint().command; + msg.deliver(getProxy().getHandler(), commandId, maxFrameSize); + assert(senderGetCommandPoint() == SessionPoint(commandId+1, 0)); // Delivery has moved sendPoint. + if (sync) { + AMQP_ClientProxy::Execution& p(getProxy().getExecution()); + Proxy::ScopedSync s(p); + p.sync(); + } +} + +void SessionState::sendCompletion() { + handler->sendCompletion(); +} + +void SessionState::senderCompleted(const SequenceSet& commands) { + qpid::SessionState::senderCompleted(commands); + semanticState.completed(commands); +} + +void SessionState::readyToSend() { + QPID_LOG(debug, getId() << ": ready to send, activating output."); + assert(handler); + semanticState.attached(); + if (rateFlowcontrol) { + qpid::sys::ScopedLock<Mutex> l(rateLock); + // Issue initial credit - use a heuristic here issue min of 300 messages or 1 secs worth + uint32_t credit = std::min(rateFlowcontrol->getRate(), 300U); + QPID_LOG(debug, getId() << ": Issuing producer message credit " << credit); + // See comment on getClusterOrderProxy() in .h file + getClusterOrderProxy().getMessage().setFlowMode("", 0); + getClusterOrderProxy().getMessage().flow("", 0, credit); + rateFlowcontrol->sentCredit(AbsTime::now(), credit); + if (mgmtObject) mgmtObject->inc_clientCredit(credit); + } +} + +Broker& SessionState::getBroker() { return broker; } + +// Session resume is not fully implemented so it is useless to set a +// non-0 timeout. Moreover it creates problems in a cluster because +// dead sessions are kept and interfere with failover. +void SessionState::setTimeout(uint32_t) { } + +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() +{ + boost::intrusive_ptr<SessionState::IncompleteIngressMsgXfer> cb(new SessionState::IncompleteIngressMsgXfer(session, msg)); + + // 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 cb; +} + + +/** 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/qpid/cpp/src/qpid/broker/SessionState.h b/qpid/cpp/src/qpid/broker/SessionState.h new file mode 100644 index 0000000000..b43df0c0aa --- /dev/null +++ b/qpid/cpp/src/qpid/broker/SessionState.h @@ -0,0 +1,284 @@ +#ifndef QPID_BROKER_SESSION_H +#define QPID_BROKER_SESSION_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/SessionState.h" +#include "qpid/framing/FrameHandler.h" +#include "qpid/framing/SequenceSet.h" +#include "qpid/sys/Time.h" +#include "qpid/management/Manageable.h" +#include "qmf/org/apache/qpid/broker/Session.h" +#include "qpid/broker/SessionAdapter.h" +#include "qpid/broker/DeliveryAdapter.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> +#include <ostream> + +namespace qpid { + +namespace framing { +class AMQP_ClientProxy; +} + +namespace sys { +class TimerTask; +} + +namespace broker { + +class Broker; +class ConnectionState; +class Message; +class SessionHandler; +class SessionManager; +class RateFlowcontrol; + +/** + * Broker-side session state includes session's handler chains, which + * may themselves have state. + */ +class SessionState : public qpid::SessionState, + public SessionContext, + public DeliveryAdapter, + public management::Manageable, + public framing::FrameHandler::InOutHandler +{ + public: + SessionState(Broker&, SessionHandler&, const SessionId&, + const SessionState::Configuration&, bool delayManagement=false); + ~SessionState(); + bool isAttached() const { return handler; } + + void detach(); + void attach(SessionHandler& handler); + void disableOutput(); + + /** @pre isAttached() */ + framing::AMQP_ClientProxy& getProxy(); + + /** @pre isAttached() */ + uint16_t getChannel() const; + + /** @pre isAttached() */ + ConnectionState& getConnection(); + bool isLocal(const ConnectionToken* t) const; + + Broker& getBroker(); + + void setTimeout(uint32_t seconds); + + /** OutputControl **/ + void abort(); + void activateOutput(); + void giveReadCredit(int32_t); + + void senderCompleted(const framing::SequenceSet& ranges); + + void sendCompletion(); + + //delivery adapter methods: + void deliver(DeliveryRecord&, bool sync); + + // Manageable entry points + management::ManagementObject* GetManagementObject (void) const; + management::Manageable::status_t + ManagementMethod (uint32_t methodId, management::Args& args, std::string&); + + void readyToSend(); + + // Used by cluster to create replica sessions. + SemanticState& getSemanticState() { return semanticState; } + boost::intrusive_ptr<Message> getMessageInProgress() { return msgBuilder.getMessage(); } + SessionAdapter& getSessionAdapter() { return adapter; } + + bool processSendCredit(uint32_t msgs); + + 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(); + + private: + void handleCommand(framing::AMQMethodBody* method, const framing::SequenceNumber& id); + void handleContent(framing::AMQFrame& frame, const framing::SequenceNumber& id); + + // 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); + + // End of the input & output chains. + void handleInLast(framing::AMQFrame& frame); + void handleOutLast(framing::AMQFrame& frame); + + void sendAcceptAndCompletion(); + + /** + * If commands are sent based on the local time (e.g. in timers), they don't have + * a well-defined ordering across cluster nodes. + * This proxy is for sending such commands. In a clustered broker it will take steps + * to synchronize command order across the cluster. In a stand-alone broker + * it is just a synonym for getProxy() + */ + framing::AMQP_ClientProxy& getClusterOrderProxy(); + + Broker& broker; + SessionHandler* handler; + sys::AbsTime expiry; // Used by SessionManager. + SemanticState semanticState; + SessionAdapter adapter; + MessageBuilder msgBuilder; + qmf::org::apache::qpid::broker::Session* mgmtObject; + qpid::framing::SequenceSet accepted; + + // State used for producer flow control (rate limited) + qpid::sys::Mutex rateLock; + 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) {} + 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; +}; + + +inline std::ostream& operator<<(std::ostream& out, const SessionState& session) { + return out << session.getId(); +} + +}} // namespace qpid::broker + + + +#endif /*!QPID_BROKER_SESSION_H*/ diff --git a/qpid/cpp/src/qpid/broker/SignalHandler.cpp b/qpid/cpp/src/qpid/broker/SignalHandler.cpp new file mode 100644 index 0000000000..16c141f21c --- /dev/null +++ b/qpid/cpp/src/qpid/broker/SignalHandler.cpp @@ -0,0 +1,54 @@ +/* + * + * 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/SignalHandler.h" +#include "qpid/broker/Broker.h" +#include "qpid/sys/Mutex.h" +#include <signal.h> + +namespace qpid { +namespace broker { + +// Lock is to ensure that broker is not concurrently set to 0 and +// deleted while we are in a call to broker->shutdown() + +sys::Mutex brokerLock; +Broker* SignalHandler::broker; + +void SignalHandler::setBroker(Broker* b) { + sys::Mutex::ScopedLock l(brokerLock); + broker = b; + signal(SIGINT,shutdownHandler); + signal(SIGTERM, shutdownHandler); + signal(SIGHUP,SIG_IGN); + signal(SIGCHLD,SIG_IGN); +} + +void SignalHandler::shutdown() { shutdownHandler(0); } + +void SignalHandler::shutdownHandler(int) { + sys::Mutex::ScopedLock l(brokerLock); + if (broker) { + broker->shutdown(); + broker = 0; + } +} + +}} // namespace qpid::broker diff --git a/qpid/cpp/src/qpid/broker/SignalHandler.h b/qpid/cpp/src/qpid/broker/SignalHandler.h new file mode 100644 index 0000000000..7bfa9ea630 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/SignalHandler.h @@ -0,0 +1,50 @@ +#ifndef QPID_BROKER_SIGNALHANDLER_H +#define QPID_BROKER_SIGNALHANDLER_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. + * + */ + +namespace qpid { +namespace broker { + +class Broker; + +/** + * Handle signals e.g. to shut-down a broker. + */ +class SignalHandler +{ + public: + /** Set the broker to be shutdown on signals. + * Must be reset by calling setBroker(0) before the broker is deleted. + */ + static void setBroker(Broker* broker); + + /** Initiate shut-down of broker */ + static void shutdown(); + + private: + static void shutdownHandler(int); + static Broker* broker; +}; +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_SIGNALHANDLER_H*/ diff --git a/qpid/cpp/src/qpid/broker/StatefulQueueObserver.h b/qpid/cpp/src/qpid/broker/StatefulQueueObserver.h new file mode 100644 index 0000000000..c682d460b7 --- /dev/null +++ b/qpid/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/qpid/cpp/src/qpid/broker/System.cpp b/qpid/cpp/src/qpid/broker/System.cpp new file mode 100644 index 0000000000..8cd2edda76 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/System.cpp @@ -0,0 +1,84 @@ +// +// 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/System.h" +#include "qpid/broker/Broker.h" +#include "qpid/management/ManagementAgent.h" +#include "qpid/framing/Uuid.h" +#include "qpid/sys/SystemInfo.h" +#include "qpid/types/Uuid.h" +#include <iostream> +#include <fstream> + +using qpid::management::ManagementAgent; +using namespace qpid::broker; +using namespace std; +namespace _qmf = qmf::org::apache::qpid::broker; + +System::System (string _dataDir, Broker* broker) : mgmtObject(0) +{ + ManagementAgent* agent = broker ? broker->getManagementAgent() : 0; + + if (agent != 0) + { + framing::Uuid systemId; + + if (_dataDir.empty ()) + { + systemId.generate (); + } + else + { + string filename (_dataDir + "/systemId"); + ifstream inFile (filename.c_str ()); + + if (inFile.good ()) + { + inFile >> systemId; + inFile.close (); + } + else + { + systemId.generate (); + ofstream outFile (filename.c_str ()); + if (outFile.good ()) + { + outFile << systemId << endl; + outFile.close (); + } + } + } + + mgmtObject = new _qmf::System(agent, this, types::Uuid(systemId.c_array())); + std::string sysname, nodename, release, version, machine; + qpid::sys::SystemInfo::getSystemId (sysname, + nodename, + release, + version, + machine); + mgmtObject->set_osName (sysname); + mgmtObject->set_nodeName (nodename); + mgmtObject->set_release (release); + mgmtObject->set_version (version); + mgmtObject->set_machine (machine); + + agent->addObject(mgmtObject, 0, true); + } +} + diff --git a/qpid/cpp/src/qpid/broker/System.h b/qpid/cpp/src/qpid/broker/System.h new file mode 100644 index 0000000000..0fc2c2bd88 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/System.h @@ -0,0 +1,51 @@ +#ifndef _BrokerSystem_ +#define _BrokerSystem_ + +// +// 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/management/Manageable.h" +#include "qmf/org/apache/qpid/broker/System.h" +#include <boost/shared_ptr.hpp> +#include <string> + +namespace qpid { +namespace broker { + +class Broker; + +class System : public management::Manageable +{ + private: + + qmf::org::apache::qpid::broker::System* mgmtObject; + + public: + + typedef boost::shared_ptr<System> shared_ptr; + + System (std::string _dataDir, Broker* broker = 0); + + management::ManagementObject* GetManagementObject (void) const + { return mgmtObject; } +}; + +}} + +#endif /*!_BrokerSystem_*/ diff --git a/qpid/cpp/src/qpid/broker/ThresholdAlerts.cpp b/qpid/cpp/src/qpid/broker/ThresholdAlerts.cpp new file mode 100644 index 0000000000..3c9e210d4d --- /dev/null +++ b/qpid/cpp/src/qpid/broker/ThresholdAlerts.cpp @@ -0,0 +1,191 @@ +/* + * + * 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/ThresholdAlerts.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/QueuedMessage.h" +#include "qpid/amqp_0_10/Codecs.h" +#include "qpid/log/Statement.h" +#include "qpid/management/ManagementAgent.h" +#include "qmf/org/apache/qpid/broker/EventQueueThresholdExceeded.h" + +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, + const uint64_t st, + const long repeat) + : name(n), agent(a), countThreshold(ct), sizeThreshold(st), + repeatInterval(repeat ? repeat*qpid::sys::TIME_SEC : 0), + count(0), size(0), lastAlert(qpid::sys::EPOCH) {} + +void ThresholdAlerts::enqueued(const QueuedMessage& m) +{ + size += m.payload->contentSize(); + ++count; + if ((countThreshold && count >= countThreshold) || (sizeThreshold && size >= sizeThreshold)) { + if ((repeatInterval == 0 && lastAlert == qpid::sys::EPOCH) + || qpid::sys::Duration(lastAlert, qpid::sys::now()) > repeatInterval) { + //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); + } + } +} + +void ThresholdAlerts::dequeued(const QueuedMessage& m) +{ + size -= m.payload->contentSize(); + --count; + if ((countThreshold && count < countThreshold) || (sizeThreshold && size < sizeThreshold)) { + lastAlert = qpid::sys::EPOCH; + } +} + + + +void ThresholdAlerts::observe(Queue& queue, qpid::management::ManagementAgent& agent, + const uint64_t countThreshold, + const uint64_t sizeThreshold, + const long repeatInterval) +{ + if (countThreshold || sizeThreshold) { + boost::shared_ptr<QueueObserver> observer( + new ThresholdAlerts(queue.getName(), agent, countThreshold, sizeThreshold, repeatInterval) + ); + queue.addObserver(observer); + } +} + +void ThresholdAlerts::observe(Queue& queue, qpid::management::ManagementAgent& agent, + const qpid::framing::FieldTable& settings, uint16_t limitRatio) + +{ + qpid::types::Variant::Map map; + qpid::amqp_0_10::translate(settings, map); + observe(queue, agent, map, limitRatio); +} + +template <class T> +class Option +{ + public: + Option(const std::string& name, T d) : defaultValue(d) { names.push_back(name); } + void addAlias(const std::string& name) { names.push_back(name); } + T get(const qpid::types::Variant::Map& settings) const + { + T value(defaultValue); + for (std::vector<std::string>::const_iterator i = names.begin(); i != names.end(); ++i) { + if (get(settings, *i, value)) break; + } + return value; + } + private: + std::vector<std::string> names; + T defaultValue; + + bool get(const qpid::types::Variant::Map& settings, const std::string& name, T& value) const + { + qpid::types::Variant::Map::const_iterator i = settings.find(name); + if (i != settings.end()) { + try { + value = (T) i->second; + } catch (const qpid::types::InvalidConversion&) { + QPID_LOG(warning, "Bad value for" << name << ": " << i->second); + } + return true; + } else { + return false; + } + } +}; + +void ThresholdAlerts::observe(Queue& queue, qpid::management::ManagementAgent& agent, + 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 specified + //percentage of any limit from the policy. + const QueuePolicy* policy = queue.getPolicy(); + 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 && 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/qpid/cpp/src/qpid/broker/ThresholdAlerts.h b/qpid/cpp/src/qpid/broker/ThresholdAlerts.h new file mode 100644 index 0000000000..c77722e700 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/ThresholdAlerts.h @@ -0,0 +1,73 @@ +#ifndef QPID_BROKER_THRESHOLDALERTS_H +#define QPID_BROKER_THRESHOLDALERTS_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/sys/Time.h" +#include "qpid/types/Variant.h" +#include <string> + +namespace qpid { +namespace framing { +class FieldTable; +} +namespace management { +class ManagementAgent; +} +namespace broker { + +class Queue; +/** + * Class to manage generation of QMF alerts when particular thresholds + * are breached on a queue. + */ +class ThresholdAlerts : public QueueObserver +{ + public: + ThresholdAlerts(const std::string& name, + qpid::management::ManagementAgent& agent, + const uint32_t countThreshold, + const uint64_t sizeThreshold, + const long repeatInterval); + void enqueued(const QueuedMessage&); + void dequeued(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, uint16_t limitRatio); + static void observe(Queue& queue, qpid::management::ManagementAgent& agent, + const qpid::types::Variant::Map& settings, uint16_t limitRatio); + private: + const std::string name; + qpid::management::ManagementAgent& agent; + const uint32_t countThreshold; + const uint64_t sizeThreshold; + const qpid::sys::Duration repeatInterval; + uint64_t count; + uint64_t size; + qpid::sys::AbsTime lastAlert; +}; +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_THRESHOLDALERTS_H*/ diff --git a/qpid/cpp/src/qpid/broker/TopicExchange.cpp b/qpid/cpp/src/qpid/broker/TopicExchange.cpp new file mode 100644 index 0000000000..644a3d628e --- /dev/null +++ b/qpid/cpp/src/qpid/broker/TopicExchange.cpp @@ -0,0 +1,692 @@ +/* + * + * 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/TopicExchange.h" +#include "qpid/broker/FedOps.h" +#include "qpid/log/Statement.h" +#include <algorithm> + + +namespace qpid { +namespace broker { + +using namespace qpid::framing; +using namespace qpid::sys; +using namespace std; +namespace _qmf = qmf::org::apache::qpid::broker; + + +// TODO aconway 2006-09-20: More efficient matching algorithm. +// Areas for improvement: +// - excessive string copying: should be 0 copy, match from original buffer. +// - match/lookup: use descision tree or other more efficient structure. + +namespace +{ +const std::string STAR("*"); +const std::string HASH("#"); +} + +// iterator for federation ReOrigin bind operation +class TopicExchange::ReOriginIter : public TopicExchange::BindingNode::TreeIterator { +public: + ReOriginIter() {}; + ~ReOriginIter() {}; + bool visit(BindingNode& node) { + if (node.bindings.fedBinding.hasLocal()) { + keys2prop.push_back(node.routePattern); + } + return true; + } + std::vector<std::string> keys2prop; +}; + + +// match iterator used by route(): builds BindingList of all unique queues +// that match the routing key. +class TopicExchange::BindingsFinderIter : public TopicExchange::BindingNode::TreeIterator { +public: + BindingsFinderIter(BindingList &bl) : b(bl) {}; + ~BindingsFinderIter() {}; + + BindingList& b; + std::set<std::string> qSet; + + bool visit(BindingNode& node) { + + Binding::vector& qv(node.bindings.bindingVector); + for (Binding::vector::iterator j = qv.begin(); j != qv.end(); j++) { + // do not duplicate queues on the binding list + if (qSet.insert(j->get()->queue->getName()).second) { + b->push_back(*j); + } + } + return true; + } +}; + + + +// Iterator to visit all bindings until a given queue is found +class TopicExchange::QueueFinderIter : public TopicExchange::BindingNode::TreeIterator { +public: + QueueFinderIter(Queue::shared_ptr queue) : queue(queue), found(false) {}; + ~QueueFinderIter() {}; + bool visit(BindingNode& node) { + + Binding::vector& qv(node.bindings.bindingVector); + Binding::vector::iterator q; + for (q = qv.begin(); q != qv.end(); q++) { + if ((*q)->queue == queue) { + found = true; + return false; // search done + } + } + return true; // continue search + } + + Queue::shared_ptr queue; + bool found; +}; + + +// Iterate over a string of '.'-separated tokens. +struct TopicExchange::TokenIterator { + typedef pair<const char*,const char*> Token; + + TokenIterator(const char* b, const char* e) : end(e), token(make_pair(b, find(b,e,'.'))) {} + + TokenIterator(const string& key) : end(&key[0]+key.size()), token(make_pair(&key[0], find(&key[0],end,'.'))) {} + + bool finished() const { return !token.first; } + + void next() { + if (token.second == end) + token.first = token.second = 0; + else { + token.first=token.second+1; + token.second=(find(token.first, end, '.')); + } + } + + void pop(string &top) { + ptrdiff_t l = len(); + if (l) { + top.assign(token.first, l); + } else top.clear(); + next(); + } + + bool match1(char c) const { + return token.second==token.first+1 && *token.first == c; + } + + bool match(const Token& token2) const { + ptrdiff_t l=len(); + return l == token2.second-token2.first && + strncmp(token.first, token2.first, l) == 0; + } + + bool match(const string& str) const { + ptrdiff_t l=len(); + return l == ptrdiff_t(str.size()) && + str.compare(0, l, token.first, l) == 0; + } + + ptrdiff_t len() const { return token.second - token.first; } + + + const char* end; + Token token; +}; + + +class TopicExchange::Normalizer : public TopicExchange::TokenIterator { + public: + Normalizer(string& p) + : TokenIterator(&p[0], &p[0]+p.size()), pattern(p) + { normalize(); } + + private: + // Apply 2 transformations: #.* -> *.# and #.# -> # + void normalize() { + while (!finished()) { + if (match1('#')) { + const char* hash1=token.first; + next(); + if (!finished()) { + if (match1('#')) { // Erase #.# -> # + pattern.erase(hash1-pattern.data(), 2); + token.first -= 2; + token.second -= 2; + end -= 2; + } + else if (match1('*')) { // Swap #.* -> *.# + swap(*const_cast<char*>(hash1), + *const_cast<char*>(token.first)); + } + } + } + else + next(); + } + } + + string& pattern; +}; + + + +// Convert sequences of * and # to a sequence of * followed by a single # +string TopicExchange::normalize(const string& pattern) { + string normal(pattern); + Normalizer n(normal); + return normal; +} + + +TopicExchange::TopicExchange(const string& _name, Manageable* _parent, Broker* b) + : Exchange(_name, _parent, b), + nBindings(0) +{ + if (mgmtExchange != 0) + mgmtExchange->set_type (typeName); +} + +TopicExchange::TopicExchange(const std::string& _name, bool _durable, + const FieldTable& _args, Manageable* _parent, Broker* b) : + Exchange(_name, _durable, _args, _parent, b), + nBindings(0) +{ + if (mgmtExchange != 0) + mgmtExchange->set_type (typeName); +} + +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) : ""); + bool propagate = false; + string routingPattern = normalize(routingKey); + + if (args == 0 || fedOp.empty() || fedOp == fedOpBind) { + RWlock::ScopedWlock l(lock); + BindingKey *bk = bindingTree.addBindingKey(routingPattern); + if (bk) { + Binding::vector& qv(bk->bindingVector); + Binding::vector::iterator q; + for (q = qv.begin(); q != qv.end(); q++) { + if ((*q)->queue == queue) { + // already bound, but may be from a different fedOrigin + bk->fedBinding.addOrigin(queue->getName(), fedOrigin); + return false; + } + } + + Binding::shared_ptr binding (new Binding (routingPattern, queue, this, FieldTable(), fedOrigin)); + binding->startManagement(); + bk->bindingVector.push_back(binding); + nBindings++; + propagate = bk->fedBinding.addOrigin(queue->getName(), fedOrigin); + if (mgmtExchange != 0) { + mgmtExchange->inc_bindingCount(); + } + QPID_LOG(debug, "Binding key [" << routingPattern << "] to queue " << queue->getName() + << " on exchange " << getName() << " (origin=" << fedOrigin << ")"); + } + } else if (fedOp == fedOpUnbind) { + 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); + } + } + } 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 + * released + */ + ReOriginIter reOriginIter; + { + RWlock::ScopedRlock l(lock); + bindingTree.iterateAll( reOriginIter ); + } /* lock dropped */ + + for (std::vector<std::string>::const_iterator key = reOriginIter.keys2prop.begin(); + key != reOriginIter.keys2prop.end(); key++) { + propagateFedOp( *key, string(), fedOpBind, string()); + } + } + + 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) +{ + 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 = getQueueBinding(queue, routingKey); + if (!bk) return 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) + break; + if(q == qv.end()) return false; + qv.erase(q); + assert(nBindings > 0); + nBindings--; + + if(qv.empty()) { + bindingTree.removeBindingKey(routingKey); + } + if (mgmtExchange != 0) { + mgmtExchange->dec_bindingCount(); + } + QPID_LOG(debug, "Unbound key [" << routingKey << "] from queue " << queue->getName() + << " on exchange " << getName()); + return true; +} + +/** 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 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()) ? bk : 0; +} + +void TopicExchange::route(Deliverable& msg, const string& routingKey, const FieldTable* /*args*/) +{ + // Note: PERFORMANCE CRITICAL!!! + 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); + 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); +} + +bool TopicExchange::isBound(Queue::shared_ptr queue, const string* const routingKey, const FieldTable* const) +{ + RWlock::ScopedRlock l(lock); + if (routingKey && queue) { + string key(normalize(*routingKey)); + return getQueueBinding(queue, key) != 0; + } else if (!routingKey && !queue) { + return nBindings > 0; + } else if (routingKey) { + if (bindingTree.getBindingKey(*routingKey)) { + return true; + } + } else { + QueueFinderIter queueFinder(queue); + bindingTree.iterateAll( queueFinder ); + return queueFinder.found; + } + return false; +} + +TopicExchange::~TopicExchange() {} + +const std::string TopicExchange::typeName("topic"); + +// +// class BindingNode +// + +TopicExchange::BindingNode::~BindingNode() +{ + childTokens.clear(); +} + + +// Add a binding pattern to the tree. Return a pointer to the binding key +// of the node that matches the binding pattern. +TopicExchange::BindingKey* +TopicExchange::BindingNode::addBindingKey(const std::string& normalizedRoute) +{ + TokenIterator bKey(normalizedRoute); + return addBindingKey(bKey, normalizedRoute); +} + + +// Return a pointer to the binding key of the leaf node that matches the binding pattern. +TopicExchange::BindingKey* +TopicExchange::BindingNode::getBindingKey(const std::string& normalizedRoute) +{ + TokenIterator bKey(normalizedRoute); + return getBindingKey(bKey); +} + + +// Delete the binding associated with the given route. +void TopicExchange::BindingNode::removeBindingKey(const std::string& normalizedRoute) +{ + TokenIterator bKey2(normalizedRoute); + removeBindingKey(bKey2, normalizedRoute); +} + +// visit each node in the tree. Note: all nodes are visited, +// even non-leaf nodes (i.e. nodes without any bindings) +bool TopicExchange::BindingNode::iterateAll(TopicExchange::BindingNode::TreeIterator& iter) +{ + if (!iter.visit(*this)) return false; + + if (starChild && !starChild->iterateAll(iter)) return false; + + if (hashChild && !hashChild->iterateAll(iter)) return false; + + for (ChildMap::iterator ptr = childTokens.begin(); + ptr != childTokens.end(); ptr++) { + + if (!ptr->second->iterateAll(iter)) return false; + } + + return true; +} + +// applies iter against only matching nodes until iter returns false +// Note Well: the iter may match against the same node more than once +// if # wildcards are present! +bool TopicExchange::BindingNode::iterateMatch(const std::string& routingKey, TreeIterator& iter) +{ + TopicExchange::TokenIterator rKey(routingKey); + return iterateMatch( rKey, iter ); +} + + +// recurse over binding using token iterator. +// Note well: bkey is modified! +TopicExchange::BindingKey* +TopicExchange::BindingNode::addBindingKey(TokenIterator &bKey, + const string& fullPattern) +{ + if (bKey.finished()) { + // this node's binding + if (routePattern.empty()) { + routePattern = fullPattern; + } else assert(routePattern == fullPattern); + + return &bindings; + + } else { + // pop the topmost token & recurse... + + if (bKey.match(STAR)) { + if (!starChild) { + starChild.reset(new StarNode()); + } + bKey.next(); + return starChild->addBindingKey(bKey, fullPattern); + + } else if (bKey.match(HASH)) { + if (!hashChild) { + hashChild.reset(new HashNode()); + } + bKey.next(); + return hashChild->addBindingKey(bKey, fullPattern); + + } else { + ChildMap::iterator ptr; + std::string next_token; + bKey.pop(next_token); + ptr = childTokens.find(next_token); + if (ptr != childTokens.end()) { + return ptr->second->addBindingKey(bKey, fullPattern); + } else { + BindingNode::shared_ptr child(new BindingNode(next_token)); + childTokens[next_token] = child; + return child->addBindingKey(bKey, fullPattern); + } + } + } +} + + +// Remove a binding pattern from the tree. Return true if the current +// node becomes a leaf without any bindings (therefore can be deleted). +// Note Well: modifies parameter bKey's value! +bool +TopicExchange::BindingNode::removeBindingKey(TokenIterator &bKey, + const string& fullPattern) +{ + bool remove; + + if (!bKey.finished()) { + + if (bKey.match(STAR)) { + bKey.next(); + if (starChild) { + remove = starChild->removeBindingKey(bKey, fullPattern); + if (remove) { + starChild.reset(); + } + } + } else if (bKey.match(HASH)) { + bKey.next(); + if (hashChild) { + remove = hashChild->removeBindingKey(bKey, fullPattern); + if (remove) { + hashChild.reset(); + } + } + } else { + ChildMap::iterator ptr; + std::string next_token; + bKey.pop(next_token); + ptr = childTokens.find(next_token); + if (ptr != childTokens.end()) { + remove = ptr->second->removeBindingKey(bKey, fullPattern); + if (remove) { + childTokens.erase(ptr); + } + } + } + } + + // no bindings and no children == parent can delete this node. + return getChildCount() == 0 && bindings.bindingVector.empty(); +} + + +// find the binding key that matches the given binding pattern. +// Note Well: modifies key parameter! +TopicExchange::BindingKey* +TopicExchange::BindingNode::getBindingKey(TokenIterator &key) +{ + if (key.finished()) { + return &bindings; + } + + string next_token; + + key.pop(next_token); + + if (next_token == STAR) { + if (starChild) + return starChild->getBindingKey(key); + } else if (next_token == HASH) { + if (hashChild) + return hashChild->getBindingKey(key); + } else { + ChildMap::iterator ptr; + ptr = childTokens.find(next_token); + if (ptr != childTokens.end()) { + return ptr->second->getBindingKey(key); + } + } + + return 0; +} + + + +// iterate over all nodes that match the given key. Note well: the set of nodes +// that are visited includes matching non-leaf nodes. +// Note well: parameter key is modified! +bool TopicExchange::BindingNode::iterateMatch(TokenIterator& key, TreeIterator& iter) +{ + // invariant: key has matched all previous tokens up to this node. + if (key.finished()) { + // exact match this node: visit if bound + if (!bindings.bindingVector.empty()) + if (!iter.visit(*this)) return false; + } + + // check remaining key against children, even if empty. + return iterateMatchChildren(key, iter); +} + + +TopicExchange::StarNode::StarNode() + : BindingNode(STAR) {} + + +// See iterateMatch() above. +// Special case: this node must verify a token is available (match exactly one). +bool TopicExchange::StarNode::iterateMatch(TokenIterator& key, TreeIterator& iter) +{ + // must match one token: + if (key.finished()) + return true; // match failed, but continue iteration on siblings + + // pop the topmost token + key.next(); + + if (key.finished()) { + // exact match this node: visit if bound + if (!bindings.bindingVector.empty()) + if (!iter.visit(*this)) return false; + } + + return iterateMatchChildren(key, iter); +} + + +TopicExchange::HashNode::HashNode() + : BindingNode(HASH) {} + + +// See iterateMatch() above. +// Special case: can match zero or more tokens at the head of the key. +bool TopicExchange::HashNode::iterateMatch(TokenIterator& key, TreeIterator& iter) +{ + // consume each token and look for a match on the + // remaining key. + while (!key.finished()) { + if (!iterateMatchChildren(key, iter)) return false; + key.next(); + } + + if (!bindings.bindingVector.empty()) + return iter.visit(*this); + + return true; +} + + +// helper: iterate over current node's matching children +bool +TopicExchange::BindingNode::iterateMatchChildren(const TopicExchange::TokenIterator& key, + TopicExchange::BindingNode::TreeIterator& iter) +{ + // always try glob - it can match empty keys + if (hashChild) { + TokenIterator tmp(key); + if (!hashChild->iterateMatch(tmp, iter)) + return false; + } + + if (!key.finished()) { + + if (starChild) { + TokenIterator tmp(key); + if (!starChild->iterateMatch(tmp, iter)) + return false; + } + + if (!childTokens.empty()) { + TokenIterator newKey(key); + std::string next_token; + newKey.pop(next_token); + + ChildMap::iterator ptr = childTokens.find(next_token); + if (ptr != childTokens.end()) { + return ptr->second->iterateMatch(newKey, iter); + } + } + } + + return true; +} + +}} // namespace qpid::broker diff --git a/qpid/cpp/src/qpid/broker/TopicExchange.h b/qpid/cpp/src/qpid/broker/TopicExchange.h new file mode 100644 index 0000000000..636918f8a1 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/TopicExchange.h @@ -0,0 +1,208 @@ +/* + * + * 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 _TopicExchange_ +#define _TopicExchange_ + +#include <map> +#include <vector> +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/Exchange.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/sys/Monitor.h" +#include "qpid/broker/Queue.h" + + +namespace qpid { +namespace broker { + +class TopicExchange : public virtual Exchange { + + struct TokenIterator; + class Normalizer; + + struct BindingKey { // binding for this node + Binding::vector bindingVector; + FedBinding fedBinding; + }; + + // Binding database: + // The dotted form of a binding key is broken up and stored in a directed tree graph. + // Common binding prefix are merged. This allows the route match alogrithm to quickly + // isolate those sub-trees that match a given routingKey. + // For example, given the routes: + // a.b.c.<...> + // a.b.d.<...> + // a.x.y.<...> + // The resulting tree would be: + // a-->b-->c-->... + // | +-->d-->... + // +-->x-->y-->... + // + class QPID_BROKER_CLASS_EXTERN BindingNode { + public: + + typedef boost::shared_ptr<BindingNode> shared_ptr; + + // for database transversal (visit a node). + class TreeIterator { + public: + TreeIterator() {}; + virtual ~TreeIterator() {}; + virtual bool visit(BindingNode& node) = 0; + }; + + BindingNode() {}; + BindingNode(const std::string& token) : token(token) {}; + QPID_BROKER_EXTERN virtual ~BindingNode(); + + // add normalizedRoute to tree, return associated BindingKey + QPID_BROKER_EXTERN BindingKey* addBindingKey(const std::string& normalizedRoute); + + // return BindingKey associated with normalizedRoute + QPID_BROKER_EXTERN BindingKey* getBindingKey(const std::string& normalizedRoute); + + // remove BindingKey associated with normalizedRoute + QPID_BROKER_EXTERN void removeBindingKey(const std::string& normalizedRoute); + + // applies iter against each node in tree until iter returns false + QPID_BROKER_EXTERN bool iterateAll(TreeIterator& iter); + + // applies iter against only matching nodes until iter returns false + QPID_BROKER_EXTERN bool iterateMatch(const std::string& routingKey, TreeIterator& iter); + + std::string routePattern; // normalized binding that matches this node + BindingKey bindings; // for matches against this node + + protected: + + std::string token; // portion of pattern represented by this node + + // children + typedef std::map<const std::string, BindingNode::shared_ptr> ChildMap; + ChildMap childTokens; + BindingNode::shared_ptr starChild; // "*" subtree + BindingNode::shared_ptr hashChild; // "#" subtree + + unsigned int getChildCount() { return childTokens.size() + + (starChild ? 1 : 0) + (hashChild ? 1 : 0); } + BindingKey* addBindingKey(TokenIterator& bKey, + const std::string& fullPattern); + bool removeBindingKey(TokenIterator& bKey, + const std::string& fullPattern); + BindingKey* getBindingKey(TokenIterator& bKey); + QPID_BROKER_EXTERN virtual bool iterateMatch(TokenIterator& rKey, TreeIterator& iter); + bool iterateMatchChildren(const TokenIterator& key, TreeIterator& iter); + }; + + // Special case: ("*" token) Node in the tree for a match exactly one wildcard + class StarNode : public BindingNode { + public: + StarNode(); + ~StarNode() {}; + + protected: + virtual bool iterateMatch(TokenIterator& key, TreeIterator& iter); + }; + + // Special case: ("#" token) Node in the tree for a match zero or more + class HashNode : public BindingNode { + public: + HashNode(); + ~HashNode() {}; + + protected: + virtual bool iterateMatch(TokenIterator& key, TreeIterator& iter); + }; + + BindingNode bindingTree; + unsigned long nBindings; + qpid::sys::RWlock lock; // protects bindingTree and nBindings + 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; + class QueueFinderIter; + + public: + static const std::string typeName; + + static QPID_BROKER_EXTERN std::string normalize(const std::string& pattern); + + QPID_BROKER_EXTERN TopicExchange(const std::string& name, + management::Manageable* parent = 0, Broker* broker = 0); + QPID_BROKER_EXTERN TopicExchange(const std::string& _name, + bool _durable, + const qpid::framing::FieldTable& _args, + management::Manageable* parent = 0, Broker* broker = 0); + + virtual std::string getType() const { return typeName; } + + QPID_BROKER_EXTERN virtual bool bind(Queue::shared_ptr queue, + const std::string& routingKey, + const qpid::framing::FieldTable* args); + + virtual bool unbind(Queue::shared_ptr queue, const std::string& routingKey, const qpid::framing::FieldTable* args); + + QPID_BROKER_EXTERN virtual void route(Deliverable& msg, + const std::string& routingKey, + const qpid::framing::FieldTable* args); + + QPID_BROKER_EXTERN virtual bool isBound(Queue::shared_ptr queue, + const std::string* const routingKey, + const qpid::framing::FieldTable* const args); + + QPID_BROKER_EXTERN virtual ~TopicExchange(); + virtual bool supportsDynamicBinding() { return true; } + + class TopicExchangeTester; + friend class TopicExchangeTester; +}; + + + +} +} + +#endif diff --git a/qpid/cpp/src/qpid/broker/TransactionalStore.h b/qpid/cpp/src/qpid/broker/TransactionalStore.h new file mode 100644 index 0000000000..2a2bac0c51 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/TransactionalStore.h @@ -0,0 +1,60 @@ +/* + * + * 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 _TransactionalStore_ +#define _TransactionalStore_ + +#include <memory> +#include <string> +#include <set> + +namespace qpid { +namespace broker { + +struct InvalidTransactionContextException : public std::exception {}; + +class TransactionContext { +public: + virtual ~TransactionContext(){} +}; + +class TPCTransactionContext : public TransactionContext { +public: + virtual ~TPCTransactionContext(){} +}; + +class TransactionalStore { +public: + virtual std::auto_ptr<TransactionContext> begin() = 0; + virtual std::auto_ptr<TPCTransactionContext> begin(const std::string& xid) = 0; + virtual void prepare(TPCTransactionContext& txn) = 0; + virtual void commit(TransactionContext& txn) = 0; + virtual void abort(TransactionContext& txn) = 0; + + virtual void collectPreparedXids(std::set<std::string>& xids) = 0; + + virtual ~TransactionalStore(){} +}; + +} +} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/TxAccept.cpp b/qpid/cpp/src/qpid/broker/TxAccept.cpp new file mode 100644 index 0000000000..928ac12c10 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/TxAccept.cpp @@ -0,0 +1,100 @@ +/* + * + * 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/TxAccept.h" +#include "qpid/log/Statement.h" + +using std::bind1st; +using std::bind2nd; +using std::mem_fun_ref; +using namespace qpid::broker; +using qpid::framing::SequenceSet; +using qpid::framing::SequenceNumber; + +TxAccept::RangeOp::RangeOp(const AckRange& r) : range(r) {} + +void TxAccept::RangeOp::prepare(TransactionContext* ctxt) +{ + for_each(range.start, range.end, bind(&DeliveryRecord::dequeue, _1, ctxt)); +} + +void TxAccept::RangeOp::commit() +{ + for_each(range.start, range.end, bind(&DeliveryRecord::committed, _1)); + for_each(range.start, range.end, bind(&DeliveryRecord::setEnded, _1)); +} + +TxAccept::RangeOps::RangeOps(DeliveryRecords& u) : unacked(u) {} + +void TxAccept::RangeOps::operator()(SequenceNumber start, SequenceNumber end) +{ + ranges.push_back(RangeOp(DeliveryRecord::findRange(unacked, start, end))); +} + +void TxAccept::RangeOps::prepare(TransactionContext* ctxt) +{ + std::for_each(ranges.begin(), ranges.end(), bind(&RangeOp::prepare, _1, ctxt)); +} + +void TxAccept::RangeOps::commit() +{ + std::for_each(ranges.begin(), ranges.end(), bind(&RangeOp::commit, _1)); + //now remove if isRedundant(): + if (!ranges.empty()) { + DeliveryRecords::iterator begin = ranges.front().range.start; + DeliveryRecords::iterator end = ranges.back().range.end; + DeliveryRecords::iterator removed = remove_if(begin, end, mem_fun_ref(&DeliveryRecord::isRedundant)); + unacked.erase(removed, end); + } +} + +TxAccept::TxAccept(const SequenceSet& _acked, DeliveryRecords& _unacked) : + acked(_acked), unacked(_unacked), ops(unacked) +{ + //populate the ops + acked.for_each(ops); +} + +bool TxAccept::prepare(TransactionContext* ctxt) throw() +{ + try{ + ops.prepare(ctxt); + return true; + }catch(const std::exception& e){ + QPID_LOG(error, "Failed to prepare: " << e.what()); + return false; + }catch(...){ + QPID_LOG(error, "Failed to prepare"); + return false; + } +} + +void TxAccept::commit() throw() +{ + try { + ops.commit(); + } catch (const std::exception& e) { + QPID_LOG(error, "Failed to commit: " << e.what()); + } catch(...) { + QPID_LOG(error, "Failed to commit (unknown error)"); + } +} + +void TxAccept::rollback() throw() {} diff --git a/qpid/cpp/src/qpid/broker/TxAccept.h b/qpid/cpp/src/qpid/broker/TxAccept.h new file mode 100644 index 0000000000..314a150176 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/TxAccept.h @@ -0,0 +1,83 @@ +/* + * + * 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 _TxAccept_ +#define _TxAccept_ + +#include <algorithm> +#include <functional> +#include <list> +#include "qpid/framing/SequenceSet.h" +#include "qpid/broker/DeliveryRecord.h" +#include "qpid/broker/TxOp.h" + +namespace qpid { + namespace broker { + /** + * Defines the transactional behaviour for accepts received by + * a transactional channel. + */ + class TxAccept : public TxOp { + struct RangeOp + { + AckRange range; + + RangeOp(const AckRange& r); + void prepare(TransactionContext* ctxt); + void commit(); + }; + + struct RangeOps + { + std::vector<RangeOp> ranges; + DeliveryRecords& unacked; + + RangeOps(DeliveryRecords& u); + + void operator()(framing::SequenceNumber start, framing::SequenceNumber end); + void prepare(TransactionContext* ctxt); + void commit(); + }; + + framing::SequenceSet acked; + DeliveryRecords& unacked; + RangeOps ops; + + public: + /** + * @param acked a representation of the accumulation of + * acks received + * @param unacked the record of delivered messages + */ + TxAccept(const framing::SequenceSet& acked, DeliveryRecords& unacked); + virtual bool prepare(TransactionContext* ctxt) throw(); + virtual void commit() throw(); + virtual void rollback() throw(); + virtual ~TxAccept(){} + virtual void accept(TxOpConstVisitor& visitor) const { visitor(*this); } + + // Used by cluster replication. + const framing::SequenceSet& getAcked() const { return acked; } + }; + } +} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/TxBuffer.cpp b/qpid/cpp/src/qpid/broker/TxBuffer.cpp new file mode 100644 index 0000000000..b509778e89 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/TxBuffer.cpp @@ -0,0 +1,80 @@ +/* + * + * 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/TxBuffer.h" +#include "qpid/log/Statement.h" + +#include <boost/mem_fn.hpp> +#include <boost/bind.hpp> +using boost::mem_fn; +using namespace qpid::broker; + +bool TxBuffer::prepare(TransactionContext* const ctxt) +{ + for(op_iterator i = ops.begin(); i < ops.end(); i++){ + if(!(*i)->prepare(ctxt)){ + return false; + } + } + return true; +} + +void TxBuffer::commit() +{ + std::for_each(ops.begin(), ops.end(), mem_fn(&TxOp::commit)); + ops.clear(); +} + +void TxBuffer::rollback() +{ + std::for_each(ops.begin(), ops.end(), mem_fn(&TxOp::rollback)); + ops.clear(); +} + +void TxBuffer::enlist(TxOp::shared_ptr op) +{ + ops.push_back(op); +} + +bool TxBuffer::commitLocal(TransactionalStore* const store) +{ + if (!store) return false; + try { + std::auto_ptr<TransactionContext> ctxt = store->begin(); + if (prepare(ctxt.get())) { + store->commit(*ctxt); + commit(); + return true; + } else { + store->abort(*ctxt); + rollback(); + return false; + } + } catch (std::exception& e) { + QPID_LOG(error, "Commit failed with exception: " << e.what()); + } catch (...) { + QPID_LOG(error, "Commit failed with unknown exception"); + } + return false; +} + +void TxBuffer::accept(TxOpConstVisitor& v) const { + std::for_each(ops.begin(), ops.end(), boost::bind(&TxOp::accept, _1, boost::ref(v))); +} diff --git a/qpid/cpp/src/qpid/broker/TxBuffer.h b/qpid/cpp/src/qpid/broker/TxBuffer.h new file mode 100644 index 0000000000..d49c8ba16a --- /dev/null +++ b/qpid/cpp/src/qpid/broker/TxBuffer.h @@ -0,0 +1,119 @@ +/* + * + * 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 _TxBuffer_ +#define _TxBuffer_ + +#include <algorithm> +#include <functional> +#include <vector> +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/TransactionalStore.h" +#include "qpid/broker/TxOp.h" + +/** + * Represents a single transaction. As such, an instance of this class + * will hold a list of operations representing the workload of the + * transaction. This work can be committed or rolled back. Committing + * is a two-stage process: first all the operations should be + * prepared, then if that succeeds they can be committed. + * + * In the 2pc case, a successful prepare may be followed by either a + * commit or a rollback. + * + * Atomicity of prepare is ensured by using a lower level + * transactional facility. This saves explicitly rolling back all the + * successfully prepared ops when one of them fails. i.e. we do not + * use 2pc internally, we instead ensure that prepare is atomic at a + * lower level. This makes individual prepare operations easier to + * code. + * + * Transactions on a messaging broker effect three types of 'action': + * (1) updates to persistent storage (2) updates to transient storage + * or cached data (3) network writes. + * + * Of these, (1) should always occur atomically during prepare to + * ensure that if the broker crashes while a transaction is being + * completed the persistent state (which is all that then remains) is + * consistent. (3) can only be done on commit, after a successful + * prepare. There is a little more flexibility with (2) but any + * changes made during prepare should be subject to the control of the + * TransactionalStore in use. + */ +namespace qpid { + namespace broker { + class TxBuffer{ + typedef std::vector<TxOp::shared_ptr>::iterator op_iterator; + std::vector<TxOp::shared_ptr> ops; + protected: + + public: + typedef boost::shared_ptr<TxBuffer> shared_ptr; + /** + * Adds an operation to the transaction. + */ + QPID_BROKER_EXTERN void enlist(TxOp::shared_ptr op); + + /** + * Requests that all ops are prepared. This should + * primarily involve making sure that a persistent record + * of the operations is stored where necessary. + * + * Once prepared, a transaction can be committed (or in + * the 2pc case, rolled back). + * + * @returns true if all the operations prepared + * successfully, false if not. + */ + QPID_BROKER_EXTERN bool prepare(TransactionContext* const ctxt); + + /** + * Signals that the ops all prepared successfully and can + * now commit, i.e. the operation can now be fully carried + * out. + * + * Should only be called after a call to prepare() returns + * true. + */ + QPID_BROKER_EXTERN void commit(); + + /** + * Signals that all ops can be rolled back. + * + * Should only be called either after a call to prepare() + * returns true (2pc) or instead of a prepare call + * ('server-local') + */ + QPID_BROKER_EXTERN void rollback(); + + /** + * Helper method for managing the process of server local + * commit + */ + QPID_BROKER_EXTERN bool commitLocal(TransactionalStore* const store); + + // Used by cluster to replicate transaction status. + void accept(TxOpConstVisitor& v) const; + }; + } +} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/TxOp.h b/qpid/cpp/src/qpid/broker/TxOp.h new file mode 100644 index 0000000000..a8fa1c2621 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/TxOp.h @@ -0,0 +1,46 @@ +/* + * + * 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 _TxOp_ +#define _TxOp_ + +#include "qpid/broker/TxOpVisitor.h" +#include "qpid/broker/TransactionalStore.h" +#include <boost/shared_ptr.hpp> + +namespace qpid { + namespace broker { + + class TxOp{ + public: + typedef boost::shared_ptr<TxOp> shared_ptr; + + virtual bool prepare(TransactionContext*) throw() = 0; + virtual void commit() throw() = 0; + virtual void rollback() throw() = 0; + virtual ~TxOp(){} + + virtual void accept(TxOpConstVisitor&) const = 0; + }; + +}} // namespace qpid::broker + + +#endif diff --git a/qpid/cpp/src/qpid/broker/TxOpVisitor.h b/qpid/cpp/src/qpid/broker/TxOpVisitor.h new file mode 100644 index 0000000000..ceb894896e --- /dev/null +++ b/qpid/cpp/src/qpid/broker/TxOpVisitor.h @@ -0,0 +1,97 @@ +#ifndef QPID_BROKER_TXOPVISITOR_H +#define QPID_BROKER_TXOPVISITOR_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. + * + */ + +namespace qpid { +namespace broker { + +class DtxAck; +class RecoveredDequeue; +class RecoveredEnqueue; +class TxAccept; +class TxPublish; + +/** + * Visitor for TxOp familly of classes. + */ +struct TxOpConstVisitor +{ + virtual ~TxOpConstVisitor() {} + virtual void operator()(const DtxAck&) = 0; + virtual void operator()(const RecoveredDequeue&) = 0; + virtual void operator()(const RecoveredEnqueue&) = 0; + virtual void operator()(const TxAccept&) = 0; + virtual void operator()(const TxPublish&) = 0; +}; + +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_TXOPVISITOR_H*/ +#ifndef QPID_BROKER_TXOPVISITOR_H +#define QPID_BROKER_TXOPVISITOR_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. + * + */ +namespace qpid { +namespace broker { + +class DtxAck; +class RecoveredDequeue; +class RecoveredEnqueue; +class TxAccept; +class TxPublish; + +/** + * Visitor for TxOp familly of classes. + */ +struct TxOpConstVisitor +{ + virtual ~TxOpConstVisitor() {} + virtual void operator()(const DtxAck&) = 0; + virtual void operator()(const RecoveredDequeue&) = 0; + virtual void operator()(const RecoveredEnqueue&) = 0; + virtual void operator()(const TxAccept&) = 0; + virtual void operator()(const TxPublish&) = 0; +}; + +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_TXOPVISITOR_H*/ diff --git a/qpid/cpp/src/qpid/broker/TxPublish.cpp b/qpid/cpp/src/qpid/broker/TxPublish.cpp new file mode 100644 index 0000000000..9c2cf4a467 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/TxPublish.cpp @@ -0,0 +1,111 @@ +/* + * + * 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/log/Statement.h" +#include "qpid/broker/TxPublish.h" +#include "qpid/broker/Queue.h" + +using boost::intrusive_ptr; +using namespace qpid::broker; + +TxPublish::TxPublish(intrusive_ptr<Message> _msg) : msg(_msg) {} + +bool TxPublish::prepare(TransactionContext* ctxt) throw() +{ + try{ + while (!queues.empty()) { + prepare(ctxt, queues.front()); + prepared.push_back(queues.front()); + queues.pop_front(); + } + return true; + }catch(const std::exception& e){ + QPID_LOG(error, "Failed to prepare: " << e.what()); + }catch(...){ + QPID_LOG(error, "Failed to prepare (unknown error)"); + } + return false; +} + +void TxPublish::commit() throw() +{ + try { + for_each(prepared.begin(), prepared.end(), Commit(msg)); + if (msg->isContentReleaseRequested()) { + // NOTE: The log messages in this section are used for flow-to-disk testing (which checks the log for the + // presence of these messages). Do not change these without also checking these tests. + if (msg->isContentReleaseBlocked()) { + QPID_LOG(debug, "Message id=\"" << msg->getProperties<qpid::framing::MessageProperties>()->getMessageId() << "\"; pid=0x" << + std::hex << msg->getPersistenceId() << std::dec << ": Content release blocked on commit"); + } else { + msg->releaseContent(); + QPID_LOG(debug, "Message id=\"" << msg->getProperties<qpid::framing::MessageProperties>()->getMessageId() << "\"; pid=0x" << + std::hex << msg->getPersistenceId() << std::dec << ": Content released on commit"); + } + } + } catch (const std::exception& e) { + QPID_LOG(error, "Failed to commit: " << e.what()); + } catch(...) { + QPID_LOG(error, "Failed to commit (unknown error)"); + } +} + +void TxPublish::rollback() throw() +{ + try { + for_each(prepared.begin(), prepared.end(), Rollback(msg)); + } catch (const std::exception& e) { + QPID_LOG(error, "Failed to complete rollback: " << e.what()); + } catch(...) { + QPID_LOG(error, "Failed to complete rollback (unknown error)"); + } + +} + +void TxPublish::deliverTo(const boost::shared_ptr<Queue>& queue){ + if (!queue->isLocal(msg)) { + queues.push_back(queue); + delivered = true; + } else { + QPID_LOG(debug, "Won't enqueue local message for " << queue->getName()); + } +} + +void TxPublish::prepare(TransactionContext* ctxt, const boost::shared_ptr<Queue> queue) +{ + queue->enqueue(ctxt, msg); +} + +TxPublish::Commit::Commit(intrusive_ptr<Message>& _msg) : msg(_msg){} + +void TxPublish::Commit::operator()(const boost::shared_ptr<Queue>& queue){ + queue->process(msg); +} + +TxPublish::Rollback::Rollback(intrusive_ptr<Message>& _msg) : msg(_msg){} + +void TxPublish::Rollback::operator()(const boost::shared_ptr<Queue>& queue){ + queue->enqueueAborted(msg); +} + +uint64_t TxPublish::contentSize () +{ + return msg->contentSize (); +} diff --git a/qpid/cpp/src/qpid/broker/TxPublish.h b/qpid/cpp/src/qpid/broker/TxPublish.h new file mode 100644 index 0000000000..f0b9c0a302 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/TxPublish.h @@ -0,0 +1,91 @@ +/* + * + * 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 _TxPublish_ +#define _TxPublish_ + +#include "qpid/broker/BrokerImportExport.h" +#include "qpid/broker/Deliverable.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/MessageStore.h" +#include "qpid/broker/TxOp.h" + +#include <algorithm> +#include <functional> +#include <list> + +#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 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); + }; + + 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>); + + 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 ~TxPublish(){} + virtual void accept(TxOpConstVisitor& visitor) const { visitor(*this); } + + 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; } + }; + } +} + + +#endif diff --git a/qpid/cpp/src/qpid/broker/Vhost.cpp b/qpid/cpp/src/qpid/broker/Vhost.cpp new file mode 100644 index 0000000000..a9ca3b42ab --- /dev/null +++ b/qpid/cpp/src/qpid/broker/Vhost.cpp @@ -0,0 +1,49 @@ +// +// 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/Vhost.h" +#include "qpid/broker/Broker.h" +#include "qpid/management/ManagementAgent.h" + +using namespace qpid::broker; +using qpid::management::ManagementAgent; +namespace _qmf = qmf::org::apache::qpid::broker; + +namespace qpid { namespace management { +class Manageable; +}} + +Vhost::Vhost (qpid::management::Manageable* parentBroker, Broker* broker) : mgmtObject(0) +{ + if (parentBroker != 0 && broker != 0) + { + ManagementAgent* agent = broker->getManagementAgent(); + + if (agent != 0) + { + mgmtObject = new _qmf::Vhost(agent, this, parentBroker, "/"); + agent->addObject(mgmtObject, 0, true); + } + } +} + +void Vhost::setFederationTag(const std::string& tag) +{ + mgmtObject->set_federationTag(tag); +} diff --git a/qpid/cpp/src/qpid/broker/Vhost.h b/qpid/cpp/src/qpid/broker/Vhost.h new file mode 100644 index 0000000000..9554d641c2 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/Vhost.h @@ -0,0 +1,50 @@ +#ifndef _Vhost_ +#define _Vhost_ + +// +// 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/management/Manageable.h" +#include "qmf/org/apache/qpid/broker/Vhost.h" +#include <boost/shared_ptr.hpp> + +namespace qpid { +namespace broker { + +class Broker; +class Vhost : public management::Manageable +{ + private: + + qmf::org::apache::qpid::broker::Vhost* mgmtObject; + + public: + + typedef boost::shared_ptr<Vhost> shared_ptr; + + Vhost (management::Manageable* parentBroker, Broker* broker = 0); + + management::ManagementObject* GetManagementObject (void) const + { return mgmtObject; } + void setFederationTag(const std::string& tag); +}; + +}} + +#endif /*!_Vhost_*/ diff --git a/qpid/cpp/src/qpid/broker/posix/BrokerDefaults.cpp b/qpid/cpp/src/qpid/broker/posix/BrokerDefaults.cpp new file mode 100644 index 0000000000..9e463fa32d --- /dev/null +++ b/qpid/cpp/src/qpid/broker/posix/BrokerDefaults.cpp @@ -0,0 +1,40 @@ +/* + * + * 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/Broker.h" +#include <stdlib.h> + +namespace qpid { +namespace broker { + +const std::string Broker::Options::DEFAULT_DATA_DIR_LOCATION("/tmp"); +const std::string Broker::Options::DEFAULT_DATA_DIR_NAME("/.qpidd"); + +std::string +Broker::Options::getHome() { + std::string home; + char *home_c = ::getenv("HOME"); + if (home_c != 0) + home += home_c; + return home; +} + +}} // namespace qpid::broker diff --git a/qpid/cpp/src/qpid/broker/windows/BrokerDefaults.cpp b/qpid/cpp/src/qpid/broker/windows/BrokerDefaults.cpp new file mode 100644 index 0000000000..b65440b5ad --- /dev/null +++ b/qpid/cpp/src/qpid/broker/windows/BrokerDefaults.cpp @@ -0,0 +1,47 @@ +/* + * + * 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/Broker.h" +#include <stdlib.h> + +namespace qpid { +namespace broker { + +const std::string Broker::Options::DEFAULT_DATA_DIR_LOCATION("\\TEMP"); +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; +} + +}} // namespace qpid::broker diff --git a/qpid/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp b/qpid/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp new file mode 100644 index 0000000000..962877a471 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp @@ -0,0 +1,193 @@ +/* + * + * 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. + * + */ + +// This source is only used on Windows; SSPI is the Windows mechanism for +// accessing authentication mechanisms, analogous to Cyrus SASL. + +#include "qpid/broker/Connection.h" +#include "qpid/log/Statement.h" +#include "qpid/framing/reply_exceptions.h" + +#include <windows.h> + +using namespace qpid::framing; +using qpid::sys::SecurityLayer; + +namespace qpid { +namespace broker { + +class NullAuthenticator : public SaslAuthenticator +{ + Connection& connection; + framing::AMQP_ClientProxy::Connection client; +public: + NullAuthenticator(Connection& connection); + ~NullAuthenticator(); + void getMechanisms(framing::Array& mechanisms); + void start(const std::string& mechanism, const std::string& response); + void step(const std::string&) {} + std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize); +}; + +class SspiAuthenticator : public SaslAuthenticator +{ + HANDLE userToken; + Connection& connection; + framing::AMQP_ClientProxy::Connection client; + +public: + SspiAuthenticator(Connection& connection); + ~SspiAuthenticator(); + void getMechanisms(framing::Array& mechanisms); + void start(const std::string& mechanism, const std::string& response); + void step(const std::string& response); + std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize); +}; + +bool SaslAuthenticator::available(void) +{ + return true; +} + +// Initialize the SASL mechanism; throw if it fails. +void SaslAuthenticator::init(const std::string& /*saslName*/, const std::string& /*saslConfig*/) +{ + return; +} + +void SaslAuthenticator::fini(void) +{ + return; +} + +std::auto_ptr<SaslAuthenticator> SaslAuthenticator::createAuthenticator(Connection& c, bool) +{ + if (c.getBroker().getOptions().auth) { + return std::auto_ptr<SaslAuthenticator>(new SspiAuthenticator(c)); + } else { + return std::auto_ptr<SaslAuthenticator>(new NullAuthenticator(c)); + } +} + +NullAuthenticator::NullAuthenticator(Connection& c) : connection(c), client(c.getOutput()) {} +NullAuthenticator::~NullAuthenticator() {} + +void NullAuthenticator::getMechanisms(Array& mechanisms) +{ + mechanisms.add(boost::shared_ptr<FieldValue>(new Str16Value("ANONYMOUS"))); +} + +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); + string::size_type i = temp.find((char)0); + string uid = temp.substr(0, i); + string pwd = temp.substr(i + 1); + connection.setUserId(uid); + } + } else { + connection.setUserId("anonymous"); + } + client.tune(framing::CHANNEL_MAX, connection.getFrameMax(), 0, 0); +} + +std::auto_ptr<SecurityLayer> NullAuthenticator::getSecurityLayer(uint16_t) +{ + std::auto_ptr<SecurityLayer> securityLayer; + return securityLayer; +} + + +SspiAuthenticator::SspiAuthenticator(Connection& c) : userToken(INVALID_HANDLE_VALUE), connection(c), client(c.getOutput()) +{ +} + +SspiAuthenticator::~SspiAuthenticator() +{ + if (INVALID_HANDLE_VALUE != userToken) { + CloseHandle(userToken); + userToken = INVALID_HANDLE_VALUE; + } +} + +void SspiAuthenticator::getMechanisms(Array& mechanisms) +{ + mechanisms.add(boost::shared_ptr<FieldValue>(new Str16Value(string("ANONYMOUS")))); + mechanisms.add(boost::shared_ptr<FieldValue>(new Str16Value(string("PLAIN")))); + QPID_LOG(info, "SASL: Mechanism list: ANONYMOUS PLAIN"); +} + +void SspiAuthenticator::start(const string& mechanism, const string& response) +{ + QPID_LOG(info, "SASL: Starting authentication with mechanism: " << mechanism); + if (mechanism == "ANONYMOUS") { + connection.setUserId("anonymous"); + client.tune(framing::CHANNEL_MAX, connection.getFrameMax(), 0, 0); + return; + } + if (mechanism != "PLAIN") + throw ConnectionForcedException("Unsupported mechanism"); + + // PLAIN's response is composed of 3 strings separated by 0 bytes: + // authorization id, authentication id (user), clear-text password. + if (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 dot("."); + int error = 0; + 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)) + error = GetLastError(); + pwd.replace(0, string::npos, 1, (char)0); + if (error != 0) { + QPID_LOG(info, + "SASL: Auth failed [" << error << "]: " << qpid::sys::strError(error)); + throw ConnectionForcedException("Authentication failed"); + } + + connection.setUserId(uid); + client.tune(framing::CHANNEL_MAX, connection.getFrameMax(), 0, 0); +} + +void SspiAuthenticator::step(const string& /*response*/) +{ + QPID_LOG(info, "SASL: Need another step!!!"); +} + +std::auto_ptr<SecurityLayer> SspiAuthenticator::getSecurityLayer(uint16_t) +{ + std::auto_ptr<SecurityLayer> securityLayer; + return securityLayer; +} + +}} diff --git a/qpid/cpp/src/qpid/broker/windows/SslProtocolFactory.cpp b/qpid/cpp/src/qpid/broker/windows/SslProtocolFactory.cpp new file mode 100644 index 0000000000..676074a590 --- /dev/null +++ b/qpid/cpp/src/qpid/broker/windows/SslProtocolFactory.cpp @@ -0,0 +1,297 @@ +/* + * + * 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/ProtocolFactory.h" + +#include "qpid/Plugin.h" +#include "qpid/broker/Broker.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/AsynchIOHandler.h" +#include "qpid/sys/ConnectionCodec.h" +#include "qpid/sys/Socket.h" +#include "qpid/sys/SystemInfo.h" +#include "qpid/sys/windows/SslAsynchIO.h" +#include <boost/bind.hpp> +#include <memory> +// security.h needs to see this to distinguish from kernel use. +#define SECURITY_WIN32 +#include <security.h> +#include <Schnlsp.h> +#undef SECURITY_WIN32 + + +namespace qpid { +namespace sys { +namespace windows { + +struct SslServerOptions : qpid::Options +{ + std::string certStore; + std::string certName; + uint16_t port; + bool clientAuth; + + SslServerOptions() : qpid::Options("SSL Options"), + certStore("My"), port(5671), clientAuth(false) + { + qpid::Address me; + if (qpid::sys::SystemInfo::getLocalHostname(me)) + certName = me.host; + else + certName = "localhost"; + + addOptions() + ("ssl-cert-store", optValue(certStore, "NAME"), "Local store name from which to obtain certificate") + ("ssl-cert-name", optValue(certName, "NAME"), "Name of the certificate to use") + ("ssl-port", optValue(port, "PORT"), "Port on which to listen for SSL connections") + ("ssl-require-client-authentication", optValue(clientAuth), + "Forces clients to authenticate in order to establish an SSL connection"); + } +}; + +class SslProtocolFactory : public qpid::sys::ProtocolFactory { + qpid::sys::Socket listener; + const bool tcpNoDelay; + const uint16_t listeningPort; + std::string brokerHost; + const bool clientAuthSelected; + std::auto_ptr<qpid::sys::AsynchAcceptor> acceptor; + ConnectFailedCallback connectFailedCallback; + CredHandle credHandle; + + public: + SslProtocolFactory(const SslServerOptions&, int backlog, bool nodelay); + ~SslProtocolFactory(); + void accept(sys::Poller::shared_ptr, sys::ConnectionCodec::Factory*); + void connect(sys::Poller::shared_ptr, const std::string& host, const std::string& port, + sys::ConnectionCodec::Factory*, + ConnectFailedCallback failed); + + uint16_t getPort() const; + bool supports(const std::string& capability); + + private: + void connectFailed(const qpid::sys::Socket&, + int err, + const std::string& msg); + void established(sys::Poller::shared_ptr, + const qpid::sys::Socket&, + sys::ConnectionCodec::Factory*, + bool isClient); +}; + +// Static instance to initialise plugin +static struct SslPlugin : public Plugin { + SslServerOptions options; + + Options* getOptions() { return &options; } + + void earlyInitialize(Target&) { + } + + void initialize(Target& target) { + broker::Broker* broker = dynamic_cast<broker::Broker*>(&target); + // Only provide to a Broker + if (broker) { + try { + 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()); + broker->registerProtocolFactory("ssl", protocol); + } catch (const std::exception& e) { + QPID_LOG(error, "Failed to initialise SSL listener: " << e.what()); + } + } + } +} sslPlugin; + +SslProtocolFactory::SslProtocolFactory(const SslServerOptions& options, + int backlog, + bool nodelay) + : tcpNoDelay(nodelay), + listeningPort(listener.listen("", boost::lexical_cast<std::string>(options.port), backlog)), + clientAuthSelected(options.clientAuth) { + + SecInvalidateHandle(&credHandle); + + // Get the certificate for this server. + HCERTSTORE certStoreHandle; + certStoreHandle = ::CertOpenStore(CERT_STORE_PROV_SYSTEM_A, + X509_ASN_ENCODING, + 0, + CERT_SYSTEM_STORE_LOCAL_MACHINE, + options.certStore.c_str()); + if (!certStoreHandle) + throw qpid::Exception(QPID_MSG("Opening store " << options.certStore << " " << qpid::sys::strError(GetLastError()))); + + PCCERT_CONTEXT certContext; + certContext = ::CertFindCertificateInStore(certStoreHandle, + X509_ASN_ENCODING, + 0, + CERT_FIND_SUBJECT_STR_A, + options.certName.c_str(), + NULL); + if (certContext == NULL) { + int err = ::GetLastError(); + ::CertCloseStore(certStoreHandle, 0); + throw qpid::Exception(QPID_MSG("Locating certificate " << options.certName << " in store " << options.certStore << " " << qpid::sys::strError(GetLastError()))); + throw QPID_WINDOWS_ERROR(err); + } + + SCHANNEL_CRED cred; + memset(&cred, 0, sizeof(cred)); + cred.dwVersion = SCHANNEL_CRED_VERSION; + cred.cCreds = 1; + cred.paCred = &certContext; + SECURITY_STATUS status = ::AcquireCredentialsHandle(NULL, + UNISP_NAME, + SECPKG_CRED_INBOUND, + NULL, + &cred, + NULL, + NULL, + &credHandle, + NULL); + if (status != SEC_E_OK) + throw QPID_WINDOWS_ERROR(status); + ::CertFreeCertificateContext(certContext); + ::CertCloseStore(certStoreHandle, 0); +} + +SslProtocolFactory::~SslProtocolFactory() { + ::FreeCredentialsHandle(&credHandle); +} + +void SslProtocolFactory::connectFailed(const qpid::sys::Socket&, + int err, + const std::string& msg) { + if (connectFailedCallback) + connectFailedCallback(err, msg); +} + +void SslProtocolFactory::established(sys::Poller::shared_ptr poller, + const qpid::sys::Socket& s, + sys::ConnectionCodec::Factory* f, + bool isClient) { + sys::AsynchIOHandler* async = new sys::AsynchIOHandler(s.getFullAddress(), f); + + if (tcpNoDelay) { + s.setTcpNoDelay(); + QPID_LOG(info, + "Set TCP_NODELAY on connection to " << s.getPeerAddress()); + } + + SslAsynchIO *aio; + if (isClient) { + async->setClient(); + aio = + new qpid::sys::windows::ClientSslAsynchIO(brokerHost, + s, + credHandle, + 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)); + } + else { + aio = + new qpid::sys::windows::ServerSslAsynchIO(clientAuthSelected, + s, + credHandle, + 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); +} + +uint16_t SslProtocolFactory::getPort() const { + return listeningPort; // Immutable no need for lock. +} + +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); +} + +void SslProtocolFactory::connect(sys::Poller::shared_ptr poller, + const std::string& host, + const std::string& port, + sys::ConnectionCodec::Factory* fact, + ConnectFailedCallback failed) +{ + SCHANNEL_CRED cred; + memset(&cred, 0, sizeof(cred)); + cred.dwVersion = SCHANNEL_CRED_VERSION; + SECURITY_STATUS status = ::AcquireCredentialsHandle(NULL, + UNISP_NAME, + SECPKG_CRED_OUTBOUND, + NULL, + &cred, + NULL, + NULL, + &credHandle, + NULL); + if (status != SEC_E_OK) + throw QPID_WINDOWS_ERROR(status); + + brokerHost = host; + // Note that the following logic does not cause a memory leak. + // The allocated Socket is freed either by the AsynchConnector + // upon connection failure or by the AsynchIO upon connection + // shutdown. The allocated AsynchConnector frees itself when it + // is no longer needed. + qpid::sys::Socket* socket = new qpid::sys::Socket(); + connectFailedCallback = failed; + AsynchConnector::create(*socket, + host, + port, + boost::bind(&SslProtocolFactory::established, + this, poller, _1, fact, true), + boost::bind(&SslProtocolFactory::connectFailed, + this, _1, _2, _3)); +} + +namespace +{ +const std::string SSL = "ssl"; +} + +bool SslProtocolFactory::supports(const std::string& capability) +{ + std::string s = capability; + transform(s.begin(), s.end(), s.begin(), tolower); + return s == SSL; +} + +}}} // namespace qpid::sys::windows diff --git a/qpid/cpp/src/qpid/client/Bounds.cpp b/qpid/cpp/src/qpid/client/Bounds.cpp new file mode 100644 index 0000000000..cc2577d5fc --- /dev/null +++ b/qpid/cpp/src/qpid/client/Bounds.cpp @@ -0,0 +1,71 @@ +/* + * + * 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/Bounds.h" + +#include "qpid/log/Statement.h" +#include "qpid/sys/Waitable.h" + +namespace qpid { +namespace client { + +using sys::Waitable; + +Bounds::Bounds(size_t maxSize) : max(maxSize), current(0) {} + +bool Bounds::expand(size_t sizeRequired, bool block) { + if (!max) return true; + Waitable::ScopedLock l(lock); + if (block) { + Waitable::ScopedWait w(lock); + while (current + sizeRequired > max) + lock.wait(); + } + current += sizeRequired; + return current <= max; +} + +void Bounds::reduce(size_t size) { + if (!max || size == 0) return; + Waitable::ScopedLock l(lock); + assert(current >= size); + current -= std::min(size, current); + if (current < max && lock.hasWaiters()) { + lock.notifyAll(); + } +} + +size_t Bounds::getCurrentSize() { + Waitable::ScopedLock l(lock); + return current; +} + +std::ostream& operator<<(std::ostream& out, const Bounds& bounds) { + out << "current=" << bounds.current << ", max=" << bounds.max << " [" << &bounds << "]"; + return out; +} + +void Bounds::setException(const sys::ExceptionHolder& e) { + Waitable::ScopedLock l(lock); + lock.setException(e); + lock.waitWaiters(); // Wait for waiting threads to exit. +} + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/Bounds.h b/qpid/cpp/src/qpid/client/Bounds.h new file mode 100644 index 0000000000..838fcb8368 --- /dev/null +++ b/qpid/cpp/src/qpid/client/Bounds.h @@ -0,0 +1,49 @@ +#ifndef QPID_CLIENT_BOUNDSCHECKING_H +#define QPID_CLIENT_BOUNDSCHECKING_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/Waitable.h" + +namespace qpid{ +namespace client{ + +class Bounds +{ + public: + Bounds(size_t maxSize); + bool expand(size_t, bool block); + void reduce(size_t); + size_t getCurrentSize(); + void setException(const sys::ExceptionHolder&); + + private: + friend std::ostream& operator<<(std::ostream&, const Bounds&); + sys::Waitable lock; + const size_t max; + size_t current; +}; + +std::ostream& operator<<(std::ostream&, const Bounds&); + + +}} + +#endif diff --git a/qpid/cpp/src/qpid/client/ChainableFrameHandler.h b/qpid/cpp/src/qpid/client/ChainableFrameHandler.h new file mode 100644 index 0000000000..29e16d53dc --- /dev/null +++ b/qpid/cpp/src/qpid/client/ChainableFrameHandler.h @@ -0,0 +1,47 @@ +/* + * + * 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 _ChainableFrameHandler_ +#define _ChainableFrameHandler_ + +#include <boost/function.hpp> +#include "qpid/framing/AMQFrame.h" + +namespace qpid { +namespace client { + +struct ChainableFrameHandler +{ + typedef boost::function<void(framing::AMQFrame&)> FrameDelegate; + + FrameDelegate in; + FrameDelegate out; + + ChainableFrameHandler() {} + ChainableFrameHandler(FrameDelegate i, FrameDelegate o): in(i), out(o) {} + virtual ~ChainableFrameHandler() {} +}; + +}} + + + +#endif diff --git a/qpid/cpp/src/qpid/client/Completion.cpp b/qpid/cpp/src/qpid/client/Completion.cpp new file mode 100644 index 0000000000..a97c8c3534 --- /dev/null +++ b/qpid/cpp/src/qpid/client/Completion.cpp @@ -0,0 +1,40 @@ +/* + * + * 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/Completion.h" +#include "qpid/client/CompletionImpl.h" +#include "qpid/client/PrivateImplRef.h" + +namespace qpid { +namespace client { + +typedef PrivateImplRef<Completion> PI; +Completion::Completion(CompletionImpl* p) { PI::ctor(*this, p); } +Completion::Completion(const Completion& c) : Handle<CompletionImpl>() { PI::copy(*this, c); } +Completion::~Completion() { PI::dtor(*this); } +Completion& Completion::operator=(const Completion& c) { return PI::assign(*this, c); } + + +void Completion::wait() { impl->wait(); } +bool Completion::isComplete() { return impl->isComplete(); } +std::string Completion::getResult() { return impl->getResult(); } + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/CompletionImpl.h b/qpid/cpp/src/qpid/client/CompletionImpl.h new file mode 100644 index 0000000000..f180708316 --- /dev/null +++ b/qpid/cpp/src/qpid/client/CompletionImpl.h @@ -0,0 +1,51 @@ +#ifndef QPID_CLIENT_COMPLETIONIMPL_H +#define QPID_CLIENT_COMPLETIONIMPL_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 "qpid/client/Future.h" +#include <boost/shared_ptr.hpp> + +namespace qpid { +namespace client { + +///@internal +class CompletionImpl : public RefCounted +{ +public: + CompletionImpl() {} + CompletionImpl(Future f, boost::shared_ptr<SessionImpl> s) : future(f), session(s) {} + + bool isComplete() { return future.isComplete(*session); } + void wait() { future.wait(*session); } + std::string getResult() { return future.getResult(*session); } + +protected: + Future future; + boost::shared_ptr<SessionImpl> session; +}; + +}} // namespace qpid::client + + +#endif /*!QPID_CLIENT_COMPLETIONIMPL_H*/ diff --git a/qpid/cpp/src/qpid/client/Connection.cpp b/qpid/cpp/src/qpid/client/Connection.cpp new file mode 100644 index 0000000000..2882ef5d42 --- /dev/null +++ b/qpid/cpp/src/qpid/client/Connection.cpp @@ -0,0 +1,161 @@ +/* + * + * 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/ConnectionImpl.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/client/Message.h" +#include "qpid/client/SessionImpl.h" +#include "qpid/client/SessionBase_0_10Access.h" +#include "qpid/Url.h" +#include "qpid/log/Logger.h" +#include "qpid/log/Options.h" +#include "qpid/log/Statement.h" +#include "qpid/framing/AMQP_HighestVersion.h" + +#include <algorithm> +#include <iostream> +#include <sstream> +#include <functional> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <boost/shared_ptr.hpp> + +using namespace qpid::framing; +using namespace qpid::sys; + + +namespace qpid { +namespace client { + +Connection::Connection() : version(framing::highestProtocolVersion) +{ + ConnectionImpl::init(); +} + +Connection::~Connection() {} + +void Connection::open( + const Url& url, + const std::string& uid, const std::string& pwd, + const std::string& vhost, + uint16_t maxFrameSize) +{ + ConnectionSettings settings; + settings.username = uid; + settings.password = pwd; + settings.virtualhost = vhost; + settings.maxFrameSize = maxFrameSize; + open(url, settings); +} + +void Connection::open(const Url& url, const ConnectionSettings& settings) { + if (url.empty()) + throw Exception(QPID_MSG("Attempt to open URL with no addresses.")); + Url::const_iterator i = url.begin(); + do { + const Address& addr = *i; + i++; + try { + ConnectionSettings cs(settings); + cs.protocol = addr.protocol; + cs.host = addr.host; + cs.port = addr.port; + open(cs); + break; + } + catch (const Exception& /*e*/) { + if (i == url.end()) throw; + } + } while (i != url.end()); +} + +void Connection::open( + const std::string& host, int port, + const std::string& uid, const std::string& pwd, + const std::string& vhost, + uint16_t maxFrameSize) +{ + ConnectionSettings settings; + settings.host = host; + settings.port = port; + settings.username = uid; + settings.password = pwd; + settings.virtualhost = vhost; + settings.maxFrameSize = maxFrameSize; + open(settings); +} + +bool Connection::isOpen() const { + return impl && impl->isOpen(); +} + +void +Connection::registerFailureCallback ( boost::function<void ()> fn ) { + failureCallback = fn; + if ( impl ) + impl->registerFailureCallback ( fn ); +} + + + +void Connection::open(const ConnectionSettings& settings) +{ + if (isOpen()) + throw Exception(QPID_MSG("Connection::open() was already called")); + + impl = ConnectionImpl::create(version, settings); + impl->open(); + if ( failureCallback ) + impl->registerFailureCallback ( failureCallback ); +} + +const ConnectionSettings& Connection::getNegotiatedSettings() +{ + if (!isOpen()) + throw Exception(QPID_MSG("Connection is not open.")); + return impl->getNegotiatedSettings(); +} + +Session Connection::newSession(const std::string& name, uint32_t timeout) { + if (!isOpen()) + throw Exception(QPID_MSG("Connection has not yet been opened")); + Session s; + SessionBase_0_10Access(s).set(impl->newSession(name, timeout)); + return s; +} + +void Connection::resume(Session& session) { + if (!isOpen()) + throw Exception(QPID_MSG("Connection is not open.")); + impl->addSession(session.impl); + session.impl->resume(impl); +} + +void Connection::close() { + if ( impl ) + impl->close(); +} + +std::vector<Url> Connection::getInitialBrokers() { + return impl ? impl->getInitialBrokers() : std::vector<Url>(); +} + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/ConnectionAccess.h b/qpid/cpp/src/qpid/client/ConnectionAccess.h new file mode 100644 index 0000000000..3a763f692f --- /dev/null +++ b/qpid/cpp/src/qpid/client/ConnectionAccess.h @@ -0,0 +1,42 @@ +#ifndef QPID_CLIENT_CONNECTIONACCESS_H +#define QPID_CLIENT_CONNECTIONACCESS_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/client/Connection.h" + +/**@file @internal Internal use only */ + +namespace qpid { +namespace client { + + + +struct ConnectionAccess { + static void setVersion(Connection& c, const framing::ProtocolVersion& v) { c.version = v; } + static boost::shared_ptr<ConnectionImpl> getImpl(Connection& c) { return c.impl; } + static void setImpl(Connection& c, boost::shared_ptr<ConnectionImpl> i) { c.impl = i; } +}; + +}} // namespace qpid::client + +#endif /*!QPID_CLIENT_CONNECTIONACCESS_H*/ diff --git a/qpid/cpp/src/qpid/client/ConnectionHandler.cpp b/qpid/cpp/src/qpid/client/ConnectionHandler.cpp new file mode 100644 index 0000000000..4fbf55aa60 --- /dev/null +++ b/qpid/cpp/src/qpid/client/ConnectionHandler.cpp @@ -0,0 +1,356 @@ +/* + * + * 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/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" +#include "qpid/framing/ClientInvoker.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/log/Helpers.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/SystemInfo.h" + +using namespace qpid::client; +using namespace qpid::framing; +using namespace qpid::framing::connection; +using qpid::sys::SecurityLayer; +using qpid::sys::Duration; +using qpid::sys::TimerTask; +using qpid::sys::Timer; +using qpid::sys::AbsTime; +using qpid::sys::TIME_SEC; +using qpid::sys::ScopedLock; +using qpid::sys::Mutex; + +namespace { +const std::string OK("OK"); +const std::string PLAIN("PLAIN"); +const std::string en_US("en_US"); + +const std::string INVALID_STATE_START("start received in invalid state"); +const std::string INVALID_STATE_TUNE("tune received in invalid state"); +const std::string INVALID_STATE_OPEN_OK("open-ok received in invalid state"); +const std::string INVALID_STATE_CLOSE_OK("close-ok received in invalid state"); + +const std::string SESSION_FLOW_CONTROL("qpid.session_flow"); +const std::string CLIENT_PROCESS_NAME("qpid.client_process"); +const std::string CLIENT_PID("qpid.client_pid"); +const std::string CLIENT_PPID("qpid.client_ppid"); +const int SESSION_FLOW_CONTROL_VER = 1; +} + +CloseCode ConnectionHandler::convert(uint16_t replyCode) +{ + switch (replyCode) { + case 200: return CLOSE_CODE_NORMAL; + case 320: return CLOSE_CODE_CONNECTION_FORCED; + case 402: return CLOSE_CODE_INVALID_PATH; + case 501: default: + return CLOSE_CODE_FRAMING_ERROR; + } +} + +ConnectionHandler::Adapter::Adapter(ConnectionHandler& h, Bounds& b) : handler(h), bounds(b) {} +void ConnectionHandler::Adapter::handle(qpid::framing::AMQFrame& f) +{ + bounds.expand(f.encodedSize(), false); + handler.out(f); +} + +ConnectionHandler::ConnectionHandler(const ConnectionSettings& s, ProtocolVersion& v, Bounds& b) + : StateManager(NOT_STARTED), ConnectionSettings(s), outHandler(*this, b), proxy(outHandler), + errorCode(CLOSE_CODE_NORMAL), version(v) +{ + insist = true; + + ESTABLISHED.insert(FAILED); + ESTABLISHED.insert(CLOSED); + ESTABLISHED.insert(OPEN); + + FINISHED.insert(FAILED); + FINISHED.insert(CLOSED); + + properties.setInt(SESSION_FLOW_CONTROL, SESSION_FLOW_CONTROL_VER); + properties.setString(CLIENT_PROCESS_NAME, sys::SystemInfo::getProcessName()); + properties.setInt(CLIENT_PID, sys::SystemInfo::getProcessId()); + properties.setInt(CLIENT_PPID, sys::SystemInfo::getParentProcessId()); +} + +void ConnectionHandler::incoming(AMQFrame& frame) +{ + if (getState() == CLOSED) { + throw Exception("Received frame on closed connection"); + } + + if (rcvTimeoutTask) { + // Received frame on connection so delay timeout + rcvTimeoutTask->restart(); + } + + AMQBody* body = frame.getBody(); + try { + if (frame.getChannel() != 0 || !invoke(static_cast<ConnectionOperations&>(*this), *body)) { + switch(getState()) { + case OPEN: + in(frame); + break; + case CLOSING: + QPID_LOG(warning, "Ignoring frame while closing connection: " << frame); + break; + default: + throw Exception("Cannot receive frames on non-zero channel until connection is established."); + } + } + }catch(std::exception& e){ + QPID_LOG(warning, "Closing connection due to " << e.what()); + setState(CLOSING); + errorCode = CLOSE_CODE_FRAMING_ERROR; + errorText = e.what(); + proxy.close(501, e.what()); + } +} + +void ConnectionHandler::outgoing(AMQFrame& frame) +{ + if (getState() == OPEN) + out(frame); + else + throw TransportFailure(errorText.empty() ? "Connection is not open." : errorText); +} + +void ConnectionHandler::waitForOpen() +{ + waitFor(ESTABLISHED); + if (getState() == FAILED || getState() == CLOSED) { + throw ConnectionException(errorCode, errorText); + } +} + +void ConnectionHandler::close() +{ + switch (getState()) { + case NEGOTIATING: + case OPENING: + fail("Connection closed before it was established"); + break; + case OPEN: + if (setState(CLOSING, OPEN)) { + proxy.close(200, OK); + if (ConnectionSettings::heartbeat) { + //heartbeat timer is turned off at this stage, so don't wait indefinately + if (!waitFor(FINISHED, qpid::sys::Duration(ConnectionSettings::heartbeat * qpid::sys::TIME_SEC))) { + QPID_LOG(warning, "Connection close timed out"); + } + } else { + waitFor(FINISHED);//FINISHED = CLOSED or FAILED + } + } + //else, state was changed from open after we checked, can only + //change to failed or closed, so nothing to do + break; + + // Nothing to do if already CLOSING, CLOSED, FAILED or if NOT_STARTED + } +} + +void ConnectionHandler::heartbeat() +{ + // Do nothing - the purpose of heartbeats is just to make sure that there is some + // traffic on the connection within the heart beat interval, we check for the + // traffic and don't need to do anything in response to heartbeats + + // Although the above is still true we're now using a received heartbeat as a trigger + // to send out our own heartbeat + proxy.heartbeat(); +} + +void ConnectionHandler::checkState(STATES s, const std::string& msg) +{ + if (getState() != s) { + throw CommandInvalidException(msg); + } +} + +void ConnectionHandler::fail(const std::string& message) +{ + errorCode = CLOSE_CODE_FRAMING_ERROR; + errorText = message; + QPID_LOG(warning, message); + setState(FAILED); +} + +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*/) +{ + checkState(NOT_STARTED, INVALID_STATE_START); + setState(NEGOTIATING); + sasl = SaslFactory::getInstance().create( username, + password, + service, + host, + minSsf, + maxSsf + ); + + 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 (sasl.get()) { + string response = sasl->start(join(mechlist), getSecuritySettings ? getSecuritySettings() : 0); + proxy.startOk(properties, sasl->getMechanism(), response, locale); + } else { + //TODO: verify that desired mechanism and locale are supported + string response = ((char)0) + username + ((char)0) + password; + proxy.startOk(properties, mechanism, response, locale); + } +} + +void ConnectionHandler::secure(const std::string& challenge) +{ + if (sasl.get()) { + string response = sasl->step(challenge); + proxy.secureOk(response); + } else { + throw NotImplementedException("Challenge-response cycle not yet implemented in client"); + } +} + +void ConnectionHandler::tune(uint16_t maxChannelsProposed, uint16_t maxFrameSizeProposed, + uint16_t heartbeatMin, uint16_t heartbeatMax) +{ + checkState(NEGOTIATING, INVALID_STATE_TUNE); + maxChannels = std::min(maxChannels, maxChannelsProposed); + maxFrameSize = std::min(maxFrameSize, maxFrameSizeProposed); + // Clip the requested heartbeat to the maximum/minimum offered + uint16_t heartbeat = ConnectionSettings::heartbeat; + heartbeat = heartbeat < heartbeatMin ? heartbeatMin : + heartbeat > heartbeatMax ? heartbeatMax : + heartbeat; + ConnectionSettings::heartbeat = heartbeat; + proxy.tuneOk(maxChannels, maxFrameSize, heartbeat); + setState(OPENING); + proxy.open(virtualhost, capabilities, insist); +} + +void ConnectionHandler::openOk ( const Array& knownBrokers ) +{ + checkState(OPENING, INVALID_STATE_OPEN_OK); + knownBrokersUrls.clear(); + framing::Array::ValueVector::const_iterator i; + for ( i = knownBrokers.begin(); i != knownBrokers.end(); ++i ) + knownBrokersUrls.push_back(Url((*i)->get<std::string>())); + if (sasl.get()) { + securityLayer = sasl->getSecurityLayer(maxFrameSize); + operUserId = sasl->getUserId(); + } + setState(OPEN); + QPID_LOG(debug, "Known-brokers for connection: " << log::formatList(knownBrokersUrls)); +} + + +void ConnectionHandler::redirect(const std::string& /*host*/, const Array& /*knownHosts*/) +{ + throw NotImplementedException("Redirection received from broker; not yet implemented in client"); +} + +void ConnectionHandler::close(uint16_t replyCode, const std::string& replyText) +{ + proxy.closeOk(); + errorCode = convert(replyCode); + errorText = replyText; + setState(CLOSED); + QPID_LOG(warning, "Broker closed connection: " << replyCode << ", " << replyText); + if (onError) { + onError(replyCode, replyText); + } +} + +void ConnectionHandler::closeOk() +{ + checkState(CLOSING, INVALID_STATE_CLOSE_OK); + if (onError && errorCode != CLOSE_CODE_NORMAL) { + onError(errorCode, errorText); + } else if (onClose) { + onClose(); + } + setState(CLOSED); +} + +bool ConnectionHandler::isOpen() const +{ + return getState() == OPEN; +} + +bool ConnectionHandler::isClosed() const +{ + int s = getState(); + return s == CLOSED || s == FAILED; +} + +bool ConnectionHandler::isClosing() const { return getState() == CLOSING; } + +std::auto_ptr<qpid::sys::SecurityLayer> ConnectionHandler::getSecurityLayer() +{ + return securityLayer; +} + +void ConnectionHandler::setRcvTimeoutTask(boost::intrusive_ptr<qpid::sys::TimerTask> t) +{ + rcvTimeoutTask = t; +} diff --git a/qpid/cpp/src/qpid/client/ConnectionHandler.h b/qpid/cpp/src/qpid/client/ConnectionHandler.h new file mode 100644 index 0000000000..6af2e987fb --- /dev/null +++ b/qpid/cpp/src/qpid/client/ConnectionHandler.h @@ -0,0 +1,139 @@ +/* + * + * 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 _ConnectionHandler_ +#define _ConnectionHandler_ + +#include "qpid/client/ChainableFrameHandler.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/Sasl.h" +#include "qpid/client/StateManager.h" +#include "qpid/framing/AMQMethodBody.h" +#include "qpid/framing/AMQP_HighestVersion.h" +#include "qpid/framing/AMQP_ClientOperations.h" +#include "qpid/framing/AMQP_ServerProxy.h" +#include "qpid/framing/Array.h" +#include "qpid/framing/enum.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/framing/FrameHandler.h" +#include "qpid/framing/InputHandler.h" +#include "qpid/sys/SecurityLayer.h" +#include "qpid/sys/Timer.h" +#include "qpid/Url.h" +#include <memory> + +namespace qpid { + +namespace sys { +struct SecuritySettings; +} + +namespace client { + +class Bounds; + +class ConnectionHandler : private StateManager, + public ConnectionSettings, + public ChainableFrameHandler, + public framing::InputHandler, + private framing::AMQP_ClientOperations::ConnectionHandler +{ + typedef framing::AMQP_ClientOperations::ConnectionHandler ConnectionOperations; + enum STATES {NOT_STARTED, NEGOTIATING, OPENING, OPEN, CLOSING, CLOSED, FAILED}; + std::set<int> ESTABLISHED, FINISHED; + + class Adapter : public framing::FrameHandler + { + ConnectionHandler& handler; + Bounds& bounds; + public: + Adapter(ConnectionHandler& h, Bounds& bounds); + void handle(framing::AMQFrame& f); + }; + + Adapter outHandler; + framing::AMQP_ServerProxy::Connection proxy; + framing::connection::CloseCode errorCode; + std::string errorText; + bool insist; + framing::ProtocolVersion version; + framing::Array capabilities; + framing::FieldTable properties; + std::auto_ptr<Sasl> sasl; + std::auto_ptr<qpid::sys::SecurityLayer> securityLayer; + boost::intrusive_ptr<qpid::sys::TimerTask> rcvTimeoutTask; + std::string operUserId; + + void checkState(STATES s, const std::string& msg); + + //methods corresponding to connection controls: + void start(const framing::FieldTable& serverProperties, + const framing::Array& mechanisms, + const framing::Array& locales); + void secure(const std::string& challenge); + void tune(uint16_t channelMax, + uint16_t frameMax, + uint16_t heartbeatMin, + uint16_t heartbeatMax); + void openOk(const framing::Array& knownHosts); + void redirect(const std::string& host, + const framing::Array& knownHosts); + void close(uint16_t replyCode, const std::string& replyText); + void closeOk(); + void heartbeat(); + +public: + using InputHandler::handle; + typedef boost::function<void()> CloseListener; + typedef boost::function<void(uint16_t, const std::string&)> ErrorListener; + typedef boost::function<const qpid::sys::SecuritySettings*()> GetSecuritySettings; + + ConnectionHandler(const ConnectionSettings&, framing::ProtocolVersion&, Bounds&); + + void received(framing::AMQFrame& f) { incoming(f); } + + void incoming(framing::AMQFrame& frame); + void outgoing(framing::AMQFrame& frame); + + void waitForOpen(); + void close(); + void fail(const std::string& message); + + // Note that open and closed aren't related by open = !closed + bool isOpen() const; + bool isClosed() const; + bool isClosing() const; + + std::auto_ptr<qpid::sys::SecurityLayer> getSecurityLayer(); + void setRcvTimeoutTask(boost::intrusive_ptr<qpid::sys::TimerTask>); + + CloseListener onClose; + ErrorListener onError; + + std::vector<Url> knownBrokersUrls; + + static framing::connection::CloseCode convert(uint16_t replyCode); + const std::string& getUserId() const { return operUserId; } + GetSecuritySettings getSecuritySettings; /** query the transport for its security details */ +}; + +}} + +#endif diff --git a/qpid/cpp/src/qpid/client/ConnectionImpl.cpp b/qpid/cpp/src/qpid/client/ConnectionImpl.cpp new file mode 100644 index 0000000000..4b7aa07065 --- /dev/null +++ b/qpid/cpp/src/qpid/client/ConnectionImpl.cpp @@ -0,0 +1,451 @@ +/* + * + * 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/ConnectionImpl.h" + +#include "qpid/client/LoadPlugins.h" +#include "qpid/client/Connector.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/client/SessionImpl.h" + +#include "qpid/log/Statement.h" +#include "qpid/Url.h" +#include "qpid/framing/enum.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/sys/Poller.h" +#include "qpid/sys/SystemInfo.h" +#include "qpid/Options.h" + +#include <boost/bind.hpp> +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/shared_ptr.hpp> + +#include <limits> +#include <vector> + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +namespace qpid { +namespace client { + +using namespace qpid::framing; +using namespace qpid::framing::connection; +using namespace qpid::sys; +using namespace qpid::framing::connection;//for connection error codes + +namespace { +// Maybe should amalgamate the singletons into a single client singleton + +// Get timer singleton +Timer& theTimer() { + static Mutex timerInitLock; + ScopedLock<Mutex> l(timerInitLock); + + static qpid::sys::Timer t; + return t; +} + +struct IOThreadOptions : public qpid::Options { + int maxIOThreads; + + IOThreadOptions(int c) : + Options("IO threading options"), + maxIOThreads(c) + { + addOptions() + ("max-iothreads", optValue(maxIOThreads, "N"), "Maximum number of io threads to use"); + } +}; + +// IO threads +class IOThread { + int maxIOThreads; + int ioThreads; + int connections; + Mutex threadLock; + std::vector<Thread> t; + Poller::shared_ptr poller_; + +public: + void add() { + ScopedLock<Mutex> l(threadLock); + ++connections; + if (!poller_) + poller_.reset(new Poller); + if (ioThreads < connections && ioThreads < maxIOThreads) { + QPID_LOG(debug, "Created IO thread: " << ioThreads); + ++ioThreads; + t.push_back( Thread(poller_.get()) ); + } + } + + void sub() { + ScopedLock<Mutex> l(threadLock); + --connections; + } + + Poller::shared_ptr poller() const { + assert(poller_); + return poller_; + } + + // Here is where the maximum number of threads is set + IOThread(int c) : + ioThreads(0), + connections(0) + { + IOThreadOptions options(c); + options.parse(0, 0, QPIDC_CONF_FILE, true); + maxIOThreads = (options.maxIOThreads != -1) ? + options.maxIOThreads : 1; + } + + // We can't destroy threads one-by-one as the only + // control we have is to shutdown the whole lot + // and we can't do that before we're unloaded as we can't + // restart the Poller after shutting it down + ~IOThread() { + std::vector<Thread> threads; + { + ScopedLock<Mutex> l(threadLock); + if (poller_) + poller_->shutdown(); + t.swap(threads); + } + for (std::vector<Thread>::iterator i = threads.begin(); i != threads.end(); ++i) { + i->join(); + } + } +}; + +IOThread& theIO() { + static IOThread io(SystemInfo::concurrency()); + return io; +} + +class HeartbeatTask : public TimerTask { + TimeoutHandler& timeout; + + void fire() { + // If we ever get here then we have timed out + QPID_LOG(debug, "Traffic timeout"); + timeout.idleIn(); + } + +public: + HeartbeatTask(Duration p, TimeoutHandler& t) : + TimerTask(p,"Heartbeat"), + timeout(t) + {} +}; + +} + +void ConnectionImpl::init() { + // Ensure that the plugin modules have been loaded + // This will make sure that any plugin protocols are available + theModuleLoader(); + + // Ensure the IO threads exist: + // This needs to be called in the Connection constructor + // so that they will still exist at last connection destruction + (void) theIO(); +} + +boost::shared_ptr<ConnectionImpl> ConnectionImpl::create(framing::ProtocolVersion version, const ConnectionSettings& settings) +{ + boost::shared_ptr<ConnectionImpl> instance(new ConnectionImpl(version, settings), boost::bind(&ConnectionImpl::release, _1)); + return instance; +} + +ConnectionImpl::ConnectionImpl(framing::ProtocolVersion v, const ConnectionSettings& settings) + : Bounds(settings.maxFrameSize * settings.bounds), + handler(settings, v, *this), + version(v), + nextChannel(1), + shutdownComplete(false), + released(false) +{ + handler.in = boost::bind(&ConnectionImpl::incoming, this, _1); + handler.out = boost::bind(&Connector::send, boost::ref(connector), _1); + handler.onClose = boost::bind(&ConnectionImpl::closed, this, + CLOSE_CODE_NORMAL, std::string()); + //only set error handler once open + handler.onError = boost::bind(&ConnectionImpl::closed, this, _1, _2); + handler.getSecuritySettings = boost::bind(&Connector::getSecuritySettings, boost::ref(connector)); +} + +const uint16_t ConnectionImpl::NEXT_CHANNEL = std::numeric_limits<uint16_t>::max(); + +ConnectionImpl::~ConnectionImpl() { + if (heartbeatTask) heartbeatTask->cancel(); + theIO().sub(); +} + +void ConnectionImpl::addSession(const boost::shared_ptr<SessionImpl>& session, uint16_t channel) +{ + Mutex::ScopedLock l(lock); + for (uint16_t i = 0; i < NEXT_CHANNEL; i++) { //will at most search through channels once + uint16_t c = channel == NEXT_CHANNEL ? nextChannel++ : channel; + boost::weak_ptr<SessionImpl>& s = sessions[c]; + boost::shared_ptr<SessionImpl> ss = s.lock(); + if (!ss) { + //channel is free, we can assign it to this session + session->setChannel(c); + s = session; + return; + } else if (channel != NEXT_CHANNEL) { + //channel is taken and was requested explicitly so don't look for another + throw SessionBusyException(QPID_MSG("Channel " << ss->getChannel() << " attached to " << ss->getId())); + } //else channel is busy, but we can keep looking for a free one + } + // If we get here, we didn't find any available channel. + throw ResourceLimitExceededException("There are no channels available"); +} + +void ConnectionImpl::handle(framing::AMQFrame& frame) +{ + handler.outgoing(frame); +} + +void ConnectionImpl::incoming(framing::AMQFrame& frame) +{ + boost::shared_ptr<SessionImpl> s; + { + Mutex::ScopedLock l(lock); + s = sessions[frame.getChannel()].lock(); + } + if (!s) { + QPID_LOG(info, *this << " dropping frame received on invalid channel: " << frame); + } else { + s->in(frame); + } +} + +bool ConnectionImpl::isOpen() const +{ + return handler.isOpen(); +} + +void ConnectionImpl::open() +{ + const std::string& protocol = handler.protocol; + const std::string& host = handler.host; + int port = handler.port; + + theIO().add(); + connector.reset(Connector::create(protocol, theIO().poller(), version, handler, this)); + connector->setInputHandler(&handler); + connector->setShutdownHandler(this); + try { + 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; + } + connector->init(); + + // Enable heartbeat if requested + uint16_t heartbeat = static_cast<ConnectionSettings&>(handler).heartbeat; + if (heartbeat) { + // Set connection timeout to be 2x heart beat interval and setup timer + heartbeatTask = new HeartbeatTask(heartbeat * 2 * TIME_SEC, *this); + handler.setRcvTimeoutTask(heartbeatTask); + theTimer().add(heartbeatTask); + } + + // If the connect fails then the connector is cleaned up either when we try to connect again + // - 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. + const std::string& userId(handler.getUserId()); + if (!userId.empty()) + handler.username = userId; + + //enable security layer if one has been negotiated: + std::auto_ptr<SecurityLayer> securityLayer = handler.getSecurityLayer(); + if (securityLayer.get()) { + QPID_LOG(debug, *this << " activating security layer"); + connector->activateSecurityLayer(securityLayer); + } else { + QPID_LOG(debug, *this << " no security layer in place"); + } +} + +void ConnectionImpl::idleIn() +{ + connector->abort(); +} + +void ConnectionImpl::idleOut() +{ + AMQFrame frame((AMQHeartbeatBody())); + connector->send(frame); +} + +void ConnectionImpl::close() +{ + if (heartbeatTask) + heartbeatTask->cancel(); + // close() must be idempotent and no-throw as it will often be called in destructors. + if (handler.isOpen()) { + try { + handler.close(); + closed(CLOSE_CODE_NORMAL, "Closed by client"); + } catch (...) {} + } + assert(!handler.isOpen()); +} + + +template <class F> void ConnectionImpl::closeInternal(const F& f) { + if (heartbeatTask) { + heartbeatTask->cancel(); + } + { + Mutex::ScopedUnlock u(lock); + connector->close(); + } + //notifying sessions of failure can result in those session being + //deleted which in turn results in a call to erase(); this can + //even happen on this thread, when 's' goes out of scope + //below. Using a copy prevents the map being modified as we + //iterate through. + SessionMap copy; + sessions.swap(copy); + for (SessionMap::iterator i = copy.begin(); i != copy.end(); ++i) { + boost::shared_ptr<SessionImpl> s = i->second.lock(); + if (s) f(s); + } +} + +void ConnectionImpl::closed(uint16_t code, const std::string& text) { + Mutex::ScopedLock l(lock); + setException(new ConnectionException(ConnectionHandler::convert(code), text)); + closeInternal(boost::bind(&SessionImpl::connectionClosed, _1, code, text)); +} + +void ConnectionImpl::shutdown() { + if (!handler.isClosed()) { + failedConnection(); + } + bool canDelete; + { + Mutex::ScopedLock l(lock); + //association with IO thread is now ended + shutdownComplete = true; + //If we have already been released, we can now delete ourselves + canDelete = released; + } + if (canDelete) delete this; +} + +void ConnectionImpl::release() { + bool isActive; + { + Mutex::ScopedLock l(lock); + isActive = connector && !shutdownComplete; + } + //If we are still active - i.e. associated with an IO thread - + //then we cannot delete ourselves yet, but must wait for the + //shutdown callback which we can trigger by calling + //connector.close() + if (isActive) { + connector->close(); + bool canDelete; + { + Mutex::ScopedLock l(lock); + released = true; + canDelete = shutdownComplete; + } + if (canDelete) delete this; + } else { + delete this; + } +} + +static const std::string CONN_CLOSED("Connection closed"); + +void ConnectionImpl::failedConnection() { + if ( failureCallback ) + failureCallback(); + + if (handler.isClosed()) return; + + bool isClosing = handler.isClosing(); + bool isOpen = handler.isOpen(); + + std::ostringstream msg; + msg << *this << " closed"; + + // FIXME aconway 2008-06-06: exception use, amqp0-10 does not seem to have + // an appropriate close-code. connection-forced is not right. + handler.fail(msg.str());//ensure connection is marked as failed before notifying sessions + + // At this point if the object isn't open and isn't closing it must have failed to open + // so we can't do the rest of the cleanup + if (!isClosing && !isOpen) return; + + Mutex::ScopedLock l(lock); + closeInternal(boost::bind(&SessionImpl::connectionBroke, _1, msg.str())); + setException(new TransportFailure(msg.str())); +} + +void ConnectionImpl::erase(uint16_t ch) { + Mutex::ScopedLock l(lock); + sessions.erase(ch); +} + +const ConnectionSettings& ConnectionImpl::getNegotiatedSettings() +{ + return handler; +} + +std::vector<qpid::Url> ConnectionImpl::getInitialBrokers() { + return handler.knownBrokersUrls; +} + +boost::shared_ptr<SessionImpl> ConnectionImpl::newSession(const std::string& name, uint32_t timeout, uint16_t channel) { + boost::shared_ptr<SessionImpl> simpl(new SessionImpl(name, shared_from_this())); + addSession(simpl, channel); + simpl->open(timeout); + return simpl; +} + +std::ostream& operator<<(std::ostream& o, const ConnectionImpl& c) { + if (c.connector) + return o << "Connection " << c.connector->getIdentifier(); + else + return o << "Connection <not connected>"; +} + + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/ConnectionImpl.h b/qpid/cpp/src/qpid/client/ConnectionImpl.h new file mode 100644 index 0000000000..cc81500b18 --- /dev/null +++ b/qpid/cpp/src/qpid/client/ConnectionImpl.h @@ -0,0 +1,107 @@ +/* + * + * 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 _ConnectionImpl_ +#define _ConnectionImpl_ + +#include "qpid/client/Bounds.h" +#include "qpid/client/ConnectionHandler.h" + +#include "qpid/framing/FrameHandler.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/ShutdownHandler.h" +#include "qpid/sys/TimeoutHandler.h" + +#include <map> +#include <iosfwd> +#include <boost/shared_ptr.hpp> +#include <boost/weak_ptr.hpp> +#include <boost/scoped_ptr.hpp> +#include <boost/enable_shared_from_this.hpp> + +namespace qpid { +namespace client { + +class Connector; +struct ConnectionSettings; +class SessionImpl; + +class ConnectionImpl : public Bounds, + public framing::FrameHandler, + public sys::TimeoutHandler, + public sys::ShutdownHandler, + public boost::enable_shared_from_this<ConnectionImpl> +{ + typedef std::map<uint16_t, boost::weak_ptr<SessionImpl> > SessionMap; + + static const uint16_t NEXT_CHANNEL; + + SessionMap sessions; + ConnectionHandler handler; + boost::scoped_ptr<Connector> connector; + framing::ProtocolVersion version; + uint16_t nextChannel; + sys::Mutex lock; + bool shutdownComplete; + bool released; + + boost::intrusive_ptr<qpid::sys::TimerTask> heartbeatTask; + + template <class F> void closeInternal(const F&); + + void incoming(framing::AMQFrame& frame); + void closed(uint16_t, const std::string&); + void idleOut(); + void idleIn(); + void shutdown(); + void failedConnection(); + void release(); + ConnectionImpl(framing::ProtocolVersion version, const ConnectionSettings& settings); + + boost::function<void ()> failureCallback; + + public: + static void init(); + static boost::shared_ptr<ConnectionImpl> create(framing::ProtocolVersion version, const ConnectionSettings& settings); + ~ConnectionImpl(); + + void open(); + bool isOpen() const; + + boost::shared_ptr<SessionImpl> newSession(const std::string& name, uint32_t timeout, uint16_t channel=NEXT_CHANNEL); + void addSession(const boost::shared_ptr<SessionImpl>&, uint16_t channel=NEXT_CHANNEL); + + void close(); + void handle(framing::AMQFrame& frame); + void erase(uint16_t channel); + const ConnectionSettings& getNegotiatedSettings(); + + std::vector<Url> getInitialBrokers(); + void registerFailureCallback ( boost::function<void ()> fn ) { failureCallback = fn; } + framing::ProtocolVersion getVersion() { return version; } + + friend std::ostream& operator<<(std::ostream&, const ConnectionImpl&); +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/client/ConnectionSettings.cpp b/qpid/cpp/src/qpid/client/ConnectionSettings.cpp new file mode 100644 index 0000000000..822e4af269 --- /dev/null +++ b/qpid/cpp/src/qpid/client/ConnectionSettings.cpp @@ -0,0 +1,57 @@ +/* + * + * 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/ConnectionSettings.h" + +#include "qpid/log/Logger.h" +#include "qpid/sys/Socket.h" +#include "qpid/Url.h" +#include "qpid/Version.h" + +namespace qpid { +namespace client { + +ConnectionSettings::ConnectionSettings() : + protocol("tcp"), + host("localhost"), + port(5672), + locale("en_US"), + heartbeat(0), + maxChannels(32767), + maxFrameSize(65535), + bounds(2), + tcpNoDelay(false), + service(qpid::saslName), + minSsf(0), + maxSsf(256), + sslCertName("") +{} + +ConnectionSettings::~ConnectionSettings() {} + +void ConnectionSettings::configureSocket(qpid::sys::Socket& socket) const +{ + if (tcpNoDelay) { + socket.setTcpNoDelay(); + QPID_LOG(info, "Set TCP_NODELAY"); + } +} + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/Connector.cpp b/qpid/cpp/src/qpid/client/Connector.cpp new file mode 100644 index 0000000000..c71dd9ecb6 --- /dev/null +++ b/qpid/cpp/src/qpid/client/Connector.cpp @@ -0,0 +1,72 @@ +/* + * + * 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/Connector.h" +#include "qpid/Url.h" +#include "qpid/Exception.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/SecurityLayer.h" + +#include <map> + +namespace qpid { +namespace client { + +using namespace qpid::sys; +using namespace qpid::framing; + +namespace { + typedef std::map<std::string, Connector::Factory*> ProtocolRegistry; + + ProtocolRegistry& theProtocolRegistry() { + static ProtocolRegistry protocolRegistry; + + return protocolRegistry; + } +} + +Connector* Connector::create(const std::string& proto, + boost::shared_ptr<Poller> p, + framing::ProtocolVersion v, const ConnectionSettings& s, ConnectionImpl* c) +{ + ProtocolRegistry::const_iterator i = theProtocolRegistry().find(proto); + if (i==theProtocolRegistry().end()) { + throw Exception(QPID_MSG("Unknown protocol: " << proto)); + } + return (i->second)(p, v, s, c); +} + +void Connector::registerFactory(const std::string& proto, Factory* connectorFactory) +{ + ProtocolRegistry::const_iterator i = theProtocolRegistry().find(proto); + if (i!=theProtocolRegistry().end()) { + QPID_LOG(error, "Tried to register protocol: " << proto << " more than once"); + } + theProtocolRegistry()[proto] = connectorFactory; + Url::addProtocol(proto); +} + +void Connector::activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer>) +{ +} + + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/Connector.h b/qpid/cpp/src/qpid/client/Connector.h new file mode 100644 index 0000000000..bc611ffe0d --- /dev/null +++ b/qpid/cpp/src/qpid/client/Connector.h @@ -0,0 +1,84 @@ +/* + * + * 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 _Connector_ +#define _Connector_ + + +#include "qpid/framing/OutputHandler.h" +#include "qpid/framing/ProtocolVersion.h" + +#include <boost/shared_ptr.hpp> + +#include <string> + +namespace qpid { + +namespace sys { +class ShutdownHandler; +class SecurityLayer; +class Poller; +struct SecuritySettings; +} + +namespace framing { +class InputHandler; +class AMQFrame; +} + +namespace client { + +struct ConnectionSettings; +class ConnectionImpl; + +///@internal +class Connector : public framing::OutputHandler +{ + public: + // Protocol connector factory related stuff (it might be better to separate this code from the TCP Connector in the future) + typedef Connector* Factory(boost::shared_ptr<qpid::sys::Poller>, + framing::ProtocolVersion, const ConnectionSettings&, ConnectionImpl*); + static Connector* create(const std::string& proto, + boost::shared_ptr<qpid::sys::Poller>, + framing::ProtocolVersion, const ConnectionSettings&, ConnectionImpl*); + static void registerFactory(const std::string& proto, Factory* connectorFactory); + + virtual ~Connector() {}; + 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; + virtual void abort() = 0; + + virtual void setInputHandler(framing::InputHandler* handler) = 0; + virtual void setShutdownHandler(sys::ShutdownHandler* handler) = 0; + virtual sys::ShutdownHandler* getShutdownHandler() const = 0; + virtual framing::OutputHandler* getOutputHandler() = 0; + virtual const std::string& getIdentifier() const = 0; + + virtual void activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer>); + + virtual const qpid::sys::SecuritySettings* getSecuritySettings() = 0; +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/client/Demux.cpp b/qpid/cpp/src/qpid/client/Demux.cpp new file mode 100644 index 0000000000..abc23c75df --- /dev/null +++ b/qpid/cpp/src/qpid/client/Demux.cpp @@ -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. + * + */ + +#include "qpid/client/Demux.h" +#include "qpid/Exception.h" +#include "qpid/framing/MessageTransferBody.h" + +#include <iostream> + +namespace qpid { +namespace client { + +ByTransferDest::ByTransferDest(const std::string& d) : dest(d) {} +bool ByTransferDest::operator()(const framing::FrameSet& frameset) const +{ + return frameset.isA<framing::MessageTransferBody>() && + frameset.as<framing::MessageTransferBody>()->getDestination() == dest; +} + +ScopedDivert::ScopedDivert(const std::string& _dest, Demux& _demuxer) : dest(_dest), demuxer(_demuxer) +{ + queue = demuxer.add(dest, ByTransferDest(dest)); +} + +ScopedDivert::~ScopedDivert() +{ + demuxer.remove(dest); +} + +Demux::Demux() : defaultQueue(new Queue()) {} + +Demux::~Demux() { close(sys::ExceptionHolder(new ClosedException())); } + +Demux::QueuePtr ScopedDivert::getQueue() +{ + return queue; +} + +void Demux::handle(framing::FrameSet::shared_ptr frameset) +{ + sys::Mutex::ScopedLock l(lock); + bool matched = false; + for (iterator i = records.begin(); i != records.end() && !matched; i++) { + if (i->condition && i->condition(*frameset)) { + matched = true; + i->queue->push(frameset); + } + } + if (!matched) { + defaultQueue->push(frameset); + } +} + +void Demux::close(const sys::ExceptionHolder& ex) +{ + sys::Mutex::ScopedLock l(lock); + for (iterator i = records.begin(); i != records.end(); i++) { + i->queue->close(ex); + } + defaultQueue->close(ex); +} + +void Demux::open() +{ + sys::Mutex::ScopedLock l(lock); + for (iterator i = records.begin(); i != records.end(); i++) { + i->queue->open(); + } + defaultQueue->open(); +} + +Demux::QueuePtr Demux::add(const std::string& name, Condition condition) +{ + sys::Mutex::ScopedLock l(lock); + iterator i = std::find_if(records.begin(), records.end(), Find(name)); + if (i == records.end()) { + Record r(name, condition); + records.push_back(r); + return r.queue; + } else { + throw Exception("Queue already exists for " + name); + } +} + +void Demux::remove(const std::string& name) +{ + sys::Mutex::ScopedLock l(lock); + records.remove_if(Find(name)); +} + +Demux::QueuePtr Demux::get(const std::string& name) +{ + sys::Mutex::ScopedLock l(lock); + iterator i = std::find_if(records.begin(), records.end(), Find(name)); + if (i == records.end()) { + throw Exception("No queue for " + name); + } + return i->queue; +} + +Demux::QueuePtr Demux::getDefault() +{ + return defaultQueue; +} + +Demux::Find::Find(const std::string& n) : name(n) {} + +bool Demux::Find::operator()(const Record& record) const +{ + return record.name == name; +} + +}} + diff --git a/qpid/cpp/src/qpid/client/Demux.h b/qpid/cpp/src/qpid/client/Demux.h new file mode 100644 index 0000000000..31dc3f9c06 --- /dev/null +++ b/qpid/cpp/src/qpid/client/Demux.h @@ -0,0 +1,103 @@ +/* + * + * 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 <list> +#include <boost/function.hpp> +#include <boost/shared_ptr.hpp> +#include "qpid/framing/FrameSet.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/BlockingQueue.h" +#include "qpid/client/ClientImportExport.h" + +#ifndef _Demux_ +#define _Demux_ + +namespace qpid { +namespace client { + +///@internal +class ByTransferDest +{ + const std::string dest; +public: + ByTransferDest(const std::string& dest); + bool operator()(const framing::FrameSet& frameset) const; +}; + +///@internal +class Demux +{ +public: + typedef boost::function<bool(const framing::FrameSet&)> Condition; + typedef sys::BlockingQueue<framing::FrameSet::shared_ptr> Queue; + typedef boost::shared_ptr<Queue> QueuePtr; + + QPID_CLIENT_EXTERN Demux(); + QPID_CLIENT_EXTERN ~Demux(); + + QPID_CLIENT_EXTERN void handle(framing::FrameSet::shared_ptr); + QPID_CLIENT_EXTERN void close(const sys::ExceptionHolder& ex); + QPID_CLIENT_EXTERN void open(); + + QPID_CLIENT_EXTERN QueuePtr add(const std::string& name, Condition); + QPID_CLIENT_EXTERN void remove(const std::string& name); + QPID_CLIENT_EXTERN QueuePtr get(const std::string& name); + QPID_CLIENT_EXTERN QueuePtr getDefault(); + +private: + struct Record + { + const std::string name; + Condition condition; + QueuePtr queue; + + Record(const std::string& n, Condition c) : name(n), condition(c), queue(new Queue()) {} + }; + + sys::Mutex lock; + std::list<Record> records; + QueuePtr defaultQueue; + + typedef std::list<Record>::iterator iterator; + + struct Find + { + const std::string name; + Find(const std::string& name); + bool operator()(const Record& record) const; + }; +}; + +class ScopedDivert +{ + const std::string dest; + Demux& demuxer; + Demux::QueuePtr queue; +public: + ScopedDivert(const std::string& dest, Demux& demuxer); + ~ScopedDivert(); + Demux::QueuePtr getQueue(); +}; + +}} // namespace qpid::client + + +#endif diff --git a/qpid/cpp/src/qpid/client/Dispatcher.cpp b/qpid/cpp/src/qpid/client/Dispatcher.cpp new file mode 100644 index 0000000000..a715c623bf --- /dev/null +++ b/qpid/cpp/src/qpid/client/Dispatcher.cpp @@ -0,0 +1,151 @@ +/* + * + * 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/Dispatcher.h" +#include "qpid/client/SubscriptionImpl.h" +#include "qpid/client/SessionImpl.h" + +#include "qpid/framing/FrameSet.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/BlockingQueue.h" +#include "qpid/client/Message.h" +#include "qpid/client/MessageImpl.h" + +#include <boost/version.hpp> +#if (BOOST_VERSION >= 104000) +# include <boost/serialization/state_saver.hpp> + using boost::serialization::state_saver; +#else +# include <boost/state_saver.hpp> + using boost::state_saver; +#endif /* BOOST_VERSION */ + +using qpid::framing::FrameSet; +using qpid::framing::MessageTransferBody; +using qpid::sys::Mutex; +using qpid::sys::ScopedLock; +using qpid::sys::Thread; + +namespace qpid { +namespace client { + +Dispatcher::Dispatcher(const Session& s, const std::string& q) + : session(s), + running(false), + autoStop(true), + failoverHandler(0) +{ + Demux& demux = SessionBase_0_10Access(session).get()->getDemux(); + queue = q.empty() ? demux.getDefault() : demux.get(q); +} + +void Dispatcher::start() +{ + worker = Thread(this); +} + +void Dispatcher::wait() +{ + worker.join(); +} + +void Dispatcher::run() +{ + Mutex::ScopedLock l(lock); + if (running) + throw Exception("Dispatcher is already running."); + state_saver<bool> reset(running); // Reset to false on exit. + running = true; + try { + while (!queue->isClosed()) { + Mutex::ScopedUnlock u(lock); + FrameSet::shared_ptr content = queue->pop(); + if (content->isA<MessageTransferBody>()) { + Message msg(new MessageImpl(*content)); + boost::intrusive_ptr<SubscriptionImpl> listener = find(msg.getDestination()); + if (!listener) { + QPID_LOG(error, "No listener found for destination " << msg.getDestination()); + } else { + assert(listener); + listener->received(msg); + } + } else { + if (handler.get()) { + handler->handle(*content); + } else { + QPID_LOG(warning, "No handler found for " << *(content->getMethod())); + } + } + } + session.sync(); // Make sure all our acks are received before returning. + } + catch (const ClosedException&) { + QPID_LOG(debug, QPID_MSG(session.getId() << ": closed by peer")); + } + catch (const TransportFailure&) { + QPID_LOG(info, QPID_MSG(session.getId() << ": transport failure")); + throw; + } + catch (const std::exception& e) { + if ( failoverHandler ) { + QPID_LOG(debug, QPID_MSG(session.getId() << " failover: " << e.what())); + failoverHandler(); + } else { + QPID_LOG(error, session.getId() << " error: " << e.what()); + throw; + } + } +} + +void Dispatcher::stop() +{ + ScopedLock<Mutex> l(lock); + queue->close(); // Will interrupt thread blocked in pop() +} + +void Dispatcher::setAutoStop(bool b) +{ + ScopedLock<Mutex> l(lock); + autoStop = b; +} + +boost::intrusive_ptr<SubscriptionImpl> Dispatcher::find(const std::string& name) +{ + ScopedLock<Mutex> l(lock); + Listeners::iterator i = listeners.find(name); + if (i == listeners.end()) { + return defaultListener; + } + return i->second; +} + +void Dispatcher::listen(const boost::intrusive_ptr<SubscriptionImpl>& subscription) { + ScopedLock<Mutex> l(lock); + listeners[subscription->getName()] = subscription; +} + +void Dispatcher::cancel(const std::string& destination) { + ScopedLock<Mutex> l(lock); + if (listeners.erase(destination) && running && autoStop && listeners.empty()) + queue->close(); +} + +}} diff --git a/qpid/cpp/src/qpid/client/Dispatcher.h b/qpid/cpp/src/qpid/client/Dispatcher.h new file mode 100644 index 0000000000..74fdb90103 --- /dev/null +++ b/qpid/cpp/src/qpid/client/Dispatcher.h @@ -0,0 +1,87 @@ +/* + * + * 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 _Dispatcher_ +#define _Dispatcher_ + +#include <map> +#include <memory> +#include <string> +#include <boost/shared_ptr.hpp> +#include "qpid/client/Session.h" +#include "qpid/client/SessionBase_0_10Access.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Runnable.h" +#include "qpid/sys/Thread.h" +#include "qpid/client/ClientImportExport.h" +#include "qpid/client/MessageListener.h" +#include "qpid/client/SubscriptionImpl.h" + +namespace qpid { +namespace client { + +class SubscriptionImpl; + +///@internal +typedef framing::Handler<framing::FrameSet> FrameSetHandler; + +///@internal +class Dispatcher : public sys::Runnable +{ + typedef std::map<std::string, boost::intrusive_ptr<SubscriptionImpl> >Listeners; + sys::Mutex lock; + sys::Thread worker; + Session session; + Demux::QueuePtr queue; + bool running; + bool autoStop; + Listeners listeners; + boost::intrusive_ptr<SubscriptionImpl> defaultListener; + std::auto_ptr<FrameSetHandler> handler; + + boost::intrusive_ptr<SubscriptionImpl> find(const std::string& name); + bool isStopped(); + + boost::function<void ()> failoverHandler; + +public: + Dispatcher(const Session& session, const std::string& queue = ""); + ~Dispatcher() {} + + void start(); + void wait(); + // As this class is marked 'internal', no extern should be made here; + // however, some test programs rely on it. + QPID_CLIENT_EXTERN void run(); + void stop(); + void setAutoStop(bool b); + + void registerFailoverHandler ( boost::function<void ()> fh ) + { + failoverHandler = fh; + } + + void listen(const boost::intrusive_ptr<SubscriptionImpl>& subscription); + void cancel(const std::string& destination); +}; + +}} + +#endif diff --git a/qpid/cpp/src/qpid/client/Execution.h b/qpid/cpp/src/qpid/client/Execution.h new file mode 100644 index 0000000000..ad622af9c1 --- /dev/null +++ b/qpid/cpp/src/qpid/client/Execution.h @@ -0,0 +1,53 @@ +/* + * + * 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 _Execution_ +#define _Execution_ + +#include "qpid/framing/SequenceNumber.h" +#include "qpid/client/Demux.h" + +namespace qpid { +namespace client { + +/**@internal + * + * Provides access to more detailed aspects of the session + * implementation. + */ +class Execution +{ +public: + virtual ~Execution() {} + /** + * Provides access to the demultiplexing function within the + * session implementation + */ + virtual Demux& getDemux() = 0; + /** + * Wait until notification has been received of completion of the + * outgoing command with the specified id. + */ + void waitForCompletion(const framing::SequenceNumber& id); +}; + +}} + +#endif diff --git a/qpid/cpp/src/qpid/client/FailoverListener.cpp b/qpid/cpp/src/qpid/client/FailoverListener.cpp new file mode 100644 index 0000000000..bf4fa91d49 --- /dev/null +++ b/qpid/cpp/src/qpid/client/FailoverListener.cpp @@ -0,0 +1,97 @@ +/* + * + * 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/FailoverListener.h" +#include "qpid/client/Session.h" +#include "qpid/framing/Uuid.h" +#include "qpid/log/Statement.h" +#include "qpid/log/Helpers.h" + +namespace qpid { +namespace client { + +const std::string FailoverListener::AMQ_FAILOVER("amq.failover"); + +FailoverListener::FailoverListener(Connection c) : + connection(c), + session(c.newSession(AMQ_FAILOVER+"."+framing::Uuid(true).str())), + subscriptions(session) +{ init(true); } + +FailoverListener::FailoverListener(Connection c, bool useInitial) : + connection(c), + session(c.newSession(AMQ_FAILOVER+"."+framing::Uuid(true).str())), + subscriptions(session) +{ init(useInitial); } + +void FailoverListener::init(bool useInitial) { + if (useInitial) knownBrokers = connection.getInitialBrokers(); + if (session.exchangeQuery(arg::name=AMQ_FAILOVER).getNotFound()) { + session.close(); + return; + } + std::string qname=session.getId().getName(); + session.queueDeclare(arg::queue=qname, arg::exclusive=true, arg::autoDelete=true); + session.exchangeBind(arg::queue=qname, arg::exchange=AMQ_FAILOVER); + subscriptions.subscribe(*this, qname, SubscriptionSettings(FlowControl::unlimited(), + ACCEPT_MODE_NONE)); + thread = sys::Thread(*this); +} + +void FailoverListener::run() { + try { + subscriptions.run(); + } catch(...) {} +} + +FailoverListener::~FailoverListener() { + try { + subscriptions.stop(); + thread.join(); + if (connection.isOpen()) { + session.sync(); + session.close(); + } + } catch (...) {} +} + +void FailoverListener::received(Message& msg) { + sys::Mutex::ScopedLock l(lock); + knownBrokers = getKnownBrokers(msg); +} + +std::vector<Url> FailoverListener::getKnownBrokers() const { + sys::Mutex::ScopedLock l(lock); + return knownBrokers; +} + +std::vector<Url> FailoverListener::getKnownBrokers(const Message& msg) { + std::vector<Url> knownBrokers; + framing::Array urlArray; + msg.getHeaders().getArray("amq.failover", urlArray); + for (framing::Array::ValueVector::const_iterator i = urlArray.begin(); + i != urlArray.end(); + ++i ) + knownBrokers.push_back(Url((*i)->get<std::string>())); + return knownBrokers; +} + + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/FailoverManager.cpp b/qpid/cpp/src/qpid/client/FailoverManager.cpp new file mode 100644 index 0000000000..9405765b47 --- /dev/null +++ b/qpid/cpp/src/qpid/client/FailoverManager.cpp @@ -0,0 +1,130 @@ +/* + * + * 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/FailoverManager.h" +#include "qpid/Exception.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Time.h" + + +namespace qpid { +namespace client { + +using qpid::sys::Monitor; +using qpid::sys::AbsTime; +using qpid::sys::Duration; + +FailoverManager::FailoverManager(const ConnectionSettings& s, + ReconnectionStrategy* rs) : settings(s), strategy(rs), state(IDLE) {} + +void FailoverManager::execute(Command& c) +{ + bool retry = false; + bool completed = false; + AbsTime failed; + while (!completed) { + try { + AsyncSession session = connect().newSession(); + if (retry) { + Duration failoverTime(failed, AbsTime::now()); + QPID_LOG(info, "Failed over for " << &c << " in " << (failoverTime/qpid::sys::TIME_MSEC) << " milliseconds"); + } + c.execute(session, retry); + session.sync();//TODO: shouldn't be required + session.close(); + completed = true; + } catch(const TransportFailure&) { + retry = true; + failed = AbsTime::now(); + } + } +} + +void FailoverManager::close() +{ + Monitor::ScopedLock l(lock); + connection.close(); +} + +Connection& FailoverManager::connect(std::vector<Url> brokers) +{ + Monitor::ScopedLock l(lock); + if (state == CANT_CONNECT) { + state = IDLE;//retry + } + while (!connection.isOpen()) { + if (state == CONNECTING) { + lock.wait(); + } else if (state == CANT_CONNECT) { + throw CannotConnectException("Cannot establish a connection"); + } else { + state = CONNECTING; + Connection c; + if (brokers.empty() && failoverListener.get()) + brokers = failoverListener->getKnownBrokers(); + attempt(c, settings, brokers); + if (c.isOpen()) state = IDLE; + else state = CANT_CONNECT; + connection = c; + lock.notifyAll(); + } + } + return connection; +} + +Connection& FailoverManager::getConnection() +{ + Monitor::ScopedLock l(lock); + return connection; +} + +void FailoverManager::attempt(Connection& c, ConnectionSettings s, std::vector<Url> urls) +{ + Monitor::ScopedUnlock u(lock); + if (strategy) strategy->editUrlList(urls); + if (urls.empty()) { + attempt(c, s); + } else { + for (std::vector<Url>::const_iterator i = urls.begin(); i != urls.end() && !c.isOpen(); ++i) { + for (Url::const_iterator j = i->begin(); j != i->end() && !c.isOpen(); ++j) { + const Address& addr = *j; + s.protocol = addr.protocol; + s.host = addr.host; + s.port = addr.port; + attempt(c, s); + } + } + } +} + +void FailoverManager::attempt(Connection& c, ConnectionSettings s) +{ + try { + QPID_LOG(info, "Attempting to connect to " << s.host << " on " << s.port << "..."); + c.open(s); + failoverListener.reset(new FailoverListener(c)); + QPID_LOG(info, "Connected to " << s.host << " on " << s.port); + } catch (const Exception& e) { + QPID_LOG(info, "Could not connect to " << s.host << " on " << s.port << ": " << e.what()); + } +} + + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/Future.cpp b/qpid/cpp/src/qpid/client/Future.cpp new file mode 100644 index 0000000000..740cd3df59 --- /dev/null +++ b/qpid/cpp/src/qpid/client/Future.cpp @@ -0,0 +1,46 @@ +/* + * + * 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/Future.h" +#include "qpid/client/SessionImpl.h" + +namespace qpid { +namespace client { + +void Future::wait(SessionImpl& session) +{ + if (!complete) { + session.waitForCompletion(command); + } + complete = true; +} + +bool Future::isComplete(SessionImpl& session) +{ + return complete || session.isComplete(command); +} + +void Future::setFutureResult(boost::shared_ptr<FutureResult> r) +{ + result = r; +} + +}} diff --git a/qpid/cpp/src/qpid/client/FutureCompletion.cpp b/qpid/cpp/src/qpid/client/FutureCompletion.cpp new file mode 100644 index 0000000000..ccfb073855 --- /dev/null +++ b/qpid/cpp/src/qpid/client/FutureCompletion.cpp @@ -0,0 +1,48 @@ +/* + * + * 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/FutureCompletion.h" + +using namespace qpid::client; +using namespace qpid::sys; + +FutureCompletion::FutureCompletion() : complete(false) {} + +bool FutureCompletion::isComplete() const +{ + Monitor::ScopedLock l(lock); + return complete; +} + +void FutureCompletion::completed() +{ + Monitor::ScopedLock l(lock); + complete = true; + lock.notifyAll(); +} + +void FutureCompletion::waitForCompletion() const +{ + Monitor::ScopedLock l(lock); + while (!complete) { + lock.wait(); + } +} diff --git a/qpid/cpp/src/qpid/client/FutureResult.cpp b/qpid/cpp/src/qpid/client/FutureResult.cpp new file mode 100644 index 0000000000..0237eb1464 --- /dev/null +++ b/qpid/cpp/src/qpid/client/FutureResult.cpp @@ -0,0 +1,43 @@ +/* + * + * 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/FutureResult.h" + +#include "qpid/client/SessionImpl.h" + +using namespace qpid::client; +using namespace qpid::framing; +using namespace qpid::sys; + +const std::string& FutureResult::getResult(SessionImpl& session) const +{ + waitForCompletion(); + session.assertOpen(); + return result; +} + +void FutureResult::received(const std::string& r) +{ + Monitor::ScopedLock l(lock); + result = r; + complete = true; + lock.notifyAll(); +} diff --git a/qpid/cpp/src/qpid/client/LoadPlugins.cpp b/qpid/cpp/src/qpid/client/LoadPlugins.cpp new file mode 100644 index 0000000000..246eb60c67 --- /dev/null +++ b/qpid/cpp/src/qpid/client/LoadPlugins.cpp @@ -0,0 +1,64 @@ +/* + * + * 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 "LoadPlugins.h" + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include "qpid/Modules.h" +#include "qpid/sys/Shlib.h" +#include <string> +#include <vector> + +using std::vector; +using std::string; + +namespace qpid { +namespace client { + +namespace { + +struct LoadtimeInitialise { + LoadtimeInitialise() { + qpid::ModuleOptions moduleOptions(QPIDC_MODULE_DIR); + string defaultPath (moduleOptions.loadDir); + moduleOptions.parse (0, 0, QPIDC_CONF_FILE, true); + + for (vector<string>::iterator iter = moduleOptions.load.begin(); + iter != moduleOptions.load.end(); + iter++) + qpid::tryShlib (iter->data(), false); + + if (!moduleOptions.noLoad) { + bool isDefault = defaultPath == moduleOptions.loadDir; + qpid::loadModuleDir (moduleOptions.loadDir, isDefault); + } + } +}; + +} // namespace + +void theModuleLoader() { + static LoadtimeInitialise l; +} + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/LoadPlugins.h b/qpid/cpp/src/qpid/client/LoadPlugins.h new file mode 100644 index 0000000000..0be4ae9f0c --- /dev/null +++ b/qpid/cpp/src/qpid/client/LoadPlugins.h @@ -0,0 +1,33 @@ +/* + * + * 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 _LoadPlugins_ +#define _LoadPlugins_ + +namespace qpid { +namespace client { + +void theModuleLoader(); + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/client/LocalQueue.cpp b/qpid/cpp/src/qpid/client/LocalQueue.cpp new file mode 100644 index 0000000000..0019adabaf --- /dev/null +++ b/qpid/cpp/src/qpid/client/LocalQueue.cpp @@ -0,0 +1,52 @@ +/* + * + * 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/LocalQueue.h" +#include "qpid/client/LocalQueueImpl.h" +#include "qpid/client/MessageImpl.h" +#include "qpid/Exception.h" +#include "qpid/framing/FrameSet.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/client/PrivateImplRef.h" +#include "qpid/client/SubscriptionImpl.h" + +namespace qpid { +namespace client { + +using namespace framing; + +typedef PrivateImplRef<LocalQueue> PI; + +LocalQueue::LocalQueue() { PI::ctor(*this, new LocalQueueImpl()); } +LocalQueue::LocalQueue(const LocalQueue& x) : Handle<LocalQueueImpl>() { PI::copy(*this, x); } +LocalQueue::~LocalQueue() { PI::dtor(*this); } +LocalQueue& LocalQueue::operator=(const LocalQueue& x) { return PI::assign(*this, x); } + +Message LocalQueue::pop(sys::Duration timeout) { return impl->pop(timeout); } + +Message LocalQueue::get(sys::Duration timeout) { return impl->get(timeout); } + +bool LocalQueue::get(Message& result, sys::Duration timeout) { return impl->get(result, timeout); } + +bool LocalQueue::empty() const { return impl->empty(); } +size_t LocalQueue::size() const { return impl->size(); } + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/LocalQueueImpl.cpp b/qpid/cpp/src/qpid/client/LocalQueueImpl.cpp new file mode 100644 index 0000000000..8b191728f4 --- /dev/null +++ b/qpid/cpp/src/qpid/client/LocalQueueImpl.cpp @@ -0,0 +1,78 @@ +/* + * + * 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/LocalQueueImpl.h" +#include "qpid/client/MessageImpl.h" +#include "qpid/Exception.h" +#include "qpid/framing/FrameSet.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/client/PrivateImplRef.h" +#include "qpid/client/SubscriptionImpl.h" +#include "qpid/client/CompletionImpl.h" + +namespace qpid { +namespace client { + +using namespace framing; + +Message LocalQueueImpl::pop(sys::Duration timeout) { return get(timeout); } + +Message LocalQueueImpl::get(sys::Duration timeout) { + Message result; + bool ok = get(result, timeout); + if (!ok) throw Exception("Timed out waiting for a message"); + return result; +} + +bool LocalQueueImpl::get(Message& result, sys::Duration timeout) { + if (!queue) + throw ClosedException(); + FrameSet::shared_ptr content; + bool ok = queue->pop(content, timeout); + if (!ok) return false; + if (content->isA<MessageTransferBody>()) { + + *MessageImpl::get(result) = MessageImpl(*content); + boost::intrusive_ptr<SubscriptionImpl> si = PrivateImplRef<Subscription>::get(subscription); + assert(si); + if (si) si->received(result); + return true; + } + else + throw CommandInvalidException( + QPID_MSG("Unexpected method: " << content->getMethod())); +} + +bool LocalQueueImpl::empty() const +{ + if (!queue) + throw ClosedException(); + return queue->empty(); +} + +size_t LocalQueueImpl::size() const +{ + if (!queue) + throw ClosedException(); + return queue->size(); +} + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/LocalQueueImpl.h b/qpid/cpp/src/qpid/client/LocalQueueImpl.h new file mode 100644 index 0000000000..75b62cf203 --- /dev/null +++ b/qpid/cpp/src/qpid/client/LocalQueueImpl.h @@ -0,0 +1,108 @@ +#ifndef QPID_CLIENT_LOCALQUEUEIMPL_H +#define QPID_CLIENT_LOCALQUEUEIMPL_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/client/ClientImportExport.h" +#include "qpid/client/Handle.h" +#include "qpid/client/Message.h" +#include "qpid/client/Subscription.h" +#include "qpid/client/Demux.h" +#include "qpid/sys/Time.h" +#include "qpid/RefCounted.h" + +namespace qpid { +namespace client { + +/** + * A local queue to collect messages retrieved from a remote broker + * queue. Create a queue and subscribe it using the SubscriptionManager. + * Messages from the remote queue on the broker will be stored in the + * local queue until you retrieve them. + * + * \ingroup clientapi + * + * \details Using a Local Queue + * + * <pre> + * LocalQueue local_queue; + * subscriptions.subscribe(local_queue, string("message_queue")); + * for (int i=0; i<10; i++) { + * Message message = local_queue.get(); + * std::cout << message.getData() << std::endl; + * } + * </pre> + * + * <h2>Getting Messages</h2> + * + * <ul><li> + * <p>get()</p> + * <pre>Message message = local_queue.get();</pre> + * <pre>// Specifying timeouts (TIME_SEC, TIME_MSEC, TIME_USEC, TIME_NSEC) + *#include <qpid/sys/Time.h> + *Message message; + *local_queue.get(message, 5*sys::TIME_SEC);</pre></li></ul> + * + * <h2>Checking size</h2> + * <ul><li> + * <p>empty()</p> + * <pre>if (local_queue.empty()) { ... }</pre></li> + * <li><p>size()</p> + * <pre>std::cout << local_queue.size();</pre></li> + * </ul> + */ + +class LocalQueueImpl : public RefCounted { + public: + /** Wait up to timeout for the next message from the local queue. + *@param result Set to the message from the queue. + *@param timeout wait up this timeout for a message to appear. + *@return true if result was set, false if queue was empty after timeout. + */ + bool get(Message& result, sys::Duration timeout=0); + + /** Get the next message off the local queue, or wait up to the timeout + * for message from the broker queue. + *@param timeout wait up this timeout for a message to appear. + *@return message from the queue. + *@throw ClosedException if subscription is closed or timeout exceeded. + */ + Message get(sys::Duration timeout=sys::TIME_INFINITE); + + /** Synonym for get() */ + Message pop(sys::Duration timeout=sys::TIME_INFINITE); + + /** Return true if local queue is empty. */ + bool empty() const; + + /** Number of messages on the local queue */ + size_t size() const; + + private: + Demux::QueuePtr queue; + Subscription subscription; + friend class SubscriptionManagerImpl; +}; + +}} // namespace qpid::client + +#endif /*!QPID_CLIENT_LOCALQUEUEIMPL_H*/ diff --git a/qpid/cpp/src/qpid/client/Message.cpp b/qpid/cpp/src/qpid/client/Message.cpp new file mode 100644 index 0000000000..00f911c57e --- /dev/null +++ b/qpid/cpp/src/qpid/client/Message.cpp @@ -0,0 +1,62 @@ +/* + * + * 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/Message.h" +#include "qpid/client/MessageImpl.h" + +namespace qpid { +namespace client { + +Message::Message(MessageImpl* mi) : impl(mi) {} + +Message::Message(const std::string& data, const std::string& routingKey) + : impl(new MessageImpl(data, routingKey)) {} + +Message::Message(const Message& m) : impl(new MessageImpl(*m.impl)) {} + +Message::~Message() { delete impl; } + +Message& Message::operator=(const Message& m) { *impl = *m.impl; return *this; } + +void Message::swap(Message& m) { std::swap(impl, m.impl); } + +std::string Message::getDestination() const { return impl->getDestination(); } +bool Message::isRedelivered() const { return impl->isRedelivered(); } +void Message::setRedelivered(bool redelivered) { impl->setRedelivered(redelivered); } +framing::FieldTable& Message::getHeaders() { return impl->getHeaders(); } +const framing::FieldTable& Message::getHeaders() const { return impl->getHeaders(); } +const framing::SequenceNumber& Message::getId() const { return impl->getId(); } + +void Message::setData(const std::string& s) { impl->setData(s); } +const std::string& Message::getData() const { return impl->getData(); } +std::string& Message::getData() { return impl->getData(); } + +void Message::appendData(const std::string& s) { impl->appendData(s); } + +bool Message::hasMessageProperties() const { return impl->hasMessageProperties(); } +framing::MessageProperties& Message::getMessageProperties() { return impl->getMessageProperties(); } +const framing::MessageProperties& Message::getMessageProperties() const { return impl->getMessageProperties(); } + +bool Message::hasDeliveryProperties() const { return impl->hasDeliveryProperties(); } +framing::DeliveryProperties& Message::getDeliveryProperties() { return impl->getDeliveryProperties(); } +const framing::DeliveryProperties& Message::getDeliveryProperties() const { return impl->getDeliveryProperties(); } + +}} diff --git a/qpid/cpp/src/qpid/client/MessageImpl.cpp b/qpid/cpp/src/qpid/client/MessageImpl.cpp new file mode 100644 index 0000000000..865c462b15 --- /dev/null +++ b/qpid/cpp/src/qpid/client/MessageImpl.cpp @@ -0,0 +1,71 @@ +/* + * + * 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/MessageImpl.h" + +namespace qpid { +namespace client { + +MessageImpl::MessageImpl(const std::string& data, const std::string& routingKey) : TransferContent(data, routingKey) {} + +std::string MessageImpl::getDestination() const +{ + return method.getDestination(); +} + +bool MessageImpl::isRedelivered() const +{ + return hasDeliveryProperties() && getDeliveryProperties().getRedelivered(); +} + +void MessageImpl::setRedelivered(bool redelivered) +{ + getDeliveryProperties().setRedelivered(redelivered); +} + +framing::FieldTable& MessageImpl::getHeaders() +{ + return getMessageProperties().getApplicationHeaders(); +} + +const framing::FieldTable& MessageImpl::getHeaders() const +{ + return getMessageProperties().getApplicationHeaders(); +} + +const framing::MessageTransferBody& MessageImpl::getMethod() const +{ + return method; +} + +const framing::SequenceNumber& MessageImpl::getId() const +{ + return id; +} + +/**@internal for incoming messages */ +MessageImpl::MessageImpl(const framing::FrameSet& frameset) : + method(*frameset.as<framing::MessageTransferBody>()), id(frameset.getId()) +{ + populate(frameset); +} + +}} diff --git a/qpid/cpp/src/qpid/client/MessageImpl.h b/qpid/cpp/src/qpid/client/MessageImpl.h new file mode 100644 index 0000000000..a64ddd20d8 --- /dev/null +++ b/qpid/cpp/src/qpid/client/MessageImpl.h @@ -0,0 +1,80 @@ +#ifndef QPID_CLIENT_MESSAGEIMPL_H +#define QPID_CLIENT_MESSAGEIMPL_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/client/Message.h" +#include "qpid/client/Session.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/TransferContent.h" +#include <string> + +namespace qpid { +namespace client { + +class MessageImpl : public framing::TransferContent +{ +public: + /** Create a Message. + *@param data Data for the message body. + *@param routingKey Passed to the exchange that routes the message. + */ + MessageImpl(const std::string& data=std::string(), + const std::string& routingKey=std::string()); + + /** The destination of messages sent to the broker is the exchange + * name. The destination of messages received from the broker is + * the delivery tag identifyig the local subscription (often this + * is the name of the subscribed queue.) + */ + std::string getDestination() const; + + /** Check the redelivered flag. */ + bool isRedelivered() const; + /** Set the redelivered flag. */ + void setRedelivered(bool redelivered); + + /** Get a modifyable reference to the message headers. */ + framing::FieldTable& getHeaders(); + + /** Get a non-modifyable reference to the message headers. */ + const framing::FieldTable& getHeaders() const; + + ///@internal + const framing::MessageTransferBody& getMethod() const; + ///@internal + const framing::SequenceNumber& getId() const; + + /**@internal for incoming messages */ + MessageImpl(const framing::FrameSet& frameset); + + static MessageImpl* get(Message& m) { return m.impl; } + static const MessageImpl* get(const Message& m) { return m.impl; } + +private: + //method and id are only set for received messages: + framing::MessageTransferBody method; + framing::SequenceNumber id; +}; + +}} + +#endif /*!QPID_CLIENT_MESSAGEIMPL_H*/ diff --git a/qpid/cpp/src/qpid/client/MessageListener.cpp b/qpid/cpp/src/qpid/client/MessageListener.cpp new file mode 100644 index 0000000000..0f2a71287c --- /dev/null +++ b/qpid/cpp/src/qpid/client/MessageListener.cpp @@ -0,0 +1,24 @@ +/* + * + * 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/MessageListener.h" + +qpid::client::MessageListener::~MessageListener() {} diff --git a/qpid/cpp/src/qpid/client/MessageReplayTracker.cpp b/qpid/cpp/src/qpid/client/MessageReplayTracker.cpp new file mode 100644 index 0000000000..3afaae74e8 --- /dev/null +++ b/qpid/cpp/src/qpid/client/MessageReplayTracker.cpp @@ -0,0 +1,78 @@ +/* + * + * 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/MessageReplayTracker.h" +#include <boost/bind.hpp> + +namespace qpid { +namespace client { + +MessageReplayTracker::MessageReplayTracker(uint f) : flushInterval(f), count(0) {} + +void MessageReplayTracker::send(const Message& message, const std::string& destination) +{ + buffer.push_back(ReplayRecord(message, destination)); + buffer.back().send(*this); + if (flushInterval && (++count % flushInterval == 0)) { + checkCompletion(); + if (!buffer.empty()) session.flush(); + } +} +void MessageReplayTracker::init(AsyncSession s) +{ + session = s; +} + +void MessageReplayTracker::replay(AsyncSession s) +{ + session = s; + std::for_each(buffer.begin(), buffer.end(), boost::bind(&ReplayRecord::send, _1, boost::ref(*this))); + session.flush(); + count = 0; +} + +void MessageReplayTracker::setFlushInterval(uint f) +{ + flushInterval = f; +} + +uint MessageReplayTracker::getFlushInterval() +{ + return flushInterval; +} + +void MessageReplayTracker::checkCompletion() +{ + buffer.remove_if(boost::bind(&ReplayRecord::isComplete, _1)); +} + +MessageReplayTracker::ReplayRecord::ReplayRecord(const Message& m, const std::string& d) : message(m), destination(d) {} + +void MessageReplayTracker::ReplayRecord::send(MessageReplayTracker& tracker) +{ + status = tracker.session.messageTransfer(arg::destination=destination, arg::content=message); +} + +bool MessageReplayTracker::ReplayRecord::isComplete() +{ + return status.isComplete(); +} + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/PrivateImplRef.h b/qpid/cpp/src/qpid/client/PrivateImplRef.h new file mode 100644 index 0000000000..503a383c31 --- /dev/null +++ b/qpid/cpp/src/qpid/client/PrivateImplRef.h @@ -0,0 +1,94 @@ +#ifndef QPID_CLIENT_PRIVATEIMPL_H +#define QPID_CLIENT_PRIVATEIMPL_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/client/ClientImportExport.h" +#include <boost/intrusive_ptr.hpp> +#include "qpid/RefCounted.h" + +namespace qpid { +namespace client { + +// FIXME aconway 2009-04-24: details! +/** @file + * + * Helper class to implement a class with a private, reference counted + * implementation and reference semantics. + * + * Such classes are used in the public API to hide implementation, they + * should. Example of use: + * + * === Foo.h + * + * template <class T> PrivateImplRef; + * class FooImpl; + * + * Foo : public Handle<FooImpl> { + * public: + * Foo(FooImpl* = 0); + * Foo(const Foo&); + * ~Foo(); + * Foo& operator=(const Foo&); + * + * int fooDo(); // and other Foo functions... + * + * private: + * typedef FooImpl Impl; + * Impl* impl; + * friend class PrivateImplRef<Foo>; + * + * === Foo.cpp + * + * typedef PrivateImplRef<Foo> PI; + * Foo::Foo(FooImpl* p) { PI::ctor(*this, p); } + * Foo::Foo(const Foo& c) : Handle<FooImpl>() { PI::copy(*this, c); } + * Foo::~Foo() { PI::dtor(*this); } + * Foo& Foo::operator=(const Foo& c) { return PI::assign(*this, c); } + * + * int foo::fooDo() { return impl->fooDo(); } + * + */ +template <class T> class PrivateImplRef { + public: + typedef typename T::Impl Impl; + typedef boost::intrusive_ptr<Impl> intrusive_ptr; + + static intrusive_ptr get(const T& t) { return intrusive_ptr(t.impl); } + + static void set(T& t, const intrusive_ptr& p) { + if (t.impl == p) return; + if (t.impl) boost::intrusive_ptr_release(t.impl); + t.impl = p.get(); + if (t.impl) boost::intrusive_ptr_add_ref(t.impl); + } + + // Helper functions to implement the ctor, dtor, copy, assign + static void ctor(T& t, Impl* p) { t.impl = p; if (p) boost::intrusive_ptr_add_ref(p); } + static void copy(T& t, const T& x) { if (&t == &x) return; t.impl = 0; assign(t, x); } + static void dtor(T& t) { if(t.impl) boost::intrusive_ptr_release(t.impl); } + static T& assign(T& t, const T& x) { set(t, get(x)); return t;} +}; + +}} // namespace qpid::client + +#endif /*!QPID_CLIENT_PRIVATEIMPL_H*/ diff --git a/qpid/cpp/src/qpid/client/QueueOptions.cpp b/qpid/cpp/src/qpid/client/QueueOptions.cpp new file mode 100644 index 0000000000..f4c1483859 --- /dev/null +++ b/qpid/cpp/src/qpid/client/QueueOptions.cpp @@ -0,0 +1,123 @@ +/* + * + * 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/QueueOptions.h" + +namespace qpid { +namespace client { + +enum QueueEventGeneration {ENQUEUE_ONLY=1, ENQUEUE_AND_DEQUEUE=2}; + + +QueueOptions::QueueOptions() +{} + +const std::string QueueOptions::strMaxCountKey("qpid.max_count"); +const std::string QueueOptions::strMaxSizeKey("qpid.max_size"); +const std::string QueueOptions::strTypeKey("qpid.policy_type"); +const std::string QueueOptions::strREJECT("reject"); +const std::string QueueOptions::strFLOW_TO_DISK("flow_to_disk"); +const std::string QueueOptions::strRING("ring"); +const std::string QueueOptions::strRING_STRICT("ring_strict"); +const std::string QueueOptions::strLastValueQueue("qpid.last_value_queue"); +const std::string QueueOptions::strPersistLastNode("qpid.persist_last_node"); +const std::string QueueOptions::strLVQMatchProperty("qpid.LVQ_key"); +const std::string QueueOptions::strLastValueQueueNoBrowse("qpid.last_value_queue_no_browse"); +const std::string QueueOptions::strQueueEventMode("qpid.queue_event_generation"); + + +QueueOptions::~QueueOptions() +{} + +void QueueOptions::setSizePolicy(QueueSizePolicy sp, uint64_t maxSize, uint32_t maxCount) +{ + if (maxCount) setInt(strMaxCountKey, maxCount); + if (maxSize) setInt(strMaxSizeKey, maxSize); + if (maxSize || maxCount){ + switch (sp) + { + case REJECT: + setString(strTypeKey, strREJECT); + break; + case FLOW_TO_DISK: + setString(strTypeKey, strFLOW_TO_DISK); + break; + case RING: + setString(strTypeKey, strRING); + break; + case RING_STRICT: + setString(strTypeKey, strRING_STRICT); + break; + case NONE: + clearSizePolicy(); + break; + } + } +} + + +void QueueOptions::setPersistLastNode() +{ + setInt(strPersistLastNode, 1); +} + +void QueueOptions::setOrdering(QueueOrderingPolicy op) +{ + if (op == LVQ){ + setInt(strLastValueQueue, 1); + }else if (op == LVQ_NO_BROWSE){ + setInt(strLastValueQueueNoBrowse, 1); + }else { + clearOrdering(); + } +} + +void QueueOptions::getLVQKey(std::string& key) +{ + key.assign(strLVQMatchProperty); +} + +void QueueOptions::clearSizePolicy() +{ + erase(strMaxCountKey); + erase(strMaxSizeKey); + erase(strTypeKey); +} + +void QueueOptions::clearPersistLastNode() +{ + erase(strPersistLastNode); +} + +void QueueOptions::clearOrdering() +{ + erase(strLastValueQueue); +} + +void QueueOptions::enableQueueEvents(bool enqueueOnly) +{ + setInt(strQueueEventMode, enqueueOnly ? ENQUEUE_ONLY : ENQUEUE_AND_DEQUEUE); +} + +} +} + + diff --git a/qpid/cpp/src/qpid/client/RdmaConnector.cpp b/qpid/cpp/src/qpid/client/RdmaConnector.cpp new file mode 100644 index 0000000000..664640f5e7 --- /dev/null +++ b/qpid/cpp/src/qpid/client/RdmaConnector.cpp @@ -0,0 +1,431 @@ +/* + * + * 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/Connector.h" + +#include "qpid/client/Bounds.h" +#include "qpid/client/ConnectionImpl.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Time.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/InitiationHandler.h" +#include "qpid/sys/rdma/RdmaIO.h" +#include "qpid/sys/rdma/rdma_exception.h" +#include "qpid/sys/Dispatcher.h" +#include "qpid/sys/Poller.h" +#include "qpid/sys/SecurityLayer.h" +#include "qpid/Msg.h" + +#include <iostream> +#include <boost/bind.hpp> +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> + +// This stuff needs to abstracted out of here to a platform specific file +#include <netdb.h> + +namespace qpid { +namespace client { + +using namespace qpid::sys; +using namespace qpid::framing; +using boost::format; +using boost::str; + +class RdmaConnector : public Connector, public sys::Codec +{ + typedef std::deque<framing::AMQFrame> Frames; + + const uint16_t maxFrameSize; + sys::Mutex lock; + Frames frames; + size_t lastEof; // Position after last EOF in frames + uint64_t currentSize; + Bounds* bounds; + + framing::ProtocolVersion version; + bool initiated; + + sys::Mutex dataConnectedLock; + bool dataConnected; + + sys::ShutdownHandler* shutdownHandler; + framing::InputHandler* input; + framing::InitiationHandler* initialiser; + framing::OutputHandler* output; + + Rdma::AsynchIO* aio; + Rdma::Connector* acon; + sys::Poller::shared_ptr poller; + std::auto_ptr<qpid::sys::SecurityLayer> securityLayer; + + ~RdmaConnector(); + + // Callbacks + void connected(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr, const Rdma::ConnectionParams&); + void connectionError(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr, Rdma::ErrorType); + void disconnected(); + void rejected(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr, const Rdma::ConnectionParams&); + + void readbuff(Rdma::AsynchIO&, Rdma::Buffer*); + void writebuff(Rdma::AsynchIO&); + void writeDataBlock(const framing::AMQDataBlock& data); + void dataError(Rdma::AsynchIO&); + void drained(); + void connectionStopped(Rdma::Connector* acon, Rdma::AsynchIO* aio); + void dataStopped(Rdma::AsynchIO* aio); + + std::string identifier; + + 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 + + void setInputHandler(framing::InputHandler* handler); + void setShutdownHandler(sys::ShutdownHandler* handler); + sys::ShutdownHandler* getShutdownHandler() const; + framing::OutputHandler* getOutputHandler(); + const std::string& getIdentifier() const; + void activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer>); + const qpid::sys::SecuritySettings* getSecuritySettings() { return 0; } + + size_t decode(const char* buffer, size_t size); + size_t encode(const char* buffer, size_t size); + bool canEncode(); + +public: + RdmaConnector(Poller::shared_ptr, + framing::ProtocolVersion pVersion, + const ConnectionSettings&, + ConnectionImpl*); +}; + +// Static constructor which registers connector here +namespace { + Connector* create(Poller::shared_ptr p, framing::ProtocolVersion v, const ConnectionSettings& s, ConnectionImpl* c) { + return new RdmaConnector(p, v, s, c); + } + + struct StaticInit { + StaticInit() { + Connector::registerFactory("rdma", &create); + Connector::registerFactory("ib", &create); + }; + } init; +} + + +RdmaConnector::RdmaConnector(Poller::shared_ptr p, + ProtocolVersion ver, + const ConnectionSettings& settings, + ConnectionImpl* cimpl) + : maxFrameSize(settings.maxFrameSize), + lastEof(0), + currentSize(0), + bounds(cimpl), + version(ver), + initiated(false), + dataConnected(false), + shutdownHandler(0), + aio(0), + acon(0), + poller(p) +{ + QPID_LOG(debug, "RdmaConnector created for " << version); +} + +namespace { + void deleteAsynchIO(Rdma::AsynchIO& aio) { + delete &aio; + } + + void deleteConnector(Rdma::ConnectionManager& con) { + delete &con; + } +} + +RdmaConnector::~RdmaConnector() { + QPID_LOG(debug, "~RdmaConnector " << identifier); + if (aio) { + aio->stop(deleteAsynchIO); + } + if (acon) { + acon->stop(deleteConnector); + } +} + +void RdmaConnector::connect(const std::string& host, const std::string& port){ + Mutex::ScopedLock l(dataConnectedLock); + assert(!dataConnected); + + acon = new Rdma::Connector( + Rdma::ConnectionParams(maxFrameSize, Rdma::DEFAULT_WR_ENTRIES), + boost::bind(&RdmaConnector::connected, this, poller, _1, _2), + boost::bind(&RdmaConnector::connectionError, this, poller, _1, _2), + boost::bind(&RdmaConnector::disconnected, this), + boost::bind(&RdmaConnector::rejected, this, poller, _1, _2)); + + SocketAddress sa(host, port); + acon->start(poller, sa); +} + +// The following only gets run when connected +void RdmaConnector::connected(Poller::shared_ptr poller, Rdma::Connection::intrusive_ptr ci, const Rdma::ConnectionParams& cp) { + try { + Mutex::ScopedLock l(dataConnectedLock); + assert(!dataConnected); + Rdma::QueuePair::intrusive_ptr q = ci->getQueuePair(); + + aio = new Rdma::AsynchIO(ci->getQueuePair(), + cp.rdmaProtocolVersion, + cp.maxRecvBufferSize, cp.initialXmitCredit , Rdma::DEFAULT_WR_ENTRIES, + boost::bind(&RdmaConnector::readbuff, this, _1, _2), + boost::bind(&RdmaConnector::writebuff, this, _1), + 0, // write buffers full + boost::bind(&RdmaConnector::dataError, this, _1)); + + identifier = str(format("[%1% %2%]") % ci->getLocalName() % ci->getPeerName()); + ProtocolInitiation init(version); + writeDataBlock(init); + + aio->start(poller); + + dataConnected = true; + + return; + } catch (const Rdma::Exception& e) { + QPID_LOG(error, "Rdma: Cannot create new connection (Rdma exception): " << e.what()); + } catch (const std::exception& e) { + QPID_LOG(error, "Rdma: Cannot create new connection (unknown exception): " << e.what()); + } + dataConnected = false; + connectionStopped(acon, aio); +} + +void RdmaConnector::connectionError(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr, Rdma::ErrorType) { + QPID_LOG(debug, "Connection Error " << identifier); + connectionStopped(acon, aio); +} + +// Bizarrely we seem to get rejected events *after* we've already got a connected event for some peer disconnects +// so we need to check whether the data connection is started or not in here +void RdmaConnector::rejected(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr, const Rdma::ConnectionParams& cp) { + QPID_LOG(debug, "Connection Rejected " << identifier << ": " << cp.maxRecvBufferSize); + if (dataConnected) { + disconnected(); + } else { + connectionStopped(acon, aio); + } +} + +void RdmaConnector::disconnected() { + QPID_LOG(debug, "Connection disconnected " << identifier); + { + Mutex::ScopedLock l(dataConnectedLock); + // If we're closed already then we'll get to drained() anyway + if (!dataConnected) return; + dataConnected = false; + } + // Make sure that all the disconnected actions take place on the data "thread" + aio->requestCallback(boost::bind(&RdmaConnector::drained, this)); +} + +void RdmaConnector::dataError(Rdma::AsynchIO&) { + QPID_LOG(debug, "Data Error " << identifier); + { + Mutex::ScopedLock l(dataConnectedLock); + // If we're closed already then we'll get to drained() anyway + if (!dataConnected) return; + dataConnected = false; + } + drained(); +} + +void RdmaConnector::close() { + QPID_LOG(debug, "RdmaConnector::close " << identifier); + { + Mutex::ScopedLock l(dataConnectedLock); + if (!dataConnected) return; + dataConnected = false; + } + aio->drainWriteQueue(boost::bind(&RdmaConnector::drained, this)); +} + +void RdmaConnector::drained() { + QPID_LOG(debug, "RdmaConnector::drained " << identifier); + assert(!dataConnected); + assert(aio); + Rdma::AsynchIO* a = aio; + aio = 0; + a->stop(boost::bind(&RdmaConnector::dataStopped, this, a)); +} + +void RdmaConnector::dataStopped(Rdma::AsynchIO* a) { + QPID_LOG(debug, "RdmaConnector::dataStopped " << identifier); + assert(!dataConnected); + assert(acon); + Rdma::Connector* c = acon; + acon = 0; + c->stop(boost::bind(&RdmaConnector::connectionStopped, this, c, a)); +} + +void RdmaConnector::connectionStopped(Rdma::Connector* c, Rdma::AsynchIO* a) { + QPID_LOG(debug, "RdmaConnector::connectionStopped " << identifier); + assert(!dataConnected); + aio = 0; + acon = 0; + delete a; + delete c; + if (shutdownHandler) { + ShutdownHandler* s = shutdownHandler; + shutdownHandler = 0; + s->shutdown(); + } +} + +void RdmaConnector::setInputHandler(InputHandler* handler){ + input = handler; +} + +void RdmaConnector::setShutdownHandler(ShutdownHandler* handler){ + shutdownHandler = handler; +} + +OutputHandler* RdmaConnector::getOutputHandler(){ + return this; +} + +sys::ShutdownHandler* RdmaConnector::getShutdownHandler() const { + return shutdownHandler; +} + +const std::string& RdmaConnector::getIdentifier() const { + return identifier; +} + +void RdmaConnector::send(AMQFrame& frame) { + // It is possible that we are called to write after we are already shutting down + Mutex::ScopedLock l(dataConnectedLock); + if (!dataConnected) return; + + bool notifyWrite = false; + { + Mutex::ScopedLock l(lock); + frames.push_back(frame); + //only ask to write if this is the end of a frameset or if we + //already have a buffers worth of data + currentSize += frame.encodedSize(); + if (frame.getEof()) { + lastEof = frames.size(); + notifyWrite = true; + } else { + notifyWrite = (currentSize >= maxFrameSize); + } + } + if (notifyWrite) aio->notifyPendingWrite(); +} + +// Called in IO thread. (write idle routine) +// This is NOT only called in response to previously calling notifyPendingWrite +void RdmaConnector::writebuff(Rdma::AsynchIO&) { + // It's possible to be disconnected and be writable + Mutex::ScopedLock l(dataConnectedLock); + if (!dataConnected) { + return; + } + Codec* codec = securityLayer.get() ? (Codec*) securityLayer.get() : (Codec*) this; + if (!codec->canEncode()) { + return; + } + Rdma::Buffer* buffer = aio->getSendBuffer(); + if (buffer) { + size_t encoded = codec->encode(buffer->bytes(), buffer->byteCount()); + buffer->dataCount(encoded); + aio->queueWrite(buffer); + } +} + +bool RdmaConnector::canEncode() +{ + Mutex::ScopedLock l(lock); + //have at least one full frameset or a whole buffers worth of data + return aio->writable() && (lastEof || currentSize >= maxFrameSize); +} + +size_t RdmaConnector::encode(const char* buffer, size_t size) +{ + framing::Buffer out(const_cast<char*>(buffer), size); + size_t bytesWritten(0); + { + Mutex::ScopedLock l(lock); + while (!frames.empty() && out.available() >= frames.front().encodedSize() ) { + frames.front().encode(out); + QPID_LOG(trace, "SENT " << identifier << ": " << frames.front()); + frames.pop_front(); + if (lastEof) --lastEof; + } + bytesWritten = size - out.available(); + currentSize -= bytesWritten; + } + if (bounds) bounds->reduce(bytesWritten); + return bytesWritten; +} + +void RdmaConnector::readbuff(Rdma::AsynchIO&, Rdma::Buffer* buff) { + Codec* codec = securityLayer.get() ? (Codec*) securityLayer.get() : (Codec*) this; + codec->decode(buff->bytes(), buff->dataCount()); +} + +size_t RdmaConnector::decode(const char* buffer, size_t size) +{ + framing::Buffer in(const_cast<char*>(buffer), size); + if (!initiated) { + framing::ProtocolInitiation protocolInit; + if (protocolInit.decode(in)) { + //TODO: check the version is correct + QPID_LOG(debug, "RECV " << identifier << " INIT(" << protocolInit << ")"); + } + initiated = true; + } + AMQFrame frame; + while(frame.decode(in)){ + QPID_LOG(trace, "RECV " << identifier << ": " << frame); + input->received(frame); + } + return size - in.available(); +} + +void RdmaConnector::writeDataBlock(const AMQDataBlock& data) { + Rdma::Buffer* buff = aio->getSendBuffer(); + framing::Buffer out(buff->bytes(), buff->byteCount()); + data.encode(out); + buff->dataCount(data.encodedSize()); + aio->queueWrite(buff); +} + +void RdmaConnector::activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer> sl) +{ + securityLayer = sl; + securityLayer->init(this); +} + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/Results.cpp b/qpid/cpp/src/qpid/client/Results.cpp new file mode 100644 index 0000000000..0de3e8bd04 --- /dev/null +++ b/qpid/cpp/src/qpid/client/Results.cpp @@ -0,0 +1,75 @@ +/* + * + * 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/Results.h" +#include "qpid/client/FutureResult.h" +#include "qpid/framing/SequenceSet.h" + +using namespace qpid::framing; + +namespace qpid { +namespace client { + +Results::Results() {} + +Results::~Results() { + try { close(); } catch (const std::exception& /*e*/) { assert(0); } +} + +void Results::close() +{ + for (Listeners::iterator i = listeners.begin(); i != listeners.end(); i++) { + i->second->completed(); + } + listeners.clear(); +} + +void Results::completed(const SequenceSet& set) +{ + //call complete on those listeners whose ids fall within the set + Listeners::iterator i = listeners.begin(); + while (i != listeners.end()) { + if (set.contains(i->first)) { + i->second->completed(); + listeners.erase(i++); + } else { + i++; + } + } +} + +void Results::received(const SequenceNumber& id, const std::string& result) +{ + Listeners::iterator i = listeners.find(id); + if (i != listeners.end()) { + i->second->received(result); + listeners.erase(i); + } +} + +Results::FutureResultPtr Results::listenForResult(const SequenceNumber& id) +{ + FutureResultPtr l(new FutureResult()); + listeners[id] = l; + return l; +} + +}} diff --git a/qpid/cpp/src/qpid/client/Results.h b/qpid/cpp/src/qpid/client/Results.h new file mode 100644 index 0000000000..4c49f6b05b --- /dev/null +++ b/qpid/cpp/src/qpid/client/Results.h @@ -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 "qpid/framing/SequenceNumber.h" +#include <map> +#include <boost/shared_ptr.hpp> + +#ifndef _Results_ +#define _Results_ + +namespace qpid { +namespace client { + +class FutureResult; + +///@internal +class Results +{ +public: + typedef boost::shared_ptr<FutureResult> FutureResultPtr; + + Results(); + ~Results(); + void completed(const framing::SequenceSet& set); + void received(const framing::SequenceNumber& id, const std::string& result); + FutureResultPtr listenForResult(const framing::SequenceNumber& point); + void close(); + +private: + typedef std::map<framing::SequenceNumber, FutureResultPtr> Listeners; + Listeners listeners; +}; + +} +} + + +#endif diff --git a/qpid/cpp/src/qpid/client/SessionBase_0_10.cpp b/qpid/cpp/src/qpid/client/SessionBase_0_10.cpp new file mode 100644 index 0000000000..e114b7aacc --- /dev/null +++ b/qpid/cpp/src/qpid/client/SessionBase_0_10.cpp @@ -0,0 +1,85 @@ +/* + * + * 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/SessionBase_0_10.h" +#include "qpid/client/Connection.h" +#include "qpid/client/ConnectionAccess.h" +#include "qpid/client/SessionImpl.h" +#include "qpid/client/Future.h" +#include "qpid/framing/all_method_bodies.h" + +namespace qpid { +namespace client { + +using namespace framing; + +SessionBase_0_10::SessionBase_0_10() {} +SessionBase_0_10::~SessionBase_0_10() {} + +void SessionBase_0_10::close() +{ + if (impl) impl->close(); +} + +void SessionBase_0_10::flush() +{ + impl->sendFlush(); +} + +void SessionBase_0_10::sync() +{ + ExecutionSyncBody b; + b.setSync(true); + impl->send(b).wait(*impl); +} + +void SessionBase_0_10::markCompleted(const framing::SequenceSet& ids, bool notifyPeer) +{ + impl->markCompleted(ids, notifyPeer); +} + +void SessionBase_0_10::markCompleted(const framing::SequenceNumber& id, bool cumulative, bool notifyPeer) +{ + impl->markCompleted(id, cumulative, notifyPeer); +} + +void SessionBase_0_10::sendCompletion() +{ + impl->sendCompletion(); +} + +uint16_t SessionBase_0_10::getChannel() const { return impl->getChannel(); } + +void SessionBase_0_10::suspend() { impl->suspend(); } +void SessionBase_0_10::resume(Connection c) { impl->resume(c.impl); } +uint32_t SessionBase_0_10::timeout(uint32_t seconds) { return impl->setTimeout(seconds); } + +SessionId SessionBase_0_10::getId() const { return impl->getId(); } + +bool SessionBase_0_10::isValid() const { return impl; } + +Connection SessionBase_0_10::getConnection() +{ + Connection c; + ConnectionAccess::setImpl(c, impl->getConnection()); + return c; +} + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/SessionBase_0_10Access.h b/qpid/cpp/src/qpid/client/SessionBase_0_10Access.h new file mode 100644 index 0000000000..4d08a7ceaf --- /dev/null +++ b/qpid/cpp/src/qpid/client/SessionBase_0_10Access.h @@ -0,0 +1,42 @@ +#ifndef QPID_CLIENT_SESSIONBASEACCESS_H +#define QPID_CLIENT_SESSIONBASEACCESS_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/client/SessionBase_0_10.h" + +/**@file @internal Internal use only */ + +namespace qpid { +namespace client { + +class SessionBase_0_10Access { + public: + SessionBase_0_10Access(SessionBase_0_10& sb_) : sb(sb_) {} + void set(const boost::shared_ptr<SessionImpl>& si) { sb.impl = si; } + boost::shared_ptr<SessionImpl> get() const { return sb.impl; } + private: + SessionBase_0_10& sb; +}; +}} // namespace qpid::client + +#endif /*!QPID_CLIENT_SESSIONBASEACCESS_H*/ diff --git a/qpid/cpp/src/qpid/client/SessionImpl.cpp b/qpid/cpp/src/qpid/client/SessionImpl.cpp new file mode 100644 index 0000000000..b507625b11 --- /dev/null +++ b/qpid/cpp/src/qpid/client/SessionImpl.cpp @@ -0,0 +1,824 @@ +/* + * + * 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/SessionImpl.h" + +#include "qpid/client/ConnectionImpl.h" +#include "qpid/client/Future.h" + +#include "qpid/framing/all_method_bodies.h" +#include "qpid/framing/ClientInvoker.h" +#include "qpid/framing/enum.h" +#include "qpid/framing/FrameSet.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/MethodContent.h" +#include "qpid/framing/SequenceSet.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/DeliveryProperties.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/IntegerTypes.h" + +#include <boost/bind.hpp> +#include <boost/shared_ptr.hpp> + +namespace { const std::string EMPTY; } + +namespace qpid { +namespace client { + +using namespace qpid::framing; +using namespace qpid::framing::session; //for detach codes + +typedef sys::Monitor::ScopedLock Lock; +typedef sys::Monitor::ScopedUnlock UnLock; +typedef sys::ScopedLock<sys::Semaphore> Acquire; + + +SessionImpl::SessionImpl(const std::string& name, boost::shared_ptr<ConnectionImpl> conn) + : state(INACTIVE), + detachedLifetime(0), + maxFrameSize(conn->getNegotiatedSettings().maxFrameSize), + id(conn->getNegotiatedSettings().username, name.empty() ? Uuid(true).str() : name), + connection(conn), + ioHandler(*this), + proxy(ioHandler), + nextIn(0), + nextOut(0), + sendMsgCredit(0), + doClearDeliveryPropertiesExchange(true), + autoDetach(true) +{ + channel.next = connection.get(); +} + +SessionImpl::~SessionImpl() { + { + Lock l(state); + if (state != DETACHED && state != DETACHING) { + if (autoDetach) { + QPID_LOG(warning, "Session was not closed cleanly: " << id); + // Inform broker but don't wait for detached as that deadlocks. + // The detached will be ignored as the channel will be invalid. + try { detach(); } catch (...) {} // ignore errors. + } + setState(DETACHED); + handleClosed(); + state.waitWaiters(); + } + delete sendMsgCredit; + } + connection->erase(channel); +} + + +FrameSet::shared_ptr SessionImpl::get() // user thread +{ + // No lock here: pop does a blocking wait. + return demux.getDefault()->pop(); +} + +const SessionId SessionImpl::getId() const //user thread +{ + return id; //id is immutable +} + +void SessionImpl::open(uint32_t timeout) // user thread +{ + Lock l(state); + if (state == INACTIVE) { + setState(ATTACHING); + proxy.attach(id.getName(), false); + waitFor(ATTACHED); + //TODO: timeout will not be set locally until get response to + //confirm, should we wait for that? + setTimeout(timeout); + proxy.commandPoint(nextOut, 0); + } else { + throw Exception("Open already called for this session"); + } +} + +void SessionImpl::close() //user thread +{ + Lock l(state); + // close() must be idempotent and no-throw as it will often be called in destructors. + if (state != DETACHED && state != DETACHING) { + try { + if (detachedLifetime) setTimeout(0); + detach(); + waitFor(DETACHED); + } catch (...) {} + setState(DETACHED); + } +} + +void SessionImpl::resume(boost::shared_ptr<ConnectionImpl>) // user thread +{ + throw NotImplementedException("Resume not yet implemented by client!"); +} + +void SessionImpl::suspend() //user thread +{ + Lock l(state); + detach(); +} + +void SessionImpl::detach() //call with lock held +{ + if (state == ATTACHED) { + setState(DETACHING); + proxy.detach(id.getName()); + } +} + + +uint16_t SessionImpl::getChannel() const // user thread +{ + return channel; +} + +void SessionImpl::setChannel(uint16_t c) // user thread +{ + //channel will only ever be set when session is detached (and + //about to be resumed) + channel = c; +} + +Demux& SessionImpl::getDemux() +{ + return demux; +} + +void SessionImpl::waitForCompletion(const SequenceNumber& id) +{ + Lock l(state); + waitForCompletionImpl(id); +} + +void SessionImpl::waitForCompletionImpl(const SequenceNumber& id) //call with lock held +{ + while (incompleteOut.contains(id)) { + checkOpen(); + state.wait(); + } +} + +bool SessionImpl::isComplete(const SequenceNumber& id) +{ + Lock l(state); + return !incompleteOut.contains(id); +} + +struct IsCompleteUpTo +{ + const SequenceNumber& id; + bool result; + + IsCompleteUpTo(const SequenceNumber& _id) : id(_id), result(true) {} + void operator()(const SequenceNumber& start, const SequenceNumber&) + { + if (start <= id) result = false; + } + +}; + +bool SessionImpl::isCompleteUpTo(const SequenceNumber& id) +{ + Lock l(state); + //return false if incompleteOut contains anything less than id, + //true otherwise + IsCompleteUpTo f(id); + incompleteIn.for_each(f); + return f.result; +} + +framing::SequenceNumber SessionImpl::getCompleteUpTo() +{ + SequenceNumber firstIncomplete; + { + Lock l(state); + firstIncomplete = incompleteIn.front(); + } + return --firstIncomplete; +} + +struct MarkCompleted +{ + const SequenceNumber& id; + SequenceSet& completedIn; + + MarkCompleted(const SequenceNumber& _id, SequenceSet& set) : id(_id), completedIn(set) {} + + void operator()(const SequenceNumber& start, const SequenceNumber& end) + { + if (id >= end) { + completedIn.add(start, end); + } else if (id >= start) { + completedIn.add(start, id); + } + } + +}; + +void SessionImpl::markCompleted(const SequenceSet& ids, bool notifyPeer) +{ + Lock l(state); + incompleteIn.remove(ids); + completedIn.add(ids); + if (notifyPeer) { + sendCompletion(); + } +} + +void SessionImpl::markCompleted(const SequenceNumber& id, bool cumulative, bool notifyPeer) +{ + Lock l(state); + if (cumulative) { + //everything in incompleteIn less than or equal to id is now complete + MarkCompleted f(id, completedIn); + incompleteIn.for_each(f); + //make sure id itself is in + completedIn.add(id); + //then remove anything thats completed from the incomplete set + incompleteIn.remove(completedIn); + } else if (incompleteIn.contains(id)) { + incompleteIn.remove(id); + completedIn.add(id); + } + if (notifyPeer) { + sendCompletion(); + } +} + +void SessionImpl::setException(const sys::ExceptionHolder& ex) { + Lock l(state); + setExceptionLH(ex); +} + +void SessionImpl::setExceptionLH(const sys::ExceptionHolder& ex) { // Call with lock held. + exceptionHolder = ex; + setState(DETACHED); +} + +/** + * Called by ConnectionImpl to notify active sessions when connection + * is explictly closed + */ +void SessionImpl::connectionClosed(uint16_t code, const std::string& text) { + setException(createConnectionException(code, text)); + handleClosed(); +} + +/** + * Called by ConnectionImpl to notify active sessions when connection + * is disconnected + */ +void SessionImpl::connectionBroke(const std::string& _text) { + setException(sys::ExceptionHolder(new TransportFailure(_text))); + handleClosed(); +} + +Future SessionImpl::send(const AMQBody& command) +{ + return sendCommand(command); +} + +Future SessionImpl::send(const AMQBody& command, const MethodContent& content) +{ + return sendCommand(command, &content); +} + +namespace { +// Functor for FrameSet::map to send header + content frames but, not method frames. +struct SendContentFn { + FrameHandler& handler; + void operator()(const AMQFrame& f) { + if (!f.getMethod()) + handler(const_cast<AMQFrame&>(f)); + } + SendContentFn(FrameHandler& h) : handler(h) {} +}; + +// Adaptor to make FrameSet look like MethodContent; used in cluster update client +struct MethodContentAdaptor : MethodContent +{ + AMQHeaderBody header; + const std::string content; + + MethodContentAdaptor(const FrameSet& f) : header(*f.getHeaders()), content(f.getContent()) {} + + AMQHeaderBody getHeader() const + { + return header; + } + const std::string& getData() const + { + return content; + } +}; + +} + +Future SessionImpl::send(const AMQBody& command, const FrameSet& content, bool reframe) { + Acquire a(sendLock); + SequenceNumber id = nextOut++; + { + Lock l(state); + checkOpen(); + incompleteOut.add(id); + } + Future f(id); + if (command.getMethod()->resultExpected()) { + Lock l(state); + //result listener must be set before the command is sent + f.setFutureResult(results.listenForResult(id)); + } + AMQFrame frame(command); + frame.setEof(false); + handleOut(frame); + + if (reframe) { + MethodContentAdaptor c(content); + sendContent(c); + } else { + SendContentFn send(out); + content.map(send); + } + return f; +} + +void SessionImpl::sendRawFrame(AMQFrame& frame) { + Acquire a(sendLock); + handleOut(frame); +} + +Future SessionImpl::sendCommand(const AMQBody& command, const MethodContent* content) +{ + // Only message transfers have content + if (content && sendMsgCredit) { + sendMsgCredit->acquire(); + } + Acquire a(sendLock); + SequenceNumber id = nextOut++; + { + Lock l(state); + checkOpen(); + incompleteOut.add(id); + } + Future f(id); + if (command.getMethod()->resultExpected()) { + Lock l(state); + //result listener must be set before the command is sent + f.setFutureResult(results.listenForResult(id)); + } + AMQFrame frame(command); + if (content) { + frame.setEof(false); + } + handleOut(frame); + if (content) { + sendContent(*content); + } + return f; +} + +void SessionImpl::sendContent(const MethodContent& content) +{ + AMQFrame header(content.getHeader()); + + // doClearDeliveryPropertiesExchange is set by cluster update client so + // it can send messages with delivery-properties.exchange set. + // + if (doClearDeliveryPropertiesExchange) { + // Normal client is not allowed to set the delivery-properties.exchange + // so clear it here. + AMQHeaderBody* headerp = static_cast<AMQHeaderBody*>(header.getBody()); + if (headerp && headerp->get<DeliveryProperties>()) + headerp->get<DeliveryProperties>(true)->clearExchangeFlag(); + } + header.setFirstSegment(false); + uint64_t data_length = content.getData().length(); + if(data_length > 0){ + header.setLastSegment(false); + handleOut(header); + /*Note: end of frame marker included in overhead but not in size*/ + const uint32_t frag_size = maxFrameSize - AMQFrame::frameOverhead(); + + if(data_length < frag_size){ + AMQFrame frame((AMQContentBody(content.getData()))); + frame.setFirstSegment(false); + handleOut(frame); + }else{ + uint32_t offset = 0; + uint32_t remaining = data_length - offset; + while (remaining > 0) { + uint32_t length = remaining > frag_size ? frag_size : remaining; + string frag(content.getData().substr(offset, length)); + AMQFrame frame((AMQContentBody(frag))); + frame.setFirstSegment(false); + frame.setLastSegment(true); + if (offset > 0) { + frame.setFirstFrame(false); + } + offset += length; + remaining = data_length - offset; + if (remaining) { + frame.setLastFrame(false); + } + handleOut(frame); + } + } + } else { + handleOut(header); + } +} + + +bool isMessageMethod(AMQMethodBody* method) +{ + return method->isA<MessageTransferBody>(); +} + +bool isMessageMethod(AMQBody* body) +{ + AMQMethodBody* method=body->getMethod(); + return method && isMessageMethod(method); +} + +bool isContentFrame(AMQFrame& frame) +{ + AMQBody* body = frame.getBody(); + uint8_t type = body->type(); + return type == HEADER_BODY || type == CONTENT_BODY || isMessageMethod(body); +} + +void SessionImpl::handleIn(AMQFrame& frame) // network thread +{ + try { + if (invoke(static_cast<SessionHandler&>(*this), *frame.getBody())) { + ; + } else if (invoke(static_cast<ExecutionHandler&>(*this), *frame.getBody())) { + //make sure the command id sequence and completion + //tracking takes account of execution commands + Lock l(state); + completedIn.add(nextIn++); + } else if (invoke(static_cast<MessageHandler&>(*this), *frame.getBody())) { + ; + } else { + //if not handled by this class, its for the application: + deliver(frame); + } + } + catch (const SessionException& e) { + setException(createSessionException(e.code, e.getMessage())); + } + catch (const ChannelException& e) { + setException(createChannelException(e.code, e.getMessage())); + } +} + +void SessionImpl::handleOut(AMQFrame& frame) // user thread +{ + sendFrame(frame, true); +} + +void SessionImpl::proxyOut(AMQFrame& frame) // network thread +{ + //Note: this case is treated slightly differently that command + //frames sent by application; session controls should not be + //blocked by bounds checking on the outgoing frame queue. + sendFrame(frame, false); +} + +void SessionImpl::sendFrame(AMQFrame& frame, bool canBlock) +{ + connection->expand(frame.encodedSize(), canBlock); + channel.handle(frame); +} + +void SessionImpl::deliver(AMQFrame& frame) // network thread +{ + if (!arriving) { + arriving = FrameSet::shared_ptr(new FrameSet(nextIn++)); + } + arriving->append(frame); + if (arriving->isComplete()) { + //message.transfers will be marked completed only when 'acked' + //as completion affects flow control; other commands will be + //considered completed as soon as processed here + if (arriving->isA<MessageTransferBody>()) { + Lock l(state); + incompleteIn.add(arriving->getId()); + } else { + Lock l(state); + completedIn.add(arriving->getId()); + } + demux.handle(arriving); + arriving.reset(); + } +} + +//control handler methods (called by network thread when controls are +//received from peer): + +void SessionImpl::attach(const std::string& /*name*/, bool /*force*/) +{ + throw NotImplementedException("Client does not support attach"); +} + +void SessionImpl::attached(const std::string& _name) +{ + Lock l(state); + if (id.getName() != _name) throw InternalErrorException("Incorrect session name"); + setState(ATTACHED); +} + +void SessionImpl::detach(const std::string& _name) +{ + Lock l(state); + if (id.getName() != _name) throw InternalErrorException("Incorrect session name"); + setState(DETACHED); + QPID_LOG(info, "Session detached by peer: " << id); + proxy.detached(_name, DETACH_CODE_NORMAL); + handleClosed(); +} + +void SessionImpl::detached(const std::string& _name, uint8_t _code) { + Lock l(state); + if (id.getName() != _name) throw InternalErrorException("Incorrect session name"); + setState(DETACHED); + if (_code) { + //TODO: make sure this works with execution.exception - don't + //want to overwrite the code from that + setExceptionLH(createChannelException(_code, "Session detached by peer")); + QPID_LOG(error, exceptionHolder.what()); + } + if (detachedLifetime == 0) { + handleClosed(); +} +} + +void SessionImpl::requestTimeout(uint32_t t) +{ + Lock l(state); + detachedLifetime = t; + proxy.timeout(t); +} + +void SessionImpl::timeout(uint32_t t) +{ + Lock l(state); + detachedLifetime = t; +} + +void SessionImpl::commandPoint(const framing::SequenceNumber& id, uint64_t offset) +{ + if (offset) throw NotImplementedException("Non-zero byte offset not yet supported for command-point"); + + Lock l(state); + nextIn = id; +} + +void SessionImpl::expected(const framing::SequenceSet& commands, const framing::Array& fragments) +{ + if (!commands.empty() || fragments.encodedSize()) { + throw NotImplementedException("Session resumption not yet supported"); + } +} + +void SessionImpl::confirmed(const framing::SequenceSet& /*commands*/, const framing::Array& /*fragments*/) +{ + //don't really care too much about this yet +} + +void SessionImpl::completed(const framing::SequenceSet& commands, bool timelyReply) +{ + Lock l(state); + incompleteOut.remove(commands); + state.notifyAll();//notify any waiters of completion + completedOut.add(commands); + //notify any waiting results of completion + results.completed(commands); + + if (timelyReply) { + proxy.knownCompleted(completedOut); + completedOut.clear(); + } +} + +void SessionImpl::knownCompleted(const framing::SequenceSet& commands) +{ + Lock l(state); + completedIn.remove(commands); +} + +void SessionImpl::flush(bool expected, bool confirmed, bool completed) +{ + Lock l(state); + if (expected) { + proxy.expected(SequenceSet(nextIn), Array()); + } + if (confirmed) { + proxy.confirmed(completedIn, Array()); + } + if (completed) { + proxy.completed(completedIn, true); + } +} + +void SessionImpl::sendCompletion() +{ + Lock l(state); + sendCompletionImpl(); +} + +void SessionImpl::sendFlush() +{ + Lock l(state); + proxy.flush(false, false, true); +} + +void SessionImpl::sendCompletionImpl() +{ + proxy.completed(completedIn, completedIn.span() > 1000); +} + +void SessionImpl::gap(const framing::SequenceSet& /*commands*/) +{ + throw NotImplementedException("gap not yet supported"); +} + +void SessionImpl::sync() {} + +void SessionImpl::result(const framing::SequenceNumber& commandId, const std::string& value) +{ + Lock l(state); + results.received(commandId, value); +} + +void SessionImpl::exception(uint16_t errorCode, + const framing::SequenceNumber& commandId, + uint8_t classCode, + uint8_t commandCode, + uint8_t /*fieldIndex*/, + const std::string& description, + const framing::FieldTable& /*errorInfo*/) +{ + Lock l(state); + setExceptionLH(createSessionException(errorCode, description)); + QPID_LOG(warning, "Exception received from broker: " << exceptionHolder.what() + << " [caused by " << commandId << " " << classCode << ":" << commandCode << "]"); + + if (detachedLifetime) + setTimeout(0); +} + +// Message methods: +void SessionImpl::accept(const qpid::framing::SequenceSet&) +{ +} + +void SessionImpl::reject(const qpid::framing::SequenceSet&, uint16_t, const std::string&) +{ +} + +void SessionImpl::release(const qpid::framing::SequenceSet&, bool) +{ +} + +MessageResumeResult SessionImpl::resume(const std::string&, const std::string&) +{ + throw NotImplementedException("resuming transfers not yet supported"); +} + +namespace { + const std::string QPID_SESSION_DEST = ""; + const uint8_t FLOW_MODE_CREDIT = 0; + const uint8_t CREDIT_MODE_MSG = 0; +} + +void SessionImpl::setFlowMode(const std::string& dest, uint8_t flowMode) +{ + if ( dest != QPID_SESSION_DEST ) { + QPID_LOG(warning, "Ignoring flow control for unknown destination: " << dest); + return; + } + + if ( flowMode != FLOW_MODE_CREDIT ) { + throw NotImplementedException("window flow control mode not supported by producer"); + } + Lock l(state); + sendMsgCredit = new sys::Semaphore(0); +} + +void SessionImpl::flow(const std::string& dest, uint8_t mode, uint32_t credit) +{ + if ( dest != QPID_SESSION_DEST ) { + QPID_LOG(warning, "Ignoring flow control for unknown destination: " << dest); + return; + } + + if ( mode != CREDIT_MODE_MSG ) { + return; + } + if (sendMsgCredit) { + sendMsgCredit->release(credit); + } +} + +void SessionImpl::stop(const std::string& dest) +{ + if ( dest != QPID_SESSION_DEST ) { + QPID_LOG(warning, "Ignoring flow control for unknown destination: " << dest); + return; + } + if (sendMsgCredit) { + sendMsgCredit->forceLock(); + } +} + +//private utility methods: + +inline void SessionImpl::setState(State s) //call with lock held +{ + state = s; +} + +inline void SessionImpl::waitFor(State s) //call with lock held +{ + // We can be DETACHED at any time + if (s == DETACHED) state.waitFor(DETACHED); + else state.waitFor(States(s, DETACHED)); + check(); +} + +void SessionImpl::check() const //call with lock held. +{ + exceptionHolder.raise(); +} + +void SessionImpl::checkOpen() const //call with lock held. +{ + check(); + if (state != ATTACHED) { + throw NotAttachedException(QPID_MSG("Session " << getId() << " isn't attached")); + } +} + +void SessionImpl::assertOpen() const +{ + Lock l(state); + checkOpen(); +} + +bool SessionImpl::hasError() const +{ + Lock l(state); + return !exceptionHolder.empty(); +} + +void SessionImpl::handleClosed() +{ + demux.close(exceptionHolder.empty() ? + sys::ExceptionHolder(new ClosedException()) : exceptionHolder); + results.close(); +} + +uint32_t SessionImpl::setTimeout(uint32_t seconds) { + proxy.requestTimeout(seconds); + // FIXME aconway 2008-10-07: wait for timeout response from broker + // and use value retured by broker. + detachedLifetime = seconds; + return detachedLifetime; +} + +uint32_t SessionImpl::getTimeout() const { + return detachedLifetime; +} + +boost::shared_ptr<ConnectionImpl> SessionImpl::getConnection() +{ + return connection; +} + +void SessionImpl::disableAutoDetach() { autoDetach = false; } + +}} diff --git a/qpid/cpp/src/qpid/client/SessionImpl.h b/qpid/cpp/src/qpid/client/SessionImpl.h new file mode 100644 index 0000000000..cd7b2c123d --- /dev/null +++ b/qpid/cpp/src/qpid/client/SessionImpl.h @@ -0,0 +1,254 @@ +/* + * + * 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 _SessionImpl_ +#define _SessionImpl_ + +#include "qpid/client/Demux.h" +#include "qpid/client/Execution.h" +#include "qpid/client/Results.h" +#include "qpid/client/ClientImportExport.h" + +#include "qpid/SessionId.h" +#include "qpid/SessionState.h" +#include "qpid/framing/FrameHandler.h" +#include "qpid/framing/ChannelHandler.h" +#include "qpid/framing/SequenceNumber.h" +#include "qpid/framing/AMQP_ClientOperations.h" +#include "qpid/framing/AMQP_ServerProxy.h" +#include "qpid/sys/Semaphore.h" +#include "qpid/sys/StateMonitor.h" +#include "qpid/sys/ExceptionHolder.h" + +#include <boost/weak_ptr.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/optional.hpp> + +namespace qpid { + +namespace framing { + +class FrameSet; +class MethodContent; +class SequenceSet; + +} + +namespace client { + +class Future; +class ConnectionImpl; +class SessionHandler; + +///@internal +class SessionImpl : public framing::FrameHandler::InOutHandler, + public Execution, + private framing::AMQP_ClientOperations::SessionHandler, + private framing::AMQP_ClientOperations::ExecutionHandler, + private framing::AMQP_ClientOperations::MessageHandler +{ +public: + SessionImpl(const std::string& name, boost::shared_ptr<ConnectionImpl>); + ~SessionImpl(); + + + //NOTE: Public functions called in user thread. + framing::FrameSet::shared_ptr get(); + + const SessionId getId() const; + + uint16_t getChannel() const; + void setChannel(uint16_t channel); + + void open(uint32_t detachedLifetime); + void close(); + void resume(boost::shared_ptr<ConnectionImpl>); + void suspend(); + + QPID_CLIENT_EXTERN void assertOpen() const; + QPID_CLIENT_EXTERN bool hasError() const; + + Future send(const framing::AMQBody& command); + Future send(const framing::AMQBody& command, const framing::MethodContent& content); + /** + * This method takes the content as a FrameSet; if reframe=false, + * the caller is resposnible for ensuring that the header and + * content frames in that set are correct for this connection + * (right flags, right fragmentation etc). If reframe=true, then + * the header and content from the frameset will be copied and + * reframed correctly for the connection. + */ + QPID_CLIENT_EXTERN Future send(const framing::AMQBody& command, const framing::FrameSet& content, bool reframe=false); + void sendRawFrame(framing::AMQFrame& frame); + + Demux& getDemux(); + void markCompleted(const framing::SequenceNumber& id, bool cumulative, bool notifyPeer); + void markCompleted(const framing::SequenceSet& ids, bool notifyPeer); + bool isComplete(const framing::SequenceNumber& id); + bool isCompleteUpTo(const framing::SequenceNumber& id); + framing::SequenceNumber getCompleteUpTo(); + void waitForCompletion(const framing::SequenceNumber& id); + void sendCompletion(); + void sendFlush(); + + void setException(const sys::ExceptionHolder&); + + //NOTE: these are called by the network thread when the connection is closed or dies + void connectionClosed(uint16_t code, const std::string& text); + void connectionBroke(const std::string& text); + + /** Set timeout in seconds, returns actual timeout allowed by broker */ + uint32_t setTimeout(uint32_t requestedSeconds); + + /** Get timeout in seconds. */ + uint32_t getTimeout() const; + + /** + * get the Connection associated with this connection + */ + boost::shared_ptr<ConnectionImpl> getConnection(); + + void setDoClearDeliveryPropertiesExchange(bool b=true) { doClearDeliveryPropertiesExchange = b; } + + /** Suppress sending detach in destructor. Used by cluster to build session state */ + void disableAutoDetach(); + +private: + enum State { + INACTIVE, + ATTACHING, + ATTACHED, + DETACHING, + DETACHED + }; + typedef framing::AMQP_ClientOperations::SessionHandler SessionHandler; + typedef framing::AMQP_ClientOperations::ExecutionHandler ExecutionHandler; + typedef framing::AMQP_ClientOperations::MessageHandler MessageHandler; + typedef sys::StateMonitor<State, DETACHED> StateMonitor; + typedef StateMonitor::Set States; + + inline void setState(State s); + inline void waitFor(State); + + void setExceptionLH(const sys::ExceptionHolder&); // LH = lock held when called. + void detach(); + + void check() const; + void checkOpen() const; + void handleClosed(); + + void handleIn(framing::AMQFrame& frame); + void handleOut(framing::AMQFrame& frame); + /** + * Sends session controls. This case is treated slightly + * differently than command frames sent by the application via + * handleOut(); session controlsare not subject to bounds checking + * on the outgoing frame queue. + */ + void proxyOut(framing::AMQFrame& frame); + void sendFrame(framing::AMQFrame& frame, bool canBlock); + void deliver(framing::AMQFrame& frame); + + Future sendCommand(const framing::AMQBody&, const framing::MethodContent* = 0); + void sendContent(const framing::MethodContent&); + void waitForCompletionImpl(const framing::SequenceNumber& id); + + void sendCompletionImpl(); + + // Note: Following methods are called by network thread in + // response to session controls from the broker + void attach(const std::string& name, bool force); + void attached(const std::string& name); + void detach(const std::string& name); + void detached(const std::string& name, uint8_t detachCode); + void requestTimeout(uint32_t timeout); + void timeout(uint32_t timeout); + void commandPoint(const framing::SequenceNumber& commandId, uint64_t commandOffset); + void expected(const framing::SequenceSet& commands, const framing::Array& fragments); + void confirmed(const framing::SequenceSet& commands, const framing::Array& fragments); + void completed(const framing::SequenceSet& commands, bool timelyReply); + void knownCompleted(const framing::SequenceSet& commands); + void flush(bool expected, bool confirmed, bool completed); + void gap(const framing::SequenceSet& commands); + + // Note: Following methods are called by network thread in + // response to execution commands from the broker + void sync(); + void result(const framing::SequenceNumber& commandId, const std::string& value); + void exception(uint16_t errorCode, + const framing::SequenceNumber& commandId, + uint8_t classCode, + uint8_t commandCode, + uint8_t fieldIndex, + const std::string& description, + const framing::FieldTable& errorInfo); + + // Note: Following methods are called by network thread in + // response to message commands from the broker + // EXCEPT Message.Transfer + void accept(const qpid::framing::SequenceSet&); + void reject(const qpid::framing::SequenceSet&, uint16_t, const std::string&); + void release(const qpid::framing::SequenceSet&, bool); + qpid::framing::MessageResumeResult resume(const std::string&, const std::string&); + void setFlowMode(const std::string&, uint8_t); + void flow(const std::string&, uint8_t, uint32_t); + void stop(const std::string&); + + + sys::ExceptionHolder exceptionHolder; + mutable StateMonitor state; + mutable sys::Semaphore sendLock; + uint32_t detachedLifetime; + const uint64_t maxFrameSize; + const SessionId id; + + boost::shared_ptr<ConnectionImpl> connection; + + framing::FrameHandler::MemFunRef<SessionImpl, &SessionImpl::proxyOut> ioHandler; + framing::ChannelHandler channel; + framing::AMQP_ServerProxy::Session proxy; + + Results results; + Demux demux; + framing::FrameSet::shared_ptr arriving; + + framing::SequenceSet incompleteIn;//incoming commands that are as yet incomplete + framing::SequenceSet completedIn;//incoming commands that are have completed + framing::SequenceSet incompleteOut;//outgoing commands not yet known to be complete + framing::SequenceSet completedOut;//outgoing commands that we know to be completed + framing::SequenceNumber nextIn; + framing::SequenceNumber nextOut; + + SessionState sessionState; + + // Only keep track of message credit + sys::Semaphore* sendMsgCredit; + + bool doClearDeliveryPropertiesExchange; + + bool autoDetach; + + friend class client::SessionHandler; +}; + +}} // namespace qpid::client + +#endif diff --git a/qpid/cpp/src/qpid/client/SslConnector.cpp b/qpid/cpp/src/qpid/client/SslConnector.cpp new file mode 100644 index 0000000000..f121cfb1ab --- /dev/null +++ b/qpid/cpp/src/qpid/client/SslConnector.cpp @@ -0,0 +1,381 @@ +/* + * + * 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/Connector.h" + +#include "config.h" +#include "qpid/client/Bounds.h" +#include "qpid/client/ConnectionImpl.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/Options.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Time.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/InitiationHandler.h" +#include "qpid/sys/ssl/util.h" +#include "qpid/sys/ssl/SslIo.h" +#include "qpid/sys/ssl/SslSocket.h" +#include "qpid/sys/Dispatcher.h" +#include "qpid/sys/Poller.h" +#include "qpid/sys/SecuritySettings.h" +#include "qpid/Msg.h" + +#include <iostream> +#include <map> +#include <boost/bind.hpp> +#include <boost/format.hpp> + +namespace qpid { +namespace client { + +using namespace qpid::sys; +using namespace qpid::sys::ssl; +using namespace qpid::framing; +using boost::format; +using boost::str; + + +class SslConnector : public Connector +{ + struct Buff; + + /** Batch up frames for writing to aio. */ + class Writer : public framing::FrameHandler { + typedef sys::ssl::SslIOBufferBase BufferBase; + typedef std::vector<framing::AMQFrame> Frames; + + const uint16_t maxFrameSize; + sys::Mutex lock; + sys::ssl::SslIO* aio; + BufferBase* buffer; + Frames frames; + size_t lastEof; // Position after last EOF in frames + framing::Buffer encode; + size_t framesEncoded; + std::string identifier; + Bounds* bounds; + + void writeOne(); + void newBuffer(); + + public: + + Writer(uint16_t maxFrameSize, Bounds*); + ~Writer(); + void init(std::string id, sys::ssl::SslIO*); + void handle(framing::AMQFrame&); + void write(sys::ssl::SslIO&); + }; + + const uint16_t maxFrameSize; + framing::ProtocolVersion version; + bool initiated; + SecuritySettings securitySettings; + + sys::Mutex closedLock; + bool closed; + + sys::ShutdownHandler* shutdownHandler; + framing::InputHandler* input; + framing::InitiationHandler* initialiser; + framing::OutputHandler* output; + + Writer writer; + + sys::ssl::SslSocket socket; + + sys::ssl::SslIO* aio; + Poller::shared_ptr poller; + + ~SslConnector(); + + void readbuff(qpid::sys::ssl::SslIO&, qpid::sys::ssl::SslIOBufferBase*); + void writebuff(qpid::sys::ssl::SslIO&); + void writeDataBlock(const framing::AMQDataBlock& data); + void eof(qpid::sys::ssl::SslIO&); + void disconnected(qpid::sys::ssl::SslIO&); + + std::string identifier; + + void connect(const std::string& host, const std::string& port); + void init(); + void close(); + void send(framing::AMQFrame& frame); + void abort() {} // TODO: Need to fix for heartbeat timeouts to work + + void setInputHandler(framing::InputHandler* handler); + void setShutdownHandler(sys::ShutdownHandler* handler); + sys::ShutdownHandler* getShutdownHandler() const; + framing::OutputHandler* getOutputHandler(); + const std::string& getIdentifier() const; + const SecuritySettings* getSecuritySettings(); + void socketClosed(qpid::sys::ssl::SslIO&, const qpid::sys::ssl::SslSocket&); + +public: + SslConnector(Poller::shared_ptr p, framing::ProtocolVersion pVersion, + const ConnectionSettings&, + ConnectionImpl*); +}; + +struct SslConnector::Buff : public SslIO::BufferBase { + Buff(size_t size) : SslIO::BufferBase(new char[size], size) {} + ~Buff() { delete [] bytes;} +}; + +// Static constructor which registers connector here +namespace { + Connector* create(Poller::shared_ptr p, framing::ProtocolVersion v, const ConnectionSettings& s, ConnectionImpl* c) { + return new SslConnector(p, v, s, c); + } + + struct StaticInit { + StaticInit() { + try { + SslOptions options; + options.parse (0, 0, QPIDC_CONF_FILE, true); + if (options.certDbPath.empty()) { + QPID_LOG(info, "SSL connector not enabled, you must set QPID_SSL_CERT_DB to enable it."); + } else { + initNSS(options); + Connector::registerFactory("ssl", &create); + } + } catch (const std::exception& e) { + QPID_LOG(error, "Failed to initialise SSL connector: " << e.what()); + } + }; + + ~StaticInit() { shutdownNSS(); } + } init; +} + +SslConnector::SslConnector(Poller::shared_ptr p, + ProtocolVersion ver, + const ConnectionSettings& settings, + ConnectionImpl* cimpl) + : maxFrameSize(settings.maxFrameSize), + version(ver), + initiated(false), + closed(true), + shutdownHandler(0), + writer(maxFrameSize, cimpl), + aio(0), + poller(p) +{ + QPID_LOG(debug, "SslConnector created for " << version.toString()); + + if (settings.sslCertName != "") { + QPID_LOG(debug, "ssl-cert-name = " << settings.sslCertName); + socket.setCertName(settings.sslCertName); + } +} + +SslConnector::~SslConnector() { + close(); +} + +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()); + } + + identifier = str(format("[%1% %2%]") % socket.getLocalPort() % socket.getPeerAddress()); + closed = false; + aio = new SslIO(socket, + boost::bind(&SslConnector::readbuff, this, _1, _2), + boost::bind(&SslConnector::eof, this, _1), + boost::bind(&SslConnector::disconnected, this, _1), + boost::bind(&SslConnector::socketClosed, this, _1, _2), + 0, // nobuffs + boost::bind(&SslConnector::writebuff, this, _1)); + writer.init(identifier, aio); +} + +void SslConnector::init(){ + Mutex::ScopedLock l(closedLock); + ProtocolInitiation init(version); + writeDataBlock(init); + for (int i = 0; i < 32; i++) { + aio->queueReadBuffer(new Buff(maxFrameSize)); + } + aio->start(poller); +} + +void SslConnector::close() { + Mutex::ScopedLock l(closedLock); + if (!closed) { + closed = true; + if (aio) + aio->queueWriteClose(); + } +} + +void SslConnector::socketClosed(SslIO&, const SslSocket&) { + if (aio) + aio->queueForDeletion(); + if (shutdownHandler) + shutdownHandler->shutdown(); +} + +void SslConnector::setInputHandler(InputHandler* handler){ + input = handler; +} + +void SslConnector::setShutdownHandler(ShutdownHandler* handler){ + shutdownHandler = handler; +} + +OutputHandler* SslConnector::getOutputHandler() { + return this; +} + +sys::ShutdownHandler* SslConnector::getShutdownHandler() const { + return shutdownHandler; +} + +const std::string& SslConnector::getIdentifier() const { + return identifier; +} + +void SslConnector::send(AMQFrame& frame) { + writer.handle(frame); +} + +SslConnector::Writer::Writer(uint16_t s, Bounds* b) : maxFrameSize(s), aio(0), buffer(0), lastEof(0), bounds(b) +{ +} + +SslConnector::Writer::~Writer() { delete buffer; } + +void SslConnector::Writer::init(std::string id, sys::ssl::SslIO* a) { + Mutex::ScopedLock l(lock); + identifier = id; + aio = a; + newBuffer(); +} +void SslConnector::Writer::handle(framing::AMQFrame& frame) { + Mutex::ScopedLock l(lock); + frames.push_back(frame); + if (frame.getEof() || (bounds && bounds->getCurrentSize() >= maxFrameSize)) { + lastEof = frames.size(); + aio->notifyPendingWrite(); + } + QPID_LOG(trace, "SENT " << identifier << ": " << frame); +} + +void SslConnector::Writer::writeOne() { + assert(buffer); + framesEncoded = 0; + + buffer->dataStart = 0; + buffer->dataCount = encode.getPosition(); + aio->queueWrite(buffer); + newBuffer(); +} + +void SslConnector::Writer::newBuffer() { + buffer = aio->getQueuedBuffer(); + if (!buffer) buffer = new Buff(maxFrameSize); + encode = framing::Buffer(buffer->bytes, buffer->byteCount); + framesEncoded = 0; +} + +// Called in IO thread. +void SslConnector::Writer::write(sys::ssl::SslIO&) { + Mutex::ScopedLock l(lock); + assert(buffer); + size_t bytesWritten(0); + for (size_t i = 0; i < lastEof; ++i) { + AMQFrame& frame = frames[i]; + uint32_t size = frame.encodedSize(); + if (size > encode.available()) writeOne(); + assert(size <= encode.available()); + frame.encode(encode); + ++framesEncoded; + bytesWritten += size; + } + frames.erase(frames.begin(), frames.begin()+lastEof); + lastEof = 0; + if (bounds) bounds->reduce(bytesWritten); + if (encode.getPosition() > 0) writeOne(); +} + +void SslConnector::readbuff(SslIO& aio, SslIO::BufferBase* buff) { + framing::Buffer in(buff->bytes+buff->dataStart, buff->dataCount); + + if (!initiated) { + framing::ProtocolInitiation protocolInit; + if (protocolInit.decode(in)) { + //TODO: check the version is correct + QPID_LOG(debug, "RECV " << identifier << " INIT(" << protocolInit << ")"); + } + initiated = true; + } + AMQFrame frame; + while(frame.decode(in)){ + QPID_LOG(trace, "RECV " << identifier << ": " << frame); + input->received(frame); + } + // TODO: unreading needs to go away, and when we can cope + // with multiple sub-buffers in the general buffer scheme, it will + if (in.available() != 0) { + // Adjust buffer for used bytes and then "unread them" + buff->dataStart += buff->dataCount-in.available(); + buff->dataCount = in.available(); + aio.unread(buff); + } else { + // Give whole buffer back to aio subsystem + aio.queueReadBuffer(buff); + } +} + +void SslConnector::writebuff(SslIO& aio_) { + writer.write(aio_); +} + +void SslConnector::writeDataBlock(const AMQDataBlock& data) { + SslIO::BufferBase* buff = new Buff(maxFrameSize); + framing::Buffer out(buff->bytes, buff->byteCount); + data.encode(out); + buff->dataCount = data.encodedSize(); + aio->queueWrite(buff); +} + +void SslConnector::eof(SslIO&) { + close(); +} + +void SslConnector::disconnected(SslIO&) { + close(); + socketClosed(*aio, socket); +} + +const SecuritySettings* SslConnector::getSecuritySettings() +{ + securitySettings.ssf = socket.getKeyLen(); + securitySettings.authid = "dummy";//set to non-empty string to enable external authentication + return &securitySettings; +} + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/StateManager.cpp b/qpid/cpp/src/qpid/client/StateManager.cpp new file mode 100644 index 0000000000..839d92abdc --- /dev/null +++ b/qpid/cpp/src/qpid/client/StateManager.cpp @@ -0,0 +1,100 @@ +/* + * + * 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/StateManager.h" +#include "qpid/framing/amqp_framing.h" + +using namespace qpid::client; +using namespace qpid::framing; +using namespace qpid::sys; + +StateManager::StateManager(int s) : state(s) {} + +void StateManager::waitForStateChange(int current) +{ + Monitor::ScopedLock l(stateLock); + while (state == current) { + stateLock.wait(); + } +} + +void StateManager::waitFor(int desired) +{ + Monitor::ScopedLock l(stateLock); + while (state != desired) { + stateLock.wait(); + } +} + +void StateManager::waitFor(std::set<int> desired) +{ + Monitor::ScopedLock l(stateLock); + while (desired.find(state) == desired.end()) { + stateLock.wait(); + } +} + +bool StateManager::waitFor(int desired, qpid::sys::Duration timeout) +{ + AbsTime end(now(), timeout); + Monitor::ScopedLock l(stateLock); + while (state != desired && now() < end) { + stateLock.wait(end); + } + return state == desired; +} + +bool StateManager::waitFor(std::set<int> desired, qpid::sys::Duration timeout) +{ + AbsTime end(now(), timeout); + Monitor::ScopedLock l(stateLock); + while (desired.find(state) == desired.end() && now() < end) { + stateLock.wait(end); + } + return desired.find(state) != desired.end(); +} + + +void StateManager::setState(int s) +{ + Monitor::ScopedLock l(stateLock); + state = s; + stateLock.notifyAll(); +} + +bool StateManager::setState(int s, int expected) +{ + Monitor::ScopedLock l(stateLock); + if (state == expected) { + state = s; + stateLock.notifyAll(); + return true; + } else { + return false; + } +} + +int StateManager::getState() const +{ + Monitor::ScopedLock l(stateLock); + return state; +} + diff --git a/qpid/cpp/src/qpid/client/StateManager.h b/qpid/cpp/src/qpid/client/StateManager.h new file mode 100644 index 0000000000..f06dbc493c --- /dev/null +++ b/qpid/cpp/src/qpid/client/StateManager.h @@ -0,0 +1,50 @@ +/* + * + * 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 _StateManager_ +#define _StateManager_ + +#include <set> +#include "qpid/sys/Monitor.h" + +namespace qpid { +namespace client { + +///@internal +class StateManager +{ + int state; + mutable sys::Monitor stateLock; + +public: + StateManager(int initial); + void setState(int state); + bool setState(int state, int expected); + int getState() const ; + void waitForStateChange(int current); + void waitFor(std::set<int> states); + void waitFor(int state); + bool waitFor(std::set<int> states, qpid::sys::Duration); + bool waitFor(int state, qpid::sys::Duration); +}; + +}} + +#endif diff --git a/qpid/cpp/src/qpid/client/Subscription.cpp b/qpid/cpp/src/qpid/client/Subscription.cpp new file mode 100644 index 0000000000..988f372604 --- /dev/null +++ b/qpid/cpp/src/qpid/client/Subscription.cpp @@ -0,0 +1,55 @@ +/* + * + * 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/Subscription.h" +#include "qpid/client/SubscriptionImpl.h" +#include "qpid/client/CompletionImpl.h" +#include "qpid/client/PrivateImplRef.h" +#include "qpid/framing/enum.h" + +namespace qpid { +namespace client { + +typedef PrivateImplRef<Subscription> PI; +Subscription::Subscription(SubscriptionImpl* p) { PI::ctor(*this, p); } +Subscription::~Subscription() { PI::dtor(*this); } +Subscription::Subscription(const Subscription& c) : Handle<SubscriptionImpl>() { PI::copy(*this, c); } +Subscription& Subscription::operator=(const Subscription& c) { return PI::assign(*this, c); } + + +std::string Subscription::getName() const { return impl->getName(); } +std::string Subscription::getQueue() const { return impl->getQueue(); } +const SubscriptionSettings& Subscription::getSettings() const { return impl->getSettings(); } +void Subscription::setFlowControl(const FlowControl& f) { impl->setFlowControl(f); } +void Subscription::setAutoAck(unsigned int n) { impl->setAutoAck(n); } +SequenceSet Subscription::getUnacquired() const { return impl->getUnacquired(); } +SequenceSet Subscription::getUnaccepted() const { return impl->getUnaccepted(); } +void Subscription::acquire(const SequenceSet& messageIds) { impl->acquire(messageIds); } +void Subscription::accept(const SequenceSet& messageIds) { impl->accept(messageIds); } +void Subscription::release(const SequenceSet& messageIds) { impl->release(messageIds); } +Session Subscription::getSession() const { return impl->getSession(); } +SubscriptionManager Subscription::getSubscriptionManager() { return impl->getSubscriptionManager(); } +void Subscription::cancel() { impl->cancel(); } +void Subscription::grantMessageCredit(uint32_t value) { impl->grantCredit(framing::message::CREDIT_UNIT_MESSAGE, value); } +void Subscription::grantByteCredit(uint32_t value) { impl->grantCredit(framing::message::CREDIT_UNIT_BYTE, value); } +}} // namespace qpid::client + + diff --git a/qpid/cpp/src/qpid/client/SubscriptionImpl.cpp b/qpid/cpp/src/qpid/client/SubscriptionImpl.cpp new file mode 100644 index 0000000000..a8a0b47d94 --- /dev/null +++ b/qpid/cpp/src/qpid/client/SubscriptionImpl.cpp @@ -0,0 +1,169 @@ +/* + * + * 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/AsyncSession.h" +#include "qpid/client/SubscriptionImpl.h" +#include "qpid/client/SessionImpl.h" +#include "qpid/client/SubscriptionManagerImpl.h" +#include "qpid/client/MessageImpl.h" +#include "qpid/client/CompletionImpl.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/client/SubscriptionSettings.h" +#include "qpid/client/SessionBase_0_10Access.h" +#include "qpid/client/PrivateImplRef.h" + +namespace qpid { +namespace client { + +using sys::Mutex; +using framing::MessageAcquireResult; + +SubscriptionImpl::SubscriptionImpl(SubscriptionManager m, const std::string& q, const SubscriptionSettings& s, const std::string& n, MessageListener* l) + : manager(*PrivateImplRef<SubscriptionManager>::get(m)), name(n), queue(q), settings(s), listener(l) +{} + +void SubscriptionImpl::subscribe() +{ + async(manager.getSession()).messageSubscribe( + arg::queue=queue, + arg::destination=name, + arg::acceptMode=settings.acceptMode, + arg::acquireMode=settings.acquireMode, + arg::exclusive=settings.exclusive); + setFlowControl(settings.flowControl); +} + +std::string SubscriptionImpl::getName() const { return name; } + +std::string SubscriptionImpl::getQueue() const { return queue; } + +const SubscriptionSettings& SubscriptionImpl::getSettings() const { + Mutex::ScopedLock l(lock); + return settings; +} + +void SubscriptionImpl::setFlowControl(const FlowControl& f) { + Mutex::ScopedLock l(lock); + AsyncSession s=manager.getSession(); + if (&settings.flowControl != &f) settings.flowControl = f; + s.messageSetFlowMode(name, f.window); + s.messageFlow(name, CREDIT_UNIT_MESSAGE, f.messages); + s.messageFlow(name, CREDIT_UNIT_BYTE, f.bytes); + s.sync(); +} + +void SubscriptionImpl::grantCredit(framing::message::CreditUnit unit, uint32_t value) { + async(manager.getSession()).messageFlow(name, unit, value); +} + +void SubscriptionImpl::setAutoAck(size_t n) { + Mutex::ScopedLock l(lock); + settings.autoAck = n; +} + +SequenceSet SubscriptionImpl::getUnacquired() const { Mutex::ScopedLock l(lock); return unacquired; } +SequenceSet SubscriptionImpl::getUnaccepted() const { Mutex::ScopedLock l(lock); return unaccepted; } + +void SubscriptionImpl::acquire(const SequenceSet& messageIds) { + Mutex::ScopedLock l(lock); + MessageAcquireResult result = manager.getSession().messageAcquire(messageIds); + unacquired.remove(result.getTransfers()); + if (settings.acceptMode == ACCEPT_MODE_EXPLICIT) + unaccepted.add(result.getTransfers()); +} + +void SubscriptionImpl::accept(const SequenceSet& messageIds) { + Mutex::ScopedLock l(lock); + manager.getSession().messageAccept(messageIds); + unaccepted.remove(messageIds); + switch (settings.completionMode) { + case COMPLETE_ON_ACCEPT: + manager.getSession().markCompleted(messageIds, true); + break; + case COMPLETE_ON_DELIVERY: + manager.getSession().sendCompletion(); + break; + default://do nothing + break; + } +} + +void SubscriptionImpl::release(const SequenceSet& messageIds) { + Mutex::ScopedLock l(lock); + manager.getSession().messageRelease(messageIds); + if (settings.acceptMode == ACCEPT_MODE_EXPLICIT) + unaccepted.remove(messageIds); +} + +Session SubscriptionImpl::getSession() const { return manager.getSession(); } + +SubscriptionManager SubscriptionImpl::getSubscriptionManager() { return SubscriptionManager(&manager); } + +void SubscriptionImpl::cancel() { manager.cancel(name); } + +void SubscriptionImpl::received(Message& m) { + Mutex::ScopedLock l(lock); + MessageImpl& mi = *MessageImpl::get(m); + if (mi.getMethod().getAcquireMode() == ACQUIRE_MODE_NOT_ACQUIRED) + unacquired.add(m.getId()); + else if (mi.getMethod().getAcceptMode() == ACCEPT_MODE_EXPLICIT) + unaccepted.add(m.getId()); + + if (listener) { + Mutex::ScopedUnlock u(lock); + listener->received(m); + } + + if (settings.completionMode == COMPLETE_ON_DELIVERY) { + manager.getSession().markCompleted(m.getId(), false, false); + } + if (settings.autoAck) { + if (unaccepted.size() >= settings.autoAck) { + async(manager.getSession()).messageAccept(unaccepted); + switch (settings.completionMode) { + case COMPLETE_ON_ACCEPT: + manager.getSession().markCompleted(unaccepted, true); + break; + case COMPLETE_ON_DELIVERY: + manager.getSession().sendCompletion(); + break; + default://do nothing + break; + } + unaccepted.clear(); + } + } +} + +Demux::QueuePtr SubscriptionImpl::divert() +{ + Session session(manager.getSession()); + Demux& demux = SessionBase_0_10Access(session).get()->getDemux(); + demuxRule = std::auto_ptr<ScopedDivert>(new ScopedDivert(name, demux)); + return demuxRule->getQueue(); +} + +void SubscriptionImpl::cancelDiversion() { + demuxRule.reset(); +} + +}} // namespace qpid::client + diff --git a/qpid/cpp/src/qpid/client/SubscriptionImpl.h b/qpid/cpp/src/qpid/client/SubscriptionImpl.h new file mode 100644 index 0000000000..da77213423 --- /dev/null +++ b/qpid/cpp/src/qpid/client/SubscriptionImpl.h @@ -0,0 +1,125 @@ +#ifndef QPID_CLIENT_SUBSCRIPTIONIMPL_H +#define QPID_CLIENT_SUBSCRIPTIONIMPL_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/client/SubscriptionSettings.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/client/Session.h" +#include "qpid/client/MessageListener.h" +#include "qpid/client/Demux.h" +#include "qpid/framing/enum.h" +#include "qpid/framing/SequenceSet.h" +#include "qpid/sys/Mutex.h" +#include "qpid/RefCounted.h" +#include "qpid/client/ClientImportExport.h" +#include <memory> + +namespace qpid { +namespace client { + +class SubscriptionManager; +class SubscriptionManagerImpl; + +class SubscriptionImpl : public RefCounted, public MessageListener { + public: + QPID_CLIENT_EXTERN SubscriptionImpl(SubscriptionManager, const std::string& queue, + const SubscriptionSettings&, const std::string& name, MessageListener* =0); + + /** The name of the subsctription, used as the "destination" for messages from the broker. + * Usually the same as the queue name but can be set differently. + */ + QPID_CLIENT_EXTERN std::string getName() const; + + /** Name of the queue this subscription subscribes to */ + QPID_CLIENT_EXTERN std::string getQueue() const; + + /** Get the flow control and acknowledgement settings for this subscription */ + QPID_CLIENT_EXTERN const SubscriptionSettings& getSettings() const; + + /** Set the flow control parameters */ + QPID_CLIENT_EXTERN void setFlowControl(const FlowControl&); + + /** Automatically acknowledge (acquire and accept) batches of n messages. + * You can disable auto-acknowledgement by setting n=0, and use acquire() and accept() + * to manually acquire and accept messages. + */ + QPID_CLIENT_EXTERN void setAutoAck(size_t n); + + /** Get the set of ID's for messages received by this subscription but not yet acquired. + * This will always be empty if acquireMode=ACQUIRE_MODE_PRE_ACQUIRED + */ + QPID_CLIENT_EXTERN SequenceSet getUnacquired() const; + + /** Get the set of ID's for messages acquired by this subscription but not yet accepted. */ + QPID_CLIENT_EXTERN SequenceSet getUnaccepted() const; + + /** Acquire messageIds and remove them from the un-acquired set for the session. */ + QPID_CLIENT_EXTERN void acquire(const SequenceSet& messageIds); + + /** Accept messageIds and remove them from the un-accepted set for the session. */ + QPID_CLIENT_EXTERN void accept(const SequenceSet& messageIds); + + /** Release messageIds and remove them from the un-accepted set for the session. */ + QPID_CLIENT_EXTERN void release(const SequenceSet& messageIds); + + /** Get the session associated with this subscription */ + QPID_CLIENT_EXTERN Session getSession() const; + + /** Get the subscription manager associated with this subscription */ + QPID_CLIENT_EXTERN SubscriptionManager getSubscriptionManager(); + + /** Send subscription request and issue appropriate flow control commands. */ + QPID_CLIENT_EXTERN void subscribe(); + + /** Cancel the subscription. */ + QPID_CLIENT_EXTERN void cancel(); + + /** Grant specified credit for this subscription **/ + QPID_CLIENT_EXTERN void grantCredit(framing::message::CreditUnit unit, uint32_t value); + + QPID_CLIENT_EXTERN void received(Message&); + + /** + * Set up demux diversion for messages sent to this subscription + */ + Demux::QueuePtr divert(); + /** + * Cancel any demux diversion that may have been setup for this + * subscription + */ + QPID_CLIENT_EXTERN void cancelDiversion(); + + private: + + mutable sys::Mutex lock; + SubscriptionManagerImpl& manager; + std::string name, queue; + SubscriptionSettings settings; + framing::SequenceSet unacquired, unaccepted; + MessageListener* listener; + std::auto_ptr<ScopedDivert> demuxRule; +}; + +}} // namespace qpid::client + +#endif /*!QPID_CLIENT_SUBSCRIPTIONIMPL_H*/ diff --git a/qpid/cpp/src/qpid/client/SubscriptionManager.cpp b/qpid/cpp/src/qpid/client/SubscriptionManager.cpp new file mode 100644 index 0000000000..485361d577 --- /dev/null +++ b/qpid/cpp/src/qpid/client/SubscriptionManager.cpp @@ -0,0 +1,106 @@ +/* + * + * 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/SubscriptionManager.h" +#include "qpid/client/SubscriptionManagerImpl.h" +#include "qpid/client/PrivateImplRef.h" + + +namespace qpid { +namespace client { + +typedef PrivateImplRef<SubscriptionManager> PI; + +SubscriptionManager::SubscriptionManager(const Session& s) { PI::ctor(*this, new SubscriptionManagerImpl(s)); } +SubscriptionManager::SubscriptionManager(SubscriptionManagerImpl* i) { PI::ctor(*this, i); } +SubscriptionManager::SubscriptionManager(const SubscriptionManager& x) : Runnable(), Handle<SubscriptionManagerImpl>() { PI::copy(*this, x); } +SubscriptionManager::~SubscriptionManager() { PI::dtor(*this); } +SubscriptionManager& SubscriptionManager::operator=(const SubscriptionManager& x) { return PI::assign(*this, x); } + +Subscription SubscriptionManager::subscribe( + MessageListener& listener, const std::string& q, const SubscriptionSettings& ss, const std::string& n) +{ return impl->subscribe(listener, q, ss, n); } + +Subscription SubscriptionManager::subscribe( + LocalQueue& lq, const std::string& q, const SubscriptionSettings& ss, const std::string& n) +{ return impl->subscribe(lq, q, ss, n); } + + +Subscription SubscriptionManager::subscribe( + MessageListener& listener, const std::string& q, const std::string& n) +{ return impl->subscribe(listener, q, n); } + + +Subscription SubscriptionManager::subscribe( + LocalQueue& lq, const std::string& q, const std::string& n) +{ return impl->subscribe(lq, q, n); } + +void SubscriptionManager::cancel(const std::string& dest) { return impl->cancel(dest); } + +void SubscriptionManager::setAutoStop(bool set) { impl->setAutoStop(set); } + +void SubscriptionManager::run() { impl->run(); } + +void SubscriptionManager::start() { impl->start(); } + +void SubscriptionManager::wait() { impl->wait(); } + +void SubscriptionManager::stop() { impl->stop(); } + +bool SubscriptionManager::get(Message& result, const std::string& queue, sys::Duration timeout) { + return impl->get(result, queue, timeout); +} + +Message SubscriptionManager::get(const std::string& queue, sys::Duration timeout) { + return impl->get(queue, timeout); +} + +Session SubscriptionManager::getSession() const { return impl->getSession(); } + +Subscription SubscriptionManager::getSubscription(const std::string& name) const { + return impl->getSubscription(name); +} +void SubscriptionManager::registerFailoverHandler (boost::function<void ()> fh) { + impl->registerFailoverHandler(fh); +} + +void SubscriptionManager::setFlowControl(const std::string& name, const FlowControl& flow) { + impl->setFlowControl(name, flow); +} + +void SubscriptionManager::setDefaultSettings(const SubscriptionSettings& s){ + impl->setDefaultSettings(s); +} + +void SubscriptionManager::setFlowControl(const std::string& name, uint32_t messages, uint32_t bytes, bool window) { + impl->setFlowControl(name, FlowControl(messages, bytes, window)); +} + +void SubscriptionManager::setFlowControl(uint32_t messages, uint32_t bytes, bool window) { + impl->setFlowControl(messages, bytes, window); +} + +void SubscriptionManager::setAcceptMode(AcceptMode mode) { impl->setAcceptMode(mode); } +void SubscriptionManager::setAcquireMode(AcquireMode mode) { impl->setAcquireMode(mode); } + +}} // namespace qpid::client + + diff --git a/qpid/cpp/src/qpid/client/SubscriptionManagerImpl.cpp b/qpid/cpp/src/qpid/client/SubscriptionManagerImpl.cpp new file mode 100644 index 0000000000..a558d90be8 --- /dev/null +++ b/qpid/cpp/src/qpid/client/SubscriptionManagerImpl.cpp @@ -0,0 +1,162 @@ +/* + * + * 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/SubscriptionManager.h" +#include "qpid/client/SubscriptionManagerImpl.h" +#include "qpid/client/SubscriptionImpl.h" +#include "qpid/client/LocalQueueImpl.h" +#include "qpid/client/PrivateImplRef.h" +#include <qpid/client/Dispatcher.h> +#include <qpid/client/Session.h> +#include <qpid/client/MessageListener.h> +#include <qpid/framing/Uuid.h> +#include <set> +#include <sstream> + + +namespace qpid { +namespace client { + +SubscriptionManagerImpl::SubscriptionManagerImpl(const Session& s) + : dispatcher(s), session(s), autoStop(true) +{} + +Subscription SubscriptionManagerImpl::subscribe( + MessageListener& listener, const std::string& q, const SubscriptionSettings& ss, const std::string& n) +{ + sys::Mutex::ScopedLock l(lock); + std::string name=n.empty() ? q:n; + boost::intrusive_ptr<SubscriptionImpl> si = new SubscriptionImpl(SubscriptionManager(this), q, ss, name, &listener); + dispatcher.listen(si); + //issue subscription request after listener is registered with dispatcher + si->subscribe(); + return subscriptions[name] = Subscription(si.get()); +} + +Subscription SubscriptionManagerImpl::subscribe( + LocalQueue& lq, const std::string& q, const SubscriptionSettings& ss, const std::string& n) +{ + sys::Mutex::ScopedLock l(lock); + std::string name=n.empty() ? q:n; + boost::intrusive_ptr<SubscriptionImpl> si = new SubscriptionImpl(SubscriptionManager(this), q, ss, name, 0); + boost::intrusive_ptr<LocalQueueImpl> lqi = PrivateImplRef<LocalQueue>::get(lq); + lqi->queue=si->divert(); + si->subscribe(); + lqi->subscription = Subscription(si.get()); + return subscriptions[name] = lqi->subscription; +} + +Subscription SubscriptionManagerImpl::subscribe( + MessageListener& listener, const std::string& q, const std::string& n) +{ + return subscribe(listener, q, defaultSettings, n); +} + +Subscription SubscriptionManagerImpl::subscribe( + LocalQueue& lq, const std::string& q, const std::string& n) +{ + return subscribe(lq, q, defaultSettings, n); +} + +void SubscriptionManagerImpl::cancel(const std::string& dest) +{ + sys::Mutex::ScopedLock l(lock); + std::map<std::string, Subscription>::iterator i = subscriptions.find(dest); + if (i != subscriptions.end()) { + sync(session).messageCancel(dest); + dispatcher.cancel(dest); + Subscription s = i->second; + if (s.isValid()) + PrivateImplRef<Subscription>::get(s)->cancelDiversion(); + subscriptions.erase(i); + } +} + +void SubscriptionManagerImpl::setAutoStop(bool set) { autoStop=set; } + +void SubscriptionManagerImpl::run() +{ + dispatcher.setAutoStop(autoStop); + dispatcher.run(); +} + +void SubscriptionManagerImpl::start() +{ + dispatcher.setAutoStop(autoStop); + dispatcher.start(); +} + +void SubscriptionManagerImpl::wait() +{ + dispatcher.wait(); +} + +void SubscriptionManagerImpl::stop() +{ + dispatcher.stop(); +} + +bool SubscriptionManagerImpl::get(Message& result, const std::string& queue, sys::Duration timeout) { + LocalQueue lq; + std::string unique = framing::Uuid(true).str(); + subscribe(lq, queue, SubscriptionSettings(FlowControl::messageCredit(1)), unique); + SubscriptionManager sm(this); + AutoCancel ac(sm, unique); + //first wait for message to be delivered if a timeout has been specified + if (timeout && lq.get(result, timeout)) + return true; + //make sure message is not on queue before final check + sync(session).messageFlush(unique); + return lq.get(result, 0); +} + +Message SubscriptionManagerImpl::get(const std::string& queue, sys::Duration timeout) { + Message result; + if (!get(result, queue, timeout)) + throw Exception("Timed out waiting for a message"); + return result; +} + +Session SubscriptionManagerImpl::getSession() const { return session; } + +Subscription SubscriptionManagerImpl::getSubscription(const std::string& name) const { + sys::Mutex::ScopedLock l(lock); + std::map<std::string, Subscription>::const_iterator i = subscriptions.find(name); + if (i == subscriptions.end()) + throw Exception(QPID_MSG("Subscription not found: " << name)); + return i->second; +} + +void SubscriptionManagerImpl::registerFailoverHandler (boost::function<void ()> fh) { + dispatcher.registerFailoverHandler(fh); +} + +void SubscriptionManagerImpl::setFlowControl(const std::string& name, const FlowControl& flow) { + getSubscription(name).setFlowControl(flow); +} + +void SubscriptionManagerImpl::setFlowControl(const std::string& name, uint32_t messages, uint32_t bytes, bool window) { + setFlowControl(name, FlowControl(messages, bytes, window)); +} + +}} // namespace qpid::client + + diff --git a/qpid/cpp/src/qpid/client/SubscriptionManagerImpl.h b/qpid/cpp/src/qpid/client/SubscriptionManagerImpl.h new file mode 100644 index 0000000000..6376a05c45 --- /dev/null +++ b/qpid/cpp/src/qpid/client/SubscriptionManagerImpl.h @@ -0,0 +1,278 @@ +#ifndef QPID_CLIENT_SUBSCRIPTIONMANAGERIMPL_H +#define QPID_CLIENT_SUBSCRIPTIONMANAGERIMPL_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/Mutex.h" +#include <qpid/client/Dispatcher.h> +#include <qpid/client/Completion.h> +#include <qpid/client/Session.h> +#include <qpid/client/AsyncSession.h> +#include <qpid/client/MessageListener.h> +#include <qpid/client/LocalQueue.h> +#include <qpid/client/Subscription.h> +#include <qpid/sys/Runnable.h> +#include <qpid/RefCounted.h> +#include <set> +#include <sstream> + +namespace qpid { +namespace client { + +/** + * A class to help create and manage subscriptions. + * + * Set up your subscriptions, then call run() to have messages + * delivered. + * + * \ingroup clientapi + * + * \details + * + * <h2>Subscribing and canceling subscriptions</h2> + * + * <ul> + * <li> + * <p>subscribe()</p> + * <pre> SubscriptionManager subscriptions(session); + * Listener listener(subscriptions); + * subscriptions.subscribe(listener, myQueue);</pre> + * <pre> SubscriptionManager subscriptions(session); + * LocalQueue local_queue; + * subscriptions.subscribe(local_queue, string("message_queue"));</pre></li> + * <li> + * <p>cancel()</p> + * <pre>subscriptions.cancel();</pre></li> + * </ul> + * + * <h2>Waiting for messages (and returning)</h2> + * + * <ul> + * <li> + * <p>run()</p> + * <pre> // Give up control to receive messages + * subscriptions.run();</pre></li> + * <li> + * <p>stop()</p> + * <pre>.// Use this code in a listener to return from run() + * subscriptions.stop();</pre></li> + * <li> + * <p>setAutoStop()</p> + * <pre>.// Return from subscriptions.run() when last subscription is cancelled + *.subscriptions.setAutoStop(true); + *.subscriptons.run(); + * </pre></li> + * <li> + * <p>Ending a subscription in a listener</p> + * <pre> + * void Listener::received(Message& message) { + * + * if (message.getData() == "That's all, folks!") { + * subscriptions.cancel(message.getDestination()); + * } + * } + * </pre> + * </li> + * </ul> + * + */ +class SubscriptionManagerImpl : public sys::Runnable, public RefCounted +{ + public: + /** Create a new SubscriptionManagerImpl associated with a session */ + SubscriptionManagerImpl(const Session& session); + + /** + * Subscribe a MessagesListener to receive messages from queue. + * + * Provide your own subclass of MessagesListener to process + * incoming messages. It will be called for each message received. + * + *@param listener Listener object to receive messages. + *@param queue Name of the queue to subscribe to. + *@param settings settings for the subscription. + *@param name unique destination name for the subscription, defaults to queue name. + */ + Subscription subscribe(MessageListener& listener, + const std::string& queue, + const SubscriptionSettings& settings, + const std::string& name=std::string()); + + /** + * Subscribe a LocalQueue to receive messages from queue. + * + * Incoming messages are stored in the queue for you to retrieve. + * + *@param queue Name of the queue to subscribe to. + *@param flow initial FlowControl for the subscription. + *@param name unique destination name for the subscription, defaults to queue name. + * If not specified, the queue name is used. + */ + Subscription subscribe(LocalQueue& localQueue, + const std::string& queue, + const SubscriptionSettings& settings, + const std::string& name=std::string()); + + /** + * Subscribe a MessagesListener to receive messages from queue. + * + * Provide your own subclass of MessagesListener to process + * incoming messages. It will be called for each message received. + * + *@param listener Listener object to receive messages. + *@param queue Name of the queue to subscribe to. + *@param name unique destination name for the subscription, defaults to queue name. + * If not specified, the queue name is used. + */ + Subscription subscribe(MessageListener& listener, + const std::string& queue, + const std::string& name=std::string()); + + /** + * Subscribe a LocalQueue to receive messages from queue. + * + * Incoming messages are stored in the queue for you to retrieve. + * + *@param queue Name of the queue to subscribe to. + *@param name unique destination name for the subscription, defaults to queue name. + * If not specified, the queue name is used. + */ + Subscription subscribe(LocalQueue& localQueue, + const std::string& queue, + const std::string& name=std::string()); + + + /** Get a single message from a queue. + *@param result is set to the message from the queue. + *@param timeout wait up this timeout for a message to appear. + *@return true if result was set, false if no message available after timeout. + */ + bool get(Message& result, const std::string& queue, sys::Duration timeout=0); + + /** Get a single message from a queue. + *@param timeout wait up this timeout for a message to appear. + *@return message from the queue. + *@throw Exception if the timeout is exceeded. + */ + Message get(const std::string& queue, sys::Duration timeout=sys::TIME_INFINITE); + + /** Get a subscription by name. + *@throw Exception if not found. + */ + Subscription getSubscription(const std::string& name) const; + + /** Cancel a subscription. See also: Subscription.cancel() */ + void cancel(const std::string& name); + + /** Deliver messages in the current thread until stop() is called. + * Only one thread may be running in a SubscriptionManager at a time. + * @see run + */ + void run(); + + /** Start a new thread to deliver messages. + * Only one thread may be running in a SubscriptionManager at a time. + * @see start + */ + void start(); + + /** + * Wait for the thread started by a call to start() to complete. + */ + void wait(); + + /** If set true, run() will stop when all subscriptions + * are cancelled. If false, run will only stop when stop() + * is called. True by default. + */ + void setAutoStop(bool set=true); + + /** Stop delivery. Causes run() to return, or the thread started with start() to exit. */ + void stop(); + + static const uint32_t UNLIMITED=0xFFFFFFFF; + + /** Set the flow control for a subscription. */ + void setFlowControl(const std::string& name, const FlowControl& flow); + + /** Set the flow control for a subscription. + *@param name: name of the subscription. + *@param messages: message credit. + *@param bytes: byte credit. + *@param window: if true use window-based flow control. + */ + void setFlowControl(const std::string& name, uint32_t messages, uint32_t bytes, bool window=true); + + /** Set the default settings for subscribe() calls that don't + * include a SubscriptionSettings parameter. + */ + void setDefaultSettings(const SubscriptionSettings& s) { defaultSettings = s; } + + /** Get the default settings for subscribe() calls that don't + * include a SubscriptionSettings parameter. + */ + const SubscriptionSettings& getDefaultSettings() const { return defaultSettings; } + + /** Get the default settings for subscribe() calls that don't + * include a SubscriptionSettings parameter. + */ + SubscriptionSettings& getDefaultSettings() { return defaultSettings; } + + /** + * Set the default flow control settings for subscribe() calls + * that don't include a SubscriptionSettings parameter. + * + *@param messages: message credit. + *@param bytes: byte credit. + *@param window: if true use window-based flow control. + */ + void setFlowControl(uint32_t messages, uint32_t bytes, bool window=true) { + defaultSettings.flowControl = FlowControl(messages, bytes, window); + } + + /** + *Set the default accept-mode for subscribe() calls that don't + *include a SubscriptionSettings parameter. + */ + void setAcceptMode(AcceptMode mode) { defaultSettings.acceptMode = mode; } + + /** + * Set the default acquire-mode subscribe()s that don't specify SubscriptionSettings. + */ + void setAcquireMode(AcquireMode mode) { defaultSettings.acquireMode = mode; } + + void registerFailoverHandler ( boost::function<void ()> fh ); + + Session getSession() const; + + private: + mutable sys::Mutex lock; + qpid::client::Dispatcher dispatcher; + qpid::client::AsyncSession session; + bool autoStop; + SubscriptionSettings defaultSettings; + std::map<std::string, Subscription> subscriptions; +}; + + +}} // namespace qpid::client + +#endif /*!QPID_CLIENT_SUBSCRIPTIONMANAGERIMPL_H*/ diff --git a/qpid/cpp/src/qpid/client/TCPConnector.cpp b/qpid/cpp/src/qpid/client/TCPConnector.cpp new file mode 100644 index 0000000000..0070b24ec0 --- /dev/null +++ b/qpid/cpp/src/qpid/client/TCPConnector.cpp @@ -0,0 +1,331 @@ +/* + * + * 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/TCPConnector.h" + +#include "qpid/client/ConnectionImpl.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Codec.h" +#include "qpid/sys/Time.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/InitiationHandler.h" +#include "qpid/sys/AsynchIO.h" +#include "qpid/sys/Dispatcher.h" +#include "qpid/sys/Poller.h" +#include "qpid/sys/SecurityLayer.h" +#include "qpid/Msg.h" + +#include <iostream> +#include <boost/bind.hpp> +#include <boost/format.hpp> + +namespace qpid { +namespace client { + +using namespace qpid::sys; +using namespace qpid::framing; +using boost::format; +using boost::str; + +struct TCPConnector::Buff : public AsynchIO::BufferBase { + Buff(size_t size) : AsynchIO::BufferBase(new char[size], size) {} + ~Buff() { delete [] bytes;} +}; + +// Static constructor which registers connector here +namespace { + Connector* create(Poller::shared_ptr p, framing::ProtocolVersion v, const ConnectionSettings& s, ConnectionImpl* c) { + return new TCPConnector(p, v, s, c); + } + + struct StaticInit { + StaticInit() { + Connector::registerFactory("tcp", &create); + }; + } init; +} + +TCPConnector::TCPConnector(Poller::shared_ptr p, + ProtocolVersion ver, + const ConnectionSettings& settings, + ConnectionImpl* cimpl) + : maxFrameSize(settings.maxFrameSize), + lastEof(0), + currentSize(0), + bounds(cimpl), + version(ver), + initiated(false), + closed(true), + shutdownHandler(0), + connector(0), + aio(0), + poller(p) +{ + QPID_LOG(debug, "TCPConnector created for " << version); + settings.configureSocket(socket); +} + +TCPConnector::~TCPConnector() { + close(); +} + +void TCPConnector::connect(const std::string& host, const std::string& port) { + Mutex::ScopedLock l(lock); + assert(closed); + connector = AsynchConnector::create( + socket, + host, port, + boost::bind(&TCPConnector::connected, this, _1), + boost::bind(&TCPConnector::connectFailed, this, _3)); + closed = false; + + connector->start(poller); +} + +void TCPConnector::connected(const Socket&) { + connector = 0; + aio = AsynchIO::create(socket, + boost::bind(&TCPConnector::readbuff, this, _1, _2), + boost::bind(&TCPConnector::eof, this, _1), + boost::bind(&TCPConnector::disconnected, this, _1), + boost::bind(&TCPConnector::socketClosed, this, _1, _2), + 0, // nobuffs + boost::bind(&TCPConnector::writebuff, this, _1)); + start(aio); + initAmqp(); + aio->start(poller); +} + +void TCPConnector::start(sys::AsynchIO* aio_) { + aio = aio_; + for (int i = 0; i < 4; i++) { + aio->queueReadBuffer(new Buff(maxFrameSize)); + } + + identifier = str(format("[%1%]") % socket.getFullAddress()); +} + +void TCPConnector::initAmqp() { + ProtocolInitiation init(version); + writeDataBlock(init); +} + +void TCPConnector::connectFailed(const std::string& msg) { + connector = 0; + QPID_LOG(warning, "Connect failed: " << msg); + socket.close(); + if (!closed) + closed = true; + if (shutdownHandler) + shutdownHandler->shutdown(); +} + +void TCPConnector::close() { + Mutex::ScopedLock l(lock); + if (!closed) { + closed = true; + if (aio) + aio->queueWriteClose(); + } +} + +void TCPConnector::socketClosed(AsynchIO&, const Socket&) { + if (aio) + aio->queueForDeletion(); + if (shutdownHandler) + shutdownHandler->shutdown(); +} + +void TCPConnector::abort() { + // Can't abort a closed connection + if (!closed) { + if (aio) { + // Established connection + aio->requestCallback(boost::bind(&TCPConnector::eof, this, _1)); + } else if (connector) { + // We're still connecting + connector->stop(); + connectFailed("Connection timedout"); + } + } +} + +void TCPConnector::setInputHandler(InputHandler* handler){ + input = handler; +} + +void TCPConnector::setShutdownHandler(ShutdownHandler* handler){ + shutdownHandler = handler; +} + +OutputHandler* TCPConnector::getOutputHandler() { + return this; +} + +sys::ShutdownHandler* TCPConnector::getShutdownHandler() const { + return shutdownHandler; +} + +const std::string& TCPConnector::getIdentifier() const { + return identifier; +} + +void TCPConnector::send(AMQFrame& frame) { + bool notifyWrite = false; + { + Mutex::ScopedLock l(lock); + frames.push_back(frame); + //only ask to write if this is the end of a frameset or if we + //already have a buffers worth of data + currentSize += frame.encodedSize(); + if (frame.getEof()) { + lastEof = frames.size(); + notifyWrite = true; + } else { + notifyWrite = (currentSize >= maxFrameSize); + } + /* + NOTE: Moving the following line into this mutex block + is a workaround for BZ 570168, in which the test + testConcurrentSenders causes a hang about 1.5% + of the time. ( To see the hang much more frequently + leave this line out of the mutex block, and put a + small usleep just before it.) + + TODO mgoulish - fix the underlying cause and then + move this call back outside the mutex. + */ + if (notifyWrite && !closed) aio->notifyPendingWrite(); + } +} + +void TCPConnector::writebuff(AsynchIO& /*aio*/) +{ + // It's possible to be disconnected and be writable + if (closed) + return; + + Codec* codec = securityLayer.get() ? (Codec*) securityLayer.get() : (Codec*) this; + if (codec->canEncode()) { + std::auto_ptr<AsynchIO::BufferBase> buffer = std::auto_ptr<AsynchIO::BufferBase>(aio->getQueuedBuffer()); + if (!buffer.get()) buffer = std::auto_ptr<AsynchIO::BufferBase>(new Buff(maxFrameSize)); + + size_t encoded = codec->encode(buffer->bytes, buffer->byteCount); + + buffer->dataStart = 0; + buffer->dataCount = encoded; + aio->queueWrite(buffer.release()); + } +} + +// Called in IO thread. +bool TCPConnector::canEncode() +{ + Mutex::ScopedLock l(lock); + //have at least one full frameset or a whole buffers worth of data + return lastEof || currentSize >= maxFrameSize; +} + +// Called in IO thread. +size_t TCPConnector::encode(const char* buffer, size_t size) +{ + framing::Buffer out(const_cast<char*>(buffer), size); + size_t bytesWritten(0); + { + Mutex::ScopedLock l(lock); + while (!frames.empty() && out.available() >= frames.front().encodedSize() ) { + frames.front().encode(out); + QPID_LOG(trace, "SENT " << identifier << ": " << frames.front()); + frames.pop_front(); + if (lastEof) --lastEof; + } + bytesWritten = size - out.available(); + currentSize -= bytesWritten; + } + if (bounds) bounds->reduce(bytesWritten); + return bytesWritten; +} + +bool TCPConnector::readbuff(AsynchIO& aio, AsynchIO::BufferBase* buff) +{ + Codec* codec = securityLayer.get() ? (Codec*) securityLayer.get() : (Codec*) this; + int32_t decoded = codec->decode(buff->bytes+buff->dataStart, buff->dataCount); + // TODO: unreading needs to go away, and when we can cope + // with multiple sub-buffers in the general buffer scheme, it will + if (decoded < buff->dataCount) { + // Adjust buffer for used bytes and then "unread them" + buff->dataStart += decoded; + buff->dataCount -= decoded; + aio.unread(buff); + } else { + // Give whole buffer back to aio subsystem + aio.queueReadBuffer(buff); + } + return true; +} + +size_t TCPConnector::decode(const char* buffer, size_t size) +{ + framing::Buffer in(const_cast<char*>(buffer), size); + if (!initiated) { + framing::ProtocolInitiation protocolInit; + if (protocolInit.decode(in)) { + QPID_LOG(debug, "RECV " << identifier << " INIT(" << protocolInit << ")"); + if(!(protocolInit==version)){ + throw Exception(QPID_MSG("Unsupported version: " << protocolInit + << " supported version " << version)); + } + } + initiated = true; + } + AMQFrame frame; + while(frame.decode(in)){ + QPID_LOG(trace, "RECV " << identifier << ": " << frame); + input->received(frame); + } + return size - in.available(); +} + +void TCPConnector::writeDataBlock(const AMQDataBlock& data) { + AsynchIO::BufferBase* buff = aio->getQueuedBuffer(); + framing::Buffer out(buff->bytes, buff->byteCount); + data.encode(out); + buff->dataCount = data.encodedSize(); + aio->queueWrite(buff); +} + +void TCPConnector::eof(AsynchIO&) { + close(); +} + +void TCPConnector::disconnected(AsynchIO&) { + close(); + socketClosed(*aio, socket); +} + +void TCPConnector::activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer> sl) +{ + securityLayer = sl; + securityLayer->init(this); +} + +}} // namespace qpid::client diff --git a/qpid/cpp/src/qpid/client/TCPConnector.h b/qpid/cpp/src/qpid/client/TCPConnector.h new file mode 100644 index 0000000000..eb3f696013 --- /dev/null +++ b/qpid/cpp/src/qpid/client/TCPConnector.h @@ -0,0 +1,120 @@ +/* + * + * 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 _TCPConnector_ +#define _TCPConnector_ + +#include "Connector.h" +#include "qpid/client/Bounds.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/sys/AsynchIO.h" +#include "qpid/sys/Codec.h" +#include "qpid/sys/IntegerTypes.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Runnable.h" +#include "qpid/sys/SecurityLayer.h" +#include "qpid/sys/Socket.h" +#include "qpid/sys/Thread.h" + +#include <boost/shared_ptr.hpp> +#include <boost/weak_ptr.hpp> +#include <deque> +#include <string> + +namespace qpid { + +namespace framing { + class InitiationHandler; +} + +namespace client { + +class TCPConnector : public Connector, public sys::Codec +{ + typedef std::deque<framing::AMQFrame> Frames; + struct Buff; + + const uint16_t maxFrameSize; + + sys::Mutex lock; + Frames frames; // Outgoing frame queue + size_t lastEof; // Position after last EOF in frames + uint64_t currentSize; + Bounds* bounds; + + framing::ProtocolVersion version; + bool initiated; + bool closed; + + sys::ShutdownHandler* shutdownHandler; + framing::InputHandler* input; + framing::InitiationHandler* initialiser; + framing::OutputHandler* output; + + sys::Socket socket; + + sys::AsynchConnector* connector; + sys::AsynchIO* aio; + std::string identifier; + boost::shared_ptr<sys::Poller> poller; + std::auto_ptr<qpid::sys::SecurityLayer> securityLayer; + + virtual void connected(const sys::Socket&); + void writeDataBlock(const framing::AMQDataBlock& data); + + void close(); + void send(framing::AMQFrame& frame); + void abort(); + + void setInputHandler(framing::InputHandler* handler); + void setShutdownHandler(sys::ShutdownHandler* handler); + sys::ShutdownHandler* getShutdownHandler() const; + framing::OutputHandler* getOutputHandler(); + const std::string& getIdentifier() const; + void activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer>); + const qpid::sys::SecuritySettings* getSecuritySettings() { return 0; } + + size_t decode(const char* buffer, size_t size); + size_t encode(const char* buffer, size_t size); + bool canEncode(); + +protected: + virtual ~TCPConnector(); + void connect(const std::string& host, const std::string& port); + void start(sys::AsynchIO* aio_); + void initAmqp(); + virtual void connectFailed(const std::string& msg); + bool readbuff(qpid::sys::AsynchIO&, qpid::sys::AsynchIOBufferBase*); + void writebuff(qpid::sys::AsynchIO&); + void eof(qpid::sys::AsynchIO&); + void disconnected(qpid::sys::AsynchIO&); + void socketClosed(qpid::sys::AsynchIO&, const qpid::sys::Socket&); + +public: + TCPConnector(boost::shared_ptr<sys::Poller>, + framing::ProtocolVersion pVersion, + const ConnectionSettings&, + ConnectionImpl*); +}; + +}} // namespace qpid::client + +#endif /* _TCPConnector_ */ diff --git a/qpid/cpp/src/qpid/client/amqp0_10/AcceptTracker.cpp b/qpid/cpp/src/qpid/client/amqp0_10/AcceptTracker.cpp new file mode 100644 index 0000000000..bfb20118b5 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/AcceptTracker.cpp @@ -0,0 +1,131 @@ +/* + * + * 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 "AcceptTracker.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +void AcceptTracker::State::accept() +{ + unconfirmed.add(unaccepted); + unaccepted.clear(); +} + +void AcceptTracker::State::accept(qpid::framing::SequenceNumber id) +{ + if (unaccepted.contains(id)) { + unaccepted.remove(id); + unconfirmed.add(id); + } +} + +void AcceptTracker::State::release() +{ + unaccepted.clear(); +} + +uint32_t AcceptTracker::State::acceptsPending() +{ + return unconfirmed.size(); +} + +void AcceptTracker::State::completed(qpid::framing::SequenceSet& set) +{ + unconfirmed.remove(set); +} + +void AcceptTracker::delivered(const std::string& destination, const qpid::framing::SequenceNumber& id) +{ + aggregateState.unaccepted.add(id); + destinationState[destination].unaccepted.add(id); +} + +void AcceptTracker::accept(qpid::client::AsyncSession& session) +{ + for (StateMap::iterator i = destinationState.begin(); i != destinationState.end(); ++i) { + i->second.accept(); + } + Record record; + record.status = session.messageAccept(aggregateState.unaccepted); + record.accepted = aggregateState.unaccepted; + pending.push_back(record); + aggregateState.accept(); +} + +void AcceptTracker::accept(qpid::framing::SequenceNumber id, qpid::client::AsyncSession& session) +{ + for (StateMap::iterator i = destinationState.begin(); i != destinationState.end(); ++i) { + i->second.accept(id); + } + Record record; + record.accepted.add(id); + record.status = session.messageAccept(record.accepted); + pending.push_back(record); + aggregateState.accept(id); +} + +void AcceptTracker::release(qpid::client::AsyncSession& session) +{ + session.messageRelease(aggregateState.unaccepted); + for (StateMap::iterator i = destinationState.begin(); i != destinationState.end(); ++i) { + i->second.release(); + } + aggregateState.release(); +} + +uint32_t AcceptTracker::acceptsPending() +{ + checkPending(); + return aggregateState.acceptsPending(); +} + +uint32_t AcceptTracker::acceptsPending(const std::string& destination) +{ + checkPending(); + return destinationState[destination].acceptsPending(); +} + +void AcceptTracker::reset() +{ + destinationState.clear(); + aggregateState.unaccepted.clear(); + aggregateState.unconfirmed.clear(); + pending.clear(); +} + +void AcceptTracker::checkPending() +{ + while (!pending.empty() && pending.front().status.isComplete()) { + completed(pending.front().accepted); + pending.pop_front(); + } +} + +void AcceptTracker::completed(qpid::framing::SequenceSet& set) +{ + for (StateMap::iterator i = destinationState.begin(); i != destinationState.end(); ++i) { + i->second.completed(set); + } + aggregateState.completed(set); +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/qpid/cpp/src/qpid/client/amqp0_10/AcceptTracker.h b/qpid/cpp/src/qpid/client/amqp0_10/AcceptTracker.h new file mode 100644 index 0000000000..87890e41cc --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/AcceptTracker.h @@ -0,0 +1,87 @@ +#ifndef QPID_CLIENT_AMQP0_10_ACCEPTTRACKER_H +#define QPID_CLIENT_AMQP0_10_ACCEPTTRACKER_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/client/AsyncSession.h" +#include "qpid/client/Completion.h" +#include "qpid/framing/SequenceNumber.h" +#include "qpid/framing/SequenceSet.h" +#include <deque> +#include <map> + +namespace qpid { +namespace client { +namespace amqp0_10 { + +/** + * Tracks the set of messages requiring acceptance, and those for + * which an accept has been issued but is yet to be confirmed + * complete. + */ +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 release(qpid::client::AsyncSession&); + uint32_t acceptsPending(); + uint32_t acceptsPending(const std::string& destination); + void reset(); + private: + struct State + { + /** + * ids of messages that have been delivered but not yet + * accepted + */ + qpid::framing::SequenceSet unaccepted; + /** + * ids of messages for which an accept has been issued but not + * yet confirmed as completed + */ + qpid::framing::SequenceSet unconfirmed; + + void accept(); + void accept(qpid::framing::SequenceNumber); + void release(); + uint32_t acceptsPending(); + void completed(qpid::framing::SequenceSet&); + }; + typedef std::map<std::string, State> StateMap; + struct Record + { + qpid::client::Completion status; + qpid::framing::SequenceSet accepted; + }; + typedef std::deque<Record> Records; + + State aggregateState; + StateMap destinationState; + Records pending; + + void checkPending(); + void completed(qpid::framing::SequenceSet&); +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_ACCEPTTRACKER_H*/ diff --git a/qpid/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp b/qpid/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp new file mode 100644 index 0000000000..f1295a3b66 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp @@ -0,0 +1,966 @@ +/* + * + * 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/amqp0_10/AddressResolution.h" +#include "qpid/amqp_0_10/Codecs.h" +#include "qpid/client/amqp0_10/MessageSource.h" +#include "qpid/client/amqp0_10/MessageSink.h" +#include "qpid/client/amqp0_10/OutgoingMessage.h" +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Message.h" +#include "qpid/types/Variant.h" +#include "qpid/messaging/exceptions.h" +#include "qpid/log/Statement.h" +#include "qpid/framing/enum.h" +#include "qpid/framing/ExchangeBoundResult.h" +#include "qpid/framing/ExchangeQueryResult.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/framing/QueueQueryResult.h" +#include "qpid/framing/ReplyTo.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/Uuid.h" +#include <boost/assign.hpp> +#include <boost/format.hpp> + +namespace qpid { +namespace client { +namespace amqp0_10 { + +using qpid::Exception; +using qpid::messaging::Address; +using qpid::messaging::AddressError; +using qpid::messaging::MalformedAddress; +using qpid::messaging::ResolutionError; +using qpid::messaging::NotFound; +using qpid::messaging::AssertionFailed; +using qpid::framing::ExchangeBoundResult; +using qpid::framing::ExchangeQueryResult; +using qpid::framing::FieldTable; +using qpid::framing::QueueQueryResult; +using qpid::framing::ReplyTo; +using qpid::framing::Uuid; +using namespace qpid::types; +using namespace qpid::framing::message; +using namespace qpid::amqp_0_10; +using namespace boost::assign; + +class Verifier +{ + public: + Verifier(); + void verify(const Address& address) const; + private: + Variant::Map defined; + void verify(const Variant::Map& allowed, const Variant::Map& actual) const; +}; + +namespace{ +const Variant EMPTY_VARIANT; +const FieldTable EMPTY_FIELD_TABLE; +const Variant::List EMPTY_LIST; +const std::string EMPTY_STRING; + +//policy types +const std::string CREATE("create"); +const std::string ASSERT("assert"); +const std::string DELETE("delete"); + +//option names +const std::string NODE("node"); +const std::string LINK("link"); +const std::string MODE("mode"); +const std::string RELIABILITY("reliability"); +const std::string NAME("name"); +const std::string DURABLE("durable"); +const std::string X_DECLARE("x-declare"); +const std::string X_SUBSCRIBE("x-subscribe"); +const std::string X_BINDINGS("x-bindings"); +const std::string EXCHANGE("exchange"); +const std::string QUEUE("queue"); +const std::string KEY("key"); +const std::string ARGUMENTS("arguments"); +const std::string ALTERNATE_EXCHANGE("alternate-exchange"); +const std::string TYPE("type"); +const std::string EXCLUSIVE("exclusive"); +const std::string AUTO_DELETE("auto-delete"); + +//policy values +const std::string ALWAYS("always"); +const std::string NEVER("never"); +const std::string RECEIVER("receiver"); +const std::string SENDER("sender"); + +//address types +const std::string QUEUE_ADDRESS("queue"); +const std::string TOPIC_ADDRESS("topic"); + +//reliability options: +const std::string UNRELIABLE("unreliable"); +const std::string AT_MOST_ONCE("at-most-once"); +const std::string AT_LEAST_ONCE("at-least-once"); +const std::string EXACTLY_ONCE("exactly-once"); + +//receiver modes: +const std::string BROWSE("browse"); +const std::string CONSUME("consume"); + +//0-10 exchange types: +const std::string TOPIC_EXCHANGE("topic"); +const std::string FANOUT_EXCHANGE("fanout"); +const std::string DIRECT_EXCHANGE("direct"); +const std::string HEADERS_EXCHANGE("headers"); +const std::string XML_EXCHANGE("xml"); +const std::string WILDCARD_ANY("#"); + +const Verifier verifier; +} + +struct Binding +{ + Binding(const Variant::Map&); + Binding(const std::string& exchange, const std::string& queue, const std::string& key); + + std::string exchange; + std::string queue; + std::string key; + FieldTable arguments; +}; + +struct Bindings : std::vector<Binding> +{ + void add(const Variant::List& bindings); + void setDefaultExchange(const std::string&); + void setDefaultQueue(const std::string&); + void bind(qpid::client::AsyncSession& session); + void unbind(qpid::client::AsyncSession& session); + void check(qpid::client::AsyncSession& session); +}; + +class Node +{ + protected: + enum CheckMode {FOR_RECEIVER, FOR_SENDER}; + + Node(const Address& address); + + const std::string name; + Variant createPolicy; + Variant assertPolicy; + Variant deletePolicy; + Bindings nodeBindings; + Bindings linkBindings; + + static bool enabled(const Variant& policy, CheckMode mode); + static bool createEnabled(const Address& address, CheckMode mode); + static void convert(const Variant& option, FieldTable& arguments); + static std::vector<std::string> RECEIVER_MODES; + static std::vector<std::string> SENDER_MODES; +}; + + +class Queue : protected Node +{ + public: + Queue(const Address& address); + protected: + void checkCreate(qpid::client::AsyncSession&, CheckMode); + void checkAssert(qpid::client::AsyncSession&, CheckMode); + void checkDelete(qpid::client::AsyncSession&, CheckMode); + private: + const bool durable; + const bool autoDelete; + const bool exclusive; + const std::string alternateExchange; + FieldTable arguments; +}; + +class Exchange : protected Node +{ + public: + Exchange(const Address& address); + protected: + void checkCreate(qpid::client::AsyncSession&, CheckMode); + void checkAssert(qpid::client::AsyncSession&, CheckMode); + void checkDelete(qpid::client::AsyncSession&, CheckMode); + + protected: + const std::string specifiedType; + private: + const bool durable; + const bool autoDelete; + const std::string alternateExchange; + FieldTable arguments; +}; + +class QueueSource : public Queue, public MessageSource +{ + public: + QueueSource(const Address& address); + void subscribe(qpid::client::AsyncSession& session, const std::string& destination); + void cancel(qpid::client::AsyncSession& session, const std::string& destination); + private: + const AcceptMode acceptMode; + const AcquireMode acquireMode; + bool exclusive; + FieldTable options; +}; + +class Subscription : public Exchange, public MessageSource +{ + public: + Subscription(const Address&, const std::string& actualType); + void subscribe(qpid::client::AsyncSession& session, const std::string& destination); + void cancel(qpid::client::AsyncSession& session, const std::string& destination); + private: + const std::string queue; + const bool reliable; + const bool durable; + const std::string actualType; + FieldTable queueOptions; + FieldTable subscriptionOptions; + Bindings bindings; + + void bindSubject(const std::string& subject); + void bindAll(); + void add(const std::string& exchange, const std::string& key); + static std::string getSubscriptionName(const std::string& base, const std::string& name); +}; + +class ExchangeSink : public Exchange, public MessageSink +{ + public: + ExchangeSink(const Address& name); + void declare(qpid::client::AsyncSession& session, const std::string& name); + void send(qpid::client::AsyncSession& session, const std::string& name, OutgoingMessage& message); + void cancel(qpid::client::AsyncSession& session, const std::string& name); + private: +}; + +class QueueSink : public Queue, public MessageSink +{ + public: + QueueSink(const Address& name); + void declare(qpid::client::AsyncSession& session, const std::string& name); + void send(qpid::client::AsyncSession& session, const std::string& name, OutgoingMessage& message); + void cancel(qpid::client::AsyncSession& session, const std::string& name); + private: +}; +bool isQueue(qpid::client::Session session, const qpid::messaging::Address& address); +bool isTopic(qpid::client::Session session, const qpid::messaging::Address& address); + +bool in(const Variant& value, const std::vector<std::string>& choices) +{ + if (!value.isVoid()) { + for (std::vector<std::string>::const_iterator i = choices.begin(); i != choices.end(); ++i) { + if (value.asString() == *i) return true; + } + } + return false; +} + +const Variant& getOption(const Variant::Map& options, const std::string& name) +{ + Variant::Map::const_iterator j = options.find(name); + if (j == options.end()) { + return EMPTY_VARIANT; + } else { + return j->second; + } +} + +const Variant& getOption(const Address& address, const std::string& name) +{ + return getOption(address.getOptions(), name); +} + +bool getReceiverPolicy(const Address& address, const std::string& key) +{ + return in(getOption(address, key), list_of<std::string>(ALWAYS)(RECEIVER)); +} + +bool getSenderPolicy(const Address& address, const std::string& key) +{ + return in(getOption(address, key), list_of<std::string>(ALWAYS)(SENDER)); +} + +struct Opt +{ + Opt(const Address& address); + Opt(const Variant::Map& base); + Opt& operator/(const std::string& name); + operator bool() const; + std::string str() const; + const Variant::List& asList() const; + void collect(qpid::framing::FieldTable& args) const; + + const Variant::Map* options; + const Variant* value; +}; + +Opt::Opt(const Address& address) : options(&(address.getOptions())), value(0) {} +Opt::Opt(const Variant::Map& base) : options(&base), value(0) {} +Opt& Opt::operator/(const std::string& name) +{ + if (options) { + Variant::Map::const_iterator j = options->find(name); + if (j == options->end()) { + value = 0; + options = 0; + } else { + value = &(j->second); + if (value->getType() == VAR_MAP) options = &(value->asMap()); + else options = 0; + } + } + return *this; +} + + +Opt::operator bool() const +{ + return value && !value->isVoid() && value->asBool(); +} + +std::string Opt::str() const +{ + if (value) return value->asString(); + else return EMPTY_STRING; +} + +const Variant::List& Opt::asList() const +{ + if (value) return value->asList(); + else return EMPTY_LIST; +} + +void Opt::collect(qpid::framing::FieldTable& args) const +{ + if (value) { + translate(value->asMap(), args); + } +} + +bool AddressResolution::is_unreliable(const Address& address) +{ + + return in((Opt(address)/LINK/RELIABILITY).str(), + list_of<std::string>(UNRELIABLE)(AT_MOST_ONCE)); +} + +bool AddressResolution::is_reliable(const Address& address) +{ + return in((Opt(address)/LINK/RELIABILITY).str(), + list_of<std::string>(AT_LEAST_ONCE)(EXACTLY_ONCE)); +} + +std::string checkAddressType(qpid::client::Session session, const Address& address) +{ + verifier.verify(address); + if (address.getName().empty()) { + throw MalformedAddress("Name cannot be null"); + } + std::string type = (Opt(address)/NODE/TYPE).str(); + if (type.empty()) { + ExchangeBoundResult result = session.exchangeBound(arg::exchange=address.getName(), arg::queue=address.getName()); + if (result.getQueueNotFound() && result.getExchangeNotFound()) { + //neither a queue nor an exchange exists with that name; treat it as a queue + type = QUEUE_ADDRESS; + } else if (result.getExchangeNotFound()) { + //name refers to a queue + type = QUEUE_ADDRESS; + } else if (result.getQueueNotFound()) { + //name refers to an exchange + type = TOPIC_ADDRESS; + } else { + //both a queue and exchange exist for that name + throw ResolutionError("Ambiguous address, please specify queue or topic as node type"); + } + } + return type; +} + +std::auto_ptr<MessageSource> AddressResolution::resolveSource(qpid::client::Session session, + const Address& address) +{ + std::string type = checkAddressType(session, address); + if (type == TOPIC_ADDRESS) { + std::string exchangeType = sync(session).exchangeQuery(address.getName()).getType(); + std::auto_ptr<MessageSource> source(new Subscription(address, exchangeType)); + QPID_LOG(debug, "treating source address as topic: " << address); + return source; + } else if (type == QUEUE_ADDRESS) { + std::auto_ptr<MessageSource> source(new QueueSource(address)); + QPID_LOG(debug, "treating source address as queue: " << address); + return source; + } else { + throw ResolutionError("Unrecognised type: " + type); + } +} + + +std::auto_ptr<MessageSink> AddressResolution::resolveSink(qpid::client::Session session, + const qpid::messaging::Address& address) +{ + std::string type = checkAddressType(session, address); + if (type == TOPIC_ADDRESS) { + std::auto_ptr<MessageSink> sink(new ExchangeSink(address)); + QPID_LOG(debug, "treating target address as topic: " << address); + return sink; + } else if (type == QUEUE_ADDRESS) { + std::auto_ptr<MessageSink> sink(new QueueSink(address)); + QPID_LOG(debug, "treating target address as queue: " << address); + return sink; + } else { + throw ResolutionError("Unrecognised type: " + type); + } +} + +bool isBrowse(const Address& address) +{ + const Variant& mode = getOption(address, MODE); + if (!mode.isVoid()) { + std::string value = mode.asString(); + if (value == BROWSE) return true; + else if (value != CONSUME) throw ResolutionError("Invalid mode"); + } + return false; +} + +QueueSource::QueueSource(const Address& address) : + Queue(address), + acceptMode(AddressResolution::is_unreliable(address) ? ACCEPT_MODE_NONE : ACCEPT_MODE_EXPLICIT), + acquireMode(isBrowse(address) ? ACQUIRE_MODE_NOT_ACQUIRED : ACQUIRE_MODE_PRE_ACQUIRED), + exclusive(false) +{ + //extract subscription arguments from address options (nb: setting + //of accept-mode/acquire-mode/destination controlled though other + //options) + exclusive = Opt(address)/LINK/X_SUBSCRIBE/EXCLUSIVE; + (Opt(address)/LINK/X_SUBSCRIBE/ARGUMENTS).collect(options); +} + +void QueueSource::subscribe(qpid::client::AsyncSession& session, const std::string& destination) +{ + checkCreate(session, FOR_RECEIVER); + checkAssert(session, FOR_RECEIVER); + linkBindings.bind(session); + session.messageSubscribe(arg::queue=name, + arg::destination=destination, + arg::acceptMode=acceptMode, + arg::acquireMode=acquireMode, + arg::exclusive=exclusive, + arg::arguments=options); +} + +void QueueSource::cancel(qpid::client::AsyncSession& session, const std::string& destination) +{ + linkBindings.unbind(session); + session.messageCancel(destination); + checkDelete(session, FOR_RECEIVER); +} + +std::string Subscription::getSubscriptionName(const std::string& base, const std::string& name) +{ + if (name.empty()) { + return (boost::format("%1%_%2%") % base % Uuid(true).str()).str(); + } else { + return (boost::format("%1%_%2%") % base % name).str(); + } +} + +Subscription::Subscription(const Address& address, const std::string& type) + : Exchange(address), + 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) +{ + (Opt(address)/LINK/X_DECLARE/ARGUMENTS).collect(queueOptions); + (Opt(address)/LINK/X_SUBSCRIBE/ARGUMENTS).collect(subscriptionOptions); + + if (!address.getSubject().empty()) bindSubject(address.getSubject()); + else if (linkBindings.empty()) bindAll(); +} + +void Subscription::bindSubject(const std::string& subject) +{ + if (actualType == HEADERS_EXCHANGE) { + Binding b(name, queue, subject); + b.arguments.setString("qpid.subject", subject); + b.arguments.setString("x-match", "all"); + bindings.push_back(b); + } else if (actualType == XML_EXCHANGE) { + Binding b(name, queue, subject); + std::string query = (boost::format("declare variable $qpid.subject external; $qpid.subject = '%1%'") + % subject).str(); + b.arguments.setString("xquery", query); + bindings.push_back(b); + } else { + //Note: the fanout exchange doesn't support any filtering, so + //the subject is ignored in that case + add(name, subject); + } +} + +void Subscription::bindAll() +{ + if (actualType == TOPIC_EXCHANGE) { + add(name, WILDCARD_ANY); + } else if (actualType == FANOUT_EXCHANGE) { + add(name, queue); + } else if (actualType == HEADERS_EXCHANGE) { + Binding b(name, queue, "match-all"); + b.arguments.setString("x-match", "all"); + bindings.push_back(b); + } else if (actualType == XML_EXCHANGE) { + Binding b(name, queue, EMPTY_STRING); + b.arguments.setString("xquery", "true()"); + bindings.push_back(b); + } else { + add(name, EMPTY_STRING); + } +} + +void Subscription::add(const std::string& exchange, const std::string& key) +{ + bindings.push_back(Binding(exchange, queue, key)); +} + +void Subscription::subscribe(qpid::client::AsyncSession& session, const std::string& destination) +{ + //create exchange if required and specified by policy: + checkCreate(session, FOR_RECEIVER); + checkAssert(session, FOR_RECEIVER); + + //create subscription queue: + session.queueDeclare(arg::queue=queue, arg::exclusive=true, + arg::autoDelete=!reliable, arg::durable=durable, arg::arguments=queueOptions); + //'default' binding: + bindings.bind(session); + //any explicit bindings: + linkBindings.setDefaultQueue(queue); + 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); +} + +void Subscription::cancel(qpid::client::AsyncSession& session, const std::string& destination) +{ + linkBindings.unbind(session); + session.messageCancel(destination); + session.queueDelete(arg::queue=queue); + checkDelete(session, FOR_RECEIVER); +} + +ExchangeSink::ExchangeSink(const Address& address) : Exchange(address) {} + +void ExchangeSink::declare(qpid::client::AsyncSession& session, const std::string&) +{ + checkCreate(session, FOR_SENDER); + checkAssert(session, FOR_SENDER); + linkBindings.bind(session); +} + +void ExchangeSink::send(qpid::client::AsyncSession& session, const std::string&, OutgoingMessage& m) +{ + m.message.getDeliveryProperties().setRoutingKey(m.getSubject()); + m.status = session.messageTransfer(arg::destination=name, arg::content=m.message); +} + +void ExchangeSink::cancel(qpid::client::AsyncSession& session, const std::string&) +{ + linkBindings.unbind(session); + checkDelete(session, FOR_SENDER); +} + +QueueSink::QueueSink(const Address& address) : Queue(address) {} + +void QueueSink::declare(qpid::client::AsyncSession& session, const std::string&) +{ + checkCreate(session, FOR_SENDER); + checkAssert(session, FOR_SENDER); + linkBindings.bind(session); +} +void QueueSink::send(qpid::client::AsyncSession& session, const std::string&, OutgoingMessage& m) +{ + m.message.getDeliveryProperties().setRoutingKey(name); + m.status = session.messageTransfer(arg::content=m.message); +} + +void QueueSink::cancel(qpid::client::AsyncSession& session, const std::string&) +{ + linkBindings.unbind(session); + checkDelete(session, FOR_SENDER); +} + +Address AddressResolution::convert(const qpid::framing::ReplyTo& rt) +{ + Address address; + if (rt.getExchange().empty()) {//if default exchange, treat as queue + address.setName(rt.getRoutingKey()); + address.setType(QUEUE_ADDRESS); + } else { + address.setName(rt.getExchange()); + address.setSubject(rt.getRoutingKey()); + address.setType(TOPIC_ADDRESS); + } + return address; +} + +qpid::framing::ReplyTo AddressResolution::convert(const Address& address) +{ + if (address.getType() == QUEUE_ADDRESS || address.getType().empty()) { + return ReplyTo(EMPTY_STRING, address.getName()); + } else if (address.getType() == TOPIC_ADDRESS) { + return ReplyTo(address.getName(), address.getSubject()); + } else { + QPID_LOG(notice, "Unrecognised type for reply-to: " << address.getType()); + return ReplyTo(EMPTY_STRING, address.getName());//treat as queue + } +} + +bool isQueue(qpid::client::Session session, const qpid::messaging::Address& address) +{ + return address.getType() == QUEUE_ADDRESS || + (address.getType().empty() && session.queueQuery(address.getName()).getQueue() == address.getName()); +} + +bool isTopic(qpid::client::Session session, const qpid::messaging::Address& address) +{ + if (address.getType().empty()) { + return !session.exchangeQuery(address.getName()).getNotFound(); + } else if (address.getType() == TOPIC_ADDRESS) { + return true; + } else { + return false; + } +} + +Node::Node(const Address& address) : name(address.getName()), + createPolicy(getOption(address, CREATE)), + assertPolicy(getOption(address, ASSERT)), + deletePolicy(getOption(address, DELETE)) +{ + nodeBindings.add((Opt(address)/NODE/X_BINDINGS).asList()); + linkBindings.add((Opt(address)/LINK/X_BINDINGS).asList()); +} + +Queue::Queue(const Address& a) : Node(a), + durable(Opt(a)/NODE/DURABLE), + autoDelete(Opt(a)/NODE/X_DECLARE/AUTO_DELETE), + exclusive(Opt(a)/NODE/X_DECLARE/EXCLUSIVE), + alternateExchange((Opt(a)/NODE/X_DECLARE/ALTERNATE_EXCHANGE).str()) +{ + (Opt(a)/NODE/X_DECLARE/ARGUMENTS).collect(arguments); + nodeBindings.setDefaultQueue(name); + linkBindings.setDefaultQueue(name); +} + +void Queue::checkCreate(qpid::client::AsyncSession& session, CheckMode mode) +{ + if (enabled(createPolicy, mode)) { + QPID_LOG(debug, "Auto-creating queue '" << name << "'"); + try { + session.queueDeclare(arg::queue=name, + arg::durable=durable, + arg::autoDelete=autoDelete, + arg::exclusive=exclusive, + arg::alternateExchange=alternateExchange, + arg::arguments=arguments); + nodeBindings.bind(session); + session.sync(); + } catch (const qpid::framing::ResourceLockedException& e) { + throw ResolutionError((boost::format("Creation failed for queue %1%; %2%") % name % e.what()).str()); + } catch (const qpid::framing::NotAllowedException& e) { + throw ResolutionError((boost::format("Creation failed for queue %1%; %2%") % name % e.what()).str()); + } catch (const qpid::framing::NotFoundException& e) {//may be thrown when creating bindings + throw ResolutionError((boost::format("Creation failed for queue %1%; %2%") % name % e.what()).str()); + } + } else { + try { + sync(session).queueDeclare(arg::queue=name, arg::passive=true); + } catch (const qpid::framing::NotFoundException& /*e*/) { + throw NotFound((boost::format("Queue %1% does not exist") % name).str()); + } + } +} + +void Queue::checkDelete(qpid::client::AsyncSession& session, CheckMode mode) +{ + //Note: queue-delete will cause a session exception if the queue + //does not exist, the query here prevents obvious cases of this + //but there is a race whenever two deletions are made concurrently + //so careful use of the delete policy is recommended at present + if (enabled(deletePolicy, mode) && sync(session).queueQuery(name).getQueue() == name) { + QPID_LOG(debug, "Auto-deleting queue '" << name << "'"); + sync(session).queueDelete(arg::queue=name); + } +} + +void Queue::checkAssert(qpid::client::AsyncSession& session, CheckMode mode) +{ + if (enabled(assertPolicy, mode)) { + QueueQueryResult result = sync(session).queueQuery(name); + if (result.getQueue() != name) { + throw NotFound((boost::format("Queue not found: %1%") % name).str()); + } else { + if (durable && !result.getDurable()) { + throw AssertionFailed((boost::format("Queue not durable: %1%") % name).str()); + } + if (autoDelete && !result.getAutoDelete()) { + throw AssertionFailed((boost::format("Queue not set to auto-delete: %1%") % name).str()); + } + if (exclusive && !result.getExclusive()) { + throw AssertionFailed((boost::format("Queue not exclusive: %1%") % name).str()); + } + if (!alternateExchange.empty() && result.getAlternateExchange() != alternateExchange) { + throw AssertionFailed((boost::format("Alternate exchange does not match for %1%, expected %2%, got %3%") + % name % alternateExchange % result.getAlternateExchange()).str()); + } + for (FieldTable::ValueMap::const_iterator i = arguments.begin(); i != arguments.end(); ++i) { + 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) { + throw AssertionFailed((boost::format("Option %1% does not match for %2%, expected %3%, got %4%") + % i->first % name % *(i->second) % *v).str()); + } + } + nodeBindings.check(session); + } + } +} + +Exchange::Exchange(const Address& a) : Node(a), + specifiedType((Opt(a)/NODE/X_DECLARE/TYPE).str()), + durable(Opt(a)/NODE/DURABLE), + autoDelete(Opt(a)/NODE/X_DECLARE/AUTO_DELETE), + alternateExchange((Opt(a)/NODE/X_DECLARE/ALTERNATE_EXCHANGE).str()) +{ + (Opt(a)/NODE/X_DECLARE/ARGUMENTS).collect(arguments); + nodeBindings.setDefaultExchange(name); + linkBindings.setDefaultExchange(name); +} + +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); + nodeBindings.bind(session); + session.sync(); + } catch (const qpid::framing::NotAllowedException& e) { + throw ResolutionError((boost::format("Create failed for exchange %1%; %2%") % name % e.what()).str()); + } catch (const qpid::framing::NotFoundException& e) {//can be caused when creating bindings + throw ResolutionError((boost::format("Create failed for exchange %1%; %2%") % name % e.what()).str()); + } + } else { + try { + sync(session).exchangeDeclare(arg::exchange=name, arg::passive=true); + } catch (const qpid::framing::NotFoundException& /*e*/) { + throw NotFound((boost::format("Exchange %1% does not exist") % name).str()); + } + } +} + +void Exchange::checkDelete(qpid::client::AsyncSession& session, CheckMode mode) +{ + //Note: exchange-delete will cause a session exception if the + //exchange does not exist, the query here prevents obvious cases + //of this but there is a race whenever two deletions are made + //concurrently so careful use of the delete policy is recommended + //at present + if (enabled(deletePolicy, mode) && !sync(session).exchangeQuery(name).getNotFound()) { + sync(session).exchangeDelete(arg::exchange=name); + } +} + +void Exchange::checkAssert(qpid::client::AsyncSession& session, CheckMode mode) +{ + if (enabled(assertPolicy, mode)) { + ExchangeQueryResult result = sync(session).exchangeQuery(name); + if (result.getNotFound()) { + throw NotFound((boost::format("Exchange not found: %1%") % name).str()); + } else { + if (specifiedType.size() && result.getType() != specifiedType) { + throw AssertionFailed((boost::format("Exchange %1% is of incorrect type, expected %2% but got %3%") + % name % specifiedType % result.getType()).str()); + } + if (durable && !result.getDurable()) { + throw AssertionFailed((boost::format("Exchange not durable: %1%") % name).str()); + } + //Note: Can't check auto-delete or alternate-exchange via + //exchange-query-result as these are not returned + //TODO: could use a passive declare to check alternate-exchange + for (FieldTable::ValueMap::const_iterator i = arguments.begin(); i != arguments.end(); ++i) { + 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) { + throw AssertionFailed((boost::format("Option %1% does not match for %2%, expected %3%, got %4%") + % i->first % name % *(i->second) % *v).str()); + } + } + nodeBindings.check(session); + } + } +} + +Binding::Binding(const Variant::Map& b) : + exchange((Opt(b)/EXCHANGE).str()), + queue((Opt(b)/QUEUE).str()), + key((Opt(b)/KEY).str()) +{ + (Opt(b)/ARGUMENTS).collect(arguments); +} + +Binding::Binding(const std::string& e, const std::string& q, const std::string& k) : exchange(e), queue(q), key(k) {} + + +void Bindings::add(const Variant::List& list) +{ + for (Variant::List::const_iterator i = list.begin(); i != list.end(); ++i) { + push_back(Binding(i->asMap())); + } +} + +void Bindings::setDefaultExchange(const std::string& exchange) +{ + for (Bindings::iterator i = begin(); i != end(); ++i) { + if (i->exchange.empty()) i->exchange = exchange; + } +} + +void Bindings::setDefaultQueue(const std::string& queue) +{ + for (Bindings::iterator i = begin(); i != end(); ++i) { + if (i->queue.empty()) i->queue = queue; + } +} + +void Bindings::bind(qpid::client::AsyncSession& session) +{ + for (Bindings::const_iterator i = begin(); i != end(); ++i) { + session.exchangeBind(arg::queue=i->queue, + arg::exchange=i->exchange, + arg::bindingKey=i->key, + arg::arguments=i->arguments); + } +} + +void Bindings::unbind(qpid::client::AsyncSession& session) +{ + for (Bindings::const_iterator i = begin(); i != end(); ++i) { + session.exchangeUnbind(arg::queue=i->queue, + arg::exchange=i->exchange, + arg::bindingKey=i->key); + } +} + +void Bindings::check(qpid::client::AsyncSession& session) +{ + for (Bindings::const_iterator i = begin(); i != end(); ++i) { + ExchangeBoundResult result = sync(session).exchangeBound(arg::queue=i->queue, + arg::exchange=i->exchange, + arg::bindingKey=i->key); + if (result.getQueueNotMatched() || result.getKeyNotMatched()) { + throw AssertionFailed((boost::format("No such binding [exchange=%1%, queue=%2%, key=%3%]") + % i->exchange % i->queue % i->key).str()); + } + } +} + +bool Node::enabled(const Variant& policy, CheckMode mode) +{ + bool result = false; + switch (mode) { + case FOR_RECEIVER: + result = in(policy, RECEIVER_MODES); + break; + case FOR_SENDER: + result = in(policy, SENDER_MODES); + break; + } + return result; +} + +bool Node::createEnabled(const Address& address, CheckMode mode) +{ + const Variant& policy = getOption(address, CREATE); + return enabled(policy, mode); +} + +void Node::convert(const Variant& options, FieldTable& arguments) +{ + if (!options.isVoid()) { + translate(options.asMap(), arguments); + } +} +std::vector<std::string> Node::RECEIVER_MODES = list_of<std::string>(ALWAYS) (RECEIVER); +std::vector<std::string> Node::SENDER_MODES = list_of<std::string>(ALWAYS) (SENDER); + +Verifier::Verifier() +{ + defined[CREATE] = true; + defined[ASSERT] = true; + defined[DELETE] = true; + defined[MODE] = true; + Variant::Map node; + node[TYPE] = true; + node[DURABLE] = true; + node[X_DECLARE] = true; + node[X_BINDINGS] = true; + defined[NODE] = node; + Variant::Map link; + link[NAME] = true; + link[DURABLE] = true; + link[RELIABILITY] = true; + link[X_SUBSCRIBE] = true; + link[X_DECLARE] = true; + link[X_BINDINGS] = true; + defined[LINK] = link; +} +void Verifier::verify(const Address& address) const +{ + verify(defined, address.getOptions()); +} + +void Verifier::verify(const Variant::Map& allowed, const Variant::Map& actual) const +{ + for (Variant::Map::const_iterator i = actual.begin(); i != actual.end(); ++i) { + Variant::Map::const_iterator option = allowed.find(i->first); + if (option == allowed.end()) { + throw AddressError((boost::format("Unrecognised option: %1%") % i->first).str()); + } else if (option->second.getType() == qpid::types::VAR_MAP) { + verify(option->second.asMap(), i->second.asMap()); + } + } +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/qpid/cpp/src/qpid/client/amqp0_10/AddressResolution.h b/qpid/cpp/src/qpid/client/amqp0_10/AddressResolution.h new file mode 100644 index 0000000000..fc8f1a1d18 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/AddressResolution.h @@ -0,0 +1,64 @@ +#ifndef QPID_CLIENT_AMQP0_10_ADDRESSRESOLUTION_H +#define QPID_CLIENT_AMQP0_10_ADDRESSRESOLUTION_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/client/Session.h" + +namespace qpid { + +namespace framing{ +class ReplyTo; +} + +namespace messaging { +class Address; +} + +namespace client { +namespace amqp0_10 { + +class MessageSource; +class MessageSink; + +/** + * Maps from a generic Address and optional Filter to an AMQP 0-10 + * MessageSource which will then be used by a ReceiverImpl instance + * created for the address. + */ +class AddressResolution +{ + public: + std::auto_ptr<MessageSource> resolveSource(qpid::client::Session session, + const qpid::messaging::Address& address); + + std::auto_ptr<MessageSink> resolveSink(qpid::client::Session session, + const qpid::messaging::Address& address); + + static qpid::messaging::Address convert(const qpid::framing::ReplyTo&); + static qpid::framing::ReplyTo convert(const qpid::messaging::Address&); + static bool is_unreliable(const qpid::messaging::Address& address); + static bool is_reliable(const qpid::messaging::Address& address); + private: +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_ADDRESSRESOLUTION_H*/ diff --git a/qpid/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp b/qpid/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp new file mode 100644 index 0000000000..a87a8dea67 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp @@ -0,0 +1,323 @@ +/* + * + * 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 "ConnectionImpl.h" +#include "SessionImpl.h" +#include "SimpleUrlParser.h" +#include "qpid/messaging/exceptions.h" +#include "qpid/messaging/Session.h" +#include "qpid/messaging/PrivateImplRef.h" +#include "qpid/framing/Uuid.h" +#include "qpid/log/Statement.h" +#include "qpid/Url.h" +#include <boost/intrusive_ptr.hpp> +#include <vector> +#include <sstream> + +namespace qpid { +namespace client { +namespace amqp0_10 { + +using qpid::types::Variant; +using qpid::types::VAR_LIST; +using qpid::framing::Uuid; + +namespace { +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()); + } +} + +std::string asString(const std::vector<std::string>& v) { + std::stringstream os; + os << "["; + for(std::vector<std::string>::const_iterator i = v.begin(); i != v.end(); ++i ) { + if (i != v.begin()) os << ", "; + os << *i; + } + os << "]"; + return os.str(); +} +} + +ConnectionImpl::ConnectionImpl(const std::string& url, const Variant::Map& options) : + reconnect(false), timeout(-1), limit(-1), + minReconnectInterval(3), maxReconnectInterval(60), + retries(0), reconnectOnLimitExceeded(true) +{ + setOptions(options); + urls.insert(urls.begin(), url); + QPID_LOG(debug, "Created connection " << url << " with " << options); +} + +void ConnectionImpl::setOptions(const Variant::Map& options) +{ + for (Variant::Map::const_iterator i = options.begin(); i != options.end(); ++i) { + setOption(i->first, i->second); + } +} + +void ConnectionImpl::setOption(const std::string& name, const Variant& value) +{ + 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" || name == "reconnect_urls") { + if (value.getType() == VAR_LIST) { + convert(value.asList(), urls); + } else { + urls.push_back(value.asString()); + } + } 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 { + throw qpid::messaging::MessagingException(QPID_MSG("Invalid option: " << name << " not recognised")); + } +} + + +void ConnectionImpl::close() +{ + while(true) { + messaging::Session session; + { + qpid::sys::Mutex::ScopedLock l(lock); + if (sessions.empty()) break; + session = sessions.begin()->second; + } + session.close(); + } + detach(); +} + +void ConnectionImpl::detach() +{ + qpid::sys::Mutex::ScopedLock l(lock); + connection.close(); +} + +bool ConnectionImpl::isOpen() const +{ + qpid::sys::Mutex::ScopedLock l(lock); + return connection.isOpen(); +} + +boost::intrusive_ptr<SessionImpl> getImplPtr(qpid::messaging::Session& session) +{ + return boost::dynamic_pointer_cast<SessionImpl>( + qpid::messaging::PrivateImplRef<qpid::messaging::Session>::get(session) + ); +} + +void ConnectionImpl::closed(SessionImpl& s) +{ + qpid::sys::Mutex::ScopedLock l(lock); + for (Sessions::iterator i = sessions.begin(); i != sessions.end(); ++i) { + if (getImplPtr(i->second).get() == &s) { + sessions.erase(i); + break; + } + } +} + +qpid::messaging::Session ConnectionImpl::getSession(const std::string& name) const +{ + qpid::sys::Mutex::ScopedLock l(lock); + Sessions::const_iterator i = sessions.find(name); + if (i == sessions.end()) { + throw qpid::messaging::KeyError("No such session: " + name); + } else { + return i->second; + } +} + +qpid::messaging::Session ConnectionImpl::newSession(bool transactional, const std::string& n) +{ + std::string name = n.empty() ? Uuid(true).str() : n; + qpid::messaging::Session impl(new SessionImpl(*this, transactional)); + while (true) { + try { + getImplPtr(impl)->setSession(connection.newSession(name)); + qpid::sys::Mutex::ScopedLock l(lock); + sessions[name] = impl; + break; + } catch (const qpid::TransportFailure&) { + open(); + } catch (const qpid::SessionException& e) { + throw qpid::messaging::SessionError(e.what()); + } catch (const std::exception& e) { + throw qpid::messaging::MessagingException(e.what()); + } + } + return impl; +} + +void ConnectionImpl::open() +{ + qpid::sys::AbsTime start = qpid::sys::now(); + qpid::sys::ScopedLock<qpid::sys::Semaphore> l(semaphore); + try { + if (!connection.isOpen()) connect(start); + } + catch (const types::Exception&) { throw; } + catch (const qpid::Exception& e) { throw messaging::ConnectionError(e.what()); } +} + +bool expired(const qpid::sys::AbsTime& start, int64_t timeout) +{ + if (timeout == 0) return true; + if (timeout < 0) return false; + qpid::sys::Duration used(start, qpid::sys::now()); + qpid::sys::Duration allowed = timeout * qpid::sys::TIME_SEC; + return allowed < used; +} + +void ConnectionImpl::connect(const qpid::sys::AbsTime& started) +{ + for (int64_t i = minReconnectInterval; !tryConnect(); i = std::min(i * 2, maxReconnectInterval)) { + if (!reconnect) { + throw qpid::messaging::TransportFailure("Failed to connect (reconnect disabled)"); + } + if (limit >= 0 && retries++ >= limit) { + throw qpid::messaging::TransportFailure("Failed to connect within reconnect limit"); + } + if (expired(started, timeout)) { + throw qpid::messaging::TransportFailure("Failed to connect within reconnect timeout"); + } + else qpid::sys::sleep(i); + } + retries = 0; +} + +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)); + } +} + +bool ConnectionImpl::tryConnect() +{ + sys::Mutex::ScopedLock l(lock); + 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); + } + 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 + QPID_LOG(info, "Failed to connect to " << *i << ": " << e.what()); + } + } + return false; +} + +bool ConnectionImpl::resetSessions(const sys::Mutex::ScopedLock& ) +{ + try { + qpid::sys::Mutex::ScopedLock l(lock); + for (Sessions::iterator i = sessions.begin(); i != sessions.end(); ++i) { + getImplPtr(i->second)->setSession(connection.newSession(i->first)); + } + return true; + } catch (const qpid::TransportFailure&) { + QPID_LOG(debug, "Connection failed while re-initialising sessions"); + return false; + } catch (const qpid::framing::ResourceLimitExceededException& e) { + if (reconnectOnLimitExceeded) { + QPID_LOG(debug, "Detaching and reconnecting due to: " << e.what()); + detach(); + return false; + } else { + throw qpid::messaging::TargetCapacityExceeded(e.what()); + } + } +} + +bool ConnectionImpl::backoff() +{ + if (reconnectOnLimitExceeded) { + detach(); + open(); + return true; + } else { + return false; + } +} +std::string ConnectionImpl::getAuthenticatedUsername() +{ + return connection.getNegotiatedSettings().username; +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/qpid/cpp/src/qpid/client/amqp0_10/ConnectionImpl.h b/qpid/cpp/src/qpid/client/amqp0_10/ConnectionImpl.h new file mode 100644 index 0000000000..09f2038312 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/ConnectionImpl.h @@ -0,0 +1,80 @@ +#ifndef QPID_CLIENT_AMQP0_10_CONNECTIONIMPL_H +#define QPID_CLIENT_AMQP0_10_CONNECTIONIMPL_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/messaging/ConnectionImpl.h" +#include "qpid/types/Variant.h" +#include "qpid/client/Connection.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Semaphore.h" +#include <map> +#include <vector> + +namespace qpid { +struct Url; + +namespace client { +namespace amqp0_10 { + +class SessionImpl; + +class ConnectionImpl : public qpid::messaging::ConnectionImpl +{ + public: + ConnectionImpl(const std::string& url, const qpid::types::Variant::Map& options); + void open(); + bool isOpen() const; + void close(); + qpid::messaging::Session newSession(bool transactional, const std::string& name); + qpid::messaging::Session getSession(const std::string& name) const; + void closed(SessionImpl&); + void detach(); + void setOption(const std::string& name, const qpid::types::Variant& value); + bool backoff(); + std::string getAuthenticatedUsername(); + private: + typedef std::map<std::string, qpid::messaging::Session> Sessions; + + mutable qpid::sys::Mutex lock;//used to protect data structures + qpid::sys::Semaphore semaphore;//used to coordinate reconnection + Sessions sessions; + qpid::client::Connection connection; + std::vector<std::string> urls; + qpid::client::ConnectionSettings settings; + bool reconnect; + int64_t timeout; + int32_t limit; + int64_t minReconnectInterval; + int64_t maxReconnectInterval; + int32_t retries; + bool reconnectOnLimitExceeded; + + void setOptions(const qpid::types::Variant::Map& options); + void connect(const qpid::sys::AbsTime& started); + bool tryConnect(); + bool resetSessions(const sys::Mutex::ScopedLock&); // dummy parameter indicates call with lock held. + void mergeUrls(const std::vector<Url>& more, const sys::Mutex::ScopedLock&); +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_CONNECTIONIMPL_H*/ diff --git a/qpid/cpp/src/qpid/client/amqp0_10/IncomingMessages.cpp b/qpid/cpp/src/qpid/client/amqp0_10/IncomingMessages.cpp new file mode 100644 index 0000000000..71e89bdba1 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/IncomingMessages.cpp @@ -0,0 +1,361 @@ +/* + * + * 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/amqp0_10/IncomingMessages.h" +#include "qpid/client/amqp0_10/AddressResolution.h" +#include "qpid/amqp_0_10/Codecs.h" +#include "qpid/client/SessionImpl.h" +#include "qpid/client/SessionBase_0_10Access.h" +#include "qpid/log/Statement.h" +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Duration.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/MessageImpl.h" +#include "qpid/types/Variant.h" +#include "qpid/framing/DeliveryProperties.h" +#include "qpid/framing/FrameSet.h" +#include "qpid/framing/MessageProperties.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/enum.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +using namespace qpid::framing; +using namespace qpid::framing::message; +using namespace qpid::amqp_0_10; +using qpid::sys::AbsTime; +using qpid::sys::Duration; +using qpid::messaging::MessageImplAccess; +using qpid::types::Variant; + +namespace { +const std::string EMPTY_STRING; + + +struct GetNone : IncomingMessages::Handler +{ + bool accept(IncomingMessages::MessageTransfer&) { return false; } +}; + +struct GetAny : IncomingMessages::Handler +{ + bool accept(IncomingMessages::MessageTransfer& transfer) + { + transfer.retrieve(0); + return true; + } +}; + +struct MatchAndTrack +{ + const std::string destination; + SequenceSet ids; + + MatchAndTrack(const std::string& d) : destination(d) {} + + bool operator()(boost::shared_ptr<qpid::framing::FrameSet> command) + { + if (command->as<MessageTransferBody>()->getDestination() == destination) { + ids.add(command->getId()); + return true; + } else { + return false; + } + } +}; + +struct Match +{ + const std::string destination; + uint32_t matched; + + Match(const std::string& d) : destination(d), matched(0) {} + + bool operator()(boost::shared_ptr<qpid::framing::FrameSet> command) + { + if (command->as<MessageTransferBody>()->getDestination() == destination) { + ++matched; + return true; + } else { + return false; + } + } +}; +} + +void IncomingMessages::setSession(qpid::client::AsyncSession s) +{ + sys::Mutex::ScopedLock l(lock); + session = s; + incoming = SessionBase_0_10Access(session).get()->getDemux().getDefault(); + acceptTracker.reset(); +} + +bool IncomingMessages::get(Handler& handler, Duration timeout) +{ + { + sys::Mutex::ScopedLock l(lock); + //search through received list for any transfer of interest: + for (FrameSetQueue::iterator i = received.begin(); i != received.end(); i++) + { + MessageTransfer transfer(*i, *this); + if (handler.accept(transfer)) { + received.erase(i); + return true; + } + } + } + //none found, check incoming: + return process(&handler, timeout); +} + +bool IncomingMessages::getNextDestination(std::string& destination, Duration timeout) +{ + sys::Mutex::ScopedLock l(lock); + //if there is not already a received message, we must wait for one + if (received.empty() && !wait(timeout)) return false; + //else we have a message in received; return the corresponding destination + destination = received.front()->as<MessageTransferBody>()->getDestination(); + return true; +} + +void IncomingMessages::accept() +{ + sys::Mutex::ScopedLock l(lock); + acceptTracker.accept(session); +} + +void IncomingMessages::accept(qpid::framing::SequenceNumber id) +{ + sys::Mutex::ScopedLock l(lock); + acceptTracker.accept(id, session); +} + + +void IncomingMessages::releaseAll() +{ + { + //first process any received messages... + sys::Mutex::ScopedLock l(lock); + while (!received.empty()) { + retrieve(received.front(), 0); + received.pop_front(); + } + } + //then pump out any available messages from incoming queue... + GetAny handler; + while (process(&handler, 0)) ; + //now release all messages + sys::Mutex::ScopedLock l(lock); + acceptTracker.release(session); +} + +void IncomingMessages::releasePending(const std::string& destination) +{ + //first pump all available messages from incoming to received... + while (process(0, 0)) ; + + //now remove all messages for this destination from received list, recording their ids... + sys::Mutex::ScopedLock l(lock); + MatchAndTrack match(destination); + for (FrameSetQueue::iterator i = received.begin(); i != received.end(); i = match(*i) ? received.erase(i) : ++i) ; + //now release those messages + session.messageRelease(match.ids); +} + +/** + * Get a frameset that is accepted by the specified handler from + * session queue, waiting for up to the specified duration and + * returning true if this could be achieved, false otherwise. Messages + * that are not accepted by the handler are pushed onto received queue + * for later retrieval. + */ +bool IncomingMessages::process(Handler* handler, qpid::sys::Duration duration) +{ + AbsTime deadline(AbsTime::now(), duration); + FrameSet::shared_ptr content; + try { + for (Duration timeout = duration; incoming->pop(content, timeout); timeout = Duration(AbsTime::now(), deadline)) { + if (content->isA<MessageTransferBody>()) { + MessageTransfer transfer(content, *this); + if (handler && handler->accept(transfer)) { + QPID_LOG(debug, "Delivered " << *content->getMethod()); + return true; + } else { + //received message for another destination, keep for later + QPID_LOG(debug, "Pushed " << *content->getMethod() << " to received queue"); + sys::Mutex::ScopedLock l(lock); + received.push_back(content); + } + } else { + //TODO: handle other types of commands (e.g. message-accept, message-flow etc) + } + } + } + catch (const qpid::ClosedException&) {} // Just return false if queue closed. + return false; +} + +bool IncomingMessages::wait(qpid::sys::Duration duration) +{ + AbsTime deadline(AbsTime::now(), duration); + FrameSet::shared_ptr content; + for (Duration timeout = duration; incoming->pop(content, timeout); timeout = Duration(AbsTime::now(), deadline)) { + if (content->isA<MessageTransferBody>()) { + QPID_LOG(debug, "Pushed " << *content->getMethod() << " to received queue"); + sys::Mutex::ScopedLock l(lock); + received.push_back(content); + return true; + } else { + //TODO: handle other types of commands (e.g. message-accept, message-flow etc) + } + } + return false; +} + +uint32_t IncomingMessages::pendingAccept() +{ + sys::Mutex::ScopedLock l(lock); + return acceptTracker.acceptsPending(); +} +uint32_t IncomingMessages::pendingAccept(const std::string& destination) +{ + sys::Mutex::ScopedLock l(lock); + return acceptTracker.acceptsPending(destination); +} + +uint32_t IncomingMessages::available() +{ + //first pump all available messages from incoming to received... + while (process(0, 0)) {} + //return the count of received messages + sys::Mutex::ScopedLock l(lock); + return received.size(); +} + +uint32_t IncomingMessages::available(const std::string& destination) +{ + //first pump all available messages from incoming to received... + while (process(0, 0)) {} + + //count all messages for this destination from received list + sys::Mutex::ScopedLock l(lock); + return std::for_each(received.begin(), received.end(), Match(destination)).matched; +} + +void populate(qpid::messaging::Message& message, FrameSet& command); + +/** + * Called when message is retrieved; records retrieval for subsequent + * acceptance, marks the command as completed and converts command to + * message if message is required + */ +void IncomingMessages::retrieve(FrameSetPtr command, qpid::messaging::Message* message) +{ + if (message) { + populate(*message, *command); + } + const MessageTransferBody* transfer = command->as<MessageTransferBody>(); + if (transfer->getAcquireMode() == ACQUIRE_MODE_PRE_ACQUIRED && transfer->getAcceptMode() == ACCEPT_MODE_EXPLICIT) { + acceptTracker.delivered(transfer->getDestination(), command->getId()); + } + session.markCompleted(command->getId(), false, false); +} + +IncomingMessages::MessageTransfer::MessageTransfer(FrameSetPtr c, IncomingMessages& p) : content(c), parent(p) {} + +const std::string& IncomingMessages::MessageTransfer::getDestination() +{ + return content->as<MessageTransferBody>()->getDestination(); +} +void IncomingMessages::MessageTransfer::retrieve(qpid::messaging::Message* message) +{ + parent.retrieve(content, message); +} + + +namespace { +//TODO: unify conversion to and from 0-10 message that is currently +//split between IncomingMessages and OutgoingMessage +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"); +} + +void populateHeaders(qpid::messaging::Message& message, + const DeliveryProperties* deliveryProperties, + const MessageProperties* messageProperties) +{ + if (deliveryProperties) { + message.setTtl(qpid::messaging::Duration(deliveryProperties->getTtl())); + message.setDurable(deliveryProperties->getDeliveryMode() == DELIVERY_MODE_PERSISTENT); + message.setPriority(deliveryProperties->getPriority()); + message.setRedelivered(deliveryProperties->getRedelivered()); + } + if (messageProperties) { + message.setContentType(messageProperties->getContentType()); + if (messageProperties->hasReplyTo()) { + message.setReplyTo(AddressResolution::convert(messageProperties->getReplyTo())); + } + message.setSubject(messageProperties->getApplicationHeaders().getAsString(SUBJECT)); + message.getProperties().clear(); + translate(messageProperties->getApplicationHeaders(), message.getProperties()); + message.setCorrelationId(messageProperties->getCorrelationId()); + message.setUserId(messageProperties->getUserId()); + if (messageProperties->hasMessageId()) { + message.setMessageId(messageProperties->getMessageId().str()); + } + //expose 0-10 specific items through special properties: + // app-id, content-encoding + if (messageProperties->hasAppId()) { + message.getProperties()[X_APP_ID] = messageProperties->getAppId(); + } + if (messageProperties->hasContentEncoding()) { + message.getProperties()[X_CONTENT_ENCODING] = messageProperties->getContentEncoding(); + } + // routing-key, others? + if (deliveryProperties && deliveryProperties->hasRoutingKey()) { + message.getProperties()[X_ROUTING_KEY] = deliveryProperties->getRoutingKey(); + } + } +} + +void populateHeaders(qpid::messaging::Message& message, const AMQHeaderBody* headers) +{ + populateHeaders(message, headers->get<DeliveryProperties>(), headers->get<MessageProperties>()); +} + +void populate(qpid::messaging::Message& message, FrameSet& command) +{ + //need to be able to link the message back to the transfer it was delivered by + //e.g. for rejecting. + MessageImplAccess::get(message).setInternalId(command.getId()); + + message.setContent(command.getContent()); + + populateHeaders(message, command.getHeaders()); +} + + +}}} // namespace qpid::client::amqp0_10 diff --git a/qpid/cpp/src/qpid/client/amqp0_10/IncomingMessages.h b/qpid/cpp/src/qpid/client/amqp0_10/IncomingMessages.h new file mode 100644 index 0000000000..f6a291bc68 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/IncomingMessages.h @@ -0,0 +1,100 @@ +#ifndef QPID_CLIENT_AMQP0_10_INCOMINGMESSAGES_H +#define QPID_CLIENT_AMQP0_10_INCOMINGMESSAGES_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 <string> +#include <boost/shared_ptr.hpp> +#include "qpid/client/AsyncSession.h" +#include "qpid/framing/SequenceSet.h" +#include "qpid/sys/BlockingQueue.h" +#include "qpid/sys/Time.h" +#include "qpid/client/amqp0_10/AcceptTracker.h" + +namespace qpid { + +namespace framing{ +class FrameSet; +} + +namespace messaging { +class Message; +} + +namespace client { +namespace amqp0_10 { + +/** + * Queue of incoming messages. + */ +class IncomingMessages +{ + public: + typedef boost::shared_ptr<qpid::framing::FrameSet> FrameSetPtr; + class MessageTransfer + { + public: + const std::string& getDestination(); + void retrieve(qpid::messaging::Message* message); + private: + FrameSetPtr content; + IncomingMessages& parent; + + MessageTransfer(FrameSetPtr, IncomingMessages&); + friend class IncomingMessages; + }; + + struct Handler + { + virtual ~Handler() {} + virtual bool accept(MessageTransfer& transfer) = 0; + }; + + void setSession(qpid::client::AsyncSession session); + 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 releaseAll(); + void releasePending(const std::string& destination); + + uint32_t pendingAccept(); + uint32_t pendingAccept(const std::string& destination); + + uint32_t available(); + uint32_t available(const std::string& destination); + private: + typedef std::deque<FrameSetPtr> FrameSetQueue; + + sys::Mutex lock; + qpid::client::AsyncSession session; + boost::shared_ptr< sys::BlockingQueue<FrameSetPtr> > incoming; + FrameSetQueue received; + AcceptTracker acceptTracker; + + bool process(Handler*, qpid::sys::Duration); + bool wait(qpid::sys::Duration); + void retrieve(FrameSetPtr, qpid::messaging::Message*); + +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_INCOMINGMESSAGES_H*/ diff --git a/qpid/cpp/src/qpid/client/amqp0_10/MessageSink.h b/qpid/cpp/src/qpid/client/amqp0_10/MessageSink.h new file mode 100644 index 0000000000..8d87a3c7bb --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/MessageSink.h @@ -0,0 +1,52 @@ +#ifndef QPID_CLIENT_AMQP0_10_MESSAGESINK_H +#define QPID_CLIENT_AMQP0_10_MESSAGESINK_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 <string> +#include "qpid/client/AsyncSession.h" + +namespace qpid { + +namespace messaging { +class Message; +} + +namespace client { +namespace amqp0_10 { + +struct OutgoingMessage; + +/** + * + */ +class MessageSink +{ + public: + virtual ~MessageSink() {} + virtual void declare(qpid::client::AsyncSession& session, const std::string& name) = 0; + virtual void send(qpid::client::AsyncSession& session, const std::string& name, OutgoingMessage& message) = 0; + virtual void cancel(qpid::client::AsyncSession& session, const std::string& name) = 0; + private: +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_MESSAGESINK_H*/ diff --git a/qpid/cpp/src/qpid/client/amqp0_10/MessageSource.h b/qpid/cpp/src/qpid/client/amqp0_10/MessageSource.h new file mode 100644 index 0000000000..74f2732f59 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/MessageSource.h @@ -0,0 +1,47 @@ +#ifndef QPID_CLIENT_AMQP0_10_MESSAGESOURCE_H +#define QPID_CLIENT_AMQP0_10_MESSAGESOURCE_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 <string> +#include "qpid/client/AsyncSession.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +/** + * Abstraction behind which the AMQP 0-10 commands required to + * establish (and tear down) an incoming stream of messages from a + * given address are hidden. + */ +class MessageSource +{ + public: + virtual ~MessageSource() {} + virtual void subscribe(qpid::client::AsyncSession& session, const std::string& destination) = 0; + virtual void cancel(qpid::client::AsyncSession& session, const std::string& destination) = 0; + + private: +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_MESSAGESOURCE_H*/ diff --git a/qpid/cpp/src/qpid/client/amqp0_10/OutgoingMessage.cpp b/qpid/cpp/src/qpid/client/amqp0_10/OutgoingMessage.cpp new file mode 100644 index 0000000000..d93416da75 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/OutgoingMessage.cpp @@ -0,0 +1,104 @@ +/* + * + * 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/amqp0_10/OutgoingMessage.h" +#include "qpid/client/amqp0_10/AddressResolution.h" +#include "qpid/amqp_0_10/Codecs.h" +#include "qpid/types/Variant.h" +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/MessageImpl.h" +#include "qpid/framing/enum.h" +#include <sstream> + +namespace qpid { +namespace client { +namespace amqp0_10 { + +using qpid::messaging::Address; +using qpid::messaging::MessageImplAccess; +using qpid::types::Variant; +using namespace qpid::framing::message; +using namespace qpid::amqp_0_10; + +namespace { +//TODO: unify conversion to and from 0-10 message that is currently +//split between IncomingMessages and OutgoingMessage +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"); +} + +void OutgoingMessage::convert(const qpid::messaging::Message& from) +{ + //TODO: need to avoid copying as much as possible + message.setData(from.getContent()); + message.getMessageProperties().setContentType(from.getContentType()); + message.getMessageProperties().setCorrelationId(from.getCorrelationId()); + message.getMessageProperties().setUserId(from.getUserId()); + const Address& address = from.getReplyTo(); + if (address) { + message.getMessageProperties().setReplyTo(AddressResolution::convert(address)); + } + translate(from.getProperties(), message.getMessageProperties().getApplicationHeaders()); + if (from.getTtl().getMilliseconds()) { + message.getDeliveryProperties().setTtl(from.getTtl().getMilliseconds()); + } + if (from.getDurable()) { + message.getDeliveryProperties().setDeliveryMode(DELIVERY_MODE_PERSISTENT); + } + if (from.getRedelivered()) { + message.getDeliveryProperties().setRedelivered(true); + } + if (from.getPriority()) message.getDeliveryProperties().setPriority(from.getPriority()); + + //allow certain 0-10 specific items to be set through special properties: + // message-id, app-id, content-encoding + if (from.getMessageId().size()) { + qpid::framing::Uuid uuid; + std::istringstream data(from.getMessageId()); + data >> uuid; + message.getMessageProperties().setMessageId(uuid); + } + Variant::Map::const_iterator i; + i = from.getProperties().find(X_APP_ID); + if (i != from.getProperties().end()) { + message.getMessageProperties().setAppId(i->second.asString()); + } + i = from.getProperties().find(X_CONTENT_ENCODING); + if (i != from.getProperties().end()) { + message.getMessageProperties().setContentEncoding(i->second.asString()); + } +} + +void OutgoingMessage::setSubject(const std::string& subject) +{ + if (!subject.empty()) { + message.getMessageProperties().getApplicationHeaders().setString(SUBJECT, subject); + } +} + +std::string OutgoingMessage::getSubject() const +{ + return message.getMessageProperties().getApplicationHeaders().getAsString(SUBJECT); +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/qpid/cpp/src/qpid/client/amqp0_10/OutgoingMessage.h b/qpid/cpp/src/qpid/client/amqp0_10/OutgoingMessage.h new file mode 100644 index 0000000000..0cdd2a2336 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/OutgoingMessage.h @@ -0,0 +1,48 @@ +#ifndef QPID_CLIENT_AMQP0_10_OUTGOINGMESSAGE_H +#define QPID_CLIENT_AMQP0_10_OUTGOINGMESSAGE_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/client/Completion.h" +#include "qpid/client/Message.h" + +namespace qpid { +namespace messaging { +class Message; +} +namespace client { +namespace amqp0_10 { + +struct OutgoingMessage +{ + qpid::client::Message message; + qpid::client::Completion status; + + void convert(const qpid::messaging::Message&); + void setSubject(const std::string& subject); + std::string getSubject() const; +}; + + + +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_OUTGOINGMESSAGE_H*/ diff --git a/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.cpp b/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.cpp new file mode 100644 index 0000000000..030b804143 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.cpp @@ -0,0 +1,225 @@ +/* + * + * 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 "ReceiverImpl.h" +#include "AddressResolution.h" +#include "MessageSource.h" +#include "SessionImpl.h" +#include "qpid/messaging/exceptions.h" +#include "qpid/messaging/Receiver.h" +#include "qpid/messaging/Session.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +using qpid::messaging::NoMessageAvailable; +using qpid::messaging::Receiver; +using qpid::messaging::Duration; + +void ReceiverImpl::received(qpid::messaging::Message&) +{ + //TODO: should this be configurable + sys::Mutex::ScopedLock l(lock); + if (capacity && --window <= capacity/2) { + session.sendCompletion(); + window = capacity; + } +} + +qpid::messaging::Message ReceiverImpl::get(qpid::messaging::Duration timeout) +{ + qpid::messaging::Message result; + if (!get(result, timeout)) throw NoMessageAvailable(); + return result; +} + +qpid::messaging::Message ReceiverImpl::fetch(qpid::messaging::Duration timeout) +{ + qpid::messaging::Message result; + if (!fetch(result, timeout)) throw NoMessageAvailable(); + return result; +} + +bool ReceiverImpl::get(qpid::messaging::Message& message, qpid::messaging::Duration timeout) +{ + Get f(*this, message, timeout); + while (!parent->execute(f)) {} + return f.result; +} + +bool ReceiverImpl::fetch(qpid::messaging::Message& message, qpid::messaging::Duration timeout) +{ + Fetch f(*this, message, timeout); + while (!parent->execute(f)) {} + return f.result; +} + +void ReceiverImpl::close() +{ + execute<Close>(); +} + +void ReceiverImpl::start() +{ + sys::Mutex::ScopedLock l(lock); + if (state == STOPPED) { + state = STARTED; + startFlow(l); + } +} + +void ReceiverImpl::stop() +{ + sys::Mutex::ScopedLock l(lock); + state = STOPPED; + session.messageStop(destination); +} + +void ReceiverImpl::setCapacity(uint32_t c) +{ + execute1<SetCapacity>(c); +} + +void ReceiverImpl::startFlow(const sys::Mutex::ScopedLock&) +{ + if (capacity > 0) { + session.messageSetFlowMode(destination, FLOW_MODE_WINDOW); + session.messageFlow(destination, CREDIT_UNIT_MESSAGE, capacity); + session.messageFlow(destination, CREDIT_UNIT_BYTE, byteCredit); + window = capacity; + } +} + +void ReceiverImpl::init(qpid::client::AsyncSession s, AddressResolution& resolver) +{ + sys::Mutex::ScopedLock l(lock); + session = s; + if (state == CANCELLED) return; + if (state == UNRESOLVED) { + source = resolver.resolveSource(session, address); + assert(source.get()); + state = STARTED; + } + source->subscribe(session, destination); + startFlow(l); +} + +const std::string& ReceiverImpl::getName() const { + sys::Mutex::ScopedLock l(lock); + return destination; +} + +uint32_t ReceiverImpl::getCapacity() +{ + sys::Mutex::ScopedLock l(lock); + return capacity; +} + +uint32_t ReceiverImpl::getAvailable() +{ + return parent->getReceivable(destination); +} + +uint32_t ReceiverImpl::getUnsettled() +{ + return parent->getUnsettledAcks(destination); +} + +ReceiverImpl::ReceiverImpl(SessionImpl& p, const std::string& name, + const qpid::messaging::Address& a) : + + parent(&p), destination(name), address(a), byteCredit(0xFFFFFFFF), + state(UNRESOLVED), capacity(0), window(0) {} + +bool ReceiverImpl::getImpl(qpid::messaging::Message& message, qpid::messaging::Duration timeout) +{ + { + sys::Mutex::ScopedLock l(lock); + if (state == CANCELLED) return false; + } + return parent->get(*this, message, timeout); +} + +bool ReceiverImpl::fetchImpl(qpid::messaging::Message& message, qpid::messaging::Duration timeout) +{ + { + sys::Mutex::ScopedLock l(lock); + if (state == CANCELLED) return false; + + if (capacity == 0 || state != STARTED) { + session.messageSetFlowMode(destination, FLOW_MODE_CREDIT); + session.messageFlow(destination, CREDIT_UNIT_MESSAGE, 1); + session.messageFlow(destination, CREDIT_UNIT_BYTE, 0xFFFFFFFF); + } + } + if (getImpl(message, timeout)) { + return true; + } else { + qpid::client::Session s; + { + sys::Mutex::ScopedLock l(lock); + if (state == CANCELLED) return false; // Might have been closed during get. + s = sync(session); + } + s.messageFlush(destination); + { + sys::Mutex::ScopedLock l(lock); + startFlow(l); //reallocate credit + } + return getImpl(message, Duration::IMMEDIATE); + } +} + +void ReceiverImpl::closeImpl() +{ + sys::Mutex::ScopedLock l(lock); + if (state != CANCELLED) { + state = CANCELLED; + sync(session).messageStop(destination); + parent->releasePending(destination); + source->cancel(session, destination); + parent->receiverCancelled(destination); + } +} + +bool ReceiverImpl::isClosed() const { + sys::Mutex::ScopedLock l(lock); + return state == CANCELLED; +} + +void ReceiverImpl::setCapacityImpl(uint32_t c) +{ + sys::Mutex::ScopedLock l(lock); + if (c != capacity) { + capacity = c; + if (state == STARTED) { + session.messageStop(destination); + startFlow(l); + } + } +} + +qpid::messaging::Session ReceiverImpl::getSession() const +{ + return qpid::messaging::Session(parent.get()); +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.h b/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.h new file mode 100644 index 0000000000..5693b7b71f --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.h @@ -0,0 +1,151 @@ +#ifndef QPID_CLIENT_AMQP0_10_RECEIVERIMPL_H +#define QPID_CLIENT_AMQP0_10_RECEIVERIMPL_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/messaging/Address.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/ReceiverImpl.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/client/amqp0_10/SessionImpl.h" +#include "qpid/messaging/Duration.h" +#include "qpid/sys/Mutex.h" +#include <boost/intrusive_ptr.hpp> +#include <memory> + +namespace qpid { +namespace client { +namespace amqp0_10 { + +class AddressResolution; +class MessageSource; + +/** + * A receiver implementation based on an AMQP 0-10 subscription. + */ +class ReceiverImpl : public qpid::messaging::ReceiverImpl +{ + public: + + enum State {UNRESOLVED, STOPPED, STARTED, CANCELLED}; + + ReceiverImpl(SessionImpl& parent, const std::string& name, + const qpid::messaging::Address& address); + + void init(qpid::client::AsyncSession session, AddressResolution& resolver); + bool get(qpid::messaging::Message& message, qpid::messaging::Duration timeout); + qpid::messaging::Message get(qpid::messaging::Duration timeout); + bool fetch(qpid::messaging::Message& message, qpid::messaging::Duration timeout); + qpid::messaging::Message fetch(qpid::messaging::Duration timeout); + void close(); + void start(); + void stop(); + const std::string& getName() const; + void setCapacity(uint32_t); + uint32_t getCapacity(); + uint32_t getAvailable(); + uint32_t getUnsettled(); + void received(qpid::messaging::Message& message); + qpid::messaging::Session getSession() const; + bool isClosed() const; + + private: + mutable sys::Mutex lock; + boost::intrusive_ptr<SessionImpl> parent; + const std::string destination; + const qpid::messaging::Address address; + const uint32_t byteCredit; + State state; + + std::auto_ptr<MessageSource> source; + uint32_t capacity; + qpid::client::AsyncSession session; + qpid::messaging::MessageListener* listener; + uint32_t window; + + void startFlow(const sys::Mutex::ScopedLock&); // Dummy param, call with lock held + //implementation of public facing methods + bool fetchImpl(qpid::messaging::Message& message, qpid::messaging::Duration timeout); + bool getImpl(qpid::messaging::Message& message, qpid::messaging::Duration timeout); + void closeImpl(); + void setCapacityImpl(uint32_t); + + //functors for public facing methods. + struct Command + { + ReceiverImpl& impl; + + Command(ReceiverImpl& i) : impl(i) {} + }; + + struct Get : Command + { + qpid::messaging::Message& message; + qpid::messaging::Duration timeout; + bool result; + + Get(ReceiverImpl& i, qpid::messaging::Message& m, qpid::messaging::Duration t) : + Command(i), message(m), timeout(t), result(false) {} + void operator()() { result = impl.getImpl(message, timeout); } + }; + + struct Fetch : Command + { + qpid::messaging::Message& message; + qpid::messaging::Duration timeout; + bool result; + + Fetch(ReceiverImpl& i, qpid::messaging::Message& m, qpid::messaging::Duration t) : + Command(i), message(m), timeout(t), result(false) {} + void operator()() { result = impl.fetchImpl(message, timeout); } + }; + + struct Close : Command + { + Close(ReceiverImpl& i) : Command(i) {} + void operator()() { impl.closeImpl(); } + }; + + struct SetCapacity : Command + { + uint32_t capacity; + + SetCapacity(ReceiverImpl& i, uint32_t c) : Command(i), capacity(c) {} + void operator()() { impl.setCapacityImpl(capacity); } + }; + + //helper templates for some common patterns + template <class F> void execute() + { + F f(*this); + parent->execute(f); + } + + template <class F, class P> void execute1(P p) + { + F f(*this, p); + parent->execute(f); + } +}; + +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_RECEIVERIMPL_H*/ diff --git a/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp b/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp new file mode 100644 index 0000000000..f2f0f1a9e5 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp @@ -0,0 +1,182 @@ +/* + * + * 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 "SenderImpl.h" +#include "MessageSink.h" +#include "SessionImpl.h" +#include "AddressResolution.h" +#include "OutgoingMessage.h" +#include "qpid/messaging/Session.h" + +namespace qpid { +namespace client { +namespace amqp0_10 { + +SenderImpl::SenderImpl(SessionImpl& _parent, const std::string& _name, + const qpid::messaging::Address& _address) : + parent(&_parent), name(_name), address(_address), state(UNRESOLVED), + capacity(50), window(0), flushed(false), unreliable(AddressResolution::is_unreliable(address)) {} + +void SenderImpl::send(const qpid::messaging::Message& message, bool sync) +{ + if (unreliable) { // immutable, don't need lock + UnreliableSend f(*this, &message); + parent->execute(f); + } else { + Send f(*this, &message); + while (f.repeat) parent->execute(f); + } + if (sync) parent->sync(true); +} + +void SenderImpl::close() +{ + execute<Close>(); +} + +void SenderImpl::setCapacity(uint32_t c) +{ + bool flush; + { + sys::Mutex::ScopedLock l(lock); + flush = c < capacity; + capacity = c; + } + execute1<CheckPendingSends>(flush); +} + +uint32_t SenderImpl::getCapacity() { + sys::Mutex::ScopedLock l(lock); + return capacity; +} + +uint32_t SenderImpl::getUnsettled() +{ + CheckPendingSends f(*this, false); + parent->execute(f); + return f.pending; +} + +void SenderImpl::init(qpid::client::AsyncSession s, AddressResolution& resolver) +{ + sys::Mutex::ScopedLock l(lock); + session = s; + if (state == UNRESOLVED) { + sink = resolver.resolveSink(session, address); + state = ACTIVE; + } + if (state == CANCELLED) { + sink->cancel(session, name); + sys::Mutex::ScopedUnlock u(lock); + parent->senderCancelled(name); + } else { + sink->declare(session, name); + replay(l); + } +} + +void SenderImpl::waitForCapacity() +{ + sys::Mutex::ScopedLock l(lock); + //TODO: add option to throw exception rather than blocking? + if (!unreliable && capacity <= + (flushed ? checkPendingSends(false, l) : outgoing.size())) + { + //Initial implementation is very basic. As outgoing is + //currently only reduced on receiving completions and we are + //blocking anyway we may as well sync(). If successful that + //should clear all outstanding sends. + session.sync(); + checkPendingSends(false, l); + } + //flush periodically and check for conmpleted sends + if (++window > (capacity / 4)) {//TODO: make this configurable? + checkPendingSends(true, l); + window = 0; + } +} + +void SenderImpl::sendImpl(const qpid::messaging::Message& m) +{ + sys::Mutex::ScopedLock l(lock); + std::auto_ptr<OutgoingMessage> msg(new OutgoingMessage()); + msg->convert(m); + msg->setSubject(m.getSubject().empty() ? address.getSubject() : m.getSubject()); + outgoing.push_back(msg.release()); + sink->send(session, name, outgoing.back()); +} + +void SenderImpl::sendUnreliable(const qpid::messaging::Message& m) +{ + sys::Mutex::ScopedLock l(lock); + OutgoingMessage msg; + msg.convert(m); + msg.setSubject(m.getSubject().empty() ? address.getSubject() : m.getSubject()); + sink->send(session, name, msg); +} + +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); + } +} + +uint32_t SenderImpl::checkPendingSends(bool flush) { + sys::Mutex::ScopedLock l(lock); + return checkPendingSends(flush, l); +} + +uint32_t SenderImpl::checkPendingSends(bool flush, const sys::Mutex::ScopedLock&) +{ + if (flush) { + session.flush(); + flushed = true; + } else { + flushed = false; + } + while (!outgoing.empty() && outgoing.front().status.isComplete()) { + outgoing.pop_front(); + } + return outgoing.size(); +} + +void SenderImpl::closeImpl() +{ + sys::Mutex::ScopedLock l(lock); + state = CANCELLED; + sink->cancel(session, name); + parent->senderCancelled(name); +} + +const std::string& SenderImpl::getName() const +{ + sys::Mutex::ScopedLock l(lock); + return name; +} + +qpid::messaging::Session SenderImpl::getSession() const +{ + sys::Mutex::ScopedLock l(lock); + return qpid::messaging::Session(parent.get()); +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.h b/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.h new file mode 100644 index 0000000000..c10c77ae18 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.h @@ -0,0 +1,160 @@ +#ifndef QPID_CLIENT_AMQP0_10_SENDERIMPL_H +#define QPID_CLIENT_AMQP0_10_SENDERIMPL_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/messaging/Address.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/SenderImpl.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/client/amqp0_10/SessionImpl.h" +#include <memory> +#include <boost/intrusive_ptr.hpp> +#include <boost/ptr_container/ptr_deque.hpp> + +namespace qpid { +namespace client { +namespace amqp0_10 { + +class AddressResolution; +class MessageSink; +struct OutgoingMessage; + +/** + * + */ +class SenderImpl : public qpid::messaging::SenderImpl +{ + public: + enum State {UNRESOLVED, ACTIVE, CANCELLED}; + + SenderImpl(SessionImpl& parent, const std::string& name, + const qpid::messaging::Address& address); + void send(const qpid::messaging::Message&, bool sync); + void close(); + void setCapacity(uint32_t); + uint32_t getCapacity(); + uint32_t getUnsettled(); + void init(qpid::client::AsyncSession, AddressResolution&); + const std::string& getName() const; + qpid::messaging::Session getSession() const; + + private: + mutable sys::Mutex lock; + boost::intrusive_ptr<SessionImpl> parent; + const std::string name; + const qpid::messaging::Address address; + State state; + std::auto_ptr<MessageSink> sink; + + qpid::client::AsyncSession session; + std::string destination; + std::string routingKey; + + typedef boost::ptr_deque<OutgoingMessage> OutgoingMessages; + OutgoingMessages outgoing; + uint32_t capacity; + uint32_t window; + bool flushed; + const bool unreliable; + + uint32_t checkPendingSends(bool flush); + // Dummy ScopedLock parameter means call with lock held + uint32_t checkPendingSends(bool flush, const sys::Mutex::ScopedLock&); + void replay(const sys::Mutex::ScopedLock&); + void waitForCapacity(); + + //logic for application visible methods: + void sendImpl(const qpid::messaging::Message&); + void sendUnreliable(const qpid::messaging::Message&); + void closeImpl(); + + + //functors for application visible methods (allowing locking and + //retry to be centralised): + struct Command + { + SenderImpl& impl; + + Command(SenderImpl& i) : impl(i) {} + }; + + struct Send : Command + { + const qpid::messaging::Message* message; + bool repeat; + + Send(SenderImpl& i, const qpid::messaging::Message* m) : Command(i), message(m), repeat(true) {} + void operator()() + { + impl.waitForCapacity(); + //from this point message will be recorded if there is any + //failure (and replayed) so need not repeat the call + repeat = false; + impl.sendImpl(*message); + } + }; + + struct UnreliableSend : Command + { + const qpid::messaging::Message* message; + + UnreliableSend(SenderImpl& i, const qpid::messaging::Message* m) : Command(i), message(m) {} + void operator()() + { + //TODO: ideally want to put messages on the outbound + //queue and pull them off in io thread, but the old + //0-10 client doesn't support that option so for now + //we simply don't queue unreliable messages + impl.sendUnreliable(*message); + } + }; + + struct Close : Command + { + Close(SenderImpl& i) : Command(i) {} + void operator()() { impl.closeImpl(); } + }; + + struct CheckPendingSends : Command + { + bool flush; + uint32_t pending; + CheckPendingSends(SenderImpl& i, bool f) : Command(i), flush(f), pending(0) {} + void operator()() { pending = impl.checkPendingSends(flush); } + }; + + //helper templates for some common patterns + template <class F> void execute() + { + F f(*this); + parent->execute(f); + } + + template <class F, class P> bool execute1(P p) + { + F f(*this, p); + return parent->execute(f); + } +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_SENDERIMPL_H*/ diff --git a/qpid/cpp/src/qpid/client/amqp0_10/SessionImpl.cpp b/qpid/cpp/src/qpid/client/amqp0_10/SessionImpl.cpp new file mode 100644 index 0000000000..75a71997fd --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/SessionImpl.cpp @@ -0,0 +1,525 @@ +/* + * + * 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/amqp0_10/SessionImpl.h" +#include "qpid/client/amqp0_10/ConnectionImpl.h" +#include "qpid/client/amqp0_10/ReceiverImpl.h" +#include "qpid/client/amqp0_10/SenderImpl.h" +#include "qpid/client/amqp0_10/MessageSource.h" +#include "qpid/client/amqp0_10/MessageSink.h" +#include "qpid/client/SessionBase_0_10Access.h" +#include "qpid/client/SessionImpl.h" +#include "qpid/messaging/PrivateImplRef.h" +#include "qpid/Exception.h" +#include "qpid/log/Statement.h" +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Connection.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/MessageImpl.h" +#include "qpid/messaging/Sender.h" +#include "qpid/messaging/Receiver.h" +#include "qpid/messaging/Session.h" +#include <boost/format.hpp> +#include <boost/function.hpp> +#include <boost/intrusive_ptr.hpp> + +using qpid::messaging::KeyError; +using qpid::messaging::NoMessageAvailable; +using qpid::messaging::MessagingException; +using qpid::messaging::TransactionAborted; +using qpid::messaging::SessionError; +using qpid::messaging::MessageImplAccess; +using qpid::messaging::Sender; +using qpid::messaging::Receiver; + +namespace qpid { +namespace client { +namespace amqp0_10 { + +typedef qpid::sys::Mutex::ScopedLock ScopedLock; +typedef qpid::sys::Mutex::ScopedUnlock ScopedUnlock; + +SessionImpl::SessionImpl(ConnectionImpl& c, bool t) : connection(&c), transactional(t) {} + +void SessionImpl::checkError() +{ + qpid::client::SessionBase_0_10Access s(session); + s.get()->assertOpen(); +} + +bool SessionImpl::hasError() +{ + qpid::client::SessionBase_0_10Access s(session); + return s.get()->hasError(); +} + +void SessionImpl::sync(bool block) +{ + if (block) retry<Sync>(); + else execute<NonBlockingSync>(); +} + +void SessionImpl::commit() +{ + if (!execute<Commit>()) { + throw TransactionAborted("Transaction aborted due to transport failure"); + } +} + +void SessionImpl::rollback() +{ + //If the session fails during this operation, the transaction will + //be rolled back anyway. + execute<Rollback>(); +} + +void SessionImpl::acknowledge(bool sync_) +{ + //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 + execute<Acknowledge>(); + sync(sync_); +} + +void SessionImpl::reject(qpid::messaging::Message& m) +{ + //Possibly want to somehow indicate failure here as well. Less + //clear need as compared to acknowledge however. + execute1<Reject>(m); +} + +void SessionImpl::release(qpid::messaging::Message& m) +{ + execute1<Release>(m); +} + +void SessionImpl::acknowledge(qpid::messaging::Message& m) +{ + //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); +} + +void SessionImpl::close() +{ + if (hasError()) { + ScopedLock l(lock); + 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 + } + while (true) { + Receiver r; + { + ScopedLock l(lock); + if (receivers.empty()) break; + r = receivers.begin()->second; + } + r.close(); // outside the lock, will call receiverCancelled + } + } + connection->closed(*this); + if (!hasError()) session.close(); +} + +template <class T, class S> boost::intrusive_ptr<S> getImplPtr(T& t) +{ + return boost::dynamic_pointer_cast<S>(qpid::messaging::PrivateImplRef<T>::get(t)); +} + +template <class T> void getFreeKey(std::string& key, T& map) +{ + std::string name = key; + int count = 1; + for (typename T::const_iterator i = map.find(name); i != map.end(); i = map.find(name)) { + name = (boost::format("%1%_%2%") % key % ++count).str(); + } + key = name; +} + + +void SessionImpl::setSession(qpid::client::Session s) +{ + ScopedLock l(lock); + session = s; + incoming.setSession(session); + if (transactional) session.txSelect(); + for (Receivers::iterator i = receivers.begin(); i != receivers.end(); ++i) { + getImplPtr<Receiver, ReceiverImpl>(i->second)->init(session, resolver); + } + for (Senders::iterator i = senders.begin(); i != senders.end(); ++i) { + getImplPtr<Sender, SenderImpl>(i->second)->init(session, resolver); + } + session.sync(); +} + +struct SessionImpl::CreateReceiver : Command +{ + qpid::messaging::Receiver result; + const qpid::messaging::Address& address; + + CreateReceiver(SessionImpl& i, const qpid::messaging::Address& a) : + Command(i), address(a) {} + void operator()() { result = impl.createReceiverImpl(address); } +}; + +Receiver SessionImpl::createReceiver(const qpid::messaging::Address& address) +{ + return get1<CreateReceiver, Receiver>(address); +} + +Receiver SessionImpl::createReceiverImpl(const qpid::messaging::Address& address) +{ + ScopedLock l(lock); + std::string name = address.getName(); + getFreeKey(name, receivers); + Receiver receiver(new ReceiverImpl(*this, name, address)); + getImplPtr<Receiver, ReceiverImpl>(receiver)->init(session, resolver); + receivers[name] = receiver; + return receiver; +} + +struct SessionImpl::CreateSender : Command +{ + qpid::messaging::Sender result; + const qpid::messaging::Address& address; + + CreateSender(SessionImpl& i, const qpid::messaging::Address& a) : + Command(i), address(a) {} + void operator()() { result = impl.createSenderImpl(address); } +}; + +Sender SessionImpl::createSender(const qpid::messaging::Address& address) +{ + return get1<CreateSender, Sender>(address); +} + +Sender SessionImpl::createSenderImpl(const qpid::messaging::Address& address) +{ + ScopedLock l(lock); + std::string name = address.getName(); + getFreeKey(name, senders); + Sender sender(new SenderImpl(*this, name, address)); + getImplPtr<Sender, SenderImpl>(sender)->init(session, resolver); + senders[name] = sender; + return sender; +} + +Sender SessionImpl::getSender(const std::string& name) const +{ + qpid::sys::Mutex::ScopedLock l(lock); + Senders::const_iterator i = senders.find(name); + if (i == senders.end()) { + throw KeyError(name); + } else { + return i->second; + } +} + +Receiver SessionImpl::getReceiver(const std::string& name) const +{ + qpid::sys::Mutex::ScopedLock l(lock); + Receivers::const_iterator i = receivers.find(name); + if (i == receivers.end()) { + throw KeyError(name); + } else { + return i->second; + } +} + +SessionImpl& SessionImpl::convert(qpid::messaging::Session& s) +{ + boost::intrusive_ptr<SessionImpl> impl = getImplPtr<qpid::messaging::Session, SessionImpl>(s); + if (!impl) { + throw SessionError(QPID_MSG("Configuration error; require qpid::client::amqp0_10::SessionImpl")); + } + return *impl; +} + +namespace { + +struct IncomingMessageHandler : IncomingMessages::Handler +{ + typedef boost::function1<bool, IncomingMessages::MessageTransfer&> Callback; + Callback callback; + + IncomingMessageHandler(Callback c) : callback(c) {} + + bool accept(IncomingMessages::MessageTransfer& transfer) + { + return callback(transfer); + } +}; + +} + + +bool SessionImpl::getNextReceiver(Receiver* receiver, IncomingMessages::MessageTransfer& transfer) +{ + ScopedLock l(lock); + Receivers::const_iterator i = receivers.find(transfer.getDestination()); + if (i == receivers.end()) { + QPID_LOG(error, "Received message for unknown destination " << transfer.getDestination()); + return false; + } else { + *receiver = i->second; + return true; + } +} + +bool SessionImpl::accept(ReceiverImpl* receiver, + qpid::messaging::Message* message, + IncomingMessages::MessageTransfer& transfer) +{ + if (receiver->getName() == transfer.getDestination()) { + transfer.retrieve(message); + receiver->received(*message); + return true; + } else { + return false; + } +} + +qpid::sys::Duration adjust(qpid::messaging::Duration timeout) +{ + uint64_t ms = timeout.getMilliseconds(); + if (ms < (uint64_t) (qpid::sys::TIME_INFINITE/qpid::sys::TIME_MSEC)) { + return ms * qpid::sys::TIME_MSEC; + } else { + return qpid::sys::TIME_INFINITE; + } +} + +bool SessionImpl::getIncoming(IncomingMessages::Handler& handler, qpid::messaging::Duration timeout) +{ + return incoming.get(handler, adjust(timeout)); +} + +bool SessionImpl::get(ReceiverImpl& receiver, qpid::messaging::Message& message, qpid::messaging::Duration timeout) +{ + IncomingMessageHandler handler(boost::bind(&SessionImpl::accept, this, &receiver, &message, _1)); + return getIncoming(handler, timeout); +} + +bool SessionImpl::nextReceiver(qpid::messaging::Receiver& receiver, qpid::messaging::Duration timeout) +{ + while (true) { + try { + std::string destination; + if (incoming.getNextDestination(destination, adjust(timeout))) { + qpid::sys::Mutex::ScopedLock l(lock); + Receivers::const_iterator i = receivers.find(destination); + if (i == receivers.end()) { + throw qpid::messaging::ReceiverError(QPID_MSG("Received message for unknown destination " << destination)); + } else { + receiver = i->second; + } + return true; + } else { + return false; + } + } catch (TransportFailure&) { + reconnect(); + } catch (const qpid::framing::ResourceLimitExceededException& e) { + if (backoff()) return false; + else throw qpid::messaging::TargetCapacityExceeded(e.what()); + } catch (const qpid::framing::UnauthorizedAccessException& e) { + throw qpid::messaging::UnauthorizedAccess(e.what()); + } catch (const qpid::SessionException& e) { + throw qpid::messaging::SessionError(e.what()); + } catch (const qpid::ConnectionException& e) { + throw qpid::messaging::ConnectionError(e.what()); + } catch (const qpid::ChannelException& e) { + throw qpid::messaging::MessagingException(e.what()); + } + } +} + +qpid::messaging::Receiver SessionImpl::nextReceiver(qpid::messaging::Duration timeout) +{ + qpid::messaging::Receiver receiver; + if (!nextReceiver(receiver, timeout)) throw NoMessageAvailable(); + if (!receiver) throw SessionError("Bad receiver returned!"); + return receiver; +} + +uint32_t SessionImpl::getReceivable() +{ + return get1<Receivable, uint32_t>((const std::string*) 0); +} +uint32_t SessionImpl::getReceivable(const std::string& destination) +{ + return get1<Receivable, uint32_t>(&destination); +} + +struct SessionImpl::Receivable : Command +{ + const std::string* destination; + uint32_t result; + + Receivable(SessionImpl& i, const std::string* d) : Command(i), destination(d), result(0) {} + void operator()() { result = impl.getReceivableImpl(destination); } +}; + +uint32_t SessionImpl::getReceivableImpl(const std::string* destination) +{ + ScopedLock l(lock); + if (destination) { + return incoming.available(*destination); + } else { + return incoming.available(); + } +} + +uint32_t SessionImpl::getUnsettledAcks() +{ + return get1<UnsettledAcks, uint32_t>((const std::string*) 0); +} + +uint32_t SessionImpl::getUnsettledAcks(const std::string& destination) +{ + return get1<UnsettledAcks, uint32_t>(&destination); +} + +struct SessionImpl::UnsettledAcks : Command +{ + const std::string* destination; + uint32_t result; + + UnsettledAcks(SessionImpl& i, const std::string* d) : Command(i), destination(d), result(0) {} + void operator()() { result = impl.getUnsettledAcksImpl(destination); } +}; + +uint32_t SessionImpl::getUnsettledAcksImpl(const std::string* destination) +{ + ScopedLock l(lock); + if (destination) { + return incoming.pendingAccept(*destination); + } else { + return incoming.pendingAccept(); + } +} + +void SessionImpl::syncImpl(bool block) +{ + if (block) session.sync(); + else session.flush(); + //cleanup unconfirmed accept records: + incoming.pendingAccept(); +} + +void SessionImpl::commitImpl() +{ + ScopedLock l(lock); + incoming.accept(); + session.txCommit(); +} + +void SessionImpl::rollbackImpl() +{ + ScopedLock l(lock); + for (Receivers::iterator i = receivers.begin(); i != receivers.end(); ++i) { + getImplPtr<Receiver, ReceiverImpl>(i->second)->stop(); + } + //ensure that stop has been processed and all previously sent + //messages are available for release: + session.sync(); + incoming.releaseAll(); + session.txRollback(); + + for (Receivers::iterator i = receivers.begin(); i != receivers.end(); ++i) { + getImplPtr<Receiver, ReceiverImpl>(i->second)->start(); + } +} + +void SessionImpl::acknowledgeImpl() +{ + ScopedLock l(lock); + if (!transactional) incoming.accept(); +} + +void SessionImpl::acknowledgeImpl(qpid::messaging::Message& m) +{ + ScopedLock l(lock); + if (!transactional) incoming.accept(MessageImplAccess::get(m).getInternalId()); +} + +void SessionImpl::rejectImpl(qpid::messaging::Message& m) +{ + SequenceSet set; + set.add(MessageImplAccess::get(m).getInternalId()); + session.messageReject(set); +} + +void SessionImpl::releaseImpl(qpid::messaging::Message& m) +{ + SequenceSet set; + set.add(MessageImplAccess::get(m).getInternalId()); + session.messageRelease(set); +} + +void SessionImpl::receiverCancelled(const std::string& name) +{ + ScopedLock l(lock); + receivers.erase(name); + session.sync(); + incoming.releasePending(name); +} + +void SessionImpl::releasePending(const std::string& name) +{ + ScopedLock l(lock); + incoming.releasePending(name); +} + +void SessionImpl::senderCancelled(const std::string& name) +{ + ScopedLock l(lock); + senders.erase(name); +} + +void SessionImpl::reconnect() +{ + connection->open(); +} + +bool SessionImpl::backoff() +{ + return connection->backoff(); +} + +qpid::messaging::Connection SessionImpl::getConnection() const +{ + return qpid::messaging::Connection(connection.get()); +} + +}}} // namespace qpid::client::amqp0_10 diff --git a/qpid/cpp/src/qpid/client/amqp0_10/SessionImpl.h b/qpid/cpp/src/qpid/client/amqp0_10/SessionImpl.h new file mode 100644 index 0000000000..2a2aa47df6 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/SessionImpl.h @@ -0,0 +1,247 @@ +#ifndef QPID_CLIENT_AMQP0_10_SESSIONIMPL_H +#define QPID_CLIENT_AMQP0_10_SESSIONIMPL_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/messaging/SessionImpl.h" +#include "qpid/messaging/Duration.h" +#include "qpid/messaging/exceptions.h" +#include "qpid/client/Session.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/client/amqp0_10/AddressResolution.h" +#include "qpid/client/amqp0_10/IncomingMessages.h" +#include "qpid/sys/Mutex.h" +#include "qpid/framing/reply_exceptions.h" +#include <boost/intrusive_ptr.hpp> + +namespace qpid { + +namespace messaging { +class Address; +class Connection; +class Message; +class Receiver; +class Sender; +class Session; +} + +namespace client { +namespace amqp0_10 { + +class ConnectionImpl; +class ReceiverImpl; +class SenderImpl; + +/** + * Implementation of the protocol independent Session interface using + * AMQP 0-10. + */ +class SessionImpl : public qpid::messaging::SessionImpl +{ + public: + SessionImpl(ConnectionImpl&, bool transactional); + void commit(); + void rollback(); + void acknowledge(bool sync); + void reject(qpid::messaging::Message&); + void release(qpid::messaging::Message&); + void acknowledge(qpid::messaging::Message& msg); + void close(); + void sync(bool block); + qpid::messaging::Sender createSender(const qpid::messaging::Address& address); + qpid::messaging::Receiver createReceiver(const qpid::messaging::Address& address); + + qpid::messaging::Sender getSender(const std::string& name) const; + qpid::messaging::Receiver getReceiver(const std::string& name) const; + + bool nextReceiver(qpid::messaging::Receiver& receiver, qpid::messaging::Duration timeout); + qpid::messaging::Receiver nextReceiver(qpid::messaging::Duration timeout); + + qpid::messaging::Connection getConnection() const; + void checkError(); + bool hasError(); + + bool get(ReceiverImpl& receiver, qpid::messaging::Message& message, qpid::messaging::Duration timeout); + + void releasePending(const std::string& destination); + void receiverCancelled(const std::string& name); + void senderCancelled(const std::string& name); + + uint32_t getReceivable(); + uint32_t getReceivable(const std::string& destination); + + uint32_t getUnsettledAcks(); + uint32_t getUnsettledAcks(const std::string& destination); + + void setSession(qpid::client::Session); + + template <class T> bool execute(T& f) + { + try { + f(); + return true; + } catch (const qpid::TransportFailure&) { + reconnect(); + return false; + } catch (const qpid::framing::ResourceLimitExceededException& e) { + if (backoff()) return false; + else throw qpid::messaging::TargetCapacityExceeded(e.what()); + } catch (const qpid::framing::UnauthorizedAccessException& e) { + throw qpid::messaging::UnauthorizedAccess(e.what()); + } catch (const qpid::SessionException& e) { + throw qpid::messaging::SessionError(e.what()); + } catch (const qpid::ConnectionException& e) { + throw qpid::messaging::ConnectionError(e.what()); + } catch (const qpid::ChannelException& e) { + throw qpid::messaging::MessagingException(e.what()); + } + } + + static SessionImpl& convert(qpid::messaging::Session&); + + private: + typedef std::map<std::string, qpid::messaging::Receiver> Receivers; + typedef std::map<std::string, qpid::messaging::Sender> Senders; + + mutable qpid::sys::Mutex lock; + boost::intrusive_ptr<ConnectionImpl> connection; + qpid::client::Session session; + AddressResolution resolver; + IncomingMessages incoming; + Receivers receivers; + Senders senders; + const bool transactional; + + bool accept(ReceiverImpl*, qpid::messaging::Message*, IncomingMessages::MessageTransfer&); + bool getIncoming(IncomingMessages::Handler& handler, qpid::messaging::Duration timeout); + bool getNextReceiver(qpid::messaging::Receiver* receiver, IncomingMessages::MessageTransfer& transfer); + void reconnect(); + bool backoff(); + + void commitImpl(); + void rollbackImpl(); + void acknowledgeImpl(); + void acknowledgeImpl(qpid::messaging::Message&); + void rejectImpl(qpid::messaging::Message&); + void releaseImpl(qpid::messaging::Message&); + void closeImpl(); + void syncImpl(bool block); + qpid::messaging::Sender createSenderImpl(const qpid::messaging::Address& address); + qpid::messaging::Receiver createReceiverImpl(const qpid::messaging::Address& address); + uint32_t getReceivableImpl(const std::string* destination); + uint32_t getUnsettledAcksImpl(const std::string* destination); + + //functors for public facing methods (allows locking and retry + //logic to be centralised) + struct Command + { + SessionImpl& impl; + + Command(SessionImpl& i) : impl(i) {} + }; + + struct Commit : Command + { + Commit(SessionImpl& i) : Command(i) {} + void operator()() { impl.commitImpl(); } + }; + + struct Rollback : Command + { + Rollback(SessionImpl& i) : Command(i) {} + void operator()() { impl.rollbackImpl(); } + }; + + struct Acknowledge : Command + { + Acknowledge(SessionImpl& i) : Command(i) {} + void operator()() { impl.acknowledgeImpl(); } + }; + + struct Sync : Command + { + Sync(SessionImpl& i) : Command(i) {} + void operator()() { impl.syncImpl(true); } + }; + + struct NonBlockingSync : Command + { + NonBlockingSync(SessionImpl& i) : Command(i) {} + void operator()() { impl.syncImpl(false); } + }; + + struct Reject : Command + { + qpid::messaging::Message& message; + + Reject(SessionImpl& i, qpid::messaging::Message& m) : Command(i), message(m) {} + void operator()() { impl.rejectImpl(message); } + }; + + struct Release : Command + { + qpid::messaging::Message& message; + + Release(SessionImpl& i, qpid::messaging::Message& m) : Command(i), message(m) {} + void operator()() { impl.releaseImpl(message); } + }; + + struct Acknowledge1 : Command + { + qpid::messaging::Message& message; + + Acknowledge1(SessionImpl& i, qpid::messaging::Message& m) : Command(i), message(m) {} + void operator()() { impl.acknowledgeImpl(message); } + }; + + struct CreateSender; + struct CreateReceiver; + struct UnsettledAcks; + struct Receivable; + + //helper templates for some common patterns + template <class F> bool execute() + { + F f(*this); + return execute(f); + } + + template <class F> void retry() + { + while (!execute<F>()) {} + } + + template <class F, class P> bool execute1(P p) + { + F f(*this, p); + return execute(f); + } + + template <class F, class R, class P> R get1(P p) + { + F f(*this, p); + while (!execute(f)) {} + return f.result; + } +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_SESSIONIMPL_H*/ diff --git a/qpid/cpp/src/qpid/client/amqp0_10/SimpleUrlParser.cpp b/qpid/cpp/src/qpid/client/amqp0_10/SimpleUrlParser.cpp new file mode 100644 index 0000000000..327c2274a6 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/SimpleUrlParser.cpp @@ -0,0 +1,79 @@ +/* + * + * 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/qpid/cpp/src/qpid/client/amqp0_10/SimpleUrlParser.h b/qpid/cpp/src/qpid/client/amqp0_10/SimpleUrlParser.h new file mode 100644 index 0000000000..24f90ca9d6 --- /dev/null +++ b/qpid/cpp/src/qpid/client/amqp0_10/SimpleUrlParser.h @@ -0,0 +1,42 @@ +#ifndef QPID_CLIENT_AMQP0_10_SIMPLEURLPARSER_H +#define QPID_CLIENT_AMQP0_10_SIMPLEURLPARSER_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 <string> + +namespace qpid { +namespace client { + +struct ConnectionSettings; + +namespace amqp0_10 { + +/** + * Parses a simple url of the form user/password@hostname:port + */ +struct SimpleUrlParser +{ + static void parse(const std::string& url, qpid::client::ConnectionSettings& result); +}; +}}} // namespace qpid::client::amqp0_10 + +#endif /*!QPID_CLIENT_AMQP0_10_SIMPLEURLPARSER_H*/ diff --git a/qpid/cpp/src/qpid/client/windows/SaslFactory.cpp b/qpid/cpp/src/qpid/client/windows/SaslFactory.cpp new file mode 100644 index 0000000000..d1ae762f1b --- /dev/null +++ b/qpid/cpp/src/qpid/client/windows/SaslFactory.cpp @@ -0,0 +1,177 @@ +/* + * + * 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/SaslFactory.h" + +#include "qpid/Exception.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/sys/SecurityLayer.h" +#include "qpid/sys/SecuritySettings.h" +#include "qpid/log/Statement.h" + +#include "boost/tokenizer.hpp" + +namespace qpid { + +using qpid::sys::SecurityLayer; +using qpid::sys::SecuritySettings; +using qpid::framing::InternalErrorException; + +struct WindowsSaslSettings +{ + WindowsSaslSettings ( ) : + username ( std::string(0) ), + password ( std::string(0) ), + service ( std::string(0) ), + host ( std::string(0) ), + minSsf ( 0 ), + maxSsf ( 0 ) + { + } + + WindowsSaslSettings ( const std::string & user, const std::string & password, const std::string & service, const std::string & host, int minSsf, int maxSsf ) : + username(user), + password(password), + service(service), + host(host), + minSsf(minSsf), + maxSsf(maxSsf) + { + } + + std::string username, + password, + service, + host; + + int minSsf, + maxSsf; +}; + +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); + std::string step(const std::string& challenge); + std::string getMechanism(); + std::string getUserId(); + std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize); + private: + WindowsSaslSettings settings; + std::string mechanism; +}; + +qpid::sys::Mutex SaslFactory::lock; +std::auto_ptr<SaslFactory> SaslFactory::instance; + +SaslFactory::SaslFactory() +{ +} + +SaslFactory::~SaslFactory() +{ +} + +SaslFactory& SaslFactory::getInstance() +{ + qpid::sys::Mutex::ScopedLock l(lock); + if (!instance.get()) { + instance = std::auto_ptr<SaslFactory>(new SaslFactory()); + } + return *instance; +} + +std::auto_ptr<Sasl> SaslFactory::create( const std::string & username, const std::string & password, const std::string & serviceName, const std::string & hostName, int minSsf, int maxSsf, bool ) +{ + std::auto_ptr<Sasl> sasl(new WindowsSasl( username, password, serviceName, hostName, minSsf, maxSsf )); + return sasl; +} + +namespace { + const std::string ANONYMOUS = "ANONYMOUS"; + const std::string PLAIN = "PLAIN"; +} + +WindowsSasl::WindowsSasl( const std::string & username, const std::string & password, const std::string & serviceName, const std::string & hostName, int minSsf, int maxSsf ) + : settings(username, password, serviceName, hostName, minSsf, maxSsf) +{ +} + +WindowsSasl::~WindowsSasl() +{ +} + +std::string WindowsSasl::start(const std::string& mechanisms, + const SecuritySettings* /*externalSettings*/) +{ + QPID_LOG(debug, "WindowsSasl::start(" << mechanisms << ")"); + + typedef boost::tokenizer<boost::char_separator<char> > tokenizer; + boost::char_separator<char> sep(" "); + bool havePlain = false; + bool haveAnon = false; + tokenizer mechs(mechanisms, sep); + for (tokenizer::iterator mech = mechs.begin(); + mech != mechs.end(); + ++mech) { + if (*mech == ANONYMOUS) + haveAnon = true; + else if (*mech == PLAIN) + havePlain = true; + } + 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; + } + else { + mechanism = ANONYMOUS; + } + return resp; +} + +std::string WindowsSasl::step(const std::string& /*challenge*/) +{ + // Shouldn't get this for PLAIN... + throw InternalErrorException(QPID_MSG("Sasl step error")); +} + +std::string WindowsSasl::getMechanism() +{ + return mechanism; +} + +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*/) +{ + return std::auto_ptr<SecurityLayer>(0); +} + +} // namespace qpid diff --git a/qpid/cpp/src/qpid/client/windows/SslConnector.cpp b/qpid/cpp/src/qpid/client/windows/SslConnector.cpp new file mode 100644 index 0000000000..785c817928 --- /dev/null +++ b/qpid/cpp/src/qpid/client/windows/SslConnector.cpp @@ -0,0 +1,181 @@ +/* + * + * 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/TCPConnector.h" + +#include "config.h" +#include "qpid/Msg.h" +#include "qpid/client/ConnectionImpl.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Dispatcher.h" +#include "qpid/sys/Poller.h" +#include "qpid/sys/Time.h" +#include "qpid/sys/windows/check.h" +#include "qpid/sys/windows/SslAsynchIO.h" + +#include <iostream> +#include <boost/bind.hpp> +#include <boost/format.hpp> + +#include <memory.h> +// security.h needs to see this to distinguish from kernel use. +#define SECURITY_WIN32 +#include <security.h> +#include <Schnlsp.h> +#undef SECURITY_WIN32 +#include <winsock2.h> + +namespace qpid { +namespace client { +namespace windows { + +using namespace qpid::sys; +using boost::format; +using boost::str; + + +class SslConnector : public qpid::client::TCPConnector +{ + qpid::sys::windows::ClientSslAsynchIO *shim; + boost::shared_ptr<qpid::sys::Poller> poller; + std::string brokerHost; + SCHANNEL_CRED cred; + CredHandle credHandle; + TimeStamp credExpiry; + + virtual ~SslConnector(); + void negotiationDone(SECURITY_STATUS status); + + // A number of AsynchIO callbacks go right through to TCPConnector, but + // we can't boost::bind to a protected ancestor, so these methods redirect + // to those TCPConnector methods. + bool redirectReadbuff(qpid::sys::AsynchIO&, qpid::sys::AsynchIOBufferBase*); + void redirectWritebuff(qpid::sys::AsynchIO&); + void redirectEof(qpid::sys::AsynchIO&); + +public: + SslConnector(boost::shared_ptr<qpid::sys::Poller>, + framing::ProtocolVersion pVersion, + const ConnectionSettings&, + ConnectionImpl*); + virtual void connect(const std::string& host, const std::string& port); + virtual void connected(const Socket&); + unsigned int getSSF(); +}; + +// Static constructor which registers connector here +namespace { + Connector* create(boost::shared_ptr<qpid::sys::Poller> p, + framing::ProtocolVersion v, + const ConnectionSettings& s, + ConnectionImpl* c) { + return new SslConnector(p, v, s, c); + } + + struct StaticInit { + StaticInit() { + try { + Connector::registerFactory("ssl", &create); + } catch (const std::exception& e) { + QPID_LOG(error, "Failed to initialise SSL connector: " << e.what()); + } + }; + ~StaticInit() { } + } init; +} + +void SslConnector::negotiationDone(SECURITY_STATUS status) +{ + if (status == SEC_E_OK) + initAmqp(); + else + connectFailed(QPID_MSG(qpid::sys::strError(status))); +} + +bool SslConnector::redirectReadbuff(qpid::sys::AsynchIO& a, + qpid::sys::AsynchIOBufferBase* b) { + return readbuff(a, b); +} + +void SslConnector::redirectWritebuff(qpid::sys::AsynchIO& a) { + writebuff(a); +} + +void SslConnector::redirectEof(qpid::sys::AsynchIO& a) { + eof(a); +} + +SslConnector::SslConnector(boost::shared_ptr<qpid::sys::Poller> p, + framing::ProtocolVersion ver, + const ConnectionSettings& settings, + ConnectionImpl* cimpl) + : TCPConnector(p, ver, settings, cimpl), shim(0), poller(p) +{ + memset(&cred, 0, sizeof(cred)); + cred.dwVersion = SCHANNEL_CRED_VERSION; + SECURITY_STATUS status = ::AcquireCredentialsHandle(NULL, + UNISP_NAME, + SECPKG_CRED_OUTBOUND, + NULL, + &cred, + NULL, + NULL, + &credHandle, + &credExpiry); + if (status != SEC_E_OK) + throw QPID_WINDOWS_ERROR(status); + QPID_LOG(debug, "SslConnector created for " << ver.toString()); +} + +SslConnector::~SslConnector() +{ + ::FreeCredentialsHandle(&credHandle); +} + + // Will this get reach via virtual method via boost::bind???? + +void SslConnector::connect(const std::string& host, const std::string& port) { + brokerHost = host; + TCPConnector::connect(host, port); +} + +void SslConnector::connected(const Socket& s) { + shim = new qpid::sys::windows::ClientSslAsynchIO(brokerHost, + s, + credHandle, + boost::bind(&SslConnector::redirectReadbuff, this, _1, _2), + boost::bind(&SslConnector::redirectEof, this, _1), + boost::bind(&SslConnector::redirectEof, this, _1), + 0, // closed + 0, // nobuffs + boost::bind(&SslConnector::redirectWritebuff, this, _1), + boost::bind(&SslConnector::negotiationDone, this, _1)); + start(shim); + shim->start(poller); +} + +unsigned int SslConnector::getSSF() +{ + return shim->getSslKeySize(); +} + +}}} // namespace qpid::client::windows diff --git a/qpid/cpp/src/qpid/cluster/Cluster.cpp b/qpid/cpp/src/qpid/cluster/Cluster.cpp new file mode 100644 index 0000000000..0daf0c7f5a --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/Cluster.cpp @@ -0,0 +1,1176 @@ +/* + * + * 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. + * + */ + +/** + * <h1>CLUSTER IMPLEMENTATION OVERVIEW</h1> + * + * The cluster works on the principle that if all members of the + * cluster receive identical input, they will all produce identical + * results. cluster::Connections intercept data received from clients + * and multicast it via CPG. The data is processed (passed to the + * broker::Connection) only when it is received from CPG in cluster + * order. Each cluster member has Connection objects for directly + * connected clients and "shadow" Connection objects for connections + * to other members. + * + * This assumes that all broker actions occur deterministically in + * response to data arriving on client connections. There are two + * situations where this assumption fails: + * - sending data in response to polling local connections for writabiliy. + * - taking actions based on a timer or timestamp comparison. + * + * 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. + * + * ClusterTimer implements periodic timed events in the cluster context. + * Used for periodic management events. + * + * <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. + * + * The cluster defines extensions to the AMQP command set in ../../../xml/cluster.xml + * which defines two classes: + * - cluster: cluster control information. + * - cluster.connection: control information for a specific connection. + * + * The following combinations are legal: + * - Data frames carrying connection data. + * - Cluster control events carrying cluster commands. + * - Connection control events carrying cluster.connection commands. + * - Connection control events carrying non-cluster frames: frames sent to the client. + * e.g. flow-control frames generated on a timer. + * + * <h1>CLUSTER INITIALIZATION OVERVIEW</h1> + * + * @see InitialStatusMap + * + * When a new member joins the CPG group, all members (including the + * new one) multicast their "initial status." The new member is in + * PRE_INIT mode until it gets a complete set of initial status + * messages from all cluster members. In a newly-forming cluster is + * then in INIT mode until the configured cluster-size members have + * joined. + * + * The newcomer uses initial status to determine + * - The cluster UUID + * - Am I speaking the correct version of the cluster protocol? + * - Do I need to get an update from an existing active member? + * - Can I recover from my own store? + * + * Pre-initialization happens in the Cluster constructor (plugin + * early-init phase) because it needs to set the recovery flag before + * the store initializes. This phase lasts until inital-status is + * received for all active members. The PollableQueues and Multicaster + * are in "bypass" mode during this phase since the poller has not + * started so there are no threads to serve pollable queues. + * + * The remaining initialization happens in Cluster::initialize() or, + * if cluster-size=N is specified, in the deliver thread when an + * initial-status control is delivered that brings the total to N. + */ +#include "qpid/Exception.h" +#include "qpid/cluster/Cluster.h" +#include "qpid/sys/ClusterSafe.h" +#include "qpid/cluster/ClusterSettings.h" +#include "qpid/cluster/Connection.h" +#include "qpid/cluster/UpdateClient.h" +#include "qpid/cluster/RetractClient.h" +#include "qpid/cluster/FailoverExchange.h" +#include "qpid/cluster/UpdateDataExchange.h" +#include "qpid/cluster/UpdateExchange.h" +#include "qpid/cluster/ClusterTimer.h" + +#include "qpid/assert.h" +#include "qmf/org/apache/qpid/cluster/ArgsClusterStopClusterNode.h" +#include "qmf/org/apache/qpid/cluster/Package.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/Connection.h" +#include "qpid/broker/NullMessageStore.h" +#include "qpid/broker/QueueRegistry.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/SessionState.h" +#include "qpid/broker/SignalHandler.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/AMQP_AllOperations.h" +#include "qpid/framing/AllInvoker.h" +#include "qpid/framing/ClusterConfigChangeBody.h" +#include "qpid/framing/ClusterConnectionDeliverCloseBody.h" +#include "qpid/framing/ClusterConnectionAbortBody.h" +#include "qpid/framing/ClusterRetractOfferBody.h" +#include "qpid/framing/ClusterConnectionDeliverDoOutputBody.h" +#include "qpid/framing/ClusterReadyBody.h" +#include "qpid/framing/ClusterShutdownBody.h" +#include "qpid/framing/ClusterUpdateOfferBody.h" +#include "qpid/framing/ClusterUpdateRequestBody.h" +#include "qpid/framing/ClusterConnectionAnnounceBody.h" +#include "qpid/framing/ClusterErrorCheckBody.h" +#include "qpid/framing/ClusterTimerWakeupBody.h" +#include "qpid/framing/ClusterDeliverToQueueBody.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/log/Helpers.h" +#include "qpid/log/Statement.h" +#include "qpid/management/ManagementAgent.h" +#include "qpid/memory.h" +#include "qpid/sys/Thread.h" + +#include <boost/shared_ptr.hpp> +#include <boost/bind.hpp> +#include <boost/cast.hpp> +#include <boost/current_function.hpp> +#include <algorithm> +#include <iterator> +#include <map> +#include <ostream> + + +namespace qpid { +namespace cluster { +using namespace qpid; +using namespace qpid::framing; +using namespace qpid::sys; +using namespace qpid::cluster; +using namespace framing::cluster; +using namespace std; +using management::ManagementAgent; +using management::ManagementObject; +using management::Manageable; +using management::Args; +namespace _qmf = ::qmf::org::apache::qpid::cluster; + +/** + * NOTE: must increment this number whenever any incompatible changes in + * cluster protocol/behavior are made. It allows early detection and + * sensible reporting of an attempt to mix different versions in a + * cluster. + * + * Currently use SVN revision to avoid clashes with versions from + * different branches. + */ +const uint32_t Cluster::CLUSTER_VERSION = 1097431; + +struct ClusterDispatcher : public framing::AMQP_AllOperations::ClusterHandler { + qpid::cluster::Cluster& cluster; + MemberId member; + Cluster::Lock& l; + ClusterDispatcher(Cluster& c, const MemberId& id, Cluster::Lock& l_) : cluster(c), member(id), l(l_) {} + + void updateRequest(const std::string& url) { cluster.updateRequest(member, url, l); } + + void initialStatus(uint32_t version, bool active, const Uuid& clusterId, + uint8_t storeState, const Uuid& shutdownId, + const std::string& firstConfig) + { + cluster.initialStatus( + member, version, active, clusterId, + framing::cluster::StoreState(storeState), shutdownId, + firstConfig, l); + } + void ready(const std::string& url) { + cluster.ready(member, url, l); + } + void configChange(const std::string& members, + const std::string& left, + const std::string& joined) + { + cluster.configChange(member, members, left, joined, l); + } + void updateOffer(uint64_t updatee) { + 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.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); + } + bool invoke(AMQBody& body) { return framing::invoke(*this, body).wasHandled(); } +}; + +Cluster::Cluster(const ClusterSettings& set, broker::Broker& b) : + settings(set), + broker(b), + mgmtObject(0), + poller(b.getPoller()), + cpg(*this), + name(settings.name), + self(cpg.self()), + clusterId(true), + mAgent(0), + expiryPolicy(new ExpiryPolicy(mcast, self, broker.getTimer())), + mcast(cpg, poller, boost::bind(&Cluster::leave, this)), + dispatcher(cpg, poller, boost::bind(&Cluster::leave, this)), + deliverEventQueue(boost::bind(&Cluster::deliveredEvent, this, _1), + boost::bind(&Cluster::leave, this), + "Error decoding events, may indicate a broker version mismatch", + poller), + deliverFrameQueue(boost::bind(&Cluster::deliveredFrame, this, _1), + boost::bind(&Cluster::leave, this), + "Error delivering frames", + poller), + failoverExchange(new FailoverExchange(broker.GetVhostObject(), &broker)), + updateDataExchange(new UpdateDataExchange(*this)), + quorum(boost::bind(&Cluster::leave, this)), + decoder(boost::bind(&Cluster::deliverFrame, this, _1)), + discarding(true), + state(PRE_INIT), + initMap(self, settings.size), + store(broker.getDataDir().getPath()), + elder(false), + lastAliveCount(0), + lastBroker(false), + updateRetracted(false), + updateClosed(false), + error(*this) +{ + 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); + broker.setClusterTimer(std::auto_ptr<sys::Timer>(timer)); + + // Failover exchange provides membership updates to clients. + broker.getExchanges().registerExchange(failoverExchange); + + // Update exchange is used during updates to replicate messages + // without modifying delivery-properties.exchange. + broker.getExchanges().registerExchange( + boost::shared_ptr<broker::Exchange>(new UpdateExchange(this))); + + // Update-data exchange is used for passing data that may be too large + // for single control frame. + broker.getExchanges().registerExchange(updateDataExchange); + + // Load my store status before we go into initialization + if (! broker::NullMessageStore::isNullStore(&broker.getStore())) { + store.load(); + clusterId = store.getClusterId(); + QPID_LOG(notice, "Cluster store state: " << store) + } + cpg.join(name); + // pump the CPG dispatch manually till we get past PRE_INIT. + while (state == PRE_INIT) + cpg.dispatchOne(); +} + +Cluster::~Cluster() { + broker.setClusterTimer(std::auto_ptr<sys::Timer>(0)); // Delete cluster timer + if (updateThread) updateThread.join(); // Join the previous updatethread. +} + +void Cluster::initialize() { + if (settings.quorum) quorum.start(poller); + if (settings.url.empty()) + myUrl = Url::getIpAddressesUrl(broker.getPort(broker::Broker::TCP_TRANSPORT)); + else + myUrl = settings.url; + broker.getKnownBrokers = boost::bind(&Cluster::getUrls, this); + broker.deferDelivery = boost::bind(&Cluster::deferDeliveryImpl, this, _1, _2); + broker.setExpiryPolicy(expiryPolicy); + deliverEventQueue.bypassOff(); + deliverEventQueue.start(); + deliverFrameQueue.bypassOff(); + deliverFrameQueue.start(); + mcast.start(); + + /// Create management object + mAgent = broker.getManagementAgent(); + if (mAgent != 0){ + _qmf::Package packageInit(mAgent); + mgmtObject = new _qmf::Cluster (mAgent, this, &broker,name,myUrl.str()); + mAgent->addObject (mgmtObject); + } + + // Run initMapCompleted immediately to process the initial configuration + // that allowed us to transition out of PRE_INIT + assert(state == INIT); + initMapCompleted(*(Mutex::ScopedLock*)0); // Fake lock, single-threaded context. + + // Add finalizer last for exception safety. + broker.addFinalizer(boost::bind(&Cluster::brokerShutdown, this)); + + // Start dispatching CPG events. + dispatcher.start(); +} + +// Called in connection thread to insert a client connection. +void Cluster::addLocalConnection(const boost::intrusive_ptr<Connection>& c) { + assert(c->getId().getMember() == self); + localConnections.insert(c); +} + +// Called in connection thread to insert an updated shadow connection. +void Cluster::addShadowConnection(const boost::intrusive_ptr<Connection>& c) { + QPID_LOG(debug, *this << " new shadow connection " << c->getId()); + // Safe to use connections here because we're pre-catchup, stalled + // and discarding, so deliveredFrame is not processing any + // connection events. + assert(discarding); + pair<ConnectionMap::iterator, bool> ib + = connections.insert(ConnectionMap::value_type(c->getId(), c)); + assert(ib.second); +} + +void Cluster::erase(const ConnectionId& id) { + Lock l(lock); + erase(id,l); +} + +// Called by Connection::deliverClose() in deliverFrameQueue thread. +void Cluster::erase(const ConnectionId& id, Lock&) { + connections.erase(id); + decoder.erase(id); +} + +std::vector<string> Cluster::getIds() const { + Lock l(lock); + return getIds(l); +} + +std::vector<string> Cluster::getIds(Lock&) const { + return map.memberIds(); +} + +std::vector<Url> Cluster::getUrls() const { + Lock l(lock); + return getUrls(l); +} + +std::vector<Url> Cluster::getUrls(Lock&) const { + return map.memberUrls(); +} + +void Cluster::leave() { + Lock l(lock); + leave(l); +} + +#define LEAVE_TRY(STMT) try { STMT; } \ + catch (const std::exception& e) { \ + QPID_LOG(warning, *this << " error leaving cluster: " << e.what()); \ + } do {} while(0) + +void Cluster::leave(Lock&) { + if (state != LEFT) { + state = LEFT; + QPID_LOG(notice, *this << " leaving cluster " << name); + // Finalize connections now now to avoid problems later in destructor. + ClusterSafeScope css; // Don't trigger cluster-safe assertions. + LEAVE_TRY(localConnections.clear()); + LEAVE_TRY(connections.clear()); + LEAVE_TRY(broker::SignalHandler::shutdown()); + } +} + +// Deliver CPG message. +void Cluster::deliver( + cpg_handle_t /*handle*/, + const cpg_name* /*group*/, + uint32_t nodeid, + uint32_t pid, + void* msg, + int msg_len) +{ + MemberId from(nodeid, pid); + framing::Buffer buf(static_cast<char*>(msg), msg_len); + Event e(Event::decodeCopy(from, buf)); + deliverEvent(e); +} + +void Cluster::deliverEvent(const Event& e) { deliverEventQueue.push(e); } + +void Cluster::deliverFrame(const EventFrame& e) { deliverFrameQueue.push(e); } + +const ClusterUpdateOfferBody* castUpdateOffer(const framing::AMQBody* body) { + return (body && body->getMethod() && + body->getMethod()->isA<ClusterUpdateOfferBody>()) ? + static_cast<const ClusterUpdateOfferBody*>(body) : 0; +} + +const ClusterConnectionAnnounceBody* castAnnounce( const framing::AMQBody *body) { + return (body && body->getMethod() && + body->getMethod()->isA<ClusterConnectionAnnounceBody>()) ? + static_cast<const ClusterConnectionAnnounceBody*>(body) : 0; +} + +// Handler for deliverEventQueue. +// This thread decodes frames from events. +void Cluster::deliveredEvent(const Event& e) { + if (e.isCluster()) { + 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 + // 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) ) { + QPID_LOG(info, *this << " stall for update offer from " << e.getMemberId() + << " to " << MemberId(offer->getUpdatee())); + deliverEventQueue.stop(); + } + deliverFrame(ef); + } + else if(!discarding) { + if (e.isControl()) + deliverFrame(EventFrame(e, e.getFrame())); + else { + try { decoder.decode(e, e.getData()); } + catch (const Exception& ex) { + // Close a connection that is sending us invalid data. + QPID_LOG(error, *this << " aborting connection " + << e.getConnectionId() << ": " << ex.what()); + framing::AMQFrame abort((ClusterConnectionAbortBody())); + deliverFrame(EventFrame(EventHeader(CONTROL, e.getConnectionId()), abort)); + } + } + } +} + +void Cluster::flagError( + Connection& connection, ErrorCheck::ErrorType type, const std::string& msg) +{ + Mutex::ScopedLock l(lock); + if (connection.isCatchUp()) { + QPID_LOG(critical, *this << " error on update connection " << connection + << ": " << msg); + leave(l); + } + error.error(connection, type, map.getFrameSeq(), map.getMembers(), msg); +} + +// Handler for deliverFrameQueue. +// This thread executes the main logic. +void Cluster::deliveredFrame(const EventFrame& efConst) { + Mutex::ScopedLock l(lock); + sys::ClusterSafeScope css; // Don't trigger cluster-safe asserts. + if (state == LEFT) return; + EventFrame e(efConst); + const ClusterUpdateOfferBody* offer = castUpdateOffer(e.frame.getBody()); + if (offer && error.isUnresolved()) { + // We can't honour an update offer that is delivered while an + // error is in progress so replace it with a retractOffer and re-start + // the event queue. + e.frame = AMQFrame( + ClusterRetractOfferBody(ProtocolVersion(), offer->getUpdatee())); + deliverEventQueue.start(); + } + // Process each frame through the error checker. + if (error.isUnresolved()) { + error.delivered(e); + while (error.canProcess()) // There is a frame ready to process. + processFrame(error.getNext(), l); + } + else + processFrame(e, l); +} + + +void Cluster::processFrame(const EventFrame& e, Lock& l) { + if (e.isCluster()) { + QPID_LOG(trace, *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")); + } + else if (state >= CATCHUP) { + map.incrementFrameSeq(); + ConnectionPtr connection = getConnection(e, l); + if (connection) { + QPID_LOG(trace, *this << " DLVR " << map.getFrameSeq() << ": " << e); + connection->deliveredFrame(e); + } + else + throw Exception(QPID_MSG("Unknown connection: " << e)); + } + else // Drop connection frames while state < CATCHUP + QPID_LOG(trace, *this << " DROP (joining): " << e); +} + +// Called in deliverFrameQueue thread +ConnectionPtr Cluster::getConnection(const EventFrame& e, Lock&) { + ConnectionId id = e.connectionId; + ConnectionMap::iterator i = connections.find(id); + if (i != connections.end()) return i->second; + ConnectionPtr cp; + // If the frame is an announcement for a new connection, add it. + const ClusterConnectionAnnounceBody *announce = castAnnounce(e.frame.getBody()); + if (e.frame.getBody() && e.frame.getMethod() && announce) + { + if (id.getMember() == self) { // Announces one of my own + cp = localConnections.getErase(id); + assert(cp); + } + else { // New remote connection, create a shadow. + qpid::sys::SecuritySettings secSettings; + if (announce) { + secSettings.ssf = announce->getSsf(); + secSettings.authid = announce->getAuthid(); + secSettings.nodict = announce->getNodict(); + } + cp = new Connection(*this, shadowOut, announce->getManagementId(), id, secSettings); + } + connections.insert(ConnectionMap::value_type(id, cp)); + } + return cp; +} + +Cluster::ConnectionVector Cluster::getConnections(Lock&) { + ConnectionVector result(connections.size()); + std::transform(connections.begin(), connections.end(), result.begin(), + boost::bind(&ConnectionMap::value_type::second, _1)); + return result; +} + +// CPG config-change callback. +void Cluster::configChange ( + cpg_handle_t /*handle*/, + const cpg_name */*group*/, + const cpg_address *members, int nMembers, + const cpg_address *left, int nLeft, + const cpg_address *joined, int nJoined) +{ + Mutex::ScopedLock l(lock); + string membersStr, leftStr, joinedStr; + // Encode members and enqueue as an event so the config change can + // be executed in the correct thread. + for (const cpg_address* p = members; p < members+nMembers; ++p) + membersStr.append(MemberId(*p).str()); + for (const cpg_address* p = left; p < left+nLeft; ++p) + leftStr.append(MemberId(*p).str()); + for (const cpg_address* p = joined; p < joined+nJoined; ++p) + joinedStr.append(MemberId(*p).str()); + deliverEvent(Event::control(ClusterConfigChangeBody( + ProtocolVersion(), membersStr, leftStr, joinedStr), + self)); +} + +void Cluster::setReady(Lock&) { + state = READY; + mcast.setReady(); + broker.getQueueEvents().enable(); + enableClusterSafe(); // Enable cluster-safe assertions. +} + +// 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 +// event that could have cause a cluster property change on any cluster member. +void Cluster::setMgmtStatus(Lock&) { + if (mgmtObject) + mgmtObject->set_status(state >= CATCHUP ? "ACTIVE" : "JOINING"); +} + +void Cluster::initMapCompleted(Lock& l) { + // Called on completion of the initial status map. + QPID_LOG(debug, *this << " initial status map complete. "); + setMgmtStatus(l); + if (state == PRE_INIT) { + // PRE_INIT means we're still in the earlyInitialize phase, in the constructor. + // We decide here whether we want to recover from our store. + // We won't recover if we are joining an active cluster or our store is dirty. + if (store.hasStore() && + store.getState() != STORE_STATE_EMPTY_STORE && + (initMap.isActive() || store.getState() == STORE_STATE_DIRTY_STORE)) + broker.setRecovery(false); // Ditch my current store. + state = INIT; + } + else if (state == INIT) { + // INIT means we are past Cluster::initialize(). + + // If we're forming an initial cluster (no active members) + // then we wait to reach the configured cluster-size + if (!initMap.isActive() && initMap.getActualSize() < initMap.getRequiredSize()) { + QPID_LOG(info, *this << initMap.getActualSize() + << " members, waiting for at least " << initMap.getRequiredSize()); + return; + } + + initMap.checkConsistent(); + elders = initMap.getElders(); + QPID_LOG(debug, *this << " elders: " << elders); + if (elders.empty()) + becomeElder(l); + else { + broker.getLinks().setPassive(true); + broker.getQueueEvents().disable(); + QPID_LOG(info, *this << " not active for links."); + } + setClusterId(initMap.getClusterId(), l); + + if (initMap.isUpdateNeeded()) { // Joining established cluster. + broker.setRecovery(false); // Ditch my current store. + broker.setClusterUpdatee(true); + if (mAgent) mAgent->suppress(true); // Suppress mgmt output during update. + state = JOINER; + mcast.mcastControl(ClusterUpdateRequestBody(ProtocolVersion(), myUrl.str()), self); + QPID_LOG(notice, *this << " joining cluster " << name); + } + else { // I can go ready. + discarding = false; + setReady(l); + memberUpdate(l); + updateMgmtMembership(l); + mcast.mcastControl(ClusterReadyBody(ProtocolVersion(), myUrl.str()), self); + QPID_LOG(notice, *this << " joined cluster " << name); + } + } +} + +void Cluster::configChange(const MemberId&, + const std::string& membersStr, + const std::string& leftStr, + const std::string& joinedStr, + Lock& l) +{ + if (state == LEFT) return; + MemberSet members = decodeMemberSet(membersStr); + MemberSet left = decodeMemberSet(leftStr); + MemberSet joined = decodeMemberSet(joinedStr); + QPID_LOG(notice, *this << " configuration change: " << members); + QPID_LOG_IF(notice, !left.empty(), *this << " Members left: " << left); + QPID_LOG_IF(notice, !joined.empty(), *this << " Members joined: " << joined); + + // If we are still joining, make sure there is someone to give us an update. + elders = intersection(elders, members); + if (elders.empty() && INIT < state && state < CATCHUP) { + QPID_LOG(critical, "Cannot update, all potential updaters left the cluster."); + leave(l); + return; + } + bool memberChange = map.configChange(members); + + // Update initital status for members joining or leaving. + initMap.configChange(members); + if (initMap.isResendNeeded()) { + mcast.mcastControl( + ClusterInitialStatusBody( + ProtocolVersion(), CLUSTER_VERSION, state > INIT, clusterId, + store.getState(), store.getShutdownId(), + initMap.getFirstConfigStr() + ), + self); + } + if (initMap.transitionToComplete()) initMapCompleted(l); + + if (state >= CATCHUP && memberChange) { + memberUpdate(l); + if (elders.empty()) becomeElder(l); + } + + updateMgmtMembership(l); // Update on every config change for consistency +} + +void Cluster::becomeElder(Lock&) { + if (elder) return; // We were already the elder. + // We are the oldest, reactive links if necessary + QPID_LOG(info, *this << " became the elder, active for links."); + elder = true; + broker.getLinks().setPassive(false); + timer->becomeElder(); +} + +void Cluster::makeOffer(const MemberId& id, Lock& ) { + if (state == READY && map.isJoiner(id)) { + state = OFFER; + QPID_LOG(info, *this << " send update-offer to " << id); + mcast.mcastControl(ClusterUpdateOfferBody(ProtocolVersion(), id), self); + } +} + +namespace { +struct AppendQueue { + ostream* os; + AppendQueue(ostream& o) : os(&o) {} + void operator()(const boost::shared_ptr<broker::Queue>& q) { + (*os) << " " << q->getName() << "=" << q->getMessageCount(); + } +}; +} // namespace + +// Log a snapshot of broker state, used for debugging inconsistency problems. +// May only be called in deliver thread. +std::string Cluster::debugSnapshot() { + assertClusterSafe(); + std::ostringstream msg; + msg << "Member joined, frameSeq=" << map.getFrameSeq() << ", queue snapshot:"; + AppendQueue append(msg); + broker.getQueues().eachQueue(append); + return msg.str(); +} + +// Called from Broker::~Broker when broker is shut down. At this +// 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(); } + catch (const std::exception& e) { + QPID_LOG(error, *this << " shutting down CPG: " << e.what()); + } + delete this; +} + +void Cluster::updateRequest(const MemberId& id, const std::string& url, Lock& l) { + map.updateRequest(id, url); + makeOffer(id, l); +} + +void Cluster::initialStatus(const MemberId& member, uint32_t version, bool active, + const framing::Uuid& id, + framing::cluster::StoreState store, + const framing::Uuid& shutdownId, + const std::string& firstConfig, + Lock& l) +{ + if (version != CLUSTER_VERSION) { + QPID_LOG(critical, *this << " incompatible cluster versions " << + version << " != " << CLUSTER_VERSION); + leave(l); + return; + } + QPID_LOG_IF(debug, state == PRE_INIT, *this + << " received initial status from " << member); + initMap.received( + member, + ClusterInitialStatusBody(ProtocolVersion(), version, active, id, + store, shutdownId, firstConfig) + ); + if (initMap.transitionToComplete()) initMapCompleted(l); +} + +void Cluster::ready(const MemberId& id, const std::string& url, Lock& l) { + try { + if (map.ready(id, Url(url))) + memberUpdate(l); + if (state == CATCHUP && id == self) { + setReady(l); + QPID_LOG(notice, *this << " caught up."); + } + } catch (const Url::Invalid& e) { + QPID_LOG(error, "Invalid URL in cluster ready command: " << url); + } + // Update management on every ready event to be consistent across cluster. + setMgmtStatus(l); + updateMgmtMembership(l); +} + +void Cluster::updateOffer(const MemberId& updater, uint64_t updateeInt, Lock& l) { + // NOTE: deliverEventQueue has been stopped at the update offer by + // deliveredEvent in case an update is required. + if (state == LEFT) return; + MemberId updatee(updateeInt); + boost::optional<Url> url = map.updateOffer(updater, updatee); + if (updater == self) { + assert(state == OFFER); + if (url) // My offer was first. + updateStart(updatee, *url, l); + else { // Another offer was first. + QPID_LOG(info, *this << " cancelled offer to " << updatee << " unstall"); + setReady(l); + makeOffer(map.firstJoiner(), l); // Maybe make another offer. + deliverEventQueue.start(); // Go back to normal processing + } + } + else if (updatee == self && url) { + assert(state == JOINER); + state = UPDATEE; + QPID_LOG(notice, *this << " receiving update from " << updater); + checkUpdateIn(l); + } + else { + QPID_LOG(info, *this << " unstall, ignore update " << updater + << " to " << updatee); + deliverEventQueue.start(); // Not involved in update. + } + if (updatee != self && url) { + QPID_LOG(debug, debugSnapshot()); + if (mAgent) mAgent->clusterUpdate(); + // Updatee will call clusterUpdate when update completes + } +} + +static client::ConnectionSettings connectionSettings(const ClusterSettings& settings) { + client::ConnectionSettings cs; + cs.username = settings.username; + cs.password = settings.password; + cs.mechanism = settings.mechanism; + return cs; +} + +void Cluster::retractOffer(const MemberId& updater, uint64_t updateeInt, Lock& l) { + // An offer was received while handling an error, and converted to a retract. + // Behavior is very similar to updateOffer. + if (state == LEFT) return; + MemberId updatee(updateeInt); + boost::optional<Url> url = map.updateOffer(updater, updatee); + if (updater == self) { + assert(state == OFFER); + if (url) { // My offer was first. + if (updateThread) + updateThread.join(); // Join the previous updateThread to avoid leaks. + updateThread = Thread(new RetractClient(*url, connectionSettings(settings))); + } + setReady(l); + makeOffer(map.firstJoiner(), l); // Maybe make another offer. + // Don't unstall the event queue, that was already done in deliveredFrame + } + QPID_LOG(debug,*this << " retracted offer " << updater << " to " << updatee); +} + +void Cluster::updateStart(const MemberId& updatee, const Url& url, Lock& l) { + // NOTE: deliverEventQueue is already stopped at the stall point by deliveredEvent. + if (state == LEFT) return; + assert(state == OFFER); + state = UPDATER; + QPID_LOG(notice, *this << " sending update to " << updatee << " at " << url); + if (updateThread) + updateThread.join(); // Join the previous updateThread to avoid leaks. + updateThread = Thread( + new UpdateClient(self, updatee, url, broker, map, *expiryPolicy, + getConnections(l), decoder, + boost::bind(&Cluster::updateOutDone, this), + boost::bind(&Cluster::updateOutError, this, _1), + connectionSettings(settings))); +} + +// Called in network thread +void Cluster::updateInClosed() { + Lock l(lock); + assert(!updateClosed); + updateClosed = true; + checkUpdateIn(l); +} + +// Called in update thread. +void Cluster::updateInDone(const ClusterMap& m) { + Lock l(lock); + updatedMap = m; + checkUpdateIn(l); +} + +void Cluster::updateInRetracted() { + Lock l(lock); + updateRetracted = true; + map.clearStatus(); + checkUpdateIn(l); +} + +bool Cluster::isExpectingUpdate() { + Lock l(lock); + return state <= UPDATEE; +} + +// Called in update thread or deliver thread. +void Cluster::checkUpdateIn(Lock& l) { + if (state != UPDATEE) return; // Wait till we reach the stall point. + 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); + // 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); + 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. + if (mAgent) { + // Update management agent now, after all update activity is complete. + updateDataExchange->updateManagementAgent(mAgent); + 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(); + } + else if (updateRetracted) { // Update was retracted, request another update + updateRetracted = false; + updateClosed = false; + state = JOINER; + QPID_LOG(notice, *this << " update retracted, sending new update request."); + mcast.mcastControl(ClusterUpdateRequestBody(ProtocolVersion(), myUrl.str()), self); + deliverEventQueue.start(); + } +} + +void Cluster::updateOutDone() { + Monitor::ScopedLock l(lock); + updateOutDone(l); +} + +void Cluster::updateOutDone(Lock& l) { + QPID_LOG(notice, *this << " update sent"); + assert(state == UPDATER); + state = READY; + deliverEventQueue.start(); // Start processing events again. + makeOffer(map.firstJoiner(), l); // Try another offer +} + +void Cluster::updateOutError(const std::exception& e) { + Monitor::ScopedLock l(lock); + QPID_LOG(error, *this << " error sending update: " << e.what()); + updateOutDone(l); +} + +void Cluster ::shutdown(const MemberId& , const Uuid& id, Lock& l) { + QPID_LOG(notice, *this << " cluster shut down by administrator."); + if (store.hasStore()) store.clean(id); + leave(l); +} + +ManagementObject* Cluster::GetManagementObject() const { return mgmtObject; } + +Manageable::status_t Cluster::ManagementMethod (uint32_t methodId, Args& args, string&) { + Lock l(lock); + QPID_LOG(debug, *this << " managementMethod [id=" << methodId << "]"); + switch (methodId) { + case _qmf::Cluster::METHOD_STOPCLUSTERNODE : + { + _qmf::ArgsClusterStopClusterNode& iargs = (_qmf::ArgsClusterStopClusterNode&) args; + stringstream stream; + stream << self; + if (iargs.i_brokerId == stream.str()) + stopClusterNode(l); + } + break; + case _qmf::Cluster::METHOD_STOPFULLCLUSTER : + stopFullCluster(l); + break; + default: + return Manageable::STATUS_UNKNOWN_METHOD; + } + return Manageable::STATUS_OK; +} + +void Cluster::stopClusterNode(Lock& l) { + QPID_LOG(notice, *this << " cluster member stopped by administrator."); + leave(l); +} + +void Cluster::stopFullCluster(Lock& ) { + QPID_LOG(notice, *this << " shutting down cluster " << name); + mcast.mcastControl(ClusterShutdownBody(ProtocolVersion(), Uuid(true)), self); +} + +void Cluster::memberUpdate(Lock& l) { + // Ignore config changes while we are joining. + if (state < CATCHUP) return; + QPID_LOG(info, *this << " member update: " << map); + size_t aliveCount = map.aliveCount(); + assert(map.isAlive(self)); + failoverExchange->updateUrls(getUrls(l)); + + // Mark store clean if I am the only broker, dirty otherwise. + if (store.hasStore()) { + if (aliveCount == 1) { + if (store.getState() != STORE_STATE_CLEAN_STORE) { + QPID_LOG(notice, *this << "Sole member of cluster, marking store clean."); + store.clean(Uuid(true)); + } + } + else { + if (store.getState() != STORE_STATE_DIRTY_STORE) { + QPID_LOG(notice, "Running in a cluster, marking store dirty."); + store.dirty(); + } + } + } + + // If I am the last member standing, set queue policies. + if (aliveCount == 1 && lastAliveCount > 1 && state >= CATCHUP) { + QPID_LOG(notice, *this << " last broker standing, update queue policies"); + lastBroker = true; + broker.getQueues().updateQueueClusterState(true); + } + else if (aliveCount > 1 && lastBroker) { + QPID_LOG(notice, *this << " last broker standing joined by " << aliveCount-1 + << " replicas, updating queue policies."); + lastBroker = false; + broker.getQueues().updateQueueClusterState(false); + } + lastAliveCount = aliveCount; + + // Close connections belonging to members that have left the cluster. + ConnectionMap::iterator i = connections.begin(); + while (i != connections.end()) { + ConnectionMap::iterator j = i++; + MemberId m = j->second->getId().getMember(); + if (m != self && !map.isMember(m)) { + j->second->close(); + erase(j->second->getId(), l); + } + } +} + +// See comment on Cluster::setMgmtStatus +void Cluster::updateMgmtMembership(Lock& l) { + if (!mgmtObject) return; + std::vector<Url> urls = getUrls(l); + mgmtObject->set_clusterSize(urls.size()); + string urlstr; + for(std::vector<Url>::iterator i = urls.begin(); i != urls.end(); i++ ) { + if (i != urls.begin()) urlstr += ";"; + urlstr += i->str(); + } + std::vector<string> ids = getIds(l); + string idstr; + for(std::vector<string>::iterator i = ids.begin(); i != ids.end(); i++ ) { + if (i != ids.begin()) idstr += ";"; + idstr += *i; + } + mgmtObject->set_members(urlstr); + mgmtObject->set_memberIDs(idstr); +} + +std::ostream& operator<<(std::ostream& o, const Cluster& cluster) { + static const char* STATE[] = { + "PRE_INIT", "INIT", "JOINER", "UPDATEE", "CATCHUP", + "READY", "OFFER", "UPDATER", "LEFT" + }; + assert(sizeof(STATE)/sizeof(*STATE) == Cluster::LEFT+1); + o << "cluster(" << cluster.self << " " << STATE[cluster.state]; + if (cluster.error.isUnresolved()) o << "/error"; + return o << ")"; +} + +MemberId Cluster::getId() const { + return self; // Immutable, no need to lock. +} + +broker::Broker& Cluster::getBroker() const { + return broker; // Immutable, no need to lock. +} + +void Cluster::setClusterId(const Uuid& uuid, Lock&) { + clusterId = uuid; + if (store.hasStore()) store.setClusterId(uuid); + if (mgmtObject) { + stringstream stream; + stream << self; + mgmtObject->set_clusterID(clusterId.str()); + mgmtObject->set_memberID(stream.str()); + } + 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 + // error. + if (state >= CATCHUP) // Don't respond pre catchup, we don't know what happened + error.respondNone(from, type, frameSeq); +} + +void Cluster::timerWakeup(const MemberId& , const std::string& name, Lock&) { + if (state >= CATCHUP) // Pre catchup our timer isn't set up. + timer->deliverWakeup(name); +} + +void Cluster::timerDrop(const MemberId& , const std::string& name, Lock&) { + QPID_LOG(debug, "Cluster timer drop " << map.getFrameSeq() << ": " << name) + if (state >= CATCHUP) // Pre catchup our timer isn't set up. + timer->deliverDrop(name); +} + +bool Cluster::isElder() const { + return elder; +} + +void Cluster::deliverToQueue(const std::string& queue, const std::string& message, Lock& l) +{ + broker::Queue::shared_ptr q = broker.getQueues().find(queue); + if (!q) { + QPID_LOG(critical, *this << " cluster delivery to non-existent queue: " << queue); + leave(l); + } + framing::Buffer buf(const_cast<char*>(message.data()), message.size()); + boost::intrusive_ptr<broker::Message> msg(new broker::Message); + msg->decodeHeader(buf); + msg->decodeContent(buf); + q->deliver(msg); +} + +bool Cluster::deferDeliveryImpl(const std::string& queue, + const boost::intrusive_ptr<broker::Message>& msg) +{ + if (isClusterSafe()) return false; + std::string message; + message.resize(msg->encodedSize()); + framing::Buffer buf(const_cast<char*>(message.data()), message.size()); + msg->encode(buf); + mcast.mcastControl(ClusterDeliverToQueueBody(ProtocolVersion(), queue, message), self); + return true; +} + +}} // namespace qpid::cluster diff --git a/qpid/cpp/src/qpid/cluster/Cluster.h b/qpid/cpp/src/qpid/cluster/Cluster.h new file mode 100644 index 0000000000..78d325cdf9 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/Cluster.h @@ -0,0 +1,308 @@ +#ifndef QPID_CLUSTER_CLUSTER_H +#define QPID_CLUSTER_CLUSTER_H + +/* + * + * 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. + * + */ + +#include "ClusterMap.h" +#include "ClusterSettings.h" +#include "Cpg.h" +#include "Decoder.h" +#include "ErrorCheck.h" +#include "Event.h" +#include "EventFrame.h" +#include "ExpiryPolicy.h" +#include "FailoverExchange.h" +#include "InitialStatusMap.h" +#include "LockedConnectionMap.h" +#include "Multicaster.h" +#include "NoOpConnectionOutputHandler.h" +#include "PollableQueue.h" +#include "PollerDispatch.h" +#include "Quorum.h" +#include "StoreStatus.h" +#include "UpdateReceiver.h" + +#include "qmf/org/apache/qpid/cluster/Cluster.h" +#include "qpid/Url.h" +#include "qpid/broker/Broker.h" +#include "qpid/management/Manageable.h" +#include "qpid/sys/Monitor.h" + +#include <boost/bind.hpp> +#include <boost/intrusive_ptr.hpp> +#include <boost/optional.hpp> + +#include <algorithm> +#include <map> +#include <vector> + +namespace qpid { + +namespace broker { +class Message; +} + +namespace framing { +class AMQBody; +struct Uuid; +} + +namespace cluster { + +class Connection; +struct EventFrame; +class ClusterTimer; +class UpdateDataExchange; + +/** + * Connection to the cluster + */ +class Cluster : private Cpg::Handler, public management::Manageable { + public: + typedef boost::intrusive_ptr<Connection> ConnectionPtr; + typedef std::vector<ConnectionPtr> ConnectionVector; + + // Public functions are thread safe unless otherwise mentioned in a comment. + + // Construct the cluster in plugin earlyInitialize. + Cluster(const ClusterSettings&, broker::Broker&); + virtual ~Cluster(); + + // Called by plugin initialize: cluster start-up requires transport plugins . + // Thread safety: only called by plugin initialize. + void initialize(); + + // Connection map. + 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; + boost::shared_ptr<FailoverExchange> getFailoverExchange() const { return failoverExchange; } + + // Leave the cluster - called when fatal errors occur. + void leave(); + + // Update completed - called in update thread + void updateInClosed(); + void updateInDone(const ClusterMap&); + 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; } + + const ClusterSettings& getSettings() const { return settings; } + + void deliverFrame(const EventFrame&); + + // Called in deliverFrame thread to indicate an error from the broker. + void flagError(Connection&, ErrorCheck::ErrorType, const std::string& msg); + + // Called only during update by Connection::shadowReady + Decoder& getDecoder() { return decoder; } + + ExpiryPolicy& getExpiryPolicy() { return *expiryPolicy; } + + UpdateReceiver& getUpdateReceiver() { return updateReceiver; } + + bool isElder() const; + + // Generates a log message for debugging purposes. + std::string debugSnapshot(); + + // Defer messages delivered in an unsafe context by multicasting. + bool deferDeliveryImpl(const std::string& queue, + const boost::intrusive_ptr<broker::Message>& msg); + + private: + typedef sys::Monitor::ScopedLock Lock; + + typedef PollableQueue<Event> PollableEventQueue; + typedef PollableQueue<EventFrame> PollableFrameQueue; + typedef std::map<ConnectionId, ConnectionPtr> ConnectionMap; + + /** 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; + + // == Called in main thread from Broker destructor. + void brokerShutdown(); + + // == Called in deliverEventQueue thread + void deliveredEvent(const Event&); + + // == Called in deliverFrameQueue thread + 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&); + void updateOffer(const MemberId& updater, uint64_t updatee, Lock&); + void retractOffer(const MemberId& updater, uint64_t updatee, Lock&); + void initialStatus(const MemberId&, + uint32_t version, + bool active, + const framing::Uuid& clusterId, + framing::cluster::StoreState, + const framing::Uuid& shutdownId, + const std::string& firstConfig, + Lock&); + void ready(const MemberId&, const std::string&, Lock&); + void configChange(const MemberId&, + const std::string& members, + 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&); + + // Helper functions + ConnectionPtr getConnection(const EventFrame&, Lock&); + ConnectionVector getConnections(Lock&); + void updateStart(const MemberId& updatee, const Url& url, Lock&); + void makeOffer(const MemberId&, Lock&); + void setReady(Lock&); + void memberUpdate(Lock&); + void setClusterId(const framing::Uuid&, Lock&); + void erase(const ConnectionId&, Lock&); + void requestUpdate(Lock& ); + void initMapCompleted(Lock&); + void becomeElder(Lock&); + void setMgmtStatus(Lock&); + void updateMgmtMembership(Lock&); + + // == Called in CPG dispatch thread + void deliver( // CPG deliver callback. + cpg_handle_t /*handle*/, + const struct cpg_name *group, + uint32_t /*nodeid*/, + uint32_t /*pid*/, + void* /*msg*/, + int /*msg_len*/); + + void deliverEvent(const Event&); + + void configChange( // CPG config change callback. + cpg_handle_t /*handle*/, + const struct cpg_name */*group*/, + const struct cpg_address */*members*/, int /*nMembers*/, + const struct cpg_address */*left*/, int /*nLeft*/, + const struct cpg_address */*joined*/, int /*nJoined*/ + ); + + // == Called in management threads. + virtual qpid::management::ManagementObject* GetManagementObject() const; + virtual management::Manageable::status_t ManagementMethod (uint32_t methodId, management::Args& args, std::string& text); + + void stopClusterNode(Lock&); + void stopFullCluster(Lock&); + + // == Called in connection IO threads . + void checkUpdateIn(Lock&); + + // == Called in UpdateClient thread. + void updateOutDone(); + void updateOutError(const std::exception&); + void updateOutDone(Lock&); + + // Immutable members set on construction, never changed. + const ClusterSettings settings; + broker::Broker& broker; + qmf::org::apache::qpid::cluster::Cluster* mgmtObject; // mgnt owns lifecycle + boost::shared_ptr<sys::Poller> poller; + Cpg cpg; + const std::string name; + Url myUrl; + const MemberId self; + framing::Uuid clusterId; + NoOpConnectionOutputHandler shadowOut; + qpid::management::ManagementAgent* mAgent; + boost::intrusive_ptr<ExpiryPolicy> expiryPolicy; + + // Thread safe members + Multicaster mcast; + PollerDispatch dispatcher; + PollableEventQueue deliverEventQueue; + PollableFrameQueue deliverFrameQueue; + boost::shared_ptr<FailoverExchange> failoverExchange; + boost::shared_ptr<UpdateDataExchange> updateDataExchange; + Quorum quorum; + LockedConnectionMap localConnections; + + // Used only in deliverEventQueue thread or when stalled for update. + Decoder decoder; + bool discarding; + + + // Remaining members are protected by lock. + mutable sys::Monitor lock; + + + // Local cluster state, cluster map + enum { + PRE_INIT,///< Have not yet received complete initial status map. + INIT, ///< Waiting to reach cluster-size. + 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 + OFFER, ///< Sent an offer, waiting for accept/reject. + UPDATER, ///< Offer accepted, sending a state update. + LEFT ///< Final state, left the cluster. + } state; + + ConnectionMap connections; + InitialStatusMap initMap; + StoreStatus store; + ClusterMap map; + MemberSet elders; + bool elder; + size_t lastAliveCount; + bool lastBroker; + sys::Thread updateThread; + boost::optional<ClusterMap> updatedMap; + bool updateRetracted, updateClosed; + ErrorCheck error; + UpdateReceiver updateReceiver; + ClusterTimer* timer; + + friend std::ostream& operator<<(std::ostream&, const Cluster&); + friend struct ClusterDispatcher; +}; + +}} // namespace qpid::cluster + + + +#endif /*!QPID_CLUSTER_CLUSTER_H*/ diff --git a/qpid/cpp/src/qpid/cluster/ClusterMap.cpp b/qpid/cpp/src/qpid/cluster/ClusterMap.cpp new file mode 100644 index 0000000000..a8389095c9 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/ClusterMap.cpp @@ -0,0 +1,176 @@ +/* + * + * 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/cluster/ClusterMap.h" +#include "qpid/Url.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/log/Statement.h" +#include <boost/bind.hpp> +#include <algorithm> +#include <functional> +#include <iterator> +#include <ostream> + +using namespace std; +using namespace boost; + +namespace qpid { +using namespace framing; + +namespace cluster { + +namespace { + +void addFieldTableValue(FieldTable::ValueMap::value_type vt, ClusterMap::Map& map, ClusterMap::Set& set) { + MemberId id(vt.first); + set.insert(id); + string url = vt.second->get<string>(); + if (!url.empty()) + map.insert(ClusterMap::Map::value_type(id, Url(url))); +} + +void insertFieldTableFromMapValue(FieldTable& ft, const ClusterMap::Map::value_type& vt) { + ft.setString(vt.first.str(), vt.second.str()); +} + +} + +ClusterMap::ClusterMap() : frameSeq(0) {} + +ClusterMap::ClusterMap(const Map& map) : frameSeq(0) { + transform(map.begin(), map.end(), inserter(alive, alive.begin()), bind(&Map::value_type::first, _1)); + members = map; +} + +ClusterMap::ClusterMap(const FieldTable& joinersFt, const FieldTable& membersFt, + framing::SequenceNumber frameSeq_) + : frameSeq(frameSeq_) +{ + for_each(joinersFt.begin(), joinersFt.end(), bind(&addFieldTableValue, _1, ref(joiners), ref(alive))); + for_each(membersFt.begin(), membersFt.end(), bind(&addFieldTableValue, _1, ref(members), ref(alive))); +} + +void ClusterMap::toMethodBody(framing::ClusterConnectionMembershipBody& b) const { + b.getJoiners().clear(); + for_each(joiners.begin(), joiners.end(), bind(&insertFieldTableFromMapValue, ref(b.getJoiners()), _1)); + for(Set::const_iterator i = alive.begin(); i != alive.end(); ++i) { + if (!isMember(*i) && !isJoiner(*i)) + b.getJoiners().setString(i->str(), string()); + } + b.getMembers().clear(); + for_each(members.begin(), members.end(), bind(&insertFieldTableFromMapValue, ref(b.getMembers()), _1)); + b.setFrameSeq(frameSeq); +} + +Url ClusterMap::getUrl(const Map& map, const MemberId& id) { + Map::const_iterator i = map.find(id); + return i == map.end() ? Url() : i->second; +} + +MemberId ClusterMap::firstJoiner() const { + return joiners.empty() ? MemberId() : joiners.begin()->first; +} + +vector<string> ClusterMap::memberIds() const { + vector<string> ids; + for (Map::const_iterator iter = members.begin(); + iter != members.end(); iter++) { + stringstream stream; + stream << iter->first; + ids.push_back(stream.str()); + } + return ids; +} + +vector<Url> ClusterMap::memberUrls() const { + vector<Url> urls(members.size()); + transform(members.begin(), members.end(), urls.begin(), + bind(&Map::value_type::second, _1)); + return urls; +} + +ClusterMap::Set ClusterMap::getAlive() const { return alive; } + +ClusterMap::Set ClusterMap::getMembers() const { + Set s; + transform(members.begin(), members.end(), inserter(s, s.begin()), + bind(&Map::value_type::first, _1)); + return s; +} + +ostream& operator<<(ostream& o, const ClusterMap::Map& m) { + ostream_iterator<MemberId> oi(o); + transform(m.begin(), m.end(), oi, bind(&ClusterMap::Map::value_type::first, _1)); + return o; +} + +ostream& operator<<(ostream& o, const ClusterMap& m) { + for (ClusterMap::Set::const_iterator i = m.alive.begin(); i != m.alive.end(); ++i) { + o << *i; + if (m.isMember(*i)) o << "(member)"; + else if (m.isJoiner(*i)) o << "(joiner)"; + else o << "(unknown)"; + o << " "; + } + o << "frameSeq=" << m.getFrameSeq(); + return o; +} + +bool ClusterMap::updateRequest(const MemberId& id, const string& url) { + try { + if (isAlive(id)) { + joiners[id] = Url(url); + return true; + } + } catch (const Url::Invalid&) { + QPID_LOG(error, "Invalid URL in cluster update request: " << url); + } + return false; +} + +bool ClusterMap::ready(const MemberId& id, const Url& url) { + return isAlive(id) && members.insert(Map::value_type(id,url)).second; +} + +bool ClusterMap::configChange(const Set& update) { + bool memberChange = false; + Set removed; + set_difference(alive.begin(), alive.end(), + update.begin(), update.end(), + inserter(removed, removed.begin())); + alive = update; + for (Set::const_iterator i = removed.begin(); i != removed.end(); ++i) { + memberChange = memberChange || members.erase(*i); + joiners.erase(*i); + } + return memberChange; +} + +optional<Url> ClusterMap::updateOffer(const MemberId& from, const MemberId& to) { + Map::iterator i = joiners.find(to); + if (isAlive(from) && i != joiners.end()) { + Url url= i->second; + joiners.erase(i); // No longer a potential updatee. + return url; + } + return optional<Url>(); +} + +}} // namespace qpid::cluster diff --git a/qpid/cpp/src/qpid/cluster/ClusterMap.h b/qpid/cpp/src/qpid/cluster/ClusterMap.h new file mode 100644 index 0000000000..cfa4ad924a --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/ClusterMap.h @@ -0,0 +1,106 @@ +#ifndef QPID_CLUSTER_CLUSTERMAP_H +#define QPID_CLUSTER_CLUSTERMAP_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 "MemberSet.h" +#include "qpid/Url.h" +#include "qpid/framing/ClusterConnectionMembershipBody.h" +#include "qpid/framing/SequenceNumber.h" + +#include <boost/function.hpp> +#include <boost/optional.hpp> + +#include <vector> +#include <deque> +#include <map> +#include <iosfwd> + +namespace qpid { +namespace cluster { + +/** + * Map of established cluster members and joiners waiting for an update, + * along with other cluster state that must be updated. + */ +class ClusterMap { + public: + typedef std::map<MemberId, Url> Map; + typedef std::set<MemberId> Set; + + ClusterMap(); + ClusterMap(const Map& map); + ClusterMap(const framing::FieldTable& joiners, const framing::FieldTable& members, + framing::SequenceNumber frameSeq); + + /** Update from config change. + *@return true if member set changed. + */ + bool configChange(const Set& members); + + bool isJoiner(const MemberId& id) const { return joiners.find(id) != joiners.end(); } + bool isMember(const MemberId& id) const { return members.find(id) != members.end(); } + bool isAlive(const MemberId& id) const { return alive.find(id) != alive.end(); } + + Url getJoinerUrl(const MemberId& id) { return getUrl(joiners, id); } + Url getMemberUrl(const MemberId& id) { return getUrl(members, id); } + + /** First joiner in the cluster in ID order, target for offers */ + MemberId firstJoiner() const; + + /** Convert map contents to a cluster control body. */ + void toMethodBody(framing::ClusterConnectionMembershipBody&) const; + + size_t aliveCount() const { return alive.size(); } + size_t memberCount() const { return members.size(); } + std::vector<std::string> memberIds() const; + std::vector<Url> memberUrls() const; + Set getAlive() const; + Set getMembers() const; + + bool updateRequest(const MemberId& id, const std::string& url); + /** Return non-empty Url if accepted */ + boost::optional<Url> updateOffer(const MemberId& from, const MemberId& to); + + /**@return true If this is a new member */ + bool ready(const MemberId& id, const Url&); + + framing::SequenceNumber getFrameSeq() const { return frameSeq; } + framing::SequenceNumber incrementFrameSeq() { return ++frameSeq; } + + /** Clear out all knowledge of joiners & members, just keep alive set */ + void clearStatus() { joiners.clear(); members.clear(); } + + private: + Url getUrl(const Map& map, const MemberId& id); + + Map joiners, members; + Set alive; + framing::SequenceNumber frameSeq; + + friend std::ostream& operator<<(std::ostream&, const Map&); + friend std::ostream& operator<<(std::ostream&, const ClusterMap&); +}; + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_CLUSTERMAP_H*/ diff --git a/qpid/cpp/src/qpid/cluster/ClusterPlugin.cpp b/qpid/cpp/src/qpid/cluster/ClusterPlugin.cpp new file mode 100644 index 0000000000..2962daaa07 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/ClusterPlugin.cpp @@ -0,0 +1,123 @@ +/* + * + * 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. + * + */ + +#include "config.h" +#include "qpid/cluster/Connection.h" +#include "qpid/cluster/ConnectionCodec.h" +#include "qpid/cluster/ClusterSettings.h" + +#include "qpid/cluster/SecureConnectionFactory.h" + +#include "qpid/cluster/Cluster.h" +#include "qpid/cluster/ConnectionCodec.h" +#include "qpid/cluster/UpdateClient.h" + +#include "qpid/broker/Broker.h" +#include "qpid/Plugin.h" +#include "qpid/Options.h" +#include "qpid/sys/AtomicValue.h" +#include "qpid/log/Statement.h" + +#include "qpid/management/ManagementAgent.h" +#include "qpid/broker/Exchange.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/SessionState.h" +#include "qpid/client/ConnectionSettings.h" + +#include <boost/shared_ptr.hpp> +#include <boost/utility/in_place_factory.hpp> +#include <boost/scoped_ptr.hpp> + +namespace qpid { +namespace cluster { + +using namespace std; +using broker::Broker; +using management::ManagementAgent; + + +/** Note separating options from settings to work around boost version differences. + * Old boost takes a reference to options objects, but new boost makes a copy. + * New boost allows a shared_ptr but that's not compatible with old boost. + */ +struct ClusterOptions : public Options { + ClusterSettings& settings; + + ClusterOptions(ClusterSettings& v) : Options("Cluster Options"), settings(v) { + addOptions() + ("cluster-name", optValue(settings.name, "NAME"), "Name of cluster to join") + ("cluster-url", optValue(settings.url,"URL"), + "Set URL of this individual broker, to be advertized to clients.\n" + "Defaults to a URL listing all the local IP addresses\n") + ("cluster-username", optValue(settings.username, ""), "Username for connections between brokers") + ("cluster-password", optValue(settings.password, ""), "Password for connections between brokers") + ("cluster-mechanism", optValue(settings.mechanism, ""), "Authentication mechanism for connections between brokers") +#if HAVE_LIBCMAN_H + ("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-read-max", optValue(settings.readMax,"N"), "Experimental: flow-control limit reads per connection. 0=no limit.") + ; + } +}; + +typedef boost::shared_ptr<sys::ConnectionCodec::Factory> CodecFactoryPtr; + +struct ClusterPlugin : public Plugin { + + ClusterSettings settings; + ClusterOptions options; + Cluster* cluster; + boost::scoped_ptr<ConnectionCodec::Factory> factory; + + ClusterPlugin() : options(settings), cluster(0) {} + + // Cluster needs to be initialized after the store + int initOrder() const { return Plugin::DEFAULT_INIT_ORDER+500; } + + Options* getOptions() { return &options; } + + void earlyInitialize(Plugin::Target& target) { + if (settings.name.empty()) return; // Only if --cluster-name option was specified. + Broker* broker = dynamic_cast<Broker*>(&target); + if (!broker) return; + cluster = new Cluster(settings, *broker); + CodecFactoryPtr simpleFactory(new broker::ConnectionFactory(*broker)); + CodecFactoryPtr clusterFactory(new ConnectionCodec::Factory(simpleFactory, *cluster)); + CodecFactoryPtr secureFactory(new SecureConnectionFactory(clusterFactory)); + broker->setConnectionFactory(secureFactory); + } + + void disallowManagementMethods(ManagementAgent* agent) { + if (!agent) return; + agent->disallowV1Methods(); + } + + void initialize(Plugin::Target& target) { + Broker* broker = dynamic_cast<Broker*>(&target); + if (broker && cluster) { + disallowManagementMethods(broker->getManagementAgent()); + cluster->initialize(); + } + } +}; + +static ClusterPlugin instance; // Static initialization. + +}} // namespace qpid::cluster diff --git a/qpid/cpp/src/qpid/cluster/ClusterSettings.h b/qpid/cpp/src/qpid/cluster/ClusterSettings.h new file mode 100644 index 0000000000..8e708aa139 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/ClusterSettings.h @@ -0,0 +1,50 @@ +#ifndef QPID_CLUSTER_CLUSTERSETTINGS_H +#define QPID_CLUSTER_CLUSTERSETTINGS_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/Url.h> +#include <string> + +namespace qpid { +namespace cluster { + +struct ClusterSettings { + std::string name; + std::string url; + bool quorum; + size_t readMax; + std::string username, password, mechanism; + size_t size; + + ClusterSettings() : quorum(false), readMax(10), size(1) + {} + + Url getUrl(uint16_t port) const { + if (url.empty()) return Url::getIpAddressesUrl(port); + return Url(url); + } +}; + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_CLUSTERSETTINGS_H*/ diff --git a/qpid/cpp/src/qpid/cluster/ClusterTimer.cpp b/qpid/cpp/src/qpid/cluster/ClusterTimer.cpp new file mode 100644 index 0000000000..f6e1c7a849 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/ClusterTimer.cpp @@ -0,0 +1,138 @@ +/* + * + * 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 "Cluster.h" +#include "ClusterTimer.h" +#include "qpid/log/Statement.h" +#include "qpid/framing/ClusterTimerWakeupBody.h" +#include "qpid/framing/ClusterTimerDropBody.h" + +namespace qpid { +namespace cluster { + +using boost::intrusive_ptr; +using std::max; +using sys::Timer; +using sys::TimerTask; + +// +// Note on use of Broker::getTimer() rather than getClusterTime in broker code. +// The following uses of getTimer() are cluster safe: +// +// LinkRegistry: maintenance visits in timer can call Bridge::create/cancel +// but these don't modify any management state. +// +// broker::Connection: +// - Heartbeats use ClusterOrderOutput to ensure consistency +// - timeout: aborts connection in timer, cluster does an orderly connection close. +// +// SessionState: scheduledCredit - uses ClusterOrderProxy +// Broker::queueCleaner: cluster implements ExpiryPolicy for consistent expiry. +// +// Broker::dtxManager: dtx disabled with cluster. +// +// requestIOProcessing: called in doOutput. +// + + +ClusterTimer::ClusterTimer(Cluster& c) : cluster(c) { + // Allow more generous overrun threshold with cluster as we + // have to do a CPG round trip before executing the task. + overran = 10*sys::TIME_MSEC; + late = 100*sys::TIME_MSEC; +} + +ClusterTimer::~ClusterTimer() {} + +// Initialization or deliver thread. +void ClusterTimer::add(intrusive_ptr<TimerTask> task) +{ + QPID_LOG(trace, "Adding cluster timer task " << task->getName()); + Map::iterator i = map.find(task->getName()); + 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()); + Timer::add(task); + } +} + +// Timer thread +void ClusterTimer::fire(intrusive_ptr<TimerTask> t) { + // Elder mcasts wakeup on fire, task is not fired until deliverWakeup + if (cluster.isElder()) { + QPID_LOG(trace, "Sending cluster timer wakeup " << t->getName()); + cluster.getMulticast().mcastControl( + framing::ClusterTimerWakeupBody(framing::ProtocolVersion(), t->getName()), + cluster.getId()); + } + else + QPID_LOG(trace, "Cluster timer task fired, but not elder " << t->getName()); +} + +// Timer thread +void ClusterTimer::drop(intrusive_ptr<TimerTask> t) { + // Elder mcasts drop, task is droped in deliverDrop + if (cluster.isElder()) { + QPID_LOG(trace, "Sending cluster timer drop " << t->getName()); + cluster.getMulticast().mcastControl( + framing::ClusterTimerDropBody(framing::ProtocolVersion(), t->getName()), + cluster.getId()); + } + else + QPID_LOG(trace, "Cluster timer task dropped, but not on elder " << t->getName()); +} + +// Deliver thread +void ClusterTimer::deliverWakeup(const std::string& name) { + QPID_LOG(trace, "Cluster timer wakeup delivered for " << name); + Map::iterator i = map.find(name); + if (i == map.end()) + throw Exception(QPID_MSG("Cluster timer wakeup non-existent task " << name)); + else { + intrusive_ptr<TimerTask> t = i->second; + map.erase(i); + Timer::fire(t); + } +} + +// Deliver thread +void ClusterTimer::deliverDrop(const std::string& name) { + QPID_LOG(trace, "Cluster timer drop delivered for " << name); + Map::iterator i = map.find(name); + if (i == map.end()) + throw Exception(QPID_MSG("Cluster timer drop non-existent task " << name)); + else { + intrusive_ptr<TimerTask> t = i->second; + map.erase(i); + } +} + +// Deliver thread +void ClusterTimer::becomeElder() { + for (Map::iterator i = map.begin(); i != map.end(); ++i) { + Timer::add(i->second); + } +} + +}} diff --git a/qpid/cpp/src/qpid/cluster/ClusterTimer.h b/qpid/cpp/src/qpid/cluster/ClusterTimer.h new file mode 100644 index 0000000000..69f6c622e4 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/ClusterTimer.h @@ -0,0 +1,64 @@ +#ifndef QPID_CLUSTER_CLUSTERTIMER_H +#define QPID_CLUSTER_CLUSTERTIMER_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/Timer.h" +#include <map> + +namespace qpid { +namespace cluster { + +class Cluster; + +/** + * Timer implementation that executes tasks consistently in the + * deliver thread across a cluster. Task is not executed when timer + * fires, instead the elder multicasts a wakeup. The task is executed + * when the wakeup is delivered. + */ +class ClusterTimer : public sys::Timer { + public: + ClusterTimer(Cluster&); + ~ClusterTimer(); + + void add(boost::intrusive_ptr<sys::TimerTask> task); + + void deliverWakeup(const std::string& name); + void deliverDrop(const std::string& name); + void becomeElder(); + + protected: + void fire(boost::intrusive_ptr<sys::TimerTask> task); + void drop(boost::intrusive_ptr<sys::TimerTask> task); + + private: + typedef std::map<std::string, boost::intrusive_ptr<sys::TimerTask> > Map; + Cluster& cluster; + Map map; +}; + + +}} + + +#endif /*!QPID_CLUSTER_CLUSTERTIMER_H*/ diff --git a/qpid/cpp/src/qpid/cluster/Connection.cpp b/qpid/cpp/src/qpid/cluster/Connection.cpp new file mode 100644 index 0000000000..b9895290e9 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/Connection.cpp @@ -0,0 +1,728 @@ +/* + * + * 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/amqp_0_10/Codecs.h" +#include "Connection.h" +#include "UpdateClient.h" +#include "Cluster.h" +#include "UpdateReceiver.h" +#include "qpid/assert.h" +#include "qpid/broker/SessionState.h" +#include "qpid/broker/SemanticState.h" +#include "qpid/broker/TxBuffer.h" +#include "qpid/broker/TxPublish.h" +#include "qpid/broker/TxAccept.h" +#include "qpid/broker/RecoveredEnqueue.h" +#include "qpid/broker/RecoveredDequeue.h" +#include "qpid/broker/Exchange.h" +#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" +#include "qpid/framing/AllInvoker.h" +#include "qpid/framing/DeliveryProperties.h" +#include "qpid/framing/ClusterConnectionDeliverCloseBody.h" +#include "qpid/framing/ClusterConnectionAnnounceBody.h" +#include "qpid/framing/ConnectionCloseBody.h" +#include "qpid/framing/ConnectionCloseOkBody.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/ClusterSafe.h" +#include "qpid/types/Variant.h" +#include "qpid/management/ManagementAgent.h" +#include <boost/current_function.hpp> + + +namespace qpid { +namespace cluster { + +using namespace framing; +using namespace framing::cluster; +using amqp_0_10::ListCodec; +using types::Variant; + +qpid::sys::AtomicValue<uint64_t> Connection::catchUpId(0x5000000000000000LL); + +Connection::NullFrameHandler Connection::nullFrameHandler; + +struct NullFrameHandler : public framing::FrameHandler { + void handle(framing::AMQFrame&) {} +}; + + +namespace { +sys::AtomicValue<uint64_t> idCounter; +const std::string shadowPrefix("[shadow]"); +} + + +// Shadow connection +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), announced(false), output(*this, out), + connectionCtor(&output, cluster.getBroker(), mgmtId, external, false, 0, true), + expectProtocolHeader(false), + mcastFrameHandler(cluster.getMulticast(), self), + updateIn(c.getUpdateReceiver()), + secureConnection(0) +{} + +// Local connection +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), announced(false), output(*this, out), + connectionCtor(&output, cluster.getBroker(), + mgmtId, + external, + isLink, + isCatchUp ? ++catchUpId : 0, + isCatchUp), // isCatchUp => shadow + expectProtocolHeader(isLink), + mcastFrameHandler(cluster.getMulticast(), self), + updateIn(c.getUpdateReceiver()), + secureConnection(0) +{ + if (isLocalClient()) { + giveReadCredit(cluster.getSettings().readMax); // Flow control + // Delay adding the connection to the management map until announce() + connectionCtor.delayManagement = true; + } + else { + // Catch-up shadow connections initialized using nextShadow id. + assert(catchUp); + if (!updateIn.nextShadowMgmtId.empty()) + connectionCtor.mgmtId = updateIn.nextShadowMgmtId; + updateIn.nextShadowMgmtId.clear(); + } + init(); + QPID_LOG(debug, cluster << " local connection " << *this); +} + +void Connection::setSecureConnection(broker::SecureConnection* sc) { + secureConnection = sc; + if (connection.get()) connection->setSecureConnection(sc); +} + +void Connection::init() { + connection = connectionCtor.construct(); + if (isLocalClient()) { + if (secureConnection) connection->setSecureConnection(secureConnection); + // Actively send cluster-order frames from local node + connection->setClusterOrderOutput(mcastFrameHandler); + } + else { // Shadow or catch-up connection + // Passive, discard cluster-order frames + connection->setClusterOrderOutput(nullFrameHandler); + // Disable client throttling, done by active node. + connection->setClientThrottling(false); + } + if (!isCatchUp()) + connection->setErrorListener(this); +} + +// 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) + output.giveReadCredit(credit); +} + +void Connection::announce( + const std::string& mgmtId, uint32_t ssf, const std::string& authid, bool nodict, + const std::string& username, const std::string& initialFrames) +{ + QPID_ASSERT(mgmtId == connectionCtor.mgmtId); + QPID_ASSERT(ssf == connectionCtor.external.ssf); + QPID_ASSERT(authid == connectionCtor.external.authid); + QPID_ASSERT(nodict == connectionCtor.external.nodict); + // Local connections are already initialized but with management delayed. + if (isLocalClient()) { + connection->addManagementObject(); + } + else if (isShadow()) { + init(); + // Play initial frames into the connection. + Buffer buf(const_cast<char*>(initialFrames.data()), initialFrames.size()); + AMQFrame frame; + while (frame.decode(buf)) + connection->received(frame); + connection->setUserId(username); + } + // Do managment actions now that the connection is replicated. + connection->raiseConnectEvent(); + QPID_LOG(debug, cluster << " replicated connection " << *this); +} + +Connection::~Connection() { + if (connection.get()) connection->setErrorListener(0); + // Don't trigger cluster-safe asserts in broker:: ~Connection as + // it may be called in an IO thread context during broker + // shutdown. + sys::ClusterSafeScope css; + connection.reset(); +} + +bool Connection::doOutput() { + return output.doOutput(); +} + +// Received from a directly connected client. +void Connection::received(framing::AMQFrame& f) { + if (!connection.get()) { + QPID_LOG(warning, cluster << " ignoring frame on closed connection " + << *this << ": " << f); + return; + } + QPID_LOG(trace, cluster << " RECV " << *this << ": " << f); + if (isLocal()) { // Local catch-up connection. + currentChannel = f.getChannel(); + if (!framing::invoke(*this, *f.getBody()).wasHandled()) + connection->received(f); + } + else { // Shadow or updated catch-up connection. + if (f.getMethod() && f.getMethod()->isA<ConnectionCloseBody>()) { + if (isShadow()) + cluster.addShadowConnection(this); + AMQFrame ok((ConnectionCloseOkBody())); + connection->getOutput().send(ok); + output.closeOutput(); + catchUp = false; + } + else + QPID_LOG(warning, cluster << " ignoring unexpected frame " << *this << ": " << 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(); +} + +struct GiveReadCreditOnExit { + Connection& connection; + int credit; + GiveReadCreditOnExit(Connection& connection_, int credit_) : + connection(connection_), credit(credit_) {} + ~GiveReadCreditOnExit() { if (credit) connection.giveReadCredit(credit); } +}; + +void Connection::deliverDoOutput(uint32_t limit) { + output.deliverDoOutput(limit); +} + +// Called in delivery thread, in cluster order. +void Connection::deliveredFrame(const EventFrame& f) { + GiveReadCreditOnExit gc(*this, f.readCredit); + assert(!catchUp); + 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. + { + if (f.type == DATA) // incoming data frames to broker::Connection + connection->received(const_cast<AMQFrame&>(f.frame)); + else { // frame control, send frame via SessionState + broker::SessionState* ss = connection->getChannel(currentChannel).getSession(); + if (ss) ss->out(const_cast<AMQFrame&>(f.frame)); + } + } +} + +// A local connection is closed by the network layer. Called in the connection thread. +void Connection::closed() { + try { + if (isUpdated()) { + QPID_LOG(debug, cluster << " update connection closed " << *this); + close(); + cluster.updateInClosed(); + } + else if (catchUp && cluster.isExpectingUpdate()) { + QPID_LOG(critical, cluster << " catch-up connection closed prematurely " << *this); + cluster.leave(); + } + else if (isLocal()) { + // This was a local replicated connection. Multicast a deliver + // closed and process any outstanding frames from the cluster + // until self-delivery of deliver-close. + output.closeOutput(); + if (announced) + cluster.getMulticast().mcastControl( + ClusterConnectionDeliverCloseBody(), self); + } + } + catch (const std::exception& e) { + QPID_LOG(error, cluster << " error closing connection " << *this << ": " << e.what()); + } +} + +// Self-delivery of close message, close the connection. +void Connection::deliverClose () { + close(); + cluster.erase(self); +} + +// Close the connection +void Connection::close() { + if (connection.get()) { + QPID_LOG(debug, cluster << " closed connection " << *this); + connection->closed(); + connection.reset(); + } +} + +// The connection has sent invalid data and should be aborted. +// All members will get the same abort since they all process the same data. +void Connection::abort() { + connection->abort(); + // Aborting the connection will result in a call to ::closed() + // and allow the connection to close in an orderly manner. +} + +// ConnectionCodec::decode receives read buffers from directly-connected clients. +size_t Connection::decode(const char* data, size_t size) { + GiveReadCreditOnExit grc(*this, 1); // Give a read credit by default. + const char* ptr = data; + const char* end = data + size; + if (catchUp) { // Handle catch-up locally. + if (!cluster.isExpectingUpdate()) { + QPID_LOG(error, "Rejecting unexpected catch-up connection."); + abort(); // Cluster is not expecting catch-up connections. + } + bool wasOpen = connection->isOpen(); + Buffer buf(const_cast<char*>(ptr), size); + ptr += size; + while (localDecoder.decode(buf)) + received(localDecoder.getFrame()); + if (!wasOpen && connection->isOpen()) { + // Connections marked as federation links 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()); + } + } + else { // Multicast local connections. + assert(isLocalClient()); + assert(connection.get()); + if (!checkProtocolHeader(ptr, size)) // Updates ptr + return 0; // Incomplete header + + 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; + cluster.getMulticast().mcastBuffer(ptr, end - ptr, self); + ptr = end; + } + } + return ptr - data; +} + +// Decode the protocol header if needed. Updates data and size +// returns true if the header is complete or already read. +bool Connection::checkProtocolHeader(const char*& data, size_t size) { + if (expectProtocolHeader) { + // This is an outgoing link connection, we will receive a protocol + // header which needs to be decoded first + framing::ProtocolInitiation pi; + Buffer buf(const_cast<char*&>(data), size); + if (pi.decode(buf)) { + //TODO: check the version is correct + expectProtocolHeader = false; + data += pi.encodedSize(); + } else { + return false; + } + } + return true; +} + +void Connection::processInitialFrames(const char*& ptr, size_t size) { + // Process the initial negotiation locally and store it so + // it can be replayed on other brokers in announce() + Buffer buf(const_cast<char*>(ptr), size); + framing::AMQFrame frame; + while (!connection->isOpen() && frame.decode(buf)) + received(frame); + initialFrames.append(ptr, buf.getPosition()); + ptr += buf.getPosition(); + if (connection->isOpen()) { // initial negotiation complete + cluster.getMulticast().mcastControl( + ClusterConnectionAnnounceBody( + ProtocolVersion(), + connectionCtor.mgmtId, + connectionCtor.external.ssf, + connectionCtor.external.authid, + connectionCtor.external.nodict, + connection->getUserId(), + initialFrames), + getId()); + announced = true; + initialFrames.clear(); + } +} + +broker::SessionState& Connection::sessionState() { + return *connection->getChannel(currentChannel).getSession(); +} + +broker::SemanticState& Connection::semanticState() { + return sessionState().getSemanticState(); +} + +void Connection::shadowPrepare(const std::string& mgmtId) { + updateIn.nextShadowMgmtId = mgmtId; +} + +void Connection::shadowSetUser(const std::string& userId) { + connection->setUserId(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()); +} + + +void Connection::sessionState( + const SequenceNumber& replayStart, + const SequenceNumber& sendCommandPoint, + const SequenceSet& sentIncomplete, + const SequenceNumber& expected, + const SequenceNumber& received, + const SequenceSet& unknownCompleted, + const SequenceSet& receivedIncomplete) +{ + sessionState().setState( + replayStart, + sendCommandPoint, + sentIncomplete, + expected, + 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. + connection->getOutputTasks().removeAll(); +} + +void Connection::outputTask(uint16_t channel, const std::string& name) { + broker::SessionState* session = connection->getChannel(channel).getSession(); + if (!session) + throw Exception(QPID_MSG(cluster << " channel not attached " << *this + << "[" << channel << "] ")); + OutputTask* task = &session->getSemanticState().find(name); + connection->getOutputTasks().addOutputTask(task); +} + +void Connection::shadowReady( + uint64_t memberId, uint64_t connectionId, const string& mgmtId, + const string& username, const string& fragment, uint32_t sendMax) +{ + QPID_ASSERT(mgmtId == getBrokerConnection()->getMgmtId()); + ConnectionId shadowId = ConnectionId(memberId, connectionId); + QPID_LOG(debug, cluster << " catch-up connection " << *this + << " becomes shadow " << shadowId); + self = shadowId; + connection->setUserId(username); + // OK to use decoder here because cluster is stalled for update. + cluster.getDecoder().get(self).setFragment(fragment.data(), fragment.size()); + connection->setErrorListener(this); + output.setSendMax(sendMax); +} + +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(); + closeUpdated(); + cluster.updateInDone(ClusterMap(joiners, members, frameSeq)); +} + +void Connection::retractOffer() { + QPID_LOG(info, cluster << " incoming update retracted on connection " << *this); + closeUpdated(); + cluster.updateInRetracted(); +} + +void Connection::closeUpdated() { + self.second = 0; // Mark this as completed update connection. + if (connection.get()) + connection->close(connection::CLOSE_CODE_NORMAL, "OK"); +} + +bool Connection::isLocal() const { + return self.first == cluster.getId() && self.second; +} + +bool Connection::isShadow() const { + return self.first != cluster.getId(); +} + +bool Connection::isUpdated() const { + return self.first == cluster.getId() && self.second == 0; +} + + +boost::shared_ptr<broker::Queue> Connection::findQueue(const std::string& qname) { + boost::shared_ptr<broker::Queue> queue = cluster.getBroker().getQueues().find(qname); + if (!queue) throw Exception(QPID_MSG(cluster << " can't find queue " << qname)); + return queue; +} + +broker::QueuedMessage Connection::getUpdateMessage() { + boost::shared_ptr<broker::Queue> updateq = findQueue(UpdateClient::UPDATE); + assert(!updateq->isDurable()); + broker::QueuedMessage m = updateq->get(); + if (!m.payload) throw Exception(QPID_MSG(cluster << " empty update queue")); + return m; +} + +void Connection::deliveryRecord(const string& qname, + const SequenceNumber& position, + const string& tag, + const SequenceNumber& id, + bool acquired, + bool accepted, + bool cancelled, + bool completed, + bool ended, + bool windowing, + bool enqueued, + uint32_t credit) +{ + broker::QueuedMessage m; + broker::Queue::shared_ptr queue = findQueue(qname); + if (!ended) { // Has a message + if (acquired) { // Message is on the update queue + m = getUpdateMessage(); + m.queue = queue.get(); + m.position = position; + if (enqueued) queue->updateEnqueued(m); //inform queue of the message + } else { // Message at original position in original queue + m = queue->find(position); + } + if (!m.payload) + throw Exception(QPID_MSG("deliveryRecord no update message")); + } + + broker::DeliveryRecord dr(m, queue, tag, acquired, accepted, windowing, credit); + dr.setId(id); + 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. +} + +void Connection::queuePosition(const string& qname, const SequenceNumber& position) { + findQueue(qname)->setPosition(position); +} + +void Connection::queueFairshareState(const std::string& qname, const uint8_t priority, const uint8_t count) +{ + if (!qpid::broker::Fairshare::setState(findQueue(qname)->getMessages(), priority, count)) { + QPID_LOG(error, "Failed to set fair share state on queue " << qname << "; this will result in inconsistencies."); + } +} + + +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."); +} + +void Connection::expiryId(uint64_t id) { + cluster.getExpiryPolicy().setId(id); +} + +std::ostream& operator<<(std::ostream& o, const Connection& c) { + const char* type="unknown"; + if (c.isLocal()) type = "local"; + else if (c.isShadow()) type = "shadow"; + else if (c.isUpdated()) type = "updated"; + const broker::Connection* bc = c.getBrokerConnection(); + if (bc) o << bc->getMgmtId(); + else o << "<disconnected>"; + return o << "(" << c.getId() << " " << type << (c.isCatchUp() ? ",catchup":"") << ")"; +} + +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()))); +} + +void Connection::txDequeue(const std::string& queue) { + txBuffer->enlist(boost::shared_ptr<broker::RecoveredDequeue>( + new broker::RecoveredDequeue(findQueue(queue), getUpdateMessage().payload))); +} + +void Connection::txEnqueue(const std::string& queue) { + txBuffer->enlist(boost::shared_ptr<broker::RecoveredEnqueue>( + 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) + txPub->deliverTo(findQueue((*i)->get<std::string>())); + txPub->delivered = delivered; + txBuffer->enlist(txPub); +} + +void Connection::txEnd() { + semanticState().setTxBuffer(txBuffer); +} + +void Connection::accumulatedAck(const qpid::framing::SequenceSet& s) { + semanticState().setAccumulatedAck(s); +} + +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); + if(ex.get() && ex->isDurable() && !ex->getName().find("amq.") == 0 && !ex->getName().find("qpid.") == 0) { + cluster.getBroker().getStore().create(*(ex.get()), ex->getArgs()); + } + QPID_LOG(debug, cluster << " updated exchange " << ex->getName()); +} + +void Connection::sessionError(uint16_t , const std::string& msg) { + // Ignore errors before isOpen(), we're not multicasting yet. + if (connection->isOpen()) + cluster.flagError(*this, ERROR_TYPE_SESSION, msg); +} + +void Connection::connectionError(const std::string& msg) { + // Ignore errors before isOpen(), we're not multicasting yet. + if (connection->isOpen()) + cluster.flagError(*this, ERROR_TYPE_CONNECTION, msg); +} + +void Connection::addQueueListener(const std::string& q, uint32_t listener) { + if (listener >= updateIn.consumerNumbering.size()) + throw Exception(QPID_MSG("Invalid listener ID: " << listener)); + findQueue(q)->getListeners().addListener(updateIn.consumerNumbering[listener]); +} + +// +// This is the handler for incoming managementsetup messages. +// +void Connection::managementSetupState( + uint64_t objectNum, uint16_t bootSequence, const framing::Uuid& id, + const std::string& vendor, const std::string& product, const std::string& instance) +{ + QPID_LOG(debug, cluster << " updated management: object number=" + << objectNum << " boot sequence=" << bootSequence + << " broker-id=" << id + << " vendor=" << vendor + << " product=" << product + << " instance=" << instance); + management::ManagementAgent* agent = cluster.getBroker().getManagementAgent(); + if (!agent) + throw Exception(QPID_MSG("Management schema update but management not enabled.")); + agent->setNextObjectId(objectNum); + agent->setBootSequence(bootSequence); + agent->setUuid(id); + agent->setName(vendor, product, instance); +} + +void Connection::config(const std::string& encoded) { + Buffer buf(const_cast<char*>(encoded.data()), encoded.size()); + string kind; + buf.getShortString (kind); + if (kind == "link") { + broker::Link::shared_ptr link = + broker::Link::decode(cluster.getBroker().getLinks(), buf); + QPID_LOG(debug, cluster << " updated link " + << link->getHost() << ":" << link->getPort()); + } + else if (kind == "bridge") { + broker::Bridge::shared_ptr bridge = + broker::Bridge::decode(cluster.getBroker().getLinks(), buf); + QPID_LOG(debug, cluster << " updated bridge " << bridge->getName()); + } + 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(); +} +}} // Namespace qpid::cluster + diff --git a/qpid/cpp/src/qpid/cluster/Connection.h b/qpid/cpp/src/qpid/cluster/Connection.h new file mode 100644 index 0000000000..a0da9efbb8 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/Connection.h @@ -0,0 +1,276 @@ +#ifndef QPID_CLUSTER_CONNECTION_H +#define QPID_CLUSTER_CONNECTION_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 "types.h" +#include "OutputInterceptor.h" +#include "McastFrameHandler.h" +#include "UpdateReceiver.h" + +#include "qpid/RefCounted.h" +#include "qpid/broker/Connection.h" +#include "qpid/broker/SecureConnection.h" +#include "qpid/broker/SemanticState.h" +#include "qpid/amqp_0_10/Connection.h" +#include "qpid/sys/AtomicValue.h" +#include "qpid/sys/ConnectionInputHandler.h" +#include "qpid/sys/ConnectionOutputHandler.h" +#include "qpid/sys/SecuritySettings.h" +#include "qpid/framing/SequenceNumber.h" +#include "qpid/framing/FrameDecoder.h" + +#include <iosfwd> + +namespace qpid { + +namespace framing { class AMQFrame; } + +namespace broker { +class SemanticState; +struct QueuedMessage; +class TxBuffer; +class TxAccept; +} + +namespace cluster { +class Cluster; +class Event; +struct EventFrame; + +/** Intercept broker::Connection calls for shadow and local cluster connections. */ +class Connection : + public RefCounted, + public sys::ConnectionInputHandler, + public framing::AMQP_AllOperations::ClusterConnectionHandler, + private broker::Connection::ErrorListener + +{ + public: + + /** Local connection. */ + Connection(Cluster&, sys::ConnectionOutputHandler& out, const std::string& mgmtId, MemberId, bool catchUp, bool isLink, + const qpid::sys::SecuritySettings& external); + /** Shadow 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(); } + + /** Local connections may be clients or catch-up connections */ + bool isLocal() const; + + bool isLocalClient() const { return isLocal() && !isCatchUp(); } + + /** True for connections that are shadowing remote broker connections */ + bool isShadow() const; + + /** True if the connection is in "catch-up" mode: building initial broker state. */ + bool isCatchUp() const { return catchUp; } + + /** True if the connection is a completed shared update connection */ + bool isUpdated() const; + + Cluster& getCluster() { return cluster; } + + // ConnectionInputHandler methods + void received(framing::AMQFrame&); + void closed(); + bool doOutput(); + void idleOut() { if (connection.get()) connection->idleOut(); } + void idleIn() { if (connection.get()) connection->idleIn(); } + + // ConnectionCodec methods - called by IO layer with a read buffer. + size_t decode(const char* buffer, size_t size); + + // Called for data delivered from the cluster. + 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&); + + void shadowSetUser(const std::string&); + + void sessionState(const framing::SequenceNumber& replayStart, + const framing::SequenceNumber& sendCommandPoint, + const framing::SequenceSet& sentIncomplete, + const framing::SequenceNumber& expected, + const framing::SequenceNumber& received, + const framing::SequenceSet& unknownCompleted, + const SequenceSet& receivedIncomplete); + + void outputTask(uint16_t channel, const std::string& name); + + void shadowReady(uint64_t memberId, + uint64_t connectionId, + const std::string& managementId, + const std::string& username, + const std::string& fragment, + uint32_t sendMax); + + void membership(const framing::FieldTable&, const framing::FieldTable&, + const framing::SequenceNumber& frameSeq); + + void retractOffer(); + + void deliveryRecord(const std::string& queue, + const framing::SequenceNumber& position, + const std::string& tag, + const framing::SequenceNumber& id, + bool acquired, + bool accepted, + bool cancelled, + bool completed, + bool ended, + bool windowing, + bool enqueued, + uint32_t credit); + + void queuePosition(const std::string&, const framing::SequenceNumber&); + void queueFairshareState(const std::string&, const uint8_t priority, const uint8_t count); + void queueObserverState(const std::string&, const std::string&, const framing::FieldTable&); + void expiryId(uint64_t); + + void txStart(); + void txAccept(const framing::SequenceSet&); + void txDequeue(const std::string&); + void txEnqueue(const std::string&); + void txPublish(const framing::Array&, bool); + void txEnd(); + void accumulatedAck(const framing::SequenceSet&); + + // Encoded exchange replication. + void exchange(const std::string& encoded); + + void giveReadCredit(int credit); + void announce(const std::string& mgmtId, uint32_t ssf, const std::string& authid, + bool nodict, const std::string& username, + const std::string& initFrames); + void close(); + void abort(); + void deliverClose(); + + OutputInterceptor& getOutput() { return output; } + + void addQueueListener(const std::string& queue, uint32_t listener); + void managementSetupState(uint64_t objectNum, + uint16_t bootSequence, + const framing::Uuid&, + const std::string& vendor, + const std::string& product, + const std::string& instance); + + void config(const std::string& encoded); + + void setSecureConnection ( broker::SecureConnection * sc ); + + void doCatchupIoCallbacks(); + + private: + struct NullFrameHandler : public framing::FrameHandler { + void handle(framing::AMQFrame&) {} + }; + + // Arguments to construct a broker::Connection + struct ConnectionCtor { + sys::ConnectionOutputHandler* out; + broker::Broker& broker; + std::string mgmtId; + qpid::sys::SecuritySettings external; + bool isLink; + uint64_t objectId; + bool shadow; + bool delayManagement; + + ConnectionCtor( + sys::ConnectionOutputHandler* out_, + broker::Broker& broker_, + const std::string& mgmtId_, + const qpid::sys::SecuritySettings& external_, + bool isLink_=false, + uint64_t objectId_=0, + bool shadow_=false, + bool delayManagement_=false + ) : out(out_), broker(broker_), mgmtId(mgmtId_), external(external_), + isLink(isLink_), objectId(objectId_), shadow(shadow_), + delayManagement(delayManagement_) + {} + + std::auto_ptr<broker::Connection> construct() { + return std::auto_ptr<broker::Connection>( + new broker::Connection( + out, broker, mgmtId, external, isLink, objectId, + shadow, delayManagement) + ); + } + }; + + static NullFrameHandler nullFrameHandler; + + // 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); + + bool checkProtocolHeader(const char*& data, size_t size); + void processInitialFrames(const char*& data, size_t size); + boost::shared_ptr<broker::Queue> findQueue(const std::string& qname); + broker::SessionState& sessionState(); + broker::SemanticState& semanticState(); + broker::QueuedMessage getUpdateMessage(); + void closeUpdated(); + + Cluster& cluster; + ConnectionId self; + bool catchUp; + bool announced; + OutputInterceptor output; + framing::FrameDecoder localDecoder; + ConnectionCtor connectionCtor; + std::auto_ptr<broker::Connection> connection; + framing::SequenceNumber deliverSeq; + framing::ChannelId currentChannel; + boost::shared_ptr<broker::TxBuffer> txBuffer; + bool expectProtocolHeader; + McastFrameHandler mcastFrameHandler; + UpdateReceiver& updateIn; + qpid::broker::SecureConnection* secureConnection; + std::string initialFrames; + + static qpid::sys::AtomicValue<uint64_t> catchUpId; + + friend std::ostream& operator<<(std::ostream&, const Connection&); +}; + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_CONNECTION_H*/ diff --git a/qpid/cpp/src/qpid/cluster/ConnectionCodec.cpp b/qpid/cpp/src/qpid/cluster/ConnectionCodec.cpp new file mode 100644 index 0000000000..d0ba8abfb3 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/ConnectionCodec.cpp @@ -0,0 +1,92 @@ +/* + * + * 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/cluster/ConnectionCodec.h" +#include "qpid/cluster/Connection.h" +#include "qpid/cluster/Cluster.h" +#include "qpid/cluster/ProxyInputHandler.h" +#include "qpid/broker/Connection.h" +#include "qpid/framing/ConnectionCloseBody.h" +#include "qpid/framing/ConnectionCloseOkBody.h" +#include "qpid/log/Statement.h" +#include "qpid/memory.h" +#include <stdexcept> +#include <boost/utility/in_place_factory.hpp> + +namespace qpid { +namespace cluster { + +using namespace framing; + +sys::ConnectionCodec* +ConnectionCodec::Factory::create(ProtocolVersion v, sys::OutputControl& out, + const std::string& id, + const qpid::sys::SecuritySettings& external) +{ + broker::Broker& broker = cluster.getBroker(); + if (broker.getConnectionCounter().allowConnection()) + { + QPID_LOG(error, "Client max connection count limit exceeded: " + << broker.getOptions().maxConnections << " connection refused"); + return 0; + } + if (v == ProtocolVersion(0, 10)) + return new ConnectionCodec(v, out, id, cluster, false, false, external); + else if (v == ProtocolVersion(0x80 + 0, 0x80 + 10)) // Catch-up connection + return new ConnectionCodec(v, out, id, cluster, true, false, external); + return 0; +} + +// Used for outgoing Link connections +sys::ConnectionCodec* +ConnectionCodec::Factory::create(sys::OutputControl& out, const std::string& logId, + const qpid::sys::SecuritySettings& external) { + return new ConnectionCodec(ProtocolVersion(0,10), out, logId, cluster, false, true, external); +} + +ConnectionCodec::ConnectionCodec( + const ProtocolVersion& v, sys::OutputControl& out, + const std::string& logId, Cluster& cluster, bool catchUp, bool isLink, const qpid::sys::SecuritySettings& external +) : codec(out, logId, isLink), + interceptor(new Connection(cluster, codec, logId, cluster.getId(), catchUp, isLink, external)) +{ + cluster.addLocalConnection(interceptor); + std::auto_ptr<sys::ConnectionInputHandler> ih(new ProxyInputHandler(interceptor)); + codec.setInputHandler(ih); + codec.setVersion(v); +} + +ConnectionCodec::~ConnectionCodec() {} + +size_t ConnectionCodec::decode(const char* buffer, size_t size) { + return interceptor->decode(buffer, size); +} + +bool ConnectionCodec::isClosed() const { return codec.isClosed(); } + +size_t ConnectionCodec::encode(const char* buffer, size_t size) { return codec.encode(buffer, size); } + +bool ConnectionCodec::canEncode() { return codec.canEncode(); } + +void ConnectionCodec::closed() { codec.closed(); } + +ProtocolVersion ConnectionCodec::getVersion() const { return codec.getVersion(); } + +}} // namespace qpid::cluster diff --git a/qpid/cpp/src/qpid/cluster/ConnectionCodec.h b/qpid/cpp/src/qpid/cluster/ConnectionCodec.h new file mode 100644 index 0000000000..17a08904d9 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/ConnectionCodec.h @@ -0,0 +1,82 @@ +#ifndef QPID_CLUSTER_CONNCTIONCODEC_H +#define QPID_CLUSTER_CONNCTIONCODEC_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/amqp_0_10/Connection.h" +#include "qpid/cluster/Connection.h" +#include <boost/shared_ptr.hpp> +#include <boost/intrusive_ptr.hpp> + +namespace qpid { + +namespace broker { +class Connection; +} + +namespace cluster { +class Cluster; + +/** + * Encapsulates the standard amqp_0_10::ConnectionCodec and sets up + * a cluster::Connection for the connection. + * + * The ConnectionCodec is deleted by the network layer when the + * connection closes. The cluster::Connection needs to be kept + * around until all cluster business on the connection is complete. + * + */ +class ConnectionCodec : public sys::ConnectionCodec { + public: + struct Factory : public sys::ConnectionCodec::Factory { + boost::shared_ptr<sys::ConnectionCodec::Factory> next; + Cluster& cluster; + Factory(boost::shared_ptr<sys::ConnectionCodec::Factory> f, Cluster& c) + : next(f), cluster(c) {} + sys::ConnectionCodec* create(framing::ProtocolVersion, sys::OutputControl&, const std::string& id, + const qpid::sys::SecuritySettings& external); + sys::ConnectionCodec* create(sys::OutputControl&, const std::string& id, + const qpid::sys::SecuritySettings& external); + }; + + ConnectionCodec(const framing::ProtocolVersion&, sys::OutputControl& out, + const std::string& logId, Cluster& c, bool catchUp, bool isLink, + const qpid::sys::SecuritySettings& external); + ~ConnectionCodec(); + + // ConnectionCodec functions. + size_t decode(const char* buffer, size_t size); + size_t encode(const char* buffer, size_t size); + bool canEncode(); + void closed(); + bool isClosed() const; + framing::ProtocolVersion getVersion() const; + void setSecureConnection(broker::SecureConnection* sc) { interceptor->setSecureConnection(sc); } + + private: + amqp_0_10::Connection codec; + boost::intrusive_ptr<cluster::Connection> interceptor; +}; + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_CONNCTIONCODEC_H*/ diff --git a/qpid/cpp/src/qpid/cluster/Cpg.cpp b/qpid/cpp/src/qpid/cluster/Cpg.cpp new file mode 100644 index 0000000000..0856bcd824 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/Cpg.cpp @@ -0,0 +1,280 @@ +/* + * + * 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. + * + */ + +#include "qpid/cluster/Cpg.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Time.h" +#include "qpid/sys/posix/PrivatePosix.h" +#include "qpid/log/Statement.h" + +#include <vector> +#include <limits> +#include <iterator> +#include <sstream> + +#include <unistd.h> + +// This is a macro instead of a function because we don't want to +// evaluate the MSG argument unless there is an error. +#define CPG_CHECK(RESULT, MSG) \ + if ((RESULT) != CPG_OK) throw Exception(errorStr((RESULT), (MSG))) + +namespace qpid { +namespace cluster { + +using namespace std; + + + +Cpg* Cpg::cpgFromHandle(cpg_handle_t handle) { + void* cpg=0; + CPG_CHECK(cpg_context_get(handle, &cpg), "Cannot get CPG instance."); + if (!cpg) throw Exception("Cannot get CPG instance."); + return reinterpret_cast<Cpg*>(cpg); +} + +// Applies the same retry-logic to all cpg calls that need it. +void Cpg::callCpg ( CpgOp & c ) { + cpg_error_t result; + unsigned int snooze = 10; + for ( unsigned int nth_try = 0; nth_try < cpgRetries; ++ nth_try ) { + if ( CPG_OK == (result = c.op(handle, & group))) { + break; + } + else if ( result == CPG_ERR_TRY_AGAIN ) { + QPID_LOG(info, "Retrying " << c.opName ); + sys::usleep ( snooze ); + snooze *= 10; + snooze = (snooze <= maxCpgRetrySleep) ? snooze : maxCpgRetrySleep; + } + else break; // Don't retry unless CPG tells us to. + } + + if ( result != CPG_OK ) + CPG_CHECK(result, c.msg(group)); +} + +// Global callback functions. +void Cpg::globalDeliver ( + cpg_handle_t handle, + const struct cpg_name *group, + uint32_t nodeid, + uint32_t pid, + void* msg, + size_t msg_len) +{ + cpgFromHandle(handle)->handler.deliver(handle, group, nodeid, pid, msg, msg_len); +} + +void Cpg::globalConfigChange( + cpg_handle_t handle, + const struct cpg_name *group, + const struct cpg_address *members, size_t nMembers, + const struct cpg_address *left, size_t nLeft, + const struct cpg_address *joined, size_t nJoined +) +{ + cpgFromHandle(handle)->handler.configChange(handle, group, members, nMembers, left, nLeft, joined, nJoined); +} + +void Cpg::globalDeliver ( + cpg_handle_t handle, + struct cpg_name *group, + uint32_t nodeid, + uint32_t pid, + void* msg, + int msg_len) +{ + cpgFromHandle(handle)->handler.deliver(handle, group, nodeid, pid, msg, msg_len); +} + +void Cpg::globalConfigChange( + cpg_handle_t handle, + struct cpg_name *group, + struct cpg_address *members, int nMembers, + struct cpg_address *left, int nLeft, + struct cpg_address *joined, int nJoined +) +{ + cpgFromHandle(handle)->handler.configChange(handle, group, members, nMembers, left, nLeft, joined, nJoined); +} + +int Cpg::getFd() { + int fd; + CPG_CHECK(cpg_fd_get(handle, &fd), "Cannot get CPG file descriptor"); + return fd; +} + +Cpg::Cpg(Handler& h) : IOHandle(new sys::IOHandlePrivate), handler(h), isShutdown(false) { + cpg_callbacks_t callbacks; + ::memset(&callbacks, 0, sizeof(callbacks)); + callbacks.cpg_deliver_fn = &globalDeliver; + callbacks.cpg_confchg_fn = &globalConfigChange; + + QPID_LOG(notice, "Initializing CPG"); + cpg_error_t err = cpg_initialize(&handle, &callbacks); + int retries = 6; // FIXME aconway 2009-08-06: make this configurable. + while (err == CPG_ERR_TRY_AGAIN && --retries) { + QPID_LOG(notice, "Re-trying CPG initialization."); + sys::sleep(5); + err = cpg_initialize(&handle, &callbacks); + } + CPG_CHECK(err, "Failed to initialize CPG."); + CPG_CHECK(cpg_context_set(handle, this), "Cannot set CPG context"); + // Note: CPG is currently unix-specific. If CPG is ported to + // windows then this needs to be refactored into + // qpid::sys::<platform> + IOHandle::impl->fd = getFd(); +} + +Cpg::~Cpg() { + try { + shutdown(); + } catch (const std::exception& e) { + QPID_LOG(error, "Error during CPG shutdown: " << e.what()); + } +} + +void Cpg::join(const std::string& name) { + group = name; + callCpg ( cpgJoinOp ); +} + +void Cpg::leave() { + callCpg ( cpgLeaveOp ); +} + + + + +bool Cpg::mcast(const iovec* iov, int iovLen) { + // Check for flow control + cpg_flow_control_state_t flowState; + CPG_CHECK(cpg_flow_control_state_get(handle, &flowState), "Cannot get CPG flow control status."); + if (flowState == CPG_FLOW_CONTROL_ENABLED) + return false; + + cpg_error_t result; + do { + result = cpg_mcast_joined(handle, CPG_TYPE_AGREED, const_cast<iovec*>(iov), iovLen); + if (result != CPG_ERR_TRY_AGAIN) CPG_CHECK(result, cantMcastMsg(group)); + } while(result == CPG_ERR_TRY_AGAIN); + return true; +} + +void Cpg::shutdown() { + if (!isShutdown) { + QPID_LOG(debug,"Shutting down CPG"); + isShutdown=true; + + callCpg ( cpgFinalizeOp ); + } +} + +void Cpg::dispatchOne() { + CPG_CHECK(cpg_dispatch(handle,CPG_DISPATCH_ONE), "Error in CPG dispatch"); +} + +void Cpg::dispatchAll() { + CPG_CHECK(cpg_dispatch(handle,CPG_DISPATCH_ALL), "Error in CPG dispatch"); +} + +void Cpg::dispatchBlocking() { + CPG_CHECK(cpg_dispatch(handle,CPG_DISPATCH_BLOCKING), "Error in CPG dispatch"); +} + +string Cpg::errorStr(cpg_error_t err, const std::string& msg) { + std::ostringstream os; + os << msg << ": "; + switch (err) { + case CPG_OK: os << "ok"; break; + case CPG_ERR_LIBRARY: os << "library"; break; + case CPG_ERR_TIMEOUT: os << "timeout"; break; + case CPG_ERR_TRY_AGAIN: os << "try again"; break; + case CPG_ERR_INVALID_PARAM: os << "invalid param"; break; + case CPG_ERR_NO_MEMORY: os << "no memory"; break; + case CPG_ERR_BAD_HANDLE: os << "bad handle"; break; + case CPG_ERR_ACCESS: os << "access denied. You may need to set your group ID to 'ais'"; break; + case CPG_ERR_NOT_EXIST: os << "not exist"; break; + case CPG_ERR_EXIST: os << "exist"; break; + case CPG_ERR_NOT_SUPPORTED: os << "not supported"; break; + case CPG_ERR_SECURITY: os << "security"; break; + case CPG_ERR_TOO_MANY_GROUPS: os << "too many groups"; break; + default: os << ": unknown cpg error " << err; + }; + os << " (" << err << ")"; + return os.str(); +} + +std::string Cpg::cantJoinMsg(const Name& group) { + return "Cannot join CPG group "+group.str(); +} + +std::string Cpg::cantFinalizeMsg(const Name& group) { + return "Cannot finalize CPG group "+group.str(); +} + +std::string Cpg::cantLeaveMsg(const Name& group) { + return "Cannot leave CPG group "+group.str(); +} + +std::string Cpg::cantMcastMsg(const Name& group) { + return "Cannot mcast to CPG group "+group.str(); +} + +MemberId Cpg::self() const { + unsigned int nodeid; + CPG_CHECK(cpg_local_get(handle, &nodeid), "Cannot get local CPG identity"); + return MemberId(nodeid, getpid()); +} + +namespace { int byte(uint32_t value, int i) { return (value >> (i*8)) & 0xff; } } + +ostream& operator<<(ostream& out, const MemberId& id) { + if (id.first) { + out << byte(id.first, 0) << "." + << byte(id.first, 1) << "." + << byte(id.first, 2) << "." + << byte(id.first, 3) + << ":"; + } + return out << id.second; +} + +ostream& operator<<(ostream& o, const ConnectionId& c) { + return o << c.first << "-" << c.second; +} + +std::string MemberId::str() const { + char s[8]; + uint32_t x; + x = htonl(first); + ::memcpy(s, &x, 4); + x = htonl(second); + ::memcpy(s+4, &x, 4); + return std::string(s,8); +} + +MemberId::MemberId(const std::string& s) { + uint32_t x; + memcpy(&x, &s[0], 4); + first = ntohl(x); + memcpy(&x, &s[4], 4); + second = ntohl(x); +} +}} // namespace qpid::cluster diff --git a/qpid/cpp/src/qpid/cluster/Cpg.h b/qpid/cpp/src/qpid/cluster/Cpg.h new file mode 100644 index 0000000000..6b81c602bd --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/Cpg.h @@ -0,0 +1,236 @@ +#ifndef CPG_H +#define CPG_H + +/* + * + * 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. + * + */ + +#include "qpid/Exception.h" +#include "qpid/cluster/types.h" +#include "qpid/sys/IOHandle.h" +#include "qpid/sys/Mutex.h" + +#include <boost/scoped_ptr.hpp> + +#include <cassert> +#include <string.h> + +namespace qpid { +namespace cluster { + +/** + * Lightweight C++ interface to cpg.h operations. + * + * Manages a single CPG handle, initialized in ctor, finialzed in destructor. + * On error all functions throw Cpg::Exception. + * + */ + +class Cpg : public sys::IOHandle { + public: + struct Exception : public ::qpid::Exception { + Exception(const std::string& msg) : ::qpid::Exception(msg) {} + }; + + struct Name : public cpg_name { + Name() { length = 0; } + Name(const char* s) { copy(s, strlen(s)); } + Name(const char* s, size_t n) { copy(s,n); } + Name(const std::string& s) { copy(s.data(), s.size()); } + void copy(const char* s, size_t n) { + assert(n < CPG_MAX_NAME_LENGTH); + memcpy(value, s, n); + length=n; + } + + std::string str() const { return std::string(value, length); } + }; + + static std::string str(const cpg_name& n) { + return std::string(n.value, n.length); + } + + struct Handler { + virtual ~Handler() {}; + virtual void deliver( + cpg_handle_t /*handle*/, + const struct cpg_name *group, + uint32_t /*nodeid*/, + uint32_t /*pid*/, + void* /*msg*/, + int /*msg_len*/) = 0; + + virtual void configChange( + cpg_handle_t /*handle*/, + const struct cpg_name */*group*/, + const struct cpg_address */*members*/, int /*nMembers*/, + const struct cpg_address */*left*/, int /*nLeft*/, + const struct cpg_address */*joined*/, int /*nJoined*/ + ) = 0; + }; + + /** Open a CPG handle. + *@param handler for CPG events. + */ + Cpg(Handler&); + + /** Destructor calls shutdown if not already calledx. */ + ~Cpg(); + + /** Disconnect from CPG */ + void shutdown(); + + void dispatchOne(); + void dispatchAll(); + void dispatchBlocking(); + + void join(const std::string& group); + void leave(); + + /** Multicast to the group. NB: must not be called concurrently. + * + *@return true if the message was multi-cast, false if + * it was not sent due to flow control. + */ + bool mcast(const iovec* iov, int iovLen); + + cpg_handle_t getHandle() const { return handle; } + + MemberId self() const; + + int getFd(); + + private: + + // Maximum number of retries for cog functions that can tell + // us to "try again later". + static const unsigned int cpgRetries = 5; + + // Don't let sleep-time between cpg retries to go above 0.1 second. + static const unsigned int maxCpgRetrySleep = 100000; + + + // Base class for the Cpg operations that need retry capability. + struct CpgOp { + std::string opName; + + CpgOp ( std::string opName ) + : opName(opName) { } + + virtual cpg_error_t op ( cpg_handle_t handle, struct cpg_name * ) = 0; + virtual std::string msg(const Name&) = 0; + virtual ~CpgOp ( ) { } + }; + + + struct CpgJoinOp : public CpgOp { + CpgJoinOp ( ) + : CpgOp ( std::string("cpg_join") ) { } + + cpg_error_t op(cpg_handle_t handle, struct cpg_name * group) { + return cpg_join ( handle, group ); + } + + std::string msg(const Name& name) { return cantJoinMsg(name); } + }; + + struct CpgLeaveOp : public CpgOp { + CpgLeaveOp ( ) + : CpgOp ( std::string("cpg_leave") ) { } + + cpg_error_t op(cpg_handle_t handle, struct cpg_name * group) { + return cpg_leave ( handle, group ); + } + + std::string msg(const Name& name) { return cantLeaveMsg(name); } + }; + + struct CpgFinalizeOp : public CpgOp { + CpgFinalizeOp ( ) + : CpgOp ( std::string("cpg_finalize") ) { } + + cpg_error_t op(cpg_handle_t handle, struct cpg_name *) { + return cpg_finalize ( handle ); + } + + std::string msg(const Name& name) { return cantFinalizeMsg(name); } + }; + + // This fn standardizes retry policy across all Cpg ops that need it. + void callCpg ( CpgOp & ); + + CpgJoinOp cpgJoinOp; + CpgLeaveOp cpgLeaveOp; + CpgFinalizeOp cpgFinalizeOp; + + static std::string errorStr(cpg_error_t err, const std::string& msg); + static std::string cantJoinMsg(const Name&); + static std::string cantLeaveMsg(const Name&); + static std::string cantMcastMsg(const Name&); + static std::string cantFinalizeMsg(const Name&); + + static Cpg* cpgFromHandle(cpg_handle_t); + + // New versions for corosync 1.0 and higher + static void globalDeliver( + cpg_handle_t handle, + const struct cpg_name *group, + uint32_t nodeid, + uint32_t pid, + void* msg, + size_t msg_len); + + static void globalConfigChange( + cpg_handle_t handle, + const struct cpg_name *group, + const struct cpg_address *members, size_t nMembers, + const struct cpg_address *left, size_t nLeft, + const struct cpg_address *joined, size_t nJoined + ); + + // Old versions for openais + static void globalDeliver( + cpg_handle_t handle, + struct cpg_name *group, + uint32_t nodeid, + uint32_t pid, + void* msg, + int msg_len); + + static void globalConfigChange( + cpg_handle_t handle, + struct cpg_name *group, + struct cpg_address *members, int nMembers, + struct cpg_address *left, int nLeft, + struct cpg_address *joined, int nJoined + ); + + cpg_handle_t handle; + Handler& handler; + bool isShutdown; + Name group; + sys::Mutex dispatchLock; +}; + +inline bool operator==(const cpg_name& a, const cpg_name& b) { + return a.length==b.length && strncmp(a.value, b.value, a.length) == 0; +} +inline bool operator!=(const cpg_name& a, const cpg_name& b) { return !(a == b); } + +}} // namespace qpid::cluster + +#endif /*!CPG_H*/ diff --git a/qpid/cpp/src/qpid/cluster/Decoder.cpp b/qpid/cpp/src/qpid/cluster/Decoder.cpp new file mode 100644 index 0000000000..23ba372d78 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/Decoder.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 "qpid/cluster/Decoder.h" +#include "qpid/cluster/EventFrame.h" +#include "qpid/framing/ClusterConnectionDeliverCloseBody.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/AMQFrame.h" + + +namespace qpid { +namespace cluster { + +void Decoder::decode(const EventHeader& eh, const char* data) { + sys::Mutex::ScopedLock l(lock); + assert(eh.getType() == DATA); // Only handle connection data events. + const char* cp = static_cast<const char*>(data); + framing::Buffer buf(const_cast<char*>(cp), eh.getSize()); + framing::FrameDecoder& decoder = map[eh.getConnectionId()]; + if (decoder.decode(buf)) { // Decoded a frame + framing::AMQFrame frame(decoder.getFrame()); + while (decoder.decode(buf)) { + callback(EventFrame(eh, frame)); + frame = decoder.getFrame(); + } + // Set read-credit on the last frame ending in this event. + // Credit will be given when this frame is processed. + callback(EventFrame(eh, frame, 1)); + } + else { + // We must give 1 unit read credit per event. + // This event does not complete any frames so + // send an empty frame with the read credit. + callback(EventFrame(eh, framing::AMQFrame(), 1)); + } +} + +void Decoder::erase(const ConnectionId& c) { + sys::Mutex::ScopedLock l(lock); + map.erase(c); +} + +framing::FrameDecoder& Decoder::get(const ConnectionId& c) { + sys::Mutex::ScopedLock l(lock); + return map[c]; +} + +}} // namespace qpid::cluster diff --git a/qpid/cpp/src/qpid/cluster/Decoder.h b/qpid/cpp/src/qpid/cluster/Decoder.h new file mode 100644 index 0000000000..3b5ada4a81 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/Decoder.h @@ -0,0 +1,59 @@ +#ifndef QPID_CLUSTER_DECODER_H +#define QPID_CLUSTER_DECODER_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/cluster/types.h" +#include "qpid/framing/FrameDecoder.h" +#include "qpid/sys/Mutex.h" +#include <boost/function.hpp> +#include <map> + +namespace qpid { +namespace cluster { + +struct EventFrame; +class EventHeader; + +/** + * A map of decoders for connections. + */ +class Decoder +{ + public: + typedef boost::function<void(const EventFrame&)> FrameHandler; + + Decoder(FrameHandler fh) : callback(fh) {} + void decode(const EventHeader& eh, const char* data); + void erase(const ConnectionId&); + framing::FrameDecoder& get(const ConnectionId& c); + + private: + typedef std::map<ConnectionId, framing::FrameDecoder> Map; + sys::Mutex lock; + Map map; + void process(const EventFrame&); + FrameHandler callback; +}; +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_DECODER_H*/ diff --git a/qpid/cpp/src/qpid/cluster/Dispatchable.h b/qpid/cpp/src/qpid/cluster/Dispatchable.h new file mode 100644 index 0000000000..e7f0df4218 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/Dispatchable.h @@ -0,0 +1,52 @@ +#ifndef QPID_CLUSTER_DISPATCHABLE_H +#define QPID_CLUSTER_DISPATCHABLE_H + +/* + * + * 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. + * + */ + +namespace qpid { +namespace cluster { + +/** + * Interface for classes that have some "events" that need dispatching + * in a thread. + */ +class Dispatchable +{ + public: + virtual ~Dispatchable() {} + + /** Dispatch one event in current thread. */ + virtual void dispatchOne() = 0; + /** Dispatch all available events, don't block. */ + virtual void dispatchAll() = 0; + /** Blocking loop to dispatch cluster events */ + virtual void dispatchBlocking() = 0; + + /** Wait for at least one event, then dispatch all available events. + * Don't block. Useful for tests. + */ + virtual void dispatchSome() { dispatchOne(); dispatchAll(); } + +}; + +}} // namespace qpid::cluster + + + +#endif /*!QPID_CLUSTER_DISPATCHABLE_H*/ diff --git a/qpid/cpp/src/qpid/cluster/ErrorCheck.cpp b/qpid/cpp/src/qpid/cluster/ErrorCheck.cpp new file mode 100644 index 0000000000..be671c0f48 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/ErrorCheck.cpp @@ -0,0 +1,155 @@ +/* + * + * 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/cluster/ErrorCheck.h" +#include "qpid/cluster/EventFrame.h" +#include "qpid/cluster/ClusterMap.h" +#include "qpid/cluster/Cluster.h" +#include "qpid/framing/ClusterErrorCheckBody.h" +#include "qpid/framing/ClusterConfigChangeBody.h" +#include "qpid/log/Statement.h" + +#include <algorithm> + +namespace qpid { +namespace cluster { + +using namespace std; +using namespace framing; +using namespace framing::cluster; + +ErrorCheck::ErrorCheck(Cluster& c) + : cluster(c), mcast(c.getMulticast()), frameSeq(0), type(ERROR_TYPE_NONE), connection(0) +{} + +void ErrorCheck::error( + Connection& c, ErrorType t, framing::SequenceNumber seq, const MemberSet& ms, + const std::string& msg) +{ + // Detected a local error, inform cluster and set error state. + assert(t != ERROR_TYPE_NONE); // Must be an error. + assert(type == ERROR_TYPE_NONE); // Can't be called when already in an error state. + type = t; + unresolved = ms; + frameSeq = seq; + connection = &c; + message = msg; + QPID_LOG(debug, cluster<< (type == ERROR_TYPE_SESSION ? " channel" : " connection") + << " error " << frameSeq << " on " << c + << " must be resolved with: " << unresolved + << ": " << message); + mcast.mcastControl( + ClusterErrorCheckBody(ProtocolVersion(), type, frameSeq), cluster.getId()); + // If there are already frames queued up by a previous error, review + // them with respect to this new error. + for (FrameQueue::iterator i = frames.begin(); i != frames.end(); i = review(i)) + ; +} + +void ErrorCheck::delivered(const EventFrame& e) { + frames.push_back(e); + review(frames.end()-1); +} + +// Review a frame in the queue with respect to the current error. +ErrorCheck::FrameQueue::iterator ErrorCheck::review(const FrameQueue::iterator& i) { + FrameQueue::iterator next = i+1; + if(!isUnresolved() || !i->frame.getBody() || !i->frame.getMethod()) + return next; // Only interested in control frames while unresolved. + const AMQMethodBody* method = i->frame.getMethod(); + if (method->isA<const ClusterErrorCheckBody>()) { + const ClusterErrorCheckBody* errorCheck = + static_cast<const ClusterErrorCheckBody*>(method); + + if (errorCheck->getFrameSeq() == frameSeq) { // Addresses current error + next = frames.erase(i); // Drop matching error check controls + if (errorCheck->getType() < type) { // my error is worse than his + QPID_LOG(critical, cluster + << " local error " << frameSeq << " did not occur on member " + << i->getMemberId() + << ": " << message); + throw Exception( + QPID_MSG("local error did not occur on all cluster members " << ": " << message)); + } + else { // his error is worse/same as mine. + QPID_LOG(debug, cluster << " error " << frameSeq + << " resolved with " << i->getMemberId()); + unresolved.erase(i->getMemberId()); + checkResolved(); + } + } + else if (errorCheck->getFrameSeq() < frameSeq && errorCheck->getType() != NONE + && i->connectionId.getMember() != cluster.getId()) + { + // This error occured before the current error so we + // have processed past it. + next = frames.erase(i); // Drop the error check control + respondNone(i->connectionId.getMember(), errorCheck->getType(), + errorCheck->getFrameSeq()); + } + // if errorCheck->getFrameSeq() > frameSeq then leave it in the queue. + } + else if (method->isA<const ClusterConfigChangeBody>()) { + const ClusterConfigChangeBody* configChange = + static_cast<const ClusterConfigChangeBody*>(method); + if (configChange) { + MemberSet members(decodeMemberSet(configChange->getMembers())); + QPID_LOG(debug, cluster << " apply config change to error " + << frameSeq << ": " << members); + MemberSet intersect; + set_intersection(members.begin(), members.end(), + unresolved.begin(), unresolved.end(), + inserter(intersect, intersect.begin())); + unresolved.swap(intersect); + checkResolved(); + } + } + return next; +} + +void ErrorCheck::checkResolved() { + if (unresolved.empty()) { // No more potentially conflicted members, we're clear. + type = ERROR_TYPE_NONE; + QPID_LOG(debug, cluster << " error " << frameSeq << " resolved."); + } + else + QPID_LOG(debug, cluster << " error " << frameSeq + << " must be resolved with " << unresolved); +} + +EventFrame ErrorCheck::getNext() { + assert(canProcess()); + EventFrame e(frames.front()); + frames.pop_front(); + return e; +} + +void ErrorCheck::respondNone(const MemberId& from, uint8_t type, framing::SequenceNumber frameSeq) { + // Don't respond to non-errors or to my own errors. + if (type == ERROR_TYPE_NONE || from == cluster.getId()) + return; + QPID_LOG(debug, cluster << " error " << frameSeq << " did not occur locally."); + mcast.mcastControl( + ClusterErrorCheckBody(ProtocolVersion(), ERROR_TYPE_NONE, frameSeq), + cluster.getId() + ); +} + +}} // namespace qpid::cluster diff --git a/qpid/cpp/src/qpid/cluster/ErrorCheck.h b/qpid/cpp/src/qpid/cluster/ErrorCheck.h new file mode 100644 index 0000000000..a417b2ec25 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/ErrorCheck.h @@ -0,0 +1,90 @@ +#ifndef QPID_CLUSTER_ERRORCHECK_H +#define QPID_CLUSTER_ERRORCHECK_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/cluster/MemberSet.h" +#include "qpid/cluster/Multicaster.h" +#include "qpid/framing/enum.h" +#include "qpid/framing/SequenceNumber.h" +#include <boost/function.hpp> +#include <deque> +#include <set> + +namespace qpid { +namespace cluster { + +struct EventFrame; +class Cluster; +class Multicaster; +class Connection; + +/** + * Error checking logic. + * + * When an error occurs queue up frames until we can determine if all + * nodes experienced the error. If not, we shut down. + */ +class ErrorCheck +{ + public: + typedef framing::cluster::ErrorType ErrorType; + typedef framing::SequenceNumber SequenceNumber; + + ErrorCheck(Cluster&); + + /** A local error has occured */ + void error(Connection&, ErrorType, SequenceNumber frameSeq, const MemberSet&, + const std::string& msg); + + /** Called when a frame is delivered */ + void delivered(const EventFrame&); + + /**@pre canProcess **/ + EventFrame getNext(); + + bool canProcess() const { return type == NONE && !frames.empty(); } + + bool isUnresolved() const { return type != NONE; } + + /** Respond to an error check saying we had no error. */ + void respondNone(const MemberId&, uint8_t type, SequenceNumber frameSeq); + + private: + static const ErrorType NONE = framing::cluster::ERROR_TYPE_NONE; + typedef std::deque<EventFrame> FrameQueue; + FrameQueue::iterator review(const FrameQueue::iterator&); + void checkResolved(); + + Cluster& cluster; + Multicaster& mcast; + FrameQueue frames; + MemberSet unresolved; + SequenceNumber frameSeq; + ErrorType type; + Connection* connection; + std::string message; +}; + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_ERRORCHECK_H*/ diff --git a/qpid/cpp/src/qpid/cluster/Event.cpp b/qpid/cpp/src/qpid/cluster/Event.cpp new file mode 100644 index 0000000000..da2bc89d8c --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/Event.cpp @@ -0,0 +1,134 @@ +/* + * + * 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/cluster/types.h" +#include "qpid/cluster/Event.h" +#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> +#include <algorithm> + +namespace qpid { +namespace cluster { + +using framing::Buffer; +using framing::AMQFrame; + +const size_t EventHeader::HEADER_SIZE = + sizeof(uint8_t) + // type + sizeof(uint64_t) + // connection pointer only, CPG provides member ID. + sizeof(uint32_t) // payload size + ; + +EventHeader::EventHeader(EventType t, const ConnectionId& c, size_t s) + : type(t), connectionId(c), size(s) {} + + +Event::Event() {} + +Event::Event(EventType t, const ConnectionId& c, size_t s) + : EventHeader(t,c,s), store(RefCountedBuffer::create(s+HEADER_SIZE)) +{} + +void EventHeader::decode(const MemberId& m, framing::Buffer& buf) { + QPID_ASSERT(buf.available() >= HEADER_SIZE); + type = (EventType)buf.getOctet(); + QPID_ASSERT(type == DATA || type == CONTROL); + connectionId = ConnectionId(m, buf.getLongLong()); + size = buf.getLong(); +} + +Event Event::decodeCopy(const MemberId& m, framing::Buffer& buf) { + Event e; + e.decode(m, buf); // Header + QPID_ASSERT(buf.available() >= e.size); + e.store = RefCountedBuffer::create(e.size + HEADER_SIZE); + memcpy(e.getData(), buf.getPointer() + buf.getPosition(), e.size); + return e; +} + +Event Event::control(const framing::AMQFrame& f, const ConnectionId& cid) { + Event e(CONTROL, cid, f.encodedSize()); + Buffer buf(e); + f.encode(buf); + return e; +} + +Event Event::control(const framing::AMQBody& body, const ConnectionId& cid) { + return control(framing::AMQFrame(body), cid); +} + +iovec Event::toIovec() const { + encodeHeader(); + iovec iov = { const_cast<char*>(getStore()), getStoreSize() }; + return iov; +} + +void EventHeader::encode(Buffer& b) const { + b.putOctet(type); + b.putLongLong(connectionId.getNumber()); + b.putLong(size); +} + +// Encode my header in my buffer. +void Event::encodeHeader () const { + Buffer b(const_cast<char*>(getStore()), HEADER_SIZE); + encode(b); + assert(b.getPosition() == HEADER_SIZE); +} + +Event::operator Buffer() const { + return Buffer(const_cast<char*>(getData()), getSize()); +} + +const AMQFrame& Event::getFrame() const { + assert(type == CONTROL); + if (!frame.getBody()) { + Buffer buf(*this); + QPID_ASSERT(frame.decode(buf)); + } + return frame; +} + +static const char* EVENT_TYPE_NAMES[] = { "data", "control" }; + +std::ostream& operator<< (std::ostream& o, EventType t) { + return o << EVENT_TYPE_NAMES[t]; +} + +std::ostream& operator<< (std::ostream& o, const EventHeader& e) { + return o << "Event[" << e.getConnectionId() << " " << e.getType() + << " " << e.getSize() << " bytes]"; +} + +std::ostream& operator<< (std::ostream& o, const Event& e) { + o << "Event[" << e.getConnectionId() << " "; + if (e.getType() == CONTROL) + o << e.getFrame(); + else + o << " data " << e.getSize() << " bytes"; + return o << "]"; +} + +}} // namespace qpid::cluster diff --git a/qpid/cpp/src/qpid/cluster/Event.h b/qpid/cpp/src/qpid/cluster/Event.h new file mode 100644 index 0000000000..13283edff7 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/Event.h @@ -0,0 +1,116 @@ +#ifndef QPID_CLUSTER_EVENT_H +#define QPID_CLUSTER_EVENT_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/cluster/types.h" +#include "qpid/BufferRef.h" +#include "qpid/framing/AMQFrame.h" +#include <sys/uio.h> // For iovec +#include <iosfwd> + +#include "qpid/cluster/types.h" + +namespace qpid { + +namespace framing { +class AMQBody; +class AMQFrame; +class Buffer; +} + +namespace cluster { + +/** Header data for a multicast event */ +class EventHeader { + public: + EventHeader(EventType t=DATA, const ConnectionId& c=ConnectionId(), size_t size=0); + void decode(const MemberId& m, framing::Buffer&); + void encode(framing::Buffer&) const; + + EventType getType() const { return type; } + ConnectionId getConnectionId() const { return connectionId; } + MemberId getMemberId() const { return connectionId.getMember(); } + + /** Size of payload data, excluding header. */ + size_t getSize() const { return size; } + /** Size of header + payload. */ + size_t getStoreSize() const { return size + HEADER_SIZE; } + + bool isCluster() const { return connectionId.getNumber() == 0; } + bool isConnection() const { return connectionId.getNumber() != 0; } + bool isControl() const { return type == CONTROL; } + + protected: + static const size_t HEADER_SIZE; + + EventType type; + ConnectionId connectionId; + size_t size; +}; + +/** + * Events are sent to/received from the cluster. + * Refcounted so they can be stored on queues. + */ +class Event : public EventHeader { + public: + Event(); + /** Create an event with a buffer that can hold size bytes plus an event header. */ + Event(EventType t, const ConnectionId& c, size_t); + + /** Create an event copied from delivered data. */ + static Event decodeCopy(const MemberId& m, framing::Buffer&); + + /** Create a control event. */ + static Event control(const framing::AMQBody&, const ConnectionId&); + + /** Create a control event. */ + static Event control(const framing::AMQFrame&, const ConnectionId&); + + // Data excluding header. + char* getData() { return store.begin() + HEADER_SIZE; } + const char* getData() const { return store.begin() + HEADER_SIZE; } + + // Store including header + char* getStore() { return store.begin(); } + const char* getStore() const { return store.begin(); } + + const framing::AMQFrame& getFrame() const; + + operator framing::Buffer() const; + + iovec toIovec() const; + + private: + void encodeHeader() const; + + BufferRef store; + mutable framing::AMQFrame frame; +}; + +std::ostream& operator << (std::ostream&, const Event&); +std::ostream& operator << (std::ostream&, const EventHeader&); + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_EVENT_H*/ diff --git a/qpid/cpp/src/qpid/cluster/EventFrame.cpp b/qpid/cpp/src/qpid/cluster/EventFrame.cpp new file mode 100644 index 0000000000..5fbe1fe57c --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/EventFrame.cpp @@ -0,0 +1,41 @@ +/* + * + * 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/cluster/EventFrame.h" +#include "qpid/cluster/Connection.h" + +namespace qpid { +namespace cluster { + +EventFrame::EventFrame() {} + +EventFrame::EventFrame(const EventHeader& e, const framing::AMQFrame& f, int rc) + : connectionId(e.getConnectionId()), frame(f), readCredit(rc), type(e.getType()) +{} + +std::ostream& operator<<(std::ostream& o, const EventFrame& e) { + if (e.frame.getBody()) o << e.frame; + else o << "null-frame"; + o << " " << e.type << " " << e.connectionId; + if (e.readCredit) o << " read-credit=" << e.readCredit; + return o; +} + +}} // namespace qpid::cluster diff --git a/qpid/cpp/src/qpid/cluster/EventFrame.h b/qpid/cpp/src/qpid/cluster/EventFrame.h new file mode 100644 index 0000000000..6b702a9bf8 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/EventFrame.h @@ -0,0 +1,60 @@ +#ifndef QPID_CLUSTER_EVENTFRAME_H +#define QPID_CLUSTER_EVENTFRAME_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/cluster/types.h" +#include "qpid/cluster/Event.h" +#include "qpid/framing/AMQFrame.h" +#include <boost/intrusive_ptr.hpp> +#include <iosfwd> + +namespace qpid { +namespace cluster { + +/** + * A frame decoded from an Event. + */ +struct EventFrame +{ + public: + EventFrame(); + + EventFrame(const EventHeader& e, const framing::AMQFrame& f, int rc=0); + + bool isCluster() const { return connectionId.getNumber() == 0; } + bool isConnection() const { return connectionId.getNumber() != 0; } + bool isLastInEvent() const { return readCredit; } + MemberId getMemberId() const { return connectionId.getMember(); } + + + ConnectionId connectionId; + framing::AMQFrame frame; + int readCredit; ///< last frame in an event, give credit when processed. + EventType type; +}; + +std::ostream& operator<<(std::ostream& o, const EventFrame& e); + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_EVENTFRAME_H*/ diff --git a/qpid/cpp/src/qpid/cluster/ExpiryPolicy.cpp b/qpid/cpp/src/qpid/cluster/ExpiryPolicy.cpp new file mode 100644 index 0000000000..d9a7b0122a --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/ExpiryPolicy.cpp @@ -0,0 +1,126 @@ +/* + * + * 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/Message.h" +#include "qpid/cluster/ExpiryPolicy.h" +#include "qpid/cluster/Multicaster.h" +#include "qpid/framing/ClusterMessageExpiredBody.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) {} + +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); +} + +// 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++); + } +} + +// 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/qpid/cpp/src/qpid/cluster/ExpiryPolicy.h b/qpid/cpp/src/qpid/cluster/ExpiryPolicy.h new file mode 100644 index 0000000000..77a656aa68 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/ExpiryPolicy.h @@ -0,0 +1,93 @@ +#ifndef QPID_CLUSTER_EXPIRYPOLICY_H +#define QPID_CLUSTER_EXPIRYPOLICY_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/cluster/types.h" +#include "qpid/broker/ExpiryPolicy.h" +#include "qpid/sys/Mutex.h" +#include <boost/function.hpp> +#include <boost/intrusive_ptr.hpp> +#include <boost/optional.hpp> +#include <map> + +namespace qpid { + +namespace broker { +class Message; +} + +namespace sys { +class Timer; +} + +namespace cluster { +class Multicaster; + +/** + * Cluster expiry policy + */ +class ExpiryPolicy : public broker::ExpiryPolicy +{ + public: + ExpiryPolicy(Multicaster&, const MemberId&, sys::Timer&); + + void willExpire(broker::Message&); + bool hasExpired(broker::Message&); + void forget(broker::Message&); + + // Send expiration notice to cluster. + void sendExpire(uint64_t); + + // 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; +}; + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_EXPIRYPOLICY_H*/ diff --git a/qpid/cpp/src/qpid/cluster/FailoverExchange.cpp b/qpid/cpp/src/qpid/cluster/FailoverExchange.cpp new file mode 100644 index 0000000000..84232dac1b --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/FailoverExchange.cpp @@ -0,0 +1,104 @@ +/* + * + * 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/cluster/FailoverExchange.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/DeliverableMessage.h" +#include "qpid/broker/Queue.h" +#include "qpid/framing/MessageProperties.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/AMQHeaderBody.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/log/Statement.h" +#include "qpid/framing/Array.h" +#include <boost/bind.hpp> +#include <algorithm> + +namespace qpid { +namespace cluster { +using namespace std; + +using namespace broker; +using namespace framing; + +const string FailoverExchange::typeName("amq.failover"); + +FailoverExchange::FailoverExchange(management::Manageable* parent, Broker* b) : Exchange(typeName, parent, b ) { + if (mgmtExchange != 0) + mgmtExchange->set_type(typeName); +} + +void FailoverExchange::setUrls(const vector<Url>& u) { + Lock l(lock); + urls = 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)); +} + +string FailoverExchange::getType() const { return typeName; } + +bool FailoverExchange::bind(Queue::shared_ptr queue, const string&, const framing::FieldTable*) { + Lock l(lock); + sendUpdate(queue); + return queues.insert(queue).second; +} + +bool FailoverExchange::unbind(Queue::shared_ptr queue, const string&, const framing::FieldTable*) { + Lock l(lock); + return queues.erase(queue); +} + +bool FailoverExchange::isBound(Queue::shared_ptr queue, const string* const, const framing::FieldTable*) { + Lock l(lock); + return queues.find(queue) != queues.end(); +} + +void FailoverExchange::route(Deliverable&, const string& , const framing::FieldTable* ) { + QPID_LOG(warning, "Message received by exchange " << typeName << " ignoring"); +} + +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) + array.add(boost::shared_ptr<Str16Value>(new Str16Value(i->str()))); + const ProtocolVersion v; + boost::intrusive_ptr<Message> msg(new Message); + AMQFrame command(MessageTransferBody(v, typeName, 1, 0)); + command.setLastSegment(false); + msg->getFrames().append(command); + AMQHeaderBody header; + header.get<MessageProperties>(true)->setContentLength(0); + header.get<MessageProperties>(true)->getApplicationHeaders().setArray(typeName, array); + AMQFrame headerFrame(header); + headerFrame.setFirstSegment(false); + msg->getFrames().append(headerFrame); + DeliverableMessage(msg).deliverTo(queue); +} + + +}} // namespace cluster diff --git a/qpid/cpp/src/qpid/cluster/FailoverExchange.h b/qpid/cpp/src/qpid/cluster/FailoverExchange.h new file mode 100644 index 0000000000..2e1edfc0ae --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/FailoverExchange.h @@ -0,0 +1,71 @@ +#ifndef QPID_CLUSTER_FAILOVEREXCHANGE_H +#define QPID_CLUSTER_FAILOVEREXCHANGE_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/Exchange.h" +#include "qpid/broker/DeliverableMessage.h" +#include "qpid/Url.h" + +#include <vector> +#include <set> + +namespace qpid { +namespace cluster { + +/** + * Failover exchange provides failover host list, as specified in AMQP 0-10. + */ +class FailoverExchange : public broker::Exchange +{ + public: + static const std::string typeName; + + FailoverExchange(management::Manageable* parent, broker::Broker* b); + + /** Set the URLs but don't send an update.*/ + void setUrls(const std::vector<Url>&); + /** Set the URLs and send an update.*/ + void updateUrls(const std::vector<Url>&); + + // Exchange overrides + std::string getType() const; + bool bind(boost::shared_ptr<broker::Queue> queue, const std::string& routingKey, const framing::FieldTable* args); + bool unbind(boost::shared_ptr<broker::Queue> queue, const std::string& routingKey, const framing::FieldTable* args); + bool isBound(boost::shared_ptr<broker::Queue> queue, const std::string* const routingKey, const framing::FieldTable* const args); + void route(broker::Deliverable& msg, const std::string& routingKey, const framing::FieldTable* args); + + 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; + + sys::Mutex lock; + Urls urls; + Queues queues; + +}; +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_FAILOVEREXCHANGE_H*/ diff --git a/qpid/cpp/src/qpid/cluster/InitialStatusMap.cpp b/qpid/cpp/src/qpid/cluster/InitialStatusMap.cpp new file mode 100644 index 0000000000..c8ecc13f2c --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/InitialStatusMap.cpp @@ -0,0 +1,226 @@ +/* + * + * 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 "InitialStatusMap.h" +#include "StoreStatus.h" +#include "qpid/log/Statement.h" +#include <algorithm> +#include <vector> +#include <boost/bind.hpp> + +namespace qpid { +namespace cluster { + +using namespace std; +using namespace boost; +using namespace framing::cluster; +using namespace framing; + +InitialStatusMap::InitialStatusMap(const MemberId& self_, size_t size_) + : self(self_), completed(), resendNeeded(), size(size_) +{} + +void InitialStatusMap::configChange(const MemberSet& members) { + resendNeeded = false; + bool wasComplete = isComplete(); + if (firstConfig.empty()) firstConfig = members; + MemberSet::const_iterator i = members.begin(); + Map::iterator j = map.begin(); + while (i != members.end() || j != map.end()) { + if (i == members.end()) { // j not in members, member left + firstConfig.erase(j->first); + Map::iterator k = j++; + map.erase(k); + } + else if (j == map.end()) { // i not in map, member joined + resendNeeded = true; + map[*i] = optional<Status>(); + ++i; + } + else if (*i < j->first) { // i not in map, member joined + resendNeeded = true; + map[*i] = optional<Status>(); + ++i; + } + else if (*i > j->first) { // j not in members, member left + firstConfig.erase(j->first); + Map::iterator k = j++; + map.erase(k); + } + else { + i++; j++; + } + } + if (resendNeeded) { // Clear all status + for (Map::iterator i = map.begin(); i != map.end(); ++i) + i->second = optional<Status>(); + } + completed = isComplete() && !wasComplete; // Set completed on the transition. +} + +void InitialStatusMap::received(const MemberId& m, const Status& s){ + bool wasComplete = isComplete(); + map[m] = s; + completed = isComplete() && !wasComplete; // Set completed on the transition. +} + +bool InitialStatusMap::notInitialized(const Map::value_type& v) { + return !v.second; +} + +bool InitialStatusMap::isComplete() const { + return !map.empty() && find_if(map.begin(), map.end(), ¬Initialized) == map.end(); +} + +bool InitialStatusMap::transitionToComplete() { + return completed; +} + +bool InitialStatusMap::isResendNeeded() { + bool ret = resendNeeded; + resendNeeded = false; + return ret; +} + +bool InitialStatusMap::isActiveEntry(const Map::value_type& v) { + return v.second && v.second->getActive(); +} + +bool InitialStatusMap::hasStore(const Map::value_type& v) { + return v.second && + (v.second->getStoreState() == STORE_STATE_CLEAN_STORE || + v.second->getStoreState() == STORE_STATE_DIRTY_STORE); +} + +bool InitialStatusMap::isActive() { + assert(isComplete()); + return (find_if(map.begin(), map.end(), &isActiveEntry) != map.end()); +} + +bool InitialStatusMap::isUpdateNeeded() { + assert(isComplete()); + // We need an update if there are any active members. + if (isActive()) return true; + + // Otherwise it depends on store status, get my own status: + Map::iterator me = map.find(self); + assert(me != map.end()); + assert(me->second); + switch (me->second->getStoreState()) { + case STORE_STATE_NO_STORE: + case STORE_STATE_EMPTY_STORE: + // If anybody has a store then we need an update. + return find_if(map.begin(), map.end(), &hasStore) != map.end(); + case STORE_STATE_DIRTY_STORE: return true; + case STORE_STATE_CLEAN_STORE: return false; // Use our own store + } + return false; +} + +MemberSet InitialStatusMap::getElders() const { + assert(isComplete()); + MemberSet elders; + for (MemberSet::const_iterator i = firstConfig.begin(); i != firstConfig.end(); ++i) { + // *i is in my first config, so a potential elder. + if (*i == self) continue; // Not my own elder + Map::const_iterator j = map.find(*i); + assert(j != map.end()); + assert(j->second); + const Status& s = *j->second; + // If I'm not in i's first config then i is older than me. + // Otherwise we were born in the same configuration so use + // member ID to break the tie. + MemberSet iFirstConfig = decodeMemberSet(s.getFirstConfig()); + if (iFirstConfig.find(self) == iFirstConfig.end() || *i > self) + elders.insert(*i); + } + return elders; +} + +// Get cluster ID from an active member or the youngest newcomer. +Uuid InitialStatusMap::getClusterId() { + assert(isComplete()); + assert(!map.empty()); + Map::iterator i = find_if(map.begin(), map.end(), &isActiveEntry); + if (i != map.end()) + return i->second->getClusterId(); // An active member + else + return map.begin()->second->getClusterId(); // Youngest newcomer in node-id order +} + +void checkId(Uuid& expect, const Uuid& actual, const string& msg) { + if (!expect) expect = actual; + assert(expect); + if (expect != actual) + throw Exception(msg); +} + +void InitialStatusMap::checkConsistent() { + assert(isComplete()); + int clean = 0; + int dirty = 0; + int empty = 0; + int none = 0; + int active = 0; + Uuid clusterId; + Uuid shutdownId; + + bool initialCluster = !isActive(); + for (Map::iterator i = map.begin(); i != map.end(); ++i) { + assert(i->second); + if (i->second->getActive()) ++active; + switch (i->second->getStoreState()) { + case STORE_STATE_NO_STORE: ++none; break; + case STORE_STATE_EMPTY_STORE: ++empty; break; + case STORE_STATE_DIRTY_STORE: + ++dirty; + checkId(clusterId, i->second->getClusterId(), + "Cluster-ID mismatch. Stores belong to different clusters."); + break; + case STORE_STATE_CLEAN_STORE: + ++clean; + checkId(clusterId, i->second->getClusterId(), + "Cluster-ID mismatch. Stores belong to different clusters."); + // Only need shutdownId to match if we are in an initially forming cluster. + if (initialCluster) + checkId(shutdownId, i->second->getShutdownId(), + "Shutdown-ID mismatch. Stores were not shut down together"); + break; + } + } + // Can't mix transient and persistent members. + if (none && (clean+dirty+empty)) + throw Exception("Mixing transient and persistent brokers in a cluster"); + + if (map.size() >= size) { + // All initial members are present. If there are no active + // members and there are dirty stores there must be at least + // one clean store. + if (!active && dirty && !clean) + throw Exception("Cannot recover, no clean store."); + } +} + +std::string InitialStatusMap::getFirstConfigStr() const { + assert(!firstConfig.empty()); + return encodeMemberSet(firstConfig); +} + +}} // namespace qpid::cluster diff --git a/qpid/cpp/src/qpid/cluster/InitialStatusMap.h b/qpid/cpp/src/qpid/cluster/InitialStatusMap.h new file mode 100644 index 0000000000..a5a600365e --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/InitialStatusMap.h @@ -0,0 +1,91 @@ +#ifndef QPID_CLUSTER_INITIALSTATUSMAP_H +#define QPID_CLUSTER_INITIALSTATUSMAP_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 "MemberSet.h" +#include <qpid/framing/ClusterInitialStatusBody.h> +#include <boost/optional.hpp> + +namespace qpid { +namespace cluster { + +/** + * Track status of cluster members during initialization. + * + * When a new member joins the CPG cluster, all members send an initial-status + * control. This map tracks those controls and provides data to make descisions + * about joining the cluster. + * + */ +class InitialStatusMap +{ + public: + typedef framing::ClusterInitialStatusBody Status; + + InitialStatusMap(const MemberId& self, size_t size); + /** Process a config change. May make isResendNeeded() true. */ + void configChange(const MemberSet& newConfig); + /** @return true if we need to re-send status */ + bool isResendNeeded(); + + /** Process received status */ + void received(const MemberId&, const Status& is); + + /**@return true if the map has an entry for all current cluster members. */ + bool isComplete() const; + + size_t getActualSize() const { return map.size(); } + size_t getRequiredSize() const { return size; } + + /**@return true if the map was completed by the last config change or received. */ + bool transitionToComplete(); + /**@pre isComplete(). @return this node's elders */ + MemberSet getElders() const; + /**@pre isComplete(). @return True if there are active members of the cluster. */ + bool isActive(); + /**@pre isComplete(). @return True if we need to request an update. */ + bool isUpdateNeeded(); + /**@pre isComplete(). @return Cluster-wide cluster ID. */ + framing::Uuid getClusterId(); + /**@pre isComplete(). @throw Exception if there are any inconsistencies. */ + void checkConsistent(); + + /** Get first config-change for this member, encoded as a string. + *@pre configChange has been called at least once. + */ + std::string getFirstConfigStr() const; + private: + typedef std::map<MemberId, boost::optional<Status> > Map; + static bool notInitialized(const Map::value_type&); + static bool isActiveEntry(const Map::value_type&); + static bool hasStore(const Map::value_type&); + + Map map; + MemberSet firstConfig; + MemberId self; + bool completed, resendNeeded; + size_t size; +}; +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_INITIALSTATUSMAP_H*/ diff --git a/qpid/cpp/src/qpid/cluster/LockedConnectionMap.h b/qpid/cpp/src/qpid/cluster/LockedConnectionMap.h new file mode 100644 index 0000000000..ac744d4f94 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/LockedConnectionMap.h @@ -0,0 +1,65 @@ +#ifndef QPID_CLUSTER_LOCKEDCONNECTIONMAP_H +#define QPID_CLUSTER_LOCKEDCONNECTIONMAP_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/cluster/types.h" +#include "qpid/sys/Mutex.h" +#include "qpid/cluster/Connection.h" + +namespace qpid { +namespace cluster { + +/** + * Thread safe map of connections. + */ +class LockedConnectionMap +{ + public: + void insert(const ConnectionPtr& c) { + sys::Mutex::ScopedLock l(lock); + assert(map.find(c->getId()) == map.end()); + map[c->getId()] = c; + } + + ConnectionPtr getErase(const ConnectionId& c) { + sys::Mutex::ScopedLock l(lock); + Map::iterator i = map.find(c); + if (i != map.end()) { + ConnectionPtr cp = i->second; + map.erase(i); + return cp; + } + else + return 0; + } + + void clear() { sys::Mutex::ScopedLock l(lock); map.clear(); } + + private: + typedef std::map<ConnectionId, ConnectionPtr> Map; + mutable sys::Mutex lock; + Map map; +}; +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_LOCKEDCONNECTIONMAP_H*/ diff --git a/qpid/cpp/src/qpid/cluster/McastFrameHandler.h b/qpid/cpp/src/qpid/cluster/McastFrameHandler.h new file mode 100644 index 0000000000..17e4c2e9f0 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/McastFrameHandler.h @@ -0,0 +1,46 @@ +#ifndef QPID_CLUSTER_MCASTFRAMEHANDLER_H +#define QPID_CLUSTER_MCASTFRAMEHANDLER_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/cluster/types.h" +#include "qpid/cluster/Multicaster.h" +#include "qpid/framing/FrameHandler.h" + +namespace qpid { +namespace cluster { + +/** + * A frame handler that multicasts frames as CONTROL events. + */ +class McastFrameHandler : public framing::FrameHandler +{ + public: + McastFrameHandler(Multicaster& m, const ConnectionId& cid) : mcast(m), connection(cid) {} + void handle(framing::AMQFrame& frame) { mcast.mcastControl(frame, connection); } + private: + Multicaster& mcast; + ConnectionId connection; +}; +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_MCASTFRAMEHANDLER_H*/ diff --git a/qpid/cpp/src/qpid/cluster/MemberSet.cpp b/qpid/cpp/src/qpid/cluster/MemberSet.cpp new file mode 100644 index 0000000000..97748947b3 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/MemberSet.cpp @@ -0,0 +1,60 @@ +/* + * + * 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 "MemberSet.h" +#include <ostream> +#include <iterator> +#include <algorithm> + +namespace qpid { +namespace cluster { + +std::string encodeMemberSet(const MemberSet& m) { + std::string addresses; + for (MemberSet::const_iterator i = m.begin(); i != m.end(); ++i) + addresses.append(i->str()); + return addresses; +} + +MemberSet decodeMemberSet(const std::string& s) { + MemberSet set; + for (std::string::const_iterator i = s.begin(); i < s.end(); i += 8) { + assert(size_t(i-s.begin())+8 <= s.size()); + set.insert(MemberId(std::string(i, i+8))); + } + return set; +} + +MemberSet intersection(const MemberSet& a, const MemberSet& b) +{ + MemberSet intersection; + std::set_intersection(a.begin(), a.end(), + b.begin(), b.end(), + std::inserter(intersection, intersection.begin())); + return intersection; + +} + +std::ostream& operator<<(std::ostream& o, const MemberSet& ms) { + copy(ms.begin(), ms.end(), std::ostream_iterator<MemberId>(o, " ")); + return o; +} + +}} // namespace qpid::cluster diff --git a/qpid/cpp/src/qpid/cluster/MemberSet.h b/qpid/cpp/src/qpid/cluster/MemberSet.h new file mode 100644 index 0000000000..7c97145dc1 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/MemberSet.h @@ -0,0 +1,45 @@ +#ifndef QPID_CLUSTER_MEMBERSET_H +#define QPID_CLUSTER_MEMBERSET_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 "types.h" +#include <set> +#include <iosfwd> + +namespace qpid { +namespace cluster { + +typedef std::set<MemberId> MemberSet; + +std::string encodeMemberSet(const MemberSet&); + +MemberSet decodeMemberSet(const std::string&); + +MemberSet intersection(const MemberSet& a, const MemberSet& b); + +std::ostream& operator<<(std::ostream& o, const MemberSet& ms); + + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_MEMBERSET_H*/ diff --git a/qpid/cpp/src/qpid/cluster/Multicaster.cpp b/qpid/cpp/src/qpid/cluster/Multicaster.cpp new file mode 100644 index 0000000000..8916de9628 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/Multicaster.cpp @@ -0,0 +1,104 @@ +/* + * + * 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/cluster/Multicaster.h" +#include "qpid/cluster/Cpg.h" +#include "qpid/log/Statement.h" +#include "qpid/framing/AMQBody.h" +#include "qpid/framing/AMQFrame.h" + +namespace qpid { +namespace cluster { + +Multicaster::Multicaster(Cpg& cpg_, + const boost::shared_ptr<sys::Poller>& poller, + boost::function<void()> onError_) : + onError(onError_), cpg(cpg_), + queue(boost::bind(&Multicaster::sendMcast, this, _1), poller), + ready(false), bypass(true) +{} + +void Multicaster::mcastControl(const framing::AMQBody& body, const ConnectionId& id) { + mcast(Event::control(body, id)); +} + +void Multicaster::mcastControl(const framing::AMQFrame& frame, const ConnectionId& id) { + mcast(Event::control(frame, id)); +} + +void Multicaster::mcastBuffer(const char* data, size_t size, const ConnectionId& id) { + Event e(DATA, id, size); + memcpy(e.getData(), data, size); + mcast(e); +} + +void Multicaster::mcast(const Event& e) { + { + sys::Mutex::ScopedLock l(lock); + if (!ready && e.isConnection()) { + holdingQueue.push_back(e); + return; + } + } + QPID_LOG(trace, "MCAST " << e); + if (bypass) { // direct, don't queue + iovec iov = e.toIovec(); + while (!cpg.mcast(&iov, 1)) + ; + } + else + queue.push(e); +} + +Multicaster::PollableEventQueue::Batch::const_iterator Multicaster::sendMcast(const PollableEventQueue::Batch& values) { + try { + PollableEventQueue::Batch::const_iterator i = values.begin(); + while( i != values.end()) { + iovec iov = i->toIovec(); + if (!cpg.mcast(&iov, 1)) { + // cpg didn't send because of CPG flow control. + break; + } + ++i; + } + return i; + } + catch (const std::exception& e) { + QPID_LOG(critical, "Multicast error: " << e.what()); + queue.stop(); + onError(); + return values.end(); + } +} + +void Multicaster::start() { + queue.start(); + bypass = false; +} + +void Multicaster::setReady() { + sys::Mutex::ScopedLock l(lock); + ready = true; + std::for_each(holdingQueue.begin(), holdingQueue.end(), boost::bind(&Multicaster::mcast, this, _1)); + holdingQueue.clear(); +} + +}} // namespace qpid::cluster diff --git a/qpid/cpp/src/qpid/cluster/Multicaster.h b/qpid/cpp/src/qpid/cluster/Multicaster.h new file mode 100644 index 0000000000..f70bd5ca31 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/Multicaster.h @@ -0,0 +1,92 @@ +#ifndef QPID_CLUSTER_MULTICASTER_H +#define QPID_CLUSTER_MULTICASTER_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/cluster/types.h" +#include "qpid/cluster/Event.h" +#include "qpid/sys/PollableQueue.h" +#include "qpid/sys/Mutex.h" +#include <boost/shared_ptr.hpp> +#include <deque> + +namespace qpid { + +namespace sys { +class Poller; +} + +namespace cluster { + +class Cpg; + +/** + * Multicast to the cluster. Shared, thread safe object. + * + * holding mode: Hold connection events for later multicast. Cluster + * events are never held. Used during PRE_INIT/INIT state when we + * want to hold any connection traffic till we are read in the + * cluster. + * + * bypass mode: Multicast cluster events directly in the calling + * thread. This mode is used by cluster in PRE_INIT state the poller + * is not yet be active. + * + * Multicaster is created in bypass+holding mode, they are disabled by + * start and setReady respectively. + */ +class Multicaster +{ + public: + /** Starts in initializing mode. */ + Multicaster(Cpg& cpg_, + const boost::shared_ptr<sys::Poller>&, + boost::function<void()> onError + ); + void mcastControl(const framing::AMQBody& controlBody, const ConnectionId&); + void mcastControl(const framing::AMQFrame& controlFrame, const ConnectionId&); + void mcastBuffer(const char*, size_t, const ConnectionId&); + void mcast(const Event& e); + + /** Start the pollable queue, turn off bypass mode. */ + void start(); + /** Switch to ready mode, release held messages. */ + void setReady(); + + private: + typedef sys::PollableQueue<Event> PollableEventQueue; + typedef std::deque<Event> PlainEventQueue; + + PollableEventQueue::Batch::const_iterator sendMcast(const PollableEventQueue::Batch& ); + + sys::Mutex lock; + boost::function<void()> onError; + Cpg& cpg; + PollableEventQueue queue; + bool ready; + PlainEventQueue holdingQueue; + std::vector<struct ::iovec> ioVector; + bool bypass; +}; +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_MULTICASTER_H*/ diff --git a/qpid/cpp/src/qpid/cluster/NoOpConnectionOutputHandler.h b/qpid/cpp/src/qpid/cluster/NoOpConnectionOutputHandler.h new file mode 100644 index 0000000000..566a82476e --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/NoOpConnectionOutputHandler.h @@ -0,0 +1,47 @@ +#ifndef QPID_CLUSTER_NOOPCONNECTIONOUTPUTHANDLER_H +#define QPID_CLUSTER_NOOPCONNECTIONOUTPUTHANDLER_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/ConnectionOutputHandler.h> + +namespace qpid { + +namespace framing { class AMQFrame; } + +namespace cluster { + +/** + * Output handler shadow connections, simply discards frames. + */ +class NoOpConnectionOutputHandler : public sys::ConnectionOutputHandler +{ + public: + virtual void send(framing::AMQFrame&) {} + virtual void close() {} + virtual void abort() {} + virtual void activateOutput() {} + virtual void giveReadCredit(int32_t) {} +}; + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_NOOPCONNECTIONOUTPUTHANDLER_H*/ diff --git a/qpid/cpp/src/qpid/cluster/Numbering.h b/qpid/cpp/src/qpid/cluster/Numbering.h new file mode 100644 index 0000000000..99e152c212 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/Numbering.h @@ -0,0 +1,68 @@ +#ifndef QPID_CLUSTER_NUMBERING_H +#define QPID_CLUSTER_NUMBERING_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 <map> +#include <vector> + +namespace qpid { +namespace cluster { + +/** + * A set of numbered T, with two way mapping number->T T->number + * Used to construct numberings of objects by code sending and receiving updates. + */ +template <class T> class Numbering +{ + public: + size_t size() const { return byNumber.size(); } + + size_t add(const T& t) { + size_t n = (*this)[t]; // Already in the set? + if (n == size()) { + byObject[t] = n; + byNumber.push_back(t); + } + return n; + } + + void clear() { byObject.clear(); byNumber.clear(); } + + /**@return object at index n or T() if n > size() */ + T operator[](size_t n) const { return(n < size()) ? byNumber[n] : T(); } + + /**@return index of t or size() if t is not in the map */ + size_t operator[](const T& t) const { + typename Map::const_iterator i = byObject.find(t); + return (i != byObject.end()) ? i->second : size(); + } + + private: + typedef std::map<T, size_t> Map; + Map byObject; + std::vector<T> byNumber; +}; + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_NUMBERING_H*/ diff --git a/qpid/cpp/src/qpid/cluster/OutputInterceptor.cpp b/qpid/cpp/src/qpid/cluster/OutputInterceptor.cpp new file mode 100644 index 0000000000..4bf03eefa2 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/OutputInterceptor.cpp @@ -0,0 +1,125 @@ +/* + * + * 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/cluster/OutputInterceptor.h" +#include "qpid/cluster/Connection.h" +#include "qpid/cluster/Cluster.h" +#include "qpid/framing/ClusterConnectionDeliverDoOutputBody.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/log/Statement.h" +#include <boost/current_function.hpp> + + +namespace qpid { +namespace cluster { + +using namespace framing; +using namespace std; + +NoOpConnectionOutputHandler OutputInterceptor::discardHandler; + +OutputInterceptor::OutputInterceptor(Connection& p, sys::ConnectionOutputHandler& h) + : parent(p), closing(false), next(&h), sendMax(2048), sent(0), sentDoOutput(false) +{} + +void OutputInterceptor::send(framing::AMQFrame& f) { + sys::Mutex::ScopedLock l(lock); + next->send(f); +} + +void OutputInterceptor::activateOutput() { + sys::Mutex::ScopedLock l(lock); + if (parent.isCatchUp()) + next->activateOutput(); + else + sendDoOutput(sendMax, l); +} + +void OutputInterceptor::abort() { + sys::Mutex::ScopedLock l(lock); + if (parent.isLocal()) { + next->abort(); + } +} + +void OutputInterceptor::giveReadCredit(int32_t credit) { + sys::Mutex::ScopedLock l(lock); + next->giveReadCredit(credit); +} + +// Called in write thread when the IO layer has no more data to write. +// 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. +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 = next->getBuffered(); + if (buffered == 0 && sent == sendMax) // Could have sent more, increase the limit. + newLimit = sendMax*2; + else if (buffered > 0 && sent > 1) // Data left unsent, reduce the limit. + newLimit = (sendMax + sent) / 2; + } + sent = 0; + while (sent < limit) { + { + sys::Mutex::ScopedUnlock u(lock); + if (!parent.getBrokerConnection()->doOutput()) break; + } + ++sent; + } + if (sent == limit) sendDoOutput(newLimit, l); +} + +void OutputInterceptor::sendDoOutput(size_t newLimit, const sys::Mutex::ScopedLock&) { + if (parent.isLocal() && !sentDoOutput && !closing) { + sentDoOutput = true; + parent.getCluster().getMulticast().mcastControl( + ClusterConnectionDeliverDoOutputBody(ProtocolVersion(), newLimit), + parent.getId()); + } +} + +// Called in connection thread when local connection closes. +void OutputInterceptor::closeOutput() { + sys::Mutex::ScopedLock l(lock); + closing = true; + next = &discardHandler; +} + +void OutputInterceptor::close() { + sys::Mutex::ScopedLock l(lock); + next->close(); +} + +size_t OutputInterceptor::getBuffered() const { + sys::Mutex::ScopedLock l(lock); + return next->getBuffered(); +} + +}} // namespace qpid::cluster diff --git a/qpid/cpp/src/qpid/cluster/OutputInterceptor.h b/qpid/cpp/src/qpid/cluster/OutputInterceptor.h new file mode 100644 index 0000000000..3abf5273a0 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/OutputInterceptor.h @@ -0,0 +1,79 @@ +#ifndef QPID_CLUSTER_OUTPUTINTERCEPTOR_H +#define QPID_CLUSTER_OUTPUTINTERCEPTOR_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/cluster/NoOpConnectionOutputHandler.h" +#include "qpid/sys/ConnectionOutputHandler.h" +#include "qpid/sys/Mutex.h" +#include "qpid/broker/ConnectionFactory.h" +#include <boost/function.hpp> + +namespace qpid { +namespace framing { class AMQFrame; } +namespace cluster { + +class Connection; + +/** + * Interceptor for connection OutputHandler, manages outgoing message replication. + */ +class OutputInterceptor : public sys::ConnectionOutputHandler { + public: + OutputInterceptor(cluster::Connection& p, sys::ConnectionOutputHandler& h); + + // sys::ConnectionOutputHandler functions + void send(framing::AMQFrame& f); + void abort(); + void activateOutput(); + void giveReadCredit(int32_t); + void close(); + size_t getBuffered() const; + + // Delivery point for doOutput requests. + void deliverDoOutput(uint32_t limit); + // Intercept doOutput requests on Connection. + bool doOutput(); + + void closeOutput(); + + 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, const sys::Mutex::ScopedLock&); + + mutable sys::Mutex lock; + bool closing; + sys::ConnectionOutputHandler* next; + static NoOpConnectionOutputHandler discardHandler; + uint32_t sendMax, sent; + bool sentDoOutput; +}; + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_OUTPUTINTERCEPTOR_H*/ diff --git a/qpid/cpp/src/qpid/cluster/PollableQueue.h b/qpid/cpp/src/qpid/cluster/PollableQueue.h new file mode 100644 index 0000000000..10e2ed6ac3 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/PollableQueue.h @@ -0,0 +1,89 @@ +#ifndef QPID_CLUSTER_POLLABLEQUEUE_H +#define QPID_CLUSTER_POLLABLEQUEUE_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/PollableQueue.h" +#include <qpid/log/Statement.h> + +namespace qpid { +namespace cluster { + +/** + * More convenient version of PollableQueue that handles iterating + * over the batch and error handling. + * + * Constructed in "bypass" mode where items are processed directly + * rather than put on the queue. This is important for the + * PRE_INIT stage when Cluster is pumping CPG dispatch directly + * before the poller has started. + * + * Calling start() starts the pollable queue and disabled bypass mode. + */ +template <class T> class PollableQueue : public sys::PollableQueue<T> { + public: + typedef boost::function<void (const T&)> Callback; + typedef boost::function<void()> ErrorCallback; + + PollableQueue(Callback f, ErrorCallback err, const std::string& msg, + const boost::shared_ptr<sys::Poller>& poller) + : sys::PollableQueue<T>(boost::bind(&PollableQueue<T>::handleBatch, this, _1), + poller), + callback(f), error(err), message(msg), bypass(true) + {} + + typename sys::PollableQueue<T>::Batch::const_iterator + handleBatch(const typename sys::PollableQueue<T>::Batch& values) { + try { + typename sys::PollableQueue<T>::Batch::const_iterator i = values.begin(); + while (i != values.end() && !this->isStopped()) { + callback(*i); + ++i; + } + return i; + } + catch (const std::exception& e) { + QPID_LOG(critical, message << ": " << e.what()); + this->stop(); + error(); + return values.end(); + } + } + + void push(const T& t) { + if (bypass) callback(t); + else sys::PollableQueue<T>::push(t); + } + + void bypassOff() { bypass = false; } + + private: + Callback callback; + ErrorCallback error; + std::string message; + bool bypass; +}; + + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_POLLABLEQUEUE_H*/ diff --git a/qpid/cpp/src/qpid/cluster/PollerDispatch.cpp b/qpid/cpp/src/qpid/cluster/PollerDispatch.cpp new file mode 100644 index 0000000000..b8d94b95a5 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/PollerDispatch.cpp @@ -0,0 +1,69 @@ +/* + * + * 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/cluster/PollerDispatch.h" + +#include "qpid/log/Statement.h" +#include <boost/bind.hpp> + +namespace qpid { +namespace cluster { + +PollerDispatch::PollerDispatch(Cpg& c, boost::shared_ptr<sys::Poller> p, + boost::function<void()> e) + : cpg(c), poller(p), onError(e), + dispatchHandle(cpg, + boost::bind(&PollerDispatch::dispatch, this, _1), // read + 0, // write + boost::bind(&PollerDispatch::disconnect, this, _1) // disconnect + ), + started(false) +{} + +PollerDispatch::~PollerDispatch() { + if (started) + dispatchHandle.stopWatch(); +} + +void PollerDispatch::start() { + dispatchHandle.startWatch(poller); + started = true; +} + +// Entry point: called by IO to dispatch CPG events. +void PollerDispatch::dispatch(sys::DispatchHandle& h) { + try { + cpg.dispatchAll(); + h.rewatch(); + } catch (const std::exception& e) { + QPID_LOG(critical, "Error in cluster dispatch: " << e.what()); + onError(); + } +} + +// Entry point: called if disconnected from CPG. +void PollerDispatch::disconnect(sys::DispatchHandle& ) { + if (!poller->hasShutdown()) { + QPID_LOG(critical, "Disconnected from cluster"); + onError(); + } +} + +}} // namespace qpid::cluster diff --git a/qpid/cpp/src/qpid/cluster/PollerDispatch.h b/qpid/cpp/src/qpid/cluster/PollerDispatch.h new file mode 100644 index 0000000000..63801e0de9 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/PollerDispatch.h @@ -0,0 +1,60 @@ +#ifndef QPID_CLUSTER_POLLERDISPATCH_H +#define QPID_CLUSTER_POLLERDISPATCH_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/cluster/Cpg.h" +#include "qpid/sys/Poller.h" +#include "qpid/sys/DispatchHandle.h" +#include <boost/function.hpp> + +namespace qpid { +namespace cluster { + +/** + * Dispatch CPG events via the poller. + */ +class PollerDispatch { + public: + PollerDispatch(Cpg&, boost::shared_ptr<sys::Poller> poller, + boost::function<void()> onError) ; + + ~PollerDispatch(); + + void start(); + + private: + // Poller callbacks + void dispatch(sys::DispatchHandle&); // Dispatch CPG events. + void disconnect(sys::DispatchHandle&); // CPG was disconnected + + Cpg& cpg; + boost::shared_ptr<sys::Poller> poller; + boost::function<void()> onError; + sys::DispatchHandleRef dispatchHandle; + bool started; + + +}; +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_POLLERDISPATCH_H*/ diff --git a/qpid/cpp/src/qpid/cluster/ProxyInputHandler.h b/qpid/cpp/src/qpid/cluster/ProxyInputHandler.h new file mode 100644 index 0000000000..ad7f2c44bd --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/ProxyInputHandler.h @@ -0,0 +1,56 @@ +#ifndef QPID_CLUSTER_PROXYINPUTHANDLER_H +#define QPID_CLUSTER_PROXYINPUTHANDLER_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/ConnectionInputHandler.h" +#include <boost/intrusive_ptr.hpp> + +namespace qpid { + +namespace framing { class AMQFrame; } + +namespace cluster { + +/** + * Proxies ConnectionInputHandler functions and ensures target.closed() + * is called, on deletion if not before. + */ +class ProxyInputHandler : public sys::ConnectionInputHandler +{ + public: + ProxyInputHandler(boost::intrusive_ptr<cluster::Connection> t) : target(t) {} + ~ProxyInputHandler() { closed(); } + + void received(framing::AMQFrame& f) { target->received(f); } + void closed() { if (target) target->closed(); target = 0; } + void idleOut() { target->idleOut(); } + void idleIn() { target->idleIn(); } + bool doOutput() { return target->doOutput(); } + + private: + boost::intrusive_ptr<cluster::Connection> target; +}; + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_PROXYINPUTHANDLER_H*/ diff --git a/qpid/cpp/src/qpid/cluster/Quorum.h b/qpid/cpp/src/qpid/cluster/Quorum.h new file mode 100644 index 0000000000..bbfa473f94 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/Quorum.h @@ -0,0 +1,32 @@ +#ifndef QPID_CLUSTER_QUORUM_H +#define QPID_CLUSTER_QUORUM_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 "config.h" + +#if HAVE_LIBCMAN_H +#include "qpid/cluster/Quorum_cman.h" +#else +#include "qpid/cluster/Quorum_null.h" +#endif + +#endif /*!QPID_CLUSTER_QUORUM_H*/ diff --git a/qpid/cpp/src/qpid/cluster/Quorum_cman.cpp b/qpid/cpp/src/qpid/cluster/Quorum_cman.cpp new file mode 100644 index 0000000000..728f824b16 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/Quorum_cman.cpp @@ -0,0 +1,105 @@ +/* + * + * 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/cluster/Quorum_cman.h" +#include "qpid/cluster/Cluster.h" +#include "qpid/log/Statement.h" +#include "qpid/Options.h" +#include "qpid/sys/Time.h" +#include "qpid/sys/posix/PrivatePosix.h" + +namespace qpid { +namespace cluster { + +namespace { + +boost::function<void()> errorFn; + +void cmanCallbackFn(cman_handle_t handle, void */*privdata*/, int reason, int /*arg*/) { + if (reason == CMAN_REASON_STATECHANGE && !cman_is_quorate(handle)) { + QPID_LOG(critical, "Lost contact with cluster quorum."); + if (errorFn) errorFn(); + cman_stop_notification(handle); + } +} +} + +Quorum::Quorum(boost::function<void()> err) : cman(0), cmanFd(0) { + errorFn = err; +} + +Quorum::~Quorum() { + if (dispatchHandle.get()) dispatchHandle->stopWatch(); + dispatchHandle.reset(); + if (cman) cman_finish(cman); +} + +void Quorum::start(boost::shared_ptr<sys::Poller> p) { + poller = p; + QPID_LOG(debug, "Connecting to quorum service."); + cman = cman_init(0); + if (cman == 0) throw ErrnoException("Can't connect to cman service"); + if (!cman_is_quorate(cman)) { + QPID_LOG(notice, "Waiting for cluster quorum."); + while(!cman_is_quorate(cman)) sys::sleep(5); + } + int err = cman_start_notification(cman, cmanCallbackFn); + if (err != 0) throw ErrnoException("Can't register for cman notifications"); + watch(getFd()); +} + +void Quorum::watch(int fd) { + cmanFd = fd; + if (dispatchHandle.get()) dispatchHandle->stopWatch(); + ioHandle.reset(new sys::PosixIOHandle(cmanFd)); + dispatchHandle.reset( + new sys::DispatchHandleRef( + *ioHandle, // This must outlive the dispatchHandleRef + boost::bind(&Quorum::dispatch, this, _1), // read + 0, // write + boost::bind(&Quorum::disconnect, this, _1) // disconnect + )); + dispatchHandle->startWatch(poller); +} + +int Quorum::getFd() { + int fd = cman_get_fd(cman); + if (fd == 0) throw ErrnoException("Can't get cman file descriptor"); + return fd; +} + +void Quorum::dispatch(sys::DispatchHandle&) { + try { + cman_dispatch(cman, CMAN_DISPATCH_ALL); + int fd = getFd(); + if (fd != cmanFd) watch(fd); + } catch (const std::exception& e) { + QPID_LOG(critical, "Error in quorum dispatch: " << e.what()); + errorFn(); + } +} + +void Quorum::disconnect(sys::DispatchHandle&) { + QPID_LOG(critical, "Disconnected from quorum service"); + errorFn(); +} + +}} // namespace qpid::cluster diff --git a/qpid/cpp/src/qpid/cluster/Quorum_cman.h b/qpid/cpp/src/qpid/cluster/Quorum_cman.h new file mode 100644 index 0000000000..98e6baee89 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/Quorum_cman.h @@ -0,0 +1,65 @@ +#ifndef QPID_CLUSTER_QUORUM_CMAN_H +#define QPID_CLUSTER_QUORUM_CMAN_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/DispatchHandle.h> +#include <boost/function.hpp> +#include <boost/shared_ptr.hpp> +#include <memory> + +extern "C" { +#include <libcman.h> +} + +namespace qpid { +namespace sys { +class Poller; +class PosixIOHandle; +} + +namespace cluster { +class Cluster; + +class Quorum { + public: + Quorum(boost::function<void ()> onError); + ~Quorum(); + void start(boost::shared_ptr<sys::Poller>); + + private: + void dispatch(sys::DispatchHandle&); + void disconnect(sys::DispatchHandle&); + int getFd(); + void watch(int fd); + + cman_handle_t cman; + int cmanFd; + std::auto_ptr<sys::PosixIOHandle> ioHandle; + std::auto_ptr<sys::DispatchHandleRef> dispatchHandle; + boost::shared_ptr<sys::Poller> poller; +}; + + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_QUORUM_CMAN_H*/ diff --git a/qpid/cpp/src/qpid/cluster/Quorum_null.h b/qpid/cpp/src/qpid/cluster/Quorum_null.h new file mode 100644 index 0000000000..dc27f0a43b --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/Quorum_null.h @@ -0,0 +1,42 @@ +#ifndef QPID_CLUSTER_QUORUM_NULL_H +#define QPID_CLUSTER_QUORUM_NULL_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 <boost/shared_ptr.hpp> +#include <boost/function.hpp> + +namespace qpid { +namespace cluster { +class Cluster; + +/** Null implementation of quorum. */ + +class Quorum { + public: + Quorum(boost::function<void ()>) {} + void start(boost::shared_ptr<sys::Poller>) {} +}; + +#endif /*!QPID_CLUSTER_QUORUM_NULL_H*/ + +}} // namespace qpid::cluster diff --git a/qpid/cpp/src/qpid/cluster/RetractClient.cpp b/qpid/cpp/src/qpid/cluster/RetractClient.cpp new file mode 100644 index 0000000000..a8c4b0d543 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/RetractClient.cpp @@ -0,0 +1,62 @@ +/* + * + * 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/cluster/RetractClient.h" +#include "qpid/cluster/UpdateClient.h" +#include "qpid/framing/ClusterConnectionRetractOfferBody.h" +#include "qpid/client/ConnectionAccess.h" +#include "qpid/client/ConnectionImpl.h" +#include "qpid/log/Statement.h" + +namespace qpid { +namespace cluster { + +using namespace framing; + +namespace { + +struct AutoClose { + client::Connection& connection; + AutoClose(client::Connection& c) : connection(c) {} + ~AutoClose() { connection.close(); } +}; +} + +RetractClient::RetractClient(const Url& u, const client::ConnectionSettings& cs) + : url(u), connectionSettings(cs) +{} + +RetractClient::~RetractClient() { delete this; } + + +void RetractClient::run() { + try { + client::Connection c = UpdateClient::catchUpConnection(); + c.open(url, connectionSettings); + AutoClose ac(c); + AMQFrame retract((ClusterConnectionRetractOfferBody())); + client::ConnectionAccess::getImpl(c)->expand(retract.encodedSize(), false); + client::ConnectionAccess::getImpl(c)->handle(retract); + } catch (const std::exception& e) { + QPID_LOG(error, " while retracting retract to " << url << ": " << e.what()); + } +} + +}} // namespace qpid::cluster diff --git a/qpid/cpp/src/qpid/cluster/RetractClient.h b/qpid/cpp/src/qpid/cluster/RetractClient.h new file mode 100644 index 0000000000..533fc3f7ef --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/RetractClient.h @@ -0,0 +1,50 @@ +#ifndef QPID_CLUSTER_RETRACTCLIENT_H +#define QPID_CLUSTER_RETRACTCLIENT_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/client/ConnectionSettings.h" +#include "qpid/sys/Runnable.h" +#include "qpid/Url.h" + + +namespace qpid { +namespace cluster { + +/** + * A client that retracts an offer to a remote broker using AMQP. @see UpdateClient + */ +class RetractClient : public sys::Runnable { + public: + + RetractClient(const Url&, const client::ConnectionSettings&); + ~RetractClient(); + void run(); // Will delete this when finished. + + private: + Url url; + client::ConnectionSettings connectionSettings; +}; + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_RETRACTCLIENT_H*/ diff --git a/qpid/cpp/src/qpid/cluster/SecureConnectionFactory.cpp b/qpid/cpp/src/qpid/cluster/SecureConnectionFactory.cpp new file mode 100644 index 0000000000..6ddef66226 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/SecureConnectionFactory.cpp @@ -0,0 +1,73 @@ +/* + * + * 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/cluster/SecureConnectionFactory.h" +#include "qpid/framing/ProtocolVersion.h" +#include "qpid/cluster/ConnectionCodec.h" +#include "qpid/broker/SecureConnection.h" +#include "qpid/sys/SecuritySettings.h" +#include "qpid/log/Statement.h" +#include <memory> + + +namespace qpid { +namespace cluster { + +using framing::ProtocolVersion; +using qpid::sys::SecuritySettings; +using qpid::broker::SecureConnection; + +typedef std::auto_ptr<qpid::broker::SecureConnection> SecureConnectionPtr; +typedef std::auto_ptr<qpid::sys::ConnectionCodec> CodecPtr; + +SecureConnectionFactory::SecureConnectionFactory(CodecFactoryPtr f) : codecFactory(f) { +} + +sys::ConnectionCodec* +SecureConnectionFactory::create(ProtocolVersion v, sys::OutputControl& out, const std::string& id, + const SecuritySettings& external) { + CodecPtr codec(codecFactory->create(v, out, id, external)); + ConnectionCodec* clusterCodec = dynamic_cast<qpid::cluster::ConnectionCodec*>(codec.get()); + if (clusterCodec) { + SecureConnectionPtr sc(new SecureConnection()); + clusterCodec->setSecureConnection(sc.get()); + sc->setCodec(codec); + return sc.release(); + } + return 0; +} + +sys::ConnectionCodec* +SecureConnectionFactory::create(sys::OutputControl& out, const std::string& id, + const SecuritySettings& external) { + // used to create connections from one broker to another + CodecPtr codec(codecFactory->create(out, id, external)); + ConnectionCodec* clusterCodec = dynamic_cast<qpid::cluster::ConnectionCodec*>(codec.get()); + if (clusterCodec) { + SecureConnectionPtr sc(new SecureConnection()); + clusterCodec->setSecureConnection(sc.get()); + sc->setCodec(codec); + return sc.release(); + } + return 0; +} + + +}} // namespace qpid::cluster diff --git a/qpid/cpp/src/qpid/cluster/SecureConnectionFactory.h b/qpid/cpp/src/qpid/cluster/SecureConnectionFactory.h new file mode 100644 index 0000000000..24d1fcfee5 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/SecureConnectionFactory.h @@ -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. + * + */ +#ifndef QPID_CLUSTER_SecureconnectionFactory +#define QPID_CLUSTER_SecureconnectionFactory + +#include "qpid/sys/ConnectionCodec.h" +#include <boost/shared_ptr.hpp> + +namespace qpid { + +namespace broker { + class Broker; +} + +namespace cluster { + +class SecureConnectionFactory : public qpid::sys::ConnectionCodec::Factory +{ + public: + typedef boost::shared_ptr<qpid::sys::ConnectionCodec::Factory> CodecFactoryPtr; + SecureConnectionFactory(CodecFactoryPtr f); + + qpid::sys::ConnectionCodec* create( + framing::ProtocolVersion, qpid::sys::OutputControl&, const std::string& id, + const qpid::sys::SecuritySettings& + ); + + /** Return "preferred" codec for outbound connections. */ + qpid::sys::ConnectionCodec* create( + qpid::sys::OutputControl&, const std::string& id, const qpid::sys::SecuritySettings& + ); + + private: + CodecFactoryPtr codecFactory; +}; + +}} // namespace qpid::cluster + + +#endif // QPID_CLUSTER_SecureconnectionFactory diff --git a/qpid/cpp/src/qpid/cluster/StoreStatus.cpp b/qpid/cpp/src/qpid/cluster/StoreStatus.cpp new file mode 100644 index 0000000000..14c999bb05 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/StoreStatus.cpp @@ -0,0 +1,170 @@ +/* + * + * 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 "StoreStatus.h" +#include "qpid/Exception.h" +#include "qpid/Msg.h" +#include "qpid/log/Statement.h" +#include <boost/filesystem/path.hpp> +#include <boost/filesystem/fstream.hpp> +#include <boost/filesystem/operations.hpp> +#include <boost/scoped_array.hpp> +#include <fstream> +#include <sstream> + +namespace qpid { +namespace cluster { + +using framing::Uuid; +using namespace framing::cluster; +namespace fs=boost::filesystem; +using namespace std; + +StoreStatus::StoreStatus(const std::string& d) + : state(STORE_STATE_NO_STORE), dataDir(d) +{} + +namespace { + +const char* SUBDIR="cluster"; +const char* STORE_STATUS="store.status"; + +string readFile(const fs::path& path) { + fs::ifstream is; + is.exceptions(std::ios::badbit | std::ios::failbit); + is.open(path); + // get length of file: + is.seekg (0, ios::end); + size_t length = is.tellg(); + is.seekg (0, ios::beg); + // load data + boost::scoped_array<char> buffer(new char[length]); + is.read(buffer.get(), length); + is.close(); + return string(buffer.get(), length); +} + +void writeFile(const fs::path& path, const string& data) { + fs::ofstream os; + os.exceptions(std::ios::badbit | std::ios::failbit); + os.open(path); + os.write(data.data(), data.size()); + os.close(); +} + +} // namespace + + +void StoreStatus::load() { + if (dataDir.empty()) { + throw Exception(QPID_MSG("No data-dir: When a store is loaded together with clustering, --data-dir must be specified.")); + } + try { + fs::path dir = fs::path(dataDir, fs::native)/SUBDIR; + create_directory(dir); + fs::path file = dir/STORE_STATUS; + if (fs::exists(file)) { + string data = readFile(file); + istringstream is(data); + is.exceptions(std::ios::badbit | std::ios::failbit); + is >> ws >> clusterId >> ws >> shutdownId; + if (!clusterId) + throw Exception(QPID_MSG("Invalid cluster store state, no cluster-id")); + if (shutdownId) state = STORE_STATE_CLEAN_STORE; + else state = STORE_STATE_DIRTY_STORE; + } + else { // Starting from empty store + clusterId = Uuid(true); + save(); + state = STORE_STATE_EMPTY_STORE; + } + } + catch (const std::exception&e) { + throw Exception(QPID_MSG("Cannot load cluster store status: " << e.what())); + } +} + +void StoreStatus::save() { + if (dataDir.empty()) return; + try { + ostringstream os; + os << clusterId << endl << shutdownId << endl; + fs::path file = fs::path(dataDir, fs::native)/SUBDIR/STORE_STATUS; + writeFile(file, os.str()); + } + catch (const std::exception& e) { + throw Exception(QPID_MSG("Cannot save cluster store status: " << e.what())); + } +} + +bool StoreStatus::hasStore() const { + return state != framing::cluster::STORE_STATE_NO_STORE; +} + +void StoreStatus::dirty() { + assert(hasStore()); + if (shutdownId) { + shutdownId = Uuid(); + save(); + } + state = STORE_STATE_DIRTY_STORE; +} + +void StoreStatus::clean(const Uuid& shutdownId_) { + assert(hasStore()); + assert(shutdownId_); + if (shutdownId_ != shutdownId) { + shutdownId = shutdownId_; + save(); + } + state = STORE_STATE_CLEAN_STORE; +} + +void StoreStatus::setClusterId(const Uuid& clusterId_) { + clusterId = clusterId_; + save(); +} + +const char* stateName(StoreState s) { + switch (s) { + case STORE_STATE_NO_STORE: return "none"; + case STORE_STATE_EMPTY_STORE: return "empty"; + case STORE_STATE_DIRTY_STORE: return "dirty"; + case STORE_STATE_CLEAN_STORE: return "clean"; + } + assert(0); + return "unknown"; +} + +ostream& operator<<(ostream& o, framing::cluster::StoreState s) { return o << stateName(s); } + +ostream& operator<<(ostream& o, const StoreStatus& s) { + o << s.getState(); + if (s.getState() == STORE_STATE_DIRTY_STORE) + o << " cluster-id=" << s.getClusterId(); + if (s.getState() == STORE_STATE_CLEAN_STORE) { + o << " cluster-id=" << s.getClusterId() + << " shutdown-id=" << s.getShutdownId(); + } + return o; +} + +}} // namespace qpid::cluster + diff --git a/qpid/cpp/src/qpid/cluster/StoreStatus.h b/qpid/cpp/src/qpid/cluster/StoreStatus.h new file mode 100644 index 0000000000..7442fcf02c --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/StoreStatus.h @@ -0,0 +1,68 @@ +#ifndef QPID_CLUSTER_STORESTATE_H +#define QPID_CLUSTER_STORESTATE_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/framing/Uuid.h" +#include "qpid/framing/SequenceNumber.h" +#include "qpid/framing/enum.h" +#include <iosfwd> + +namespace qpid { +namespace cluster { + +/** + * State of the store for cluster purposes. + */ +class StoreStatus +{ + public: + typedef framing::Uuid Uuid; + typedef framing::cluster::StoreState StoreState; + + StoreStatus(const std::string& dir); + + framing::cluster::StoreState getState() const { return state; } + + const Uuid& getClusterId() const { return clusterId; } + void setClusterId(const Uuid&); + const Uuid& getShutdownId() const { return shutdownId; } + + void load(); + void dirty(); // Mark the store in use. + void clean(const Uuid& shutdownId); // Mark the store clean. + bool hasStore() const; + + private: + void save(); + + framing::cluster::StoreState state; + Uuid clusterId, shutdownId; + std::string dataDir; +}; + +const char* stateName(framing::cluster::StoreState); +std::ostream& operator<<(std::ostream&, framing::cluster::StoreState); +std::ostream& operator<<(std::ostream&, const StoreStatus&); +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_STORESTATE_H*/ diff --git a/qpid/cpp/src/qpid/cluster/UpdateClient.cpp b/qpid/cpp/src/qpid/cluster/UpdateClient.cpp new file mode 100644 index 0000000000..a15c14ff48 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/UpdateClient.cpp @@ -0,0 +1,641 @@ +/* + * + * 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/amqp_0_10/Codecs.h" +#include "qpid/cluster/UpdateClient.h" +#include "qpid/cluster/Cluster.h" +#include "qpid/cluster/ClusterMap.h" +#include "qpid/cluster/Connection.h" +#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/ConnectionImpl.h" +#include "qpid/client/Future.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/Fairshare.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/QueueRegistry.h" +#include "qpid/broker/LinkRegistry.h" +#include "qpid/broker/Bridge.h" +#include "qpid/broker/Link.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/Exchange.h" +#include "qpid/broker/ExchangeRegistry.h" +#include "qpid/broker/SessionHandler.h" +#include "qpid/broker/SessionState.h" +#include "qpid/broker/TxOpVisitor.h" +#include "qpid/broker/DtxAck.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" +#include "qpid/framing/ClusterConnectionSessionStateBody.h" +#include "qpid/framing/ClusterConnectionConsumerStateBody.h" +#include "qpid/framing/enum.h" +#include "qpid/framing/ProtocolVersion.h" +#include "qpid/framing/TypeCode.h" +#include "qpid/log/Statement.h" +#include "qpid/types/Variant.h" +#include "qpid/Url.h" +#include "qmf/org/apache/qpid/broker/ManagementSetupState.h" +#include <boost/bind.hpp> +#include <boost/cast.hpp> +#include <algorithm> +#include <sstream> + +namespace qpid { +namespace cluster { + +using amqp_0_10::ListCodec; +using broker::Broker; +using broker::Exchange; +using broker::Queue; +using broker::QueueBinding; +using broker::Message; +using broker::SemanticState; +using types::Variant; + +using namespace framing; +namespace arg=client::arg; +using client::SessionBase_0_10Access; + +std::ostream& operator<<(std::ostream& o, const UpdateClient& c) { + return o << "cluster(" << c.updaterId << " UPDATER)"; +} + +struct ClusterConnectionProxy : public AMQP_AllProxy::ClusterConnection, public framing::FrameHandler +{ + boost::shared_ptr<qpid::client::ConnectionImpl> connection; + + ClusterConnectionProxy(client::Connection c) : + AMQP_AllProxy::ClusterConnection(*static_cast<framing::FrameHandler*>(this)), + connection(client::ConnectionAccess::getImpl(c)) {} + ClusterConnectionProxy(client::AsyncSession s) : + AMQP_AllProxy::ClusterConnection(SessionBase_0_10Access(s).get()->out) {} + + void handle(framing::AMQFrame& f) + { + assert(connection); + connection->expand(f.encodedSize(), false); + connection->handle(f); + } +}; + +// Create a connection with special version that marks it as a catch-up connection. +client::Connection UpdateClient::catchUpConnection() { + client::Connection c; + client::ConnectionAccess::setVersion(c, ProtocolVersion(0x80 , 0x80 + 10)); + return c; +} + +// Send a control body directly to the session. +void send(client::AsyncSession& s, const AMQBody& body) { + client::SessionBase_0_10Access sb(s); + sb.get()->send(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_, + const Cluster::ConnectionVector& cons, Decoder& decoder_, + const boost::function<void()>& ok, + const boost::function<void(const std::exception&)>& fail, + const client::ConnectionSettings& cs +) + : updaterId(updater), updateeId(updatee), updateeUrl(url), updaterBroker(broker), map(m), + expiry(expiry_), connections(cons), decoder(decoder_), + connection(catchUpConnection()), shadowConnection(catchUpConnection()), + done(ok), failed(fail), connectionSettings(cs) +{} + +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); + update(); + done(); + } catch (const std::exception& e) { + failed(e); + } + delete this; +} + +void UpdateClient::update() { + QPID_LOG(debug, *this << " updating state to " << updateeId + << " at " << updateeUrl); + Broker& b = updaterBroker; + + updateManagementSetupState(); + + b.getExchanges().eachExchange(boost::bind(&UpdateClient::updateExchange, this, _1)); + b.getQueues().eachQueue(boost::bind(&UpdateClient::updateNonExclusiveQueue, this, _1)); + + // Update queue is used to transfer acquired messages that are no + // 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(); + + session.close(); + + ClusterConnectionMembershipBody membership; + map.toMethodBody(membership); + AMQFrame frame(membership); + client::ConnectionAccess::getImpl(connection)->expand(frame.encodedSize(), false); + client::ConnectionAccess::getImpl(connection)->handle(frame); + + // 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 + // Connection object. Remove when the bug is fixed. + // + sys::usleep(10*1000); + + QPID_LOG(debug, *this << " update completed to " << updateeId << " at " << updateeUrl); +} + +namespace { +template <class T> std::string encode(const T& t) { + std::string encoded; + encoded.resize(t.encodedSize()); + framing::Buffer buf(const_cast<char*>(encoded.data()), encoded.size()); + t.encode(buf); + return encoded; +} + +template <class T> std::string encode(const T& t, bool encodeKind) { + std::string encoded; + encoded.resize(t.encodedSize()); + framing::Buffer buf(const_cast<char*>(encoded.data()), encoded.size()); + t.encode(buf, encodeKind); + return encoded; +} +} // namespace + + +// Propagate the management state +void UpdateClient::updateManagementSetupState() +{ + management::ManagementAgent* agent = updaterBroker.getManagementAgent(); + if (!agent) return; + + QPID_LOG(debug, *this << " updating management setup-state."); + std::string vendor, product, instance; + agent->getName(vendor, product, instance); + ClusterConnectionProxy(session).managementSetupState( + agent->getNextObjectId(), agent->getBootSequence(), agent->getUuid(), + vendor, product, instance); +} + +void UpdateClient::updateManagementAgent() +{ + management::ManagementAgent* agent = updaterBroker.getManagementAgent(); + if (!agent) return; + string data; + + QPID_LOG(debug, *this << " updating management schemas. ") + agent->exportSchemas(data); + session.messageTransfer( + arg::content=client::Message(data, UpdateDataExchange::MANAGEMENT_SCHEMAS_KEY), + arg::destination=UpdateDataExchange::EXCHANGE_NAME); + + QPID_LOG(debug, *this << " updating management agents. ") + agent->exportAgents(data); + session.messageTransfer( + arg::content=client::Message(data, UpdateDataExchange::MANAGEMENT_AGENTS_KEY), + arg::destination=UpdateDataExchange::EXCHANGE_NAME); + + QPID_LOG(debug, *this << " updating management deleted objects. ") + typedef management::ManagementAgent::DeletedObjectList DeletedObjectList; + DeletedObjectList deleted; + agent->exportDeletedObjects(deleted); + Variant::List list; + for (DeletedObjectList::iterator i = deleted.begin(); i != deleted.end(); ++i) { + string encoded; + (*i)->encode(encoded); + list.push_back(encoded); + } + ListCodec::encode(list, data); + session.messageTransfer( + arg::content=client::Message(data, UpdateDataExchange::MANAGEMENT_DELETED_OBJECTS_KEY), + arg::destination=UpdateDataExchange::EXCHANGE_NAME); +} + +void UpdateClient::updateExchange(const boost::shared_ptr<Exchange>& ex) { + QPID_LOG(debug, *this << " updating exchange " << ex->getName()); + ClusterConnectionProxy(session).exchange(encode(*ex)); +} + +/** Bind a queue to the update exchange and update messges to it + * setting the message possition as needed. + */ +class MessageUpdater { + std::string queue; + bool haveLastPos; + 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_) { + session.exchangeBind(queue, UpdateClient::UPDATE); + } + + ~MessageUpdater() { + try { + session.exchangeUnbind(queue, UpdateClient::UPDATE); + } + catch (const std::exception& e) { + // Don't throw in a destructor. + QPID_LOG(error, "Unbinding update queue " << queue << ": " << e.what()); + } + } + + + void updateQueuedMessage(const broker::QueuedMessage& message) { + // Send the queue position if necessary. + if (!haveLastPos || message.position - lastPos != 1) { + ClusterConnectionProxy(session).queuePosition(queue, message.position.getValue()-1); + haveLastPos = true; + } + 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); + } + + // We can't send a broker::Message via the normal client API, + // and it would be expensive to copy it into a client::Message + // so we go a bit under the client API covers here. + // + SessionBase_0_10Access sb(session); + // Disable client code that clears the delivery-properties.exchange + sb.get()->setDoClearDeliveryPropertiesExchange(false); + 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()){ + uint16_t maxFrameSize = sb.get()->getConnection()->getNegotiatedSettings().maxFrameSize; + 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); + sb.get()->sendRawFrame(frame); + } + } + } + + void updateMessage(const boost::intrusive_ptr<broker::Message>& message) { + updateQueuedMessage(broker::QueuedMessage(0, message, haveLastPos? lastPos.getValue()+1 : 1)); + } +}; + +void UpdateClient::updateQueue(client::AsyncSession& s, const boost::shared_ptr<Queue>& q) { + broker::Exchange::shared_ptr alternateExchange = q->getAlternateExchange(); + s.queueDeclare( + arg::queue = q->getName(), + arg::durable = q->isDurable(), + arg::autoDelete = q->isAutoDelete(), + arg::alternateExchange = alternateExchange ? alternateExchange->getName() : "", + arg::arguments = q->getSettings(), + arg::exclusive = q->hasExclusiveOwner() + ); + MessageUpdater updater(q->getName(), s, expiry); + q->eachMessage(boost::bind(&MessageUpdater::updateQueuedMessage, &updater, _1)); + q->eachBinding(boost::bind(&UpdateClient::updateBinding, this, s, q->getName(), _1)); + ClusterConnectionProxy(s).queuePosition(q->getName(), q->getPosition()); + uint priority, count; + if (qpid::broker::Fairshare::getState(q->getMessages(), priority, count)) { + ClusterConnectionProxy(s).queueFairshareState(q->getName(), priority, count); + } +} + +void UpdateClient::updateExclusiveQueue(const boost::shared_ptr<broker::Queue>& q) { + QPID_LOG(debug, *this << " updating exclusive queue " << q->getName() << " on " << shadowSession.getId()); + updateQueue(shadowSession, q); +} + +void UpdateClient::updateNonExclusiveQueue(const boost::shared_ptr<broker::Queue>& q) { + if (!q->hasExclusiveOwner()) { + QPID_LOG(debug, *this << " updating queue " << q->getName()); + updateQueue(session, q); + }//else queue will be updated as part of session state of owning session +} + +void UpdateClient::updateBinding(client::AsyncSession& s, const std::string& queue, const QueueBinding& binding) { + s.exchangeBind(queue, binding.exchange, binding.key, binding.args); +} + +void UpdateClient::updateOutputTask(const sys::OutputTask* task) { + const SemanticState::ConsumerImpl* cci = + 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() + << " channel=" << channel); +} + +void UpdateClient::updateConnection(const boost::intrusive_ptr<Connection>& updateConnection) { + 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); + // Make sure its received before opening shadow connection + session.sync(); + + // Open shadow connection and update it. + shadowConnection = catchUpConnection(); + + connectionSettings.maxFrameSize = bc.getFrameMax(); + shadowConnection.open(updateeUrl, connectionSettings); + ClusterConnectionProxy(shadowConnection).shadowSetUser(bc.getUserId()); + + bc.eachSessionHandler(boost::bind(&UpdateClient::updateSession, this, _1)); + // Safe to use decoder here because we are stalled for update. + std::pair<const char*, size_t> fragment = decoder.get(updateConnection->getId()).getFragment(); + bc.getOutputTasks().eachOutput( + boost::bind(&UpdateClient::updateOutputTask, this, _1)); + ClusterConnectionProxy(shadowConnection).shadowReady( + updateConnection->getId().getMember(), + updateConnection->getId().getNumber(), + bc.getMgmtId(), + bc.getUserId(), + string(fragment.first, fragment.second), + updateConnection->getOutput().getSendMax() + ); + shadowConnection.close(); + QPID_LOG(debug, *this << " updated connection " << *updateConnection); +} + +void UpdateClient::updateSession(broker::SessionHandler& sh) { + broker::SessionState* ss = sh.getSession(); + if (!ss) return; // no session. + + QPID_LOG(debug, *this << " updating session " << ss->getId()); + + // 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(); + client::SessionBase_0_10Access(shadowSession).set(simpl); + AMQP_AllProxy::ClusterConnection proxy(simpl->out); + + // Re-create session state on remote connection. + + QPID_LOG(debug, *this << " updating exclusive queues."); + ss->getSessionAdapter().eachExclusiveQueue(boost::bind(&UpdateClient::updateExclusiveQueue, this, _1)); + + QPID_LOG(debug, *this << " updating consumers."); + ss->getSemanticState().eachConsumer( + boost::bind(&UpdateClient::updateConsumer, this, _1)); + + 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)); + + updateTxState(ss->getSemanticState()); // Tx transaction state. + + // 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) + --received; + + // Sync the session to ensure all responses from broker have been processed. + shadowSession.sync(); + + // Reset command-sequence state. + proxy.sessionState( + ss->senderGetReplayPoint().command, + ss->senderGetCommandPoint().command, + ss->senderGetIncomplete(), + std::max(received, ss->receiverGetExpected().command), + received, + ss->receiverGetUnknownComplete(), + ss->receiverGetIncomplete() + ); + + // Send frames for partial message in progress. + if (inProgress) { + inProgress->getFrames().map(simpl->out); + } + QPID_LOG(debug, *this << " updated session " << sh.getSession()->getId()); +} + +void UpdateClient::updateConsumer( + const broker::SemanticState::ConsumerImpl::shared_ptr& ci) +{ + QPID_LOG(debug, *this << " updating consumer " << ci->getName() << " on " + << shadowSession.getId()); + + using namespace message; + shadowSession.messageSubscribe( + arg::queue = ci->getQueue()->getName(), + arg::destination = ci->getName(), + 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(), + arg::resumeId = ci->getResumeId(), + 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()); + ClusterConnectionProxy(shadowSession).consumerState( + ci->getName(), + ci->isBlocked(), + ci->isNotifyEnabled(), + ci->position + ); + consumerNumbering.add(ci.get()); + + QPID_LOG(debug, *this << " updated consumer " << ci->getName() + << " on " << shadowSession.getId()); +} + +void UpdateClient::updateUnacked(const broker::DeliveryRecord& dr) { + if (!dr.isEnded() && dr.isAcquired() && 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()); + } + ClusterConnectionProxy(shadowSession).deliveryRecord( + dr.getQueue()->getName(), + dr.getMessage().position, + dr.getTag(), + dr.getId(), + dr.isAcquired(), + dr.isAccepted(), + dr.isCancelled(), + dr.isComplete(), + dr.isEnded(), + dr.isWindowing(), + dr.getQueue()->isEnqueued(dr.getMessage()), + dr.getCredit() + ); +} + +class TxOpUpdater : public broker::TxOpConstVisitor, public MessageUpdater { + public: + 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::RecoveredDequeue& rdeq) { + updateMessage(rdeq.getMessage()); + proxy.txEnqueue(rdeq.getQueue()->getName()); + } + + void operator()(const broker::RecoveredEnqueue& renq) { + updateMessage(renq.getMessage()); + proxy.txEnqueue(renq.getQueue()->getName()); + } + + void operator()(const broker::TxAccept& txAccept) { + proxy.txAccept(txAccept.getAcked()); + } + + void operator()(const broker::TxPublish& txPub) { + updateMessage(txPub.getMessage()); + typedef std::list<Queue::shared_ptr> QueueList; + const QueueList& qlist = txPub.getQueues(); + 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()))); + proxy.txPublish(qarray, txPub.delivered); + } + + private: + UpdateClient& parent; + client::AsyncSession session; + ClusterConnectionProxy proxy; +}; + +void UpdateClient::updateTxState(broker::SemanticState& s) { + QPID_LOG(debug, *this << " updating TX transaction state."); + ClusterConnectionProxy proxy(shadowSession); + proxy.accumulatedAck(s.getAccumulatedAck()); + broker::TxBuffer::shared_ptr txBuffer = s.getTxBuffer(); + if (txBuffer) { + proxy.txStart(); + TxOpUpdater updater(*this, shadowSession, expiry); + txBuffer->accept(updater); + proxy.txEnd(); + } +} + +void UpdateClient::updateQueueListeners(const boost::shared_ptr<broker::Queue>& queue) { + queue->getListeners().eachListener( + boost::bind(&UpdateClient::updateQueueListener, this, queue->getName(), _1)); +} + +void UpdateClient::updateQueueListener(std::string& q, + const boost::shared_ptr<broker::Consumer>& c) +{ + SemanticState::ConsumerImpl* ci = dynamic_cast<SemanticState::ConsumerImpl*>(c.get()); + size_t n = consumerNumbering[ci]; + if (n >= consumerNumbering.size()) + throw Exception(QPID_MSG("Unexpected listener on queue " << q)); + ClusterConnectionProxy(session).addQueueListener(q, n); +} + +void UpdateClient::updateLinks() { + broker::LinkRegistry& links = updaterBroker.getLinks(); + links.eachLink(boost::bind(&UpdateClient::updateLink, this, _1)); + links.eachBridge(boost::bind(&UpdateClient::updateBridge, this, _1)); +} + +void UpdateClient::updateLink(const boost::shared_ptr<broker::Link>& link) { + QPID_LOG(debug, *this << " updating link " + << link->getHost() << ":" << link->getPort()); + ClusterConnectionProxy(session).config(encode(*link)); +} + +void UpdateClient::updateBridge(const boost::shared_ptr<broker::Bridge>& bridge) { + QPID_LOG(debug, *this << " updating bridge " << bridge->getName()); + 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 ); + } +} + + +}} // namespace qpid::cluster diff --git a/qpid/cpp/src/qpid/cluster/UpdateClient.h b/qpid/cpp/src/qpid/cluster/UpdateClient.h new file mode 100644 index 0000000000..b72d090d73 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/UpdateClient.h @@ -0,0 +1,133 @@ +#ifndef QPID_CLUSTER_UPDATECLIENT_H +#define QPID_CLUSTER_UPDATECLIENT_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/cluster/ClusterMap.h" +#include "qpid/cluster/Numbering.h" +#include "qpid/client/Connection.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/broker/SemanticState.h" +#include "qpid/sys/Runnable.h" +#include <boost/shared_ptr.hpp> +#include <iosfwd> + +namespace qpid { + +struct Url; + +namespace broker { + +class Broker; +class Queue; +class Exchange; +class QueueBindings; +struct QueueBinding; +struct QueuedMessage; +class SessionHandler; +class DeliveryRecord; +class SessionState; +class SemanticState; +class Decoder; +class Link; +class Bridge; +class QueueObserver; + +} // namespace broker + +namespace cluster { + +class Cluster; +class Connection; +class ClusterMap; +class Decoder; +class ExpiryPolicy; + +/** + * A client that updates the contents of a local broker to a remote one using AMQP. + */ +class UpdateClient : public sys::Runnable { + public: + static const std::string UPDATE; // Name for special update queue and exchange. + 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& + ); + + ~UpdateClient(); + void update(); + void run(); // Will delete this when finished. + + void updateUnacked(const broker::DeliveryRecord&); + + private: + void updateQueue(client::AsyncSession&, const boost::shared_ptr<broker::Queue>&); + void updateNonExclusiveQueue(const boost::shared_ptr<broker::Queue>&); + void updateExclusiveQueue(const boost::shared_ptr<broker::Queue>&); + void updateExchange(const boost::shared_ptr<broker::Exchange>&); + void updateMessage(const broker::QueuedMessage&); + void updateMessageTo(const broker::QueuedMessage&, const std::string& queue, client::Session s); + 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 updateOutputTask(const sys::OutputTask* task); + void updateConsumer(const broker::SemanticState::ConsumerImpl::shared_ptr&); + void updateQueueListeners(const boost::shared_ptr<broker::Queue>&); + void updateQueueListener(std::string& q, const boost::shared_ptr<broker::Consumer>& c); + void updateManagementSetupState(); + void updateManagementAgent(); + 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>); + + + Numbering<broker::SemanticState::ConsumerImpl*> consumerNumbering; + MemberId updaterId; + MemberId updateeId; + Url updateeUrl; + broker::Broker& updaterBroker; + ClusterMap map; + ExpiryPolicy& expiry; + std::vector<boost::intrusive_ptr<Connection> > connections; + Decoder& decoder; + client::Connection connection, shadowConnection; + client::AsyncSession session, shadowSession; + boost::function<void()> done; + boost::function<void(const std::exception& e)> failed; + client::ConnectionSettings connectionSettings; + + friend std::ostream& operator<<(std::ostream&, const UpdateClient&); +}; + + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_UPDATECLIENT_H*/ diff --git a/qpid/cpp/src/qpid/cluster/UpdateDataExchange.cpp b/qpid/cpp/src/qpid/cluster/UpdateDataExchange.cpp new file mode 100644 index 0000000000..e5cd82e3d3 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/UpdateDataExchange.cpp @@ -0,0 +1,77 @@ +/* + * + * 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 "UpdateDataExchange.h" +#include "Cluster.h" +#include "qpid/amqp_0_10/Codecs.h" +#include "qpid/broker/Deliverable.h" +#include "qpid/broker/Message.h" +#include "qpid/log/Statement.h" +#include "qpid/management/ManagementAgent.h" +#include "qpid/types/Variant.h" + +namespace qpid { +namespace cluster { + +const std::string UpdateDataExchange::EXCHANGE_NAME("qpid.cluster-update-data"); +const std::string UpdateDataExchange::EXCHANGE_TYPE("qpid.cluster-update-data"); +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"); + +UpdateDataExchange::UpdateDataExchange(Cluster& cluster) : + Exchange(EXCHANGE_NAME, &cluster) +{} + +void UpdateDataExchange::route(broker::Deliverable& msg, const std::string& routingKey, + const qpid::framing::FieldTable* ) +{ + std::string data = msg.getMessage().getFrames().getContent(); + if (routingKey == MANAGEMENT_AGENTS_KEY) managementAgents = data; + else if (routingKey == MANAGEMENT_SCHEMAS_KEY) managementSchemas = data; + else if (routingKey == MANAGEMENT_DELETED_OBJECTS_KEY) managementDeletedObjects = data; + else throw Exception( + QPID_MSG("Cluster update-data exchange received unknown routing-key: " + << routingKey)); +} + +void UpdateDataExchange::updateManagementAgent(management::ManagementAgent* agent) { + if (!agent) return; + + framing::Buffer buf1(const_cast<char*>(managementAgents.data()), managementAgents.size()); + agent->importAgents(buf1); + + framing::Buffer buf2(const_cast<char*>(managementSchemas.data()), managementSchemas.size()); + agent->importSchemas(buf2); + + using amqp_0_10::ListCodec; + using types::Variant; + Variant::List encoded; + ListCodec::decode(managementDeletedObjects, encoded); + management::ManagementAgent::DeletedObjectList objects; + for (Variant::List::iterator i = encoded.begin(); i != encoded.end(); ++i) { + objects.push_back(management::ManagementAgent::DeletedObject::shared_ptr( + new management::ManagementAgent::DeletedObject(*i))); + } + agent->importDeletedObjects(objects); +} + + +}} // namespace qpid::cluster diff --git a/qpid/cpp/src/qpid/cluster/UpdateDataExchange.h b/qpid/cpp/src/qpid/cluster/UpdateDataExchange.h new file mode 100644 index 0000000000..d2f6c35ad0 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/UpdateDataExchange.h @@ -0,0 +1,84 @@ +#ifndef QPID_CLUSTER_UPDATEDATAEXCHANGE_H +#define QPID_CLUSTER_UPDATEDATAEXCHANGE_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/Exchange.h" +#include "types.h" +#include <iosfwd> + +namespace qpid { + +namespace management { +class ManagementAgent; +} + +namespace cluster { +class Cluster; + +/** + * An exchange used to send data that is to large for a control + * during update. The routing key indicates the type of data. + */ +class UpdateDataExchange : public broker::Exchange +{ + public: + static const std::string EXCHANGE_NAME; + static const std::string EXCHANGE_TYPE; + static const std::string MANAGEMENT_AGENTS_KEY; + static const std::string MANAGEMENT_SCHEMAS_KEY; + static const std::string MANAGEMENT_DELETED_OBJECTS_KEY; + + UpdateDataExchange(Cluster& parent); + + void route(broker::Deliverable& msg, const std::string& routingKey, + const framing::FieldTable* args); + + // Not implemented + std::string getType() const { return EXCHANGE_TYPE; } + + bool bind(boost::shared_ptr<broker::Queue>, + const std::string&, + const qpid::framing::FieldTable*) + { return false; } + + bool unbind(boost::shared_ptr<broker::Queue>, + const std::string&, + const qpid::framing::FieldTable*) + { return false; } + + bool isBound(boost::shared_ptr<broker::Queue>, + const std::string*, + const qpid::framing::FieldTable*) + { return false; } + + void updateManagementAgent(management::ManagementAgent* agent); + + private: + std::string managementAgents; + std::string managementSchemas; + std::string managementDeletedObjects; +}; + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_UPDATEDATAEXCHANGE_H*/ diff --git a/qpid/cpp/src/qpid/cluster/UpdateExchange.cpp b/qpid/cpp/src/qpid/cluster/UpdateExchange.cpp new file mode 100644 index 0000000000..11937f296f --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/UpdateExchange.cpp @@ -0,0 +1,47 @@ +/* + * + * 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/MessageTransferBody.h" +#include "qpid/broker/Message.h" +#include "UpdateExchange.h" + +namespace qpid { +namespace cluster { + +using framing::MessageTransferBody; +using framing::DeliveryProperties; + +UpdateExchange::UpdateExchange(management::Manageable* parent) + : broker::Exchange(UpdateClient::UPDATE, parent), + broker::FanOutExchange(UpdateClient::UPDATE, parent) {} + + +void UpdateExchange::setProperties(const boost::intrusive_ptr<broker::Message>& msg) { + MessageTransferBody* transfer = msg->getMethod<MessageTransferBody>(); + assert(transfer); + const DeliveryProperties* props = msg->getProperties<DeliveryProperties>(); + assert(props); + if (props->hasExchange()) + transfer->setDestination(props->getExchange()); + else + transfer->clearDestinationFlag(); +} + +}} // namespace qpid::cluster diff --git a/qpid/cpp/src/qpid/cluster/UpdateExchange.h b/qpid/cpp/src/qpid/cluster/UpdateExchange.h new file mode 100644 index 0000000000..9d7d9ee5fc --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/UpdateExchange.h @@ -0,0 +1,45 @@ +#ifndef QPID_CLUSTER_UPDATEEXCHANGE_H +#define QPID_CLUSTER_UPDATEEXCHANGE_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/cluster/UpdateClient.h" +#include "qpid/broker/FanOutExchange.h" + + +namespace qpid { +namespace cluster { + +/** + * A keyless exchange (like fanout exchange) that does not modify + * delivery-properties.exchange but copies it to the MessageTransfer. + */ +class UpdateExchange : public broker::FanOutExchange +{ + public: + UpdateExchange(management::Manageable* parent); + void setProperties(const boost::intrusive_ptr<broker::Message>&); +}; + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_UPDATEEXCHANGE_H*/ diff --git a/qpid/cpp/src/qpid/cluster/UpdateReceiver.h b/qpid/cpp/src/qpid/cluster/UpdateReceiver.h new file mode 100644 index 0000000000..7e8ce47662 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/UpdateReceiver.h @@ -0,0 +1,45 @@ +#ifndef QPID_CLUSTER_UPDATESTATE_H +#define QPID_CLUSTER_UPDATESTATE_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 "Numbering.h" +#include "qpid/broker/SemanticState.h" + +namespace qpid { +namespace cluster { + +/** + * Cluster-wide state used when receiving an update. + */ +class UpdateReceiver { + public: + /** Numbering used to identify Queue listeners as consumers */ + typedef Numbering<boost::shared_ptr<broker::SemanticState::ConsumerImpl> > ConsumerNumbering; + ConsumerNumbering consumerNumbering; + + /** Management-id for the next shadow connection */ + std::string nextShadowMgmtId; +}; +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_UPDATESTATE_H*/ diff --git a/qpid/cpp/src/qpid/cluster/WatchDogPlugin.cpp b/qpid/cpp/src/qpid/cluster/WatchDogPlugin.cpp new file mode 100644 index 0000000000..57ba5cf2fd --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/WatchDogPlugin.cpp @@ -0,0 +1,137 @@ +/* + * + * 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. + * + */ + +/**@file + + The watchdog plug-in will kill the qpidd broker process if it + becomes stuck for longer than a configured interval. + + If the watchdog plugin is loaded and the --watchdog-interval=N + option is set then the broker starts a watchdog process and signals + it every N/2 seconds. + + The watchdog process runs a very simple program that starts a timer + for N seconds, and resets the timer to N seconds whenever it is + signalled by the broker. If the timer ever reaches 0 the watchdog + kills the broker process (with kill -9) and exits. + + This is useful in a cluster setting because in some insttances + (e.g. while resolving an error) it's possible for a stuck process + to hang other cluster members that are waiting for it to send a + message. Using the watchdog, the stuck process is terminated and + removed fromt the cluster allowing other members to continue and + clients of the stuck process to fail over to other members. + +*/ +#include "config.h" +#include "qpid/Plugin.h" +#include "qpid/Options.h" +#include "qpid/log/Statement.h" +#include "qpid/broker/Broker.h" +#include "qpid/sys/Timer.h" +#include "qpid/sys/Fork.h" +#include <sys/types.h> +#include <sys/wait.h> +#include <signal.h> + +namespace qpid { +namespace cluster { + +using broker::Broker; + +struct Settings { + Settings() : interval(0) {} + int interval; +}; + +struct WatchDogOptions : public qpid::Options { + Settings& settings; + + WatchDogOptions(Settings& s) : settings(s) { + addOptions() + ("watchdog-interval", optValue(settings.interval, "N"), + "broker is automatically killed if it is hung for more than \ + N seconds. 0 disables watchdog."); + } +}; + +struct WatchDogTask : public sys::TimerTask { + int pid; + sys::Timer& timer; + int interval; + + WatchDogTask(int pid_, sys::Timer& t, int _interval) + : TimerTask(_interval*sys::TIME_SEC/2,"WatchDog"), pid(pid_), timer(t), interval(_interval) {} + + void fire() { + timer.add (new WatchDogTask(pid, timer, interval)); + QPID_LOG(debug, "Sending keepalive signal to watchdog"); + ::kill(pid, SIGUSR1); + } +}; + +struct WatchDogPlugin : public qpid::Plugin, public qpid::sys::Fork { + Settings settings; + WatchDogOptions options; + Broker* broker; + int watchdogPid; + + WatchDogPlugin() : options(settings), broker(0), watchdogPid(0) {} + + ~WatchDogPlugin() { + if (watchdogPid) ::kill(watchdogPid, SIGTERM); + ::waitpid(watchdogPid, 0, 0); + } + + Options* getOptions() { return &options; } + + void earlyInitialize(qpid::Plugin::Target& target) { + broker = dynamic_cast<Broker*>(&target); + if (broker && settings.interval) { + QPID_LOG(notice, "Starting watchdog process with interval of " << + settings.interval << " seconds"); + fork(); + } + } + + void initialize(Target&) {} + + protected: + + void child() { // Child of fork + const char* watchdog = ::getenv("QPID_WATCHDOG_EXEC"); // For use in tests + if (!watchdog) watchdog=QPID_LIBEXEC_DIR "/qpidd_watchdog"; + std::string interval = boost::lexical_cast<std::string>(settings.interval); + ::execl(watchdog, watchdog, interval.c_str(), NULL); + QPID_LOG(critical, "Failed to exec watchdog program " << watchdog ); + ::kill(::getppid(), SIGKILL); + exit(1); + } + + void parent(int pid) { // Parent of fork + watchdogPid = pid; + broker->getTimer().add( + new WatchDogTask(watchdogPid, broker->getTimer(), settings.interval)); + // TODO aconway 2009-08-10: to be extra safe, we could monitor + // the watchdog child and re-start it if it exits. + } +}; + +static WatchDogPlugin instance; // Static initialization. + +}} // namespace qpid::cluster diff --git a/qpid/cpp/src/qpid/cluster/management-schema.xml b/qpid/cpp/src/qpid/cluster/management-schema.xml new file mode 100644 index 0000000000..a6292e9113 --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/management-schema.xml @@ -0,0 +1,61 @@ +<schema package="org.apache.qpid.cluster"> + + <!-- + 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. + --> + + <!-- Type information: + +Numeric types with "_wm" suffix are watermarked numbers. These are compound +values containing a current value, and a low and high water mark for the reporting +interval. The low and high water marks are set to the current value at the +beginning of each interval and track the minimum and maximum values of the statistic +over the interval respectively. + +Access rights for configuration elements: + +RO => Read Only +RC => Read/Create, can be set at create time only, read-only thereafter +RW => Read/Write + +If access rights are omitted for a property, they are assumed to be RO. + + --> + + <class name="Cluster"> + <property name="brokerRef" type="objId" references="Broker" access="RC" index="y" parentRef="y"/> + <property name="clusterName" type="sstr" access="RC" desc="Name of cluster this server is a member of"/> + <property name="clusterID" type="sstr" access="RO" desc="Globally unique ID (UUID) for this cluster instance"/> + <property name="memberID" type="sstr" access="RO" desc="ID of this member of the cluster"/> + <property name="publishedURL" type="sstr" access="RC" desc="URL this node advertizes itself as"/> + <property name="clusterSize" type="uint16" access="RO" desc="Number of brokers currently in the cluster"/> + <property name="status" type="sstr" access="RO" desc="Cluster node status (STALLED,ACTIVE,JOINING)"/> + <property name="members" type="lstr" access="RO" desc="List of member URLs delimited by ';'"/> + <property name="memberIDs" type="lstr" access="RO" desc="List of member IDs delimited by ';'"/> + + <method name="stopClusterNode"> + <arg name="brokerId" type="sstr" dir="I"/> + </method> + <method name="stopFullCluster"/> + + </class> + + + +</schema> + diff --git a/qpid/cpp/src/qpid/cluster/qpidd_watchdog.cpp b/qpid/cpp/src/qpid/cluster/qpidd_watchdog.cpp new file mode 100644 index 0000000000..51c5ed4b3f --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/qpidd_watchdog.cpp @@ -0,0 +1,63 @@ +/* + * + * 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. + * + */ + +/** @file helper executable for WatchDogPlugin.cpp */ + +#include <sys/types.h> +#include <sys/time.h> +#include <signal.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <limits.h> + +long timeout; + +void killParent(int) { + ::kill(getppid(), SIGKILL); + ::fprintf(stderr, "Watchdog killed unresponsive broker, pid=%d\n", ::getppid()); + ::exit(1); +} + +void resetTimer(int) { + struct ::itimerval itval = { { 0, 0 }, { timeout, 0 } }; + if (::setitimer(ITIMER_REAL, &itval, 0) !=0) { + ::perror("Watchdog failed to set timer"); + killParent(0); + ::exit(1); + } +} + +/** Simple watchdog program: kill parent process if timeout + * expires without a SIGUSR1. + * Will be killed with SIGHUP when parent shuts down. + * Args: timeout in seconds. + */ +int main(int argc, char** argv) { + if(argc != 2 || (timeout = atoi(argv[1])) == 0) { + ::fprintf(stderr, "Usage: %s <timeout_seconds>\n", argv[0]); + ::exit(1); + } + ::signal(SIGUSR1, resetTimer); + ::signal(SIGALRM, killParent); + resetTimer(0); + while (true) { sleep(INT_MAX); } +} diff --git a/qpid/cpp/src/qpid/cluster/types.h b/qpid/cpp/src/qpid/cluster/types.h new file mode 100644 index 0000000000..bfb4fd5b9e --- /dev/null +++ b/qpid/cpp/src/qpid/cluster/types.h @@ -0,0 +1,84 @@ +#ifndef QPID_CLUSTER_TYPES_H +#define QPID_CLUSTER_TYPES_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 "config.h" +#include "qpid/Url.h" +#include "qpid/RefCounted.h" +#include "qpid/sys/IntegerTypes.h" +#include <boost/intrusive_ptr.hpp> +#include <utility> +#include <iosfwd> +#include <string> + +extern "C" { +#if defined (HAVE_OPENAIS_CPG_H) +# include <openais/cpg.h> +#elif defined (HAVE_COROSYNC_CPG_H) +# include <corosync/cpg.h> +#else +# error "No cpg.h header file available" +#endif +} + +namespace qpid { +namespace cluster { + +class Connection; +typedef boost::intrusive_ptr<Connection> ConnectionPtr; + +/** Types of cluster event. */ +enum EventType { DATA, CONTROL }; + +/** first=node-id, second=pid */ +struct MemberId : std::pair<uint32_t, uint32_t> { + MemberId(uint64_t n=0) : std::pair<uint32_t,uint32_t>( n >> 32, n & 0xffffffff) {} + MemberId(uint32_t node, uint32_t pid) : std::pair<uint32_t,uint32_t>(node, pid) {} + MemberId(const cpg_address& caddr) : std::pair<uint32_t,uint32_t>(caddr.nodeid, caddr.pid) {} + MemberId(const std::string&); // Decode from string. + uint32_t getNode() const { return first; } + uint32_t getPid() const { return second; } + operator uint64_t() const { return (uint64_t(first)<<32ull) + second; } + + // MemberId as byte string, network byte order. Not human readable. + std::string str() const; +}; + +inline bool operator==(const cpg_address& caddr, const MemberId& id) { return id == MemberId(caddr); } + +std::ostream& operator<<(std::ostream&, const MemberId&); + +struct ConnectionId : public std::pair<MemberId, uint64_t> { + ConnectionId(const MemberId& m=MemberId(), uint64_t c=0) : std::pair<MemberId, uint64_t> (m,c) {} + ConnectionId(uint64_t m, uint64_t c) : std::pair<MemberId, uint64_t>(MemberId(m), c) {} + MemberId getMember() const { return first; } + uint64_t getNumber() const { return second; } +}; + +std::ostream& operator<<(std::ostream&, const ConnectionId&); + +std::ostream& operator<<(std::ostream&, EventType); + +}} // namespace qpid::cluster + +#endif /*!QPID_CLUSTER_TYPES_H*/ diff --git a/qpid/cpp/src/qpid/console/Agent.cpp b/qpid/cpp/src/qpid/console/Agent.cpp new file mode 100644 index 0000000000..fa76a13583 --- /dev/null +++ b/qpid/cpp/src/qpid/console/Agent.cpp @@ -0,0 +1,30 @@ +/* + * + * 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/console/Agent.h" + +std::ostream& qpid::console::operator<<(std::ostream& o, const Agent& agent) +{ + o << "Agent at bank " << agent.getBrokerBank() << "." << agent.getAgentBank() << + " (" << agent.getLabel() << ")"; + return o; +} + diff --git a/qpid/cpp/src/qpid/console/Broker.cpp b/qpid/cpp/src/qpid/console/Broker.cpp new file mode 100644 index 0000000000..86a17d4a10 --- /dev/null +++ b/qpid/cpp/src/qpid/console/Broker.cpp @@ -0,0 +1,333 @@ +/* + * + * 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/console/Broker.h" +#include "qpid/console/Object.h" +#include "qpid/console/Value.h" +#include "qpid/console/SessionManager.h" +#include "qpid/console/ConsoleListener.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/SystemInfo.h" + +using namespace qpid::client; +using namespace qpid::console; +using namespace qpid::framing; +using namespace qpid::sys; +using namespace std; + +Broker::Broker(SessionManager& sm, ConnectionSettings& settings) : + sessionManager(sm), connected(false), connectionSettings(settings), + reqsOutstanding(1), syncInFlight(false), topicBound(false), methodObject(0), + connThreadBody(*this), connThread(connThreadBody) +{ + string osName; + string nodeName; + string release; + string version; + string machine; + + sys::SystemInfo::getSystemId(osName, nodeName, release, version, machine); + uint32_t pid = sys::SystemInfo::getParentProcessId(); + + stringstream text; + + text << "qmfc-cpp-" << nodeName << "-" << pid; + amqpSessionId = string(text.str()); + + QPID_LOG(debug, "Broker::Broker: constructed, amqpSessionId=" << amqpSessionId); +} + +Broker::~Broker() +{ + connThreadBody.shutdown(); + connThread.join(); + resetAgents(); + // resetAgents() does not delete the broker agent... + for (AgentMap::iterator iter = agents.begin(); iter != agents.end(); iter++) { + delete iter->second; + } +} + +string Broker::getUrl() const +{ + stringstream url; + url << connectionSettings.host << ":" << connectionSettings.port; + return url.str(); +} + +void Broker::encodeHeader(Buffer& buf, uint8_t opcode, uint32_t seq) const +{ + buf.putOctet('A'); + buf.putOctet('M'); + buf.putOctet('2'); + buf.putOctet(opcode); + buf.putLong (seq); +} + +bool Broker::checkHeader(Buffer& buf, uint8_t *opcode, uint32_t *seq) const +{ + if (buf.getSize() < 8) + return false; + + uint8_t h1 = buf.getOctet(); + uint8_t h2 = buf.getOctet(); + uint8_t h3 = buf.getOctet(); + + *opcode = buf.getOctet(); + *seq = buf.getLong(); + + return h1 == 'A' && h2 == 'M' && h3 == '2'; +} + +void Broker::received(qpid::client::Message& msg) +{ +#define QMF_HEADER_SIZE 8 + string data = msg.getData(); + Buffer inBuffer(const_cast<char*>(data.c_str()), data.size()); + uint8_t opcode; + uint32_t sequence; + + while (inBuffer.available() >= QMF_HEADER_SIZE) { + if (checkHeader(inBuffer, &opcode, &sequence)) { + QPID_LOG(trace, "Broker::received: opcode=" << opcode << " seq=" << sequence); + + if (opcode == 'b') sessionManager.handleBrokerResp(this, inBuffer, sequence); + else if (opcode == 'p') sessionManager.handlePackageInd(this, inBuffer, sequence); + else if (opcode == 'z') sessionManager.handleCommandComplete(this, inBuffer, sequence); + else if (opcode == 'q') sessionManager.handleClassInd(this, inBuffer, sequence); + else if (opcode == 'm') sessionManager.handleMethodResp(this, inBuffer, sequence); + else if (opcode == 'h') sessionManager.handleHeartbeatInd(this, inBuffer, sequence); + else if (opcode == 'e') sessionManager.handleEventInd(this, inBuffer, sequence); + else if (opcode == 's') sessionManager.handleSchemaResp(this, inBuffer, sequence); + else if (opcode == 'c') sessionManager.handleContentInd(this, inBuffer, sequence, true, false); + else if (opcode == 'i') sessionManager.handleContentInd(this, inBuffer, sequence, false, true); + else if (opcode == 'g') sessionManager.handleContentInd(this, inBuffer, sequence, true, true); + } else + return; + } +} + +void Broker::resetAgents() +{ + for (AgentMap::iterator iter = agents.begin(); iter != agents.end(); iter++) { + if (sessionManager.listener != 0) + sessionManager.listener->delAgent(*(iter->second)); + delete iter->second; + } + + agents.clear(); + agents[0x0000000100000000LL] = new Agent(this, 0, "BrokerAgent"); +} + +void Broker::updateAgent(const Object& object) +{ + uint32_t brokerBank = object.attrUint("brokerBank"); + uint32_t agentBank = object.attrUint("agentBank"); + uint64_t agentKey = ((uint64_t) brokerBank << 32) | (uint64_t) agentBank; + AgentMap::iterator iter = agents.find(agentKey); + + if (object.isDeleted()) { + if (iter != agents.end()) { + if (sessionManager.listener != 0) + sessionManager.listener->delAgent(*(iter->second)); + delete iter->second; + agents.erase(iter); + } + } else { + if (iter == agents.end()) { + Agent* agent = new Agent(this, agentBank, object.attrString("label")); + agents[agentKey] = agent; + if (sessionManager.listener != 0) + sessionManager.listener->newAgent(*agent); + } + } +} + +void Broker::ConnectionThread::run() +{ + static const int delayMin(1); + static const int delayMax(128); + static const int delayFactor(2); + int delay(delayMin); + string dest("qmfc"); + + sessionId.generate(); + queueName << "qmfc-" << sessionId; + + while (true) { + try { + broker.topicBound = false; + broker.reqsOutstanding = 1; + connection.open(broker.connectionSettings); + session = connection.newSession(queueName.str()); + subscriptions = new client::SubscriptionManager(session); + + session.queueDeclare(arg::queue=queueName.str(), arg::autoDelete=true, + arg::exclusive=true); + session.exchangeBind(arg::exchange="amq.direct", arg::queue=queueName.str(), + arg::bindingKey=queueName.str()); + + subscriptions->setAcceptMode(ACCEPT_MODE_NONE); + subscriptions->setAcquireMode(ACQUIRE_MODE_PRE_ACQUIRED); + subscriptions->subscribe(broker, queueName.str(), dest); + subscriptions->setFlowControl(dest, FlowControl::unlimited()); + { + Mutex::ScopedLock _lock(connLock); + if (shuttingDown) + return; + operational = true; + broker.resetAgents(); + broker.connected = true; + broker.sessionManager.handleBrokerConnect(&broker); + broker.sessionManager.startProtocol(&broker); + try { + Mutex::ScopedUnlock _unlock(connLock); + subscriptions->run(); + } catch (std::exception) {} + + operational = false; + broker.connected = false; + broker.sessionManager.handleBrokerDisconnect(&broker); + } + delay = delayMin; + connection.close(); + delete subscriptions; + subscriptions = 0; + } catch (std::exception &e) { + QPID_LOG(debug, " outer exception: " << e.what()); + if (delay < delayMax) + delay *= delayFactor; + } + + { + Mutex::ScopedLock _lock(connLock); + if (shuttingDown) + return; + { + Mutex::ScopedUnlock _unlock(connLock); + ::sleep(delay); + } + if (shuttingDown) + return; + } + } +} + +Broker::ConnectionThread::~ConnectionThread() +{ + if (subscriptions != 0) { + delete subscriptions; + } +} + +void Broker::ConnectionThread::sendBuffer(Buffer& buf, uint32_t length, + const string& exchange, const string& routingKey) +{ + { + Mutex::ScopedLock _lock(connLock); + if (!operational) + return; + } + + client::Message msg; + string data; + + buf.getRawData(data, length); + msg.getDeliveryProperties().setRoutingKey(routingKey); + msg.getMessageProperties().setReplyTo(ReplyTo("amq.direct", queueName.str())); + msg.setData(data); + try { + session.messageTransfer(arg::content=msg, arg::destination=exchange); + } catch(std::exception&) {} +} + +void Broker::ConnectionThread::bindExchange(const std::string& exchange, const std::string& key) +{ + { + Mutex::ScopedLock _lock(connLock); + if (!operational) + return; + } + + QPID_LOG(debug, "Broker::ConnectionThread::bindExchange: exchange=" << exchange << " key=" << key); + session.exchangeBind(arg::exchange=exchange, arg::queue=queueName.str(), + arg::bindingKey=key); +} + +void Broker::ConnectionThread::shutdown() +{ + { + Mutex::ScopedLock _lock(connLock); + shuttingDown = true; + } + if (subscriptions) + subscriptions->stop(); +} + +void Broker::waitForStable() +{ + Mutex::ScopedLock l(lock); + if (reqsOutstanding == 0) + return; + syncInFlight = true; + while (reqsOutstanding != 0) { + bool result = cond.wait(lock, AbsTime(now(), TIME_SEC * sessionManager.settings.getTimeout)); + if (!result) + throw(Exception("Timed out waiting for broker to synchronize")); + } +} + +void Broker::incOutstanding() +{ + Mutex::ScopedLock l(lock); + reqsOutstanding++; +} + +void Broker::decOutstanding() +{ + Mutex::ScopedLock l(lock); + reqsOutstanding--; + if (reqsOutstanding == 0) { + if (!topicBound) { + topicBound = true; + for (vector<string>::const_iterator iter = sessionManager.bindingKeyList.begin(); + iter != sessionManager.bindingKeyList.end(); iter++) + connThreadBody.bindExchange("qpid.management", *iter); + } + if (syncInFlight) { + syncInFlight = false; + cond.notify(); + } + } +} + +void Broker::appendAgents(Agent::Vector& agentlist) const +{ + for (AgentMap::const_iterator iter = agents.begin(); iter != agents.end(); iter++) { + agentlist.push_back(iter->second); + } +} + +ostream& qpid::console::operator<<(ostream& o, const Broker& k) +{ + o << "Broker: " << k.connectionSettings.host << ":" << k.connectionSettings.port; + return o; +} diff --git a/qpid/cpp/src/qpid/console/ClassKey.cpp b/qpid/cpp/src/qpid/console/ClassKey.cpp new file mode 100644 index 0000000000..7a16113bae --- /dev/null +++ b/qpid/cpp/src/qpid/console/ClassKey.cpp @@ -0,0 +1,105 @@ +/* + * + * 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/console/ClassKey.h" +#include <string.h> +#include <cstdio> + +using namespace std; +using namespace qpid::console; + +ClassKey::ClassKey(const string& _package, const string& _name, const uint8_t* _hash) : + package(_package), name(_name) +{ + ::memcpy(hash, _hash, HASH_SIZE); +} + +string ClassKey::getHashString() const +{ + char cstr[36]; + ::sprintf(cstr, "%02x%02x%02x%02x-%02x%02x%02x%02x-%02x%02x%02x%02x-%02x%02x%02x%02x", + hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7], + hash[8], hash[9], hash[10], hash[11], hash[12], hash[13], hash[14], hash[15]); + return string(cstr); +} + +string ClassKey::str() const +{ + string result(package + ":" + name + "(" + getHashString() + ")"); + return result; +} + +bool ClassKey::operator==(const ClassKey& other) const +{ + return ::memcmp(hash, other.hash, HASH_SIZE) == 0 && + name == other.name && + package == other.package; +} + +bool ClassKey::operator!=(const ClassKey& other) const +{ + return !(*this == other); +} + +bool ClassKey::operator<(const ClassKey& other) const +{ + int cmp = ::memcmp(hash, other.hash, HASH_SIZE); + if (cmp != 0) + return cmp < 0; + cmp = name.compare(other.name); + if (cmp != 0) + return cmp < 0; + return package < other.package; +} + +bool ClassKey::operator>(const ClassKey& other) const +{ + int cmp = ::memcmp(hash, other.hash, HASH_SIZE); + if (cmp != 0) + return cmp > 0; + cmp = name.compare(other.name); + if (cmp != 0) + return cmp > 0; + return package > other.package; +} + +bool ClassKey::operator<=(const ClassKey& other) const +{ + return !(*this > other); +} + +bool ClassKey::operator>=(const ClassKey& other) const +{ + return !(*this < other); +} + +void ClassKey::encode(qpid::framing::Buffer& buffer) const +{ + buffer.putShortString(package); + buffer.putShortString(name); + buffer.putBin128(const_cast<uint8_t*>(hash)); +} + +ostream& qpid::console::operator<<(ostream& o, const ClassKey& k) +{ + o << k.str(); + return o; +} diff --git a/qpid/cpp/src/qpid/console/Event.cpp b/qpid/cpp/src/qpid/console/Event.cpp new file mode 100644 index 0000000000..3e14804b35 --- /dev/null +++ b/qpid/cpp/src/qpid/console/Event.cpp @@ -0,0 +1,205 @@ +/* + * + * 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/console/Broker.h" +#include "qpid/console/ClassKey.h" +#include "qpid/console/Schema.h" +#include "qpid/console/Event.h" +#include "qpid/console/Value.h" +#include "qpid/sys/Time.h" +#include "qpid/framing/Buffer.h" + +using namespace qpid::console; +using namespace std; +using qpid::framing::Uuid; +using qpid::framing::FieldTable; + +Event::Event(Broker* _broker, SchemaClass* _schema, qpid::framing::Buffer& buffer) : + broker(_broker), schema(_schema) +{ + timestamp = buffer.getLongLong(); + severity = (Severity) buffer.getOctet(); + for (vector<SchemaArgument*>::const_iterator aIter = schema->arguments.begin(); + aIter != schema->arguments.end(); aIter++) { + SchemaArgument* argument = *aIter; + attributes[argument->name] = argument->decodeValue(buffer); + } +} + +const ClassKey& Event::getClassKey() const +{ + return schema->getClassKey(); +} + +string Event::getSeverityString() const +{ + switch (severity) { + case SEV_EMERGENCY : return string("EMER"); + case SEV_ALERT : return string("ALERT"); + case SEV_CRITICAL : return string("CRIT"); + case SEV_ERROR : return string("ERROR"); + case SEV_WARNING : return string("WARN"); + case SEV_NOTICE : return string("NOTIC"); + case SEV_INFO : return string("INFO"); + case SEV_DEBUG : return string("DEBUG"); + } + return string("<UNKNOWN>"); +} + +ObjectId Event::attrRef(const string& key) const +{ + Object::AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return ObjectId(); + Value::Ptr val = iter->second; + if (!val->isObjectId()) + return ObjectId(); + return val->asObjectId(); +} + +uint32_t Event::attrUint(const string& key) const +{ + Object::AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return 0; + Value::Ptr val = iter->second; + if (!val->isUint()) + return 0; + return val->asUint(); +} + +int32_t Event::attrInt(const string& key) const +{ + Object::AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return 0; + Value::Ptr val = iter->second; + if (!val->isInt()) + return 0; + return val->asInt(); +} + +uint64_t Event::attrUint64(const string& key) const +{ + Object::AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return 0; + Value::Ptr val = iter->second; + if (!val->isUint64()) + return 0; + return val->asUint64(); +} + +int64_t Event::attrInt64(const string& key) const +{ + Object::AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return 0; + Value::Ptr val = iter->second; + if (!val->isInt64()) + return 0; + return val->asInt64(); +} + +string Event::attrString(const string& key) const +{ + Object::AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return string(); + Value::Ptr val = iter->second; + if (!val->isString()) + return string(); + return val->asString(); +} + +bool Event::attrBool(const string& key) const +{ + Object::AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return false; + Value::Ptr val = iter->second; + if (!val->isBool()) + return false; + return val->asBool(); +} + +float Event::attrFloat(const string& key) const +{ + Object::AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return 0.0; + Value::Ptr val = iter->second; + if (!val->isFloat()) + return 0.0; + return val->asFloat(); +} + +double Event::attrDouble(const string& key) const +{ + Object::AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return 0.0; + Value::Ptr val = iter->second; + if (!val->isDouble()) + return 0.0; + return val->asDouble(); +} + +Uuid Event::attrUuid(const string& key) const +{ + Object::AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return Uuid(); + Value::Ptr val = iter->second; + if (!val->isUuid()) + return Uuid(); + return val->asUuid(); +} + +FieldTable Event::attrMap(const string& key) const +{ + Object::AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return FieldTable(); + Value::Ptr val = iter->second; + if (!val->isMap()) + return FieldTable(); + return val->asMap(); +} + + +std::ostream& qpid::console::operator<<(std::ostream& o, const Event& event) +{ + const ClassKey& key = event.getClassKey(); + sys::AbsTime aTime(sys::AbsTime(), sys::Duration(event.getTimestamp())); + o << aTime << " " << event.getSeverityString() << " " << + key.getPackageName() << ":" << key.getClassName() << + " broker=" << event.getBroker()->getUrl(); + + const Object::AttributeMap& attributes = event.getAttributes(); + for (Object::AttributeMap::const_iterator iter = attributes.begin(); + iter != attributes.end(); iter++) { + o << " " << iter->first << "=" << iter->second->str(); + } + return o; +} + + diff --git a/qpid/cpp/src/qpid/console/Object.cpp b/qpid/cpp/src/qpid/console/Object.cpp new file mode 100644 index 0000000000..6570e293ab --- /dev/null +++ b/qpid/cpp/src/qpid/console/Object.cpp @@ -0,0 +1,384 @@ +/* + * + * 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/console/SessionManager.h" +#include "qpid/console/Broker.h" +#include "qpid/console/Object.h" +#include "qpid/console/Schema.h" +#include "qpid/console/ClassKey.h" +#include "qpid/console/Value.h" +#include "qpid/framing/Buffer.h" +#include "qpid/sys/Mutex.h" + +using namespace qpid::console; +using namespace qpid::sys; +using namespace qpid; +using namespace std; +using qpid::framing::Uuid; +using qpid::framing::FieldTable; + +void Object::AttributeMap::addRef(const string& key, const ObjectId& val) +{ + (*this)[key] = Value::Ptr(new RefValue(val)); +} + +void Object::AttributeMap::addUint(const string& key, uint32_t val) +{ + (*this)[key] = Value::Ptr(new UintValue(val)); +} + +void Object::AttributeMap::addInt(const string& key, int32_t val) +{ + (*this)[key] = Value::Ptr(new IntValue(val)); +} + +void Object::AttributeMap::addUint64(const string& key, uint64_t val) +{ + (*this)[key] = Value::Ptr(new Uint64Value(val)); +} + +void Object::AttributeMap::addInt64(const string& key, int64_t val) +{ + (*this)[key] = Value::Ptr(new Int64Value(val)); +} + +void Object::AttributeMap::addString(const string& key, const string& val) +{ + (*this)[key] = Value::Ptr(new StringValue(val)); +} + +void Object::AttributeMap::addBool(const string& key, bool val) +{ + (*this)[key] = Value::Ptr(new BoolValue(val)); +} + +void Object::AttributeMap::addFloat(const string& key, float val) +{ + (*this)[key] = Value::Ptr(new FloatValue(val)); +} + +void Object::AttributeMap::addDouble(const string& key, double val) +{ + (*this)[key] = Value::Ptr(new DoubleValue(val)); +} + +void Object::AttributeMap::addUuid(const string& key, const Uuid& val) +{ + (*this)[key] = Value::Ptr(new UuidValue(val)); +} + +void Object::AttributeMap::addMap(const string& key, const FieldTable& val) +{ + (*this)[key] = Value::Ptr(new MapValue(val)); +} + +Object::Object(Broker* b, SchemaClass* s, framing::Buffer& buffer, bool prop, bool stat) : + broker(b), schema(s), pendingMethod(0) +{ + currentTime = buffer.getLongLong(); + createTime = buffer.getLongLong(); + deleteTime = buffer.getLongLong(); + objectId.decode(buffer); + + if (prop) { + set<string> excludes; + parsePresenceMasks(buffer, excludes); + for (vector<SchemaProperty*>::const_iterator pIter = schema->properties.begin(); + pIter != schema->properties.end(); pIter++) { + SchemaProperty* property = *pIter; + if (excludes.count(property->name) != 0) { + attributes[property->name] = Value::Ptr(new NullValue()); + } else { + attributes[property->name] = property->decodeValue(buffer); + } + } + } + + if (stat) { + for (vector<SchemaStatistic*>::const_iterator sIter = schema->statistics.begin(); + sIter != schema->statistics.end(); sIter++) { + SchemaStatistic* statistic = *sIter; + attributes[statistic->name] = statistic->decodeValue(buffer); + } + } +} + +Object::~Object() {} + +const ClassKey& Object::getClassKey() const +{ + return schema->getClassKey(); +} + +string Object::getIndex() const +{ + string result; + + for (vector<SchemaProperty*>::const_iterator pIter = schema->properties.begin(); + pIter != schema->properties.end(); pIter++) { + SchemaProperty* property = *pIter; + if (property->isIndex) { + AttributeMap::const_iterator vIter = attributes.find(property->name); + if (vIter != attributes.end()) { + if (!result.empty()) + result += ":"; + result += vIter->second->str(); + } + } + } + return result; +} + +void Object::mergeUpdate(const Object& /*updated*/) +{ + // TODO +} + +void Object::invokeMethod(const string name, const AttributeMap& args, MethodResponse& result) +{ + for (vector<SchemaMethod*>::const_iterator iter = schema->methods.begin(); + iter != schema->methods.end(); iter++) { + if ((*iter)->name == name) { + SchemaMethod* method = *iter; + char rawbuffer[65536]; + framing::Buffer buffer(rawbuffer, 65536); + uint32_t sequence = broker->sessionManager.sequenceManager.reserve("method"); + pendingMethod = method; + broker->methodObject = this; + broker->encodeHeader(buffer, 'M', sequence); + objectId.encode(buffer); + schema->key.encode(buffer); + buffer.putShortString(name); + + for (vector<SchemaArgument*>::const_iterator aIter = method->arguments.begin(); + aIter != method->arguments.end(); aIter++) { + SchemaArgument* arg = *aIter; + if (arg->dirInput) { + AttributeMap::const_iterator attr = args.find(arg->name); + if (attr != args.end()) { + ValueFactory::encodeValue(arg->typeCode, attr->second, buffer); + } else { + // TODO Use the default value instead of throwing + throw Exception("Missing arguments in method call"); + } + } + } + + uint32_t length = buffer.getPosition(); + buffer.reset(); + stringstream routingKey; + routingKey << "agent." << objectId.getBrokerBank() << "." << objectId.getAgentBank(); + broker->connThreadBody.sendBuffer(buffer, length, "qpid.management", routingKey.str()); + + { + Mutex::ScopedLock l(broker->lock); + bool ok = true; + while (pendingMethod != 0 && ok) { + ok = broker->cond.wait(broker->lock, AbsTime(now(), broker->sessionManager.settings.methodTimeout * TIME_SEC)); + } + + if (!ok) { + result.code = 0x1001; + result.text.assign("Method call timed out"); + result.arguments.clear(); + } else { + result = methodResponse; + } + } + } + } +} + +void Object::handleMethodResp(framing::Buffer& buffer, uint32_t sequence) +{ + broker->sessionManager.sequenceManager.release(sequence); + methodResponse.code = buffer.getLong(); + buffer.getMediumString(methodResponse.text); + methodResponse.arguments.clear(); + + for (vector<SchemaArgument*>::const_iterator aIter = pendingMethod->arguments.begin(); + aIter != pendingMethod->arguments.end(); aIter++) { + SchemaArgument* arg = *aIter; + if (arg->dirOutput) { + methodResponse.arguments[arg->name] = arg->decodeValue(buffer); + } + } + + { + Mutex::ScopedLock l(broker->lock); + pendingMethod = 0; + broker->cond.notify(); + } +} + +ObjectId Object::attrRef(const string& key) const +{ + AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return ObjectId(); + Value::Ptr val = iter->second; + if (!val->isObjectId()) + return ObjectId(); + return val->asObjectId(); +} + +uint32_t Object::attrUint(const string& key) const +{ + AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return 0; + Value::Ptr val = iter->second; + if (!val->isUint()) + return 0; + return val->asUint(); +} + +int32_t Object::attrInt(const string& key) const +{ + AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return 0; + Value::Ptr val = iter->second; + if (!val->isInt()) + return 0; + return val->asInt(); +} + +uint64_t Object::attrUint64(const string& key) const +{ + AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return 0; + Value::Ptr val = iter->second; + if (!val->isUint64()) + return 0; + return val->asUint64(); +} + +int64_t Object::attrInt64(const string& key) const +{ + AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return 0; + Value::Ptr val = iter->second; + if (!val->isInt64()) + return 0; + return val->asInt64(); +} + +string Object::attrString(const string& key) const +{ + AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return string(); + Value::Ptr val = iter->second; + if (!val->isString()) + return string(); + return val->asString(); +} + +bool Object::attrBool(const string& key) const +{ + AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return false; + Value::Ptr val = iter->second; + if (!val->isBool()) + return false; + return val->asBool(); +} + +float Object::attrFloat(const string& key) const +{ + AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return 0.0; + Value::Ptr val = iter->second; + if (!val->isFloat()) + return 0.0; + return val->asFloat(); +} + +double Object::attrDouble(const string& key) const +{ + AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return 0.0; + Value::Ptr val = iter->second; + if (!val->isDouble()) + return 0.0; + return val->asDouble(); +} + +Uuid Object::attrUuid(const string& key) const +{ + AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return Uuid(); + Value::Ptr val = iter->second; + if (!val->isUuid()) + return Uuid(); + return val->asUuid(); +} + +FieldTable Object::attrMap(const string& key) const +{ + AttributeMap::const_iterator iter = attributes.find(key); + if (iter == attributes.end()) + return FieldTable(); + Value::Ptr val = iter->second; + if (!val->isMap()) + return FieldTable(); + return val->asMap(); +} + +void Object::parsePresenceMasks(framing::Buffer& buffer, set<string>& excludeList) +{ + excludeList.clear(); + uint8_t bit = 0; + uint8_t mask = 0; + + for (vector<SchemaProperty*>::const_iterator pIter = schema->properties.begin(); + pIter != schema->properties.end(); pIter++) { + SchemaProperty* property = *pIter; + if (property->isOptional) { + if (bit == 0) { + mask = buffer.getOctet(); + bit = 1; + } + if ((mask & bit) == 0) + excludeList.insert(property->name); + if (bit == 0x80) + bit = 0; + else + bit = bit << 1; + } + } +} + +ostream& qpid::console::operator<<(ostream& o, const Object& object) +{ + const ClassKey& key = object.getClassKey(); + o << key.getPackageName() << ":" << key.getClassName() << "[" << object.getObjectId() << "] " << + object.getIndex(); + return o; +} + diff --git a/qpid/cpp/src/qpid/console/ObjectId.cpp b/qpid/cpp/src/qpid/console/ObjectId.cpp new file mode 100644 index 0000000000..fbaad20d57 --- /dev/null +++ b/qpid/cpp/src/qpid/console/ObjectId.cpp @@ -0,0 +1,91 @@ +/* + * + * 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/console/ObjectId.h" +#include "qpid/framing/Buffer.h" + +using namespace qpid::console; +using namespace qpid; +using namespace std; + +ObjectId::ObjectId(framing::Buffer& buffer) +{ + decode(buffer); +} + +void ObjectId::decode(framing::Buffer& buffer) +{ + first = buffer.getLongLong(); + second = buffer.getLongLong(); +} + +void ObjectId::encode(framing::Buffer& buffer) +{ + buffer.putLongLong(first); + buffer.putLongLong(second); +} + +bool ObjectId::operator==(const ObjectId& other) const +{ + return second == other.second && first == other.first; +} + +bool ObjectId::operator!=(const ObjectId& other) const +{ + return !(*this == other); +} + +bool ObjectId::operator<(const ObjectId& other) const +{ + if (first < other.first) + return true; + if (first > other.first) + return false; + return second < other.second; +} + +bool ObjectId::operator>(const ObjectId& other) const +{ + if (first > other.first) + return true; + if (first < other.first) + return false; + return second > other.second; +} + +bool ObjectId::operator<=(const ObjectId& other) const +{ + return !(*this > other); +} + +bool ObjectId::operator>=(const ObjectId& other) const +{ + return !(*this < other); +} + +ostream& qpid::console::operator<<(ostream& o, const ObjectId& id) +{ + o << (int) id.getFlags() << "-" << id.getSequence() << "-" << id.getBrokerBank() << "-" << + id.getAgentBank() << "-" << id.getObject(); + return o; +} + + diff --git a/qpid/cpp/src/qpid/console/Package.cpp b/qpid/cpp/src/qpid/console/Package.cpp new file mode 100644 index 0000000000..e5d6fa29fd --- /dev/null +++ b/qpid/cpp/src/qpid/console/Package.cpp @@ -0,0 +1,41 @@ +/* + * + * 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/console/Package.h" + +using namespace qpid::console; + +SchemaClass* Package::getClass(const std::string& className, uint8_t* hash) +{ + NameHash key(className, hash); + ClassMap::iterator iter = classes.find(key); + if (iter != classes.end()) + return iter->second; + return 0; +} + +void Package::addClass(const std::string& className, uint8_t* hash, SchemaClass* schemaClass) +{ + NameHash key(className, hash); + ClassMap::iterator iter = classes.find(key); + if (iter == classes.end()) + classes[key] = schemaClass; +} diff --git a/qpid/cpp/src/qpid/console/Schema.cpp b/qpid/cpp/src/qpid/console/Schema.cpp new file mode 100644 index 0000000000..a3dbd91201 --- /dev/null +++ b/qpid/cpp/src/qpid/console/Schema.cpp @@ -0,0 +1,165 @@ +/* + * + * 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/console/Schema.h" +#include "qpid/console/Value.h" +#include "qpid/framing/FieldTable.h" + +using namespace qpid::console; +using namespace qpid; +using std::string; +using std::vector; + +SchemaArgument::SchemaArgument(framing::Buffer& buffer, bool forMethod) +{ + framing::FieldTable map; + map.decode(buffer); + + name = map.getAsString("name"); + typeCode = map.getAsInt("type"); + unit = map.getAsString("unit"); + min = map.getAsInt("min"); + max = map.getAsInt("max"); + maxLen = map.getAsInt("maxlen"); + desc = map.getAsString("desc"); + + dirInput = false; + dirOutput = false; + if (forMethod) { + string dir(map.getAsString("dir")); + if (dir.find('I') != dir.npos || dir.find('i') != dir.npos) + dirInput = true; + if (dir.find('O') != dir.npos || dir.find('o') != dir.npos) + dirOutput = true; + } +} + +Value::Ptr SchemaArgument::decodeValue(framing::Buffer& buffer) +{ + return ValueFactory::newValue(typeCode, buffer); +} + +SchemaProperty::SchemaProperty(framing::Buffer& buffer) +{ + framing::FieldTable map; + map.decode(buffer); + + name = map.getAsString("name"); + typeCode = map.getAsInt("type"); + accessCode = map.getAsInt("access"); + isIndex = map.getAsInt("index") != 0; + isOptional = map.getAsInt("optional") != 0; + unit = map.getAsString("unit"); + min = map.getAsInt("min"); + max = map.getAsInt("max"); + maxLen = map.getAsInt("maxlen"); + desc = map.getAsString("desc"); +} + +Value::Ptr SchemaProperty::decodeValue(framing::Buffer& buffer) +{ + return ValueFactory::newValue(typeCode, buffer); +} + +SchemaStatistic::SchemaStatistic(framing::Buffer& buffer) +{ + framing::FieldTable map; + map.decode(buffer); + + name = map.getAsString("name"); + typeCode = map.getAsInt("type"); + unit = map.getAsString("unit"); + desc = map.getAsString("desc"); +} + +Value::Ptr SchemaStatistic::decodeValue(framing::Buffer& buffer) +{ + return ValueFactory::newValue(typeCode, buffer); +} + +SchemaMethod::SchemaMethod(framing::Buffer& buffer) +{ + framing::FieldTable map; + map.decode(buffer); + + name = map.getAsString("name"); + desc = map.getAsString("desc"); + int argCount = map.getAsInt("argCount"); + + for (int i = 0; i < argCount; i++) + arguments.push_back(new SchemaArgument(buffer, true)); +} + +SchemaMethod::~SchemaMethod() +{ + for (vector<SchemaArgument*>::iterator iter = arguments.begin(); + iter != arguments.end(); iter++) + delete *iter; +} + +SchemaClass::SchemaClass(const uint8_t _kind, const ClassKey& _key, framing::Buffer& buffer) : + kind(_kind), key(_key) +{ + if (kind == KIND_TABLE) { + uint8_t hasSupertype = 0; //buffer.getOctet(); + uint16_t propCount = buffer.getShort(); + uint16_t statCount = buffer.getShort(); + uint16_t methodCount = buffer.getShort(); + + if (hasSupertype) { + string unused; + buffer.getShortString(unused); + buffer.getShortString(unused); + buffer.getLongLong(); + buffer.getLongLong(); + } + + for (uint16_t idx = 0; idx < propCount; idx++) + properties.push_back(new SchemaProperty(buffer)); + for (uint16_t idx = 0; idx < statCount; idx++) + statistics.push_back(new SchemaStatistic(buffer)); + for (uint16_t idx = 0; idx < methodCount; idx++) + methods.push_back(new SchemaMethod(buffer)); + + } else if (kind == KIND_EVENT) { + uint16_t argCount = buffer.getShort(); + + for (uint16_t idx = 0; idx < argCount; idx++) + arguments.push_back(new SchemaArgument(buffer)); + } +} + +SchemaClass::~SchemaClass() +{ + for (vector<SchemaProperty*>::iterator iter = properties.begin(); + iter != properties.end(); iter++) + delete *iter; + for (vector<SchemaStatistic*>::iterator iter = statistics.begin(); + iter != statistics.end(); iter++) + delete *iter; + for (vector<SchemaMethod*>::iterator iter = methods.begin(); + iter != methods.end(); iter++) + delete *iter; + for (vector<SchemaArgument*>::iterator iter = arguments.begin(); + iter != arguments.end(); iter++) + delete *iter; +} + diff --git a/qpid/cpp/src/qpid/console/SequenceManager.cpp b/qpid/cpp/src/qpid/console/SequenceManager.cpp new file mode 100644 index 0000000000..86ea829749 --- /dev/null +++ b/qpid/cpp/src/qpid/console/SequenceManager.cpp @@ -0,0 +1,48 @@ +/* + * + * 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/console/SequenceManager.h" + +using namespace qpid::console; +using namespace qpid::sys; +using std::string; +using std::cout; +using std::endl; + +uint32_t SequenceManager::reserve(const std::string& context) +{ + Mutex::ScopedLock l(lock); + uint32_t result = sequence++; + pending[result] = context; + return result; +} + +std::string SequenceManager::release(uint32_t seq) +{ + Mutex::ScopedLock l(lock); + std::map<uint32_t, string>::iterator iter = pending.find(seq); + if (iter == pending.end()) + return string(); + string result(iter->second); + pending.erase(iter); + return result; +} + diff --git a/qpid/cpp/src/qpid/console/SessionManager.cpp b/qpid/cpp/src/qpid/console/SessionManager.cpp new file mode 100644 index 0000000000..80c5959417 --- /dev/null +++ b/qpid/cpp/src/qpid/console/SessionManager.cpp @@ -0,0 +1,517 @@ +/* + * + * 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/console/SessionManager.h" +#include "qpid/console/Schema.h" +#include "qpid/console/Agent.h" +#include "qpid/console/ConsoleListener.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Time.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/Uuid.h" +#include "qpid/framing/FieldTable.h" + +using namespace qpid::console; +using namespace qpid::sys; +using namespace qpid; +using namespace std; +using qpid::framing::Buffer; +using qpid::framing::FieldTable; + +SessionManager::SessionManager(ConsoleListener* _listener, Settings _settings) : + listener(_listener), settings(_settings) +{ + bindingKeys(); +} + +SessionManager::~SessionManager() +{ + for (vector<Broker*>::iterator iter = brokers.begin(); + iter != brokers.end(); iter++) + delete *iter; + + for (map<string, Package*>::iterator iter = packages.begin(); + iter != packages.end(); iter++) { + for (Package::ClassMap::iterator citer = iter->second->classes.begin(); + citer != iter->second->classes.end(); + citer++) + delete citer->second; + delete iter->second; + } +} + +Broker* SessionManager::addBroker(client::ConnectionSettings& settings) +{ + Broker* broker(new Broker(*this, settings)); + { + Mutex::ScopedLock l(brokerListLock); + brokers.push_back(broker); + } + return broker; +} + +void SessionManager::delBroker(Broker* broker) +{ + Mutex::ScopedLock l(brokerListLock); + for (vector<Broker*>::iterator iter = brokers.begin(); + iter != brokers.end(); iter++) + if (*iter == broker) { + brokers.erase(iter); + delete broker; + return; + } +} + +void SessionManager::getPackages(NameVector& packageNames) +{ + allBrokersStable(); + packageNames.clear(); + { + Mutex::ScopedLock l(lock); + for (map<string, Package*>::iterator iter = packages.begin(); + iter != packages.end(); iter++) + packageNames.push_back(iter->first); + } +} + +void SessionManager::getClasses(KeyVector& classKeys, const std::string& packageName) +{ + allBrokersStable(); + classKeys.clear(); + map<string, Package*>::iterator iter = packages.find(packageName); + if (iter == packages.end()) + return; + + Package& package = *(iter->second); + for (Package::ClassMap::const_iterator piter = package.classes.begin(); + piter != package.classes.end(); piter++) { + ClassKey key(piter->second->getClassKey()); + classKeys.push_back(key); + } +} + +SchemaClass& SessionManager::getSchema(const ClassKey& classKey) +{ + allBrokersStable(); + map<string, Package*>::iterator iter = packages.find(classKey.getPackageName()); + if (iter == packages.end()) + throw Exception("Unknown package"); + + Package& package = *(iter->second); + Package::NameHash key(classKey.getClassName(), classKey.getHash()); + Package::ClassMap::iterator cIter = package.classes.find(key); + if (cIter == package.classes.end()) + throw Exception("Unknown class"); + + return *(cIter->second); +} + +void SessionManager::bindPackage(const std::string& packageName) +{ + stringstream key; + key << "console.obj.*.*." << packageName << ".#"; + bindingKeyList.push_back(key.str()); + for (vector<Broker*>::iterator iter = brokers.begin(); iter != brokers.end(); iter++) + (*iter)->addBinding(key.str()); +} + +void SessionManager::bindClass(const ClassKey& classKey) +{ + bindClass(classKey.getPackageName(), classKey.getClassName()); +} + +void SessionManager::bindClass(const std::string& packageName, const std::string& className) +{ + stringstream key; + key << "console.obj.*.*." << packageName << "." << className << ".#"; + bindingKeyList.push_back(key.str()); + for (vector<Broker*>::iterator iter = brokers.begin(); + iter != brokers.end(); iter++) + (*iter)->addBinding(key.str()); +} + + +void SessionManager::bindEvent(const ClassKey& classKey) +{ + bindEvent(classKey.getPackageName(), classKey.getClassName()); +} + + +void SessionManager::bindEvent(const std::string& packageName, const std::string& eventName) +{ + if (!settings.userBindings) throw Exception("Session not configured for userBindings."); + if (settings.rcvEvents) throw Exception("Session already configured to receive all events."); + + stringstream key; + key << "console.event.*.*." << packageName; + if (eventName.length()) { + key << "." << eventName << ".#"; + } else { + key << ".#"; + } + + bindingKeyList.push_back(key.str()); + for (vector<Broker*>::iterator iter = brokers.begin(); + iter != brokers.end(); iter++) + (*iter)->addBinding(key.str()); +} + + +void SessionManager::getAgents(Agent::Vector& agents, Broker* broker) +{ + agents.clear(); + if (broker != 0) { + broker->appendAgents(agents); + } else { + for (vector<Broker*>::iterator iter = brokers.begin(); iter != brokers.end(); iter++) { + (*iter)->appendAgents(agents); + } + } +} + +void SessionManager::getObjects(Object::Vector& objects, const std::string& className, + Broker* _broker, Agent* _agent) +{ + Agent::Vector agentList; + + if (_agent != 0) { + agentList.push_back(_agent); + _agent->getBroker()->waitForStable(); + } else { + if (_broker != 0) { + _broker->appendAgents(agentList); + _broker->waitForStable(); + } else { + allBrokersStable(); + Mutex::ScopedLock _lock(brokerListLock); + for (vector<Broker*>::iterator iter = brokers.begin(); iter != brokers.end(); iter++) { + (*iter)->appendAgents(agentList); + } + } + } + + FieldTable ft; + uint32_t sequence; + ft.setString("_class", className); + + getResult.clear(); + syncSequenceList.clear(); + error = string(); + + if (agentList.empty()) { + objects = getResult; + return; + } + + for (Agent::Vector::iterator iter = agentList.begin(); iter != agentList.end(); iter++) { + Agent* agent = *iter; + char rawbuffer[512]; + Buffer buffer(rawbuffer, 512); + stringstream routingKey; + routingKey << "agent." << agent->getBrokerBank() << "." << agent->getAgentBank(); + { + Mutex::ScopedLock _lock(lock); + sequence = sequenceManager.reserve("multiget"); + syncSequenceList.insert(sequence); + } + agent->getBroker()->encodeHeader(buffer, 'G', sequence); + ft.encode(buffer); + uint32_t length = buffer.getPosition(); + buffer.reset(); + agent->getBroker()->connThreadBody.sendBuffer(buffer, length, "qpid.management", routingKey.str()); + } + + { + Mutex::ScopedLock _lock(lock); + sys::AbsTime startTime = sys::now(); + while (!syncSequenceList.empty() && error.empty()) { + cv.wait(lock, AbsTime(now(), settings.getTimeout * TIME_SEC)); + sys::AbsTime currTime = sys::now(); + if (sys::Duration(startTime, currTime) > settings.getTimeout * TIME_SEC) + break; + } + } + + objects = getResult; +} + +void SessionManager::bindingKeys() +{ + bindingKeyList.push_back("schema.#"); + if (settings.rcvObjects && settings.rcvEvents && settings.rcvHeartbeats && !settings.userBindings) { + bindingKeyList.push_back("console.#"); + } else { + if (settings.rcvObjects && !settings.userBindings) + bindingKeyList.push_back("console.obj.#"); + else + bindingKeyList.push_back("console.obj.*.*.org.apache.qpid.broker.agent"); + if (settings.rcvEvents) + bindingKeyList.push_back("console.event.#"); + if (settings.rcvHeartbeats) + bindingKeyList.push_back("console.heartbeat"); + } +} + +void SessionManager::allBrokersStable() +{ + Mutex::ScopedLock l(brokerListLock); + for (vector<Broker*>::iterator iter = brokers.begin(); + iter != brokers.end(); iter++) + if ((*iter)->isConnected()) + (*iter)->waitForStable(); +} + +void SessionManager::startProtocol(Broker* broker) +{ + char rawbuffer[512]; + Buffer buffer(rawbuffer, 512); + + broker->encodeHeader(buffer, 'B'); + uint32_t length = 512 - buffer.available(); + buffer.reset(); + broker->connThreadBody.sendBuffer(buffer, length); +} + + +void SessionManager::handleBrokerResp(Broker* broker, Buffer& inBuffer, uint32_t) +{ + framing::Uuid brokerId; + + brokerId.decode(inBuffer); + broker->setBrokerId(brokerId); + + char rawbuffer[512]; + Buffer buffer(rawbuffer, 512); + + uint32_t sequence = sequenceManager.reserve("startup"); + broker->encodeHeader(buffer, 'P', sequence); + uint32_t length = 512 - buffer.available(); + buffer.reset(); + broker->connThreadBody.sendBuffer(buffer, length); + + if (listener != 0) { + listener->brokerInfo(*broker); + } +} + +void SessionManager::handlePackageInd(Broker* broker, Buffer& inBuffer, uint32_t) +{ + string packageName; + inBuffer.getShortString(packageName); + + { + Mutex::ScopedLock l(lock); + map<string, Package*>::iterator iter = packages.find(packageName); + if (iter == packages.end()) { + packages[packageName] = new Package(packageName); + if (listener != 0) + listener->newPackage(packageName); + } + } + + broker->incOutstanding(); + char rawbuffer[512]; + Buffer buffer(rawbuffer, 512); + + uint32_t sequence = sequenceManager.reserve("startup"); + broker->encodeHeader(buffer, 'Q', sequence); + buffer.putShortString(packageName); + uint32_t length = 512 - buffer.available(); + buffer.reset(); + broker->connThreadBody.sendBuffer(buffer, length); +} + +void SessionManager::handleCommandComplete(Broker* broker, Buffer& inBuffer, uint32_t sequence) +{ + Mutex::ScopedLock l(lock); + uint32_t resultCode = inBuffer.getLong(); + string resultText; + inBuffer.getShortString(resultText); + string context = sequenceManager.release(sequence); + if (resultCode != 0) + QPID_LOG(debug, "Received error in completion: " << resultCode << " " << resultText); + if (context == "startup") { + broker->decOutstanding(); + } else if (context == "multiget") { + if (syncSequenceList.count(sequence) == 1) { + syncSequenceList.erase(sequence); + if (syncSequenceList.empty()) { + cv.notify(); + } + } + } + // TODO: Other context cases +} + +void SessionManager::handleClassInd(Broker* broker, Buffer& inBuffer, uint32_t) +{ + uint8_t kind; + string packageName; + string className; + uint8_t hash[16]; + + kind = inBuffer.getOctet(); + inBuffer.getShortString(packageName); + inBuffer.getShortString(className); + inBuffer.getBin128(hash); + + { + Mutex::ScopedLock l(lock); + map<string, Package*>::iterator pIter = packages.find(packageName); + if (pIter == packages.end() || pIter->second->getClass(className, hash)) + return; + } + + broker->incOutstanding(); + char rawbuffer[512]; + Buffer buffer(rawbuffer, 512); + + uint32_t sequence = sequenceManager.reserve("startup"); + broker->encodeHeader(buffer, 'S', sequence); + buffer.putShortString(packageName); + buffer.putShortString(className); + buffer.putBin128(hash); + uint32_t length = 512 - buffer.available(); + buffer.reset(); + broker->connThreadBody.sendBuffer(buffer, length); +} + +void SessionManager::handleMethodResp(Broker* broker, Buffer& buffer, uint32_t sequence) +{ + if (broker->methodObject) { + broker->methodObject->handleMethodResp(buffer, sequence); + } +} + +void SessionManager::handleHeartbeatInd(Broker* /*broker*/, Buffer& /*inBuffer*/, uint32_t /*sequence*/) +{ +} + +void SessionManager::handleEventInd(Broker* broker, Buffer& buffer, uint32_t /*sequence*/) +{ + string packageName; + string className; + uint8_t hash[16]; + SchemaClass* schemaClass; + + buffer.getShortString(packageName); + buffer.getShortString(className); + buffer.getBin128(hash); + + { + Mutex::ScopedLock l(lock); + map<string, Package*>::iterator pIter = packages.find(packageName); + if (pIter == packages.end()) + return; + schemaClass = pIter->second->getClass(className, hash); + if (schemaClass == 0) + return; + } + + Event event(broker, schemaClass, buffer); + + if (listener) + listener->event(event); +} + +void SessionManager::handleSchemaResp(Broker* broker, Buffer& inBuffer, uint32_t sequence) +{ + uint8_t kind; + string packageName; + string className; + uint8_t hash[16]; + + kind = inBuffer.getOctet(); + inBuffer.getShortString(packageName); + inBuffer.getShortString(className); + inBuffer.getBin128(hash); + + { + Mutex::ScopedLock l(lock); + map<string, Package*>::iterator pIter = packages.find(packageName); + if (pIter != packages.end() && !pIter->second->getClass(className, hash)) { + ClassKey key(packageName, className, hash); + SchemaClass* schemaClass(new SchemaClass(kind, key, inBuffer)); + pIter->second->addClass(className, hash, schemaClass); + if (listener != 0) { + listener->newClass(schemaClass->getClassKey()); + } + } + } + + sequenceManager.release(sequence); + broker->decOutstanding(); +} + +void SessionManager::handleContentInd(Broker* broker, Buffer& buffer, uint32_t sequence, bool prop, bool stat) +{ + string packageName; + string className; + uint8_t hash[16]; + SchemaClass* schemaClass; + + buffer.getShortString(packageName); + buffer.getShortString(className); + buffer.getBin128(hash); + + { + Mutex::ScopedLock l(lock); + map<string, Package*>::iterator pIter = packages.find(packageName); + if (pIter == packages.end()) + return; + schemaClass = pIter->second->getClass(className, hash); + if (schemaClass == 0) + return; + } + + Object object(broker, schemaClass, buffer, prop, stat); + + if (prop && className == "agent" && packageName == "org.apache.qpid.broker") + broker->updateAgent(object); + + { + Mutex::ScopedLock l(lock); + if (syncSequenceList.count(sequence) == 1) { + if (!object.isDeleted()) + getResult.push_back(object); + return; + } + } + + if (listener) { + if (prop) + listener->objectProps(*broker, object); + if (stat) + listener->objectStats(*broker, object); + } +} + +void SessionManager::handleBrokerConnect(Broker* broker) +{ + if (listener != 0) + listener->brokerConnected(*broker); +} + +void SessionManager::handleBrokerDisconnect(Broker* broker) +{ + if (listener != 0) + listener->brokerDisconnected(*broker); +} + diff --git a/qpid/cpp/src/qpid/console/Value.cpp b/qpid/cpp/src/qpid/console/Value.cpp new file mode 100644 index 0000000000..47c6a4ce57 --- /dev/null +++ b/qpid/cpp/src/qpid/console/Value.cpp @@ -0,0 +1,171 @@ +/* + * + * 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/console/Value.h" +#include "qpid/framing/Buffer.h" + +#include <sstream> + +using namespace qpid; +using namespace qpid::console; +using namespace std; + +string NullValue::str() const +{ + return "<Null>"; +} + +RefValue::RefValue(framing::Buffer& buffer) +{ + uint64_t first = buffer.getLongLong(); + uint64_t second = buffer.getLongLong(); + value.setValue(first, second); +} + +string RefValue::str() const +{ + stringstream s; + s << value; + return s.str(); +} + +string UintValue::str() const +{ + stringstream s; + s << value; + return s.str(); +} + +string IntValue::str() const +{ + stringstream s; + s << value; + return s.str(); +} + +string Uint64Value::str() const +{ + stringstream s; + s << value; + return s.str(); +} + +string Int64Value::str() const +{ + stringstream s; + s << value; + return s.str(); +} + +StringValue::StringValue(framing::Buffer& buffer, int tc) +{ + if (tc == 6) + buffer.getShortString(value); + else + buffer.getMediumString(value); +} + +string BoolValue::str() const +{ + return value ? "T" : "F"; +} + +string FloatValue::str() const +{ + stringstream s; + s << value; + return s.str(); +} + +string DoubleValue::str() const +{ + stringstream s; + s << value; + return s.str(); +} + +UuidValue::UuidValue(framing::Buffer& buffer) +{ + value.decode(buffer); +} + +string MapValue::str() const +{ + stringstream s; + s << value; + return s.str(); +} + +MapValue::MapValue(framing::Buffer& buffer) +{ + value.decode(buffer); +} + + +Value::Ptr ValueFactory::newValue(int typeCode, framing::Buffer& buffer) +{ + switch (typeCode) { + case 1: return Value::Ptr(new UintValue(buffer.getOctet())); // U8 + case 2: return Value::Ptr(new UintValue(buffer.getShort())); // U16 + case 3: return Value::Ptr(new UintValue(buffer.getLong())); // U32 + case 4: return Value::Ptr(new Uint64Value(buffer.getLongLong())); // U64 + case 6: return Value::Ptr(new StringValue(buffer, 6)); // SSTR + case 7: return Value::Ptr(new StringValue(buffer, 7)); // LSTR + case 8: return Value::Ptr(new Int64Value(buffer.getLongLong())); // ABSTIME + case 9: return Value::Ptr(new Uint64Value(buffer.getLongLong())); // DELTATIME + case 10: return Value::Ptr(new RefValue(buffer)); // REF + case 11: return Value::Ptr(new BoolValue(buffer.getOctet())); // BOOL + case 12: return Value::Ptr(new FloatValue(buffer.getFloat())); // FLOAT + case 13: return Value::Ptr(new DoubleValue(buffer.getDouble())); // DOUBLE + case 14: return Value::Ptr(new UuidValue(buffer)); // UUID + case 15: return Value::Ptr(new MapValue(buffer)); // MAP + case 16: return Value::Ptr(new IntValue(buffer.getOctet())); // S8 + case 17: return Value::Ptr(new IntValue(buffer.getShort())); // S16 + case 18: return Value::Ptr(new IntValue(buffer.getLong())); // S32 + case 19: return Value::Ptr(new Int64Value(buffer.getLongLong())); // S64 + } + + return Value::Ptr(); +} + +void ValueFactory::encodeValue(int typeCode, Value::Ptr value, framing::Buffer& buffer) +{ + switch (typeCode) { + case 1: buffer.putOctet(value->asUint()); return; // U8 + case 2: buffer.putShort(value->asUint()); return; // U16 + case 3: buffer.putLong(value->asUint()); return; // U32 + case 4: buffer.putLongLong(value->asUint64()); return; // U64 + case 6: buffer.putShortString(value->asString()); return; // SSTR + case 7: buffer.putMediumString(value->asString()); return; // LSTR + case 8: buffer.putLongLong(value->asInt64()); return; // ABSTIME + case 9: buffer.putLongLong(value->asUint64()); return; // DELTATIME + case 10: value->asObjectId().encode(buffer); return; // REF + case 11: buffer.putOctet(value->asBool() ? 1 : 0); return; // BOOL + case 12: buffer.putFloat(value->asFloat()); return; // FLOAT + case 13: buffer.putDouble(value->asDouble()); return; // DOUBLE + case 14: value->asUuid().encode(buffer); return; // UUID + case 15: value->asMap().encode(buffer); return; // MAP + case 16: buffer.putOctet(value->asInt()); return; // S8 + case 17: buffer.putShort(value->asInt()); return; // S16 + case 18: buffer.putLong(value->asInt()); return; // S32 + case 19: buffer.putLongLong(value->asInt64()); return; // S64 + } +} diff --git a/qpid/cpp/src/qpid/framing/AMQBody.cpp b/qpid/cpp/src/qpid/framing/AMQBody.cpp new file mode 100644 index 0000000000..b3eeae0615 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/AMQBody.cpp @@ -0,0 +1,64 @@ +/* + * + * 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/AMQBody.h" +#include "qpid/framing/AMQMethodBody.h" +#include "qpid/framing/AMQHeaderBody.h" +#include "qpid/framing/AMQContentBody.h" +#include "qpid/framing/AMQHeartbeatBody.h" +#include <iostream> + +namespace qpid { +namespace framing { + +std::ostream& operator<<(std::ostream& out, const AMQBody& body) +{ + body.print(out); + return out; +} + +AMQBody::~AMQBody() {} + +namespace { +struct MatchBodies : public AMQBodyConstVisitor { + const AMQBody& body; + bool match; + + MatchBodies(const AMQBody& b) : body(b), match(false) {} + virtual ~MatchBodies() {} + + virtual void visit(const AMQHeaderBody&) { match=dynamic_cast<const AMQHeaderBody*>(&body); } + virtual void visit(const AMQContentBody&) { match=dynamic_cast<const AMQContentBody*>(&body); } + virtual void visit(const AMQHeartbeatBody&) { match=dynamic_cast<const AMQHeartbeatBody*>(&body); } + virtual void visit(const AMQMethodBody& x) { + const AMQMethodBody* y=dynamic_cast<const AMQMethodBody*>(&body); + match = (y && y->amqpMethodId() == x.amqpMethodId() && y->amqpClassId() == x.amqpClassId()); + } +}; + +} +bool AMQBody::match(const AMQBody& a, const AMQBody& b) { + MatchBodies matcher(a); + b.accept(matcher); + return matcher.match; +} + +}} // namespace diff --git a/qpid/cpp/src/qpid/framing/AMQBody.h b/qpid/cpp/src/qpid/framing/AMQBody.h new file mode 100644 index 0000000000..56d1d250c1 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/AMQBody.h @@ -0,0 +1,86 @@ +#ifndef QPID_FRAMING_AMQBODY_H +#define QPID_FRAMING_AMQBODY_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/framing/amqp_types.h" +#include "qpid/RefCounted.h" +#include "qpid/framing/BodyFactory.h" +#include <boost/intrusive_ptr.hpp> +#include <ostream> +#include "qpid/CommonImportExport.h" + +namespace qpid { +namespace framing { + +class Buffer; + +class AMQMethodBody; +class AMQHeaderBody; +class AMQContentBody; +class AMQHeartbeatBody; + +struct AMQBodyConstVisitor { + virtual ~AMQBodyConstVisitor() {} + virtual void visit(const AMQHeaderBody&) = 0; + virtual void visit(const AMQContentBody&) = 0; + virtual void visit(const AMQHeartbeatBody&) = 0; + virtual void visit(const AMQMethodBody&) = 0; +}; + +class QPID_COMMON_CLASS_EXTERN AMQBody : public RefCounted { + public: + AMQBody() {} + QPID_COMMON_EXTERN virtual ~AMQBody(); + + // Make AMQBody copyable even though RefCounted. + AMQBody(const AMQBody&) : RefCounted() {} + AMQBody& operator=(const AMQBody&) { return *this; } + + virtual uint8_t type() const = 0; + + virtual void encode(Buffer& buffer) const = 0; + virtual void decode(Buffer& buffer, uint32_t=0) = 0; + virtual uint32_t encodedSize() const = 0; + + virtual void print(std::ostream& out) const = 0; + virtual void accept(AMQBodyConstVisitor&) const = 0; + + virtual AMQMethodBody* getMethod() { return 0; } + virtual const AMQMethodBody* getMethod() const { return 0; } + + /** Match if same type and same class/method ID for methods */ + static bool match(const AMQBody& , const AMQBody& ); + virtual boost::intrusive_ptr<AMQBody> clone() const = 0; +}; + +QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream& out, const AMQBody& body) ; + +enum BodyTypes { + METHOD_BODY = 1, + HEADER_BODY = 2, + CONTENT_BODY = 3, + HEARTBEAT_BODY = 8 +}; + +}} // namespace qpid::framing + +#endif /*!QPID_FRAMING_AMQBODY_H*/ diff --git a/qpid/cpp/src/qpid/framing/AMQCommandControlBody.h b/qpid/cpp/src/qpid/framing/AMQCommandControlBody.h new file mode 100644 index 0000000000..d12b70a168 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/AMQCommandControlBody.h @@ -0,0 +1,70 @@ +#ifndef QPID_FRAMING_AMQCOMMANDCONTROLBODY_H +#define QPID_FRAMING_AMQCOMMANDCONTROLBODY_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/amqp_0_10/helpers.h" +#include "qpid/framing/AMQBody.h" + +namespace qpid { +namespace framing { + +/** + * AMQBody wrapper for Command and Control. + * Temporary measure to fit with old code. + */ +template <class T> class AMQCommandControlBody : public AMQBody, public T +{ + public: + virtual uint8_t type() const { return 100+T::SEGMENT_TYPE; } + + virtual void encode(Buffer& buffer) const { + Codec::encode(buffer.getIterator(), static_cast<const T&>(*this)); + } + virtual void decode(Buffer& buffer, uint32_t=0) { + Codec::decode(buffer.getIterator(), static_cast<T&>(*this)); + } + virtual uint32_t encodedSize() const { + Codec::size(buffer.getIterator(), static_cast<const T&>(*this)); + } + + virtual void print(std::ostream& out) const { + out << static_cast<const T&>(*this) << endl; + } + virtual void AMQBody::accept(AMQBodyConstVisitor&) const { assert(0); } +}; + +class CommandBody : public AMQCommandControlBody<amqp_0_10::Command> { + using Command::accept; // Hide AMQBody::accept + virtual Command* getCommand() { return this; } + virtual const Command* getCommand() const { return this; } +}; + +class ControlBody : public AMQCommandControlBody<amqp_0_10::Control> { + using Control::accept; // Hide AMQBody::accept + virtual Control* getControl() { return this; } + virtual const Control* getControl() const { return this; } +}; + +}} // namespace qpid::framing + +#endif /*!QPID_FRAMING_AMQCOMMANDCONTROLBODY_H*/ diff --git a/qpid/cpp/src/qpid/framing/AMQContentBody.cpp b/qpid/cpp/src/qpid/framing/AMQContentBody.cpp new file mode 100644 index 0000000000..72f7d9978e --- /dev/null +++ b/qpid/cpp/src/qpid/framing/AMQContentBody.cpp @@ -0,0 +1,46 @@ +/* + * + * 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/AMQContentBody.h" +#include <iostream> + +qpid::framing::AMQContentBody::AMQContentBody(){ +} + +qpid::framing::AMQContentBody::AMQContentBody(const string& _data) : data(_data){ +} + +uint32_t qpid::framing::AMQContentBody::encodedSize() const{ + return data.size(); +} +void qpid::framing::AMQContentBody::encode(Buffer& buffer) const{ + buffer.putRawData(data); +} +void qpid::framing::AMQContentBody::decode(Buffer& buffer, uint32_t _size){ + buffer.getRawData(data, _size); +} + +void qpid::framing::AMQContentBody::print(std::ostream& out) const +{ + out << "content (" << encodedSize() << " bytes)"; + const size_t max = 32; + out << " " << data.substr(0, max); + if (data.size() > max) out << "..."; +} diff --git a/qpid/cpp/src/qpid/framing/AMQContentBody.h b/qpid/cpp/src/qpid/framing/AMQContentBody.h new file mode 100644 index 0000000000..e25451e354 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/AMQContentBody.h @@ -0,0 +1,55 @@ +/* + * + * 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/amqp_types.h" +#include "qpid/framing/AMQBody.h" +#include "qpid/framing/Buffer.h" +#include "qpid/CommonImportExport.h" + +#ifndef _AMQContentBody_ +#define _AMQContentBody_ + +namespace qpid { +namespace framing { + +class QPID_COMMON_CLASS_EXTERN AMQContentBody : public AMQBody +{ + string data; + +public: + QPID_COMMON_EXTERN AMQContentBody(); + QPID_COMMON_EXTERN AMQContentBody(const string& data); + inline virtual ~AMQContentBody(){} + 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; + void accept(AMQBodyConstVisitor& v) const { v.visit(*this); } + boost::intrusive_ptr<AMQBody> clone() const { return BodyFactory::copy(*this); } +}; + +} +} + + +#endif diff --git a/qpid/cpp/src/qpid/framing/AMQDataBlock.h b/qpid/cpp/src/qpid/framing/AMQDataBlock.h new file mode 100644 index 0000000000..7f0d0dc2b5 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/AMQDataBlock.h @@ -0,0 +1,42 @@ +/* + * + * 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/Buffer.h" + +#ifndef _AMQDataBlock_ +#define _AMQDataBlock_ + +namespace qpid { +namespace framing { + +class AMQDataBlock +{ +public: + virtual ~AMQDataBlock() {} + virtual void encode(Buffer& buffer) const = 0; + virtual bool decode(Buffer& buffer) = 0; + virtual uint32_t encodedSize() const = 0; +}; + +} +} + + +#endif diff --git a/qpid/cpp/src/qpid/framing/AMQFrame.cpp b/qpid/cpp/src/qpid/framing/AMQFrame.cpp new file mode 100644 index 0000000000..cd60cd971f --- /dev/null +++ b/qpid/cpp/src/qpid/framing/AMQFrame.cpp @@ -0,0 +1,153 @@ +/* + * + * 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/AMQFrame.h" + +#include "qpid/framing/AMQMethodBody.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/BodyFactory.h" +#include "qpid/framing/MethodBodyFactory.h" +#include "qpid/Msg.h" + +#include <boost/format.hpp> +#include <iostream> + +namespace qpid { +namespace framing { + +void AMQFrame::init() { bof = eof = bos = eos = true; subchannel=0; channel=0; } + +AMQFrame::AMQFrame(const boost::intrusive_ptr<AMQBody>& b) : body(b) { init(); } + +AMQFrame::AMQFrame(const AMQBody& b) : body(b.clone()) { init(); } + +AMQFrame::~AMQFrame() {} + +void AMQFrame::setMethod(ClassId c, MethodId m) { body = MethodBodyFactory::create(c,m); } + +uint32_t AMQFrame::encodedSize() const { + uint32_t size = frameOverhead() + body->encodedSize(); + if (body->getMethod()) + size += sizeof(ClassId)+sizeof(MethodId); + return size; +} + +uint32_t AMQFrame::frameOverhead() { + return 12 /*frame header*/; +} + +uint16_t AMQFrame::DECODE_SIZE_MIN=4; + +uint16_t AMQFrame::decodeSize(char* data) { + Buffer buf(data+2, DECODE_SIZE_MIN); + return buf.getShort(); +} + +void AMQFrame::encode(Buffer& buffer) const +{ + //set track first (controls on track 0, everything else on 1): + uint8_t track = getBody()->type() ? 1 : 0; + + uint8_t flags = (bof ? 0x08 : 0) | (eof ? 0x04 : 0) | (bos ? 0x02 : 0) | (eos ? 0x01 : 0); + buffer.putOctet(flags); + buffer.putOctet(getBody()->type()); + buffer.putShort(encodedSize()); + buffer.putOctet(0); + buffer.putOctet(0x0f & track); + buffer.putShort(channel); + buffer.putLong(0); + const AMQMethodBody* method=getMethod(); + if (method) { + buffer.putOctet(method->amqpClassId()); + buffer.putOctet(method->amqpMethodId()); + } + body->encode(buffer); +} + +bool AMQFrame::decode(Buffer& buffer) +{ + if(buffer.available() < frameOverhead()) + return false; + buffer.record(); + + uint8_t flags = buffer.getOctet(); + uint8_t framing_version = (flags & 0xc0) >> 6; + if (framing_version != 0) + throw FramingErrorException(QPID_MSG("Framing version unsupported")); + bof = flags & 0x08; + eof = flags & 0x04; + bos = flags & 0x02; + eos = flags & 0x01; + uint8_t type = buffer.getOctet(); + uint16_t frame_size = buffer.getShort(); + if (frame_size < frameOverhead()) + throw FramingErrorException(QPID_MSG("Frame size too small " << frame_size)); + uint8_t reserved1 = buffer.getOctet(); + uint8_t field1 = buffer.getOctet(); + subchannel = field1 & 0x0f; + channel = buffer.getShort(); + (void) buffer.getLong(); // reserved2 + + // Verify that the protocol header meets current spec + // TODO: should we check reserved2 against zero as well? - the + // spec isn't clear + if ((flags & 0x30) != 0 || reserved1 != 0 || (field1 & 0xf0) != 0) + throw FramingErrorException(QPID_MSG("Reserved bits not zero")); + + // TODO: should no longer care about body size and only pass up + // B,E,b,e flags + uint16_t body_size = frame_size - frameOverhead(); + if (buffer.available() < body_size){ + buffer.restore(); + return false; + } + + switch(type) + { + case 0://CONTROL + case METHOD_BODY: { + ClassId c = buffer.getOctet(); + MethodId m = buffer.getOctet(); + body = MethodBodyFactory::create(c, m); + break; + } + case HEADER_BODY: body = BodyFactory::create<AMQHeaderBody>(); break; + case CONTENT_BODY: body = BodyFactory::create<AMQContentBody>(); break; + case HEARTBEAT_BODY: body = BodyFactory::create<AMQHeartbeatBody>(); break; + default: + throw IllegalArgumentException(QPID_MSG("Invalid frame type " << type)); + } + body->decode(buffer, body_size); + + return true; +} + +std::ostream& operator<<(std::ostream& out, const AMQFrame& f) +{ + return + out << "Frame[" + << (f.getBof() ? "B" : "") << (f.getEof() ? "E" : "") + << (f.getBos() ? "b" : "") << (f.getEos() ? "e" : "") << "; " + << "channel=" << f.getChannel() << "; " << *f.getBody() + << "]"; +} + + +}} // namespace qpid::framing diff --git a/qpid/cpp/src/qpid/framing/AMQFrame.h b/qpid/cpp/src/qpid/framing/AMQFrame.h new file mode 100644 index 0000000000..c669d12bc0 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/AMQFrame.h @@ -0,0 +1,112 @@ +#ifndef _AMQFrame_ +#define _AMQFrame_ + +/* + * + * 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/AMQDataBlock.h" +#include "qpid/framing/AMQHeaderBody.h" +#include "qpid/framing/AMQContentBody.h" +#include "qpid/framing/AMQHeartbeatBody.h" +#include "qpid/framing/ProtocolVersion.h" +#include <boost/intrusive_ptr.hpp> +#include <boost/cast.hpp> +#include "qpid/CommonImportExport.h" + +namespace qpid { +namespace framing { + +class QPID_COMMON_CLASS_EXTERN AMQFrame : public AMQDataBlock +{ + public: + QPID_COMMON_EXTERN AMQFrame(const boost::intrusive_ptr<AMQBody>& b=0); + QPID_COMMON_EXTERN AMQFrame(const AMQBody& b); + QPID_COMMON_EXTERN ~AMQFrame(); + + ChannelId getChannel() const { return channel; } + void setChannel(ChannelId c) { channel = c; } + + AMQBody* getBody() { return body.get(); } + const AMQBody* getBody() const { return body.get(); } + + AMQMethodBody* getMethod() { return getBody() ? getBody()->getMethod() : 0; } + const AMQMethodBody* getMethod() const { return getBody() ? getBody()->getMethod() : 0; } + + void setMethod(ClassId c, MethodId m); + + template <class T> T* castBody() { + return boost::polymorphic_downcast<T*>(getBody()); + } + + template <class T> const T* castBody() const { + return boost::polymorphic_downcast<const T*>(getBody()); + } + + QPID_COMMON_EXTERN void encode(Buffer& buffer) const; + QPID_COMMON_EXTERN bool decode(Buffer& buffer); + QPID_COMMON_EXTERN uint32_t encodedSize() const; + + // 0-10 terminology: first/last frame (in segment) first/last segment (in assembly) + + bool isFirstSegment() const { return bof; } + bool isLastSegment() const { return eof; } + bool isFirstFrame() const { return bos; } + bool isLastFrame() const { return eos; } + + void setFirstSegment(bool set=true) { bof = set; } + void setLastSegment(bool set=true) { eof = set; } + void setFirstFrame(bool set=true) { bos = set; } + void setLastFrame(bool set=true) { eos = set; } + + // 0-9 terminology: beginning/end of frameset, beginning/end of segment. + + bool getBof() const { return bof; } + void setBof(bool isBof) { bof = isBof; } + bool getEof() const { return eof; } + void setEof(bool isEof) { eof = isEof; } + + bool getBos() const { return bos; } + void setBos(bool isBos) { bos = isBos; } + bool getEos() const { return eos; } + void setEos(bool isEos) { eos = isEos; } + + static uint16_t DECODE_SIZE_MIN; + QPID_COMMON_EXTERN static uint32_t frameOverhead(); + /** Must point to at least DECODE_SIZE_MIN bytes of data */ + static uint16_t decodeSize(char* data); + + private: + void init(); + + boost::intrusive_ptr<AMQBody> body; + uint16_t channel : 16; + uint8_t subchannel : 8; + bool bof : 1; + bool eof : 1; + bool bos : 1; + bool eos : 1; +}; + +QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream&, const AMQFrame&); + +}} // namespace qpid::framing + + +#endif diff --git a/qpid/cpp/src/qpid/framing/AMQHeaderBody.cpp b/qpid/cpp/src/qpid/framing/AMQHeaderBody.cpp new file mode 100644 index 0000000000..14218f1b45 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/AMQHeaderBody.cpp @@ -0,0 +1,63 @@ +/* + * + * 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/AMQHeaderBody.h" +#include "qpid/Exception.h" +#include "qpid/log/Statement.h" + +uint32_t qpid::framing::AMQHeaderBody::encodedSize() const { + return properties.encodedSize(); +} + +void qpid::framing::AMQHeaderBody::encode(Buffer& buffer) const { + properties.encode(buffer); +} + +void qpid::framing::AMQHeaderBody::decode(Buffer& buffer, uint32_t size) { + uint32_t limit = buffer.available() - size; + while (buffer.available() > limit + 2) { + uint32_t len = buffer.getLong(); + uint16_t type = buffer.getShort(); + if (!properties.decode(buffer, len, type)) { + // TODO: should just skip & keep for later dispatch. + throw Exception(QPID_MSG("Unexpected property type: " << type)); + } + } +} + +uint64_t qpid::framing::AMQHeaderBody::getContentLength() const +{ + const MessageProperties* mProps = get<MessageProperties>(); + if (mProps) + return mProps->getContentLength(); + return 0; +} + +void qpid::framing::AMQHeaderBody::print(std::ostream& out) const +{ + out << "header (" << encodedSize() << " bytes)"; + out << "; properties={"; + properties.print(out); + out << "}"; +} + +void qpid::framing::AMQHeaderBody::accept(AMQBodyConstVisitor& v) const { + v.visit(*this); +} diff --git a/qpid/cpp/src/qpid/framing/AMQHeaderBody.h b/qpid/cpp/src/qpid/framing/AMQHeaderBody.h new file mode 100644 index 0000000000..a8c326969a --- /dev/null +++ b/qpid/cpp/src/qpid/framing/AMQHeaderBody.h @@ -0,0 +1,109 @@ +#ifndef QPID_FRAMING_AMQHEADERBODY_H +#define QPID_FRAMING_AMQHEADERBODY_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/framing/amqp_types.h" +#include "qpid/framing/AMQBody.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/DeliveryProperties.h" +#include "qpid/framing/MessageProperties.h" +#include "qpid/CommonImportExport.h" +#include <iostream> + +#include <boost/optional.hpp> + + +namespace qpid { +namespace framing { + +class QPID_COMMON_CLASS_EXTERN AMQHeaderBody : public AMQBody +{ + template <class T> struct OptProps { boost::optional<T> props; }; + template <class Base, class T> + struct PropSet : public Base, public OptProps<T> { + uint32_t encodedSize() const { + const boost::optional<T>& p=this->OptProps<T>::props; + return (p ? p->encodedSize() : 0) + Base::encodedSize(); + } + void encode(Buffer& buffer) const { + const boost::optional<T>& p=this->OptProps<T>::props; + if (p) p->encode(buffer); + Base::encode(buffer); + } + bool decode(Buffer& buffer, uint32_t size, uint16_t type) { + boost::optional<T>& p=this->OptProps<T>::props; + if (type == T::TYPE) { + p=T(); + p->decodeStructBody(buffer, size); + return true; + } + 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; + Base::print(out); + } + }; + + struct Empty { + uint32_t encodedSize() const { return 0; } + void encode(Buffer&) const {}; + bool decode(Buffer&, uint32_t, uint16_t) const { return false; }; + void print(std::ostream&) const {} + }; + + // Could use boost::mpl::fold to construct a larger set. + typedef PropSet<PropSet<Empty, DeliveryProperties>, MessageProperties> Properties; + + Properties properties; + +public: + + inline uint8_t type() const { return HEADER_BODY; } + + 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 uint64_t getContentLength() const; + QPID_COMMON_EXTERN void print(std::ostream& out) const; + QPID_COMMON_EXTERN void accept(AMQBodyConstVisitor&) const; + + template <class T> T* get(bool create) { + boost::optional<T>& p=properties.OptProps<T>::props; + if (create && !p) p=T(); + return p.get_ptr(); + } + + template <class T> const T* get() const { + return properties.OptProps<T>::props.get_ptr(); + } + + boost::intrusive_ptr<AMQBody> clone() const { return BodyFactory::copy(*this); } +}; + +}} + + + +#endif /*!QPID_FRAMING_AMQHEADERBODY_H*/ diff --git a/qpid/cpp/src/qpid/framing/AMQHeartbeatBody.cpp b/qpid/cpp/src/qpid/framing/AMQHeartbeatBody.cpp new file mode 100644 index 0000000000..477616221c --- /dev/null +++ b/qpid/cpp/src/qpid/framing/AMQHeartbeatBody.cpp @@ -0,0 +1,29 @@ +/* + * + * 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/AMQHeartbeatBody.h" +#include <iostream> + +qpid::framing::AMQHeartbeatBody::~AMQHeartbeatBody() {} + +void qpid::framing::AMQHeartbeatBody::print(std::ostream& out) const { + out << "heartbeat"; +} diff --git a/qpid/cpp/src/qpid/framing/AMQHeartbeatBody.h b/qpid/cpp/src/qpid/framing/AMQHeartbeatBody.h new file mode 100644 index 0000000000..19ac2be013 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/AMQHeartbeatBody.h @@ -0,0 +1,48 @@ +/* + * + * 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/amqp_types.h" +#include "qpid/framing/AMQBody.h" +#include "qpid/framing/Buffer.h" +#include "qpid/CommonImportExport.h" + +#ifndef _AMQHeartbeatBody_ +#define _AMQHeartbeatBody_ + +namespace qpid { +namespace framing { + +class QPID_COMMON_CLASS_EXTERN AMQHeartbeatBody : public AMQBody +{ +public: + QPID_COMMON_EXTERN virtual ~AMQHeartbeatBody(); + inline uint32_t encodedSize() const { return 0; } + inline uint8_t type() const { return HEARTBEAT_BODY; } + inline void encode(Buffer& ) const {} + inline void decode(Buffer& , uint32_t /*size*/) {} + QPID_COMMON_EXTERN virtual void print(std::ostream& out) const; + void accept(AMQBodyConstVisitor& v) const { v.visit(*this); } + boost::intrusive_ptr<AMQBody> clone() const { return BodyFactory::copy(*this); } +}; + +} +} + +#endif diff --git a/qpid/cpp/src/qpid/framing/AMQMethodBody.cpp b/qpid/cpp/src/qpid/framing/AMQMethodBody.cpp new file mode 100644 index 0000000000..594af4c6dc --- /dev/null +++ b/qpid/cpp/src/qpid/framing/AMQMethodBody.cpp @@ -0,0 +1,28 @@ +/* + * + * 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/AMQMethodBody.h" + +namespace qpid { +namespace framing { + +AMQMethodBody::~AMQMethodBody() {} + +}} // namespace qpid::framing diff --git a/qpid/cpp/src/qpid/framing/AMQMethodBody.h b/qpid/cpp/src/qpid/framing/AMQMethodBody.h new file mode 100644 index 0000000000..c634180712 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/AMQMethodBody.h @@ -0,0 +1,72 @@ +#ifndef _AMQMethodBody_ +#define _AMQMethodBody_ + +/* + * + * 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/amqp_types.h" +#include "qpid/framing/AMQBody.h" +#include "qpid/framing/ProtocolVersion.h" +#include "qpid/CommonImportExport.h" + +#include <boost/shared_ptr.hpp> +#include <ostream> +#include <assert.h> + +namespace qpid { +namespace framing { + +class Buffer; +class AMQP_ServerOperations; +class MethodBodyConstVisitor; + +class AMQMethodBody : public AMQBody { + public: + AMQMethodBody() {} + QPID_COMMON_EXTERN virtual ~AMQMethodBody(); + + virtual void accept(MethodBodyConstVisitor&) const = 0; + + virtual MethodId amqpMethodId() const = 0; + virtual ClassId amqpClassId() const = 0; + virtual bool isContentBearing() const = 0; + virtual bool resultExpected() const = 0; + virtual bool responseExpected() const = 0; + + template <class T> bool isA() const { + return amqpClassId()==T::CLASS_ID && amqpMethodId()==T::METHOD_ID; + } + + virtual uint32_t encodedSize() const = 0; + virtual uint8_t type() const { return METHOD_BODY; } + + virtual bool isSync() const { return false; /*only ModelMethods can have the sync flag set*/ } + virtual void setSync(bool) const { /*only ModelMethods can have the sync flag set*/ } + + AMQMethodBody* getMethod() { return this; } + const AMQMethodBody* getMethod() const { return this; } + void accept(AMQBodyConstVisitor& v) const { v.visit(*this); } +}; + + +}} // namespace qpid::framing + + +#endif diff --git a/qpid/cpp/src/qpid/framing/AMQP_HighestVersion.h b/qpid/cpp/src/qpid/framing/AMQP_HighestVersion.h new file mode 100644 index 0000000000..42139c7937 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/AMQP_HighestVersion.h @@ -0,0 +1,40 @@ +/* + * + * 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. + * + */ + +/* + * This file used to be auto-generated by Qpid Gentools v.0.1 + * its here temporarily until we get a full solution to multi-version support + */ +#ifndef qpid_framing_highestProtocolVersion__ +#define qpid_framing_highestProtocolVersion__ + +#include "qpid/framing/ProtocolVersion.h" + + +namespace qpid { +namespace framing { + +static ProtocolVersion highestProtocolVersion(0, 10); + +} /* namespace framing */ +} /* namespace qpid */ + +#endif diff --git a/qpid/cpp/src/qpid/framing/AccumulatedAck.cpp b/qpid/cpp/src/qpid/framing/AccumulatedAck.cpp new file mode 100644 index 0000000000..2e6433a82f --- /dev/null +++ b/qpid/cpp/src/qpid/framing/AccumulatedAck.cpp @@ -0,0 +1,164 @@ +/* + * + * 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/AccumulatedAck.h" + +#include <assert.h> +#include <iostream> +#include <boost/bind.hpp> + +using std::list; +using std::max; +using std::min; +using namespace qpid::framing; + +AccumulatedAck::AccumulatedAck(SequenceNumber r) : mark(r) {} + +void AccumulatedAck::update(SequenceNumber first, SequenceNumber last){ + assert(first <= last); + if (last < mark) return; + + + Range r(first, last); + bool handled = false; + bool markMerged = false; + list<Range>::iterator merged = ranges.end(); + if (r.mergeable(mark)) { + mark = r.end; + markMerged = true; + handled = true; + } else { + for (list<Range>::iterator i = ranges.begin(); i != ranges.end() && !handled; i++) { + if (i->merge(r)) { + merged = i; + handled = true; + } else if (r.start < i->start) { + ranges.insert(i, r); + handled = true; + } + } + } + if (!handled) { + ranges.push_back(r); + } else { + while (!ranges.empty() && ranges.front().end <= mark) { + ranges.pop_front(); + } + if (markMerged) { + //new range is incorporated, but may be possible to consolidate + merged = ranges.begin(); + while (merged != ranges.end() && merged->mergeable(mark)) { + mark = merged->end; + merged = ranges.erase(merged); + } + } + if (merged != ranges.end()) { + //consolidate ranges + list<Range>::iterator i = merged; + list<Range>::iterator j = i++; + while (i != ranges.end() && j->merge(*i)) { + j = i++; + } + } + } +} + +void AccumulatedAck::consolidate(){} + +void AccumulatedAck::clear(){ + mark = SequenceNumber(0);//not sure that this is valid when wraparound is a possibility + ranges.clear(); +} + +bool AccumulatedAck::covers(SequenceNumber tag) const{ + if (tag <= mark) return true; + for (list<Range>::const_iterator i = ranges.begin(); i != ranges.end(); i++) { + if (i->contains(tag)) return true; + } + return false; +} + +void AccumulatedAck::collectRanges(SequenceNumberSet& set) const +{ + for (list<Range>::const_iterator i = ranges.begin(); i != ranges.end(); i++) { + set.push_back(i->start); + set.push_back(i->end); + } +} + +void AccumulatedAck::update(const SequenceNumber cumulative, const SequenceNumberSet& range) +{ + update(mark, cumulative); + range.processRanges(*this); +} + + +bool Range::contains(SequenceNumber i) const +{ + return i >= start && i <= end; +} + +bool Range::intersect(const Range& r) const +{ + return r.contains(start) || r.contains(end) || contains(r.start) || contains(r.end); +} + +bool Range::merge(const Range& r) +{ + if (intersect(r) || mergeable(r.end) || r.mergeable(end)) { + start = min(start, r.start); + end = max(end, r.end); + return true; + } else { + return false; + } +} + +bool Range::mergeable(const SequenceNumber& s) const +{ + if (contains(s) || start - s == 1) { + return true; + } else { + return false; + } +} + +Range::Range(SequenceNumber s, SequenceNumber e) : start(s), end(e) {} + + +namespace qpid{ +namespace framing{ + std::ostream& operator<<(std::ostream& out, const Range& r) + { + out << "[" << r.start.getValue() << "-" << r.end.getValue() << "]"; + return out; + } + + std::ostream& operator<<(std::ostream& out, const AccumulatedAck& a) + { + out << "{mark: " << a.mark.getValue() << ", ranges: ("; + for (list<Range>::const_iterator i = a.ranges.begin(); i != a.ranges.end(); i++) { + if (i != a.ranges.begin()) out << ", "; + out << *i; + } + out << ")]"; + return out; + } +}} diff --git a/qpid/cpp/src/qpid/framing/AccumulatedAck.h b/qpid/cpp/src/qpid/framing/AccumulatedAck.h new file mode 100644 index 0000000000..8e241b4ba1 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/AccumulatedAck.h @@ -0,0 +1,77 @@ +/* + * + * 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 _AccumulatedAck_ +#define _AccumulatedAck_ + +#include <algorithm> +#include <functional> +#include <list> +#include <ostream> +#include "qpid/framing/SequenceNumber.h" +#include "qpid/framing/SequenceNumberSet.h" +#include "qpid/CommonImportExport.h" + +namespace qpid { + namespace framing { + + struct Range + { + SequenceNumber start; + SequenceNumber end; + + Range(SequenceNumber s, SequenceNumber e); + bool contains(SequenceNumber i) const; + bool intersect(const Range& r) const; + bool merge(const Range& r); + bool mergeable(const SequenceNumber& r) const; + }; + /** + * Keeps an accumulated record of acknowledged messages (by delivery + * tag). + */ + class AccumulatedAck { + public: + /** + * Everything up to this value has been acknowledged. + */ + SequenceNumber mark; + /** + * List of individually acknowledged messages greater than the + * 'mark'. + */ + std::list<Range> ranges; + + QPID_COMMON_EXTERN explicit AccumulatedAck(SequenceNumber r = SequenceNumber()); + QPID_COMMON_EXTERN void update(SequenceNumber firstTag, SequenceNumber lastTag); + QPID_COMMON_EXTERN void consolidate(); + QPID_COMMON_EXTERN void clear(); + QPID_COMMON_EXTERN bool covers(SequenceNumber tag) const; + void collectRanges(SequenceNumberSet& set) const; + QPID_COMMON_EXTERN void update(const SequenceNumber cumulative, const SequenceNumberSet& range); + void operator()(SequenceNumber first, SequenceNumber last) { update(first, last); } + }; + QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream&, const Range&); + QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream&, const AccumulatedAck&); + } +} + + +#endif diff --git a/qpid/cpp/src/qpid/framing/Array.cpp b/qpid/cpp/src/qpid/framing/Array.cpp new file mode 100644 index 0000000000..454e8e298f --- /dev/null +++ b/qpid/cpp/src/qpid/framing/Array.cpp @@ -0,0 +1,131 @@ +/* + * + * 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/Array.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/FieldValue.h" +#include "qpid/Exception.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/Msg.h" +#include <assert.h> + +namespace qpid { +namespace framing { + +Array::Array() : type(TYPE_CODE_VOID) {} + +Array::Array(TypeCode t) : type(t) {} + +Array::Array(uint8_t t) : type(typeCode(t)) {} + +Array::Array(const std::vector<std::string>& in) +{ + type = TYPE_CODE_STR16; + for (std::vector<std::string>::const_iterator i = in.begin(); i != in.end(); ++i) { + ValuePtr value(new Str16Value(*i)); + values.push_back(value); + } +} + +uint32_t Array::encodedSize() const { + //note: size is only included when used as a 'top level' type + uint32_t len(4/*size*/ + 1/*type*/ + 4/*count*/); + for(ValueVector::const_iterator i = values.begin(); i != values.end(); ++i) { + len += (*i)->getData().encodedSize(); + } + return len; +} + +int Array::count() const { + return values.size(); +} + +std::ostream& operator<<(std::ostream& out, const Array& a) { + out << typeName(a.getType()) << "{"; + for(Array::ValueVector::const_iterator i = a.values.begin(); i != a.values.end(); ++i) { + if (i != a.values.begin()) out << ", "; + (*i)->print(out); + } + return out << "}"; +} + +void Array::encode(Buffer& buffer) const{ + buffer.putLong(encodedSize() - 4);//size added only when array is a top-level type + buffer.putOctet(type); + buffer.putLong(count()); + for (ValueVector::const_iterator i = values.begin(); i!=values.end(); ++i) { + (*i)->getData().encode(buffer); + } +} + +void Array::decode(Buffer& buffer){ + values.clear(); + uint32_t size = buffer.getLong();//size added only when array is a top-level type + uint32_t available = buffer.available(); + if (available < size) { + throw IllegalArgumentException(QPID_MSG("Not enough data for array, expected " + << size << " bytes but only " << available << " available")); + } + if (size) { + type = TypeCode(buffer.getOctet()); + uint32_t count = buffer.getLong(); + + FieldValue dummy; + dummy.setType(type); + available = buffer.available(); + if (available < count * dummy.getData().encodedSize()) { + throw IllegalArgumentException(QPID_MSG("Not enough data for array, expected " + << count << " items of " << dummy.getData().encodedSize() + << " bytes each but only " << available << " bytes available")); + } + + for (uint32_t i = 0; i < count; i++) { + ValuePtr value(new FieldValue); + value->setType(type); + value->getData().decode(buffer); + values.push_back(ValuePtr(value)); + } + } +} + + +bool Array::operator==(const Array& x) const { + if (type != x.type) return false; + if (values.size() != x.values.size()) return false; + + for (ValueVector::const_iterator i = values.begin(), j = x.values.begin(); i != values.end(); ++i, ++j) { + if (*(i->get()) != *(j->get())) return false; + } + + return true; +} + +void Array::insert(iterator i, ValuePtr value) { + if (type != value->getType()) { + // FIXME aconway 2008-10-31: put meaningful strings in this message. + throw Exception(QPID_MSG("Wrong type of value in Array, expected " << type + << " but found " << TypeCode(value->getType()))); + } + values.insert(i, value); +} + + +} +} diff --git a/qpid/cpp/src/qpid/framing/Blob.cpp b/qpid/cpp/src/qpid/framing/Blob.cpp new file mode 100644 index 0000000000..0c8316f3d2 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/Blob.cpp @@ -0,0 +1,31 @@ +/* + * 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/Blob.h" + + +namespace qpid { +namespace framing { + +void BlobHelper<void>::destroy(void*) {} + +void BlobHelper<void>::copy(void*, const void*) {} + +}} // namespace qpid::framing diff --git a/qpid/cpp/src/qpid/framing/Blob.h b/qpid/cpp/src/qpid/framing/Blob.h new file mode 100644 index 0000000000..9878d92fe4 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/Blob.h @@ -0,0 +1,21 @@ +/* + * + * 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. + * + */ + diff --git a/qpid/cpp/src/qpid/framing/BodyFactory.h b/qpid/cpp/src/qpid/framing/BodyFactory.h new file mode 100644 index 0000000000..6a8d9b1988 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/BodyFactory.h @@ -0,0 +1,47 @@ +#ifndef QPID_FRAMING_BODYFACTORY_H +#define QPID_FRAMING_BODYFACTORY_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 <boost/intrusive_ptr.hpp> + +namespace qpid { +namespace framing { + +/** + * Indirect creation of body types to allow centralized changes to + * memory management strategy. + */ +class BodyFactory { + public: + template <class BodyType> static boost::intrusive_ptr<BodyType> create() { + return new BodyType; + } + + template <class BodyType> static boost::intrusive_ptr<BodyType> copy(const BodyType& body) { + return new BodyType(body); + } +}; + +}} // namespace qpid::framing + +#endif /*!QPID_FRAMING_BODYFACTORY_H*/ diff --git a/qpid/cpp/src/qpid/framing/BodyHandler.cpp b/qpid/cpp/src/qpid/framing/BodyHandler.cpp new file mode 100644 index 0000000000..db302b1e4c --- /dev/null +++ b/qpid/cpp/src/qpid/framing/BodyHandler.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 "qpid/framing/BodyHandler.h" +#include "qpid/framing/AMQMethodBody.h" +#include "qpid/framing/AMQHeaderBody.h" +#include "qpid/framing/AMQContentBody.h" +#include "qpid/framing/AMQHeartbeatBody.h" +#include <boost/cast.hpp> +#include "qpid/framing/reply_exceptions.h" +#include "qpid/Msg.h" + +using namespace qpid::framing; +using namespace boost; + +BodyHandler::~BodyHandler() {} + +// TODO aconway 2007-08-13: Replace with visitor. +void BodyHandler::handleBody(AMQBody* body) { + switch(body->type()) + { + case METHOD_BODY: + handleMethod(polymorphic_downcast<AMQMethodBody*>(body)); + break; + case HEADER_BODY: + handleHeader(polymorphic_downcast<AMQHeaderBody*>(body)); + break; + case CONTENT_BODY: + handleContent(polymorphic_downcast<AMQContentBody*>(body)); + break; + case HEARTBEAT_BODY: + handleHeartbeat(polymorphic_downcast<AMQHeartbeatBody*>(body)); + break; + default: + throw FramingErrorException( + QPID_MSG("Invalid frame type " << body->type())); + } +} + diff --git a/qpid/cpp/src/qpid/framing/BodyHandler.h b/qpid/cpp/src/qpid/framing/BodyHandler.h new file mode 100644 index 0000000000..9ded737195 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/BodyHandler.h @@ -0,0 +1,56 @@ +#ifndef _BodyHandler_ +#define _BodyHandler_ + +/* + * + * 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/shared_ptr.hpp> + +namespace qpid { +namespace framing { +class AMQBody; +class AMQMethodBody; +class AMQHeaderBody; +class AMQContentBody; +class AMQHeartbeatBody; + +// TODO aconway 2007-08-10: rework using Visitor pattern? + +/** + * Interface to handle incoming frame bodies. + * Derived classes provide logic for each frame type. + */ +class BodyHandler { + public: + virtual ~BodyHandler(); + virtual void handleBody(AMQBody* body); + + protected: + virtual void handleMethod(AMQMethodBody*) = 0; + virtual void handleHeader(AMQHeaderBody*) = 0; + virtual void handleContent(AMQContentBody*) = 0; + virtual void handleHeartbeat(AMQHeartbeatBody*) = 0; +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/framing/Buffer.cpp b/qpid/cpp/src/qpid/framing/Buffer.cpp new file mode 100644 index 0000000000..5a5bc0325e --- /dev/null +++ b/qpid/cpp/src/qpid/framing/Buffer.cpp @@ -0,0 +1,345 @@ +/* + * + * 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/Buffer.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/Msg.h" +#include <string.h> +#include <boost/format.hpp> +namespace qpid { + +namespace framing { + +Buffer::Buffer(char* _data, uint32_t _size) + : size(_size), data(_data), position(0) { +} + +void Buffer::record(){ + r_position = position; +} + +void Buffer::restore(bool reRecord){ + uint32_t savedPosition = position; + + position = r_position; + + if (reRecord) + r_position = savedPosition; +} + +void Buffer::reset(){ + position = 0; +} + +/////////////////////////////////////////////////// + +void Buffer::putOctet(uint8_t i){ + data[position++] = i; + assert(position <= size); +} + +void Buffer::putShort(uint16_t i){ + uint16_t b = i; + data[position++] = (uint8_t) (0xFF & (b >> 8)); + data[position++] = (uint8_t) (0xFF & b); + assert(position <= size); +} + +void Buffer::putLong(uint32_t i){ + uint32_t b = i; + data[position++] = (uint8_t) (0xFF & (b >> 24)); + data[position++] = (uint8_t) (0xFF & (b >> 16)); + data[position++] = (uint8_t) (0xFF & (b >> 8)); + data[position++] = (uint8_t) (0xFF & b); + assert(position <= size); +} + +void Buffer::putLongLong(uint64_t i){ + uint32_t hi = i >> 32; + uint32_t lo = i; + putLong(hi); + putLong(lo); +} + +void Buffer::putInt8(int8_t i){ + data[position++] = (uint8_t) i; + assert(position <= size); +} + +void Buffer::putInt16(int16_t i){ + putShort((uint16_t) i); +} + +void Buffer::putInt32(int32_t i){ + putLong((uint32_t) i); +} + +void Buffer::putInt64(int64_t i){ + putLongLong((uint64_t) i); +} + +void Buffer::putFloat(float f){ + union { + uint32_t i; + float f; + } val; + + val.f = f; + putLong (val.i); +} + +void Buffer::putDouble(double f){ + union { + uint64_t i; + double f; + } val; + + val.f = f; + putLongLong (val.i); +} + +void Buffer::putBin128(const uint8_t* b){ + memcpy (data + position, b, 16); + position += 16; +} + +uint8_t Buffer::getOctet(){ + uint8_t octet = static_cast<uint8_t>(data[position++]); + assert(position <= size); + return octet; +} + +uint16_t Buffer::getShort(){ + uint16_t hi = (unsigned char) data[position++]; + hi = hi << 8; + hi |= (unsigned char) data[position++]; + assert(position <= size); + return hi; +} + +uint32_t Buffer::getLong(){ + uint32_t a = (unsigned char) data[position++]; + uint32_t b = (unsigned char) data[position++]; + uint32_t c = (unsigned char) data[position++]; + uint32_t d = (unsigned char) data[position++]; + assert(position <= size); + a = a << 24; + a |= b << 16; + a |= c << 8; + a |= d; + return a; +} + +uint64_t Buffer::getLongLong(){ + uint64_t hi = getLong(); + uint64_t lo = getLong(); + hi = hi << 32; + return hi | lo; +} + +int8_t Buffer::getInt8(){ + int8_t i = static_cast<int8_t>(data[position++]); + assert(position <= size); + return i; +} + +int16_t Buffer::getInt16(){ + return (int16_t) getShort(); +} + +int32_t Buffer::getInt32(){ + return (int32_t) getLong(); +} + +int64_t Buffer::getInt64(){ + return (int64_t) getLongLong(); +} + +float Buffer::getFloat(){ + union { + uint32_t i; + float f; + } val; + val.i = getLong(); + return val.f; +} + +double Buffer::getDouble(){ + union { + uint64_t i; + double f; + } val; + val.i = getLongLong(); + return val.f; +} + +template <> +uint64_t Buffer::getUInt<1>() { + return getOctet(); +} + +template <> +uint64_t Buffer::getUInt<2>() { + return getShort(); +} + +template <> +uint64_t Buffer::getUInt<4>() { + return getLong(); +} + +template <> +uint64_t Buffer::getUInt<8>() { + return getLongLong(); +} + +template <> +void Buffer::putUInt<1>(uint64_t i) { + if (std::numeric_limits<uint8_t>::min() <= i && i <= std::numeric_limits<uint8_t>::max()) { + putOctet(i); + return; + } + throw Exception(QPID_MSG("Could not encode (" << i << ") as uint8_t.")); +} + +template <> +void Buffer::putUInt<2>(uint64_t i) { + if (std::numeric_limits<uint16_t>::min() <= i && i <= std::numeric_limits<uint16_t>::max()) { + putShort(i); + return; + } + throw Exception(QPID_MSG("Could not encode (" << i << ") as uint16_t.")); +} + +template <> +void Buffer::putUInt<4>(uint64_t i) { + if (std::numeric_limits<uint32_t>::min() <= i && i <= std::numeric_limits<uint32_t>::max()) { + putLong(i); + return; + } + throw Exception(QPID_MSG("Could not encode (" << i << ") as uint32_t.")); +} + +template <> +void Buffer::putUInt<8>(uint64_t i) { + putLongLong(i); +} + +void Buffer::putShortString(const string& s){ + size_t slen = s.length(); + if (slen <= std::numeric_limits<uint8_t>::max()) { + uint8_t len = (uint8_t) slen; + checkAvailable(slen + 1); + putOctet(len); + s.copy(data + position, len); + position += len; + return; + } + throw Exception(QPID_MSG("Could not encode string of " << slen << " bytes as uint8_t string.")); +} + +void Buffer::putMediumString(const string& s){ + size_t slen = s.length(); + if (slen <= std::numeric_limits<uint16_t>::max()) { + uint16_t len = (uint16_t) slen; + checkAvailable(slen + 2); + putShort(len); + s.copy(data + position, len); + position += len; + return; + } + throw Exception(QPID_MSG("Could not encode string of " << slen << " bytes as uint16_t string.")); +} + +void Buffer::putLongString(const string& s){ + uint32_t len = s.length(); + checkAvailable(len + 4); + putLong(len); + s.copy(data + position, len); + position += len; +} + +void Buffer::getShortString(string& s){ + uint8_t len = getOctet(); + checkAvailable(len); + s.assign(data + position, len); + position += len; +} + +void Buffer::getMediumString(string& s){ + uint16_t len = getShort(); + checkAvailable(len); + s.assign(data + position, len); + position += len; +} + +void Buffer::getLongString(string& s){ + uint32_t len = getLong(); + checkAvailable(len); + s.assign(data + position, len); + position += len; +} + +void Buffer::getBin128(uint8_t* b){ + memcpy (b, data + position, 16); + position += 16; +} + +void Buffer::putRawData(const string& s){ + uint32_t len = s.length(); + checkAvailable(len); + s.copy(data + position, len); + position += len; +} + +void Buffer::getRawData(string& s, uint32_t len){ + checkAvailable(len); + s.assign(data + position, len); + position += len; +} + +void Buffer::putRawData(const uint8_t* s, size_t len){ + checkAvailable(len); + memcpy(data + position, s, len); + position += len; +} + +void Buffer::getRawData(uint8_t* s, size_t len){ + checkAvailable(len); + memcpy(s, data + position, len); + position += len; +} + +void Buffer::dump(std::ostream& out) const { + for (uint32_t i = position; i < size; i++) + { + if (i != position) + out << " "; + out << boost::format("%02x") % ((unsigned) (uint8_t) data[i]); + } +} + +std::ostream& operator<<(std::ostream& out, const Buffer& b){ + out << "Buffer["; + b.dump(out); + return out << "]"; +} + +}} diff --git a/qpid/cpp/src/qpid/framing/ChannelHandler.h b/qpid/cpp/src/qpid/framing/ChannelHandler.h new file mode 100644 index 0000000000..ddab204578 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/ChannelHandler.h @@ -0,0 +1,53 @@ +#ifndef QPID_FRAMING_CHANNELHANDLER_H +#define QPID_FRAMING_CHANNELHANDLER_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/framing/FrameHandler.h" +#include "qpid/framing/AMQFrame.h" + +namespace qpid { +namespace framing { + +/** + * Sets the channel number on outgoing frames. + */ +class ChannelHandler : public FrameHandler +{ + public: + ChannelHandler(uint16_t channelId=0, FrameHandler* next=0) + : FrameHandler(next), channel(channelId) {} + void handle(AMQFrame& frame) { + frame.setChannel(channel); + next->handle(frame); + } + uint16_t get() const { return channel; } + ChannelHandler& set(uint16_t ch) { channel=ch; return *this; } + operator uint16_t() const { return get(); } + ChannelHandler& operator=(uint16_t ch) { return set(ch); } + + private: + uint16_t channel; +}; + +}} // namespace qpid::framing + +#endif /*!QPID_FRAMING_CHANNELHANDLER_H*/ diff --git a/qpid/cpp/src/qpid/framing/Endian.cpp b/qpid/cpp/src/qpid/framing/Endian.cpp new file mode 100644 index 0000000000..5acc3c459f --- /dev/null +++ b/qpid/cpp/src/qpid/framing/Endian.cpp @@ -0,0 +1,52 @@ +/* + * + * 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/Endian.h" + +namespace qpid { +namespace framing { + +Endian::Endian() : littleEndian(!testBigEndian()) {} + +bool Endian::testBigEndian() +{ + uint16_t a = 1; + uint16_t b; + uint8_t* p = (uint8_t*) &b; + p[0] = 0xFF & (a >> 8); + p[1] = 0xFF & (a); + return a == b; +} + +uint8_t* Endian::convertIfRequired(uint8_t* const octets, int width) +{ + if (instance.littleEndian) { + for (int i = 0; i < (width/2); i++) { + uint8_t temp = octets[i]; + octets[i] = octets[width - (1 + i)]; + octets[width - (1 + i)] = temp; + } + } + return octets; +} + +const Endian Endian::instance; + +}} // namespace qpid::framing diff --git a/qpid/cpp/src/qpid/framing/Endian.h b/qpid/cpp/src/qpid/framing/Endian.h new file mode 100644 index 0000000000..077d5a3e9b --- /dev/null +++ b/qpid/cpp/src/qpid/framing/Endian.h @@ -0,0 +1,46 @@ +#ifndef QPID_FRAMING_ENDIAN_H +#define QPID_FRAMING_ENDIAN_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/IntegerTypes.h" + +namespace qpid { +namespace framing { + +/** + * Conversion utility for little-endian platforms that need to convert + * to and from network ordered octet sequences + */ +class Endian +{ + public: + static uint8_t* convertIfRequired(uint8_t* const octets, int width); + private: + const bool littleEndian; + Endian(); + static const Endian instance; + static bool testBigEndian(); +}; +}} // namespace qpid::framing + +#endif /*!QPID_FRAMING_ENDIAN_H*/ diff --git a/qpid/cpp/src/qpid/framing/FieldTable.cpp b/qpid/cpp/src/qpid/framing/FieldTable.cpp new file mode 100644 index 0000000000..21eaea0f4d --- /dev/null +++ b/qpid/cpp/src/qpid/framing/FieldTable.cpp @@ -0,0 +1,247 @@ +/* + * + * 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/framing/Array.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/Endian.h" +#include "qpid/framing/FieldValue.h" +#include "qpid/Exception.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/Msg.h" +#include <assert.h> + +namespace qpid { +namespace framing { + +FieldTable::FieldTable(const FieldTable& ft) +{ + *this = ft; +} + +FieldTable& FieldTable::operator=(const FieldTable& ft) +{ + clear(); + values = ft.values; + return *this; +} + +FieldTable::~FieldTable() {} + +uint32_t FieldTable::encodedSize() const { + uint32_t len(4/*size field*/ + 4/*count field*/); + for(ValueMap::const_iterator i = values.begin(); i != values.end(); ++i) { + // shortstr_len_byte + key size + value size + len += 1 + (i->first).size() + (i->second)->encodedSize(); + } + return len; +} + +int FieldTable::count() const { + return values.size(); +} + +namespace +{ +std::ostream& operator<<(std::ostream& out, const FieldTable::ValueMap::value_type& i) { + return out << i.first << ":" << *i.second; +} +} + +std::ostream& operator<<(std::ostream& out, const FieldTable& t) { + out << "{"; + FieldTable::ValueMap::const_iterator i = t.begin(); + if (i != t.end()) out << *i++; + while (i != t.end()) + { + out << "," << *i++; + } + return out << "}"; +} + +void FieldTable::set(const std::string& name, const ValuePtr& value){ + values[name] = value; +} + +void FieldTable::setString(const std::string& name, const std::string& value){ + values[name] = ValuePtr(new Str16Value(value)); +} + +void FieldTable::setInt(const std::string& name, const int value){ + values[name] = ValuePtr(new IntegerValue(value)); +} + +void FieldTable::setInt64(const std::string& name, const int64_t value){ + values[name] = ValuePtr(new Integer64Value(value)); +} + +void FieldTable::setTimestamp(const std::string& name, const uint64_t value){ + values[name] = ValuePtr(new TimeValue(value)); +} + +void FieldTable::setUInt64(const std::string& name, const uint64_t value){ + values[name] = ValuePtr(new Unsigned64Value(value)); +} + +void FieldTable::setTable(const std::string& name, const FieldTable& value) +{ + values[name] = ValuePtr(new FieldTableValue(value)); +} +void FieldTable::setArray(const std::string& name, const Array& value) +{ + values[name] = ValuePtr(new ArrayValue(value)); +} + +void FieldTable::setFloat(const std::string& name, const float value){ + values[name] = ValuePtr(new FloatValue(value)); +} + +void FieldTable::setDouble(const std::string& name, double value){ + values[name] = ValuePtr(new DoubleValue(value)); +} + +FieldTable::ValuePtr FieldTable::get(const std::string& name) const +{ + ValuePtr value; + ValueMap::const_iterator i = values.find(name); + if ( i!=values.end() ) + value = i->second; + return value; +} + +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 <class T> +T getValue(const FieldTable::ValuePtr value) +{ + if (!value || !value->convertsTo<T>()) + return default_value<T>(); + + return value->get<T>(); +} + +std::string FieldTable::getAsString(const std::string& name) const { + return getValue<std::string>(get(name)); +} + +int FieldTable::getAsInt(const std::string& name) const { + return getValue<int>(get(name)); +} + +uint64_t FieldTable::getAsUInt64(const std::string& name) const { + return static_cast<uint64_t>( getValue<int64_t>(get(name))); +} + +int64_t FieldTable::getAsInt64(const std::string& name) const { + return getValue<int64_t>(get(name)); +} + +bool FieldTable::getTable(const std::string& name, FieldTable& value) const { + return getEncodedValue<FieldTable>(get(name), value); +} + +bool FieldTable::getArray(const std::string& name, Array& value) const { + return getEncodedValue<Array>(get(name), value); +} + +template <class T, int width, uint8_t typecode> +bool getRawFixedWidthValue(FieldTable::ValuePtr vptr, T& value) +{ + if (vptr && vptr->getType() == typecode) { + value = vptr->get<T>(); + return true; + } + return false; +} + +bool FieldTable::getFloat(const std::string& name, float& value) const { + return getRawFixedWidthValue<float, 4, 0x23>(get(name), value); +} + +bool FieldTable::getDouble(const std::string& name, double& value) const { + return getRawFixedWidthValue<double, 8, 0x33>(get(name), value); +} + +//uint64_t FieldTable::getTimestamp(const std::string& name) const { +// return getValue<uint64_t>(name); +//} + +void FieldTable::encode(Buffer& buffer) const { + buffer.putLong(encodedSize() - 4); + buffer.putLong(values.size()); + for (ValueMap::const_iterator i = values.begin(); i!=values.end(); ++i) { + buffer.putShortString(i->first); + i->second->encode(buffer); + } +} + +void FieldTable::decode(Buffer& buffer){ + clear(); + uint32_t len = buffer.getLong(); + if (len) { + uint32_t available = buffer.available(); + if (available < len) + throw IllegalArgumentException(QPID_MSG("Not enough data for field table.")); + uint32_t count = buffer.getLong(); + uint32_t leftover = available - len; + while(buffer.available() > leftover && count--){ + std::string name; + ValuePtr value(new FieldValue); + + buffer.getShortString(name); + value->decode(buffer); + values[name] = ValuePtr(value); + } + } +} + +bool FieldTable::operator==(const FieldTable& x) const { + if (values.size() != x.values.size()) return false; + for (ValueMap::const_iterator i = values.begin(); i != values.end(); ++i) { + ValueMap::const_iterator j = x.values.find(i->first); + if (j == x.values.end()) return false; + if (*(i->second) != *(j->second)) return false; + } + return true; +} + +void FieldTable::erase(const std::string& name) +{ + if (values.find(name) != values.end()) + values.erase(name); +} + +std::pair<FieldTable::ValueMap::iterator, bool> FieldTable::insert(const ValueMap::value_type& value) +{ + return values.insert(value); +} + +FieldTable::ValueMap::iterator FieldTable::insert(ValueMap::iterator position, const ValueMap::value_type& value) +{ + return values.insert(position, value); +} + + +} +} diff --git a/qpid/cpp/src/qpid/framing/FieldValue.cpp b/qpid/cpp/src/qpid/framing/FieldValue.cpp new file mode 100644 index 0000000000..ce5a50117c --- /dev/null +++ b/qpid/cpp/src/qpid/framing/FieldValue.cpp @@ -0,0 +1,234 @@ +/* + * + * 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/FieldValue.h" +#include "qpid/framing/Array.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/Endian.h" +#include "qpid/framing/List.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/Msg.h" + +namespace qpid { +namespace framing { + +uint8_t FieldValue::getType() const +{ + return typeOctet; +} + +void FieldValue::setType(uint8_t type) +{ + typeOctet = type; + if (typeOctet == 0xA8) { + data.reset(new EncodedValue<FieldTable>()); + } else if (typeOctet == 0xA9) { + data.reset(new EncodedValue<List>()); + } else if (typeOctet == 0xAA) { + data.reset(new EncodedValue<Array>()); + } else { + uint8_t lenType = typeOctet >> 4; + switch(lenType){ + case 0: + data.reset(new FixedWidthValue<1>()); + break; + case 1: + data.reset(new FixedWidthValue<2>()); + break; + case 2: + data.reset(new FixedWidthValue<4>()); + break; + case 3: + data.reset(new FixedWidthValue<8>()); + break; + case 4: + data.reset(new FixedWidthValue<16>()); + break; + case 5: + data.reset(new FixedWidthValue<32>()); + break; + case 6: + data.reset(new FixedWidthValue<64>()); + break; + case 7: + data.reset(new FixedWidthValue<128>()); + break; + case 8: + data.reset(new VariableWidthValue<1>()); + break; + case 9: + data.reset(new VariableWidthValue<2>()); + break; + case 0xA: + data.reset(new VariableWidthValue<4>()); + break; + case 0xC: + data.reset(new FixedWidthValue<5>()); + break; + case 0xD: + data.reset(new FixedWidthValue<9>()); + break; + case 0xF: + data.reset(new FixedWidthValue<0>()); + break; + default: + throw IllegalArgumentException(QPID_MSG("Unknown field table value type: " << (int)typeOctet)); + } + } +} + +void FieldValue::decode(Buffer& buffer) +{ + setType(buffer.getOctet()); + data->decode(buffer); +} + +void FieldValue::encode(Buffer& buffer) +{ + buffer.putOctet(typeOctet); + data->encode(buffer); +} + +bool FieldValue::operator==(const FieldValue& v) const +{ + return + typeOctet == v.typeOctet && + *data == *v.data; +} + +Str8Value::Str8Value(const std::string& v) : + FieldValue( + TYPE_CODE_STR8, + new VariableWidthValue<1>( + reinterpret_cast<const uint8_t*>(v.data()), + reinterpret_cast<const uint8_t*>(v.data()+v.size()))) +{ +} + +Str16Value::Str16Value(const std::string& v) : + FieldValue( + 0x95, + new VariableWidthValue<2>( + reinterpret_cast<const uint8_t*>(v.data()), + reinterpret_cast<const uint8_t*>(v.data()+v.size()))) +{} + +Var16Value::Var16Value(const std::string& v, uint8_t code) : + FieldValue( + code, + new VariableWidthValue<2>( + reinterpret_cast<const uint8_t*>(v.data()), + reinterpret_cast<const uint8_t*>(v.data()+v.size()))) +{} +Var32Value::Var32Value(const std::string& v, uint8_t code) : + FieldValue( + code, + new VariableWidthValue<4>( + reinterpret_cast<const uint8_t*>(v.data()), + reinterpret_cast<const uint8_t*>(v.data()+v.size()))) +{} + +Struct32Value::Struct32Value(const std::string& v) : + FieldValue( + 0xAB, + new VariableWidthValue<4>( + reinterpret_cast<const uint8_t*>(v.data()), + reinterpret_cast<const uint8_t*>(v.data()+v.size()))) +{} + +IntegerValue::IntegerValue(int v) : + FieldValue(0x21, new FixedWidthValue<4>(v)) +{} + +FloatValue::FloatValue(float v) : + FieldValue(0x23, new FixedWidthValue<4>(Endian::convertIfRequired(reinterpret_cast<uint8_t*>(&v), 4))) +{} + +DoubleValue::DoubleValue(double v) : + FieldValue(0x33, new FixedWidthValue<8>(Endian::convertIfRequired(reinterpret_cast<uint8_t*>(&v), 8))) +{} + +Integer64Value::Integer64Value(int64_t v) : + FieldValue(0x31, new FixedWidthValue<8>(v)) +{} + +Unsigned64Value::Unsigned64Value(uint64_t v) : + FieldValue(0x32, new FixedWidthValue<8>(v)) +{} + + +TimeValue::TimeValue(uint64_t v) : + FieldValue(0x38, new FixedWidthValue<8>(v)) +{ +} + +FieldTableValue::FieldTableValue(const FieldTable& f) : FieldValue(0xa8, new EncodedValue<FieldTable>(f)) +{ +} + +ListValue::ListValue(const List& l) : FieldValue(0xa9, new EncodedValue<List>(l)) +{ +} + +ArrayValue::ArrayValue(const Array& a) : FieldValue(0xaa, new EncodedValue<Array>(a)) +{ +} + +VoidValue::VoidValue() : FieldValue(0xf0, new FixedWidthValue<0>()) {} + +BoolValue::BoolValue(bool b) : + FieldValue(0x08, new FixedWidthValue<1>(b)) +{} + +Unsigned8Value::Unsigned8Value(uint8_t v) : + FieldValue(0x02, new FixedWidthValue<1>(v)) +{} +Unsigned16Value::Unsigned16Value(uint16_t v) : + FieldValue(0x12, new FixedWidthValue<2>(v)) +{} +Unsigned32Value::Unsigned32Value(uint32_t v) : + FieldValue(0x22, new FixedWidthValue<4>(v)) +{} + +Integer8Value::Integer8Value(int8_t v) : + FieldValue(0x01, new FixedWidthValue<1>(v)) +{} +Integer16Value::Integer16Value(int16_t v) : + FieldValue(0x11, new FixedWidthValue<2>(v)) +{} +UuidValue::UuidValue(const unsigned char* v) : + FieldValue(0x48, new FixedWidthValue<16>(v)) +{} + +void FieldValue::print(std::ostream& out) const { + data->print(out); + out << TypeCode(typeOctet) << '('; + if (data->convertsToString()) out << data->getString(); + else if (data->convertsToInt()) out << data->getInt(); + else data->print(out); + out << ')'; +} + +uint8_t* FieldValue::convertIfRequired(uint8_t* const octets, int width) +{ + return Endian::convertIfRequired(octets, width); +} + +}} diff --git a/qpid/cpp/src/qpid/framing/FrameDecoder.cpp b/qpid/cpp/src/qpid/framing/FrameDecoder.cpp new file mode 100644 index 0000000000..90cbbd84a1 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/FrameDecoder.cpp @@ -0,0 +1,81 @@ +/* + * + * 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/FrameDecoder.h" +#include "qpid/framing/Buffer.h" +#include "qpid/log/Statement.h" +#include "qpid/framing/reply_exceptions.h" +#include <algorithm> +#include <string.h> + +namespace qpid { +namespace framing { + +namespace { +/** Append up to n bytes from start of buf to end of bytes. */ +void append(std::vector<char>& bytes, Buffer& buffer, size_t n) { + size_t oldSize = bytes.size(); + if ((n = std::min(n, size_t(buffer.available()))) == 0) + return; + bytes.resize(oldSize+n); + char* p = &bytes[oldSize]; + buffer.getRawData(reinterpret_cast<uint8_t*>(p), n); +} +} + +bool FrameDecoder::decode(Buffer& buffer) { + if (buffer.available() == 0) return false; + if (fragment.empty()) { + if (frame.decode(buffer)) // Decode from buffer + return true; + else // Store fragment + append(fragment, buffer, buffer.available()); + } + else { // Already have a fragment + // Get enough data to decode the frame size. + if (fragment.size() < AMQFrame::DECODE_SIZE_MIN) { + append(fragment, buffer, AMQFrame::DECODE_SIZE_MIN - fragment.size()); + } + if (fragment.size() >= AMQFrame::DECODE_SIZE_MIN) { + uint16_t size = AMQFrame::decodeSize(&fragment[0]); + if (size <= fragment.size()) + throw FramingErrorException(QPID_MSG("Frame size " << size << " is too small.")); + append(fragment, buffer, size-fragment.size()); + Buffer b(&fragment[0], fragment.size()); + if (frame.decode(b)) { + assert(b.available() == 0); + fragment.clear(); + return true; + } + } + } + return false; +} + +void FrameDecoder::setFragment(const char* data, size_t size) { + fragment.resize(size); + ::memcpy(&fragment[0], data, size); +} + +std::pair<const char*, size_t> FrameDecoder::getFragment() const { + return std::pair<const char*, size_t>(&fragment[0], fragment.size()); +} + +}} // namespace qpid::framing diff --git a/qpid/cpp/src/qpid/framing/FrameDecoder.h b/qpid/cpp/src/qpid/framing/FrameDecoder.h new file mode 100644 index 0000000000..26bed6c447 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/FrameDecoder.h @@ -0,0 +1,52 @@ +#ifndef QPID_FRAMING_FRAMEDECODER_H +#define QPID_FRAMING_FRAMEDECODER_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/framing/AMQFrame.h" +#include "qpid/CommonImportExport.h" + +namespace qpid { +namespace framing { + +/** + * Decode a frame from buffer. If buffer does not contain a complete + * frame, caches the fragment for the next call to decode. + */ +class FrameDecoder +{ + public: + QPID_COMMON_EXTERN bool decode(Buffer& buffer); + const AMQFrame& getFrame() const { return frame; } + AMQFrame& getFrame() { return frame; } + + void setFragment(const char*, size_t); + std::pair<const char*, size_t> getFragment() const; + + private: + std::vector<char> fragment; + AMQFrame frame; + +}; +}} // namespace qpid::framing + +#endif /*!QPID_FRAMING_FRAMEDECODER_H*/ diff --git a/qpid/cpp/src/qpid/framing/FrameDefaultVisitor.h b/qpid/cpp/src/qpid/framing/FrameDefaultVisitor.h new file mode 100644 index 0000000000..bd676960bf --- /dev/null +++ b/qpid/cpp/src/qpid/framing/FrameDefaultVisitor.h @@ -0,0 +1,60 @@ +#ifndef QPID_FRAMING_FRAMEVISITOR_H +#define QPID_FRAMING_FRAMEVISITOR_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/framing/MethodBodyDefaultVisitor.h" +#include "qpid/framing/AMQBody.h" +#include "qpid/framing/AMQMethodBody.h" +#include "qpid/framing/AMQHeaderBody.h" +#include "qpid/framing/AMQContentBody.h" +#include "qpid/framing/AMQHeartbeatBody.h" + +namespace qpid { +namespace framing { +/** + * Visitor for all concrete frame body types, which combines + * AMQBodyConstVisitor and MethodBodyDefaultVisitor. + * + * Derived classes can override visit methods to specify actions. + * Derived classes must override defaultVisit(), which is called + * for any non-overridden visit functions. + * + */ +struct FrameDefaultVisitor : public AMQBodyConstVisitor, + protected MethodBodyDefaultVisitor +{ + virtual void defaultVisit(const AMQBody&) = 0; + void defaultVisit(const AMQMethodBody& method) { defaultVisit(static_cast<const AMQBody&>(method)); } + + void visit(const AMQHeaderBody& b) { defaultVisit(b); } + void visit(const AMQContentBody& b) { defaultVisit(b); } + void visit(const AMQHeartbeatBody& b) { defaultVisit(b); } + void visit(const AMQMethodBody& b) { b.accept(static_cast<MethodBodyDefaultVisitor&>(*this)); } + + using AMQBodyConstVisitor::visit; + using MethodBodyDefaultVisitor::visit; +}; + +}} // namespace qpid::framing + + +#endif /*!QPID_FRAMING_FRAMEVISITOR_H*/ diff --git a/qpid/cpp/src/qpid/framing/FrameHandler.h b/qpid/cpp/src/qpid/framing/FrameHandler.h new file mode 100644 index 0000000000..fa1fb535ef --- /dev/null +++ b/qpid/cpp/src/qpid/framing/FrameHandler.h @@ -0,0 +1,33 @@ +#ifndef QPID_FRAMING_FRAMEHANDLER_H +#define QPID_FRAMING_FRAMEHANDLER_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/framing/Handler.h" + +namespace qpid { +namespace framing { + +class AMQFrame; +typedef Handler<AMQFrame&> FrameHandler; + + +}} +#endif /*!QPID_FRAMING_FRAMEHANDLER_H*/ diff --git a/qpid/cpp/src/qpid/framing/FrameSet.cpp b/qpid/cpp/src/qpid/framing/FrameSet.cpp new file mode 100644 index 0000000000..255aaf6e6b --- /dev/null +++ b/qpid/cpp/src/qpid/framing/FrameSet.cpp @@ -0,0 +1,105 @@ +/* + * + * 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/FrameSet.h" +#include "qpid/framing/all_method_bodies.h" +#include "qpid/framing/frame_functors.h" +#include "qpid/framing/MessageProperties.h" +#include "qpid/framing/TypeFilter.h" + +using namespace qpid::framing; +using namespace boost; + +FrameSet::FrameSet(const SequenceNumber& _id) : id(_id),contentSize(0),recalculateSize(true) { } +FrameSet::FrameSet(const FrameSet& original) : id(original.id), contentSize(0), recalculateSize(true) +{ + for (Frames::const_iterator i = original.begin(); i != original.end(); ++i) { + parts.push_back(AMQFrame(*(i->getBody()))); + parts.back().setFirstSegment(i->isFirstSegment()); + parts.back().setLastSegment(i->isLastSegment()); + parts.back().setFirstFrame(i->isFirstFrame()); + parts.back().setLastFrame(i->isLastFrame()); + } +} + +void FrameSet::append(const AMQFrame& part) +{ + parts.push_back(part); + recalculateSize = true; +} + +bool FrameSet::isComplete() const +{ + return !parts.empty() && parts.back().getEof() && parts.back().getEos(); +} + +bool FrameSet::isContentBearing() const +{ + const AMQMethodBody* method = getMethod(); + return method && method->isContentBearing(); +} + +const AMQMethodBody* FrameSet::getMethod() const +{ + return parts.empty() ? 0 : parts[0].getMethod(); +} + +AMQMethodBody* FrameSet::getMethod() +{ + return parts.empty() ? 0 : parts[0].getMethod(); +} + +const AMQHeaderBody* FrameSet::getHeaders() const +{ + return parts.size() < 2 ? 0 : parts[1].castBody<AMQHeaderBody>(); +} + +AMQHeaderBody* FrameSet::getHeaders() +{ + return parts.size() < 2 ? 0 : parts[1].castBody<AMQHeaderBody>(); +} + +uint64_t FrameSet::getContentSize() const +{ + if (recalculateSize) + { + SumBodySize sum; + map_if(sum, TypeFilter<CONTENT_BODY>()); + contentSize = sum.getSize(); + recalculateSize = false; + } + return contentSize; +} + +void FrameSet::getContent(std::string& out) const { + out.clear(); + out.reserve(getContentSize()); + for(Frames::const_iterator i = parts.begin(); i != parts.end(); i++) { + if (i->getBody()->type() == CONTENT_BODY) + out += i->castBody<AMQContentBody>()->getData(); + } +} + +std::string FrameSet::getContent() const { + std::string out; + getContent(out); + return out; +} diff --git a/qpid/cpp/src/qpid/framing/FrameSet.h b/qpid/cpp/src/qpid/framing/FrameSet.h new file mode 100644 index 0000000000..cae75e5ec8 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/FrameSet.h @@ -0,0 +1,119 @@ +/* + * + * 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 <string> +#include "qpid/InlineVector.h" +#include "qpid/framing/amqp_framing.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/SequenceNumber.h" +#include "qpid/CommonImportExport.h" + +#ifndef _FrameSet_ +#define _FrameSet_ + +namespace qpid { +namespace framing { + +/** + * Collects the frames representing a message. + */ +class FrameSet +{ + typedef InlineVector<AMQFrame, 4> Frames; + const SequenceNumber id; + Frames parts; + mutable uint64_t contentSize; + mutable bool recalculateSize; + +public: + typedef boost::shared_ptr<FrameSet> shared_ptr; + + QPID_COMMON_EXTERN FrameSet(const SequenceNumber& id); + QPID_COMMON_EXTERN FrameSet(const FrameSet&); + QPID_COMMON_EXTERN void append(const AMQFrame& part); + QPID_COMMON_EXTERN bool isComplete() const; + + QPID_COMMON_EXTERN uint64_t getContentSize() const; + + QPID_COMMON_EXTERN void getContent(std::string&) const; + QPID_COMMON_EXTERN std::string getContent() const; + + bool isContentBearing() const; + + QPID_COMMON_EXTERN const AMQMethodBody* getMethod() const; + QPID_COMMON_EXTERN AMQMethodBody* getMethod(); + QPID_COMMON_EXTERN const AMQHeaderBody* getHeaders() const; + QPID_COMMON_EXTERN AMQHeaderBody* getHeaders(); + + template <class T> bool isA() const { + const AMQMethodBody* method = getMethod(); + return method && method->isA<T>(); + } + + template <class T> const T* as() const { + const AMQMethodBody* method = getMethod(); + return (method && method->isA<T>()) ? dynamic_cast<const T*>(method) : 0; + } + + template <class T> T* as() { + AMQMethodBody* method = getMethod(); + return (method && method->isA<T>()) ? dynamic_cast<T*>(method) : 0; + } + + template <class T> const T* getHeaderProperties() const { + const AMQHeaderBody* header = getHeaders(); + return header ? header->get<T>() : 0; + } + + Frames::const_iterator begin() const { return parts.begin(); } + Frames::const_iterator end() const { return parts.end(); } + + const SequenceNumber& getId() const { return id; } + + template <class P> void remove(P predicate) { + parts.erase(std::remove_if(parts.begin(), parts.end(), predicate), parts.end()); + } + + template <class F> void map(F& functor) { + std::for_each(parts.begin(), parts.end(), functor); + } + + template <class F> void map(F& functor) const { + std::for_each(parts.begin(), parts.end(), functor); + } + + template <class F, class P> void map_if(F& functor, P predicate) { + for(Frames::iterator i = parts.begin(); i != parts.end(); i++) { + if (predicate(*i)) functor(*i); + } + } + + template <class F, class P> void map_if(F& functor, P predicate) const { + for(Frames::const_iterator i = parts.begin(); i != parts.end(); i++) { + if (predicate(*i)) functor(*i); + } + } +}; + +} +} + + +#endif diff --git a/qpid/cpp/src/qpid/framing/Handler.h b/qpid/cpp/src/qpid/framing/Handler.h new file mode 100644 index 0000000000..fa8db36f49 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/Handler.h @@ -0,0 +1,101 @@ +#ifndef QPID_FRAMING_HANDLER_H +#define QPID_FRAMING_HANDLER_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 <boost/shared_ptr.hpp> +#include <boost/type_traits/remove_reference.hpp> +#include <assert.h> + +namespace qpid { +namespace framing { + +template <class T> +struct Handler { + typedef T HandledType; + typedef void handleFptr(T); + typedef void result_type; // Compatible with std/boost functors. + + Handler(Handler<T>* next_=0) : next(next_) {} + virtual ~Handler() {} + virtual void handle(T) = 0; + + /** Allow functor syntax for calling handle */ + void operator()(T t) { handle(t); } + + + /** Pointer to next handler in a linked list. */ + Handler<T>* next; + + /** Adapt any void(T) functor as a Handler. + * Functor<F>(f) will copy f. + * Functor<F&>(f) will only take a reference to x. + */ + template <class F> class Functor : public Handler<T> { + public: + Functor(F f, Handler<T>* next=0) : Handler<T>(next), functor(f) {} + void handle(T t) { functor(t); } + private: + F functor; + }; + + /** Adapt a member function of X as a Handler. + * Only holds a reference to its target, not a copy. + */ + template <class X, void (X::*F)(T)> + class MemFunRef : public Handler<T> { + public: + MemFunRef(X& x, Handler<T>* next=0) : Handler(next), target(&x) {} + void handle(T t) { (target->*F)(t); } + + /** Allow calling with -> syntax */ + MemFunRef* operator->() { return this; } + + private: + X* target; + }; + + /** Interface for a handler that implements a + * pair of in/out handle operations. + * @see InOutHandler + */ + class InOutHandlerInterface { + public: + virtual ~InOutHandlerInterface() {} + virtual void handleIn(T) = 0; + virtual void handleOut(T) = 0; + }; + + /** Support for implementing an in-out handler pair as a single class. + * Overrides handleIn, handleOut functions in a single class. + */ + struct InOutHandler : protected InOutHandlerInterface { + InOutHandler(Handler<T>* nextIn=0, Handler<T>* nextOut=0) : in(*this, nextIn), out(*this, nextOut) {} + MemFunRef<InOutHandlerInterface, &InOutHandlerInterface::handleIn> in; + MemFunRef<InOutHandlerInterface, &InOutHandlerInterface::handleOut> out; + }; +}; + + + +}} +#endif /*!QPID_FRAMING_HANDLER_H*/ +// diff --git a/qpid/cpp/src/qpid/framing/HeaderProperties.h b/qpid/cpp/src/qpid/framing/HeaderProperties.h new file mode 100644 index 0000000000..8b1828daec --- /dev/null +++ b/qpid/cpp/src/qpid/framing/HeaderProperties.h @@ -0,0 +1,44 @@ +/* + * + * 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/amqp_types.h" +#include "qpid/framing/Buffer.h" + +#ifndef _HeaderProperties_ +#define _HeaderProperties_ + +namespace qpid { +namespace framing { + + class HeaderProperties + { + + public: + inline virtual ~HeaderProperties(){} + virtual uint8_t classId() const = 0; + virtual uint32_t encodedSize() const = 0; + virtual void encode(Buffer& buffer) const = 0; + virtual void decode(Buffer& buffer, uint32_t size) = 0; + }; +} +} + + +#endif diff --git a/qpid/cpp/src/qpid/framing/InitiationHandler.cpp b/qpid/cpp/src/qpid/framing/InitiationHandler.cpp new file mode 100644 index 0000000000..7ded505a47 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/InitiationHandler.cpp @@ -0,0 +1,24 @@ +/* + * + * 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/InitiationHandler.h" + +qpid::framing::InitiationHandler::~InitiationHandler() {} diff --git a/qpid/cpp/src/qpid/framing/InitiationHandler.h b/qpid/cpp/src/qpid/framing/InitiationHandler.h new file mode 100644 index 0000000000..5dfcc6b468 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/InitiationHandler.h @@ -0,0 +1,41 @@ +/* + * + * 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 <string> + +#ifndef _InitiationHandler_ +#define _InitiationHandler_ + +#include "qpid/framing/ProtocolInitiation.h" + +namespace qpid { +namespace framing { + + class InitiationHandler{ + public: + virtual ~InitiationHandler(); + virtual void initiated(const ProtocolInitiation&) = 0; + }; + +} +} + + +#endif diff --git a/qpid/cpp/src/qpid/framing/InputHandler.h b/qpid/cpp/src/qpid/framing/InputHandler.h new file mode 100644 index 0000000000..3efb23632a --- /dev/null +++ b/qpid/cpp/src/qpid/framing/InputHandler.h @@ -0,0 +1,41 @@ +#ifndef _InputHandler_ +#define _InputHandler_ +/* + * + * 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/FrameHandler.h" +#include <boost/noncopyable.hpp> + +namespace qpid { +namespace framing { + +// TODO aconway 2007-08-29: Eliminate, replace with FrameHandler. +class InputHandler : public FrameHandler { + public: + virtual ~InputHandler() {} + virtual void received(AMQFrame&) = 0; + void handle(AMQFrame& f) { received(f); } +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/framing/Invoker.h b/qpid/cpp/src/qpid/framing/Invoker.h new file mode 100644 index 0000000000..4f1cf7c331 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/Invoker.h @@ -0,0 +1,86 @@ +#ifndef QPID_FRAMING_INVOKER_H +#define QPID_FRAMING_INVOKER_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/framing/AMQMethodBody.h" +#include "qpid/framing/MethodBodyDefaultVisitor.h" +#include "qpid/framing/StructHelper.h" + +#include <boost/optional.hpp> + +namespace qpid { +namespace framing { + +class AMQMethodBody; + +/** + * Base class for invoker visitors. + */ +class Invoker: public MethodBodyDefaultVisitor, protected StructHelper +{ + public: + struct Result { + public: + Result() : handled(false) {} + const std::string& getResult() const { return result; } + bool hasResult() const { return !result.empty(); } + bool wasHandled() const { return handled; } + operator bool() const { return handled; } + + std::string result; + bool handled; + }; + + void defaultVisit(const AMQMethodBody&) {} + Result getResult() const { return result; } + + protected: + Result result; +}; + +/** + * Invoke an invocable object. + * Invocable classes must provide a nested type Invoker. + */ +template <class Invocable> +Invoker::Result invoke(Invocable& target, const AMQMethodBody& body) { + typename Invocable::Invoker invoker(target); + body.accept(invoker); + return invoker.getResult(); +} + +/** + * Invoke an invocable object. + * Invocable classes must provide a nested type Invoker. + */ +template <class Invocable> +Invoker::Result invoke(Invocable& target, const AMQBody& body) { + typename Invocable::Invoker invoker(target); + const AMQMethodBody* method = body.getMethod(); + if (method) + method->accept(invoker); + return invoker.getResult(); +} + +}} // namespace qpid::framing + +#endif /*!QPID_FRAMING_INVOKER_H*/ diff --git a/qpid/cpp/src/qpid/framing/IsInSequenceSet.h b/qpid/cpp/src/qpid/framing/IsInSequenceSet.h new file mode 100644 index 0000000000..fe10c1b9fa --- /dev/null +++ b/qpid/cpp/src/qpid/framing/IsInSequenceSet.h @@ -0,0 +1,51 @@ +#ifndef QPID_FRAMING_ISINSEQUENCESET_H +#define QPID_FRAMING_ISINSEQUENCESET_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/framing/SequenceSet.h" + +namespace qpid { +namespace framing { +/** + * Functor to test whether values are in a sequence set. This is a + * stateful functor that requires the values to be supplied in order + * and takes advantage of that ordering to avoid multiple scans. + */ +class IsInSequenceSet +{ + public: + IsInSequenceSet(const SequenceSet& s) : set(s), i(set.rangesBegin()) {} + + bool operator()(const SequenceNumber& n) { + while (i != set.rangesEnd() && i->end() <= n) ++i; + return i != set.rangesEnd() && i->begin() <= n; + } + + private: + const SequenceSet& set; + SequenceSet::RangeIterator i; +}; + +}} // namespace qpid::framing + +#endif /*!QPID_FRAMING_ISINSEQUENCESET_H*/ diff --git a/qpid/cpp/src/qpid/framing/List.cpp b/qpid/cpp/src/qpid/framing/List.cpp new file mode 100644 index 0000000000..963ebc206b --- /dev/null +++ b/qpid/cpp/src/qpid/framing/List.cpp @@ -0,0 +1,84 @@ +/* + * + * 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/List.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/FieldValue.h" +#include "qpid/Exception.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/Msg.h" + +namespace qpid { +namespace framing { + +uint32_t List::encodedSize() const +{ + uint32_t len(4/*size*/ + 4/*count*/); + for(Values::const_iterator i = values.begin(); i != values.end(); ++i) { + len += (*i)->encodedSize(); + } + return len; +} + +void List::encode(Buffer& buffer) const +{ + buffer.putLong(encodedSize() - 4); + buffer.putLong(size()); + for (Values::const_iterator i = values.begin(); i!=values.end(); ++i) { + (*i)->encode(buffer); + } +} + +void List::decode(Buffer& buffer) +{ + values.clear(); + uint32_t size = buffer.getLong(); + uint32_t available = buffer.available(); + if (available < size) { + throw IllegalArgumentException(QPID_MSG("Not enough data for list, expected " + << size << " bytes but only " << available << " available")); + } + if (size) { + uint32_t count = buffer.getLong(); + for (uint32_t i = 0; i < count; i++) { + ValuePtr value(new FieldValue); + value->decode(buffer); + values.push_back(value); + } + } +} + + +bool List::operator==(const List& other) const { + return values.size() == other.values.size() && + std::equal(values.begin(), values.end(), other.values.begin()); +} + +std::ostream& operator<<(std::ostream& out, const List& l) +{ + out << "{"; + for(List::Values::const_iterator i = l.values.begin(); i != l.values.end(); ++i) { + if (i != l.values.begin()) out << ", "; + (*i)->print(out); + } + return out << "}"; +} + +}} // namespace qpid::framing diff --git a/qpid/cpp/src/qpid/framing/MethodBodyFactory.h b/qpid/cpp/src/qpid/framing/MethodBodyFactory.h new file mode 100644 index 0000000000..88bc444795 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/MethodBodyFactory.h @@ -0,0 +1,45 @@ +#ifndef QPID_FRAMING_METHODBODYFACTORY_H +#define QPID_FRAMING_METHODBODYFACTORY_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/framing/amqp_types.h" +#include "qpid/framing/AMQBody.h" +#include <boost/intrusive_ptr.hpp> + +namespace qpid { +namespace framing { + +class AMQMethodBody; + +/** + * Functions to create instances of AMQMethodBody sub-classes. + * Note: MethodBodyFactory.cpp file is generated by rubygen. + */ +class MethodBodyFactory +{ + public: + static boost::intrusive_ptr<AMQMethodBody> create(ClassId c, MethodId m); +}; + +}} // namespace qpid::framing + +#endif /*!QPID_FRAMING_METHODBODYFACTORY_H*/ diff --git a/qpid/cpp/src/qpid/framing/MethodContent.h b/qpid/cpp/src/qpid/framing/MethodContent.h new file mode 100644 index 0000000000..b290a0c140 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/MethodContent.h @@ -0,0 +1,40 @@ +/* + * + * 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 _MethodContent_ +#define _MethodContent_ + +#include <string> +#include "qpid/framing/AMQHeaderBody.h" + +namespace qpid { +namespace framing { + +class MethodContent +{ +public: + virtual ~MethodContent() {} + //TODO: rethink this interface + virtual AMQHeaderBody getHeader() const = 0; + virtual const std::string& getData() const = 0; +}; + +}} +#endif diff --git a/qpid/cpp/src/qpid/framing/ModelMethod.h b/qpid/cpp/src/qpid/framing/ModelMethod.h new file mode 100644 index 0000000000..d99bd06cfa --- /dev/null +++ b/qpid/cpp/src/qpid/framing/ModelMethod.h @@ -0,0 +1,49 @@ +#ifndef _ModelMethod_ +#define _ModelMethod_ + +/* + * + * 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/AMQMethodBody.h" +#include "qpid/framing/Header.h" + +namespace qpid { +namespace framing { + + +class ModelMethod : public AMQMethodBody +{ + mutable Header header; +public: + virtual ~ModelMethod() {} + virtual void encodeHeader(Buffer& buffer) const { header.encode(buffer); } + virtual void decodeHeader(Buffer& buffer, uint32_t size=0) { header.decode(buffer, size); } + virtual uint32_t headerSize() const { return header.encodedSize(); } + virtual bool isSync() const { return header.getSync(); } + virtual void setSync(bool on) const { header.setSync(on); } + Header& getHeader() { return header; } + const Header& getHeader() const { return header; } +}; + + +}} // namespace qpid::framing + + +#endif diff --git a/qpid/cpp/src/qpid/framing/OutputHandler.h b/qpid/cpp/src/qpid/framing/OutputHandler.h new file mode 100644 index 0000000000..88c95589da --- /dev/null +++ b/qpid/cpp/src/qpid/framing/OutputHandler.h @@ -0,0 +1,42 @@ +#ifndef _OutputHandler_ +#define _OutputHandler_ + +/* + * + * 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/noncopyable.hpp> +#include "qpid/framing/FrameHandler.h" + +namespace qpid { +namespace framing { + +// TODO aconway 2007-08-29: Replace with FrameHandler. +class OutputHandler : public FrameHandler { + public: + virtual ~OutputHandler() {} + virtual void send(AMQFrame&) = 0; + void handle(AMQFrame& f) { send(f); } +}; + + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/framing/ProtocolInitiation.cpp b/qpid/cpp/src/qpid/framing/ProtocolInitiation.cpp new file mode 100644 index 0000000000..e617015d64 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/ProtocolInitiation.cpp @@ -0,0 +1,66 @@ +/* + * + * 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/ProtocolInitiation.h" + +namespace qpid { +namespace framing { + +ProtocolInitiation::ProtocolInitiation(){} + +ProtocolInitiation::ProtocolInitiation(uint8_t _major, uint8_t _minor) : version(_major, _minor) {} + +ProtocolInitiation::ProtocolInitiation(ProtocolVersion p) : version(p) {} + +ProtocolInitiation::~ProtocolInitiation(){} + +void ProtocolInitiation::encode(Buffer& buffer) const { + buffer.putOctet('A'); + buffer.putOctet('M'); + buffer.putOctet('Q'); + buffer.putOctet('P'); + buffer.putOctet(1);//class + buffer.putOctet(1);//instance + buffer.putOctet(version.getMajor()); + buffer.putOctet(version.getMinor()); +} + +bool ProtocolInitiation::decode(Buffer& buffer){ + if(buffer.available() >= 8){ + buffer.getOctet();//A + buffer.getOctet();//M + buffer.getOctet();//Q + buffer.getOctet();//P + buffer.getOctet();//class + buffer.getOctet();//instance + version.setMajor(buffer.getOctet()); + version.setMinor(buffer.getOctet()); + return true; + }else{ + return false; + } +} + + +std::ostream& operator<<(std::ostream& o, const framing::ProtocolInitiation& pi) { + return o << int(pi.getMajor()) << "-" << int(pi.getMinor()); +} + +}} // namespace qpid::framing diff --git a/qpid/cpp/src/qpid/framing/ProtocolInitiation.h b/qpid/cpp/src/qpid/framing/ProtocolInitiation.h new file mode 100644 index 0000000000..c519bc2442 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/ProtocolInitiation.h @@ -0,0 +1,59 @@ +/* + * + * 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/amqp_types.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/AMQDataBlock.h" +#include "qpid/framing/ProtocolVersion.h" +#include "qpid/CommonImportExport.h" + +#ifndef _ProtocolInitiation_ +#define _ProtocolInitiation_ + +namespace qpid { +namespace framing { + +class ProtocolInitiation : public AMQDataBlock +{ +private: + ProtocolVersion version; + +public: + QPID_COMMON_EXTERN ProtocolInitiation(); + QPID_COMMON_EXTERN ProtocolInitiation(uint8_t major, uint8_t minor); + QPID_COMMON_EXTERN ProtocolInitiation(ProtocolVersion p); + QPID_COMMON_EXTERN virtual ~ProtocolInitiation(); + QPID_COMMON_EXTERN virtual void encode(Buffer& buffer) const; + QPID_COMMON_EXTERN virtual bool decode(Buffer& buffer); + inline virtual uint32_t encodedSize() const { return 8; } + inline uint8_t getMajor() const { return version.getMajor(); } + inline uint8_t getMinor() const { return version.getMinor(); } + inline ProtocolVersion getVersion() const { return version; } + bool operator==(ProtocolVersion v) const { return v == getVersion(); } +}; + +QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream& o, const framing::ProtocolInitiation& pi); + + +} +} + + +#endif diff --git a/qpid/cpp/src/qpid/framing/ProtocolVersion.cpp b/qpid/cpp/src/qpid/framing/ProtocolVersion.cpp new file mode 100644 index 0000000000..c63cddb4cc --- /dev/null +++ b/qpid/cpp/src/qpid/framing/ProtocolVersion.cpp @@ -0,0 +1,44 @@ +/* + * + * 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/ProtocolVersion.h" +#include <sstream> + +using namespace qpid::framing; + +const std::string ProtocolVersion::toString() const +{ + std::stringstream ss; + ss << major_ << "-" << minor_; + return ss.str(); +} + +ProtocolVersion& ProtocolVersion::operator=(ProtocolVersion p) +{ + major_ = p.major_; + minor_ = p.minor_; + return *this; +} + +bool ProtocolVersion::operator==(ProtocolVersion p) const +{ + return major_ == p.major_ && minor_ == p.minor_; +} + diff --git a/qpid/cpp/src/qpid/framing/Proxy.cpp b/qpid/cpp/src/qpid/framing/Proxy.cpp new file mode 100644 index 0000000000..452fb13b01 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/Proxy.cpp @@ -0,0 +1,51 @@ +/* + * + * 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. + * + */ + +#include "qpid/framing/Proxy.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/AMQMethodBody.h" +#include "qpid/log/Statement.h" + +namespace qpid { +namespace framing { + +Proxy::Proxy(FrameHandler& h) : out(&h), sync(false) {} + +Proxy::~Proxy() {} + +void Proxy::send(const AMQBody& b) { + if (sync) { + const AMQMethodBody* m = dynamic_cast<const AMQMethodBody*>(&b); + if (m) m->setSync(sync); + } + AMQFrame f(b); + out->handle(f); +} + +ProtocolVersion Proxy::getVersion() const { + return ProtocolVersion(); +} + +FrameHandler& Proxy::getHandler() { return *out; } + +void Proxy::setHandler(FrameHandler& f) { out=&f; } + +Proxy::ScopedSync::ScopedSync(Proxy& p) : proxy(p) { proxy.sync = true; } +Proxy::ScopedSync::~ScopedSync() { proxy.sync = false; } + +}} // namespace qpid::framing diff --git a/qpid/cpp/src/qpid/framing/Proxy.h b/qpid/cpp/src/qpid/framing/Proxy.h new file mode 100644 index 0000000000..0884e9cbd2 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/Proxy.h @@ -0,0 +1,64 @@ +#ifndef _framing_Proxy_h +#define _framing_Proxy_h + +/* + * + * 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. + * + */ + +#include "qpid/framing/FrameHandler.h" +#include "qpid/framing/ProtocolVersion.h" + +#include "qpid/CommonImportExport.h" + +namespace qpid { +namespace framing { + +class AMQBody; + +/** + * Base class for proxies. + */ +class Proxy +{ + public: + class ScopedSync + { + Proxy& proxy; + public: + QPID_COMMON_EXTERN ScopedSync(Proxy& p); + QPID_COMMON_EXTERN ~ScopedSync(); + }; + + QPID_COMMON_EXTERN Proxy(FrameHandler& h); + QPID_COMMON_EXTERN virtual ~Proxy(); + + QPID_COMMON_EXTERN void send(const AMQBody&); + + QPID_COMMON_EXTERN ProtocolVersion getVersion() const; + + QPID_COMMON_EXTERN FrameHandler& getHandler(); + QPID_COMMON_EXTERN void setHandler(FrameHandler&); + private: + FrameHandler* out; + bool sync; +}; + +}} // namespace qpid::framing + + + +#endif /*!_framing_Proxy_h*/ diff --git a/qpid/cpp/src/qpid/framing/ResizableBuffer.h b/qpid/cpp/src/qpid/framing/ResizableBuffer.h new file mode 100644 index 0000000000..0abc5ba7f4 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/ResizableBuffer.h @@ -0,0 +1,60 @@ +#ifndef QPID_FRAMING_RESIZABLEBUFFER_H +#define QPID_FRAMING_RESIZABLEBUFFER_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/framing/Buffer.h" +#include <vector> + +namespace qpid { +namespace framing { + +/** + * A buffer that maintains its own storage and can be resized, + * keeping any data already written to the buffer. + */ +class ResizableBuffer : public Buffer +{ + public: + ResizableBuffer(size_t initialSize) : store(initialSize) { + static_cast<Buffer&>(*this) = Buffer(&store[0], store.size()); + } + + void resize(size_t newSize) { + size_t oldPos = getPosition(); + store.resize(newSize); + static_cast<Buffer&>(*this) = Buffer(&store[0], store.size()); + setPosition(oldPos); + } + + /** Make sure at least n bytes are available */ + void makeAvailable(size_t n) { + if (n > available()) + resize(getSize() + n - available()); + } + + private: + std::vector<char> store; +}; +}} // namespace qpid::framing + +#endif /*!QPID_FRAMING_RESIZABLEBUFFER_H*/ diff --git a/qpid/cpp/src/qpid/framing/SendContent.cpp b/qpid/cpp/src/qpid/framing/SendContent.cpp new file mode 100644 index 0000000000..04b60396da --- /dev/null +++ b/qpid/cpp/src/qpid/framing/SendContent.cpp @@ -0,0 +1,66 @@ +/* + * + * 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/SendContent.h" + +qpid::framing::SendContent::SendContent(FrameHandler& h, uint16_t mfs, uint efc) : handler(h), + maxFrameSize(mfs), + expectedFrameCount(efc), frameCount(0) {} + +void qpid::framing::SendContent::operator()(const AMQFrame& f) +{ + bool first = frameCount == 0; + bool last = ++frameCount == expectedFrameCount; + + uint16_t maxContentSize = maxFrameSize - AMQFrame::frameOverhead(); + const AMQContentBody* body(f.castBody<AMQContentBody>()); + if (body->encodedSize() > maxContentSize) { + uint32_t offset = 0; + for (int chunk = body->encodedSize() / maxContentSize; chunk > 0; chunk--) { + sendFragment(*body, offset, maxContentSize, first && offset == 0, last && offset + maxContentSize == body->encodedSize()); + offset += maxContentSize; + } + uint32_t remainder = body->encodedSize() % maxContentSize; + if (remainder) { + sendFragment(*body, offset, remainder, first && offset == 0, last); + } + } else { + AMQFrame copy(f); + setFlags(copy, first, last); + handler.handle(copy); + } +} + +void qpid::framing::SendContent::sendFragment(const AMQContentBody& body, uint32_t offset, uint16_t size, bool first, bool last) const +{ + AMQFrame fragment((AMQContentBody(body.getData().substr(offset, size)))); + setFlags(fragment, first, last); + handler.handle(fragment); +} + +void qpid::framing::SendContent::setFlags(AMQFrame& f, bool first, bool last) const +{ + f.setBof(false); + f.setBos(first); + f.setEof(true);//content is always the last segment + f.setEos(last); +} + diff --git a/qpid/cpp/src/qpid/framing/SendContent.h b/qpid/cpp/src/qpid/framing/SendContent.h new file mode 100644 index 0000000000..1c464b9c8b --- /dev/null +++ b/qpid/cpp/src/qpid/framing/SendContent.h @@ -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 <string> +#include "qpid/framing/amqp_framing.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/FrameHandler.h" +#include "qpid/CommonImportExport.h" + +#ifndef _SendContent_ +#define _SendContent_ + +namespace qpid { +namespace framing { + +/** + * Functor that sends frame to handler, refragmenting if + * necessary. Currently only works on content frames but this could be + * changed once we support multi-frame segments in general. + */ +class SendContent +{ + FrameHandler& handler; + const uint16_t maxFrameSize; + uint expectedFrameCount; + uint frameCount; + + void sendFragment(const AMQContentBody& body, uint32_t offset, uint16_t size, bool first, bool last) const; + void setFlags(AMQFrame& f, bool first, bool last) const; +public: + QPID_COMMON_EXTERN SendContent(FrameHandler& _handler, uint16_t _maxFrameSize, uint frameCount); + QPID_COMMON_EXTERN void operator()(const AMQFrame& f); +}; + +} +} + + +#endif diff --git a/qpid/cpp/src/qpid/framing/SequenceNumber.cpp b/qpid/cpp/src/qpid/framing/SequenceNumber.cpp new file mode 100644 index 0000000000..41cb236629 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/SequenceNumber.cpp @@ -0,0 +1,50 @@ +/* + * + * 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/SequenceNumber.h" +#include "qpid/framing/Buffer.h" +#include <ostream> + +using qpid::framing::SequenceNumber; +using qpid::framing::Buffer; + +void SequenceNumber::encode(Buffer& buffer) const +{ + buffer.putLong(value); +} + +void SequenceNumber::decode(Buffer& buffer) +{ + value = buffer.getLong(); +} + +uint32_t SequenceNumber::encodedSize() const { + return 4; +} + +namespace qpid { +namespace framing { + +std::ostream& operator<<(std::ostream& o, const SequenceNumber& n) { + return o << n.getValue(); +} + +}} diff --git a/qpid/cpp/src/qpid/framing/SequenceNumberSet.cpp b/qpid/cpp/src/qpid/framing/SequenceNumberSet.cpp new file mode 100644 index 0000000000..e9d78f3c17 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/SequenceNumberSet.cpp @@ -0,0 +1,90 @@ +/* + * + * 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/SequenceNumberSet.h" + +using namespace qpid::framing; + +void SequenceNumberSet::encode(Buffer& buffer) const +{ + buffer.putShort(size() * 4); + for (const_iterator i = begin(); i != end(); i++) { + buffer.putLong(i->getValue()); + } +} + +void SequenceNumberSet::decode(Buffer& buffer) +{ + clear(); + uint16_t count = (buffer.getShort() / 4); + for (uint16_t i = 0; i < count; i++) { + push_back(SequenceNumber(buffer.getLong())); + } +} + +uint32_t SequenceNumberSet::encodedSize() const +{ + return 2 /*count*/ + (size() * 4); +} + +SequenceNumberSet SequenceNumberSet::condense() const +{ + SequenceNumberSet result; + const_iterator last = end(); + const_iterator start = end(); + for (const_iterator i = begin(); i != end(); i++) { + if (start == end()) { + start = i; + } else if (*i - *last > 1) { + result.push_back(*start); + result.push_back(*last); + start = i; + } + last = i; + } + if (start != end()) { + result.push_back(*start); + result.push_back(*last); + } + return result; +} + +void SequenceNumberSet::addRange(const SequenceNumber& start, const SequenceNumber& end) +{ + push_back(start); + push_back(end); +} + +namespace qpid{ +namespace framing{ + +std::ostream& operator<<(std::ostream& out, const SequenceNumberSet& set) { + out << "{"; + for (SequenceNumberSet::const_iterator i = set.begin(); i != set.end(); i++) { + if (i != set.begin()) out << ", "; + out << (i->getValue()); + } + out << "}"; + return out; +} + +} +} diff --git a/qpid/cpp/src/qpid/framing/SequenceNumberSet.h b/qpid/cpp/src/qpid/framing/SequenceNumberSet.h new file mode 100644 index 0000000000..c8356c8163 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/SequenceNumberSet.h @@ -0,0 +1,69 @@ +/* + * + * 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 _framing_SequenceNumberSet_h +#define _framing_SequenceNumberSet_h + +#include <ostream> +#include "qpid/framing/amqp_types.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/SequenceNumber.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/InlineVector.h" +#include "qpid/CommonImportExport.h" + +namespace qpid { +namespace framing { + +class SequenceNumberSet : public InlineVector<SequenceNumber, 2> +{ + typedef InlineVector<SequenceNumber, 2> Base; +public: + typedef Base::const_iterator const_iterator; + typedef Base::iterator iterator; + + void encode(Buffer& buffer) const; + void decode(Buffer& buffer); + uint32_t encodedSize() const; + QPID_COMMON_EXTERN SequenceNumberSet condense() const; + QPID_COMMON_EXTERN void addRange(const SequenceNumber& start, const SequenceNumber& end); + + template <class T> + void processRanges(T& t) const + { + if (size() % 2) { //must be even number + throw InvalidArgumentException("SequenceNumberSet contains odd number of elements"); + } + + for (SequenceNumberSet::const_iterator i = begin(); i != end(); i++) { + SequenceNumber first = *(i); + SequenceNumber last = *(++i); + t(first, last); + } + } + + friend QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream&, const SequenceNumberSet&); +}; + + +}} // namespace qpid::framing + + +#endif diff --git a/qpid/cpp/src/qpid/framing/SequenceSet.cpp b/qpid/cpp/src/qpid/framing/SequenceSet.cpp new file mode 100644 index 0000000000..72fcd8a9e2 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/SequenceSet.cpp @@ -0,0 +1,102 @@ +/* + * + * 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/SequenceSet.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/Msg.h" + +using namespace qpid::framing; +using std::max; +using std::min; + +namespace qpid { +namespace framing { + +namespace { +//each range contains 2 numbers, 4 bytes each +uint16_t RANGE_SIZE = 2 * 4; +} + +void SequenceSet::encode(Buffer& buffer) const +{ + buffer.putShort(rangesSize() * RANGE_SIZE); + for (RangeIterator i = rangesBegin(); i != rangesEnd(); i++) { + buffer.putLong(i->first().getValue()); + buffer.putLong(i->last().getValue()); + } +} + +void SequenceSet::decode(Buffer& buffer) +{ + clear(); + uint16_t size = buffer.getShort(); + uint16_t count = size / RANGE_SIZE;//number of ranges + if (size % RANGE_SIZE) + throw IllegalArgumentException(QPID_MSG("Invalid size for sequence set: " << size)); + + for (uint16_t i = 0; i < count; i++) { + add(SequenceNumber(buffer.getLong()), SequenceNumber(buffer.getLong())); + } +} + +uint32_t SequenceSet::encodedSize() const { + return 2 /*size field*/ + (rangesSize() * RANGE_SIZE); +} + +bool SequenceSet::contains(const SequenceNumber& s) const { + return RangeSet<SequenceNumber>::contains(s); +} + +void SequenceSet::add(const SequenceNumber& s) { *this += s; } + +void SequenceSet::add(const SequenceNumber& start, const SequenceNumber& finish) { + *this += Range<SequenceNumber>::makeClosed(std::min(start,finish), std::max(start, finish)); +} + +void SequenceSet::add(const SequenceSet& set) { *this += set; } + +void SequenceSet::remove(const SequenceSet& set) { *this -= set; } + +void SequenceSet::remove(const SequenceNumber& start, const SequenceNumber& finish) { + *this -= Range<SequenceNumber>::makeClosed(std::min(start,finish), std::max(start, finish)); +} + +void SequenceSet::remove(const SequenceNumber& s) { *this -= s; } + + +struct RangePrinter { + std::ostream& out; + RangePrinter(std::ostream& o) : out(o) {} + void operator()(SequenceNumber i, SequenceNumber j) const { + out << "[" << i.getValue() << "," << j.getValue() << "] "; + } +}; + +std::ostream& operator<<(std::ostream& o, const SequenceSet& s) { + RangePrinter print(o); + o << "{ "; + s.for_each(print); + return o << "}"; +} + +}} // namespace qpid::framing + diff --git a/qpid/cpp/src/qpid/framing/TemplateVisitor.h b/qpid/cpp/src/qpid/framing/TemplateVisitor.h new file mode 100644 index 0000000000..d6d59603f7 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/TemplateVisitor.h @@ -0,0 +1,89 @@ +#ifndef QPID_FRAMING_TEMPLATEVISITOR_H +#define QPID_FRAMING_TEMPLATEVISITOR_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 <boost/mpl/fold.hpp> +#include <boost/utility/value_init.hpp> + +namespace qpid { +namespace framing { + +/** + * Metafunction to generate a visitor class derived from Base, with a + * visit for each type in TypeList calling functor F. TypeList may be + * any boost::mpl type collection e.g. mpl::list. + * + * Generated class is: TemplateVisitor<Base, F, TypeList>::type + * + * @see make_visitor + */ +template <class VisitTemplate, class TypeList, class F> +class TemplateVisitor +{ + struct Base : public VisitorBase { + F action; + Base(F f) : action(f) {} + using VisitorBase::visit; + }; + + template <class B, class T> struct Visit : public B { + Visit(F action) : B(action) {} + using B::visit; + void visit(const T& body) { action(body); } + }; + + typedef typename boost::mpl::fold< + TypeList, Base, Visit<boost::mpl::placeholders::_1, + boost::mpl::placeholders::_2> + >::type type; +}; + +/** + * Construct a TemplateVisitor to perform the given action, + * for example: + * @code + */ +template <class VisitorBase, class TypeList, class F> +TemplateVisitor<VisitorBase,TypeList,F>::type make_visitor(F action) { + return TemplateVisitor<VisitorBase,TypeList,F>::type(action); +}; + +/** + * For method body classes in TypeList, invoke the corresponding function + * on Target and return true. For other body types return false. + */ +template <class TypeList, class Target> +bool invoke(const AMQBody& body, Target& target) { + typename InvokeVisitor<TypeList, Target>::type v(target); + body.accept(v); + return v.target; +} + +}} // namespace qpid::framing + + +#endif /*!QPID_FRAMING_INVOKEVISITOR_H*/ + +}} // namespace qpid::framing + + + +#endif /*!QPID_FRAMING_TEMPLATEVISITOR_H*/ diff --git a/qpid/cpp/src/qpid/framing/TransferContent.cpp b/qpid/cpp/src/qpid/framing/TransferContent.cpp new file mode 100644 index 0000000000..837d7d346a --- /dev/null +++ b/qpid/cpp/src/qpid/framing/TransferContent.cpp @@ -0,0 +1,102 @@ +/* + * + * 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/TransferContent.h" + +namespace qpid { +namespace framing { + +TransferContent::TransferContent(const std::string& data, const std::string& key) { + setData(data); + if (!key.empty()) getDeliveryProperties().setRoutingKey(key); +} + + +AMQHeaderBody TransferContent::getHeader() const +{ + return header; +} + +const std::string& TransferContent::getData() const { + return data; +} + +std::string& TransferContent::getData() { + return data; +} + +void TransferContent::setData(const std::string& _data) +{ + data = _data; + header.get<MessageProperties>(true)->setContentLength(data.size()); +} + +void TransferContent::appendData(const std::string& _data) +{ + data += _data; + header.get<MessageProperties>(true)->setContentLength(data.size()); +} + +MessageProperties& TransferContent::getMessageProperties() +{ + return *header.get<MessageProperties>(true); +} + +DeliveryProperties& TransferContent::getDeliveryProperties() +{ + return *header.get<DeliveryProperties>(true); +} + +void TransferContent::populate(const FrameSet& frameset) +{ + const AMQHeaderBody* h = frameset.getHeaders(); + if (h) { + header = *h; + } + frameset.getContent(data); +} + +const MessageProperties& TransferContent::getMessageProperties() const +{ + const MessageProperties* props = header.get<MessageProperties>(); + if (!props) throw Exception("No message properties."); + return *props; +} + +const DeliveryProperties& TransferContent::getDeliveryProperties() const +{ + const DeliveryProperties* props = header.get<DeliveryProperties>(); + if (!props) throw Exception("No message properties."); + return *props; +} + +bool TransferContent::hasMessageProperties() const +{ + return header.get<MessageProperties>(); +} + +bool TransferContent::hasDeliveryProperties() const +{ + return header.get<DeliveryProperties>(); +} + + +}} diff --git a/qpid/cpp/src/qpid/framing/TransferContent.h b/qpid/cpp/src/qpid/framing/TransferContent.h new file mode 100644 index 0000000000..9a698a1823 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/TransferContent.h @@ -0,0 +1,64 @@ +/* + * + * 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 _TransferContent_ +#define _TransferContent_ + +#include "qpid/framing/FrameSet.h" +#include "qpid/framing/MethodContent.h" +#include "qpid/Exception.h" +#include "qpid/framing/MessageProperties.h" +#include "qpid/framing/DeliveryProperties.h" +#include "qpid/CommonImportExport.h" + +namespace qpid { +namespace framing { + +/** Message content */ +class QPID_COMMON_CLASS_EXTERN TransferContent : public MethodContent +{ + AMQHeaderBody header; + std::string data; +public: + QPID_COMMON_EXTERN TransferContent(const std::string& data = std::string(), const std::string& key=std::string()); + + ///@internal + QPID_COMMON_EXTERN AMQHeaderBody getHeader() const; + + QPID_COMMON_EXTERN void setData(const std::string&); + QPID_COMMON_EXTERN const std::string& getData() const; + QPID_COMMON_EXTERN std::string& getData(); + + QPID_COMMON_EXTERN void appendData(const std::string&); + + QPID_COMMON_EXTERN bool hasMessageProperties() const; + QPID_COMMON_EXTERN MessageProperties& getMessageProperties(); + QPID_COMMON_EXTERN const MessageProperties& getMessageProperties() const; + + QPID_COMMON_EXTERN bool hasDeliveryProperties() const; + QPID_COMMON_EXTERN DeliveryProperties& getDeliveryProperties(); + QPID_COMMON_EXTERN const DeliveryProperties& getDeliveryProperties() const; + + ///@internal + QPID_COMMON_EXTERN void populate(const FrameSet& frameset); +}; + +}} +#endif diff --git a/qpid/cpp/src/qpid/framing/TypeFilter.h b/qpid/cpp/src/qpid/framing/TypeFilter.h new file mode 100644 index 0000000000..d1c42de583 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/TypeFilter.h @@ -0,0 +1,51 @@ +#ifndef QPID_FRAMING_TYPEFILTER_H +#define QPID_FRAMING_TYPEFILTER_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 <string> +#include "qpid/framing/amqp_framing.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/FrameHandler.h" + +namespace qpid { +namespace framing { + +/** + * Predicate that selects frames by type + */ +template <uint8_t Type> +struct TypeFilter { + bool operator()(const AMQFrame& f) const { + return f.getBody()->type() == Type; + } +}; + +template <uint8_t T1, uint8_t T2> +struct TypeFilter2 { + bool operator()(const AMQFrame& f) const { + return f.getBody()->type() == T1 || f.getBody()->type() == T2; + } +}; + +}} // namespace qpid::framing + +#endif /*!QPID_FRAMING_TYPEFILTER_H*/ diff --git a/qpid/cpp/src/qpid/framing/Uuid.cpp b/qpid/cpp/src/qpid/framing/Uuid.cpp new file mode 100644 index 0000000000..945c0a4d24 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/Uuid.cpp @@ -0,0 +1,97 @@ +/* + * + * 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. + * + */ + +#include "qpid/framing/Uuid.h" + +#include "qpid/sys/uuid.h" +#include "qpid/Exception.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/Msg.h" + +namespace qpid { +namespace framing { + +using namespace std; + +static const size_t UNPARSED_SIZE=36; + +Uuid::Uuid(bool unique) { + if (unique) { + generate(); + } else { + clear(); + } +} + +Uuid::Uuid(const uint8_t* data) { + assign(data); +} + +void Uuid::assign(const uint8_t* data) { + // This const cast is for Solaris which has a + // uuid_copy that takes a non const 2nd argument + uuid_copy(c_array(), const_cast<uint8_t*>(data)); +} + +void Uuid::generate() { + uuid_generate(c_array()); +} + +void Uuid::clear() { + uuid_clear(c_array()); +} + +// Force int 0/!0 to false/true; avoids compile warnings. +bool Uuid::isNull() const { + return !!uuid_is_null(data()); +} + +void Uuid::encode(Buffer& buf) const { + buf.putRawData(data(), size()); +} + +void Uuid::decode(Buffer& buf) { + if (buf.available() < size()) + throw IllegalArgumentException(QPID_MSG("Not enough data for UUID.")); + buf.getRawData(c_array(), size()); +} + +ostream& operator<<(ostream& out, Uuid uuid) { + char unparsed[UNPARSED_SIZE + 1]; + uuid_unparse(uuid.data(), unparsed); + return out << unparsed; +} + +istream& operator>>(istream& in, Uuid& uuid) { + char unparsed[UNPARSED_SIZE + 1] = {0}; + in.get(unparsed, sizeof(unparsed)); + if (!in.fail()) { + if (uuid_parse(unparsed, uuid.c_array()) != 0) + in.setstate(ios::failbit); + } + return in; +} + +std::string Uuid::str() const { + std::ostringstream os; + os << *this; + return os.str(); +} + +}} // namespace qpid::framing diff --git a/qpid/cpp/src/qpid/framing/Visitor.h b/qpid/cpp/src/qpid/framing/Visitor.h new file mode 100644 index 0000000000..759ee65914 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/Visitor.h @@ -0,0 +1,92 @@ +#ifndef QPID_FRAMING_VISITOR_H +#define QPID_FRAMING_VISITOR_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 <boost/mpl/vector.hpp> +#include <boost/type_traits/remove_reference.hpp> +#include <boost/preprocessor/seq/for_each.hpp> + +namespace qpid { +namespace framing { + +/** @file Generic visitor pattern. */ + +/** visit() interface for type T (optional return type R, default is void.) + * To create a visitor for a set of types T1, T2 ... do this: + * struct MyVisitor : public Visit<T1>, public Visit<T2> ... {}; + *@param T Type to visit. This must be forward declared, and need not be defined. + */ +template <class T, class R=void> struct Visit { + typedef R ReturnType; + typedef T VisitType; + + virtual ~Visit() {} + virtual R visit(T&) = 0; +}; + + +#define QPID_VISITOR_DECL(_1,_2,T) class T; + +#define QPID_VISITOR_BASE(_1,_2,T) , public ::qpid::framing::Visit<T> + +/** Convenience macro to generate a visitor interface. + * QPID_VISITOR(MyVisitor,(A)(B)(C)); is equivalent to: + * @code + * class A; class B; class C; + * class MyVisitor : public Visit<A> , public Visit<B> , public Visit<C> {}; + * @endcode + * @param visitor name of the generated visitor class. + * @param bases a sequence of visitable types in the form (T1)(T2)... + * Any parenthesized notations are due to quirks of the preprocesser. + */ +#define QPID_VISITOR(visitor,types) \ + BOOST_PP_SEQ_FOR_EACH(QPID_VISITOR_DECL, _, types) \ + class visitor : public ::qpid::framing::Visit<BOOST_PP_SEQ_HEAD(types)> \ + BOOST_PP_SEQ_FOR_EACH(QPID_VISITOR_BASE, _, BOOST_PP_SEQ_TAIL(types)) \ + {} + +/** The root class for the hierarchy of objects visitable by Visitor V. + * Defines virtual accept(). + */ +template <class V, class R=void> +struct VisitableRoot { + typedef V VisitorType; + typedef R ReturnType; + virtual ~VisitableRoot() {} + virtual R accept(V& v) = 0; +}; + +/** The base class for concrete visitable classes. + * Implements accept(). + * @param T type of visitable class (CRTP). + * @param Base base class to inherit from. + */ +template <class T, class Base> +struct Visitable : public Base { + void accept(typename Base::VisitorType& v) { + static_cast<Visit<T>& >(v).visit(static_cast<T&>(*this)); + } +}; + +}} // namespace qpid::framing + +#endif /*!QPID_FRAMING_VISITOR_H*/ diff --git a/qpid/cpp/src/qpid/framing/amqp_framing.h b/qpid/cpp/src/qpid/framing/amqp_framing.h new file mode 100644 index 0000000000..3a8b39afb5 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/amqp_framing.h @@ -0,0 +1,32 @@ +/* + * + * 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/amqp_types.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/AMQBody.h" +#include "qpid/framing/BodyHandler.h" +#include "qpid/framing/AMQMethodBody.h" +#include "qpid/framing/AMQHeaderBody.h" +#include "qpid/framing/AMQContentBody.h" +#include "qpid/framing/AMQHeartbeatBody.h" +#include "qpid/framing/InputHandler.h" +#include "qpid/framing/OutputHandler.h" +#include "qpid/framing/ProtocolInitiation.h" +#include "qpid/framing/ProtocolVersion.h" diff --git a/qpid/cpp/src/qpid/framing/frame_functors.h b/qpid/cpp/src/qpid/framing/frame_functors.h new file mode 100644 index 0000000000..d2064d6a57 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/frame_functors.h @@ -0,0 +1,116 @@ +/* + * + * 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 <string> +#include <ostream> +#include <iostream> +#include "qpid/framing/amqp_framing.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/Buffer.h" + +#ifndef _frame_functors_ +#define _frame_functors_ + +namespace qpid { +namespace framing { + +class SumFrameSize +{ + uint64_t size; +public: + SumFrameSize() : size(0) {} + void operator()(const AMQFrame& f) { size += f.encodedSize(); } + uint64_t getSize() { return size; } +}; + +class SumBodySize +{ + uint64_t size; +public: + SumBodySize() : size(0) {} + void operator()(const AMQFrame& f) { size += f.getBody()->encodedSize(); } + uint64_t getSize() { return size; } +}; + +class Count +{ + uint count; +public: + Count() : count(0) {} + void operator()(const AMQFrame&) { count++; } + uint getCount() { return count; } +}; + +class EncodeFrame +{ + Buffer& buffer; +public: + EncodeFrame(Buffer& b) : buffer(b) {} + void operator()(const AMQFrame& f) { f.encode(buffer); } +}; + +class EncodeBody +{ + Buffer& buffer; +public: + EncodeBody(Buffer& b) : buffer(b) {} + void operator()(const AMQFrame& f) { f.getBody()->encode(buffer); } +}; + +/** + * Sends to the specified handler a copy of the frame it is applied to. + */ +class Relay +{ + FrameHandler& handler; + +public: + Relay(FrameHandler& h) : handler(h) {} + + void operator()(const AMQFrame& f) + { + AMQFrame copy(f); + handler.handle(copy); + } +}; + +class Print +{ + std::ostream& out; +public: + Print(std::ostream& o) : out(o) {} + + void operator()(const AMQFrame& f) + { + out << f << std::endl; + } +}; + +class MarkLastSegment +{ +public: + void operator()(AMQFrame& f) const { f.setEof(true); } +}; + +} +} + + +#endif diff --git a/qpid/cpp/src/qpid/framing/variant.h b/qpid/cpp/src/qpid/framing/variant.h new file mode 100644 index 0000000000..8e13063385 --- /dev/null +++ b/qpid/cpp/src/qpid/framing/variant.h @@ -0,0 +1,91 @@ +#ifndef QPID_FRAMING_VARIANT_H +#define QPID_FRAMING_VARIANT_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. + * + */ + +/**@file Tools for using boost::variant. */ + + +#include <boost/variant.hpp> + +namespace qpid { +namespace framing { +class Buffer; + +/** boost::static_visitor that throws an exception if variant contains a blank. + * Subclasses need to have a using() declaration, which can be generated + * with QPID_USING_NOBLANK(R) + */ +template <class R=void> +struct NoBlankVisitor : public boost::static_visitor<R> { + R foundBlank() const { + assert(0); + throw Exception(QPID_MSG("Invalid variant value.")); + } + R operator()(const boost::blank&) const { return foundBlank(); } + R operator()(boost::blank&) const { return foundBlank(); } +}; + + +}} // qpid::framing + + +/** Generate a using statement, needed in visitors inheriting NoBlankVisitor + * @param R return type. + */ +#define QPID_USING_NOBLANK(R) using ::qpid::framing::NoBlankVisitor<R>::operator() + +namespace qpid { +namespace framing { + +/** Convert the variant value to type R. */ +template <class R> struct ConvertVisitor : public NoBlankVisitor<R> { + QPID_USING_NOBLANK(R); + template <class T> R operator()(T& t) const { return t; } +}; + +/** Convert the address of variant value to type R. */ +template <class R> struct AddressVisitor : public NoBlankVisitor<R> { + QPID_USING_NOBLANK(R); + template <class T> R operator()(T& t) const { return &t; } +}; + +/** Apply a visitor to the nested variant.*/ +template<class V> +struct ApplyVisitor : public NoBlankVisitor<typename V::result_type> { + QPID_USING_NOBLANK(typename V::result_type); + const V& visitor; + ApplyVisitor(const V& v) : visitor(v) {} + template <class T> typename V::result_type operator()(T& t) const { + return boost::apply_visitor(visitor, t); + } +}; + +/** Convenience function to construct and apply an ApplyVisitor */ +template <class Visitor, class Visitable> +typename Visitor::result_type applyApplyVisitor(const Visitor& visitor, Visitable& visitable) { + return boost::apply_visitor(ApplyVisitor<Visitor>(visitor), visitable); +} + +}} // namespace qpid::framing + + +#endif /*!QPID_FRAMING_VARIANT_H*/ diff --git a/qpid/cpp/src/qpid/log/Helpers.h b/qpid/cpp/src/qpid/log/Helpers.h new file mode 100644 index 0000000000..82ef8244be --- /dev/null +++ b/qpid/cpp/src/qpid/log/Helpers.h @@ -0,0 +1,79 @@ +#ifndef QPID_LOG_HELPERS_H +#define QPID_LOG_HELPERS_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 <boost/range.hpp> + +#include <ostream> + +namespace qpid { +namespace log { + +/** @file Helper classes for logging complex types */ + +/// @internal +template <class Range> +struct ListFormatter { + typedef typename boost::range_const_iterator<Range>::type Iterator; + boost::iterator_range<Iterator> range; + const char* separator; + + ListFormatter(const Range& r, const char* s=", ") : range(r), separator(s) {} +}; + +/// @internal +template <class Range> +std::ostream& operator<<(std::ostream& out, const ListFormatter<Range>& sl) { + typename ListFormatter<Range>::Iterator i = sl.range.begin(); + if (i != sl.range.end()) out << *(i++); + while (i != sl.range.end()) out << sl.separator << *(i++); + return out; +} + +/** Return a formatting object with operator << + * to stream range as a separated list. + *@param range: a range - all standard containers are ranges, + * as is a pair of iterators. + *@param separator: printed between elements, default ", " + */ +template <class Range> +ListFormatter<Range> formatList(const Range& range, const char* separator=", ") { + return ListFormatter<Range>(range, separator); +} + +/** Return a formatting object with operator << + * to stream the range defined by iterators [begin, end) + * as a separated list. + *@param begin, end: Beginning and end of range. + *@param separator: printed between elements, default ", " + */ +template <class U, class V> +ListFormatter<std::pair<U,V> > formatList(U begin, V end, const char* separator=", ") { + return formatList(std::make_pair(begin,end), separator); +} + + +}} // namespace qpid::log + + + +#endif /*!QPID_LOG_HELPERS_H*/ diff --git a/qpid/cpp/src/qpid/log/Logger.cpp b/qpid/cpp/src/qpid/log/Logger.cpp new file mode 100644 index 0000000000..1600822142 --- /dev/null +++ b/qpid/cpp/src/qpid/log/Logger.cpp @@ -0,0 +1,167 @@ +/* + * + * 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. + * + */ + +#include "qpid/log/Logger.h" +#include "qpid/log/Options.h" +#include "qpid/log/SinkOptions.h" +#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> +#include <algorithm> +#include <sstream> +#include <iomanip> +#include <stdexcept> +#include <time.h> + + +namespace qpid { +namespace log { + +using namespace std; + +typedef sys::Mutex::ScopedLock ScopedLock; + +inline void Logger::enable_unlocked(Statement* s) { + s->enabled=selector.isEnabled(s->level, s->function); +} + +Logger& Logger::instance() { + return boost::details::pool::singleton_default<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); + configure(opts); +} + +Logger::~Logger() {} + +void Logger::select(const Selector& s) { + ScopedLock l(lock); + selector=s; + std::for_each(statements.begin(), statements.end(), + boost::bind(&Logger::enable_unlocked, this, _1)); +} + +Logger::Output::Output() {} +Logger::Output::~Output() {} + +void Logger::log(const Statement& s, const std::string& msg) { + // Format the message outside the lock. + std::ostringstream os; + if (!prefix.empty()) + os << prefix << ": "; + 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) + os << "[0x" << hex << qpid::sys::Thread::logId() << "] "; + if (flags&FILE) + os << s.file << ":"; + if (flags&LINE) + os << dec << s.line << ":"; + if (flags&FUNCTION) + os << s.function << ":"; + if (flags & (FILE|LINE|FUNCTION)) + os << " "; + os << msg << endl; + std::string formatted=os.str(); + { + ScopedLock l(lock); + std::for_each(outputs.begin(), outputs.end(), + boost::bind(&Output::log, _1, s, formatted)); + } +} + +void Logger::output(std::auto_ptr<Output> out) { + ScopedLock l(lock); + outputs.push_back(out.release()); +} + +void Logger::clear() { + select(Selector()); // locked + format(0); // locked + ScopedLock l(lock); + outputs.clear(); +} + +void Logger::format(int formatFlags) { + ScopedLock l(lock); + flags=formatFlags; +} + +static int bitIf(bool test, int bit) { + return test ? bit : 0; +} + +int Logger::format(const Options& opts) { + int flags= + bitIf(opts.level, LEVEL) | + bitIf(opts.time, TIME) | + bitIf(opts.source, (FILE|LINE)) | + bitIf(opts.function, FUNCTION) | + bitIf(opts.thread, THREAD) | + bitIf(opts.hiresTs, HIRES); + format(flags); + return flags; +} + +void Logger::add(Statement& s) { + ScopedLock l(lock); + enable_unlocked(&s); + statements.insert(&s); +} + +void Logger::configure(const Options& opts) { + options = opts; + clear(); + Options o(opts); + if (o.trace) + o.selectors.push_back("trace+"); + format(o); + select(Selector(o)); + setPrefix(opts.prefix); + options.sinkOptions->setup(this); +} + +void Logger::reconfigure(const std::vector<std::string>& selectors) { + options.selectors = selectors; + select(Selector(options)); +} + +void Logger::setPrefix(const std::string& p) { prefix = p; } + +}} // namespace qpid::log diff --git a/qpid/cpp/src/qpid/log/Options.cpp b/qpid/cpp/src/qpid/log/Options.cpp new file mode 100644 index 0000000000..0001d00bdf --- /dev/null +++ b/qpid/cpp/src/qpid/log/Options.cpp @@ -0,0 +1,111 @@ +/* + * + * 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. + * + */ + +#include "qpid/log/Options.h" +#include "qpid/log/SinkOptions.h" +#include "qpid/log/Statement.h" +#include "qpid/Options.h" +#include <map> +#include <string> +#include <algorithm> + +namespace qpid { +namespace log { + +using namespace std; + +Options::Options(const std::string& argv0_, const std::string& name_) : + qpid::Options(name_), + argv0(argv0_), + name(name_), + time(true), + level(true), + thread(false), + source(false), + function(false), + hiresTs(false), + trace(false), + sinkOptions (SinkOptions::create(argv0_)) +{ + selectors.push_back("notice+"); + + ostringstream levels; + levels << LevelTraits::name(Level(0)); + for (int i = 1; i < LevelTraits::COUNT; ++i) + levels << " " << LevelTraits::name(Level(i)); + + addOptions() + ("trace,t", optValue(trace), "Enables all logging" ) + ("log-enable", optValue(selectors, "RULE"), + ("Enables logging for selected levels and components. " + "RULE is in the form 'LEVEL[+][:PATTERN]' " + "Levels are one of: \n\t "+levels.str()+"\n" + "For example:\n" + "\t'--log-enable warning+' " + "logs all warning, error and critical messages.\n" + "\t'--log-enable debug:framing' " + "logs debug messages from the framing namespace. " + "This option can be used multiple times").c_str()) + ("log-time", optValue(time, "yes|no"), "Include time in log messages") + ("log-level", optValue(level,"yes|no"), "Include severity level in log messages") + ("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); +} + +Options::Options(const Options &o) : + qpid::Options(o.name), + argv0(o.argv0), + name(o.name), + selectors(o.selectors), + time(o.time), + level(o.level), + thread(o.thread), + source(o.source), + function(o.function), + hiresTs(o.hiresTs), + trace(o.trace), + prefix(o.prefix), + sinkOptions (SinkOptions::create(o.argv0)) +{ + *sinkOptions = *o.sinkOptions; +} + +Options& Options::operator=(const Options& x) { + if (this != &x) { + argv0 = x.argv0; + name = x.name; + selectors = x.selectors; + time = x.time; + level= x.level; + thread = x.thread; + source = x.source; + function = x.function; + hiresTs = x.hiresTs; + trace = x.trace; + prefix = x.prefix; + *sinkOptions = *x.sinkOptions; + } + return *this; +} + +}} // namespace qpid::log diff --git a/qpid/cpp/src/qpid/log/OstreamOutput.cpp b/qpid/cpp/src/qpid/log/OstreamOutput.cpp new file mode 100644 index 0000000000..9b6ec1f8aa --- /dev/null +++ b/qpid/cpp/src/qpid/log/OstreamOutput.cpp @@ -0,0 +1,41 @@ +/* + * + * 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. + * + */ + +#include "qpid/log/OstreamOutput.h" +#include <stdexcept> + +using namespace std; + +namespace qpid { +namespace log { + +OstreamOutput::OstreamOutput(std::ostream& o) : out(&o) {} + +OstreamOutput::OstreamOutput(const std::string& file) + : out(new ofstream(file.c_str(), ios_base::out | ios_base::app)), + mine(out) +{ + if (!out->good()) + throw std::runtime_error("Can't open log file: "+file); +} + +void OstreamOutput::log(const Statement&, const std::string& m) { + *out << m << flush; +} + +}} // namespace qpid::log diff --git a/qpid/cpp/src/qpid/log/OstreamOutput.h b/qpid/cpp/src/qpid/log/OstreamOutput.h new file mode 100644 index 0000000000..12fd4ce425 --- /dev/null +++ b/qpid/cpp/src/qpid/log/OstreamOutput.h @@ -0,0 +1,41 @@ +#ifndef QPID_LOG_OSTREAMOUTPUT_H +#define QPID_LOG_OSTREAMOUTPUT_H + +/* + * 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/log/Logger.h" +#include <boost/scoped_ptr.hpp> +#include <fstream> +#include <ostream> + +namespace qpid { +namespace log { + +/** + * OstreamOutput is a reusable logging sink that directs logging to a C++ + * ostream. + */ +class OstreamOutput : public qpid::log::Logger::Output { +public: + QPID_COMMON_EXTERN OstreamOutput(std::ostream& o); + QPID_COMMON_EXTERN OstreamOutput(const std::string& file); + + virtual void log(const Statement&, const std::string& m); + +private: + std::ostream* out; + boost::scoped_ptr<std::ostream> mine; +}; + +}} // namespace qpid::log + +#endif /*!QPID_LOG_OSTREAMOUTPUT_H*/ diff --git a/qpid/cpp/src/qpid/log/Selector.cpp b/qpid/cpp/src/qpid/log/Selector.cpp new file mode 100644 index 0000000000..a4bc580470 --- /dev/null +++ b/qpid/cpp/src/qpid/log/Selector.cpp @@ -0,0 +1,68 @@ +/* + * + * 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. + * + */ + +#include "qpid/log/Selector.h" +#include "qpid/log/Options.h" +#include <boost/bind.hpp> +#include <algorithm> +#include <string.h> + +namespace qpid { +namespace log { + +using namespace std; + +void Selector::enable(const string& enableStr) { + string level, pattern; + size_t c=enableStr.find(':'); + if (c==string::npos) { + level=enableStr; + } + else { + level=enableStr.substr(0,c); + pattern=enableStr.substr(c+1); + } + if (!level.empty() && level[level.size()-1]=='+') { + for (int i = LevelTraits::level(level.substr(0,level.size()-1)); + i < LevelTraits::COUNT; + ++i) + enable(Level(i), pattern); + } + else { + enable(LevelTraits::level(level), pattern); + } +} + +Selector::Selector(const Options& opt){ + for_each(opt.selectors.begin(), opt.selectors.end(), + boost::bind(&Selector::enable, this, _1)); +} + +bool Selector::isEnabled(Level level, const char* function) { + const char* functionEnd = function+::strlen(function); + for (std::vector<std::string>::iterator i=substrings[level].begin(); + i != substrings[level].end(); + ++i) + { + if (std::search(function, functionEnd, i->begin(), i->end()) != functionEnd) + return true; + } + return false; +} + +}} // namespace qpid::log diff --git a/qpid/cpp/src/qpid/log/Statement.cpp b/qpid/cpp/src/qpid/log/Statement.cpp new file mode 100644 index 0000000000..6a32b50096 --- /dev/null +++ b/qpid/cpp/src/qpid/log/Statement.cpp @@ -0,0 +1,83 @@ +/* + * + * 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. + * + */ + +#include "qpid/log/Statement.h" +#include "qpid/log/Logger.h" +#include <boost/bind.hpp> +#include <stdexcept> +#include <algorithm> +#include <ctype.h> + +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' }; + +std::string quote(const std::string& str) { + NonPrint nonPrint; + size_t n = std::count_if(str.begin(), str.end(), nonPrint); + 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) { + if (nonPrint(*i)) { + ret.push_back('\\'); + ret.push_back('x'); + ret.push_back(hex[((*i) >> 4)&0xf]); + ret.push_back(hex[(*i) & 0xf]); + } + else ret.push_back(*i); + } + return ret; +} + +} + +void Statement::log(const std::string& message) { + Logger::instance().log(*this, quote(message)); +} + +Statement::Initializer::Initializer(Statement& s) : statement(s) { + Logger::instance().add(s); +} + +namespace { +const char* names[LevelTraits::COUNT] = { + "trace", "debug", "info", "notice", "warning", "error", "critical" +}; + +} // namespace + +Level LevelTraits::level(const char* name) { + for (int i =0; i < LevelTraits::COUNT; ++i) { + if (strcmp(names[i], name)==0) + return Level(i); + } + throw std::runtime_error(std::string("Invalid log level name: ")+name); +} + +const char* LevelTraits::name(Level l) { + return names[l]; +} + +}} // namespace qpid::log diff --git a/qpid/cpp/src/qpid/log/posix/SinkOptions.cpp b/qpid/cpp/src/qpid/log/posix/SinkOptions.cpp new file mode 100644 index 0000000000..292e9147f6 --- /dev/null +++ b/qpid/cpp/src/qpid/log/posix/SinkOptions.cpp @@ -0,0 +1,215 @@ +/* + * + * 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. + * + */ + +#include "qpid/log/posix/SinkOptions.h" +#include "qpid/log/SinkOptions.h" +#include "qpid/log/Logger.h" +#include "qpid/log/OstreamOutput.h" +#include "qpid/memory.h" +#include "qpid/Exception.h" +#include <iostream> +#include <map> +#include <string> +#include <syslog.h> + +using std::string; +using qpid::Exception; + +namespace { + +// SyslogFacilities maps from syslog values to the text equivalents. +class SyslogFacilities { +public: + typedef std::map<string, int> ByName; + typedef std::map<int, string> ByValue; + + SyslogFacilities() { + struct NameValue { const char* name; int value; }; + NameValue nameValue[] = { + { "AUTH", LOG_AUTH }, +#ifdef HAVE_LOG_AUTHPRIV + { "AUTHPRIV", LOG_AUTHPRIV }, +#endif + { "CRON", LOG_CRON }, + { "DAEMON", LOG_DAEMON }, +#ifdef HAVE_LOG_FTP + { "FTP", LOG_FTP }, +#endif + { "KERN", LOG_KERN }, + { "LOCAL0", LOG_LOCAL0 }, + { "LOCAL1", LOG_LOCAL1 }, + { "LOCAL2", LOG_LOCAL2 }, + { "LOCAL3", LOG_LOCAL3 }, + { "LOCAL4", LOG_LOCAL4 }, + { "LOCAL5", LOG_LOCAL5 }, + { "LOCAL6", LOG_LOCAL6 }, + { "LOCAL7", LOG_LOCAL7 }, + { "LPR", LOG_LPR }, + { "MAIL", LOG_MAIL }, + { "NEWS", LOG_NEWS }, + { "SYSLOG", LOG_SYSLOG }, + { "USER", LOG_USER }, + { "UUCP", LOG_UUCP } + }; + for (size_t i = 0; i < sizeof(nameValue)/sizeof(nameValue[0]); ++i) { + byName.insert(ByName::value_type(nameValue[i].name, nameValue[i].value)); + // Recognise with and without LOG_ prefix e.g.: AUTH and LOG_AUTH + byName.insert(ByName::value_type(string("LOG_")+nameValue[i].name, nameValue[i].value)); + byValue.insert(ByValue::value_type(nameValue[i].value, string("LOG_")+nameValue[i].name)); + } + } + + int value(const string& name) const { + string key(name); + std::transform(key.begin(), key.end(), key.begin(), ::toupper); + ByName::const_iterator i = byName.find(key); + if (i == byName.end()) + throw Exception("Not a valid syslog facility: " + name); + return i->second; + } + + string name(int value) const { + ByValue::const_iterator i = byValue.find(value); + if (i == byValue.end()) + throw Exception("Not a valid syslog value: " + value); + return i->second; + } + + private: + ByName byName; + ByValue byValue; +}; + +// 'priorities' maps qpid log levels to syslog priorities. They are in +// order of qpid log levels and must map to: +// "trace", "debug", "info", "notice", "warning", "error", "critical" +static int priorities[qpid::log::LevelTraits::COUNT] = { + LOG_DEBUG, LOG_DEBUG, LOG_INFO, LOG_NOTICE, + LOG_WARNING, LOG_ERR, LOG_CRIT +}; + +std::string basename(const std::string path) { + size_t i = path.find_last_of('/'); + return path.substr((i == std::string::npos) ? 0 : i+1); +} + +} // namespace + +namespace qpid { +namespace log { +namespace posix { + +std::ostream& operator<<(std::ostream& o, const SyslogFacility& f) { + return o << SyslogFacilities().name(f.value); +} + +std::istream& operator>>(std::istream& i, SyslogFacility& f) { + std::string name; + i >> name; + f.value = SyslogFacilities().value(name); + return i; +} + +class SyslogOutput : public qpid::log::Logger::Output { +public: + SyslogOutput(const std::string& logName, const SyslogFacility& logFacility) + : name(logName), facility(logFacility.value) + { + ::openlog(name.c_str(), LOG_PID, facility); + } + + virtual ~SyslogOutput() { + ::closelog(); + } + + virtual void log(const Statement& s, const std::string& m) + { + syslog(priorities[s.level], "%s", m.c_str()); + } + +private: + std::string name; + int facility; +}; + +SinkOptions::SinkOptions(const std::string& argv0) + : qpid::log::SinkOptions(), + logToStderr(true), + logToStdout(false), + logToSyslog(false), + syslogName(basename(argv0)), + syslogFacility(LOG_DAEMON) { + + addOptions() + ("log-to-stderr", optValue(logToStderr, "yes|no"), "Send logging output to stderr") + ("log-to-stdout", optValue(logToStdout, "yes|no"), "Send logging output to stdout") + ("log-to-file", optValue(logFile, "FILE"), "Send log output to FILE.") + ("log-to-syslog", optValue(logToSyslog, "yes|no"), "Send logging output to syslog;\n\tcustomize using --syslog-name and --syslog-facility") + ("syslog-name", optValue(syslogName, "NAME"), "Name to use in syslog messages") + ("syslog-facility", optValue(syslogFacility,"LOG_XXX"), "Facility to use in syslog messages") + ; + +} + +qpid::log::SinkOptions& SinkOptions::operator=(const qpid::log::SinkOptions& rhs) { + const SinkOptions *prhs = dynamic_cast<const SinkOptions*>(&rhs); + if (this != prhs) { + logToStderr = prhs->logToStderr; + logToStdout = prhs->logToStdout; + logToSyslog = prhs->logToSyslog; + logFile = prhs->logFile; + syslogName = prhs->syslogName; + syslogFacility.value = prhs->syslogFacility.value; + } + return *this; +} + +void SinkOptions::detached(void) { + if (logToStderr && !logToStdout && !logToSyslog) { + logToStderr = false; + logToSyslog = true; + } +} + +// The Logger acting on these options calls setup() to request any +// Sinks be set up and fed back to the logger. +void SinkOptions::setup(qpid::log::Logger *logger) { + if (logToStderr) + logger->output(make_auto_ptr<qpid::log::Logger::Output> + (new qpid::log::OstreamOutput(std::clog))); + if (logToStdout) + logger->output(make_auto_ptr<qpid::log::Logger::Output> + (new qpid::log::OstreamOutput(std::cout))); + + if (logFile.length() > 0) + logger->output(make_auto_ptr<qpid::log::Logger::Output> + (new qpid::log::OstreamOutput(logFile))); + + if (logToSyslog) + logger->output(make_auto_ptr<qpid::log::Logger::Output> + (new SyslogOutput(syslogName, syslogFacility))); + +} + +} // namespace qpid::log::posix + +SinkOptions* SinkOptions::create(const std::string& argv0) { + return new qpid::log::posix::SinkOptions (argv0); +} + +}} // namespace qpid::log diff --git a/qpid/cpp/src/qpid/log/posix/SinkOptions.h b/qpid/cpp/src/qpid/log/posix/SinkOptions.h new file mode 100644 index 0000000000..d929c29025 --- /dev/null +++ b/qpid/cpp/src/qpid/log/posix/SinkOptions.h @@ -0,0 +1,64 @@ +#ifndef QPID_LOG_POSIX_SINKOPTIONS_H +#define QPID_LOG_POSIX_SINKOPTIONS_H + +/* + * + * 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. + * + */ + +#include "qpid/log/SinkOptions.h" +#include <string> + +namespace qpid { +namespace log { +namespace posix { + +/** + * Provides a type that can be passed to << and >> operators to convert + * syslog facility values to/from strings. + */ +struct SyslogFacility { + int value; + SyslogFacility(int i=0) : value(i) {} +}; + +struct SinkOptions : public qpid::log::SinkOptions { + SinkOptions(const std::string& argv0); + virtual ~SinkOptions() {} + + virtual qpid::log::SinkOptions& operator=(const qpid::log::SinkOptions& rhs); + + // This allows the caller to indicate that there's no normal outputs + // available. For example, when running as a daemon. In these cases, the + // platform's "syslog"-type output should replace the default stderr + // unless some other sink has been selected. + virtual void detached(void); + + // The Logger acting on these options calls setup() to request any + // Sinks be set up and fed back to the logger. + virtual void setup(qpid::log::Logger *logger); + + bool logToStderr; + bool logToStdout; + bool logToSyslog; + std::string logFile; + std::string syslogName; + SyslogFacility syslogFacility; +}; + +}}} // namespace qpid::log::posix + +#endif /*!QPID_LOG_POSIX_SINKOPTIONS_H*/ diff --git a/qpid/cpp/src/qpid/log/windows/SinkOptions.cpp b/qpid/cpp/src/qpid/log/windows/SinkOptions.cpp new file mode 100644 index 0000000000..0c74bea64e --- /dev/null +++ b/qpid/cpp/src/qpid/log/windows/SinkOptions.cpp @@ -0,0 +1,148 @@ +/* + * + * 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. + * + */ + +#include "qpid/log/windows/SinkOptions.h" +#include "qpid/log/SinkOptions.h" +#include "qpid/log/Logger.h" +#include "qpid/log/OstreamOutput.h" +#include "qpid/memory.h" +#include "qpid/Exception.h" +#include <iostream> +#include <map> +#include <string> + +#include <windows.h> + +using qpid::Exception; + +namespace qpid { +namespace log { +namespace windows { + +namespace { + +// 'eventTypes' maps qpid log levels to Windows event types. They are in +// order of qpid log levels and must map to: +// "trace", "debug", "info", "notice", "warning", "error", "critical" +static int eventTypes[qpid::log::LevelTraits::COUNT] = { + EVENTLOG_INFORMATION_TYPE, /* trace */ + EVENTLOG_INFORMATION_TYPE, /* debug */ + EVENTLOG_INFORMATION_TYPE, /* info */ + EVENTLOG_INFORMATION_TYPE, /* notice */ + EVENTLOG_WARNING_TYPE, /* warning */ + EVENTLOG_ERROR_TYPE, /* error */ + EVENTLOG_ERROR_TYPE /* critical */ +}; + +} // namespace + +class EventLogOutput : public qpid::log::Logger::Output { +public: + EventLogOutput(const std::string& /*sourceName*/) : logHandle(0) + { + logHandle = OpenEventLog(0, "Application"); + } + + virtual ~EventLogOutput() { + if (logHandle) + CloseEventLog(logHandle); + } + + virtual void log(const Statement& s, const std::string& m) + { + if (logHandle) { + const char *msg = m.c_str(); + ReportEvent(logHandle, + eventTypes[s.level], + 0, /* category unused */ + 0, /* event id */ + 0, /* user security id */ + 1, /* number of strings */ + 0, /* no event-specific data */ + &msg, + 0); + } + } + +private: + HANDLE logHandle; +}; + +SinkOptions::SinkOptions(const std::string& /*argv0*/) + : qpid::log::SinkOptions(), + logToStderr(true), + logToStdout(false), + logToEventLog(false), + eventSource("Application") +{ + addOptions() + ("log-to-stderr", optValue(logToStderr, "yes|no"), "Send logging output to stderr") + ("log-to-stdout", optValue(logToStdout, "yes|no"), "Send logging output to stdout") + ("log-to-file", optValue(logFile, "FILE"), "Send log output to FILE.") + ("log-to-eventlog", optValue(logToEventLog, "yes|no"), "Send logging output to event log;\n\tcustomize using --syslog-name and --syslog-facility") + ("eventlog-source-name", optValue(eventSource, "Application"), "Event source to log to") + ; + +} + +qpid::log::SinkOptions& SinkOptions::operator=(const qpid::log::SinkOptions& rhs) { + const SinkOptions *prhs = dynamic_cast<const SinkOptions*>(&rhs); + if (this != prhs) { + logToStderr = prhs->logToStderr; + logToStdout = prhs->logToStdout; + logToEventLog = prhs->logToEventLog; + eventSource = prhs->eventSource; + logFile = prhs->logFile; + } + return *this; +} + +void SinkOptions::detached(void) { + if (logToStderr && !logToStdout && !logToEventLog) { + logToStderr = false; + logToEventLog = true; + } +} + +// The Logger acting on these options calls setup() to request any +// Sinks be set up and fed back to the logger. +void SinkOptions::setup(qpid::log::Logger *logger) { + if (logToStderr) + logger->output(make_auto_ptr<qpid::log::Logger::Output> + (new qpid::log::OstreamOutput(std::clog))); + if (logToStdout) + logger->output(make_auto_ptr<qpid::log::Logger::Output> + (new qpid::log::OstreamOutput(std::cout))); + + if (logFile.length() > 0) + logger->output(make_auto_ptr<qpid::log::Logger::Output> + (new qpid::log::OstreamOutput(logFile))); + + if (logToEventLog) + logger->output(make_auto_ptr<qpid::log::Logger::Output> + (new EventLogOutput(eventSource))); + +} + +} // namespace windows + +SinkOptions* SinkOptions::create(const std::string& argv0) { + return new qpid::log::windows::SinkOptions (argv0); +} + +}} // namespace qpid::log diff --git a/qpid/cpp/src/qpid/log/windows/SinkOptions.h b/qpid/cpp/src/qpid/log/windows/SinkOptions.h new file mode 100644 index 0000000000..f270c504a2 --- /dev/null +++ b/qpid/cpp/src/qpid/log/windows/SinkOptions.h @@ -0,0 +1,54 @@ +#ifndef QPID_LOG_WINDOWS_SINKOPTIONS_H +#define QPID_LOG_WINDOWS_SINKOPTIONS_H + +/* + * + * 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. + * + */ + +#include "qpid/log/SinkOptions.h" +#include <string> + +namespace qpid { +namespace log { +namespace windows { + +struct QPID_COMMON_CLASS_EXTERN SinkOptions : public qpid::log::SinkOptions { + QPID_COMMON_EXTERN SinkOptions(const std::string& argv0); + virtual ~SinkOptions() {} + + QPID_COMMON_EXTERN virtual qpid::log::SinkOptions& operator=(const qpid::log::SinkOptions& rhs); + + // This allows the caller to indicate that there's no normal outputs + // available. For example, when running as a service. In these cases, the + // platform's "syslog"-type output should replace the default stderr + // unless some other sink has been selected. + QPID_COMMON_EXTERN virtual void detached(void); + + // The Logger acting on these options calls setup() to request any + // Sinks be set up and fed back to the logger. + QPID_COMMON_EXTERN virtual void setup(qpid::log::Logger *logger); + + bool logToStderr; + bool logToStdout; + bool logToEventLog; + std::string eventSource; + std::string logFile; +}; + +}}} // namespace qpid::log::windows + +#endif /*!QPID_LOG_WINDOWS_SINKOPTIONS_H*/ diff --git a/qpid/cpp/src/qpid/management/Buffer.cpp b/qpid/cpp/src/qpid/management/Buffer.cpp new file mode 100644 index 0000000000..7556b2a243 --- /dev/null +++ b/qpid/cpp/src/qpid/management/Buffer.cpp @@ -0,0 +1,106 @@ +/* + * 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/management/Buffer.h" +#include "qpid/framing/Buffer.h" +#include "qpid/amqp_0_10/Codecs.h" + +using namespace std; + +namespace qpid { +namespace management { + +Buffer::Buffer(char* data, uint32_t size) : impl(new framing::Buffer(data, size)) {} +Buffer::~Buffer() { delete impl; } +void Buffer::record() { impl->record(); } +void Buffer::restore(bool reRecord) { impl->restore(reRecord); } +void Buffer::reset() { impl->reset(); } +uint32_t Buffer::available() { return impl->available(); } +uint32_t Buffer::getSize() { return impl->getSize(); } +uint32_t Buffer::getPosition() { return impl->getPosition(); } +char* Buffer::getPointer() { return impl->getPointer(); } +void Buffer::putOctet(uint8_t i) { impl->putOctet(i); } +void Buffer::putShort(uint16_t i) { impl->putShort(i); } +void Buffer::putLong(uint32_t i) { impl->putLong(i); } +void Buffer::putLongLong(uint64_t i) { impl->putLongLong(i); } +void Buffer::putInt8(int8_t i) { impl->putInt8(i); } +void Buffer::putInt16(int16_t i) { impl->putInt16(i); } +void Buffer::putInt32(int32_t i) { impl->putInt32(i); } +void Buffer::putInt64(int64_t i) { impl->putInt64(i); } +void Buffer::putFloat(float i) { impl->putFloat(i); } +void Buffer::putDouble(double i) { impl->putDouble(i); } +void Buffer::putBin128(const uint8_t* i) { impl->putBin128(i); } +uint8_t Buffer::getOctet() { return impl->getOctet(); } +uint16_t Buffer::getShort() { return impl->getShort(); } +uint32_t Buffer::getLong() { return impl->getLong(); } +uint64_t Buffer::getLongLong() { return impl->getLongLong(); } +int8_t Buffer:: getInt8() { return impl-> getInt8(); } +int16_t Buffer::getInt16() { return impl->getInt16(); } +int32_t Buffer::getInt32() { return impl->getInt32(); } +int64_t Buffer::getInt64() { return impl->getInt64(); } +float Buffer::getFloat() { return impl->getFloat(); } +double Buffer::getDouble() { return impl->getDouble(); } +void Buffer::putShortString(const string& i) { impl->putShortString(i); } +void Buffer::putMediumString(const string& i) { impl->putMediumString(i); } +void Buffer::putLongString(const string& i) { impl->putLongString(i); } +void Buffer::getShortString(string& i) { impl->getShortString(i); } +void Buffer::getMediumString(string& i) { impl->getMediumString(i); } +void Buffer::getLongString(string& i) { impl->getLongString(i); } +void Buffer::getBin128(uint8_t* i) { impl->getBin128(i); } +void Buffer::putRawData(const string& i) { impl->putRawData(i); } +void Buffer::getRawData(string& s, uint32_t size) { impl->getRawData(s, size); } +void Buffer::putRawData(const uint8_t* data, size_t size) { impl->putRawData(data, size); } +void Buffer::getRawData(uint8_t* data, size_t size) { impl->getRawData(data, size); } + +void Buffer::putMap(const types::Variant::Map& i) +{ + string encoded; + amqp_0_10::MapCodec::encode(i, encoded); + impl->putRawData(encoded); +} + +void Buffer::putList(const types::Variant::List& i) +{ + string encoded; + amqp_0_10::ListCodec::encode(i, encoded); + impl->putRawData(encoded); +} + +void Buffer::getMap(types::Variant::Map& map) +{ + string encoded; + uint32_t saved = impl->getPosition(); + uint32_t length = impl->getLong(); + impl->setPosition(saved); + impl->getRawData(encoded, length + sizeof(uint32_t)); + amqp_0_10::MapCodec::decode(encoded, map); +} + +void Buffer::getList(types::Variant::List& list) +{ + string encoded; + uint32_t saved = impl->getPosition(); + uint32_t length = impl->getLong(); + impl->setPosition(saved); + impl->getRawData(encoded, length + sizeof(uint32_t)); + amqp_0_10::ListCodec::decode(encoded, list); +} + +}} diff --git a/qpid/cpp/src/qpid/management/ConnectionSettings.cpp b/qpid/cpp/src/qpid/management/ConnectionSettings.cpp new file mode 100644 index 0000000000..1421a26867 --- /dev/null +++ b/qpid/cpp/src/qpid/management/ConnectionSettings.cpp @@ -0,0 +1,40 @@ +/* + * + * 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/management/ConnectionSettings.h" +#include "qpid/Version.h" + +qpid::management::ConnectionSettings::ConnectionSettings() : + protocol("tcp"), + host("localhost"), + port(5672), + locale("en_US"), + heartbeat(0), + maxChannels(32767), + maxFrameSize(65535), + bounds(2), + tcpNoDelay(false), + service(qpid::saslName), + minSsf(0), + maxSsf(256) +{} + +qpid::management::ConnectionSettings::~ConnectionSettings() {} + diff --git a/qpid/cpp/src/qpid/management/Manageable.cpp b/qpid/cpp/src/qpid/management/Manageable.cpp new file mode 100644 index 0000000000..651215ffb5 --- /dev/null +++ b/qpid/cpp/src/qpid/management/Manageable.cpp @@ -0,0 +1,53 @@ +// +// 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/management/Manageable.h" + +using namespace qpid::management; +using std::string; + +string Manageable::StatusText (status_t status, string text) +{ + if ((status & STATUS_USER) == STATUS_USER) + return text; + + switch (status) + { + case STATUS_OK : return "OK"; + case STATUS_UNKNOWN_OBJECT : return "UnknownObject"; + case STATUS_UNKNOWN_METHOD : return "UnknownMethod"; + case STATUS_NOT_IMPLEMENTED : return "NotImplemented"; + case STATUS_PARAMETER_INVALID : return "InvalidParameter"; + case STATUS_FEATURE_NOT_IMPLEMENTED : return "FeatureNotImplemented"; + case STATUS_FORBIDDEN : return "Forbidden"; + } + + return "??"; +} + +Manageable::status_t Manageable::ManagementMethod (uint32_t, Args&, std::string&) +{ + return STATUS_UNKNOWN_METHOD; +} + +bool Manageable::AuthorizeMethod(uint32_t, Args&, const std::string&) +{ + return true; +} + diff --git a/qpid/cpp/src/qpid/management/ManagementAgent.cpp b/qpid/cpp/src/qpid/management/ManagementAgent.cpp new file mode 100644 index 0000000000..8a12a57fa6 --- /dev/null +++ b/qpid/cpp/src/qpid/management/ManagementAgent.cpp @@ -0,0 +1,3121 @@ +/* + * + * 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. + * + */ + + +// NOTE on use of log levels: The criteria for using trace vs. debug +// is to use trace for log messages that are generated for each +// unbatched stats/props notification and debug for everything else. + +#include "qpid/management/ManagementAgent.h" +#include "qpid/management/ManagementObject.h" +#include "qpid/broker/DeliverableMessage.h" +#include "qpid/log/Statement.h" +#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" +#include "qpid/types/Uuid.h" +#include "qpid/framing/List.h" +#include "qpid/amqp_0_10/Codecs.h" +#include <list> +#include <iostream> +#include <fstream> +#include <sstream> +#include <typeinfo> + +using boost::intrusive_ptr; +using qpid::framing::Uuid; +using qpid::types::Variant; +using qpid::amqp_0_10::MapCodec; +using qpid::amqp_0_10::ListCodec; +using qpid::sys::Mutex; +using namespace qpid::framing; +using namespace qpid::management; +using namespace qpid::broker; +using namespace qpid; +using namespace std; +namespace _qmf = qmf::org::apache::qpid::broker; + + +namespace { + const string defaultVendorName("vendor"); + const string defaultProductName("product"); + + // Create a valid binding key substring by + // replacing all '.' chars with '_' + const string keyifyNameStr(const string& name) + { + string n2 = name; + + size_t pos = n2.find('.'); + while (pos != n2.npos) { + n2.replace(pos, 1, "_"); + pos = n2.find('.', pos); + } + return n2; + } + +struct ScopedManagementContext +{ + ScopedManagementContext(const qpid::broker::ConnectionState* context) + { + setManagementExecutionContext(context); + } + ~ScopedManagementContext() + { + setManagementExecutionContext(0); + } +}; +} + + +static Variant::Map mapEncodeSchemaId(const string& pname, + const string& cname, + const string& type, + const uint8_t *md5Sum) +{ + Variant::Map map_; + + map_["_package_name"] = pname; + map_["_class_name"] = cname; + map_["_type"] = type; + map_["_hash"] = qpid::types::Uuid(md5Sum); + return map_; +} + + +ManagementAgent::RemoteAgent::~RemoteAgent () +{ + QPID_LOG(debug, "Remote Agent removed bank=[" << brokerBank << "." << agentBank << "]"); + if (mgmtObject != 0) { + mgmtObject->resourceDestroy(); + agent.deleteObjectNowLH(mgmtObject->getObjectId()); + } +} + +ManagementAgent::ManagementAgent (const bool qmfV1, const bool qmfV2) : + threadPoolSize(1), interval(10), broker(0), timer(0), + startTime(sys::now()), + suppressed(false), disallowAllV1Methods(false), + vendorNameKey(defaultVendorName), productNameKey(defaultProductName), + qmf1Support(qmfV1), qmf2Support(qmfV2), maxReplyObjs(100), + msgBuffer(MA_BUFFER_SIZE) +{ + nextObjectId = 1; + brokerBank = 1; + bootSequence = 1; + nextRemoteBank = 10; + nextRequestSequence = 1; + clientWasAdded = false; + attrMap["_vendor"] = defaultVendorName; + attrMap["_product"] = defaultProductName; +} + +ManagementAgent::~ManagementAgent () +{ + { + sys::Mutex::ScopedLock lock (userLock); + + // Reset the shared pointers to exchanges. If this is not done now, the exchanges + // will stick around until dExchange and mExchange are implicitly destroyed (long + // after this destructor completes). Those exchanges hold references to management + // objects that will be invalid. + dExchange.reset(); + mExchange.reset(); + v2Topic.reset(); + v2Direct.reset(); + + moveNewObjectsLH(); + for (ManagementObjectMap::iterator iter = managementObjects.begin (); + iter != managementObjects.end (); + iter++) { + ManagementObject* object = iter->second; + delete object; + } + managementObjects.clear(); + } +} + +void ManagementAgent::configure(const string& _dataDir, uint16_t _interval, + qpid::broker::Broker* _broker, int _threads) +{ + dataDir = _dataDir; + interval = _interval; + broker = _broker; + threadPoolSize = _threads; + ManagementObject::maxThreads = threadPoolSize; + + // Get from file or generate and save to file. + if (dataDir.empty()) + { + uuid.generate(); + QPID_LOG (info, "ManagementAgent has no data directory, generated new broker ID: " + << uuid); + } + else + { + string filename(dataDir + "/.mbrokerdata"); + ifstream inFile(filename.c_str ()); + + if (inFile.good()) + { + inFile >> uuid; + inFile >> bootSequence; + inFile >> nextRemoteBank; + inFile.close(); + if (uuid.isNull()) { + uuid.generate(); + QPID_LOG (info, "No stored broker ID found - ManagementAgent generated broker ID: " << uuid); + } else + QPID_LOG (info, "ManagementAgent restored broker ID: " << uuid); + + // if sequence goes beyond a 12-bit field, skip zero and wrap to 1. + bootSequence++; + if (bootSequence & 0xF000) + bootSequence = 1; + writeData(); + } + else + { + uuid.generate(); + QPID_LOG (info, "ManagementAgent generated broker ID: " << uuid); + writeData(); + } + + QPID_LOG (debug, "ManagementAgent boot sequence: " << bootSequence); + } +} + +void ManagementAgent::pluginsInitialized() { + // Do this here so cluster plugin has the chance to set up the timer. + timer = &broker->getClusterTimer(); + timer->add(new Periodic(*this, interval)); +} + + +void ManagementAgent::setName(const string& vendor, const string& product, const string& instance) +{ + if (vendor.find(':') != vendor.npos) { + throw Exception("vendor string cannot contain a ':' character."); + } + if (product.find(':') != product.npos) { + throw Exception("product string cannot contain a ':' character."); + } + attrMap["_vendor"] = vendor; + attrMap["_product"] = product; + string inst; + if (instance.empty()) { + if (uuid.isNull()) + { + throw Exception("ManagementAgent::configure() must be called if default name is used."); + } + inst = uuid.str(); + } else + inst = instance; + + name_address = vendor + ":" + product + ":" + inst; + attrMap["_instance"] = inst; + attrMap["_name"] = name_address; + + vendorNameKey = keyifyNameStr(vendor); + productNameKey = keyifyNameStr(product); + instanceNameKey = keyifyNameStr(inst); +} + + +void ManagementAgent::getName(string& vendor, string& product, string& instance) +{ + vendor = std::string(attrMap["_vendor"]); + product = std::string(attrMap["_product"]); + instance = std::string(attrMap["_instance"]); +} + + +const std::string& ManagementAgent::getAddress() +{ + return name_address; +} + + +void ManagementAgent::writeData () +{ + string filename (dataDir + "/.mbrokerdata"); + ofstream outFile (filename.c_str ()); + + if (outFile.good()) + { + outFile << uuid << " " << bootSequence << " " << nextRemoteBank << endl; + outFile.close(); + } +} + +void ManagementAgent::setExchange(qpid::broker::Exchange::shared_ptr _mexchange, + qpid::broker::Exchange::shared_ptr _dexchange) +{ + mExchange = _mexchange; + dExchange = _dexchange; +} + +void ManagementAgent::setExchangeV2(qpid::broker::Exchange::shared_ptr _texchange, + qpid::broker::Exchange::shared_ptr _dexchange) +{ + v2Topic = _texchange; + v2Direct = _dexchange; +} + +void ManagementAgent::registerClass (const string& packageName, + const string& className, + uint8_t* md5Sum, + ManagementObject::writeSchemaCall_t schemaCall) +{ + sys::Mutex::ScopedLock lock(userLock); + PackageMap::iterator pIter = findOrAddPackageLH(packageName); + addClassLH(ManagementItem::CLASS_KIND_TABLE, pIter, className, md5Sum, schemaCall); +} + +void ManagementAgent::registerEvent (const string& packageName, + const string& eventName, + uint8_t* md5Sum, + ManagementObject::writeSchemaCall_t schemaCall) +{ + sys::Mutex::ScopedLock lock(userLock); + PackageMap::iterator pIter = findOrAddPackageLH(packageName); + addClassLH(ManagementItem::CLASS_KIND_EVENT, pIter, eventName, md5Sum, schemaCall); +} + +// Deprecated: V1 objects +ObjectId ManagementAgent::addObject(ManagementObject* object, uint64_t persistId, bool persistent) +{ + uint16_t sequence; + uint64_t objectNum; + + sequence = persistent ? 0 : bootSequence; + objectNum = persistId ? persistId : nextObjectId++; + + ObjectId objId(0 /*flags*/, sequence, brokerBank, objectNum); + objId.setV2Key(*object); // let object generate the v2 key + + object->setObjectId(objId); + + { + sys::Mutex::ScopedLock lock(addLock); + newManagementObjects.push_back(object); + } + QPID_LOG(debug, "Management object (V1) added: " << objId.getV2Key()); + return objId; +} + + + +ObjectId ManagementAgent::addObject(ManagementObject* object, + const string& key, + bool persistent) +{ + uint16_t sequence; + + sequence = persistent ? 0 : bootSequence; + + ObjectId objId(0 /*flags*/, sequence, brokerBank); + if (key.empty()) { + objId.setV2Key(*object); // let object generate the key + } else { + objId.setV2Key(key); + } + + object->setObjectId(objId); + { + sys::Mutex::ScopedLock lock(addLock); + newManagementObjects.push_back(object); + } + QPID_LOG(debug, "Management object added: " << objId.getV2Key()); + return objId; +} + +void ManagementAgent::raiseEvent(const ManagementEvent& event, severity_t severity) +{ + static const std::string severityStr[] = { + "emerg", "alert", "crit", "error", "warn", + "note", "info", "debug" + }; + sys::Mutex::ScopedLock lock (userLock); + uint8_t sev = (severity == SEV_DEFAULT) ? event.getSeverity() : (uint8_t) severity; + + if (qmf1Support) { + Buffer outBuffer(eventBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + + encodeHeader(outBuffer, 'e'); + outBuffer.putShortString(event.getPackageName()); + outBuffer.putShortString(event.getEventName()); + outBuffer.putBin128(event.getMd5Sum()); + outBuffer.putLongLong(uint64_t(sys::Duration(sys::EPOCH, sys::now()))); + outBuffer.putOctet(sev); + string sBuf; + event.encode(sBuf); + outBuffer.putRawData(sBuf); + outLen = MA_BUFFER_SIZE - outBuffer.available(); + outBuffer.reset(); + sendBufferLH(outBuffer, outLen, mExchange, + "console.event.1.0." + event.getPackageName() + "." + event.getEventName()); + QPID_LOG(debug, "SEND raiseEvent (v1) class=" << event.getPackageName() << "." << event.getEventName()); + } + + if (qmf2Support) { + Variant::Map map_; + Variant::Map schemaId; + Variant::Map values; + Variant::Map headers; + + map_["_schema_id"] = mapEncodeSchemaId(event.getPackageName(), + event.getEventName(), + "_event", + event.getMd5Sum()); + event.mapEncode(values); + map_["_values"] = values; + map_["_timestamp"] = uint64_t(sys::Duration(sys::EPOCH, sys::now())); + map_["_severity"] = sev; + + headers["method"] = "indication"; + headers["qmf.opcode"] = "_data_indication"; + headers["qmf.content"] = "_event"; + headers["qmf.agent"] = name_address; + + stringstream key; + key << "agent.ind.event." << keyifyNameStr(event.getPackageName()) + << "." << keyifyNameStr(event.getEventName()) + << "." << severityStr[sev] + << "." << vendorNameKey + << "." << productNameKey; + if (!instanceNameKey.empty()) + key << "." << instanceNameKey; + + + string content; + Variant::List list_; + list_.push_back(map_); + ListCodec::encode(list_, content); + sendBufferLH(content, "", headers, "amqp/list", v2Topic, key.str()); + QPID_LOG(debug, "SEND raiseEvent (v2) class=" << event.getPackageName() << "." << event.getEventName()); + } +} + +ManagementAgent::Periodic::Periodic (ManagementAgent& _agent, uint32_t _seconds) + : TimerTask (sys::Duration((_seconds ? _seconds : 1) * sys::TIME_SEC), + "ManagementAgent::periodicProcessing"), + agent(_agent) {} + +ManagementAgent::Periodic::~Periodic () {} + +void ManagementAgent::Periodic::fire () +{ + agent.timer->add (new Periodic (agent, agent.interval)); + agent.periodicProcessing (); +} + +void ManagementAgent::clientAdded (const string& routingKey) +{ + sys::Mutex::ScopedLock lock(userLock); + + // + // If this routing key is not relevant to object updates, exit. + // + if ((routingKey.compare(0, 1, "#") != 0) && + (routingKey.compare(0, 9, "console.#") != 0) && + (routingKey.compare(0, 12, "console.obj.") != 0)) + return; + + // + // Mark local objects for full-update. + // + clientWasAdded = true; + + // + // If the routing key is relevant for local objects only, don't involve + // any of the remote agents. + // + if (routingKey.compare(0, 39, "console.obj.*.*.org.apache.qpid.broker.") == 0) + return; + + std::list<std::string> rkeys; + + for (RemoteAgentMap::iterator aIter = remoteAgents.begin(); + aIter != remoteAgents.end(); + aIter++) { + rkeys.push_back(aIter->second->routingKey); + } + + while (rkeys.size()) { + char localBuffer[16]; + Buffer outBuffer(localBuffer, 16); + uint32_t outLen; + + encodeHeader(outBuffer, 'x'); + outLen = outBuffer.getPosition(); + outBuffer.reset(); + sendBufferLH(outBuffer, outLen, dExchange, rkeys.front()); + QPID_LOG(debug, "SEND ConsoleAddedIndication to=" << rkeys.front()); + rkeys.pop_front(); + } +} + +void ManagementAgent::clusterUpdate() { + // Called on all cluster memebers when a new member joins a cluster. + // Set clientWasAdded so that on the next periodicProcessing we will do + // a full update on all cluster members. + sys::Mutex::ScopedLock l(userLock); + moveNewObjectsLH(); // keep lists consistent with updater/updatee. + moveDeletedObjectsLH(); + clientWasAdded = true; + debugSnapshot("Cluster member joined"); +} + +void ManagementAgent::encodeHeader (Buffer& buf, uint8_t opcode, uint32_t seq) +{ + buf.putOctet ('A'); + buf.putOctet ('M'); + buf.putOctet ('2'); + buf.putOctet (opcode); + buf.putLong (seq); +} + +bool ManagementAgent::checkHeader (Buffer& buf, uint8_t *opcode, uint32_t *seq) +{ + uint8_t h1 = buf.getOctet(); + uint8_t h2 = buf.getOctet(); + uint8_t h3 = buf.getOctet(); + + *opcode = buf.getOctet(); + *seq = buf.getLong(); + + return h1 == 'A' && h2 == 'M' && h3 == '2'; +} + +// NOTE WELL: assumes userLock is held by caller (LH) +// NOTE EVEN WELLER: drops this lock when delivering the message!!! +void ManagementAgent::sendBufferLH(Buffer& buf, + uint32_t length, + qpid::broker::Exchange::shared_ptr exchange, + const string& routingKey) +{ + if (suppressed) { + QPID_LOG(debug, "Suppressing management message to " << routingKey); + return; + } + if (exchange.get() == 0) return; + + intrusive_ptr<Message> msg(new Message()); + AMQFrame method((MessageTransferBody(ProtocolVersion(), exchange->getName (), 0, 0))); + AMQFrame header((AMQHeaderBody())); + AMQFrame content((AMQContentBody())); + + content.castBody<AMQContentBody>()->decode(buf, length); + + method.setEof(false); + header.setBof(false); + header.setEof(false); + content.setBof(false); + + msg->getFrames().append(method); + msg->getFrames().append(header); + + MessageProperties* props = + msg->getFrames().getHeaders()->get<MessageProperties>(true); + props->setContentLength(length); + + DeliveryProperties* dp = + msg->getFrames().getHeaders()->get<DeliveryProperties>(true); + dp->setRoutingKey(routingKey); + + msg->getFrames().append(content); + msg->setIsManagementMessage(true); + + { + sys::Mutex::ScopedUnlock u(userLock); + + DeliverableMessage deliverable (msg); + try { + exchange->route(deliverable, routingKey, 0); + } catch(exception&) {} + } + buf.reset(); +} + + +void ManagementAgent::sendBufferLH(Buffer& buf, + uint32_t length, + const string& exchange, + const string& routingKey) +{ + qpid::broker::Exchange::shared_ptr ex(broker->getExchanges().get(exchange)); + if (ex.get() != 0) + sendBufferLH(buf, length, ex, routingKey); +} + + +// NOTE WELL: assumes userLock is held by caller (LH) +// NOTE EVEN WELLER: drops this lock when delivering the message!!! +void ManagementAgent::sendBufferLH(const string& data, + const string& cid, + const Variant::Map& headers, + const string& content_type, + qpid::broker::Exchange::shared_ptr exchange, + const string& routingKey, + uint64_t ttl_msec) +{ + Variant::Map::const_iterator i; + + if (suppressed) { + QPID_LOG(debug, "Suppressing management message to " << routingKey); + return; + } + if (exchange.get() == 0) return; + + intrusive_ptr<Message> msg(new Message()); + AMQFrame method((MessageTransferBody(ProtocolVersion(), exchange->getName (), 0, 0))); + AMQFrame header((AMQHeaderBody())); + AMQFrame content((AMQContentBody(data))); + + method.setEof(false); + header.setBof(false); + header.setEof(false); + content.setBof(false); + + msg->getFrames().append(method); + msg->getFrames().append(header); + + MessageProperties* props = + msg->getFrames().getHeaders()->get<MessageProperties>(true); + props->setContentLength(data.length()); + if (!cid.empty()) { + props->setCorrelationId(cid); + } + props->setContentType(content_type); + props->setAppId("qmf2"); + + for (i = headers.begin(); i != headers.end(); ++i) { + msg->getOrInsertHeaders().setString(i->first, i->second.asString()); + } + + DeliveryProperties* dp = + msg->getFrames().getHeaders()->get<DeliveryProperties>(true); + dp->setRoutingKey(routingKey); + if (ttl_msec) { + dp->setTtl(ttl_msec); + msg->setTimestamp(broker->getExpiryPolicy()); + } + msg->getFrames().append(content); + msg->setIsManagementMessage(true); + + { + sys::Mutex::ScopedUnlock u(userLock); + + DeliverableMessage deliverable (msg); + try { + exchange->route(deliverable, routingKey, 0); + } catch(exception&) {} + } +} + + +void ManagementAgent::sendBufferLH(const string& data, + const string& cid, + const Variant::Map& headers, + const string& content_type, + const string& exchange, + const string& routingKey, + uint64_t ttl_msec) +{ + qpid::broker::Exchange::shared_ptr ex(broker->getExchanges().get(exchange)); + if (ex.get() != 0) + sendBufferLH(data, cid, headers, content_type, ex, routingKey, ttl_msec); +} + + +/** Objects that have been added since the last periodic poll are temporarily + * saved in the newManagementObjects list. This allows objects to be + * added without needing to block on the userLock (addLock is used instead). + * These new objects need to be integrated into the object database + * (managementObjects) *before* they can be properly managed. This routine + * performs the integration. + * + * Note well: objects on the newManagementObjects list may have been + * marked as "deleted", and, possibly re-added. This would result in + * duplicate object ids. To avoid clashes, don't put deleted objects + * into the active object database. + */ +void ManagementAgent::moveNewObjectsLH() +{ + sys::Mutex::ScopedLock lock (addLock); + while (!newManagementObjects.empty()) { + ManagementObject *object = newManagementObjects.back(); + newManagementObjects.pop_back(); + + if (object->isDeleted()) { + DeletedObject::shared_ptr dptr(new DeletedObject(object, qmf1Support, qmf2Support)); + pendingDeletedObjs[dptr->getKey()].push_back(dptr); + delete object; + } else { // add to active object list, check for duplicates. + ObjectId oid = object->getObjectId(); + ManagementObjectMap::iterator destIter = managementObjects.find(oid); + if (destIter != managementObjects.end()) { + // duplicate found. It is OK if the old object has been marked + // deleted... + ManagementObject *oldObj = destIter->second; + if (oldObj->isDeleted()) { + DeletedObject::shared_ptr dptr(new DeletedObject(oldObj, qmf1Support, qmf2Support)); + pendingDeletedObjs[dptr->getKey()].push_back(dptr); + delete oldObj; + } else { + // Duplicate non-deleted objects? This is a user error - oids must be unique. + // for now, leak the old object (safer than deleting - may still be referenced) + // and complain loudly... + QPID_LOG(error, "Detected two management objects with the same identifier: " << oid); + } + } + managementObjects[oid] = object; + } + } +} + +void ManagementAgent::periodicProcessing (void) +{ +#define BUFSIZE 65536 +#define HEADROOM 4096 + debugSnapshot("Management agent periodic processing"); + sys::Mutex::ScopedLock lock (userLock); + uint32_t contentSize; + string routingKey; + string sBuf; + + uint64_t uptime = sys::Duration(startTime, sys::now()); + static_cast<_qmf::Broker*>(broker->GetManagementObject())->set_uptime(uptime); + + moveNewObjectsLH(); + + // + // Clear the been-here flag on all objects in the map. + // + for (ManagementObjectMap::iterator iter = managementObjects.begin(); + iter != managementObjects.end(); + iter++) { + ManagementObject* object = iter->second; + object->setFlags(0); + if (clientWasAdded) { + object->setForcePublish(true); + } + } + + clientWasAdded = false; + + // first send the pending deletes before sending updates. This prevents a + // "false delete" scenario: if an object was deleted then re-added during + // the last poll cycle, it will have a delete entry and an active entry. + // if we sent the active update first, _then_ the delete update, clients + // would incorrectly think the object was deleted. See QPID-2997 + // + bool objectsDeleted = moveDeletedObjectsLH(); + if (!pendingDeletedObjs.empty()) { + // use a temporary copy of the pending deletes so dropping the lock when + // the buffer is sent is safe. + PendingDeletedObjsMap tmp(pendingDeletedObjs); + pendingDeletedObjs.clear(); + + for (PendingDeletedObjsMap::iterator mIter = tmp.begin(); mIter != tmp.end(); mIter++) { + std::string packageName; + std::string className; + msgBuffer.reset(); + uint32_t v1Objs = 0; + uint32_t v2Objs = 0; + Variant::List list_; + + size_t pos = mIter->first.find(":"); + packageName = mIter->first.substr(0, pos); + className = mIter->first.substr(pos+1); + + for (DeletedObjectList::iterator lIter = mIter->second.begin(); + lIter != mIter->second.end(); lIter++) { + msgBuffer.makeAvailable(HEADROOM); // Make sure there's buffer space. + std::string oid = (*lIter)->objectId; + if (!(*lIter)->encodedV1Config.empty()) { + encodeHeader(msgBuffer, 'c'); + msgBuffer.putRawData((*lIter)->encodedV1Config); + QPID_LOG(trace, "Deleting V1 properties " << oid + << " len=" << (*lIter)->encodedV1Config.size()); + v1Objs++; + } + if (!(*lIter)->encodedV1Inst.empty()) { + encodeHeader(msgBuffer, 'i'); + msgBuffer.putRawData((*lIter)->encodedV1Inst); + QPID_LOG(trace, "Deleting V1 statistics " << oid + << " len=" << (*lIter)->encodedV1Inst.size()); + v1Objs++; + } + if (v1Objs >= maxReplyObjs) { + v1Objs = 0; + contentSize = msgBuffer.getSize(); + stringstream key; + key << "console.obj.1.0." << packageName << "." << className; + msgBuffer.reset(); + sendBufferLH(msgBuffer, contentSize, mExchange, key.str()); // UNLOCKS USERLOCK + QPID_LOG(debug, "SEND V1 Multicast ContentInd V1 (delete) to=" + << key.str() << " len=" << contentSize); + } + + if (!(*lIter)->encodedV2.empty()) { + QPID_LOG(trace, "Deleting V2 " << "map=" << (*lIter)->encodedV2); + list_.push_back((*lIter)->encodedV2); + if (++v2Objs >= maxReplyObjs) { + v2Objs = 0; + + string content; + ListCodec::encode(list_, content); + list_.clear(); + if (content.length()) { + stringstream key; + Variant::Map headers; + key << "agent.ind.data." << keyifyNameStr(packageName) + << "." << keyifyNameStr(className) + << "." << vendorNameKey + << "." << productNameKey; + if (!instanceNameKey.empty()) + key << "." << instanceNameKey; + + headers["method"] = "indication"; + headers["qmf.opcode"] = "_data_indication"; + headers["qmf.content"] = "_data"; + headers["qmf.agent"] = name_address; + + sendBufferLH(content, "", headers, "amqp/list", v2Topic, key.str()); // UNLOCKS USERLOCK + QPID_LOG(debug, "SEND Multicast ContentInd V2 (delete) to=" << key.str() << " len=" << content.length()); + } + } + } + } // end current list + + // send any remaining objects... + + if (v1Objs) { + contentSize = BUFSIZE - msgBuffer.available(); + stringstream key; + key << "console.obj.1.0." << packageName << "." << className; + msgBuffer.reset(); + sendBufferLH(msgBuffer, contentSize, mExchange, key.str()); // UNLOCKS USERLOCK + QPID_LOG(debug, "SEND V1 Multicast ContentInd V1 (delete) to=" << key.str() << " len=" << contentSize); + } + + if (!list_.empty()) { + string content; + ListCodec::encode(list_, content); + list_.clear(); + if (content.length()) { + stringstream key; + Variant::Map headers; + key << "agent.ind.data." << keyifyNameStr(packageName) + << "." << keyifyNameStr(className) + << "." << vendorNameKey + << "." << productNameKey; + if (!instanceNameKey.empty()) + key << "." << instanceNameKey; + + headers["method"] = "indication"; + headers["qmf.opcode"] = "_data_indication"; + headers["qmf.content"] = "_data"; + headers["qmf.agent"] = name_address; + + sendBufferLH(content, "", headers, "amqp/list", v2Topic, key.str()); // UNLOCKS USERLOCK + QPID_LOG(debug, "SEND Multicast ContentInd V2 (delete) to=" << key.str() << " len=" << content.length()); + } + } + } // end map + } + + // + // Process the entire object map. Remember: we drop the userLock each time we call + // sendBuffer(). This allows the managementObjects map to be altered during the + // sendBuffer() call, so always restart the search after a sendBuffer() call + // + while (1) { + msgBuffer.reset(); + Variant::List list_; + uint32_t pcount; + uint32_t scount; + uint32_t v1Objs, v2Objs; + ManagementObjectMap::iterator baseIter; + std::string packageName; + std::string className; + + for (baseIter = managementObjects.begin(); + baseIter != managementObjects.end(); + baseIter++) { + ManagementObject* baseObject = baseIter->second; + // + // Skip until we find a base object requiring processing... + // + if (baseObject->getFlags() == 0) { + packageName = baseObject->getPackageName(); + className = baseObject->getClassName(); + break; + } + } + + if (baseIter == managementObjects.end()) + break; // done - all objects processed + + pcount = scount = 0; + v1Objs = 0; + v2Objs = 0; + list_.clear(); + msgBuffer.reset(); + + for (ManagementObjectMap::iterator iter = baseIter; + iter != managementObjects.end(); + iter++) { + msgBuffer.makeAvailable(HEADROOM); // Make sure there's buffer space + ManagementObject* baseObject = baseIter->second; + ManagementObject* object = iter->second; + bool send_stats, send_props; + if (baseObject->isSameClass(*object) && object->getFlags() == 0) { + object->setFlags(1); + if (object->getConfigChanged() || object->getInstChanged()) + object->setUpdateTime(); + + // skip any objects marked deleted since our first pass. Deal with them + // on the next periodic cycle... + if (object->isDeleted()) { + continue; + } + + send_props = (object->getConfigChanged() || object->getForcePublish()); + send_stats = (object->hasInst() && (object->getInstChanged() || object->getForcePublish())); + + if (send_props && qmf1Support) { + size_t pos = msgBuffer.getPosition(); + encodeHeader(msgBuffer, 'c'); + sBuf.clear(); + object->writeProperties(sBuf); + msgBuffer.putRawData(sBuf); + QPID_LOG(trace, "Changed V1 properties " + << object->getObjectId().getV2Key() + << " len=" << msgBuffer.getPosition()-pos); + ++v1Objs; + } + + if (send_stats && qmf1Support) { + size_t pos = msgBuffer.getPosition(); + encodeHeader(msgBuffer, 'i'); + sBuf.clear(); + object->writeStatistics(sBuf); + msgBuffer.putRawData(sBuf); + QPID_LOG(trace, "Changed V1 statistics " + << object->getObjectId().getV2Key() + << " len=" << msgBuffer.getPosition()-pos); + ++v1Objs; + } + + if ((send_stats || send_props) && qmf2Support) { + 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(), + "_data", + object->getMd5Sum()); + object->writeTimestamps(map_); + object->mapEncodeValues(values, send_props, send_stats); + map_["_values"] = values; + list_.push_back(map_); + v2Objs++; + QPID_LOG(trace, "Changed V2" + << (send_stats? " statistics":"") + << (send_props? " properties":"") + << " map=" << map_); + } + + if (send_props) pcount++; + if (send_stats) scount++; + + object->setForcePublish(false); + + if ((qmf1Support && (v1Objs >= maxReplyObjs)) || + (qmf2Support && (v2Objs >= maxReplyObjs))) + break; // have enough objects, send an indication... + } + } + + if (pcount || scount) { + if (qmf1Support) { + contentSize = BUFSIZE - msgBuffer.available(); + if (contentSize > 0) { + stringstream key; + key << "console.obj.1.0." << packageName << "." << className; + msgBuffer.reset(); + sendBufferLH(msgBuffer, contentSize, mExchange, key.str()); // UNLOCKS USERLOCK + QPID_LOG(debug, "SEND V1 Multicast ContentInd to=" << key.str() + << " props=" << pcount + << " stats=" << scount + << " len=" << contentSize); + } + } + + if (qmf2Support) { + string content; + ListCodec::encode(list_, content); + if (content.length()) { + stringstream key; + Variant::Map headers; + key << "agent.ind.data." << keyifyNameStr(packageName) + << "." << keyifyNameStr(className) + << "." << vendorNameKey + << "." << productNameKey; + if (!instanceNameKey.empty()) + key << "." << instanceNameKey; + + headers["method"] = "indication"; + headers["qmf.opcode"] = "_data_indication"; + headers["qmf.content"] = "_data"; + headers["qmf.agent"] = name_address; + + sendBufferLH(content, "", headers, "amqp/list", v2Topic, key.str()); // UNLOCKS USERLOCK + QPID_LOG(debug, "SEND Multicast ContentInd to=" << key.str() + << " props=" << pcount + << " stats=" << scount + << " len=" << content.length()); + } + } + } + } // end processing updates for all objects + + if (objectsDeleted) deleteOrphanedAgentsLH(); + + // heartbeat generation + + if (qmf1Support) { +#define BUFSIZE 65536 + uint32_t contentSize; + char msgChars[BUFSIZE]; + Buffer msgBuffer(msgChars, BUFSIZE); + encodeHeader(msgBuffer, 'h'); + msgBuffer.putLongLong(uint64_t(sys::Duration(sys::EPOCH, sys::now()))); + + contentSize = BUFSIZE - msgBuffer.available (); + msgBuffer.reset (); + routingKey = "console.heartbeat.1.0"; + sendBufferLH(msgBuffer, contentSize, mExchange, routingKey); + QPID_LOG(debug, "SEND HeartbeatInd to=" << routingKey); + } + + if (qmf2Support) { + std::stringstream addr_key; + + addr_key << "agent.ind.heartbeat." << vendorNameKey << "." << productNameKey; + if (!instanceNameKey.empty()) + addr_key << "." << instanceNameKey; + + Variant::Map map; + Variant::Map headers; + + headers["method"] = "indication"; + headers["qmf.opcode"] = "_agent_heartbeat_indication"; + headers["qmf.agent"] = name_address; + + map["_values"] = attrMap; + map["_values"].asMap()["_timestamp"] = uint64_t(sys::Duration(sys::EPOCH, sys::now())); + map["_values"].asMap()["_heartbeat_interval"] = interval; + map["_values"].asMap()["_epoch"] = bootSequence; + + string content; + MapCodec::encode(map, content); + + // Set TTL (in msecs) on outgoing heartbeat indications based on the interval + // time to prevent stale heartbeats from getting to the consoles. + sendBufferLH(content, "", headers, "amqp/map", v2Topic, addr_key.str(), interval * 2 * 1000); + + QPID_LOG(debug, "SENT AgentHeartbeat name=" << name_address); + } +} + +void ManagementAgent::deleteObjectNowLH(const ObjectId& oid) +{ + ManagementObjectMap::iterator iter = managementObjects.find(oid); + if (iter == managementObjects.end()) + return; + ManagementObject* object = iter->second; + if (!object->isDeleted()) + return; + + // since sendBufferLH drops the userLock, don't call it until we + // are done manipulating the object. +#define DNOW_BUFSIZE 2048 + char msgChars[DNOW_BUFSIZE]; + Buffer msgBuffer(msgChars, DNOW_BUFSIZE); + Variant::List list_; + stringstream v1key, v2key; + + if (qmf1Support) { + string sBuf; + + v1key << "console.obj.1.0." << object->getPackageName() << "." << object->getClassName(); + encodeHeader(msgBuffer, 'c'); + object->writeProperties(sBuf); + msgBuffer.putRawData(sBuf); + } + + if (qmf2Support) { + Variant::Map map_; + Variant::Map values; + + map_["_schema_id"] = mapEncodeSchemaId(object->getPackageName(), + object->getClassName(), + "_data", + object->getMd5Sum()); + object->writeTimestamps(map_); + object->mapEncodeValues(values, true, false); + map_["_values"] = values; + list_.push_back(map_); + v2key << "agent.ind.data." << keyifyNameStr(object->getPackageName()) + << "." << keyifyNameStr(object->getClassName()) + << "." << vendorNameKey + << "." << productNameKey; + if (!instanceNameKey.empty()) + v2key << "." << instanceNameKey; + } + + object = 0; + managementObjects.erase(oid); + + // object deleted, ok to drop lock now. + + if (qmf1Support) { + uint32_t contentSize = msgBuffer.getPosition(); + msgBuffer.reset(); + sendBufferLH(msgBuffer, contentSize, mExchange, v1key.str()); + QPID_LOG(debug, "SEND Immediate(delete) ContentInd to=" << v1key.str()); + } + + if (qmf2Support) { + Variant::Map headers; + headers["method"] = "indication"; + headers["qmf.opcode"] = "_data_indication"; + headers["qmf.content"] = "_data"; + headers["qmf.agent"] = name_address; + + string content; + ListCodec::encode(list_, content); + sendBufferLH(content, "", headers, "amqp/list", v2Topic, v2key.str()); + QPID_LOG(debug, "SEND Immediate(delete) ContentInd to=" << v2key.str()); + } +} + +void ManagementAgent::sendCommandCompleteLH(const string& replyToKey, uint32_t sequence, + uint32_t code, const string& text) +{ + Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + + encodeHeader (outBuffer, 'z', sequence); + outBuffer.putLong (code); + outBuffer.putShortString (text); + outLen = MA_BUFFER_SIZE - outBuffer.available (); + outBuffer.reset (); + sendBufferLH(outBuffer, outLen, dExchange, replyToKey); + QPID_LOG(debug, "SEND CommandCompleteInd code=" << code << " text=" << text << " to=" << + replyToKey << " seq=" << sequence); +} + +void ManagementAgent::sendExceptionLH(const string& rte, const string& rtk, const string& cid, + const string& text, uint32_t code, bool viaLocal) +{ + static const string addr_exchange("qmf.default.direct"); + + Variant::Map map; + Variant::Map headers; + Variant::Map values; + string content; + + headers["method"] = "indication"; + headers["qmf.opcode"] = "_exception"; + headers["qmf.agent"] = viaLocal ? "broker" : name_address; + + values["error_code"] = code; + values["error_text"] = text; + map["_values"] = values; + + MapCodec::encode(map, content); + sendBufferLH(content, cid, headers, "amqp/map", rte, rtk); + + QPID_LOG(debug, "SENT Exception code=" << code <<" text=" << text); +} + +bool ManagementAgent::dispatchCommand (Deliverable& deliverable, + const string& routingKey, + const FieldTable* /*args*/, + const bool topic, + int qmfVersion) +{ + sys::Mutex::ScopedLock lock (userLock); + Message& msg = ((DeliverableMessage&) deliverable).getMessage (); + + if (topic && qmfVersion == 1) { + + // qmf1 is bound only to the topic management exchange. + // Parse the routing key. This management broker should act as though it + // is bound to the exchange to match the following keys: + // + // agent.1.0.# + // broker + // schema.# + + if (routingKey == "broker") { + dispatchAgentCommandLH(msg); + return false; + } + + if (routingKey.length() > 6) { + + if (routingKey.compare(0, 9, "agent.1.0") == 0) { + dispatchAgentCommandLH(msg); + return false; + } + + if (routingKey.compare(0, 8, "agent.1.") == 0) { + return authorizeAgentMessageLH(msg); + } + + if (routingKey.compare(0, 7, "schema.") == 0) { + dispatchAgentCommandLH(msg); + return true; + } + } + } + + if (qmfVersion == 2) { + + if (topic) { + // Intercept messages bound to: + // "console.ind.locate.# - process these messages, and also allow them to be forwarded. + if (routingKey == "console.request.agent_locate") { + dispatchAgentCommandLH(msg); + return true; + } + + } else { // direct exchange + + // Intercept messages bound to: + // "broker" - generic alias for the local broker + // "<name_address>" - the broker agent's proper name + // and do not forward them futher + if (routingKey == "broker" || routingKey == name_address) { + dispatchAgentCommandLH(msg, routingKey == "broker"); + return false; + } + } + } + + return true; +} + +void ManagementAgent::handleMethodRequestLH(Buffer& inBuffer, const string& replyToKey, uint32_t sequence, const ConnectionToken* connToken) +{ + moveNewObjectsLH(); + + string methodName; + string packageName; + string className; + uint8_t hash[16]; + Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + AclModule* acl = broker->getAcl(); + string inArgs; + + string sBuf; + inBuffer.getRawData(sBuf, 16); + ObjectId objId; + objId.decode(sBuf); + inBuffer.getShortString(packageName); + inBuffer.getShortString(className); + inBuffer.getBin128(hash); + inBuffer.getShortString(methodName); + inBuffer.getRawData(inArgs, inBuffer.available()); + + QPID_LOG(debug, "RECV MethodRequest (v1) class=" << packageName << ":" << className << "(" << Uuid(hash) << ") method=" << + methodName << " replyTo=" << replyToKey); + + encodeHeader(outBuffer, 'm', sequence); + + if (disallowAllV1Methods) { + outBuffer.putLong(Manageable::STATUS_FORBIDDEN); + outBuffer.putMediumString("QMFv1 methods forbidden on this broker, use QMFv2"); + outLen = MA_BUFFER_SIZE - outBuffer.available(); + outBuffer.reset(); + sendBufferLH(outBuffer, outLen, dExchange, replyToKey); + QPID_LOG(debug, "SEND MethodResponse status=FORBIDDEN reason='All QMFv1 Methods Forbidden' seq=" << sequence); + return; + } + + DisallowedMethods::const_iterator i = disallowed.find(make_pair(className, methodName)); + if (i != disallowed.end()) { + outBuffer.putLong(Manageable::STATUS_FORBIDDEN); + outBuffer.putMediumString(i->second); + outLen = MA_BUFFER_SIZE - outBuffer.available(); + outBuffer.reset(); + sendBufferLH(outBuffer, outLen, dExchange, replyToKey); + QPID_LOG(debug, "SEND MethodResponse status=FORBIDDEN text=" << i->second << " seq=" << sequence); + return; + } + + string userId = ((const qpid::broker::ConnectionState*) connToken)->getUserId(); + if (acl != 0) { + map<acl::Property, string> params; + params[acl::PROP_SCHEMAPACKAGE] = packageName; + params[acl::PROP_SCHEMACLASS] = className; + + if (!acl->authorise(userId, acl::ACT_ACCESS, acl::OBJ_METHOD, methodName, ¶ms)) { + outBuffer.putLong(Manageable::STATUS_FORBIDDEN); + outBuffer.putMediumString(Manageable::StatusText(Manageable::STATUS_FORBIDDEN)); + outLen = MA_BUFFER_SIZE - outBuffer.available(); + outBuffer.reset(); + sendBufferLH(outBuffer, outLen, dExchange, replyToKey); + QPID_LOG(debug, "SEND MethodResponse status=FORBIDDEN" << " seq=" << sequence); + return; + } + } + + ManagementObjectMap::iterator iter = numericFind(objId); + if (iter == managementObjects.end() || iter->second->isDeleted()) { + outBuffer.putLong (Manageable::STATUS_UNKNOWN_OBJECT); + outBuffer.putMediumString(Manageable::StatusText (Manageable::STATUS_UNKNOWN_OBJECT)); + } else { + if ((iter->second->getPackageName() != packageName) || + (iter->second->getClassName() != className)) { + outBuffer.putLong (Manageable::STATUS_PARAMETER_INVALID); + outBuffer.putMediumString(Manageable::StatusText (Manageable::STATUS_PARAMETER_INVALID)); + } + else + try { + outBuffer.record(); + sys::Mutex::ScopedUnlock u(userLock); + string outBuf; + iter->second->doMethod(methodName, inArgs, outBuf, userId); + outBuffer.putRawData(outBuf); + } catch(exception& e) { + outBuffer.restore(); + outBuffer.putLong(Manageable::STATUS_EXCEPTION); + outBuffer.putMediumString(e.what()); + } + } + + outLen = MA_BUFFER_SIZE - outBuffer.available(); + outBuffer.reset(); + sendBufferLH(outBuffer, outLen, dExchange, replyToKey); + QPID_LOG(debug, "SEND MethodResponse (v1) to=" << replyToKey << " seq=" << sequence); +} + + +void ManagementAgent::handleMethodRequestLH (const string& body, const string& rte, const string& rtk, + const string& cid, const ConnectionToken* connToken, bool viaLocal) +{ + moveNewObjectsLH(); + + string methodName; + Variant::Map inMap; + MapCodec::decode(body, inMap); + Variant::Map::const_iterator oid, mid; + string content; + string error; + uint32_t errorCode(0); + + Variant::Map outMap; + Variant::Map headers; + + headers["method"] = "response"; + headers["qmf.opcode"] = "_method_response"; + headers["qmf.agent"] = viaLocal ? "broker" : name_address; + + if ((oid = inMap.find("_object_id")) == inMap.end() || + (mid = inMap.find("_method_name")) == inMap.end()) { + sendExceptionLH(rte, rtk, cid, Manageable::StatusText(Manageable::STATUS_PARAMETER_INVALID), + Manageable::STATUS_PARAMETER_INVALID, viaLocal); + return; + } + + ObjectId objId; + Variant::Map inArgs; + Variant::Map callMap; + + try { + // coversions will throw if input is invalid. + objId = ObjectId(oid->second.asMap()); + methodName = mid->second.getString(); + + mid = inMap.find("_arguments"); + if (mid != inMap.end()) { + inArgs = (mid->second).asMap(); + } + } catch(exception& e) { + sendExceptionLH(rte, rtk, cid, e.what(), Manageable::STATUS_EXCEPTION, viaLocal); + return; + } + + ManagementObjectMap::iterator iter = managementObjects.find(objId); + + if (iter == managementObjects.end() || iter->second->isDeleted()) { + stringstream estr; + estr << "No object found with ID=" << objId; + sendExceptionLH(rte, rtk, cid, estr.str(), 1, viaLocal); + return; + } + + // validate + AclModule* acl = broker->getAcl(); + DisallowedMethods::const_iterator i; + + i = disallowed.find(make_pair(iter->second->getClassName(), methodName)); + if (i != disallowed.end()) { + sendExceptionLH(rte, rtk, cid, i->second, Manageable::STATUS_FORBIDDEN, viaLocal); + return; + } + + string userId = ((const qpid::broker::ConnectionState*) connToken)->getUserId(); + if (acl != 0) { + map<acl::Property, string> params; + params[acl::PROP_SCHEMAPACKAGE] = iter->second->getPackageName(); + params[acl::PROP_SCHEMACLASS] = iter->second->getClassName(); + + if (!acl->authorise(userId, acl::ACT_ACCESS, acl::OBJ_METHOD, methodName, ¶ms)) { + sendExceptionLH(rte, rtk, cid, Manageable::StatusText(Manageable::STATUS_FORBIDDEN), + Manageable::STATUS_FORBIDDEN, viaLocal); + return; + } + } + + // invoke the method + + QPID_LOG(debug, "RECV MethodRequest (v2) class=" << iter->second->getPackageName() + << ":" << iter->second->getClassName() << " method=" << + methodName << " replyTo=" << rte << "/" << rtk << " objId=" << objId << " inArgs=" << inArgs); + + try { + sys::Mutex::ScopedUnlock u(userLock); + iter->second->doMethod(methodName, inArgs, callMap, userId); + errorCode = callMap["_status_code"].asUint32(); + if (errorCode == 0) { + outMap["_arguments"] = Variant::Map(); + for (Variant::Map::const_iterator iter = callMap.begin(); + iter != callMap.end(); iter++) + if (iter->first != "_status_code" && iter->first != "_status_text") + outMap["_arguments"].asMap()[iter->first] = iter->second; + } else + error = callMap["_status_text"].asString(); + } catch(exception& e) { + sendExceptionLH(rte, rtk, cid, e.what(), Manageable::STATUS_EXCEPTION, viaLocal); + return; + } + + if (errorCode != 0) { + sendExceptionLH(rte, rtk, cid, error, errorCode, viaLocal); + return; + } + + MapCodec::encode(outMap, content); + sendBufferLH(content, cid, headers, "amqp/map", rte, rtk); + QPID_LOG(debug, "SEND MethodResponse (v2) to=" << rte << "/" << rtk << " seq=" << cid << " map=" << outMap); +} + + +void ManagementAgent::handleBrokerRequestLH (Buffer&, const string& replyToKey, uint32_t sequence) +{ + Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + + QPID_LOG(debug, "RECV BrokerRequest replyTo=" << replyToKey); + + encodeHeader (outBuffer, 'b', sequence); + uuid.encode (outBuffer); + + outLen = MA_BUFFER_SIZE - outBuffer.available (); + outBuffer.reset (); + sendBufferLH(outBuffer, outLen, dExchange, replyToKey); + QPID_LOG(debug, "SEND BrokerResponse to=" << replyToKey); +} + +void ManagementAgent::handlePackageQueryLH (Buffer&, const string& replyToKey, uint32_t sequence) +{ + QPID_LOG(debug, "RECV PackageQuery replyTo=" << replyToKey); + Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + + for (PackageMap::iterator pIter = packages.begin (); + pIter != packages.end (); + pIter++) + { + encodeHeader (outBuffer, 'p', sequence); + encodePackageIndication (outBuffer, pIter); + } + + outLen = MA_BUFFER_SIZE - outBuffer.available (); + if (outLen) { + outBuffer.reset (); + sendBufferLH(outBuffer, outLen, dExchange, replyToKey); + QPID_LOG(debug, "SEND PackageInd to=" << replyToKey << " seq=" << sequence); + } + + sendCommandCompleteLH(replyToKey, sequence); +} + +void ManagementAgent::handlePackageIndLH (Buffer& inBuffer, const string& replyToKey, uint32_t sequence) +{ + string packageName; + + inBuffer.getShortString(packageName); + + QPID_LOG(debug, "RECV PackageInd package=" << packageName << " replyTo=" << replyToKey << " seq=" << sequence); + + findOrAddPackageLH(packageName); +} + +void ManagementAgent::handleClassQueryLH(Buffer& inBuffer, const string& replyToKey, uint32_t sequence) +{ + string packageName; + + inBuffer.getShortString(packageName); + + QPID_LOG(debug, "RECV ClassQuery package=" << packageName << " replyTo=" << replyToKey << " seq=" << sequence); + + PackageMap::iterator pIter = packages.find(packageName); + if (pIter != packages.end()) + { + typedef std::pair<SchemaClassKey, uint8_t> _ckeyType; + std::list<_ckeyType> classes; + ClassMap &cMap = pIter->second; + for (ClassMap::iterator cIter = cMap.begin(); + cIter != cMap.end(); + cIter++) { + if (cIter->second.hasSchema()) { + classes.push_back(make_pair(cIter->first, cIter->second.kind)); + } + } + + while (classes.size()) { + Buffer outBuffer(outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + + encodeHeader(outBuffer, 'q', sequence); + encodeClassIndication(outBuffer, packageName, classes.front().first, classes.front().second); + + outLen = MA_BUFFER_SIZE - outBuffer.available(); + outBuffer.reset(); + sendBufferLH(outBuffer, outLen, dExchange, replyToKey); + QPID_LOG(debug, "SEND ClassInd class=" << packageName << ":" << classes.front().first.name << + "(" << Uuid(classes.front().first.hash) << ") to=" << replyToKey << " seq=" << sequence); + classes.pop_front(); + } + + } + sendCommandCompleteLH(replyToKey, sequence); +} + +void ManagementAgent::handleClassIndLH (Buffer& inBuffer, const string& replyToKey, uint32_t) +{ + string packageName; + SchemaClassKey key; + + uint8_t kind = inBuffer.getOctet(); + inBuffer.getShortString(packageName); + inBuffer.getShortString(key.name); + inBuffer.getBin128(key.hash); + + QPID_LOG(debug, "RECV ClassInd class=" << packageName << ":" << key.name << "(" << Uuid(key.hash) << + "), replyTo=" << replyToKey); + + PackageMap::iterator pIter = findOrAddPackageLH(packageName); + ClassMap::iterator cIter = pIter->second.find(key); + if (cIter == pIter->second.end() || !cIter->second.hasSchema()) { + Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + uint32_t sequence = nextRequestSequence++; + + // Schema Request + encodeHeader (outBuffer, 'S', sequence); + outBuffer.putShortString(packageName); + key.encode(outBuffer); + outLen = MA_BUFFER_SIZE - outBuffer.available (); + outBuffer.reset (); + sendBufferLH(outBuffer, outLen, dExchange, replyToKey); + QPID_LOG(debug, "SEND SchemaRequest class=" << packageName << ":" << key.name << "(" << Uuid(key.hash) << + "), to=" << replyToKey << " seq=" << sequence); + + if (cIter != pIter->second.end()) + pIter->second.erase(key); + + pIter->second.insert(pair<SchemaClassKey, SchemaClass>(key, SchemaClass(kind, sequence))); + } +} + +void ManagementAgent::SchemaClass::appendSchema(Buffer& buf) +{ + // If the management package is attached locally (embedded in the broker or + // linked in via plug-in), call the schema handler directly. If the package + // is from a remote management agent, send the stored schema information. + + if (writeSchemaCall != 0) { + string schema; + writeSchemaCall(schema); + buf.putRawData(schema); + } else + buf.putRawData(reinterpret_cast<uint8_t*>(&data[0]), data.size()); +} + +void ManagementAgent::handleSchemaRequestLH(Buffer& inBuffer, const string& rte, const string& rtk, uint32_t sequence) +{ + string packageName; + SchemaClassKey key; + + inBuffer.getShortString (packageName); + key.decode(inBuffer); + + QPID_LOG(debug, "RECV SchemaRequest class=" << packageName << ":" << key.name << "(" << Uuid(key.hash) << + "), replyTo=" << rte << "/" << rtk << " seq=" << sequence); + + PackageMap::iterator pIter = packages.find(packageName); + if (pIter != packages.end()) { + ClassMap& cMap = pIter->second; + ClassMap::iterator cIter = cMap.find(key); + if (cIter != cMap.end()) { + Buffer outBuffer(outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + SchemaClass& classInfo = cIter->second; + + if (classInfo.hasSchema()) { + encodeHeader(outBuffer, 's', sequence); + classInfo.appendSchema(outBuffer); + outLen = MA_BUFFER_SIZE - outBuffer.available(); + outBuffer.reset(); + sendBufferLH(outBuffer, outLen, rte, rtk); + QPID_LOG(debug, "SEND SchemaResponse to=" << rte << "/" << rtk << " seq=" << sequence); + } + else + sendCommandCompleteLH(rtk, sequence, 1, "Schema not available"); + } + else + sendCommandCompleteLH(rtk, sequence, 1, "Class key not found"); + } + else + sendCommandCompleteLH(rtk, sequence, 1, "Package not found"); +} + +void ManagementAgent::handleSchemaResponseLH(Buffer& inBuffer, const string& /*replyToKey*/, uint32_t sequence) +{ + string packageName; + SchemaClassKey key; + + inBuffer.record(); + inBuffer.getOctet(); + inBuffer.getShortString(packageName); + key.decode(inBuffer); + inBuffer.restore(); + + QPID_LOG(debug, "RECV SchemaResponse class=" << packageName << ":" << key.name << "(" << Uuid(key.hash) << ")" << " seq=" << sequence); + + PackageMap::iterator pIter = packages.find(packageName); + if (pIter != packages.end()) { + ClassMap& cMap = pIter->second; + ClassMap::iterator cIter = cMap.find(key); + if (cIter != cMap.end() && cIter->second.pendingSequence == sequence) { + size_t length = validateSchema(inBuffer, cIter->second.kind); + if (length == 0) { + QPID_LOG(warning, "Management Agent received invalid schema response: " << packageName << "." << key.name); + cMap.erase(key); + } else { + cIter->second.data.resize(length); + inBuffer.getRawData(reinterpret_cast<uint8_t*>(&cIter->second.data[0]), length); + + // Publish a class-indication message + Buffer outBuffer(outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + + encodeHeader(outBuffer, 'q'); + encodeClassIndication(outBuffer, pIter->first, cIter->first, cIter->second.kind); + outLen = MA_BUFFER_SIZE - outBuffer.available(); + outBuffer.reset(); + sendBufferLH(outBuffer, outLen, mExchange, "schema.class"); + QPID_LOG(debug, "SEND ClassInd class=" << packageName << ":" << key.name << "(" << Uuid(key.hash) << ")" << + " to=schema.class"); + } + } + } +} + +bool ManagementAgent::bankInUse (uint32_t bank) +{ + for (RemoteAgentMap::iterator aIter = remoteAgents.begin(); + aIter != remoteAgents.end(); + aIter++) + if (aIter->second->agentBank == bank) + return true; + return false; +} + +uint32_t ManagementAgent::allocateNewBank () +{ + while (bankInUse (nextRemoteBank)) + nextRemoteBank++; + + uint32_t allocated = nextRemoteBank++; + writeData (); + return allocated; +} + +uint32_t ManagementAgent::assignBankLH (uint32_t requestedBank) +{ + if (requestedBank == 0 || bankInUse (requestedBank)) + return allocateNewBank (); + return requestedBank; +} + +void ManagementAgent::deleteOrphanedAgentsLH() +{ + list<ObjectId> deleteList; + + for (RemoteAgentMap::const_iterator aIter = remoteAgents.begin(); aIter != remoteAgents.end(); aIter++) { + bool found = false; + + for (ManagementObjectMap::iterator iter = managementObjects.begin(); + iter != managementObjects.end(); + iter++) { + if (iter->first == aIter->first && !iter->second->isDeleted()) { + found = true; + break; + } + } + + if (!found) + deleteList.push_back(aIter->first); + } + + for (list<ObjectId>::const_iterator dIter = deleteList.begin(); dIter != deleteList.end(); dIter++) + remoteAgents.erase(*dIter); +} + +void ManagementAgent::handleAttachRequestLH (Buffer& inBuffer, const string& replyToKey, uint32_t sequence, const ConnectionToken* connToken) +{ + string label; + uint32_t requestedBrokerBank, requestedAgentBank; + uint32_t assignedBank; + ObjectId connectionRef = ((const ConnectionState*) connToken)->GetManagementObject()->getObjectId(); + Uuid systemId; + + moveNewObjectsLH(); + deleteOrphanedAgentsLH(); + RemoteAgentMap::iterator aIter = remoteAgents.find(connectionRef); + if (aIter != remoteAgents.end()) { + // There already exists an agent on this session. Reject the request. + sendCommandCompleteLH(replyToKey, sequence, 1, "Connection already has remote agent"); + return; + } + + inBuffer.getShortString(label); + systemId.decode(inBuffer); + requestedBrokerBank = inBuffer.getLong(); + requestedAgentBank = inBuffer.getLong(); + + QPID_LOG(debug, "RECV (Agent)AttachRequest label=" << label << " reqBrokerBank=" << requestedBrokerBank << + " reqAgentBank=" << requestedAgentBank << " replyTo=" << replyToKey << " seq=" << sequence); + + assignedBank = assignBankLH(requestedAgentBank); + + boost::shared_ptr<RemoteAgent> agent(new RemoteAgent(*this)); + agent->brokerBank = brokerBank; + agent->agentBank = assignedBank; + agent->routingKey = replyToKey; + agent->connectionRef = connectionRef; + agent->mgmtObject = new _qmf::Agent (this, agent.get()); + agent->mgmtObject->set_connectionRef(agent->connectionRef); + agent->mgmtObject->set_label (label); + agent->mgmtObject->set_registeredTo (broker->GetManagementObject()->getObjectId()); + agent->mgmtObject->set_systemId ((const unsigned char*)systemId.data()); + agent->mgmtObject->set_brokerBank (brokerBank); + agent->mgmtObject->set_agentBank (assignedBank); + addObject (agent->mgmtObject, 0); + remoteAgents[connectionRef] = agent; + + QPID_LOG(debug, "Remote Agent registered bank=[" << brokerBank << "." << assignedBank << "] replyTo=" << replyToKey); + + // Send an Attach Response + Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + + encodeHeader (outBuffer, 'a', sequence); + outBuffer.putLong (brokerBank); + outBuffer.putLong (assignedBank); + outLen = MA_BUFFER_SIZE - outBuffer.available (); + outBuffer.reset (); + sendBufferLH(outBuffer, outLen, dExchange, replyToKey); + QPID_LOG(debug, "SEND AttachResponse brokerBank=" << brokerBank << " agentBank=" << assignedBank << + " to=" << replyToKey << " seq=" << sequence); +} + +void ManagementAgent::handleGetQueryLH(Buffer& inBuffer, const string& replyToKey, uint32_t sequence) +{ + FieldTable ft; + FieldTable::ValuePtr value; + + moveNewObjectsLH(); + + ft.decode(inBuffer); + + QPID_LOG(debug, "RECV GetQuery (v1) query=" << ft << " seq=" << sequence); + + value = ft.get("_class"); + if (value.get() == 0 || !value->convertsTo<string>()) { + value = ft.get("_objectid"); + if (value.get() == 0 || !value->convertsTo<string>()) + return; + + ObjectId selector(value->get<string>()); + ManagementObjectMap::iterator iter = numericFind(selector); + if (iter != managementObjects.end()) { + ManagementObject* object = iter->second; + Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + + if (object->getConfigChanged() || object->getInstChanged()) + object->setUpdateTime(); + + if (!object->isDeleted()) { + string sBuf; + encodeHeader(outBuffer, 'g', sequence); + object->writeProperties(sBuf); + outBuffer.putRawData(sBuf); + sBuf.clear(); + object->writeStatistics(sBuf, true); + outBuffer.putRawData(sBuf); + outLen = MA_BUFFER_SIZE - outBuffer.available (); + outBuffer.reset (); + sendBufferLH(outBuffer, outLen, dExchange, replyToKey); + QPID_LOG(debug, "SEND GetResponse (v1) to=" << replyToKey << " seq=" << sequence); + } + } + sendCommandCompleteLH(replyToKey, sequence); + return; + } + + string className (value->get<string>()); + std::list<ObjectId>matches; + + // build up a set of all objects to be dumped + for (ManagementObjectMap::iterator iter = managementObjects.begin(); + iter != managementObjects.end(); + iter++) { + ManagementObject* object = iter->second; + if (object->getClassName () == className) { + matches.push_back(object->getObjectId()); + } + } + + // send them (as sendBufferLH drops the userLock) + Buffer outBuffer (outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + while (matches.size()) { + ObjectId objId = matches.front(); + ManagementObjectMap::iterator oIter = managementObjects.find( objId ); + if (oIter != managementObjects.end()) { + ManagementObject* object = oIter->second; + + if (object->getConfigChanged() || object->getInstChanged()) + object->setUpdateTime(); + + if (!object->isDeleted()) { + string sProps, sStats; + object->writeProperties(sProps); + object->writeStatistics(sStats, true); + + size_t len = 8 + sProps.length() + sStats.length(); // 8 == size of header in bytes. + if (len > MA_BUFFER_SIZE) { + QPID_LOG(error, "Object " << objId << " too large for output buffer - discarded!"); + } else { + if (outBuffer.available() < len) { // not enough room in current buffer, send it. + outLen = MA_BUFFER_SIZE - outBuffer.available (); + outBuffer.reset (); + sendBufferLH(outBuffer, outLen, dExchange, replyToKey); // drops lock + QPID_LOG(debug, "SEND GetResponse (v1) to=" << replyToKey << " seq=" << sequence); + continue; // lock dropped, need to re-find _SAME_ objid as it may have been deleted. + } + encodeHeader(outBuffer, 'g', sequence); + outBuffer.putRawData(sProps); + outBuffer.putRawData(sStats); + } + } + } + matches.pop_front(); + } + + outLen = MA_BUFFER_SIZE - outBuffer.available (); + if (outLen) { + outBuffer.reset (); + sendBufferLH(outBuffer, outLen, dExchange, replyToKey); + QPID_LOG(debug, "SEND GetResponse (v1) to=" << replyToKey << " seq=" << sequence); + } + + sendCommandCompleteLH(replyToKey, sequence); +} + + +void ManagementAgent::handleGetQueryLH(const string& body, const string& rte, const string& rtk, const string& cid, bool viaLocal) +{ + moveNewObjectsLH(); + + Variant::Map inMap; + Variant::Map::const_iterator i; + Variant::Map headers; + + MapCodec::decode(body, inMap); + QPID_LOG(debug, "RECV GetQuery (v2): map=" << inMap << " seq=" << cid); + + headers["method"] = "response"; + headers["qmf.opcode"] = "_query_response"; + headers["qmf.content"] = "_data"; + headers["qmf.agent"] = viaLocal ? "broker" : name_address; + + /* + * Unpack the _what element of the query. Currently we only support OBJECT queries. + */ + i = inMap.find("_what"); + if (i == inMap.end()) { + sendExceptionLH(rte, rtk, cid, "_what element missing in Query"); + return; + } + + if (i->second.getType() != qpid::types::VAR_STRING) { + sendExceptionLH(rte, rtk, cid, "_what element is not a string"); + return; + } + + if (i->second.asString() != "OBJECT") { + sendExceptionLH(rte, rtk, cid, "Query for _what => '" + i->second.asString() + "' not supported"); + return; + } + + string className; + string packageName; + + /* + * Handle the _schema_id element, if supplied. + */ + i = inMap.find("_schema_id"); + if (i != inMap.end() && i->second.getType() == qpid::types::VAR_MAP) { + const Variant::Map& schemaIdMap(i->second.asMap()); + + Variant::Map::const_iterator s_iter = schemaIdMap.find("_class_name"); + if (s_iter != schemaIdMap.end() && s_iter->second.getType() == qpid::types::VAR_STRING) + className = s_iter->second.asString(); + + s_iter = schemaIdMap.find("_package_name"); + if (s_iter != schemaIdMap.end() && s_iter->second.getType() == qpid::types::VAR_STRING) + packageName = s_iter->second.asString(); + } + + + /* + * Unpack the _object_id element of the query if it is present. If it is present, find that one + * object and return it. If it is not present, send a class-based result. + */ + i = inMap.find("_object_id"); + if (i != inMap.end() && i->second.getType() == qpid::types::VAR_MAP) { + Variant::List list_; + ObjectId objId(i->second.asMap()); + + ManagementObjectMap::iterator iter = managementObjects.find(objId); + if (iter != managementObjects.end()) { + ManagementObject* object = iter->second; + + if (object->getConfigChanged() || object->getInstChanged()) + object->setUpdateTime(); + + if (!object->isDeleted()) { + Variant::Map map_; + Variant::Map values; + Variant::Map oidMap; + + object->mapEncodeValues(values, true, true); // write both stats and properties + objId.mapEncode(oidMap); + map_["_values"] = values; + map_["_object_id"] = oidMap; + map_["_schema_id"] = mapEncodeSchemaId(object->getPackageName(), + object->getClassName(), + "_data", + object->getMd5Sum()); + list_.push_back(map_); + } + + string content; + + ListCodec::encode(list_, content); + sendBufferLH(content, cid, headers, "amqp/list", rte, rtk); + QPID_LOG(debug, "SENT QueryResponse (query by object_id) to=" << rte << "/" << rtk); + return; + } + } else { + // send class-based result. + Variant::List _list; + Variant::List _subList; + unsigned int objCount = 0; + + for (ManagementObjectMap::iterator iter = managementObjects.begin(); + iter != managementObjects.end(); + iter++) { + ManagementObject* object = iter->second; + if (object->getClassName() == className && + (packageName.empty() || object->getPackageName() == packageName)) { + + + if (!object->isDeleted()) { + Variant::Map map_; + Variant::Map values; + Variant::Map oidMap; + + if (object->getConfigChanged() || object->getInstChanged()) + object->setUpdateTime(); + + object->writeTimestamps(map_); + object->mapEncodeValues(values, true, true); // write both stats and properties + iter->first.mapEncode(oidMap); + + map_["_values"] = values; + map_["_object_id"] = oidMap; + map_["_schema_id"] = mapEncodeSchemaId(object->getPackageName(), + object->getClassName(), + "_data", + object->getMd5Sum()); + _subList.push_back(map_); + if (++objCount >= maxReplyObjs) { + objCount = 0; + _list.push_back(_subList); + _subList.clear(); + } + } + } + } + + if (_subList.size()) + _list.push_back(_subList); + + headers["partial"] = Variant(); + string content; + while (_list.size() > 1) { + ListCodec::encode(_list.front().asList(), content); + sendBufferLH(content, cid, headers, "amqp/list", rte, rtk); + _list.pop_front(); + QPID_LOG(debug, "SENT QueryResponse (partial, query by schema_id) to=" << rte << "/" << rtk << " len=" << content.length()); + } + headers.erase("partial"); + ListCodec::encode(_list.size() ? _list.front().asList() : Variant::List(), content); + sendBufferLH(content, cid, headers, "amqp/list", rte, rtk); + QPID_LOG(debug, "SENT QueryResponse (query by schema_id) to=" << rte << "/" << rtk << " len=" << content.length()); + return; + } + + // Unrecognized query - Send empty message to indicate CommandComplete + string content; + ListCodec::encode(Variant::List(), content); + sendBufferLH(content, cid, headers, "amqp/list", rte, rtk); + QPID_LOG(debug, "SENT QueryResponse (empty) to=" << rte << "/" << rtk); +} + + +void ManagementAgent::handleLocateRequestLH(const string&, const string& rte, const string& rtk, const string& cid) +{ + QPID_LOG(debug, "RCVD AgentLocateRequest"); + + Variant::Map map; + Variant::Map headers; + + headers["method"] = "indication"; + headers["qmf.opcode"] = "_agent_locate_response"; + headers["qmf.agent"] = name_address; + + map["_values"] = attrMap; + map["_values"].asMap()["_timestamp"] = uint64_t(sys::Duration(sys::EPOCH, sys::now())); + map["_values"].asMap()["_heartbeat_interval"] = interval; + map["_values"].asMap()["_epoch"] = bootSequence; + + string content; + MapCodec::encode(map, content); + sendBufferLH(content, cid, headers, "amqp/map", rte, rtk); + clientWasAdded = true; + + QPID_LOG(debug, "SENT AgentLocateResponse replyTo=" << rte << "/" << rtk); +} + + +bool ManagementAgent::authorizeAgentMessageLH(Message& msg) +{ + Buffer inBuffer (inputBuffer, MA_BUFFER_SIZE); + uint32_t sequence = 0; + bool methodReq = false; + bool mapMsg = false; + string packageName; + string className; + string methodName; + string cid; + + // + // If the message is larger than our working buffer size, we can't determine if it's + // authorized or not. In this case, return true (authorized) if there is no ACL in place, + // otherwise return false; + // + if (msg.encodedSize() > MA_BUFFER_SIZE) + return broker->getAcl() == 0; + + msg.encodeContent(inBuffer); + uint32_t bufferLen = inBuffer.getPosition(); + inBuffer.reset(); + + const framing::MessageProperties* p = + msg.getFrames().getHeaders()->get<framing::MessageProperties>(); + + const framing::FieldTable *headers = msg.getApplicationHeaders(); + + if (headers && msg.getAppId() == "qmf2") + { + mapMsg = true; + + if (p && p->hasCorrelationId()) { + cid = p->getCorrelationId(); + } + + if (headers->getAsString("qmf.opcode") == "_method_request") + { + methodReq = true; + + // extract object id and method name + + string body; + inBuffer.getRawData(body, bufferLen); + Variant::Map inMap; + MapCodec::decode(body, inMap); + Variant::Map::const_iterator oid, mid; + + ObjectId objId; + + if ((oid = inMap.find("_object_id")) == inMap.end() || + (mid = inMap.find("_method_name")) == inMap.end()) { + QPID_LOG(warning, + "Missing fields in QMF authorize req received."); + return false; + } + + try { + // coversions will throw if input is invalid. + objId = ObjectId(oid->second.asMap()); + methodName = mid->second.getString(); + } catch(exception& /*e*/) { + QPID_LOG(warning, + "Badly formatted QMF authorize req received."); + return false; + } + + // look up schema for object to get package and class name + + ManagementObjectMap::iterator iter = managementObjects.find(objId); + + if (iter == managementObjects.end() || iter->second->isDeleted()) { + QPID_LOG(debug, "ManagementAgent::authorizeAgentMessageLH: stale object id " << + objId); + return false; + } + + packageName = iter->second->getPackageName(); + className = iter->second->getClassName(); + } + } else { // old style binary message format + + uint8_t opcode; + + if (!checkHeader(inBuffer, &opcode, &sequence)) + return false; + + if (opcode == 'M') { + methodReq = true; + + // extract method name & schema package and class name + + uint8_t hash[16]; + inBuffer.getLongLong(); // skip over object id + inBuffer.getLongLong(); + inBuffer.getShortString(packageName); + inBuffer.getShortString(className); + inBuffer.getBin128(hash); + inBuffer.getShortString(methodName); + + } + } + + if (methodReq) { + // TODO: check method call against ACL list. + map<acl::Property, string> params; + AclModule* acl = broker->getAcl(); + if (acl == 0) + return true; + + string userId = ((const qpid::broker::ConnectionState*) msg.getPublisher())->getUserId(); + params[acl::PROP_SCHEMAPACKAGE] = packageName; + params[acl::PROP_SCHEMACLASS] = className; + + if (acl->authorise(userId, acl::ACT_ACCESS, acl::OBJ_METHOD, methodName, ¶ms)) + return true; + + // authorization failed, send reply if replyTo present + + const framing::MessageProperties* p = + msg.getFrames().getHeaders()->get<framing::MessageProperties>(); + if (p && p->hasReplyTo()) { + const framing::ReplyTo& rt = p->getReplyTo(); + string rte = rt.getExchange(); + string rtk = rt.getRoutingKey(); + string cid; + if (p && p->hasCorrelationId()) + cid = p->getCorrelationId(); + + if (mapMsg) { + sendExceptionLH(rte, rtk, cid, Manageable::StatusText(Manageable::STATUS_FORBIDDEN), + Manageable::STATUS_FORBIDDEN, false); + } else { + + Buffer outBuffer(outputBuffer, MA_BUFFER_SIZE); + uint32_t outLen; + + encodeHeader(outBuffer, 'm', sequence); + outBuffer.putLong(Manageable::STATUS_FORBIDDEN); + outBuffer.putMediumString(Manageable::StatusText(Manageable::STATUS_FORBIDDEN)); + outLen = MA_BUFFER_SIZE - outBuffer.available(); + outBuffer.reset(); + sendBufferLH(outBuffer, outLen, rte, rtk); + } + + QPID_LOG(debug, "SEND MethodResponse status=FORBIDDEN" << " seq=" << sequence); + } + + return false; + } + + return true; +} + +void ManagementAgent::dispatchAgentCommandLH(Message& msg, bool viaLocal) +{ + string rte; + string rtk; + const framing::MessageProperties* p = + msg.getFrames().getHeaders()->get<framing::MessageProperties>(); + if (p && p->hasReplyTo()) { + const framing::ReplyTo& rt = p->getReplyTo(); + rte = rt.getExchange(); + rtk = rt.getRoutingKey(); + } + else + return; + + Buffer inBuffer(inputBuffer, MA_BUFFER_SIZE); + uint8_t opcode; + + if (msg.encodedSize() > MA_BUFFER_SIZE) { + QPID_LOG(debug, "ManagementAgent::dispatchAgentCommandLH: Message too large: " << + msg.encodedSize()); + return; + } + + msg.encodeContent(inBuffer); + 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") + { + string opcode = headers->getAsString("qmf.opcode"); + string contentType = headers->getAsString("qmf.content"); + string body; + string cid; + inBuffer.getRawData(body, bufferLen); + + if (p && p->hasCorrelationId()) { + cid = p->getCorrelationId(); + } + + if (opcode == "_method_request") + return handleMethodRequestLH(body, rte, rtk, cid, msg.getPublisher(), viaLocal); + else if (opcode == "_query_request") + return handleGetQueryLH(body, rte, rtk, cid, viaLocal); + else if (opcode == "_agent_locate_request") + return handleLocateRequestLH(body, rte, rtk, cid); + + QPID_LOG(warning, "Support for QMF Opcode [" << opcode << "] TBD!!!"); + return; + } + + // old preV2 binary messages + + while (inBuffer.getPosition() < bufferLen) { + uint32_t sequence; + if (!checkHeader(inBuffer, &opcode, &sequence)) + return; + + if (opcode == 'B') handleBrokerRequestLH (inBuffer, rtk, sequence); + else if (opcode == 'P') handlePackageQueryLH (inBuffer, rtk, sequence); + else if (opcode == 'p') handlePackageIndLH (inBuffer, rtk, sequence); + else if (opcode == 'Q') handleClassQueryLH (inBuffer, rtk, sequence); + else if (opcode == 'q') handleClassIndLH (inBuffer, rtk, sequence); + else if (opcode == 'S') handleSchemaRequestLH (inBuffer, rte, rtk, sequence); + else if (opcode == 's') handleSchemaResponseLH (inBuffer, rtk, sequence); + else if (opcode == 'A') handleAttachRequestLH (inBuffer, rtk, sequence, msg.getPublisher()); + else if (opcode == 'G') handleGetQueryLH (inBuffer, rtk, sequence); + else if (opcode == 'M') handleMethodRequestLH (inBuffer, rtk, sequence, msg.getPublisher()); + } +} + +ManagementAgent::PackageMap::iterator ManagementAgent::findOrAddPackageLH(string name) +{ + PackageMap::iterator pIter = packages.find (name); + if (pIter != packages.end ()) + return pIter; + + // No such package found, create a new map entry. + pair<PackageMap::iterator, bool> result = + packages.insert(pair<string, ClassMap>(name, ClassMap())); + QPID_LOG (debug, "ManagementAgent added package " << name); + + // 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 (); + sendBufferLH(outBuffer, outLen, mExchange, "schema.package"); + QPID_LOG(debug, "SEND PackageInd package=" << name << " to=schema.package"); + + return result.first; +} + +void ManagementAgent::addClassLH(uint8_t kind, + PackageMap::iterator pIter, + const string& className, + uint8_t* md5Sum, + ManagementObject::writeSchemaCall_t schemaCall) +{ + SchemaClassKey key; + ClassMap& cMap = pIter->second; + + key.name = className; + memcpy(&key.hash, md5Sum, 16); + + ClassMap::iterator cIter = cMap.find(key); + if (cIter != cMap.end()) + return; + + // No such class found, create a new class with local information. + QPID_LOG (debug, "ManagementAgent added class " << pIter->first << ":" << + key.name); + + cMap.insert(pair<SchemaClassKey, SchemaClass>(key, SchemaClass(kind, schemaCall))); + cIter = cMap.find(key); +} + +void ManagementAgent::encodePackageIndication(Buffer& buf, + PackageMap::iterator pIter) +{ + buf.putShortString((*pIter).first); +} + +void ManagementAgent::encodeClassIndication(Buffer& buf, + const std::string packageName, + const SchemaClassKey key, + uint8_t kind) +{ + buf.putOctet(kind); + buf.putShortString(packageName); + key.encode(buf); +} + +size_t ManagementAgent::validateSchema(Buffer& inBuffer, uint8_t kind) +{ + if (kind == ManagementItem::CLASS_KIND_TABLE) + return validateTableSchema(inBuffer); + else if (kind == ManagementItem::CLASS_KIND_EVENT) + return validateEventSchema(inBuffer); + return 0; +} + +size_t ManagementAgent::validateTableSchema(Buffer& inBuffer) +{ + uint32_t start = inBuffer.getPosition(); + uint32_t end; + string text; + uint8_t hash[16]; + + try { + inBuffer.record(); + uint8_t kind = inBuffer.getOctet(); + if (kind != ManagementItem::CLASS_KIND_TABLE) + return 0; + + inBuffer.getShortString(text); + inBuffer.getShortString(text); + inBuffer.getBin128(hash); + + uint8_t superType = 0; //inBuffer.getOctet(); + + uint16_t propCount = inBuffer.getShort(); + uint16_t statCount = inBuffer.getShort(); + uint16_t methCount = inBuffer.getShort(); + + if (superType == 1) { + inBuffer.getShortString(text); + inBuffer.getShortString(text); + inBuffer.getBin128(hash); + } + + for (uint16_t idx = 0; idx < propCount + statCount; idx++) { + FieldTable ft; + ft.decode(inBuffer); + } + + for (uint16_t idx = 0; idx < methCount; idx++) { + FieldTable ft; + ft.decode(inBuffer); + if (!ft.isSet("argCount")) + return 0; + int argCount = ft.getAsInt("argCount"); + for (int mIdx = 0; mIdx < argCount; mIdx++) { + FieldTable aft; + aft.decode(inBuffer); + } + } + } catch (exception& /*e*/) { + return 0; + } + + end = inBuffer.getPosition(); + inBuffer.restore(); // restore original position + return end - start; +} + +size_t ManagementAgent::validateEventSchema(Buffer& inBuffer) +{ + uint32_t start = inBuffer.getPosition(); + uint32_t end; + string text; + uint8_t hash[16]; + + try { + inBuffer.record(); + uint8_t kind = inBuffer.getOctet(); + if (kind != ManagementItem::CLASS_KIND_EVENT) + return 0; + + inBuffer.getShortString(text); + inBuffer.getShortString(text); + inBuffer.getBin128(hash); + + uint8_t superType = 0; //inBuffer.getOctet(); + + uint16_t argCount = inBuffer.getShort(); + + if (superType == 1) { + inBuffer.getShortString(text); + inBuffer.getShortString(text); + inBuffer.getBin128(hash); + } + for (uint16_t idx = 0; idx < argCount; idx++) { + FieldTable ft; + ft.decode(inBuffer); + } + } catch (exception& /*e*/) { + return 0; + } + + end = inBuffer.getPosition(); + inBuffer.restore(); // restore original position + return end - start; +} + +ManagementObjectMap::iterator ManagementAgent::numericFind(const ObjectId& oid) +{ + ManagementObjectMap::iterator iter = managementObjects.begin(); + for (; iter != managementObjects.end(); iter++) { + if (oid.equalV1(iter->first)) + break; + } + + return iter; +} + +void ManagementAgent::disallow(const string& className, const string& methodName, const string& message) { + disallowed[make_pair(className, methodName)] = message; +} + +void ManagementAgent::SchemaClassKey::mapEncode(Variant::Map& _map) const { + _map["_cname"] = name; + _map["_hash"] = qpid::types::Uuid(hash); +} + +void ManagementAgent::SchemaClassKey::mapDecode(const Variant::Map& _map) { + Variant::Map::const_iterator i; + + if ((i = _map.find("_cname")) != _map.end()) { + name = i->second.asString(); + } + + if ((i = _map.find("_hash")) != _map.end()) { + const qpid::types::Uuid& uuid = i->second.asUuid(); + memcpy(hash, uuid.data(), uuid.size()); + } +} + +void ManagementAgent::SchemaClassKey::encode(qpid::framing::Buffer& buffer) const { + buffer.checkAvailable(encodedBufSize()); + buffer.putShortString(name); + buffer.putBin128(hash); +} + +void ManagementAgent::SchemaClassKey::decode(qpid::framing::Buffer& buffer) { + buffer.checkAvailable(encodedBufSize()); + buffer.getShortString(name); + buffer.getBin128(hash); +} + +uint32_t ManagementAgent::SchemaClassKey::encodedBufSize() const { + return 1 + name.size() + 16 /* bin128 */; +} + +void ManagementAgent::SchemaClass::mapEncode(Variant::Map& _map) const { + _map["_type"] = kind; + _map["_pending_sequence"] = pendingSequence; + _map["_data"] = data; +} + +void ManagementAgent::SchemaClass::mapDecode(const Variant::Map& _map) { + Variant::Map::const_iterator i; + + if ((i = _map.find("_type")) != _map.end()) { + kind = i->second; + } + if ((i = _map.find("_pending_sequence")) != _map.end()) { + pendingSequence = i->second; + } + if ((i = _map.find("_data")) != _map.end()) { + data = i->second.asString(); + } +} + +void ManagementAgent::exportSchemas(string& out) { + Variant::List list_; + Variant::Map map_, kmap, cmap; + + for (PackageMap::const_iterator i = packages.begin(); i != packages.end(); ++i) { + string name = i->first; + const ClassMap& classes = i ->second; + for (ClassMap::const_iterator j = classes.begin(); j != classes.end(); ++j) { + const SchemaClassKey& key = j->first; + const SchemaClass& klass = j->second; + if (klass.writeSchemaCall == 0) { // Ignore built-in schemas. + // Encode name, schema-key, schema-class + + map_.clear(); + kmap.clear(); + cmap.clear(); + + key.mapEncode(kmap); + klass.mapEncode(cmap); + + map_["_pname"] = name; + map_["_key"] = kmap; + map_["_class"] = cmap; + list_.push_back(map_); + } + } + } + + ListCodec::encode(list_, out); +} + +void ManagementAgent::importSchemas(qpid::framing::Buffer& inBuf) { + + string buf(inBuf.getPointer(), inBuf.available()); + Variant::List content; + ListCodec::decode(buf, content); + Variant::List::const_iterator l; + + + for (l = content.begin(); l != content.end(); l++) { + string package; + SchemaClassKey key; + SchemaClass klass; + Variant::Map map_, kmap, cmap; + Variant::Map::const_iterator i; + + map_ = l->asMap(); + + if ((i = map_.find("_pname")) != map_.end()) { + package = i->second.asString(); + + if ((i = map_.find("_key")) != map_.end()) { + key.mapDecode(i->second.asMap()); + + if ((i = map_.find("_class")) != map_.end()) { + klass.mapDecode(i->second.asMap()); + + packages[package][key] = klass; + } + } + } + } +} + +void ManagementAgent::RemoteAgent::mapEncode(Variant::Map& map_) const { + Variant::Map _objId, _values; + + map_["_brokerBank"] = brokerBank; + map_["_agentBank"] = agentBank; + map_["_routingKey"] = routingKey; + + connectionRef.mapEncode(_objId); + map_["_object_id"] = _objId; + + mgmtObject->mapEncodeValues(_values, true, false); + map_["_values"] = _values; +} + +void ManagementAgent::RemoteAgent::mapDecode(const Variant::Map& map_) { + Variant::Map::const_iterator i; + + if ((i = map_.find("_brokerBank")) != map_.end()) { + brokerBank = i->second; + } + + if ((i = map_.find("_agentBank")) != map_.end()) { + agentBank = i->second; + } + + if ((i = map_.find("_routingKey")) != map_.end()) { + routingKey = i->second.getString(); + } + + if ((i = map_.find("_object_id")) != map_.end()) { + connectionRef.mapDecode(i->second.asMap()); + } + + mgmtObject = new _qmf::Agent(&agent, this); + + if ((i = map_.find("_values")) != map_.end()) { + mgmtObject->mapDecodeValues(i->second.asMap()); + } + + // TODO aconway 2010-03-04: see comment in encode(), readProperties doesn't set v2key. + mgmtObject->set_connectionRef(connectionRef); +} + +void ManagementAgent::exportAgents(string& out) { + Variant::List list_; + Variant::Map map_, omap, amap; + + for (RemoteAgentMap::const_iterator i = remoteAgents.begin(); + i != remoteAgents.end(); + ++i) + { + // TODO aconway 2010-03-04: see comment in ManagementAgent::RemoteAgent::encode + boost::shared_ptr<RemoteAgent> agent(i->second); + + map_.clear(); + amap.clear(); + + agent->mapEncode(amap); + map_["_remote_agent"] = amap; + list_.push_back(map_); + } + + ListCodec::encode(list_, out); +} + +void ManagementAgent::importAgents(qpid::framing::Buffer& inBuf) { + string buf(inBuf.getPointer(), inBuf.available()); + Variant::List content; + ListCodec::decode(buf, content); + Variant::List::const_iterator l; + sys::Mutex::ScopedLock lock(userLock); + + for (l = content.begin(); l != content.end(); l++) { + boost::shared_ptr<RemoteAgent> agent(new RemoteAgent(*this)); + Variant::Map map_; + Variant::Map::const_iterator i; + + map_ = l->asMap(); + + if ((i = map_.find("_remote_agent")) != map_.end()) { + + agent->mapDecode(i->second.asMap()); + + addObject (agent->mgmtObject, 0, false); + remoteAgents[agent->connectionRef] = agent; + } + } +} + +namespace { +bool isDeletedMap(const ManagementObjectMap::value_type& value) { + return value.second->isDeleted(); +} + +bool isDeletedVector(const ManagementObjectVector::value_type& value) { + return value->isDeleted(); +} + +string summarizeMap(const char* name, const ManagementObjectMap& map) { + ostringstream o; + size_t deleted = std::count_if(map.begin(), map.end(), isDeletedMap); + o << map.size() << " " << name << " (" << deleted << " deleted), "; + return o.str(); +} + +string summarizeVector(const char* name, const ManagementObjectVector& map) { + ostringstream o; + size_t deleted = std::count_if(map.begin(), map.end(), isDeletedVector); + o << map.size() << " " << name << " (" << deleted << " deleted), "; + return o.str(); +} + +string dumpMap(const ManagementObjectMap& map) { + ostringstream o; + for (ManagementObjectMap::const_iterator i = map.begin(); i != map.end(); ++i) { + o << endl << " " << i->second->getObjectId().getV2Key() + << (i->second->isDeleted() ? " (deleted)" : ""); + } + return o.str(); +} + +string dumpVector(const ManagementObjectVector& map) { + ostringstream o; + for (ManagementObjectVector::const_iterator i = map.begin(); i != map.end(); ++i) { + o << endl << " " << (*i)->getObjectId().getV2Key() + << ((*i)->isDeleted() ? " (deleted)" : ""); + } + return o.str(); +} + +} // namespace + +string ManagementAgent::summarizeAgents() { + ostringstream msg; + if (!remoteAgents.empty()) { + msg << remoteAgents.size() << " agents("; + for (RemoteAgentMap::const_iterator i=remoteAgents.begin(); + i != remoteAgents.end(); ++i) + msg << " " << i->second->routingKey; + msg << "), "; + } + return msg.str(); +} + + +void ManagementAgent::debugSnapshot(const char* title) { + QPID_LOG(debug, title << ": management snapshot: " + << packages.size() << " packages, " + << summarizeMap("objects", managementObjects) + << summarizeVector("new objects ", newManagementObjects) + << pendingDeletedObjs.size() << " pending deletes" + << summarizeAgents()); + + QPID_LOG_IF(trace, managementObjects.size(), + title << ": objects" << dumpMap(managementObjects)); + QPID_LOG_IF(trace, newManagementObjects.size(), + 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); + } + + 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>(); break; //uint8 + case 0x03: out = in->getIntegerValue<uint8_t>(); break; // + // case 0x04: break; //TODO: iso-8859-15 char // char + case 0x08: out = static_cast<bool>(in->getIntegerValue<uint8_t>()); 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. +void ManagementAgent::exportDeletedObjects(DeletedObjectList& outList) +{ + outList.clear(); + + sys::Mutex::ScopedLock lock (userLock); + + moveNewObjectsLH(); + moveDeletedObjectsLH(); + + // now copy the pending deletes into the outList + for (PendingDeletedObjsMap::iterator mIter = pendingDeletedObjs.begin(); + mIter != pendingDeletedObjs.end(); mIter++) { + for (DeletedObjectList::iterator lIter = mIter->second.begin(); + lIter != mIter->second.end(); lIter++) { + outList.push_back(*lIter); + } + } +} + +// Called by cluster to reset the management agent's list of deleted +// objects to match the rest of the cluster. +void ManagementAgent::importDeletedObjects(const DeletedObjectList& inList) +{ + sys::Mutex::ScopedLock lock (userLock); + // Clear out any existing deleted objects + moveNewObjectsLH(); + pendingDeletedObjs.clear(); + ManagementObjectMap::iterator i = managementObjects.begin(); + // Silently drop any deleted objects left over from receiving the update. + while (i != managementObjects.end()) { + ManagementObject* object = i->second; + if (object->isDeleted()) { + delete object; + managementObjects.erase(i++); + } + else ++i; + } + for (DeletedObjectList::const_iterator lIter = inList.begin(); lIter != inList.end(); lIter++) { + + std::string classkey((*lIter)->packageName + std::string(":") + (*lIter)->className); + pendingDeletedObjs[classkey].push_back(*lIter); + } +} + + +// construct a DeletedObject from a management object. +ManagementAgent::DeletedObject::DeletedObject(ManagementObject *src, bool v1, bool v2) + : packageName(src->getPackageName()), + className(src->getClassName()) +{ + bool send_stats = (src->hasInst() && (src->getInstChanged() || src->getForcePublish())); + + stringstream oid; + oid << src->getObjectId(); + objectId = oid.str(); + + if (v1) { + src->writeProperties(encodedV1Config); + if (send_stats) { + src->writeStatistics(encodedV1Inst); + } + } + + if (v2) { + Variant::Map map_; + Variant::Map values; + Variant::Map oid; + + src->getObjectId().mapEncode(oid); + map_["_object_id"] = oid; + map_["_schema_id"] = mapEncodeSchemaId(src->getPackageName(), + src->getClassName(), + "_data", + src->getMd5Sum()); + src->writeTimestamps(map_); + src->mapEncodeValues(values, true, send_stats); + map_["_values"] = values; + + encodedV2 = map_; + } +} + + + +// construct a DeletedObject from an encoded representation. Used by +// clustering to move deleted objects between clustered brokers. See +// DeletedObject::encode() for the reverse. +ManagementAgent::DeletedObject::DeletedObject(const std::string& encoded) +{ + qpid::types::Variant::Map map_; + MapCodec::decode(encoded, map_); + + packageName = map_["_package_name"].getString(); + className = map_["_class_name"].getString(); + objectId = map_["_object_id"].getString(); + + encodedV1Config = map_["_v1_config"].getString(); + encodedV1Inst = map_["_v1_inst"].getString(); + encodedV2 = map_["_v2_data"].asMap(); +} + + +// encode a DeletedObject to a string buffer. Used by +// clustering to move deleted objects between clustered brokers. See +// DeletedObject(const std::string&) for the reverse. +void ManagementAgent::DeletedObject::encode(std::string& toBuffer) +{ + qpid::types::Variant::Map map_; + + + map_["_package_name"] = packageName; + map_["_class_name"] = className; + map_["_object_id"] = objectId; + + map_["_v1_config"] = encodedV1Config; + map_["_v1_inst"] = encodedV1Inst; + map_["_v2_data"] = encodedV2; + + MapCodec::encode(map_, toBuffer); +} + +// Remove Deleted objects, and save for later publishing... +bool ManagementAgent::moveDeletedObjectsLH() { + typedef vector<pair<ObjectId, ManagementObject*> > DeleteList; + DeleteList deleteList; + for (ManagementObjectMap::iterator iter = managementObjects.begin(); + iter != managementObjects.end(); + ++iter) + { + ManagementObject* object = iter->second; + if (object->isDeleted()) deleteList.push_back(*iter); + } + + // Iterate in reverse over deleted object list + for (DeleteList::reverse_iterator iter = deleteList.rbegin(); + iter != deleteList.rend(); + iter++) + { + ManagementObject* delObj = iter->second; + assert(delObj->isDeleted()); + DeletedObject::shared_ptr dptr(new DeletedObject(delObj, qmf1Support, qmf2Support)); + + pendingDeletedObjs[dptr->getKey()].push_back(dptr); + managementObjects.erase(iter->first); + delete iter->second; + } + 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/qpid/cpp/src/qpid/management/ManagementAgent.h b/qpid/cpp/src/qpid/management/ManagementAgent.h new file mode 100644 index 0000000000..fb15dc6ed1 --- /dev/null +++ b/qpid/cpp/src/qpid/management/ManagementAgent.h @@ -0,0 +1,432 @@ +#ifndef _ManagementAgent_ +#define _ManagementAgent_ + +/* + * + * 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/BrokerImportExport.h" +#include "qpid/Options.h" +#include "qpid/broker/Exchange.h" +#include "qpid/framing/Uuid.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Timer.h" +#include "qpid/broker/ConnectionToken.h" +#include "qpid/management/ManagementObject.h" +#include "qpid/management/ManagementEvent.h" +#include "qpid/management/Manageable.h" +#include "qmf/org/apache/qpid/broker/Agent.h" +#include "qpid/types/Variant.h" +#include <qpid/framing/AMQFrame.h> +#include <qpid/framing/FieldValue.h> +#include <qpid/framing/ResizableBuffer.h> +#include <memory> +#include <string> +#include <map> + +namespace qpid { +namespace broker { +class ConnectionState; +} +namespace management { + +class ManagementAgent +{ +private: + + int threadPoolSize; + +public: + typedef enum { + SEV_EMERG = 0, + SEV_ALERT = 1, + SEV_CRIT = 2, + SEV_ERROR = 3, + SEV_WARN = 4, + SEV_NOTE = 5, + SEV_INFO = 6, + SEV_DEBUG = 7, + SEV_DEFAULT = 8 + } severity_t; + + + ManagementAgent (const bool qmfV1, const bool qmfV2); + virtual ~ManagementAgent (); + + /** Called before plugins are initialized */ + void configure (const std::string& dataDir, uint16_t interval, + qpid::broker::Broker* broker, int threadPoolSize); + /** Called after plugins are initialized. */ + void pluginsInitialized(); + + /** Called by cluster to suppress management output during update. */ + void suppress(bool s) { suppressed = s; } + + void setName(const std::string& vendor, + const std::string& product, + const std::string& instance=""); + void getName(std::string& vendor, std::string& product, std::string& instance); + const std::string& getAddress(); + + void setInterval(uint16_t _interval) { interval = _interval; } + void setExchange(qpid::broker::Exchange::shared_ptr mgmtExchange, + qpid::broker::Exchange::shared_ptr directExchange); + void setExchangeV2(qpid::broker::Exchange::shared_ptr topicExchange, + qpid::broker::Exchange::shared_ptr directExchange); + + int getMaxThreads () { return threadPoolSize; } + QPID_BROKER_EXTERN void registerClass (const std::string& packageName, + const std::string& className, + uint8_t* md5Sum, + ManagementObject::writeSchemaCall_t schemaCall); + QPID_BROKER_EXTERN void registerEvent (const std::string& packageName, + const std::string& eventName, + uint8_t* md5Sum, + ManagementObject::writeSchemaCall_t schemaCall); + QPID_BROKER_EXTERN ObjectId addObject (ManagementObject* object, + uint64_t persistId = 0, + bool persistent = false); + QPID_BROKER_EXTERN ObjectId addObject (ManagementObject* object, + const std::string& key, + bool persistent = false); + QPID_BROKER_EXTERN void raiseEvent(const ManagementEvent& event, + severity_t severity = SEV_DEFAULT); + QPID_BROKER_EXTERN void clientAdded (const std::string& routingKey); + + QPID_BROKER_EXTERN void clusterUpdate(); + + bool dispatchCommand (qpid::broker::Deliverable& msg, + const std::string& routingKey, + const framing::FieldTable* args, + const bool topic, + int qmfVersion); + + /** Disallow a method. Attempts to call it will receive an exception with message. */ + void disallow(const std::string& className, const std::string& methodName, const std::string& message); + + /** Disallow all QMFv1 methods (used in clustered brokers). */ + void disallowV1Methods() { disallowAllV1Methods = true; } + + /** Serialize my schemas as a binary blob into schemaOut */ + void exportSchemas(std::string& schemaOut); + + /** Serialize my remote-agent map as a binary blob into agentsOut */ + void exportAgents(std::string& agentsOut); + + /** Decode a serialized schemas and add to my schema cache */ + void importSchemas(framing::Buffer& inBuf); + + /** Decode a serialized agent map */ + void importAgents(framing::Buffer& inBuf); + + // these are in support of the managementSetup-state stuff, for synch'ing clustered brokers + uint64_t getNextObjectId(void) { return nextObjectId; } + void setNextObjectId(uint64_t o) { nextObjectId = o; } + + uint16_t getBootSequence(void) { return bootSequence; } + void setBootSequence(uint16_t b) { bootSequence = b; writeData(); } + + 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 + // update are not visible to the cluster replication code. These + // interfaces allow clustering to gather up all the management + // objects that are deleted in order to allow all clustered + // brokers to publish the same set of deleted objects. + + class DeletedObject { + public: + typedef boost::shared_ptr<DeletedObject> shared_ptr; + DeletedObject(ManagementObject *, bool v1, bool v2); + DeletedObject( const std::string &encoded ); + ~DeletedObject() {}; + void encode( std::string& toBuffer ); + const std::string getKey() const { + // used to batch up objects of the same class type + return std::string(packageName + std::string(":") + className); + } + + private: + friend class ManagementAgent; + + std::string packageName; + std::string className; + std::string objectId; + + std::string encodedV1Config; // qmfv1 properties + std::string encodedV1Inst; // qmfv1 statistics + qpid::types::Variant::Map encodedV2; + }; + + typedef std::vector<DeletedObject::shared_ptr> DeletedObjectList; + + /** returns a snapshot of all currently deleted management objects. */ + void exportDeletedObjects( DeletedObjectList& outList ); + + /** Import a list of deleted objects to send on next publish interval. */ + void importDeletedObjects( const DeletedObjectList& inList ); + +private: + struct Periodic : public qpid::sys::TimerTask + { + ManagementAgent& agent; + + Periodic (ManagementAgent& agent, uint32_t seconds); + virtual ~Periodic (); + void fire (); + }; + + // Storage for tracking remote management agents, attached via the client + // management agent API. + // + struct RemoteAgent : public Manageable + { + ManagementAgent& agent; + uint32_t brokerBank; + uint32_t agentBank; + std::string routingKey; + ObjectId connectionRef; + qmf::org::apache::qpid::broker::Agent* mgmtObject; + RemoteAgent(ManagementAgent& _agent) : agent(_agent), mgmtObject(0) {} + ManagementObject* GetManagementObject (void) const { return mgmtObject; } + + virtual ~RemoteAgent (); + void mapEncode(qpid::types::Variant::Map& _map) const; + void mapDecode(const qpid::types::Variant::Map& _map); + }; + + typedef std::map<ObjectId, boost::shared_ptr<RemoteAgent> > RemoteAgentMap; + + // Storage for known schema classes: + // + // SchemaClassKey -- Key elements for map lookups + // SchemaClassKeyComp -- Comparison class for SchemaClassKey + // SchemaClass -- Non-key elements for classes + // + struct SchemaClassKey + { + std::string name; + uint8_t hash[16]; + + void mapEncode(qpid::types::Variant::Map& _map) const; + void mapDecode(const qpid::types::Variant::Map& _map); + void encode(framing::Buffer& buffer) const; + void decode(framing::Buffer& buffer); + uint32_t encodedBufSize() const; + }; + + struct SchemaClassKeyComp + { + bool operator() (const SchemaClassKey& lhs, const SchemaClassKey& rhs) const + { + if (lhs.name != rhs.name) + return lhs.name < rhs.name; + else + for (int i = 0; i < 16; i++) + if (lhs.hash[i] != rhs.hash[i]) + return lhs.hash[i] < rhs.hash[i]; + return false; + } + }; + + + struct SchemaClass + { + uint8_t kind; + ManagementObject::writeSchemaCall_t writeSchemaCall; + std::string data; + uint32_t pendingSequence; + + SchemaClass(uint8_t _kind=0, uint32_t seq=0) : + kind(_kind), writeSchemaCall(0), pendingSequence(seq) {} + SchemaClass(uint8_t _kind, ManagementObject::writeSchemaCall_t call) : + kind(_kind), writeSchemaCall(call), pendingSequence(0) {} + bool hasSchema () { return (writeSchemaCall != 0) || !data.empty(); } + void appendSchema (framing::Buffer& buf); + + void mapEncode(qpid::types::Variant::Map& _map) const; + void mapDecode(const qpid::types::Variant::Map& _map); + }; + + typedef std::map<SchemaClassKey, SchemaClass, SchemaClassKeyComp> ClassMap; + typedef std::map<std::string, ClassMap> PackageMap; + + RemoteAgentMap remoteAgents; + PackageMap packages; + + // + // Protected by userLock + // + ManagementObjectMap managementObjects; + + // + // Protected by addLock + // + ManagementObjectVector newManagementObjects; + + framing::Uuid uuid; + + // + // Lock hierarchy: If a thread needs to take both addLock and userLock, + // it MUST take userLock first, then addLock. + // + sys::Mutex userLock; + sys::Mutex addLock; + + qpid::broker::Exchange::shared_ptr mExchange; + qpid::broker::Exchange::shared_ptr dExchange; + qpid::broker::Exchange::shared_ptr v2Topic; + qpid::broker::Exchange::shared_ptr v2Direct; + std::string dataDir; + uint16_t interval; + qpid::broker::Broker* broker; + qpid::sys::Timer* timer; + uint16_t bootSequence; + uint32_t nextObjectId; + uint32_t brokerBank; + uint32_t nextRemoteBank; + uint32_t nextRequestSequence; + bool clientWasAdded; + const qpid::sys::AbsTime startTime; + bool suppressed; + + typedef std::pair<std::string,std::string> MethodName; + typedef std::map<MethodName, std::string> DisallowedMethods; + DisallowedMethods disallowed; + bool disallowAllV1Methods; + + // Agent name and address + qpid::types::Variant::Map attrMap; + std::string name_address; + std::string vendorNameKey; // "." --> "_" + std::string productNameKey; // "." --> "_" + std::string instanceNameKey; // "." --> "_" + + // supported management protocol + bool qmf1Support; + bool qmf2Support; + + // Maximum # of objects allowed in a single V2 response + // message. + uint32_t maxReplyObjs; + + // list of objects that have been deleted, but have yet to be published + // one final time. + // Indexed by a string composed of the object's package and class name. + // Protected by userLock. + typedef std::map<std::string, DeletedObjectList> PendingDeletedObjsMap; + PendingDeletedObjsMap pendingDeletedObjs; + +# define MA_BUFFER_SIZE 65536 + char inputBuffer[MA_BUFFER_SIZE]; + char outputBuffer[MA_BUFFER_SIZE]; + char eventBuffer[MA_BUFFER_SIZE]; + framing::ResizableBuffer msgBuffer; + + void writeData (); + void periodicProcessing (void); + void deleteObjectNowLH(const ObjectId& oid); + void encodeHeader (framing::Buffer& buf, uint8_t opcode, uint32_t seq = 0); + bool checkHeader (framing::Buffer& buf, uint8_t *opcode, uint32_t *seq); + void sendBufferLH(framing::Buffer& buf, + uint32_t length, + qpid::broker::Exchange::shared_ptr exchange, + const std::string& routingKey); + void sendBufferLH(framing::Buffer& buf, + uint32_t length, + const std::string& exchange, + const std::string& routingKey); + void sendBufferLH(const std::string& data, + const std::string& cid, + const qpid::types::Variant::Map& headers, + const std::string& content_type, + qpid::broker::Exchange::shared_ptr exchange, + const std::string& routingKey, + uint64_t ttl_msec = 0); + void sendBufferLH(const std::string& data, + const std::string& cid, + const qpid::types::Variant::Map& headers, + const std::string& content_type, + const std::string& exchange, + const std::string& routingKey, + uint64_t ttl_msec = 0); + void moveNewObjectsLH(); + bool moveDeletedObjectsLH(); + + bool authorizeAgentMessageLH(qpid::broker::Message& msg); + void dispatchAgentCommandLH(qpid::broker::Message& msg, bool viaLocal=false); + + PackageMap::iterator findOrAddPackageLH(std::string name); + void addClassLH(uint8_t kind, + PackageMap::iterator pIter, + const std::string& className, + uint8_t* md5Sum, + ManagementObject::writeSchemaCall_t schemaCall); + void encodePackageIndication (framing::Buffer& buf, + PackageMap::iterator pIter); + void encodeClassIndication (framing::Buffer& buf, + const std::string packageName, + const struct SchemaClassKey key, + uint8_t kind); + bool bankInUse (uint32_t bank); + uint32_t allocateNewBank (); + uint32_t assignBankLH (uint32_t requestedPrefix); + void deleteOrphanedAgentsLH(); + void sendCommandCompleteLH(const std::string& replyToKey, uint32_t sequence, + uint32_t code = 0, const std::string& text = "OK"); + void sendExceptionLH(const std::string& rte, const std::string& rtk, const std::string& cid, const std::string& text, uint32_t code=1, bool viaLocal=false); + void handleBrokerRequestLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence); + void handlePackageQueryLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence); + void handlePackageIndLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence); + void handleClassQueryLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence); + void handleClassIndLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence); + void handleSchemaRequestLH (framing::Buffer& inBuffer, const std::string& replyToEx, const std::string& replyToKey, uint32_t sequence); + void handleSchemaResponseLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence); + void handleAttachRequestLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence, const qpid::broker::ConnectionToken* connToken); + void handleGetQueryLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence); + void handleMethodRequestLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence, const qpid::broker::ConnectionToken* connToken); + void handleGetQueryLH (const std::string& body, const std::string& replyToEx, const std::string& replyToKey, const std::string& cid, bool viaLocal); + void handleMethodRequestLH (const std::string& body, const std::string& replyToEx, const std::string& replyToKey, const std::string& cid, const qpid::broker::ConnectionToken* connToken, bool viaLocal); + void handleLocateRequestLH (const std::string& body, const std::string& replyToEx, const std::string &replyToKey, const std::string& cid); + + + size_t validateSchema(framing::Buffer&, uint8_t kind); + size_t validateTableSchema(framing::Buffer&); + size_t validateEventSchema(framing::Buffer&); + ManagementObjectMap::iterator numericFind(const ObjectId& oid); + + std::string summarizeAgents(); + void debugSnapshot(const char* title); +}; + +void setManagementExecutionContext(const qpid::broker::ConnectionState*); +const qpid::broker::ConnectionState* getManagementExecutionContext(); +}} + +#endif /*!_ManagementAgent_*/ diff --git a/qpid/cpp/src/qpid/management/ManagementDirectExchange.cpp b/qpid/cpp/src/qpid/management/ManagementDirectExchange.cpp new file mode 100644 index 0000000000..1d5f8bbd6b --- /dev/null +++ b/qpid/cpp/src/qpid/management/ManagementDirectExchange.cpp @@ -0,0 +1,67 @@ +/* + * + * 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/management/ManagementDirectExchange.h" +#include "qpid/log/Statement.h" +#include <assert.h> + +using namespace qpid::management; +using namespace qpid::broker; +using namespace qpid::framing; +using namespace qpid::sys; + +ManagementDirectExchange::ManagementDirectExchange(const string& _name, Manageable* _parent, Broker* b) : + Exchange (_name, _parent, b), + DirectExchange(_name, _parent, b), + managementAgent(0) {} +ManagementDirectExchange::ManagementDirectExchange(const std::string& _name, + bool _durable, + const FieldTable& _args, + Manageable* _parent, Broker* b) : + Exchange (_name, _durable, _args, _parent, b), + DirectExchange(_name, _durable, _args, _parent, b), + managementAgent(0) {} + +void ManagementDirectExchange::route(Deliverable& msg, + const string& routingKey, + const FieldTable* args) +{ + bool routeIt = true; + + if (managementAgent) + routeIt = managementAgent->dispatchCommand(msg, routingKey, args, false, qmfVersion); + + if (routeIt) + DirectExchange::route(msg, routingKey, args); +} + +void ManagementDirectExchange::setManagmentAgent(ManagementAgent* agent, int qv) +{ + managementAgent = agent; + qmfVersion = qv; + assert(qmfVersion == 2); // QMFv1 doesn't use a specialized direct exchange +} + + +ManagementDirectExchange::~ManagementDirectExchange() {} + +const std::string ManagementDirectExchange::typeName("management-direct"); + diff --git a/qpid/cpp/src/qpid/management/ManagementDirectExchange.h b/qpid/cpp/src/qpid/management/ManagementDirectExchange.h new file mode 100644 index 0000000000..7507179c06 --- /dev/null +++ b/qpid/cpp/src/qpid/management/ManagementDirectExchange.h @@ -0,0 +1,59 @@ +/* + * + * 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 _ManagementDirectExchange_ +#define _ManagementDirectExchange_ + +#include "qpid/broker/DirectExchange.h" +#include "qpid/management/ManagementAgent.h" + +namespace qpid { +namespace broker { + +class ManagementDirectExchange : public virtual DirectExchange +{ + private: + management::ManagementAgent* managementAgent; + int qmfVersion; + + public: + static const std::string typeName; + + ManagementDirectExchange(const std::string& name, Manageable* _parent = 0, Broker* broker = 0); + ManagementDirectExchange(const std::string& _name, bool _durable, + const qpid::framing::FieldTable& _args, + Manageable* _parent = 0, Broker* broker = 0); + + virtual std::string getType() const { return typeName; } + + virtual void route(Deliverable& msg, + const std::string& routingKey, + const qpid::framing::FieldTable* args); + + void setManagmentAgent(management::ManagementAgent* agent, int qmfVersion); + + virtual ~ManagementDirectExchange(); +}; + + +} +} + +#endif diff --git a/qpid/cpp/src/qpid/management/ManagementObject.cpp b/qpid/cpp/src/qpid/management/ManagementObject.cpp new file mode 100644 index 0000000000..b4d469afbe --- /dev/null +++ b/qpid/cpp/src/qpid/management/ManagementObject.cpp @@ -0,0 +1,385 @@ +/* + * + * 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/management/Manageable.h" +#include "qpid/management/ManagementObject.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/framing/Buffer.h" +#include "qpid/sys/Time.h" +#include "qpid/sys/Thread.h" +#include "qpid/log/Statement.h" +#include <boost/lexical_cast.hpp> + +#include <stdlib.h> + +using namespace std; +using namespace qpid; +using namespace qpid::management; + +void AgentAttachment::setBanks(uint32_t broker, uint32_t bank) +{ + first = + ((uint64_t) (broker & 0x000fffff)) << 28 | + ((uint64_t) (bank & 0x0fffffff)); +} + +// Deprecated +ObjectId::ObjectId(uint8_t flags, uint16_t seq, uint32_t broker, uint64_t object) + : agent(0), agentEpoch(seq) +{ + first = + ((uint64_t) (flags & 0x0f)) << 60 | + ((uint64_t) (seq & 0x0fff)) << 48 | + ((uint64_t) (broker & 0x000fffff)) << 28; + second = object; +} + + +ObjectId::ObjectId(uint8_t flags, uint16_t seq, uint32_t broker) + : agent(0), second(0), agentEpoch(seq) +{ + first = + ((uint64_t) (flags & 0x0f)) << 60 | + ((uint64_t) (seq & 0x0fff)) << 48 | + ((uint64_t) (broker & 0x000fffff)) << 28; +} + +ObjectId::ObjectId(AgentAttachment* _agent, uint8_t flags, uint16_t seq) + : agent(_agent), second(0), agentEpoch(seq) +{ + + first = + ((uint64_t) (flags & 0x0f)) << 60 | + ((uint64_t) (seq & 0x0fff)) << 48; +} + + +ObjectId::ObjectId(istream& in) : agent(0) +{ + string text; + in >> text; + fromString(text); +} + +ObjectId::ObjectId(const string& text) : agent(0) +{ + fromString(text); +} + +void ObjectId::fromString(const string& text) +{ +#define FIELDS 5 +#if defined (_WIN32) && !defined (atoll) +# define atoll(X) _atoi64(X) +#endif + + // format: + // V1: <flags>-<sequence>-<broker-bank>-<agent-bank>-<uint64-app-id> + // V2: Not used + + string copy(text.c_str()); + char* cText; + char* field[FIELDS]; + bool atFieldStart = true; + int idx = 0; + + cText = const_cast<char*>(copy.c_str()); + for (char* cursor = cText; *cursor; cursor++) { + if (atFieldStart) { + if (idx >= FIELDS) + throw Exception("Invalid ObjectId format"); + field[idx++] = cursor; + atFieldStart = false; + } else { + if (*cursor == '-') { + *cursor = '\0'; + atFieldStart = true; + } + } + } + + if (idx != FIELDS) + throw Exception("Invalid ObjectId format"); + + agentEpoch = atoll(field[1]); + + first = (atoll(field[0]) << 60) + + (atoll(field[1]) << 48) + + (atoll(field[2]) << 28); + + agentName = string(field[3]); + second = atoll(field[4]); +} + + +bool ObjectId::operator==(const ObjectId &other) const +{ + return v2Key == other.v2Key; +} + +bool ObjectId::operator<(const ObjectId &other) const +{ + return v2Key < other.v2Key; +} + +bool ObjectId::equalV1(const ObjectId &other) const +{ + uint64_t otherFirst = agent == 0 ? other.first : other.first & 0xffff000000000000LL; + return first == otherFirst && second == other.second; +} + +// encode as V1-format binary +void ObjectId::encode(string& buffer) const +{ + const uint32_t len = 16; + char _data[len]; + qpid::framing::Buffer body(_data, len); + + if (agent == 0) + body.putLongLong(first); + else + body.putLongLong(first | agent->first); + body.putLongLong(second); + + body.reset(); + body.getRawData(buffer, len); +} + +// decode as V1-format binary +void ObjectId::decode(const string& buffer) +{ + const uint32_t len = 16; + char _data[len]; + qpid::framing::Buffer body(_data, len); + + body.checkAvailable(buffer.length()); + body.putRawData(buffer); + body.reset(); + first = body.getLongLong(); + second = body.getLongLong(); + v2Key = boost::lexical_cast<string>(second); +} + +// generate the V2 key from the index fields defined +// in the schema. +void ObjectId::setV2Key(const ManagementObject& object) +{ + stringstream oname; + oname << object.getPackageName() << ":" << object.getClassName() << ":" << object.getKey(); + v2Key = oname.str(); +} + + +// encode as V2-format map +void ObjectId::mapEncode(types::Variant::Map& map) const +{ + map["_object_name"] = v2Key; + if (!agentName.empty()) + map["_agent_name"] = agentName; + if (agentEpoch) + map["_agent_epoch"] = agentEpoch; +} + +// decode as v2-format map +void ObjectId::mapDecode(const types::Variant::Map& map) +{ + types::Variant::Map::const_iterator i; + + if ((i = map.find("_object_name")) != map.end()) + v2Key = i->second.asString(); + else + throw Exception("Required _object_name field missing."); + + if ((i = map.find("_agent_name")) != map.end()) + agentName = i->second.asString(); + + if ((i = map.find("_agent_epoch")) != map.end()) + agentEpoch = i->second.asInt64(); +} + + +ObjectId::operator types::Variant::Map() const +{ + types::Variant::Map m; + mapEncode(m); + return m; +} + + + +namespace qpid { +namespace management { + +ostream& operator<<(ostream& out, const ObjectId& i) +{ + uint64_t virtFirst = i.first; + if (i.agent) + virtFirst |= i.agent->getFirst(); + + out << ((virtFirst & 0xF000000000000000LL) >> 60) << + "-" << ((virtFirst & 0x0FFF000000000000LL) >> 48) << + "-" << ((virtFirst & 0x0000FFFFF0000000LL) >> 28) << + "-" << i.agentName << + "-" << i.second << + "(" << i.v2Key << ")"; + return out; +} + +}} + +ManagementObject::ManagementObject(Manageable* _core) : +createTime(qpid::sys::Duration(sys::EPOCH, sys::now())), + destroyTime(0), updateTime(createTime), configChanged(true), + instChanged(true), deleted(false), + coreObject(_core), flags(0), forcePublish(false) {} + +void ManagementObject::setUpdateTime() +{ + updateTime = sys::Duration(sys::EPOCH, sys::now()); +} + +void ManagementObject::resourceDestroy() +{ + QPID_LOG(trace, "Management object marked deleted: " << getObjectId().getV2Key()); + destroyTime = sys::Duration(sys::EPOCH, sys::now()); + deleted = true; +} + +int ManagementObject::maxThreads = 1; +int ManagementObject::nextThreadIndex = 0; + +void ManagementObject::writeTimestamps (string& buf) const +{ + char _data[4000]; + qpid::framing::Buffer body(_data, 4000); + + body.putShortString (getPackageName ()); + body.putShortString (getClassName ()); + body.putBin128 (getMd5Sum ()); + body.putLongLong (updateTime); + body.putLongLong (createTime); + body.putLongLong (destroyTime); + + uint32_t len = body.getPosition(); + body.reset(); + body.getRawData(buf, len); + + string oid; + objectId.encode(oid); + buf += oid; +} + +void ManagementObject::readTimestamps (const string& buf) +{ + char _data[4000]; + qpid::framing::Buffer body(_data, 4000); + string unused; + uint8_t unusedUuid[16]; + + body.checkAvailable(buf.length()); + body.putRawData(buf); + body.reset(); + + body.getShortString(unused); + body.getShortString(unused); + body.getBin128(unusedUuid); + updateTime = body.getLongLong(); + createTime = body.getLongLong(); + destroyTime = body.getLongLong(); +} + +uint32_t ManagementObject::writeTimestampsSize() const +{ + return 1 + getPackageName().length() + // str8 + 1 + getClassName().length() + // str8 + 16 + // bin128 + 8 + // uint64 + 8 + // uint64 + 8 + // uint64 + objectId.encodedSize(); // objectId +} + + +void ManagementObject::writeTimestamps (types::Variant::Map& map) const +{ + // types::Variant::Map oid, sid; + + // sid["_package_name"] = getPackageName(); + // sid["_class_name"] = getClassName(); + // sid["_hash"] = qpid::types::Uuid(getMd5Sum()); + // map["_schema_id"] = sid; + + // objectId.mapEncode(oid); + // map["_object_id"] = oid; + + map["_update_ts"] = updateTime; + map["_create_ts"] = createTime; + map["_delete_ts"] = destroyTime; +} + +void ManagementObject::readTimestamps (const types::Variant::Map& map) +{ + types::Variant::Map::const_iterator i; + + if ((i = map.find("_update_ts")) != map.end()) + updateTime = i->second.asUint64(); + if ((i = map.find("_create_ts")) != map.end()) + createTime = i->second.asUint64(); + if ((i = map.find("_delete_ts")) != map.end()) + destroyTime = i->second.asUint64(); +} + + +void ManagementObject::setReference(ObjectId) {} + +int ManagementObject::getThreadIndex() { + static QPID_TSS int thisIndex = -1; + if (thisIndex == -1) { + Mutex::ScopedLock mutex(accessLock); + thisIndex = nextThreadIndex; + if (nextThreadIndex < maxThreads - 1) + nextThreadIndex++; + } + return thisIndex; +} + + +// void ManagementObject::mapEncode(types::Variant::Map& map, +// bool includeProperties, +// bool includeStatistics) +// { +// types::Variant::Map values; + +// writeTimestamps(map); + +// mapEncodeValues(values, includeProperties, includeStatistics); +// map["_values"] = values; +// } + +// void ManagementObject::mapDecode(const types::Variant::Map& map) +// { +// types::Variant::Map::const_iterator i; + +// readTimestamps(map); + +// if ((i = map.find("_values")) != map.end()) +// mapDecodeValues(i->second.asMap()); +// } diff --git a/qpid/cpp/src/qpid/management/ManagementTopicExchange.cpp b/qpid/cpp/src/qpid/management/ManagementTopicExchange.cpp new file mode 100644 index 0000000000..ee8657646f --- /dev/null +++ b/qpid/cpp/src/qpid/management/ManagementTopicExchange.cpp @@ -0,0 +1,75 @@ +/* + * + * 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/management/ManagementTopicExchange.h" +#include "qpid/log/Statement.h" + +using namespace qpid::management; +using namespace qpid::broker; +using namespace qpid::framing; +using namespace qpid::sys; + +ManagementTopicExchange::ManagementTopicExchange(const string& _name, Manageable* _parent, Broker* b) : + Exchange (_name, _parent, b), + TopicExchange(_name, _parent, b), + managementAgent(0) {} +ManagementTopicExchange::ManagementTopicExchange(const std::string& _name, + bool _durable, + const FieldTable& _args, + Manageable* _parent, Broker* b) : + Exchange (_name, _durable, _args, _parent, b), + TopicExchange(_name, _durable, _args, _parent, b), + managementAgent(0) {} + +void ManagementTopicExchange::route(Deliverable& msg, + const string& routingKey, + const FieldTable* args) +{ + bool routeIt = true; + + // Intercept management agent commands + if (managementAgent) + routeIt = managementAgent->dispatchCommand(msg, routingKey, args, true, qmfVersion); + + if (routeIt) + TopicExchange::route(msg, routingKey, args); +} + +bool ManagementTopicExchange::bind(Queue::shared_ptr queue, + const string& routingKey, + const qpid::framing::FieldTable* args) +{ + if (qmfVersion == 1) + managementAgent->clientAdded(routingKey); + return TopicExchange::bind(queue, routingKey, args); +} + +void ManagementTopicExchange::setManagmentAgent(ManagementAgent* agent, int qv) +{ + managementAgent = agent; + qmfVersion = qv; +} + + +ManagementTopicExchange::~ManagementTopicExchange() {} + +const std::string ManagementTopicExchange::typeName("management-topic"); + diff --git a/qpid/cpp/src/qpid/management/ManagementTopicExchange.h b/qpid/cpp/src/qpid/management/ManagementTopicExchange.h new file mode 100644 index 0000000000..232300265e --- /dev/null +++ b/qpid/cpp/src/qpid/management/ManagementTopicExchange.h @@ -0,0 +1,63 @@ +/* + * + * 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 _ManagementTopicExchange_ +#define _ManagementTopicExchange_ + +#include "qpid/broker/TopicExchange.h" +#include "qpid/management/ManagementAgent.h" + +namespace qpid { +namespace broker { + +class ManagementTopicExchange : public virtual TopicExchange +{ + private: + management::ManagementAgent* managementAgent; + int qmfVersion; + + public: + static const std::string typeName; + + ManagementTopicExchange(const std::string& name, Manageable* _parent = 0, Broker* broker = 0); + ManagementTopicExchange(const std::string& _name, bool _durable, + const qpid::framing::FieldTable& _args, + Manageable* _parent = 0, Broker* broker = 0); + + virtual std::string getType() const { return typeName; } + + virtual void route(Deliverable& msg, + const std::string& routingKey, + const qpid::framing::FieldTable* args); + + virtual bool bind(Queue::shared_ptr queue, + const std::string& routingKey, + const qpid::framing::FieldTable* args); + + void setManagmentAgent(management::ManagementAgent* agent, int qmfVersion); + + virtual ~ManagementTopicExchange(); +}; + + +} +} + +#endif diff --git a/qpid/cpp/src/qpid/management/Mutex.cpp b/qpid/cpp/src/qpid/management/Mutex.cpp new file mode 100644 index 0000000000..f05abb01dc --- /dev/null +++ b/qpid/cpp/src/qpid/management/Mutex.cpp @@ -0,0 +1,29 @@ +/* + * + * Copyright (c) 2008 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. + * + */ + +#include "qpid/management/Mutex.h" +#include "qpid/sys/Mutex.h" + +using namespace std; +using namespace qpid::management; + +Mutex::Mutex() : impl(new sys::Mutex()) {} +Mutex::~Mutex() { delete impl; } +void Mutex::lock() { impl->lock(); } +void Mutex::unlock() { impl->unlock(); } + diff --git a/qpid/cpp/src/qpid/memory.h b/qpid/cpp/src/qpid/memory.h new file mode 100644 index 0000000000..99d7a71e7b --- /dev/null +++ b/qpid/cpp/src/qpid/memory.h @@ -0,0 +1,32 @@ +#ifndef QPID_AUTO_PTR_H +#define QPID_AUTO_PTR_H + +/* + * + * 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. + * + */ + +#include <memory> +namespace qpid { +/** Convenient template for creating auto_ptr in-place in an argument list. */ +template <class T> +std::auto_ptr<T> make_auto_ptr(T* ptr) { return std::auto_ptr<T>(ptr); } + +} // namespace qpid + + + +#endif /*!QPID_AUTO_PTR_H*/ diff --git a/qpid/cpp/src/qpid/messaging/Address.cpp b/qpid/cpp/src/qpid/messaging/Address.cpp new file mode 100644 index 0000000000..a516959edb --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/Address.cpp @@ -0,0 +1,151 @@ +/* + * + * 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/framing/Uuid.h" +#include <sstream> +#include <boost/format.hpp> + +namespace qpid { +namespace messaging { + +using namespace qpid::types; + +namespace { +const std::string SUBJECT_DIVIDER = "/"; +const std::string OPTIONS_DIVIDER = ";"; +const std::string SPACE = " "; +const std::string TYPE = "type"; +} +class AddressImpl +{ + public: + std::string name; + std::string subject; + Variant::Map options; + + AddressImpl() {} + AddressImpl(const std::string& n, const std::string& s, const Variant::Map& o) : + name(n), subject(s), options(o) {} +}; + +class AddressParser +{ + public: + AddressParser(const std::string&); + bool parse(Address& address); + private: + const std::string& input; + std::string::size_type current; + static const std::string RESERVED; + + bool readChar(char c); + bool readQuotedString(std::string& s); + bool readQuotedValue(Variant& value); + bool readString(std::string& value, char delimiter); + bool readWord(std::string& word, const std::string& delims = RESERVED); + bool readSimpleValue(Variant& word); + bool readKey(std::string& key); + bool readValue(Variant& value); + bool readKeyValuePair(Variant::Map& map); + bool readMap(Variant& value); + bool readList(Variant& value); + bool readName(std::string& name); + bool readSubject(std::string& subject); + bool error(const std::string& message); + bool eos(); + bool iswhitespace(); + bool in(const std::string& delims); + bool isreserved(); +}; + +Address::Address() : impl(new AddressImpl()) {} +Address::Address(const std::string& address) : impl(new AddressImpl()) +{ + AddressParser parser(address); + parser.parse(*this); +} +Address::Address(const std::string& name, const std::string& subject, const Variant::Map& options, + const std::string& type) + : impl(new AddressImpl(name, subject, options)) { setType(type); } +Address::Address(const Address& a) : + impl(new AddressImpl(a.impl->name, a.impl->subject, a.impl->options)) {} +Address::~Address() { delete impl; } + +Address& Address::operator=(const Address& a) { *impl = *a.impl; return *this; } + + +std::string Address::str() const +{ + std::stringstream out; + out << impl->name; + if (!impl->subject.empty()) out << SUBJECT_DIVIDER << impl->subject; + if (!impl->options.empty()) out << OPTIONS_DIVIDER << impl->options; + return out.str(); +} +Address::operator bool() const { return !impl->name.empty(); } +bool Address::operator !() const { return impl->name.empty(); } + +const std::string& Address::getName() const { return impl->name; } +void Address::setName(const std::string& name) { impl->name = name; } +const std::string& Address::getSubject() const { return impl->subject; } +void Address::setSubject(const std::string& subject) { impl->subject = subject; } +const Variant::Map& Address::getOptions() const { return impl->options; } +Variant::Map& Address::getOptions() { return impl->options; } +void Address::setOptions(const Variant::Map& options) { impl->options = options; } + + +namespace{ +const Variant EMPTY_VARIANT; +const std::string EMPTY_STRING; +const std::string NODE_PROPERTIES="node"; +} + +const Variant& find(const Variant::Map& map, const std::string& key) +{ + Variant::Map::const_iterator i = map.find(key); + if (i == map.end()) return EMPTY_VARIANT; + else return i->second; +} + +std::string Address::getType() const +{ + const Variant& props = find(impl->options, NODE_PROPERTIES); + if (props.getType() == VAR_MAP) { + const Variant& type = find(props.asMap(), TYPE); + if (!type.isVoid()) return type.asString(); + } + return EMPTY_STRING; +} + +void Address::setType(const std::string& type) +{ + Variant& props = impl->options[NODE_PROPERTIES]; + if (props.isVoid()) props = Variant::Map(); + props.asMap()[TYPE] = type; +} + +std::ostream& operator<<(std::ostream& out, const Address& address) +{ + out << address.str(); + return out; +} + +}} // namespace qpid::messaging diff --git a/qpid/cpp/src/qpid/messaging/AddressParser.cpp b/qpid/cpp/src/qpid/messaging/AddressParser.cpp new file mode 100644 index 0000000000..4c8f35fbc5 --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/AddressParser.cpp @@ -0,0 +1,265 @@ +/* + * + * 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 "AddressParser.h" +#include "qpid/framing/Uuid.h" +#include <boost/format.hpp> + +namespace qpid { +namespace messaging { + +using namespace qpid::types; + +AddressParser::AddressParser(const std::string& s) : input(s), current(0) {} + +bool AddressParser::error(const std::string& message) +{ + throw MalformedAddress((boost::format("%1%, character %2% of %3%") % message % current % input).str()); +} + +bool AddressParser::parse(Address& address) +{ + std::string name; + if (readName(name)) { + if (name.find('#') == 0) name = qpid::framing::Uuid(true).str() + name; + address.setName(name); + if (readChar('/')) { + std::string subject; + readSubject(subject); + address.setSubject(subject); + } + if (readChar(';')) { + Variant options = Variant::Map(); + if (readMap(options)) { + address.setOptions(options.asMap()); + } + } + //skip trailing whitespace + while (!eos() && iswhitespace()) ++current; + return eos() || error("Unexpected chars in address: " + input.substr(current)); + } else { + return input.empty() || error("Expected name"); + } +} + +bool AddressParser::parseMap(Variant::Map& map) +{ + if (readChar('{')) { + readMapEntries(map); + return readChar('}') || error("Unmatched '{'!"); + } else { + return false; + } +} + +bool AddressParser::parseList(Variant::List& list) +{ + if (readChar('[')) { + readListItems(list); + return readChar(']') || error("Unmatched '['!"); + } else { + return false; + } +} + + +bool AddressParser::readList(Variant& value) +{ + if (readChar('[')) { + value = Variant::List(); + readListItems(value.asList()); + return readChar(']') || error("Unmatched '['!"); + } else { + return false; + } +} + +void AddressParser::readListItems(Variant::List& list) +{ + Variant item; + while (readValueIfExists(item)) { + list.push_back(item); + if (!readChar(',')) break; + } +} + +bool AddressParser::readMap(Variant& value) +{ + if (readChar('{')) { + value = Variant::Map(); + readMapEntries(value.asMap()); + return readChar('}') || error("Unmatched '{'!"); + } else { + return false; + } +} + +void AddressParser::readMapEntries(Variant::Map& map) +{ + while (readKeyValuePair(map) && readChar(',')) {} +} + +bool AddressParser::readKeyValuePair(Variant::Map& map) +{ + std::string key; + Variant value; + if (readKey(key)) { + if (readChar(':') && readValue(value)) { + map[key] = value; + return true; + } else { + return error("Bad key-value pair, expected ':'"); + } + } else { + return false; + } +} + +bool AddressParser::readKey(std::string& key) +{ + return readWord(key) || readQuotedString(key); +} + +bool AddressParser::readValue(Variant& value) +{ + return readValueIfExists(value) || error("Expected value"); +} + +bool AddressParser::readValueIfExists(Variant& value) +{ + return readSimpleValue(value) || readQuotedValue(value) || + readMap(value) || readList(value); +} + +bool AddressParser::readString(std::string& value, char delimiter) +{ + if (readChar(delimiter)) { + std::string::size_type start = current++; + while (!eos()) { + if (input.at(current) == delimiter) { + if (current > start) { + value = input.substr(start, current - start); + } else { + value = ""; + } + ++current; + return true; + } else { + ++current; + } + } + return error("Unmatched delimiter"); + } else { + return false; + } +} + +bool AddressParser::readName(std::string& name) +{ + return readQuotedString(name) || readWord(name, "/;"); +} + +bool AddressParser::readSubject(std::string& subject) +{ + return readQuotedString(subject) || readWord(subject, ";"); +} + +bool AddressParser::readQuotedString(std::string& s) +{ + return readString(s, '"') || readString(s, '\''); +} + +bool AddressParser::readQuotedValue(Variant& value) +{ + std::string s; + if (readQuotedString(s)) { + value = s; + return true; + } else { + return false; + } +} + +bool AddressParser::readSimpleValue(Variant& value) +{ + std::string s; + if (readWord(s)) { + value.parse(s); + return true; + } else { + return false; + } +} + +bool AddressParser::readWord(std::string& value, const std::string& delims) +{ + //skip leading whitespace + while (!eos() && iswhitespace()) ++current; + + //read any number of non-whitespace, non-reserved chars into value + std::string::size_type start = current; + while (!eos() && !iswhitespace() && !in(delims)) ++current; + + if (current > start) { + value = input.substr(start, current - start); + return true; + } else { + return false; + } +} + +bool AddressParser::readChar(char c) +{ + while (!eos()) { + if (iswhitespace()) { + ++current; + } else if (input.at(current) == c) { + ++current; + return true; + } else { + return false; + } + } + return false; +} + +bool AddressParser::iswhitespace() +{ + return ::isspace(input.at(current)); +} + +bool AddressParser::isreserved() +{ + return in(RESERVED); +} + +bool AddressParser::in(const std::string& chars) +{ + return chars.find(input.at(current)) != std::string::npos; +} + +bool AddressParser::eos() +{ + return current >= input.size(); +} + +const std::string AddressParser::RESERVED = "\'\"{}[],:/"; + +}} // namespace qpid::messaging diff --git a/qpid/cpp/src/qpid/messaging/AddressParser.h b/qpid/cpp/src/qpid/messaging/AddressParser.h new file mode 100644 index 0000000000..1635331d19 --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/AddressParser.h @@ -0,0 +1,66 @@ +#ifndef QPID_MESSAGING_ADDRESSPARSER_H +#define QPID_MESSAGING_ADDRESSPARSER_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/messaging/Address.h" + +namespace qpid { +namespace messaging { + +class AddressParser +{ + public: + AddressParser(const std::string&); + bool parse(Address& address); + bool parseMap(qpid::types::Variant::Map& map); + bool parseList(qpid::types::Variant::List& list); + private: + const std::string& input; + std::string::size_type current; + static const std::string RESERVED; + + bool readChar(char c); + bool readQuotedString(std::string& s); + bool readQuotedValue(qpid::types::Variant& value); + bool readString(std::string& value, char delimiter); + bool readWord(std::string& word, const std::string& delims = RESERVED); + bool readSimpleValue(qpid::types::Variant& word); + bool readKey(std::string& key); + bool readValue(qpid::types::Variant& value); + bool readValueIfExists(qpid::types::Variant& value); + bool readKeyValuePair(qpid::types::Variant::Map& map); + bool readMap(qpid::types::Variant& value); + bool readList(qpid::types::Variant& value); + bool readName(std::string& name); + bool readSubject(std::string& subject); + bool error(const std::string& message); + bool eos(); + bool iswhitespace(); + bool in(const std::string& delims); + bool isreserved(); + void readListItems(qpid::types::Variant::List& list); + void readMapEntries(qpid::types::Variant::Map& map); +}; + +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_ADDRESSPARSER_H*/ diff --git a/qpid/cpp/src/qpid/messaging/Connection.cpp b/qpid/cpp/src/qpid/messaging/Connection.cpp new file mode 100644 index 0000000000..bd90aa54a7 --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/Connection.cpp @@ -0,0 +1,82 @@ +/* + * + * 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/Connection.h" +#include "qpid/messaging/AddressParser.h" +#include "qpid/messaging/ConnectionImpl.h" +#include "qpid/messaging/Session.h" +#include "qpid/messaging/SessionImpl.h" +#include "qpid/messaging/PrivateImplRef.h" +#include "qpid/client/amqp0_10/ConnectionImpl.h" +#include "qpid/log/Statement.h" + +namespace qpid { +namespace messaging { + +using namespace qpid::types; + +typedef PrivateImplRef<qpid::messaging::Connection> PI; + +Connection::Connection(ConnectionImpl* impl) { PI::ctor(*this, impl); } +Connection::Connection(const Connection& c) : Handle<ConnectionImpl>() { PI::copy(*this, c); } +Connection& Connection::operator=(const Connection& c) { return PI::assign(*this, c); } +Connection::~Connection() { PI::dtor(*this); } + +Connection::Connection(const std::string& url, const std::string& o) +{ + Variant::Map options; + AddressParser parser(o); + if (o.empty() || parser.parseMap(options)) { + PI::ctor(*this, new qpid::client::amqp0_10::ConnectionImpl(url, options)); + } else { + throw InvalidOptionString("Invalid option string: " + o); + } +} +Connection::Connection(const std::string& url, const Variant::Map& options) +{ + PI::ctor(*this, new qpid::client::amqp0_10::ConnectionImpl(url, options)); +} + +Connection::Connection() +{ + Variant::Map options; + std::string url = "amqp:tcp:127.0.0.1:5672"; + PI::ctor(*this, new qpid::client::amqp0_10::ConnectionImpl(url, options)); +} + +void Connection::open() { impl->open(); } +bool Connection::isOpen() { return impl->isOpen(); } +bool Connection::isOpen() const { return impl->isOpen(); } +void Connection::close() { impl->close(); } +Session Connection::createSession(const std::string& name) { return impl->newSession(false, name); } +Session Connection::createTransactionalSession(const std::string& name) +{ + return impl->newSession(true, name); +} +Session Connection::getSession(const std::string& name) const { return impl->getSession(name); } +void Connection::setOption(const std::string& name, const Variant& value) +{ + impl->setOption(name, value); +} +std::string Connection::getAuthenticatedUsername() +{ + return impl->getAuthenticatedUsername(); +} +}} // namespace qpid::messaging diff --git a/qpid/cpp/src/qpid/messaging/ConnectionImpl.h b/qpid/cpp/src/qpid/messaging/ConnectionImpl.h new file mode 100644 index 0000000000..1e11d9a6d5 --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/ConnectionImpl.h @@ -0,0 +1,52 @@ +#ifndef QPID_MESSAGING_CONNECTIONIMPL_H +#define QPID_MESSAGING_CONNECTIONIMPL_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 <string> +#include "qpid/RefCounted.h" + +namespace qpid { + +namespace types { +class Variant; +} + +namespace messaging { + +class Session; + +class ConnectionImpl : public virtual qpid::RefCounted +{ + public: + virtual ~ConnectionImpl() {} + virtual void open() = 0; + virtual bool isOpen() const = 0; + virtual void close() = 0; + virtual Session newSession(bool transactional, const std::string& name) = 0; + virtual Session getSession(const std::string& name) const = 0; + virtual void setOption(const std::string& name, const qpid::types::Variant& value) = 0; + virtual std::string getAuthenticatedUsername() = 0; + private: +}; +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_CONNECTIONIMPL_H*/ diff --git a/qpid/cpp/src/qpid/messaging/Duration.cpp b/qpid/cpp/src/qpid/messaging/Duration.cpp new file mode 100644 index 0000000000..a23e9f5bcb --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/Duration.cpp @@ -0,0 +1,55 @@ +/* + * + * 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/Duration.h" +#include <limits> + +namespace qpid { +namespace messaging { + +Duration::Duration(uint64_t ms) : milliseconds(ms) {} +uint64_t Duration::getMilliseconds() const { return milliseconds; } + +Duration operator*(const Duration& duration, uint64_t multiplier) +{ + return Duration(duration.getMilliseconds() * multiplier); +} + +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); +const Duration Duration::MINUTE(SECOND * 60); + +}} // namespace qpid::messaging diff --git a/qpid/cpp/src/qpid/messaging/FailoverUpdates.cpp b/qpid/cpp/src/qpid/messaging/FailoverUpdates.cpp new file mode 100644 index 0000000000..4f2fcf2e82 --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/FailoverUpdates.cpp @@ -0,0 +1,85 @@ +/* + * + * 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/FailoverUpdates.h" +#include "qpid/messaging/Connection.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/Receiver.h" +#include "qpid/messaging/Session.h" +#include "qpid/sys/Runnable.h" +#include "qpid/sys/Thread.h" +#include "qpid/log/Statement.h" +#include "qpid/Exception.h" +#include "qpid/Url.h" +#include "qpid/framing/Uuid.h" +#include <vector> + +namespace qpid { +namespace messaging { +using framing::Uuid; + +struct FailoverUpdatesImpl : qpid::sys::Runnable +{ + Connection connection; + Session session; + Receiver receiver; + qpid::sys::Thread thread; + + FailoverUpdatesImpl(Connection& c) : connection(c) + { + session = connection.createSession("failover-updates."+Uuid(true).str()); + receiver = session.createReceiver("amq.failover"); + thread = qpid::sys::Thread(*this); + } + + ~FailoverUpdatesImpl() { + try { + session.close(); + } catch(...) {} // Squash exceptions in a destructor. + thread.join(); + } + + void run() + { + try { + Message message; + while (receiver.fetch(message)) { + connection.setOption("reconnect-urls", message.getProperties()["amq.failover"]); + QPID_LOG(debug, "Set reconnect-urls to " << message.getProperties()["amq.failover"]); + session.acknowledge(); + } + } + catch (const ClosedException&) {} + catch (const qpid::TransportFailure& e) { + QPID_LOG(warning, "Failover updates stopped on loss of connection. " << e.what()); + } + catch (const std::exception& e) { + QPID_LOG(warning, "Failover updates stopped due to exception: " << e.what()); + } + } +}; + +FailoverUpdates::FailoverUpdates(Connection& connection) : impl(new FailoverUpdatesImpl(connection)) {} +FailoverUpdates::~FailoverUpdates() { if (impl) { delete impl; } } +FailoverUpdates::FailoverUpdates(const FailoverUpdates&) : impl(0) {} +FailoverUpdates& FailoverUpdates::operator=(const FailoverUpdates&) { return *this; } + + +}} // namespace qpid::messaging diff --git a/qpid/cpp/src/qpid/messaging/HandleInstantiator.cpp b/qpid/cpp/src/qpid/messaging/HandleInstantiator.cpp new file mode 100644 index 0000000000..c9a7680bb4 --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/HandleInstantiator.cpp @@ -0,0 +1,64 @@ +/*
+ *
+ * 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/Connection.h"
+#include "qpid/messaging/Receiver.h"
+#include "qpid/messaging/Sender.h"
+#include "qpid/messaging/Session.h"
+
+namespace qpid {
+namespace messaging {
+
+using namespace qpid::types;
+
+void HandleInstantiatorDoNotCall(void)
+{
+ // This function exists to instantiate various template Handle
+ // bool functions. The instances are then available to
+ // the qpidmessaging DLL and subsequently exported.
+ // This function must not be exported nor called called.
+ // For further information refer to
+ // https://issues.apache.org/jira/browse/QPID-2926
+
+ Connection connection;
+ if (connection.isValid()) connection.close();
+ if (connection.isNull() ) connection.close();
+ if (connection ) connection.close();
+ if (!connection ) connection.close();
+
+ Receiver receiver;
+ if (receiver.isValid()) receiver.close();
+ if (receiver.isNull() ) receiver.close();
+ if (receiver ) receiver.close();
+ if (!receiver ) receiver.close();
+
+ Sender sender;
+ if (sender.isValid()) sender.close();
+ if (sender.isNull() ) sender.close();
+ if (sender ) sender.close();
+ if (!sender ) sender.close();
+
+ Session session;
+ if (session.isValid()) session.close();
+ if (session.isNull() ) session.close();
+ if (session ) session.close();
+ if (!session ) session.close();
+}
+}} // namespace qpid::messaging
diff --git a/qpid/cpp/src/qpid/messaging/Message.cpp b/qpid/cpp/src/qpid/messaging/Message.cpp new file mode 100644 index 0000000000..83cdfd3c55 --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/Message.cpp @@ -0,0 +1,148 @@ +/* + * + * 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/Message.h" +#include "qpid/messaging/MessageImpl.h" +#include "qpid/amqp_0_10/Codecs.h" +#include <boost/format.hpp> + +namespace qpid { +namespace messaging { + +using namespace qpid::types; + +Message::Message(const std::string& bytes) : impl(new MessageImpl(bytes)) {} +Message::Message(const char* bytes, size_t count) : impl(new MessageImpl(bytes, count)) {} + +Message::Message(const Message& m) : impl(new MessageImpl(*m.impl)) {} +Message::~Message() { delete impl; } + +Message& Message::operator=(const Message& m) { *impl = *m.impl; return *this; } + +void Message::setReplyTo(const Address& d) { impl->setReplyTo(d); } +const Address& Message::getReplyTo() const { return impl->getReplyTo(); } + +void Message::setSubject(const std::string& s) { impl->setSubject(s); } +const std::string& Message::getSubject() const { return impl->getSubject(); } + +void Message::setContentType(const std::string& s) { impl->setContentType(s); } +const std::string& Message::getContentType() const { return impl->getContentType(); } + +void Message::setMessageId(const std::string& id) { impl->messageId = id; } +const std::string& Message::getMessageId() const { return impl->messageId; } + +void Message::setUserId(const std::string& id) { impl->userId = id; } +const std::string& Message::getUserId() const { return impl->userId; } + +void Message::setCorrelationId(const std::string& id) { impl->correlationId = id; } +const std::string& Message::getCorrelationId() const { return impl->correlationId; } + +uint8_t Message::getPriority() const { return impl->priority; } +void Message::setPriority(uint8_t priority) { impl->priority = priority; } + +void Message::setTtl(Duration ttl) { impl->ttl = ttl.getMilliseconds(); } +Duration Message::getTtl() const { return Duration(impl->ttl); } + +void Message::setDurable(bool durable) { impl->durable = durable; } +bool Message::getDurable() const { return impl->durable; } + +bool Message::getRedelivered() const { return impl->redelivered; } +void Message::setRedelivered(bool redelivered) { impl->redelivered = redelivered; } + +const Variant::Map& Message::getProperties() const { return impl->getHeaders(); } +Variant::Map& Message::getProperties() { return impl->getHeaders(); } +void Message::setProperty(const std::string& k, const qpid::types::Variant& v) { impl->setHeader(k,v); } + +void Message::setContent(const std::string& c) { impl->setBytes(c); } +void Message::setContent(const char* chars, size_t count) { impl->setBytes(chars, count); } +std::string Message::getContent() const { return impl->getBytes(); } + +const char* Message::getContentPtr() const +{ + return impl->getBytes().data(); +} + +size_t Message::getContentSize() const +{ + return impl->getBytes().size(); +} + +EncodingException::EncodingException(const std::string& msg) : qpid::types::Exception(msg) {} + +const std::string BAD_ENCODING("Unsupported encoding: %1% (only %2% is supported at present)."); + +template <class C> struct MessageCodec +{ + static bool checkEncoding(const std::string& requested) + { + if (requested.size()) { + if (requested == C::contentType) return true; + else throw EncodingException((boost::format(BAD_ENCODING) % requested % C::contentType).str()); + } else { + return false; + } + } + + /* + * Currently only support a single encoding type for both list and + * map, based on AMQP 0-10, though wider support is anticipated in the + * future. This method simply checks that the desired encoding (if one + * is specified, either through the message-content or through an + * override) is indeed supported. + */ + static void checkEncoding(const Message& message, const std::string& requested) + { + checkEncoding(requested) || checkEncoding(message.getContentType()); + } + + static void decode(const Message& message, typename C::ObjectType& object, const std::string& encoding) + { + checkEncoding(message, encoding); + C::decode(message.getContent(), object); + } + + static void encode(const typename C::ObjectType& map, Message& message, const std::string& encoding) + { + checkEncoding(message, encoding); + std::string content; + C::encode(map, content); + message.setContentType(C::contentType); + message.setContent(content); + } +}; + +void decode(const Message& message, Variant::Map& map, const std::string& encoding) +{ + MessageCodec<qpid::amqp_0_10::MapCodec>::decode(message, map, encoding); +} +void decode(const Message& message, Variant::List& list, const std::string& encoding) +{ + MessageCodec<qpid::amqp_0_10::ListCodec>::decode(message, list, encoding); +} +void encode(const Variant::Map& map, Message& message, const std::string& encoding) +{ + MessageCodec<qpid::amqp_0_10::MapCodec>::encode(map, message, encoding); +} +void encode(const Variant::List& list, Message& message, const std::string& encoding) +{ + MessageCodec<qpid::amqp_0_10::ListCodec>::encode(list, message, encoding); +} + +}} // namespace qpid::messaging diff --git a/qpid/cpp/src/qpid/messaging/MessageImpl.cpp b/qpid/cpp/src/qpid/messaging/MessageImpl.cpp new file mode 100644 index 0000000000..0601800e46 --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/MessageImpl.cpp @@ -0,0 +1,79 @@ +/* + * + * 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 "MessageImpl.h" +#include "qpid/messaging/Message.h" + +namespace qpid { +namespace messaging { + +namespace { +const std::string EMPTY_STRING = ""; +} + +using namespace qpid::types; + +MessageImpl::MessageImpl(const std::string& c) : + priority(0), + ttl(0), + durable(false), + redelivered(false), + bytes(c), + internalId(0) {} +MessageImpl::MessageImpl(const char* chars, size_t count) : + priority(0), + ttl(0), + durable (false), + redelivered(false), + bytes(chars, count), + internalId(0) {} + +void MessageImpl::setReplyTo(const Address& d) { replyTo = d; } +const Address& MessageImpl::getReplyTo() const { return replyTo; } + +void MessageImpl::setSubject(const std::string& s) { subject = s; } +const std::string& MessageImpl::getSubject() const { return subject; } + +void MessageImpl::setContentType(const std::string& s) { contentType = s; } +const std::string& MessageImpl::getContentType() const { return contentType; } + +const Variant::Map& MessageImpl::getHeaders() const { return headers; } +Variant::Map& MessageImpl::getHeaders() { return headers; } +void MessageImpl::setHeader(const std::string& key, const qpid::types::Variant& val) { headers[key] = val; } + +//should these methods be on MessageContent? +void MessageImpl::setBytes(const std::string& c) { bytes = c; } +void MessageImpl::setBytes(const char* chars, size_t count) { bytes.assign(chars, count); } +const std::string& MessageImpl::getBytes() const { return bytes; } +std::string& MessageImpl::getBytes() { return bytes; } + +void MessageImpl::setInternalId(qpid::framing::SequenceNumber i) { internalId = i; } +qpid::framing::SequenceNumber MessageImpl::getInternalId() { return internalId; } + +MessageImpl& MessageImplAccess::get(Message& msg) +{ + return *msg.impl; +} +const MessageImpl& MessageImplAccess::get(const Message& msg) +{ + return *msg.impl; +} + +}} // namespace qpid::messaging diff --git a/qpid/cpp/src/qpid/messaging/MessageImpl.h b/qpid/cpp/src/qpid/messaging/MessageImpl.h new file mode 100644 index 0000000000..57df6b3fda --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/MessageImpl.h @@ -0,0 +1,90 @@ +#ifndef QPID_MESSAGING_MESSAGEIMPL_H +#define QPID_MESSAGING_MESSAGEIMPL_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/messaging/Address.h" +#include "qpid/types/Variant.h" +#include "qpid/framing/SequenceNumber.h" + +namespace qpid { +namespace messaging { + +struct MessageImpl +{ + Address replyTo; + std::string subject; + std::string contentType; + std::string messageId; + std::string userId; + std::string correlationId; + uint8_t priority; + uint64_t ttl; + bool durable; + bool redelivered; + qpid::types::Variant::Map headers; + + std::string bytes; + + qpid::framing::SequenceNumber internalId; + + MessageImpl(const std::string& c); + MessageImpl(const char* chars, size_t count); + + void setReplyTo(const Address& d); + const Address& getReplyTo() const; + + void setSubject(const std::string& s); + const std::string& getSubject() const; + + void setContentType(const std::string& s); + const std::string& getContentType() const; + + const qpid::types::Variant::Map& getHeaders() const; + qpid::types::Variant::Map& getHeaders(); + void setHeader(const std::string& key, const qpid::types::Variant& val); + + void setBytes(const std::string& bytes); + void setBytes(const char* chars, size_t count); + const std::string& getBytes() const; + std::string& getBytes(); + + void setInternalId(qpid::framing::SequenceNumber id); + qpid::framing::SequenceNumber getInternalId(); + +}; + +class Message; + +/** + * Provides access to the internal MessageImpl for a message which is + * useful when accessing any message state not exposed directly + * through the public API. + */ +struct MessageImplAccess +{ + static MessageImpl& get(Message&); + static const MessageImpl& get(const Message&); +}; + +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_MESSAGEIMPL_H*/ diff --git a/qpid/cpp/src/qpid/messaging/PrivateImplRef.h b/qpid/cpp/src/qpid/messaging/PrivateImplRef.h new file mode 100644 index 0000000000..e77c58d071 --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/PrivateImplRef.h @@ -0,0 +1,94 @@ +#ifndef QPID_MESSAGING_PRIVATEIMPL_H +#define QPID_MESSAGING_PRIVATEIMPL_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/messaging/ImportExport.h" +#include <boost/intrusive_ptr.hpp> +#include "qpid/RefCounted.h" + +namespace qpid { +namespace messaging { + +/** + * Helper class to implement a class with a private, reference counted + * implementation and reference semantics. + * + * Such classes are used in the public API to hide implementation, they + * should. Example of use: + * + * === Foo.h + * + * template <class T> PrivateImplRef; + * class FooImpl; + * + * Foo : public Handle<FooImpl> { + * public: + * Foo(FooImpl* = 0); + * Foo(const Foo&); + * ~Foo(); + * Foo& operator=(const Foo&); + * + * int fooDo(); // and other Foo functions... + * + * private: + * typedef FooImpl Impl; + * Impl* impl; + * friend class PrivateImplRef<Foo>; + * + * === Foo.cpp + * + * typedef PrivateImplRef<Foo> PI; + * Foo::Foo(FooImpl* p) { PI::ctor(*this, p); } + * Foo::Foo(const Foo& c) : Handle<FooImpl>() { PI::copy(*this, c); } + * Foo::~Foo() { PI::dtor(*this); } + * Foo& Foo::operator=(const Foo& c) { return PI::assign(*this, c); } + * + * int foo::fooDo() { return impl->fooDo(); } + * + */ +template <class T> class PrivateImplRef { + public: + typedef typename T::Impl Impl; + typedef boost::intrusive_ptr<Impl> intrusive_ptr; + + /** Get the implementation pointer from a handle */ + static intrusive_ptr get(const T& t) { return intrusive_ptr(t.impl); } + + /** Set the implementation pointer in a handle */ + static void set(T& t, const intrusive_ptr& p) { + if (t.impl == p) return; + if (t.impl) boost::intrusive_ptr_release(t.impl); + t.impl = p.get(); + if (t.impl) boost::intrusive_ptr_add_ref(t.impl); + } + + // Helper functions to implement the ctor, dtor, copy, assign + static void ctor(T& t, Impl* p) { t.impl = p; if (p) boost::intrusive_ptr_add_ref(p); } + static void copy(T& t, const T& x) { if (&t == &x) return; t.impl = 0; assign(t, x); } + static void dtor(T& t) { if(t.impl) boost::intrusive_ptr_release(t.impl); } + static T& assign(T& t, const T& x) { set(t, get(x)); return t;} +}; + +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_PRIVATEIMPL_H*/ diff --git a/qpid/cpp/src/qpid/messaging/Receiver.cpp b/qpid/cpp/src/qpid/messaging/Receiver.cpp new file mode 100644 index 0000000000..78e0c5daa3 --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/Receiver.cpp @@ -0,0 +1,48 @@ +/* + * + * 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/Receiver.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/ReceiverImpl.h" +#include "qpid/messaging/Session.h" +#include "qpid/messaging/PrivateImplRef.h" + +namespace qpid { +namespace messaging { + +typedef PrivateImplRef<qpid::messaging::Receiver> PI; + +Receiver::Receiver(ReceiverImpl* impl) { PI::ctor(*this, impl); } +Receiver::Receiver(const Receiver& s) : Handle<ReceiverImpl>() { PI::copy(*this, s); } +Receiver::~Receiver() { PI::dtor(*this); } +Receiver& Receiver::operator=(const Receiver& s) { return PI::assign(*this, s); } +bool Receiver::get(Message& message, Duration timeout) { return impl->get(message, timeout); } +Message Receiver::get(Duration timeout) { return impl->get(timeout); } +bool Receiver::fetch(Message& message, Duration timeout) { return impl->fetch(message, timeout); } +Message Receiver::fetch(Duration timeout) { return impl->fetch(timeout); } +void Receiver::setCapacity(uint32_t c) { impl->setCapacity(c); } +uint32_t Receiver::getCapacity() { return impl->getCapacity(); } +uint32_t Receiver::getAvailable() { return impl->getAvailable(); } +uint32_t Receiver::getUnsettled() { return impl->getUnsettled(); } +void Receiver::close() { impl->close(); } +const std::string& Receiver::getName() const { return impl->getName(); } +Session Receiver::getSession() const { return impl->getSession(); } +bool Receiver::isClosed() const { return impl->isClosed(); } +}} // namespace qpid::messaging diff --git a/qpid/cpp/src/qpid/messaging/ReceiverImpl.h b/qpid/cpp/src/qpid/messaging/ReceiverImpl.h new file mode 100644 index 0000000000..57059bfd28 --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/ReceiverImpl.h @@ -0,0 +1,52 @@ +#ifndef QPID_MESSAGING_RECEIVERIMPL_H +#define QPID_MESSAGING_RECEIVERIMPL_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" + +namespace qpid { +namespace messaging { + +class Message; +class MessageListener; +class Session; + +class ReceiverImpl : public virtual qpid::RefCounted +{ + public: + virtual ~ReceiverImpl() {} + virtual bool get(Message& message, Duration timeout) = 0; + virtual Message get(Duration timeout) = 0; + virtual bool fetch(Message& message, Duration timeout) = 0; + virtual Message fetch(Duration timeout) = 0; + virtual void setCapacity(uint32_t) = 0; + virtual uint32_t getCapacity() = 0; + virtual uint32_t getAvailable() = 0; + virtual uint32_t getUnsettled() = 0; + virtual void close() = 0; + virtual const std::string& getName() const = 0; + virtual Session getSession() const = 0; + virtual bool isClosed() const = 0; +}; +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_RECEIVERIMPL_H*/ diff --git a/qpid/cpp/src/qpid/messaging/Sender.cpp b/qpid/cpp/src/qpid/messaging/Sender.cpp new file mode 100644 index 0000000000..53dbb69777 --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/Sender.cpp @@ -0,0 +1,44 @@ +/* + * + * 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/Sender.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/SenderImpl.h" +#include "qpid/messaging/Session.h" +#include "qpid/messaging/PrivateImplRef.h" + +namespace qpid { +namespace messaging { +typedef PrivateImplRef<qpid::messaging::Sender> PI; + +Sender::Sender(SenderImpl* impl) { PI::ctor(*this, impl); } +Sender::Sender(const Sender& s) : qpid::messaging::Handle<SenderImpl>() { PI::copy(*this, s); } +Sender::~Sender() { PI::dtor(*this); } +Sender& Sender::operator=(const Sender& s) { return PI::assign(*this, s); } +void Sender::send(const Message& message, bool sync) { impl->send(message, sync); } +void Sender::close() { impl->close(); } +void Sender::setCapacity(uint32_t c) { impl->setCapacity(c); } +uint32_t Sender::getCapacity() { return impl->getCapacity(); } +uint32_t Sender::getUnsettled() { return impl->getUnsettled(); } +uint32_t Sender::getAvailable() { return getCapacity() - getUnsettled(); } +const std::string& Sender::getName() const { return impl->getName(); } +Session Sender::getSession() const { return impl->getSession(); } + +}} // namespace qpid::messaging diff --git a/qpid/cpp/src/qpid/messaging/SenderImpl.h b/qpid/cpp/src/qpid/messaging/SenderImpl.h new file mode 100644 index 0000000000..a1ca02c72c --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/SenderImpl.h @@ -0,0 +1,47 @@ +#ifndef QPID_MESSAGING_SENDERIMPL_H +#define QPID_MESSAGING_SENDERIMPL_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" + +namespace qpid { +namespace messaging { + +class Message; +class Session; + +class SenderImpl : public virtual qpid::RefCounted +{ + public: + virtual ~SenderImpl() {} + virtual void send(const Message& message, bool sync) = 0; + virtual void close() = 0; + virtual void setCapacity(uint32_t) = 0; + virtual uint32_t getCapacity() = 0; + virtual uint32_t getUnsettled() = 0; + virtual const std::string& getName() const = 0; + virtual Session getSession() const = 0; + private: +}; +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_SENDERIMPL_H*/ diff --git a/qpid/cpp/src/qpid/messaging/Session.cpp b/qpid/cpp/src/qpid/messaging/Session.cpp new file mode 100644 index 0000000000..496953a8e5 --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/Session.cpp @@ -0,0 +1,109 @@ +/* + * + * 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/Session.h" +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Connection.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/Sender.h" +#include "qpid/messaging/Receiver.h" +#include "qpid/messaging/SessionImpl.h" +#include "qpid/messaging/PrivateImplRef.h" + +namespace qpid { +namespace messaging { + +typedef PrivateImplRef<qpid::messaging::Session> PI; + +Session::Session(SessionImpl* impl) { PI::ctor(*this, impl); } +Session::Session(const Session& s) : Handle<SessionImpl>() { PI::copy(*this, s); } +Session::~Session() { PI::dtor(*this); } +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::reject(Message& m) { impl->reject(m); } +void Session::release(Message& m) { impl->release(m); } +void Session::close() { impl->close(); } + +Sender Session::createSender(const Address& address) +{ + return impl->createSender(address); +} +Receiver Session::createReceiver(const Address& address) +{ + return impl->createReceiver(address); +} + +Sender Session::createSender(const std::string& address) +{ + return impl->createSender(Address(address)); +} +Receiver Session::createReceiver(const std::string& address) +{ + return impl->createReceiver(Address(address)); +} + +void Session::sync(bool block) +{ + impl->sync(block); +} + +bool Session::nextReceiver(Receiver& receiver, Duration timeout) +{ + return impl->nextReceiver(receiver, timeout); +} + + +Receiver Session::nextReceiver(Duration timeout) +{ + return impl->nextReceiver(timeout); +} + +uint32_t Session::getReceivable() { return impl->getReceivable(); } +uint32_t Session::getUnsettledAcks() { return impl->getUnsettledAcks(); } + +Sender Session::getSender(const std::string& name) const +{ + return impl->getSender(name); +} +Receiver Session::getReceiver(const std::string& name) const +{ + return impl->getReceiver(name); +} + +Connection Session::getConnection() const +{ + return impl->getConnection(); +} + +void Session::checkError() { impl->checkError(); } +bool Session::hasError() +{ + try { + checkError(); + return false; + } catch (const std::exception&) { + return true; + } +} + +}} // namespace qpid::messaging diff --git a/qpid/cpp/src/qpid/messaging/SessionImpl.h b/qpid/cpp/src/qpid/messaging/SessionImpl.h new file mode 100644 index 0000000000..02a254e4f2 --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/SessionImpl.h @@ -0,0 +1,63 @@ +#ifndef QPID_MESSAGING_SESSIONIMPL_H +#define QPID_MESSAGING_SESSIONIMPL_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 <string> +#include "qpid/messaging/Duration.h" + +namespace qpid { +namespace messaging { + +class Address; +class Connection; +class Message; +class Sender; +class Receiver; + +class SessionImpl : public virtual qpid::RefCounted +{ + public: + virtual ~SessionImpl() {} + virtual void commit() = 0; + virtual void rollback() = 0; + virtual void acknowledge(bool sync) = 0; + virtual void acknowledge(Message&) = 0; + virtual void reject(Message&) = 0; + virtual void release(Message&) = 0; + virtual void close() = 0; + virtual void sync(bool block) = 0; + virtual Sender createSender(const Address& address) = 0; + virtual Receiver createReceiver(const Address& address) = 0; + virtual bool nextReceiver(Receiver& receiver, Duration timeout) = 0; + virtual Receiver nextReceiver(Duration timeout) = 0; + virtual uint32_t getReceivable() = 0; + virtual uint32_t getUnsettledAcks() = 0; + virtual Sender getSender(const std::string& name) const = 0; + virtual Receiver getReceiver(const std::string& name) const = 0; + virtual Connection getConnection() const = 0; + virtual void checkError() = 0; + private: +}; +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_SESSIONIMPL_H*/ diff --git a/qpid/cpp/src/qpid/messaging/exceptions.cpp b/qpid/cpp/src/qpid/messaging/exceptions.cpp new file mode 100644 index 0000000000..5d2683fffe --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/exceptions.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/messaging/exceptions.h" + +namespace qpid { +namespace messaging { + +MessagingException::MessagingException(const std::string& msg) : qpid::types::Exception(msg) {} +MessagingException::~MessagingException() throw() {} + +InvalidOptionString::InvalidOptionString(const std::string& msg) : MessagingException(msg) {} +KeyError::KeyError(const std::string& msg) : MessagingException(msg) {} + + +LinkError::LinkError(const std::string& msg) : MessagingException(msg) {} + +AddressError::AddressError(const std::string& msg) : LinkError(msg) {} +ResolutionError::ResolutionError(const std::string& msg) : AddressError(msg) {} +MalformedAddress::MalformedAddress(const std::string& msg) : AddressError(msg) {} +AssertionFailed::AssertionFailed(const std::string& msg) : ResolutionError(msg) {} +NotFound::NotFound(const std::string& msg) : ResolutionError(msg) {} + +ReceiverError::ReceiverError(const std::string& msg) : LinkError(msg) {} +FetchError::FetchError(const std::string& msg) : ReceiverError(msg) {} +NoMessageAvailable::NoMessageAvailable() : FetchError("No message to fetch") {} + +SenderError::SenderError(const std::string& msg) : LinkError(msg) {} +SendError::SendError(const std::string& msg) : SenderError(msg) {} +TargetCapacityExceeded::TargetCapacityExceeded(const std::string& msg) : SendError(msg) {} + +SessionError::SessionError(const std::string& msg) : MessagingException(msg) {} +TransactionError::TransactionError(const std::string& msg) : SessionError(msg) {} +TransactionAborted::TransactionAborted(const std::string& msg) : TransactionError(msg) {} +UnauthorizedAccess::UnauthorizedAccess(const std::string& msg) : SessionError(msg) {} + +ConnectionError::ConnectionError(const std::string& msg) : MessagingException(msg) {} + +TransportFailure::TransportFailure(const std::string& msg) : MessagingException(msg) {} + +}} // namespace qpid::messaging diff --git a/qpid/cpp/src/qpid/pointer_to_other.h b/qpid/cpp/src/qpid/pointer_to_other.h new file mode 100644 index 0000000000..a99dc89658 --- /dev/null +++ b/qpid/cpp/src/qpid/pointer_to_other.h @@ -0,0 +1,62 @@ +#ifndef QPID_POINTERTOOTHER_H +#define QPID_POINTERTOOTHER_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. + * + */ + +namespace qpid { + +// Defines the same pointer type (raw or smart) to another pointee type + +template<class T, class U> +struct pointer_to_other; + +template<class T, class U, + template<class> class Sp> +struct pointer_to_other< Sp<T>, U > + { + typedef Sp<U> type; + }; + +template<class T, class T2, class U, + template<class, class> class Sp> +struct pointer_to_other< Sp<T, T2>, U > + { + typedef Sp<U, T2> type; + }; + +template<class T, class T2, class T3, class U, + template<class, class, class> class Sp> +struct pointer_to_other< Sp<T, T2, T3>, U > + { + typedef Sp<U, T2, T3> type; + }; + +template<class T, class U> +struct pointer_to_other< T*, U > +{ + typedef U* type; +}; + +} // namespace qpid + + + +#endif /*!QPID_POINTERTOOTHER_H*/ diff --git a/qpid/cpp/src/qpid/ptr_map.h b/qpid/cpp/src/qpid/ptr_map.h new file mode 100644 index 0000000000..6ffcd48e89 --- /dev/null +++ b/qpid/cpp/src/qpid/ptr_map.h @@ -0,0 +1,57 @@ +#ifndef QPID_PTR_MAP +#define QPID_PTR_MAP + +/* + * + * 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/ptr_container/ptr_map.hpp> +#include <boost/utility/enable_if.hpp> +#include <boost/type_traits/is_same.hpp> +#include <boost/type_traits/remove_const.hpp> + +namespace qpid { + +/** @file + * Workaround for API change between boost 1.33 and 1.34. + * + * To be portable across these versions, code using boost::ptr_map + * iterators should use ptr_map_ptr(i) to get the pointer from + * boost::ptr_map::iterator i. + * + * @see http://www.boost.org/libs/ptr_container/doc/ptr_container.html#upgrading-from-boost-v-1-33 + */ + + +typedef boost::is_same<boost::ptr_map<int, int>::iterator::value_type, int> IsOldPtrMap; + +template <class Iter> +typename boost::enable_if<IsOldPtrMap, typename Iter::value_type*>::type +ptr_map_ptr(const Iter& i) { return &*i; } + +template <class Iter> +typename boost::disable_if<IsOldPtrMap, + typename boost::remove_const<typename Iter::value_type::second_type>::type + >::type +ptr_map_ptr(const Iter& i) { return i->second; } + +} // namespace qpid + +#endif /*!QPID_PTR_MAP*/ diff --git a/qpid/cpp/src/qpid/replication/ReplicatingEventListener.cpp b/qpid/cpp/src/qpid/replication/ReplicatingEventListener.cpp new file mode 100644 index 0000000000..b7d52372f4 --- /dev/null +++ b/qpid/cpp/src/qpid/replication/ReplicatingEventListener.cpp @@ -0,0 +1,201 @@ +/* + * + * 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/replication/ReplicatingEventListener.h" +#include "qpid/replication/constants.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/DeliverableMessage.h" +#include "qpid/broker/QueueEvents.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/FrameHandler.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/log/Statement.h" + +namespace qpid { +namespace replication { + +using namespace qpid::broker; +using namespace qpid::framing; +using namespace qpid::replication::constants; + +void ReplicatingEventListener::handle(QueueEvents::Event event) +{ + switch (event.type) { + case QueueEvents::ENQUEUE: + deliverEnqueueMessage(event.msg); + QPID_LOG(debug, "Queuing 'enqueue' event on " << event.msg.queue->getName() << " for replication"); + break; + case QueueEvents::DEQUEUE: + deliverDequeueMessage(event.msg); + QPID_LOG(debug, "Queuing 'dequeue' event from " << event.msg.queue->getName() << " for replication, (from position " + << event.msg.position << ")"); + break; + } +} + +namespace { +const std::string EMPTY; +} + +void ReplicatingEventListener::deliverDequeueMessage(const QueuedMessage& dequeued) +{ + FieldTable headers; + headers.setString(REPLICATION_TARGET_QUEUE, dequeued.queue->getName()); + headers.setInt(REPLICATION_EVENT_TYPE, DEQUEUE); + headers.setInt(DEQUEUED_MESSAGE_POSITION, dequeued.position); + boost::intrusive_ptr<Message> msg(createMessage(headers)); + DeliveryProperties* props = msg->getFrames().getHeaders()->get<DeliveryProperties>(true); + props->setRoutingKey(dequeued.queue->getName()); + route(msg); +} + +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); + route(msg); +} + +void ReplicatingEventListener::route(boost::intrusive_ptr<qpid::broker::Message> msg) +{ + try { + if (exchange) { + DeliverableMessage deliverable(msg); + exchange->route(deliverable, msg->getRoutingKey(), msg->getApplicationHeaders()); + } else if (queue) { + queue->deliver(msg); + } else { + QPID_LOG(error, "Cannot route replication event, neither replication queue nor exchange configured"); + } + } catch (const std::exception& e) { + QPID_LOG(error, "Error enqueing replication event: " << e.what()); + } +} + + +boost::intrusive_ptr<Message> ReplicatingEventListener::createMessage(const FieldTable& headers) +{ + boost::intrusive_ptr<Message> msg(new Message()); + AMQFrame method((MessageTransferBody(ProtocolVersion(), EMPTY, 0, 0))); + AMQFrame header((AMQHeaderBody())); + header.setBof(false); + header.setEof(true); + header.setBos(true); + header.setEos(true); + msg->getFrames().append(method); + msg->getFrames().append(header); + MessageProperties* props = msg->getFrames().getHeaders()->get<MessageProperties>(true); + props->setApplicationHeaders(headers); + return msg; +} + +struct AppendingHandler : FrameHandler +{ + boost::intrusive_ptr<Message> msg; + + AppendingHandler(boost::intrusive_ptr<Message> m) : msg(m) {} + + void handle(AMQFrame& f) + { + msg->getFrames().append(f); + } +}; + +boost::intrusive_ptr<Message> ReplicatingEventListener::cloneMessage(Queue& queue, boost::intrusive_ptr<Message> original) +{ + boost::intrusive_ptr<Message> copy(new Message()); + AMQFrame method((MessageTransferBody(ProtocolVersion(), EMPTY, 0, 0))); + AppendingHandler handler(copy); + handler.handle(method); + + //To avoid modifying original headers, create new frame with + //cloned body: + AMQFrame header(*original->getFrames().getHeaders()); + header.setBof(false); + header.setEof(!original->getFrames().getContentSize());//if there is any content then the header is not the end of the frameset + header.setBos(true); + header.setEos(true); + handler.handle(header); + + original->sendContent(queue, handler, std::numeric_limits<int16_t>::max()); + return copy; +} + +Options* ReplicatingEventListener::getOptions() +{ + return &options; +} + +void ReplicatingEventListener::initialize(Plugin::Target& target) +{ + Broker* broker = dynamic_cast<broker::Broker*>(&target); + if (broker) { + broker->addFinalizer(boost::bind(&ReplicatingEventListener::shutdown, this)); + if (!options.exchange.empty()) { + if (!options.queue.empty()) { + QPID_LOG(warning, "Replication queue option ignored as replication exchange has been specified"); + } + try { + exchange = broker->getExchanges().declare(options.exchange, options.exchangeType).first; + } catch (const UnknownExchangeTypeException&) { + QPID_LOG(error, "Replication disabled due to invalid type: " << options.exchangeType); + } + } else if (!options.queue.empty()) { + if (options.createQueue) { + queue = broker->getQueues().declare(options.queue).first; + } else { + queue = broker->getQueues().find(options.queue); + } + if (queue) { + queue->insertSequenceNumbers(REPLICATION_EVENT_SEQNO); + } else { + QPID_LOG(error, "Replication queue named '" << options.queue << "' does not exist; replication plugin disabled."); + } + } + if (queue || exchange) { + QueueEvents::EventListener callback = boost::bind(&ReplicatingEventListener::handle, this, _1); + broker->getQueueEvents().registerListener(options.name, callback); + QPID_LOG(info, "Registered replicating queue event listener"); + } + } +} + +void ReplicatingEventListener::earlyInitialize(Target&) {} +void ReplicatingEventListener::shutdown() { queue.reset(); exchange.reset(); } + +ReplicatingEventListener::PluginOptions::PluginOptions() : Options("Queue Replication Options"), + exchangeType("direct"), + name("replicator"), + createQueue(false) +{ + addOptions() + ("replication-exchange-name", optValue(exchange, "EXCHANGE"), "Exchange to which events for other queues are routed") + ("replication-exchange-type", optValue(exchangeType, "direct|topic etc"), "Type of exchange to use") + ("replication-queue", optValue(queue, "QUEUE"), "Queue on which events for other queues are recorded") + ("replication-listener-name", optValue(name, "NAME"), "name by which to register the replicating event listener") + ("create-replication-queue", optValue(createQueue), "if set, the replication will be created if it does not exist"); +} + +static ReplicatingEventListener plugin; + +}} // namespace qpid::replication diff --git a/qpid/cpp/src/qpid/replication/ReplicatingEventListener.h b/qpid/cpp/src/qpid/replication/ReplicatingEventListener.h new file mode 100644 index 0000000000..74418d00e6 --- /dev/null +++ b/qpid/cpp/src/qpid/replication/ReplicatingEventListener.h @@ -0,0 +1,78 @@ +#ifndef QPID_REPLICATION_REPLICATINGEVENTLISTENER_H +#define QPID_REPLICATION_REPLICATINGEVENTLISTENER_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/Plugin.h" +#include "qpid/Options.h" +#include "qpid/broker/Exchange.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/QueueEvents.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/framing/SequenceNumber.h" + +namespace qpid { +namespace replication { + +/** + * An event listener plugin that records queue events as messages on a + * replication queue, from where they can be consumed (e.g. by an + * inter-broker link to the corresponding QueueReplicationExchange + * plugin. + */ +class ReplicatingEventListener : public Plugin +{ + public: + Options* getOptions(); + void earlyInitialize(Plugin::Target& target); + void initialize(Plugin::Target& target); + void handle(qpid::broker::QueueEvents::Event); + private: + struct PluginOptions : public Options + { + std::string queue; + std::string exchange; + std::string exchangeType; + std::string name; + bool createQueue; + + PluginOptions(); + }; + + PluginOptions options; + qpid::broker::Queue::shared_ptr queue; + qpid::broker::Exchange::shared_ptr exchange; + + void deliverDequeueMessage(const qpid::broker::QueuedMessage& enqueued); + void deliverEnqueueMessage(const qpid::broker::QueuedMessage& enqueued); + void route(boost::intrusive_ptr<qpid::broker::Message>); + void shutdown(); + + boost::intrusive_ptr<qpid::broker::Message> createMessage(const qpid::framing::FieldTable& headers); + boost::intrusive_ptr<qpid::broker::Message> cloneMessage(qpid::broker::Queue& queue, + boost::intrusive_ptr<qpid::broker::Message> original); +}; + +}} // namespace qpid::replication + +#endif /*!QPID_REPLICATION_REPLICATINGEVENTLISTENER_H*/ diff --git a/qpid/cpp/src/qpid/replication/ReplicationExchange.cpp b/qpid/cpp/src/qpid/replication/ReplicationExchange.cpp new file mode 100644 index 0000000000..4b6d25ac7d --- /dev/null +++ b/qpid/cpp/src/qpid/replication/ReplicationExchange.cpp @@ -0,0 +1,234 @@ +/* + * + * 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/replication/ReplicationExchange.h" +#include "qpid/replication/constants.h" +#include "qpid/Plugin.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/QueueRegistry.h" +#include "qpid/broker/ExchangeRegistry.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/log/Statement.h" +#include <boost/bind.hpp> + +namespace qpid { +namespace replication { + +using namespace qpid::broker; +using namespace qpid::framing; +using namespace qpid::replication::constants; + +const std::string SEQUENCE_VALUE("qpid.replication-event.sequence"); +ReplicationExchange::ReplicationExchange(const std::string& name, bool durable, + const FieldTable& _args, + QueueRegistry& qr, + Manageable* parent, Broker* broker) + : Exchange(name, durable, _args, parent, broker), queues(qr), sequence(args.getAsInt64(SEQUENCE_VALUE)), init(false) +{ + args.setInt64(SEQUENCE_VALUE, sequence); + if (mgmtExchange != 0) + mgmtExchange->set_type(typeName); +} + +std::string ReplicationExchange::getType() const { return typeName; } + +void ReplicationExchange::route(Deliverable& msg, const std::string& /*routingKey*/, const FieldTable* args) +{ + if (mgmtExchange != 0) { + mgmtExchange->inc_msgReceives(); + mgmtExchange->inc_byteReceives(msg.contentSize()); + } + if (args) { + int eventType = args->getAsInt(REPLICATION_EVENT_TYPE); + if (eventType) { + if (isDuplicate(args)) return; + switch (eventType) { + case ENQUEUE: + handleEnqueueEvent(args, msg); + return; + case DEQUEUE: + handleDequeueEvent(args, msg); + return; + default: + throw IllegalArgumentException(QPID_MSG("Illegal value for " << REPLICATION_EVENT_TYPE << ": " << eventType)); + } + } + } else { + QPID_LOG(warning, "Dropping unexpected message with no headers"); + if (mgmtExchange != 0) { + mgmtExchange->inc_msgDrops(); + mgmtExchange->inc_byteDrops(msg.contentSize()); + } + } +} + +void ReplicationExchange::handleEnqueueEvent(const FieldTable* args, Deliverable& msg) +{ + std::string queueName = args->getAsString(REPLICATION_TARGET_QUEUE); + Queue::shared_ptr queue = queues.find(queueName); + if (queue) { + + SequenceNumber seqno1(args->getAsInt(QUEUE_MESSAGE_POSITION)); + + // note that queue will ++ before enqueue. + if (queue->getPosition() > --seqno1) // test queue.pos < seqnumber + { + QPID_LOG(error, "Cannot enqueue replicated message. Destination Queue " << queueName << " ahead of source queue"); + mgmtExchange->inc_msgDrops(); + mgmtExchange->inc_byteDrops(msg.contentSize()); + } 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.deliverTo(queue); + QPID_LOG(debug, "Enqueued replicated message onto " << queueName); + if (mgmtExchange != 0) { + mgmtExchange->inc_msgRoutes(); + mgmtExchange->inc_byteRoutes( msg.contentSize()); + } + } + } else { + QPID_LOG(error, "Cannot enqueue replicated message. Queue " << queueName << " does not exist"); + if (mgmtExchange != 0) { + mgmtExchange->inc_msgDrops(); + mgmtExchange->inc_byteDrops(msg.contentSize()); + } + } +} + +void ReplicationExchange::handleDequeueEvent(const FieldTable* args, Deliverable& msg) +{ + std::string queueName = args->getAsString(REPLICATION_TARGET_QUEUE); + Queue::shared_ptr queue = queues.find(queueName); + if (queue) { + SequenceNumber position(args->getAsInt(DEQUEUED_MESSAGE_POSITION)); + QueuedMessage dequeued; + if (queue->acquireMessageAt(position, dequeued)) { + queue->dequeue(0, dequeued); + QPID_LOG(debug, "Processed replicated 'dequeue' event from " << queueName << " at position " << position); + if (mgmtExchange != 0) { + mgmtExchange->inc_msgRoutes(); + mgmtExchange->inc_byteRoutes(msg.contentSize()); + } + } else { + QPID_LOG(warning, "Could not acquire message " << position << " from " << queueName); + if (mgmtExchange != 0) { + mgmtExchange->inc_msgDrops(); + mgmtExchange->inc_byteDrops(msg.contentSize()); + } + } + } else { + QPID_LOG(error, "Cannot process replicated 'dequeue' event. Queue " << queueName << " does not exist"); + if (mgmtExchange != 0) { + mgmtExchange->inc_msgDrops(); + mgmtExchange->inc_byteDrops(msg.contentSize()); + } + } +} + +bool ReplicationExchange::isDuplicate(const FieldTable* args) +{ + if (!args->get(REPLICATION_EVENT_SEQNO)) return false; + SequenceNumber seqno(args->getAsInt(REPLICATION_EVENT_SEQNO)); + if (!init) { + init = true; + sequence = seqno; + return false; + } else if (seqno > sequence) { + if (seqno - sequence > 1) { + QPID_LOG(error, "Gap in replication event sequence between: " << sequence << " and " << seqno); + } + sequence = seqno; + return false; + } else { + QPID_LOG(info, "Duplicate detected: seqno=" << seqno << " (last seqno=" << sequence << ")"); + return true; + } +} + +bool ReplicationExchange::bind(Queue::shared_ptr /*queue*/, const std::string& /*routingKey*/, const FieldTable* /*args*/) +{ + throw NotImplementedException("Replication exchange does not support bind operation"); +} + +bool ReplicationExchange::unbind(Queue::shared_ptr /*queue*/, const std::string& /*routingKey*/, const FieldTable* /*args*/) +{ + throw NotImplementedException("Replication exchange does not support unbind operation"); +} + +bool ReplicationExchange::isBound(Queue::shared_ptr /*queue*/, const string* const /*routingKey*/, const FieldTable* const /*args*/) +{ + return false; +} + +const std::string ReplicationExchange::typeName("replication"); + + +void ReplicationExchange::encode(Buffer& buffer) const +{ + args.setInt64(std::string(SEQUENCE_VALUE), sequence); + Exchange::encode(buffer); +} + + +struct ReplicationExchangePlugin : Plugin +{ + Broker* broker; + + ReplicationExchangePlugin(); + void earlyInitialize(Plugin::Target& target); + void initialize(Plugin::Target& target); + Exchange::shared_ptr create(const std::string& name, bool durable, + const framing::FieldTable& args, + management::Manageable* parent, + qpid::broker::Broker* broker); +}; + +ReplicationExchangePlugin::ReplicationExchangePlugin() : broker(0) {} + +Exchange::shared_ptr ReplicationExchangePlugin::create(const std::string& name, bool durable, + const framing::FieldTable& args, + management::Manageable* parent, qpid::broker::Broker* broker) +{ + Exchange::shared_ptr e(new ReplicationExchange(name, durable, args, broker->getQueues(), parent, broker)); + return e; +} + + +void ReplicationExchangePlugin::earlyInitialize(Plugin::Target& target) +{ + broker = dynamic_cast<broker::Broker*>(&target); + if (broker) { + ExchangeRegistry::FactoryFunction f = boost::bind(&ReplicationExchangePlugin::create, this, _1, _2, _3, _4, _5); + broker->getExchanges().registerType(ReplicationExchange::typeName, f); + QPID_LOG(info, "Registered replication exchange"); + } +} + +void ReplicationExchangePlugin::initialize(Target&) {} + +static ReplicationExchangePlugin exchangePlugin; + +}} // namespace qpid::replication diff --git a/qpid/cpp/src/qpid/replication/ReplicationExchange.h b/qpid/cpp/src/qpid/replication/ReplicationExchange.h new file mode 100644 index 0000000000..4b34e0df13 --- /dev/null +++ b/qpid/cpp/src/qpid/replication/ReplicationExchange.h @@ -0,0 +1,72 @@ +#ifndef QPID_REPLICATION_REPLICATIONEXCHANGE_H +#define QPID_REPLICATION_REPLICATIONEXCHANGE_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/Exchange.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/SequenceNumber.h" + +namespace qpid { + +namespace broker { +class QueueRegistry; +} + +namespace replication { + +/** + * A custom exchange plugin that processes incoming messages + * representing enqueue or dequeue events for particular queues and + * carries out the corresponding action to replicate that on the local + * broker. + */ +class ReplicationExchange : public qpid::broker::Exchange +{ + public: + static const std::string typeName; + + ReplicationExchange(const std::string& name, bool durable, + const qpid::framing::FieldTable& args, + qpid::broker::QueueRegistry& queues, + qpid::management::Manageable* parent = 0, + qpid::broker::Broker* broker = 0); + + std::string getType() const; + + void route(qpid::broker::Deliverable& msg, const std::string& routingKey, const qpid::framing::FieldTable* args); + + bool bind(boost::shared_ptr<broker::Queue> queue, const std::string& routingKey, const qpid::framing::FieldTable* args); + bool unbind(boost::shared_ptr<broker::Queue> queue, const std::string& routingKey, const qpid::framing::FieldTable* args); + bool isBound(boost::shared_ptr<broker::Queue> queue, const std::string* const routingKey, const qpid::framing::FieldTable* const args); + private: + qpid::broker::QueueRegistry& queues; + qpid::framing::SequenceNumber sequence; + bool init; + + bool isDuplicate(const qpid::framing::FieldTable* args); + void handleEnqueueEvent(const qpid::framing::FieldTable* args, qpid::broker::Deliverable& msg); + void handleDequeueEvent(const qpid::framing::FieldTable* args, qpid::broker::Deliverable& msg); + void encode(framing::Buffer& buffer) const; +}; +}} // namespace qpid::replication + +#endif /*!QPID_REPLICATION_REPLICATIONEXCHANGE_H*/ diff --git a/qpid/cpp/src/qpid/replication/constants.h b/qpid/cpp/src/qpid/replication/constants.h new file mode 100644 index 0000000000..c5ba7d3d6a --- /dev/null +++ b/qpid/cpp/src/qpid/replication/constants.h @@ -0,0 +1,34 @@ +/* + * + * 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. + * + */ +namespace qpid { +namespace replication { +namespace constants { + +const std::string REPLICATION_EVENT_TYPE("qpid.replication.type"); +const std::string REPLICATION_EVENT_SEQNO("qpid.replication.seqno"); +const std::string REPLICATION_TARGET_QUEUE("qpid.replication.target_queue"); +const std::string DEQUEUED_MESSAGE_POSITION("qpid.replication.message"); +const std::string QUEUE_MESSAGE_POSITION("qpid.replication.queue.position"); + +const int ENQUEUE(1); +const int DEQUEUE(2); + +}}} diff --git a/qpid/cpp/src/qpid/store/CMakeLists.txt b/qpid/cpp/src/qpid/store/CMakeLists.txt new file mode 100644 index 0000000000..464d2de052 --- /dev/null +++ b/qpid/cpp/src/qpid/store/CMakeLists.txt @@ -0,0 +1,111 @@ +# +# 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. +# + +project(qpidc_store) + +#set (CMAKE_VERBOSE_MAKEFILE ON) # for debugging + +include_directories( ${Boost_INCLUDE_DIR} ) + +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ) +include_directories( ${CMAKE_HOME_DIRECTORY}/include ) + +link_directories( ${Boost_LIBRARY_DIRS} ) + +set (store_SOURCES + MessageStorePlugin.cpp + ) +add_library (store MODULE ${store_SOURCES}) +target_link_libraries (store qpidbroker ${Boost_PROGRAM_OPTIONS_LIBRARY}) +if (CMAKE_COMPILER_IS_GNUCXX) + set_target_properties (store PROPERTIES + PREFIX "" + LINK_FLAGS -Wl,--no-undefined) +endif (CMAKE_COMPILER_IS_GNUCXX) + +if (CMAKE_SYSTEM_NAME STREQUAL Windows) + if (MSVC) + add_definitions( + /D "NOMINMAX" + /D "WIN32_LEAN_AND_MEAN" + ) + endif (MSVC) +endif (CMAKE_SYSTEM_NAME STREQUAL Windows) + +set_target_properties (store PROPERTIES VERSION ${qpidc_version}) +install (TARGETS store # RUNTIME + DESTINATION ${QPIDD_MODULE_DIR} + COMPONENT ${QPID_COMPONENT_BROKER}) + +# Build the MS SQL Storage Provider plugin +set (mssql_default ON) +if (NOT CMAKE_SYSTEM_NAME STREQUAL Windows) + set(mssql_default OFF) +endif (NOT CMAKE_SYSTEM_NAME STREQUAL Windows) +option(BUILD_MSSQL "Build MS SQL Store provider plugin" ${mssql_default}) +if (BUILD_MSSQL) + add_library (mssql_store MODULE + ms-sql/MsSqlProvider.cpp + ms-sql/AmqpTransaction.cpp + ms-sql/BindingRecordset.cpp + ms-sql/BlobAdapter.cpp + ms-sql/BlobEncoder.cpp + ms-sql/BlobRecordset.cpp + ms-sql/DatabaseConnection.cpp + ms-sql/MessageMapRecordset.cpp + ms-sql/MessageRecordset.cpp + ms-sql/Recordset.cpp + ms-sql/SqlTransaction.cpp + ms-sql/State.cpp + ms-sql/TplRecordset.cpp + ms-sql/VariantHelper.cpp) + target_link_libraries (mssql_store qpidbroker qpidcommon ${Boost_PROGRAM_OPTIONS_LIBRARY}) + install (TARGETS mssql_store # RUNTIME + DESTINATION ${QPIDD_MODULE_DIR} + COMPONENT ${QPID_COMPONENT_BROKER}) +endif (BUILD_MSSQL) + +# Build the MS SQL-CLFS Storage Provider plugin +set (msclfs_default ON) +if (NOT CMAKE_SYSTEM_NAME STREQUAL Windows) + set(msclfs_default OFF) +endif (NOT CMAKE_SYSTEM_NAME STREQUAL Windows) +option(BUILD_MSCLFS "Build MS hybrid SQL-CLFS Store provider plugin" ${msclfs_default}) +if (BUILD_MSCLFS) + add_library (msclfs_store MODULE + ms-clfs/MsSqlClfsProvider.cpp + ms-clfs/Log.cpp + ms-clfs/MessageLog.cpp + ms-clfs/Messages.cpp + ms-clfs/Transaction.cpp + ms-clfs/TransactionLog.cpp + ms-sql/BindingRecordset.cpp + ms-sql/BlobAdapter.cpp + ms-sql/BlobEncoder.cpp + ms-sql/BlobRecordset.cpp + ms-sql/DatabaseConnection.cpp + ms-sql/Recordset.cpp + ms-sql/State.cpp + ms-sql/VariantHelper.cpp) + include_directories(ms-sql) + target_link_libraries (msclfs_store qpidbroker qpidcommon ${Boost_PROGRAM_OPTIONS_LIBRARY} clfsw32.lib) + install (TARGETS msclfs_store # RUNTIME + DESTINATION ${QPIDD_MODULE_DIR} + COMPONENT ${QPID_COMPONENT_BROKER}) +endif (BUILD_MSCLFS) diff --git a/qpid/cpp/src/qpid/store/MessageStorePlugin.cpp b/qpid/cpp/src/qpid/store/MessageStorePlugin.cpp new file mode 100644 index 0000000000..2a8d971987 --- /dev/null +++ b/qpid/cpp/src/qpid/store/MessageStorePlugin.cpp @@ -0,0 +1,469 @@ +/* + * + * 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 "MessageStorePlugin.h" +#include "StorageProvider.h" +#include "StoreException.h" +#include "qpid/broker/Broker.h" +#include "qpid/Plugin.h" +#include "qpid/Options.h" +#include "qpid/DataDir.h" +#include "qpid/log/Statement.h" + +/* + * The MessageStore pointer given to the Broker points to static storage. + * Thus, it cannot be deleted, especially by the broker. To prevent deletion, + * this no-op deleter is used with the boost::shared_ptr. When the last + * shared_ptr is destroyed, the deleter is called rather than delete(). + */ +namespace { + class NoopDeleter { + public: + NoopDeleter() {} + void operator()(qpid::broker::MessageStore * /*p*/) {} + }; +} + +namespace qpid { +namespace store { + +static MessageStorePlugin static_instance_registers_plugin; + + +MessageStorePlugin::StoreOptions::StoreOptions(const std::string& name) : + qpid::Options(name) +{ + addOptions() + ("storage-provider", qpid::optValue(providerName, "PROVIDER"), + "Name of the storage provider to use.") + ; +} + + +void +MessageStorePlugin::earlyInitialize (qpid::Plugin::Target& target) +{ + qpid::broker::Broker* b = + dynamic_cast<qpid::broker::Broker*>(&target); + if (0 == b) + return; // Only listen to Broker targets + + broker = b; + + // See if there are any storage provider plugins ready. If not, we can't + // do a message store. + qpid::Plugin::earlyInitAll(*this); + + if (providers.empty()) { + QPID_LOG(warning, + "Message store plugin: No storage providers available."); + provider = providers.end(); + return; + } + if (!options.providerName.empty()) { + // If specific one was chosen, locate it in loaded set of providers. + provider = providers.find(options.providerName); + if (provider == providers.end()) + throw Exception("Message store plugin: storage provider '" + + options.providerName + + "' does not exist."); + } + else { + // No specific provider chosen; if there's only one, use it. Else + // report the need to pick one. + if (providers.size() > 1) { + provider = providers.end(); + throw Exception("Message store plugin: multiple provider plugins " + "loaded; must either load only one or select one " + "using --storage-provider"); + } + provider = providers.begin(); + } + + provider->second->activate(*this); + NoopDeleter d; + boost::shared_ptr<qpid::broker::MessageStore> sp(this, d); + broker->setStore(sp); + target.addFinalizer(boost::bind(&MessageStorePlugin::finalizeMe, this)); +} + +void +MessageStorePlugin::initialize(qpid::Plugin::Target& target) +{ + qpid::broker::Broker* broker = + dynamic_cast<qpid::broker::Broker*>(&target); + if (0 == broker) + return; // Only listen to Broker targets + + // Pass along the initialize step to the provider that's activated. + if (provider != providers.end()) { + provider->second->initialize(*this); + } + // qpid::Plugin::initializeAll(*this); +} + +void +MessageStorePlugin::finalizeMe() +{ + finalize(); // Call finalizers on any Provider plugins +} + +void +MessageStorePlugin::providerAvailable(const std::string name, + StorageProvider *be) +{ + ProviderMap::value_type newSp(name, be); + std::pair<ProviderMap::iterator, bool> inserted = providers.insert(newSp); + if (inserted.second == false) + QPID_LOG(warning, "Storage provider " << name << " duplicate; ignored."); +} + +void +MessageStorePlugin::truncateInit(const bool /*saveStoreContent*/) +{ + QPID_LOG(info, "Store: truncateInit"); +} + + +/** + * Record the existence of a durable queue + */ +void +MessageStorePlugin::create(broker::PersistableQueue& queue, + const framing::FieldTable& args) +{ + if (queue.getName().size() == 0) + { + QPID_LOG(error, + "Cannot create store for empty (null) queue name - " + "ignoring and attempting to continue."); + return; + } + if (queue.getPersistenceId()) { + THROW_STORE_EXCEPTION("Queue already created: " + queue.getName()); + } + provider->second->create(queue, args); +} + +/** + * Destroy a durable queue + */ +void +MessageStorePlugin::destroy(broker::PersistableQueue& queue) +{ + provider->second->destroy(queue); +} + +/** + * Record the existence of a durable exchange + */ +void +MessageStorePlugin::create(const broker::PersistableExchange& exchange, + const framing::FieldTable& args) +{ + if (exchange.getPersistenceId()) { + THROW_STORE_EXCEPTION("Exchange already created: " + exchange.getName()); + } + provider->second->create(exchange, args); +} + +/** + * Destroy a durable exchange + */ +void +MessageStorePlugin::destroy(const broker::PersistableExchange& exchange) +{ + provider->second->destroy(exchange); +} + +/** + * Record a binding + */ +void +MessageStorePlugin::bind(const broker::PersistableExchange& exchange, + const broker::PersistableQueue& queue, + const std::string& key, + const framing::FieldTable& args) +{ + provider->second->bind(exchange, queue, key, args); +} + +/** + * Forget a binding + */ +void +MessageStorePlugin::unbind(const broker::PersistableExchange& exchange, + const broker::PersistableQueue& queue, + const std::string& key, + const framing::FieldTable& args) +{ + provider->second->unbind(exchange, queue, key, args); +} + +/** + * Record generic durable configuration + */ +void +MessageStorePlugin::create(const broker::PersistableConfig& config) +{ + if (config.getPersistenceId()) { + THROW_STORE_EXCEPTION("Config item already created: " + + config.getName()); + } + provider->second->create(config); +} + +/** + * Destroy generic durable configuration + */ +void +MessageStorePlugin::destroy(const broker::PersistableConfig& config) +{ + provider->second->destroy(config); +} + +/** + * Stores a message before it has been enqueued + * (enqueueing automatically stores the message so this is + * only required if storage is required prior to that + * point). + */ +void +MessageStorePlugin::stage(const boost::intrusive_ptr<broker::PersistableMessage>& msg) +{ + if (msg->getPersistenceId() == 0 && !msg->isContentReleased()) { + provider->second->stage(msg); + } +} + +/** + * Destroys a previously staged message. This only needs + * to be called if the message is never enqueued. (Once + * enqueued, deletion will be automatic when the message + * is dequeued from all queues it was enqueued onto). + */ +void +MessageStorePlugin::destroy(broker::PersistableMessage& msg) +{ + if (msg.getPersistenceId()) + provider->second->destroy(msg); +} + +/** + * Appends content to a previously staged message + */ +void +MessageStorePlugin::appendContent + (const boost::intrusive_ptr<const broker::PersistableMessage>& msg, + const std::string& data) +{ + if (msg->getPersistenceId()) + provider->second->appendContent(msg, data); + else + THROW_STORE_EXCEPTION("Cannot append content. Message not known to store!"); +} + +/** + * Loads (a section) of content data for the specified + * message (previously stored through a call to stage or + * enqueue) into data. The offset refers to the content + * only (i.e. an offset of 0 implies that the start of the + * content should be loaded, not the headers or related + * meta-data). + */ +void +MessageStorePlugin::loadContent(const broker::PersistableQueue& queue, + const boost::intrusive_ptr<const broker::PersistableMessage>& msg, + std::string& data, + uint64_t offset, + uint32_t length) +{ + if (msg->getPersistenceId()) + provider->second->loadContent(queue, msg, data, offset, length); + else + THROW_STORE_EXCEPTION("Cannot load content. Message not known to store!"); +} + +/** + * Enqueues a message, storing the message if it has not + * been previously stored and recording that the given + * message is on the given queue. + * + * Note: The operation is asynchronous so the return of this function does + * not mean the operation is complete. + */ +void +MessageStorePlugin::enqueue(broker::TransactionContext* ctxt, + const boost::intrusive_ptr<broker::PersistableMessage>& msg, + const broker::PersistableQueue& queue) +{ + if (queue.getPersistenceId() == 0) { + THROW_STORE_EXCEPTION("Queue not created: " + queue.getName()); + } + provider->second->enqueue(ctxt, msg, queue); +} + +/** + * Dequeues a message, recording that the given message is + * no longer on the given queue and deleting the message + * if it is no longer on any other queue. + * + * Note: The operation is asynchronous so the return of this function does + * not mean the operation is complete. + */ +void +MessageStorePlugin::dequeue(broker::TransactionContext* ctxt, + const boost::intrusive_ptr<broker::PersistableMessage>& msg, + const broker::PersistableQueue& queue) +{ + provider->second->dequeue(ctxt, msg, queue); +} + +/** + * Flushes all async messages to disk for the specified queue + * + * Note: The operation is asynchronous so the return of this function does + * not mean the operation is complete. + */ +void +MessageStorePlugin::flush(const broker::PersistableQueue& queue) +{ + provider->second->flush(queue); +} + +/** + * Returns the number of outstanding AIO's for a given queue + * + * If 0, than all the enqueue / dequeues have been stored + * to disk. + */ +uint32_t +MessageStorePlugin::outstandingQueueAIO(const broker::PersistableQueue& queue) +{ + return provider->second->outstandingQueueAIO(queue); +} + +std::auto_ptr<broker::TransactionContext> +MessageStorePlugin::begin() +{ + return provider->second->begin(); +} + +std::auto_ptr<broker::TPCTransactionContext> +MessageStorePlugin::begin(const std::string& xid) +{ + return provider->second->begin(xid); +} + +void +MessageStorePlugin::prepare(broker::TPCTransactionContext& ctxt) +{ + provider->second->prepare(ctxt); +} + +void +MessageStorePlugin::commit(broker::TransactionContext& ctxt) +{ + provider->second->commit(ctxt); +} + +void +MessageStorePlugin::abort(broker::TransactionContext& ctxt) +{ + provider->second->abort(ctxt); +} + +void +MessageStorePlugin::collectPreparedXids(std::set<std::string>& xids) +{ + provider->second->collectPreparedXids(xids); +} + +/** + * Request recovery of queue and message state; inherited from Recoverable + */ +void +MessageStorePlugin::recover(broker::RecoveryManager& recoverer) +{ + ExchangeMap exchanges; + QueueMap queues; + MessageMap messages; + MessageQueueMap messageQueueMap; + std::vector<std::string> xids; + PreparedTransactionMap dtxMap; + + provider->second->recoverConfigs(recoverer); + provider->second->recoverExchanges(recoverer, exchanges); + provider->second->recoverQueues(recoverer, queues); + provider->second->recoverBindings(recoverer, exchanges, queues); + // Important to recover messages before transactions in the SQL-CLFS + // case. If this becomes a problem, it may be possible to resolve it. + // If in doubt please raise a jira and notify Steve Huston + // <shuston@riverace.com>. + provider->second->recoverMessages(recoverer, messages, messageQueueMap); + provider->second->recoverTransactions(recoverer, dtxMap); + // Enqueue msgs where needed. + for (MessageQueueMap::const_iterator i = messageQueueMap.begin(); + i != messageQueueMap.end(); + ++i) { + // Locate the message corresponding to the current message Id + MessageMap::const_iterator iMsg = messages.find(i->first); + if (iMsg == messages.end()) { + std::ostringstream oss; + oss << "No matching message trying to re-enqueue message " + << i->first; + THROW_STORE_EXCEPTION(oss.str()); + } + broker::RecoverableMessage::shared_ptr msg = iMsg->second; + // Now for each queue referenced in the queue map, locate it + // and re-enqueue the message. + for (std::vector<QueueEntry>::const_iterator j = i->second.begin(); + j != i->second.end(); + ++j) { + // Locate the queue corresponding to the current queue Id + QueueMap::const_iterator iQ = queues.find(j->queueId); + if (iQ == queues.end()) { + std::ostringstream oss; + oss << "No matching queue trying to re-enqueue message " + << " on queue Id " << j->queueId; + THROW_STORE_EXCEPTION(oss.str()); + } + // Messages involved in prepared transactions have their status + // updated accordingly. First, though, restore a message that + // is expected to be on a queue, including non-transacted + // messages and those pending dequeue in a dtx. + if (j->tplStatus != QueueEntry::ADDING) + iQ->second->recover(msg); + switch(j->tplStatus) { + case QueueEntry::ADDING: + dtxMap[j->xid]->enqueue(iQ->second, msg); + break; + case QueueEntry::REMOVING: + dtxMap[j->xid]->dequeue(iQ->second, msg); + break; + default: + break; + } + } + } +} + +}} // namespace qpid::store diff --git a/qpid/cpp/src/qpid/store/MessageStorePlugin.h b/qpid/cpp/src/qpid/store/MessageStorePlugin.h new file mode 100644 index 0000000000..4a9bb2aecb --- /dev/null +++ b/qpid/cpp/src/qpid/store/MessageStorePlugin.h @@ -0,0 +1,288 @@ +#ifndef QPID_STORE_MESSAGESTOREPLUGIN_H +#define QPID_STORE_MESSAGESTOREPLUGIN_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/Plugin.h" +#include "qpid/Options.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/MessageStore.h" +#include "qpid/broker/PersistableExchange.h" +#include "qpid/broker/PersistableMessage.h" +#include "qpid/broker/PersistableQueue.h" +#include "qpid/management/Manageable.h" + +#include <string> + +using namespace qpid; + +namespace qpid { +namespace store { + +class StorageProvider; + +/** + * @class MessageStorePlugin + * + * MessageStorePlugin is the front end of the persistent message store + * plugin. It is responsible for coordinating recovery, initialization, + * transactions (both local and distributed), flow-to-disk loading and + * unloading and persisting broker state (queues, bindings etc.). + * Actual storage operations are carried out by a message store storage + * provider that implements the qpid::store::StorageProvider interface. + */ +class MessageStorePlugin : + public qpid::Plugin, + public qpid::broker::MessageStore, // Frontend classes + public qpid::Plugin::Target // Provider target + // @TODO Need a mgmt story for this. Maybe allow r/o access to provider store info? public qpid::management::Manageable +{ + public: + MessageStorePlugin() : broker(0) {} + + /** + * @name Methods inherited from qpid::Plugin + */ + //@{ + virtual Options* getOptions() { return &options; } + virtual void earlyInitialize (Plugin::Target& target); + virtual void initialize(Plugin::Target& target); + //@} + + /// Finalizer; calls Target::finalize() to run finalizers on + /// StorageProviders. + void finalizeMe(); + + /** + * Called by StorageProvider instances during the earlyInitialize sequence. + * Each StorageProvider must supply a unique name by which it is known and a + * pointer to itself. + */ + virtual void providerAvailable(const std::string name, StorageProvider *be); + + /** + * @name Methods inherited from qpid::broker::MessageStore + */ + //@{ + /** + * If called before recovery, will discard the database and reinitialize + * using an empty store. This is used when cluster nodes recover and + * must get their content from a cluster sync rather than directly from + * the store. + * + * @param saveStoreContent If true, the store's contents should be + * saved to a backup location before + * reinitializing the store content. + */ + virtual void truncateInit(const bool saveStoreContent = false); + + /** + * Record the existence of a durable queue + */ + virtual void create(broker::PersistableQueue& queue, + const framing::FieldTable& args); + /** + * Destroy a durable queue + */ + virtual void destroy(broker::PersistableQueue& queue); + + /** + * Record the existence of a durable exchange + */ + virtual void create(const broker::PersistableExchange& exchange, + const framing::FieldTable& args); + /** + * Destroy a durable exchange + */ + virtual void destroy(const broker::PersistableExchange& exchange); + + /** + * Record a binding + */ + virtual void bind(const broker::PersistableExchange& exchange, + const broker::PersistableQueue& queue, + const std::string& key, + const framing::FieldTable& args); + + /** + * Forget a binding + */ + virtual void unbind(const broker::PersistableExchange& exchange, + const broker::PersistableQueue& queue, + const std::string& key, + const framing::FieldTable& args); + + /** + * Record generic durable configuration + */ + virtual void create(const broker::PersistableConfig& config); + + /** + * Destroy generic durable configuration + */ + virtual void destroy(const broker::PersistableConfig& config); + + /** + * Stores a message before it has been enqueued + * (enqueueing automatically stores the message so this is + * only required if storage is required prior to that + * point). If the message has not yet been stored it will + * store the headers as well as any content passed in. A + * persistence id will be set on the message which can be + * used to load the content or to append to it. + */ + virtual void stage(const boost::intrusive_ptr<broker::PersistableMessage>& msg); + + /** + * Destroys a previously staged message. This only needs + * to be called if the message is never enqueued. (Once + * enqueued, deletion will be automatic when the message + * is dequeued from all queues it was enqueued onto). + */ + virtual void destroy(broker::PersistableMessage& msg); + + /** + * Appends content to a previously staged message + */ + virtual void appendContent(const boost::intrusive_ptr<const broker::PersistableMessage>& msg, + const std::string& data); + + /** + * Loads (a section) of content data for the specified + * message (previously stored through a call to stage or + * enqueue) into data. The offset refers to the content + * only (i.e. an offset of 0 implies that the start of the + * content should be loaded, not the headers or related + * meta-data). + */ + virtual void loadContent(const broker::PersistableQueue& queue, + const boost::intrusive_ptr<const broker::PersistableMessage>& msg, + std::string& data, + uint64_t offset, + uint32_t length); + + /** + * Enqueues a message, storing the message if it has not + * been previously stored and recording that the given + * message is on the given queue. + * + * Note: The operation is asynchronous so the return of this function does + * not mean the operation is complete. + * + * @param msg the message to enqueue + * @param queue the name of the queue onto which it is to be enqueued + * @param xid (a pointer to) an identifier of the + * distributed transaction in which the operation takes + * place or null for 'local' transactions + */ + virtual void enqueue(broker::TransactionContext* ctxt, + const boost::intrusive_ptr<broker::PersistableMessage>& msg, + const broker::PersistableQueue& queue); + + /** + * Dequeues a message, recording that the given message is + * no longer on the given queue and deleting the message + * if it is no longer on any other queue. + * + * + * Note: The operation is asynchronous so the return of this function does + * not mean the operation is complete. + * + * @param msg the message to dequeue + * @param queue the name of the queue from which it is to be dequeued + * @param xid (a pointer to) an identifier of the + * distributed transaction in which the operation takes + * place or null for 'local' transactions + */ + virtual void dequeue(broker::TransactionContext* ctxt, + const boost::intrusive_ptr<broker::PersistableMessage>& msg, + const broker::PersistableQueue& queue); + + /** + * Flushes all async messages to disk for the specified queue + * + * + * Note: The operation is asynchronous so the return of this function does + * not mean the operation is complete. + * + * @param queue the name of the queue from which it is to be dequeued + */ + virtual void flush(const broker::PersistableQueue& queue); + + /** + * Returns the number of outstanding AIO's for a given queue + * + * If 0, than all the enqueue / dequeues have been stored + * to disk + * + * @param queue the name of the queue to check for outstanding AIO + */ + virtual uint32_t outstandingQueueAIO(const broker::PersistableQueue& queue); + //@} + + /** + * @name Methods inherited from qpid::broker::TransactionalStore + */ + //@{ + std::auto_ptr<broker::TransactionContext> begin(); + + std::auto_ptr<broker::TPCTransactionContext> begin(const std::string& xid); + + void prepare(broker::TPCTransactionContext& ctxt); + + void commit(broker::TransactionContext& ctxt); + + void abort(broker::TransactionContext& ctxt); + + void collectPreparedXids(std::set<std::string>& xids); + //@} + + /** + * Request recovery of queue and message state; inherited from Recoverable + */ + virtual void recover(broker::RecoveryManager& recoverer); + + // inline management::Manageable::status_t ManagementMethod (uint32_t, management::Args&, std::string&) + // { return management::Manageable::STATUS_OK; } + + // So storage provider can get the broker info. + broker::Broker *getBroker() { return broker; } + + protected: + + struct StoreOptions : public qpid::Options { + StoreOptions(const std::string& name="Store Options"); + std::string providerName; + }; + StoreOptions options; + + typedef std::map<const std::string, StorageProvider*> ProviderMap; + ProviderMap providers; + ProviderMap::const_iterator provider; + + broker::Broker *broker; + +}; // class MessageStoreImpl + +}} // namespace qpid::store + +#endif /* QPID_SERIALIZER_H */ diff --git a/qpid/cpp/src/qpid/store/StorageProvider.h b/qpid/cpp/src/qpid/store/StorageProvider.h new file mode 100644 index 0000000000..bc8d187517 --- /dev/null +++ b/qpid/cpp/src/qpid/store/StorageProvider.h @@ -0,0 +1,343 @@ +#ifndef QPID_STORE_STORAGEPROVIDER_H +#define QPID_STORE_STORAGEPROVIDER_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 <map> +#include <stdexcept> +#include <vector> +#include "qpid/Exception.h" +#include "qpid/Plugin.h" +#include "qpid/Options.h" +#include "qpid/broker/MessageStore.h" + +using qpid::broker::PersistableConfig; +using qpid::broker::PersistableExchange; +using qpid::broker::PersistableMessage; +using qpid::broker::PersistableQueue; + +namespace qpid { +namespace store { + +typedef std::map<uint64_t, qpid::broker::RecoverableExchange::shared_ptr> + ExchangeMap; +typedef std::map<uint64_t, qpid::broker::RecoverableQueue::shared_ptr> + QueueMap; +typedef std::map<uint64_t, qpid::broker::RecoverableMessage::shared_ptr> + MessageMap; +// Msg Id -> vector of queue entries where message is queued +struct QueueEntry { + enum TplStatus { NONE = 0, ADDING = 1, REMOVING = 2 }; + uint64_t queueId; + TplStatus tplStatus; + std::string xid; + + QueueEntry(uint64_t id, TplStatus tpl = NONE, const std::string& x = "") + : queueId(id), tplStatus(tpl), xid(x) {} + + bool operator==(const QueueEntry& rhs) { + if (queueId != rhs.queueId) return false; + if (tplStatus == NONE && rhs.tplStatus == NONE) return true; + return xid == rhs.xid; + } +}; +typedef std::map<uint64_t, std::vector<QueueEntry> > MessageQueueMap; +typedef std::map<std::string, qpid::broker::RecoverableTransaction::shared_ptr> + PreparedTransactionMap; + +class MessageStorePlugin; + +/** + * @class StorageProvider + * + * StorageProvider defines the interface for the storage provider plugin to the + * Qpid broker persistence store plugin. + * + * @TODO Should StorageProvider also inherit from MessageStore? If so, then + * maybe remove Recoverable from MessageStore's inheritance and move it + * to MessageStorePlugin? In any event, somehow the discardInit() feature + * needs to get added here. + */ +class StorageProvider : public qpid::Plugin, public qpid::broker::MessageStore +{ +public: + + class Exception : public qpid::Exception + { + public: + virtual ~Exception() throw() {} + virtual const char *what() const throw() = 0; + }; + + /** + * @name Methods inherited from qpid::Plugin + */ + //@{ + /** + * Return a pointer to the provider's options. The options will be + * updated during option parsing by the host program; therefore, the + * referenced Options object must remain valid past this function's return. + * + * @return An options group or 0 for no options. Default returns 0. + * Plugin retains ownership of return value. + */ + virtual qpid::Options* getOptions() = 0; + + /** + * Initialize Plugin functionality on a Target, called before + * initializing the target. + * + * StorageProviders should respond only to Targets of class + * qpid::store::MessageStorePlugin and ignore all others. + * + * When called, the provider should invoke the method + * qpid::store::MessageStorePlugin::providerAvailable() to alert the + * message store of StorageProvider's availability. + * + * Called before the target itself is initialized. + */ + virtual void earlyInitialize (Plugin::Target& target) = 0; + + /** + * Initialize StorageProvider functionality. Called after initializing + * the target. + * + * StorageProviders should respond only to Targets of class + * qpid::store::MessageStorePlugin and ignore all others. + * + * Called after the target is fully initialized. + */ + virtual void initialize(Plugin::Target& target) = 0; + //@} + + /** + * Receive notification that this provider is the one that will actively + * handle storage for the target. If the provider is to be used, this + * method will be called after earlyInitialize() and before any + * recovery operations (recovery, in turn, precedes call to initialize()). + * Thus, it is wise to not actually do any database ops from within + * earlyInitialize() - they can wait until activate() is called because + * at that point it is certain the database will be needed. + */ + virtual void activate(MessageStorePlugin &store) = 0; + + /** + * @name Methods inherited from qpid::broker::MessageStore + */ + //@{ + /** + * If called after init() but before recovery, will discard the database + * and reinitialize using an empty store dir. If @a pushDownStoreFiles + * is true, the content of the store dir will be moved to a backup dir + * inside the store dir. This is used when cluster nodes recover and must + * get thier content from a cluster sync rather than directly fromt the + * store. + * + * @param pushDownStoreFiles If true, will move content of the store dir + * into a subdir, leaving the store dir + * otherwise empty. + */ + virtual void truncateInit(const bool pushDownStoreFiles = false) = 0; + + /** + * Record the existence of a durable queue + */ + virtual void create(PersistableQueue& queue, + const qpid::framing::FieldTable& args) = 0; + /** + * Destroy a durable queue + */ + virtual void destroy(PersistableQueue& queue) = 0; + + /** + * Record the existence of a durable exchange + */ + virtual void create(const PersistableExchange& exchange, + const qpid::framing::FieldTable& args) = 0; + /** + * Destroy a durable exchange + */ + virtual void destroy(const PersistableExchange& exchange) = 0; + + /** + * Record a binding + */ + virtual void bind(const PersistableExchange& exchange, + const PersistableQueue& queue, + const std::string& key, + const qpid::framing::FieldTable& args) = 0; + + /** + * Forget a binding + */ + virtual void unbind(const PersistableExchange& exchange, + const PersistableQueue& queue, + const std::string& key, + const qpid::framing::FieldTable& args) = 0; + + /** + * Record generic durable configuration + */ + virtual void create(const PersistableConfig& config) = 0; + + /** + * Destroy generic durable configuration + */ + virtual void destroy(const PersistableConfig& config) = 0; + + /** + * Stores a messages before it has been enqueued + * (enqueueing automatically stores the message so this is + * only required if storage is required prior to that + * point). If the message has not yet been stored it will + * store the headers as well as any content passed in. A + * persistence id will be set on the message which can be + * used to load the content or to append to it. + */ + virtual void stage(const boost::intrusive_ptr<PersistableMessage>& msg) = 0; + + /** + * Destroys a previously staged message. This only needs + * to be called if the message is never enqueued. (Once + * enqueued, deletion will be automatic when the message + * is dequeued from all queues it was enqueued onto). + */ + virtual void destroy(PersistableMessage& msg) = 0; + + /** + * Appends content to a previously staged message + */ + virtual void appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg, + const std::string& data) = 0; + + /** + * Loads (a section) of content data for the specified + * message (previously stored through a call to stage or + * enqueue) into data. The offset refers to the content + * only (i.e. an offset of 0 implies that the start of the + * content should be loaded, not the headers or related + * meta-data). + */ + virtual void loadContent(const PersistableQueue& queue, + const boost::intrusive_ptr<const PersistableMessage>& msg, + std::string& data, + uint64_t offset, + uint32_t length) = 0; + + /** + * Enqueues a message, storing the message if it has not + * been previously stored and recording that the given + * message is on the given queue. + * + * Note: that this is async so the return of the function does + * not mean the opperation is complete. + * + * @param msg the message to enqueue + * @param queue the name of the queue onto which it is to be enqueued + * @param xid (a pointer to) an identifier of the + * distributed transaction in which the operation takes + * place or null for 'local' transactions + */ + virtual void enqueue(qpid::broker::TransactionContext* ctxt, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue) = 0; + + /** + * Dequeues a message, recording that the given message is + * no longer on the given queue and deleting the message + * if it is no longer on any other queue. + * + * Note: that this is async so the return of the function does + * not mean the opperation is complete. + * + * @param msg the message to dequeue + * @param queue the name of the queue from which it is to be dequeued + * @param xid (a pointer to) an identifier of the + * distributed transaction in which the operation takes + * place or null for 'local' transactions + */ + virtual void dequeue(qpid::broker::TransactionContext* ctxt, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue) = 0; + + /** + * Flushes all async messages to disk for the specified queue + * + * Note: that this is async so the return of the function does + * not mean the opperation is complete. + * + * @param queue the name of the queue from which it is to be dequeued + */ + virtual void flush(const qpid::broker::PersistableQueue& queue) = 0; + + /** + * Returns the number of outstanding AIO's for a given queue + * + * If 0, than all the enqueue / dequeues have been stored + * to disk + * + * @param queue the name of the queue to check for outstanding AIO + */ + virtual uint32_t outstandingQueueAIO(const PersistableQueue& queue) = 0; + //@} + + /** + * @TODO This should probably not be here - it's only here because + * MessageStore inherits from Recoverable... maybe move that derivation. + * + * As it is now, we don't use this. Separate recover methods are + * declared below for individual types, which also set up maps of + * messages, queues, transactions for the main store plugin to handle + * properly. + * + * Request recovery of queue and message state. + */ + virtual void recover(qpid::broker::RecoveryManager& /*recoverer*/) {} + + /** + * @name Methods that do the recovery of the various objects that + * were saved. + */ + //@{ + + /** + * Recover bindings. + */ + virtual void recoverConfigs(qpid::broker::RecoveryManager& recoverer) = 0; + virtual void recoverExchanges(qpid::broker::RecoveryManager& recoverer, + ExchangeMap& exchangeMap) = 0; + virtual void recoverQueues(qpid::broker::RecoveryManager& recoverer, + QueueMap& queueMap) = 0; + virtual void recoverBindings(qpid::broker::RecoveryManager& recoverer, + const ExchangeMap& exchangeMap, + const QueueMap& queueMap) = 0; + virtual void recoverMessages(qpid::broker::RecoveryManager& recoverer, + MessageMap& messageMap, + MessageQueueMap& messageQueueMap) = 0; + virtual void recoverTransactions(qpid::broker::RecoveryManager& recoverer, + PreparedTransactionMap& dtxMap) = 0; + //@} +}; + +}} // namespace qpid::store + +#endif /* QPID_STORE_STORAGEPROVIDER_H */ diff --git a/qpid/cpp/src/qpid/store/StoreException.h b/qpid/cpp/src/qpid/store/StoreException.h new file mode 100644 index 0000000000..1dc7f670ec --- /dev/null +++ b/qpid/cpp/src/qpid/store/StoreException.h @@ -0,0 +1,49 @@ +#ifndef QPID_STORE_STOREEXCEPTION_H +#define QPID_STORE_STOREEXCEPTION_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 <exception> +#include <boost/format.hpp> +#include "StorageProvider.h" + +namespace qpid { +namespace store { + +class StoreException : public std::exception +{ + std::string text; +public: + StoreException(const std::string& _text) : text(_text) {} + StoreException(const std::string& _text, + const StorageProvider::Exception& cause) + : text(_text + ": " + cause.what()) {} + virtual ~StoreException() throw() {} + virtual const char* what() const throw() { return text.c_str(); } +}; + +#define THROW_STORE_EXCEPTION(MESSAGE) throw qpid::store::StoreException(boost::str(boost::format("%s (%s:%d)") % (MESSAGE) % __FILE__ % __LINE__)) +#define THROW_STORE_EXCEPTION_2(MESSAGE, EXCEPTION) throw qpid::store::StoreException(boost::str(boost::format("%s (%s:%d)") % (MESSAGE) % __FILE__ % __LINE__), EXCEPTION) + +}} // namespace qpid::store + +#endif /* QPID_STORE_STOREEXCEPTION_H */ diff --git a/qpid/cpp/src/qpid/store/ms-clfs/Log.cpp b/qpid/cpp/src/qpid/store/ms-clfs/Log.cpp new file mode 100644 index 0000000000..e6cb10c133 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-clfs/Log.cpp @@ -0,0 +1,182 @@ +/* + * + * 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 <windows.h> +#include <clfsw32.h> +#include <clfsmgmtw32.h> +#include <sstream> +#include <string> +#include <vector> +#include <stdlib.h> +#include <qpid/sys/windows/check.h> + +#include "Log.h" + +namespace qpid { +namespace store { +namespace ms_clfs { + +Log::~Log() +{ + if (marshal != 0) + ::DeleteLogMarshallingArea(marshal); + ::CloseHandle(handle); +} + +void +Log::open(const std::string& path, const TuningParameters& params) +{ + this->containerSize = static_cast<ULONGLONG>(params.containerSize); + logPath = path; + std::string logSpec = "log:" + path; + size_t specLength = logSpec.length(); + std::auto_ptr<wchar_t> wLogSpec(new wchar_t[specLength + 1]); + size_t converted; + mbstowcs_s(&converted, + wLogSpec.get(), specLength+1, + logSpec.c_str(), specLength); + handle = ::CreateLogFile(wLogSpec.get(), + GENERIC_WRITE | GENERIC_READ, + 0, + 0, + OPEN_ALWAYS, + 0); + QPID_WINDOWS_CHECK_NOT(handle, INVALID_HANDLE_VALUE); + CLFS_INFORMATION info; + ULONG infoSize = sizeof(info); + BOOL ok = ::GetLogFileInformation(handle, &info, &infoSize); + QPID_WINDOWS_CHECK_NOT(ok, 0); + ok = ::RegisterManageableLogClient(handle, 0); + QPID_WINDOWS_CHECK_NOT(ok, 0); + + // Set up policies for how many containers to initially create and how + // large each container should be. Also, auto-grow the log when container + // space runs out. + CLFS_MGMT_POLICY logPolicy; + logPolicy.Version = CLFS_MGMT_POLICY_VERSION; + logPolicy.LengthInBytes = sizeof(logPolicy); + logPolicy.PolicyFlags = 0; + + // If this is the first time this log is opened, give an opportunity to + // initialize its content. + bool needInitialize(false); + if (info.TotalContainers == 0) { + // New log; set the configured container size and create the + // initial set of containers. + logPolicy.PolicyType = ClfsMgmtPolicyNewContainerSize; + logPolicy.PolicyParameters.NewContainerSize.SizeInBytes = containerSize; + ok = ::InstallLogPolicy(handle, &logPolicy); + QPID_WINDOWS_CHECK_NOT(ok, 0); + + ULONGLONG desired(params.containers), actual(0); + ok = ::SetLogFileSizeWithPolicy(handle, &desired, &actual); + QPID_WINDOWS_CHECK_NOT(ok, 0); + + needInitialize = true; + } + // Ensure that the log is extended as needed and will shrink when 50% + // becomes unused. + logPolicy.PolicyType = ClfsMgmtPolicyAutoGrow; + logPolicy.PolicyParameters.AutoGrow.Enabled = 1; + ok = ::InstallLogPolicy(handle, &logPolicy); + QPID_WINDOWS_CHECK_NOT(ok, 0); + logPolicy.PolicyType = ClfsMgmtPolicyAutoShrink; + logPolicy.PolicyParameters.AutoShrink.Percentage = params.shrinkPct; + ok = ::InstallLogPolicy(handle, &logPolicy); + QPID_WINDOWS_CHECK_NOT(ok, 0); + + // Need a marshaling area + ok = ::CreateLogMarshallingArea(handle, + NULL, NULL, NULL, // Alloc, free, context + marshallingBufferSize(), + params.maxWriteBuffers, + 1, // Max read buffers + &marshal); + QPID_WINDOWS_CHECK_NOT(ok, 0); + if (needInitialize) + initialize(); +} + +uint32_t +Log::marshallingBufferSize() +{ + // Default implementation returns the minimum marshalling buffer size; + // derived ones should come up with a more fitting value. + // + // Find the directory name part of the log specification, including the + // trailing '\'. + size_t dirMarker = logPath.rfind('\\'); + if (dirMarker == std::string::npos) + dirMarker = logPath.rfind('/'); + DWORD bytesPerSector; + DWORD dontCare; + ::GetDiskFreeSpace(logPath.substr(0, dirMarker).c_str(), + &dontCare, + &bytesPerSector, + &dontCare, + &dontCare); + return bytesPerSector; +} + +CLFS_LSN +Log::write(void* entry, uint32_t length, CLFS_LSN* prev) +{ + CLFS_WRITE_ENTRY desc; + desc.Buffer = entry; + desc.ByteLength = length; + CLFS_LSN lsn; + BOOL ok = ::ReserveAndAppendLog(marshal, + &desc, 1, // Buffer descriptor + 0, prev, // Undo-Next, Prev + 0, 0, // Reservation + CLFS_FLAG_FORCE_FLUSH, + &lsn, + 0); + QPID_WINDOWS_CHECK_NOT(ok, 0); + return lsn; +} + +// Get the current base LSN of the log. +CLFS_LSN +Log::getBase() +{ + CLFS_INFORMATION info; + ULONG infoSize = sizeof(info); + BOOL ok = ::GetLogFileInformation(handle, &info, &infoSize); + QPID_WINDOWS_CHECK_NOT(ok, 0); + return info.BaseLsn; +} + +void +Log::moveTail(const CLFS_LSN& oldest) +{ + BOOL ok = ::AdvanceLogBase(marshal, + const_cast<PCLFS_LSN>(&oldest), + 0, NULL); + // If multiple threads are manipulating things they may get out of + // order when moving the tail; if someone already moved it further + // than this, it's ok - ignore it. + if (ok || ::GetLastError() == ERROR_LOG_START_OF_LOG) + return; + QPID_WINDOWS_CHECK_NOT(ok, 0); +} + +}}} // namespace qpid::store::ms_clfs diff --git a/qpid/cpp/src/qpid/store/ms-clfs/Log.h b/qpid/cpp/src/qpid/store/ms-clfs/Log.h new file mode 100644 index 0000000000..2f7eb6cada --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-clfs/Log.h @@ -0,0 +1,78 @@ +#ifndef QPID_STORE_MSCLFS_LOG_H +#define QPID_STORE_MSCLFS_LOG_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 <string> +#include <windows.h> +#include <clfsw32.h> +#include <qpid/sys/IntegerTypes.h> + +namespace qpid { +namespace store { +namespace ms_clfs { + +/** + * @class Log + * + * Represents a CLFS-housed log. + */ +class Log { + +protected: + HANDLE handle; + ULONGLONG containerSize; + std::string logPath; + PVOID marshal; + + // Give subclasses a chance to initialize a new log. Called after a new + // log is created, initial set of containers is added, and marshalling + // area is allocated. + virtual void initialize() {} + +public: + struct TuningParameters { + size_t containerSize; + unsigned short containers; + unsigned short shrinkPct; + uint32_t maxWriteBuffers; + }; + + Log() : handle(INVALID_HANDLE_VALUE), containerSize(0), marshal(0) {} + virtual ~Log(); + + void open(const std::string& path, const TuningParameters& params); + + virtual uint32_t marshallingBufferSize(); + + CLFS_LSN write(void* entry, uint32_t length, CLFS_LSN* prev = 0); + + // Get the current base LSN of the log. + CLFS_LSN getBase(); + + // Move the log tail to the indicated LSN. + void moveTail(const CLFS_LSN& oldest); +}; + +}}} // namespace qpid::store::ms_clfs + +#endif /* QPID_STORE_MSCLFS_LOG_H */ diff --git a/qpid/cpp/src/qpid/store/ms-clfs/Lsn.h b/qpid/cpp/src/qpid/store/ms-clfs/Lsn.h new file mode 100644 index 0000000000..7f46c1f266 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-clfs/Lsn.h @@ -0,0 +1,36 @@ +#ifndef QPID_STORE_MSCLFS_LSN_H +#define QPID_STORE_MSCLFS_LSN_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 <clfsw32.h> + +namespace { + // Make it easy to assign LSNs + inline CLFS_LSN idToLsn(const uint64_t val) + { CLFS_LSN lsn; lsn.Internal = val; return lsn; } + + inline uint64_t lsnToId(const CLFS_LSN& lsn) + { uint64_t val = lsn.Internal; return val; } +} + +#endif /* QPID_STORE_MSCLFS_LSN_H */ diff --git a/qpid/cpp/src/qpid/store/ms-clfs/MSSqlClfsProvider.cpp b/qpid/cpp/src/qpid/store/ms-clfs/MSSqlClfsProvider.cpp new file mode 100644 index 0000000000..586aaaf980 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-clfs/MSSqlClfsProvider.cpp @@ -0,0 +1,1121 @@ +/* + * + * 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 <list> +#include <map> +#include <set> +#include <stdlib.h> +#include <string> +#include <windows.h> +#include <clfsw32.h> +#include <qpid/broker/RecoverableQueue.h> +#include <qpid/log/Statement.h> +#include <qpid/store/MessageStorePlugin.h> +#include <qpid/store/StoreException.h> +#include <qpid/store/StorageProvider.h> +#include <qpid/sys/Mutex.h> +#include <boost/foreach.hpp> +#include <boost/make_shared.hpp> + +// From ms-sql... +#include "BlobAdapter.h" +#include "BlobRecordset.h" +#include "BindingRecordset.h" +#include "DatabaseConnection.h" +#include "Exception.h" +#include "State.h" +#include "VariantHelper.h" +using qpid::store::ms_sql::BlobAdapter; +using qpid::store::ms_sql::BlobRecordset; +using qpid::store::ms_sql::BindingRecordset; +using qpid::store::ms_sql::DatabaseConnection; +using qpid::store::ms_sql::ADOException; +using qpid::store::ms_sql::State; +using qpid::store::ms_sql::VariantHelper; + +#include "Log.h" +#include "Messages.h" +#include "Transaction.h" +#include "TransactionLog.h" + +// Bring in ADO 2.8 (yes, I know it says "15", but that's it...) +#import "C:\Program Files\Common Files\System\ado\msado15.dll" \ + no_namespace rename("EOF", "EndOfFile") +#include <comdef.h> +namespace { +inline void TESTHR(HRESULT x) {if FAILED(x) _com_issue_error(x);}; + +// Table names +const std::string TblBinding("tblBinding"); +const std::string TblConfig("tblConfig"); +const std::string TblExchange("tblExchange"); +const std::string TblQueue("tblQueue"); + +} + +namespace qpid { +namespace store { +namespace ms_clfs { + +/** + * @class MSSqlClfsProvider + * + * Implements a qpid::store::StorageProvider that uses a hybrid Microsoft + * SQL Server and Windows CLFS approach as the backend data store for Qpid. + */ +class MSSqlClfsProvider : public qpid::store::StorageProvider +{ +protected: + void finalizeMe(); + + void dump(); + +public: + MSSqlClfsProvider(); + ~MSSqlClfsProvider(); + + virtual qpid::Options* getOptions() { return &options; } + + virtual void earlyInitialize (Plugin::Target& target); + virtual void initialize(Plugin::Target& target); + + /** + * Receive notification that this provider is the one that will actively + * handle provider storage for the target. If the provider is to be used, + * this method will be called after earlyInitialize() and before any + * recovery operations (recovery, in turn, precedes call to initialize()). + */ + virtual void activate(MessageStorePlugin &store); + + /** + * @name Methods inherited from qpid::broker::MessageStore + */ + //@{ + /** + * If called after init() but before recovery, will discard the database + * and reinitialize using an empty store dir. If @a pushDownStoreFiles + * is true, the content of the store dir will be moved to a backup dir + * inside the store dir. This is used when cluster nodes recover and must + * get their content from a cluster sync rather than directly from the + * store. + * + * @param pushDownStoreFiles If true, will move content of the store dir + * into a subdir, leaving the store dir + * otherwise empty. + */ + virtual void truncateInit(const bool pushDownStoreFiles = false); + + /** + * Record the existence of a durable queue + */ + virtual void create(PersistableQueue& queue, + const qpid::framing::FieldTable& args); + /** + * Destroy a durable queue + */ + virtual void destroy(PersistableQueue& queue); + + /** + * Record the existence of a durable exchange + */ + virtual void create(const PersistableExchange& exchange, + const qpid::framing::FieldTable& args); + /** + * Destroy a durable exchange + */ + virtual void destroy(const PersistableExchange& exchange); + + /** + * Record a binding + */ + virtual void bind(const PersistableExchange& exchange, + const PersistableQueue& queue, + const std::string& key, + const qpid::framing::FieldTable& args); + + /** + * Forget a binding + */ + virtual void unbind(const PersistableExchange& exchange, + const PersistableQueue& queue, + const std::string& key, + const qpid::framing::FieldTable& args); + + /** + * Record generic durable configuration + */ + virtual void create(const PersistableConfig& config); + + /** + * Destroy generic durable configuration + */ + virtual void destroy(const PersistableConfig& config); + + /** + * Stores a messages before it has been enqueued + * (enqueueing automatically stores the message so this is + * only required if storage is required prior to that + * point). If the message has not yet been stored it will + * store the headers as well as any content passed in. A + * persistence id will be set on the message which can be + * used to load the content or to append to it. + */ + virtual void stage(const boost::intrusive_ptr<PersistableMessage>& msg); + + /** + * Destroys a previously staged message. This only needs + * to be called if the message is never enqueued. (Once + * enqueued, deletion will be automatic when the message + * is dequeued from all queues it was enqueued onto). + */ + virtual void destroy(PersistableMessage& msg); + + /** + * Appends content to a previously staged message + */ + virtual void appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg, + const std::string& data); + + /** + * Loads (a section) of content data for the specified + * message (previously stored through a call to stage or + * enqueue) into data. The offset refers to the content + * only (i.e. an offset of 0 implies that the start of the + * content should be loaded, not the headers or related + * meta-data). + */ + virtual void loadContent(const qpid::broker::PersistableQueue& queue, + const boost::intrusive_ptr<const PersistableMessage>& msg, + std::string& data, + uint64_t offset, + uint32_t length); + + /** + * Enqueues a message, storing the message if it has not + * been previously stored and recording that the given + * message is on the given queue. + * + * Note: that this is async so the return of the function does + * not mean the opperation is complete. + * + * @param msg the message to enqueue + * @param queue the name of the queue onto which it is to be enqueued + * @param xid (a pointer to) an identifier of the + * distributed transaction in which the operation takes + * place or null for 'local' transactions + */ + virtual void enqueue(qpid::broker::TransactionContext* ctxt, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue); + + /** + * Dequeues a message, recording that the given message is + * no longer on the given queue and deleting the message + * if it is no longer on any other queue. + * + * Note: that this is async so the return of the function does + * not mean the opperation is complete. + * + * @param msg the message to dequeue + * @param queue the name of the queue from which it is to be dequeued + * @param xid (a pointer to) an identifier of the + * distributed transaction in which the operation takes + * place or null for 'local' transactions + */ + virtual void dequeue(qpid::broker::TransactionContext* ctxt, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue); + + /** + * Flushes all async messages to disk for the specified queue + * + * Note: this is a no-op for this provider. + * + * @param queue the name of the queue from which it is to be dequeued + */ + virtual void flush(const PersistableQueue& queue) {}; + + /** + * Returns the number of outstanding AIO's for a given queue + * + * If 0, than all the enqueue / dequeues have been stored + * to disk + * + * @param queue the name of the queue to check for outstanding AIO + */ + virtual uint32_t outstandingQueueAIO(const PersistableQueue& queue) + {return 0;} + //@} + + /** + * @name Methods inherited from qpid::broker::TransactionalStore + */ + //@{ + virtual std::auto_ptr<qpid::broker::TransactionContext> begin(); + virtual std::auto_ptr<qpid::broker::TPCTransactionContext> begin(const std::string& xid); + virtual void prepare(qpid::broker::TPCTransactionContext& txn); + virtual void commit(qpid::broker::TransactionContext& txn); + virtual void abort(qpid::broker::TransactionContext& txn); + virtual void collectPreparedXids(std::set<std::string>& xids); + //@} + + virtual void recoverConfigs(qpid::broker::RecoveryManager& recoverer); + virtual void recoverExchanges(qpid::broker::RecoveryManager& recoverer, + ExchangeMap& exchangeMap); + virtual void recoverQueues(qpid::broker::RecoveryManager& recoverer, + QueueMap& queueMap); + virtual void recoverBindings(qpid::broker::RecoveryManager& recoverer, + const ExchangeMap& exchangeMap, + const QueueMap& queueMap); + virtual void recoverMessages(qpid::broker::RecoveryManager& recoverer, + MessageMap& messageMap, + MessageQueueMap& messageQueueMap); + virtual void recoverTransactions(qpid::broker::RecoveryManager& recoverer, + PreparedTransactionMap& dtxMap); + +private: + struct ProviderOptions : public qpid::Options + { + std::string connectString; + std::string catalogName; + std::string storeDir; + size_t containerSize; + unsigned short initialContainers; + uint32_t maxWriteBuffers; + + ProviderOptions(const std::string &name) + : qpid::Options(name), + catalogName("QpidStore"), + containerSize(1024 * 1024), + initialContainers(2), + maxWriteBuffers(10) + { + const enum { NAMELEN = MAX_COMPUTERNAME_LENGTH + 1 }; + TCHAR myName[NAMELEN]; + DWORD myNameLen = NAMELEN; + GetComputerName(myName, &myNameLen); + connectString = "Data Source="; + connectString += myName; + connectString += "\\SQLEXPRESS;Integrated Security=SSPI"; + addOptions() + ("connect", + qpid::optValue(connectString, "STRING"), + "Connection string for the database to use. Will prepend " + "Provider=SQLOLEDB;") + ("catalog", + qpid::optValue(catalogName, "DB NAME"), + "Catalog (database) name") + ("store-dir", + qpid::optValue(storeDir, "DIR"), + "Location to store message and transaction data " + "(default uses data-dir if available)") + ("container-size", + qpid::optValue(containerSize, "VALUE"), + "Bytes per container; min 512K. Only used when creating " + "a new log") + ("initial-containers", + qpid::optValue(initialContainers, "VALUE"), + "Number of containers to add if creating a new log") + ("max-write-buffers", + qpid::optValue(maxWriteBuffers, "VALUE"), + "Maximum write buffers outstanding before log is flushed " + "(0 means no limit)") + ; + } + }; + ProviderOptions options; + std::string brokerDataDir; + Messages messages; + // TransactionLog requires itself to have a shared_ptr reference to start. + TransactionLog::shared_ptr transactions; + + // Each thread has a separate connection to the database and also needs + // to manage its COM initialize/finalize individually. This is done by + // keeping a thread-specific State. + boost::thread_specific_ptr<State> dbState; + + State *initState(); + DatabaseConnection *initConnection(void); + void createDb(DatabaseConnection *db, const std::string &name); + void createLogs(); +}; + +static MSSqlClfsProvider static_instance_registers_plugin; + +void +MSSqlClfsProvider::finalizeMe() +{ + dbState.reset(); +} + +MSSqlClfsProvider::MSSqlClfsProvider() + : options("MS SQL/CLFS Provider options") +{ + transactions = boost::make_shared<TransactionLog>(); +} + +MSSqlClfsProvider::~MSSqlClfsProvider() +{ +} + +void +MSSqlClfsProvider::earlyInitialize(Plugin::Target &target) +{ + MessageStorePlugin *store = dynamic_cast<MessageStorePlugin *>(&target); + if (store) { + // Check the store dir option; if not specified, need to + // grab the broker's data dir. + if (options.storeDir.empty()) { + DataDir& dir = store->getBroker()->getDataDir(); + if (dir.isEnabled()) { + options.storeDir = dir.getPath(); + } + else { + QPID_LOG(error, + "MSSQL-CLFS: --store-dir required if --no-data-dir specified"); + return; + } + } + + // If CLFS is not available on this system, give up now. + try { + Log::TuningParameters params; + params.containerSize = options.containerSize; + params.containers = options.initialContainers; + params.shrinkPct = 50; + params.maxWriteBuffers = options.maxWriteBuffers; + std::string msgPath = options.storeDir + "\\" + "messages"; + messages.openLog(msgPath, params); + std::string transPath = options.storeDir + "\\" + "transactions"; + transactions->open(transPath, params); + } + catch (std::exception &e) { + QPID_LOG(error, e.what()); + return; + } + + // If the database init fails, report it and don't register; give + // the rest of the broker a chance to run. + // + // Don't try to initConnection() since that will fail if the + // database doesn't exist. Instead, try to open a connection without + // a database name, then search for the database. There's still a + // chance this provider won't be selected for the store too, so be + // be sure to close the database connection before return to avoid + // leaving a connection up that will not be used. + try { + initState(); // This initializes COM + std::auto_ptr<DatabaseConnection> db(new DatabaseConnection()); + db->open(options.connectString, ""); + _ConnectionPtr conn(*db); + _RecordsetPtr pCatalogs = NULL; + VariantHelper<std::string> catalogName(options.catalogName); + pCatalogs = conn->OpenSchema(adSchemaCatalogs, catalogName); + if (pCatalogs->EndOfFile) { + // Database doesn't exist; create it + QPID_LOG(notice, + "MSSQL-CLFS: Creating database " + options.catalogName); + createDb(db.get(), options.catalogName); + } + else { + QPID_LOG(notice, + "MSSQL-CLFS: Database located: " + options.catalogName); + } + if (pCatalogs) { + if (pCatalogs->State == adStateOpen) + pCatalogs->Close(); + pCatalogs = 0; + } + db->close(); + store->providerAvailable("MSSQL-CLFS", this); + } + catch (qpid::Exception &e) { + QPID_LOG(error, e.what()); + return; + } + store->addFinalizer(boost::bind(&MSSqlClfsProvider::finalizeMe, this)); + } +} + +void +MSSqlClfsProvider::initialize(Plugin::Target& target) +{ +} + +void +MSSqlClfsProvider::activate(MessageStorePlugin &store) +{ + QPID_LOG(info, "MS SQL/CLFS Provider is up"); +} + +void +MSSqlClfsProvider::truncateInit(const bool pushDownStoreFiles) +{ +} + +void +MSSqlClfsProvider::create(PersistableQueue& queue, + const qpid::framing::FieldTable& /*args needed for jrnl*/) +{ + DatabaseConnection *db = initConnection(); + BlobRecordset rsQueues; + try { + db->beginTransaction(); + rsQueues.open(db, TblQueue); + rsQueues.add(queue); + db->commitTransaction(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + db->rollbackTransaction(); + throw ADOException("Error creating queue " + queue.getName(), e, errs); + } + catch(std::exception& e) { + db->rollbackTransaction(); + THROW_STORE_EXCEPTION(e.what()); + } +} + +/** + * Destroy a durable queue + */ +void +MSSqlClfsProvider::destroy(PersistableQueue& queue) +{ + DatabaseConnection *db = initConnection(); + BlobRecordset rsQueues; + BindingRecordset rsBindings; + try { + db->beginTransaction(); + rsQueues.open(db, TblQueue); + rsBindings.open(db, TblBinding); + // Remove bindings first; the queue IDs can't be ripped out from + // under the references in the bindings table. + rsBindings.removeForQueue(queue.getPersistenceId()); + rsQueues.remove(queue); + db->commitTransaction(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + db->rollbackTransaction(); + throw ADOException("Error deleting queue " + queue.getName(), e, errs); + } + + /* + * Now that the SQL stuff has recorded the queue deletion, expunge + * all record of the queue from the messages set. Any errors logging + * these removals are swallowed because during a recovery the queue + * Id won't be present (the SQL stuff already committed) so any references + * to it in message operations will be removed. + */ + messages.expunge(queue.getPersistenceId()); +} + +/** + * Record the existence of a durable exchange + */ +void +MSSqlClfsProvider::create(const PersistableExchange& exchange, + const qpid::framing::FieldTable& args) +{ + DatabaseConnection *db = initConnection(); + BlobRecordset rsExchanges; + try { + db->beginTransaction(); + rsExchanges.open(db, TblExchange); + rsExchanges.add(exchange); + db->commitTransaction(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + db->rollbackTransaction(); + throw ADOException("Error creating exchange " + exchange.getName(), + e, + errs); + } +} + +/** + * Destroy a durable exchange + */ +void +MSSqlClfsProvider::destroy(const PersistableExchange& exchange) +{ + DatabaseConnection *db = initConnection(); + BlobRecordset rsExchanges; + BindingRecordset rsBindings; + try { + db->beginTransaction(); + rsExchanges.open(db, TblExchange); + rsBindings.open(db, TblBinding); + // Remove bindings first; the exchange IDs can't be ripped out from + // under the references in the bindings table. + rsBindings.removeForExchange(exchange.getPersistenceId()); + rsExchanges.remove(exchange); + db->commitTransaction(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + db->rollbackTransaction(); + throw ADOException("Error deleting exchange " + exchange.getName(), + e, + errs); + } +} + +/** + * Record a binding + */ +void +MSSqlClfsProvider::bind(const PersistableExchange& exchange, + const PersistableQueue& queue, + const std::string& key, + const qpid::framing::FieldTable& args) +{ + DatabaseConnection *db = initConnection(); + BindingRecordset rsBindings; + try { + db->beginTransaction(); + rsBindings.open(db, TblBinding); + rsBindings.add(exchange.getPersistenceId(), + queue.getPersistenceId(), + key, + args); + db->commitTransaction(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + db->rollbackTransaction(); + throw ADOException("Error binding exchange " + exchange.getName() + + " to queue " + queue.getName(), + e, + errs); + } +} + +/** + * Forget a binding + */ +void +MSSqlClfsProvider::unbind(const PersistableExchange& exchange, + const PersistableQueue& queue, + const std::string& key, + const qpid::framing::FieldTable& args) +{ + DatabaseConnection *db = initConnection(); + BindingRecordset rsBindings; + try { + db->beginTransaction(); + rsBindings.open(db, TblBinding); + rsBindings.remove(exchange.getPersistenceId(), + queue.getPersistenceId(), + key, + args); + db->commitTransaction(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + db->rollbackTransaction(); + throw ADOException("Error unbinding exchange " + exchange.getName() + + " from queue " + queue.getName(), + e, + errs); + } +} + +/** + * Record generic durable configuration + */ +void +MSSqlClfsProvider::create(const PersistableConfig& config) +{ + DatabaseConnection *db = initConnection(); + BlobRecordset rsConfigs; + try { + db->beginTransaction(); + rsConfigs.open(db, TblConfig); + rsConfigs.add(config); + db->commitTransaction(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + db->rollbackTransaction(); + throw ADOException("Error creating config " + config.getName(), e, errs); + } +} + +/** + * Destroy generic durable configuration + */ +void +MSSqlClfsProvider::destroy(const PersistableConfig& config) +{ + DatabaseConnection *db = initConnection(); + BlobRecordset rsConfigs; + try { + db->beginTransaction(); + rsConfigs.open(db, TblConfig); + rsConfigs.remove(config); + db->commitTransaction(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + db->rollbackTransaction(); + throw ADOException("Error deleting config " + config.getName(), e, errs); + } +} + +/** + * Stores a messages before it has been enqueued + * (enqueueing automatically stores the message so this is + * only required if storage is required prior to that + * point). If the message has not yet been stored it will + * store the headers as well as any content passed in. A + * persistence id will be set on the message which can be + * used to load the content or to append to it. + */ +void +MSSqlClfsProvider::stage(const boost::intrusive_ptr<PersistableMessage>& msg) +{ +#if 0 + DatabaseConnection *db = initConnection(); + MessageRecordset rsMessages; + try { + db->beginTransaction(); + rsMessages.open(db, TblMessage); + rsMessages.add(msg); + db->commitTransaction(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + db->rollbackTransaction(); + throw ADOException("Error staging message", e, errs); + } +#endif +} + +/** + * Destroys a previously staged message. This only needs + * to be called if the message is never enqueued. (Once + * enqueued, deletion will be automatic when the message + * is dequeued from all queues it was enqueued onto). + */ +void +MSSqlClfsProvider::destroy(PersistableMessage& msg) +{ +#if 0 + DatabaseConnection *db = initConnection(); + BlobRecordset rsMessages; + try { + db->beginTransaction(); + rsMessages.open(db, TblMessage); + rsMessages.remove(msg); + db->commitTransaction(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + db->rollbackTransaction(); + throw ADOException("Error deleting message", e, errs); + } +#endif +} + +/** + * Appends content to a previously staged message + */ +void +MSSqlClfsProvider::appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg, + const std::string& data) +{ +#if 0 + DatabaseConnection *db = initConnection(); + MessageRecordset rsMessages; + try { + db->beginTransaction(); + rsMessages.open(db, TblMessage); + rsMessages.append(msg, data); + db->commitTransaction(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + db->rollbackTransaction(); + throw ADOException("Error appending to message", e, errs); + } +#endif +} + +/** + * Loads (a section) of content data for the specified + * message (previously stored through a call to stage or + * enqueue) into data. The offset refers to the content + * only (i.e. an offset of 0 implies that the start of the + * content should be loaded, not the headers or related + * meta-data). + */ +void +MSSqlClfsProvider::loadContent(const qpid::broker::PersistableQueue& /*queue*/, + const boost::intrusive_ptr<const PersistableMessage>& msg, + std::string& data, + uint64_t offset, + uint32_t length) +{ + // Message log keeps all messages in one log, so we don't need the + // queue reference. + messages.loadContent(msg->getPersistenceId(), data, offset, length); +} + +/** + * Enqueues a message, storing the message if it has not + * been previously stored and recording that the given + * message is on the given queue. + * + * @param ctxt The transaction context under which this enqueue happens. + * @param msg The message to enqueue + * @param queue the name of the queue onto which it is to be enqueued + */ +void +MSSqlClfsProvider::enqueue(qpid::broker::TransactionContext* ctxt, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue) +{ + Transaction::shared_ptr t; + TransactionContext *ctx = dynamic_cast<TransactionContext*>(ctxt); + if (ctx) + t = ctx->getTransaction(); + else { + TPCTransactionContext *tctx; + tctx = dynamic_cast<TPCTransactionContext*>(ctxt); + if (tctx) + t = tctx->getTransaction(); + } + uint64_t msgId = msg->getPersistenceId(); + if (msgId == 0) { + messages.add(msg); + msgId = msg->getPersistenceId(); + } + messages.enqueue(msgId, queue.getPersistenceId(), t); + msg->enqueueComplete(); +} + +/** + * Dequeues a message, recording that the given message is + * no longer on the given queue and deleting the message + * if it is no longer on any other queue. + * + * @param ctxt The transaction context under which this dequeue happens. + * @param msg The message to dequeue + * @param queue The queue from which it is to be dequeued + */ +void +MSSqlClfsProvider::dequeue(qpid::broker::TransactionContext* ctxt, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue) +{ + Transaction::shared_ptr t; + TransactionContext *ctx = dynamic_cast<TransactionContext*>(ctxt); + if (ctx) + t = ctx->getTransaction(); + else { + TPCTransactionContext *tctx; + tctx = dynamic_cast<TPCTransactionContext*>(ctxt); + if (tctx) + t = tctx->getTransaction(); + } + messages.dequeue(msg->getPersistenceId(), queue.getPersistenceId(), t); + msg->dequeueComplete(); +} + +std::auto_ptr<qpid::broker::TransactionContext> +MSSqlClfsProvider::begin() +{ + Transaction::shared_ptr t = transactions->begin(); + std::auto_ptr<qpid::broker::TransactionContext> tc(new TransactionContext(t)); + return tc; +} + +std::auto_ptr<qpid::broker::TPCTransactionContext> +MSSqlClfsProvider::begin(const std::string& xid) +{ + TPCTransaction::shared_ptr t = transactions->begin(xid); + std::auto_ptr<qpid::broker::TPCTransactionContext> tc(new TPCTransactionContext(t)); + return tc; +} + +void +MSSqlClfsProvider::prepare(qpid::broker::TPCTransactionContext& txn) +{ + TPCTransactionContext *ctx = dynamic_cast<TPCTransactionContext*> (&txn); + if (ctx == 0) + throw qpid::broker::InvalidTransactionContextException(); + ctx->getTransaction()->prepare(); +} + +void +MSSqlClfsProvider::commit(qpid::broker::TransactionContext& txn) +{ + Transaction::shared_ptr t; + TransactionContext *ctx = dynamic_cast<TransactionContext*>(&txn); + if (ctx) + t = ctx->getTransaction(); + else { + TPCTransactionContext *tctx; + tctx = dynamic_cast<TPCTransactionContext*>(&txn); + if (tctx == 0) + throw qpid::broker::InvalidTransactionContextException(); + t = tctx->getTransaction(); + } + t->commit(messages); +} + +void +MSSqlClfsProvider::abort(qpid::broker::TransactionContext& txn) +{ + Transaction::shared_ptr t; + TransactionContext *ctx = dynamic_cast<TransactionContext*>(&txn); + if (ctx) + t = ctx->getTransaction(); + else { + TPCTransactionContext *tctx; + tctx = dynamic_cast<TPCTransactionContext*>(&txn); + if (tctx == 0) + throw qpid::broker::InvalidTransactionContextException(); + t = tctx->getTransaction(); + } + t->abort(messages); +} + +void +MSSqlClfsProvider::collectPreparedXids(std::set<std::string>& xids) +{ + std::map<std::string, TPCTransaction::shared_ptr> preparedMap; + transactions->collectPreparedXids(preparedMap); + std::map<std::string, TPCTransaction::shared_ptr>::const_iterator i; + for (i = preparedMap.begin(); i != preparedMap.end(); ++i) { + xids.insert(i->first); + } +} + +// @TODO Much of this recovery code is way too similar... refactor to +// a recover template method on BlobRecordset. + +void +MSSqlClfsProvider::recoverConfigs(qpid::broker::RecoveryManager& recoverer) +{ + DatabaseConnection *db = initConnection(); + BlobRecordset rsConfigs; + rsConfigs.open(db, TblConfig); + _RecordsetPtr p = (_RecordsetPtr)rsConfigs; + if (p->BOF && p->EndOfFile) + return; // Nothing to do + p->MoveFirst(); + while (!p->EndOfFile) { + uint64_t id = p->Fields->Item["persistenceId"]->Value; + long blobSize = p->Fields->Item["fieldTableBlob"]->ActualSize; + BlobAdapter blob(blobSize); + blob = p->Fields->Item["fieldTableBlob"]->GetChunk(blobSize); + // Recreate the Config instance and reset its ID. + broker::RecoverableConfig::shared_ptr config = + recoverer.recoverConfig(blob); + config->setPersistenceId(id); + p->MoveNext(); + } +} + +void +MSSqlClfsProvider::recoverExchanges(qpid::broker::RecoveryManager& recoverer, + ExchangeMap& exchangeMap) +{ + DatabaseConnection *db = initConnection(); + BlobRecordset rsExchanges; + rsExchanges.open(db, TblExchange); + _RecordsetPtr p = (_RecordsetPtr)rsExchanges; + if (p->BOF && p->EndOfFile) + return; // Nothing to do + p->MoveFirst(); + while (!p->EndOfFile) { + uint64_t id = p->Fields->Item["persistenceId"]->Value; + long blobSize = p->Fields->Item["fieldTableBlob"]->ActualSize; + BlobAdapter blob(blobSize); + blob = p->Fields->Item["fieldTableBlob"]->GetChunk(blobSize); + // Recreate the Exchange instance, reset its ID, and remember the + // ones restored for matching up when recovering bindings. + broker::RecoverableExchange::shared_ptr exchange = + recoverer.recoverExchange(blob); + exchange->setPersistenceId(id); + exchangeMap[id] = exchange; + p->MoveNext(); + } +} + +void +MSSqlClfsProvider::recoverQueues(qpid::broker::RecoveryManager& recoverer, + QueueMap& queueMap) +{ + DatabaseConnection *db = initConnection(); + BlobRecordset rsQueues; + rsQueues.open(db, TblQueue); + _RecordsetPtr p = (_RecordsetPtr)rsQueues; + if (p->BOF && p->EndOfFile) + return; // Nothing to do + p->MoveFirst(); + while (!p->EndOfFile) { + uint64_t id = p->Fields->Item["persistenceId"]->Value; + long blobSize = p->Fields->Item["fieldTableBlob"]->ActualSize; + BlobAdapter blob(blobSize); + blob = p->Fields->Item["fieldTableBlob"]->GetChunk(blobSize); + // Recreate the Queue instance and reset its ID. + broker::RecoverableQueue::shared_ptr queue = + recoverer.recoverQueue(blob); + queue->setPersistenceId(id); + queueMap[id] = queue; + p->MoveNext(); + } +} + +void +MSSqlClfsProvider::recoverBindings(qpid::broker::RecoveryManager& recoverer, + const ExchangeMap& exchangeMap, + const QueueMap& queueMap) +{ + DatabaseConnection *db = initConnection(); + BindingRecordset rsBindings; + rsBindings.open(db, TblBinding); + rsBindings.recover(recoverer, exchangeMap, queueMap); +} + +void +MSSqlClfsProvider::recoverMessages(qpid::broker::RecoveryManager& recoverer, + MessageMap& messageMap, + MessageQueueMap& messageQueueMap) +{ + // Read the list of valid queue Ids to ensure that no broken msg->queue + // refs get restored. + DatabaseConnection *db = initConnection(); + BlobRecordset rsQueues; + rsQueues.open(db, TblQueue); + _RecordsetPtr p = (_RecordsetPtr)rsQueues; + std::set<uint64_t> validQueues; + if (!(p->BOF && p->EndOfFile)) { + p->MoveFirst(); + while (!p->EndOfFile) { + uint64_t id = p->Fields->Item["persistenceId"]->Value; + validQueues.insert(id); + p->MoveNext(); + } + } + std::map<uint64_t, Transaction::shared_ptr> transMap; + transactions->recover(transMap); + messages.recover(recoverer, + validQueues, + transMap, + messageMap, + messageQueueMap); +} + +void +MSSqlClfsProvider::recoverTransactions(qpid::broker::RecoveryManager& recoverer, + PreparedTransactionMap& dtxMap) +{ + std::map<std::string, TPCTransaction::shared_ptr> preparedMap; + transactions->collectPreparedXids(preparedMap); + std::map<std::string, TPCTransaction::shared_ptr>::const_iterator i; + for (i = preparedMap.begin(); i != preparedMap.end(); ++i) { + std::auto_ptr<TPCTransactionContext> ctx(new TPCTransactionContext(i->second)); + std::auto_ptr<qpid::broker::TPCTransactionContext> brokerCtx(ctx); + dtxMap[i->first] = recoverer.recoverTransaction(i->first, brokerCtx); + } +} + +////////////// Internal Methods + +State * +MSSqlClfsProvider::initState() +{ + State *state = dbState.get(); // See if thread has initialized + if (!state) { + state = new State; + dbState.reset(state); + } + return state; +} + +DatabaseConnection * +MSSqlClfsProvider::initConnection(void) +{ + State *state = initState(); + if (state->dbConn != 0) + return state->dbConn; // And the DatabaseConnection is set up too + std::auto_ptr<DatabaseConnection> db(new DatabaseConnection); + db->open(options.connectString, options.catalogName); + state->dbConn = db.release(); + return state->dbConn; +} + +void +MSSqlClfsProvider::createDb(DatabaseConnection *db, const std::string &name) +{ + const std::string dbCmd = "CREATE DATABASE " + name; + const std::string useCmd = "USE " + name; + const std::string tableCmd = "CREATE TABLE "; + const std::string colSpecs = + " (persistenceId bigint PRIMARY KEY NOT NULL IDENTITY(1,1)," + " fieldTableBlob varbinary(MAX) NOT NULL)"; + const std::string bindingSpecs = + " (exchangeId bigint REFERENCES tblExchange(persistenceId) NOT NULL," + " queueId bigint REFERENCES tblQueue(persistenceId) NOT NULL," + " routingKey varchar(255)," + " fieldTableBlob varbinary(MAX))"; + + _variant_t unused; + _bstr_t dbStr = dbCmd.c_str(); + _ConnectionPtr conn(*db); + try { + conn->Execute(dbStr, &unused, adExecuteNoRecords); + _bstr_t useStr = useCmd.c_str(); + conn->Execute(useStr, &unused, adExecuteNoRecords); + std::string makeTable = tableCmd + TblQueue + colSpecs; + _bstr_t makeTableStr = makeTable.c_str(); + conn->Execute(makeTableStr, &unused, adExecuteNoRecords); + makeTable = tableCmd + TblExchange + colSpecs; + makeTableStr = makeTable.c_str(); + conn->Execute(makeTableStr, &unused, adExecuteNoRecords); + makeTable = tableCmd + TblConfig + colSpecs; + makeTableStr = makeTable.c_str(); + conn->Execute(makeTableStr, &unused, adExecuteNoRecords); + makeTable = tableCmd + TblBinding + bindingSpecs; + makeTableStr = makeTable.c_str(); + conn->Execute(makeTableStr, &unused, adExecuteNoRecords); + } + catch(_com_error &e) { + throw ADOException("MSSQL can't create " + name, e, db->getErrors()); + } +} + +void +MSSqlClfsProvider::dump() +{ + // dump all db records to qpid_log + QPID_LOG(notice, "DB Dump: (not dumping anything)"); + // rsQueues.dump(); +} + + +}}} // namespace qpid::store::ms_sql diff --git a/qpid/cpp/src/qpid/store/ms-clfs/MessageLog.cpp b/qpid/cpp/src/qpid/store/ms-clfs/MessageLog.cpp new file mode 100644 index 0000000000..14d63a4cd4 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-clfs/MessageLog.cpp @@ -0,0 +1,406 @@ +/* + * + * 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 <windows.h> +#include <clfsw32.h> +#include <exception> +#include <malloc.h> +#include <memory.h> +#include <qpid/framing/Buffer.h> +#include <qpid/log/Statement.h> +#include <qpid/sys/IntegerTypes.h> +#include <qpid/sys/windows/check.h> + +#include "MessageLog.h" +#include "Lsn.h" + +namespace { + +// Structures that hold log records. Each has a type field at the start. +enum MessageEntryType { + MessageStartEntry = 1, + MessageChunkEntry = 2, + MessageDeleteEntry = 3, + MessageEnqueueEntry = 4, + MessageDequeueEntry = 5 +}; +static const uint32_t MaxMessageContentLength = 64 * 1024; + +// Message-Start +struct MessageStart { + MessageEntryType type; + // If the complete message encoding doesn't fit, remainder is in + // MessageChunk records to follow. + // headerLength is the size of the message's header in content. It is + // part of the totalLength and the segmentLength. + uint32_t headerLength; + uint32_t totalLength; + uint32_t segmentLength; + char content[MaxMessageContentLength]; + + MessageStart() + : type(MessageStartEntry), + headerLength(0), + totalLength(0), + segmentLength(0) {} +}; +// Message-Chunk +struct MessageChunk { + MessageEntryType type; + uint32_t segmentLength; + char content[MaxMessageContentLength]; + + MessageChunk() : type(MessageChunkEntry), segmentLength(0) {} +}; +// Message-Delete +struct MessageDelete { + MessageEntryType type; + + MessageDelete() : type(MessageDeleteEntry) {} +}; +// Message-Enqueue +struct MessageEnqueue { + MessageEntryType type; + uint64_t queueId; + uint64_t transId; + + MessageEnqueue(uint64_t qId = 0, uint64_t tId = 0) + : type(MessageEnqueueEntry), queueId(qId), transId(tId) {} +}; +// Message-Dequeue +struct MessageDequeue { + MessageEntryType type; + uint64_t queueId; + uint64_t transId; + + MessageDequeue(uint64_t qId = 0, uint64_t tId = 0) + : type(MessageDequeueEntry), queueId(qId), transId(tId) {} +}; + +} // namespace + +namespace qpid { +namespace store { +namespace ms_clfs { + +void +MessageLog::initialize() +{ + // Write something to occupy the first record, preventing a real message + // from being lsn/id 0. Delete of a non-existant id is easily tossed + // during recovery if no other messages have caused the tail to be moved + // up past this dummy record by then. + deleteMessage(0, 0); +} + +uint32_t +MessageLog::marshallingBufferSize() +{ + size_t biggestNeed = std::max(sizeof(MessageStart), sizeof(MessageEnqueue)); + uint32_t defSize = static_cast<uint32_t>(biggestNeed); + uint32_t minSize = Log::marshallingBufferSize(); + if (defSize <= minSize) + return minSize; + // Round up to multiple of minSize + return (defSize + minSize) / minSize * minSize; +} + +uint64_t +MessageLog::add(const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg) +{ + // The message may be too long to fit in one record; if so, write + // Message-Chunk records to contain the rest. If it does all fit in one + // record, though, optimize the encoding by going straight to the + // Message-Start record rather than encoding then copying to the record. + // In all case + MessageStart entry; + uint32_t encodedMessageLength = msg->encodedSize(); + entry.headerLength = msg->encodedHeaderSize(); + entry.totalLength = encodedMessageLength; + CLFS_LSN location, lastChunkLsn; + std::auto_ptr<char> encodeStage; + char *encodeBuff = 0; + bool oneRecord = encodedMessageLength <= MaxMessageContentLength; + if (oneRecord) { + encodeBuff = entry.content; + entry.segmentLength = encodedMessageLength; + } + else { + encodeStage.reset(new char[encodedMessageLength]); + encodeBuff = encodeStage.get(); + entry.segmentLength = MaxMessageContentLength; + } + qpid::framing::Buffer buff(encodeBuff, encodedMessageLength); + msg->encode(buff); + if (!oneRecord) + memcpy_s(entry.content, sizeof(entry.content), + encodeBuff, entry.segmentLength); + uint32_t entryLength = static_cast<uint32_t>(sizeof(entry)); + entryLength -= (MaxMessageContentLength - entry.segmentLength); + location = write(&entry, entryLength); + // Write any Message-Chunk records before setting the message's id. + uint32_t sent = entry.segmentLength; + uint32_t remaining = encodedMessageLength - entry.segmentLength; + while (remaining > 0) { + MessageChunk chunk; + chunk.segmentLength = std::max(MaxMessageContentLength, remaining); + memcpy_s(chunk.content, sizeof(chunk.content), + encodeStage.get() + sent, chunk.segmentLength); + entryLength = static_cast<uint32_t>(sizeof(chunk)); + entryLength -= (MaxMessageContentLength - chunk.segmentLength); + lastChunkLsn = write(&chunk, entryLength, &location); + sent += chunk.segmentLength; + remaining -= chunk.segmentLength; + } + return lsnToId(location); +} + +void +MessageLog::deleteMessage(uint64_t messageId, uint64_t newFirstId) +{ + MessageDelete deleteEntry; + CLFS_LSN msgLsn = idToLsn(messageId); + write(&deleteEntry, sizeof(deleteEntry), &msgLsn); + if (newFirstId != 0) + moveTail(idToLsn(newFirstId)); +} + +// Load part or all of a message's content from previously stored +// log record(s). +void +MessageLog::loadContent(uint64_t messageId, + std::string& data, + uint64_t offset, + uint32_t length) +{ +} + +void +MessageLog::recordEnqueue (uint64_t messageId, + uint64_t queueId, + uint64_t transactionId) +{ + MessageEnqueue entry(queueId, transactionId); + CLFS_LSN msgLsn = idToLsn(messageId); + write(&entry, sizeof(entry), &msgLsn); +} + +void +MessageLog::recordDequeue (uint64_t messageId, + uint64_t queueId, + uint64_t transactionId) +{ + MessageDequeue entry(queueId, transactionId); + CLFS_LSN msgLsn = idToLsn(messageId); + write(&entry, sizeof(entry), &msgLsn); +} + +void +MessageLog::recover(qpid::broker::RecoveryManager& recoverer, + qpid::store::MessageMap& messageMap, + std::map<uint64_t, std::vector<RecoveredMsgOp> >& messageOps) +{ + // If context and content needs to be saved while reassembling messages + // split across log records, save the info and reassembly buffer. + struct MessageBlocks { + uint32_t totalLength; + uint32_t soFarLength; + boost::shared_ptr<char> content; + + MessageBlocks() : totalLength(0), soFarLength(0), content((char*)0) {} + }; + std::map<uint64_t, MessageBlocks> reassemblies; + std::map<uint64_t, MessageBlocks>::iterator at; + + QPID_LOG(debug, "Recovering message log"); + + // Note that there may be message refs in the log which are deleted, so + // be sure to only add msgs at message-start record, and ignore those + // that don't have an existing message record. + // Get the base LSN - that's how to say "start reading at the beginning" + CLFS_INFORMATION info; + ULONG infoLength = sizeof (info); + BOOL ok = ::GetLogFileInformation(handle, &info, &infoLength); + QPID_WINDOWS_CHECK_NOT(ok, 0); + + // Pointers for the various record types that can be assigned in the + // reading loop below. + MessageStart *start; + MessageChunk *chunk; + MessageEnqueue *enqueue; + MessageDequeue *dequeue; + + qpid::store::MessageMap::iterator messageMapSpot; + qpid::store::MessageQueueMap::iterator queueMapSpot; + PVOID recordPointer; + ULONG recordLength; + CLFS_RECORD_TYPE recordType = ClfsDataRecord; + CLFS_LSN messageLsn, current, undoNext; + PVOID readContext; + uint64_t msgId; + // Note 'current' in case it's needed below; ReadNextLogRecord returns it + // via a parameter. + current = info.BaseLsn; + ok = ::ReadLogRecord(marshal, + &info.BaseLsn, + ClfsContextForward, + &recordPointer, + &recordLength, + &recordType, + &undoNext, + &messageLsn, + &readContext, + 0); + while (ok) { + // All the record types this class writes have a MessageEntryType in the + // beginning. Based on that, do what's needed. + MessageEntryType *t = + reinterpret_cast<MessageEntryType *>(recordPointer); + switch(*t) { + case MessageStartEntry: + start = reinterpret_cast<MessageStart *>(recordPointer); + msgId = lsnToId(current); + QPID_LOG(debug, "Message Start, id " << msgId); + // If the message content is split across multiple log records, save + // this content off to the side until the remaining record(s) are + // located. + if (start->totalLength == start->segmentLength) { // Whole thing + // Start by recovering the header then see if the rest of + // the content is desired. + qpid::framing::Buffer buff(start->content, start->headerLength); + qpid::broker::RecoverableMessage::shared_ptr m = + recoverer.recoverMessage(buff); + m->setPersistenceId(msgId); + messageMap[msgId] = m; + uint32_t contentLength = + start->totalLength - start->headerLength; + if (m->loadContent(contentLength)) { + qpid::framing::Buffer content(&(start->content[start->headerLength]), + contentLength); + m->decodeContent(content); + } + } + else { + // Save it in a block big enough. + MessageBlocks b; + b.totalLength = start->totalLength; + b.soFarLength = start->segmentLength; + b.content.reset(new char[b.totalLength]); + memcpy_s(b.content.get(), b.totalLength, + start->content, start->segmentLength); + reassemblies[msgId] = b; + } + break; + case MessageChunkEntry: + chunk = reinterpret_cast<MessageChunk *>(recordPointer); + // Remember, all entries chained to MessageStart via previous. + msgId = lsnToId(messageLsn); + QPID_LOG(debug, "Message Chunk for id " << msgId); + at = reassemblies.find(msgId); + if (at == reassemblies.end()) { + QPID_LOG(debug, "Message frag for " << msgId << + " but no start; discarded"); + } + else { + MessageBlocks *b = &(at->second); + if (b->soFarLength + chunk->segmentLength > b->totalLength) + throw std::runtime_error("Invalid message chunk length"); + memcpy_s(b->content.get() + b->soFarLength, + b->totalLength - b->soFarLength, + chunk->content, + chunk->segmentLength); + b->soFarLength += chunk->segmentLength; + if (b->totalLength == b->soFarLength) { + qpid::framing::Buffer buff(b->content.get(), + b->totalLength); + qpid::broker::RecoverableMessage::shared_ptr m = + recoverer.recoverMessage(buff); + m->setPersistenceId(msgId); + messageMap[msgId] = m; + reassemblies.erase(at); + } + } + break; + case MessageDeleteEntry: + msgId = lsnToId(messageLsn); + QPID_LOG(debug, "Message Delete, id " << msgId); + messageMap.erase(msgId); + messageOps.erase(msgId); + break; + case MessageEnqueueEntry: + enqueue = reinterpret_cast<MessageEnqueue *>(recordPointer); + msgId = lsnToId(messageLsn); + QPID_LOG(debug, "Message " << msgId << " Enqueue on queue " << + enqueue->queueId << ", txn " << enqueue->transId); + if (messageMap.find(msgId) == messageMap.end()) { + QPID_LOG(debug, + "Message " << msgId << " doesn't exist; discarded"); + } + else { + std::vector<RecoveredMsgOp>& ops = messageOps[msgId]; + RecoveredMsgOp op(RECOVERED_ENQUEUE, + enqueue->queueId, + enqueue->transId); + ops.push_back(op); + } + break; + case MessageDequeueEntry: + dequeue = reinterpret_cast<MessageDequeue *>(recordPointer); + msgId = lsnToId(messageLsn); + QPID_LOG(debug, "Message " << msgId << " Dequeue from queue " << + dequeue->queueId); + if (messageMap.find(msgId) == messageMap.end()) { + QPID_LOG(debug, + "Message " << msgId << " doesn't exist; discarded"); + } + else { + std::vector<RecoveredMsgOp>& ops = messageOps[msgId]; + RecoveredMsgOp op(RECOVERED_DEQUEUE, + dequeue->queueId, + dequeue->transId); + ops.push_back(op); + } + break; + default: + throw std::runtime_error("Bad message log entry type"); + } + + recordType = ClfsDataRecord; + ok = ::ReadNextLogRecord(readContext, + &recordPointer, + &recordLength, + &recordType, + 0, // No userLsn + &undoNext, + &messageLsn, + ¤t, + 0); + } + DWORD status = ::GetLastError(); + ::TerminateReadLog(readContext); + if (status == ERROR_HANDLE_EOF) { // No more records + QPID_LOG(debug, "Message log recovered"); + return; + } + throw QPID_WINDOWS_ERROR(status); +} + +}}} // namespace qpid::store::ms_clfs diff --git a/qpid/cpp/src/qpid/store/ms-clfs/MessageLog.h b/qpid/cpp/src/qpid/store/ms-clfs/MessageLog.h new file mode 100644 index 0000000000..b3705287a6 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-clfs/MessageLog.h @@ -0,0 +1,107 @@ +#ifndef QPID_STORE_MSCLFS_MESSAGELOG_H +#define QPID_STORE_MSCLFS_MESSAGELOG_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 <boost/intrusive_ptr.hpp> +#include <qpid/broker/PersistableMessage.h> +#include <qpid/broker/RecoveryManager.h> +#include <qpid/sys/IntegerTypes.h> +#include <qpid/store/StorageProvider.h> + +#include "Log.h" + +namespace qpid { +namespace store { +namespace ms_clfs { + +/** + * @class MessageLog + * + * Represents a CLFS-housed message log. + */ +class MessageLog : public Log { + +protected: + // Message log needs to have a no-op first record written in the log + // to ensure that no real message gets an ID 0. + virtual void initialize(); + +public: + // Inherited and reimplemented from Log. Figure the minimum marshalling + // buffer size needed for the records this class writes. + virtual uint32_t marshallingBufferSize(); + + // Add the specified message to the log; Return the persistence Id. + uint64_t add(const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg); + + // Write a Delete entry for messageId. If newFirstId is not 0, it is now + // the earliest valid message in the log, so move the tail up to it. + void deleteMessage(uint64_t messageId, uint64_t newFirstId); + + // Load part or all of a message's content from previously stored + // log record(s). + void loadContent(uint64_t messageId, + std::string& data, + uint64_t offset, + uint32_t length); + + // Enqueue and dequeue operations track messages' transit across + // queues; each operation may be associated with a transaction. If + // the transactionId is 0 the operation is not associated with a + // transaction. + void recordEnqueue (uint64_t messageId, + uint64_t queueId, + uint64_t transactionId); + void recordDequeue (uint64_t messageId, + uint64_t queueId, + uint64_t transactionId); + + // Recover the messages and their queueing records from the log. + // @param recoverer Recovery manager used to recreate broker objects from + // encoded framing buffers recovered from the log. + // @param messageMap This method fills in the map of id -> ptr of + // recovered messages. + // @param messageOps This method fills in the map of msg id -> + // vector of operations involving the message that were + // recovered from the log. It is the caller's + // responsibility to sort the operations out and + // ascertain which operations should be acted on. The + // order of operations in the vector is as they were + // read in order from the log. + typedef enum { RECOVERED_ENQUEUE = 1, RECOVERED_DEQUEUE } RecoveredOpType; + struct RecoveredMsgOp { + RecoveredOpType op; + uint64_t queueId; + uint64_t txnId; + + RecoveredMsgOp(RecoveredOpType o, const uint64_t& q, const uint64_t& t) + : op(o), queueId(q), txnId(t) {} + }; + void recover(qpid::broker::RecoveryManager& recoverer, + qpid::store::MessageMap& messageMap, + std::map<uint64_t, std::vector<RecoveredMsgOp> >& messageOps); +}; + +}}} // namespace qpid::store::ms_clfs + +#endif /* QPID_STORE_MSCLFS_MESSAGELOG_H */ diff --git a/qpid/cpp/src/qpid/store/ms-clfs/Messages.cpp b/qpid/cpp/src/qpid/store/ms-clfs/Messages.cpp new file mode 100644 index 0000000000..db5d2ebf4c --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-clfs/Messages.cpp @@ -0,0 +1,472 @@ +/* + * + * 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/log/Statement.h> + +#include "Messages.h" +#include "Lsn.h" +#include "qpid/store/StoreException.h" +#include <boost/foreach.hpp> + +namespace qpid { +namespace store { +namespace ms_clfs { + +void +Messages::openLog(const std::string& path, const Log::TuningParameters& params) +{ + log.open (path, params); +} + +void +Messages::add(const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg) +{ + uint64_t id = log.add(msg); + msg->setPersistenceId(id); + std::auto_ptr<MessageInfo> autom(new MessageInfo); + MessageInfo::shared_ptr m(autom); + std::pair<uint64_t, MessageInfo::shared_ptr> p(id, m); + { + qpid::sys::ScopedWlock<qpid::sys::RWlock> l(lock); + messages.insert(p); + // If there's only this one message there, move the tail to it. + // This prevents the log from continually growing when messages + // are added and removed one at a time. + if (messages.size() == 1) { + CLFS_LSN newTail = idToLsn(id); + log.moveTail(newTail); + } + } +} + +void +Messages::enqueue(uint64_t msgId, uint64_t queueId, Transaction::shared_ptr& t) +{ + MessageInfo::shared_ptr p; + { + qpid::sys::ScopedRlock<qpid::sys::RWlock> l(lock); + MessageMap::const_iterator i = messages.find(msgId); + if (i == messages.end()) + THROW_STORE_EXCEPTION("Message does not exist"); + p = i->second; + } + MessageInfo::Location loc(queueId, t, MessageInfo::TRANSACTION_ENQUEUE); + { + qpid::sys::ScopedLock<qpid::sys::Mutex> l(p->whereLock); + p->where.push_back(loc); + uint64_t transactionId = 0; + if (t.get() != 0) { + transactionId = t->getId(); + t->enroll(msgId); + } + try { + log.recordEnqueue(msgId, queueId, transactionId); + } + catch (...) { + // Undo the record-keeping if the log wasn't written correctly. + if (transactionId != 0) + t->unenroll(msgId); + p->where.pop_back(); + throw; + } + } +} + +void +Messages::dequeue(uint64_t msgId, uint64_t queueId, Transaction::shared_ptr& t) +{ + MessageInfo::shared_ptr p; + { + qpid::sys::ScopedRlock<qpid::sys::RWlock> l(lock); + MessageMap::const_iterator i = messages.find(msgId); + if (i == messages.end()) + THROW_STORE_EXCEPTION("Message does not exist"); + p = i->second; + } + { + // Locate the 'where' entry for the specified queue. Once this operation + // is recorded in the log, update the 'where' entry to reflect it. + // Note that an existing entry in 'where' that refers to a transaction + // is not eligible for this operation. + qpid::sys::ScopedLock<qpid::sys::Mutex> l(p->whereLock); + std::list<MessageInfo::Location>::iterator i; + for (i = p->where.begin(); i != p->where.end(); ++i) { + if (i->queueId == queueId && i->transaction.get() == 0) + break; + } + if (i == p->where.end()) + THROW_STORE_EXCEPTION("Message not on queue"); + uint64_t transactionId = 0; + if (t.get() != 0) { + transactionId = t->getId(); + t->enroll(msgId); + } + try { + log.recordDequeue(msgId, queueId, transactionId); + } + catch (...) { + // Undo the record-keeping if the log wasn't written correctly. + if (transactionId != 0) + t->unenroll(msgId); + throw; + } + // Ok, logged successfully. If this is a transactional op, note + // the transaction. If non-transactional, remove the 'where' entry. + if (transactionId != 0) { + i->transaction = t; + i->disposition = MessageInfo::TRANSACTION_DEQUEUE; + } + else { + p->where.erase(i); + // If the message doesn't exist on any other queues, remove it. + if (p->where.empty()) + remove(msgId); + } + } +} + +// Commit a previous provisional enqueue or dequeue of a particular message +// actions under a specified transaction. If this results in the message's +// being removed from all queues, it is deleted. +void +Messages::commit(uint64_t msgId, Transaction::shared_ptr& t) +{ + MessageInfo::shared_ptr p; + { + qpid::sys::ScopedRlock<qpid::sys::RWlock> l(lock); + MessageMap::const_iterator i = messages.find(msgId); + if (i == messages.end()) + THROW_STORE_EXCEPTION("Message does not exist"); + p = i->second; + } + { + qpid::sys::ScopedLock<qpid::sys::Mutex> l(p->whereLock); + std::list<MessageInfo::Location>::iterator i; + for (i = p->where.begin(); i != p->where.end(); ++i) { + if (i->transaction != t) + continue; + // Transactional dequeues can now remove the item from the + // where list; enqueues just clear the transaction reference. + if (i->disposition == MessageInfo::TRANSACTION_DEQUEUE) + i = p->where.erase(i); + else + i->transaction.reset(); + } + } + // If committing results in this message having no further enqueue + // references, delete it. If the delete fails, swallow the exception + // and let recovery take care of removing it later. + if (p->where.empty()) { + try { + remove(msgId); + } + catch(...) {} + } +} + +// Abort a previous provisional enqueue or dequeue of a particular message +// actions under a specified transaction. If this results in the message's +// being removed from all queues, it is deleted. +void +Messages::abort(uint64_t msgId, Transaction::shared_ptr& t) +{ + MessageInfo::shared_ptr p; + { + qpid::sys::ScopedRlock<qpid::sys::RWlock> l(lock); + MessageMap::const_iterator i = messages.find(msgId); + if (i == messages.end()) + THROW_STORE_EXCEPTION("Message does not exist"); + p = i->second; + } + { + qpid::sys::ScopedLock<qpid::sys::Mutex> l(p->whereLock); + std::list<MessageInfo::Location>::iterator i = p->where.begin(); + while (i != p->where.end()) { + if (i->transaction != t) { + ++i; + continue; + } + // Aborted transactional dequeues result in the message remaining + // enqueued like before the operation; enqueues clear the + // message from the where list - like the enqueue never happened. + if (i->disposition == MessageInfo::TRANSACTION_ENQUEUE) + i = p->where.erase(i); + else { + i->transaction.reset(); + ++i; + } + } + } + // If aborting results in this message having no further enqueue + // references, delete it. If the delete fails, swallow the exception + // and let recovery take care of removing it later. + if (p->where.empty()) { + try { + remove(msgId); + } + catch(...) {} + } +} + +// Load part or all of a message's content from previously stored +// log record(s). +void +Messages::loadContent(uint64_t msgId, + std::string& data, + uint64_t offset, + uint32_t length) +{ + log.loadContent(msgId, data, offset, length); +} + +// Recover the current set of messages and where they're queued from +// the log. +void +Messages::recover(qpid::broker::RecoveryManager& recoverer, + const std::set<uint64_t> &validQueues, + const std::map<uint64_t, Transaction::shared_ptr>& transMap, + qpid::store::MessageMap& messageMap, + qpid::store::MessageQueueMap& messageQueueMap) +{ + std::map<uint64_t, std::vector<MessageLog::RecoveredMsgOp> > messageOps; + log.recover(recoverer, messageMap, messageOps); + // Now read through the messageOps replaying the operations with the + // knowledge of which transactions committed, aborted, etc. A transaction + // should not be deleted until there are no messages referencing it so + // a message operation with a transaction id not found in transMap is + // a serious problem. + QPID_LOG(debug, "Beginning CLFS-recovered message operation replay"); + // Keep track of any messages that are recovered from the log but don't + // have any place to be. This can happen, for example, if the broker + // crashes while logging a message deletion. After all the recovery is + // done, delete all the homeless messages. + std::vector<uint64_t> homeless; + std::map<uint64_t, std::vector<MessageLog::RecoveredMsgOp> >::const_iterator msg; + for (msg = messageOps.begin(); msg != messageOps.end(); ++msg) { + uint64_t msgId = msg->first; + const std::vector<MessageLog::RecoveredMsgOp>& ops = msg->second; + QPID_LOG(debug, "Message " << msgId << "; " << ops.size() << " op(s)"); + MessageInfo::shared_ptr m(new MessageInfo); + std::vector<QueueEntry>& entries = messageQueueMap[msgId]; + std::vector<MessageLog::RecoveredMsgOp>::const_iterator op; + for (op = ops.begin(); op != ops.end(); ++op) { + QueueEntry entry(op->queueId); + MessageInfo::Location loc(op->queueId); + std::string dir = + op->op == MessageLog::RECOVERED_ENQUEUE ? "enqueue" + : "dequeue"; + if (validQueues.find(op->queueId) == validQueues.end()) { + QPID_LOG(info, + "Message " << msgId << dir << " on non-existant queue " + << op->queueId << "; dropped"); + continue; + } + if (op->txnId != 0) { + // Be sure to enroll this message in the transaction even if + // it has committed or aborted. This ensures that the + // transaction isn't removed from the log while finalizing the + // recovery. If it were to be removed and the broker failed + // again before removing this message during normal operation, + // it couldn't be recovered again. + // + // Recall what is being reconstructed; 2 things: + // 1. This class's 'messages' list which keeps track + // of the queues each message is on and the transactions + // each message is enrolled in. For this, aborted + // transactions cause the result of the operation to be + // ignored, but the message does need to be enrolled in + // the transaction to properly maintain the transaction + // references until the message is deleted. + // 2. The StorageProvider's MessageQueueMap, which also + // has an entry for each queue each message is on and + // its TPL status and associated xid. + const Transaction::shared_ptr &t = + transMap.find(op->txnId)->second; + // Prepared transactions cause the operation to be + // provisionally acted on, and the message to be enrolled in + // the transaction for when it commits/aborts. This is + // noted in the QueueEntry for the StorageProvider's map. + if (t->getState() == Transaction::TRANS_PREPARED) { + QPID_LOG(debug, dir << " for queue " << op->queueId << + ", prepared txn " << op->txnId); + TPCTransaction::shared_ptr tpct(boost::dynamic_pointer_cast<TPCTransaction>(t)); + if (tpct.get() == 0) + THROW_STORE_EXCEPTION("Invalid transaction state"); + t->enroll(msgId); + entry.xid = tpct->getXid(); + loc.transaction = t; + if (op->op == MessageLog::RECOVERED_ENQUEUE) { + entry.tplStatus = QueueEntry::ADDING; + loc.disposition = MessageInfo::TRANSACTION_ENQUEUE; + } + else { + entry.tplStatus = QueueEntry::REMOVING; + loc.disposition = MessageInfo::TRANSACTION_DEQUEUE; + } + } + else if (t->getState() != Transaction::TRANS_COMMITTED) { + QPID_LOG(debug, dir << " for queue " << op->queueId << + ", txn " << op->txnId << ", rolling back"); + continue; + } + } + // Here for non-transactional and prepared transactional operations + // to set up the messageQueueMap entries. Note that at this point + // a committed transactional operation looks like a + // non-transactional one as far as the QueueEntry is + // concerned - just do it. If this is an entry enqueuing a + // message, just add it to the entries list. If it's a dequeue + // operation, locate the matching entry for the queue and delete + // it if the current op is non-transactional; if it's a prepared + // transaction then replace the existing entry with the current + // one that notes the message is enqueued but being removed under + // a prepared transaction. + QPID_LOG(debug, dir + " at queue " << entry.queueId); + if (op->op == MessageLog::RECOVERED_ENQUEUE) { + entries.push_back(entry); + m->where.push_back(loc); + } + else { + std::vector<QueueEntry>::iterator i = entries.begin(); + while (i != entries.end()) { + if (i->queueId == entry.queueId) { + if (entry.tplStatus != QueueEntry::NONE) + *i = entry; + else + entries.erase(i); + break; + } + ++i; + } + std::list<MessageInfo::Location>::iterator w = m->where.begin(); + while (w != m->where.end()) { + if (w->queueId == loc.queueId) { + if (loc.transaction.get() != 0) { + *w = loc; + ++w; + } + else { + w = m->where.erase(w); + } + } + } + } + } + // Now that all the queue entries have been set correctly, see if + // there are any entries; they may have all been removed during + // recovery. If there are none, add this message to the homeless + // list to be deleted from the log after the recovery is done. + if (m->where.size() == 0) { + homeless.push_back(msgId); + messageMap.erase(msgId); + messageQueueMap.erase(msgId); + } + else { + std::pair<uint64_t, MessageInfo::shared_ptr> p(msgId, m); + messages.insert(p); + } + } + + QPID_LOG(debug, "Message log recovery done."); + // Done! Ok, go back and delete all the homeless messages. + BOOST_FOREACH(uint64_t msg, homeless) { + QPID_LOG(debug, "Deleting homeless message " << msg); + remove(msg); + } +} + +// Expunge is called when a queue is deleted. All references to that +// queue must be expunged from all messages. 'Dequeue' log records are +// written for each queue entry removed, but any errors are swallowed. +// On recovery there's a list of valid queues passed in. The deleted +// queue will not be on that list so if any references to it are +// recovered they'll get weeded out then. +void +Messages::expunge(uint64_t queueId) +{ + std::vector<uint64_t> toBeDeleted; // Messages to be deleted later. + + { + // Lock everybody out since all messages are possibly in play. + // There also may be other threads already working on particular + // messages so individual message mutex still must be acquired. + qpid::sys::ScopedWlock<qpid::sys::RWlock> l(lock); + MessageMap::iterator m; + for (m = messages.begin(); m != messages.end(); ++m) { + MessageInfo::shared_ptr p = m->second; + { + qpid::sys::ScopedLock<qpid::sys::Mutex> ml(p->whereLock); + std::list<MessageInfo::Location>::iterator i = p->where.begin(); + while (i != p->where.end()) { + if (i->queueId != queueId) { + ++i; + continue; + } + // If this entry is involved in a transaction, unenroll it. + // Then remove the entry. + if (i->transaction.get() != 0) + i->transaction->unenroll(m->first); + i = p->where.erase(i); + try { + log.recordDequeue(m->first, queueId, 0); + } + catch(...) { + } + } + if (p->where.size() == 0) + toBeDeleted.push_back(m->first); + } + } + } + // Swallow any exceptions during this; don't care. Recover it later + // if needed. + try { + BOOST_FOREACH(uint64_t msg, toBeDeleted) + remove(msg); + } + catch(...) { + } +} + +// Remove a specified message from those controlled by this object. +void +Messages::remove(uint64_t messageId) +{ + uint64_t newFirstId = 0; + { + qpid::sys::ScopedWlock<qpid::sys::RWlock> l(lock); + messages.erase(messageId); + // May have deleted the first entry; if so the log can release that. + // If this message being deleted results in an empty list of + // messages, move the tail up to this message's LSN. This may + // result in one or more messages being stranded in the log + // until there's more activity. If a restart happens while these + // unneeded log records are there, the presence of the MessageDelete + // entry will cause the message(s) to be ignored anyway. + if (messages.empty()) + newFirstId = messageId; + else if (messages.begin()->first > messageId) + newFirstId = messages.begin()->first; + } + log.deleteMessage(messageId, newFirstId); +} + +}}} // namespace qpid::store::ms_clfs diff --git a/qpid/cpp/src/qpid/store/ms-clfs/Messages.h b/qpid/cpp/src/qpid/store/ms-clfs/Messages.h new file mode 100644 index 0000000000..93cc8bfe62 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-clfs/Messages.h @@ -0,0 +1,144 @@ +#ifndef QPID_STORE_MSCLFS_MESSAGES_H +#define QPID_STORE_MSCLFS_MESSAGES_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 <windows.h> +#include <map> +#include <set> +#include <vector> +#include <boost/intrusive_ptr.hpp> +#include <qpid/broker/PersistableMessage.h> +#include <qpid/sys/Mutex.h> + +#include "MessageLog.h" +#include "Transaction.h" + +namespace qpid { +namespace store { +namespace ms_clfs { + +class Messages { + + struct MessageInfo { + // How many queues this message is on, whether actually (non-transacted) + // or provisionally (included in a non-yet-committed transaction). + volatile LONG enqueuedCount; + + // Keep a list of transactional operations this message is + // referenced in. When the transaction changes/finalizes these all + // need to be acted on. + typedef enum { TRANSACTION_NONE = 0, + TRANSACTION_ENQUEUE, + TRANSACTION_DEQUEUE } TransType; +#if 0 + std::map<Transaction::shared_ptr, std::vector<TransType> > transOps; + qpid::sys::Mutex transOpsLock; +#endif + // Think what I need is a list of "where is this message" - queue id, + // transaction ref, what kind of trans op (enq/deq). Then "remove all + // queue refs" can search through all messages looking for queue ids + // and undo them. Write "remove from queue" record to log. Also need to + // add "remove from queue" to recovery. + struct Location { + uint64_t queueId; + Transaction::shared_ptr transaction; + TransType disposition; + + Location(uint64_t q) + : queueId(q), transaction(), disposition(TRANSACTION_NONE) {} + Location(uint64_t q, Transaction::shared_ptr& t, TransType d) + : queueId(q), transaction(t), disposition(d) {} + }; + qpid::sys::Mutex whereLock; + std::list<Location> where; + // The transactions vector just keeps a shared_ptr to each + // Transaction this message was involved in, regardless of the + // disposition or transaction state. Keeping a valid shared_ptr + // prevents the Transaction from being deleted. As long as there + // are any messages that referred to a transaction, that + // transaction's state needs to be known so the message disposition + // can be correctly recovered if needed. + std::vector<Transaction::shared_ptr> transactions; + + typedef boost::shared_ptr<MessageInfo> shared_ptr; + + MessageInfo() : enqueuedCount(0) {} + }; + + qpid::sys::RWlock lock; + typedef std::map<uint64_t, MessageInfo::shared_ptr> MessageMap; + MessageMap messages; + MessageLog log; + + // Remove a specified message from those controlled by this object. + void remove(uint64_t messageId); + +public: + void openLog(const std::string& path, const Log::TuningParameters& params); + + // Add the specified message to the log and list of known messages. + // Upon successful return the message's persistenceId is set. + void add(const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg); + + // Add the specified queue to the message's list of places it is + // enqueued. + void enqueue(uint64_t msgId, uint64_t queueId, Transaction::shared_ptr& t); + + // Remove the specified queue from the message's list of places it is + // enqueued. If there are no other queues holding the message, it is + // deleted. + void dequeue(uint64_t msgId, uint64_t queueId, Transaction::shared_ptr& t); + + // Commit a previous provisional enqueue or dequeue of a particular message + // actions under a specified transaction. If this results in the message's + // being removed from all queues, it is deleted. + void commit(uint64_t msgId, Transaction::shared_ptr& transaction); + + // Abort a previous provisional enqueue or dequeue of a particular message + // actions under a specified transaction. If this results in the message's + // being removed from all queues, it is deleted. + void abort(uint64_t msgId, Transaction::shared_ptr& transaction); + + // Load part or all of a message's content from previously stored + // log record(s). + void loadContent(uint64_t msgId, + std::string& data, + uint64_t offset, + uint32_t length); + + // Expunge is called when a queue is deleted. All references to that + // queue must be expunged from all messages. + void expunge(uint64_t queueId); + + // Recover the current set of messages and where they're queued from + // the log. + void recover(qpid::broker::RecoveryManager& recoverer, + const std::set<uint64_t> &validQueues, + const std::map<uint64_t, Transaction::shared_ptr>& transMap, + qpid::store::MessageMap& messageMap, + qpid::store::MessageQueueMap& messageQueueMap); +}; + +}}} // namespace qpid::store::ms_clfs + +#endif /* QPID_STORE_MSCLFS_MESSAGES_H */ diff --git a/qpid/cpp/src/qpid/store/ms-clfs/Transaction.cpp b/qpid/cpp/src/qpid/store/ms-clfs/Transaction.cpp new file mode 100644 index 0000000000..f94fef6f84 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-clfs/Transaction.cpp @@ -0,0 +1,83 @@ +/* + * + * 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 "Transaction.h" +#include "Messages.h" + +namespace qpid { +namespace store { +namespace ms_clfs { + +Transaction::~Transaction() +{ + // Transactions that are recovered then found to be deleted get destroyed + // but need not be logged. + if (state != TRANS_DELETED) + log->deleteTransaction(id); +} + +void +Transaction::enroll(uint64_t msgId) +{ + qpid::sys::ScopedWlock<qpid::sys::RWlock> l(enrollLock); + enrolledMessages.push_back(msgId); +} + +void +Transaction::unenroll(uint64_t msgId) +{ + qpid::sys::ScopedWlock<qpid::sys::RWlock> l(enrollLock); + for (std::vector<uint64_t>::iterator i = enrolledMessages.begin(); + i < enrolledMessages.end(); + ++i) { + if (*i == msgId) { + enrolledMessages.erase(i); + break; + } + } +} + +void +Transaction::abort(Messages& messages) +{ + log->recordAbort(id); + for (size_t i = 0; i < enrolledMessages.size(); ++i) + messages.abort(enrolledMessages[i], shared_from_this()); + state = TRANS_ABORTED; +} + +void +Transaction::commit(Messages& messages) +{ + log->recordCommit(id); + for (size_t i = 0; i < enrolledMessages.size(); ++i) + messages.commit(enrolledMessages[i], shared_from_this()); + state = TRANS_COMMITTED; +} + +void +TPCTransaction::prepare(void) +{ + log->recordPrepare(id); + state = TRANS_PREPARED; +} + +}}} // namespace qpid::store::ms_clfs diff --git a/qpid/cpp/src/qpid/store/ms-clfs/Transaction.h b/qpid/cpp/src/qpid/store/ms-clfs/Transaction.h new file mode 100644 index 0000000000..fd07f2fb2e --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-clfs/Transaction.h @@ -0,0 +1,146 @@ +#ifndef QPID_STORE_MSCLFS_TRANSACTION_H +#define QPID_STORE_MSCLFS_TRANSACTION_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/TransactionalStore.h> +#include <qpid/sys/Mutex.h> +#include <boost/enable_shared_from_this.hpp> +#include <boost/shared_ptr.hpp> +#include <string> + +#include "TransactionLog.h" + +namespace qpid { +namespace store { +namespace ms_clfs { + +class Messages; + +/** + * @class Transaction + * + * Class representing an AMQP transaction. This is used around a set of + * enqueue and dequeue operations that occur when the broker is acting + * on a transaction commit/abort from the client. + * This class is what the store uses internally to implement things a + * transaction needs; the broker knows about TransactionContext, which + * holds a pointer to Transaction. + * + * NOTE: All references to Transactions (and TPCTransactions, below) are + * through Boost shared_ptr instances. All messages enrolled in a transaction + * hold a shared_ptr. Thus, a Transaction object will not be deleted until all + * messages holding a reference to it are deleted. This fact is also used + * during recovery to automatically clean up and delete any Transaction without + * messages left referring to it. + */ +class Transaction : public boost::enable_shared_from_this<Transaction> { +private: + // TransactionLog has to create all Transaction instances. + Transaction() {} + +public: + + typedef boost::shared_ptr<Transaction> shared_ptr; + typedef enum { TRANS_OPEN = 1, + TRANS_PREPARED, + TRANS_ABORTED, + TRANS_COMMITTED, + TRANS_DELETED } State; + + virtual ~Transaction(); + + uint64_t getId() { return id; } + State getState() { return state; } + + void enroll(uint64_t msgId); + void unenroll(uint64_t msgId); // For failed ops, not normal end-of-trans + + void abort(Messages& messages); + void commit(Messages& messages); + +protected: + friend class TransactionLog; + Transaction(uint64_t _id, const TransactionLog::shared_ptr& _log) + : id(_id), state(TRANS_OPEN), log(_log) {} + + uint64_t id; + State state; + TransactionLog::shared_ptr log; + std::vector<uint64_t> enrolledMessages; + qpid::sys::RWlock enrollLock; +}; + +class TransactionContext : public qpid::broker::TransactionContext { + Transaction::shared_ptr transaction; + +public: + TransactionContext(const Transaction::shared_ptr& _transaction) + : transaction(_transaction) {} + + virtual Transaction::shared_ptr& getTransaction() { return transaction; } +}; + +/** + * @class TPCTransaction + * + * Class representing a Two-Phase-Commit (TPC) AMQP transaction. This is + * used around a set of enqueue and dequeue operations that occur when the + * broker is acting on a transaction prepare/commit/abort from the client. + * This class is what the store uses internally to implement things a + * transaction needs; the broker knows about TPCTransactionContext, which + * holds a pointer to TPCTransaction. + */ +class TPCTransaction : public Transaction { + + friend class TransactionLog; + TPCTransaction(uint64_t _id, + const TransactionLog::shared_ptr& _log, + const std::string& _xid) + : Transaction(_id, _log), xid(_xid) {} + + std::string xid; + +public: + typedef boost::shared_ptr<TPCTransaction> shared_ptr; + + virtual ~TPCTransaction() {} + + void prepare(); + bool isPrepared() const { return state == TRANS_PREPARED; } + + const std::string& getXid(void) const { return xid; } +}; + +class TPCTransactionContext : public qpid::broker::TPCTransactionContext { + TPCTransaction::shared_ptr transaction; + +public: + TPCTransactionContext(const TPCTransaction::shared_ptr& _transaction) + : transaction(_transaction) {} + + virtual TPCTransaction::shared_ptr& getTransaction() { return transaction; } +}; + +}}} // namespace qpid::store::ms_clfs + +#endif /* QPID_STORE_MSCLFS_TRANSACTION_H */ diff --git a/qpid/cpp/src/qpid/store/ms-clfs/TransactionLog.cpp b/qpid/cpp/src/qpid/store/ms-clfs/TransactionLog.cpp new file mode 100644 index 0000000000..04780e83e8 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-clfs/TransactionLog.cpp @@ -0,0 +1,428 @@ +/* + * + * 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 <windows.h> +#include <clfsw32.h> +#include <exception> +#include <malloc.h> +#include <memory.h> +#include <qpid/framing/Buffer.h> +#include <qpid/log/Statement.h> +#include <qpid/sys/IntegerTypes.h> +#include <qpid/sys/windows/check.h> + +#include "TransactionLog.h" +#include "Transaction.h" +#include "Lsn.h" + +namespace { + +// Structures that hold log records. Each has a type field at the start. +enum TransactionEntryType { + TransactionStartDtxEntry = 1, + TransactionStartTxEntry = 2, + TransactionPrepareEntry = 3, + TransactionCommitEntry = 4, + TransactionAbortEntry = 5, + TransactionDeleteEntry = 6 +}; +// The only thing that really takes up space in transaction records is the +// xid. Max xid length is in the neighborhood of 600 bytes. Leave some room. +static const uint32_t MaxTransactionContentLength = 1024; + +// Dtx-Start +struct TransactionStartDtx { + TransactionEntryType type; + uint32_t length; + char content[MaxTransactionContentLength]; + + TransactionStartDtx() + : type(TransactionStartDtxEntry), length(0) {} +}; +// Tx-Start +struct TransactionStartTx { + TransactionEntryType type; + + TransactionStartTx() + : type(TransactionStartTxEntry) {} +}; +// Prepare +struct TransactionPrepare { + TransactionEntryType type; + + TransactionPrepare() + : type(TransactionPrepareEntry) {} +}; +// Commit +struct TransactionCommit { + TransactionEntryType type; + + TransactionCommit() + : type(TransactionCommitEntry) {} +}; +// Abort +struct TransactionAbort { + TransactionEntryType type; + + TransactionAbort() + : type(TransactionAbortEntry) {} +}; +// Delete +struct TransactionDelete { + TransactionEntryType type; + + TransactionDelete() + : type(TransactionDeleteEntry) {} +}; + +} // namespace + +namespace qpid { +namespace store { +namespace ms_clfs { + +void +TransactionLog::initialize() +{ + // Write something to occupy the first record, preventing a real + // transaction from being lsn/id 0. Delete of a non-existant id is easily + // tossed during recovery if no other transactions have caused the tail + // to be moved up past this dummy record by then. + deleteTransaction(0); +} + +uint32_t +TransactionLog::marshallingBufferSize() +{ + size_t biggestNeed = sizeof(TransactionStartDtx); + uint32_t defSize = static_cast<uint32_t>(biggestNeed); + uint32_t minSize = Log::marshallingBufferSize(); + if (defSize <= minSize) + return minSize; + // Round up to multiple of minSize + return (defSize + minSize) / minSize * minSize; +} + +// Get a new Transaction +boost::shared_ptr<Transaction> +TransactionLog::begin() +{ + TransactionStartTx entry; + CLFS_LSN location; + uint64_t id; + uint32_t entryLength = static_cast<uint32_t>(sizeof(entry)); + location = write(&entry, entryLength); + try { + qpid::sys::ScopedLock<qpid::sys::Mutex> l(idsLock); + id = lsnToId(location); + std::auto_ptr<Transaction> t(new Transaction(id, shared_from_this())); + boost::shared_ptr<Transaction> t2(t); + boost::weak_ptr<Transaction> weak_t2(t2); + { + qpid::sys::ScopedLock<qpid::sys::Mutex> l(idsLock); + validIds[id] = weak_t2; + } + return t2; + } + catch(...) { + deleteTransaction(id); + throw; + } +} + +// Get a new TPCTransaction +boost::shared_ptr<TPCTransaction> +TransactionLog::begin(const std::string& xid) +{ + TransactionStartDtx entry; + CLFS_LSN location; + uint64_t id; + uint32_t entryLength = static_cast<uint32_t>(sizeof(entry)); + entry.length = static_cast<uint32_t>(xid.length()); + memcpy_s(entry.content, sizeof(entry.content), + xid.c_str(), xid.length()); + entryLength -= (sizeof(entry.content) - entry.length); + location = write(&entry, entryLength); + try { + id = lsnToId(location); + std::auto_ptr<TPCTransaction> t(new TPCTransaction(id, + shared_from_this(), + xid)); + boost::shared_ptr<TPCTransaction> t2(t); + boost::weak_ptr<Transaction> weak_t2(t2); + { + qpid::sys::ScopedLock<qpid::sys::Mutex> l(idsLock); + validIds[id] = weak_t2; + } + return t2; + } + catch(...) { + deleteTransaction(id); + throw; + } +} + +void +TransactionLog::recordPrepare(uint64_t transId) +{ + TransactionPrepare entry; + CLFS_LSN transLsn = idToLsn(transId); + write(&entry, sizeof(entry), &transLsn); +} + +void +TransactionLog::recordCommit(uint64_t transId) +{ + TransactionCommit entry; + CLFS_LSN transLsn = idToLsn(transId); + write(&entry, sizeof(entry), &transLsn); + { + qpid::sys::ScopedLock<qpid::sys::Mutex> l(idsLock); + validIds[transId].reset(); + } +} + +void +TransactionLog::recordAbort(uint64_t transId) +{ + TransactionAbort entry; + CLFS_LSN transLsn = idToLsn(transId); + write(&entry, sizeof(entry), &transLsn); + { + qpid::sys::ScopedLock<qpid::sys::Mutex> l(idsLock); + validIds[transId].reset(); + } +} + +void +TransactionLog::deleteTransaction(uint64_t transId) +{ + uint64_t newFirstId = 0; + { + qpid::sys::ScopedLock<qpid::sys::Mutex> l(idsLock); + validIds.erase(transId); + // May have deleted the first entry; if so the log can release that. + // If this deletion results in an empty list of transactions, + // move the tail up to this transaction's LSN. This may result in + // one or more transactions being stranded in the log until there's + // more activity. If a restart happens while these unneeded log + // records are there, the presence of the TransactionDelete + // entry will cause them to be ignored anyway. + if (validIds.empty()) + newFirstId = transId; + else if (validIds.begin()->first > transId) + newFirstId = validIds.begin()->first; + } + TransactionDelete deleteEntry; + CLFS_LSN transLsn = idToLsn(transId); + write(&deleteEntry, sizeof(deleteEntry), &transLsn); + if (newFirstId != 0) + moveTail(idToLsn(newFirstId)); +} + +void +TransactionLog::collectPreparedXids(std::map<std::string, TPCTransaction::shared_ptr>& preparedMap) +{ + // Go through all the known transactions; if the transaction is still + // valid (open or prepared) it will have weak_ptr to the Transaction. + // If it can be downcast and has a state of TRANS_PREPARED, add to the map. + qpid::sys::ScopedLock<qpid::sys::Mutex> l(idsLock); + std::map<uint64_t, boost::weak_ptr<Transaction> >::const_iterator i; + for (i = validIds.begin(); i != validIds.end(); ++i) { + Transaction::shared_ptr t = i->second.lock(); + if (t.get() == 0) + continue; + TPCTransaction::shared_ptr tpct(boost::dynamic_pointer_cast<TPCTransaction>(t)); + if (tpct.get() == 0) + continue; + if (tpct->state == Transaction::TRANS_PREPARED) + preparedMap[tpct->getXid()] = tpct; + } +} + +void +TransactionLog::recover(std::map<uint64_t, Transaction::shared_ptr>& transMap) +{ + QPID_LOG(debug, "Recovering transaction log"); + + // Note that there may be transaction refs in the log which are deleted, + // so be sure to only add transactions at Start records, and ignore those + // that don't have an existing message record. + // Get the base LSN - that's how to say "start reading at the beginning" + CLFS_INFORMATION info; + ULONG infoLength = sizeof (info); + BOOL ok = ::GetLogFileInformation(handle, &info, &infoLength); + QPID_WINDOWS_CHECK_NOT(ok, 0); + + // Pointers for the various record types that can be assigned in the + // reading loop below. + TransactionStartDtx *startDtxEntry; + TransactionStartTx *startTxEntry; + + PVOID recordPointer; + ULONG recordLength; + CLFS_RECORD_TYPE recordType = ClfsDataRecord; + CLFS_LSN transLsn, current, undoNext; + PVOID readContext; + uint64_t transId; + // Note 'current' in case it's needed below; ReadNextLogRecord returns it + // via a parameter. + current = info.BaseLsn; + ok = ::ReadLogRecord(marshal, + &info.BaseLsn, + ClfsContextForward, + &recordPointer, + &recordLength, + &recordType, + &undoNext, + &transLsn, + &readContext, + 0); + + std::auto_ptr<Transaction> tPtr; + std::auto_ptr<TPCTransaction> tpcPtr; + while (ok) { + std::string xid; + + // All the record types this class writes have a TransactionEntryType + // in the beginning. Based on that, do what's needed. + TransactionEntryType *t = + reinterpret_cast<TransactionEntryType *>(recordPointer); + switch(*t) { + case TransactionStartDtxEntry: + startDtxEntry = + reinterpret_cast<TransactionStartDtx *>(recordPointer); + transId = lsnToId(current); + QPID_LOG(debug, "Dtx start, id " << transId); + xid.assign(startDtxEntry->content, startDtxEntry->length); + tpcPtr.reset(new TPCTransaction(transId, shared_from_this(), xid)); + transMap[transId] = boost::shared_ptr<TPCTransaction>(tpcPtr); + break; + case TransactionStartTxEntry: + startTxEntry = + reinterpret_cast<TransactionStartTx *>(recordPointer); + transId = lsnToId(current); + QPID_LOG(debug, "Tx start, id " << transId); + tPtr.reset(new Transaction(transId, shared_from_this())); + transMap[transId] = boost::shared_ptr<Transaction>(tPtr); + break; + case TransactionPrepareEntry: + transId = lsnToId(transLsn); + QPID_LOG(debug, "Dtx prepare, id " << transId); + if (transMap.find(transId) == transMap.end()) { + QPID_LOG(debug, + "Dtx " << transId << " doesn't exist; discarded"); + } + else { + transMap[transId]->state = Transaction::TRANS_PREPARED; + } + break; + case TransactionCommitEntry: + transId = lsnToId(transLsn); + QPID_LOG(debug, "Txn commit, id " << transId); + if (transMap.find(transId) == transMap.end()) { + QPID_LOG(debug, + "Txn " << transId << " doesn't exist; discarded"); + } + else { + transMap[transId]->state = Transaction::TRANS_COMMITTED; + } + break; + case TransactionAbortEntry: + transId = lsnToId(transLsn); + QPID_LOG(debug, "Txn abort, id " << transId); + if (transMap.find(transId) == transMap.end()) { + QPID_LOG(debug, + "Txn " << transId << " doesn't exist; discarded"); + } + else { + transMap[transId]->state = Transaction::TRANS_ABORTED; + } + break; + case TransactionDeleteEntry: + transId = lsnToId(transLsn); + QPID_LOG(debug, "Txn delete, id " << transId); + if (transMap.find(transId) == transMap.end()) { + QPID_LOG(debug, + "Txn " << transId << " doesn't exist; discarded"); + } + else { + transMap[transId]->state = Transaction::TRANS_DELETED; + transMap.erase(transId); + } + break; + default: + throw std::runtime_error("Bad transaction log entry type"); + } + + recordType = ClfsDataRecord; + ok = ::ReadNextLogRecord(readContext, + &recordPointer, + &recordLength, + &recordType, + 0, // No userLsn + &undoNext, + &transLsn, + ¤t, + 0); + } + DWORD status = ::GetLastError(); + ::TerminateReadLog(readContext); + if (status != ERROR_HANDLE_EOF) // No more records + throw QPID_WINDOWS_ERROR(status); + + QPID_LOG(debug, "Transaction log recovered"); + + // At this point we have a list of all the not-deleted transactions that + // were in existence when the broker last ran. All transactions of both + // Dtx and Tx types that haven't prepared or committed will be aborted. + // This will give the proper background against which to decide each + // message's disposition when recovering messages that were involved in + // transactions. + // In addition to recovering and aborting transactions, rebuild the + // validIds map now that we know which ids are really valid. + std::map<uint64_t, Transaction::shared_ptr>::const_iterator i; + for (i = transMap.begin(); i != transMap.end(); ++i) { + switch(i->second->state) { + case Transaction::TRANS_OPEN: + QPID_LOG(debug, "Txn " << i->first << " was open; aborted"); + i->second->state = Transaction::TRANS_ABORTED; + break; + case Transaction::TRANS_ABORTED: + QPID_LOG(debug, "Txn " << i->first << " was aborted"); + break; + case Transaction::TRANS_COMMITTED: + QPID_LOG(debug, "Txn " << i->first << " was committed"); + break; + case Transaction::TRANS_PREPARED: + QPID_LOG(debug, "Txn " << i->first << " was prepared"); + break; + case Transaction::TRANS_DELETED: + QPID_LOG(error, + "Txn " << i->first << " was deleted; shouldn't be here"); + break; + } + boost::weak_ptr<Transaction> weak_txn(i->second); + validIds[i->first] = weak_txn; + } +} + +}}} // namespace qpid::store::ms_clfs diff --git a/qpid/cpp/src/qpid/store/ms-clfs/TransactionLog.h b/qpid/cpp/src/qpid/store/ms-clfs/TransactionLog.h new file mode 100644 index 0000000000..7ca27c229e --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-clfs/TransactionLog.h @@ -0,0 +1,104 @@ +#ifndef QPID_STORE_MSCLFS_TRANSACTIONLOG_H +#define QPID_STORE_MSCLFS_TRANSACTIONLOG_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 <set> + +#include <boost/enable_shared_from_this.hpp> +#include <boost/shared_ptr.hpp> + +#include <qpid/broker/RecoveryManager.h> +#include <qpid/sys/IntegerTypes.h> +#include <qpid/sys/Mutex.h> + +#include "Log.h" + +namespace qpid { +namespace store { +namespace ms_clfs { + +class Transaction; +class TPCTransaction; + +/** + * @class TransactionLog + * + * Represents a CLFS-housed transaction log. + */ +class TransactionLog : public Log, + public boost::enable_shared_from_this<TransactionLog> { + + // To know when it's ok to move the log tail the lowest valid Id must + // always be known. Keep track of valid Ids here. These are transactions + // which have not yet been Deleted in the log. They may be new, in progress, + // prepared, committed, or aborted - but not deleted. + // Entries corresponding to not-yet-finalized transactions (i.e., open or + // prepared) also have a weak_ptr so the Transaction can be accessed. + // This is primarily to check its state and get a list of prepared Xids. + std::map<uint64_t, boost::weak_ptr<Transaction> > validIds; + qpid::sys::Mutex idsLock; + +protected: + // Transaction log needs to have a no-op first record written in the log + // to ensure that no real transaction gets an ID 0; messages think trans + // id 0 means "no transaction." + virtual void initialize(); + +public: + // Inherited and reimplemented from Log. Figure the minimum marshalling + // buffer size needed for the records this class writes. + virtual uint32_t marshallingBufferSize(); + + typedef boost::shared_ptr<TransactionLog> shared_ptr; + + // Get a new Transaction + boost::shared_ptr<Transaction> begin(); + + // Get a new TPCTransaction + boost::shared_ptr<TPCTransaction> begin(const std::string& xid); + + void recordPrepare(uint64_t transId); + void recordCommit(uint64_t transId); + void recordAbort(uint64_t transId); + void deleteTransaction(uint64_t transId); + + // Fill @arg preparedMap with Xid->TPCTransaction::shared_ptr for all + // currently prepared transactions. + void collectPreparedXids(std::map<std::string, boost::shared_ptr<TPCTransaction> >& preparedMap); + + // Recover the transactions and their state from the log. + // Every non-deleted transaction recovered from the log will be + // represented in @arg transMap. The recovering messages can use this + // information to tell if a transaction referred to in an enqueue/dequeue + // operation should be recovered or dropped by examining transaction state. + // + // @param recoverer Recovery manager used to recreate broker objects from + // entries recovered from the log. + // @param transMap This method fills in the map of id -> shared_ptr of + // recovered transactions. + void recover(std::map<uint64_t, boost::shared_ptr<Transaction> >& transMap); +}; + +}}} // namespace qpid::store::ms_clfs + +#endif /* QPID_STORE_MSCLFS_TRANSACTIONLOG_H */ diff --git a/qpid/cpp/src/qpid/store/ms-sql/AmqpTransaction.cpp b/qpid/cpp/src/qpid/store/ms-sql/AmqpTransaction.cpp new file mode 100644 index 0000000000..095d1bf331 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/AmqpTransaction.cpp @@ -0,0 +1,67 @@ +/* + * + * 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 "AmqpTransaction.h" +#include "DatabaseConnection.h" + +namespace qpid { +namespace store { +namespace ms_sql { + +AmqpTransaction::AmqpTransaction(const boost::shared_ptr<DatabaseConnection>& _db) + : db(_db), sqlTrans(_db) +{ +} + +AmqpTransaction::~AmqpTransaction() +{ +} + +void +AmqpTransaction::sqlBegin() +{ + sqlTrans.begin(); +} + +void +AmqpTransaction::sqlCommit() +{ + sqlTrans.commit(); +} + +void +AmqpTransaction::sqlAbort() +{ + sqlTrans.abort(); +} + + +AmqpTPCTransaction::AmqpTPCTransaction(const boost::shared_ptr<DatabaseConnection>& db, + const std::string& _xid) + : AmqpTransaction(db), prepared(false), xid(_xid) +{ +} + +AmqpTPCTransaction::~AmqpTPCTransaction() +{ +} + +}}} // namespace qpid::store::ms_sql diff --git a/qpid/cpp/src/qpid/store/ms-sql/AmqpTransaction.h b/qpid/cpp/src/qpid/store/ms-sql/AmqpTransaction.h new file mode 100644 index 0000000000..625fab5595 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/AmqpTransaction.h @@ -0,0 +1,85 @@ +#ifndef QPID_STORE_MSSQL_AMQPTRANSACTION_H +#define QPID_STORE_MSSQL_AMQPTRANSACTION_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/TransactionalStore.h> +#include <boost/shared_ptr.hpp> +#include <string> + +#include "SqlTransaction.h" + +namespace qpid { +namespace store { +namespace ms_sql { + +class DatabaseConnection; + +/** + * @class AmqpTransaction + * + * Class representing an AMQP transaction. This is used around a set of + * enqueue and dequeue operations that occur when the broker is acting + * on a transaction commit/abort from the client. + */ +class AmqpTransaction : public qpid::broker::TransactionContext { + + boost::shared_ptr<DatabaseConnection> db; + SqlTransaction sqlTrans; + +public: + AmqpTransaction(const boost::shared_ptr<DatabaseConnection>& _db); + virtual ~AmqpTransaction(); + + DatabaseConnection *dbConn() { return db.get(); } + + void sqlBegin(); + void sqlCommit(); + void sqlAbort(); +}; + +/** + * @class AmqpTPCTransaction + * + * Class representing a Two-Phase-Commit (TPC) AMQP transaction. This is + * used around a set of enqueue and dequeue operations that occur when the + * broker is acting on a transaction prepare/commit/abort from the client. + */ +class AmqpTPCTransaction : public AmqpTransaction, + public qpid::broker::TPCTransactionContext { + bool prepared; + std::string xid; + +public: + AmqpTPCTransaction(const boost::shared_ptr<DatabaseConnection>& db, + const std::string& _xid); + virtual ~AmqpTPCTransaction(); + + void setPrepared(void) { prepared = true; } + bool isPrepared(void) const { return prepared; } + + const std::string& getXid(void) const { return xid; } +}; + +}}} // namespace qpid::store::ms_sql + +#endif /* QPID_STORE_MSSQL_AMQPTRANSACTION_H */ diff --git a/qpid/cpp/src/qpid/store/ms-sql/BindingRecordset.cpp b/qpid/cpp/src/qpid/store/ms-sql/BindingRecordset.cpp new file mode 100644 index 0000000000..1dc4370312 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/BindingRecordset.cpp @@ -0,0 +1,165 @@ +/* + * + * 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/Exception.h> +#include <qpid/log/Statement.h> + +#include "BindingRecordset.h" +#include "BlobAdapter.h" +#include "BlobEncoder.h" +#include "VariantHelper.h" + +namespace qpid { +namespace store { +namespace ms_sql { + +void +BindingRecordset::removeFilter(const std::string& filter) +{ + rs->PutFilter (VariantHelper<std::string>(filter)); + long recs = rs->GetRecordCount(); + if (recs == 0) + return; // Nothing to do + while (recs > 0) { + // Deleting adAffectAll doesn't work as documented; go one by one. + rs->Delete(adAffectCurrent); + if (--recs > 0) + rs->MoveNext(); + } + rs->Update(); +} + +void +BindingRecordset::add(uint64_t exchangeId, + uint64_t queueId, + const std::string& routingKey, + const qpid::framing::FieldTable& args) +{ + VariantHelper<std::string> routingKeyStr(routingKey); + BlobEncoder blob (args); // Marshall field table to a blob + rs->AddNew(); + rs->Fields->GetItem("exchangeId")->Value = exchangeId; + rs->Fields->GetItem("queueId")->Value = queueId; + rs->Fields->GetItem("routingKey")->Value = routingKeyStr; + rs->Fields->GetItem("fieldTableBlob")->AppendChunk(blob); + rs->Update(); +} + +void +BindingRecordset::remove(uint64_t exchangeId, + uint64_t queueId, + const std::string& routingKey, + const qpid::framing::FieldTable& /*args*/) +{ + // Look up the affected binding. + std::ostringstream filter; + filter << "exchangeId = " << exchangeId + << " AND queueId = " << queueId + << " AND routingKey = '" << routingKey << "'" << std::ends; + removeFilter(filter.str()); +} + +void +BindingRecordset::removeForExchange(uint64_t exchangeId) +{ + // Look up the affected bindings by the exchange ID + std::ostringstream filter; + filter << "exchangeId = " << exchangeId << std::ends; + removeFilter(filter.str()); +} + +void +BindingRecordset::removeForQueue(uint64_t queueId) +{ + // Look up the affected bindings by the queue ID + std::ostringstream filter; + filter << "queueId = " << queueId << std::ends; + removeFilter(filter.str()); +} + +void +BindingRecordset::recover(broker::RecoveryManager& recoverer, + const store::ExchangeMap& exchMap, + const store::QueueMap& queueMap) +{ + if (rs->BOF && rs->EndOfFile) + return; // Nothing to do + rs->MoveFirst(); + Binding b; + IADORecordBinding *piAdoRecordBinding; + rs->QueryInterface(__uuidof(IADORecordBinding), + (LPVOID *)&piAdoRecordBinding); + piAdoRecordBinding->BindToRecordset(&b); + while (!rs->EndOfFile) { + long blobSize = rs->Fields->Item["fieldTableBlob"]->ActualSize; + BlobAdapter blob(blobSize); + blob = rs->Fields->Item["fieldTableBlob"]->GetChunk(blobSize); + store::ExchangeMap::const_iterator exch = exchMap.find(b.exchangeId); + if (exch == exchMap.end()) { + std::ostringstream msg; + msg << "Error recovering bindings; exchange ID " << b.exchangeId + << " not found in exchange map"; + throw qpid::Exception(msg.str()); + } + broker::RecoverableExchange::shared_ptr exchPtr = exch->second; + store::QueueMap::const_iterator q = queueMap.find(b.queueId); + if (q == queueMap.end()) { + std::ostringstream msg; + msg << "Error recovering bindings; queue ID " << b.queueId + << " not found in queue map"; + throw qpid::Exception(msg.str()); + } + broker::RecoverableQueue::shared_ptr qPtr = q->second; + // The recovery manager wants the queue name, so get it from the + // RecoverableQueue. + std::string key(b.routingKey); + exchPtr->bind(qPtr->getName(), key, blob); + rs->MoveNext(); + } + + piAdoRecordBinding->Release(); +} + +void +BindingRecordset::dump() +{ + Recordset::dump(); + if (rs->EndOfFile && rs->BOF) // No records + return; + rs->MoveFirst(); + + Binding b; + IADORecordBinding *piAdoRecordBinding; + rs->QueryInterface(__uuidof(IADORecordBinding), + (LPVOID *)&piAdoRecordBinding); + piAdoRecordBinding->BindToRecordset(&b); + + while (VARIANT_FALSE == rs->EndOfFile) { + QPID_LOG(notice, "exch Id " << b.exchangeId + << ", q Id " << b.queueId + << ", k: " << b.routingKey); + rs->MoveNext(); + } + + piAdoRecordBinding->Release(); +} + +}}} // namespace qpid::store::ms_sql diff --git a/qpid/cpp/src/qpid/store/ms-sql/BindingRecordset.h b/qpid/cpp/src/qpid/store/ms-sql/BindingRecordset.h new file mode 100644 index 0000000000..3cb732de75 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/BindingRecordset.h @@ -0,0 +1,88 @@ +#ifndef QPID_STORE_MSSQL_BINDINGRECORDSET_H +#define QPID_STORE_MSSQL_BINDINGRECORDSET_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 <icrsint.h> +#include "Recordset.h" +#include <qpid/store/StorageProvider.h> +#include <qpid/broker/RecoveryManager.h> + +namespace qpid { +namespace store { +namespace ms_sql { + +/** + * @class BindingRecordset + * + * Class for the binding records. + */ +class BindingRecordset : public Recordset { + + class Binding : public CADORecordBinding { + BEGIN_ADO_BINDING(Binding) + ADO_FIXED_LENGTH_ENTRY2(1, adBigInt, exchangeId, FALSE) + ADO_FIXED_LENGTH_ENTRY2(2, adBigInt, queueId, FALSE) + ADO_VARIABLE_LENGTH_ENTRY4(3, adVarChar, routingKey, + sizeof(routingKey), FALSE) + END_ADO_BINDING() + + public: + uint64_t exchangeId; + uint64_t queueId; + char routingKey[256]; + }; + + // Remove all records matching the specified filter/query. + void removeFilter(const std::string& filter); + +public: + // Add a new binding + void add(uint64_t exchangeId, + uint64_t queueId, + const std::string& routingKey, + const qpid::framing::FieldTable& args); + + // Remove a specific binding + void remove(uint64_t exchangeId, + uint64_t queueId, + const std::string& routingKey, + const qpid::framing::FieldTable& args); + + // Remove all bindings for the specified exchange + void removeForExchange(uint64_t exchangeId); + + // Remove all bindings for the specified queue + void removeForQueue(uint64_t queueId); + + // Recover bindings set using exchMap to get from Id to RecoverableExchange. + void recover(qpid::broker::RecoveryManager& recoverer, + const qpid::store::ExchangeMap& exchMap, + const qpid::store::QueueMap& queueMap); + + // Dump table contents; useful for debugging. + void dump(); +}; + +}}} // namespace qpid::store::ms_sql + +#endif /* QPID_STORE_MSSQL_BINDINGRECORDSET_H */ diff --git a/qpid/cpp/src/qpid/store/ms-sql/BlobAdapter.cpp b/qpid/cpp/src/qpid/store/ms-sql/BlobAdapter.cpp new file mode 100644 index 0000000000..1889f34e41 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/BlobAdapter.cpp @@ -0,0 +1,64 @@ +/* + * + * 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 "BlobAdapter.h" +#include <qpid/Exception.h> + +namespace qpid { +namespace store { +namespace ms_sql { + +void +BlobAdapter::extractBuff() +{ + // To give a valid Buffer back, lock the safearray, obtaining a pointer to + // the actual data. Record the pointer in the Buffer so the destructor + // knows to unlock the safearray. + if (buff.getPointer() == 0) { + char *blob; + SafeArrayAccessData(this->parray, (void **)&blob); + qpid::framing::Buffer lockedBuff(blob, buff.getSize()); + buff = lockedBuff; + } +} + + +BlobAdapter::~BlobAdapter() +{ + // If buff's pointer is set, the safearray is locked, so unlock it + if (buff.getPointer() != 0) + SafeArrayUnaccessData(this->parray); +} + +BlobAdapter::operator qpid::framing::Buffer& () +{ + extractBuff(); + return buff; +} + +BlobAdapter::operator qpid::framing::FieldTable& () +{ + extractBuff(); + fields.decode(buff); + return fields; +} + +}}} // namespace qpid::store::ms_sql diff --git a/qpid/cpp/src/qpid/store/ms-sql/BlobAdapter.h b/qpid/cpp/src/qpid/store/ms-sql/BlobAdapter.h new file mode 100644 index 0000000000..1c666392bc --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/BlobAdapter.h @@ -0,0 +1,62 @@ +#ifndef QPID_STORE_MSSQL_BLOBADAPTER_H +#define QPID_STORE_MSSQL_BLOBADAPTER_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 <comutil.h> +#include <qpid/framing/Buffer.h> +#include <qpid/framing/FieldTable.h> + +namespace qpid { +namespace store { +namespace ms_sql { + +/** + * @class BlobAdapter + * + * Adapter for accessing a blob (varbinary SQL field) as a qpid::framing::Buffer + * in an exception-safe way. + */ +class BlobAdapter : public _variant_t { +private: + // This Buffer's pointer indicates whether or not a safearray has + // been locked; if it's 0, no locking was done. + qpid::framing::Buffer buff; + qpid::framing::FieldTable fields; + + void extractBuff(); + +public: + // Initialize with the known length of the data that will come. + // Assigning a _variant_t to this object will set up the array to be + // accessed with the operator Buffer&() + BlobAdapter(long blobSize) : _variant_t(), buff(0, blobSize) {} + ~BlobAdapter(); + BlobAdapter& operator=(_variant_t& var_t_Src) + { _variant_t::operator=(var_t_Src); return *this; } + operator qpid::framing::Buffer& (); + operator qpid::framing::FieldTable& (); +}; + +}}} // namespace qpid::store::ms_sql + +#endif /* QPID_STORE_MSSQL_BLOBADAPTER_H */ diff --git a/qpid/cpp/src/qpid/store/ms-sql/BlobEncoder.cpp b/qpid/cpp/src/qpid/store/ms-sql/BlobEncoder.cpp new file mode 100644 index 0000000000..75d3dc2d86 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/BlobEncoder.cpp @@ -0,0 +1,133 @@ +/* + * + * 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 "BlobEncoder.h" +#include <qpid/Exception.h> +#include <qpid/broker/Persistable.h> +#include <qpid/broker/PersistableMessage.h> +#include <boost/intrusive_ptr.hpp> +#include <memory.h> + +namespace qpid { +namespace store { +namespace ms_sql { + +template <class ITEM> void +BlobEncoder::encode(const ITEM &item) +{ + SAFEARRAYBOUND bound[1] = {0, 0}; + bound[0].cElements = item.encodedSize(); + blob = SafeArrayCreate(VT_UI1, 1, bound); + if (S_OK != SafeArrayLock(blob)) { + SafeArrayDestroy(blob); + blob = 0; + throw qpid::Exception("Error locking blob area for persistable item"); + } + try { + qpid::framing::Buffer buff((char *)blob->pvData, bound[0].cElements); + item.encode(buff); + } + catch(...) { + SafeArrayUnlock(blob); + SafeArrayDestroy(blob); + blob = 0; + throw; + } + this->vt = VT_ARRAY | VT_UI1; + this->parray = blob; + SafeArrayUnlock(blob); +} + +template <> void +BlobEncoder::encode(const boost::intrusive_ptr<qpid::broker::PersistableMessage> &item) +{ + // NOTE! If this code changes, verify the recovery code in MessageRecordset + SAFEARRAYBOUND bound[1] = {0, 0}; + bound[0].cElements = item->encodedSize() + sizeof(uint32_t); + blob = SafeArrayCreate(VT_UI1, 1, bound); + if (S_OK != SafeArrayLock(blob)) { + SafeArrayDestroy(blob); + blob = 0; + throw qpid::Exception("Error locking blob area for message"); + } + try { + uint32_t headerSize = item->encodedHeaderSize(); + qpid::framing::Buffer buff((char *)blob->pvData, bound[0].cElements); + buff.putLong(headerSize); + item->encode(buff); + } + catch(...) { + SafeArrayUnlock(blob); + SafeArrayDestroy(blob); + blob = 0; + throw; + } + this->vt = VT_ARRAY | VT_UI1; + this->parray = blob; + SafeArrayUnlock(blob); +} + +template <> void +BlobEncoder::encode(const std::string &item) +{ + SAFEARRAYBOUND bound[1] = {0, 0}; + bound[0].cElements = item.size(); + blob = SafeArrayCreate(VT_UI1, 1, bound); + if (S_OK != SafeArrayLock(blob)) { + SafeArrayDestroy(blob); + blob = 0; + throw qpid::Exception("Error locking blob area for string"); + } + memcpy_s(blob->pvData, item.size(), item.data(), item.size()); + this->vt = VT_ARRAY | VT_UI1; + this->parray = blob; + SafeArrayUnlock(blob); +} + +BlobEncoder::BlobEncoder(const qpid::broker::Persistable &item) : blob(0) +{ + encode(item); +} + +BlobEncoder::BlobEncoder(const boost::intrusive_ptr<qpid::broker::PersistableMessage> &msg) : blob(0) +{ + encode(msg); +} + +BlobEncoder::BlobEncoder(const qpid::framing::FieldTable &fields) : blob(0) +{ + encode(fields); +} + +BlobEncoder::BlobEncoder(const std::string &data) : blob(0) +{ + encode(data); +} + +BlobEncoder::~BlobEncoder() +{ + if (blob) + SafeArrayDestroy(blob); + blob = 0; + this->parray = 0; +} + +}}} // namespace qpid::store::ms_sql diff --git a/qpid/cpp/src/qpid/store/ms-sql/BlobEncoder.h b/qpid/cpp/src/qpid/store/ms-sql/BlobEncoder.h new file mode 100644 index 0000000000..d2b56223c1 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/BlobEncoder.h @@ -0,0 +1,61 @@ +#ifndef QPID_STORE_MSSQL_BLOBENCODER_H +#define QPID_STORE_MSSQL_BLOBENCODER_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 <comutil.h> +#include <string> +#include <boost/intrusive_ptr.hpp> +#include <qpid/broker/Persistable.h> +#include <qpid/broker/PersistableMessage.h> +#include <qpid/framing/Buffer.h> +#include <qpid/framing/FieldTable.h> + +namespace qpid { +namespace store { +namespace ms_sql { + +/** + * @class BlobEncoder + * + * Encodes a blob (varbinary) field from a qpid::broker::Persistable or a + * qpid::framing::FieldTable (both of which can be encoded to + * qpid::framing::Buffer) so it can be passed to ADO methods for writing + * to the database. + */ +class BlobEncoder : public _variant_t { +private: + SAFEARRAY *blob; + + template <class ITEM> void encode(const ITEM &item); + +public: + BlobEncoder(const qpid::broker::Persistable &item); + BlobEncoder(const boost::intrusive_ptr<qpid::broker::PersistableMessage> &msg); + BlobEncoder(const qpid::framing::FieldTable &fields); + BlobEncoder(const std::string& data); + ~BlobEncoder(); +}; + +}}} // namespace qpid::store::ms_sql + +#endif /* QPID_STORE_MSSQL_BLOBENCODER_H */ diff --git a/qpid/cpp/src/qpid/store/ms-sql/BlobRecordset.cpp b/qpid/cpp/src/qpid/store/ms-sql/BlobRecordset.cpp new file mode 100644 index 0000000000..ef1757dbad --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/BlobRecordset.cpp @@ -0,0 +1,86 @@ +/* + * + * 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/Exception.h> +#include <qpid/log/Statement.h> + +#include "BlobRecordset.h" +#include "BlobEncoder.h" +#include "VariantHelper.h" + +namespace qpid { +namespace store { +namespace ms_sql { + +void +BlobRecordset::add(const qpid::broker::Persistable& item) +{ + BlobEncoder blob (item); // Marshall item info to a blob + rs->AddNew(); + rs->Fields->GetItem("fieldTableBlob")->AppendChunk(blob); + rs->Update(); + uint64_t id = rs->Fields->Item["persistenceId"]->Value; + item.setPersistenceId(id); +} + +void +BlobRecordset::remove(uint64_t id) +{ + // Look up the item by its persistenceId + std::ostringstream filter; + filter << "persistenceId = " << id << std::ends; + rs->PutFilter (VariantHelper<std::string>(filter.str())); + if (!rs->EndOfFile) { + // Delete the record + rs->Delete(adAffectCurrent); + rs->Update(); + } +} + +void +BlobRecordset::remove(const qpid::broker::Persistable& item) +{ + remove(item.getPersistenceId()); +} + +void +BlobRecordset::dump() +{ + Recordset::dump(); +#if 1 + if (rs->EndOfFile && rs->BOF) // No records + return; + + rs->MoveFirst(); + while (!rs->EndOfFile) { + uint64_t id = rs->Fields->Item["persistenceId"]->Value; + QPID_LOG(notice, " -> " << id); + rs->MoveNext(); + } +#else + for (Iterator iter = begin(); iter != end(); ++iter) { + uint64_t id = *iter.first; + QPID_LOG(notice, " -> " << id); + } +#endif +} + +}}} // namespace qpid::store::ms_sql diff --git a/qpid/cpp/src/qpid/store/ms-sql/BlobRecordset.h b/qpid/cpp/src/qpid/store/ms-sql/BlobRecordset.h new file mode 100644 index 0000000000..4d1c338746 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/BlobRecordset.h @@ -0,0 +1,54 @@ +#ifndef QPID_STORE_MSSQL_BLOBRECORDSET_H +#define QPID_STORE_MSSQL_BLOBRECORDSET_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 "Recordset.h" +#include <qpid/broker/Persistable.h> +#include <string> + +namespace qpid { +namespace store { +namespace ms_sql { + +/** + * @class BlobRecordset + * + * Class for the "blob" records that record an id, varbinary(max) pair. + */ +class BlobRecordset : public Recordset { +protected: + +public: + void add(const qpid::broker::Persistable& item); + + // Remove a record given its Id. + void remove(uint64_t id); + void remove(const qpid::broker::Persistable& item); + + // Dump table contents; useful for debugging. + void dump(); +}; + +}}} // namespace qpid::store::ms_sql + +#endif /* QPID_STORE_MSSQL_BLOBRECORDSET_H */ diff --git a/qpid/cpp/src/qpid/store/ms-sql/DatabaseConnection.cpp b/qpid/cpp/src/qpid/store/ms-sql/DatabaseConnection.cpp new file mode 100644 index 0000000000..3219ea526a --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/DatabaseConnection.cpp @@ -0,0 +1,91 @@ +/* + * + * 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 "DatabaseConnection.h" +#include "Exception.h" +#include <comdef.h> +namespace { +inline void TESTHR(HRESULT x) {if FAILED(x) _com_issue_error(x);}; +} + +namespace qpid { +namespace store { +namespace ms_sql { + +DatabaseConnection::DatabaseConnection() : conn(0) +{ +} + +DatabaseConnection::~DatabaseConnection() +{ + close(); +} + +void +DatabaseConnection::open(const std::string& connectString, + const std::string& dbName) +{ + if (conn && conn->State == adStateOpen) + return; + std::string adoConnect = "Provider=SQLOLEDB;" + connectString; + try { + TESTHR(conn.CreateInstance(__uuidof(Connection))); + conn->ConnectionString = adoConnect.c_str(); + conn->Open("", "", "", adConnectUnspecified); + if (dbName.length() > 0) + conn->DefaultDatabase = dbName.c_str(); + } + catch(_com_error &e) { + close(); + throw ADOException("MSSQL can't open " + dbName + " at " + adoConnect, e); + } +} + +void +DatabaseConnection::close() +{ + if (conn && conn->State == adStateOpen) + conn->Close(); + conn = 0; +} + +std::string +DatabaseConnection::getErrors() +{ + long errCount = conn->Errors->Count; + if (errCount <= 0) + return ""; + // Collection ranges from 0 to nCount -1. + std::ostringstream messages; + ErrorPtr pErr = NULL; + for (long i = 0 ; i < errCount ; i++ ) { + if (i > 0) + messages << "\n"; + messages << "[" << i << "] "; + pErr = conn->Errors->GetItem(i); + messages << "Error " << pErr->Number << ": " + << (LPCSTR)pErr->Description; + } + messages << std::ends; + return messages.str(); +} + +}}} // namespace qpid::store::ms_sql diff --git a/qpid/cpp/src/qpid/store/ms-sql/DatabaseConnection.h b/qpid/cpp/src/qpid/store/ms-sql/DatabaseConnection.h new file mode 100644 index 0000000000..785d1587c5 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/DatabaseConnection.h @@ -0,0 +1,64 @@ +#ifndef QPID_STORE_MSSQL_DATABASECONNECTION_H +#define QPID_STORE_MSSQL_DATABASECONNECTION_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. + * + */ + +// Bring in ADO 2.8 (yes, I know it says "15", but that's it...) +#import "C:\Program Files\Common Files\System\ado\msado15.dll" \ + no_namespace rename("EOF", "EndOfFile") + +#include <string> + +namespace qpid { +namespace store { +namespace ms_sql { + +/** + * @class DatabaseConnection + * + * Represents a connection to the SQL database. This class wraps the + * needed _ConnectionPtr for ADO as well as the needed COM initialization + * and cleanup that each thread requires. It is expected that this class + * will be maintained in thread-specific storage so it has no locks. + */ +class DatabaseConnection { +protected: + _ConnectionPtr conn; + +public: + DatabaseConnection(); + ~DatabaseConnection(); + void open(const std::string& connectString, + const std::string& dbName = ""); + void close(); + operator _ConnectionPtr () { return conn; } + + void beginTransaction() { conn->BeginTrans(); } + void commitTransaction() {conn->CommitTrans(); } + void rollbackTransaction() { conn->RollbackTrans(); } + + std::string getErrors(); +}; + +}}} // namespace qpid::store::ms_sql + +#endif /* QPID_STORE_MSSQL_DATABASECONNECTION_H */ diff --git a/qpid/cpp/src/qpid/store/ms-sql/Exception.h b/qpid/cpp/src/qpid/store/ms-sql/Exception.h new file mode 100644 index 0000000000..65ec3388ff --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/Exception.h @@ -0,0 +1,66 @@ +#ifndef QPID_STORE_MSSQL_EXCEPTION_H +#define QPID_STORE_MSSQL_EXCEPTION_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 <string> +#include <comdef.h> +#include <qpid/store/StorageProvider.h> + +namespace qpid { +namespace store { +namespace ms_sql { + +class Exception : public qpid::store::StorageProvider::Exception +{ +protected: + std::string text; +public: + Exception(const std::string& _text) : text(_text) {} + virtual ~Exception() {} + virtual const char* what() const throw() { return text.c_str(); } +}; + +class ADOException : public Exception +{ +public: + ADOException(const std::string& _text, + _com_error &e, + const std::string& providerErrors = "") + : Exception(_text) { + text += ": "; + text += e.ErrorMessage(); + IErrorInfo *i = e.ErrorInfo(); + if (i != 0) { + text += ": "; + _bstr_t wmsg = e.Description(); + text += (const char *)wmsg; + i->Release(); + } + if (providerErrors.length() > 0) + text += providerErrors; + } +}; + +}}} // namespace qpid::store::ms_sql + +#endif /* QPID_STORE_MSSQL_EXCEPTION_H */ diff --git a/qpid/cpp/src/qpid/store/ms-sql/MSSqlProvider.cpp b/qpid/cpp/src/qpid/store/ms-sql/MSSqlProvider.cpp new file mode 100644 index 0000000000..7f22db3d02 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/MSSqlProvider.cpp @@ -0,0 +1,1305 @@ +/* + * + * 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 <stdlib.h> +#include <string> +#include <windows.h> +#include <qpid/broker/RecoverableQueue.h> +#include <qpid/log/Statement.h> +#include <qpid/store/MessageStorePlugin.h> +#include <qpid/store/StorageProvider.h> +#include "AmqpTransaction.h" +#include "BlobAdapter.h" +#include "BlobRecordset.h" +#include "BindingRecordset.h" +#include "MessageMapRecordset.h" +#include "MessageRecordset.h" +#include "TplRecordset.h" +#include "DatabaseConnection.h" +#include "Exception.h" +#include "State.h" +#include "VariantHelper.h" + +// Bring in ADO 2.8 (yes, I know it says "15", but that's it...) +#import "C:\Program Files\Common Files\System\ado\msado15.dll" \ + no_namespace rename("EOF", "EndOfFile") +#include <comdef.h> +namespace { +inline void TESTHR(HRESULT x) {if FAILED(x) _com_issue_error(x);}; + +// Table names +const std::string TblBinding("tblBinding"); +const std::string TblConfig("tblConfig"); +const std::string TblExchange("tblExchange"); +const std::string TblMessage("tblMessage"); +const std::string TblMessageMap("tblMessageMap"); +const std::string TblQueue("tblQueue"); +const std::string TblTpl("tblTPL"); +} + +namespace qpid { +namespace store { +namespace ms_sql { + +/** + * @class MSSqlProvider + * + * Implements a qpid::store::StorageProvider that uses Microsoft SQL Server as + * the backend data store for Qpid. + */ +class MSSqlProvider : public qpid::store::StorageProvider +{ +protected: + void finalizeMe(); + + void dump(); + +public: + MSSqlProvider(); + ~MSSqlProvider(); + + virtual qpid::Options* getOptions() { return &options; } + + virtual void earlyInitialize (Plugin::Target& target); + virtual void initialize(Plugin::Target& target); + + /** + * Receive notification that this provider is the one that will actively + * handle provider storage for the target. If the provider is to be used, + * this method will be called after earlyInitialize() and before any + * recovery operations (recovery, in turn, precedes call to initialize()). + */ + virtual void activate(MessageStorePlugin &store); + + /** + * @name Methods inherited from qpid::broker::MessageStore + */ + //@{ + /** + * If called after init() but before recovery, will discard the database + * and reinitialize using an empty store dir. If @a pushDownStoreFiles + * is true, the content of the store dir will be moved to a backup dir + * inside the store dir. This is used when cluster nodes recover and must + * get thier content from a cluster sync rather than directly fromt the + * store. + * + * @param pushDownStoreFiles If true, will move content of the store dir + * into a subdir, leaving the store dir + * otherwise empty. + */ + virtual void truncateInit(const bool pushDownStoreFiles = false); + + /** + * Record the existence of a durable queue + */ + virtual void create(PersistableQueue& queue, + const qpid::framing::FieldTable& args); + /** + * Destroy a durable queue + */ + virtual void destroy(PersistableQueue& queue); + + /** + * Record the existence of a durable exchange + */ + virtual void create(const PersistableExchange& exchange, + const qpid::framing::FieldTable& args); + /** + * Destroy a durable exchange + */ + virtual void destroy(const PersistableExchange& exchange); + + /** + * Record a binding + */ + virtual void bind(const PersistableExchange& exchange, + const PersistableQueue& queue, + const std::string& key, + const qpid::framing::FieldTable& args); + + /** + * Forget a binding + */ + virtual void unbind(const PersistableExchange& exchange, + const PersistableQueue& queue, + const std::string& key, + const qpid::framing::FieldTable& args); + + /** + * Record generic durable configuration + */ + virtual void create(const PersistableConfig& config); + + /** + * Destroy generic durable configuration + */ + virtual void destroy(const PersistableConfig& config); + + /** + * Stores a messages before it has been enqueued + * (enqueueing automatically stores the message so this is + * only required if storage is required prior to that + * point). If the message has not yet been stored it will + * store the headers as well as any content passed in. A + * persistence id will be set on the message which can be + * used to load the content or to append to it. + */ + virtual void stage(const boost::intrusive_ptr<PersistableMessage>& msg); + + /** + * Destroys a previously staged message. This only needs + * to be called if the message is never enqueued. (Once + * enqueued, deletion will be automatic when the message + * is dequeued from all queues it was enqueued onto). + */ + virtual void destroy(PersistableMessage& msg); + + /** + * Appends content to a previously staged message + */ + virtual void appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg, + const std::string& data); + + /** + * Loads (a section) of content data for the specified + * message (previously stored through a call to stage or + * enqueue) into data. The offset refers to the content + * only (i.e. an offset of 0 implies that the start of the + * content should be loaded, not the headers or related + * meta-data). + */ + virtual void loadContent(const qpid::broker::PersistableQueue& queue, + const boost::intrusive_ptr<const PersistableMessage>& msg, + std::string& data, + uint64_t offset, + uint32_t length); + + /** + * Enqueues a message, storing the message if it has not + * been previously stored and recording that the given + * message is on the given queue. + * + * Note: that this is async so the return of the function does + * not mean the opperation is complete. + * + * @param msg the message to enqueue + * @param queue the name of the queue onto which it is to be enqueued + * @param xid (a pointer to) an identifier of the + * distributed transaction in which the operation takes + * place or null for 'local' transactions + */ + virtual void enqueue(qpid::broker::TransactionContext* ctxt, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue); + + /** + * Dequeues a message, recording that the given message is + * no longer on the given queue and deleting the message + * if it is no longer on any other queue. + * + * Note: that this is async so the return of the function does + * not mean the opperation is complete. + * + * @param msg the message to dequeue + * @param queue the name of the queue from which it is to be dequeued + * @param xid (a pointer to) an identifier of the + * distributed transaction in which the operation takes + * place or null for 'local' transactions + */ + virtual void dequeue(qpid::broker::TransactionContext* ctxt, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue); + + /** + * Flushes all async messages to disk for the specified queue + * + * Note: this is a no-op for this provider. + * + * @param queue the name of the queue from which it is to be dequeued + */ + virtual void flush(const PersistableQueue& queue) {}; + + /** + * Returns the number of outstanding AIO's for a given queue + * + * If 0, than all the enqueue / dequeues have been stored + * to disk + * + * @param queue the name of the queue to check for outstanding AIO + */ + virtual uint32_t outstandingQueueAIO(const PersistableQueue& queue) + {return 0;} + //@} + + /** + * @name Methods inherited from qpid::broker::TransactionalStore + */ + //@{ + virtual std::auto_ptr<qpid::broker::TransactionContext> begin(); + virtual std::auto_ptr<qpid::broker::TPCTransactionContext> begin(const std::string& xid); + virtual void prepare(qpid::broker::TPCTransactionContext& txn); + virtual void commit(qpid::broker::TransactionContext& txn); + virtual void abort(qpid::broker::TransactionContext& txn); + virtual void collectPreparedXids(std::set<std::string>& xids); + //@} + + virtual void recoverConfigs(qpid::broker::RecoveryManager& recoverer); + virtual void recoverExchanges(qpid::broker::RecoveryManager& recoverer, + ExchangeMap& exchangeMap); + virtual void recoverQueues(qpid::broker::RecoveryManager& recoverer, + QueueMap& queueMap); + virtual void recoverBindings(qpid::broker::RecoveryManager& recoverer, + const ExchangeMap& exchangeMap, + const QueueMap& queueMap); + virtual void recoverMessages(qpid::broker::RecoveryManager& recoverer, + MessageMap& messageMap, + MessageQueueMap& messageQueueMap); + virtual void recoverTransactions(qpid::broker::RecoveryManager& recoverer, + PreparedTransactionMap& dtxMap); + +private: + struct ProviderOptions : public qpid::Options + { + std::string connectString; + std::string catalogName; + + ProviderOptions(const std::string &name) + : qpid::Options(name), + catalogName("QpidStore") + { + const enum { NAMELEN = MAX_COMPUTERNAME_LENGTH + 1 }; + TCHAR myName[NAMELEN]; + DWORD myNameLen = NAMELEN; + GetComputerName(myName, &myNameLen); + connectString = "Data Source="; + connectString += myName; + connectString += "\\SQLEXPRESS;Integrated Security=SSPI"; + addOptions() + ("connect", + qpid::optValue(connectString, "STRING"), + "Connection string for the database to use. Will prepend " + "Provider=SQLOLEDB;") + ("catalog", + qpid::optValue(catalogName, "DB NAME"), + "Catalog (database) name") + ; + } + }; + ProviderOptions options; + + // Each thread has a separate connection to the database and also needs + // to manage its COM initialize/finalize individually. This is done by + // keeping a thread-specific State. + boost::thread_specific_ptr<State> dbState; + + State *initState(); + DatabaseConnection *initConnection(void); + void createDb(DatabaseConnection *db, const std::string &name); +}; + +static MSSqlProvider static_instance_registers_plugin; + +void +MSSqlProvider::finalizeMe() +{ + dbState.reset(); +} + +MSSqlProvider::MSSqlProvider() + : options("MS SQL Provider options") +{ +} + +MSSqlProvider::~MSSqlProvider() +{ +} + +void +MSSqlProvider::earlyInitialize(Plugin::Target &target) +{ + MessageStorePlugin *store = dynamic_cast<MessageStorePlugin *>(&target); + if (store) { + // If the database init fails, report it and don't register; give + // the rest of the broker a chance to run. + // + // Don't try to initConnection() since that will fail if the + // database doesn't exist. Instead, try to open a connection without + // a database name, then search for the database. There's still a + // chance this provider won't be selected for the store too, so be + // be sure to close the database connection before return to avoid + // leaving a connection up that will not be used. + try { + initState(); // This initializes COM + std::auto_ptr<DatabaseConnection> db(new DatabaseConnection()); + db->open(options.connectString, ""); + _ConnectionPtr conn(*db); + _RecordsetPtr pCatalogs = NULL; + VariantHelper<std::string> catalogName(options.catalogName); + pCatalogs = conn->OpenSchema(adSchemaCatalogs, catalogName); + if (pCatalogs->EndOfFile) { + // Database doesn't exist; create it + QPID_LOG(notice, + "MSSQL: Creating database " + options.catalogName); + createDb(db.get(), options.catalogName); + } + else { + QPID_LOG(notice, + "MSSQL: Database located: " + options.catalogName); + } + if (pCatalogs) { + if (pCatalogs->State == adStateOpen) + pCatalogs->Close(); + pCatalogs = 0; + } + db->close(); + store->providerAvailable("MSSQL", this); + } + catch (qpid::Exception &e) { + QPID_LOG(error, e.what()); + return; + } + store->addFinalizer(boost::bind(&MSSqlProvider::finalizeMe, this)); + } +} + +void +MSSqlProvider::initialize(Plugin::Target& target) +{ +} + +void +MSSqlProvider::activate(MessageStorePlugin &store) +{ + QPID_LOG(info, "MS SQL Provider is up"); +} + +void +MSSqlProvider::truncateInit(const bool pushDownStoreFiles) +{ +} + +void +MSSqlProvider::create(PersistableQueue& queue, + const qpid::framing::FieldTable& /*args needed for jrnl*/) +{ + DatabaseConnection *db = initConnection(); + BlobRecordset rsQueues; + try { + db->beginTransaction(); + rsQueues.open(db, TblQueue); + rsQueues.add(queue); + db->commitTransaction(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + db->rollbackTransaction(); + throw ADOException("Error creating queue " + queue.getName(), e, errs); + } +} + +/** + * Destroy a durable queue + */ +void +MSSqlProvider::destroy(PersistableQueue& queue) +{ + DatabaseConnection *db = initConnection(); + BlobRecordset rsQueues; + BindingRecordset rsBindings; + MessageRecordset rsMessages; + MessageMapRecordset rsMessageMaps; + try { + db->beginTransaction(); + rsQueues.open(db, TblQueue); + rsBindings.open(db, TblBinding); + rsMessages.open(db, TblMessage); + rsMessageMaps.open(db, TblMessageMap); + // Remove bindings first; the queue IDs can't be ripped out from + // under the references in the bindings table. Then remove the + // message->queue entries for the queue, also because the queue can't + // be deleted while there are references to it. If there are messages + // orphaned by removing the queue references, they're deleted by + // a trigger on the tblMessageMap table. Lastly, the queue record + // can be removed. + rsBindings.removeForQueue(queue.getPersistenceId()); + rsMessageMaps.removeForQueue(queue.getPersistenceId()); + rsQueues.remove(queue); + db->commitTransaction(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + db->rollbackTransaction(); + throw ADOException("Error deleting queue " + queue.getName(), e, errs); + } +} + +/** + * Record the existence of a durable exchange + */ +void +MSSqlProvider::create(const PersistableExchange& exchange, + const qpid::framing::FieldTable& args) +{ + DatabaseConnection *db = initConnection(); + BlobRecordset rsExchanges; + try { + db->beginTransaction(); + rsExchanges.open(db, TblExchange); + rsExchanges.add(exchange); + db->commitTransaction(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + db->rollbackTransaction(); + throw ADOException("Error creating exchange " + exchange.getName(), + e, + errs); + } +} + +/** + * Destroy a durable exchange + */ +void +MSSqlProvider::destroy(const PersistableExchange& exchange) +{ + DatabaseConnection *db = initConnection(); + BlobRecordset rsExchanges; + BindingRecordset rsBindings; + try { + db->beginTransaction(); + rsExchanges.open(db, TblExchange); + rsBindings.open(db, TblBinding); + // Remove bindings first; the exchange IDs can't be ripped out from + // under the references in the bindings table. + rsBindings.removeForExchange(exchange.getPersistenceId()); + rsExchanges.remove(exchange); + db->commitTransaction(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + db->rollbackTransaction(); + throw ADOException("Error deleting exchange " + exchange.getName(), + e, + errs); + } +} + +/** + * Record a binding + */ +void +MSSqlProvider::bind(const PersistableExchange& exchange, + const PersistableQueue& queue, + const std::string& key, + const qpid::framing::FieldTable& args) +{ + DatabaseConnection *db = initConnection(); + BindingRecordset rsBindings; + try { + db->beginTransaction(); + rsBindings.open(db, TblBinding); + rsBindings.add(exchange.getPersistenceId(), + queue.getPersistenceId(), + key, + args); + db->commitTransaction(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + db->rollbackTransaction(); + throw ADOException("Error binding exchange " + exchange.getName() + + " to queue " + queue.getName(), + e, + errs); + } +} + +/** + * Forget a binding + */ +void +MSSqlProvider::unbind(const PersistableExchange& exchange, + const PersistableQueue& queue, + const std::string& key, + const qpid::framing::FieldTable& args) +{ + DatabaseConnection *db = initConnection(); + BindingRecordset rsBindings; + try { + db->beginTransaction(); + rsBindings.open(db, TblBinding); + rsBindings.remove(exchange.getPersistenceId(), + queue.getPersistenceId(), + key, + args); + db->commitTransaction(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + db->rollbackTransaction(); + throw ADOException("Error unbinding exchange " + exchange.getName() + + " from queue " + queue.getName(), + e, + errs); + } +} + +/** + * Record generic durable configuration + */ +void +MSSqlProvider::create(const PersistableConfig& config) +{ + DatabaseConnection *db = initConnection(); + BlobRecordset rsConfigs; + try { + db->beginTransaction(); + rsConfigs.open(db, TblConfig); + rsConfigs.add(config); + db->commitTransaction(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + db->rollbackTransaction(); + throw ADOException("Error creating config " + config.getName(), e, errs); + } +} + +/** + * Destroy generic durable configuration + */ +void +MSSqlProvider::destroy(const PersistableConfig& config) +{ + DatabaseConnection *db = initConnection(); + BlobRecordset rsConfigs; + try { + db->beginTransaction(); + rsConfigs.open(db, TblConfig); + rsConfigs.remove(config); + db->commitTransaction(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + db->rollbackTransaction(); + throw ADOException("Error deleting config " + config.getName(), e, errs); + } +} + +/** + * Stores a messages before it has been enqueued + * (enqueueing automatically stores the message so this is + * only required if storage is required prior to that + * point). If the message has not yet been stored it will + * store the headers as well as any content passed in. A + * persistence id will be set on the message which can be + * used to load the content or to append to it. + */ +void +MSSqlProvider::stage(const boost::intrusive_ptr<PersistableMessage>& msg) +{ + DatabaseConnection *db = initConnection(); + MessageRecordset rsMessages; + try { + db->beginTransaction(); + rsMessages.open(db, TblMessage); + rsMessages.add(msg); + db->commitTransaction(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + db->rollbackTransaction(); + throw ADOException("Error staging message", e, errs); + } +} + +/** + * Destroys a previously staged message. This only needs + * to be called if the message is never enqueued. (Once + * enqueued, deletion will be automatic when the message + * is dequeued from all queues it was enqueued onto). + */ +void +MSSqlProvider::destroy(PersistableMessage& msg) +{ + DatabaseConnection *db = initConnection(); + BlobRecordset rsMessages; + try { + db->beginTransaction(); + rsMessages.open(db, TblMessage); + rsMessages.remove(msg); + db->commitTransaction(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + db->rollbackTransaction(); + throw ADOException("Error deleting message", e, errs); + } +} + +/** + * Appends content to a previously staged message + */ +void +MSSqlProvider::appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg, + const std::string& data) +{ + DatabaseConnection *db = initConnection(); + MessageRecordset rsMessages; + try { + db->beginTransaction(); + rsMessages.open(db, TblMessage); + rsMessages.append(msg, data); + db->commitTransaction(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + db->rollbackTransaction(); + throw ADOException("Error appending to message", e, errs); + } +} + +/** + * Loads (a section) of content data for the specified + * message (previously stored through a call to stage or + * enqueue) into data. The offset refers to the content + * only (i.e. an offset of 0 implies that the start of the + * content should be loaded, not the headers or related + * meta-data). + */ +void +MSSqlProvider::loadContent(const qpid::broker::PersistableQueue& /*queue*/, + const boost::intrusive_ptr<const PersistableMessage>& msg, + std::string& data, + uint64_t offset, + uint32_t length) +{ + // SQL store keeps all messages in one table, so we don't need the + // queue reference. + DatabaseConnection *db = initConnection(); + MessageRecordset rsMessages; + try { + rsMessages.open(db, TblMessage); + rsMessages.loadContent(msg, data, offset, length); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + throw ADOException("Error loading message content", e, errs); + } +} + +/** + * Enqueues a message, storing the message if it has not + * been previously stored and recording that the given + * message is on the given queue. + * + * @param ctxt The transaction context under which this enqueue happens. + * @param msg The message to enqueue + * @param queue the name of the queue onto which it is to be enqueued + */ +void +MSSqlProvider::enqueue(qpid::broker::TransactionContext* ctxt, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue) +{ + // If this enqueue is in the context of a transaction, use the specified + // transaction to nest a new transaction for this operation. However, if + // this is not in the context of a transaction, then just use the thread's + // DatabaseConnection with a ADO transaction. + DatabaseConnection *db = 0; + std::string xid; + AmqpTransaction *atxn = dynamic_cast<AmqpTransaction*> (ctxt); + if (atxn == 0) { + db = initConnection(); + db->beginTransaction(); + } + else { + (void)initState(); // Ensure this thread is initialized + // It's a transactional enqueue; if it's TPC, grab the xid. + AmqpTPCTransaction *tpcTxn = dynamic_cast<AmqpTPCTransaction*> (ctxt); + if (tpcTxn) + xid = tpcTxn->getXid(); + db = atxn->dbConn(); + try { + atxn->sqlBegin(); + } + catch(_com_error &e) { + throw ADOException("Error queuing message", e, db->getErrors()); + } + } + + MessageRecordset rsMessages; + MessageMapRecordset rsMap; + try { + if (msg->getPersistenceId() == 0) { // Message itself not yet saved + rsMessages.open(db, TblMessage); + rsMessages.add(msg); + } + rsMap.open(db, TblMessageMap); + rsMap.add(msg->getPersistenceId(), queue.getPersistenceId(), xid); + if (atxn) + atxn->sqlCommit(); + else + db->commitTransaction(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + if (atxn) + atxn->sqlAbort(); + else + db->rollbackTransaction(); + throw ADOException("Error queuing message", e, errs); + } + msg->enqueueComplete(); +} + +/** + * Dequeues a message, recording that the given message is + * no longer on the given queue and deleting the message + * if it is no longer on any other queue. + * + * @param ctxt The transaction context under which this dequeue happens. + * @param msg The message to dequeue + * @param queue The queue from which it is to be dequeued + */ +void +MSSqlProvider::dequeue(qpid::broker::TransactionContext* ctxt, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue) +{ + // If this dequeue is in the context of a transaction, use the specified + // transaction to nest a new transaction for this operation. However, if + // this is not in the context of a transaction, then just use the thread's + // DatabaseConnection with a ADO transaction. + DatabaseConnection *db = 0; + std::string xid; + AmqpTransaction *atxn = dynamic_cast<AmqpTransaction*> (ctxt); + if (atxn == 0) { + db = initConnection(); + db->beginTransaction(); + } + else { + (void)initState(); // Ensure this thread is initialized + // It's a transactional dequeue; if it's TPC, grab the xid. + AmqpTPCTransaction *tpcTxn = dynamic_cast<AmqpTPCTransaction*> (ctxt); + if (tpcTxn) + xid = tpcTxn->getXid(); + db = atxn->dbConn(); + try { + atxn->sqlBegin(); + } + catch(_com_error &e) { + throw ADOException("Error queuing message", e, db->getErrors()); + } + } + + MessageMapRecordset rsMap; + try { + rsMap.open(db, TblMessageMap); + // TPC dequeues are just marked pending and will actually be removed + // when the transaction commits; Single-phase dequeues are removed + // now, relying on the SQL transaction to put it back if the + // transaction doesn't commit. + if (!xid.empty()) { + rsMap.pendingRemove(msg->getPersistenceId(), + queue.getPersistenceId(), + xid); + } + else { + rsMap.remove(msg->getPersistenceId(), + queue.getPersistenceId()); + } + if (atxn) + atxn->sqlCommit(); + else + db->commitTransaction(); + } + catch(ms_sql::Exception&) { + if (atxn) + atxn->sqlAbort(); + else + db->rollbackTransaction(); + throw; + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + if (atxn) + atxn->sqlAbort(); + else + db->rollbackTransaction(); + throw ADOException("Error dequeuing message", e, errs); + } + msg->dequeueComplete(); +} + +std::auto_ptr<qpid::broker::TransactionContext> +MSSqlProvider::begin() +{ + (void)initState(); // Ensure this thread is initialized + + // Transactions are associated with the Connection, so this transaction + // context needs its own connection. At the time of writing, single-phase + // transactions are dealt with completely on one thread, so we really + // could just use the thread-specific DatabaseConnection for this. + // However, that would introduce an ugly, hidden coupling, so play + // it safe and handle this just like a TPC transaction, which actually + // can be prepared and committed/aborted from different threads, + // making it a bad idea to try using the thread-local DatabaseConnection. + boost::shared_ptr<DatabaseConnection> db(new DatabaseConnection); + db->open(options.connectString, options.catalogName); + std::auto_ptr<AmqpTransaction> tx(new AmqpTransaction(db)); + tx->sqlBegin(); + std::auto_ptr<qpid::broker::TransactionContext> tc(tx); + return tc; +} + +std::auto_ptr<qpid::broker::TPCTransactionContext> +MSSqlProvider::begin(const std::string& xid) +{ + (void)initState(); // Ensure this thread is initialized + boost::shared_ptr<DatabaseConnection> db(new DatabaseConnection); + db->open(options.connectString, options.catalogName); + std::auto_ptr<AmqpTPCTransaction> tx(new AmqpTPCTransaction(db, xid)); + tx->sqlBegin(); + + TplRecordset rsTpl; + try { + tx->sqlBegin(); + rsTpl.open(db.get(), TblTpl); + rsTpl.add(xid); + tx->sqlCommit(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + tx->sqlAbort(); + throw ADOException("Error adding TPL record", e, errs); + } + + std::auto_ptr<qpid::broker::TPCTransactionContext> tc(tx); + return tc; +} + +void +MSSqlProvider::prepare(qpid::broker::TPCTransactionContext& txn) +{ + // Commit all the marked-up enqueue/dequeue ops and the TPL record. + // On commit/rollback the TPL will be removed and the TPL markups + // on the message map will be cleaned up as well. + (void)initState(); // Ensure this thread is initialized + AmqpTPCTransaction *atxn = dynamic_cast<AmqpTPCTransaction*> (&txn); + if (atxn == 0) + throw qpid::broker::InvalidTransactionContextException(); + try { + atxn->sqlCommit(); + } + catch(_com_error &e) { + throw ADOException("Error preparing", e, atxn->dbConn()->getErrors()); + } + atxn->setPrepared(); +} + +void +MSSqlProvider::commit(qpid::broker::TransactionContext& txn) +{ + (void)initState(); // Ensure this thread is initialized + /* + * One-phase transactions simply commit the outer SQL transaction + * that was begun on begin(). Two-phase transactions are different - + * the SQL transaction started on begin() was committed on prepare() + * so all the SQL records reflecting the enqueue/dequeue actions for + * the transaction are recorded but with xid markups on them to reflect + * that they are prepared but not committed. Now go back and remove + * the markups, deleting those marked for removal. + */ + AmqpTPCTransaction *p2txn = dynamic_cast<AmqpTPCTransaction*> (&txn); + if (p2txn == 0) { + AmqpTransaction *p1txn = dynamic_cast<AmqpTransaction*> (&txn); + if (p1txn == 0) + throw qpid::broker::InvalidTransactionContextException(); + p1txn->sqlCommit(); + return; + } + + DatabaseConnection *db(p2txn->dbConn()); + TplRecordset rsTpl; + MessageMapRecordset rsMessageMap; + try { + db->beginTransaction(); + rsTpl.open(db, TblTpl); + rsMessageMap.open(db, TblMessageMap); + rsMessageMap.commitPrepared(p2txn->getXid()); + rsTpl.remove(p2txn->getXid()); + db->commitTransaction(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + db->rollbackTransaction(); + throw ADOException("Error committing transaction", e, errs); + } +} + +void +MSSqlProvider::abort(qpid::broker::TransactionContext& txn) +{ + (void)initState(); // Ensure this thread is initialized + /* + * One-phase and non-prepared two-phase transactions simply abort + * the outer SQL transaction that was begun on begin(). However, prepared + * two-phase transactions are different - the SQL transaction started + * on begin() was committed on prepare() so all the SQL records + * reflecting the enqueue/dequeue actions for the transaction are + * recorded but with xid markups on them to reflect that they are + * prepared but not committed. Now go back and remove the markups, + * deleting those marked for addition. + */ + AmqpTPCTransaction *p2txn = dynamic_cast<AmqpTPCTransaction*> (&txn); + if (p2txn == 0 || !p2txn->isPrepared()) { + AmqpTransaction *p1txn = dynamic_cast<AmqpTransaction*> (&txn); + if (p1txn == 0) + throw qpid::broker::InvalidTransactionContextException(); + p1txn->sqlAbort(); + return; + } + + DatabaseConnection *db(p2txn->dbConn()); + TplRecordset rsTpl; + MessageMapRecordset rsMessageMap; + try { + db->beginTransaction(); + rsTpl.open(db, TblTpl); + rsMessageMap.open(db, TblMessageMap); + rsMessageMap.abortPrepared(p2txn->getXid()); + rsTpl.remove(p2txn->getXid()); + db->commitTransaction(); + } + catch(_com_error &e) { + std::string errs = db->getErrors(); + db->rollbackTransaction(); + throw ADOException("Error committing transaction", e, errs); + } + + + (void)initState(); // Ensure this thread is initialized + AmqpTransaction *atxn = dynamic_cast<AmqpTransaction*> (&txn); + if (atxn == 0) + throw qpid::broker::InvalidTransactionContextException(); + atxn->sqlAbort(); +} + +void +MSSqlProvider::collectPreparedXids(std::set<std::string>& xids) +{ + DatabaseConnection *db = initConnection(); + try { + TplRecordset rsTpl; + rsTpl.open(db, TblTpl); + rsTpl.recover(xids); + } + catch(_com_error &e) { + throw ADOException("Error reading TPL", e, db->getErrors()); + } +} + +// @TODO Much of this recovery code is way too similar... refactor to +// a recover template method on BlobRecordset. + +void +MSSqlProvider::recoverConfigs(qpid::broker::RecoveryManager& recoverer) +{ + DatabaseConnection *db = 0; + try { + db = initConnection(); + BlobRecordset rsConfigs; + rsConfigs.open(db, TblConfig); + _RecordsetPtr p = (_RecordsetPtr)rsConfigs; + if (p->BOF && p->EndOfFile) + return; // Nothing to do + p->MoveFirst(); + while (!p->EndOfFile) { + uint64_t id = p->Fields->Item["persistenceId"]->Value; + long blobSize = p->Fields->Item["fieldTableBlob"]->ActualSize; + BlobAdapter blob(blobSize); + blob = p->Fields->Item["fieldTableBlob"]->GetChunk(blobSize); + // Recreate the Config instance and reset its ID. + broker::RecoverableConfig::shared_ptr config = + recoverer.recoverConfig(blob); + config->setPersistenceId(id); + p->MoveNext(); + } + } + catch(_com_error &e) { + throw ADOException("Error recovering configs", + e, + db ? db->getErrors() : ""); + } +} + +void +MSSqlProvider::recoverExchanges(qpid::broker::RecoveryManager& recoverer, + ExchangeMap& exchangeMap) +{ + DatabaseConnection *db = 0; + try { + db = initConnection(); + BlobRecordset rsExchanges; + rsExchanges.open(db, TblExchange); + _RecordsetPtr p = (_RecordsetPtr)rsExchanges; + if (p->BOF && p->EndOfFile) + return; // Nothing to do + p->MoveFirst(); + while (!p->EndOfFile) { + uint64_t id = p->Fields->Item["persistenceId"]->Value; + long blobSize = p->Fields->Item["fieldTableBlob"]->ActualSize; + BlobAdapter blob(blobSize); + blob = p->Fields->Item["fieldTableBlob"]->GetChunk(blobSize); + // Recreate the Exchange instance, reset its ID, and remember the + // ones restored for matching up when recovering bindings. + broker::RecoverableExchange::shared_ptr exchange = + recoverer.recoverExchange(blob); + exchange->setPersistenceId(id); + exchangeMap[id] = exchange; + p->MoveNext(); + } + } + catch(_com_error &e) { + throw ADOException("Error recovering exchanges", + e, + db ? db->getErrors() : ""); + } +} + +void +MSSqlProvider::recoverQueues(qpid::broker::RecoveryManager& recoverer, + QueueMap& queueMap) +{ + DatabaseConnection *db = 0; + try { + db = initConnection(); + BlobRecordset rsQueues; + rsQueues.open(db, TblQueue); + _RecordsetPtr p = (_RecordsetPtr)rsQueues; + if (p->BOF && p->EndOfFile) + return; // Nothing to do + p->MoveFirst(); + while (!p->EndOfFile) { + uint64_t id = p->Fields->Item["persistenceId"]->Value; + long blobSize = p->Fields->Item["fieldTableBlob"]->ActualSize; + BlobAdapter blob(blobSize); + blob = p->Fields->Item["fieldTableBlob"]->GetChunk(blobSize); + // Recreate the Queue instance and reset its ID. + broker::RecoverableQueue::shared_ptr queue = + recoverer.recoverQueue(blob); + queue->setPersistenceId(id); + queueMap[id] = queue; + p->MoveNext(); + } + } + catch(_com_error &e) { + throw ADOException("Error recovering queues", + e, + db ? db->getErrors() : ""); + } +} + +void +MSSqlProvider::recoverBindings(qpid::broker::RecoveryManager& recoverer, + const ExchangeMap& exchangeMap, + const QueueMap& queueMap) +{ + DatabaseConnection *db = 0; + try { + db = initConnection(); + BindingRecordset rsBindings; + rsBindings.open(db, TblBinding); + rsBindings.recover(recoverer, exchangeMap, queueMap); + } + catch(_com_error &e) { + throw ADOException("Error recovering bindings", + e, + db ? db->getErrors() : ""); + } +} + +void +MSSqlProvider::recoverMessages(qpid::broker::RecoveryManager& recoverer, + MessageMap& messageMap, + MessageQueueMap& messageQueueMap) +{ + DatabaseConnection *db = 0; + try { + db = initConnection(); + MessageRecordset rsMessages; + rsMessages.open(db, TblMessage); + rsMessages.recover(recoverer, messageMap); + + MessageMapRecordset rsMessageMaps; + rsMessageMaps.open(db, TblMessageMap); + rsMessageMaps.recover(messageQueueMap); + } + catch(_com_error &e) { + throw ADOException("Error recovering messages", + e, + db ? db->getErrors() : ""); + } +} + +void +MSSqlProvider::recoverTransactions(qpid::broker::RecoveryManager& recoverer, + PreparedTransactionMap& dtxMap) +{ + DatabaseConnection *db = initConnection(); + std::set<std::string> xids; + try { + TplRecordset rsTpl; + rsTpl.open(db, TblTpl); + rsTpl.recover(xids); + } + catch(_com_error &e) { + throw ADOException("Error recovering TPL records", e, db->getErrors()); + } + + try { + // Rebuild the needed RecoverableTransactions. + for (std::set<std::string>::const_iterator iXid = xids.begin(); + iXid != xids.end(); + ++iXid) { + boost::shared_ptr<DatabaseConnection> dbX(new DatabaseConnection); + dbX->open(options.connectString, options.catalogName); + std::auto_ptr<AmqpTPCTransaction> tx(new AmqpTPCTransaction(dbX, + *iXid)); + tx->setPrepared(); + std::auto_ptr<qpid::broker::TPCTransactionContext> tc(tx); + dtxMap[*iXid] = recoverer.recoverTransaction(*iXid, tc); + } + } + catch(_com_error &e) { + throw ADOException("Error recreating dtx connection", e); + } +} + +////////////// Internal Methods + +State * +MSSqlProvider::initState() +{ + State *state = dbState.get(); // See if thread has initialized + if (!state) { + state = new State; + dbState.reset(state); + } + return state; +} + +DatabaseConnection * +MSSqlProvider::initConnection(void) +{ + State *state = initState(); + if (state->dbConn != 0) + return state->dbConn; // And the DatabaseConnection is set up too + std::auto_ptr<DatabaseConnection> db(new DatabaseConnection); + db->open(options.connectString, options.catalogName); + state->dbConn = db.release(); + return state->dbConn; +} + +void +MSSqlProvider::createDb(DatabaseConnection *db, const std::string &name) +{ + const std::string dbCmd = "CREATE DATABASE " + name; + const std::string useCmd = "USE " + name; + const std::string tableCmd = "CREATE TABLE "; + const std::string colSpecs = + " (persistenceId bigint PRIMARY KEY NOT NULL IDENTITY(1,1)," + " fieldTableBlob varbinary(MAX) NOT NULL)"; + const std::string bindingSpecs = + " (exchangeId bigint REFERENCES tblExchange(persistenceId) NOT NULL," + " queueId bigint REFERENCES tblQueue(persistenceId) NOT NULL," + " routingKey varchar(255)," + " fieldTableBlob varbinary(MAX))"; + const std::string messageMapSpecs = + " (messageId bigint REFERENCES tblMessage(persistenceId) NOT NULL," + " queueId bigint REFERENCES tblQueue(persistenceId) NOT NULL," + " prepareStatus tinyint CHECK (prepareStatus IS NULL OR " + " prepareStatus IN (1, 2))," + " xid varbinary(512) REFERENCES tblTPL(xid)" + " CONSTRAINT CK_NoDups UNIQUE NONCLUSTERED (messageId, queueId) )"; + const std::string tplSpecs = " (xid varbinary(512) PRIMARY KEY NOT NULL)"; + // SET NOCOUNT ON added to prevent extra result sets from + // interfering with SELECT statements. (Added by SQL Management) + const std::string removeUnrefMsgsTrigger = + "CREATE TRIGGER dbo.RemoveUnreferencedMessages " + "ON tblMessageMap AFTER DELETE AS BEGIN " + "SET NOCOUNT ON; " + "DELETE FROM tblMessage " + "WHERE tblMessage.persistenceId IN " + " (SELECT messageId FROM deleted) AND" + " NOT EXISTS(SELECT * FROM tblMessageMap" + " WHERE tblMessageMap.messageId IN" + " (SELECT messageId FROM deleted)) " + "END"; + + _variant_t unused; + _bstr_t dbStr = dbCmd.c_str(); + _ConnectionPtr conn(*db); + try { + conn->Execute(dbStr, &unused, adExecuteNoRecords); + _bstr_t useStr = useCmd.c_str(); + conn->Execute(useStr, &unused, adExecuteNoRecords); + std::string makeTable = tableCmd + TblQueue + colSpecs; + _bstr_t makeTableStr = makeTable.c_str(); + conn->Execute(makeTableStr, &unused, adExecuteNoRecords); + makeTable = tableCmd + TblExchange + colSpecs; + makeTableStr = makeTable.c_str(); + conn->Execute(makeTableStr, &unused, adExecuteNoRecords); + makeTable = tableCmd + TblConfig + colSpecs; + makeTableStr = makeTable.c_str(); + conn->Execute(makeTableStr, &unused, adExecuteNoRecords); + makeTable = tableCmd + TblMessage + colSpecs; + makeTableStr = makeTable.c_str(); + conn->Execute(makeTableStr, &unused, adExecuteNoRecords); + makeTable = tableCmd + TblBinding + bindingSpecs; + makeTableStr = makeTable.c_str(); + conn->Execute(makeTableStr, &unused, adExecuteNoRecords); + makeTable = tableCmd + TblTpl + tplSpecs; + makeTableStr = makeTable.c_str(); + conn->Execute(makeTableStr, &unused, adExecuteNoRecords); + makeTable = tableCmd + TblMessageMap + messageMapSpecs; + makeTableStr = makeTable.c_str(); + conn->Execute(makeTableStr, &unused, adExecuteNoRecords); + _bstr_t addTriggerStr = removeUnrefMsgsTrigger.c_str(); + conn->Execute(addTriggerStr, &unused, adExecuteNoRecords); + } + catch(_com_error &e) { + throw ADOException("MSSQL can't create " + name, e, db->getErrors()); + } +} + +void +MSSqlProvider::dump() +{ + // dump all db records to qpid_log + QPID_LOG(notice, "DB Dump: (not dumping anything)"); + // rsQueues.dump(); +} + + +}}} // namespace qpid::store::ms_sql diff --git a/qpid/cpp/src/qpid/store/ms-sql/MessageMapRecordset.cpp b/qpid/cpp/src/qpid/store/ms-sql/MessageMapRecordset.cpp new file mode 100644 index 0000000000..ce9fa61010 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/MessageMapRecordset.cpp @@ -0,0 +1,267 @@ +/* + * + * 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/Exception.h> +#include <qpid/log/Statement.h> +#include <qpid/store/StorageProvider.h> + +#include "MessageMapRecordset.h" +#include "BlobEncoder.h" +#include "DatabaseConnection.h" +#include "Exception.h" +#include "VariantHelper.h" + +namespace { +inline void TESTHR(HRESULT x) {if FAILED(x) _com_issue_error(x);}; +} + +namespace qpid { +namespace store { +namespace ms_sql { + +void +MessageMapRecordset::open(DatabaseConnection* conn, const std::string& table) +{ + init(conn, table); +} + +void +MessageMapRecordset::add(uint64_t messageId, + uint64_t queueId, + const std::string& xid) +{ + std::ostringstream command; + command << "INSERT INTO " << tableName + << " (messageId, queueId"; + if (!xid.empty()) + command << ", prepareStatus, xid"; + command << ") VALUES (" << messageId << "," << queueId; + if (!xid.empty()) + command << "," << PREPARE_ADD << ",?"; + command << ")" << std::ends; + + _CommandPtr cmd = NULL; + _ParameterPtr xidVal = NULL; + TESTHR(cmd.CreateInstance(__uuidof(Command))); + _ConnectionPtr p = *dbConn; + cmd->ActiveConnection = p; + cmd->CommandText = command.str().c_str(); + cmd->CommandType = adCmdText; + if (!xid.empty()) { + TESTHR(xidVal.CreateInstance(__uuidof(Parameter))); + xidVal->Name = "@xid"; + xidVal->Type = adVarBinary; + xidVal->Size = xid.length(); + xidVal->Direction = adParamInput; + xidVal->Value = BlobEncoder(xid); + cmd->Parameters->Append(xidVal); + } + cmd->Execute(NULL, NULL, adCmdText | adExecuteNoRecords); +} + +void +MessageMapRecordset::remove(uint64_t messageId, uint64_t queueId) +{ + std::ostringstream command; + command << "DELETE FROM " << tableName + << " WHERE queueId = " << queueId + << " AND messageId = " << messageId << std::ends; + _CommandPtr cmd = NULL; + TESTHR(cmd.CreateInstance(__uuidof(Command))); + _ConnectionPtr p = *dbConn; + cmd->ActiveConnection = p; + cmd->CommandText = command.str().c_str(); + cmd->CommandType = adCmdText; + _variant_t deletedRecords; + cmd->Execute(&deletedRecords, NULL, adCmdText | adExecuteNoRecords); + if ((long)deletedRecords == 0) + throw ms_sql::Exception("Message does not exist in queue mapping"); + // Trigger on deleting the mapping takes care of deleting orphaned + // message record from tblMessage. +} + +void +MessageMapRecordset::pendingRemove(uint64_t messageId, + uint64_t queueId, + const std::string& xid) +{ + // Look up the mapping for the specified message and queue. There + // should be only one because of the uniqueness constraint in the + // SQL table. Update it to reflect it's pending delete with + // the specified xid. + std::ostringstream command; + command << "UPDATE " << tableName + << " SET prepareStatus=" << PREPARE_REMOVE + << " , xid=?" + << " WHERE queueId = " << queueId + << " AND messageId = " << messageId << std::ends; + + _CommandPtr cmd = NULL; + _ParameterPtr xidVal = NULL; + TESTHR(cmd.CreateInstance(__uuidof(Command))); + TESTHR(xidVal.CreateInstance(__uuidof(Parameter))); + _ConnectionPtr p = *dbConn; + cmd->ActiveConnection = p; + cmd->CommandText = command.str().c_str(); + cmd->CommandType = adCmdText; + xidVal->Name = "@xid"; + xidVal->Type = adVarBinary; + xidVal->Size = xid.length(); + xidVal->Direction = adParamInput; + xidVal->Value = BlobEncoder(xid); + cmd->Parameters->Append(xidVal); + cmd->Execute(NULL, NULL, adCmdText | adExecuteNoRecords); +} + +void +MessageMapRecordset::removeForQueue(uint64_t queueId) +{ + std::ostringstream command; + command << "DELETE FROM " << tableName + << " WHERE queueId = " << queueId << std::ends; + _CommandPtr cmd = NULL; + + TESTHR(cmd.CreateInstance(__uuidof(Command))); + _ConnectionPtr p = *dbConn; + cmd->ActiveConnection = p; + cmd->CommandText = command.str().c_str(); + cmd->CommandType = adCmdText; + cmd->Execute(NULL, NULL, adCmdText | adExecuteNoRecords); +} + +void +MessageMapRecordset::commitPrepared(const std::string& xid) +{ + // Find all the records for the specified xid. Records marked as adding + // are now permanent so remove the xid and prepareStatus. Records marked + // as removing are removed entirely. + openRs(); + MessageMap m; + IADORecordBinding *piAdoRecordBinding; + rs->QueryInterface(__uuidof(IADORecordBinding), + (LPVOID *)&piAdoRecordBinding); + piAdoRecordBinding->BindToRecordset(&m); + for (; !rs->EndOfFile; rs->MoveNext()) { + if (m.xidStatus != adFldOK) + continue; + const std::string x(m.xid, m.xidLength); + if (x != xid) + continue; + if (m.prepareStatus == PREPARE_REMOVE) { + rs->Delete(adAffectCurrent); + } + else { + _variant_t dbNull; + dbNull.ChangeType(VT_NULL); + rs->Fields->GetItem("prepareStatus")->Value = dbNull; + rs->Fields->GetItem("xid")->Value = dbNull; + } + rs->Update(); + } + piAdoRecordBinding->Release(); +} + +void +MessageMapRecordset::abortPrepared(const std::string& xid) +{ + // Find all the records for the specified xid. Records marked as adding + // need to be removed while records marked as removing are put back to + // no xid and no prepareStatus. + openRs(); + MessageMap m; + IADORecordBinding *piAdoRecordBinding; + rs->QueryInterface(__uuidof(IADORecordBinding), + (LPVOID *)&piAdoRecordBinding); + piAdoRecordBinding->BindToRecordset(&m); + for (; !rs->EndOfFile; rs->MoveNext()) { + if (m.xidStatus != adFldOK) + continue; + const std::string x(m.xid, m.xidLength); + if (x != xid) + continue; + if (m.prepareStatus == PREPARE_ADD) { + rs->Delete(adAffectCurrent); + } + else { + _variant_t dbNull; + dbNull.ChangeType(VT_NULL); + rs->Fields->GetItem("prepareStatus")->Value = dbNull; + rs->Fields->GetItem("xid")->Value = dbNull; + } + rs->Update(); + } + piAdoRecordBinding->Release(); +} + +void +MessageMapRecordset::recover(MessageQueueMap& msgMap) +{ + openRs(); + if (rs->BOF && rs->EndOfFile) + return; // Nothing to do + rs->MoveFirst(); + MessageMap b; + IADORecordBinding *piAdoRecordBinding; + rs->QueryInterface(__uuidof(IADORecordBinding), + (LPVOID *)&piAdoRecordBinding); + piAdoRecordBinding->BindToRecordset(&b); + while (!rs->EndOfFile) { + qpid::store::QueueEntry entry(b.queueId); + if (b.xidStatus == adFldOK && b.xidLength > 0) { + entry.xid.assign(b.xid, b.xidLength); + entry.tplStatus = + b.prepareStatus == PREPARE_ADD ? QueueEntry::ADDING + : QueueEntry::REMOVING; + } + else { + entry.tplStatus = QueueEntry::NONE; + } + msgMap[b.messageId].push_back(entry); + rs->MoveNext(); + } + + piAdoRecordBinding->Release(); +} + +void +MessageMapRecordset::dump() +{ + openRs(); + Recordset::dump(); + if (rs->EndOfFile && rs->BOF) // No records + return; + rs->MoveFirst(); + + MessageMap m; + IADORecordBinding *piAdoRecordBinding; + rs->QueryInterface(__uuidof(IADORecordBinding), + (LPVOID *)&piAdoRecordBinding); + piAdoRecordBinding->BindToRecordset(&m); + + while (!rs->EndOfFile) { + QPID_LOG(notice, "msg " << m.messageId << " on queue " << m.queueId); + rs->MoveNext(); + } + + piAdoRecordBinding->Release(); +} + +}}} // namespace qpid::store::ms_sql diff --git a/qpid/cpp/src/qpid/store/ms-sql/MessageMapRecordset.h b/qpid/cpp/src/qpid/store/ms-sql/MessageMapRecordset.h new file mode 100644 index 0000000000..1b0c2f073e --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/MessageMapRecordset.h @@ -0,0 +1,100 @@ +#ifndef QPID_STORE_MSSQL_MESSAGEMAPRECORDSET_H +#define QPID_STORE_MSSQL_MESSAGEMAPRECORDSET_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 <icrsint.h> +#include "Recordset.h" +#include <qpid/broker/RecoveryManager.h> + +namespace qpid { +namespace store { +namespace ms_sql { + +/** + * @class MessageMapRecordset + * + * Class for the message map (message -> queue) records. + */ +class MessageMapRecordset : public Recordset { + + // These values are defined in a constraint on the tblMessageMap table. + // the prepareStatus column can only be null, 1, or 2. + enum { PREPARE_ADD=1, PREPARE_REMOVE=2 }; + + class MessageMap : public CADORecordBinding { + BEGIN_ADO_BINDING(MessageMap) + ADO_FIXED_LENGTH_ENTRY2(1, adBigInt, messageId, FALSE) + ADO_FIXED_LENGTH_ENTRY2(2, adBigInt, queueId, FALSE) + ADO_FIXED_LENGTH_ENTRY2(3, adTinyInt, prepareStatus, FALSE) + ADO_VARIABLE_LENGTH_ENTRY(4, adVarBinary, xid, sizeof(xid), + xidStatus, xidLength, FALSE) + END_ADO_BINDING() + + public: + uint64_t messageId; + uint64_t queueId; + uint8_t prepareStatus; + char xid[512]; + int xidStatus; + uint32_t xidLength; + }; + + void selectOnXid(const std::string& xid); + +public: + virtual void open(DatabaseConnection* conn, const std::string& table); + + // Add a new mapping + void add(uint64_t messageId, + uint64_t queueId, + const std::string& xid = ""); + + // Remove a specific mapping. + void remove(uint64_t messageId, uint64_t queueId); + + // Mark the indicated message->queue entry pending removal. The entry + // for the mapping is updated to indicate pending removal with the + // specified xid. + void pendingRemove(uint64_t messageId, + uint64_t queueId, + const std::string& xid); + + // Remove mappings for all messages on a specified queue. + void removeForQueue(uint64_t queueId); + + // Commit records recorded as prepared. + void commitPrepared(const std::string& xid); + + // Abort prepared changes. + void abortPrepared(const std::string& xid); + + // Recover the mappings of message ID -> vector<queue ID>. + void recover(MessageQueueMap& msgMap); + + // Dump table contents; useful for debugging. + void dump(); +}; + +}}} // namespace qpid::store::ms_sql + +#endif /* QPID_STORE_MSSQL_MESSAGEMAPRECORDSET_H */ diff --git a/qpid/cpp/src/qpid/store/ms-sql/MessageRecordset.cpp b/qpid/cpp/src/qpid/store/ms-sql/MessageRecordset.cpp new file mode 100644 index 0000000000..b62a333df6 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/MessageRecordset.cpp @@ -0,0 +1,184 @@ +/* + * + * 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/Exception.h> +#include <qpid/log/Statement.h> + +#include "MessageRecordset.h" +#include "BlobAdapter.h" +#include "BlobEncoder.h" +#include "VariantHelper.h" + +#include <boost/intrusive_ptr.hpp> + +class qpid::broker::PersistableMessage; + +namespace qpid { +namespace store { +namespace ms_sql { + +void +MessageRecordset::add(const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg) +{ + BlobEncoder blob (msg); // Marshall headers and content to a blob + rs->AddNew(); + rs->Fields->GetItem("fieldTableBlob")->AppendChunk(blob); + rs->Update(); + uint64_t id = rs->Fields->Item["persistenceId"]->Value; + msg->setPersistenceId(id); +} + +void +MessageRecordset::append(const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg, + const std::string& data) +{ + // Look up the message by its Id + std::ostringstream filter; + filter << "persistenceId = " << msg->getPersistenceId() << std::ends; + rs->PutFilter (VariantHelper<std::string>(filter.str())); + if (rs->RecordCount == 0) { + throw Exception("Can't append to message not stored in database"); + } + BlobEncoder blob (data); // Marshall string data to a blob + rs->Fields->GetItem("fieldTableBlob")->AppendChunk(blob); + rs->Update(); +} + +void +MessageRecordset::remove(const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg) +{ + BlobRecordset::remove(msg->getPersistenceId()); +} + +void +MessageRecordset::loadContent(const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg, + std::string& data, + uint64_t offset, + uint32_t length) +{ + // Look up the message by its Id + std::ostringstream filter; + filter << "persistenceId = " << msg->getPersistenceId() << std::ends; + rs->PutFilter (VariantHelper<std::string>(filter.str())); + if (rs->RecordCount == 0) { + throw Exception("Can't load message not stored in database"); + } + + // NOTE! If this code needs to change, please verify the encoding + // code in BlobEncoder. + long blobSize = rs->Fields->Item["fieldTableBlob"]->ActualSize; + uint32_t headerSize; + const size_t headerFieldLength = sizeof(headerSize); + BlobAdapter blob(headerFieldLength); + blob = + rs->Fields->Item["fieldTableBlob"]->GetChunk((long)headerFieldLength); + headerSize = ((qpid::framing::Buffer&)blob).getLong(); + + // GetChunk always begins reading where the previous GetChunk left off, + // so we can't just tell it to ignore the header and read the data. + // So, read the header plus the offset, plus the desired data, then + // copy the desired data to the supplied string. If this ends up asking + // for more than is available in the field, reduce it to what's there. + long getSize = headerSize + offset + length; + if (getSize + (long)headerFieldLength > blobSize) { + size_t reduce = (getSize + headerFieldLength) - blobSize; + getSize -= reduce; + length -= reduce; + } + BlobAdapter header_plus(getSize); + header_plus = rs->Fields->Item["fieldTableBlob"]->GetChunk(getSize); + uint8_t *throw_away = new uint8_t[headerSize + offset]; + ((qpid::framing::Buffer&)header_plus).getRawData(throw_away, headerSize + offset); + delete throw_away; + ((qpid::framing::Buffer&)header_plus).getRawData(data, length); +} + +void +MessageRecordset::recover(qpid::broker::RecoveryManager& recoverer, + std::map<uint64_t, broker::RecoverableMessage::shared_ptr>& messageMap) +{ + if (rs->BOF && rs->EndOfFile) + return; // Nothing to do + rs->MoveFirst(); + Binding b; + IADORecordBinding *piAdoRecordBinding; + rs->QueryInterface(__uuidof(IADORecordBinding), + (LPVOID *)&piAdoRecordBinding); + piAdoRecordBinding->BindToRecordset(&b); + while (!rs->EndOfFile) { + // The blob was written as normal, but with the header length + // prepended in a uint32_t. Due to message staging threshold + // limits, the header may be all that's read in; get it first, + // recover that message header, then see if the rest is needed. + // + // NOTE! If this code needs to change, please verify the encoding + // code in BlobEncoder. + long blobSize = rs->Fields->Item["fieldTableBlob"]->ActualSize; + uint32_t headerSize; + const size_t headerFieldLength = sizeof(headerSize); + BlobAdapter blob(headerFieldLength); + blob = + rs->Fields->Item["fieldTableBlob"]->GetChunk((long)headerFieldLength); + headerSize = ((qpid::framing::Buffer&)blob).getLong(); + BlobAdapter header(headerSize); + header = rs->Fields->Item["fieldTableBlob"]->GetChunk(headerSize); + broker::RecoverableMessage::shared_ptr msg; + msg = recoverer.recoverMessage(header); + msg->setPersistenceId(b.messageId); + messageMap[b.messageId] = msg; + + // Now, do we need the rest of the content? + long contentLength = blobSize - headerFieldLength - headerSize; + if (msg->loadContent(contentLength)) { + BlobAdapter content(contentLength); + content = + rs->Fields->Item["fieldTableBlob"]->GetChunk(contentLength); + msg->decodeContent(content); + } + rs->MoveNext(); + } + + piAdoRecordBinding->Release(); +} + +void +MessageRecordset::dump() +{ + Recordset::dump(); + if (rs->EndOfFile && rs->BOF) // No records + return; + rs->MoveFirst(); + + Binding b; + IADORecordBinding *piAdoRecordBinding; + rs->QueryInterface(__uuidof(IADORecordBinding), + (LPVOID *)&piAdoRecordBinding); + piAdoRecordBinding->BindToRecordset(&b); + + while (VARIANT_FALSE == rs->EndOfFile) { + QPID_LOG(notice, "Msg " << b.messageId); + rs->MoveNext(); + } + + piAdoRecordBinding->Release(); +} + +}}} // namespace qpid::store::ms_sql diff --git a/qpid/cpp/src/qpid/store/ms-sql/MessageRecordset.h b/qpid/cpp/src/qpid/store/ms-sql/MessageRecordset.h new file mode 100644 index 0000000000..698b2561fe --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/MessageRecordset.h @@ -0,0 +1,85 @@ +#ifndef QPID_STORE_MSSQL_MESSAGERECORDSET_H +#define QPID_STORE_MSSQL_MESSAGERECORDSET_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 <icrsint.h> +#include "BlobRecordset.h" +#include <qpid/broker/PersistableMessage.h> +#include <qpid/broker/RecoveryManager.h> +#include <boost/intrusive_ptr.hpp> + +namespace qpid { +namespace store { +namespace ms_sql { + +/** + * @class MessageRecordset + * + * Class for storing and recovering messages. Messages are primarily blobs + * and handled similarly. However, messages larger than the staging threshold + * are not contained completely in memory; they're left mostly in the store + * and the header is held in memory. So when the message "blob" is saved, + * an additional size-of-the-header field is prepended to the blob. + * On recovery, the size-of-the-header is used to get only what's needed + * until it's determined if the entire message is to be recovered to memory. + */ +class MessageRecordset : public BlobRecordset { + class Binding : public CADORecordBinding { + BEGIN_ADO_BINDING(Binding) + ADO_FIXED_LENGTH_ENTRY2(1, adBigInt, messageId, FALSE) + END_ADO_BINDING() + + public: + uint64_t messageId; + }; + +public: + // Store a message. Store the header size (4 bytes) then the regular + // blob comprising the message. + void add(const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg); + + // Append additional content to an existing message. + void append(const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg, + const std::string& data); + + // Remove an existing message + void remove(const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg); + + // Load all or part of a stored message. This skips the header parts and + // loads content. + void loadContent(const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg, + std::string& data, + uint64_t offset, + uint32_t length); + + // Recover messages and save a map of those recovered. + void recover(qpid::broker::RecoveryManager& recoverer, + std::map<uint64_t, broker::RecoverableMessage::shared_ptr>& messageMap); + + // Dump table contents; useful for debugging. + void dump(); +}; + +}}} // namespace qpid::store::ms_sql + +#endif /* QPID_STORE_MSSQL_MESSAGERECORDSET_H */ diff --git a/qpid/cpp/src/qpid/store/ms-sql/Recordset.cpp b/qpid/cpp/src/qpid/store/ms-sql/Recordset.cpp new file mode 100644 index 0000000000..e706799951 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/Recordset.cpp @@ -0,0 +1,92 @@ +/* + * + * 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/Exception.h> +#include <qpid/log/Statement.h> + +#include "Recordset.h" +#include "BlobEncoder.h" +#include "DatabaseConnection.h" +#include "VariantHelper.h" + +namespace { +inline void TESTHR(HRESULT x) {if FAILED(x) _com_issue_error(x);}; +} + +namespace qpid { +namespace store { +namespace ms_sql { + + +void +Recordset::init(DatabaseConnection* conn, const std::string& table) +{ + dbConn = conn; + TESTHR(rs.CreateInstance(__uuidof(::Recordset))); + tableName = table; +} + +void +Recordset::openRs() +{ + // Client-side cursors needed to get access to newly added + // identity column immediately. Recordsets need this to get the + // persistence ID for the broker objects. + rs->CursorLocation = adUseClient; + _ConnectionPtr p = *dbConn; + rs->Open(tableName.c_str(), + _variant_t((IDispatch *)p, true), + adOpenStatic, + adLockOptimistic, + adCmdTable); +} + +void +Recordset::open(DatabaseConnection* conn, const std::string& table) +{ + init(conn, table); + openRs(); +} + +void +Recordset::close() +{ + if (rs && rs->State == adStateOpen) + rs->Close(); +} + +void +Recordset::requery() +{ + // Restore the recordset to reflect all current records. + rs->Filter = ""; + rs->Requery(-1); +} + +void +Recordset::dump() +{ + long count = rs->RecordCount; + QPID_LOG(notice, "DB Dump: " + tableName << + ": " << count << " records"); +} + +}}} // namespace qpid::store::ms_sql diff --git a/qpid/cpp/src/qpid/store/ms-sql/Recordset.h b/qpid/cpp/src/qpid/store/ms-sql/Recordset.h new file mode 100644 index 0000000000..032b2bd434 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/Recordset.h @@ -0,0 +1,75 @@ +#ifndef QPID_STORE_MSSQL_RECORDSET_H +#define QPID_STORE_MSSQL_RECORDSET_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. + * + */ + + +// Bring in ADO 2.8 (yes, I know it says "15", but that's it...) +#import "C:\Program Files\Common Files\System\ado\msado15.dll" \ + no_namespace rename("EOF", "EndOfFile") +#include <comdef.h> +#include <comutil.h> +#include <string> +#if 0 +#include <utility> +#endif + +namespace qpid { +namespace store { +namespace ms_sql { + +class DatabaseConnection; + +/** + * @class Recordset + * + * Represents an ADO Recordset, abstracting out the common operations needed + * on the common tables used that have 2 fields, persistence ID and blob. + */ +class Recordset { +protected: + _RecordsetPtr rs; + DatabaseConnection* dbConn; + std::string tableName; + + void init(DatabaseConnection* conn, const std::string& table); + void openRs(); + +public: + Recordset() : rs(0), dbConn(0) {} + virtual ~Recordset() { close(); rs = 0; dbConn = 0; } + + /** + * Default open() reads all records into the recordset. + */ + virtual void open(DatabaseConnection* conn, const std::string& table); + void close(); + void requery(); + operator _RecordsetPtr () { return rs; } + + // Dump table contents; useful for debugging. + void dump(); +}; + +}}} // namespace qpid::store::ms_sql + +#endif /* QPID_STORE_MSSQL_RECORDSET_H */ diff --git a/qpid/cpp/src/qpid/store/ms-sql/SqlTransaction.cpp b/qpid/cpp/src/qpid/store/ms-sql/SqlTransaction.cpp new file mode 100644 index 0000000000..6ad7725570 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/SqlTransaction.cpp @@ -0,0 +1,71 @@ +/*
+ *
+ * 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 "SqlTransaction.h"
+#include "DatabaseConnection.h"
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+SqlTransaction::SqlTransaction(const boost::shared_ptr<DatabaseConnection>& _db)
+ : db(_db), transDepth(0)
+{
+}
+
+SqlTransaction::~SqlTransaction()
+{
+ if (transDepth > 0)
+ this->abort();
+}
+
+void
+SqlTransaction::begin()
+{
+ _bstr_t beginCmd("BEGIN TRANSACTION");
+ _ConnectionPtr c = *db;
+ c->Execute(beginCmd, NULL, adExecuteNoRecords);
+ ++transDepth;
+}
+
+void
+SqlTransaction::commit()
+{
+ if (transDepth > 0) {
+ _bstr_t commitCmd("COMMIT TRANSACTION");
+ _ConnectionPtr c = *db;
+ c->Execute(commitCmd, NULL, adExecuteNoRecords);
+ --transDepth;
+ }
+}
+
+void
+SqlTransaction::abort()
+{
+ if (transDepth > 0) {
+ _bstr_t rollbackCmd("ROLLBACK TRANSACTION");
+ _ConnectionPtr c = *db;
+ c->Execute(rollbackCmd, NULL, adExecuteNoRecords);
+ transDepth = 0;
+ }
+}
+
+}}} // namespace qpid::store::ms_sql
diff --git a/qpid/cpp/src/qpid/store/ms-sql/SqlTransaction.h b/qpid/cpp/src/qpid/store/ms-sql/SqlTransaction.h new file mode 100644 index 0000000000..8b5239b786 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/SqlTransaction.h @@ -0,0 +1,67 @@ +#ifndef QPID_STORE_MSSQL_SQLTRANSACTION_H
+#define QPID_STORE_MSSQL_SQLTRANSACTION_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 <boost/shared_ptr.hpp>
+#include <string>
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+class DatabaseConnection;
+
+/**
+ * @class SqlTransaction
+ *
+ * Class representing an SQL transaction.
+ * Since ADO w/ SQLOLEDB can't do nested transaction via its BeginTrans(),
+ * et al, nested transactions are carried out with direct SQL commands.
+ * To ensure the state of this is known, keep track of how deeply the
+ * transactions are nested. This is more of a safety/sanity check since
+ * AMQP doesn't provide nested transactions.
+ */
+class SqlTransaction {
+
+ boost::shared_ptr<DatabaseConnection> db;
+
+ // Since ADO w/ SQLOLEDB can't do nested transaction via its BeginTrans(),
+ // et al, nested transactions are carried out with direct SQL commands.
+ // To ensure the state of this is known, keep track of how deeply the
+ // transactions are nested.
+ unsigned int transDepth;
+
+public:
+ SqlTransaction(const boost::shared_ptr<DatabaseConnection>& _db);
+ ~SqlTransaction();
+
+ DatabaseConnection *dbConn() { return db.get(); }
+
+ void begin();
+ void commit();
+ void abort();
+};
+
+}}} // namespace qpid::store::ms_sql
+
+#endif /* QPID_STORE_MSSQL_SQLTRANSACTION_H */
diff --git a/qpid/cpp/src/qpid/store/ms-sql/State.cpp b/qpid/cpp/src/qpid/store/ms-sql/State.cpp new file mode 100644 index 0000000000..720603dd11 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/State.cpp @@ -0,0 +1,45 @@ +/* + * + * 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 "State.h" +#include "DatabaseConnection.h" +#include "Exception.h" +#include <comdef.h> + +namespace qpid { +namespace store { +namespace ms_sql { + +State::State() : dbConn(0) +{ + HRESULT hr = ::CoInitializeEx(NULL, COINIT_MULTITHREADED); + if (hr != S_OK && hr != S_FALSE) + throw Exception("Error initializing COM"); +} + +State::~State() +{ + if (dbConn) + delete dbConn; + ::CoUninitialize(); +} + +}}} // namespace qpid::store::ms_sql diff --git a/qpid/cpp/src/qpid/store/ms-sql/State.h b/qpid/cpp/src/qpid/store/ms-sql/State.h new file mode 100644 index 0000000000..6350bc5bd2 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/State.h @@ -0,0 +1,52 @@ +#ifndef QPID_STORE_MSSQL_STATE_H +#define QPID_STORE_MSSQL_STATE_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. + * + */ + +namespace qpid { +namespace store { +namespace ms_sql { + +class DatabaseConnection; + +/** + * @struct State + * + * Represents a thread's state for accessing ADO and the database. + * Creating an instance of State initializes COM for this thread, and + * destroying it uninitializes COM. There's also a DatabaseConnection + * for this thread's default access to the database. More DatabaseConnections + * can always be created, but State has one that can always be used by + * the thread whose state is represented. + * + * This class is intended to be one-per-thread, so it should be accessed + * via thread-specific storage. + */ +struct State { + State(); + ~State(); + DatabaseConnection *dbConn; +}; + +}}} // namespace qpid::store::ms_sql + +#endif /* QPID_STORE_MSSQL_STATE_H */ diff --git a/qpid/cpp/src/qpid/store/ms-sql/TplRecordset.cpp b/qpid/cpp/src/qpid/store/ms-sql/TplRecordset.cpp new file mode 100644 index 0000000000..1309d921a9 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/TplRecordset.cpp @@ -0,0 +1,128 @@ +/*
+ *
+ * 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 <string>
+#include <qpid/Exception.h>
+#include <qpid/log/Statement.h>
+
+#include "TplRecordset.h"
+#include "BlobEncoder.h"
+#include "DatabaseConnection.h"
+#include "VariantHelper.h"
+
+namespace {
+inline void TESTHR(HRESULT x) {if FAILED(x) _com_issue_error(x);};
+}
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+void
+TplRecordset::open(DatabaseConnection* conn, const std::string& table)
+{
+ init(conn, table);
+ // Don't actually open until we know what to do. It's far easier and more
+ // efficient to simply do most of these TPL/xid ops in a single statement.
+}
+
+void
+TplRecordset::add(const std::string& xid)
+{
+ const std::string command =
+ "INSERT INTO " + tableName + " ( xid ) VALUES ( ? )";
+ _CommandPtr cmd = NULL;
+ _ParameterPtr xidVal = NULL;
+
+ TESTHR(cmd.CreateInstance(__uuidof(Command)));
+ TESTHR(xidVal.CreateInstance(__uuidof(Parameter)));
+ _ConnectionPtr p = *dbConn;
+ cmd->ActiveConnection = p;
+ cmd->CommandText = command.c_str();
+ cmd->CommandType = adCmdText;
+ xidVal->Name = "@xid";
+ xidVal->Type = adVarBinary;
+ xidVal->Size = xid.length();
+ xidVal->Direction = adParamInput;
+ xidVal->Value = BlobEncoder(xid);
+ cmd->Parameters->Append(xidVal);
+ cmd->Execute(NULL, NULL, adCmdText | adExecuteNoRecords);
+}
+
+void
+TplRecordset::remove(const std::string& xid)
+{
+ // Look up the item by its xid
+ const std::string command =
+ "DELETE FROM " + tableName + " WHERE xid = ?";
+ _CommandPtr cmd = NULL;
+ _ParameterPtr xidVal = NULL;
+
+ TESTHR(cmd.CreateInstance(__uuidof(Command)));
+ TESTHR(xidVal.CreateInstance(__uuidof(Parameter)));
+ _ConnectionPtr p = *dbConn;
+ cmd->ActiveConnection = p;
+ cmd->CommandText = command.c_str();
+ cmd->CommandType = adCmdText;
+ xidVal->Name = "@xid";
+ xidVal->Type = adVarBinary;
+ xidVal->Size = xid.length();
+ xidVal->Direction = adParamInput;
+ xidVal->Value = BlobEncoder(xid);
+ cmd->Parameters->Append(xidVal);
+ _variant_t deletedRecords;
+ cmd->Execute(&deletedRecords, NULL, adCmdText | adExecuteNoRecords);
+}
+
+void
+TplRecordset::recover(std::set<std::string>& xids)
+{
+ openRs();
+ if (rs->BOF && rs->EndOfFile)
+ return; // Nothing to do
+ rs->MoveFirst();
+ while (!rs->EndOfFile) {
+ _variant_t wxid = rs->Fields->Item["xid"]->Value;
+ char *xidBytes;
+ SafeArrayAccessData(wxid.parray, (void **)&xidBytes);
+ std::string xid(xidBytes, rs->Fields->Item["xid"]->ActualSize);
+ xids.insert(xid);
+ SafeArrayUnaccessData(wxid.parray);
+ rs->MoveNext();
+ }
+}
+
+void
+TplRecordset::dump()
+{
+ Recordset::dump();
+ if (rs->EndOfFile && rs->BOF) // No records
+ return;
+
+ rs->MoveFirst();
+ while (!rs->EndOfFile) {
+ _bstr_t wxid = rs->Fields->Item["xid"]->Value;
+ QPID_LOG(notice, " -> " << (const char *)wxid);
+ rs->MoveNext();
+ }
+}
+
+}}} // namespace qpid::store::ms_sql
diff --git a/qpid/cpp/src/qpid/store/ms-sql/TplRecordset.h b/qpid/cpp/src/qpid/store/ms-sql/TplRecordset.h new file mode 100644 index 0000000000..fbde51738c --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/TplRecordset.h @@ -0,0 +1,58 @@ +#ifndef QPID_STORE_MSSQL_TPLRECORDSET_H
+#define QPID_STORE_MSSQL_TPLRECORDSET_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 "Recordset.h"
+#include <string>
+#include <set>
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+/**
+ * @class TplRecordset
+ *
+ * Class for the TPL (Transaction Prepared List) records.
+ */
+class TplRecordset : public Recordset {
+protected:
+
+public:
+ virtual void open(DatabaseConnection* conn, const std::string& table);
+
+ void add(const std::string& xid);
+
+ // Remove a record given its xid.
+ void remove(const std::string& xid);
+
+ // Recover prepared transaction XIDs.
+ void recover(std::set<std::string>& xids);
+
+ // Dump table contents; useful for debugging.
+ void dump();
+};
+
+}}} // namespace qpid::store::ms_sql
+
+#endif /* QPID_STORE_MSSQL_TPLRECORDSET_H */
diff --git a/qpid/cpp/src/qpid/store/ms-sql/VariantHelper.cpp b/qpid/cpp/src/qpid/store/ms-sql/VariantHelper.cpp new file mode 100644 index 0000000000..acec95c1f9 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/VariantHelper.cpp @@ -0,0 +1,71 @@ +/* + * + * 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 <string> +#include "VariantHelper.h" + +namespace qpid { +namespace store { +namespace ms_sql { + +template <class Wrapped> +VariantHelper<Wrapped>::VariantHelper() +{ + var.vt = VT_EMPTY; +} + +template <class Wrapped> +VariantHelper<Wrapped>::operator const _variant_t& () const +{ + return var; +} + +// Specialization for using _variant_t to wrap a std::string +VariantHelper<std::string>::VariantHelper(const std::string &init) +{ + if (init.empty() || init.length() == 0) { + var.vt = VT_BSTR; + var.bstrVal = NULL; + } + else { + var.SetString(init.c_str()); + } +} + +VariantHelper<std::string>& +VariantHelper<std::string>::operator=(const std::string &rhs) +{ + if (rhs.empty() || rhs.length() == 0) { + var.vt = VT_BSTR; + var.bstrVal = NULL; + } + else { + var.SetString(rhs.c_str()); + } + return *this; +} + +VariantHelper<std::string>::operator const _variant_t& () const +{ + return var; +} + +}}} // namespace qpid::store::ms_sql diff --git a/qpid/cpp/src/qpid/store/ms-sql/VariantHelper.h b/qpid/cpp/src/qpid/store/ms-sql/VariantHelper.h new file mode 100644 index 0000000000..723dbc3b76 --- /dev/null +++ b/qpid/cpp/src/qpid/store/ms-sql/VariantHelper.h @@ -0,0 +1,61 @@ +#ifndef QPID_STORE_MSSQL_VARIANTHELPER_H +#define QPID_STORE_MSSQL_VARIANTHELPER_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 <comutil.h> + +namespace qpid { +namespace store { +namespace ms_sql { + +/** + * @class VariantHelper + * + * Class template to wrap the details of working with _variant_t objects. + */ +template <class Wrapped> class VariantHelper { +private: + _variant_t var; + +public: + VariantHelper(); + VariantHelper(const Wrapped &init); + + VariantHelper& operator =(const Wrapped& rhs); + operator const _variant_t& () const; +}; + +// Specialization for using _variant_t to wrap a std::string +template<> class VariantHelper<std::string> { +private: + _variant_t var; + +public: + VariantHelper(const std::string &init); + VariantHelper& operator =(const std::string& rhs); + operator const _variant_t& () const; +}; + +}}} // namespace qpid::store::ms_sql + +#endif /* QPID_STORE_MSSQL_VARIANTHELPER_H */ diff --git a/qpid/cpp/src/qpid/sys/AggregateOutput.cpp b/qpid/cpp/src/qpid/sys/AggregateOutput.cpp new file mode 100644 index 0000000000..fc95f46fb9 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/AggregateOutput.cpp @@ -0,0 +1,88 @@ +/* + * + * 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/AggregateOutput.h" +#include "qpid/log/Statement.h" +#include <algorithm> + +namespace qpid { +namespace sys { + +AggregateOutput::AggregateOutput(OutputControl& c) : busy(false), control(c) {} + +void AggregateOutput::abort() { control.abort(); } + +void AggregateOutput::activateOutput() { control.activateOutput(); } + +void AggregateOutput::giveReadCredit(int32_t credit) { control.giveReadCredit(credit); } + +namespace { +// Clear the busy flag and notify waiting threads in destructor. +struct ScopedBusy { + bool& flag; + Monitor& monitor; + ScopedBusy(bool& f, Monitor& m) : flag(f), monitor(m) { f = true; } + ~ScopedBusy() { flag = false; monitor.notifyAll(); } +}; +} + +bool AggregateOutput::doOutput() { + Mutex::ScopedLock l(lock); + ScopedBusy sb(busy, lock); + + while (!tasks.empty()) { + OutputTask* t=tasks.front(); + tasks.pop_front(); + bool didOutput; + { + // Allow concurrent call to addOutputTask. + // removeOutputTask will wait till !busy before removing a task. + Mutex::ScopedUnlock u(lock); + didOutput = t->doOutput(); + } + if (didOutput) { + tasks.push_back(t); + return true; + } + } + return false; +} + +void AggregateOutput::addOutputTask(OutputTask* task) { + Mutex::ScopedLock l(lock); + tasks.push_back(task); +} + +void AggregateOutput::removeOutputTask(OutputTask* task) { + Mutex::ScopedLock l(lock); + while (busy) lock.wait(); + tasks.erase(std::remove(tasks.begin(), tasks.end(), task), tasks.end()); +} + +void AggregateOutput::removeAll() +{ + Mutex::ScopedLock l(lock); + while (busy) lock.wait(); + tasks.clear(); +} + + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/AggregateOutput.h b/qpid/cpp/src/qpid/sys/AggregateOutput.h new file mode 100644 index 0000000000..d7c0ff29e3 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/AggregateOutput.h @@ -0,0 +1,77 @@ +/* + * + * 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 _AggregateOutput_ +#define _AggregateOutput_ + +#include "qpid/sys/Monitor.h" +#include "qpid/sys/OutputControl.h" +#include "qpid/sys/OutputTask.h" +#include "qpid/CommonImportExport.h" + +#include <algorithm> +#include <deque> + +namespace qpid { +namespace sys { + +/** + * Holds a collection of output tasks, doOutput picks the next one to execute. + * + * Tasks are automatically removed if their doOutput() or hasOutput() returns false. + * + * Thread safe. addOutputTask may be called in one connection thread while + * doOutput is called in another. + */ + +class QPID_COMMON_CLASS_EXTERN AggregateOutput : public OutputTask, public OutputControl +{ + typedef std::deque<OutputTask*> TaskList; + + Monitor lock; + TaskList tasks; + bool busy; + OutputControl& control; + + public: + QPID_COMMON_EXTERN AggregateOutput(OutputControl& c); + + // These may be called concurrently with any function. + QPID_COMMON_EXTERN void abort(); + QPID_COMMON_EXTERN void activateOutput(); + QPID_COMMON_EXTERN void giveReadCredit(int32_t); + QPID_COMMON_EXTERN void addOutputTask(OutputTask* t); + + // These functions must not be called concurrently with each other. + QPID_COMMON_EXTERN bool doOutput(); + QPID_COMMON_EXTERN void removeOutputTask(OutputTask* t); + QPID_COMMON_EXTERN void removeAll(); + + /** Apply f to each OutputTask* in the tasks list */ + template <class F> void eachOutput(F f) { + Mutex::ScopedLock l(lock); + std::for_each(tasks.begin(), tasks.end(), f); + } +}; + +}} // namespace qpid::sys + + +#endif diff --git a/qpid/cpp/src/qpid/sys/AsynchIO.h b/qpid/cpp/src/qpid/sys/AsynchIO.h new file mode 100644 index 0000000000..41f74f7ed0 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/AsynchIO.h @@ -0,0 +1,160 @@ +#ifndef _sys_AsynchIO +#define _sys_AsynchIO +/* + * + * 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/IntegerTypes.h" +#include "qpid/CommonImportExport.h" + +#include <string.h> + +#include <boost/function.hpp> +#include <boost/shared_ptr.hpp> + +namespace qpid { +namespace sys { + +class Socket; +class Poller; + +/* + * Asynchronous acceptor: accepts connections then does a callback with the + * accepted fd + */ +class AsynchAcceptor { +public: + typedef boost::function1<void, const Socket&> Callback; + + QPID_COMMON_EXTERN static AsynchAcceptor* create(const Socket& s, Callback callback); + virtual ~AsynchAcceptor() {}; + virtual void start(boost::shared_ptr<Poller> poller) = 0; +}; + +/* + * Asynchronous connector: starts the process of initiating a connection and + * invokes a callback when completed or failed. + */ +class AsynchConnector { +public: + typedef boost::function1<void, const Socket&> ConnectedCallback; + typedef boost::function3<void, const Socket&, int, const std::string&> FailedCallback; + + // Call create() to allocate a new AsynchConnector object with the + // specified poller, addressing, and callbacks. + // This method is implemented in platform-specific code to + // create a correctly typed object. The platform code also manages + // 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, + const std::string& hostname, + const std::string& port, + ConnectedCallback connCb, + FailedCallback failCb); + virtual void start(boost::shared_ptr<Poller> poller) = 0; + virtual void stop() {}; +protected: + AsynchConnector() {} + virtual ~AsynchConnector() {} +}; + +struct AsynchIOBufferBase { + char* const bytes; + const int32_t byteCount; + int32_t dataStart; + int32_t dataCount; + + AsynchIOBufferBase(char* const b, const int32_t s) : + bytes(b), + byteCount(s), + dataStart(0), + dataCount(0) + {} + + virtual ~AsynchIOBufferBase() + {} + + void squish() { + if (dataStart != 0) { + ::memmove(bytes, bytes + dataStart, dataCount); + dataStart = 0; + } + } +}; + +/* + * Asychronous reader/writer: + * Reader accepts buffers to read into; reads into the provided buffers + * and then does a callback with the buffer and amount read. Optionally it + * can callback when there is something to read but no buffer to read it into. + * + * Writer accepts a buffer and queues it for writing; can also be given + * a callback for when writing is "idle" (ie fd is writable, but nothing + * to write). + */ +class AsynchIO { +public: + typedef AsynchIOBufferBase BufferBase; + + typedef boost::function2<void, AsynchIO&, BufferBase*> ReadCallback; + typedef boost::function1<void, AsynchIO&> EofCallback; + typedef boost::function1<void, AsynchIO&> DisconnectCallback; + typedef boost::function2<void, AsynchIO&, const Socket&> ClosedCallback; + typedef boost::function1<void, AsynchIO&> BuffersEmptyCallback; + typedef boost::function1<void, AsynchIO&> IdleCallback; + typedef boost::function1<void, AsynchIO&> RequestCallback; + + // Call create() to allocate a new AsynchIO object with the specified + // callbacks. This method is implemented in platform-specific code to + // create a correctly typed object. The platform code also manages + // deletes. To correctly manage heaps when needed, the allocate and + // delete should both be done from the same class/library. + QPID_COMMON_EXTERN static AsynchIO* create(const Socket& s, + ReadCallback rCb, + EofCallback eofCb, + DisconnectCallback disCb, + ClosedCallback cCb = 0, + BuffersEmptyCallback eCb = 0, + IdleCallback iCb = 0); +public: + virtual void queueForDeletion() = 0; + + virtual void start(boost::shared_ptr<Poller> poller) = 0; + virtual void queueReadBuffer(BufferBase* buff) = 0; + virtual void unread(BufferBase* buff) = 0; + virtual void queueWrite(BufferBase* buff) = 0; + virtual void notifyPendingWrite() = 0; + virtual void queueWriteClose() = 0; + virtual bool writeQueueEmpty() = 0; + virtual void startReading() = 0; + virtual void stopReading() = 0; + virtual void requestCallback(RequestCallback) = 0; + virtual BufferBase* getQueuedBuffer() = 0; + +protected: + // Derived class manages lifetime; must be constructed using the + // static create() method. Deletes not allowed from outside. + AsynchIO() {} + virtual ~AsynchIO() {} +}; + +}} + +#endif // _sys_AsynchIO diff --git a/qpid/cpp/src/qpid/sys/AsynchIOHandler.cpp b/qpid/cpp/src/qpid/sys/AsynchIOHandler.cpp new file mode 100644 index 0000000000..30a87d9d44 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/AsynchIOHandler.cpp @@ -0,0 +1,228 @@ +/* + * + * 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/AsynchIOHandler.h" +#include "qpid/sys/AsynchIO.h" +#include "qpid/sys/Socket.h" +#include "qpid/sys/SecuritySettings.h" +#include "qpid/framing/AMQP_HighestVersion.h" +#include "qpid/framing/ProtocolInitiation.h" +#include "qpid/log/Statement.h" + +#include <boost/bind.hpp> + +namespace qpid { +namespace sys { + +// Buffer definition +struct Buff : public AsynchIO::BufferBase { + Buff() : + AsynchIO::BufferBase(new char[65536], 65536) + {} + ~Buff() + { delete [] bytes;} +}; + +AsynchIOHandler::AsynchIOHandler(std::string id, ConnectionCodec::Factory* f) : + identifier(id), + aio(0), + factory(f), + codec(0), + readError(false), + isClient(false), + readCredit(InfiniteCredit) +{} + +AsynchIOHandler::~AsynchIOHandler() { + if (codec) + codec->closed(); + delete codec; +} + +void AsynchIOHandler::init(AsynchIO* a, int numBuffs) { + aio = a; + + // Give connection some buffers to use + for (int i = 0; i < numBuffs; i++) { + aio->queueReadBuffer(new Buff); + } +} + +void AsynchIOHandler::write(const framing::ProtocolInitiation& data) +{ + QPID_LOG(debug, "SENT [" << identifier << "] INIT(" << data << ")"); + AsynchIO::BufferBase* buff = aio->getQueuedBuffer(); + if (!buff) + buff = new Buff; + framing::Buffer out(buff->bytes, buff->byteCount); + data.encode(out); + buff->dataCount = data.encodedSize(); + aio->queueWrite(buff); +} + +void AsynchIOHandler::abort() { + // Don't disconnect if we're already disconnecting + if (!readError) { + aio->requestCallback(boost::bind(&AsynchIOHandler::eof, this, _1)); + } +} + +void AsynchIOHandler::activateOutput() { + aio->notifyPendingWrite(); +} + +// Input side +void AsynchIOHandler::giveReadCredit(int32_t credit) { + // Check whether we started in the don't about credit state + if (readCredit.boolCompareAndSwap(InfiniteCredit, credit)) + return; + // TODO In theory should be able to use an atomic operation before taking the lock + // but in practice there seems to be an unexplained race in that case + ScopedLock<Mutex> l(creditLock); + if (readCredit.fetchAndAdd(credit) != 0) + return; + assert(readCredit.get() >= 0); + if (readCredit.get() != 0) + aio->startReading(); +} + +void AsynchIOHandler::readbuff(AsynchIO& , AsynchIO::BufferBase* buff) { + if (readError) { + return; + } + + // Check here for read credit + if (readCredit.get() != InfiniteCredit) { + if (readCredit.get() == 0) { + // FIXME aconway 2009-10-01: Workaround to avoid "false wakeups". + // readbuff is sometimes called with no credit. + // This should be fixed somewhere else to avoid such calls. + aio->unread(buff); + return; + } + // TODO In theory should be able to use an atomic operation before taking the lock + // but in practice there seems to be an unexplained race in that case + ScopedLock<Mutex> l(creditLock); + if (--readCredit == 0) { + assert(readCredit.get() >= 0); + if (readCredit.get() == 0) { + aio->stopReading(); + } + } + } + + size_t decoded = 0; + if (codec) { // Already initiated + try { + decoded = codec->decode(buff->bytes+buff->dataStart, buff->dataCount); + }catch(const std::exception& e){ + QPID_LOG(error, e.what()); + readError = true; + aio->queueWriteClose(); + } + }else{ + framing::Buffer in(buff->bytes+buff->dataStart, buff->dataCount); + framing::ProtocolInitiation protocolInit; + if (protocolInit.decode(in)) { + decoded = in.getPosition(); + QPID_LOG(debug, "RECV [" << identifier << "] INIT(" << protocolInit << ")"); + try { + codec = factory->create(protocolInit.getVersion(), *this, identifier, SecuritySettings()); + if (!codec) { + //TODO: may still want to revise this... + //send valid version header & close connection. + write(framing::ProtocolInitiation(framing::highestProtocolVersion)); + readError = true; + aio->queueWriteClose(); + } + } catch (const std::exception& e) { + QPID_LOG(error, e.what()); + readError = true; + aio->queueWriteClose(); + } + } + } + // TODO: unreading needs to go away, and when we can cope + // with multiple sub-buffers in the general buffer scheme, it will + if (decoded != size_t(buff->dataCount)) { + // Adjust buffer for used bytes and then "unread them" + buff->dataStart += decoded; + buff->dataCount -= decoded; + aio->unread(buff); + } else { + // Give whole buffer back to aio subsystem + aio->queueReadBuffer(buff); + } +} + +void AsynchIOHandler::eof(AsynchIO& a) { + disconnect(a); + readError = true; + aio->queueWriteClose(); +} + +void AsynchIOHandler::closedSocket(AsynchIO&, const Socket& s) { + // If we closed with data still to send log a warning + if (!aio->writeQueueEmpty()) { + QPID_LOG(warning, "CLOSING [" << identifier << "] unsent data (probably due to client disconnect)"); + } + delete &s; + aio->queueForDeletion(); + delete this; +} + +void AsynchIOHandler::disconnect(AsynchIO&) { + QPID_LOG(debug, "DISCONNECTED [" << identifier << "]"); + if (codec) codec->closed(); +} + +// Notifications +void AsynchIOHandler::nobuffs(AsynchIO&) { +} + +void AsynchIOHandler::idle(AsynchIO&){ + if (isClient && codec == 0) { + codec = factory->create(*this, identifier, SecuritySettings()); + write(framing::ProtocolInitiation(codec->getVersion())); + return; + } + if (codec == 0) return; + try { + if (codec->canEncode()) { + // Try and get a queued buffer if not then construct new one + AsynchIO::BufferBase* buff = aio->getQueuedBuffer(); + if (!buff) buff = new Buff; + size_t encoded=codec->encode(buff->bytes, buff->byteCount); + buff->dataCount = encoded; + aio->queueWrite(buff); + } + if (codec->isClosed()) { + readError = true; + aio->queueWriteClose(); + } + } catch (const std::exception& e) { + QPID_LOG(error, e.what()); + readError = true; + aio->queueWriteClose(); + } +} + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/AsynchIOHandler.h b/qpid/cpp/src/qpid/sys/AsynchIOHandler.h new file mode 100644 index 0000000000..b9867606c4 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/AsynchIOHandler.h @@ -0,0 +1,80 @@ +#ifndef _sys_AsynchIOHandler_h +#define _sys_AsynchIOHandler_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/OutputControl.h" +#include "qpid/sys/ConnectionCodec.h" +#include "qpid/sys/AtomicValue.h" +#include "qpid/sys/Mutex.h" +#include "qpid/CommonImportExport.h" + +namespace qpid { + +namespace framing { + class ProtocolInitiation; +} + +namespace sys { + +class AsynchIO; +struct AsynchIOBufferBase; +class Socket; + +class AsynchIOHandler : public OutputControl { + std::string identifier; + AsynchIO* aio; + ConnectionCodec::Factory* factory; + ConnectionCodec* codec; + bool readError; + bool isClient; + AtomicValue<int32_t> readCredit; + static const int32_t InfiniteCredit = -1; + Mutex creditLock; + + void write(const framing::ProtocolInitiation&); + + public: + QPID_COMMON_EXTERN AsynchIOHandler(std::string id, ConnectionCodec::Factory* f); + QPID_COMMON_EXTERN ~AsynchIOHandler(); + QPID_COMMON_EXTERN void init(AsynchIO* a, int numBuffs); + + QPID_COMMON_INLINE_EXTERN void setClient() { isClient = true; } + + // Output side + QPID_COMMON_EXTERN void abort(); + QPID_COMMON_EXTERN void activateOutput(); + QPID_COMMON_EXTERN void giveReadCredit(int32_t credit); + + // Input side + QPID_COMMON_EXTERN void readbuff(AsynchIO& aio, AsynchIOBufferBase* buff); + QPID_COMMON_EXTERN void eof(AsynchIO& aio); + QPID_COMMON_EXTERN void disconnect(AsynchIO& aio); + + // Notifications + QPID_COMMON_EXTERN void nobuffs(AsynchIO& aio); + QPID_COMMON_EXTERN void idle(AsynchIO& aio); + QPID_COMMON_EXTERN void closedSocket(AsynchIO& aio, const Socket& s); +}; + +}} // namespace qpid::sys + +#endif // _sys_AsynchIOHandler_h diff --git a/qpid/cpp/src/qpid/sys/AtomicCount.h b/qpid/cpp/src/qpid/sys/AtomicCount.h new file mode 100644 index 0000000000..94580c61f3 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/AtomicCount.h @@ -0,0 +1,52 @@ +#ifndef _posix_AtomicCount_h +#define _posix_AtomicCount_h + +/* + * + * 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. + * + */ + +#include <boost/detail/atomic_count.hpp> +#include "qpid/sys/ScopedIncrement.h" + +namespace qpid { +namespace sys { + +/** + * Atomic counter. + */ +class AtomicCount { + public: + typedef ::qpid::sys::ScopedDecrement<AtomicCount> ScopedDecrement; + typedef ::qpid::sys::ScopedIncrement<AtomicCount> ScopedIncrement; + + AtomicCount(long value = 0) : count(value) {} + + void operator++() { ++count ; } + + long operator--() { return --count; } + + operator long() const { return count; } + + private: + boost::detail::atomic_count count; +}; + + +}} + + +#endif // _posix_AtomicCount_h diff --git a/qpid/cpp/src/qpid/sys/AtomicValue.h b/qpid/cpp/src/qpid/sys/AtomicValue.h new file mode 100644 index 0000000000..bf995f991e --- /dev/null +++ b/qpid/cpp/src/qpid/sys/AtomicValue.h @@ -0,0 +1,39 @@ +#ifndef QPID_SYS_ATOMICVALUE_H +#define QPID_SYS_ATOMICVALUE_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. + * + */ + +// 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" + +#else +// Fall-back to mutex locked operations if we don't have atomic ops. +#include "qpid/sys/AtomicValue_mutex.h" +#endif + +#endif /*!QPID_SYS_ATOMICVALUE_GCC_H*/ diff --git a/qpid/cpp/src/qpid/sys/AtomicValue_gcc.h b/qpid/cpp/src/qpid/sys/AtomicValue_gcc.h new file mode 100644 index 0000000000..d022b07c1d --- /dev/null +++ b/qpid/cpp/src/qpid/sys/AtomicValue_gcc.h @@ -0,0 +1,68 @@ +#ifndef QPID_SYS_ATOMICVALUE_GCC_H +#define QPID_SYS_ATOMICVALUE_GCC_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. + * + */ + +#if !defined(QPID_SYS_ATOMICVALUE_H) +#error "This file should only be included via AtomicValue.h." +#endif + +namespace qpid { +namespace sys { + +/** + * Atomic value of type T. T must be an integral type of size 1,2,4 or 8 bytes. + * All operations are atomic and preform a full memory barrier unless otherwise noted. + */ +template <class T> +class AtomicValue +{ + public: + AtomicValue(T init=0) : value(init) {} + + // 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); } + inline T operator++() { return *this += 1; } + inline T operator--() { return *this -= 1; } + + // Update and return old value. + inline T fetchAndAdd(T n) { return __sync_fetch_and_add(&value, n); } + inline T fetchAndSub(T n) { return __sync_fetch_and_sub(&value, n); } + inline T operator++(int) { return fetchAndAdd(1); } + inline T operator--(int) { return fetchAndSub(1); } + + /** 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. */ + 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; +}; + +}} // namespace qpid::sys + +#endif /*!QPID_SYS_ATOMICVALUE_GCC_H*/ diff --git a/qpid/cpp/src/qpid/sys/AtomicValue_mutex.h b/qpid/cpp/src/qpid/sys/AtomicValue_mutex.h new file mode 100644 index 0000000000..e4d433e7f5 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/AtomicValue_mutex.h @@ -0,0 +1,83 @@ +#ifndef QPID_SYS_ATOMICVALUE_MUTEX_H +#define QPID_SYS_ATOMICVALUE_MUTEX_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. + * + */ + +#if !defined(QPID_SYS_ATOMICVALUE_H) +#error "This file should only be included via AtomicValue.h." +#endif + +#include "qpid/sys/Mutex.h" + +namespace qpid { +namespace sys { + +/** + * Atomic value of type T. T must be an integral type of size 1,2,4 or 8 bytes. + * All operations are atomic and preform a full memory barrier unless otherwise noted. + */ +template <class T> +class AtomicValue +{ + public: + AtomicValue(T init=0) : value(init) {} + + // Update and return new value. + inline T operator+=(T n) { Lock l(lock); return value += n; } + inline T operator-=(T n) { Lock l(lock); return value -= n; } + inline T operator++() { return *this += 1; } + inline T operator--() { return *this -= 1; } + + // Update and return old value. + inline T fetchAndAdd(T n) { Lock l(lock); T old=value; value += n; return old; } + inline T fetchAndSub(T n) { Lock l(lock); T old=value; value -= n; return old; } + inline T operator++(int) { return fetchAndAdd(1); } + inline T operator--(int) { return fetchAndSub(1); } + + AtomicValue& operator=(T newval) { Lock l(lock); value = newval; return *this; } + + /** If current value == testval then set to newval. Returns the old value. */ + T valueCompareAndSwap(T testval, T newval) { + Lock l(lock); + T old=value; + if (value == testval) value = newval; + return old; + } + + /** If current value == testval then set to newval. Returns true if the swap was performed. */ + bool boolCompareAndSwap(T testval, T newval) { + Lock l(lock); + if (value == testval) { value = newval; return true; } + return false; + } + + T get() const { Lock l(lock); return value; } + + private: + typedef Mutex::ScopedLock Lock; + T value; + mutable Mutex lock; +}; + +}} // namespace qpid::sys + +#endif /*!QPID_SYS_ATOMICVALUE_MUTEX_H*/ diff --git a/qpid/cpp/src/qpid/sys/BlockingQueue.h b/qpid/cpp/src/qpid/sys/BlockingQueue.h new file mode 100644 index 0000000000..ca6b529930 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/BlockingQueue.h @@ -0,0 +1,129 @@ +#ifndef QPID_SYS_BLOCKINGQUEUE_H +#define QPID_SYS_BLOCKINGQUEUE_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/Waitable.h" + +#include <queue> + +namespace qpid { +namespace sys { + +/** + * A simple blocking queue template + */ +template <class T> +class BlockingQueue +{ + mutable sys::Waitable waitable; + std::queue<T> queue; + +public: + BlockingQueue() {} + ~BlockingQueue() { close(); } + + /** Pop from the queue, block up to timeout if empty. + *@param result Set to value popped from queue. + *@param timeout Defaults to infinite. + *@return true if result was set, false if queue empty after timeout. + */ + bool pop(T& result, Duration timeout=TIME_INFINITE) { + Mutex::ScopedLock l(waitable); + { + Waitable::ScopedWait w(waitable); + if (timeout == TIME_INFINITE) { + while (queue.empty()) waitable.wait(); + } else if (timeout) { + AbsTime deadline(now(),timeout); + while (queue.empty() && deadline > now()) waitable.wait(deadline); + } else { + //ensure zero timeout pop does not miss the fact that + //queue is closed + waitable.checkException(); + } + } + if (queue.empty()) return false; + result = queue.front(); + queue.pop(); + if (!queue.empty()) + waitable.notify(); // Notify another waiter. + return true; + } + + T pop(Duration timeout=TIME_INFINITE) { + T result; + bool ok = pop(result, timeout); + if (!ok) + throw Exception("Timed out waiting on a blocking queue"); + return result; + } + + /** Push a value onto the queue. + * Note it is not an error to push onto a closed queue. + */ + void push(const T& t) { + Mutex::ScopedLock l(waitable); + queue.push(t); + waitable.notify(); // Notify a waiter. + } + + /** + * Close the queue. + *@ex exception to throw to waiting threads. ClosedException by default. + */ + void close(const ExceptionHolder& ex=ExceptionHolder(new ClosedException())) + { + Mutex::ScopedLock l(waitable); + if (!waitable.hasException()) { + waitable.setException(ex); + waitable.notifyAll(); + waitable.waitWaiters(); // Ensure no threads are still waiting. + } + } + + /** Open a closed queue. */ + void open() { + Mutex::ScopedLock l(waitable); + waitable.resetException(); + } + + bool isClosed() const { + Mutex::ScopedLock l(waitable); + return waitable.hasException(); + } + + bool empty() const { + Mutex::ScopedLock l(waitable); + return queue.empty(); + } + size_t size() const { + Mutex::ScopedLock l(waitable); + return queue.size(); + } +}; + +}} + + + +#endif /*!QPID_SYS_BLOCKINGQUEUE_H*/ diff --git a/qpid/cpp/src/qpid/sys/ClusterSafe.cpp b/qpid/cpp/src/qpid/sys/ClusterSafe.cpp new file mode 100644 index 0000000000..dd37615145 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/ClusterSafe.cpp @@ -0,0 +1,66 @@ +/* + * + * 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 "ClusterSafe.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Thread.h" +#include <stdlib.h> + +namespace qpid { +namespace sys { + +namespace { +bool inCluster = false; +QPID_TSS bool inContext = false; +} + +bool isClusterSafe() { return !inCluster || inContext; } + +void assertClusterSafe() { + if (!isClusterSafe()) { + QPID_LOG(critical, "Modified cluster state outside of cluster context"); + ::abort(); + } +} + +ClusterSafeScope::ClusterSafeScope() { + save = inContext; + inContext = true; +} + +ClusterSafeScope::~ClusterSafeScope() { + assert(inContext); + inContext = save; +} + +ClusterUnsafeScope::ClusterUnsafeScope() { + save = inContext; + inContext = false; +} + +ClusterUnsafeScope::~ClusterUnsafeScope() { + assert(!inContext); + inContext = save; +} + +void enableClusterSafe() { inCluster = true; } + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/ClusterSafe.h b/qpid/cpp/src/qpid/sys/ClusterSafe.h new file mode 100644 index 0000000000..27e4eb46a5 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/ClusterSafe.h @@ -0,0 +1,87 @@ +#ifndef QPID_SYS_CLUSTERSAFE_H +#define QPID_SYS_CLUSTERSAFE_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/CommonImportExport.h" + +namespace qpid { +namespace sys { + +/** + * Assertion to add to code that modifies clustered state. + * + * In a non-clustered broker this is a no-op. + * + * In a clustered broker, checks that it is being called + * in a context where it is safe to modify clustered state. + * If not it aborts the process as this is a serious bug. + * + * This function is in the common library rather than the cluster + * library because it is called by code in the broker library. + */ +QPID_COMMON_EXTERN void assertClusterSafe(); + +/** + * In a non-clustered broker, returns true. + * + * In a clustered broker returns true if we are in a context where it + * is safe to modify cluster state. + * + * This function is in the common library rather than the cluster + * library because it is called by code in the broker library. + */ +QPID_COMMON_EXTERN bool isClusterSafe(); + +/** + * Mark a scope as cluster safe. Sets isClusterSafe in constructor and resets + * to previous value in destructor. + */ +class ClusterSafeScope { + public: + ClusterSafeScope(); + ~ClusterSafeScope(); + private: + bool save; +}; + +/** + * 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. + */ +void enableClusterSafe(); + +}} // namespace qpid::sys + +#endif /*!QPID_SYS_CLUSTERSAFE_H*/ diff --git a/qpid/cpp/src/qpid/sys/Codec.h b/qpid/cpp/src/qpid/sys/Codec.h new file mode 100644 index 0000000000..ace721fbcc --- /dev/null +++ b/qpid/cpp/src/qpid/sys/Codec.h @@ -0,0 +1,52 @@ +#ifndef QPID_SYS_CODEC_H +#define QPID_SYS_CODEC_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 <cstddef> + +namespace qpid { +namespace sys { + +/** + * Generic codec interface + */ +class Codec +{ + public: + virtual ~Codec() {} + + /** Decode from buffer, return number of bytes decoded. + * @return may be less than size if there was incomplete + * data at the end of the buffer. + */ + virtual std::size_t decode(const char* buffer, std::size_t size) = 0; + + + /** Encode into buffer, return number of bytes encoded */ + virtual std::size_t encode(const char* buffer, std::size_t size) = 0; + + /** Return true if we have data to encode */ + virtual bool canEncode() = 0; +}; +}} // namespace qpid::sys + +#endif /*!QPID_SYS_CODEC_H*/ diff --git a/qpid/cpp/src/qpid/sys/ConnectionCodec.h b/qpid/cpp/src/qpid/sys/ConnectionCodec.h new file mode 100644 index 0000000000..c2890f06dc --- /dev/null +++ b/qpid/cpp/src/qpid/sys/ConnectionCodec.h @@ -0,0 +1,68 @@ +#ifndef QPID_SYS_CONNECTION_CODEC_H +#define QPID_SYS_CONNECTION_CODEC_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/Codec.h" +#include "qpid/framing/ProtocolVersion.h" + +namespace qpid { + +namespace sys { + +class InputHandlerFactory; +class OutputControl; +struct SecuritySettings; + +/** + * Interface of coder/decoder for a connection of a specific protocol + * version. + */ +class ConnectionCodec : public Codec { + public: + virtual ~ConnectionCodec() {} + + /** Network connection was closed from other end. */ + virtual void closed() = 0; + + virtual bool isClosed() const = 0; + + virtual framing::ProtocolVersion getVersion() const = 0; + + struct Factory { + virtual ~Factory() {} + + /** Return 0 if version unknown */ + virtual ConnectionCodec* create( + framing::ProtocolVersion, OutputControl&, const std::string& id, + const SecuritySettings& + ) = 0; + + /** Return "preferred" codec for outbound connections. */ + virtual ConnectionCodec* create( + OutputControl&, const std::string& id, const SecuritySettings& + ) = 0; + }; +}; + +}} // namespace qpid::sys + +#endif /*!QPID_SYS_CONNECTION_CODEC_H*/ diff --git a/qpid/cpp/src/qpid/sys/ConnectionInputHandler.h b/qpid/cpp/src/qpid/sys/ConnectionInputHandler.h new file mode 100644 index 0000000000..92de808308 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/ConnectionInputHandler.h @@ -0,0 +1,52 @@ +/* + * + * 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 _ConnectionInputHandler_ +#define _ConnectionInputHandler_ + +#include "qpid/framing/InputHandler.h" +#include "qpid/sys/OutputTask.h" +#include "qpid/sys/TimeoutHandler.h" + +namespace qpid { +namespace sys { + + +/** + * ConnectionInputHandler provides methods to process incoming frames + * using InputHandler::receive() and to generate outgoing messages in + * OutputTask::doOutput() + * + */ + + class ConnectionInputHandler : + public qpid::framing::InputHandler, + public TimeoutHandler, public OutputTask + { + public: + + virtual void closed() = 0; + }; + +} +} + + +#endif diff --git a/qpid/cpp/src/qpid/sys/ConnectionInputHandlerFactory.h b/qpid/cpp/src/qpid/sys/ConnectionInputHandlerFactory.h new file mode 100644 index 0000000000..9bb7e13686 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/ConnectionInputHandlerFactory.h @@ -0,0 +1,54 @@ +/* + * + * 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 _ConnectionInputHandlerFactory_ +#define _ConnectionInputHandlerFactory_ + +#include <boost/noncopyable.hpp> +#include <string> + +namespace qpid { +namespace sys { + +class ConnectionOutputHandler; +class ConnectionInputHandler; + +/** + * Callback interface used by the Acceptor to + * create a ConnectionInputHandler for each new connection. + */ +class ConnectionInputHandlerFactory : private boost::noncopyable +{ + public: + /** + *@param out handler for connection output. + *@param id identify the connection for management purposes. + */ + virtual ConnectionInputHandler* create(ConnectionOutputHandler* out, + const std::string& id, + bool isClient) = 0; + + virtual ~ConnectionInputHandlerFactory(){} +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/sys/ConnectionOutputHandler.h b/qpid/cpp/src/qpid/sys/ConnectionOutputHandler.h new file mode 100644 index 0000000000..421dd7c269 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/ConnectionOutputHandler.h @@ -0,0 +1,43 @@ +/* + * + * 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 _ConnectionOutputHandler_ +#define _ConnectionOutputHandler_ + +#include "qpid/framing/OutputHandler.h" +#include "qpid/sys/OutputControl.h" + +namespace qpid { +namespace sys { + +/** + * Provides the output handler associated with a connection. + */ +class ConnectionOutputHandler : public virtual qpid::framing::OutputHandler, public OutputControl +{ + public: + virtual void close() = 0; + virtual size_t getBuffered() const { return 0; } +}; + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/sys/ConnectionOutputHandlerPtr.h b/qpid/cpp/src/qpid/sys/ConnectionOutputHandlerPtr.h new file mode 100644 index 0000000000..95a08d15ae --- /dev/null +++ b/qpid/cpp/src/qpid/sys/ConnectionOutputHandlerPtr.h @@ -0,0 +1,56 @@ +#ifndef QPID_SYS_CONNECTIONOUTPUTHANDLERPTR_H +#define QPID_SYS_CONNECTIONOUTPUTHANDLERPTR_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/ConnectionOutputHandler.h" + +namespace qpid { +namespace sys { + +/** + * A ConnectionOutputHandler that delegates to another + * ConnectionOutputHandler. Allows the "real" ConnectionOutputHandler + * to be changed without updating all the pointers/references + * using the ConnectionOutputHandlerPtr + */ +class ConnectionOutputHandlerPtr : public ConnectionOutputHandler +{ + public: + ConnectionOutputHandlerPtr(ConnectionOutputHandler* p) : next(p) { assert(next); } + void set(ConnectionOutputHandler* p) { next = p; assert(next); } + ConnectionOutputHandler* get() { return next; } + const ConnectionOutputHandler* get() const { return next; } + + void close() { next->close(); } + size_t getBuffered() const { return next->getBuffered(); } + void abort() { next->abort(); } + void activateOutput() { next->activateOutput(); } + void giveReadCredit(int32_t credit) { next->giveReadCredit(credit); } + void send(framing::AMQFrame& f) { next->send(f); } + + private: + ConnectionOutputHandler* next; +}; +}} // namespace qpid::sys + +#endif /*!QPID_SYS_CONNECTIONOUTPUTHANDLERPTR_H*/ diff --git a/qpid/cpp/src/qpid/sys/CopyOnWriteArray.h b/qpid/cpp/src/qpid/sys/CopyOnWriteArray.h new file mode 100644 index 0000000000..45a231dfd8 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/CopyOnWriteArray.h @@ -0,0 +1,156 @@ +#ifndef QPID_SYS_COPYONWRITEARRAY_H +#define QPID_SYS_COPYONWRITEARRAY_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/Mutex.h" +#include <algorithm> +#include <vector> +#include <boost/shared_ptr.hpp> + +namespace qpid { +namespace sys { + +/** + * An array that copies on adding/removing element allowing lock-free + * iteration. + */ +template <class T> +class CopyOnWriteArray +{ +public: + typedef boost::shared_ptr<const std::vector<T> > ConstPtr; + + CopyOnWriteArray() {} + CopyOnWriteArray(const CopyOnWriteArray& c) : array(c.array) {} + + void add(T& t) + { + Mutex::ScopedLock l(lock); + ArrayPtr copy(array ? new std::vector<T>(*array) : new std::vector<T>()); + copy->push_back(t); + array = copy; + } + + bool remove(T& t) + { + Mutex::ScopedLock l(lock); + if (array && std::find(array->begin(), array->end(), t) != array->end()) { + ArrayPtr copy(new std::vector<T>(*array)); + copy->erase(std::find(copy->begin(), copy->end(), t)); + array = copy; + return true; + } else { + return false; + } + } + + bool clear() + { + Mutex::ScopedLock l(lock); + if (array && !array->empty()) { + ArrayPtr copy; + array = copy; + return true; + } else { + return false; + } + } + + template <class F> + bool add_unless(T& t, F f) + { + Mutex::ScopedLock l(lock); + if (array && std::find_if(array->begin(), array->end(), f) != array->end()) { + return false; + } else { + ArrayPtr copy(array ? new std::vector<T>(*array) : new std::vector<T>()); + copy->push_back(t); + array = copy; + return true; + } + } + + template <class F> + bool remove_if(F f) + { + Mutex::ScopedLock l(lock); + if (array && std::find_if(array->begin(), array->end(), f) != array->end()) { + ArrayPtr copy(new std::vector<T>(*array)); + copy->erase(std::remove_if(copy->begin(), copy->end(), f), copy->end()); + array = copy; + return true; + } + return false; + } + + template <class TestFn, class ModifierFn> + bool modify_if(TestFn f, ModifierFn & m) + { + if (!array) + return false; + { + Mutex::ScopedLock l(lock); + if (std::find_if(array->begin(), array->end(), f) != array->end()) + { + ArrayPtr copy(new std::vector<T>(*array)); + m(*std::find_if(copy->begin(), copy->end(), f)); + array = copy; + return true; + } + } + return false; + } + + template <class F> + F for_each(F f) + { + ArrayPtr a; + { + Mutex::ScopedLock l(lock); + a = array; + } + if (!a) return f; + return std::for_each(a->begin(), a->end(), f); + } + + ConstPtr snapshot() + { + ConstPtr a; + { + Mutex::ScopedLock l(lock); + a = array; + } + return a; + } + +private: + typedef boost::shared_ptr< std::vector<T> > ArrayPtr; + Mutex lock; + ArrayPtr array; +}; + +}} + + + +#endif /*!QPID_SYS_COPYONWRITEARRAY_H*/ diff --git a/qpid/cpp/src/qpid/sys/DeletionManager.h b/qpid/cpp/src/qpid/sys/DeletionManager.h new file mode 100644 index 0000000000..c1fea19f30 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/DeletionManager.h @@ -0,0 +1,162 @@ +#ifndef _sys_DeletionManager_h +#define _sys_DeletionManager_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 <vector> +#include <algorithm> +#include <boost/shared_ptr.hpp> + +namespace qpid { +namespace sys { + +struct deleter +{ + template <typename T> + void operator()(T* ptr){ delete ptr;} +}; + +/** + * DeletionManager keeps track of handles that need to be deleted but may still be + * in use by one of the threads concurrently. + * + * The mode of operation is like this: + * - When we want to delete but we might still be using the handle we + * * Transfer ownership of the handle to this class + * * Mark the handle as (potentially) in use by every thread + * - Then subsequently at points where the thread code knows it isn't + * using any handles it declares that it is using no handles + * - When the last thread declares no use of a handle it automatically + * gets deleted by the shared_ptr implementation + * + * The class only has static members and data and so can only be used once for + * any particular handle type + */ +template <typename H> +class DeletionManager +{ + struct ThreadStatus; + +public: + // Mark every thread as using the handle - it will be deleted + // below after every thread marks the handle as unused + static void markForDeletion(H* handle) { + allThreadsStatuses.addHandle(shared_ptr(handle)); + } + + // Mark this thread is not using any handle - + // handles get deleted here when no one else + // is using them either + static void markAllUnusedInThisThread() { + ThreadStatus* threadStatus = getThreadStatus(); + ScopedLock<Mutex> l(threadStatus->lock); + + // The actual deletions will happen here when all the shared_ptr + // ref counts hit 0 (that is when every thread marks the handle unused) + threadStatus->handles.clear(); + } + + static void destroyThreadState() { + ThreadStatus* threadStatus = getThreadStatus(); + allThreadsStatuses.delThreadStatus(threadStatus); + delete threadStatus; + threadStatus = 0; + } + +private: + + static ThreadStatus*& getThreadStatus() { + static __thread ThreadStatus* threadStatus = 0; + + // Thread local vars can't be dynamically constructed so we need + // to check whether we've made it yet and construct it if not + // (no locking necessary for the check as it's thread local!) + if (!threadStatus) { + threadStatus = new ThreadStatus; + allThreadsStatuses.addThreadStatus(threadStatus); + } + + return threadStatus; + } + + typedef boost::shared_ptr<H> shared_ptr; + + // In theory we know that we never need more handles than the number of + // threads runnning so we could use a fixed size array. However at this point + // in the code we don't have easy access to this information. + struct ThreadStatus + { + Mutex lock; + std::vector<shared_ptr> handles; + }; + + class AllThreadsStatuses + { + Mutex lock; + std::vector<ThreadStatus*> statuses; + + struct handleAdder + { + shared_ptr handle; + + handleAdder(shared_ptr h): handle(h) {} + + void operator()(ThreadStatus* ptr) { + ScopedLock<Mutex> l(ptr->lock); + ptr->handles.push_back(handle); + } + }; + + public: + // Need this to be able to do static initialisation + explicit AllThreadsStatuses(int) {} + + ~AllThreadsStatuses() { + ScopedLock<Mutex> l(lock); + std::for_each(statuses.begin(), statuses.end(), deleter()); + } + + void addThreadStatus(ThreadStatus* t) { + ScopedLock<Mutex> l(lock); + statuses.push_back(t); + } + + void delThreadStatus(ThreadStatus* t) { + ScopedLock<Mutex> l(lock); + typename std::vector<ThreadStatus*>::iterator it = + std::find(statuses.begin(),statuses.end(), t); + if (it != statuses.end()) { + statuses.erase(it); + } + } + + void addHandle(shared_ptr h) { + ScopedLock<Mutex> l(lock); + std::for_each(statuses.begin(), statuses.end(), handleAdder(h)); + } + }; + + static AllThreadsStatuses allThreadsStatuses; +}; + +}} +#endif // _sys_DeletionManager_h diff --git a/qpid/cpp/src/qpid/sys/DispatchHandle.cpp b/qpid/cpp/src/qpid/sys/DispatchHandle.cpp new file mode 100644 index 0000000000..5d6fc4e72f --- /dev/null +++ b/qpid/cpp/src/qpid/sys/DispatchHandle.cpp @@ -0,0 +1,352 @@ +/* + * + * 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/DispatchHandle.h" +#include "qpid/log/Statement.h" + +#include <algorithm> + +#include <boost/cast.hpp> + +#include <assert.h> + +namespace qpid { +namespace sys { + +DispatchHandle::DispatchHandle(const IOHandle& h, Callback rCb, Callback wCb, Callback dCb) : + PollerHandle(h), + readableCallback(rCb), + writableCallback(wCb), + disconnectedCallback(dCb), + state(IDLE) +{ +} + + +DispatchHandle::~DispatchHandle() { +} + +void DispatchHandle::startWatch(Poller::shared_ptr poller0) { + bool r = readableCallback; + bool w = writableCallback; + + ScopedLock<Mutex> lock(stateLock); + assert(state == IDLE); + + poller = poller0; + poller->registerHandle(*this); + state = WAITING; + Poller::Direction dir = r ? + ( w ? Poller::INOUT : Poller::INPUT ) : + ( w ? Poller::OUTPUT : Poller::NONE ); + poller->monitorHandle(*this, dir); +} + +void DispatchHandle::rewatch() { + bool r = readableCallback; + bool w = writableCallback; + if (!r && !w) { + return; + } + Poller::Direction dir = r ? + ( w ? Poller::INOUT : Poller::INPUT ) : + ( w ? Poller::OUTPUT : Poller::NONE ); + + ScopedLock<Mutex> lock(stateLock); + switch(state) { + case IDLE: + case STOPPING: + case DELETING: + return; + default: + break; + } + assert(poller); + poller->monitorHandle(*this, dir); +} + +void DispatchHandle::rewatchRead() { + if (!readableCallback) { + return; + } + + ScopedLock<Mutex> lock(stateLock); + switch(state) { + case IDLE: + case STOPPING: + case DELETING: + return; + default: + break; + } + assert(poller); + poller->monitorHandle(*this, Poller::INPUT); +} + +void DispatchHandle::rewatchWrite() { + if (!writableCallback) { + return; + } + + ScopedLock<Mutex> lock(stateLock); + switch(state) { + case IDLE: + case STOPPING: + case DELETING: + return; + default: + break; + } + assert(poller); + poller->monitorHandle(*this, Poller::OUTPUT); +} + +void DispatchHandle::unwatchRead() { + if (!readableCallback) { + return; + } + + ScopedLock<Mutex> lock(stateLock); + switch(state) { + case IDLE: + case STOPPING: + case DELETING: + return; + default: + break; + } + assert(poller); + poller->unmonitorHandle(*this, Poller::INPUT); +} + +void DispatchHandle::unwatchWrite() { + if (!writableCallback) { + return; + } + + ScopedLock<Mutex> lock(stateLock); + switch(state) { + case IDLE: + case STOPPING: + case DELETING: + return; + default: + break; + } + assert(poller); + poller->unmonitorHandle(*this, Poller::OUTPUT); +} + +void DispatchHandle::unwatch() { + ScopedLock<Mutex> lock(stateLock); + switch(state) { + case IDLE: + case STOPPING: + case DELETING: + return; + default: + break; + } + assert(poller); + poller->unmonitorHandle(*this, Poller::INOUT); +} + +void DispatchHandle::stopWatch() { + ScopedLock<Mutex> lock(stateLock); + switch (state) { + case IDLE: + assert(state != IDLE); + return; + case STOPPING: + assert(state != STOPPING); + return; + case CALLING: + state = STOPPING; + break; + case WAITING: + state = IDLE; + break; + case DELETING: + return; + } + assert(poller); + poller->unregisterHandle(*this); + poller.reset(); +} + +// If we are in the IDLE/STOPPING state we can't do the callback as we've +// not/no longer got the fd registered in any poller +void DispatchHandle::call(Callback iCb) { + assert(iCb); + ScopedLock<Mutex> lock(stateLock); + switch (state) { + case IDLE: + case STOPPING: + case DELETING: + return; + default: + interruptedCallbacks.push(iCb); + assert(poller); + (void) poller->interrupt(*this); + } +} + +// The slightly strange switch structure +// is to ensure that the lock is released before +// we do the delete +void DispatchHandle::doDelete() { + { + ScopedLock<Mutex> lock(stateLock); + // Ensure that we're no longer watching anything + switch (state) { + case IDLE: + state = DELETING; + break; + case STOPPING: + state = DELETING; + return; + case WAITING: + state = DELETING; + assert(poller); + (void) poller->interrupt(*this); + poller->unregisterHandle(*this); + return; + case CALLING: + state = DELETING; + assert(poller); + poller->unregisterHandle(*this); + return; + case DELETING: + return; + } + } + // If we're IDLE we can do this right away + delete this; +} + +void DispatchHandle::processEvent(Poller::EventType type) { + + // Phase I + { + ScopedLock<Mutex> lock(stateLock); + + switch(state) { + case IDLE: + // Can get here if a non connection thread stops watching + // whilst we were stuck in the above lock + return; + case WAITING: + state = CALLING; + break; + case CALLING: + assert(state!=CALLING); + return; + case STOPPING: + assert(state!=STOPPING); + return; + case DELETING: + // Need to make sure we clean up any pending callbacks in this case + std::swap(callbacks, interruptedCallbacks); + goto saybyebye; + } + + std::swap(callbacks, interruptedCallbacks); + } + + // Do callbacks - whilst we are doing the callbacks we are prevented from processing + // the same handle until we re-enable it. To avoid rentering the callbacks for a single + // handle re-enabling in the callbacks is actually deferred until they are complete. + try { + switch (type) { + case Poller::READABLE: + readableCallback(*this); + break; + case Poller::WRITABLE: + writableCallback(*this); + break; + case Poller::READ_WRITABLE: + readableCallback(*this); + writableCallback(*this); + break; + case Poller::DISCONNECTED: + if (disconnectedCallback) { + disconnectedCallback(*this); + } + break; + case Poller::INTERRUPTED: + { + // We'll actually do the interrupt below + } + break; + default: + assert(false); + } + + // If we have any callbacks do them now - + // (because we use a copy from before the previous callbacks we won't + // do anything yet that was just added) + while (callbacks.size() > 0) { + { + ScopedLock<Mutex> lock(stateLock); + switch (state) { + case DELETING: + goto finishcallbacks; + default: + break; + } + } + Callback cb = callbacks.front(); + assert(cb); + cb(*this); + callbacks.pop(); + } + } catch (std::exception& e) { + // One of the callbacks threw an exception - that's not allowed + QPID_LOG(error, "Caught exception in state: " << state << " with event: " << type << ": " << e.what()); + // It would be nice to clean up and delete ourselves here, but we can't + } + +finishcallbacks: + { + ScopedLock<Mutex> lock(stateLock); + switch (state) { + case IDLE: + assert(state!=IDLE); + return; + case STOPPING: + state = IDLE; + return; + case WAITING: + assert(state!=WAITING); + return; + case CALLING: + state = WAITING; + return; + case DELETING: + break; + } + } + +saybyebye: + delete this; +} + +}} diff --git a/qpid/cpp/src/qpid/sys/DispatchHandle.h b/qpid/cpp/src/qpid/sys/DispatchHandle.h new file mode 100644 index 0000000000..115a3c44f7 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/DispatchHandle.h @@ -0,0 +1,150 @@ +#ifndef _sys_DispatchHandle_h +#define _sys_DispatchHandle_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/Poller.h" +#include "qpid/sys/Mutex.h" +#include "qpid/CommonImportExport.h" +#include <boost/function.hpp> + +#include <queue> + +namespace qpid { +namespace sys { + +class DispatchHandleRef; +/** + * In order to have your own handle (file descriptor on Unix) watched by the poller + * you need to: + * + * - Subclass IOHandle, in the constructor supply an appropriate + * IOHandlerPrivate object for the platform. + * + * - Construct a DispatchHandle passing it your IOHandle and + * callback functions for read, write and disconnect events. + * + * - Ensure the DispatchHandle is not deleted until the poller is no longer using it. + * TODO: astitcher document DispatchHandleRef to simplify this. + * + * When an event occurs on the handle, the poller calls the relevant callback and + * stops watching that handle. Your callback can call rewatch() or related functions + * to re-enable polling. + */ +class DispatchHandle : public PollerHandle { + friend class DispatchHandleRef; +public: + typedef boost::function1<void, DispatchHandle&> Callback; + typedef std::queue<Callback> CallbackQueue; + +private: + Callback readableCallback; + Callback writableCallback; + Callback disconnectedCallback; + CallbackQueue interruptedCallbacks; + CallbackQueue callbacks; // Double buffer + Poller::shared_ptr poller; + Mutex stateLock; + enum { + IDLE, + STOPPING, + WAITING, + CALLING, + DELETING + } state; + +public: + /** + * Provide a handle to poll and a set of callbacks. Note + * callbacks can be 0, meaning you are not interested in that + * event. + * + *@param h: the handle to watch. The IOHandle encapsulates a + * platfrom-specific handle to an IO object (e.g. a file descriptor + * on Unix.) + *@param rCb Callback called when the handle is readable. + *@param wCb Callback called when the handle is writable. + *@param dCb Callback called when the handle is disconnected. + */ + QPID_COMMON_EXTERN DispatchHandle(const IOHandle& h, Callback rCb, Callback wCb, Callback dCb); + QPID_COMMON_EXTERN ~DispatchHandle(); + + /** Add this DispatchHandle to the poller to be watched. */ + QPID_COMMON_EXTERN void startWatch(Poller::shared_ptr poller); + + /** Resume watching for all non-0 callbacks. */ + QPID_COMMON_EXTERN void rewatch(); + /** Resume watching for read only. */ + QPID_COMMON_EXTERN void rewatchRead(); + + /** Resume watching for write only. */ + QPID_COMMON_EXTERN void rewatchWrite(); + + /** Stop watching temporarily. The DispatchHandle remains + associated with the poller and can be re-activated using + rewatch. */ + QPID_COMMON_EXTERN void unwatch(); + /** Stop watching for read */ + QPID_COMMON_EXTERN void unwatchRead(); + /** Stop watching for write */ + QPID_COMMON_EXTERN void unwatchWrite(); + + /** Stop watching permanently. Disassociates from the poller. */ + QPID_COMMON_EXTERN void stopWatch(); + + /** Interrupt watching this handle and make a serialised callback that respects the + * same exclusivity guarantees as the other callbacks + */ + QPID_COMMON_EXTERN void call(Callback iCb); + +protected: + QPID_COMMON_EXTERN void doDelete(); + +private: + QPID_COMMON_EXTERN void processEvent(Poller::EventType dir); +}; + +class DispatchHandleRef { + DispatchHandle* ref; + +public: + typedef boost::function1<void, DispatchHandle&> Callback; + DispatchHandleRef(const IOHandle& h, Callback rCb, Callback wCb, Callback dCb) : + ref(new DispatchHandle(h, rCb, wCb, dCb)) + {} + + ~DispatchHandleRef() { ref->doDelete(); } + + void startWatch(Poller::shared_ptr poller) { ref->startWatch(poller); } + void rewatch() { ref->rewatch(); } + void rewatchRead() { ref->rewatchRead(); } + void rewatchWrite() { ref->rewatchWrite(); } + void unwatch() { ref->unwatch(); } + void unwatchRead() { ref->unwatchRead(); } + void unwatchWrite() { ref->unwatchWrite(); } + void stopWatch() { ref->stopWatch(); } + void call(Callback iCb) { ref->call(iCb); } +}; + +}} + +#endif // _sys_DispatchHandle_h diff --git a/qpid/cpp/src/qpid/sys/Dispatcher.cpp b/qpid/cpp/src/qpid/sys/Dispatcher.cpp new file mode 100644 index 0000000000..5f52dcd990 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/Dispatcher.cpp @@ -0,0 +1,40 @@ +/* + * + * 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/Dispatcher.h" + +#include <assert.h> + +namespace qpid { +namespace sys { + +Dispatcher::Dispatcher(Poller::shared_ptr poller0) : + poller(poller0) { +} + +Dispatcher::~Dispatcher() { +} + +void Dispatcher::run() { + poller->run(); +} + +}} diff --git a/qpid/cpp/src/qpid/sys/Dispatcher.h b/qpid/cpp/src/qpid/sys/Dispatcher.h new file mode 100644 index 0000000000..e8213d0579 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/Dispatcher.h @@ -0,0 +1,44 @@ +#ifndef _sys_Dispatcher_h +#define _sys_Dispatcher_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/Poller.h" +#include "qpid/sys/Runnable.h" +#include "qpid/CommonImportExport.h" + +namespace qpid { +namespace sys { + +class Dispatcher : public Runnable { + const Poller::shared_ptr poller; + +public: + QPID_COMMON_EXTERN Dispatcher(Poller::shared_ptr poller); + QPID_COMMON_EXTERN ~Dispatcher(); + + QPID_COMMON_EXTERN void run(); +}; + +}} + +#endif // _sys_Dispatcher_h diff --git a/qpid/cpp/src/qpid/sys/FileSysDir.h b/qpid/cpp/src/qpid/sys/FileSysDir.h new file mode 100755 index 0000000000..ffe7823f0a --- /dev/null +++ b/qpid/cpp/src/qpid/sys/FileSysDir.h @@ -0,0 +1,62 @@ +#ifndef QPID_SYS_FILESYSDIR_H +#define QPID_SYS_FILESYSDIR_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 <string> + +namespace qpid { +namespace sys { + +/** + * @class FileSysDir + * + * Represents a filesystem directory accessible from the local host. + * This class simply checks existence of, and creates, a directory. It could + * be added to later to list contents, etc. + */ +class FileSysDir +{ + const std::string dirPath; + + public: + + FileSysDir (std::string path) : dirPath(path) {} + ~FileSysDir () {} + + /** + * Check to see if the directory exists and is a directory. Throws an + * exception if there is an error checking existence or if the path + * exists but is not a directory. + * + * @retval true if the path exists and is a directory. + * @retval false if the path does not exist. + */ + bool exists (void) const; + + void mkdir(void); + + std::string getPath () { return dirPath; } +}; + +}} // namespace qpid::sys + +#endif /*!QPID_SYS_FILESYSDIR_H*/ diff --git a/qpid/cpp/src/qpid/sys/Fork.h b/qpid/cpp/src/qpid/sys/Fork.h new file mode 100644 index 0000000000..4ec061f7bc --- /dev/null +++ b/qpid/cpp/src/qpid/sys/Fork.h @@ -0,0 +1,24 @@ +#ifndef QPID_SYS_FORK_H +#define QPID_SYS_FORK_H + +/* + * + * 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. + * + */ + +#include "posix/Fork.h" + +#endif /*!QPID_SYS_FORK_H*/ diff --git a/qpid/cpp/src/qpid/sys/LockFile.h b/qpid/cpp/src/qpid/sys/LockFile.h new file mode 100644 index 0000000000..14a76cbf3e --- /dev/null +++ b/qpid/cpp/src/qpid/sys/LockFile.h @@ -0,0 +1,64 @@ +#ifndef _sys_LockFile_h +#define _sys_LockFile_h + +/* + * + * Copyright (c) 2008 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. + * + */ + +#include <boost/noncopyable.hpp> +#include <boost/shared_ptr.hpp> +#include <string> + +#include "qpid/CommonImportExport.h" +#include "qpid/sys/IntegerTypes.h" + +namespace qpid { +namespace sys { + +class LockFilePrivate; + +/** + * @class LockFile + * + * LockFile represents a locked file suitable for a coarse-grain system + * lock. For example, the broker uses this to ensure that only one broker + * runs. A common usage idiom is to store the current "owner" process ID + * in the lock file - if the lock file exists, but the stored process ID + * doesn't, the old owner has probably died without cleaning up the lock + * file. + */ +class LockFile : private boost::noncopyable +{ + std::string path; + bool created; + boost::shared_ptr<LockFilePrivate> impl; + +protected: + int read(void*, size_t) const; + int write(void*, size_t) const; + +public: + QPID_COMMON_EXTERN LockFile(const std::string& path_, bool create); + QPID_COMMON_EXTERN ~LockFile(); +}; + +}} /* namespace qpid::sys */ + +#endif /*!_sys_LockFile_h*/ + + + diff --git a/qpid/cpp/src/qpid/sys/LockPtr.h b/qpid/cpp/src/qpid/sys/LockPtr.h new file mode 100644 index 0000000000..738a864317 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/LockPtr.h @@ -0,0 +1,89 @@ +#ifndef QPID_SYS_LOCKPTR_H +#define QPID_SYS_LOCKPTR_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/Mutex.h" +#include <boost/noncopyable.hpp> + +namespace qpid { +namespace sys { + +class Mutex; + +/** + * LockPtr is a smart pointer to T. It is constructed from a volatile + * T* and a Lock (by default a Mutex). It const_casts away the + * volatile qualifier and locks the Lock for the duration of its + * + * Used in conjuntion with the "volatile" keyword to get the compiler + * to help enforce correct concurrent use of mutli-threaded objects. + * See ochttp://www.ddj.com/cpp/184403766 for a detailed discussion. + * + * To summarize the convention: + * - Declare thread-safe member functions as volatile. + * - Declare instances of the class that may be called concurrently as volatile. + * - Use LockPtr to cast away the volatile qualifier while taking a lock. + * + * This means that code calling on a concurrently-used object + * (declared volatile) can only call thread-safe (volatile) member + * functions. Code that needs to use thread-unsafe members must use a + * LockPtr, thereby acquiring the lock and making it safe to do so. + * + * A good type-safe pattern is the internally-locked object: + * - It has it's own private lock member. + * - All public functions are thread safe and declared volatile. + * - Any thread-unsafe, non-volatile functions are private. + * - Only member function implementations use LockPtr to access private functions. + * + * This encapsulates all the locking logic inside the class. + * + * One nice feature of this convention is the common case where you + * need a public, locked version of some function foo() and also a + * private unlocked version to avoid recursive locks. They can be declared as + * volatile and non-volatile overloads of the same function: + * + * // public + * void Thing::foo() volatile { LockPtr<Thing>(this, myLock)->foo(); } + * // private + * void Thing::foo() { ... do stuff ...} + */ + +template <class T, class Lock> class LockPtr : public boost::noncopyable { + public: + LockPtr(volatile T* p, Lock& l) : ptr(const_cast<T*>(p)), lock(l) { lock.lock(); } + LockPtr(volatile T* p, volatile Lock& l) : ptr(const_cast<T*>(p)), lock(const_cast<Lock&>(l)) { lock.lock(); } + ~LockPtr() { lock.unlock(); } + + T& operator*() { return *ptr; } + T* operator->() { return ptr; } + + private: + T* ptr; + Lock& lock; +}; + + +}} // namespace qpid::sys + + +#endif /*!QPID_SYS_LOCKPTR_H*/ diff --git a/qpid/cpp/src/qpid/sys/OutputControl.h b/qpid/cpp/src/qpid/sys/OutputControl.h new file mode 100644 index 0000000000..eae99beb0f --- /dev/null +++ b/qpid/cpp/src/qpid/sys/OutputControl.h @@ -0,0 +1,43 @@ +/* + * + * 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/IntegerTypes.h" + +#ifndef _OutputControl_ +#define _OutputControl_ + +namespace qpid { +namespace sys { + + class OutputControl + { + public: + virtual ~OutputControl() {} + virtual void abort() = 0; + virtual void activateOutput() = 0; + virtual void giveReadCredit(int32_t credit) = 0; + }; + +} +} + + +#endif diff --git a/qpid/cpp/src/qpid/sys/OutputTask.h b/qpid/cpp/src/qpid/sys/OutputTask.h new file mode 100644 index 0000000000..fb08a63cd0 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/OutputTask.h @@ -0,0 +1,41 @@ +/* + * + * 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 _OutputTask_ +#define _OutputTask_ + +namespace qpid { +namespace sys { + + class OutputTask + { + public: + virtual ~OutputTask() {} + /** Generate some output. + *@return true if output was generated, false if there is no work to do. + */ + virtual bool doOutput() = 0; + }; + +} +} + + +#endif diff --git a/qpid/cpp/src/qpid/sys/PipeHandle.h b/qpid/cpp/src/qpid/sys/PipeHandle.h new file mode 100755 index 0000000000..8aac76996b --- /dev/null +++ b/qpid/cpp/src/qpid/sys/PipeHandle.h @@ -0,0 +1,51 @@ +#ifndef _sys_PipeHandle_h +#define _sys_PipeHandle_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/IntegerTypes.h" +#include "qpid/CommonImportExport.h" +#include <string> + +// This class is a portability wrapper around pipe fds. +// It currently exists primarily and solely for the purpose of +// integration with single-threaded components that require QMF +// integration through a signalling fd. + +namespace qpid { +namespace sys { + + class PipeHandle { + private: + int writeFd; + int readFd; + public: + QPID_COMMON_EXTERN PipeHandle(bool nonBlocking=true); + QPID_COMMON_EXTERN ~PipeHandle(); + QPID_COMMON_EXTERN int read(void* buf, size_t bufSize); + QPID_COMMON_EXTERN int write(const void* buf, size_t bufSize); + QPID_COMMON_EXTERN int getReadHandle(); + }; + +}} + +#endif /*!_sys_PipeHandle_h*/ diff --git a/qpid/cpp/src/qpid/sys/PollableCondition.h b/qpid/cpp/src/qpid/sys/PollableCondition.h new file mode 100644 index 0000000000..2eb6f2d947 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/PollableCondition.h @@ -0,0 +1,64 @@ +#ifndef QPID_SYS_POLLABLECONDITION_H +#define QPID_SYS_POLLABLECONDITION_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/Poller.h" +#include "qpid/CommonImportExport.h" +#include <boost/function.hpp> +#include <boost/shared_ptr.hpp> + + +namespace qpid { +namespace sys { + +class PollableConditionPrivate; + +class PollableCondition { +public: + typedef boost::function1<void, PollableCondition&> Callback; + + QPID_COMMON_EXTERN PollableCondition(const Callback& cb, + const boost::shared_ptr<sys::Poller>& poller); + + QPID_COMMON_EXTERN ~PollableCondition(); + + /** + * Set the condition. Triggers callback to Callback from Poller. + */ + QPID_COMMON_EXTERN void set(); + + /** + * Clear the condition. Stops callbacks from Poller. + */ + QPID_COMMON_EXTERN void clear(); + + private: + PollableConditionPrivate *impl; + + Callback callback; + boost::shared_ptr<sys::Poller> poller; +}; + +}} // namespace qpid::sys + +#endif /*!QPID_SYS_POLLABLECONDITION_H*/ diff --git a/qpid/cpp/src/qpid/sys/PollableQueue.h b/qpid/cpp/src/qpid/sys/PollableQueue.h new file mode 100644 index 0000000000..81c2301c1e --- /dev/null +++ b/qpid/cpp/src/qpid/sys/PollableQueue.h @@ -0,0 +1,176 @@ +#ifndef QPID_SYS_POLLABLEQUEUE_H +#define QPID_SYS_POLLABLEQUEUE_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/PollableCondition.h" +#include "qpid/sys/Monitor.h" +#include "qpid/sys/Thread.h" +#include <boost/function.hpp> +#include <boost/bind.hpp> +#include <algorithm> +#include <vector> + +namespace qpid { +namespace sys { + +class Poller; + +/** + * A queue whose item processing is dispatched by sys::Poller. + * Any thread can push to the queue; items pushed trigger an event the Poller + * recognizes. When a Poller I/O thread dispatches the event, a + * user-specified callback is invoked with all items on the queue. + */ +template <class T> +class PollableQueue { + public: + typedef std::vector<T> Batch; + typedef T value_type; + + /** + * Callback to process a batch of items from the queue. + * + * @param batch Queue of values to process. Any items remaining + * on return from Callback are put back on the queue. + * @return iterator pointing to the first un-processed item in batch. + * Items from this point up to batch.end() are put back on the queue. + */ + typedef boost::function<typename Batch::const_iterator (const Batch& batch)> Callback; + + /** + * Constructor; sets necessary parameters. + * + * @param cb Callback that will be called to process items on the + * queue. Will be called from a Poller I/O thread. + * @param poller Poller to use for dispatching queue events. + */ + PollableQueue(const Callback& cb, + const boost::shared_ptr<sys::Poller>& poller); + + ~PollableQueue(); + + /** Push a value onto the queue. Thread safe */ + void push(const T& t); + + /** Start polling. */ + void start(); + + /** Stop polling and wait for the current callback, if any, to complete. */ + void stop(); + + /** Are we currently stopped?*/ + bool isStopped() const { ScopedLock l(lock); return stopped; } + + size_t size() { ScopedLock l(lock); return queue.size(); } + bool empty() { ScopedLock l(lock); return queue.empty(); } + + /** + * Allow any queued events to be processed; intended for calling + * after all dispatch threads exit the Poller loop in order to + * 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; + Batch queue, batch; + Thread dispatcher; + bool stopped; +}; + +template <class T> PollableQueue<T>::PollableQueue( + const Callback& cb, const boost::shared_ptr<sys::Poller>& p) + : callback(cb), + condition(boost::bind(&PollableQueue<T>::dispatch, this, _1), p), + stopped(true) +{ +} + +template <class T> void PollableQueue<T>::start() { + ScopedLock l(lock); + if (!stopped) return; + stopped = false; + if (!queue.empty()) condition.set(); +} + +template <class T> PollableQueue<T>::~PollableQueue() { +} + +template <class T> void PollableQueue<T>::push(const T& t) { + ScopedLock l(lock); + if (queue.empty() && !stopped) condition.set(); + queue.push_back(t); +} + +template <class T> void PollableQueue<T>::dispatch(PollableCondition& cond) { + ScopedLock l(lock); + assert(!dispatcher); + dispatcher = Thread::current(); + process(); + dispatcher = Thread(); + if (queue.empty()) cond.clear(); + if (stopped) lock.notifyAll(); +} + +template <class T> void PollableQueue<T>::process() { + // Called with lock held + while (!stopped && !queue.empty()) { + assert(batch.empty()); + batch.swap(queue); + typename Batch::const_iterator putBack; + { + ScopedUnlock u(lock); // Allow concurrent push to queue. + putBack = callback(batch); + } + // put back unprocessed items. + queue.insert(queue.begin(), putBack, typename Batch::const_iterator(batch.end())); + batch.clear(); + } +} + +template <class T> void PollableQueue<T>::shutdown() { + ScopedLock l(lock); + process(); +} + +template <class T> void PollableQueue<T>::stop() { + ScopedLock l(lock); + if (stopped) return; + condition.clear(); + stopped = true; + // Avoid deadlock if stop is called from the dispatch thread + if (dispatcher && dispatcher != Thread::current()) + while (dispatcher) lock.wait(); +} + +}} // namespace qpid::sys + +#endif /*!QPID_SYS_POLLABLEQUEUE_H*/ diff --git a/qpid/cpp/src/qpid/sys/Poller.h b/qpid/cpp/src/qpid/sys/Poller.h new file mode 100644 index 0000000000..01ee139ee6 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/Poller.h @@ -0,0 +1,135 @@ +#ifndef _sys_Poller_h +#define _sys_Poller_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" +#include "qpid/sys/Runnable.h" +#include "qpid/CommonImportExport.h" +#include <boost/shared_ptr.hpp> + +namespace qpid { +namespace sys { + +/** + * Poller is an abstract base class that registers callbacks to be + * called when there is IO activity. Concrete derived classes + * implement polling APIs such as epoll or equivalents on other + * operating systems. + * + * On the broker, Connection::received() is called with incoming + * frames from clients, and Connection::doOutput() is called when a + * connection is writeable. + * + * @see DispatchHandler for more details of normal use. + */ +class PollerHandle; +class PollerPrivate; +class Poller : public Runnable { + PollerPrivate* const impl; + +public: + typedef boost::shared_ptr<Poller> shared_ptr; + + enum Direction { + NONE = 0, + INPUT, + OUTPUT, + INOUT + }; + + enum EventType { + INVALID = 0, + READABLE, + WRITABLE, + READ_WRITABLE, + DISCONNECTED, + SHUTDOWN, + TIMEOUT, + INTERRUPTED + }; + + struct Event { + PollerHandle* handle; + EventType type; + + Event(PollerHandle* handle0, EventType type0) : + handle(handle0), + type(type0) { + } + + void process(); + }; + + QPID_COMMON_EXTERN Poller(); + QPID_COMMON_EXTERN ~Poller(); + /** Note: this function is async-signal safe */ + QPID_COMMON_EXTERN void shutdown(); + + // Interrupt waiting for a specific poller handle + // returns true if we could interrupt the handle + // - in this case on return the handle is no longer being monitored, + // but we will receive an event from some invocation of poller::wait + // with the handle and the INTERRUPTED event type + // if it returns false then the handle is not being monitored by the poller + // - This can either be because it has just received an event which has been + // reported and has not been reenabled since. + // - Because it was removed from the monitoring set + // - Or because it is already being interrupted + QPID_COMMON_EXTERN bool interrupt(PollerHandle& handle); + + // Poller run loop + QPID_COMMON_EXTERN void run(); + + QPID_COMMON_EXTERN void registerHandle(PollerHandle& handle); + QPID_COMMON_EXTERN void unregisterHandle(PollerHandle& handle); + QPID_COMMON_EXTERN void monitorHandle(PollerHandle& handle, Direction dir); + QPID_COMMON_EXTERN void unmonitorHandle(PollerHandle& handle, Direction dir); + QPID_COMMON_EXTERN Event wait(Duration timeout = TIME_INFINITE); + + QPID_COMMON_EXTERN bool hasShutdown(); +}; + +/** + * Handle class to use for polling + */ +class IOHandle; +class PollerHandlePrivate; +class PollerHandle { + friend class Poller; + friend class PollerPrivate; + friend struct Poller::Event; + + PollerHandlePrivate* const impl; + QPID_COMMON_INLINE_EXTERN virtual void processEvent(Poller::EventType) {}; + +public: + QPID_COMMON_EXTERN PollerHandle(const IOHandle& h); + QPID_COMMON_EXTERN virtual ~PollerHandle(); +}; + +inline void Poller::Event::process() { + handle->processEvent(type); +} + +}} +#endif // _sys_Poller_h diff --git a/qpid/cpp/src/qpid/sys/ProtocolFactory.h b/qpid/cpp/src/qpid/sys/ProtocolFactory.h new file mode 100644 index 0000000000..4d198a92da --- /dev/null +++ b/qpid/cpp/src/qpid/sys/ProtocolFactory.h @@ -0,0 +1,57 @@ +#ifndef _sys_ProtocolFactory_h +#define _sys_ProtocolFactory_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/IntegerTypes.h" +#include "qpid/SharedObject.h" +#include "qpid/sys/ConnectionCodec.h" +#include <boost/function.hpp> + +namespace qpid { +namespace sys { + +class Poller; + +class ProtocolFactory : public qpid::SharedObject<ProtocolFactory> +{ + public: + typedef boost::function2<void, int, std::string> ConnectFailedCallback; + + virtual ~ProtocolFactory() = 0; + virtual uint16_t getPort() const = 0; + virtual void accept(boost::shared_ptr<Poller>, ConnectionCodec::Factory*) = 0; + virtual void connect( + boost::shared_ptr<Poller>, + const std::string& host, const std::string& port, + ConnectionCodec::Factory* codec, + ConnectFailedCallback failed) = 0; + virtual bool supports(const std::string& /*capability*/) { return false; } +}; + +inline ProtocolFactory::~ProtocolFactory() {} + +}} + + + +#endif //!_sys_ProtocolFactory_h diff --git a/qpid/cpp/src/qpid/sys/RdmaIOPlugin.cpp b/qpid/cpp/src/qpid/sys/RdmaIOPlugin.cpp new file mode 100644 index 0000000000..631d116b41 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/RdmaIOPlugin.cpp @@ -0,0 +1,399 @@ +/* + * + * 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/ProtocolFactory.h" + +#include "qpid/Plugin.h" +#include "qpid/broker/Broker.h" +#include "qpid/framing/AMQP_HighestVersion.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/rdma/RdmaIO.h" +#include "qpid/sys/rdma/rdma_exception.h" +#include "qpid/sys/OutputControl.h" +#include "qpid/sys/SecuritySettings.h" + +#include <boost/bind.hpp> +#include <memory> + +#include <netdb.h> + +using std::auto_ptr; +using std::string; +using std::stringstream; + +namespace qpid { +namespace sys { + +class RdmaIOHandler : public OutputControl { + std::string identifier; + ConnectionCodec::Factory* factory; + ConnectionCodec* codec; + bool readError; + + sys::Mutex pollingLock; + bool polling; + + Rdma::AsynchIO* aio; + Rdma::Connection::intrusive_ptr connection; + + void write(const framing::ProtocolInitiation&); + void disconnectAction(); + + public: + RdmaIOHandler(Rdma::Connection::intrusive_ptr c, ConnectionCodec::Factory* f); + ~RdmaIOHandler(); + void init(Rdma::AsynchIO* a); + void start(Poller::shared_ptr poller); + + // Output side + void close(); + void abort(); + void activateOutput(); + void giveReadCredit(int32_t credit); + void initProtocolOut(); + + // Input side + void readbuff(Rdma::AsynchIO& aio, Rdma::Buffer* buff); + void initProtocolIn(Rdma::Buffer* buff); + + // Notifications + void full(Rdma::AsynchIO& aio); + void idle(Rdma::AsynchIO& aio); + void error(Rdma::AsynchIO& aio); + void disconnected(); + void drained(); +}; + +RdmaIOHandler::RdmaIOHandler(Rdma::Connection::intrusive_ptr c, qpid::sys::ConnectionCodec::Factory* f) : + identifier(c->getFullName()), + factory(f), + codec(0), + readError(false), + polling(false), + connection(c) +{ +} + +RdmaIOHandler::~RdmaIOHandler() { + if (codec) + codec->closed(); + delete codec; + delete aio; +} + +void RdmaIOHandler::init(Rdma::AsynchIO* a) { + aio = a; +} + +void RdmaIOHandler::start(Poller::shared_ptr poller) { + Mutex::ScopedLock l(pollingLock); + assert(!polling); + + polling = true; + + aio->start(poller); +} + +void RdmaIOHandler::write(const framing::ProtocolInitiation& data) +{ + QPID_LOG(debug, "Rdma: SENT [" << identifier << "] INIT(" << data << ")"); + Rdma::Buffer* buff = aio->getSendBuffer(); + assert(buff); + framing::Buffer out(buff->bytes(), buff->byteCount()); + data.encode(out); + buff->dataCount(data.encodedSize()); + aio->queueWrite(buff); +} + +void RdmaIOHandler::close() { + aio->drainWriteQueue(boost::bind(&RdmaIOHandler::drained, this)); +} + +// TODO: Dummy implementation, need to fill this in for heartbeat timeout to work +void RdmaIOHandler::abort() { +} + +void RdmaIOHandler::activateOutput() { + aio->notifyPendingWrite(); +} + +void RdmaIOHandler::idle(Rdma::AsynchIO&) { + // TODO: Shouldn't need this test as idle() should only ever be called when + // the connection is writable anyway + if ( !aio->writable() ) { + return; + } + if (codec == 0) return; + if (!codec->canEncode()) { + return; + } + Rdma::Buffer* buff = aio->getSendBuffer(); + if (buff) { + size_t encoded=codec->encode(buff->bytes(), buff->byteCount()); + buff->dataCount(encoded); + aio->queueWrite(buff); + if (codec->isClosed()) { + close(); + } + } +} + +void RdmaIOHandler::initProtocolOut() { + // We mustn't have already started the conversation + // but we must be able to send + assert( codec == 0 ); + assert( aio->writable() ); + codec = factory->create(*this, identifier, SecuritySettings()); + write(framing::ProtocolInitiation(codec->getVersion())); +} + +void RdmaIOHandler::error(Rdma::AsynchIO&) { + disconnected(); +} + +namespace { + void stopped(RdmaIOHandler* async) { + delete async; + } +} + +void RdmaIOHandler::disconnectAction() { + { + Mutex::ScopedLock l(pollingLock); + // If we're closed already then we'll get to drained() anyway + if (!polling) return; + polling = false; + } + aio->stop(boost::bind(&stopped, this)); +} + +void RdmaIOHandler::disconnected() { + aio->requestCallback(boost::bind(&RdmaIOHandler::disconnectAction, this)); +} + +void RdmaIOHandler::drained() { + // We know we've drained the write queue now, but we don't have to do anything + // because we can rely on the client to disconnect to trigger the connection + // cleanup. +} + +void RdmaIOHandler::full(Rdma::AsynchIO&) { + QPID_LOG(debug, "Rdma: buffer full [" << identifier << "]"); +} + +// TODO: Dummy implementation of read throttling +void RdmaIOHandler::giveReadCredit(int32_t) { +} + +// The logic here is subtly different from TCP as RDMA is message oriented +// so we define that an RDMA message is a frame - in this case there is no putting back +// of any message remainder - there shouldn't be any. And what we read here can't be +// smaller than a frame +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()); + }else{ + // Need to start protocol processing + initProtocolIn(buff); + } + }catch(const std::exception& e){ + QPID_LOG(error, e.what()); + readError = true; + close(); + } +} + +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()); + + // If we failed to create the codec then we don't understand the offered protocol version + if (!codec) { + // send valid version header & close connection. + write(framing::ProtocolInitiation(framing::highestProtocolVersion)); + readError = true; + close(); + } + } +} + +class RdmaIOProtocolFactory : public ProtocolFactory { + auto_ptr<Rdma::Listener> listener; + const uint16_t listeningPort; + + public: + RdmaIOProtocolFactory(int16_t port, int backlog); + void accept(Poller::shared_ptr, ConnectionCodec::Factory*); + void connect(Poller::shared_ptr, const string& host, const std::string& port, ConnectionCodec::Factory*, ConnectFailedCallback); + + uint16_t getPort() const; + + private: + bool request(Rdma::Connection::intrusive_ptr, const Rdma::ConnectionParams&, ConnectionCodec::Factory*); + void established(Poller::shared_ptr, Rdma::Connection::intrusive_ptr); + void connected(Poller::shared_ptr, Rdma::Connection::intrusive_ptr, const Rdma::ConnectionParams&, ConnectionCodec::Factory*); + void connectionError(Rdma::Connection::intrusive_ptr, Rdma::ErrorType); + void disconnected(Rdma::Connection::intrusive_ptr); + void rejected(Rdma::Connection::intrusive_ptr, const Rdma::ConnectionParams&, ConnectFailedCallback); +}; + +// Static instance to initialise plugin +static class RdmaIOPlugin : public Plugin { + void earlyInitialize(Target&) { + } + + void initialize(Target& target) { + // Check whether we actually have any rdma devices + if ( Rdma::deviceCount() == 0 ) { + QPID_LOG(info, "Rdma: Disabled: no rdma devices found"); + return; + } + + 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 RdmaIOProtocolFactory(opts.port, opts.connectionBacklog)); + QPID_LOG(notice, "Rdma: Listening on RDMA port " << protocol->getPort()); + broker->registerProtocolFactory("rdma", protocol); + } + } +} rdmaPlugin; + +RdmaIOProtocolFactory::RdmaIOProtocolFactory(int16_t port, int /*backlog*/) : + listeningPort(port) +{} + +void RdmaIOProtocolFactory::established(Poller::shared_ptr poller, Rdma::Connection::intrusive_ptr ci) { + RdmaIOHandler* async = ci->getContext<RdmaIOHandler>(); + async->start(poller); +} + +bool RdmaIOProtocolFactory::request(Rdma::Connection::intrusive_ptr ci, const Rdma::ConnectionParams& cp, + ConnectionCodec::Factory* f) { + try { + if (cp.rdmaProtocolVersion == 0) { + QPID_LOG(warning, "Rdma: connection from protocol version 0 client"); + } + RdmaIOHandler* async = new RdmaIOHandler(ci, f); + Rdma::AsynchIO* aio = + new Rdma::AsynchIO(ci->getQueuePair(), + cp.rdmaProtocolVersion, + cp.maxRecvBufferSize, cp.initialXmitCredit, Rdma::DEFAULT_WR_ENTRIES, + boost::bind(&RdmaIOHandler::readbuff, async, _1, _2), + boost::bind(&RdmaIOHandler::idle, async, _1), + 0, // boost::bind(&RdmaIOHandler::full, async, _1), + boost::bind(&RdmaIOHandler::error, async, _1)); + async->init(aio); + + // Record aio so we can get it back from a connection + ci->addContext(async); + return true; + } catch (const Rdma::Exception& e) { + QPID_LOG(error, "Rdma: Cannot accept new connection (Rdma exception): " << e.what()); + } catch (const std::exception& e) { + QPID_LOG(error, "Rdma: Cannot accept new connection (unknown exception): " << e.what()); + } + + // If we get here we caught an exception so reject connection + return false; +} + +void RdmaIOProtocolFactory::connectionError(Rdma::Connection::intrusive_ptr, Rdma::ErrorType) { +} + +void RdmaIOProtocolFactory::disconnected(Rdma::Connection::intrusive_ptr ci) { + // If we've got a connection already tear it down, otherwise ignore + RdmaIOHandler* async = ci->getContext<RdmaIOHandler>(); + if (async) { + // Make sure we don't disconnect more than once + ci->removeContext(); + async->disconnected(); + } +} + +uint16_t RdmaIOProtocolFactory::getPort() const { + return listeningPort; // Immutable no need for lock. +} + +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), + boost::bind(&RdmaIOProtocolFactory::established, this, poller, _1), + boost::bind(&RdmaIOProtocolFactory::connectionError, this, _1, _2), + boost::bind(&RdmaIOProtocolFactory::disconnected, this, _1), + boost::bind(&RdmaIOProtocolFactory::request, this, _1, _2, fact))); + + SocketAddress sa("",boost::lexical_cast<std::string>(listeningPort)); + listener->start(poller, sa); +} + +// Only used for outgoing connections (in federation) +void RdmaIOProtocolFactory::rejected(Rdma::Connection::intrusive_ptr, const Rdma::ConnectionParams&, ConnectFailedCallback failed) { + failed(-1, "Connection rejected"); +} + +// Do the same as connection request and established but mark a client too +void RdmaIOProtocolFactory::connected(Poller::shared_ptr poller, Rdma::Connection::intrusive_ptr ci, const Rdma::ConnectionParams& cp, + ConnectionCodec::Factory* f) { + (void) request(ci, cp, f); + established(poller, ci); + RdmaIOHandler* async = ci->getContext<RdmaIOHandler>(); + async->initProtocolOut(); +} + +void RdmaIOProtocolFactory::connect( + Poller::shared_ptr poller, + const std::string& host, const std::string& port, + ConnectionCodec::Factory* f, + ConnectFailedCallback failed) +{ + Rdma::Connector* c = + new Rdma::Connector( + Rdma::ConnectionParams(8000, Rdma::DEFAULT_WR_ENTRIES), + boost::bind(&RdmaIOProtocolFactory::connected, this, poller, _1, _2, f), + boost::bind(&RdmaIOProtocolFactory::connectionError, this, _1, _2), + boost::bind(&RdmaIOProtocolFactory::disconnected, this, _1), + boost::bind(&RdmaIOProtocolFactory::rejected, this, _1, _2, failed)); + + SocketAddress sa(host, port); + c->start(poller, sa); +} + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/Runnable.cpp b/qpid/cpp/src/qpid/sys/Runnable.cpp new file mode 100644 index 0000000000..325d87c91b --- /dev/null +++ b/qpid/cpp/src/qpid/sys/Runnable.cpp @@ -0,0 +1,32 @@ +/* + * + * 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. + * + */ + +#include "qpid/sys/Runnable.h" +#include <boost/bind.hpp> + +namespace qpid { +namespace sys { + +Runnable::~Runnable() {} + +Runnable::Functor Runnable::functor() +{ + return boost::bind(&Runnable::run, this); +} + +}} diff --git a/qpid/cpp/src/qpid/sys/ScopedIncrement.h b/qpid/cpp/src/qpid/sys/ScopedIncrement.h new file mode 100644 index 0000000000..8645ab2484 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/ScopedIncrement.h @@ -0,0 +1,67 @@ +#ifndef _posix_ScopedIncrement_h +#define _posix_ScopedIncrement_h + +/* + * + * 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. + * + */ + +#include <boost/noncopyable.hpp> +#include <boost/function.hpp> + +namespace qpid { +namespace sys { + +/** + * Increment counter in constructor and decrement in destructor. + * Optionally call a function if the decremented counter value is 0. + * Note the function must not throw, it is called in the destructor. + */ +template <class T, class F=boost::function<void()> > +class ScopedIncrement : boost::noncopyable +{ + public: + ScopedIncrement(T& c, F f=0) + : count(c), callback(f) { ++count; } + ~ScopedIncrement() { if (--count == 0 && callback) callback(); } + + private: + T& count; + F callback; +}; + + +/** Decrement counter in constructor and increment in destructor. */ +template <class T> +class ScopedDecrement : boost::noncopyable +{ + public: + ScopedDecrement(T& c) : count(c) { value = --count; } + ~ScopedDecrement() { ++count; } + + /** Return the value after the decrement. */ + operator long() { return value; } + + private: + T& count; + long value; +}; + + +}} + + +#endif // _posix_ScopedIncrement_h diff --git a/qpid/cpp/src/qpid/sys/SecurityLayer.h b/qpid/cpp/src/qpid/sys/SecurityLayer.h new file mode 100644 index 0000000000..52bc40e352 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/SecurityLayer.h @@ -0,0 +1,42 @@ +#ifndef QPID_SYS_SECURITYLAYER_H +#define QPID_SYS_SECURITYLAYER_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/Codec.h" + +namespace qpid { +namespace sys { + +/** + * Defines interface to a SASL negotiated Security Layer (for + * encryption/integrity) + */ +class SecurityLayer : public Codec +{ + public: + virtual void init(Codec*) = 0; + virtual ~SecurityLayer() {} +}; + +}} // namespace qpid::sys + +#endif /*!QPID_SYS_SECURITYLAYER_H*/ diff --git a/qpid/cpp/src/qpid/sys/SecuritySettings.h b/qpid/cpp/src/qpid/sys/SecuritySettings.h new file mode 100644 index 0000000000..bfcd08fd0f --- /dev/null +++ b/qpid/cpp/src/qpid/sys/SecuritySettings.h @@ -0,0 +1,58 @@ +#ifndef QPID_SYS_SECURITYSETTINGS_H +#define QPID_SYS_SECURITYSETTINGS_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. + * + */ +namespace qpid { +namespace sys { + +/** + * Conveys security information from a given transport to the upper + * layers. + */ +struct SecuritySettings +{ + /** + * Security Strength Factor (SSF). Possible values are: + * + * @li 0 No security + * @li 1 Integrity checking only + * @li >1 Integrity and confidentiality with the number + * giving the encryption key length. + */ + unsigned int ssf; + /** + * An authorisation id + */ + std::string authid; + + /** + * Disables SASL mechanisms that are vulnerable to passive + * dictionary-based password attacks + */ + bool nodict; + + SecuritySettings() : ssf(0), nodict(false) {} +}; + +}} // namespace qpid::sys + +#endif /*!QPID_SYS_SECURITYSETTINGS_H*/ diff --git a/qpid/cpp/src/qpid/sys/Semaphore.h b/qpid/cpp/src/qpid/sys/Semaphore.h new file mode 100644 index 0000000000..9d70f89aeb --- /dev/null +++ b/qpid/cpp/src/qpid/sys/Semaphore.h @@ -0,0 +1,79 @@ +#ifndef _sys_Semaphore_h +#define _sys_Semaphore_h + +/* + * + * 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. + * + */ + +#include "qpid/sys/Monitor.h" + +namespace qpid { +namespace sys { + +class Semaphore +{ +public: + Semaphore(uint c = 1) : count(c) {} + + void lock() { acquire(); } + void unlock() { release(); } + bool trylock() { return tryAcquire(); } + + bool tryAcquire() + { + Monitor::ScopedLock l(monitor); + if (count) { + count--; + return true; + } else { + return false; + } + } + + void acquire() + { + Monitor::ScopedLock l(monitor); + while (count == 0) monitor.wait(); + count--; + } + + void release(uint n) + { + Monitor::ScopedLock l(monitor); + if (count==0) monitor.notifyAll(); + count+=n; + } + + void release() + { + release(1); + } + + void forceLock() + { + Monitor::ScopedLock l(monitor); + count = 0; + } + +private: + Monitor monitor; + uint count; +}; + +}} + +#endif /*!_sys_Semaphore_h*/ diff --git a/qpid/cpp/src/qpid/sys/Shlib.cpp b/qpid/cpp/src/qpid/sys/Shlib.cpp new file mode 100644 index 0000000000..342d726876 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/Shlib.cpp @@ -0,0 +1,38 @@ +/* + * 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/Shlib.h" + +#include "qpid/log/Statement.h" + +namespace qpid { +namespace sys { + +AutoShlib::~AutoShlib() throw() { + try { + unload(); + } catch(const std::exception& e) { + QPID_LOG(error, "Unloading shared library: " << e.what()); + } +} + +// Note: Other functions are defined in apr/Shlib.cpp or posix/Shlib.cpp. + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/Shlib.h b/qpid/cpp/src/qpid/sys/Shlib.h new file mode 100644 index 0000000000..7f66cfec14 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/Shlib.h @@ -0,0 +1,76 @@ +#ifndef QPID_SYS_SHLIB_H +#define QPID_SYS_SHLIB_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/CommonImportExport.h" +#include <boost/noncopyable.hpp> +#include <iostream> + +namespace qpid { +namespace sys { + +/** Encapsulates a shared library handle. + *@see AutoShlib + */ +class Shlib { + public: + /** Load a shared library */ + Shlib(const char* libname) { load(libname); } + + /** Load a shared library */ + Shlib(const std::string& libname) { load(libname.c_str()); } + + /** Unload shared library. */ + QPID_COMMON_EXTERN void unload(); + + /** Look up symbol. */ + QPID_COMMON_EXTERN void* getSymbol(const char* symbol); + + /** Look up symbol in shared library, cast it to the desired + * pointer type, void* by default. + */ + template <class T> + T getSymbol(const char* symbol) { + // Double cast avoids warning about casting object to function pointer + return reinterpret_cast<T>(reinterpret_cast<intptr_t>( + this->getSymbol(symbol))); + } + + private: + void* handle; + QPID_COMMON_EXTERN void load(const char* libname); +}; + +/** A shared library handle that unloads the shlib in it's dtor */ +class AutoShlib : public Shlib { + public: + /** Load shared library */ + AutoShlib(const std::string& libname) : Shlib(libname) {} + /** Calls unload() */ + QPID_COMMON_EXTERN ~AutoShlib() throw(); +}; + + +}} // namespace qpid::sys + +#endif /*!QPID_SYS_SHLIB_H*/ diff --git a/qpid/cpp/src/qpid/sys/ShutdownHandler.h b/qpid/cpp/src/qpid/sys/ShutdownHandler.h new file mode 100644 index 0000000000..88baecb5b6 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/ShutdownHandler.h @@ -0,0 +1,37 @@ +/* + * + * 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 _ShutdownHandler_ +#define _ShutdownHandler_ + +namespace qpid { +namespace sys { + + class ShutdownHandler + { + public: + virtual void shutdown() = 0; + virtual ~ShutdownHandler(){} + }; + +} +} + +#endif diff --git a/qpid/cpp/src/qpid/sys/Socket.h b/qpid/cpp/src/qpid/sys/Socket.h new file mode 100644 index 0000000000..9f62f3be1c --- /dev/null +++ b/qpid/cpp/src/qpid/sys/Socket.h @@ -0,0 +1,103 @@ +#ifndef _sys_Socket_h +#define _sys_Socket_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" +#include "qpid/sys/IntegerTypes.h" +#include "qpid/CommonImportExport.h" +#include <string> + +namespace qpid { +namespace sys { + +class Duration; +class SocketAddress; + +class QPID_COMMON_CLASS_EXTERN Socket : public IOHandle +{ +public: + /** Create a socket wrapper for descriptor. */ + QPID_COMMON_EXTERN Socket(); + + /** Set socket non blocking */ + void setNonblocking() const; + + QPID_COMMON_EXTERN void setTcpNoDelay() 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; + + /** Bind to a port and start listening. + *@param port 0 means choose an available port. + *@param backlog maximum number of pending connections. + *@return The bound port. + */ + 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 an address (host and port) for the remote end of the + * socket + */ + QPID_COMMON_EXTERN std::string getPeerAddress() const; + /** + * Returns an address (host and port) for the local end of the + * socket + */ + QPID_COMMON_EXTERN std::string getLocalAddress() const; + + /** + * Returns the full address of the connection: local and remote host and port. + */ + 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; + + /** Accept a connection from a socket that is already listening + * and has an incoming connection + */ + QPID_COMMON_EXTERN Socket* accept() const; + + // TODO The following are raw operations, maybe they need better wrapping? + QPID_COMMON_EXTERN int read(void *buf, size_t count) const; + QPID_COMMON_EXTERN int write(const void *buf, size_t count) const; + +private: + /** Create socket */ + void createSocket(const SocketAddress&) const; + + Socket(IOHandlePrivate*); + mutable std::string localname; + mutable std::string peername; + mutable bool nonblocking; + mutable bool nodelay; +}; + +}} +#endif /*!_sys_Socket_h*/ diff --git a/qpid/cpp/src/qpid/sys/SocketAddress.h b/qpid/cpp/src/qpid/sys/SocketAddress.h new file mode 100644 index 0000000000..c2120338cf --- /dev/null +++ b/qpid/cpp/src/qpid/sys/SocketAddress.h @@ -0,0 +1,53 @@ +#ifndef _sys_SocketAddress_h +#define _sys_SocketAddress_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/IntegerTypes.h" +#include "qpid/CommonImportExport.h" +#include <string> + +struct addrinfo; + +namespace qpid { +namespace sys { + +class SocketAddress { + friend const ::addrinfo& getAddrInfo(const SocketAddress&); + +public: + /** Create a SocketAddress from hostname and port*/ + QPID_COMMON_EXTERN SocketAddress(const std::string& host, const std::string& port); + QPID_COMMON_EXTERN SocketAddress(const SocketAddress&); + QPID_COMMON_EXTERN SocketAddress& operator=(const SocketAddress&); + QPID_COMMON_EXTERN ~SocketAddress(); + + std::string asString(bool numeric=true) const; + +private: + std::string host; + std::string port; + mutable ::addrinfo* addrInfo; +}; + +}} +#endif /*!_sys_SocketAddress_h*/ diff --git a/qpid/cpp/src/qpid/sys/SslPlugin.cpp b/qpid/cpp/src/qpid/sys/SslPlugin.cpp new file mode 100644 index 0000000000..471a0cef60 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/SslPlugin.cpp @@ -0,0 +1,186 @@ +/* + * + * 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/ProtocolFactory.h" + +#include "qpid/Plugin.h" +#include "qpid/sys/ssl/check.h" +#include "qpid/sys/ssl/util.h" +#include "qpid/sys/ssl/SslHandler.h" +#include "qpid/sys/ssl/SslIo.h" +#include "qpid/sys/ssl/SslSocket.h" +#include "qpid/broker/Broker.h" +#include "qpid/log/Statement.h" + +#include <boost/bind.hpp> +#include <memory> + + +namespace qpid { +namespace sys { + +struct SslServerOptions : ssl::SslOptions +{ + uint16_t port; + bool clientAuth; + bool nodict; + + SslServerOptions() : port(5671), + clientAuth(false), + nodict(false) + { + addOptions() + ("ssl-port", optValue(port, "PORT"), "Port on which to listen for SSL connections") + ("ssl-require-client-authentication", optValue(clientAuth), + "Forces clients to authenticate in order to establish an SSL connection") + ("ssl-sasl-no-dict", optValue(nodict), + "Disables SASL mechanisms that are vulnerable to passive dictionary-based password attacks"); + } +}; + +class SslProtocolFactory : public ProtocolFactory { + const bool tcpNoDelay; + qpid::sys::ssl::SslSocket listener; + const uint16_t listeningPort; + std::auto_ptr<qpid::sys::ssl::SslAcceptor> acceptor; + bool nodict; + + public: + SslProtocolFactory(const SslServerOptions&, int backlog, bool nodelay); + void accept(Poller::shared_ptr, ConnectionCodec::Factory*); + 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; + bool supports(const std::string& capability); + + private: + void established(Poller::shared_ptr, const qpid::sys::ssl::SslSocket&, ConnectionCodec::Factory*, + bool isClient); +}; + +// Static instance to initialise plugin +static struct SslPlugin : public Plugin { + SslServerOptions options; + + Options* getOptions() { return &options; } + + ~SslPlugin() { ssl::shutdownNSS(); } + + void earlyInitialize(Target&) { + } + + void initialize(Target& target) { + broker::Broker* broker = dynamic_cast<broker::Broker*>(&target); + // Only provide to a Broker + if (broker) { + if (options.certDbPath.empty()) { + 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()); + broker->registerProtocolFactory("ssl", protocol); + } catch (const std::exception& e) { + QPID_LOG(error, "Failed to initialise SSL plugin: " << e.what()); + } + } + } + } +} sslPlugin; + +SslProtocolFactory::SslProtocolFactory(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) { + qpid::sys::ssl::SslHandler* async = new qpid::sys::ssl::SslHandler(s.getFullAddress(), f, nodict); + + if (tcpNoDelay) { + s.setTcpNoDelay(tcpNoDelay); + QPID_LOG(info, "Set TCP_NODELAY on connection to " << s.getPeerAddress()); + } + + 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), + boost::bind(&qpid::sys::ssl::SslHandler::disconnect, async, _1), + boost::bind(&qpid::sys::ssl::SslHandler::closedSocket, async, _1, _2), + boost::bind(&qpid::sys::ssl::SslHandler::nobuffs, async, _1), + boost::bind(&qpid::sys::ssl::SslHandler::idle, async, _1)); + + async->init(aio, 4); + aio->start(poller); +} + +uint16_t SslProtocolFactory::getPort() const { + return listeningPort; // Immutable no need for lock. +} + +void SslProtocolFactory::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))); + acceptor->start(poller); +} + +void SslProtocolFactory::connect( + Poller::shared_ptr poller, + const std::string& host, const std::string& port, + ConnectionCodec::Factory* fact, + ConnectFailedCallback failed) +{ + // Note that the following logic does not cause a memory leak. + // The allocated Socket is freed either by the SslConnector + // upon connection failure or by the SslIoHandle upon connection + // shutdown. The allocated SslConnector frees itself when it + // 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); +} + +namespace +{ +const std::string SSL = "ssl"; +} + +bool SslProtocolFactory::supports(const std::string& capability) +{ + std::string s = capability; + transform(s.begin(), s.end(), s.begin(), tolower); + return s == SSL; +} + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/StateMonitor.h b/qpid/cpp/src/qpid/sys/StateMonitor.h new file mode 100644 index 0000000000..eac37a8543 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/StateMonitor.h @@ -0,0 +1,78 @@ +#ifndef QPID_SYS_STATEMONITOR_H +#define QPID_SYS_STATEMONITOR_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/Waitable.h" + +#include <bitset> + +namespace qpid { +namespace sys { + +/** + * A monitor with an enum state value. + * + *@param Enum: enum type to use for states. + *@param EnumMax: Highest enum value. + */ +template <class Enum, size_t MaxEnum> +class StateMonitor : public Waitable +{ + public: + struct Set : public std::bitset<MaxEnum + 1> { + Set() {} + Set(Enum s) { set(s); } + 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); } + }; + + + StateMonitor(Enum initial) { state=initial; } + + /** @pre Caller holds a ScopedLock. */ + void set(Enum s) { state=s; notifyAll(); } + /** @pre Caller holds a ScopedLock. */ + StateMonitor& operator=(Enum s) { set(s); return *this; } + + /** @pre Caller holds a ScopedLock. */ + Enum get() const { return state; } + /** @pre Caller holds a ScopedLock. */ + operator Enum() const { return state; } + + /** @pre Caller holds a ScopedLock */ + void waitFor(Enum s) { ScopedWait w(*this); while (s != state) wait(); } + /** @pre Caller holds a ScopedLock */ + void waitFor(Set s) { ScopedWait w(*this); while (!s.test(state)) wait(); } + /** @pre Caller holds a ScopedLock */ + void waitNot(Enum s) { ScopedWait w(*this); while (s == state) wait(); } + /** @pre Caller holds a ScopedLock */ + void waitNot(Set s) { ScopedWait w(*this); while (s.test(state)) wait(); } + + private: + Enum state; +}; + +}} + + +#endif /*!QPID_SYS_STATEMONITOR_H*/ diff --git a/qpid/cpp/src/qpid/sys/TCPIOPlugin.cpp b/qpid/cpp/src/qpid/sys/TCPIOPlugin.cpp new file mode 100644 index 0000000000..34338ce434 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/TCPIOPlugin.cpp @@ -0,0 +1,152 @@ +/* + * + * 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/ProtocolFactory.h" +#include "qpid/sys/AsynchIOHandler.h" +#include "qpid/sys/AsynchIO.h" + +#include "qpid/Plugin.h" +#include "qpid/sys/Socket.h" +#include "qpid/sys/Poller.h" +#include "qpid/broker/Broker.h" +#include "qpid/log/Statement.h" + +#include <boost/bind.hpp> +#include <memory> + +namespace qpid { +namespace sys { + +class AsynchIOProtocolFactory : public ProtocolFactory { + const bool tcpNoDelay; + Socket listener; + const uint16_t listeningPort; + std::auto_ptr<AsynchAcceptor> acceptor; + + public: + AsynchIOProtocolFactory(const std::string& host, const std::string& port, int backlog, bool nodelay); + void accept(Poller::shared_ptr, ConnectionCodec::Factory*); + void connect(Poller::shared_ptr, const std::string& host, const std::string& port, + ConnectionCodec::Factory*, + ConnectFailedCallback); + + uint16_t getPort() const; + + private: + void established(Poller::shared_ptr, const Socket&, ConnectionCodec::Factory*, + bool isClient); + void connectFailed(const Socket&, int, const std::string&, ConnectFailedCallback); +}; + +// 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 protocolt( + new AsynchIOProtocolFactory( + "", boost::lexical_cast<std::string>(opts.port), + opts.connectionBacklog, + opts.tcpNoDelay)); + QPID_LOG(notice, "Listening on TCP port " << protocolt->getPort()); + broker->registerProtocolFactory("tcp", protocolt); + } + } +} tcpPlugin; + +AsynchIOProtocolFactory::AsynchIOProtocolFactory(const std::string& host, const std::string& port, int backlog, bool nodelay) : + tcpNoDelay(nodelay), listeningPort(listener.listen(host, port, backlog)) +{} + +void AsynchIOProtocolFactory::established(Poller::shared_ptr poller, const Socket& s, + ConnectionCodec::Factory* f, bool isClient) { + 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); +} + +uint16_t AsynchIOProtocolFactory::getPort() const { + return listeningPort; // Immutable no need for lock. +} + +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); +} + +void AsynchIOProtocolFactory::connectFailed( + const Socket& s, int ec, const std::string& emsg, + ConnectFailedCallback failedCb) +{ + failedCb(ec, emsg); + s.close(); + delete &s; +} + +void AsynchIOProtocolFactory::connect( + Poller::shared_ptr poller, + const std::string& host, const std::string& port, + ConnectionCodec::Factory* fact, + ConnectFailedCallback failed) +{ + // Note that the following logic does not cause a memory leak. + // The allocated Socket is freed either by the AsynchConnector + // 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(); + AsynchConnector* c = AsynchConnector::create( + *socket, + host, + port, + boost::bind(&AsynchIOProtocolFactory::established, + this, poller, _1, fact, true), + boost::bind(&AsynchIOProtocolFactory::connectFailed, + this, _1, _2, _3, failed)); + c->start(poller); +} + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/TimeoutHandler.h b/qpid/cpp/src/qpid/sys/TimeoutHandler.h new file mode 100644 index 0000000000..0c10709bbf --- /dev/null +++ b/qpid/cpp/src/qpid/sys/TimeoutHandler.h @@ -0,0 +1,39 @@ +/* + * + * 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 _TimeoutHandler_ +#define _TimeoutHandler_ + +namespace qpid { +namespace sys { + + class TimeoutHandler + { + public: + virtual void idleOut() = 0; + virtual void idleIn() = 0; + virtual ~TimeoutHandler(){} + }; + +} +} + + +#endif diff --git a/qpid/cpp/src/qpid/sys/Timer.cpp b/qpid/cpp/src/qpid/sys/Timer.cpp new file mode 100644 index 0000000000..fdb2e8c6bb --- /dev/null +++ b/qpid/cpp/src/qpid/sys/Timer.cpp @@ -0,0 +1,205 @@ +/* + * + * 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/Timer.h" +#include "qpid/sys/Mutex.h" +#include "qpid/log/Statement.h" + +#include <numeric> + +using boost::intrusive_ptr; +using std::max; + +namespace qpid { +namespace sys { + +TimerTask::TimerTask(Duration timeout, const std::string& n) : + name(n), + sortTime(AbsTime::FarFuture()), + period(timeout), + nextFireTime(AbsTime::now(), timeout), + cancelled(false) +{} + +TimerTask::TimerTask(AbsTime time, const std::string& n) : + name(n), + sortTime(AbsTime::FarFuture()), + period(0), + nextFireTime(time), + cancelled(false) +{} + +TimerTask::~TimerTask() {} + +bool TimerTask::readyToFire() const { + return !(nextFireTime > AbsTime::now()); +} + +void TimerTask::fireTask() { + cancelled = true; + fire(); +} + +// This can only be used to setup the next fire time. After the Timer has already fired +void TimerTask::setupNextFire() { + if (period && readyToFire()) { + nextFireTime = max(AbsTime::now(), AbsTime(nextFireTime, period)); + cancelled = false; + } else { + QPID_LOG(error, name << " couldn't setup next timer firing: " << Duration(nextFireTime, AbsTime::now()) << "[" << period << "]"); + } +} + +// Only allow tasks to be delayed +void TimerTask::restart() { nextFireTime = max(nextFireTime, AbsTime(AbsTime::now(), period)); } + +void TimerTask::cancel() { + ScopedLock<Mutex> l(callbackLock); + cancelled = true; +} + +Timer::Timer() : + active(false), + late(50 * TIME_MSEC), + overran(2 * TIME_MSEC), + lateCancel(500 * TIME_MSEC), + warn(5 * TIME_SEC) +{ + start(); +} + +Timer::~Timer() +{ + stop(); +} + +// TODO AStitcher 21/08/09 The threshholds for emitting warnings are a little arbitrary +void Timer::run() +{ + Monitor::ScopedLock l(monitor); + while (active) { + if (tasks.empty()) { + monitor.wait(); + } else { + intrusive_ptr<TimerTask> t = tasks.top(); + tasks.pop(); + assert(!(t->nextFireTime < t->sortTime)); + + // warn on extreme lateness + AbsTime start(AbsTime::now()); + Duration delay(t->sortTime, start); + { + ScopedLock<Mutex> l(t->callbackLock); + if (t->cancelled) { + { + Monitor::ScopedUnlock u(monitor); + drop(t); + } + if (delay > lateCancel) { + QPID_LOG(debug, t->name << " cancelled timer woken up " << + delay / TIME_MSEC << "ms late"); + } + continue; + } else if(Duration(t->nextFireTime, start) >= 0) { + { + Monitor::ScopedUnlock u(monitor); + fire(t); + } + // Warn if callback overran next timer's start. + AbsTime end(AbsTime::now()); + Duration overrun (0); + if (!tasks.empty()) { + overrun = Duration(tasks.top()->nextFireTime, end); + } + bool warningsEnabled; + QPID_LOG_TEST(warning, warningsEnabled); + if (warningsEnabled) { + 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); + } + continue; + } else { + // If the timer was adjusted into the future it might no longer + // be the next event, so push and then get top to make sure + // You can only push events into the future + t->sortTime = t->nextFireTime; + tasks.push(t); + } + } + assert(!tasks.empty()); + monitor.wait(tasks.top()->sortTime); + } + } +} + +void Timer::add(intrusive_ptr<TimerTask> task) +{ + Monitor::ScopedLock l(monitor); + task->sortTime = task->nextFireTime; + tasks.push(task); + monitor.notify(); +} + +void Timer::start() +{ + Monitor::ScopedLock l(monitor); + if (!active) { + active = true; + runner = Thread(this); + } +} + +void Timer::stop() +{ + { + Monitor::ScopedLock l(monitor); + if (!active) return; + active = false; + monitor.notifyAll(); + } + runner.join(); +} + +// Allow subclasses to override behavior when firing a task. +void Timer::fire(boost::intrusive_ptr<TimerTask> t) { + 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. +void Timer::drop(boost::intrusive_ptr<TimerTask>) {} + +bool operator<(const intrusive_ptr<TimerTask>& a, + const intrusive_ptr<TimerTask>& b) +{ + // Lower priority if time is later + return a.get() && b.get() && a->sortTime > b->sortTime; +} + +}} diff --git a/qpid/cpp/src/qpid/sys/Timer.h b/qpid/cpp/src/qpid/sys/Timer.h new file mode 100644 index 0000000000..98ba39ce38 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/Timer.h @@ -0,0 +1,107 @@ +/* + * + * 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 sys_Timer +#define sys_Timer + +#include "qpid/sys/TimerWarnings.h" +#include "qpid/sys/Monitor.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Thread.h" +#include "qpid/sys/Runnable.h" +#include "qpid/RefCounted.h" +#include "qpid/CommonImportExport.h" +#include <memory> +#include <queue> + +#include <boost/intrusive_ptr.hpp> + +namespace qpid { +namespace sys { + +class Timer; + +class TimerTask : public RefCounted { + friend class Timer; + friend bool operator<(const boost::intrusive_ptr<TimerTask>&, + const boost::intrusive_ptr<TimerTask>&); + + std::string name; + AbsTime sortTime; + Duration period; + AbsTime nextFireTime; + Mutex callbackLock; + volatile bool cancelled; + + bool readyToFire() const; + void fireTask(); + + public: + QPID_COMMON_EXTERN TimerTask(Duration period, const std::string& name); + QPID_COMMON_EXTERN TimerTask(AbsTime fireTime, const std::string& name); + QPID_COMMON_EXTERN virtual ~TimerTask(); + + QPID_COMMON_EXTERN void setupNextFire(); + QPID_COMMON_EXTERN void restart(); + QPID_COMMON_EXTERN void cancel(); + + std::string getName() const { return name; } + + protected: + // Must be overridden with callback + virtual void fire() = 0; +}; + +// For the priority_queue order +bool operator<(const boost::intrusive_ptr<TimerTask>& a, + const boost::intrusive_ptr<TimerTask>& b); + +class Timer : private Runnable { + qpid::sys::Monitor monitor; + std::priority_queue<boost::intrusive_ptr<TimerTask> > tasks; + qpid::sys::Thread runner; + bool active; + + // Runnable interface + void run(); + + public: + QPID_COMMON_EXTERN Timer(); + QPID_COMMON_EXTERN virtual ~Timer(); + + QPID_COMMON_EXTERN virtual void add(boost::intrusive_ptr<TimerTask> task); + QPID_COMMON_EXTERN virtual void start(); + QPID_COMMON_EXTERN virtual void stop(); + + protected: + QPID_COMMON_EXTERN virtual void fire(boost::intrusive_ptr<TimerTask> task); + QPID_COMMON_EXTERN virtual void drop(boost::intrusive_ptr<TimerTask> task); + // Allow derived classes to change the late/overran thresholds. + Duration late; + Duration overran; + Duration lateCancel; + TimerWarnings warn; +}; + + +}} + + +#endif diff --git a/qpid/cpp/src/qpid/sys/TimerWarnings.cpp b/qpid/cpp/src/qpid/sys/TimerWarnings.cpp new file mode 100644 index 0000000000..87c3169456 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/TimerWarnings.cpp @@ -0,0 +1,82 @@ +/* + * + * 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 "TimerWarnings.h" +#include "qpid/log/Statement.h" + +namespace qpid { +namespace sys { + +TimerWarnings::TimerWarnings(Duration reportInterval) : + interval(reportInterval), + nextReport(now(), reportInterval) +{} + +void TimerWarnings::late(const std::string& task, Duration delay) { + taskStats[task].lateDelay.add(delay); + log(); +} + +void TimerWarnings::overran(const std::string& task, Duration overrun, Duration time) +{ + taskStats[task].overranOverrun.add(overrun); + taskStats[task].overranTime.add(time); + log(); +} + +void TimerWarnings::lateAndOverran( + const std::string& task, Duration delay, Duration overrun, Duration time) +{ + taskStats[task].lateAndOverranDelay.add(delay); + taskStats[task].lateAndOverranOverrun.add(overrun); + taskStats[task].lateAndOverranTime.add(time); + log(); +} + +void TimerWarnings::log() { + if (!taskStats.empty() && nextReport < now()) { + for (TaskStatsMap::iterator i = taskStats.begin(); i != taskStats.end(); ++i) { + std::string task = i->first; + TaskStats& stats = i->second; + if (stats.lateDelay.count) + QPID_LOG(warning, 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 " + << stats.overranOverrun.count << " times by " + << stats.overranOverrun.average()/TIME_MSEC << "ms (taking " + << stats.overranTime.average() << "ns) on average."); + + if (stats.lateAndOverranOverrun.count) + QPID_LOG(warning, 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); + taskStats.clear(); + } +} + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/TimerWarnings.h b/qpid/cpp/src/qpid/sys/TimerWarnings.h new file mode 100644 index 0000000000..337a434ab5 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/TimerWarnings.h @@ -0,0 +1,81 @@ +#ifndef QPID_SYS_TIMERWARNINGS_H +#define QPID_SYS_TIMERWARNINGS_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" +#include <map> +#include <string> + +namespace qpid { +namespace sys { + +/** + * The Timer class logs warnings when timer tasks are late and/or overrun. + * + * It is too expensive to log a warning for every late/overrun + * incident, doing so aggravates the problem of tasks over-running and + * being late. + * + * This class collects statistical data about each incident and prints + * an aggregated report at regular intervals. + */ +class TimerWarnings +{ + public: + TimerWarnings(Duration reportInterval); + + void late(const std::string& task, Duration delay); + + void overran(const std::string& task, Duration overrun, Duration time); + + void lateAndOverran(const std::string& task, + Duration delay, Duration overrun, Duration time); + + private: + struct Statistic { + Statistic() : total(0), count(0) {} + void add(int64_t value) { total += value; ++count; } + int64_t average() const { return count ? total/count : 0; } + int64_t total; + int64_t count; + }; + + // Keep statistics for 3 classes of incident: late, overrun and both. + struct TaskStats { + Statistic lateDelay; // Just late + Statistic overranOverrun, overranTime; // Just overrun + // Both + Statistic lateAndOverranDelay, lateAndOverranOverrun, lateAndOverranTime; + }; + + typedef std::map<std::string, TaskStats> TaskStatsMap; + + void log(); + + Duration interval; + AbsTime nextReport; + TaskStatsMap taskStats; +}; +}} // namespace qpid::sys + +#endif /*!QPID_SYS_TIMERWARNINGS_H*/ diff --git a/qpid/cpp/src/qpid/sys/Waitable.h b/qpid/cpp/src/qpid/sys/Waitable.h new file mode 100644 index 0000000000..8f6bd17049 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/Waitable.h @@ -0,0 +1,117 @@ +#ifndef QPID_SYS_WAITABLE_H +#define QPID_SYS_WAITABLE_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/Monitor.h" +#include "qpid/sys/ExceptionHolder.h" +#include <assert.h> + +namespace qpid { +namespace sys { + +/** + * A monitor that keeps track of waiting threads. Threads declare a + * ScopedWait around wait() inside a ScopedLock to be considered + * waiters. + * + * Allows waiting threads to be interrupted by an exception. + */ +class Waitable : public Monitor { + public: + Waitable() : waiters(0) {} + + ~Waitable() { assert(waiters == 0); } + + /** Use this inside a scoped lock around the + * call to wait() to be counted as a waiter. + */ + struct ScopedWait { + Waitable& w; + ScopedWait(Waitable& w_) : w(w_) { ++w.waiters; } + ~ScopedWait() { if (--w.waiters==0) w.notifyAll(); } + }; + + /** Block till there are no more waiters in ScopedWaits. + * waitWaiters() does not raise an exception even if waiters + * were interrupted by one. + *@pre Must be called inside a ScopedLock but NOT a ScopedWait. + */ + void waitWaiters() { + while (waiters != 0) + Monitor::wait(); + } + + /** Returns the number of outstanding ScopedWaits. + * Must be called with the lock held. + */ + size_t hasWaiters() const { + return waiters; + } + + /** Set an execption to interrupt waiters in ScopedWait. + * Must be called with the lock held. + */ + void setException(const ExceptionHolder& e) { + exception = e; + notifyAll(); + + } + + /** True if the waitable has an exception */ + bool hasException() const { return exception; } + + /** Throws if the waitable has an exception */ + void checkException() const { exception.raise(); } + + /** Clear the exception if any */ + void resetException() { exception.reset(); } + + /** Throws an exception if one is set before or during the wait. */ + void wait() { + ExCheck e(exception); + Monitor::wait(); + } + + /** Throws an exception if one is set before or during the wait. */ + bool wait(const AbsTime& absoluteTime) { + ExCheck e(exception); + return Monitor::wait(absoluteTime); + } + + private: + struct ExCheck { + const ExceptionHolder& exception; + ExCheck(const ExceptionHolder& e) : exception(e) { e.raise(); } + ~ExCheck() { exception.raise(); } + }; + + size_t waiters; + ExceptionHolder exception; + + friend struct ScopedWait; +}; + +}} // namespace qpid::sys + + + +#endif /*!QPID_SYS_WAITABLE_H*/ diff --git a/qpid/cpp/src/qpid/sys/alloca.h b/qpid/cpp/src/qpid/sys/alloca.h new file mode 100644 index 0000000000..0f58920908 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/alloca.h @@ -0,0 +1,42 @@ +#ifndef QPID_SYS_ALLOCA_H +#define QPID_SYS_ALLOCA_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. + * + */ + +#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 +# if !defined _WINDOWS && !defined WIN32 +# include <alloca.h> +# endif +#endif + +#endif /*!QPID_SYS_ALLOCA_H*/ diff --git a/qpid/cpp/src/qpid/sys/apr/APRBase.cpp b/qpid/cpp/src/qpid/sys/apr/APRBase.cpp new file mode 100644 index 0000000000..8bdba66bdc --- /dev/null +++ b/qpid/cpp/src/qpid/sys/apr/APRBase.cpp @@ -0,0 +1,89 @@ +/* + * + * 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 "qpid/log/Statement.h" +#include "qpid/sys/apr/APRBase.h" + +using namespace qpid::sys; + +APRBase* APRBase::instance = 0; + +APRBase* APRBase::getInstance(){ + if(instance == 0){ + instance = new APRBase(); + } + return instance; +} + + +APRBase::APRBase() : count(0){ + apr_initialize(); + CHECK_APR_SUCCESS(apr_pool_create(&pool, 0)); + CHECK_APR_SUCCESS(apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_NESTED, pool)); +} + +APRBase::~APRBase(){ + CHECK_APR_SUCCESS(apr_thread_mutex_destroy(mutex)); + apr_pool_destroy(pool); + apr_terminate(); +} + +bool APRBase::_increment(){ + bool deleted(false); + CHECK_APR_SUCCESS(apr_thread_mutex_lock(mutex)); + if(this == instance){ + count++; + }else{ + deleted = true; + } + CHECK_APR_SUCCESS(apr_thread_mutex_unlock(mutex)); + return !deleted; +} + +void APRBase::_decrement(){ + APRBase* copy = 0; + CHECK_APR_SUCCESS(apr_thread_mutex_lock(mutex)); + if(--count == 0){ + copy = instance; + instance = 0; + } + CHECK_APR_SUCCESS(apr_thread_mutex_unlock(mutex)); + if(copy != 0){ + delete copy; + } +} + +void APRBase::increment(){ + int count = 0; + while(count++ < 2 && !getInstance()->_increment()) + QPID_LOG(warning, "APR initialization triggered concurrently with termination."); +} + +void APRBase::decrement(){ + getInstance()->_decrement(); +} + +std::string qpid::sys::get_desc(apr_status_t status){ + const int size = 50; + char tmp[size]; + return std::string(apr_strerror(status, tmp, size)); +} + diff --git a/qpid/cpp/src/qpid/sys/apr/APRBase.h b/qpid/cpp/src/qpid/sys/apr/APRBase.h new file mode 100644 index 0000000000..7b5644a129 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/apr/APRBase.h @@ -0,0 +1,74 @@ +/* + * + * 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 _APRBase_ +#define _APRBase_ + +#include <string> +#include <apr_thread_mutex.h> +#include <apr_errno.h> + +namespace qpid { +namespace sys { + + /** + * Use of APR libraries necessitates explicit init and terminate + * calls. Any class using APR libs should obtain the reference to + * this singleton and increment on construction, decrement on + * destruction. This class can then correctly initialise apr + * before the first use and terminate after the last use. + */ + class APRBase{ + static APRBase* instance; + apr_pool_t* pool; + apr_thread_mutex_t* mutex; + int count; + + APRBase(); + ~APRBase(); + static APRBase* getInstance(); + bool _increment(); + void _decrement(); + public: + static void increment(); + static void decrement(); + }; + + //this is also a convenient place for a helper function for error checking: + void check(apr_status_t status, const char* file, const int line); + std::string get_desc(apr_status_t status); + +#define CHECK_APR_SUCCESS(A) qpid::sys::check(A, __FILE__, __LINE__); + +} +} + +// Inlined as it is called *a lot* +void inline qpid::sys::check(apr_status_t status, const char* file, const int line){ + if (status != APR_SUCCESS){ + char tmp[256]; + throw Exception(QPID_MSG(apr_strerror(status, tmp, size))) + } +} + + + + +#endif diff --git a/qpid/cpp/src/qpid/sys/apr/APRPool.cpp b/qpid/cpp/src/qpid/sys/apr/APRPool.cpp new file mode 100644 index 0000000000..e221bfc2f1 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/apr/APRPool.cpp @@ -0,0 +1,41 @@ +/* + * + * 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/apr/APRPool.h" +#include "qpid/sys/apr/APRBase.h" +#include <boost/pool/detail/singleton.hpp> + +using namespace qpid::sys; + +APRPool::APRPool(){ + APRBase::increment(); + CHECK_APR_SUCCESS(apr_pool_create(&pool, NULL)); +} + +APRPool::~APRPool(){ + apr_pool_destroy(pool); + APRBase::decrement(); +} + +apr_pool_t* APRPool::get() { + return boost::details::pool::singleton_default<APRPool>::instance().pool; +} + diff --git a/qpid/cpp/src/qpid/sys/apr/APRPool.h b/qpid/cpp/src/qpid/sys/apr/APRPool.h new file mode 100644 index 0000000000..da7661fcfa --- /dev/null +++ b/qpid/cpp/src/qpid/sys/apr/APRPool.h @@ -0,0 +1,50 @@ +#ifndef _APRPool_ +#define _APRPool_ + +/* + * + * 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/noncopyable.hpp> +#include <apr_pools.h> + +namespace qpid { +namespace sys { +/** + * Singleton APR memory pool. + */ +class APRPool : private boost::noncopyable { + public: + APRPool(); + ~APRPool(); + + /** Get singleton instance */ + static apr_pool_t* get(); + + private: + apr_pool_t* pool; +}; + +}} + + + + + +#endif /*!_APRPool_*/ diff --git a/qpid/cpp/src/qpid/sys/apr/Condition.h b/qpid/cpp/src/qpid/sys/apr/Condition.h new file mode 100644 index 0000000000..66d465ca75 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/apr/Condition.h @@ -0,0 +1,84 @@ +#ifndef _sys_apr_Condition_h +#define _sys_apr_Condition_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/apr/APRPool.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Time.h" + +#include <sys/errno.h> +#include <boost/noncopyable.hpp> +#include <apr_thread_cond.h> + +namespace qpid { +namespace sys { + +/** + * A condition variable for thread synchronization. + */ +class Condition +{ + public: + inline Condition(); + inline ~Condition(); + inline void wait(Mutex&); + inline bool wait(Mutex&, const AbsTime& absoluteTime); + inline void notify(); + inline void notifyAll(); + + private: + apr_thread_cond_t* condition; +}; + + +Condition::Condition() { + CHECK_APR_SUCCESS(apr_thread_cond_create(&condition, APRPool::get())); +} + +Condition::~Condition() { + CHECK_APR_SUCCESS(apr_thread_cond_destroy(condition)); +} + +void Condition::wait(Mutex& mutex) { + CHECK_APR_SUCCESS(apr_thread_cond_wait(condition, mutex.mutex)); +} + +bool Condition::wait(Mutex& mutex, const AbsTime& absoluteTime){ + // APR uses microseconds. + apr_status_t status = + apr_thread_cond_timedwait( + condition, mutex.mutex, Duration(now(), absoluteTime)/TIME_USEC); + if(status != APR_TIMEUP) CHECK_APR_SUCCESS(status); + return status == 0; +} + +void Condition::notify(){ + CHECK_APR_SUCCESS(apr_thread_cond_signal(condition)); +} + +void Condition::notifyAll(){ + CHECK_APR_SUCCESS(apr_thread_cond_broadcast(condition)); +} + +}} +#endif /*!_sys_apr_Condition_h*/ diff --git a/qpid/cpp/src/qpid/sys/apr/Mutex.h b/qpid/cpp/src/qpid/sys/apr/Mutex.h new file mode 100644 index 0000000000..cb75f5b339 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/apr/Mutex.h @@ -0,0 +1,124 @@ +#ifndef _sys_apr_Mutex_h +#define _sys_apr_Mutex_h + +/* + * + * 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. + * + */ + +#include "qpid/sys/apr/APRBase.h" +#include "qpid/sys/apr/APRPool.h" + +#include <boost/noncopyable.hpp> +#include <apr_thread_mutex.h> + +namespace qpid { +namespace sys { + +class Condition; + +/** + * Mutex lock. + */ +class Mutex : private boost::noncopyable { + public: + typedef ScopedLock<Mutex> ScopedLock; + typedef ScopedUnlock<Mutex> ScopedUnlock; + + inline Mutex(); + inline ~Mutex(); + inline void lock(); + inline void unlock(); + inline bool trylock(); + + protected: + apr_thread_mutex_t* mutex; + friend class Condition; +}; + +Mutex::Mutex() { + CHECK_APR_SUCCESS(apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_NESTED, APRPool::get())); +} + +Mutex::~Mutex(){ + CHECK_APR_SUCCESS(apr_thread_mutex_destroy(mutex)); +} + +void Mutex::lock() { + CHECK_APR_SUCCESS(apr_thread_mutex_lock(mutex)); +} +void Mutex::unlock() { + CHECK_APR_SUCCESS(apr_thread_mutex_unlock(mutex)); +} + +bool Mutex::trylock() { + return apr_thread_mutex_trylock(mutex) == 0; +} + + +/** + * RW lock. + */ +class RWlock : private boost::noncopyable { + friend class Condition; + +public: + typedef ScopedRlock<RWlock> ScopedRlock; + typedef ScopedWlock<RWlock> ScopedWlock; + + inline RWlock(); + inline ~RWlock(); + inline void wlock(); // will write-lock + inline void rlock(); // will read-lock + inline void unlock(); + inline bool trywlock(); // will write-try + inline bool tryrlock(); // will read-try + + protected: + apr_thread_mutex_t* mutex; +}; + +RWlock::RWlock() { + CHECK_APR_SUCCESS(apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_NESTED, APRPool::get())); +} + +RWlock::~RWlock(){ + CHECK_APR_SUCCESS(apr_thread_mutex_destroy(mutex)); +} + +void RWlock::wlock() { + CHECK_APR_SUCCESS(apr_thread_mutex_lock(mutex)); +} + +void RWlock::rlock() { + CHECK_APR_SUCCESS(apr_thread_mutex_lock(mutex)); +} + +void RWlock::unlock() { + CHECK_APR_SUCCESS(apr_thread_mutex_unlock(mutex)); +} + +bool RWlock::trywlock() { + return apr_thread_mutex_trylock(mutex) == 0; +} + +bool RWlock::tryrlock() { + return apr_thread_mutex_trylock(mutex) == 0; +} + + +}} +#endif /*!_sys_apr_Mutex_h*/ diff --git a/qpid/cpp/src/qpid/sys/apr/Shlib.cpp b/qpid/cpp/src/qpid/sys/apr/Shlib.cpp new file mode 100644 index 0000000000..b7ee13a03b --- /dev/null +++ b/qpid/cpp/src/qpid/sys/apr/Shlib.cpp @@ -0,0 +1,49 @@ +/* + * 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/Shlib.h" +#include "qpid/sys/apr/APRBase.h" +#include "qpid/sys/apr/APRPool.h" +#include <apr_dso.h> + +namespace qpid { +namespace sys { + +void Shlib::load(const char* libname) { + apr_dso_handle_t* aprHandle; + CHECK_APR_SUCCESS( + apr_dso_load(&aprHandle, libname, APRPool::get())); + handle=aprHandle; +} + +void Shlib::unload() { + CHECK_APR_SUCCESS( + apr_dso_unload(static_cast<apr_dso_handle_t*>(handle))); +} + +void* Shlib::getSymbol(const char* name) { + apr_dso_handle_sym_t symbol; + CHECK_APR_SUCCESS(apr_dso_sym(&symbol, + static_cast<apr_dso_handle_t*>(handle), + name)); + return (void*) symbol; +} + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/apr/Socket.cpp b/qpid/cpp/src/qpid/sys/apr/Socket.cpp new file mode 100644 index 0000000000..d9024d11c1 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/apr/Socket.cpp @@ -0,0 +1,114 @@ +/* + * + * 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/Socket.h" + +#include "qpid/sys/apr/APRBase.h" +#include "qpid/sys/apr/APRPool.h" + +#include <apr_network_io.h> + +namespace qpid { +namespace sys { + +class SocketPrivate { +public: + SocketPrivate(apr_socket_t* s = 0) : + socket(s) + {} + + apr_socket_t* socket; +}; + +Socket::Socket() : + impl(new SocketPrivate) +{ + createTcp(); +} + +Socket::Socket(SocketPrivate* sp) : + impl(sp) +{} + +Socket::~Socket() { + delete impl; +} + +void Socket::createTcp() const { + apr_socket_t*& socket = impl->socket; + apr_socket_t* s; + CHECK_APR_SUCCESS( + apr_socket_create( + &s, APR_INET, SOCK_STREAM, APR_PROTO_TCP, + APRPool::get())); + socket = s; +} + +void Socket::setTimeout(const Duration& interval) const { + apr_socket_t*& socket = impl->socket; + apr_socket_timeout_set(socket, interval/TIME_USEC); +} + +void Socket::connect(const std::string& host, int port) const { + apr_socket_t*& socket = impl->socket; + apr_sockaddr_t* address; + CHECK_APR_SUCCESS( + apr_sockaddr_info_get( + &address, host.c_str(), APR_UNSPEC, port, APR_IPV4_ADDR_OK, + APRPool::get())); + CHECK_APR_SUCCESS(apr_socket_connect(socket, address)); +} + +void Socket::close() const { + apr_socket_t*& socket = impl->socket; + if (socket == 0) return; + CHECK_APR_SUCCESS(apr_socket_close(socket)); + socket = 0; +} + +ssize_t Socket::send(const void* data, size_t size) const +{ + apr_socket_t*& socket = impl->socket; + apr_size_t sent = size; + apr_status_t status = + apr_socket_send(socket, reinterpret_cast<const char*>(data), &sent); + if (APR_STATUS_IS_TIMEUP(status)) return SOCKET_TIMEOUT; + if (APR_STATUS_IS_EOF(status)) return SOCKET_EOF; + CHECK_APR_SUCCESS(status); + return sent; +} + +ssize_t Socket::recv(void* data, size_t size) const +{ + apr_socket_t*& socket = impl->socket; + apr_size_t received = size; + apr_status_t status = + apr_socket_recv(socket, reinterpret_cast<char*>(data), &received); + if (APR_STATUS_IS_TIMEUP(status)) + return SOCKET_TIMEOUT; + if (APR_STATUS_IS_EOF(status)) + return SOCKET_EOF; + CHECK_APR_SUCCESS(status); + return received; +} + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/apr/Thread.cpp b/qpid/cpp/src/qpid/sys/apr/Thread.cpp new file mode 100644 index 0000000000..b52d0e6ace --- /dev/null +++ b/qpid/cpp/src/qpid/sys/apr/Thread.cpp @@ -0,0 +1,34 @@ +/* + * + * 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/apr/Thread.h" +#include "qpid/sys/Runnable.h" + +using namespace qpid::sys; +using qpid::sys::Runnable; + +void* APR_THREAD_FUNC Thread::runRunnable(apr_thread_t* thread, void *data) { + reinterpret_cast<Runnable*>(data)->run(); + CHECK_APR_SUCCESS(apr_thread_exit(thread, APR_SUCCESS)); + return NULL; +} + + diff --git a/qpid/cpp/src/qpid/sys/apr/Thread.h b/qpid/cpp/src/qpid/sys/apr/Thread.h new file mode 100644 index 0000000000..6cc63db5c9 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/apr/Thread.h @@ -0,0 +1,106 @@ +#ifndef _sys_apr_Thread_h +#define _sys_apr_Thread_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/apr/APRPool.h" +#include "qpid/sys/apr/APRBase.h" + +#include <apr_thread_proc.h> +#include <apr_portable.h> + +namespace qpid { +namespace sys { + +class Runnable; + +class Thread +{ + public: + inline static Thread current(); + + /** ID of current thread for logging. + * Workaround for broken Thread::current() in APR + */ + inline static long logId(); + + inline static void yield(); + + inline Thread(); + inline explicit Thread(qpid::sys::Runnable*); + inline explicit Thread(qpid::sys::Runnable&); + + inline void join(); + + inline long id(); + + private: + static void* APR_THREAD_FUNC runRunnable(apr_thread_t* thread, void *data); + inline Thread(apr_thread_t* t); + apr_thread_t* thread; +}; + +Thread::Thread() : thread(0) {} + +Thread::Thread(Runnable* runnable) { + CHECK_APR_SUCCESS( + apr_thread_create(&thread, 0, runRunnable, runnable, APRPool::get())); +} + +Thread::Thread(Runnable& runnable) { + CHECK_APR_SUCCESS( + apr_thread_create(&thread, 0, runRunnable, &runnable, APRPool::get())); +} + +void Thread::join(){ + apr_status_t status; + if (thread != 0) + CHECK_APR_SUCCESS(apr_thread_join(&status, thread)); +} + +long Thread::id() { + return long(thread); +} + +/** ID of current thread for logging. + * Workaround for broken Thread::current() in APR + */ +long Thread::logId() { + return static_cast<long>(apr_os_thread_current()); +} + +Thread::Thread(apr_thread_t* t) : thread(t) {} + +Thread Thread::current(){ + apr_thread_t* thr; + apr_os_thread_t osthr = apr_os_thread_current(); + CHECK_APR_SUCCESS(apr_os_thread_put(&thr, &osthr, APRPool::get())); + return Thread(thr); +} + +void Thread::yield() +{ + apr_thread_yield(); +} + +}} +#endif /*!_sys_apr_Thread_h*/ diff --git a/qpid/cpp/src/qpid/sys/apr/Time.cpp b/qpid/cpp/src/qpid/sys/apr/Time.cpp new file mode 100644 index 0000000000..34e740b144 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/apr/Time.cpp @@ -0,0 +1,36 @@ +/* + * + * 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" + +#include <apr_time.h> + +namespace qpid { +namespace sys { + +AbsTime AbsTime::now() { + AbsTime time_now; + time_now.time_ns = apr_time_now() * TIME_USEC; + return time_now; +} + +}} + diff --git a/qpid/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.cpp b/qpid/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.cpp new file mode 100644 index 0000000000..3d868da64b --- /dev/null +++ b/qpid/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.cpp @@ -0,0 +1,127 @@ +/* + * + * 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/cyrus/CyrusSecurityLayer.h" +#include <algorithm> +#include "qpid/framing/reply_exceptions.h" +#include "qpid/log/Statement.h" +#include <string.h> + +namespace qpid { +namespace sys { +namespace cyrus { + +CyrusSecurityLayer::CyrusSecurityLayer(sasl_conn_t* c, uint16_t maxFrameSize) : + conn(c), decrypted(0), decryptedSize(0), encrypted(0), encryptedSize(0), codec(0), maxInputSize(0), + decodeBuffer(maxFrameSize), encodeBuffer(maxFrameSize), encoded(0) +{ + const void* value(0); + int result = sasl_getprop(conn, SASL_MAXOUTBUF, &value); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL encode error: " << sasl_errdetail(conn))); + } + maxInputSize = *(reinterpret_cast<const unsigned*>(value)); +} + +size_t CyrusSecurityLayer::decode(const char* input, size_t size) +{ + size_t inStart = 0; + do { + size_t inSize = std::min(size - inStart, maxInputSize); + int result = sasl_decode(conn, input + inStart, inSize, &decrypted, &decryptedSize); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL decode error: " << sasl_errdetail(conn))); + } + inStart += inSize; + size_t copied = 0; + do { + size_t count = std::min(decryptedSize - copied, decodeBuffer.size - decodeBuffer.position); + ::memcpy(decodeBuffer.data + decodeBuffer.position, decrypted + copied, count); + copied += count; + decodeBuffer.position += count; + size_t decodedSize = codec->decode(decodeBuffer.data, decodeBuffer.position); + if (decodedSize < decodeBuffer.position) { + ::memmove(decodeBuffer.data, decodeBuffer.data + decodedSize, decodeBuffer.position - decodedSize); + } + decodeBuffer.position -= decodedSize; + } while (copied < decryptedSize); + } while (inStart < size); + return size; +} + +size_t CyrusSecurityLayer::encode(const char* buffer, size_t size) +{ + size_t processed = 0;//records how many bytes have been written to buffer + do { + if (!encrypted) { + if (!encoded) { + encodeBuffer.position = 0; + encoded = codec->encode(encodeBuffer.data, encodeBuffer.size); + if (!encoded) break;//nothing more to do + } + + size_t encryptable = std::min(encoded, maxInputSize); + int result = sasl_encode(conn, encodeBuffer.data + encodeBuffer.position, encryptable, &encrypted, &encryptedSize); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL encode error: " << sasl_errdetail(conn))); + } + encodeBuffer.position += encryptable; + encoded -= encryptable; + } + size_t remaining = size - processed; + if (remaining < encryptedSize) { + //can't fit all encrypted data in the buffer we've + //been given, copy in what we can and hold on to the + //rest until the next call + ::memcpy(const_cast<char*>(buffer + processed), encrypted, remaining); + processed += remaining; + encrypted += remaining; + encryptedSize -= remaining; + } else { + ::memcpy(const_cast<char*>(buffer + processed), encrypted, encryptedSize); + processed += encryptedSize; + encrypted = 0; + encryptedSize = 0; + } + } while (processed < size); + return processed; +} + +bool CyrusSecurityLayer::canEncode() +{ + return codec && (encrypted || codec->canEncode()); +} + +void CyrusSecurityLayer::init(qpid::sys::Codec* c) +{ + codec = c; +} + +CyrusSecurityLayer::DataBuffer::DataBuffer(size_t s) : position(0), size(s) +{ + data = new char[size]; +} + +CyrusSecurityLayer::DataBuffer::~DataBuffer() +{ + delete[] data; +} + +}}} // namespace qpid::sys::cyrus diff --git a/qpid/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.h b/qpid/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.h new file mode 100644 index 0000000000..1645cf1a58 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.h @@ -0,0 +1,68 @@ +#ifndef QPID_SYS_CYRUS_CYRUSSECURITYLAYER_H +#define QPID_SYS_CYRUS_CYRUSSECURITYLAYER_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/IntegerTypes.h" +#include "qpid/sys/SecurityLayer.h" +#include <sasl/sasl.h> + +namespace qpid { +namespace sys { +namespace cyrus { + + +/** + * Implementation of SASL security layer using cyrus-sasl library + */ +class CyrusSecurityLayer : public qpid::sys::SecurityLayer +{ + public: + CyrusSecurityLayer(sasl_conn_t*, uint16_t maxFrameSize); + size_t decode(const char* buffer, size_t size); + size_t encode(const char* buffer, size_t size); + bool canEncode(); + void init(qpid::sys::Codec*); + private: + struct DataBuffer + { + char* data; + size_t position; + const size_t size; + DataBuffer(size_t); + ~DataBuffer(); + }; + + sasl_conn_t* conn; + const char* decrypted; + unsigned decryptedSize; + const char* encrypted; + unsigned encryptedSize; + qpid::sys::Codec* codec; + size_t maxInputSize; + DataBuffer decodeBuffer; + DataBuffer encodeBuffer; + size_t encoded; +}; +}}} // namespace qpid::sys::cyrus + +#endif /*!QPID_SYS_CYRUS_CYRUSSECURITYLAYER_H*/ diff --git a/qpid/cpp/src/qpid/sys/epoll/EpollPoller.cpp b/qpid/cpp/src/qpid/sys/epoll/EpollPoller.cpp new file mode 100644 index 0000000000..9ad05c71a3 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/epoll/EpollPoller.cpp @@ -0,0 +1,674 @@ +/* + * + * 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/Poller.h" +#include "qpid/sys/IOHandle.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/AtomicCount.h" +#include "qpid/sys/DeletionManager.h" +#include "qpid/sys/posix/check.h" +#include "qpid/sys/posix/PrivatePosix.h" +#include "qpid/log/Statement.h" + +#include <sys/epoll.h> +#include <errno.h> +#include <signal.h> + +#include <assert.h> +#include <queue> +#include <set> +#include <exception> + +namespace qpid { +namespace sys { + +// Deletion manager to handle deferring deletion of PollerHandles to when they definitely aren't being used +DeletionManager<PollerHandlePrivate> PollerHandleDeletionManager; + +// Instantiate (and define) class static for DeletionManager +template <> +DeletionManager<PollerHandlePrivate>::AllThreadsStatuses DeletionManager<PollerHandlePrivate>::allThreadsStatuses(0); + +class PollerHandlePrivate { + friend class Poller; + friend class PollerPrivate; + friend class PollerHandle; + + enum FDStat { + ABSENT, + MONITORED, + INACTIVE, + HUNGUP, + MONITORED_HUNGUP, + INTERRUPTED, + INTERRUPTED_HUNGUP, + DELETED + }; + + ::__uint32_t events; + const IOHandlePrivate* ioHandle; + PollerHandle* pollerHandle; + FDStat stat; + Mutex lock; + + PollerHandlePrivate(const IOHandlePrivate* h, PollerHandle* p) : + events(0), + ioHandle(h), + pollerHandle(p), + stat(ABSENT) { + } + + int fd() const { + return toFd(ioHandle); + } + + bool isActive() const { + return stat == MONITORED || stat == MONITORED_HUNGUP; + } + + void setActive() { + stat = (stat == HUNGUP || stat == INTERRUPTED_HUNGUP) + ? MONITORED_HUNGUP + : MONITORED; + } + + bool isInactive() const { + return stat == INACTIVE || stat == HUNGUP; + } + + void setInactive() { + stat = INACTIVE; + } + + bool isIdle() const { + return stat == ABSENT; + } + + void setIdle() { + stat = ABSENT; + } + + bool isHungup() const { + return + stat == MONITORED_HUNGUP || + stat == HUNGUP || + stat == INTERRUPTED_HUNGUP; + } + + void setHungup() { + assert(stat == MONITORED); + stat = HUNGUP; + } + + bool isInterrupted() const { + return stat == INTERRUPTED || stat == INTERRUPTED_HUNGUP; + } + + void setInterrupted() { + stat = (stat == MONITORED_HUNGUP || stat == HUNGUP) + ? INTERRUPTED_HUNGUP + : INTERRUPTED; + } + + bool isDeleted() const { + return stat == DELETED; + } + + void setDeleted() { + stat = DELETED; + } +}; + +PollerHandle::PollerHandle(const IOHandle& h) : + impl(new PollerHandlePrivate(h.impl, this)) +{} + +PollerHandle::~PollerHandle() { + { + ScopedLock<Mutex> l(impl->lock); + if (impl->isDeleted()) { + return; + } + impl->pollerHandle = 0; + if (impl->isInterrupted()) { + impl->setDeleted(); + return; + } + assert(impl->isIdle()); + impl->setDeleted(); + } + PollerHandleDeletionManager.markForDeletion(impl); +} + +class HandleSet +{ + Mutex lock; + std::set<PollerHandle*> handles; + public: + void add(PollerHandle*); + void remove(PollerHandle*); + void cleanup(); +}; + +void HandleSet::add(PollerHandle* h) +{ + ScopedLock<Mutex> l(lock); + handles.insert(h); +} +void HandleSet::remove(PollerHandle* h) +{ + ScopedLock<Mutex> l(lock); + handles.erase(h); +} +void HandleSet::cleanup() +{ + // Inform all registered handles of disconnection + std::set<PollerHandle*> copy; + handles.swap(copy); + for (std::set<PollerHandle*>::const_iterator i = copy.begin(); i != copy.end(); ++i) { + Poller::Event event(*i, Poller::DISCONNECTED); + event.process(); + } +} + +/** + * Concrete implementation of Poller to use the Linux specific epoll + * interface + */ +class PollerPrivate { + friend class Poller; + + static const int DefaultFds = 256; + + struct ReadablePipe { + int fds[2]; + + /** + * This encapsulates an always readable pipe which we can add + * to the epoll set to force epoll_wait to return + */ + ReadablePipe() { + QPID_POSIX_CHECK(::pipe(fds)); + // Just write the pipe's fds to the pipe + QPID_POSIX_CHECK(::write(fds[1], fds, 2)); + } + + ~ReadablePipe() { + ::close(fds[0]); + ::close(fds[1]); + } + + int getFD() { + return fds[0]; + } + }; + + static ReadablePipe alwaysReadable; + static int alwaysReadableFd; + + class InterruptHandle: public PollerHandle { + std::queue<PollerHandle*> handles; + + void processEvent(Poller::EventType) { + PollerHandle* handle = handles.front(); + handles.pop(); + assert(handle); + + // Synthesise event + Poller::Event event(handle, Poller::INTERRUPTED); + + // Process synthesised event + event.process(); + } + + public: + InterruptHandle() : + PollerHandle(DummyIOHandle) + {} + + void addHandle(PollerHandle& h) { + handles.push(&h); + } + + PollerHandle* getHandle() { + PollerHandle* handle = handles.front(); + handles.pop(); + return handle; + } + + bool queuedHandles() { + return handles.size() > 0; + } + }; + + const int epollFd; + bool isShutdown; + InterruptHandle interruptHandle; + HandleSet registeredHandles; + AtomicCount threadCount; + + static ::__uint32_t directionToEpollEvent(Poller::Direction dir) { + switch (dir) { + case Poller::INPUT: return ::EPOLLIN; + case Poller::OUTPUT: return ::EPOLLOUT; + case Poller::INOUT: return ::EPOLLIN | ::EPOLLOUT; + default: return 0; + } + } + + static Poller::EventType epollToDirection(::__uint32_t events) { + // POLLOUT & POLLHUP are mutually exclusive really, but at least socketpairs + // can give you both! + events = (events & ::EPOLLHUP) ? events & ~::EPOLLOUT : events; + ::__uint32_t e = events & (::EPOLLIN | ::EPOLLOUT); + switch (e) { + case ::EPOLLIN: return Poller::READABLE; + case ::EPOLLOUT: return Poller::WRITABLE; + case ::EPOLLIN | ::EPOLLOUT: return Poller::READ_WRITABLE; + default: + return (events & (::EPOLLHUP | ::EPOLLERR)) ? + Poller::DISCONNECTED : Poller::INVALID; + } + } + + PollerPrivate() : + epollFd(::epoll_create(DefaultFds)), + isShutdown(false) { + QPID_POSIX_CHECK(epollFd); + // Add always readable fd into our set (but not listening to it yet) + ::epoll_event epe; + epe.events = 0; + epe.data.u64 = 1; + QPID_POSIX_CHECK(::epoll_ctl(epollFd, EPOLL_CTL_ADD, alwaysReadableFd, &epe)); + } + + ~PollerPrivate() { + // It's probably okay to ignore any errors here as there can't be data loss + ::close(epollFd); + + // Need to put the interruptHandle in idle state to delete it + static_cast<PollerHandle&>(interruptHandle).impl->setIdle(); + } + + void resetMode(PollerHandlePrivate& handle); + + void interrupt() { + ::epoll_event epe; + // Use EPOLLONESHOT so we only wake a single thread + epe.events = ::EPOLLIN | ::EPOLLONESHOT; + epe.data.u64 = 0; // Keep valgrind happy + epe.data.ptr = &static_cast<PollerHandle&>(interruptHandle); + QPID_POSIX_CHECK(::epoll_ctl(epollFd, EPOLL_CTL_MOD, alwaysReadableFd, &epe)); + } + + void interruptAll() { + ::epoll_event epe; + // Not EPOLLONESHOT, so we eventually get all threads + epe.events = ::EPOLLIN; + epe.data.u64 = 2; // Keep valgrind happy + QPID_POSIX_CHECK(::epoll_ctl(epollFd, EPOLL_CTL_MOD, alwaysReadableFd, &epe)); + } +}; + +PollerPrivate::ReadablePipe PollerPrivate::alwaysReadable; +int PollerPrivate::alwaysReadableFd = alwaysReadable.getFD(); + +void Poller::registerHandle(PollerHandle& handle) { + PollerHandlePrivate& eh = *handle.impl; + ScopedLock<Mutex> l(eh.lock); + assert(eh.isIdle()); + + ::epoll_event epe; + epe.events = ::EPOLLONESHOT; + epe.data.u64 = 0; // Keep valgrind happy + epe.data.ptr = &eh; + + impl->registeredHandles.add(&handle); + QPID_POSIX_CHECK(::epoll_ctl(impl->epollFd, EPOLL_CTL_ADD, eh.fd(), &epe)); + + eh.setActive(); +} + +void Poller::unregisterHandle(PollerHandle& handle) { + PollerHandlePrivate& eh = *handle.impl; + ScopedLock<Mutex> l(eh.lock); + assert(!eh.isIdle()); + + impl->registeredHandles.remove(&handle); + int rc = ::epoll_ctl(impl->epollFd, EPOLL_CTL_DEL, eh.fd(), 0); + // Ignore EBADF since deleting a nonexistent fd has the overall required result! + // And allows the case where a sloppy program closes the fd and then does the delFd() + if (rc == -1 && errno != EBADF) { + QPID_POSIX_CHECK(rc); + } + + eh.setIdle(); +} + +void PollerPrivate::resetMode(PollerHandlePrivate& eh) { + PollerHandle* ph; + { + ScopedLock<Mutex> l(eh.lock); + assert(!eh.isActive()); + + if (eh.isIdle() || eh.isDeleted()) { + return; + } + + if (eh.events==0) { + eh.setActive(); + return; + } + + if (!eh.isInterrupted()) { + ::epoll_event epe; + epe.events = eh.events | ::EPOLLONESHOT; + epe.data.u64 = 0; // Keep valgrind happy + epe.data.ptr = &eh; + + QPID_POSIX_CHECK(::epoll_ctl(epollFd, EPOLL_CTL_MOD, eh.fd(), &epe)); + + eh.setActive(); + return; + } + ph = eh.pollerHandle; + } + + PollerHandlePrivate& ihp = *static_cast<PollerHandle&>(interruptHandle).impl; + ScopedLock<Mutex> l(ihp.lock); + interruptHandle.addHandle(*ph); + ihp.setActive(); + interrupt(); +} + +void Poller::monitorHandle(PollerHandle& handle, Direction dir) { + PollerHandlePrivate& eh = *handle.impl; + ScopedLock<Mutex> l(eh.lock); + assert(!eh.isIdle()); + + ::__uint32_t oldEvents = eh.events; + eh.events |= PollerPrivate::directionToEpollEvent(dir); + + // If no change nothing more to do - avoid unnecessary system call + if (oldEvents==eh.events) { + return; + } + + // If we're not actually listening wait till we are to perform change + if (!eh.isActive()) { + return; + } + + ::epoll_event epe; + epe.events = eh.events | ::EPOLLONESHOT; + epe.data.u64 = 0; // Keep valgrind happy + epe.data.ptr = &eh; + + QPID_POSIX_CHECK(::epoll_ctl(impl->epollFd, EPOLL_CTL_MOD, eh.fd(), &epe)); +} + +void Poller::unmonitorHandle(PollerHandle& handle, Direction dir) { + PollerHandlePrivate& eh = *handle.impl; + ScopedLock<Mutex> l(eh.lock); + assert(!eh.isIdle()); + + ::__uint32_t oldEvents = eh.events; + eh.events &= ~PollerPrivate::directionToEpollEvent(dir); + + // If no change nothing more to do - avoid unnecessary system call + if (oldEvents==eh.events) { + return; + } + + // If we're not actually listening wait till we are to perform change + if (!eh.isActive()) { + return; + } + + ::epoll_event epe; + epe.events = eh.events | ::EPOLLONESHOT; + epe.data.u64 = 0; // Keep valgrind happy + epe.data.ptr = &eh; + + QPID_POSIX_CHECK(::epoll_ctl(impl->epollFd, EPOLL_CTL_MOD, eh.fd(), &epe)); +} + +void Poller::shutdown() { + // NB: this function must be async-signal safe, it must not + // call any function that is not async-signal safe. + + // Allow sloppy code to shut us down more than once + if (impl->isShutdown) + return; + + // Don't use any locking here - isShutdown will be visible to all + // after the epoll_ctl() anyway (it's a memory barrier) + impl->isShutdown = true; + + impl->interruptAll(); +} + +bool Poller::interrupt(PollerHandle& handle) { + { + PollerHandlePrivate& eh = *handle.impl; + ScopedLock<Mutex> l(eh.lock); + if (eh.isIdle() || eh.isDeleted()) { + return false; + } + + if (eh.isInterrupted()) { + return true; + } + + // Stop monitoring handle for read or write + ::epoll_event epe; + epe.events = 0; + epe.data.u64 = 0; // Keep valgrind happy + epe.data.ptr = &eh; + QPID_POSIX_CHECK(::epoll_ctl(impl->epollFd, EPOLL_CTL_MOD, eh.fd(), &epe)); + + if (eh.isInactive()) { + eh.setInterrupted(); + return true; + } + eh.setInterrupted(); + } + + PollerPrivate::InterruptHandle& ih = impl->interruptHandle; + PollerHandlePrivate& eh = *static_cast<PollerHandle&>(ih).impl; + ScopedLock<Mutex> l(eh.lock); + ih.addHandle(handle); + + impl->interrupt(); + eh.setActive(); + return true; +} + +void Poller::run() { + // Ensure that we exit thread responsibly under all circumstances + try { + // Make sure we can't be interrupted by signals at a bad time + ::sigset_t ss; + ::sigfillset(&ss); + ::pthread_sigmask(SIG_SETMASK, &ss, 0); + + ++(impl->threadCount); + do { + Event event = wait(); + + // If can read/write then dispatch appropriate callbacks + if (event.handle) { + event.process(); + } else { + // Handle shutdown + switch (event.type) { + case SHUTDOWN: + PollerHandleDeletionManager.destroyThreadState(); + //last thread to respond to shutdown cleans up: + if (--(impl->threadCount) == 0) impl->registeredHandles.cleanup(); + return; + default: + // This should be impossible + assert(false); + } + } + } while (true); + } catch (const std::exception& e) { + QPID_LOG(error, "IO worker thread exiting with unhandled exception: " << e.what()); + } + PollerHandleDeletionManager.destroyThreadState(); + --(impl->threadCount); +} + +bool Poller::hasShutdown() +{ + return impl->isShutdown; +} + +Poller::Event Poller::wait(Duration timeout) { + static __thread PollerHandlePrivate* lastReturnedHandle = 0; + epoll_event epe; + int timeoutMs = (timeout == TIME_INFINITE) ? -1 : timeout / TIME_MSEC; + AbsTime targetTimeout = + (timeout == TIME_INFINITE) ? + FAR_FUTURE : + AbsTime(now(), timeout); + + if (lastReturnedHandle) { + impl->resetMode(*lastReturnedHandle); + lastReturnedHandle = 0; + } + + // Repeat until we weren't interrupted by signal + do { + PollerHandleDeletionManager.markAllUnusedInThisThread(); + int rc = ::epoll_wait(impl->epollFd, &epe, 1, timeoutMs); + if (rc ==-1 && errno != EINTR) { + QPID_POSIX_CHECK(rc); + } else if (rc > 0) { + assert(rc == 1); + void* dataPtr = epe.data.ptr; + + // Check if this is an interrupt + PollerPrivate::InterruptHandle& interruptHandle = impl->interruptHandle; + if (dataPtr == &interruptHandle) { + // If we are shutting down we need to rearm the shutdown interrupt to + // ensure everyone still sees it. It's okay that this might be overridden + // below as we will be back here if it is. + if (impl->isShutdown) { + impl->interruptAll(); + } + PollerHandle* wrappedHandle = 0; + { + ScopedLock<Mutex> l(interruptHandle.impl->lock); + if (interruptHandle.impl->isActive()) { + wrappedHandle = interruptHandle.getHandle(); + // If there is an interrupt queued behind this one we need to arm it + // We do it this way so that another thread can pick it up + if (interruptHandle.queuedHandles()) { + impl->interrupt(); + interruptHandle.impl->setActive(); + } else { + interruptHandle.impl->setInactive(); + } + } + } + if (wrappedHandle) { + PollerHandlePrivate& eh = *wrappedHandle->impl; + { + ScopedLock<Mutex> l(eh.lock); + if (!eh.isDeleted()) { + if (!eh.isIdle()) { + eh.setInactive(); + } + lastReturnedHandle = &eh; + assert(eh.pollerHandle == wrappedHandle); + return Event(wrappedHandle, INTERRUPTED); + } + } + PollerHandleDeletionManager.markForDeletion(&eh); + } + continue; + } + + // Check for shutdown + if (impl->isShutdown) { + PollerHandleDeletionManager.markAllUnusedInThisThread(); + return Event(0, SHUTDOWN); + } + + PollerHandlePrivate& eh = *static_cast<PollerHandlePrivate*>(dataPtr); + ScopedLock<Mutex> l(eh.lock); + + // the handle could have gone inactive since we left the epoll_wait + if (eh.isActive()) { + PollerHandle* handle = eh.pollerHandle; + assert(handle); + + // If the connection has been hungup we could still be readable + // (just not writable), allow us to readable until we get here again + if (epe.events & ::EPOLLHUP) { + if (eh.isHungup()) { + eh.setInactive(); + // Don't set up last Handle so that we don't reset this handle + // on re-entering Poller::wait. This means that we will never + // be set active again once we've returned disconnected, and so + // can never be returned again. + return Event(handle, DISCONNECTED); + } + eh.setHungup(); + } else { + eh.setInactive(); + } + lastReturnedHandle = &eh; + return Event(handle, PollerPrivate::epollToDirection(epe.events)); + } + } + // We only get here if one of the following: + // * epoll_wait was interrupted by a signal + // * epoll_wait timed out + // * the state of the handle changed after being returned by epoll_wait + // + // The only things we can do here are return a timeout or wait more. + // Obviously if we timed out we return timeout; if the wait was meant to + // be indefinite then we should never return with a time out so we go again. + // If the wait wasn't indefinite, we check whether we are after the target wait + // time or not + if (timeoutMs == -1) { + continue; + } + if (rc == 0 && now() > targetTimeout) { + PollerHandleDeletionManager.markAllUnusedInThisThread(); + return Event(0, TIMEOUT); + } + } while (true); +} + +// Concrete constructors +Poller::Poller() : + impl(new PollerPrivate()) +{} + +Poller::~Poller() { + delete impl; +} + +}} diff --git a/qpid/cpp/src/qpid/sys/posix/AsynchIO.cpp b/qpid/cpp/src/qpid/sys/posix/AsynchIO.cpp new file mode 100644 index 0000000000..b5a0b0bf32 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/posix/AsynchIO.cpp @@ -0,0 +1,611 @@ +/* + * + * 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/AsynchIO.h" +#include "qpid/sys/Socket.h" +#include "qpid/sys/SocketAddress.h" +#include "qpid/sys/Poller.h" +#include "qpid/sys/DispatchHandle.h" +#include "qpid/sys/Time.h" +#include "qpid/log/Statement.h" + +#include "qpid/sys/posix/check.h" + +// TODO The basic algorithm here is not really POSIX specific and with a +// bit more abstraction could (should) be promoted to be platform portable +#include <unistd.h> +#include <sys/socket.h> +#include <signal.h> +#include <errno.h> +#include <string.h> + +#include <boost/bind.hpp> +#include <boost/lexical_cast.hpp> + +using namespace qpid::sys; + +namespace { + +struct StaticInit { + StaticInit() { + /** + * Make *process* not generate SIGPIPE when writing to closed + * pipe/socket (necessary as default action is to terminate process) + */ + ::signal(SIGPIPE, SIG_IGN); + }; +} init; + +/* + * We keep per thread state to avoid locking overhead. The assumption is that + * on average all the connections are serviced by all the threads so the state + * recorded in each thread is about the same. If this turns out not to be the + * case we could rebalance the info occasionally. + */ +__thread int threadReadTotal = 0; +__thread int threadMaxRead = 0; +__thread int threadReadCount = 0; +__thread int threadWriteTotal = 0; +__thread int threadWriteCount = 0; +__thread int64_t threadMaxReadTimeNs = 2 * 1000000; // start at 2ms +} + +/* + * Asynch Acceptor + */ +namespace qpid { +namespace sys { +namespace posix { + +class AsynchAcceptor : public qpid::sys::AsynchAcceptor { +public: + AsynchAcceptor(const Socket& s, AsynchAcceptor::Callback callback); + ~AsynchAcceptor(); + void start(Poller::shared_ptr poller); + +private: + void readable(DispatchHandle& handle); + +private: + AsynchAcceptor::Callback acceptedCallback; + DispatchHandle handle; + const Socket& socket; + +}; + +AsynchAcceptor::AsynchAcceptor(const Socket& s, + AsynchAcceptor::Callback callback) : + acceptedCallback(callback), + handle(s, boost::bind(&AsynchAcceptor::readable, this, _1), 0, 0), + socket(s) { + + s.setNonblocking(); +} + +AsynchAcceptor::~AsynchAcceptor() { + handle.stopWatch(); +} + +void AsynchAcceptor::start(Poller::shared_ptr poller) { + handle.startWatch(poller); +} + +/* + * We keep on accepting as long as there is something to accept + */ +void AsynchAcceptor::readable(DispatchHandle& h) { + Socket* s; + do { + errno = 0; + // TODO: Currently we ignore the peers address, perhaps we should + // log it or use it for connection acceptance. + try { + s = socket.accept(); + if (s) { + acceptedCallback(*s); + } else { + break; + } + } catch (const std::exception& e) { + QPID_LOG(error, "Could not accept socket: " << e.what()); + break; + } + } while (true); + + h.rewatch(); +} + +/* + * POSIX version of AsynchIO TCP socket connector. + * + * The class is implemented in terms of DispatchHandle to allow it to be + * deleted by deleting the contained DispatchHandle. + */ +class AsynchConnector : public qpid::sys::AsynchConnector, + private DispatchHandle { + +private: + void connComplete(DispatchHandle& handle); + +private: + ConnectedCallback connCallback; + FailedCallback failCallback; + const Socket& socket; + +public: + AsynchConnector(const Socket& socket, + const std::string& hostname, + const std::string& port, + ConnectedCallback connCb, + FailedCallback failCb); + void start(Poller::shared_ptr poller); + void stop(); +}; + +AsynchConnector::AsynchConnector(const Socket& s, + const std::string& hostname, + const std::string& port, + ConnectedCallback connCb, + FailedCallback failCb) : + DispatchHandle(s, + 0, + boost::bind(&AsynchConnector::connComplete, this, _1), + boost::bind(&AsynchConnector::connComplete, this, _1)), + connCallback(connCb), + failCallback(failCb), + socket(s) +{ + socket.setNonblocking(); + SocketAddress sa(hostname, port); + // Note, not catching any exceptions here, also has effect of destructing + socket.connect(sa); +} + +void AsynchConnector::start(Poller::shared_ptr poller) +{ + startWatch(poller); +} + +void AsynchConnector::stop() +{ + stopWatch(); +} + +void AsynchConnector::connComplete(DispatchHandle& h) +{ + h.stopWatch(); + int errCode = socket.getError(); + if (errCode == 0) { + connCallback(socket); + } else { + failCallback(socket, errCode, strError(errCode)); + } + DispatchHandle::doDelete(); +} + +/* + * POSIX version of AsynchIO reader/writer + * + * The class is implemented in terms of DispatchHandle to allow it to be + * deleted by deleting the contained DispatchHandle. + */ +class AsynchIO : public qpid::sys::AsynchIO, private DispatchHandle { + +public: + AsynchIO(const Socket& s, + ReadCallback rCb, + EofCallback eofCb, + DisconnectCallback disCb, + ClosedCallback cCb = 0, + BuffersEmptyCallback eCb = 0, + IdleCallback iCb = 0); + + // Methods inherited from qpid::sys::AsynchIO + + virtual void queueForDeletion(); + + virtual void start(Poller::shared_ptr poller); + virtual void queueReadBuffer(BufferBase* buff); + virtual void unread(BufferBase* buff); + virtual void queueWrite(BufferBase* buff); + virtual void notifyPendingWrite(); + virtual void queueWriteClose(); + virtual bool writeQueueEmpty(); + virtual void startReading(); + virtual void stopReading(); + virtual void requestCallback(RequestCallback); + virtual BufferBase* getQueuedBuffer(); + +private: + ~AsynchIO(); + + // Methods that are callback targets from Dispatcher. + void readable(DispatchHandle& handle); + void writeable(DispatchHandle& handle); + void disconnected(DispatchHandle& handle); + void requestedCall(RequestCallback); + void close(DispatchHandle& handle); + +private: + ReadCallback readCallback; + EofCallback eofCallback; + DisconnectCallback disCallback; + ClosedCallback closedCallback; + BuffersEmptyCallback emptyCallback; + IdleCallback idleCallback; + const Socket& socket; + std::deque<BufferBase*> bufferQueue; + std::deque<BufferBase*> writeQueue; + bool queuedClose; + /** + * This flag is used to detect and handle concurrency between + * calls to notifyPendingWrite() (which can be made from any thread) and + * the execution of the writeable() method (which is always on the + * thread processing this handle. + */ + volatile bool writePending; + /** + * This records whether we've been reading is flow controlled: + * it's safe as a simple boolean as the only way to be stopped + * is in calls only allowed in the callback context, the only calls + * checking it are also in calls only allowed in callback context. + */ + volatile bool readingStopped; +}; + +AsynchIO::AsynchIO(const Socket& s, + ReadCallback rCb, EofCallback eofCb, DisconnectCallback disCb, + ClosedCallback cCb, BuffersEmptyCallback eCb, IdleCallback iCb) : + + DispatchHandle(s, + boost::bind(&AsynchIO::readable, this, _1), + boost::bind(&AsynchIO::writeable, this, _1), + boost::bind(&AsynchIO::disconnected, this, _1)), + readCallback(rCb), + eofCallback(eofCb), + disCallback(disCb), + closedCallback(cCb), + emptyCallback(eCb), + idleCallback(iCb), + socket(s), + queuedClose(false), + writePending(false), + readingStopped(false) { + + s.setNonblocking(); +} + +struct deleter +{ + template <typename T> + void operator()(T *ptr){ delete ptr;} +}; + +AsynchIO::~AsynchIO() { + std::for_each( bufferQueue.begin(), bufferQueue.end(), deleter()); + std::for_each( writeQueue.begin(), writeQueue.end(), deleter()); +} + +void AsynchIO::queueForDeletion() { + DispatchHandle::doDelete(); +} + +void AsynchIO::start(Poller::shared_ptr poller) { + DispatchHandle::startWatch(poller); +} + +void AsynchIO::queueReadBuffer(BufferBase* buff) { + assert(buff); + buff->dataStart = 0; + buff->dataCount = 0; + + bool queueWasEmpty = bufferQueue.empty(); + bufferQueue.push_back(buff); + if (queueWasEmpty && !readingStopped) + DispatchHandle::rewatchRead(); +} + +void AsynchIO::unread(BufferBase* buff) { + assert(buff); + buff->squish(); + + bool queueWasEmpty = bufferQueue.empty(); + bufferQueue.push_front(buff); + if (queueWasEmpty && !readingStopped) + DispatchHandle::rewatchRead(); +} + +void AsynchIO::queueWrite(BufferBase* buff) { + assert(buff); + // If we've already closed the socket then throw the write away + if (queuedClose) { + queueReadBuffer(buff); + return; + } else { + writeQueue.push_front(buff); + } + writePending = false; + DispatchHandle::rewatchWrite(); +} + +// This can happen outside the callback context +void AsynchIO::notifyPendingWrite() { + writePending = true; + DispatchHandle::rewatchWrite(); +} + +void AsynchIO::queueWriteClose() { + queuedClose = true; + DispatchHandle::rewatchWrite(); +} + +bool AsynchIO::writeQueueEmpty() { + return writeQueue.empty(); +} + +// This can happen outside the callback context +void AsynchIO::startReading() { + readingStopped = false; + DispatchHandle::rewatchRead(); +} + +void AsynchIO::stopReading() { + readingStopped = true; + DispatchHandle::unwatchRead(); +} + +void AsynchIO::requestCallback(RequestCallback callback) { + // TODO creating a function object every time isn't all that + // efficient - if this becomes heavily used do something better (what?) + assert(callback); + DispatchHandle::call(boost::bind(&AsynchIO::requestedCall, this, callback)); +} + +void AsynchIO::requestedCall(RequestCallback callback) { + assert(callback); + callback(*this); +} + +/** Return a queued buffer if there are enough + * to spare + */ +AsynchIO::BufferBase* AsynchIO::getQueuedBuffer() { + // Always keep at least one buffer (it might have data that was "unread" in it) + if (bufferQueue.size()<=1) + return 0; + BufferBase* buff = bufferQueue.back(); + assert(buff); + buff->dataStart = 0; + buff->dataCount = 0; + bufferQueue.pop_back(); + return buff; +} + +/* + * We keep on reading as long as we have something to read, a buffer + * to put it in and reading is not stopped by flow control. + */ +void AsynchIO::readable(DispatchHandle& h) { + if (readingStopped) { + // We have been flow controlled. + return; + } + int readTotal = 0; + AbsTime readStartTime = AbsTime::now(); + do { + // (Try to) get a buffer + if (!bufferQueue.empty()) { + // Read into buffer + BufferBase* buff = bufferQueue.front(); + assert(buff); + bufferQueue.pop_front(); + errno = 0; + int readCount = buff->byteCount-buff->dataCount; + int rc = socket.read(buff->bytes + buff->dataCount, readCount); + if (rc > 0) { + buff->dataCount += rc; + threadReadTotal += rc; + readTotal += rc; + + readCallback(*this, buff); + if (readingStopped) { + // We have been flow controlled. + break; + } + + if (rc != readCount) { + // If we didn't fill the read buffer then time to stop reading + break; + } + + // Stop reading if we've overrun our timeslot + if (Duration(readStartTime, AbsTime::now()) > threadMaxReadTimeNs) { + break; + } + + } else { + // Put buffer back (at front so it doesn't interfere with unread buffers) + bufferQueue.push_front(buff); + assert(buff); + + // Eof or other side has gone away + if (rc == 0 || errno == ECONNRESET) { + eofCallback(*this); + h.unwatchRead(); + break; + } else if (errno == EAGAIN) { + // We have just put a buffer back so we know + // we can carry on watching for reads + break; + } else { + // Report error then just treat as a socket disconnect + QPID_LOG(error, "Error reading socket: " << qpid::sys::strError(errno) << "(" << errno << ")" ); + eofCallback(*this); + h.unwatchRead(); + break; + } + } + } else { + // Something to read but no buffer + if (emptyCallback) { + emptyCallback(*this); + } + // If we still have no buffers we can't do anything more + if (bufferQueue.empty()) { + h.unwatchRead(); + break; + } + + } + } while (true); + + ++threadReadCount; + threadMaxRead = std::max(threadMaxRead, readTotal); + return; +} + +/* + * We carry on writing whilst we have data to write and we can write + */ +void AsynchIO::writeable(DispatchHandle& h) { + int writeTotal = 0; + do { + // See if we've got something to write + if (!writeQueue.empty()) { + // Write buffer + BufferBase* buff = writeQueue.back(); + writeQueue.pop_back(); + errno = 0; + assert(buff->dataStart+buff->dataCount <= buff->byteCount); + int rc = socket.write(buff->bytes+buff->dataStart, buff->dataCount); + if (rc >= 0) { + threadWriteTotal += rc; + writeTotal += rc; + + // If we didn't write full buffer put rest back + if (rc != buff->dataCount) { + buff->dataStart += rc; + buff->dataCount -= rc; + writeQueue.push_back(buff); + break; + } + + // Recycle the buffer + queueReadBuffer(buff); + + // If we've already written more than the max for reading then stop + // (this is to stop writes dominating reads) + if (writeTotal > threadMaxRead) + break; + } else { + // Put buffer back + writeQueue.push_back(buff); + if (errno == ECONNRESET || errno == EPIPE) { + // Just stop watching for write here - we'll get a + // disconnect callback soon enough + h.unwatchWrite(); + break; + } else if (errno == EAGAIN) { + // We have just put a buffer back so we know + // we can carry on watching for writes + break; + } else { + // Report error then just treat as a socket disconnect + QPID_LOG(error, "Error writing socket: " << qpid::sys::strError(errno) << "(" << errno << ")" ); + h.unwatchWrite(); + break; + } + } + } else { + // If we're waiting to close the socket then can do it now as there is nothing to write + if (queuedClose) { + close(h); + break; + } + // Fd is writable, but nothing to write + if (idleCallback) { + writePending = false; + idleCallback(*this); + } + // If we still have no buffers to write we can't do anything more + if (writeQueue.empty() && !writePending && !queuedClose) { + h.unwatchWrite(); + // The following handles the case where writePending is + // set to true after the test above; in this case its + // possible that the unwatchWrite overwrites the + // desired rewatchWrite so we correct that here + if (writePending) + h.rewatchWrite(); + break; + } + } + } while (true); + + ++threadWriteCount; + return; +} + +void AsynchIO::disconnected(DispatchHandle& h) { + // If we have not already queued close then call disconnected callback before closing + if (!queuedClose && disCallback) disCallback(*this); + close(h); +} + +/* + * Close the socket and callback to say we've done it + */ +void AsynchIO::close(DispatchHandle& h) { + h.stopWatch(); + socket.close(); + if (closedCallback) { + closedCallback(*this, socket); + } +} + +} // namespace posix + +AsynchAcceptor* AsynchAcceptor::create(const Socket& s, + Callback callback) +{ + return new posix::AsynchAcceptor(s, callback); +} + +AsynchConnector* AsynchConnector::create(const Socket& s, + const std::string& hostname, + const std::string& port, + ConnectedCallback connCb, + FailedCallback failCb) +{ + return new posix::AsynchConnector(s, hostname, port, connCb, failCb); +} + +AsynchIO* AsynchIO::create(const Socket& s, + AsynchIO::ReadCallback rCb, + AsynchIO::EofCallback eofCb, + AsynchIO::DisconnectCallback disCb, + AsynchIO::ClosedCallback cCb, + AsynchIO::BuffersEmptyCallback eCb, + AsynchIO::IdleCallback iCb) +{ + return new posix::AsynchIO(s, rCb, eofCb, disCb, cCb, eCb, iCb); +} + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/posix/FileSysDir.cpp b/qpid/cpp/src/qpid/sys/posix/FileSysDir.cpp new file mode 100755 index 0000000000..22dc487e74 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/posix/FileSysDir.cpp @@ -0,0 +1,54 @@ +/* + * + * 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. + * + */ + +#include "qpid/sys/FileSysDir.h" +#include "qpid/sys/StrError.h" +#include "qpid/Exception.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <cerrno> +#include <unistd.h> + +namespace qpid { +namespace sys { + +bool FileSysDir::exists (void) const +{ + const char *cpath = dirPath.c_str (); + struct stat s; + if (::stat(cpath, &s)) { + if (errno == ENOENT) { + return false; + } + throw qpid::Exception (strError(errno) + + ": Can't check directory: " + dirPath); + } + if (S_ISDIR(s.st_mode)) + return true; + throw qpid::Exception(dirPath + " is not a directory"); +} + +void FileSysDir::mkdir(void) +{ + if (::mkdir(dirPath.c_str(), 0755)) + throw Exception ("Can't create directory: " + dirPath); +} + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/posix/Fork.cpp b/qpid/cpp/src/qpid/sys/posix/Fork.cpp new file mode 100644 index 0000000000..a0d404a16e --- /dev/null +++ b/qpid/cpp/src/qpid/sys/posix/Fork.cpp @@ -0,0 +1,129 @@ +/* + * + * 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. + * + */ +#include "qpid/sys/Fork.h" +#include "qpid/log/Statement.h" +#include "qpid/Exception.h" + +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/select.h> +#include <sys/types.h> +#include <unistd.h> + +namespace qpid { +namespace sys { + +using namespace std; + +namespace { + +void writeStr(int fd, const std::string& str) { + const char* WRITE_ERR = "Error writing to parent process"; + int size = str.size(); + if (int(sizeof(size)) > ::write(fd, &size, sizeof(size))) throw ErrnoException(WRITE_ERR); + if (size > ::write(fd, str.data(), size)) throw ErrnoException(WRITE_ERR); +} + +string readStr(int fd) { + string value; + const char* READ_ERR = "Error reading from forked process"; + int size; + if (int(sizeof(size)) > ::read(fd, &size, sizeof(size))) throw ErrnoException(READ_ERR); + if (size > 0) { // Read string message + value.resize(size); + if (size > ::read(fd, const_cast<char*>(value.data()), size)) throw ErrnoException(READ_ERR); + } + return value; +} + +} // namespace + +Fork::Fork() {} +Fork::~Fork() {} + +void Fork::fork() { + pid_t pid = ::fork(); + if (pid < 0) throw ErrnoException("Failed to fork the process"); + if (pid == 0) child(); + else parent(pid); +} + +ForkWithMessage::ForkWithMessage() { + pipeFds[0] = pipeFds[1] = -1; +} + +struct AutoCloseFd { + int fd; + AutoCloseFd(int d) : fd(d) {} + ~AutoCloseFd() { ::close(fd); } +}; + +void ForkWithMessage::fork() { + if(::pipe(pipeFds) < 0) throw ErrnoException("Can't create pipe"); + pid_t pid = ::fork(); + if(pid < 0) throw ErrnoException("Fork fork failed"); + if (pid == 0) { // Child + AutoCloseFd ac(pipeFds[1]); // Write side. + ::close(pipeFds[0]); // Read side + try { + child(); + } + catch (const std::exception& e) { + QPID_LOG(error, "Error in forked child: " << e.what()); + std::string msg = e.what(); + if (msg.empty()) msg = " "; // Make sure we send a non-empty error string. + writeStr(pipeFds[1], msg); + } + } + else { // Parent + close(pipeFds[1]); // Write side. + AutoCloseFd ac(pipeFds[0]); // Read side + parent(pid); + } +} + +string ForkWithMessage::wait(int timeout) { // parent waits for child. + errno = 0; + struct timeval tv; + tv.tv_sec = timeout; + tv.tv_usec = 0; + + fd_set fds; + FD_ZERO(&fds); + FD_SET(pipeFds[0], &fds); + int n=select(FD_SETSIZE, &fds, 0, 0, &tv); + if(n<0) throw ErrnoException("Error waiting for fork"); + if (n==0) throw Exception("Timed out waiting for fork"); + + string error = readStr(pipeFds[0]); + if (error.empty()) return readStr(pipeFds[0]); + else throw Exception("Error in forked process: " + error); +} + +// Write empty error string followed by value string to pipe. +void ForkWithMessage::ready(const string& value) { // child + // Write empty string for error followed by value. + writeStr(pipeFds[1], string()); // No error + writeStr(pipeFds[1], value); +} + + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/posix/Fork.h b/qpid/cpp/src/qpid/sys/posix/Fork.h new file mode 100644 index 0000000000..698c61ed30 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/posix/Fork.h @@ -0,0 +1,82 @@ +#ifndef QPID_SYS_POSIX_FORK_H +#define QPID_SYS_POSIX_FORK_H + +/* + * + * 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. + * + */ + +#include <string> +#include <sys/types.h> + +namespace qpid { +namespace sys { + +/** + * Fork the process. Call parent() in parent and child() in child. + */ +class Fork { + public: + Fork(); + virtual ~Fork(); + + /** + * Fork the process. + * Calls parent() in the parent process, child() in the child. + */ + virtual void fork(); + + protected: + + /** Called in parent process. + *@child pid of child process + */ + virtual void parent(pid_t child) = 0; + + /** Called in child process */ + virtual void child() = 0; +}; + +/** + * Like Fork but also allows the child to send a string message + * or throw an exception to the parent. + */ +class ForkWithMessage : public Fork { + public: + ForkWithMessage(); + void fork(); + + protected: + /** Call from parent(): wait for child to send a value or throw exception. + * @timeout in seconds to wait for response. + * @return value passed by child to ready(). + */ + std::string wait(int timeout); + + /** Call from child(): Send a value to the parent. + *@param value returned by parent call to wait(). + */ + void ready(const std::string& value); + + private: + int pipeFds[2]; +}; + +}} // namespace qpid::sys + + + +#endif /*!QPID_SYS_POSIX_FORK_H*/ diff --git a/qpid/cpp/src/qpid/sys/posix/IOHandle.cpp b/qpid/cpp/src/qpid/sys/posix/IOHandle.cpp new file mode 100644 index 0000000000..9c049ee1de --- /dev/null +++ b/qpid/cpp/src/qpid/sys/posix/IOHandle.cpp @@ -0,0 +1,44 @@ +/* + * + * 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" + +#include "qpid/sys/posix/PrivatePosix.h" + +namespace qpid { +namespace sys { + +int toFd(const IOHandlePrivate* h) +{ + return h->fd; +} + +NullIOHandle DummyIOHandle; + +IOHandle::IOHandle(IOHandlePrivate* h) : + impl(h) +{} + +IOHandle::~IOHandle() { + delete impl; +} + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/posix/LockFile.cpp b/qpid/cpp/src/qpid/sys/posix/LockFile.cpp new file mode 100755 index 0000000000..1862ff6ac9 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/posix/LockFile.cpp @@ -0,0 +1,108 @@ +/* + * + * Copyright (c) 2008 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. + * + */ + +#include "qpid/sys/LockFile.h" +#include "qpid/sys/posix/PidFile.h" + +#include <string> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "qpid/sys/posix/check.h" + +namespace qpid { +namespace sys { + +class LockFilePrivate { + friend class LockFile; + friend class PidFile; + + int fd; + +public: + LockFilePrivate(int f) : fd(f) {} +}; + +LockFile::LockFile(const std::string& path_, bool create) + : path(path_), created(create) { + + errno = 0; + int flags=create ? O_WRONLY|O_CREAT|O_NOFOLLOW : O_RDWR; + int fd = ::open(path.c_str(), flags, 0644); + if (fd < 0) throw ErrnoException("Cannot open " + path, errno); + if (::lockf(fd, F_TLOCK, 0) < 0) { + ::close(fd); + throw ErrnoException("Cannot lock " + path, errno); + } + impl.reset(new LockFilePrivate(fd)); +} + +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. + ::close(f); + impl->fd = -1; + } + } +} + +int LockFile::read(void* bytes, size_t len) const { + if (!impl) + throw Exception("Lock file not open: " + path); + + ssize_t rc = ::read(impl->fd, bytes, len); + if ((ssize_t)len > rc) { + throw Exception("Cannot read lock file: " + path); + } + return rc; +} + +int LockFile::write(void* bytes, size_t len) const { + if (!impl) + throw Exception("Lock file not open: " + path); + + ssize_t rc = ::write(impl->fd, bytes, len); + if ((ssize_t)len > rc) { + throw Exception("Cannot write lock file: " + path); + } + return rc; +} + +PidFile::PidFile(const std::string& path_, bool create): + LockFile(path_, create) +{} + +pid_t PidFile::readPid(void) const { + pid_t pid; + int desired_read = sizeof(pid_t); + read(&pid, desired_read); + return pid; +} + +void PidFile::writePid(void) { + pid_t pid = getpid(); + int desired_write = sizeof(pid_t); + write(&pid, desired_write); +} + +}} /* namespace qpid::sys */ diff --git a/qpid/cpp/src/qpid/sys/posix/Mutex.cpp b/qpid/cpp/src/qpid/sys/posix/Mutex.cpp new file mode 100644 index 0000000000..0e1f0d30c2 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/posix/Mutex.cpp @@ -0,0 +1,46 @@ +/* + * + * 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/Mutex.h" + +namespace qpid { +namespace sys { + +/** + * Initialise a recursive mutex attr for use in creating mutexes later + * (we use pthread_once to make sure it is initialised exactly once) + */ + +namespace { +pthread_once_t onceControl = PTHREAD_ONCE_INIT; +pthread_mutexattr_t mutexattr; + +void initMutexattr() { + pthread_mutexattr_init(&mutexattr); + pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_RECURSIVE); +} +} + +const pthread_mutexattr_t* Mutex::getAttribute() { + pthread_once(&onceControl, initMutexattr); + return &mutexattr; +} + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/posix/PidFile.h b/qpid/cpp/src/qpid/sys/posix/PidFile.h new file mode 100644 index 0000000000..fb19d407f4 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/posix/PidFile.h @@ -0,0 +1,62 @@ +#ifndef _sys_PidFile_h +#define _sys_PidFile_h + +/* + * + * Copyright (c) 2008 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. + * + */ + +#include "qpid/sys/LockFile.h" + +#include "qpid/CommonImportExport.h" +#include "qpid/sys/IntegerTypes.h" + +#include <boost/noncopyable.hpp> +#include <boost/shared_ptr.hpp> +#include <string> + +namespace qpid { +namespace sys { + +class PidFile : public LockFile +{ +public: + QPID_COMMON_EXTERN PidFile(const std::string& path_, bool create); + + /** + * Read the process ID from the lock file. This method assumes that + * if there is a process ID in the file, it was written there by + * writePid(); thus, it's at the start of the file. + * + * Throws an exception if there is an error reading the file. + * + * @returns The stored process ID. No validity check is done on it. + */ + QPID_COMMON_EXTERN pid_t readPid(void) const; + + /** + * Write the current process's ID to the lock file. It's written at + * the start of the file and will overwrite any other content that + * may be in the file. + * + * Throws an exception if the write fails. + */ + QPID_COMMON_EXTERN void writePid(void); +}; + +}} /* namespace qpid::sys */ + +#endif /*!_sys_PidFile_h*/ diff --git a/qpid/cpp/src/qpid/sys/posix/PipeHandle.cpp b/qpid/cpp/src/qpid/sys/posix/PipeHandle.cpp new file mode 100755 index 0000000000..4b19783338 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/posix/PipeHandle.cpp @@ -0,0 +1,64 @@ +// +// 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/PipeHandle.h" +#include "qpid/sys/posix/check.h" +#include <unistd.h> +#include <fcntl.h> +#include <sys/socket.h> + +namespace qpid { +namespace sys { + +PipeHandle::PipeHandle(bool nonBlocking) { + + int pair[2]; + pair[0] = pair[1] = -1; + + if (socketpair(PF_UNIX, SOCK_STREAM, 0, pair) == -1) + throw qpid::Exception(QPID_MSG("Creation of pipe failed")); + + writeFd = pair[0]; + readFd = pair[1]; + + // Set the socket to non-blocking + if (nonBlocking) { + int flags = fcntl(readFd, F_GETFL); + fcntl(readFd, F_SETFL, flags | O_NONBLOCK); + } +} + +PipeHandle::~PipeHandle() { + close(readFd); + close(writeFd); +} + +int PipeHandle::read(void* buf, size_t bufSize) { + return ::read(readFd,buf,bufSize); +} + +int PipeHandle::write(const void* buf, size_t bufSize) { + return ::write(writeFd,buf,bufSize); +} + +int PipeHandle::getReadHandle() { + return readFd; +} + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/posix/PollableCondition.cpp b/qpid/cpp/src/qpid/sys/posix/PollableCondition.cpp new file mode 100644 index 0000000000..b22a615a54 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/posix/PollableCondition.cpp @@ -0,0 +1,124 @@ +#ifndef QPID_SYS_LINUX_POLLABLECONDITION_CPP +#define QPID_SYS_LINUX_POLLABLECONDITION_CPP + +/* + * + * 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/PollableCondition.h" +#include "qpid/sys/DispatchHandle.h" +#include "qpid/sys/IOHandle.h" +#include "qpid/sys/posix/PrivatePosix.h" +#include "qpid/Exception.h" + +#include <boost/bind.hpp> + +#include <unistd.h> +#include <fcntl.h> + +namespace qpid { +namespace sys { + +class PollableConditionPrivate : public sys::IOHandle { + friend class PollableCondition; + +private: + PollableConditionPrivate(const sys::PollableCondition::Callback& cb, + sys::PollableCondition& parent, + const boost::shared_ptr<sys::Poller>& poller); + ~PollableConditionPrivate(); + + void dispatch(sys::DispatchHandle& h); + void set(); + void clear(); + +private: + PollableCondition::Callback cb; + PollableCondition& parent; + boost::shared_ptr<sys::Poller> poller; + int writeFd; + std::auto_ptr<DispatchHandleRef> handle; +}; + +PollableConditionPrivate::PollableConditionPrivate( + const sys::PollableCondition::Callback& cb, + sys::PollableCondition& parent, + const boost::shared_ptr<sys::Poller>& poller +) : IOHandle(new sys::IOHandlePrivate), cb(cb), parent(parent) +{ + int fds[2]; + if (::pipe(fds) == -1) + throw ErrnoException(QPID_MSG("Can't create PollableCondition")); + impl->fd = fds[0]; + writeFd = fds[1]; + if (::fcntl(impl->fd, F_SETFL, O_NONBLOCK) == -1) + throw ErrnoException(QPID_MSG("Can't create PollableCondition")); + if (::fcntl(writeFd, F_SETFL, O_NONBLOCK) == -1) + throw ErrnoException(QPID_MSG("Can't create PollableCondition")); + handle.reset (new DispatchHandleRef( + *this, + boost::bind(&sys::PollableConditionPrivate::dispatch, this, _1), + 0, 0)); + handle->startWatch(poller); + handle->unwatch(); + + // Make the read FD readable + static const char dummy=0; + ssize_t n = ::write(writeFd, &dummy, 1); + if (n == -1 && errno != EAGAIN) + throw ErrnoException("Error setting PollableCondition"); +} + +PollableConditionPrivate::~PollableConditionPrivate() { + handle->stopWatch(); + close(writeFd); +} + +void PollableConditionPrivate::dispatch(sys::DispatchHandle&) { + cb(parent); +} + +void PollableConditionPrivate::set() { + handle->rewatch(); +} + +void PollableConditionPrivate::clear() { + handle->unwatch(); +} + + +PollableCondition::PollableCondition(const Callback& cb, + const boost::shared_ptr<sys::Poller>& poller +) : impl(new PollableConditionPrivate(cb, *this, poller)) +{ +} + +PollableCondition::~PollableCondition() +{ + delete impl; +} + +void PollableCondition::set() { impl->set(); } + +void PollableCondition::clear() { impl->clear(); } + +}} // namespace qpid::sys + +#endif /*!QPID_SYS_LINUX_POLLABLECONDITION_CPP*/ diff --git a/qpid/cpp/src/qpid/sys/posix/Shlib.cpp b/qpid/cpp/src/qpid/sys/posix/Shlib.cpp new file mode 100644 index 0000000000..3fb685d5b8 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/posix/Shlib.cpp @@ -0,0 +1,60 @@ +/* + * 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/Shlib.h" +#include "qpid/Exception.h" +#include "qpid/Msg.h" +#include <dlfcn.h> + + +namespace qpid { +namespace sys { + +void Shlib::load(const char* name) { + ::dlerror(); + handle = ::dlopen(name, RTLD_NOW); + const char* error = ::dlerror(); + if (error) { + throw Exception(QPID_MSG(error << ": " << name)); + } +} + +void Shlib::unload() { + if (handle) { + ::dlerror(); + ::dlclose(handle); + const char* error = ::dlerror(); + if (error) { + throw Exception(QPID_MSG(error)); + } + handle = 0; + } +} + +void* Shlib::getSymbol(const char* name) { + ::dlerror(); + void* sym = ::dlsym(handle, name); + const char* error = ::dlerror(); + if (error) + throw Exception(QPID_MSG(error << ": " << name)); + return sym; +} + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/posix/Socket.cpp b/qpid/cpp/src/qpid/sys/posix/Socket.cpp new file mode 100644 index 0000000000..aa25f8062d --- /dev/null +++ b/qpid/cpp/src/qpid/sys/posix/Socket.cpp @@ -0,0 +1,247 @@ +/* + * + * 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/Socket.h" + +#include "qpid/sys/SocketAddress.h" +#include "qpid/sys/posix/check.h" +#include "qpid/sys/posix/PrivatePosix.h" + +#include <fcntl.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/errno.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <netdb.h> +#include <cstdlib> +#include <string.h> +#include <iostream> + +#include <boost/format.hpp> + +namespace qpid { +namespace sys { + +namespace { +std::string getName(int fd, bool local) +{ + ::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 (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); +} +} + +Socket::Socket() : + IOHandle(new IOHandlePrivate), + nonblocking(false), + nodelay(false) +{} + +Socket::Socket(IOHandlePrivate* h) : + IOHandle(h), + nonblocking(false), + nodelay(false) +{} + +void Socket::createSocket(const SocketAddress& sa) const +{ + int& socket = impl->fd; + if (socket != -1) Socket::close(); + int s = ::socket(getAddrInfo(sa).ai_family, getAddrInfo(sa).ai_socktype, 0); + if (s < 0) throw QPID_POSIX_ERROR(errno); + socket = s; + + try { + if (nonblocking) setNonblocking(); + if (nodelay) setTcpNoDelay(); + } catch (std::exception&) { + ::close(s); + socket = -1; + throw; + } +} + +void Socket::setNonblocking() const { + int& socket = impl->fd; + nonblocking = true; + if (socket != -1) { + QPID_POSIX_CHECK(::fcntl(socket, F_SETFL, O_NONBLOCK)); + } +} + +void Socket::setTcpNoDelay() const +{ + int& socket = impl->fd; + nodelay = true; + if (socket != -1) { + int flag = 1; + int result = setsockopt(impl->fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag)); + QPID_POSIX_CHECK(result); + } +} + +void Socket::connect(const std::string& host, const std::string& port) const +{ + SocketAddress sa(host, port); + connect(sa); +} + +void Socket::connect(const SocketAddress& addr) const +{ + // 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); + + const int& socket = impl->fd; + // 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) << ": " << 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)); + } +} + +void +Socket::close() const +{ + int& socket = impl->fd; + if (socket == -1) return; + if (::close(socket) < 0) throw QPID_POSIX_ERROR(errno); + socket = -1; +} + +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& sa, int backlog) const +{ + createSocket(sa); + + const int& socket = impl->fd; + int yes=1; + 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); +} + +Socket* Socket::accept() const +{ + int afd = ::accept(impl->fd, 0, 0); + 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); +} + +int Socket::read(void *buf, size_t count) const +{ + return ::read(impl->fd, buf, count); +} + +int Socket::write(const void *buf, size_t count) const +{ + return ::write(impl->fd, buf, count); +} + +std::string Socket::getPeerAddress() const +{ + if (peername.empty()) { + peername = getName(impl->fd, false); + } + return peername; +} + +std::string Socket::getLocalAddress() const +{ + if (localname.empty()) { + localname = getName(impl->fd, true); + } + return localname; +} + +int Socket::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; +} + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/posix/SocketAddress.cpp b/qpid/cpp/src/qpid/sys/posix/SocketAddress.cpp new file mode 100644 index 0000000000..10f1c8a563 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/posix/SocketAddress.cpp @@ -0,0 +1,107 @@ +/* + * + * 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/SocketAddress.h" + +#include "qpid/sys/posix/check.h" + +#include <sys/socket.h> +#include <string.h> +#include <netdb.h> + +#include <algorithm> + +namespace qpid { +namespace sys { + +SocketAddress::SocketAddress(const std::string& host0, const std::string& port0) : + host(host0), + port(port0), + addrInfo(0) +{ +} + +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() +{ + if (addrInfo) { + ::freeaddrinfo(addrInfo); + } +} + +std::string SocketAddress::asString(bool numeric) const +{ + if (!numeric) + return host + ":" + port; + // Canonicalise into numeric id + const ::addrinfo& ai = getAddrInfo(*this); + char servName[NI_MAXSERV]; + char dispName[NI_MAXHOST]; + if (int rc=::getnameinfo(ai.ai_addr, ai.ai_addrlen, + dispName, sizeof(dispName), + servName, sizeof(servName), + NI_NUMERICHOST | NI_NUMERICSERV) != 0) + throw QPID_POSIX_ERROR(rc); + std::string s(dispName); + s += ":"; + s += servName; + return s; +} + +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_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))); + } + + return *sa.addrInfo; +} + +}} diff --git a/qpid/cpp/src/qpid/sys/posix/StrError.cpp b/qpid/cpp/src/qpid/sys/posix/StrError.cpp new file mode 100644 index 0000000000..633e20213c --- /dev/null +++ b/qpid/cpp/src/qpid/sys/posix/StrError.cpp @@ -0,0 +1,41 @@ +/* + * + * 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/StrError.h" + +#include <string.h> + +namespace qpid { +namespace sys { + +std::string strError(int err) { + char buf[512] = "Unknown error"; +#ifdef _GNU_SOURCE + // GNU strerror_r returns the message + return ::strerror_r(err, buf, sizeof(buf)); +#else + // POSIX strerror_r doesn't return the buffer + ::strerror_r(err, buf, sizeof(buf)); + return std::string(buf); +#endif +} + +}} diff --git a/qpid/cpp/src/qpid/sys/posix/SystemInfo.cpp b/qpid/cpp/src/qpid/sys/posix/SystemInfo.cpp new file mode 100755 index 0000000000..a19ab6885c --- /dev/null +++ b/qpid/cpp/src/qpid/sys/posix/SystemInfo.cpp @@ -0,0 +1,153 @@ +/* + * 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/SystemInfo.h" + +#include "qpid/sys/posix/check.h" + +#include <sys/ioctl.h> +#include <sys/utsname.h> +#include <sys/types.h> // For FreeBSD +#include <sys/socket.h> // For FreeBSD +#include <netinet/in.h> // For FreeBSD +#include <ifaddrs.h> +#include <iostream> +#include <fstream> +#include <sstream> +#include <netdb.h> + +#ifndef HOST_NAME_MAX +# define HOST_NAME_MAX 256 +#endif + +using namespace std; + +namespace qpid { +namespace sys { + +long SystemInfo::concurrency() { +#ifdef _SC_NPROCESSORS_ONLN // Linux specific. + return sysconf(_SC_NPROCESSORS_ONLN); +#else + return -1; +#endif +} + +bool SystemInfo::getLocalHostname (Address &address) { + char name[HOST_NAME_MAX]; + if (::gethostname(name, sizeof(name)) != 0) + return false; + address.host = name; + return true; +} + +static const string LOCALHOST("127.0.0.1"); +static const string TCP("tcp"); + +void SystemInfo::getLocalIpAddresses (uint16_t port, + std::vector<Address> &addrList) { + ::ifaddrs* ifaddr = 0; + QPID_POSIX_CHECK(::getifaddrs(&ifaddr)); + for (::ifaddrs* ifap = ifaddr; ifap != 0; ifap = ifap->ifa_next) { + if (ifap->ifa_addr == 0) continue; + + int family = ifap->ifa_addr->sa_family; + switch (family) { + case AF_INET: { + char dispName[NI_MAXHOST]; + int rc = ::getnameinfo( + ifap->ifa_addr, + (family == AF_INET) + ? sizeof(struct sockaddr_in) + : sizeof(struct sockaddr_in6), + dispName, sizeof(dispName), + 0, 0, NI_NUMERICHOST); + if (rc != 0) { + throw QPID_POSIX_ERROR(rc); + } + string addr(dispName); + if (addr != LOCALHOST) { + addrList.push_back(Address(TCP, addr, port)); + } + break; + } + // TODO: Url parsing currently can't cope with IPv6 addresses so don't return them + // when it can cope move this line to above "case AF_INET:" + case AF_INET6: + default: + continue; + } + } + freeifaddrs(ifaddr); + + if (addrList.empty()) { + addrList.push_back(Address(TCP, LOCALHOST, port)); + } +} + +void SystemInfo::getSystemId (std::string &osName, + std::string &nodeName, + std::string &release, + std::string &version, + std::string &machine) +{ + struct utsname _uname; + if (uname (&_uname) == 0) + { + osName = _uname.sysname; + nodeName = _uname.nodename; + release = _uname.release; + version = _uname.version; + machine = _uname.machine; + } +} + +uint32_t SystemInfo::getProcessId() +{ + return (uint32_t) ::getpid(); +} + +uint32_t SystemInfo::getParentProcessId() +{ + return (uint32_t) ::getppid(); +} + +// Linux specific (Solaris has quite different stuff in /proc) +string SystemInfo::getProcessName() +{ + string value; + + ifstream input("/proc/self/status"); + if (input.good()) { + while (!input.eof()) { + string key; + input >> key; + if (key == "Name:") { + input >> value; + break; + } + } + input.close(); + } + + return value; +} + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/posix/Thread.cpp b/qpid/cpp/src/qpid/sys/posix/Thread.cpp new file mode 100644 index 0000000000..a1d6396763 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/posix/Thread.cpp @@ -0,0 +1,88 @@ +/* + * + * 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/Thread.h" + +#include "qpid/sys/Runnable.h" +#include "qpid/sys/posix/check.h" + +#include <pthread.h> + +namespace qpid { +namespace sys { + +namespace { +void* runRunnable(void* p) +{ + static_cast<Runnable*>(p)->run(); + return 0; +} +} + +class ThreadPrivate { +public: + pthread_t thread; + + ThreadPrivate(Runnable* runnable) { + QPID_POSIX_ASSERT_THROW_IF(::pthread_create(&thread, NULL, runRunnable, runnable)); + } + + ThreadPrivate() : thread(::pthread_self()) {} +}; + +Thread::Thread() {} + +Thread::Thread(Runnable* runnable) : impl(new ThreadPrivate(runnable)) {} + +Thread::Thread(Runnable& runnable) : impl(new ThreadPrivate(&runnable)) {} + +Thread::operator bool() { + return impl; +} + +bool Thread::operator==(const Thread& t) const { + return ::pthread_equal(impl->thread, t.impl->thread) != 0; +} + +bool Thread::operator!=(const Thread& t) const { + return !(*this==t); +} + +void Thread::join(){ + if (impl) { + QPID_POSIX_ASSERT_THROW_IF(::pthread_join(impl->thread, 0)); + } +} + +unsigned long Thread::logId() { + // This does need to be the C cast operator as + // pthread_t could be either a pointer or an integer + // and so we can't know static_cast<> or reinterpret_cast<> + return (unsigned long) ::pthread_self(); +} + +Thread Thread::current() { + Thread t; + t.impl.reset(new ThreadPrivate()); + return t; +} + +}} diff --git a/qpid/cpp/src/qpid/sys/posix/Time.cpp b/qpid/cpp/src/qpid/sys/posix/Time.cpp new file mode 100644 index 0000000000..9661f0c5e8 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/posix/Time.cpp @@ -0,0 +1,121 @@ +/* + * + * 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/posix/PrivatePosix.h" + +#include "qpid/sys/Time.h" +#include <ostream> +#include <time.h> +#include <stdio.h> +#include <sys/time.h> +#include <unistd.h> +#include <iomanip> + +namespace { +int64_t max_abstime() { return std::numeric_limits<int64_t>::max(); } +} + +namespace qpid { +namespace sys { + +AbsTime::AbsTime(const AbsTime& t, const Duration& d) : + timepoint(d == Duration::max() ? max_abstime() : t.timepoint+d.nanosecs) +{} + +AbsTime AbsTime::Epoch() { + AbsTime epoch; epoch.timepoint = 0; + return epoch; +} + +AbsTime AbsTime::FarFuture() { + AbsTime ff; ff.timepoint = max_abstime(); return ff; +} + +AbsTime AbsTime::now() { + struct timespec ts; + ::clock_gettime(CLOCK_REALTIME, &ts); + AbsTime time_now; + time_now.timepoint = toTime(ts).nanosecs; + return time_now; +} + +Duration::Duration(const AbsTime& start, const AbsTime& finish) : + nanosecs(finish.timepoint - start.timepoint) +{} + +struct timespec& toTimespec(struct timespec& ts, const Duration& t) { + ts.tv_sec = t / TIME_SEC; + ts.tv_nsec = t % TIME_SEC; + return ts; +} + +struct timeval& toTimeval(struct timeval& tv, const Duration& t) { + tv.tv_sec = t/TIME_SEC; + tv.tv_usec = (t%TIME_SEC)/TIME_USEC; + return tv; +} + +Duration toTime(const struct timespec& ts) { + return ts.tv_sec*TIME_SEC + ts.tv_nsec; +} + +std::ostream& operator<<(std::ostream& o, const Duration& d) { + return o << int64_t(d) << "ns"; +} + +namespace { +inline std::ostream& outputFormattedTime(std::ostream& o, const ::time_t* time) { + ::tm timeinfo; + char time_string[100]; + ::strftime(time_string, 100, + "%Y-%m-%d %H:%M:%S", + localtime_r(time, &timeinfo)); + return o << time_string; +} +} + +std::ostream& operator<<(std::ostream& o, const AbsTime& t) { + ::time_t rawtime(t.timepoint/TIME_SEC); + return outputFormattedTime(o, &rawtime); +} + +void outputFormattedNow(std::ostream& o) { + ::time_t rawtime; + ::time(&rawtime); + outputFormattedTime(o, &rawtime); + 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); +} + +void usleep(uint64_t usecs) { + ::usleep(usecs); +} + +}} diff --git a/qpid/cpp/src/qpid/sys/rdma/RdmaClient.cpp b/qpid/cpp/src/qpid/sys/rdma/RdmaClient.cpp new file mode 100644 index 0000000000..38e9b59541 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/rdma/RdmaClient.cpp @@ -0,0 +1,247 @@ +/* + * + * 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/rdma/RdmaIO.h" +#include "qpid/sys/rdma/rdma_exception.h" +#include "qpid/sys/Time.h" +#include "qpid/sys/Thread.h" + +#include <netdb.h> +#include <arpa/inet.h> + +#include <vector> +#include <string> +#include <iostream> +#include <algorithm> +#include <cmath> +#include <boost/bind.hpp> + +using std::vector; +using std::string; +using std::cout; +using std::cerr; +using std::copy; +using std::rand; + +using qpid::sys::Thread; +using qpid::sys::Poller; +using qpid::sys::Dispatcher; +using qpid::sys::SocketAddress; +using qpid::sys::AbsTime; +using qpid::sys::Duration; +using qpid::sys::TIME_SEC; +using qpid::sys::TIME_INFINITE; + +namespace qpid { +namespace tests { + +// count of messages +int64_t smsgs = 0; +int64_t sbytes = 0; +int64_t rmsgs = 0; +int64_t rbytes = 0; + +int target = 1000000; +int msgsize = 200; +AbsTime startTime; +Duration sendingDuration(TIME_INFINITE); +Duration fullTestDuration(TIME_INFINITE); + +// Random generator +// This is an RNG designed by George Marsaglia see http://en.wikipedia.org/wiki/Xorshift +class Xor128Generator { + uint32_t x; + uint32_t y; + uint32_t z; + uint32_t w; + +public: + Xor128Generator() : + x(123456789),y(362436069),z(521288629),w(88675123) + {++(*this);} + + Xor128Generator& operator++() { + uint32_t t = x ^ (x << 11); + x = y; y = z; z = w; + w = w ^ (w >> 19) ^ t ^ (t >> 8); + return *this; + } + + uint32_t operator*() { + return w; + } +}; + +Xor128Generator output; +Xor128Generator input; + +void write(Rdma::AsynchIO& aio) { + while (aio.writable() && smsgs < target) { + Rdma::Buffer* b = aio.getSendBuffer(); + if (!b) break; + b->dataCount(msgsize); + uint32_t* ip = reinterpret_cast<uint32_t*>(b->bytes()); + uint32_t* lip = ip + b->dataCount() / sizeof(uint32_t); + while (ip != lip) {*ip++ = *output; ++output;} + aio.queueWrite(b); + ++smsgs; + sbytes += msgsize; + } +} + +void dataError(Rdma::AsynchIO&) { + cout << "Data error:\n"; +} + +void data(Poller::shared_ptr p, Rdma::AsynchIO& aio, Rdma::Buffer* b) { + ++rmsgs; + rbytes += b->dataCount(); + + // Check message is unaltered + bool match = true; + uint32_t* ip = reinterpret_cast<uint32_t*>(b->bytes()); + uint32_t* lip = ip + b->dataCount() / sizeof(uint32_t); + while (ip != lip) { if (*ip++ != *input) {match = false; break;} ++input;} + if (!match) { + cout << "Data doesn't match: at msg " << rmsgs << " byte " << rbytes-b->dataCount() << " (ish)\n"; + exit(1); + } + + // When all messages have been recvd stop + if (rmsgs < target) { + write(aio); + } else { + fullTestDuration = std::min(fullTestDuration, Duration(startTime, AbsTime::now())); + if (aio.incompletedWrites() == 0) + p->shutdown(); + } +} + +void full(Rdma::AsynchIO& a, Rdma::Buffer* b) { + // Warn as we shouldn't get here anymore + cerr << "!"; + + // Don't need to keep buffer just adjust the counts + --smsgs; + sbytes -= b->dataCount(); + + // Give buffer back + a.returnSendBuffer(b); +} + +void idle(Poller::shared_ptr p, Rdma::AsynchIO& aio) { + if (smsgs < target) { + write(aio); + } else { + sendingDuration = std::min(sendingDuration, Duration(startTime, AbsTime::now())); + if (rmsgs >= target && aio.incompletedWrites() == 0) + p->shutdown(); + } +} + +void drained(Rdma::AsynchIO&) { + cout << "Drained:\n"; +} + +void connected(Poller::shared_ptr poller, Rdma::Connection::intrusive_ptr& ci, const Rdma::ConnectionParams& cp) { + cout << "Connected\n"; + Rdma::QueuePair::intrusive_ptr q = ci->getQueuePair(); + + Rdma::AsynchIO* aio = new Rdma::AsynchIO(ci->getQueuePair(), + cp.rdmaProtocolVersion, + cp.maxRecvBufferSize, cp.initialXmitCredit , Rdma::DEFAULT_WR_ENTRIES, + boost::bind(&data, poller, _1, _2), + boost::bind(&idle, poller, _1), + &full, + dataError); + + startTime = AbsTime::now(); + write(*aio); + + aio->start(poller); +} + +void disconnected(boost::shared_ptr<Poller> p, Rdma::Connection::intrusive_ptr&) { + cout << "Disconnected\n"; + p->shutdown(); +} + +void connectionError(boost::shared_ptr<Poller> p, Rdma::Connection::intrusive_ptr&, const Rdma::ErrorType) { + cout << "Connection error\n"; + p->shutdown(); +} + +void rejected(boost::shared_ptr<Poller> p, Rdma::Connection::intrusive_ptr&, const Rdma::ConnectionParams&) { + cout << "Connection rejected\n"; + p->shutdown(); +} + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char* argv[]) { + vector<string> args(&argv[0], &argv[argc]); + + string host = args[1]; + string port = (args.size() < 3) ? "20079" : args[2]; + + if (args.size() > 3) + msgsize = atoi(args[3].c_str()); + cout << "Message size: " << msgsize << "\n"; + + try { + boost::shared_ptr<Poller> p(new Poller()); + + Rdma::Connector c( + Rdma::ConnectionParams(msgsize, Rdma::DEFAULT_WR_ENTRIES), + boost::bind(&connected, p, _1, _2), + boost::bind(&connectionError, p, _1, _2), + boost::bind(&disconnected, p, _1), + boost::bind(&rejected, p, _1, _2)); + + SocketAddress sa(host, port); + cout << "Connecting to: " << sa.asString() <<"\n"; + c.start(p, sa); + + // The poller loop blocks all signals so run in its own thread + Thread t(*p); + t.join(); + } catch (Rdma::Exception& e) { + int err = e.getError(); + cerr << "Error: " << e.what() << "(" << err << ")\n"; + } + + cout + << "Sent: " << smsgs + << "msgs (" << sbytes + << "bytes) in: " << double(sendingDuration)/TIME_SEC + << "s: " << double(smsgs)*TIME_SEC/sendingDuration + << "msgs/s(" << double(sbytes)*TIME_SEC/sendingDuration + << "bytes/s)\n"; + cout + << "Recd: " << rmsgs + << "msgs (" << rbytes + << "bytes) in: " << double(fullTestDuration)/TIME_SEC + << "s: " << double(rmsgs)*TIME_SEC/fullTestDuration + << "msgs/s(" << double(rbytes)*TIME_SEC/fullTestDuration + << "bytes/s)\n"; + +} diff --git a/qpid/cpp/src/qpid/sys/rdma/RdmaIO.cpp b/qpid/cpp/src/qpid/sys/rdma/RdmaIO.cpp new file mode 100644 index 0000000000..78bcdec68e --- /dev/null +++ b/qpid/cpp/src/qpid/sys/rdma/RdmaIO.cpp @@ -0,0 +1,720 @@ +/* + * + * 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/rdma/RdmaIO.h" + +#include "qpid/log/Statement.h" + +#include <string> +#include <boost/bind.hpp> + +using qpid::sys::SocketAddress; +using qpid::sys::DispatchHandle; +using qpid::sys::Poller; +using qpid::sys::ScopedLock; +using qpid::sys::Mutex; + +namespace Rdma { + // Set packing as these are 'on the wire' structures +# pragma pack(push, 1) + + // Header structure for each transmitted frame + struct FrameHeader { + const static uint32_t FlagsMask = 0xf0000000; + uint32_t data; // written in network order + + FrameHeader() {} + FrameHeader(uint32_t credit, uint32_t flags = 0) { + data = htonl((credit & ~FlagsMask) | (flags & FlagsMask)); + } + + uint32_t credit() const { + return ntohl(data) & ~FlagsMask; + } + + uint32_t flags() const { + return ntohl(data) & FlagsMask; + } + }; + + const size_t FrameHeaderSize = sizeof(FrameHeader); + + // Structure for Connection Parameters on the network + // + // The original version (now called 0) of these parameters had a couple of mistakes: + // * No way to version the protocol (need to introduce a new protocol for iWarp) + // * Used host order int32 (but only deployed on LE archs as far as we know) + // so effectively was LE on the wire which is the opposite of network order. + // + // Fortunately the values sent were sufficiently restricted that a 16 bit short could + // be carved out to indicate the protocol version as these bits were always sent as 0. + // + // So the current version of parameters uses the last 2 bytes to indicate the protocol + // version, if this is 0 then we interpret the rest of the struct without byte swapping + // to remain compatible with the previous protocol. + struct NConnectionParams { + uint32_t maxRecvBufferSize; + uint16_t initialXmitCredit; + uint16_t rdmaProtocolVersion; + + NConnectionParams(const ConnectionParams& c) : + maxRecvBufferSize(c.rdmaProtocolVersion ? htonl(c.maxRecvBufferSize) : c.maxRecvBufferSize), + initialXmitCredit(c.rdmaProtocolVersion ? htons(c.initialXmitCredit) : c.initialXmitCredit), + // 0 is the same with/without byteswapping! + rdmaProtocolVersion(htons(c.rdmaProtocolVersion)) + {} + + operator ConnectionParams() const { + return + ConnectionParams( + rdmaProtocolVersion ? ntohl(maxRecvBufferSize) : maxRecvBufferSize, + rdmaProtocolVersion ? ntohs(initialXmitCredit) : initialXmitCredit, + ntohs(rdmaProtocolVersion)); + } + }; +# pragma pack(pop) + + class IOException : public std::exception { + std::string s; + + public: + IOException(std::string s0): s(s0) {} + ~IOException() throw() {} + + const char* what() const throw() { + return s.c_str(); + } + }; + + AsynchIO::AsynchIO( + QueuePair::intrusive_ptr q, + int version, + int size, + int xCredit, + int rCount, + ReadCallback rc, + IdleCallback ic, + FullCallback fc, + ErrorCallback ec + ) : + protocolVersion(version), + bufferSize(size), + recvCredit(0), + xmitCredit(xCredit), + recvBufferCount(rCount), + xmitBufferCount(xCredit), + outstandingWrites(0), + draining(false), + state(IDLE), + qp(q), + dataHandle(*qp, boost::bind(&AsynchIO::dataEvent, this), 0, 0), + readCallback(rc), + idleCallback(ic), + fullCallback(fc), + errorCallback(ec), + pendingWriteAction(boost::bind(&AsynchIO::writeEvent, this)) + { + if (protocolVersion > maxSupportedProtocolVersion) + throw IOException("Unsupported Rdma Protocol"); + qp->nonblocking(); + qp->notifyRecv(); + qp->notifySend(); + + // Prepost recv buffers before we go any further + qp->allocateRecvBuffers(recvBufferCount, bufferSize+FrameHeaderSize); + + // Create xmit buffers, reserve space for frame header. + qp->createSendBuffers(xmitBufferCount, bufferSize, FrameHeaderSize); + } + + AsynchIO::~AsynchIO() { + // Warn if we are deleting whilst there are still unreclaimed write buffers + if ( outstandingWrites>0 ) + QPID_LOG(error, "RDMA: qp=" << qp << ": Deleting queue before all write buffers finished"); + + // Turn off callbacks if necessary (before doing the deletes) + if (state != STOPPED) { + QPID_LOG(error, "RDMA: qp=" << qp << ": Deleting queue whilst not shutdown"); + dataHandle.stopWatch(); + } + // TODO: It might turn out to be more efficient in high connection loads to reuse the + // buffers rather than having to reregister them all the time (this would be straightforward if all + // connections haver the same buffer size and harder otherwise) + } + + void AsynchIO::start(Poller::shared_ptr poller) { + dataHandle.startWatch(poller); + } + + // State constraints + // On entry: None + // On exit: STOPPED + // Mark for deletion/Delete this object when we have no outstanding writes + void AsynchIO::stop(NotifyCallback nc) { + ScopedLock<Mutex> l(stateLock); + state = STOPPED; + notifyCallback = nc; + dataHandle.call(boost::bind(&AsynchIO::doStoppedCallback, this)); + } + + namespace { + void requestedCall(AsynchIO* aio, AsynchIO::RequestCallback callback) { + assert(callback); + callback(*aio); + } + } + + void AsynchIO::requestCallback(RequestCallback callback) { + // TODO creating a function object every time isn't all that + // efficient - if this becomes heavily used do something better (what?) + assert(callback); + dataHandle.call(boost::bind(&requestedCall, this, callback)); + } + + // Mark writing closed (so we don't accept any more writes or make any idle callbacks) + void AsynchIO::drainWriteQueue(NotifyCallback nc) { + draining = true; + notifyCallback = nc; + } + + void AsynchIO::queueBuffer(Buffer* buff, int credit) { + switch (protocolVersion) { + case 0: + if (!buff) { + Buffer* ob = getSendBuffer(); + // Have to send something as adapters hate it when you try to transfer 0 bytes + *reinterpret_cast< uint32_t* >(ob->bytes()) = htonl(credit); + ob->dataCount(sizeof(uint32_t)); + qp->postSend(credit | IgnoreData, ob); + } else if (credit > 0) { + qp->postSend(credit, buff); + } else { + qp->postSend(buff); + } + break; + case 1: + if (!buff) + buff = getSendBuffer(); + // Add FrameHeader after frame data + FrameHeader header(credit); + 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; + } + } + + Buffer* AsynchIO::extractBuffer(const QueuePairEvent& e) { + Buffer* b = e.getBuffer(); + switch (protocolVersion) { + case 0: { + bool dataPresent = true; + // Get our xmitCredit if it was sent + if (e.immPresent() ) { + assert(xmitCredit>=0); + xmitCredit += (e.getImm() & ~FlagsMask); + dataPresent = ((e.getImm() & IgnoreData) == 0); + assert(xmitCredit>0); + } + if (!dataPresent) { + b->dataCount(0); + } + break; + } + case 1: + b->dataCount(b->dataCount()-FrameHeaderSize); + FrameHeader header; + ::memcpy(&header, b->bytes()+b->dataCount(), FrameHeaderSize); + assert(xmitCredit>=0); + xmitCredit += header.credit(); + assert(xmitCredit>=0); + break; + } + + return b; + } + + void AsynchIO::queueWrite(Buffer* buff) { + // Make sure we don't overrun our available buffers + // either at our end or the known available at the peers end + if (writable()) { + // TODO: We might want to batch up sending credit + int creditSent = recvCredit & ~FlagsMask; + queueBuffer(buff, creditSent); + recvCredit -= creditSent; + ++outstandingWrites; + --xmitCredit; + assert(xmitCredit>=0); + } else { + if (fullCallback) { + fullCallback(*this, buff); + } else { + QPID_LOG(error, "RDMA: qp=" << qp << ": Write queue full, but no callback, throwing buffer away"); + returnSendBuffer(buff); + } + } + } + + // State constraints + // On entry: None + // On exit: NOTIFY_PENDING || STOPPED + void AsynchIO::notifyPendingWrite() { + ScopedLock<Mutex> l(stateLock); + switch (state) { + case IDLE: + dataHandle.call(pendingWriteAction); + // Fall Thru + case NOTIFY: + state = NOTIFY_PENDING; + break; + case NOTIFY_PENDING: + case STOPPED: + break; + } + } + + // State constraints + // On entry: IDLE || STOPPED + // On exit: IDLE || STOPPED + void AsynchIO::dataEvent() { + { + ScopedLock<Mutex> l(stateLock); + + if (state == STOPPED) return; + + state = NOTIFY_PENDING; + } + processCompletions(); + + writeEvent(); + } + + // State constraints + // On entry: NOTIFY_PENDING || STOPPED + // On exit: IDLE || STOPPED + void AsynchIO::writeEvent() { + State newState; + do { + { + ScopedLock<Mutex> l(stateLock); + + switch (state) { + case STOPPED: + return; + default: + state = NOTIFY; + } + } + + doWriteCallback(); + + { + ScopedLock<Mutex> l(stateLock); + + newState = state; + switch (newState) { + case NOTIFY_PENDING: + case STOPPED: + break; + default: + state = IDLE; + } + } + } while (newState == NOTIFY_PENDING); + } + + void AsynchIO::processCompletions() { + QueuePair::intrusive_ptr q = qp->getNextChannelEvent(); + + // Re-enable notification for queue: + // This needs to happen before we could do anything that could generate more work completion + // events (ie the callbacks etc. in the following). + // This can't make us reenter this code as the handle attached to the completion queue will still be + // disabled by the poller until we leave this code + qp->notifyRecv(); + qp->notifySend(); + + int recvEvents = 0; + int sendEvents = 0; + + // If no event do nothing + if (!q) + return; + + assert(q == qp); + + // Repeat until no more events + do { + QueuePairEvent e(qp->getNextEvent()); + if (!e) + break; + + ::ibv_wc_status status = e.getEventStatus(); + if (status != IBV_WC_SUCCESS) { + // Need special check for IBV_WC_WR_FLUSH_ERR here + // we will get this for every send/recv queue entry that was pending + // when disconnected, these aren't real errors and mostly need to be ignored + if (status == IBV_WC_WR_FLUSH_ERR) { + QueueDirection dir = e.getDirection(); + if (dir == SEND) { + Buffer* b = e.getBuffer(); + ++sendEvents; + returnSendBuffer(b); + --outstandingWrites; + } else { + ++recvEvents; + } + continue; + } + errorCallback(*this); + // TODO: Probably need to flush queues at this point + return; + } + + // Test if recv (or recv with imm) + //::ibv_wc_opcode eventType = e.getEventType(); + QueueDirection dir = e.getDirection(); + if (dir == RECV) { + ++recvEvents; + + Buffer* b = extractBuffer(e); + + // if there was no data sent then the message was only to update our credit + if ( b->dataCount() > 0 ) { + readCallback(*this, b); + } + + // At this point the buffer has been consumed so put it back on the recv queue + // TODO: Is this safe to do if the connection is disconnected already? + qp->postRecv(b); + + // Received another message + ++recvCredit; + + // Send recvCredit if it is large enough (it will have got this large because we've not sent anything recently) + if (recvCredit > recvBufferCount/2) { + // TODO: This should use RDMA write with imm as there might not ever be a buffer to receive this message + // but this is a little unlikely, as to get in this state we have to have received messages without sending any + // for a while so its likely we've received an credit update from the far side. + if (writable()) { + int creditSent = recvCredit & ~FlagsMask; + queueBuffer(0, creditSent); + recvCredit -= creditSent; + ++outstandingWrites; + --xmitCredit; + assert(xmitCredit>=0); + } else { + QPID_LOG(warning, "RDMA: qp=" << qp << ": Unable to send unsolicited credit"); + } + } + } else { + Buffer* b = e.getBuffer(); + ++sendEvents; + returnSendBuffer(b); + --outstandingWrites; + } + } while (true); + + // Not sure if this is expected or not + if (recvEvents == 0 && sendEvents == 0) { + QPID_LOG(debug, "RDMA: qp=" << qp << ": Got channel event with no recv/send completions"); + } + } + + void AsynchIO::doWriteCallback() { + // TODO: maybe don't call idle unless we're low on write buffers + // Keep on calling the idle routine as long as we are writable and we got something to write last call + + // Do callback even if there are no available free buffers as the application itself might be + // holding onto buffers + while (writable()) { + int xc = xmitCredit; + idleCallback(*this); + // Check whether we actually wrote anything + if (xmitCredit == xc) { + QPID_LOG(debug, "RDMA: qp=" << qp << ": Called for data, but got none: xmitCredit=" << xmitCredit); + return; + } + } + + checkDrained(); + } + + void AsynchIO::checkDrained() { + // If we've got all the write confirmations and we're draining + // We might get deleted in the drained callback so return immediately + if (draining) { + if (outstandingWrites == 0) { + draining = false; + NotifyCallback nc; + nc.swap(notifyCallback); + nc(*this); + } + return; + } + } + + void AsynchIO::doStoppedCallback() { + // Ensure we can't get any more callbacks (except for the stopped callback) + dataHandle.stopWatch(); + + NotifyCallback nc; + nc.swap(notifyCallback); + nc(*this); + } + + ConnectionManager::ConnectionManager( + ErrorCallback errc, + DisconnectedCallback dc + ) : + state(IDLE), + ci(Connection::make()), + handle(*ci, boost::bind(&ConnectionManager::event, this, _1), 0, 0), + errorCallback(errc), + disconnectedCallback(dc) + { + QPID_LOG(debug, "RDMA: ci=" << ci << ": Creating ConnectionManager"); + ci->nonblocking(); + } + + ConnectionManager::~ConnectionManager() + { + QPID_LOG(debug, "RDMA: ci=" << ci << ": Deleting ConnectionManager"); + } + + void ConnectionManager::start(Poller::shared_ptr poller, const qpid::sys::SocketAddress& addr) { + startConnection(ci, addr); + handle.startWatch(poller); + } + + void ConnectionManager::doStoppedCallback() { + // Ensure we can't get any more callbacks (except for the stopped callback) + handle.stopWatch(); + + NotifyCallback nc; + nc.swap(notifyCallback); + nc(*this); + } + + void ConnectionManager::stop(NotifyCallback nc) { + state = STOPPED; + notifyCallback = nc; + handle.call(boost::bind(&ConnectionManager::doStoppedCallback, this)); + } + + void ConnectionManager::event(DispatchHandle&) { + if (state.get() == STOPPED) return; + connectionEvent(ci); + } + + Listener::Listener( + const ConnectionParams& cp, + EstablishedCallback ec, + ErrorCallback errc, + DisconnectedCallback dc, + ConnectionRequestCallback crc + ) : + ConnectionManager(errc, dc), + checkConnectionParams(cp), + connectionRequestCallback(crc), + establishedCallback(ec) + { + } + + void Listener::startConnection(Connection::intrusive_ptr ci, const qpid::sys::SocketAddress& addr) { + ci->bind(addr); + ci->listen(); + } + + namespace { + const int64_t PoisonContext = -1; + } + + void Listener::connectionEvent(Connection::intrusive_ptr ci) { + ConnectionEvent e(ci->getNextEvent()); + + // If (for whatever reason) there was no event do nothing + if (!e) + return; + + // Important documentation ommision the new rdma_cm_id + // you get from CONNECT_REQUEST has the same context info + // as its parent listening rdma_cm_id + ::rdma_cm_event_type eventType = e.getEventType(); + ::rdma_conn_param conn_param = e.getConnectionParam(); + Rdma::Connection::intrusive_ptr id = e.getConnection(); + + // Check for previous disconnection (it appears that you actually can get connection + // request events after a disconnect event in rare circumstances) + if (reinterpret_cast<int64_t>(id->getContext<void*>())==PoisonContext) + return; + + switch (eventType) { + case RDMA_CM_EVENT_CONNECT_REQUEST: { + // Make sure peer has sent params we can use + if (!conn_param.private_data || conn_param.private_data_len < sizeof(NConnectionParams)) { + QPID_LOG(warning, "Rdma: rejecting connection attempt: unusable connection parameters"); + id->reject(); + break; + } + + const NConnectionParams* rcp = static_cast<const NConnectionParams*>(conn_param.private_data); + ConnectionParams cp = *rcp; + + // Reject if requested msg size is bigger than we allow + if ( + cp.maxRecvBufferSize > checkConnectionParams.maxRecvBufferSize || + cp.initialXmitCredit > checkConnectionParams.initialXmitCredit + ) { + QPID_LOG(warning, "Rdma: rejecting connection attempt: connection parameters out of range: (" + << cp.maxRecvBufferSize << ">" << checkConnectionParams.maxRecvBufferSize << " || " + << cp.initialXmitCredit << ">" << checkConnectionParams.initialXmitCredit + << ")"); + id->reject(&checkConnectionParams); + break; + } + + bool accept = true; + if (connectionRequestCallback) + accept = connectionRequestCallback(id, cp); + + if (accept) { + // Accept connection + cp.initialXmitCredit = checkConnectionParams.initialXmitCredit; + id->accept(conn_param, rcp); + } else { + // Reject connection + QPID_LOG(warning, "Rdma: rejecting connection attempt: application policy"); + id->reject(); + } + break; + } + case RDMA_CM_EVENT_ESTABLISHED: + establishedCallback(id); + break; + case RDMA_CM_EVENT_DISCONNECTED: + disconnectedCallback(id); + // Poison the id context so that we do no more callbacks on it + id->removeContext(); + id->addContext(reinterpret_cast<void*>(PoisonContext)); + break; + case RDMA_CM_EVENT_CONNECT_ERROR: + errorCallback(id, CONNECT_ERROR); + break; + default: + // Unexpected response + errorCallback(id, UNKNOWN); + //std::cerr << "Warning: unexpected response to listen - " << eventType << "\n"; + } + } + + Connector::Connector( + const ConnectionParams& cp, + ConnectedCallback cc, + ErrorCallback errc, + DisconnectedCallback dc, + RejectedCallback rc + ) : + ConnectionManager(errc, dc), + connectionParams(cp), + rejectedCallback(rc), + connectedCallback(cc) + { + } + + void Connector::startConnection(Connection::intrusive_ptr ci, const qpid::sys::SocketAddress& addr) { + ci->resolve_addr(addr); + } + + void Connector::connectionEvent(Connection::intrusive_ptr ci) { + ConnectionEvent e(ci->getNextEvent()); + + // If (for whatever reason) there was no event do nothing + if (!e) + return; + + ::rdma_cm_event_type eventType = e.getEventType(); + ::rdma_conn_param conn_param = e.getConnectionParam(); + Rdma::Connection::intrusive_ptr id = e.getConnection(); + switch (eventType) { + case RDMA_CM_EVENT_ADDR_RESOLVED: + // RESOLVE_ADDR + ci->resolve_route(); + break; + case RDMA_CM_EVENT_ADDR_ERROR: + // RESOLVE_ADDR + errorCallback(ci, ADDR_ERROR); + break; + case RDMA_CM_EVENT_ROUTE_RESOLVED: { + // RESOLVE_ROUTE: + NConnectionParams rcp(connectionParams); + ci->connect(&rcp); + break; + } + case RDMA_CM_EVENT_ROUTE_ERROR: + // RESOLVE_ROUTE: + errorCallback(ci, ROUTE_ERROR); + break; + case RDMA_CM_EVENT_CONNECT_ERROR: + // CONNECTING + errorCallback(ci, CONNECT_ERROR); + break; + case RDMA_CM_EVENT_UNREACHABLE: + // CONNECTING + errorCallback(ci, UNREACHABLE); + break; + case RDMA_CM_EVENT_REJECTED: { + // CONNECTING + + // We can get this event if our peer is not running on the other side + // in this case we could get nearly anything in the private data: + // From private_data == 0 && private_data_len == 0 (Chelsio iWarp) + // to 148 bytes of zeros (Mellanox IB) + // + // So assume that if the the private data is absent or not the size of + // the connection parameters it isn't valid + ConnectionParams cp(0, 0, 0); + if (conn_param.private_data && conn_param.private_data_len == sizeof(NConnectionParams)) { + // Extract private data from event + const NConnectionParams* rcp = static_cast<const NConnectionParams*>(conn_param.private_data); + cp = *rcp; + } + rejectedCallback(ci, cp); + break; + } + case RDMA_CM_EVENT_ESTABLISHED: { + // CONNECTING + // Extract private data from event + assert(conn_param.private_data && conn_param.private_data_len >= sizeof(NConnectionParams)); + const NConnectionParams* rcp = static_cast<const NConnectionParams*>(conn_param.private_data); + ConnectionParams cp = *rcp; + connectedCallback(ci, cp); + break; + } + case RDMA_CM_EVENT_DISCONNECTED: + // ESTABLISHED + disconnectedCallback(ci); + break; + default: + QPID_LOG(warning, "RDMA: Unexpected event in connect: " << eventType); + } + } +} diff --git a/qpid/cpp/src/qpid/sys/rdma/RdmaIO.h b/qpid/cpp/src/qpid/sys/rdma/RdmaIO.h new file mode 100644 index 0000000000..ec9caaf08d --- /dev/null +++ b/qpid/cpp/src/qpid/sys/rdma/RdmaIO.h @@ -0,0 +1,250 @@ +/* + * + * 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 Rdma_Acceptor_h +#define Rdma_Acceptor_h + +#include "qpid/sys/rdma/rdma_wrap.h" + +#include "qpid/sys/AtomicValue.h" +#include "qpid/sys/Dispatcher.h" +#include "qpid/sys/DispatchHandle.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/SocketAddress.h" + +#include <netinet/in.h> + +#include <boost/function.hpp> + +namespace Rdma { + + class Connection; + + class AsynchIO + { + typedef boost::function1<void, AsynchIO&> ErrorCallback; + typedef boost::function2<void, AsynchIO&, Buffer*> ReadCallback; + typedef boost::function1<void, AsynchIO&> IdleCallback; + typedef boost::function2<void, AsynchIO&, Buffer*> FullCallback; + typedef boost::function1<void, AsynchIO&> NotifyCallback; + + int protocolVersion; + int bufferSize; + int recvCredit; + int xmitCredit; + int recvBufferCount; + int xmitBufferCount; + int outstandingWrites; + bool draining; + enum State {IDLE, NOTIFY, NOTIFY_PENDING, STOPPED}; + State state; + qpid::sys::Mutex stateLock; + QueuePair::intrusive_ptr qp; + qpid::sys::DispatchHandleRef dataHandle; + + ReadCallback readCallback; + IdleCallback idleCallback; + FullCallback fullCallback; + ErrorCallback errorCallback; + NotifyCallback notifyCallback; + qpid::sys::DispatchHandle::Callback pendingWriteAction; + + public: + typedef boost::function1<void, AsynchIO&> RequestCallback; + + // TODO: Instead of specifying a buffer size specify the amount of memory the AsynchIO class can use + // for buffers both read and write (allocate half to each up front) and fail if we cannot allocate that much + // locked memory + AsynchIO( + QueuePair::intrusive_ptr q, + int version, + int size, + int xCredit, + int rCount, + ReadCallback rc, + IdleCallback ic, + FullCallback fc, + ErrorCallback ec + ); + ~AsynchIO(); + + void start(qpid::sys::Poller::shared_ptr poller); + bool writable() const; + void queueWrite(Buffer* buff); + void notifyPendingWrite(); + void drainWriteQueue(NotifyCallback); + void stop(NotifyCallback); + void requestCallback(RequestCallback); + int incompletedWrites() const; + Buffer* getSendBuffer(); + void returnSendBuffer(Buffer*); + + private: + const static int maxSupportedProtocolVersion = 1; + + // Constants for the peer-peer command messages + // These are sent in the high bits if the imm data of an rdma message + // The low bits are used to send the credit + const static int FlagsMask = 0xF0000000; // Mask for all flag bits - be sure to update this if you add more command bits + const static int IgnoreData = 0x10000000; // Message contains no application data + + void dataEvent(); + void writeEvent(); + void processCompletions(); + void doWriteCallback(); + void checkDrained(); + void doStoppedCallback(); + + void queueBuffer(Buffer* buff, int credit); + Buffer* extractBuffer(const QueuePairEvent& e); + }; + + // We're only writable if: + // * not draining write queue + // * we've got space in the transmit queue + // * we've got credit to transmit + // * if there's only 1 transmit credit we must send some credit + inline bool AsynchIO::writable() const { + assert(xmitCredit>=0); + return !draining && + outstandingWrites < xmitBufferCount && + xmitCredit > 0 && + ( xmitCredit > 1 || recvCredit > 0); + } + + inline int AsynchIO::incompletedWrites() const { + return outstandingWrites; + } + + inline Buffer* AsynchIO::getSendBuffer() { + return qp->getSendBuffer(); + } + + inline void AsynchIO::returnSendBuffer(Buffer* b) { + qp->returnSendBuffer(b); + } + + // These are the parameters necessary to start the conversation + // * Each peer HAS to allocate buffers of the size of the maximum receive from its peer + // * Each peer HAS to know the initial "credit" it has for transmitting to its peer + struct ConnectionParams { + uint32_t maxRecvBufferSize; + uint16_t initialXmitCredit; + uint16_t rdmaProtocolVersion; + + // Default to protocol version 1 + ConnectionParams(uint32_t s, uint16_t c, uint16_t v = 1) : + maxRecvBufferSize(s), + initialXmitCredit(c), + rdmaProtocolVersion(v) + {} + }; + + enum ErrorType { + ADDR_ERROR, + ROUTE_ERROR, + CONNECT_ERROR, + UNREACHABLE, + UNKNOWN + }; + + typedef boost::function2<void, Rdma::Connection::intrusive_ptr, ErrorType> ErrorCallback; + typedef boost::function1<void, Rdma::Connection::intrusive_ptr> DisconnectedCallback; + + class ConnectionManager { + typedef boost::function1<void, ConnectionManager&> NotifyCallback; + + enum State {IDLE, STOPPED}; + qpid::sys::AtomicValue<State> state; + Connection::intrusive_ptr ci; + qpid::sys::DispatchHandleRef handle; + NotifyCallback notifyCallback; + + protected: + ErrorCallback errorCallback; + DisconnectedCallback disconnectedCallback; + + public: + ConnectionManager( + ErrorCallback errc, + DisconnectedCallback dc + ); + + virtual ~ConnectionManager(); + + void start(qpid::sys::Poller::shared_ptr poller, const qpid::sys::SocketAddress& addr); + void stop(NotifyCallback); + + private: + void event(qpid::sys::DispatchHandle& handle); + void doStoppedCallback(); + + virtual void startConnection(Connection::intrusive_ptr ci, const qpid::sys::SocketAddress& addr) = 0; + virtual void connectionEvent(Connection::intrusive_ptr ci) = 0; + }; + + typedef boost::function2<bool, Rdma::Connection::intrusive_ptr, const ConnectionParams&> ConnectionRequestCallback; + typedef boost::function1<void, Rdma::Connection::intrusive_ptr> EstablishedCallback; + + class Listener : public ConnectionManager + { + ConnectionParams checkConnectionParams; + ConnectionRequestCallback connectionRequestCallback; + EstablishedCallback establishedCallback; + + public: + Listener( + const ConnectionParams& cp, + EstablishedCallback ec, + ErrorCallback errc, + DisconnectedCallback dc, + ConnectionRequestCallback crc = 0 + ); + + private: + void startConnection(Connection::intrusive_ptr ci, const qpid::sys::SocketAddress& addr); + void connectionEvent(Connection::intrusive_ptr ci); + }; + + typedef boost::function2<void, Rdma::Connection::intrusive_ptr, const ConnectionParams&> RejectedCallback; + typedef boost::function2<void, Rdma::Connection::intrusive_ptr, const ConnectionParams&> ConnectedCallback; + + class Connector : public ConnectionManager + { + ConnectionParams connectionParams; + RejectedCallback rejectedCallback; + ConnectedCallback connectedCallback; + + public: + Connector( + const ConnectionParams& cp, + ConnectedCallback cc, + ErrorCallback errc, + DisconnectedCallback dc, + RejectedCallback rc = 0 + ); + + private: + void startConnection(Connection::intrusive_ptr ci, const qpid::sys::SocketAddress& addr); + void connectionEvent(Connection::intrusive_ptr ci); + }; +} + +#endif // Rdma_Acceptor_h diff --git a/qpid/cpp/src/qpid/sys/rdma/RdmaServer.cpp b/qpid/cpp/src/qpid/sys/rdma/RdmaServer.cpp new file mode 100644 index 0000000000..9b0710fd8f --- /dev/null +++ b/qpid/cpp/src/qpid/sys/rdma/RdmaServer.cpp @@ -0,0 +1,210 @@ +/* + * + * 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/Thread.h" +#include "qpid/sys/rdma/RdmaIO.h" +#include "qpid/sys/rdma/rdma_exception.h" + +#include <arpa/inet.h> + +#include <vector> +#include <queue> +#include <string> +#include <iostream> + +#include <boost/bind.hpp> + +using std::vector; +using std::queue; +using std::string; +using std::cout; +using std::cerr; + +using qpid::sys::Thread; +using qpid::sys::SocketAddress; +using qpid::sys::Poller; + +// All the accepted connections +namespace qpid { +namespace tests { + +struct Buffer { + char* bytes() const {return bytes_;} + int32_t byteCount() const {return size;} + + Buffer(const int32_t s): + bytes_(new char[s]), + size(s) + { + } + + ~Buffer() { + delete [] bytes_; + } +private: + char* bytes_; + int32_t size; +}; + +struct ConRec { + Rdma::Connection::intrusive_ptr connection; + Rdma::AsynchIO* data; + queue<Buffer*> queuedWrites; + + ConRec(Rdma::Connection::intrusive_ptr c) : + connection(c) + {} +}; + +void dataError(Rdma::AsynchIO&) { + cout << "Data error:\n"; +} + +void idle(ConRec* cr, Rdma::AsynchIO& a) { + // Need to make sure full is not called as it would reorder messages + while (!cr->queuedWrites.empty() && a.writable()) { + Rdma::Buffer* rbuf = a.getSendBuffer(); + if (!rbuf) break; + Buffer* buf = cr->queuedWrites.front(); + cr->queuedWrites.pop(); + std::copy(buf->bytes(), buf->bytes()+buf->byteCount(), rbuf->bytes()); + rbuf->dataCount(buf->byteCount()); + delete buf; + a.queueWrite(rbuf); + } +} + +void data(ConRec* cr, Rdma::AsynchIO& a, Rdma::Buffer* b) { + // Echo data back + Rdma::Buffer* buf = 0; + if (cr->queuedWrites.empty() && a.writable()) { + buf = a.getSendBuffer(); + } + if (buf) { + std::copy(b->bytes(), b->bytes()+b->dataCount(), buf->bytes()); + buf->dataCount(b->dataCount()); + a.queueWrite(buf); + } else { + Buffer* buf = new Buffer(b->dataCount()); + std::copy(b->bytes(), b->bytes()+b->dataCount(), buf->bytes()); + cr->queuedWrites.push(buf); + // Try to empty queue + idle(cr, a); + } +} + +void full(ConRec*, Rdma::AsynchIO&, Rdma::Buffer*) { + // Shouldn't ever be called + cout << "!"; +} + +void drained(Rdma::AsynchIO&) { + cout << "Drained:\n"; +} + +void disconnected(Rdma::Connection::intrusive_ptr& ci) { + ConRec* cr = ci->getContext<ConRec>(); + cr->connection->disconnect(); + cr->data->drainWriteQueue(drained); + delete cr; + cout << "Disconnected: " << cr << "\n"; +} + +void connectionError(Rdma::Connection::intrusive_ptr& ci, Rdma::ErrorType) { + ConRec* cr = ci->getContext<ConRec>(); + cr->connection->disconnect(); + if (cr) { + cr->data->drainWriteQueue(drained); + delete cr; + } + cout << "Connection error: " << cr << "\n"; +} + +bool connectionRequest(Rdma::Connection::intrusive_ptr& ci, const Rdma::ConnectionParams& cp) { + cout << "Incoming connection: "; + + // For fun reject alternate connection attempts + static bool x = false; + x = true; + + // Must create aio here so as to prepost buffers *before* we accept connection + if (x) { + ConRec* cr = new ConRec(ci); + Rdma::AsynchIO* aio = + new Rdma::AsynchIO(ci->getQueuePair(), + cp.rdmaProtocolVersion, + cp.maxRecvBufferSize, cp.initialXmitCredit, Rdma::DEFAULT_WR_ENTRIES, + boost::bind(data, cr, _1, _2), + boost::bind(idle, cr, _1), + boost::bind(full, cr, _1, _2), + dataError); + ci->addContext(cr); + cr->data = aio; + cout << "Accept=>" << cr << "\n"; + } else { + cout << "Reject\n"; + } + + return x; +} + +void connected(Poller::shared_ptr poller, Rdma::Connection::intrusive_ptr& ci) { + static int cnt = 0; + ConRec* cr = ci->getContext<ConRec>(); + cout << "Connected: " << cr << "(" << ++cnt << ")\n"; + + cr->data->start(poller); +} + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char* argv[]) { + vector<string> args(&argv[0], &argv[argc]); + + std::string port = (args.size() < 2) ? "20079" : args[1]; + cout << "Listening on port: " << port << "\n"; + + try { + boost::shared_ptr<Poller> p(new Poller()); + + Rdma::Listener a( + Rdma::ConnectionParams(16384, Rdma::DEFAULT_WR_ENTRIES), + boost::bind(connected, p, _1), + connectionError, + disconnected, + connectionRequest); + + + SocketAddress sa("", port); + a.start(p, sa); + + // The poller loop blocks all signals so run in its own thread + Thread t(*p); + + ::pause(); + p->shutdown(); + t.join(); + } catch (Rdma::Exception& e) { + int err = e.getError(); + cerr << "Error: " << e.what() << "(" << err << ")\n"; + } +} diff --git a/qpid/cpp/src/qpid/sys/rdma/rdma_exception.h b/qpid/cpp/src/qpid/sys/rdma/rdma_exception.h new file mode 100644 index 0000000000..a3a289e38a --- /dev/null +++ b/qpid/cpp/src/qpid/sys/rdma/rdma_exception.h @@ -0,0 +1,69 @@ +/* + * + * 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 RDMA_EXCEPTION_H +#define RDMA_EXCEPTION_H + +#include <exception> + +#include <errno.h> +#include <string.h> + +namespace Rdma { + static __thread char s[50]; + class Exception : public std::exception { + int err; + + public: + Exception(int e) : err(e) {} + int getError() { return err; } + const char* what() const throw() { + return ::strerror_r(err, s, 50); + } + }; + + inline void THROW_ERRNO() { + throw Rdma::Exception(errno); + } + + inline void CHECK(int rc) { + if (rc != 0) + throw Rdma::Exception((rc == -1) ? errno : rc >0 ? rc : -rc); + } + + inline int GETERR(int rc) { + return (rc == -1) ? errno : rc > 0 ? rc : -rc; + } + + inline void CHECK_IBV(int rc) { + if (rc != 0) + throw Rdma::Exception(rc); + } + + template <typename T> + inline + T* CHECK_NULL(T* rc) { + if (rc == 0) + THROW_ERRNO(); + return rc; + } +} + +#endif // RDMA_EXCEPTION_H diff --git a/qpid/cpp/src/qpid/sys/rdma/rdma_factories.cpp b/qpid/cpp/src/qpid/sys/rdma/rdma_factories.cpp new file mode 100644 index 0000000000..a66f5b4035 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/rdma/rdma_factories.cpp @@ -0,0 +1,105 @@ +/* + * + * 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/rdma/rdma_factories.h" + +#include "qpid/sys/rdma/rdma_exception.h" + + +namespace Rdma { + // Intentionally ignore return values for these functions + // - we can't do anything about then anyway + void acker(::rdma_cm_event* e) throw () { + if (e) (void) ::rdma_ack_cm_event(e); + } + + void destroyEChannel(::rdma_event_channel* c) throw () { + if (c) (void) ::rdma_destroy_event_channel(c); + } + + void destroyId(::rdma_cm_id* i) throw () { + if (i) (void) ::rdma_destroy_id(i); + } + + void deallocPd(::ibv_pd* p) throw () { + if (p) (void) ::ibv_dealloc_pd(p); + } + + void deregMr(::ibv_mr* mr) throw () { + if (mr) (void) ::ibv_dereg_mr(mr); + } + + void destroyCChannel(::ibv_comp_channel* c) throw () { + if (c) (void) ::ibv_destroy_comp_channel(c); + } + + void destroyCq(::ibv_cq* cq) throw () { + if (cq) (void) ::ibv_destroy_cq(cq); + } + + void destroyQp(::ibv_qp* qp) throw () { + if (qp) (void) ::ibv_destroy_qp(qp); + } + + boost::shared_ptr< ::rdma_cm_id > mkId(::rdma_cm_id* i) { + return boost::shared_ptr< ::rdma_cm_id >(i, destroyId); + } + + boost::shared_ptr< ::rdma_cm_event > mkEvent(::rdma_cm_event* e) { + return boost::shared_ptr< ::rdma_cm_event >(e, acker); + } + + boost::shared_ptr< ::ibv_qp > mkQp(::ibv_qp* qp) { + return boost::shared_ptr< ::ibv_qp > (qp, destroyQp); + } + + boost::shared_ptr< ::rdma_event_channel > mkEChannel() { + ::rdma_event_channel* c = CHECK_NULL(::rdma_create_event_channel()); + return boost::shared_ptr< ::rdma_event_channel >(c, destroyEChannel); + } + + boost::shared_ptr< ::rdma_cm_id > + mkId(::rdma_event_channel* ec, void* context, ::rdma_port_space ps) { + ::rdma_cm_id* i; + CHECK(::rdma_create_id(ec, &i, context, ps)); + return mkId(i); + } + + boost::shared_ptr< ::ibv_pd > allocPd(::ibv_context* c) { + ::ibv_pd* pd = CHECK_NULL(::ibv_alloc_pd(c)); + return boost::shared_ptr< ::ibv_pd >(pd, deallocPd); + } + + boost::shared_ptr< ::ibv_mr > regMr(::ibv_pd* pd, void* addr, size_t length, ::ibv_access_flags access) { + ::ibv_mr* mr = CHECK_NULL(::ibv_reg_mr(pd, addr, length, access)); + return boost::shared_ptr< ::ibv_mr >(mr, deregMr); + } + + boost::shared_ptr< ::ibv_comp_channel > mkCChannel(::ibv_context* c) { + ::ibv_comp_channel* cc = CHECK_NULL(::ibv_create_comp_channel(c)); + return boost::shared_ptr< ::ibv_comp_channel >(cc, destroyCChannel); + } + + boost::shared_ptr< ::ibv_cq > + mkCq(::ibv_context* c, int cqe, void* context, ::ibv_comp_channel* cc) { + ::ibv_cq* cq = CHECK_NULL(::ibv_create_cq(c, cqe, context, cc, 0)); + return boost::shared_ptr< ::ibv_cq >(cq, destroyCq); + } +} diff --git a/qpid/cpp/src/qpid/sys/rdma/rdma_factories.h b/qpid/cpp/src/qpid/sys/rdma/rdma_factories.h new file mode 100644 index 0000000000..bfca71fc7e --- /dev/null +++ b/qpid/cpp/src/qpid/sys/rdma/rdma_factories.h @@ -0,0 +1,40 @@ +/* + * + * 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 RDMA_FACTORIES_H +#define RDMA_FACTORIES_H + +#include <rdma/rdma_cma.h> + +#include <boost/shared_ptr.hpp> + +namespace Rdma { + boost::shared_ptr< ::rdma_event_channel > mkEChannel(); + boost::shared_ptr< ::rdma_cm_id > mkId(::rdma_event_channel* ec, void* context, ::rdma_port_space ps); + boost::shared_ptr< ::rdma_cm_id > mkId(::rdma_cm_id* i); + boost::shared_ptr< ::rdma_cm_event > mkEvent(::rdma_cm_event* e); + boost::shared_ptr< ::ibv_qp > mkQp(::ibv_qp* qp); + boost::shared_ptr< ::ibv_pd > allocPd(::ibv_context* c); + boost::shared_ptr< ::ibv_mr > regMr(::ibv_pd* pd, void* addr, size_t length, ::ibv_access_flags access); + boost::shared_ptr< ::ibv_comp_channel > mkCChannel(::ibv_context* c); + boost::shared_ptr< ::ibv_cq > mkCq(::ibv_context* c, int cqe, void* context, ::ibv_comp_channel* cc); +} + +#endif // RDMA_FACTORIES_H diff --git a/qpid/cpp/src/qpid/sys/rdma/rdma_wrap.cpp b/qpid/cpp/src/qpid/sys/rdma/rdma_wrap.cpp new file mode 100644 index 0000000000..efe454c5be --- /dev/null +++ b/qpid/cpp/src/qpid/sys/rdma/rdma_wrap.cpp @@ -0,0 +1,566 @@ +/* + * + * 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/rdma/rdma_wrap.h" + +#include "qpid/sys/rdma/rdma_factories.h" +#include "qpid/sys/rdma/rdma_exception.h" + +#include "qpid/sys/posix/PrivatePosix.h" + +#include <fcntl.h> +#include <netdb.h> + +#include <iostream> +#include <stdexcept> + +namespace Rdma { + const ::rdma_conn_param DEFAULT_CONNECT_PARAM = { + 0, // .private_data + 0, // .private_data_len + 4, // .responder_resources + 4, // .initiator_depth + 0, // .flow_control + 5, // .retry_count + 7 // .rnr_retry_count + }; + + // This is moderately inefficient so don't use in a critical path + int deviceCount() { + int count; + ::ibv_free_device_list(::ibv_get_device_list(&count)); + return count; + } + + 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; + sge.lkey = lkey; + } + + QueuePairEvent::QueuePairEvent() : + dir(NONE) + {} + + QueuePairEvent::QueuePairEvent( + const ::ibv_wc& w, + boost::shared_ptr< ::ibv_cq > c, + QueueDirection d) : + cq(c), + wc(w), + dir(d) + { + assert(dir != NONE); + } + + QueuePairEvent::operator bool() const { + return dir != NONE; + } + + bool QueuePairEvent::immPresent() const { + return wc.wc_flags & IBV_WC_WITH_IMM; + } + + uint32_t QueuePairEvent::getImm() const { + return ntohl(wc.imm_data); + } + + QueueDirection QueuePairEvent::getDirection() const { + return dir; + } + + ::ibv_wc_opcode QueuePairEvent::getEventType() const { + return wc.opcode; + } + + ::ibv_wc_status QueuePairEvent::getEventStatus() const { + return wc.status; + } + + Buffer* QueuePairEvent::getBuffer() const { + Buffer* b = reinterpret_cast<Buffer*>(wc.wr_id); + b->dataCount(wc.byte_len); + return b; + } + + QueuePair::QueuePair(boost::shared_ptr< ::rdma_cm_id > i) : + qpid::sys::IOHandle(new qpid::sys::IOHandlePrivate), + pd(allocPd(i->verbs)), + cchannel(mkCChannel(i->verbs)), + scq(mkCq(i->verbs, DEFAULT_CQ_ENTRIES, 0, cchannel.get())), + rcq(mkCq(i->verbs, DEFAULT_CQ_ENTRIES, 0, cchannel.get())), + outstandingSendEvents(0), + outstandingRecvEvents(0) + { + impl->fd = cchannel->fd; + + // Set cq context to this QueuePair object so we can find + // ourselves again + scq->cq_context = this; + rcq->cq_context = this; + + ::ibv_device_attr dev_attr; + CHECK(::ibv_query_device(i->verbs, &dev_attr)); + + ::ibv_qp_init_attr qp_attr = {}; + + // TODO: make a default struct for this + qp_attr.cap.max_send_wr = DEFAULT_WR_ENTRIES; + qp_attr.cap.max_send_sge = 1; + qp_attr.cap.max_recv_wr = DEFAULT_WR_ENTRIES; + qp_attr.cap.max_recv_sge = 1; + + qp_attr.send_cq = scq.get(); + qp_attr.recv_cq = rcq.get(); + qp_attr.qp_type = IBV_QPT_RC; + + CHECK(::rdma_create_qp(i.get(), pd.get(), &qp_attr)); + qp = mkQp(i->qp); + + // Set the qp context to this so we can find ourselves again + qp->qp_context = this; + } + + QueuePair::~QueuePair() { + // Reset back pointer in case someone else has the qp + qp->qp_context = 0; + + // Dispose queue pair before we ack events + qp.reset(); + + if (outstandingSendEvents > 0) + ::ibv_ack_cq_events(scq.get(), outstandingSendEvents); + if (outstandingRecvEvents > 0) + ::ibv_ack_cq_events(rcq.get(), outstandingRecvEvents); + + // Deallocate recv buffer memory + if (rmr) delete [] static_cast<char*>(rmr->addr); + + // Deallocate recv buffer memory + if (smr) delete [] static_cast<char*>(smr->addr); + + // The buffers vectors automatically deletes all the buffers we've allocated + } + + // Create buffers to use for writing + void QueuePair::createSendBuffers(int sendBufferCount, int bufferSize, int reserved) + { + assert(!smr); + + // Round up buffersize to cacheline (64 bytes) + int dataLength = (bufferSize+reserved+63) & (~63); + + // Allocate memory block for all receive buffers + 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*dataLength], bufferSize, reserved)); + freeBuffers.push_back(i); + } + } + + Buffer* QueuePair::getSendBuffer() { + qpid::sys::ScopedLock<qpid::sys::Mutex> l(bufferLock); + if (freeBuffers.empty()) + return 0; + int i = freeBuffers.back(); + freeBuffers.pop_back(); + assert(i >= 0 && i < int(sendBuffers.size())); + Buffer* b = &sendBuffers[i]; + b->dataCount(0); + return b; + } + + void QueuePair::returnSendBuffer(Buffer* b) { + qpid::sys::ScopedLock<qpid::sys::Mutex> l(bufferLock); + int i = b - &sendBuffers[0]; + assert(i >= 0 && i < int(sendBuffers.size())); + freeBuffers.push_back(i); + } + + void QueuePair::allocateRecvBuffers(int recvBufferCount, int bufferSize) + { + assert(!rmr); + + // Round up buffersize to cacheline (64 bytes) + bufferSize = (bufferSize+63) & (~63); + + // Allocate memory block for all receive buffers + char* mem = new char [recvBufferCount * bufferSize]; + rmr = regMr(pd.get(), mem, recvBufferCount * bufferSize, ::IBV_ACCESS_LOCAL_WRITE); + recvBuffers.reserve(recvBufferCount); + for (int i = 0; i<recvBufferCount; ++i) { + // Allocate recv buffer + recvBuffers.push_back(Buffer(rmr->lkey, &mem[i*bufferSize], bufferSize)); + postRecv(&recvBuffers[i]); + } + } + + // Make channel non-blocking by making + // associated fd nonblocking + void QueuePair::nonblocking() { + ::fcntl(cchannel->fd, F_SETFL, O_NONBLOCK); + } + + // If we get EAGAIN because the channel has been set non blocking + // and we'd have to wait then return an empty event + QueuePair::intrusive_ptr QueuePair::getNextChannelEvent() { + // First find out which cq has the event + ::ibv_cq* cq; + void* ctx; + int rc = ::ibv_get_cq_event(cchannel.get(), &cq, &ctx); + if (rc == -1 && errno == EAGAIN) + return 0; + CHECK(rc); + + // Batch acknowledge the event + if (cq == scq.get()) { + if (++outstandingSendEvents > DEFAULT_CQ_ENTRIES / 2) { + ::ibv_ack_cq_events(cq, outstandingSendEvents); + outstandingSendEvents = 0; + } + } else if (cq == rcq.get()) { + if (++outstandingRecvEvents > DEFAULT_CQ_ENTRIES / 2) { + ::ibv_ack_cq_events(cq, outstandingRecvEvents); + outstandingRecvEvents = 0; + } + } + + return static_cast<QueuePair*>(ctx); + } + + QueuePairEvent QueuePair::getNextEvent() { + ::ibv_wc w; + if (::ibv_poll_cq(scq.get(), 1, &w) == 1) + return QueuePairEvent(w, scq, SEND); + else if (::ibv_poll_cq(rcq.get(), 1, &w) == 1) + return QueuePairEvent(w, rcq, RECV); + else + return QueuePairEvent(); + } + + void QueuePair::notifyRecv() { + CHECK_IBV(ibv_req_notify_cq(rcq.get(), 0)); + } + + void QueuePair::notifySend() { + CHECK_IBV(ibv_req_notify_cq(scq.get(), 0)); + } + + void QueuePair::postRecv(Buffer* buf) { + ::ibv_recv_wr rwr = {}; + + rwr.wr_id = reinterpret_cast<uint64_t>(buf); + // We are given the whole buffer + buf->dataCount(buf->byteCount()); + rwr.sg_list = &buf->sge; + rwr.num_sge = 1; + + ::ibv_recv_wr* badrwr = 0; + CHECK(::ibv_post_recv(qp.get(), &rwr, &badrwr)); + if (badrwr) + throw std::logic_error("ibv_post_recv(): Bad rwr"); + } + + void QueuePair::postSend(Buffer* buf) { + ::ibv_send_wr swr = {}; + + swr.wr_id = reinterpret_cast<uint64_t>(buf); + swr.opcode = IBV_WR_SEND; + swr.send_flags = IBV_SEND_SIGNALED; + swr.sg_list = &buf->sge; + swr.num_sge = 1; + + ::ibv_send_wr* badswr = 0; + CHECK(::ibv_post_send(qp.get(), &swr, &badswr)); + if (badswr) + throw std::logic_error("ibv_post_send(): Bad swr"); + } + + void QueuePair::postSend(uint32_t imm, Buffer* buf) { + ::ibv_send_wr swr = {}; + + swr.wr_id = reinterpret_cast<uint64_t>(buf); + swr.imm_data = htonl(imm); + swr.opcode = IBV_WR_SEND_WITH_IMM; + swr.send_flags = IBV_SEND_SIGNALED; + swr.sg_list = &buf->sge; + swr.num_sge = 1; + + ::ibv_send_wr* badswr = 0; + CHECK(::ibv_post_send(qp.get(), &swr, &badswr)); + if (badswr) + throw std::logic_error("ibv_post_send(): Bad swr"); + } + + ConnectionEvent::ConnectionEvent(::rdma_cm_event* e) : + id((e->event != RDMA_CM_EVENT_CONNECT_REQUEST) ? + Connection::find(e->id) : new Connection(e->id)), + listen_id(Connection::find(e->listen_id)), + event(mkEvent(e)) + {} + + ConnectionEvent::operator bool() const { + return event; + } + + ::rdma_cm_event_type ConnectionEvent::getEventType() const { + return event->event; + } + + ::rdma_conn_param ConnectionEvent::getConnectionParam() const { + // It's badly documented, but it seems from the librdma source code that all the following + // event types have a valid param.conn + switch (event->event) { + case RDMA_CM_EVENT_CONNECT_REQUEST: + case RDMA_CM_EVENT_ESTABLISHED: + case RDMA_CM_EVENT_REJECTED: + case RDMA_CM_EVENT_DISCONNECTED: + case RDMA_CM_EVENT_CONNECT_ERROR: + return event->param.conn; + default: + ::rdma_conn_param p = {}; + return p; + } + } + + boost::intrusive_ptr<Connection> ConnectionEvent::getConnection () const { + return id; + } + + boost::intrusive_ptr<Connection> ConnectionEvent::getListenId() const { + return listen_id; + } + + // Wrap the passed in rdma_cm_id with a Connection + // this basically happens only on connection request + Connection::Connection(::rdma_cm_id* i) : + qpid::sys::IOHandle(new qpid::sys::IOHandlePrivate), + id(mkId(i)), + context(0) + { + impl->fd = id->channel->fd; + + // Just overwrite the previous context as it will + // have come from the listening connection + if (i) + i->context = this; + } + + Connection::Connection() : + qpid::sys::IOHandle(new qpid::sys::IOHandlePrivate), + channel(mkEChannel()), + id(mkId(channel.get(), this, RDMA_PS_TCP)), + context(0) + { + impl->fd = channel->fd; + } + + Connection::~Connection() { + // Reset the id context in case someone else has it + id->context = 0; + } + + void Connection::ensureQueuePair() { + assert(id.get()); + + // Only allocate a queue pair if there isn't one already + if (qp) + return; + + qp = new QueuePair(id); + } + + Connection::intrusive_ptr Connection::make() { + return new Connection(); + } + + Connection::intrusive_ptr Connection::find(::rdma_cm_id* i) { + if (!i) + return 0; + Connection* id = static_cast< Connection* >(i->context); + if (!id) + throw std::logic_error("Couldn't find existing Connection"); + return id; + } + + // Make channel non-blocking by making + // associated fd nonblocking + void Connection::nonblocking() { + assert(id.get()); + ::fcntl(id->channel->fd, F_SETFL, O_NONBLOCK); + } + + // If we get EAGAIN because the channel has been set non blocking + // and we'd have to wait then return an empty event + ConnectionEvent Connection::getNextEvent() { + assert(id.get()); + ::rdma_cm_event* e; + int rc = ::rdma_get_cm_event(id->channel, &e); + if (GETERR(rc) == EAGAIN) + return ConnectionEvent(); + CHECK(rc); + return ConnectionEvent(e); + } + + void Connection::bind(const qpid::sys::SocketAddress& src_addr) const { + assert(id.get()); + CHECK(::rdma_bind_addr(id.get(), getAddrInfo(src_addr).ai_addr)); + } + + void Connection::listen(int backlog) const { + assert(id.get()); + CHECK(::rdma_listen(id.get(), backlog)); + } + + void Connection::resolve_addr( + const qpid::sys::SocketAddress& dst_addr, + int timeout_ms) const + { + assert(id.get()); + CHECK(::rdma_resolve_addr(id.get(), 0, getAddrInfo(dst_addr).ai_addr, timeout_ms)); + } + + void Connection::resolve_route(int timeout_ms) const { + assert(id.get()); + CHECK(::rdma_resolve_route(id.get(), timeout_ms)); + } + + void Connection::disconnect() const { + assert(id.get()); + int rc = ::rdma_disconnect(id.get()); + // iWarp doesn't let you disconnect a disconnected connection + // but Infiniband can do so it's okay to call rdma_disconnect() + // in response to a disconnect event, but we may get an error + if (GETERR(rc) == EINVAL) + return; + CHECK(rc); + } + + // TODO: Currently you can only connect with the default connection parameters + void Connection::connect(const void* data, size_t len) { + assert(id.get()); + // Need to have a queue pair before we can connect + ensureQueuePair(); + + ::rdma_conn_param p = DEFAULT_CONNECT_PARAM; + p.private_data = data; + p.private_data_len = len; + CHECK(::rdma_connect(id.get(), &p)); + } + + void Connection::connect() { + connect(0, 0); + } + + void Connection::accept(const ::rdma_conn_param& param, const void* data, size_t len) { + assert(id.get()); + // Need to have a queue pair before we can accept + ensureQueuePair(); + + ::rdma_conn_param p = param; + p.private_data = data; + p.private_data_len = len; + CHECK(::rdma_accept(id.get(), &p)); + } + + void Connection::accept(const ::rdma_conn_param& param) { + accept(param, 0, 0); + } + + void Connection::reject(const void* data, size_t len) const { + assert(id.get()); + CHECK(::rdma_reject(id.get(), data, len)); + } + + void Connection::reject() const { + assert(id.get()); + CHECK(::rdma_reject(id.get(), 0, 0)); + } + + QueuePair::intrusive_ptr Connection::getQueuePair() { + assert(id.get()); + + ensureQueuePair(); + + return qp; + } + + std::string Connection::getLocalName() const { + ::sockaddr* addr = ::rdma_get_local_addr(id.get()); + char hostName[NI_MAXHOST]; + char portName[NI_MAXSERV]; + CHECK_IBV(::getnameinfo( + addr, sizeof(::sockaddr_storage), + hostName, sizeof(hostName), + portName, sizeof(portName), + NI_NUMERICHOST | NI_NUMERICSERV)); + std::string r(hostName); + r += ":"; + r += portName; + return r; + } + + std::string Connection::getPeerName() const { + ::sockaddr* addr = ::rdma_get_peer_addr(id.get()); + char hostName[NI_MAXHOST]; + char portName[NI_MAXSERV]; + CHECK_IBV(::getnameinfo( + addr, sizeof(::sockaddr_storage), + hostName, sizeof(hostName), + portName, sizeof(portName), + NI_NUMERICHOST | NI_NUMERICSERV)); + std::string r(hostName); + r += ":"; + r += portName; + return r; + } +} + +std::ostream& operator<<(std::ostream& o, ::rdma_cm_event_type t) { +# define CHECK_TYPE(t) case t: o << #t; break; + switch(t) { + CHECK_TYPE(RDMA_CM_EVENT_ADDR_RESOLVED) + CHECK_TYPE(RDMA_CM_EVENT_ADDR_ERROR) + CHECK_TYPE(RDMA_CM_EVENT_ROUTE_RESOLVED) + CHECK_TYPE(RDMA_CM_EVENT_ROUTE_ERROR) + CHECK_TYPE(RDMA_CM_EVENT_CONNECT_REQUEST) + CHECK_TYPE(RDMA_CM_EVENT_CONNECT_RESPONSE) + CHECK_TYPE(RDMA_CM_EVENT_CONNECT_ERROR) + CHECK_TYPE(RDMA_CM_EVENT_UNREACHABLE) + CHECK_TYPE(RDMA_CM_EVENT_REJECTED) + CHECK_TYPE(RDMA_CM_EVENT_ESTABLISHED) + CHECK_TYPE(RDMA_CM_EVENT_DISCONNECTED) + CHECK_TYPE(RDMA_CM_EVENT_DEVICE_REMOVAL) + CHECK_TYPE(RDMA_CM_EVENT_MULTICAST_JOIN) + CHECK_TYPE(RDMA_CM_EVENT_MULTICAST_ERROR) + default: + o << "UNKNOWN_EVENT"; + } +# undef CHECK_TYPE + return o; +} diff --git a/qpid/cpp/src/qpid/sys/rdma/rdma_wrap.h b/qpid/cpp/src/qpid/sys/rdma/rdma_wrap.h new file mode 100644 index 0000000000..8e3429027b --- /dev/null +++ b/qpid/cpp/src/qpid/sys/rdma/rdma_wrap.h @@ -0,0 +1,287 @@ +/* + * + * 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 RDMA_WRAP_H +#define RDMA_WRAP_H + +#include <rdma/rdma_cma.h> + +#include "qpid/RefCounted.h" +#include "qpid/sys/IOHandle.h" +#include "qpid/sys/Mutex.h" + +#include <boost/shared_ptr.hpp> +#include <boost/intrusive_ptr.hpp> +#include <boost/ptr_container/ptr_deque.hpp> + +#include <vector> + +namespace qpid { +namespace sys { + class SocketAddress; +}} + +namespace Rdma { + const int DEFAULT_TIMEOUT = 2000; // 2 secs + const int DEFAULT_BACKLOG = 100; + const int DEFAULT_CQ_ENTRIES = 256; + const int DEFAULT_WR_ENTRIES = 64; + extern const ::rdma_conn_param DEFAULT_CONNECT_PARAM; + + int deviceCount(); + + struct Buffer { + friend class QueuePair; + friend class QueuePairEvent; + + char* bytes() const; + int32_t byteCount() const; + int32_t dataCount() const; + void dataCount(int32_t); + + private: + 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; + }; + + inline char* Buffer::bytes() const { + return (char*) sge.addr; + } + + /** return the number of bytes available for application data */ + inline int32_t Buffer::byteCount() const { + return bufferSize - reserved; + } + + inline int32_t Buffer::dataCount() const { + return sge.length; + } + + inline void Buffer::dataCount(int32_t s) { + // catch any attempt to overflow a buffer + assert(s <= bufferSize + reserved); + sge.length = s; + } + + class Connection; + + enum QueueDirection { + NONE, + SEND, + RECV + }; + + class QueuePairEvent { + boost::shared_ptr< ::ibv_cq > cq; + ::ibv_wc wc; + QueueDirection dir; + + friend class QueuePair; + + QueuePairEvent(); + QueuePairEvent( + const ::ibv_wc& w, + boost::shared_ptr< ::ibv_cq > c, + QueueDirection d); + + public: + operator bool() const; + bool immPresent() const; + uint32_t getImm() const; + QueueDirection getDirection() const; + ::ibv_wc_opcode getEventType() const; + ::ibv_wc_status getEventStatus() const; + Buffer* getBuffer() const; + }; + + // Wrapper for a queue pair - this has the functionality for + // putting buffers on the receive queue and for sending buffers + // to the other end of the connection. + class QueuePair : public qpid::sys::IOHandle, public qpid::RefCounted { + friend class Connection; + + boost::shared_ptr< ::ibv_pd > pd; + boost::shared_ptr< ::ibv_mr > smr; + boost::shared_ptr< ::ibv_mr > rmr; + boost::shared_ptr< ::ibv_comp_channel > cchannel; + boost::shared_ptr< ::ibv_cq > scq; + boost::shared_ptr< ::ibv_cq > rcq; + boost::shared_ptr< ::ibv_qp > qp; + int outstandingSendEvents; + int outstandingRecvEvents; + std::vector<Buffer> sendBuffers; + std::vector<Buffer> recvBuffers; + qpid::sys::Mutex bufferLock; + std::vector<int> freeBuffers; + + QueuePair(boost::shared_ptr< ::rdma_cm_id > id); + ~QueuePair(); + + public: + typedef boost::intrusive_ptr<QueuePair> intrusive_ptr; + + // Create a buffers to use for writing + void createSendBuffers(int sendBufferCount, int dataSize, int headerSize); + + // Get a send buffer + Buffer* getSendBuffer(); + + // Return buffer to pool after use + void returnSendBuffer(Buffer* b); + + // Create and post recv buffers + void allocateRecvBuffers(int recvBufferCount, int bufferSize); + + // Make channel non-blocking by making + // associated fd nonblocking + void nonblocking(); + + // If we get EAGAIN because the channel has been set non blocking + // and we'd have to wait then return an empty event + QueuePair::intrusive_ptr getNextChannelEvent(); + + QueuePairEvent getNextEvent(); + + void postRecv(Buffer* buf); + void postSend(Buffer* buf); + void postSend(uint32_t imm, Buffer* buf); + void notifyRecv(); + void notifySend(); + }; + + class ConnectionEvent { + friend class Connection; + + // The order of the members is important as we have to acknowledge + // the event before destroying the ids on destruction + boost::intrusive_ptr<Connection> id; + boost::intrusive_ptr<Connection> listen_id; + boost::shared_ptr< ::rdma_cm_event > event; + + ConnectionEvent() {} + ConnectionEvent(::rdma_cm_event* e); + + // Default copy, assignment and destructor ok + public: + operator bool() const; + ::rdma_cm_event_type getEventType() const; + ::rdma_conn_param getConnectionParam() const; + boost::intrusive_ptr<Connection> getConnection () const; + boost::intrusive_ptr<Connection> getListenId() const; + }; + + // For the moment this is a fairly simple wrapper for rdma_cm_id. + // + // NB: It allocates a protection domain (pd) per connection which means that + // registered buffers can't be shared between different connections + // (this can only happen between connections on the same controller in any case, + // so needs careful management if used) + class Connection : public qpid::sys::IOHandle, public qpid::RefCounted { + boost::shared_ptr< ::rdma_event_channel > channel; + boost::shared_ptr< ::rdma_cm_id > id; + QueuePair::intrusive_ptr qp; + + void* context; + + friend class ConnectionEvent; + friend class QueuePair; + + // Wrap the passed in rdma_cm_id with a Connection + // this basically happens only on connection request + Connection(::rdma_cm_id* i); + Connection(); + ~Connection(); + + void ensureQueuePair(); + + public: + typedef boost::intrusive_ptr<Connection> intrusive_ptr; + + static intrusive_ptr make(); + static intrusive_ptr find(::rdma_cm_id* i); + + template <typename T> + void addContext(T* c) { + // Don't allow replacing context + if (!context) + context = c; + } + + void removeContext() { + context = 0; + } + + template <typename T> + T* getContext() { + return static_cast<T*>(context); + } + + // Make channel non-blocking by making + // associated fd nonblocking + void nonblocking(); + + // If we get EAGAIN because the channel has been set non blocking + // and we'd have to wait then return an empty event + ConnectionEvent getNextEvent(); + + void bind(const qpid::sys::SocketAddress& src_addr) const; + void listen(int backlog = DEFAULT_BACKLOG) const; + void resolve_addr( + const qpid::sys::SocketAddress& dst_addr, + int timeout_ms = DEFAULT_TIMEOUT) const; + void resolve_route(int timeout_ms = DEFAULT_TIMEOUT) const; + void disconnect() const; + + // TODO: Currently you can only connect with the default connection parameters + void connect(const void* data, size_t len); + void connect(); + template <typename T> + void connect(const T* data) { + connect(data, sizeof(T)); + } + + // TODO: Not sure how to default accept params - they come from the connection request + // event + void accept(const ::rdma_conn_param& param, const void* data, size_t len); + void accept(const ::rdma_conn_param& param); + template <typename T> + void accept(const ::rdma_conn_param& param, const T* data) { + accept(param, data, sizeof(T)); + } + + void reject(const void* data, size_t len) const; + void reject() const; + template <typename T> + void reject(const T* data) const { + reject(data, sizeof(T)); + } + + QueuePair::intrusive_ptr getQueuePair(); + std::string getLocalName() const; + std::string getPeerName() const; + std::string getFullName() const { return getLocalName()+"-"+getPeerName(); } + }; +} + +std::ostream& operator<<(std::ostream& o, ::rdma_cm_event_type t); + +#endif // RDMA_WRAP_H diff --git a/qpid/cpp/src/qpid/sys/solaris/ECFPoller.cpp b/qpid/cpp/src/qpid/sys/solaris/ECFPoller.cpp new file mode 100644 index 0000000000..06d542c938 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/solaris/ECFPoller.cpp @@ -0,0 +1,444 @@ +/* + * + * 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/log/Logger.h" +#include "qpid/sys/Poller.h" +#include "qpid/sys/IOHandle.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/DeletionManager.h" +#include "qpid/sys/posix/check.h" +#include "qpid/sys/posix/PrivatePosix.h" + +#include <port.h> +#include <poll.h> +#include <errno.h> +#include <pthread.h> +#include <signal.h> + +#include <assert.h> +#include <queue> +#include <exception> + + +//TODO: Remove this +#include "qpid/sys/Dispatcher.h" + +namespace qpid { +namespace sys { + +// Deletion manager to handle deferring deletion of PollerHandles to when they definitely aren't being used +DeletionManager<PollerHandlePrivate> PollerHandleDeletionManager; + +// Instantiate (and define) class static for DeletionManager +template <> +DeletionManager<PollerHandlePrivate>::AllThreadsStatuses DeletionManager<PollerHandlePrivate>::allThreadsStatuses(0); + +class PollerHandlePrivate { + friend class Poller; + friend class PollerHandle; + + enum FDStat { + ABSENT, + MONITORED, + INACTIVE, + HUNGUP, + MONITORED_HUNGUP, + DELETED + }; + + int fd; + uint32_t events; + FDStat stat; + Mutex lock; + + PollerHandlePrivate(int f) : + fd(f), + events(0), + stat(ABSENT) { + } + + bool isActive() const { + return stat == MONITORED || stat == MONITORED_HUNGUP; + } + + void setActive() { + stat = (stat == HUNGUP) ? MONITORED_HUNGUP : MONITORED; + } + + bool isInactive() const { + return stat == INACTIVE || stat == HUNGUP; + } + + void setInactive() { + stat = INACTIVE; + } + + bool isIdle() const { + return stat == ABSENT; + } + + void setIdle() { + stat = ABSENT; + } + + bool isHungup() const { + return stat == MONITORED_HUNGUP || stat == HUNGUP; + } + + void setHungup() { + assert(stat == MONITORED); + stat = HUNGUP; + } + + bool isDeleted() const { + return stat == DELETED; + } + + void setDeleted() { + stat = DELETED; + } +}; + +PollerHandle::PollerHandle(const IOHandle& h) : + impl(new PollerHandlePrivate(toFd(h.impl))) +{} + +PollerHandle::~PollerHandle() { + { + ScopedLock<Mutex> l(impl->lock); + if (impl->isDeleted()) { + return; + } + if (impl->isActive()) { + impl->setDeleted(); + } + } + PollerHandleDeletionManager.markForDeletion(impl); +} + +/** + * Concrete implementation of Poller to use the Solaris Event Completion + * Framework interface + */ +class PollerPrivate { + friend class Poller; + + class InterruptHandle: public PollerHandle { + std::queue<PollerHandle*> handles; + + void processEvent(Poller::EventType) { + PollerHandle* handle = handles.front(); + handles.pop(); + assert(handle); + + //Synthesise event + Poller::Event event(handle, Poller::INTERRUPTED); + + //Process synthesised event + event.process(); + } + + public: + InterruptHandle() : PollerHandle(DummyIOHandle) {} + + void addHandle(PollerHandle& h) { + handles.push(&h); + } + + PollerHandle *getHandle() { + PollerHandle* handle = handles.front(); + handles.pop(); + return handle; + } + + bool queuedHandles() { + return handles.size() > 0; + } + }; + + const int portId; + bool isShutdown; + InterruptHandle interruptHandle; + + static uint32_t directionToPollEvent(Poller::Direction dir) { + switch (dir) { + case Poller::INPUT: return POLLIN; + case Poller::OUTPUT: return POLLOUT; + case Poller::INOUT: return POLLIN | POLLOUT; + default: return 0; + } + } + + static Poller::EventType pollToDirection(uint32_t events) { + uint32_t e = events & (POLLIN | POLLOUT); + switch (e) { + case POLLIN: return Poller::READABLE; + case POLLOUT: return Poller::WRITABLE; + case POLLIN | POLLOUT: return Poller::READ_WRITABLE; + default: + return (events & (POLLHUP | POLLERR)) ? + Poller::DISCONNECTED : Poller::INVALID; + } + } + + PollerPrivate() : + portId(::port_create()), + isShutdown(false) { + QPID_POSIX_CHECK(portId); + QPID_LOG(trace, "port_create returned port Id: " << portId); + } + + ~PollerPrivate() { + } + + void interrupt() { + //Send an Alarm to the port + //We need to send a nonzero event mask, using POLLHUP, + //nevertheless the wait method will only look for a PORT_ALERT_SET + QPID_LOG(trace, "Sending a port_alert to " << portId); + QPID_POSIX_CHECK(::port_alert(portId, PORT_ALERT_SET, POLLHUP, + &static_cast<PollerHandle&>(interruptHandle))); + } +}; + +void Poller::addFd(PollerHandle& handle, Direction dir) { + PollerHandlePrivate& eh = *handle.impl; + ScopedLock<Mutex> l(eh.lock); + + uint32_t events = 0; + + if (eh.isIdle()) { + events = PollerPrivate::directionToPollEvent(dir); + } else { + assert(eh.isActive()); + events = eh.events | PollerPrivate::directionToPollEvent(dir); + } + + //port_associate can be used to add an association or modify an + //existing one + QPID_POSIX_CHECK(::port_associate(impl->portId, PORT_SOURCE_FD, (uintptr_t) eh.fd, events, &handle)); + eh.events = events; + eh.setActive(); + QPID_LOG(trace, "Poller::addFd(handle=" << &handle + << "[" << typeid(&handle).name() + << "], fd=" << eh.fd << ")"); +} + +void Poller::delFd(PollerHandle& handle) { + PollerHandlePrivate& eh = *handle.impl; + ScopedLock<Mutex> l(eh.lock); + assert(!eh.isIdle()); + int rc = ::port_dissociate(impl->portId, PORT_SOURCE_FD, (uintptr_t) eh.fd); + //Allow closing an invalid fd, allowing users to close fd before + //doing delFd() + if (rc == -1 && errno != EBADFD) { + QPID_POSIX_CHECK(rc); + } + eh.setIdle(); + QPID_LOG(trace, "Poller::delFd(handle=" << &handle + << ", fd=" << eh.fd << ")"); +} + +// modFd is equivalent to delFd followed by addFd +void Poller::modFd(PollerHandle& handle, Direction dir) { + PollerHandlePrivate& eh = *handle.impl; + ScopedLock<Mutex> l(eh.lock); + assert(!eh.isIdle()); + + eh.events = PollerPrivate::directionToPollEvent(dir); + + //If fd is already associated, events and user arguments are updated + //So, no need to check if fd is already associated + QPID_POSIX_CHECK(::port_associate(impl->portId, PORT_SOURCE_FD, (uintptr_t) eh.fd, eh.events, &handle)); + eh.setActive(); + QPID_LOG(trace, "Poller::modFd(handle=" << &handle + << ", fd=" << eh.fd << ")"); +} + +void Poller::rearmFd(PollerHandle& handle) { + PollerHandlePrivate& eh = *handle.impl; + ScopedLock<Mutex> l(eh.lock); + assert(eh.isInactive()); + + QPID_POSIX_CHECK(::port_associate(impl->portId, PORT_SOURCE_FD, (uintptr_t) eh.fd, eh.events, &handle)); + eh.setActive(); + QPID_LOG(trace, "Poller::rearmdFd(handle=" << &handle + << ", fd=" << eh.fd << ")"); +} + +void Poller::shutdown() { + //Allow sloppy code to shut us down more than once + if (impl->isShutdown) + return; + + impl->isShutdown = true; + impl->interrupt(); +} + +bool Poller::hasShutdown() +{ + return impl->isShutdown; +} + +bool Poller::interrupt(PollerHandle& handle) { + PollerPrivate::InterruptHandle& ih = impl->interruptHandle; + PollerHandlePrivate& eh = *static_cast<PollerHandle&>(ih).impl; + ScopedLock<Mutex> l(eh.lock); + ih.addHandle(handle); + impl->interrupt(); + eh.setActive(); + return true; +} + +void Poller::run() { + // Make sure we can't be interrupted by signals at a bad time + ::sigset_t ss; + ::sigfillset(&ss); + ::pthread_sigmask(SIG_SETMASK, &ss, 0); + + do { + Event event = wait(); + + // If can read/write then dispatch appropriate callbacks + if (event.handle) { + event.process(); + } else { + // Handle shutdown + switch (event.type) { + case SHUTDOWN: + return; + default: + // This should be impossible + assert(false); + } + } + } while (true); +} + +Poller::Event Poller::wait(Duration timeout) { + timespec_t tout; + timespec_t* ptout = NULL; + port_event_t pe; + + AbsTime targetTimeout = (timeout == TIME_INFINITE) ? FAR_FUTURE : + AbsTime(now(), timeout); + + if (timeout != TIME_INFINITE) { + tout.tv_sec = 0; + tout.tv_nsec = timeout; + ptout = &tout; + } + + do { + PollerHandleDeletionManager.markAllUnusedInThisThread(); + QPID_LOG(trace, "About to enter port_get on " << impl->portId + << ". Thread " << pthread_self() + << ", timeout=" << timeout); + + + int rc = ::port_get(impl->portId, &pe, ptout); + + QPID_LOG(trace, "port_get on " << impl->portId + << " returned " << rc); + + if (impl->isShutdown) { + PollerHandleDeletionManager.markAllUnusedInThisThread(); + return Event(0, SHUTDOWN); + } + + if (rc < 0) { + switch (errno) { + case EINTR: + continue; + case ETIME: + return Event(0, TIMEOUT); + default: + QPID_POSIX_CHECK(rc); + } + } else { + PollerHandle* handle = static_cast<PollerHandle*>(pe.portev_user); + PollerHandlePrivate& eh = *handle->impl; + ScopedLock<Mutex> l(eh.lock); + + if (eh.isActive()) { + QPID_LOG(trace, "Handle is active"); + //We use alert mode to notify interrupts + if (pe.portev_source == PORT_SOURCE_ALERT && + handle == &impl->interruptHandle) { + QPID_LOG(trace, "Interrupt notified"); + + PollerHandle* wrappedHandle = impl->interruptHandle.getHandle(); + + if (impl->interruptHandle.queuedHandles()) { + impl->interrupt(); + eh.setActive(); + } else { + eh.setInactive(); + } + return Event(wrappedHandle, INTERRUPTED); + } + + if (pe.portev_source == PORT_SOURCE_FD) { + QPID_LOG(trace, "About to send handle: " << handle); + if (pe.portev_events & POLLHUP) { + if (eh.isHungup()) { + return Event(handle, DISCONNECTED); + } + eh.setHungup(); + } else { + eh.setInactive(); + } + QPID_LOG(trace, "Sending event (thread: " + << pthread_self() << ") for handle " << handle + << ", direction= " + << PollerPrivate::pollToDirection(pe.portev_events)); + return Event(handle, PollerPrivate::pollToDirection(pe.portev_events)); + } + } else if (eh.isDeleted()) { + //Remove the handle from the poller + int rc = ::port_dissociate(impl->portId, PORT_SOURCE_FD, + (uintptr_t) eh.fd); + if (rc == -1 && errno != EBADFD) { + QPID_POSIX_CHECK(rc); + } + } + } + + if (timeout == TIME_INFINITE) { + continue; + } + if (rc == 0 && now() > targetTimeout) { + PollerHandleDeletionManager.markAllUnusedInThisThread(); + return Event(0, TIMEOUT); + } + } while (true); +} + +// Concrete constructors +Poller::Poller() : + impl(new PollerPrivate()) +{} + +Poller::~Poller() { + delete impl; +} + +}} diff --git a/qpid/cpp/src/qpid/sys/solaris/SystemInfo.cpp b/qpid/cpp/src/qpid/sys/solaris/SystemInfo.cpp new file mode 100755 index 0000000000..765e5a7eb0 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/solaris/SystemInfo.cpp @@ -0,0 +1,124 @@ +/* + * 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/SystemInfo.h" + +#define BSD_COMP +#include <sys/ioctl.h> +#include <netdb.h> +#undef BDS_COMP + + +#include <unistd.h> +#include <net/if.h> +#include <sys/types.h> +#include <sys/utsname.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdio.h> +#include <errno.h> +#include <limits.h> +#include <procfs.h> +#include <fcntl.h> +#include <sys/types.h> + +using namespace std; + +namespace qpid { +namespace sys { + +long SystemInfo::concurrency() { + return sysconf(_SC_NPROCESSORS_ONLN); +} + +bool SystemInfo::getLocalHostname(Address &address) { + char name[MAXHOSTNAMELEN]; + if (::gethostname(name, sizeof(name)) != 0) + return false; + address.host = name; + return true; +} + +static const string LOCALHOST("127.0.0.1"); +static const string TCP("tcp"); + +void SystemInfo::getLocalIpAddresses(uint16_t port, + std::vector<Address> &addrList) { + int s = socket(PF_INET, SOCK_STREAM, 0); + for (int i=1;;i++) { + struct lifreq ifr; + ifr.lifr_index = i; + if (::ioctl(s, SIOCGIFADDR, &ifr) < 0) { + break; + } + struct sockaddr_in *sin = (struct sockaddr_in *) &ifr.lifr_addr; + std::string addr(inet_ntoa(sin->sin_addr)); + if (addr != LOCALHOST) + addrList.push_back(Address(TCP, addr, port)); + } + if (addrList.empty()) { + addrList.push_back(Address(TCP, LOCALHOST, port)); + } + close (s); +} + +void SystemInfo::getSystemId(std::string &osName, + std::string &nodeName, + std::string &release, + std::string &version, + std::string &machine) { + struct utsname _uname; + if (uname (&_uname) == 0) { + osName = _uname.sysname; + nodeName = _uname.nodename; + release = _uname.release; + version = _uname.version; + machine = _uname.machine; + } +} + +uint32_t SystemInfo::getProcessId() +{ + return (uint32_t) ::getpid(); +} + +uint32_t SystemInfo::getParentProcessId() +{ + return (uint32_t) ::getppid(); +} + +string SystemInfo::getProcessName() +{ + psinfo processInfo; + char procfile[PATH_MAX]; + int fd; + string value; + + snprintf(procfile, PATH_MAX, "/proc/%d/psinfo", getProcessId()); + if ((fd = open(procfile, O_RDONLY)) >= 0) { + if (read(fd, (void *) &processInfo, sizeof(processInfo)) == sizeof(processInfo)) { + value = processInfo.pr_fname; + } + } + return value; +} + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/ssl/SslHandler.cpp b/qpid/cpp/src/qpid/sys/ssl/SslHandler.cpp new file mode 100644 index 0000000000..5516d72065 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/ssl/SslHandler.cpp @@ -0,0 +1,195 @@ +/* + * + * 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/ssl/SslHandler.h" + +#include "qpid/sys/ssl/SslIo.h" +#include "qpid/sys/ssl/SslSocket.h" +#include "qpid/framing/AMQP_HighestVersion.h" +#include "qpid/framing/ProtocolInitiation.h" +#include "qpid/log/Statement.h" + +#include <boost/bind.hpp> + +namespace qpid { +namespace sys { +namespace ssl { + + +// Buffer definition +struct Buff : public SslIO::BufferBase { + Buff() : + SslIO::BufferBase(new char[65536], 65536) + {} + ~Buff() + { delete [] bytes;} +}; + +SslHandler::SslHandler(std::string id, ConnectionCodec::Factory* f, bool _nodict) : + identifier(id), + aio(0), + factory(f), + codec(0), + readError(false), + isClient(false), + nodict(_nodict) +{} + +SslHandler::~SslHandler() { + if (codec) + codec->closed(); + delete codec; +} + +void SslHandler::init(SslIO* a, int numBuffs) { + aio = a; + + // Give connection some buffers to use + for (int i = 0; i < numBuffs; i++) { + aio->queueReadBuffer(new Buff); + } +} + +void SslHandler::write(const framing::ProtocolInitiation& data) +{ + QPID_LOG(debug, "SENT [" << identifier << "] INIT(" << data << ")"); + SslIO::BufferBase* buff = aio->getQueuedBuffer(); + if (!buff) + buff = new Buff; + framing::Buffer out(buff->bytes, buff->byteCount); + data.encode(out); + buff->dataCount = data.encodedSize(); + aio->queueWrite(buff); +} + +void SslHandler::abort() { + // TODO: can't implement currently as underlying functionality not implemented + // aio->requestCallback(boost::bind(&SslHandler::eof, this, _1)); +} +void SslHandler::activateOutput() { + aio->notifyPendingWrite(); +} + +void SslHandler::giveReadCredit(int32_t) { + // FIXME aconway 2008-12-05: not yet implemented. +} + +// Input side +void SslHandler::readbuff(SslIO& , SslIO::BufferBase* buff) { + if (readError) { + return; + } + size_t decoded = 0; + if (codec) { // Already initiated + try { + decoded = codec->decode(buff->bytes+buff->dataStart, buff->dataCount); + }catch(const std::exception& e){ + QPID_LOG(error, e.what()); + readError = true; + aio->queueWriteClose(); + } + }else{ + framing::Buffer in(buff->bytes+buff->dataStart, buff->dataCount); + framing::ProtocolInitiation protocolInit; + if (protocolInit.decode(in)) { + decoded = in.getPosition(); + QPID_LOG(debug, "RECV [" << identifier << "] INIT(" << protocolInit << ")"); + try { + codec = factory->create(protocolInit.getVersion(), *this, identifier, getSecuritySettings(aio)); + if (!codec) { + //TODO: may still want to revise this... + //send valid version header & close connection. + write(framing::ProtocolInitiation(framing::highestProtocolVersion)); + readError = true; + aio->queueWriteClose(); + } + } catch (const std::exception& e) { + QPID_LOG(error, e.what()); + readError = true; + aio->queueWriteClose(); + } + } + } + // TODO: unreading needs to go away, and when we can cope + // with multiple sub-buffers in the general buffer scheme, it will + if (decoded != size_t(buff->dataCount)) { + // Adjust buffer for used bytes and then "unread them" + buff->dataStart += decoded; + buff->dataCount -= decoded; + aio->unread(buff); + } else { + // Give whole buffer back to aio subsystem + aio->queueReadBuffer(buff); + } +} + +void SslHandler::eof(SslIO&) { + QPID_LOG(debug, "DISCONNECTED [" << identifier << "]"); + if (codec) codec->closed(); + aio->queueWriteClose(); +} + +void SslHandler::closedSocket(SslIO&, const SslSocket& s) { + // If we closed with data still to send log a warning + if (!aio->writeQueueEmpty()) { + QPID_LOG(warning, "CLOSING [" << identifier << "] unsent data (probably due to client disconnect)"); + } + delete &s; + aio->queueForDeletion(); + delete this; +} + +void SslHandler::disconnect(SslIO& a) { + // treat the same as eof + eof(a); +} + +// Notifications +void SslHandler::nobuffs(SslIO&) { +} + +void SslHandler::idle(SslIO&){ + if (isClient && codec == 0) { + codec = factory->create(*this, identifier, getSecuritySettings(aio)); + write(framing::ProtocolInitiation(codec->getVersion())); + return; + } + if (codec == 0) return; + if (codec->canEncode()) { + // Try and get a queued buffer if not then construct new one + SslIO::BufferBase* buff = aio->getQueuedBuffer(); + if (!buff) buff = new Buff; + size_t encoded=codec->encode(buff->bytes, buff->byteCount); + buff->dataCount = encoded; + aio->queueWrite(buff); + } + if (codec->isClosed()) + aio->queueWriteClose(); +} + +SecuritySettings SslHandler::getSecuritySettings(SslIO* aio) +{ + SecuritySettings settings = aio->getSecuritySettings(); + settings.nodict = nodict; + return settings; +} + + +}}} // namespace qpid::sys::ssl diff --git a/qpid/cpp/src/qpid/sys/ssl/SslHandler.h b/qpid/cpp/src/qpid/sys/ssl/SslHandler.h new file mode 100644 index 0000000000..400fa317fd --- /dev/null +++ b/qpid/cpp/src/qpid/sys/ssl/SslHandler.h @@ -0,0 +1,78 @@ +#ifndef QPID_SYS_SSL_SSLHANDLER_H +#define QPID_SYS_SSL_SSLHANDLER_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/ConnectionCodec.h" +#include "qpid/sys/OutputControl.h" + +namespace qpid { + +namespace framing { + class ProtocolInitiation; +} + +namespace sys { +namespace ssl { + +class SslIO; +struct SslIOBufferBase; +class SslSocket; + +class SslHandler : public OutputControl { + std::string identifier; + SslIO* aio; + ConnectionCodec::Factory* factory; + ConnectionCodec* codec; + bool readError; + bool isClient; + bool nodict; + + void write(const framing::ProtocolInitiation&); + qpid::sys::SecuritySettings getSecuritySettings(SslIO* aio); + + public: + SslHandler(std::string id, ConnectionCodec::Factory* f, bool nodict); + ~SslHandler(); + void init(SslIO* a, int numBuffs); + + void setClient() { isClient = true; } + + // Output side + void abort(); + void activateOutput(); + void giveReadCredit(int32_t); + + // Input side + void readbuff(SslIO& aio, SslIOBufferBase* buff); + void eof(SslIO& aio); + void disconnect(SslIO& aio); + + // Notifications + void nobuffs(SslIO& aio); + void idle(SslIO& aio); + void closedSocket(SslIO& aio, const SslSocket& s); +}; + +}}} // namespace qpid::sys::ssl + +#endif /*!QPID_SYS_SSL_SSLHANDLER_H*/ diff --git a/qpid/cpp/src/qpid/sys/ssl/SslIo.cpp b/qpid/cpp/src/qpid/sys/ssl/SslIo.cpp new file mode 100644 index 0000000000..734ebb483a --- /dev/null +++ b/qpid/cpp/src/qpid/sys/ssl/SslIo.cpp @@ -0,0 +1,447 @@ +/* + * + * 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/ssl/SslIo.h" +#include "qpid/sys/ssl/SslSocket.h" +#include "qpid/sys/ssl/check.h" + +#include "qpid/sys/Time.h" +#include "qpid/sys/posix/check.h" +#include "qpid/log/Statement.h" + +// TODO The basic algorithm here is not really POSIX specific and with a bit more abstraction +// could (should) be promoted to be platform portable +#include <unistd.h> +#include <sys/socket.h> +#include <signal.h> +#include <errno.h> +#include <string.h> + +#include <boost/bind.hpp> + +using namespace qpid::sys; +using namespace qpid::sys::ssl; + +namespace { + +/* + * Make *process* not generate SIGPIPE when writing to closed + * pipe/socket (necessary as default action is to terminate process) + */ +void ignoreSigpipe() { + ::signal(SIGPIPE, SIG_IGN); +} + +/* + * We keep per thread state to avoid locking overhead. The assumption is that + * on average all the connections are serviced by all the threads so the state + * recorded in each thread is about the same. If this turns out not to be the + * case we could rebalance the info occasionally. + */ +__thread int threadReadTotal = 0; +__thread int threadMaxRead = 0; +__thread int threadReadCount = 0; +__thread int threadWriteTotal = 0; +__thread int threadWriteCount = 0; +__thread int64_t threadMaxReadTimeNs = 2 * 1000000; // start at 2ms +} + +/* + * Asynch Acceptor + */ + +SslAcceptor::SslAcceptor(const SslSocket& s, Callback callback) : + acceptedCallback(callback), + handle(s, boost::bind(&SslAcceptor::readable, this, _1), 0, 0), + socket(s) { + + s.setNonblocking(); + ignoreSigpipe(); +} + +SslAcceptor::~SslAcceptor() +{ + handle.stopWatch(); +} + +void SslAcceptor::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; + do { + errno = 0; + // TODO: Currently we ignore the peers address, perhaps we should + // log it or use it for connection acceptance. + try { + s = socket.accept(); + if (s) { + acceptedCallback(*s); + } else { + break; + } + } catch (const std::exception& e) { + QPID_LOG(error, "Could not accept socket: " << e.what()); + } + } while (true); + + h.rewatch(); +} + +/* + * Asynch Connector + */ + +SslConnector::SslConnector(const SslSocket& s, + Poller::shared_ptr poller, + std::string hostname, + std::string port, + ConnectedCallback connCb, + FailedCallback failCb) : + DispatchHandle(s, + 0, + boost::bind(&SslConnector::connComplete, this, _1), + boost::bind(&SslConnector::connComplete, this, _1)), + connCallback(connCb), + failCallback(failCb), + socket(s) +{ + //TODO: would be better for connect to be performed on a + //non-blocking socket, but that doesn't work at present so connect + //blocks until complete + try { + socket.connect(hostname, port); + socket.setNonblocking(); + startWatch(poller); + } catch(std::exception& e) { + failure(-1, std::string(e.what())); + } +} + +void SslConnector::connComplete(DispatchHandle& h) +{ + int errCode = socket.getError(); + + h.stopWatch(); + if (errCode == 0) { + connCallback(socket); + DispatchHandle::doDelete(); + } else { + // TODO: This need to be fixed as strerror isn't thread safe + failure(errCode, std::string(::strerror(errCode))); + } +} + +void SslConnector::failure(int errCode, std::string message) +{ + if (failCallback) + failCallback(errCode, message); + + socket.close(); + delete &socket; + + DispatchHandle::doDelete(); +} + +/* + * Asynch reader/writer + */ +SslIO::SslIO(const SslSocket& s, + ReadCallback rCb, EofCallback eofCb, DisconnectCallback disCb, + ClosedCallback cCb, BuffersEmptyCallback eCb, IdleCallback iCb) : + + DispatchHandle(s, + boost::bind(&SslIO::readable, this, _1), + boost::bind(&SslIO::writeable, this, _1), + boost::bind(&SslIO::disconnected, this, _1)), + readCallback(rCb), + eofCallback(eofCb), + disCallback(disCb), + closedCallback(cCb), + emptyCallback(eCb), + idleCallback(iCb), + socket(s), + queuedClose(false), + writePending(false) { + + s.setNonblocking(); +} + +struct deleter +{ + template <typename T> + void operator()(T *ptr){ delete ptr;} +}; + +SslIO::~SslIO() { + std::for_each( bufferQueue.begin(), bufferQueue.end(), deleter()); + std::for_each( writeQueue.begin(), writeQueue.end(), deleter()); +} + +void SslIO::queueForDeletion() { + DispatchHandle::doDelete(); +} + +void SslIO::start(Poller::shared_ptr poller) { + DispatchHandle::startWatch(poller); +} + +void SslIO::queueReadBuffer(BufferBase* buff) { + assert(buff); + buff->dataStart = 0; + buff->dataCount = 0; + bufferQueue.push_back(buff); + DispatchHandle::rewatchRead(); +} + +void SslIO::unread(BufferBase* buff) { + assert(buff); + if (buff->dataStart != 0) { + memmove(buff->bytes, buff->bytes+buff->dataStart, buff->dataCount); + buff->dataStart = 0; + } + bufferQueue.push_front(buff); + DispatchHandle::rewatchRead(); +} + +void SslIO::queueWrite(BufferBase* buff) { + assert(buff); + // If we've already closed the socket then throw the write away + if (queuedClose) { + bufferQueue.push_front(buff); + return; + } else { + writeQueue.push_front(buff); + } + writePending = false; + DispatchHandle::rewatchWrite(); +} + +void SslIO::notifyPendingWrite() { + writePending = true; + DispatchHandle::rewatchWrite(); +} + +void SslIO::queueWriteClose() { + queuedClose = true; + DispatchHandle::rewatchWrite(); +} + +/** Return a queued buffer if there are enough + * to spare + */ +SslIO::BufferBase* SslIO::getQueuedBuffer() { + // Always keep at least one buffer (it might have data that was "unread" in it) + if (bufferQueue.size()<=1) + return 0; + BufferBase* buff = bufferQueue.back(); + assert(buff); + buff->dataStart = 0; + buff->dataCount = 0; + bufferQueue.pop_back(); + return buff; +} + +/* + * We keep on reading as long as we have something to read and a buffer to put + * it in + */ +void SslIO::readable(DispatchHandle& h) { + int readTotal = 0; + AbsTime readStartTime = AbsTime::now(); + do { + // (Try to) get a buffer + if (!bufferQueue.empty()) { + // Read into buffer + BufferBase* buff = bufferQueue.front(); + assert(buff); + bufferQueue.pop_front(); + errno = 0; + int readCount = buff->byteCount-buff->dataCount; + int rc = socket.read(buff->bytes + buff->dataCount, readCount); + if (rc > 0) { + buff->dataCount += rc; + threadReadTotal += rc; + readTotal += rc; + + readCallback(*this, buff); + if (rc != readCount) { + // If we didn't fill the read buffer then time to stop reading + break; + } + + // Stop reading if we've overrun our timeslot + if (Duration(readStartTime, AbsTime::now()) > threadMaxReadTimeNs) { + break; + } + + } else { + // Put buffer back (at front so it doesn't interfere with unread buffers) + bufferQueue.push_front(buff); + assert(buff); + + // Eof or other side has gone away + if (rc == 0 || errno == ECONNRESET) { + eofCallback(*this); + h.unwatchRead(); + break; + } else if (errno == EAGAIN) { + // We have just put a buffer back so we know + // we can carry on watching for reads + break; + } else { + // Report error then just treat as a socket disconnect + QPID_LOG(error, "Error reading socket: " << getErrorString(PR_GetError())); + eofCallback(*this); + h.unwatchRead(); + break; + } + } + } else { + // Something to read but no buffer + if (emptyCallback) { + emptyCallback(*this); + } + // If we still have no buffers we can't do anything more + if (bufferQueue.empty()) { + h.unwatchRead(); + break; + } + + } + } while (true); + + ++threadReadCount; + threadMaxRead = std::max(threadMaxRead, readTotal); + return; +} + +/* + * We carry on writing whilst we have data to write and we can write + */ +void SslIO::writeable(DispatchHandle& h) { + int writeTotal = 0; + do { + // See if we've got something to write + if (!writeQueue.empty()) { + // Write buffer + BufferBase* buff = writeQueue.back(); + writeQueue.pop_back(); + errno = 0; + assert(buff->dataStart+buff->dataCount <= buff->byteCount); + int rc = socket.write(buff->bytes+buff->dataStart, buff->dataCount); + if (rc >= 0) { + threadWriteTotal += rc; + writeTotal += rc; + + // If we didn't write full buffer put rest back + if (rc != buff->dataCount) { + buff->dataStart += rc; + buff->dataCount -= rc; + writeQueue.push_back(buff); + break; + } + + // Recycle the buffer + queueReadBuffer(buff); + + // If we've already written more than the max for reading then stop + // (this is to stop writes dominating reads) + if (writeTotal > threadMaxRead) + break; + } else { + // Put buffer back + writeQueue.push_back(buff); + if (errno == ECONNRESET || errno == EPIPE) { + // Just stop watching for write here - we'll get a + // disconnect callback soon enough + h.unwatchWrite(); + break; + } else if (errno == EAGAIN) { + // We have just put a buffer back so we know + // we can carry on watching for writes + break; + } else { + QPID_LOG(error, "Error writing to socket: " << getErrorString(PR_GetError())); + h.unwatchWrite(); + break; + } + } + } else { + // If we're waiting to close the socket then can do it now as there is nothing to write + if (queuedClose) { + close(h); + break; + } + // Fd is writable, but nothing to write + if (idleCallback) { + writePending = false; + idleCallback(*this); + } + // If we still have no buffers to write we can't do anything more + if (writeQueue.empty() && !writePending && !queuedClose) { + h.unwatchWrite(); + // The following handles the case where writePending is + // set to true after the test above; in this case its + // possible that the unwatchWrite overwrites the + // desired rewatchWrite so we correct that here + if (writePending) + h.rewatchWrite(); + break; + } + } + } while (true); + + ++threadWriteCount; + return; +} + +void SslIO::disconnected(DispatchHandle& h) { + // If we've already queued close do it instead of disconnected callback + if (queuedClose) { + close(h); + } else if (disCallback) { + disCallback(*this); + h.unwatch(); + } +} + +/* + * Close the socket and callback to say we've done it + */ +void SslIO::close(DispatchHandle& h) { + h.stopWatch(); + socket.close(); + if (closedCallback) { + closedCallback(*this, socket); + } +} + +SecuritySettings SslIO::getSecuritySettings() { + SecuritySettings settings; + settings.ssf = socket.getKeyLen(); + settings.authid = socket.getClientAuthId(); + return settings; +} diff --git a/qpid/cpp/src/qpid/sys/ssl/SslIo.h b/qpid/cpp/src/qpid/sys/ssl/SslIo.h new file mode 100644 index 0000000000..8785852c24 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/ssl/SslIo.h @@ -0,0 +1,172 @@ +#ifndef _sys_ssl_SslIO +#define _sys_ssl_SslIO +/* + * + * 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/DispatchHandle.h" +#include "qpid/sys/SecuritySettings.h" + +#include <boost/function.hpp> +#include <deque> + +namespace qpid { +namespace sys { +namespace ssl { + +class SslSocket; + +/* + * Asynchronous ssl acceptor: accepts connections then does a callback + * with the accepted fd + */ +class SslAcceptor { +public: + typedef boost::function1<void, const SslSocket&> Callback; + +private: + Callback acceptedCallback; + qpid::sys::DispatchHandle handle; + const SslSocket& socket; + +public: + SslAcceptor(const SslSocket& s, Callback callback); + ~SslAcceptor(); + void start(qpid::sys::Poller::shared_ptr poller); + +private: + void readable(qpid::sys::DispatchHandle& handle); +}; + +/* + * Asynchronous ssl connector: starts the process of initiating a + * connection and invokes a callback when completed or failed. + */ +class SslConnector : private qpid::sys::DispatchHandle { +public: + typedef boost::function1<void, const SslSocket&> ConnectedCallback; + typedef boost::function2<void, int, std::string> FailedCallback; + +private: + ConnectedCallback connCallback; + FailedCallback failCallback; + const SslSocket& socket; + +public: + SslConnector(const SslSocket& socket, + Poller::shared_ptr poller, + std::string hostname, + std::string port, + ConnectedCallback connCb, + FailedCallback failCb = 0); + +private: + void connComplete(DispatchHandle& handle); + void failure(int, std::string); +}; + +struct SslIOBufferBase { + char* const bytes; + const int32_t byteCount; + int32_t dataStart; + int32_t dataCount; + + SslIOBufferBase(char* const b, const int32_t s) : + bytes(b), + byteCount(s), + dataStart(0), + dataCount(0) + {} + + virtual ~SslIOBufferBase() + {} +}; + +/* + * Asychronous reader/writer: + * Reader accepts buffers to read into; reads into the provided buffers + * and then does a callback with the buffer and amount read. Optionally it can callback + * when there is something to read but no buffer to read it into. + * + * Writer accepts a buffer and queues it for writing; can also be given + * a callback for when writing is "idle" (ie fd is writable, but nothing to write) + * + * The class is implemented in terms of DispatchHandle to allow it to be deleted by deleting + * the contained DispatchHandle + */ +class SslIO : private qpid::sys::DispatchHandle { +public: + typedef SslIOBufferBase BufferBase; + + typedef boost::function2<void, SslIO&, BufferBase*> ReadCallback; + typedef boost::function1<void, SslIO&> EofCallback; + typedef boost::function1<void, SslIO&> DisconnectCallback; + typedef boost::function2<void, SslIO&, const SslSocket&> ClosedCallback; + typedef boost::function1<void, SslIO&> BuffersEmptyCallback; + typedef boost::function1<void, SslIO&> IdleCallback; + + +private: + ReadCallback readCallback; + EofCallback eofCallback; + DisconnectCallback disCallback; + ClosedCallback closedCallback; + BuffersEmptyCallback emptyCallback; + IdleCallback idleCallback; + const SslSocket& socket; + std::deque<BufferBase*> bufferQueue; + std::deque<BufferBase*> writeQueue; + bool queuedClose; + /** + * This flag is used to detect and handle concurrency between + * calls to notifyPendingWrite() (which can be made from any thread) and + * the execution of the writeable() method (which is always on the + * thread processing this handle. + */ + volatile bool writePending; + +public: + SslIO(const SslSocket& s, + ReadCallback rCb, EofCallback eofCb, DisconnectCallback disCb, + ClosedCallback cCb = 0, BuffersEmptyCallback eCb = 0, IdleCallback iCb = 0); + void queueForDeletion(); + + void start(qpid::sys::Poller::shared_ptr poller); + void queueReadBuffer(BufferBase* buff); + void unread(BufferBase* buff); + void queueWrite(BufferBase* buff); + void notifyPendingWrite(); + void queueWriteClose(); + bool writeQueueEmpty() { return writeQueue.empty(); } + BufferBase* getQueuedBuffer(); + + qpid::sys::SecuritySettings getSecuritySettings(); + +private: + ~SslIO(); + void readable(qpid::sys::DispatchHandle& handle); + void writeable(qpid::sys::DispatchHandle& handle); + void disconnected(qpid::sys::DispatchHandle& handle); + void close(qpid::sys::DispatchHandle& handle); +}; + +}}} + +#endif // _sys_ssl_SslIO diff --git a/qpid/cpp/src/qpid/sys/ssl/SslSocket.cpp b/qpid/cpp/src/qpid/sys/ssl/SslSocket.cpp new file mode 100644 index 0000000000..f7483a220c --- /dev/null +++ b/qpid/cpp/src/qpid/sys/ssl/SslSocket.cpp @@ -0,0 +1,360 @@ +/* + * + * 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/ssl/SslSocket.h" +#include "qpid/sys/ssl/check.h" +#include "qpid/sys/ssl/util.h" +#include "qpid/Exception.h" +#include "qpid/sys/posix/check.h" +#include "qpid/sys/posix/PrivatePosix.h" + +#include <fcntl.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/errno.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <netdb.h> +#include <cstdlib> +#include <string.h> +#include <iostream> + +#include <private/pprio.h> +#include <nss.h> +#include <pk11pub.h> +#include <ssl.h> +#include <key.h> + +#include <boost/format.hpp> + +namespace qpid { +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 + ::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]; + if (int rc=::getnameinfo((::sockaddr*)&name, namelen, 0, 0, + servName, sizeof(servName), + NI_NUMERICHOST | NI_NUMERICSERV) != 0) + throw QPID_POSIX_ERROR(rc); + return servName; +} + +const std::string DOMAIN_SEPARATOR("@"); +const std::string DC_SEPARATOR("."); +const std::string DC("DC"); +const std::string DN_DELIMS(" ,="); + +std::string getDomainFromSubject(std::string subject) +{ + std::string::size_type last = subject.find_first_not_of(DN_DELIMS, 0); + std::string::size_type i = subject.find_first_of(DN_DELIMS, last); + + std::string domain; + bool nextTokenIsDC = false; + while (std::string::npos != i || std::string::npos != last) + { + std::string token = subject.substr(last, i - last); + if (nextTokenIsDC) { + if (domain.size()) domain += DC_SEPARATOR; + domain += token; + nextTokenIsDC = false; + } else if (token == DC) { + nextTokenIsDC = true; + } + last = subject.find_first_not_of(DN_DELIMS, i); + i = subject.find_first_of(DN_DELIMS, last); + } + return domain; +} + +} + +SslSocket::SslSocket() : IOHandle(new IOHandlePrivate()), socket(0), prototype(0) +{ + impl->fd = ::socket (PF_INET, SOCK_STREAM, 0); + if (impl->fd < 0) throw QPID_POSIX_ERROR(errno); + socket = SSL_ImportFD(0, PR_ImportTCPSocket(impl->fd)); +} + +/** + * This form of the constructor is used with the server-side sockets + * 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) +{ + socket = SSL_ImportFD(model, PR_ImportTCPSocket(impl->fd)); + NSS_CHECK(SSL_ResetHandshake(socket, true)); +} + +void SslSocket::setNonblocking() const +{ + PRSocketOptionData option; + option.option = PR_SockOpt_Nonblocking; + option.value.non_blocking = true; + PR_SetSocketOption(socket, &option); +} + +void SslSocket::connect(const std::string& host, const std::string& port) const +{ + std::stringstream namestream; + namestream << host << ":" << port; + connectname = namestream.str(); + + void* arg; + // Use the connection's cert-name if it has one; else use global cert-name + if (certname != "") { + arg = const_cast<char*>(certname.c_str()); + } else if (SslOptions::global.certName.empty()) { + arg = 0; + } else { + arg = const_cast<char*>(SslOptions::global.certName.c_str()); + } + NSS_CHECK(SSL_GetClientAuthDataHook(socket, NSS_GetClientAuthData, arg)); + NSS_CHECK(SSL_SetURL(socket, host.data())); + + char hostBuffer[PR_NETDB_BUF_SIZE]; + PRHostEnt hostEntry; + PR_CHECK(PR_GetHostByName(host.data(), hostBuffer, PR_NETDB_BUF_SIZE, &hostEntry)); + PRNetAddr 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) { + throw Exception(QPID_MSG("Could not resolve address for host.")); + } + PR_CHECK(PR_Connect(socket, &address, PR_INTERVAL_NO_TIMEOUT)); + NSS_CHECK(SSL_ForceHandshake(socket)); +} + +void SslSocket::close() const +{ + if (impl->fd > 0) { + PR_Close(socket); + impl->fd = -1; + } +} + +int SslSocket::listen(uint16_t port, int backlog, const std::string& certName, bool clientAuth) const +{ + //configure prototype socket: + prototype = SSL_ImportFD(0, PR_NewTCPSocket()); + if (clientAuth) { + NSS_CHECK(SSL_OptionSet(prototype, SSL_REQUEST_CERTIFICATE, PR_TRUE)); + NSS_CHECK(SSL_OptionSet(prototype, SSL_REQUIRE_CERTIFICATE, PR_TRUE)); + } + + //get certificate and key (is this the correct way?) + CERTCertificate *cert = PK11_FindCertFromNickname(const_cast<char*>(certName.c_str()), 0); + if (!cert) throw Exception(QPID_MSG("Failed to load certificate '" << certName << "'")); + SECKEYPrivateKey *key = PK11_FindKeyByAnyCert(cert, 0); + if (!key) throw Exception(QPID_MSG("Failed to retrieve private key from certificate")); + NSS_CHECK(SSL_ConfigSecureServer(prototype, cert, key, NSS_FindCertKEAType(cert))); + SECKEY_DestroyPrivateKey(key); + CERT_DestroyCertificate(cert); + + //bind and listen + const int& socket = impl->fd; + int yes=1; + QPID_POSIX_CHECK(setsockopt(socket,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(yes))); + struct sockaddr_in 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)) < 0) + throw Exception(QPID_MSG("Can't bind to port " << port << ": " << strError(errno))); + if (::listen(socket, backlog) < 0) + throw Exception(QPID_MSG("Can't listen on port " << port << ": " << strError(errno))); + + socklen_t namelen = sizeof(name); + if (::getsockname(socket, (struct sockaddr*)&name, &namelen) < 0) + throw QPID_POSIX_ERROR(errno); + + return ntohs(name.sin_port); +} + +SslSocket* SslSocket::accept() const +{ + int afd = ::accept(impl->fd, 0, 0); + if ( afd >= 0) { + return new SslSocket(new IOHandlePrivate(afd), prototype); + } else if (errno == EAGAIN) { + return 0; + } else { + throw QPID_POSIX_ERROR(errno); + } +} + +int SslSocket::read(void *buf, size_t count) const +{ + return PR_Read(socket, buf, count); +} + +int SslSocket::write(const void *buf, size_t count) const +{ + return PR_Write(socket, buf, count); +} + +std::string SslSocket::getSockname() const +{ + return getName(impl->fd, true); +} + +std::string SslSocket::getPeername() const +{ + return getName(impl->fd, false); +} + +std::string SslSocket::getPeerAddress() const +{ + if (!connectname.empty()) + return connectname; + return getName(impl->fd, false, true); +} + +std::string SslSocket::getLocalAddress() const +{ + return getName(impl->fd, true, true); +} + +uint16_t SslSocket::getLocalPort() const +{ + return std::atoi(getService(impl->fd, true).c_str()); +} + +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) { + PRSocketOptionData option; + option.option = PR_SockOpt_NoDelay; + option.value.no_delay = true; + PR_SetSocketOption(socket, &option); + } +} + +void SslSocket::setCertName(const std::string& name) +{ + certname = name; +} + + +/** get the bit length of the current cipher's key */ +int SslSocket::getKeyLen() const +{ + int enabled = 0; + int keySize = 0; + SECStatus rc; + + rc = SSL_SecurityStatus( socket, + &enabled, + NULL, + NULL, + &keySize, + NULL, NULL ); + if (rc == SECSuccess && enabled) { + return keySize; + } + return 0; +} + +std::string SslSocket::getClientAuthId() const +{ + std::string authId; + CERTCertificate* cert = SSL_PeerCertificate(socket); + if (cert) { + authId = CERT_GetCommonName(&(cert->subject)); + /* + * The NSS function CERT_GetDomainComponentName only returns + * the last component of the domain name, so we have to parse + * the subject manually to extract the full domain. + */ + std::string domain = getDomainFromSubject(cert->subjectName); + if (!domain.empty()) { + authId += DOMAIN_SEPARATOR; + authId += domain; + } + CERT_DestroyCertificate(cert); + } + return authId; +} + +}}} // namespace qpid::sys::ssl diff --git a/qpid/cpp/src/qpid/sys/ssl/SslSocket.h b/qpid/cpp/src/qpid/sys/ssl/SslSocket.h new file mode 100644 index 0000000000..993859495b --- /dev/null +++ b/qpid/cpp/src/qpid/sys/ssl/SslSocket.h @@ -0,0 +1,132 @@ +#ifndef _sys_ssl_Socket_h +#define _sys_ssl_Socket_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" +#include <nspr.h> + +#include <string> + +struct sockaddr; + +namespace qpid { +namespace sys { + +class Duration; + +namespace ssl { + +class SslSocket : public qpid::sys::IOHandle +{ +public: + /** Create a socket wrapper for descriptor. */ + SslSocket(); + + /** Set socket non blocking */ + void setNonblocking() const; + + /** Set tcp-nodelay */ + void setTcpNoDelay(bool nodelay) const; + + /** Set SSL cert-name. Allows the cert-name to be set per + * connection, overriding global cert-name settings from + * NSSInit().*/ + void setCertName(const std::string& certName); + + void connect(const std::string& host, const std::string& port) const; + + void close() const; + + /** Bind to a port and start listening. + *@param port 0 means choose an available port. + *@param backlog maximum number of pending connections. + *@param certName name of certificate to use to identify the server + *@return The bound port. + */ + int listen(uint16_t port = 0, int backlog = 10, const std::string& certName = "localhost.localdomain", bool clientAuth = false) const; + + /** + * Accept a connection from a socket that is already listening + * and has an incoming connection + */ + SslSocket* accept() const; + + // TODO The following are raw operations, maybe they need better wrapping? + 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: + mutable std::string connectname; + mutable PRFileDesc* socket; + std::string certname; + + /** + * 'model' socket, with configuration to use when importing + * accepted sockets for use as ssl sockets. Set on listen(), used + * in accept to pass through to newly created socket instances. + */ + mutable PRFileDesc* prototype; + + SslSocket(IOHandlePrivate* ioph, PRFileDesc* model); +}; + +}}} +#endif /*!_sys_ssl_Socket_h*/ diff --git a/qpid/cpp/src/qpid/sys/ssl/check.cpp b/qpid/cpp/src/qpid/sys/ssl/check.cpp new file mode 100644 index 0000000000..72a2e265bd --- /dev/null +++ b/qpid/cpp/src/qpid/sys/ssl/check.cpp @@ -0,0 +1,85 @@ +/* + * + * 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/ssl/check.h" +#include <secerr.h> +#include <sslerr.h> +#include <boost/format.hpp> + +using boost::format; +using boost::str; + +namespace qpid { +namespace sys { +namespace ssl { + +ErrorString::ErrorString() : code(PR_GetError()), buffer(new char[PR_GetErrorTextLength()]), used(PR_GetErrorText(buffer)) {} + +ErrorString::~ErrorString() +{ + delete[] buffer; +} + +std::string ErrorString::getString() const +{ + std::string msg = std::string(buffer, used); + if (!used) { + //seems most of the NSPR/NSS errors don't have text set for + //them, add a few specific ones in here. (TODO: more complete + //list?): + return getErrorString(code); + } else { + return str(format("%1% [%2%]") % msg % code); + } +} + +std::string getErrorString(int code) +{ + std::string msg; + switch (code) { + case SSL_ERROR_EXPORT_ONLY_SERVER: msg = "Unable to communicate securely. Peer does not support high-grade encryption."; break; + case SSL_ERROR_US_ONLY_SERVER: msg = "Unable to communicate securely. Peer requires high-grade encryption which is not supported."; break; + case SSL_ERROR_NO_CYPHER_OVERLAP: msg = "Cannot communicate securely with peer: no common encryption algorithm(s)."; break; + case SSL_ERROR_NO_CERTIFICATE: msg = "Unable to find the certificate or key necessary for authentication."; break; + case SSL_ERROR_BAD_CERTIFICATE: msg = "Unable to communicate securely with peer: peers's certificate was rejected."; break; + case SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE: msg = "Unsupported certificate type."; break; + case SSL_ERROR_WRONG_CERTIFICATE: msg = "Client authentication failed: private key in key database does not correspond to public key in certificate database."; break; + case SSL_ERROR_BAD_CERT_DOMAIN: msg = "Unable to communicate securely with peer: requested domain name does not match the server's certificate."; break; + case SSL_ERROR_BAD_CERT_ALERT: msg = "SSL peer cannot verify your certificate."; break; + case SSL_ERROR_REVOKED_CERT_ALERT: msg = "SSL peer rejected your certificate as revoked."; break; + case SSL_ERROR_EXPIRED_CERT_ALERT: msg = "SSL peer rejected your certificate as expired."; break; + + case PR_DIRECTORY_LOOKUP_ERROR: msg = "A directory lookup on a network address has failed"; break; + case PR_CONNECT_RESET_ERROR: msg = "TCP connection reset by peer"; break; + case PR_END_OF_FILE_ERROR: msg = "Encountered end of file"; break; + case SEC_ERROR_EXPIRED_CERTIFICATE: msg = "Peer's certificate has expired"; break; + default: msg = (code < -6000) ? "NSS error" : "NSPR error"; break; + } + return str(format("%1% [%2%]") % msg % code); +} + +std::ostream& operator<<(std::ostream& out, const ErrorString& err) +{ + out << err.getString(); + return out; +} + + +}}} // namespace qpid::sys::ssl diff --git a/qpid/cpp/src/qpid/sys/ssl/check.h b/qpid/cpp/src/qpid/sys/ssl/check.h new file mode 100644 index 0000000000..28d3c74ad0 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/ssl/check.h @@ -0,0 +1,57 @@ +#ifndef QPID_SYS_SSL_CHECK_H +#define QPID_SYS_SSL_CHECK_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/Msg.h" + +#include <iostream> +#include <string> +#include <nspr.h> +#include <nss.h> + +namespace qpid { +namespace sys { +namespace ssl { + +std::string getErrorString(int code); + +class ErrorString +{ + public: + ErrorString(); + ~ErrorString(); + std::string getString() const; + private: + const int code; + char* const buffer; + const size_t used; +}; + +std::ostream& operator<<(std::ostream& out, const ErrorString& err); + +}}} // namespace qpid::sys::ssl + + +#define NSS_CHECK(value) if (value != SECSuccess) { throw Exception(QPID_MSG("Failed: " << qpid::sys::ssl::ErrorString())); } +#define PR_CHECK(value) if (value != PR_SUCCESS) { throw Exception(QPID_MSG("Failed: " << qpid::sys::ssl::ErrorString())); } + +#endif /*!QPID_SYS_SSL_CHECK_H*/ diff --git a/qpid/cpp/src/qpid/sys/ssl/util.cpp b/qpid/cpp/src/qpid/sys/ssl/util.cpp new file mode 100644 index 0000000000..3078e894df --- /dev/null +++ b/qpid/cpp/src/qpid/sys/ssl/util.cpp @@ -0,0 +1,120 @@ +/* + * + * 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/ssl/util.h" +#include "qpid/sys/ssl/check.h" +#include "qpid/Exception.h" +#include "qpid/sys/SystemInfo.h" + +#include <unistd.h> +#include <nspr.h> +#include <nss.h> +#include <pk11pub.h> +#include <ssl.h> + +#include <iostream> +#include <fstream> +#include <boost/filesystem/operations.hpp> +#include <boost/filesystem/path.hpp> + +namespace qpid { +namespace sys { +namespace ssl { + +static const std::string LOCALHOST("127.0.0.1"); + +std::string defaultCertName() +{ + Address address; + if (SystemInfo::getLocalHostname(address)) { + return address.host; + } else { + return LOCALHOST; + } +} + +SslOptions::SslOptions() : qpid::Options("SSL Settings"), + certName(defaultCertName()), + exportPolicy(false) +{ + addOptions() + ("ssl-use-export-policy", optValue(exportPolicy), "Use NSS export policy") + ("ssl-cert-password-file", optValue(certPasswordFile, "PATH"), "File containing password to use for accessing certificate database") + ("ssl-cert-db", optValue(certDbPath, "PATH"), "Path to directory containing certificate database") + ("ssl-cert-name", optValue(certName, "NAME"), "Name of the certificate to use"); +} + +SslOptions& SslOptions::operator=(const SslOptions& o) +{ + certDbPath = o.certDbPath; + certName = o.certName; + certPasswordFile = o.certPasswordFile; + exportPolicy = o.exportPolicy; + return *this; +} + +char* promptForPassword(PK11SlotInfo*, PRBool retry, void*) +{ + if (retry) return 0; + //TODO: something else? + return PL_strdup(getpass("Please enter the password for accessing the certificate database:")); +} + +SslOptions SslOptions::global; + +char* readPasswordFromFile(PK11SlotInfo*, PRBool retry, void*) +{ + const std::string& passwordFile = SslOptions::global.certPasswordFile; + if (retry || passwordFile.empty() || !boost::filesystem::exists(passwordFile)) { + return 0; + } else { + std::ifstream file(passwordFile.c_str()); + std::string password; + file >> password; + return PL_strdup(password.c_str()); + } +} + +void initNSS(const SslOptions& options, bool server) +{ + SslOptions::global = options; + if (options.certPasswordFile.empty()) { + PK11_SetPasswordFunc(promptForPassword); + } else { + PK11_SetPasswordFunc(readPasswordFromFile); + } + NSS_CHECK(NSS_Init(options.certDbPath.c_str())); + if (options.exportPolicy) { + NSS_CHECK(NSS_SetExportPolicy()); + } else { + NSS_CHECK(NSS_SetDomesticPolicy()); + } + if (server) { + //use defaults for all args, TODO: may want to make this configurable + SSL_ConfigServerSessionIDCache(0, 0, 0, 0); + } +} + +void shutdownNSS() +{ + NSS_Shutdown(); +} + +}}} // namespace qpid::sys::ssl diff --git a/qpid/cpp/src/qpid/sys/ssl/util.h b/qpid/cpp/src/qpid/sys/ssl/util.h new file mode 100644 index 0000000000..f34adab7be --- /dev/null +++ b/qpid/cpp/src/qpid/sys/ssl/util.h @@ -0,0 +1,50 @@ +#ifndef QPID_SYS_SSL_UTIL_H +#define QPID_SYS_SSL_UTIL_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/Options.h" +#include <string> + +namespace qpid { +namespace sys { +namespace ssl { + +struct SslOptions : qpid::Options +{ + static SslOptions global; + + std::string certDbPath; + std::string certName; + std::string certPasswordFile; + bool exportPolicy; + + SslOptions(); + SslOptions& operator=(const SslOptions&); +}; + +void initNSS(const SslOptions& options, bool server = false); +void shutdownNSS(); + +}}} // namespace qpid::sys::ssl + +#endif /*!QPID_SYS_SSL_UTIL_H*/ diff --git a/qpid/cpp/src/qpid/sys/uuid.h b/qpid/cpp/src/qpid/sys/uuid.h new file mode 100644 index 0000000000..804ab34463 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/uuid.h @@ -0,0 +1,28 @@ +#ifndef _sys_uuid_h +#define _sys_uuid_h + +/* + * + * 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. + * + */ + +#ifdef _WIN32 +# include "qpid/sys/windows/uuid.h" +#else +# include <uuid/uuid.h> +#endif /* _WIN32 */ + +#endif /* _sys_uuid_h */ diff --git a/qpid/cpp/src/qpid/sys/windows/AsynchIO.cpp b/qpid/cpp/src/qpid/sys/windows/AsynchIO.cpp new file mode 100644 index 0000000000..8d84fdb7b2 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/windows/AsynchIO.cpp @@ -0,0 +1,755 @@ +/* + * + * 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/windows/AsynchIoResult.h" +#include "qpid/sys/windows/IoHandlePrivate.h" +#include "qpid/sys/AsynchIO.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Socket.h" +#include "qpid/sys/Poller.h" +#include "qpid/sys/Thread.h" +#include "qpid/sys/Time.h" +#include "qpid/log/Statement.h" + +#include "qpid/sys/windows/check.h" +#include "qpid/sys/windows/mingw32_compat.h" + +#include <boost/thread/once.hpp> + +#include <queue> +#include <winsock2.h> +#include <mswsock.h> +#include <windows.h> + +#include <boost/bind.hpp> + +namespace { + + typedef qpid::sys::ScopedLock<qpid::sys::Mutex> QLock; + +/* + * The function pointers for AcceptEx and ConnectEx need to be looked up + * at run time. Make sure this is done only once. + */ +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); + GUID guidAcceptEx = WSAID_ACCEPTEX; + DWORD dwBytes = 0; + WSAIoctl(h, + SIO_GET_EXTENSION_FUNCTION_POINTER, + &guidAcceptEx, + sizeof(guidAcceptEx), + &fnAcceptEx, + sizeof(fnAcceptEx), + &dwBytes, + NULL, + NULL); + closesocket(h); + if (fnAcceptEx == 0) + throw qpid::Exception(QPID_MSG("Failed to look up AcceptEx")); +} + +} + +namespace qpid { +namespace sys { +namespace windows { + +/* + * Asynch Acceptor + * + */ +class AsynchAcceptor : public qpid::sys::AsynchAcceptor { + + friend class AsynchAcceptResult; + +public: + AsynchAcceptor(const Socket& s, AsynchAcceptor::Callback callback); + ~AsynchAcceptor(); + void start(Poller::shared_ptr poller); + +private: + void restart(void); + + AsynchAcceptor::Callback acceptedCallback; + const Socket& socket; +}; + +AsynchAcceptor::AsynchAcceptor(const Socket& s, Callback callback) + : acceptedCallback(callback), + socket(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() +{ + socket.close(); +} + +void AsynchAcceptor::start(Poller::shared_ptr poller) { + PollerHandle ph = PollerHandle(socket); + poller->monitorHandle(ph, Poller::INPUT); + restart (); +} + +void AsynchAcceptor::restart(void) { + DWORD bytesReceived = 0; // Not used, needed for AcceptEx API + AsynchAcceptResult *result = new AsynchAcceptResult(acceptedCallback, + this, + toSocketHandle(socket)); + BOOL status; + 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()); +} + +void AsynchAcceptResult::success(size_t /*bytesTransferred*/) { + ::setsockopt (toSocketHandle(*newSocket), + SOL_SOCKET, + SO_UPDATE_ACCEPT_CONTEXT, + (char*)&listener, + sizeof (listener)); + callback(*(newSocket.release())); + acceptor->restart (); + delete this; +} + +void AsynchAcceptResult::failure(int /*status*/) { + //if (status != WSA_OPERATION_ABORTED) + // Can there be anything else? ; + delete this; +} + +/* + * AsynchConnector does synchronous connects for now... to do asynch the + * IocpPoller will need some extension to register an event handle as a + * CONNECT-type "direction", the connect completion/result will need an + * event handle to associate with the connecting handle. But there's no + * time for that right now... + */ +class AsynchConnector : public qpid::sys::AsynchConnector { +private: + ConnectedCallback connCallback; + FailedCallback failCallback; + const Socket& socket; + const std::string hostname; + const std::string port; + +public: + AsynchConnector(const Socket& socket, + const std::string& hostname, + const std::string& port, + ConnectedCallback connCb, + FailedCallback failCb = 0); + void start(Poller::shared_ptr poller); +}; + +AsynchConnector::AsynchConnector(const Socket& sock, + const std::string& hname, + const std::string& p, + ConnectedCallback connCb, + FailedCallback failCb) : + connCallback(connCb), failCallback(failCb), socket(sock), + hostname(hname), port(p) +{ +} + +void AsynchConnector::start(Poller::shared_ptr) +{ + try { + socket.connect(hostname, port); + socket.setNonblocking(); + connCallback(socket); + } catch(std::exception& e) { + if (failCallback) + failCallback(socket, -1, std::string(e.what())); + socket.close(); + } +} + +} // namespace windows + +AsynchAcceptor* AsynchAcceptor::create(const Socket& s, + Callback callback) +{ + return new windows::AsynchAcceptor(s, callback); +} + +AsynchConnector* qpid::sys::AsynchConnector::create(const Socket& s, + const std::string& hostname, + const std::string& port, + ConnectedCallback connCb, + FailedCallback failCb) +{ + return new windows::AsynchConnector(s, + hostname, + port, + connCb, + failCb); +} + + +/* + * Asynch reader/writer + */ + +namespace windows { + +class AsynchIO : public qpid::sys::AsynchIO { +public: + AsynchIO(const Socket& s, + ReadCallback rCb, + EofCallback eofCb, + DisconnectCallback disCb, + ClosedCallback cCb = 0, + BuffersEmptyCallback eCb = 0, + IdleCallback iCb = 0); + ~AsynchIO(); + + // Methods inherited from qpid::sys::AsynchIO + + /** + * Notify the object is should delete itself as soon as possible. + */ + virtual void queueForDeletion(); + + /// Take any actions needed to prepare for working with the poller. + virtual void start(Poller::shared_ptr poller); + virtual void queueReadBuffer(BufferBase* buff); + virtual void unread(BufferBase* buff); + virtual void queueWrite(BufferBase* buff); + virtual void notifyPendingWrite(); + virtual void queueWriteClose(); + virtual bool writeQueueEmpty(); + virtual void startReading(); + virtual void stopReading(); + virtual void requestCallback(RequestCallback); + + /** + * getQueuedBuffer returns a buffer from the buffer queue, if one is + * available. + * + * @retval Pointer to BufferBase buffer; 0 if none is available. + */ + virtual BufferBase* getQueuedBuffer(); + +private: + ReadCallback readCallback; + EofCallback eofCallback; + DisconnectCallback disCallback; + ClosedCallback closedCallback; + BuffersEmptyCallback emptyCallback; + IdleCallback idleCallback; + const Socket& socket; + Poller::shared_ptr poller; + + std::deque<BufferBase*> bufferQueue; + std::deque<BufferBase*> writeQueue; + /* The MSVC-supplied deque is not thread-safe; keep locks to serialize + * access to the buffer queue and write queue. + */ + Mutex bufferQueueLock; + + // Number of outstanding I/O operations. + volatile LONG opsInProgress; + // Is there a write in progress? + volatile bool writeInProgress; + // Deletion requested, but there are callbacks in progress. + volatile bool queuedDelete; + // Socket close requested, but there are operations in progress. + volatile bool queuedClose; + +private: + // Dispatch events that have completed. + void notifyEof(void); + void notifyDisconnect(void); + void notifyClosed(void); + void notifyBuffersEmpty(void); + void notifyIdle(void); + + /** + * Initiate a write of the specified buffer. There's no callback for + * write completion to the AsynchIO object. + */ + void startWrite(AsynchIO::BufferBase* buff); + + void close(void); + + /** + * readComplete is called when a read request is complete. + * + * @param result Results of the operation. + */ + void readComplete(AsynchReadResult *result); + + /** + * writeComplete is called when a write request is complete. + * + * @param result Results of the operation. + */ + void writeComplete(AsynchWriteResult *result); + + /** + * Queue of completions to run. This queue enforces the requirement + * from upper layers that only one thread at a time is allowed to act + * on any given connection. Once a thread is busy processing a completion + * on this object, other threads that dispatch completions queue the + * completions here for the in-progress thread to handle when done. + * Thus, any threads can dispatch a completion from the IocpPoller, but + * this class ensures that actual processing at the connection level is + * only on one thread at a time. + */ + std::queue<AsynchIoResult *> completionQueue; + volatile bool working; + Mutex completionLock; + + /** + * Called when there's a completion to process. + */ + void completion(AsynchIoResult *result); +}; + +// This is used to encapsulate pure callbacks into a handle +class CallbackHandle : public IOHandle { +public: + CallbackHandle(AsynchIoResult::Completer completeCb, + AsynchIO::RequestCallback reqCb = 0) : + IOHandle(new IOHandlePrivate (INVALID_SOCKET, completeCb, reqCb)) + {} +}; + +AsynchIO::AsynchIO(const Socket& s, + ReadCallback rCb, + EofCallback eofCb, + DisconnectCallback disCb, + ClosedCallback cCb, + BuffersEmptyCallback eCb, + IdleCallback iCb) : + + readCallback(rCb), + eofCallback(eofCb), + disCallback(disCb), + closedCallback(cCb), + emptyCallback(eCb), + idleCallback(iCb), + socket(s), + opsInProgress(0), + writeInProgress(false), + queuedDelete(false), + queuedClose(false), + working(false) { +} + +struct deleter +{ + template <typename T> + void operator()(T *ptr){ delete ptr;} +}; + +AsynchIO::~AsynchIO() { + std::for_each( bufferQueue.begin(), bufferQueue.end(), deleter()); + std::for_each( writeQueue.begin(), writeQueue.end(), deleter()); +} + +void AsynchIO::queueForDeletion() { + queuedDelete = true; + if (opsInProgress > 0) { + QPID_LOG(info, "Delete AsynchIO queued; ops in progress"); + // AsynchIOHandler calls this then deletes itself; don't do any more + // callbacks. + readCallback = 0; + eofCallback = 0; + disCallback = 0; + closedCallback = 0; + emptyCallback = 0; + idleCallback = 0; + } + else { + delete this; + } +} + +void AsynchIO::start(Poller::shared_ptr poller0) { + PollerHandle ph = PollerHandle(socket); + poller = poller0; + poller->monitorHandle(ph, Poller::INPUT); + if (writeQueue.size() > 0) // Already have data queued for write + notifyPendingWrite(); + startReading(); +} + +void AsynchIO::queueReadBuffer(AsynchIO::BufferBase* buff) { + assert(buff); + buff->dataStart = 0; + buff->dataCount = 0; + QLock l(bufferQueueLock); + bufferQueue.push_back(buff); +} + +void AsynchIO::unread(AsynchIO::BufferBase* buff) { + assert(buff); + buff->squish(); + QLock l(bufferQueueLock); + bufferQueue.push_front(buff); +} + +void AsynchIO::queueWrite(AsynchIO::BufferBase* buff) { + assert(buff); + QLock l(bufferQueueLock); + writeQueue.push_back(buff); + if (!writeInProgress) + notifyPendingWrite(); +} + +void AsynchIO::notifyPendingWrite() { + // This method is generally called from a processing thread; transfer + // work on this to an I/O thread. Much of the upper layer code assumes + // that all I/O-related things happen in an I/O thread. + if (poller == 0) // Not really going yet... + return; + + InterlockedIncrement(&opsInProgress); + PollerHandle ph(CallbackHandle(boost::bind(&AsynchIO::completion, this, _1))); + poller->monitorHandle(ph, Poller::OUTPUT); +} + +void AsynchIO::queueWriteClose() { + queuedClose = true; + if (!writeInProgress) + notifyPendingWrite(); +} + +bool AsynchIO::writeQueueEmpty() { + QLock l(bufferQueueLock); + return writeQueue.size() == 0; +} + +/* + * Initiate a read operation. AsynchIO::readComplete() will be + * called when the read is complete and data is available. + */ +void AsynchIO::startReading() { + if (queuedDelete) + return; + + // (Try to) get a buffer; look on the front since there may be an + // "unread" one there with data remaining from last time. + AsynchIO::BufferBase *buff = 0; + { + QLock l(bufferQueueLock); + + if (!bufferQueue.empty()) { + buff = bufferQueue.front(); + assert(buff); + bufferQueue.pop_front(); + } + } + if (buff != 0) { + int readCount = buff->byteCount - buff->dataCount; + AsynchReadResult *result = + new AsynchReadResult(boost::bind(&AsynchIO::completion, this, _1), + buff, + readCount); + DWORD bytesReceived = 0, flags = 0; + InterlockedIncrement(&opsInProgress); + int status = WSARecv(toSocketHandle(socket), + const_cast<LPWSABUF>(result->getWSABUF()), 1, + &bytesReceived, + &flags, + result->overlapped(), + 0); + if (status != 0) { + int error = WSAGetLastError(); + if (error != WSA_IO_PENDING) { + result->failure(error); + result = 0; // result is invalid here + return; + } + } + // On status 0 or WSA_IO_PENDING, completion will handle the rest. + } + else { + notifyBuffersEmpty(); + } + return; +} + +// stopReading was added to prevent a race condition with read-credit on Linux. +// It may or may not be required on windows. +// +// AsynchIOHandler::readbuff() calls stopReading() inside the same +// critical section that protects startReading() in +// AsynchIOHandler::giveReadCredit(). +// +void AsynchIO::stopReading() {} + +// Queue the specified callback for invocation from an I/O thread. +void AsynchIO::requestCallback(RequestCallback callback) { + // This method is generally called from a processing thread; transfer + // work on this to an I/O thread. Much of the upper layer code assumes + // that all I/O-related things happen in an I/O thread. + if (poller == 0) // Not really going yet... + return; + + InterlockedIncrement(&opsInProgress); + PollerHandle ph(CallbackHandle( + boost::bind(&AsynchIO::completion, this, _1), + callback)); + poller->monitorHandle(ph, Poller::INPUT); +} + +/** + * Return a queued buffer if there are enough to spare. + */ +AsynchIO::BufferBase* AsynchIO::getQueuedBuffer() { + QLock l(bufferQueueLock); + // Always keep at least one buffer (it might have data that was + // "unread" in it). + if (bufferQueue.size() <= 1) + return 0; + BufferBase* buff = bufferQueue.back(); + assert(buff); + bufferQueue.pop_back(); + return buff; +} + +void AsynchIO::notifyEof(void) { + if (eofCallback) + eofCallback(*this); +} + +void AsynchIO::notifyDisconnect(void) { + if (disCallback) + disCallback(*this); +} + +void AsynchIO::notifyClosed(void) { + if (closedCallback) + closedCallback(*this, socket); +} + +void AsynchIO::notifyBuffersEmpty(void) { + if (emptyCallback) + emptyCallback(*this); +} + +void AsynchIO::notifyIdle(void) { + if (idleCallback) + idleCallback(*this); +} + +/* + * Asynch reader/writer using overlapped I/O + */ + +void AsynchIO::startWrite(AsynchIO::BufferBase* buff) { + writeInProgress = true; + InterlockedIncrement(&opsInProgress); + AsynchWriteResult *result = + new AsynchWriteResult(boost::bind(&AsynchIO::completion, this, _1), + buff, + buff->dataCount); + DWORD bytesSent = 0; + int status = WSASend(toSocketHandle(socket), + const_cast<LPWSABUF>(result->getWSABUF()), 1, + &bytesSent, + 0, + result->overlapped(), + 0); + if (status != 0) { + int error = WSAGetLastError(); + if (error != WSA_IO_PENDING) { + result->failure(error); // Also decrements in-progress count + result = 0; // result is invalid here + return; + } + } + // On status 0 or WSA_IO_PENDING, completion will handle the rest. + return; +} + +/* + * Close the socket and callback to say we've done it + */ +void AsynchIO::close(void) { + socket.close(); + notifyClosed(); +} + +void AsynchIO::readComplete(AsynchReadResult *result) { + int status = result->getStatus(); + size_t bytes = result->getTransferred(); + if (status == 0 && bytes > 0) { + bool restartRead = true; // May not if receiver doesn't want more + if (readCallback) + readCallback(*this, result->getBuff()); + if (restartRead) + startReading(); + } + else { + // No data read, so put the buffer back. It may be partially filled, + // so "unread" it back to the front of the queue. + unread(result->getBuff()); + notifyEof(); + if (status != 0) + { + notifyDisconnect(); + } + } +} + +/* + * NOTE - this completion is called for completed writes and also when + * a write is desired. The difference is in the buff - if a write is desired + * the buff is 0. + */ +void AsynchIO::writeComplete(AsynchWriteResult *result) { + int status = result->getStatus(); + size_t bytes = result->getTransferred(); + AsynchIO::BufferBase *buff = result->getBuff(); + if (buff != 0) { + writeInProgress = false; + if (status == 0 && bytes > 0) { + if (bytes < result->getRequested()) // Still more to go; resubmit + startWrite(buff); + else + queueReadBuffer(buff); // All done; back to the pool + } + else { + // An error... if it's a connection close, ignore it - it will be + // noticed and handled on a read completion any moment now. + // What to do with real error??? Save the Buffer? + } + } + + // If there are no writes outstanding, check for more writes to initiate + // (either queued or via idle). The opsInProgress count is handled in + // completion() + if (!writeInProgress) { + bool writing = false; + { + QLock l(bufferQueueLock); + if (writeQueue.size() > 0) { + buff = writeQueue.front(); + assert(buff); + writeQueue.pop_front(); + startWrite(buff); + writing = true; + } + } + if (!writing && !queuedClose) { + notifyIdle(); + } + } + return; +} + +void AsynchIO::completion(AsynchIoResult *result) { + { + ScopedLock<Mutex> l(completionLock); + if (working) { + completionQueue.push(result); + return; + } + + // First thread in with something to do; note we're working then keep + // handling completions. + working = true; + while (result != 0) { + // New scope to unlock temporarily. + { + ScopedUnlock<Mutex> ul(completionLock); + AsynchReadResult *r = dynamic_cast<AsynchReadResult*>(result); + if (r != 0) + readComplete(r); + else { + AsynchWriteResult *w = + dynamic_cast<AsynchWriteResult*>(result); + if (w != 0) + writeComplete(w); + else { + AsynchCallbackRequest *req = + dynamic_cast<AsynchCallbackRequest*>(result); + req->reqCallback(*this); + } + } + delete result; + result = 0; + InterlockedDecrement(&opsInProgress); + } + // Lock is held again. + if (completionQueue.empty()) + continue; + result = completionQueue.front(); + completionQueue.pop(); + } + working = false; + } + // Lock released; ok to close if ops are done and close requested. + // Layer above will call back to queueForDeletion() if it hasn't + // already been done. If it already has, go ahead and delete. + if (opsInProgress == 0) { + if (queuedClose) + // close() may cause a delete; don't trust 'this' on return + close(); + else if (queuedDelete) + delete this; + } +} + +} // namespace windows + +AsynchIO* qpid::sys::AsynchIO::create(const Socket& s, + AsynchIO::ReadCallback rCb, + AsynchIO::EofCallback eofCb, + AsynchIO::DisconnectCallback disCb, + AsynchIO::ClosedCallback cCb, + AsynchIO::BuffersEmptyCallback eCb, + AsynchIO::IdleCallback iCb) +{ + return new qpid::sys::windows::AsynchIO(s, rCb, eofCb, disCb, cCb, eCb, iCb); +} + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/windows/AsynchIoResult.h b/qpid/cpp/src/qpid/sys/windows/AsynchIoResult.h new file mode 100755 index 0000000000..b11324918b --- /dev/null +++ b/qpid/cpp/src/qpid/sys/windows/AsynchIoResult.h @@ -0,0 +1,204 @@ +#ifndef _windows_asynchIoResult_h +#define _windows_asynchIoResult_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/AsynchIO.h" +#include "qpid/sys/Socket.h" +#include <memory.h> +#include <winsock2.h> +#include <ws2tcpip.h> + +namespace qpid { +namespace sys { +namespace windows { + +/* + * AsynchIoResult defines the class that receives the result of an + * asynchronous I/O operation, either send/recv or accept/connect. + * + * Operation factories should set one of these up before beginning the + * operation. Poller knows how to dispatch completion to this class. + * This class must be subclassed for needed operations; this class provides + * an interface only and cannot be instantiated. + * + * This class is tied to Windows; it inherits from OVERLAPPED so that the + * IocpPoller can cast OVERLAPPED pointers back to AsynchIoResult and call + * the completion handler. + */ +class AsynchResult : private OVERLAPPED { +public: + LPOVERLAPPED overlapped(void) { return this; } + static AsynchResult* from_overlapped(LPOVERLAPPED ol) { + return static_cast<AsynchResult*>(ol); + } + virtual void success (size_t bytesTransferred) { + bytes = bytesTransferred; + status = 0; + complete(); + } + virtual void failure (int error) { + bytes = 0; + status = error; + complete(); + } + size_t getTransferred(void) const { return bytes; } + int getStatus(void) const { return status; } + +protected: + AsynchResult() : bytes(0), status(0) + { memset(overlapped(), 0, sizeof(OVERLAPPED)); } + ~AsynchResult() {} + virtual void complete(void) = 0; + + size_t bytes; + int status; +}; + +class AsynchAcceptor; + +class AsynchAcceptResult : public AsynchResult { + + friend class AsynchAcceptor; + +public: + AsynchAcceptResult(qpid::sys::AsynchAcceptor::Callback cb, + AsynchAcceptor *acceptor, + 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; + + // 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, + SOCKADDRBUFLEN = 2 * SOCKADDRMAXLEN }; + char addressBuffer[SOCKADDRBUFLEN]; +}; + +class AsynchIoResult : public AsynchResult { +public: + typedef boost::function1<void, AsynchIoResult *> Completer; + + virtual ~AsynchIoResult() {} + qpid::sys::AsynchIO::BufferBase *getBuff(void) const { return iobuff; } + size_t getRequested(void) const { return requested; } + const WSABUF *getWSABUF(void) const { return &wsabuf; } + +protected: + void setBuff (qpid::sys::AsynchIO::BufferBase *buffer) { iobuff = buffer; } + +protected: + AsynchIoResult(Completer cb, + qpid::sys::AsynchIO::BufferBase *buff, size_t length) + : completionCallback(cb), iobuff(buff), requested(length) {} + + virtual void complete(void) = 0; + WSABUF wsabuf; + Completer completionCallback; + +private: + qpid::sys::AsynchIO::BufferBase *iobuff; + size_t requested; // Number of bytes in original I/O request +}; + +class AsynchReadResult : public AsynchIoResult { + + // complete() updates buffer then does completion callback. + virtual void complete(void) { + getBuff()->dataCount += bytes; + completionCallback(this); + } + +public: + AsynchReadResult(AsynchIoResult::Completer cb, + qpid::sys::AsynchIO::BufferBase *buff, + size_t length) + : AsynchIoResult(cb, buff, length) { + wsabuf.buf = buff->bytes + buff->dataCount; + wsabuf.len = length; + } +}; + +class AsynchWriteResult : public AsynchIoResult { + + // complete() updates buffer then does completion callback. + virtual void complete(void) { + qpid::sys::AsynchIO::BufferBase *b = getBuff(); + b->dataStart += bytes; + b->dataCount -= bytes; + completionCallback(this); + } + +public: + AsynchWriteResult(AsynchIoResult::Completer cb, + qpid::sys::AsynchIO::BufferBase *buff, + size_t length) + : AsynchIoResult(cb, buff, length) { + wsabuf.buf = buff ? buff->bytes : 0; + wsabuf.len = length; + } +}; + +class AsynchWriteWanted : public AsynchWriteResult { + + // complete() just does completion callback; no buffers used. + virtual void complete(void) { + completionCallback(this); + } + +public: + AsynchWriteWanted(AsynchIoResult::Completer cb) + : AsynchWriteResult(cb, 0, 0) { + wsabuf.buf = 0; + wsabuf.len = 0; + } +}; + +class AsynchCallbackRequest : public AsynchIoResult { + // complete() needs to simply call the completionCallback; no buffers. + virtual void complete(void) { + completionCallback(this); + } + +public: + AsynchCallbackRequest(AsynchIoResult::Completer cb, + qpid::sys::AsynchIO::RequestCallback reqCb) + : AsynchIoResult(cb, 0, 0), reqCallback(reqCb) { + wsabuf.buf = 0; + wsabuf.len = 0; + } + + qpid::sys::AsynchIO::RequestCallback reqCallback; +}; + +}}} // qpid::sys::windows + +#endif /*!_windows_asynchIoResult_h*/ diff --git a/qpid/cpp/src/qpid/sys/windows/FileSysDir.cpp b/qpid/cpp/src/qpid/sys/windows/FileSysDir.cpp new file mode 100644 index 0000000000..88f1637d48 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/windows/FileSysDir.cpp @@ -0,0 +1,53 @@ +/* + * + * 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. + * + */ + +#include "qpid/sys/FileSysDir.h" +#include "qpid/sys/StrError.h" +#include "qpid/Exception.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <direct.h> +#include <errno.h> + +namespace qpid { +namespace sys { + +bool FileSysDir::exists (void) const +{ + const char *cpath = dirPath.c_str (); + struct _stat s; + if (::_stat(cpath, &s)) { + if (errno == ENOENT) { + return false; + } + throw qpid::Exception (strError(errno) + + ": Can't check directory: " + dirPath); + } + if (s.st_mode & _S_IFDIR) + return true; + throw qpid::Exception(dirPath + " is not a directory"); +} + +void FileSysDir::mkdir(void) +{ + if (::_mkdir(dirPath.c_str()) == -1) + throw Exception ("Can't create directory: " + dirPath); +} + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/windows/IOHandle.cpp b/qpid/cpp/src/qpid/sys/windows/IOHandle.cpp new file mode 100755 index 0000000000..250737cb99 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/windows/IOHandle.cpp @@ -0,0 +1,42 @@ +/* + * + * 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" +#include "qpid/sys/windows/IoHandlePrivate.h" +#include <windows.h> + +namespace qpid { +namespace sys { + +SOCKET toFd(const IOHandlePrivate* h) +{ + return h->fd; +} + +IOHandle::IOHandle(IOHandlePrivate* h) : + impl(h) +{} + +IOHandle::~IOHandle() { + delete impl; +} + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/windows/IoHandlePrivate.h b/qpid/cpp/src/qpid/sys/windows/IoHandlePrivate.h new file mode 100755 index 0000000000..5943db5cc7 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/windows/IoHandlePrivate.h @@ -0,0 +1,61 @@ +#ifndef _sys_windows_IoHandlePrivate_h +#define _sys_windows_IoHandlePrivate_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/AsynchIO.h" +#include "qpid/sys/windows/AsynchIoResult.h" +#include "qpid/CommonImportExport.h" + +#include <winsock2.h> + +namespace qpid { +namespace sys { + +// Private fd related implementation details +// There should be either a valid socket handle or a completer callback. +// Handle is used to associate with poller's iocp; completer is used to +// inject a completion that will very quickly trigger a callback to the +// completer from an I/O thread. If the callback mechanism is used, there +// can be a RequestCallback set - this carries the callback object through +// from AsynchIO::requestCallback() through to the I/O completion processing. +class IOHandlePrivate { + friend QPID_COMMON_EXTERN SOCKET toSocketHandle(const Socket& s); + static IOHandlePrivate* getImpl(const IOHandle& h); + +public: + IOHandlePrivate(SOCKET f = INVALID_SOCKET, + windows::AsynchIoResult::Completer cb = 0, + AsynchIO::RequestCallback reqCallback = 0) : + fd(f), event(cb), cbRequest(reqCallback) + {} + + SOCKET fd; + windows::AsynchIoResult::Completer event; + AsynchIO::RequestCallback cbRequest; +}; + +QPID_COMMON_EXTERN SOCKET toSocketHandle(const Socket& s); + +}} + +#endif /* _sys_windows_IoHandlePrivate_h */ diff --git a/qpid/cpp/src/qpid/sys/windows/IocpPoller.cpp b/qpid/cpp/src/qpid/sys/windows/IocpPoller.cpp new file mode 100755 index 0000000000..1805dd2cd8 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/windows/IocpPoller.cpp @@ -0,0 +1,219 @@ +/* + * + * 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/Poller.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Dispatcher.h" + +#include "qpid/sys/windows/AsynchIoResult.h" +#include "qpid/sys/windows/IoHandlePrivate.h" +#include "qpid/sys/windows/check.h" + +#include <winsock2.h> +#include <windows.h> + +#include <assert.h> +#include <vector> +#include <exception> + +namespace qpid { +namespace sys { + +class PollerHandlePrivate { + friend class Poller; + friend class PollerHandle; + + SOCKET fd; + windows::AsynchIoResult::Completer cb; + AsynchIO::RequestCallback cbRequest; + + PollerHandlePrivate(SOCKET f, + windows::AsynchIoResult::Completer cb0 = 0, + AsynchIO::RequestCallback rcb = 0) + : fd(f), cb(cb0), cbRequest(rcb) + { + } + +}; + +PollerHandle::PollerHandle(const IOHandle& h) : + impl(new PollerHandlePrivate(toSocketHandle(static_cast<const Socket&>(h)), h.impl->event, h.impl->cbRequest)) +{} + +PollerHandle::~PollerHandle() { + delete impl; +} + +/** + * Concrete implementation of Poller to use the Windows I/O Completion + * port (IOCP) facility. + */ +class PollerPrivate { + friend class Poller; + + const HANDLE iocp; + + // The number of threads running the event loop. + volatile LONG threadsRunning; + + // Shutdown request is handled by setting isShutdown and injecting a + // well-formed completion event into the iocp. + bool isShutdown; + + PollerPrivate() : + iocp(::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)), + threadsRunning(0), + isShutdown(false) { + QPID_WINDOWS_CHECK_NULL(iocp); + } + + ~PollerPrivate() { + // It's probably okay to ignore any errors here as there can't be + // data loss + ::CloseHandle(iocp); + } +}; + +void Poller::shutdown() { + // Allow sloppy code to shut us down more than once. + if (impl->isShutdown) + return; + ULONG_PTR key = 1; // Tell wait() it's a shutdown, not I/O + PostQueuedCompletionStatus(impl->iocp, 0, key, 0); +} + +bool Poller::hasShutdown() +{ + return impl->isShutdown; +} + +bool Poller::interrupt(PollerHandle&) { + return false; // There's no concept of a registered handle. +} + +void Poller::run() { + do { + Poller::Event event = this->wait(); + + // Handle shutdown + switch (event.type) { + case Poller::SHUTDOWN: + return; + break; + case Poller::INVALID: // On any type of success or fail completion + break; + default: + // This should be impossible + assert(false); + } + } while (true); +} + +void Poller::monitorHandle(PollerHandle& handle, Direction dir) { + HANDLE h = (HANDLE)(handle.impl->fd); + if (h != INVALID_HANDLE_VALUE) { + HANDLE iocpHandle = ::CreateIoCompletionPort (h, impl->iocp, 0, 0); + QPID_WINDOWS_CHECK_NULL(iocpHandle); + } + else { + // INPUT is used to request a callback; OUTPUT to request a write + assert(dir == Poller::INPUT || dir == Poller::OUTPUT); + + if (dir == Poller::OUTPUT) { + windows::AsynchWriteWanted *result = + new windows::AsynchWriteWanted(handle.impl->cb); + PostQueuedCompletionStatus(impl->iocp, 0, 0, result->overlapped()); + } + else { + windows::AsynchCallbackRequest *result = + new windows::AsynchCallbackRequest(handle.impl->cb, + handle.impl->cbRequest); + PostQueuedCompletionStatus(impl->iocp, 0, 0, result->overlapped()); + } + } +} + +// All no-ops... +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; + DWORD numTransferred = 0; + ULONG_PTR completionKey = 0; + OVERLAPPED *overlapped = 0; + windows::AsynchResult *result = 0; + + // Wait for either an I/O operation to finish (thus signaling the + // IOCP handle) or a shutdown request to be made (thus signaling the + // shutdown event). + if (timeout == TIME_INFINITE) + timeoutMs = INFINITE; + else + timeoutMs = static_cast<DWORD>(timeout / TIME_MSEC); + + InterlockedIncrement(&impl->threadsRunning); + bool goodOp = ::GetQueuedCompletionStatus (impl->iocp, + &numTransferred, + &completionKey, + &overlapped, + timeoutMs); + LONG remainingThreads = InterlockedDecrement(&impl->threadsRunning); + if (goodOp) { + // Dequeued a successful completion. If it's a posted packet from + // shutdown() the overlapped ptr is 0 and key is 1. Else downcast + // the OVERLAPPED pointer to an AsynchIoResult and call the + // completion handler. + if (overlapped == 0 && completionKey == 1) { + // If there are other threads still running this wait, re-post + // the completion. + if (remainingThreads > 0) + PostQueuedCompletionStatus(impl->iocp, 0, completionKey, 0); + return Event(0, SHUTDOWN); + } + + result = windows::AsynchResult::from_overlapped(overlapped); + result->success (static_cast<size_t>(numTransferred)); + } + else { + if (overlapped != 0) { + // Dequeued a completion for a failed operation. Downcast back + // to the result object and inform it that the operation failed. + DWORD status = ::GetLastError(); + result = windows::AsynchResult::from_overlapped(overlapped); + result->failure (static_cast<int>(status)); + } + } + return Event(0, INVALID); // TODO - this may need to be changed. + +} + +// Concrete constructors +Poller::Poller() : + impl(new PollerPrivate()) +{} + +Poller::~Poller() { + delete impl; +} + +}} diff --git a/qpid/cpp/src/qpid/sys/windows/LockFile.cpp b/qpid/cpp/src/qpid/sys/windows/LockFile.cpp new file mode 100755 index 0000000000..048c2d5b18 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/windows/LockFile.cpp @@ -0,0 +1,64 @@ +/* + * + * Copyright (c) 2008 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. + * + */ + +#include "qpid/sys/LockFile.h" +#include "qpid/sys/windows/check.h" + +#include <windows.h> + +namespace qpid { +namespace sys { + +class LockFilePrivate { + friend class LockFile; + + HANDLE fd; + +public: + LockFilePrivate(HANDLE f) : fd(f) {} +}; + +LockFile::LockFile(const std::string& path_, bool create) + : path(path_), created(create) { + + HANDLE h = ::CreateFile(path.c_str(), + create ? (GENERIC_READ|GENERIC_WRITE) : GENERIC_READ, + FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, + 0, /* Default security */ + create ? OPEN_ALWAYS : OPEN_EXISTING, + FILE_FLAG_DELETE_ON_CLOSE, /* Delete file when closed */ + NULL); + if (h == INVALID_HANDLE_VALUE) + throw qpid::Exception(path + ": " + qpid::sys::strError(GetLastError())); + + // Lock up to 4Gb + if (!::LockFile(h, 0, 0, 0xffffffff, 0)) + throw qpid::Exception(path + ": " + qpid::sys::strError(GetLastError())); + impl.reset(new LockFilePrivate(h)); +} + +LockFile::~LockFile() { + if (impl) { + if (impl->fd != INVALID_HANDLE_VALUE) { + ::UnlockFile(impl->fd, 0, 0, 0xffffffff, 0); + ::CloseHandle(impl->fd); + } + } +} + +}} /* namespace qpid::sys */ diff --git a/qpid/cpp/src/qpid/sys/windows/PipeHandle.cpp b/qpid/cpp/src/qpid/sys/windows/PipeHandle.cpp new file mode 100755 index 0000000000..062458ae5f --- /dev/null +++ b/qpid/cpp/src/qpid/sys/windows/PipeHandle.cpp @@ -0,0 +1,101 @@ +// +// 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/PipeHandle.h" +#include "qpid/sys/windows/check.h" +#include <winsock2.h> + +namespace qpid { +namespace sys { + +PipeHandle::PipeHandle(bool nonBlocking) { + + SOCKET listener, pair[2]; + struct sockaddr_in addr; + int err; + int addrlen = sizeof(addr); + pair[0] = pair[1] = INVALID_SOCKET; + if ((listener = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) + throw QPID_WINDOWS_ERROR(WSAGetLastError()); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = 0; + + err = bind(listener, (const struct sockaddr*) &addr, sizeof(addr)); + if (err == SOCKET_ERROR) { + err = WSAGetLastError(); + closesocket(listener); + throw QPID_WINDOWS_ERROR(err); + } + + err = getsockname(listener, (struct sockaddr*) &addr, &addrlen); + if (err == SOCKET_ERROR) { + err = WSAGetLastError(); + closesocket(listener); + throw QPID_WINDOWS_ERROR(err); + } + + try { + if (listen(listener, 1) == SOCKET_ERROR) + throw QPID_WINDOWS_ERROR(WSAGetLastError()); + if ((pair[0] = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) + throw QPID_WINDOWS_ERROR(WSAGetLastError()); + if (connect(pair[0], (const struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) + throw QPID_WINDOWS_ERROR(WSAGetLastError()); + if ((pair[1] = accept(listener, NULL, NULL)) == INVALID_SOCKET) + throw QPID_WINDOWS_ERROR(WSAGetLastError()); + + closesocket(listener); + writeFd = pair[0]; + readFd = pair[1]; + } + catch (...) { + closesocket(listener); + if (pair[0] != INVALID_SOCKET) + closesocket(pair[0]); + throw; + } + + // Set the socket to non-blocking + if (nonBlocking) { + unsigned long nonblock = 1; + ioctlsocket(readFd, FIONBIO, &nonblock); + } +} + +PipeHandle::~PipeHandle() { + closesocket(readFd); + closesocket(writeFd); +} + +int PipeHandle::read(void* buf, size_t bufSize) { + return ::recv(readFd, (char *)buf, bufSize, 0); +} + +int PipeHandle::write(const void* buf, size_t bufSize) { + return ::send(writeFd, (const char *)buf, bufSize, 0); +} + +int PipeHandle::getReadHandle() { + return readFd; +} + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/windows/PollableCondition.cpp b/qpid/cpp/src/qpid/sys/windows/PollableCondition.cpp new file mode 100644 index 0000000000..6a1d9045b4 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/windows/PollableCondition.cpp @@ -0,0 +1,114 @@ +#ifndef QPID_SYS_WINDOWS_POLLABLECONDITION_CPP +#define QPID_SYS_WINDOWS_POLLABLECONDITION_CPP + +/* + * + * 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/PollableCondition.h" +#include "qpid/sys/IOHandle.h" +#include "qpid/sys/windows/AsynchIoResult.h" +#include "qpid/sys/windows/IoHandlePrivate.h" + +#include <boost/bind.hpp> +#include <windows.h> + +namespace qpid { +namespace sys { + +// PollableConditionPrivate will reuse the IocpPoller's ability to queue +// a completion to the IOCP and have it dispatched to the completer callback +// noted in the IOHandlePrivate when the request is queued. The +// AsynchCallbackRequest object is not really used - we already have the +// desired callback for the user of PollableCondition. +class PollableConditionPrivate : private IOHandle { + friend class PollableCondition; + +private: + PollableConditionPrivate(const sys::PollableCondition::Callback& cb, + sys::PollableCondition& parent, + const boost::shared_ptr<sys::Poller>& poller); + ~PollableConditionPrivate(); + + void poke(); + void dispatch(windows::AsynchIoResult *result); + +private: + PollableCondition::Callback cb; + PollableCondition& parent; + boost::shared_ptr<sys::Poller> poller; + LONG isSet; +}; + +PollableConditionPrivate::PollableConditionPrivate(const sys::PollableCondition::Callback& cb, + sys::PollableCondition& parent, + const boost::shared_ptr<sys::Poller>& poller) + : IOHandle(new sys::IOHandlePrivate(INVALID_SOCKET, + boost::bind(&PollableConditionPrivate::dispatch, this, _1))), + cb(cb), parent(parent), poller(poller), isSet(0) +{ +} + +PollableConditionPrivate::~PollableConditionPrivate() +{ +} + +void PollableConditionPrivate::poke() +{ + // monitorHandle will queue a completion for the IOCP; when it's handled, a + // poller thread will call back to dispatch() below. + PollerHandle ph(*this); + poller->monitorHandle(ph, Poller::INPUT); +} + +void PollableConditionPrivate::dispatch(windows::AsynchIoResult *result) +{ + delete result; // Poller::monitorHandle() allocates this + cb(parent); + if (isSet) + poke(); +} + + /* PollableCondition */ + +PollableCondition::PollableCondition(const Callback& cb, + const boost::shared_ptr<sys::Poller>& poller) + : impl(new PollableConditionPrivate(cb, *this, poller)) +{ +} + +PollableCondition::~PollableCondition() +{ + delete impl; +} + +void PollableCondition::set() { + // Add one to the set count and poke it to provoke a callback + ::InterlockedIncrement(&impl->isSet); + impl->poke(); +} + +void PollableCondition::clear() { + ::InterlockedExchange(&impl->isSet, 0); +} + +}} // namespace qpid::sys + +#endif /*!QPID_SYS_WINDOWS_POLLABLECONDITION_CPP*/ diff --git a/qpid/cpp/src/qpid/sys/windows/Shlib.cpp b/qpid/cpp/src/qpid/sys/windows/Shlib.cpp new file mode 100644 index 0000000000..ba18747eb4 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/windows/Shlib.cpp @@ -0,0 +1,54 @@ +/* + * 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/Shlib.h" +#include "qpid/Exception.h" +#include "qpid/sys/windows/check.h" +#include <windows.h> + +namespace qpid { +namespace sys { + +void Shlib::load(const char* name) { + HMODULE h = LoadLibrary(name); + if (h == NULL) { + throw QPID_WINDOWS_ERROR(GetLastError()); + } + handle = static_cast<void*>(h); +} + +void Shlib::unload() { + if (handle) { + if (FreeLibrary(static_cast<HMODULE>(handle)) == 0) { + throw QPID_WINDOWS_ERROR(GetLastError()); + } + handle = 0; + } +} + +void* Shlib::getSymbol(const char* 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; +} + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/windows/Socket.cpp b/qpid/cpp/src/qpid/sys/windows/Socket.cpp new file mode 100755 index 0000000000..baa80f04e0 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/windows/Socket.cpp @@ -0,0 +1,289 @@ +/* + * + * 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. + * + */ + +// Ensure we get all of winsock2.h +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 +#endif + +#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 <cstdlib> +#include <string.h> + +#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). +// So, this simple static with an interlocked increment will do for known +// use cases at this time. Since this will only shut down winsock at process +// termination, there may be some problems with client programs that also +// expect to load and unload winsock, but we'll see... +// If someone does get an easy-to-use singleton sometime, converting to it +// may be preferable. + +namespace { + +static LONG volatile initialized = 0; + +class WinSockSetup { + // : public boost::details::pool::singleton_default<WinSockSetup> { + +public: + WinSockSetup() { + LONG timesEntered = InterlockedIncrement(&initialized); + if (timesEntered > 1) + return; + err = 0; + WORD wVersionRequested; + WSADATA wsaData; + + /* Request WinSock 2.2 */ + wVersionRequested = MAKEWORD(2, 2); + err = WSAStartup(wVersionRequested, &wsaData); + } + + ~WinSockSetup() { + WSACleanup(); + } + +public: + int error(void) const { return err; } + +protected: + DWORD err; +}; + +static WinSockSetup setup; + +} /* namespace */ + +namespace qpid { +namespace sys { + +namespace { + +std::string getName(SOCKET fd, bool local) +{ + 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)); + } + + char servName[NI_MAXSERV]; + char dispName[NI_MAXHOST]; + 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); +} +} // namespace + +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), + nonblocking(false), + nodelay(false) +{} + +void +Socket::createSocket(const SocketAddress& sa) const +{ + SOCKET& socket = impl->fd; + if (socket != INVALID_SOCKET) Socket::close(); + + SOCKET s = ::socket (getAddrInfo(sa).ai_family, + getAddrInfo(sa).ai_socktype, + 0); + if (s == INVALID_SOCKET) throw QPID_WINDOWS_ERROR(WSAGetLastError()); + socket = s; + + try { + if (nonblocking) setNonblocking(); + if (nodelay) setTcpNoDelay(); + } catch (std::exception&) { + closesocket(s); + socket = INVALID_SOCKET; + throw; + } +} + +void Socket::setNonblocking() const { + u_long nonblock = 1; + QPID_WINSOCK_CHECK(ioctlsocket(impl->fd, FIONBIO, &nonblock)); +} + +void Socket::connect(const std::string& host, const std::string& port) const +{ + SocketAddress sa(host, port); + connect(sa); +} + +void +Socket::connect(const SocketAddress& addr) const +{ + peername = addr.asString(false); + + const SOCKET& socket = impl->fd; + const addrinfo *addrs = &(getAddrInfo(addr)); + int error = 0; + 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) << ": " << peername)); +} + +void +Socket::close() const +{ + SOCKET& socket = impl->fd; + if (socket == INVALID_SOCKET) return; + QPID_WINSOCK_CHECK(closesocket(socket)); + socket = INVALID_SOCKET; +} + + +int Socket::write(const void *buf, size_t count) const +{ + const SOCKET& socket = impl->fd; + int sent = ::send(socket, (const char *)buf, count, 0); + if (sent == SOCKET_ERROR) + return -1; + return sent; +} + +int Socket::read(void *buf, size_t count) const +{ + const SOCKET& socket = impl->fd; + int received = ::recv(socket, (char *)buf, count, 0); + if (received == SOCKET_ERROR) + return -1; + return received; +} + +int Socket::listen(const std::string&, const std::string& port, int backlog) const +{ + 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(boost::lexical_cast<uint16_t>(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 (::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); +} + +Socket* Socket::accept() const +{ + SOCKET afd = ::accept(impl->fd, 0, 0); + if (afd != INVALID_SOCKET) + return new Socket(new IOHandlePrivate(afd)); + else if (WSAGetLastError() == EAGAIN) + return 0; + else throw QPID_WINDOWS_ERROR(WSAGetLastError()); +} + +std::string Socket::getPeerAddress() const +{ + if (peername.empty()) + peername = getName(impl->fd, false); + return peername; +} + +std::string Socket::getLocalAddress() const +{ + if (localname.empty()) + localname = getName(impl->fd, true); + return localname; +} + +int Socket::getError() const +{ + int result; + socklen_t rSize = sizeof (result); + + QPID_WINSOCK_CHECK(::getsockopt(impl->fd, SOL_SOCKET, SO_ERROR, (char *)&result, &rSize)); + return result; +} + +void Socket::setTcpNoDelay() const +{ + int flag = 1; + int result = setsockopt(impl->fd, + IPPROTO_TCP, + TCP_NODELAY, + (char *)&flag, + sizeof(flag)); + QPID_WINSOCK_CHECK(result); + nodelay = true; +} + +inline IOHandlePrivate* IOHandlePrivate::getImpl(const qpid::sys::IOHandle &h) +{ + return h.impl; +} + +SOCKET toSocketHandle(const Socket& s) +{ + return IOHandlePrivate::getImpl(s)->fd; +} + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/windows/SocketAddress.cpp b/qpid/cpp/src/qpid/sys/windows/SocketAddress.cpp new file mode 100644 index 0000000000..ac43cd2d23 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/windows/SocketAddress.cpp @@ -0,0 +1,76 @@ +/* + * + * 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. + * + */ + +// Ensure we get all of winsock2.h +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 +#endif + +#include "qpid/sys/SocketAddress.h" + +#include "qpid/sys/windows/check.h" + +#include <winsock2.h> +#include <ws2tcpip.h> +#include <string.h> + +namespace qpid { +namespace sys { + +SocketAddress::SocketAddress(const std::string& host0, const std::string& port0) : + host(host0), + 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() +{ + ::freeaddrinfo(addrInfo); +} + +std::string SocketAddress::asString(bool) const +{ + return host + ":" + port; +} + +const ::addrinfo& getAddrInfo(const SocketAddress& sa) +{ + return *sa.addrInfo; +} + +}} diff --git a/qpid/cpp/src/qpid/sys/windows/SslAsynchIO.cpp b/qpid/cpp/src/qpid/sys/windows/SslAsynchIO.cpp new file mode 100644 index 0000000000..11a3389e45 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/windows/SslAsynchIO.cpp @@ -0,0 +1,661 @@ +/* + * + * 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 "SslAsynchIO.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/Socket.h" +#include "qpid/sys/Poller.h" +#include "qpid/sys/Thread.h" +#include "qpid/sys/Time.h" +#include "qpid/log/Statement.h" + +#include "qpid/sys/windows/check.h" + +// security.h needs to see this to distinguish from kernel use. +#define SECURITY_WIN32 +#include <security.h> +#include <Schnlsp.h> +#undef SECURITY_WIN32 + +#include <queue> +#include <boost/bind.hpp> + +namespace { + + /* + * To make the SSL encryption more efficient, set up a new BufferBase + * that leaves room for the SSL header to be prepended and the SSL + * trailer to be appended. + * + * This works by accepting a properly formed BufferBase, remembering it, + * and resetting the members of this struct to reflect the reserved + * header and trailer areas. It's only needed for giving buffers up to + * the frame layer for writing into. + */ + struct SslIoBuff : public qpid::sys::AsynchIO::BufferBase { + std::auto_ptr<qpid::sys::AsynchIO::BufferBase> aioBuff; + + SslIoBuff (qpid::sys::AsynchIO::BufferBase *base, + const SecPkgContext_StreamSizes &sizes) + : qpid::sys::AsynchIO::BufferBase(&base->bytes[sizes.cbHeader], + std::min(base->byteCount - sizes.cbHeader - sizes.cbTrailer, + sizes.cbMaximumMessage)), + aioBuff(base) + {} + + ~SslIoBuff() {} + qpid::sys::AsynchIO::BufferBase* release() { return aioBuff.release(); } + }; +} + +namespace qpid { +namespace sys { +namespace windows { + +SslAsynchIO::SslAsynchIO(const qpid::sys::Socket& s, + CredHandle hCred, + ReadCallback rCb, + EofCallback eofCb, + DisconnectCallback disCb, + ClosedCallback cCb, + BuffersEmptyCallback eCb, + IdleCallback iCb, + NegotiateDoneCallback nCb) : + credHandle(hCred), + aio(0), + state(Negotiating), + readCallback(rCb), + idleCallback(iCb), + negotiateDoneCallback(nCb), + callbacksInProgress(0), + queuedDelete(false), + leftoverPlaintext(0) +{ + SecInvalidateHandle(&ctxtHandle); + peerAddress = s.getPeerAddress(); + aio = qpid::sys::AsynchIO::create(s, + boost::bind(&SslAsynchIO::sslDataIn, this, _1, _2), + eofCb, + disCb, + cCb, + eCb, + boost::bind(&SslAsynchIO::idle, this, _1)); +} + +SslAsynchIO::~SslAsynchIO() { + if (leftoverPlaintext) { + delete leftoverPlaintext; + leftoverPlaintext = 0; + } +} + +void SslAsynchIO::queueForDeletion() { + // This method effectively disconnects the layer above; pass it on the + // AsynchIO and delete. + aio->queueForDeletion(); + queuedDelete = true; + if (!callbacksInProgress) + delete this; +} + +void SslAsynchIO::start(qpid::sys::Poller::shared_ptr poller) { + aio->start(poller); + startNegotiate(); +} + +void SslAsynchIO::queueReadBuffer(AsynchIO::BufferBase* buff) { + aio->queueReadBuffer(buff); +} + +void SslAsynchIO::unread(AsynchIO::BufferBase* buff) { + // This is plaintext data being given back for more. Since it's already + // decrypted, don't give it back to the aio layer; keep it to append + // any new data for the upper layer. + assert(buff); + buff->squish(); + assert(leftoverPlaintext == 0); + leftoverPlaintext = buff; +} + +void SslAsynchIO::queueWrite(AsynchIO::BufferBase* buff) { + // @@TODO: Need to delay the write if the session is renegotiating. + + // Should not have gotten here without an SslIoBuff. This assert is + // primarily to catch any stray cases where write is called with a buffer + // not obtained via getQueuedBuffer. + SslIoBuff *sslBuff = dynamic_cast<SslIoBuff*>(buff); + assert(sslBuff != 0); + + // Encrypt and hand off to the io layer. Remember that the upper layer's + // encoding was working on, and adjusting counts for, the SslIoBuff. + // Update the count of the original BufferBase before handing off to + // the I/O layer. + buff = sslBuff->release(); + SecBuffer buffs[4]; + buffs[0].cbBuffer = schSizes.cbHeader; + buffs[0].BufferType = SECBUFFER_STREAM_HEADER; + buffs[0].pvBuffer = buff->bytes; // This space was left by SslIoBuff + buffs[1].cbBuffer = sslBuff->dataCount; + buffs[1].BufferType = SECBUFFER_DATA; + buffs[1].pvBuffer = sslBuff->bytes; + buffs[2].cbBuffer = schSizes.cbTrailer; + buffs[2].BufferType = SECBUFFER_STREAM_TRAILER; + buffs[2].pvBuffer = &sslBuff->bytes[sslBuff->dataCount]; + buffs[3].cbBuffer = 0; + buffs[3].BufferType = SECBUFFER_EMPTY; + buffs[3].pvBuffer = 0; + SecBufferDesc buffDesc; + buffDesc.ulVersion = SECBUFFER_VERSION; + buffDesc.cBuffers = 4; + buffDesc.pBuffers = buffs; + SECURITY_STATUS status = ::EncryptMessage(&ctxtHandle, 0, &buffDesc, 0); + + // EncryptMessage encrypts the data in place. The header and trailer + // areas were left previously and must now be included in the updated + // count of bytes to write to the peer. + delete sslBuff; + buff->dataCount = buffs[0].cbBuffer + buffs[1].cbBuffer + buffs[2].cbBuffer; + aio->queueWrite(buff); +} + +void SslAsynchIO::notifyPendingWrite() { + aio->notifyPendingWrite(); +} + +void SslAsynchIO::queueWriteClose() { + if (state == Negotiating) { + // Never got going, so don't bother trying to close SSL down orderly. + state = ShuttingDown; + aio->queueWriteClose(); + return; + } + + state = ShuttingDown; + + DWORD shutdown = SCHANNEL_SHUTDOWN; + SecBuffer shutBuff; + shutBuff.cbBuffer = sizeof(DWORD); + shutBuff.BufferType = SECBUFFER_TOKEN; + shutBuff.pvBuffer = &shutdown; + SecBufferDesc desc; + desc.ulVersion = SECBUFFER_VERSION; + desc.cBuffers = 1; + desc.pBuffers = &shutBuff; + ::ApplyControlToken(&ctxtHandle, &desc); + negotiateStep(0); + // When the shutdown sequence is done, negotiateDone() will handle + // shutting down aio. +} + +bool SslAsynchIO::writeQueueEmpty() { + return aio->writeQueueEmpty(); +} + +/* + * Initiate a read operation. AsynchIO::readComplete() will be + * called when the read is complete and data is available. + */ +void SslAsynchIO::startReading() { + aio->startReading(); +} + +void SslAsynchIO::stopReading() { + aio->stopReading(); +} + +// Queue the specified callback for invocation from an I/O thread. +void SslAsynchIO::requestCallback(RequestCallback callback) { + aio->requestCallback(callback); +} + +/** + * Return a queued buffer read to put new data in for writing. + * This method ALWAYS returns a SslIoBuff reflecting a BufferBase from + * the aio layer that has header and trailer space reserved. + */ +AsynchIO::BufferBase* SslAsynchIO::getQueuedBuffer() { + SslIoBuff *sslBuff = 0; + BufferBase* buff = aio->getQueuedBuffer(); + if (buff == 0) + return 0; + + sslBuff = new SslIoBuff(buff, schSizes); + return sslBuff; +} + +unsigned int SslAsynchIO::getSslKeySize() { + SecPkgContext_KeyInfo info; + memset(&info, 0, sizeof(info)); + ::QueryContextAttributes(&ctxtHandle, SECPKG_ATTR_KEY_INFO, &info); + return info.KeySize; +} + +void SslAsynchIO::negotiationDone() { + switch(state) { + case Negotiating: + ::QueryContextAttributes(&ctxtHandle, + SECPKG_ATTR_STREAM_SIZES, + &schSizes); + state = Running; + if (negotiateDoneCallback) + negotiateDoneCallback(SEC_E_OK); + break; + case Redo: + state = Running; + break; + case ShuttingDown: + aio->queueWriteClose(); + break; + default: + assert(0); + } +} + +void SslAsynchIO::negotiationFailed(SECURITY_STATUS status) { + QPID_LOG(notice, "SSL negotiation failed to " << peerAddress << ": " << + qpid::sys::strError(status)); + if (negotiateDoneCallback) + negotiateDoneCallback(status); + else + queueWriteClose(); +} + +void SslAsynchIO::sslDataIn(qpid::sys::AsynchIO& a, BufferBase *buff) { + if (state != Running) { + negotiateStep(buff); + return; + } + + // Decrypt the buffer; if there's legit data, pass it on through. + // However, it's also possible that the peer hasn't supplied enough + // data yet, or the session needs to be renegotiated, or the session + // is ending. + SecBuffer recvBuffs[4]; + recvBuffs[0].cbBuffer = buff->dataCount; + recvBuffs[0].BufferType = SECBUFFER_DATA; + recvBuffs[0].pvBuffer = &buff->bytes[buff->dataStart]; + recvBuffs[1].BufferType = SECBUFFER_EMPTY; + recvBuffs[2].BufferType = SECBUFFER_EMPTY; + recvBuffs[3].BufferType = SECBUFFER_EMPTY; + SecBufferDesc buffDesc; + buffDesc.ulVersion = SECBUFFER_VERSION; + buffDesc.cBuffers = 4; + buffDesc.pBuffers = recvBuffs; + SECURITY_STATUS status = ::DecryptMessage(&ctxtHandle, &buffDesc, 0, NULL); + if (status != SEC_E_OK) { + if (status == SEC_E_INCOMPLETE_MESSAGE) { + // Give the partially filled buffer back and get more data + a.unread(buff); + } + else { + // Don't need this any more... + a.queueReadBuffer(buff); + + if (status == SEC_I_RENEGOTIATE) { + state = Redo; + negotiateStep(0); + } + else if (status == SEC_I_CONTEXT_EXPIRED) { + queueWriteClose(); + } + else { + throw QPID_WINDOWS_ERROR(status); + } + } + return; + } + + // All decrypted and verified... continue with AMQP. The recvBuffs have + // been set up by DecryptMessage to demarcate the SSL header, data, and + // trailer, as well as any extra data left over. Walk through and find + // that info, adjusting the buff data accordingly to reflect only the + // actual decrypted data. + // If there's extra data, copy that out to a new buffer and run through + // this method again. + BufferBase *extraBuff = 0; + for (int i = 0; i < 4; i++) { + switch (recvBuffs[i].BufferType) { + case SECBUFFER_STREAM_HEADER: + buff->dataStart += recvBuffs[i].cbBuffer; + // Fall through - also don't count these bytes as data + case SECBUFFER_STREAM_TRAILER: + buff->dataCount -= recvBuffs[i].cbBuffer; + break; + case SECBUFFER_EXTRA: + // Very important to get this buffer from the downstream aio. + // The ones constructed from the local getQueuedBuffer() are + // restricted size for encrypting. However, data coming up from + // TCP may have a bunch of SSL segments coalesced and be much + // larger than the maximum single SSL segment. + extraBuff = a.getQueuedBuffer(); + if (0 == extraBuff) + throw QPID_WINDOWS_ERROR(WSAENOBUFS); + memmove(extraBuff->bytes, + recvBuffs[i].pvBuffer, + recvBuffs[i].cbBuffer); + extraBuff->dataCount = recvBuffs[i].cbBuffer; + break; + default: + break; + } + } + + // Since we've already taken (possibly) all the available bytes from the + // aio layer, need to be sure that everything that's processable is + // processed before returning back to aio. It could be that any + // leftoverPlaintext data plus new buff data won't fit in one buffer, so + // need to keep going around the input processing loop until either + // all the bytes are gone, or there's less than a full frame remaining + // (so we can count on more bytes being on the way via aio). + do { + BufferBase *temp = 0; + // Now that buff reflects only decrypted data, see if there was any + // partial data left over from last time. If so, append this new + // data to that and release the current buff back to aio. Assume that + // leftoverPlaintext was squished so the data starts at 0. + if (leftoverPlaintext != 0) { + // There is leftover data; append all the new data that will fit. + int32_t count = buff->dataCount; + if (leftoverPlaintext->dataCount + count > leftoverPlaintext->byteCount) + count = (leftoverPlaintext->byteCount - leftoverPlaintext->dataCount); + ::memmove(&leftoverPlaintext->bytes[leftoverPlaintext->dataCount], + &buff->bytes[buff->dataStart], + count); + leftoverPlaintext->dataCount += count; + buff->dataCount -= count; + buff->dataStart += count; + if (buff->dataCount == 0) { + a.queueReadBuffer(buff); + buff = 0; + } + // Prepare to pass the buffer up. Beware that the read callback + // may do an unread(), so move the leftoverPlaintext pointer + // out of the way. It also may release the buffer back to aio, + // so in either event, the pointer passed to the callback is not + // valid on return. + temp = leftoverPlaintext; + leftoverPlaintext = 0; + } + else { + temp = buff; + buff = 0; + } + if (readCallback) { + // The callback guard here is to prevent an upcall from deleting + // this out from under us via queueForDeletion(). + ++callbacksInProgress; + readCallback(*this, temp); + --callbacksInProgress; + } + else + a.queueReadBuffer(temp); // What else can we do with this??? + } while (buff != 0); + + // Ok, the current decrypted data is done. If there was any extra data, + // go back and handle that. + if (extraBuff != 0) + sslDataIn(a, extraBuff); + + // If the upper layer queued for delete, do that now that all the + // callbacks are done. + if (queuedDelete && callbacksInProgress == 0) + delete this; +} + +void SslAsynchIO::idle(qpid::sys::AsynchIO&) { + // Don't relay idle indication to layer above until SSL session is up. + if (state == Running) { + state = Running; + if (idleCallback) + idleCallback(*this); + } +} + + /**************************************************/ + +ClientSslAsynchIO::ClientSslAsynchIO(const std::string& brokerHost, + const qpid::sys::Socket& s, + CredHandle hCred, + ReadCallback rCb, + EofCallback eofCb, + DisconnectCallback disCb, + ClosedCallback cCb, + BuffersEmptyCallback eCb, + IdleCallback iCb, + NegotiateDoneCallback nCb) : + SslAsynchIO(s, hCred, rCb, eofCb, disCb, cCb, eCb, iCb, nCb), + serverHost(brokerHost) +{ +} + +void ClientSslAsynchIO::startNegotiate() { + // SEC_CHAR is non-const, so do all the typing here. + SEC_CHAR *host = const_cast<SEC_CHAR *>(serverHost.c_str()); + + // Need a buffer to receive the token to send to the server. + BufferBase *buff = aio->getQueuedBuffer(); + ULONG ctxtRequested = ISC_REQ_STREAM; + ULONG ctxtAttrs; + // sendBuffs gets information to forward to the peer. + SecBuffer sendBuffs[2]; + sendBuffs[0].cbBuffer = buff->byteCount; + sendBuffs[0].BufferType = SECBUFFER_TOKEN; + sendBuffs[0].pvBuffer = buff->bytes; + sendBuffs[1].cbBuffer = 0; + sendBuffs[1].BufferType = SECBUFFER_EMPTY; + sendBuffs[1].pvBuffer = 0; + SecBufferDesc sendBuffDesc; + sendBuffDesc.ulVersion = SECBUFFER_VERSION; + sendBuffDesc.cBuffers = 2; + sendBuffDesc.pBuffers = sendBuffs; + SECURITY_STATUS status = ::InitializeSecurityContext(&credHandle, + NULL, + host, + ctxtRequested, + 0, + 0, + NULL, + 0, + &ctxtHandle, + &sendBuffDesc, + &ctxtAttrs, + NULL); + if (status == SEC_I_CONTINUE_NEEDED) { + buff->dataCount = sendBuffs[0].cbBuffer; + aio->queueWrite(buff); + } +} + +void ClientSslAsynchIO::negotiateStep(BufferBase* buff) { + // SEC_CHAR is non-const, so do all the typing here. + SEC_CHAR *host = const_cast<SEC_CHAR *>(serverHost.c_str()); + ULONG ctxtRequested = ISC_REQ_STREAM; + ULONG ctxtAttrs; + + // tokenBuffs describe the buffer that's coming in. It should have + // a token from the SSL server. + SecBuffer tokenBuffs[2]; + tokenBuffs[0].cbBuffer = buff ? buff->dataCount : 0; + tokenBuffs[0].BufferType = SECBUFFER_TOKEN; + tokenBuffs[0].pvBuffer = buff ? buff->bytes : 0; + tokenBuffs[1].cbBuffer = 0; + tokenBuffs[1].BufferType = SECBUFFER_EMPTY; + tokenBuffs[1].pvBuffer = 0; + SecBufferDesc tokenBuffDesc; + tokenBuffDesc.ulVersion = SECBUFFER_VERSION; + tokenBuffDesc.cBuffers = 2; + tokenBuffDesc.pBuffers = tokenBuffs; + + // Need a buffer to receive any token to send back to the server. + BufferBase *sendbuff = aio->getQueuedBuffer(); + // sendBuffs gets information to forward to the peer. + SecBuffer sendBuffs[2]; + sendBuffs[0].cbBuffer = sendbuff->byteCount; + sendBuffs[0].BufferType = SECBUFFER_TOKEN; + sendBuffs[0].pvBuffer = sendbuff->bytes; + sendBuffs[1].cbBuffer = 0; + sendBuffs[1].BufferType = SECBUFFER_EMPTY; + sendBuffs[1].pvBuffer = 0; + SecBufferDesc sendBuffDesc; + sendBuffDesc.ulVersion = SECBUFFER_VERSION; + sendBuffDesc.cBuffers = 2; + sendBuffDesc.pBuffers = sendBuffs; + + SECURITY_STATUS status = ::InitializeSecurityContext(&credHandle, + &ctxtHandle, + host, + ctxtRequested, + 0, + 0, + &tokenBuffDesc, + 0, + NULL, + &sendBuffDesc, + &ctxtAttrs, + NULL); + + if (status == SEC_E_INCOMPLETE_MESSAGE) { + // Not enough - get more data from the server then try again. + aio->unread(buff); + aio->queueReadBuffer(sendbuff); // Don't need this one for now... + return; + } + // Done with the buffer that came in... + if (buff) + aio->queueReadBuffer(buff); + if (status == SEC_I_CONTINUE_NEEDED) { + sendbuff->dataCount = sendBuffs[0].cbBuffer; + aio->queueWrite(sendbuff); + return; + } + // Nothing to send back to the server... + aio->queueReadBuffer(sendbuff); + // SEC_I_CONTEXT_EXPIRED means session stop complete; SEC_E_OK can be + // either session stop or negotiation done (session up). + if (status == SEC_E_OK || status == SEC_I_CONTEXT_EXPIRED) + negotiationDone(); + else + negotiationFailed(status); +} + +/*************************************************/ + +ServerSslAsynchIO::ServerSslAsynchIO(bool clientMustAuthenticate, + const qpid::sys::Socket& s, + CredHandle hCred, + ReadCallback rCb, + EofCallback eofCb, + DisconnectCallback disCb, + ClosedCallback cCb, + BuffersEmptyCallback eCb, + IdleCallback iCb, + NegotiateDoneCallback nCb) : + SslAsynchIO(s, hCred, rCb, eofCb, disCb, cCb, eCb, iCb, nCb), + clientAuth(clientMustAuthenticate) +{ +} + +void ServerSslAsynchIO::startNegotiate() { + // Nothing... need the client to send a token first. +} + +void ServerSslAsynchIO::negotiateStep(BufferBase* buff) { + ULONG ctxtRequested = ASC_REQ_STREAM; + if (clientAuth) + ctxtRequested |= ASC_REQ_MUTUAL_AUTH; + ULONG ctxtAttrs; + + // tokenBuffs describe the buffer that's coming in. It should have + // a token from the SSL server except if shutting down or renegotiating. + SecBuffer tokenBuffs[2]; + tokenBuffs[0].cbBuffer = buff ? buff->dataCount : 0; + tokenBuffs[0].BufferType = SECBUFFER_TOKEN; + tokenBuffs[0].pvBuffer = buff ? buff->bytes : 0; + tokenBuffs[1].cbBuffer = 0; + tokenBuffs[1].BufferType = SECBUFFER_EMPTY; + tokenBuffs[1].pvBuffer = 0; + SecBufferDesc tokenBuffDesc; + tokenBuffDesc.ulVersion = SECBUFFER_VERSION; + tokenBuffDesc.cBuffers = 2; + tokenBuffDesc.pBuffers = tokenBuffs; + + // Need a buffer to receive any token to send back to the server. + BufferBase *sendbuff = aio->getQueuedBuffer(); + // sendBuffs gets information to forward to the peer. + SecBuffer sendBuffs[2]; + sendBuffs[0].cbBuffer = sendbuff->byteCount; + sendBuffs[0].BufferType = SECBUFFER_TOKEN; + sendBuffs[0].pvBuffer = sendbuff->bytes; + sendBuffs[1].cbBuffer = 0; + sendBuffs[1].BufferType = SECBUFFER_EMPTY; + sendBuffs[1].pvBuffer = 0; + SecBufferDesc sendBuffDesc; + sendBuffDesc.ulVersion = SECBUFFER_VERSION; + sendBuffDesc.cBuffers = 2; + sendBuffDesc.pBuffers = sendBuffs; + PCtxtHandle ctxtHandlePtr = (SecIsValidHandle(&ctxtHandle)) ? &ctxtHandle : 0; + SECURITY_STATUS status = ::AcceptSecurityContext(&credHandle, + ctxtHandlePtr, + &tokenBuffDesc, + ctxtRequested, + 0, + &ctxtHandle, + &sendBuffDesc, + &ctxtAttrs, + NULL); + if (status == SEC_E_INCOMPLETE_MESSAGE) { + // Not enough - get more data from the server then try again. + if (buff) + aio->unread(buff); + aio->queueReadBuffer(sendbuff); // Don't need this one for now... + return; + } + // Done with the buffer that came in... + if (buff) + aio->queueReadBuffer(buff); + if (status == SEC_I_CONTINUE_NEEDED) { + sendbuff->dataCount = sendBuffs[0].cbBuffer; + aio->queueWrite(sendbuff); + return; + } + // There may have been a token generated; if so, send it to the client. + if (sendBuffs[0].cbBuffer > 0) { + sendbuff->dataCount = sendBuffs[0].cbBuffer; + aio->queueWrite(sendbuff); + } + else + // Nothing to send back to the server... + aio->queueReadBuffer(sendbuff); + + // SEC_I_CONTEXT_EXPIRED means session stop complete; SEC_E_OK can be + // either session stop or negotiation done (session up). + if (status == SEC_E_OK || status == SEC_I_CONTEXT_EXPIRED) { + if (clientAuth) + QPID_LOG(warning, "DID WE CHECK FOR CLIENT AUTH???"); + + negotiationDone(); + } + else { + negotiationFailed(status); + } +} + +}}} // namespace qpid::sys::windows diff --git a/qpid/cpp/src/qpid/sys/windows/SslAsynchIO.h b/qpid/cpp/src/qpid/sys/windows/SslAsynchIO.h new file mode 100644 index 0000000000..3cdf2c8f08 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/windows/SslAsynchIO.h @@ -0,0 +1,191 @@ +#ifndef _sys_windows_SslAsynchIO +#define _sys_windows_SslAsynchIO + +/* + * + * 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/AsynchIO.h" +#include "qpid/sys/IntegerTypes.h" +#include "qpid/sys/Poller.h" +#include "qpid/CommonImportExport.h" +#include <boost/function.hpp> +#include <boost/shared_ptr.hpp> +#include <windows.h> +// security.h needs to see this to distinguish from kernel use. +#define SECURITY_WIN32 +#include <security.h> +#include <Schnlsp.h> +#undef SECURITY_WIN32 + +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 + * gets involved for SSL negotiations and encrypt/decrypt. The details of + * how this all works are invisible to the layers on either side. The only + * change from normal AsynchIO usage is that there's an extra callback + * from SslAsynchIO to indicate that the initial session negotiation is + * complete. + * + * The details of session negotiation are different for client and server + * SSL roles. These differences are handled by deriving separate client + * and server role classes. + */ +class SslAsynchIO : public qpid::sys::AsynchIO { +public: + typedef boost::function1<void, SECURITY_STATUS> NegotiateDoneCallback; + + SslAsynchIO(const qpid::sys::Socket& s, + CredHandle hCred, + ReadCallback rCb, + EofCallback eofCb, + DisconnectCallback disCb, + ClosedCallback cCb = 0, + BuffersEmptyCallback eCb = 0, + IdleCallback iCb = 0, + NegotiateDoneCallback nCb = 0); + ~SslAsynchIO(); + + virtual void queueForDeletion(); + + virtual void start(qpid::sys::Poller::shared_ptr poller); + virtual void queueReadBuffer(BufferBase* buff); + virtual void unread(BufferBase* buff); + virtual void queueWrite(BufferBase* buff); + virtual void notifyPendingWrite(); + virtual void queueWriteClose(); + virtual bool writeQueueEmpty(); + virtual void startReading(); + virtual void stopReading(); + virtual void requestCallback(RequestCallback); + virtual BufferBase* getQueuedBuffer(); + + QPID_COMMON_EXTERN unsigned int getSslKeySize(); + +protected: + CredHandle credHandle; + + // AsynchIO layer below that's actually doing the I/O + qpid::sys::AsynchIO *aio; + + // Track what the state of the SSL session is. Have to know when it's + // time to notify the upper layer that the session is up, and also to + // know when it's not legit to pass data through to either side. + enum { Negotiating, Running, Redo, ShuttingDown } state; + bool sessionUp; + CtxtHandle ctxtHandle; + TimeStamp credExpiry; + + // Client- and server-side SSL subclasses implement these to do the + // proper negotiation steps. negotiateStep() is called with a buffer + // just received from the peer. + virtual void startNegotiate() = 0; + virtual void negotiateStep(BufferBase *buff) = 0; + + // The negotiating steps call one of these when it's finalized: + void negotiationDone(); + void negotiationFailed(SECURITY_STATUS status); + +private: + // These are callbacks from AsynchIO to here. + void sslDataIn(qpid::sys::AsynchIO& a, BufferBase *buff); + void idle(qpid::sys::AsynchIO&); + + // These callbacks are to the layer above. + ReadCallback readCallback; + IdleCallback idleCallback; + NegotiateDoneCallback negotiateDoneCallback; + volatile unsigned int callbacksInProgress; // >0 if w/in callbacks + volatile bool queuedDelete; + + // Address of peer, in case it's needed for logging. + std::string peerAddress; + + // Partial buffer of decrypted plaintext given back by the layer above. + AsynchIO::BufferBase *leftoverPlaintext; + + SecPkgContext_StreamSizes schSizes; +}; + +/* + * SSL/Schannel client-side shim between the frame-handling and AsynchIO + * layers. + */ +class ClientSslAsynchIO : public SslAsynchIO { +public: + // Args same as for SslIoShim, with the addition of brokerHost which is + // the expected SSL name of the server. + QPID_COMMON_EXTERN ClientSslAsynchIO(const std::string& brokerHost, + const qpid::sys::Socket& s, + CredHandle hCred, + ReadCallback rCb, + EofCallback eofCb, + DisconnectCallback disCb, + ClosedCallback cCb = 0, + BuffersEmptyCallback eCb = 0, + IdleCallback iCb = 0, + NegotiateDoneCallback nCb = 0); + +private: + std::string serverHost; + + // Client- and server-side SSL subclasses implement these to do the + // proper negotiation steps. negotiateStep() is called with a buffer + // just received from the peer. + void startNegotiate(); + void negotiateStep(BufferBase *buff); +}; +/* + * SSL/Schannel server-side shim between the frame-handling and AsynchIO + * layers. + */ +class ServerSslAsynchIO : public SslAsynchIO { +public: + QPID_COMMON_EXTERN ServerSslAsynchIO(bool clientMustAuthenticate, + const qpid::sys::Socket& s, + CredHandle hCred, + ReadCallback rCb, + EofCallback eofCb, + DisconnectCallback disCb, + ClosedCallback cCb = 0, + BuffersEmptyCallback eCb = 0, + IdleCallback iCb = 0, + NegotiateDoneCallback nCb = 0); + +private: + bool clientAuth; + + // Client- and server-side SSL subclasses implement these to do the + // proper negotiation steps. negotiateStep() is called with a buffer + // just received from the peer. + void startNegotiate(); + void negotiateStep(BufferBase *buff); +}; + +}}} // namespace qpid::sys::windows + +#endif // _sys_windows_SslAsynchIO diff --git a/qpid/cpp/src/qpid/sys/windows/StrError.cpp b/qpid/cpp/src/qpid/sys/windows/StrError.cpp new file mode 100755 index 0000000000..546d399d16 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/windows/StrError.cpp @@ -0,0 +1,52 @@ +/* + * + * 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/StrError.h" +#include <string> +#include <string.h> +#include <windows.h> + +namespace qpid { +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, + err, + 0, // Default language + buf, + bufsize, + 0)) + { +#ifdef _MSC_VER + strerror_s(buf, bufsize, err); +#else + return std::string(strerror(err)); +#endif + } + return std::string(buf); +} + +}} diff --git a/qpid/cpp/src/qpid/sys/windows/SystemInfo.cpp b/qpid/cpp/src/qpid/sys/windows/SystemInfo.cpp new file mode 100755 index 0000000000..4da440bdd4 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/windows/SystemInfo.cpp @@ -0,0 +1,203 @@ +/* + * 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. + * + */ + +/* GetNativeSystemInfo call requires _WIN32_WINNT 0x0501 or higher */ +#ifndef _WIN32_WINNT +# define _WIN32_WINNT 0x0501 +#endif + +#include "qpid/sys/IntegerTypes.h" +#include "qpid/sys/SystemInfo.h" + +#include <winsock2.h> +#include <ws2tcpip.h> +#include <windows.h> +#include <tlhelp32.h> + +#ifndef HOST_NAME_MAX +# define HOST_NAME_MAX 256 +#endif + +namespace qpid { +namespace sys { + +long SystemInfo::concurrency() { + SYSTEM_INFO sys_info; + ::GetSystemInfo (&sys_info); + long activeProcessors = 0; + DWORD_PTR mask = sys_info.dwActiveProcessorMask; + while (mask != 0) { + if (mask & 1) + ++activeProcessors; + mask >>= 1; + } + return activeProcessors; +} + +bool SystemInfo::getLocalHostname (Address &address) { + char name[HOST_NAME_MAX]; + if (::gethostname(name, sizeof(name)) != 0) { + errno = WSAGetLastError(); + return false; + } + address.host = name; + return true; +} + +static const std::string LOCALHOST("127.0.0.1"); +static const std::string TCP("tcp"); + +void SystemInfo::getLocalIpAddresses (uint16_t port, + std::vector<Address> &addrList) { + enum { MAX_URL_INTERFACES = 100 }; + + SOCKET s = socket (PF_INET, SOCK_STREAM, 0); + if (s != INVALID_SOCKET) { + INTERFACE_INFO interfaces[MAX_URL_INTERFACES]; + DWORD filledBytes = 0; + WSAIoctl (s, + SIO_GET_INTERFACE_LIST, + 0, + 0, + interfaces, + sizeof (interfaces), + &filledBytes, + 0, + 0); + unsigned int interfaceCount = filledBytes / sizeof (INTERFACE_INFO); + for (unsigned int i = 0; i < interfaceCount; ++i) { + if (interfaces[i].iiFlags & IFF_UP) { + std::string addr(inet_ntoa(interfaces[i].iiAddress.AddressIn.sin_addr)); + if (addr != LOCALHOST) + addrList.push_back(Address(TCP, addr, port)); + } + } + closesocket (s); + } +} + +void SystemInfo::getSystemId (std::string &osName, + std::string &nodeName, + std::string &release, + std::string &version, + std::string &machine) +{ + osName = "Microsoft Windows"; + + char node[MAX_COMPUTERNAME_LENGTH + 1]; + DWORD nodelen = MAX_COMPUTERNAME_LENGTH + 1; + GetComputerName (node, &nodelen); + nodeName = node; + + OSVERSIONINFOEX vinfo; + vinfo.dwOSVersionInfoSize = sizeof(vinfo); + GetVersionEx ((OSVERSIONINFO *)&vinfo); + + SYSTEM_INFO sinfo; + GetNativeSystemInfo(&sinfo); + + switch(vinfo.dwMajorVersion) { + case 5: + switch(vinfo.dwMinorVersion) { + case 0: + release ="2000"; + break; + case 1: + release = "XP"; + break; + case 2: + if (sinfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 || + sinfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64) + release = "XP-64"; + else + release = "Server 2003"; + break; + default: + release = "Windows"; + } + break; + case 6: + if (vinfo.wProductType == VER_NT_SERVER) + release = "Server 2008"; + else + release = "Vista"; + break; + default: + release = "Microsoft Windows"; + } + version = vinfo.szCSDVersion; + + switch(sinfo.wProcessorArchitecture) { + case PROCESSOR_ARCHITECTURE_AMD64: + machine = "x86-64"; + break; + case PROCESSOR_ARCHITECTURE_IA64: + machine = "IA64"; + break; + case PROCESSOR_ARCHITECTURE_INTEL: + machine = "x86"; + break; + default: + machine = "unknown"; + break; + } +} + +uint32_t SystemInfo::getProcessId() +{ + return static_cast<uint32_t>(::GetCurrentProcessId()); +} + +uint32_t SystemInfo::getParentProcessId() +{ + // Only want info for the current process, so ask for something specific. + // The module info won't be used here but it keeps the snapshot limited to + // the current process so a search through all processes is not needed. + HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0); + if (snap == INVALID_HANDLE_VALUE) + return 0; + PROCESSENTRY32 entry; + entry.dwSize = sizeof(entry); + if (!Process32First(snap, &entry)) + entry.th32ParentProcessID = 0; + CloseHandle(snap); + return static_cast<uint32_t>(entry.th32ParentProcessID); +} + +std::string SystemInfo::getProcessName() +{ + std::string name; + + // Only want info for the current process, so ask for something specific. + // The module info won't be used here but it keeps the snapshot limited to + // the current process so a search through all processes is not needed. + HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0); + if (snap == INVALID_HANDLE_VALUE) + return name; + PROCESSENTRY32 entry; + entry.dwSize = sizeof(entry); + if (!Process32First(snap, &entry)) + entry.szExeFile[0] = '\0'; + CloseHandle(snap); + name = entry.szExeFile; + return name; +} + +}} // namespace qpid::sys diff --git a/qpid/cpp/src/qpid/sys/windows/Thread.cpp b/qpid/cpp/src/qpid/sys/windows/Thread.cpp new file mode 100755 index 0000000000..583a9613a3 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/windows/Thread.cpp @@ -0,0 +1,100 @@ +/* + * + * 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/Thread.h" +#include "qpid/sys/Runnable.h" +#include "qpid/sys/windows/check.h" + +#include <process.h> +#include <windows.h> + +namespace { +unsigned __stdcall runRunnable(void* p) +{ + static_cast<qpid::sys::Runnable*>(p)->run(); + _endthreadex(0); + return 0; +} +} + +namespace qpid { +namespace sys { + +class ThreadPrivate { + friend class Thread; + + HANDLE threadHandle; + 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); + } + + ThreadPrivate() + : threadHandle(GetCurrentThread()), threadId(GetCurrentThreadId()) {} +}; + +Thread::Thread() {} + +Thread::Thread(Runnable* runnable) : impl(new ThreadPrivate(runnable)) {} + +Thread::Thread(Runnable& runnable) : impl(new ThreadPrivate(&runnable)) {} + +Thread::operator bool() { + return impl; +} + +bool Thread::operator==(const Thread& t) const { + return impl->threadId == t.impl->threadId; +} + +bool Thread::operator!=(const Thread& t) const { + return !(*this==t); +} + +void Thread::join() { + if (impl) { + DWORD status = WaitForSingleObject (impl->threadHandle, INFINITE); + QPID_WINDOWS_CHECK_NOT(status, WAIT_FAILED); + CloseHandle (impl->threadHandle); + impl->threadHandle = 0; + } +} + +unsigned long Thread::logId() { + return GetCurrentThreadId(); +} + +/* static */ +Thread Thread::current() { + Thread t; + t.impl.reset(new ThreadPrivate()); + return t; +} + +}} /* qpid::sys */ diff --git a/qpid/cpp/src/qpid/sys/windows/Time.cpp b/qpid/cpp/src/qpid/sys/windows/Time.cpp new file mode 100644 index 0000000000..25c50819cd --- /dev/null +++ b/qpid/cpp/src/qpid/sys/windows/Time.cpp @@ -0,0 +1,136 @@ +/* + * + * 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" +#include <ostream> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/thread/thread_time.hpp> +#include <windows.h> + +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 { + +AbsTime::AbsTime(const AbsTime& t, const Duration& d) { + if (d == Duration::max()) { + timepoint = ptime(max_date_time); + } + else { + time_duration td = microseconds(d.nanosecs / 1000); + timepoint = t.timepoint + td; + } +} + +AbsTime AbsTime::FarFuture() { + AbsTime ff; + ptime maxd(max_date_time); + ff.timepoint = maxd; + return ff; +} + +AbsTime AbsTime::Epoch() { + AbsTime time_epoch; + time_epoch.timepoint = boost::posix_time::from_time_t(0); + return time_epoch; +} + +AbsTime AbsTime::now() { + AbsTime time_now; + time_now.timepoint = boost::get_system_time(); + return time_now; +} + +Duration::Duration(const AbsTime& start, const AbsTime& finish) { + time_duration d = finish.timepoint - start.timepoint; + nanosecs = d.total_nanoseconds(); +} + +std::ostream& operator<<(std::ostream& o, const Duration& d) { + return o << int64_t(d) << "ns"; +} + +std::ostream& operator<<(std::ostream& o, const AbsTime& t) { + std::string time_string = to_simple_string(t.timepoint); + return o << time_string; +} + + +void sleep(int secs) { + ::Sleep(secs * 1000); +} + +void usleep(uint64_t usecs) { + DWORD msecs = usecs / 1000; + if (msecs == 0) + msecs = 1; + ::Sleep(msecs); +} + +void outputFormattedNow(std::ostream& o) { + ::time_t rawtime; + ::tm timeinfo; + 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/qpid/cpp/src/qpid/sys/windows/mingw32_compat.h b/qpid/cpp/src/qpid/sys/windows/mingw32_compat.h new file mode 100644 index 0000000000..51f613cc25 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/windows/mingw32_compat.h @@ -0,0 +1,39 @@ +#ifndef _sys_windows_mingw32_compat +#define _sys_windows_mingw32_compat +/* + * + * 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. + * + */ + +#ifdef WIN32 +#ifndef _MSC_VER + +// +// 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/qpid/cpp/src/qpid/sys/windows/uuid.cpp b/qpid/cpp/src/qpid/sys/windows/uuid.cpp new file mode 100644 index 0000000000..3316ecbc00 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/windows/uuid.cpp @@ -0,0 +1,67 @@ +/* + * + * 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 <rpc.h> +#ifdef uuid_t /* Done in rpcdce.h */ +# undef uuid_t +#endif + +#include "qpid/sys/windows/uuid.h" + +#include <string.h> + +void uuid_clear (uuid_t uu) { + UuidCreateNil (reinterpret_cast<UUID*>(uu)); +} + +void uuid_copy (uuid_t dst, const uuid_t src) { + memcpy (dst, src, qpid::sys::UuidSize); +} + +void uuid_generate (uuid_t out) { + UuidCreate (reinterpret_cast<UUID*>(out)); +} + +int uuid_is_null (const uuid_t uu) { + RPC_STATUS unused; + return UuidIsNil ((UUID*)uu, &unused); +} + +int uuid_parse (const char *in, uuid_t uu) { + return UuidFromString ((unsigned char*)in, (UUID*)uu) == RPC_S_OK ? 0 : -1; +} + +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); + } +} + +int uuid_compare (const uuid_t a, const uuid_t b) { + RPC_STATUS unused; + return !UuidEqual((UUID*)a, (UUID*)b, &unused); +} diff --git a/qpid/cpp/src/qpid/sys/windows/uuid.h b/qpid/cpp/src/qpid/sys/windows/uuid.h new file mode 100644 index 0000000000..8ab132e9ce --- /dev/null +++ b/qpid/cpp/src/qpid/sys/windows/uuid.h @@ -0,0 +1,39 @@ +#ifndef _sys_windows_uuid_h +#define _sys_windows_uuid_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/types/ImportExport.h" +#include <qpid/sys/IntegerTypes.h> + +namespace qpid { namespace sys { const size_t UuidSize = 16; }} +typedef uint8_t uuid_t[qpid::sys::UuidSize]; + +QPID_TYPES_EXTERN void uuid_clear (uuid_t uu); +QPID_TYPES_EXTERN void uuid_copy (uuid_t dst, const uuid_t src); +QPID_TYPES_EXTERN void uuid_generate (uuid_t out); +QPID_TYPES_EXTERN int uuid_is_null (const uuid_t uu); // Returns 1 if null, else 0 +QPID_TYPES_EXTERN int uuid_parse (const char *in, uuid_t uu); // Returns 0 on success, else -1 +QPID_TYPES_EXTERN void uuid_unparse (const uuid_t uu, char *out); +QPID_TYPES_EXTERN int uuid_compare (const uuid_t a, const uuid_t b); + +#endif /*!_sys_windows_uuid_h*/ diff --git a/qpid/cpp/src/qpid/types/Exception.cpp b/qpid/cpp/src/qpid/types/Exception.cpp new file mode 100644 index 0000000000..71390e6abd --- /dev/null +++ b/qpid/cpp/src/qpid/types/Exception.cpp @@ -0,0 +1,30 @@ +/* + * + * 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/types/Exception.h" + +namespace qpid { +namespace types { + +Exception::Exception(const std::string& msg) throw() : message(msg) {} +Exception::~Exception() throw() {} +const char* Exception::what() const throw() { return message.c_str(); } + +}} // namespace qpid::types diff --git a/qpid/cpp/src/qpid/types/Uuid.cpp b/qpid/cpp/src/qpid/types/Uuid.cpp new file mode 100644 index 0000000000..9face4e5d2 --- /dev/null +++ b/qpid/cpp/src/qpid/types/Uuid.cpp @@ -0,0 +1,140 @@ +/* + * + * 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/types/Uuid.h" +#include "qpid/sys/uuid.h" +#include <sstream> +#include <iostream> +#include <string.h> + +namespace qpid { +namespace types { + +using namespace std; + +const size_t Uuid::SIZE=16; +static const size_t UNPARSED_SIZE=36; + +Uuid::Uuid(bool unique) +{ + if (unique) { + generate(); + } else { + clear(); + } +} + +Uuid::Uuid(const Uuid& other) +{ + ::memcpy(bytes, other.bytes, Uuid::SIZE); +} + +Uuid::Uuid(const unsigned char* uuid) +{ + ::memcpy(bytes, uuid, Uuid::SIZE); +} + +Uuid& Uuid::operator=(const Uuid& other) +{ + if (this == &other) return *this; + ::memcpy(bytes, other.bytes, Uuid::SIZE); + return *this; +} + +void Uuid::generate() +{ + uuid_generate(bytes); +} + +void Uuid::clear() +{ + uuid_clear(bytes); +} + +// Force int 0/!0 to false/true; avoids compile warnings. +bool Uuid::isNull() const +{ + return !!uuid_is_null(bytes); +} + +Uuid::operator bool() const { return !isNull(); } +bool Uuid::operator!() const { return isNull(); } + +size_t Uuid::size() const { return SIZE; } + +const unsigned char* Uuid::data() const +{ + return bytes; +} + +bool operator==(const Uuid& a, const Uuid& b) +{ + return uuid_compare(a.bytes, b.bytes) == 0; +} + +bool operator!=(const Uuid& a, const Uuid& b) +{ + return !(a == b); +} + +bool operator<(const Uuid& a, const Uuid& b) +{ + return uuid_compare(a.bytes, b.bytes) < 0; +} + +bool operator>(const Uuid& a, const Uuid& b) +{ + return uuid_compare(a.bytes, b.bytes) > 0; +} + +bool operator<=(const Uuid& a, const Uuid& b) +{ + return uuid_compare(a.bytes, b.bytes) <= 0; +} + +bool operator>=(const Uuid& a, const Uuid& b) +{ + return uuid_compare(a.bytes, b.bytes) >= 0; +} + +ostream& operator<<(ostream& out, Uuid uuid) +{ + char unparsed[UNPARSED_SIZE + 1]; + uuid_unparse(uuid.bytes, unparsed); + return out << unparsed; +} + +istream& operator>>(istream& in, Uuid& uuid) +{ + char unparsed[UNPARSED_SIZE + 1] = {0}; + in.get(unparsed, sizeof(unparsed)); + if (uuid_parse(unparsed, uuid.bytes) != 0) + in.setstate(ios::failbit); + return in; +} + +std::string Uuid::str() const +{ + std::ostringstream os; + os << *this; + return os.str(); +} + +}} // namespace qpid::types diff --git a/qpid/cpp/src/qpid/types/Variant.cpp b/qpid/cpp/src/qpid/types/Variant.cpp new file mode 100644 index 0000000000..0b28234025 --- /dev/null +++ b/qpid/cpp/src/qpid/types/Variant.cpp @@ -0,0 +1,890 @@ +/* + * + * 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/types/Variant.h" +#include "qpid/Msg.h" +#include "qpid/log/Statement.h" +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/algorithm/string.hpp> +#include <algorithm> +#include <limits> +#include <sstream> + +namespace qpid { +namespace types { + +namespace { +const std::string EMPTY; +const std::string PREFIX("invalid conversion: "); +} + +InvalidConversion::InvalidConversion(const std::string& msg) : Exception(PREFIX + msg) {} + +class VariantImpl +{ + public: + VariantImpl(); + VariantImpl(bool); + VariantImpl(uint8_t); + VariantImpl(uint16_t); + VariantImpl(uint32_t); + VariantImpl(uint64_t); + VariantImpl(int8_t); + VariantImpl(int16_t); + VariantImpl(int32_t); + VariantImpl(int64_t); + VariantImpl(float); + VariantImpl(double); + VariantImpl(const std::string&, const std::string& encoding=std::string()); + VariantImpl(const Variant::Map&); + VariantImpl(const Variant::List&); + VariantImpl(const Uuid&); + ~VariantImpl(); + + VariantType getType() const; + + bool asBool() const; + uint8_t asUint8() const; + uint16_t asUint16() const; + uint32_t asUint32() const; + uint64_t asUint64() const; + int8_t asInt8() const; + int16_t asInt16() const; + int32_t asInt32() const; + int64_t asInt64() const; + float asFloat() const; + double asDouble() const; + std::string asString() const; + Uuid asUuid() const; + + const Variant::Map& asMap() const; + Variant::Map& asMap(); + const Variant::List& asList() const; + Variant::List& asList(); + + const std::string& getString() const; + std::string& getString(); + + void setEncoding(const std::string&); + const std::string& getEncoding() const; + + bool isEqualTo(VariantImpl&) const; + bool isEquivalentTo(VariantImpl&) const; + + static VariantImpl* create(const Variant&); + private: + const VariantType type; + union { + bool b; + uint8_t ui8; + uint16_t ui16; + uint32_t ui32; + uint64_t ui64; + int8_t i8; + int16_t i16; + int32_t i32; + int64_t i64; + float f; + double d; + void* v;//variable width data + } value; + std::string encoding;//optional encoding for variable length data + + template<class T> T convertFromString() const + { + std::string* s = reinterpret_cast<std::string*>(value.v); + 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)); + } +}; + + +VariantImpl::VariantImpl() : type(VAR_VOID) { value.i64 = 0; } +VariantImpl::VariantImpl(bool b) : type(VAR_BOOL) { value.b = b; } +VariantImpl::VariantImpl(uint8_t i) : type(VAR_UINT8) { value.ui8 = i; } +VariantImpl::VariantImpl(uint16_t i) : type(VAR_UINT16) { value.ui16 = i; } +VariantImpl::VariantImpl(uint32_t i) : type(VAR_UINT32) { value.ui32 = i; } +VariantImpl::VariantImpl(uint64_t i) : type(VAR_UINT64) { value.ui64 = i; } +VariantImpl::VariantImpl(int8_t i) : type(VAR_INT8) { value.i8 = i; } +VariantImpl::VariantImpl(int16_t i) : type(VAR_INT16) { value.i16 = i; } +VariantImpl::VariantImpl(int32_t i) : type(VAR_INT32) { value.i32 = i; } +VariantImpl::VariantImpl(int64_t i) : type(VAR_INT64) { value.i64 = i; } +VariantImpl::VariantImpl(float f) : type(VAR_FLOAT) { value.f = f; } +VariantImpl::VariantImpl(double d) : type(VAR_DOUBLE) { value.d = d; } +VariantImpl::VariantImpl(const std::string& s, const std::string& e) + : type(VAR_STRING), encoding(e) { value.v = new std::string(s); } +VariantImpl::VariantImpl(const Variant::Map& m) : type(VAR_MAP) { value.v = new Variant::Map(m); } +VariantImpl::VariantImpl(const Variant::List& l) : type(VAR_LIST) { value.v = new Variant::List(l); } +VariantImpl::VariantImpl(const Uuid& u) : type(VAR_UUID) { value.v = new Uuid(u); } + +VariantImpl::~VariantImpl() { + switch (type) { + case VAR_STRING: + delete reinterpret_cast<std::string*>(value.v); + break; + case VAR_MAP: + delete reinterpret_cast<Variant::Map*>(value.v); + break; + case VAR_LIST: + delete reinterpret_cast<Variant::List*>(value.v); + break; + case VAR_UUID: + delete reinterpret_cast<Uuid*>(value.v); + break; + default: + break; + } +} + +VariantType VariantImpl::getType() const { return type; } + +namespace { + +bool same_char(char a, char b) +{ + return toupper(a) == toupper(b); +} + +bool caseInsensitiveMatch(const std::string& a, const std::string& b) +{ + return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin(), &same_char); +} + +const std::string TRUE("True"); +const std::string FALSE("False"); + +bool toBool(const std::string& s) +{ + if (caseInsensitiveMatch(s, TRUE)) return true; + if (caseInsensitiveMatch(s, FALSE)) return false; + try { return boost::lexical_cast<int>(s); } catch(const boost::bad_lexical_cast&) {} + throw InvalidConversion(QPID_MSG("Cannot convert " << s << " to bool")); +} + +template <class T> std::string toString(const T& t) +{ + std::stringstream out; + out << t; + return out.str(); +} + +template <class T> bool equal(const T& a, const T& b) +{ + return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin()); +} + +} + +bool VariantImpl::asBool() const +{ + switch(type) { + case VAR_VOID: return false; + case VAR_BOOL: return value.b; + case VAR_UINT8: return value.ui8; + case VAR_UINT16: return value.ui16; + case VAR_UINT32: return value.ui32; + case VAR_UINT64: return value.ui64; + case VAR_INT8: return value.i8; + case VAR_INT16: return value.i16; + case VAR_INT32: return value.i32; + case VAR_INT64: return value.i64; + case VAR_STRING: return toBool(*reinterpret_cast<std::string*>(value.v)); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_BOOL))); + } +} +uint8_t VariantImpl::asUint8() const +{ + switch(type) { + case VAR_UINT8: return value.ui8; + case VAR_UINT16: + if (value.ui16 <= 0x00ff) + return uint8_t(value.ui16); + break; + case VAR_UINT32: + if (value.ui32 <= 0x000000ff) + return uint8_t(value.ui32); + break; + case VAR_UINT64: + if (value.ui64 <= 0x00000000000000ff) + return uint8_t(value.ui64); + break; + case VAR_INT8: + if (value.i8 >= 0) + return uint8_t(value.i8); + break; + case VAR_INT16: + if (value.i16 >= 0 && value.i16 <= 0x00ff) + return uint8_t(value.i16); + break; + case VAR_INT32: + if (value.i32 >= 0 && value.i32 <= 0x000000ff) + return uint8_t(value.i32); + break; + case VAR_INT64: + if (value.i64 >= 0 && value.i64 <= 0x00000000000000ff) + return uint8_t(value.i64); + break; + case VAR_STRING: return convertFromString<uint8_t>(); + default: break; + } + throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_UINT8))); +} +uint16_t VariantImpl::asUint16() const +{ + switch(type) { + case VAR_UINT8: return value.ui8; + case VAR_UINT16: return value.ui16; + case VAR_UINT32: + if (value.ui32 <= 0x0000ffff) + return uint16_t(value.ui32); + break; + case VAR_UINT64: + if (value.ui64 <= 0x000000000000ffff) + return uint16_t(value.ui64); + break; + case VAR_INT8: + if (value.i8 >= 0) + return uint16_t(value.i8); + break; + case VAR_INT16: + if (value.i16 >= 0) + return uint16_t(value.i16); + break; + case VAR_INT32: + if (value.i32 >= 0 && value.i32 <= 0x0000ffff) + return uint16_t(value.i32); + break; + case VAR_INT64: + if (value.i64 >= 0 && value.i64 <= 0x000000000000ffff) + return uint16_t(value.i64); + break; + case VAR_STRING: return convertFromString<uint16_t>(); + default: break; + } + throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_UINT16))); +} +uint32_t VariantImpl::asUint32() const +{ + switch(type) { + case VAR_UINT8: return value.ui8; + case VAR_UINT16: return value.ui16; + case VAR_UINT32: return value.ui32; + case VAR_UINT64: + if (value.ui64 <= 0x00000000ffffffff) + return uint32_t(value.ui64); + break; + case VAR_INT8: + if (value.i8 >= 0) + return uint32_t(value.i8); + break; + case VAR_INT16: + if (value.i16 >= 0) + return uint32_t(value.i16); + break; + case VAR_INT32: + if (value.i32 >= 0) + return uint32_t(value.i32); + break; + case VAR_INT64: + if (value.i64 >= 0 && value.i64 <= 0x00000000ffffffff) + return uint32_t(value.i64); + break; + case VAR_STRING: return convertFromString<uint32_t>(); + default: break; + } + throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_UINT32))); +} +uint64_t VariantImpl::asUint64() const +{ + switch(type) { + case VAR_UINT8: return value.ui8; + case VAR_UINT16: return value.ui16; + case VAR_UINT32: return value.ui32; + case VAR_UINT64: return value.ui64; + case VAR_INT8: + if (value.i8 >= 0) + return uint64_t(value.i8); + break; + case VAR_INT16: + if (value.i16 >= 0) + return uint64_t(value.i16); + break; + case VAR_INT32: + if (value.i32 >= 0) + return uint64_t(value.i32); + break; + case VAR_INT64: + if (value.i64 >= 0) + return uint64_t(value.i64); + break; + case VAR_STRING: return convertFromString<uint64_t>(); + default: break; + } + throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_UINT64))); +} + +int8_t VariantImpl::asInt8() const +{ + switch(type) { + case VAR_INT8: return value.i8; + case VAR_INT16: + if ((value.i16 >= std::numeric_limits<int8_t>::min()) && (value.i16 <= std::numeric_limits<int8_t>::max())) + return int8_t(value.i16); + break; + case VAR_INT32: + if ((value.i32 >= std::numeric_limits<int8_t>::min()) && (value.i32 <= std::numeric_limits<int8_t>::max())) + return int8_t(value.i32); + break; + case VAR_INT64: + if ((value.i64 >= std::numeric_limits<int8_t>::min()) && (value.i64 <= std::numeric_limits<int8_t>::max())) + return int8_t(value.i64); + break; + case VAR_UINT8: + if (value.ui8 <= std::numeric_limits<int8_t>::max()) + return int8_t(value.ui8); + break; + case VAR_UINT16: + if (value.ui16 <= std::numeric_limits<int8_t>::max()) + return int8_t(value.ui16); + break; + case VAR_UINT32: + if (value.ui32 <= (uint32_t) std::numeric_limits<int8_t>::max()) + return int8_t(value.ui32); + break; + case VAR_UINT64: + if (value.ui64 <= (uint64_t) std::numeric_limits<int8_t>::max()) + return int8_t(value.ui64); + break; + case VAR_STRING: return convertFromString<int8_t>(); + default: break; + } + throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_INT8))); +} +int16_t VariantImpl::asInt16() const +{ + switch(type) { + case VAR_INT8: return value.i8; + case VAR_INT16: return value.i16; + case VAR_INT32: + if ((value.i32 >= std::numeric_limits<int16_t>::min()) && (value.i32 <= std::numeric_limits<int16_t>::max())) + return int16_t(value.i32); + break; + case VAR_INT64: + if ((value.i64 >= std::numeric_limits<int16_t>::min()) && (value.i64 <= std::numeric_limits<int16_t>::max())) + return int16_t(value.i64); + break; + case VAR_UINT8: return int16_t(value.ui8); + case VAR_UINT16: + if (value.ui16 <= std::numeric_limits<int16_t>::max()) + return int16_t(value.ui16); + break; + case VAR_UINT32: + if (value.ui32 <= (uint32_t) std::numeric_limits<int16_t>::max()) + return int16_t(value.ui32); + break; + case VAR_UINT64: + if (value.ui64 <= (uint64_t) std::numeric_limits<int16_t>::max()) + return int16_t(value.ui64); + break; + case VAR_STRING: return convertFromString<int16_t>(); + default: break; + } + throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_INT16))); +} +int32_t VariantImpl::asInt32() const +{ + switch(type) { + case VAR_INT8: return value.i8; + case VAR_INT16: return value.i16; + case VAR_INT32: return value.i32; + case VAR_INT64: + if ((value.i64 >= std::numeric_limits<int32_t>::min()) && (value.i64 <= std::numeric_limits<int32_t>::max())) + return int32_t(value.i64); + break; + case VAR_UINT8: return int32_t(value.ui8); + case VAR_UINT16: return int32_t(value.ui16); + case VAR_UINT32: + if (value.ui32 <= (uint32_t) std::numeric_limits<int32_t>::max()) + return int32_t(value.ui32); + break; + case VAR_UINT64: + if (value.ui64 <= (uint64_t) std::numeric_limits<int32_t>::max()) + return int32_t(value.ui64); + break; + case VAR_STRING: return convertFromString<int32_t>(); + default: break; + } + throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_INT32))); +} +int64_t VariantImpl::asInt64() const +{ + switch(type) { + case VAR_INT8: return value.i8; + case VAR_INT16: return value.i16; + case VAR_INT32: return value.i32; + case VAR_INT64: return value.i64; + case VAR_UINT8: return int64_t(value.ui8); + case VAR_UINT16: return int64_t(value.ui16); + case VAR_UINT32: return int64_t(value.ui32); + case VAR_UINT64: + if (value.ui64 <= (uint64_t) std::numeric_limits<int64_t>::max()) + return int64_t(value.ui64); + break; + case VAR_STRING: return convertFromString<int64_t>(); + default: break; + } + throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_INT64))); +} +float VariantImpl::asFloat() const +{ + switch(type) { + case VAR_FLOAT: return value.f; + case VAR_STRING: return convertFromString<float>(); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_FLOAT))); + } +} +double VariantImpl::asDouble() const +{ + switch(type) { + case VAR_FLOAT: return value.f; + case VAR_DOUBLE: return value.d; + case VAR_STRING: return convertFromString<double>(); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_DOUBLE))); + } +} +std::string VariantImpl::asString() const +{ + switch(type) { + case VAR_VOID: return EMPTY; + case VAR_BOOL: return value.b ? TRUE : FALSE; + case VAR_UINT8: return boost::lexical_cast<std::string>((int) value.ui8); + case VAR_UINT16: return boost::lexical_cast<std::string>(value.ui16); + case VAR_UINT32: return boost::lexical_cast<std::string>(value.ui32); + case VAR_UINT64: return boost::lexical_cast<std::string>(value.ui64); + case VAR_INT8: return boost::lexical_cast<std::string>((int) value.i8); + case VAR_INT16: return boost::lexical_cast<std::string>(value.i16); + case VAR_INT32: return boost::lexical_cast<std::string>(value.i32); + case VAR_INT64: return boost::lexical_cast<std::string>(value.i64); + case VAR_DOUBLE: return boost::lexical_cast<std::string>(value.d); + case VAR_FLOAT: return boost::lexical_cast<std::string>(value.f); + case VAR_STRING: return *reinterpret_cast<std::string*>(value.v); + case VAR_UUID: return reinterpret_cast<Uuid*>(value.v)->str(); + case VAR_LIST: return toString(asList()); + case VAR_MAP: return toString(asMap()); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_STRING))); + } +} +Uuid VariantImpl::asUuid() const +{ + switch(type) { + case VAR_UUID: return *reinterpret_cast<Uuid*>(value.v); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_UUID))); + } +} + +bool VariantImpl::isEqualTo(VariantImpl& other) const +{ + if (type == other.type) { + switch(type) { + case VAR_VOID: return true; + case VAR_BOOL: return value.b == other.value.b; + case VAR_UINT8: return value.ui8 == other.value.ui8; + case VAR_UINT16: return value.ui16 == other.value.ui16; + case VAR_UINT32: return value.ui32 == other.value.ui32; + case VAR_UINT64: return value.ui64 == other.value.ui64; + case VAR_INT8: return value.i8 == other.value.i8; + case VAR_INT16: return value.i16 == other.value.i16; + case VAR_INT32: return value.i32 == other.value.i32; + case VAR_INT64: return value.i64 == other.value.i64; + case VAR_DOUBLE: return value.d == other.value.d; + case VAR_FLOAT: return value.f == other.value.f; + case VAR_STRING: return *reinterpret_cast<std::string*>(value.v) + == *reinterpret_cast<std::string*>(other.value.v); + case VAR_UUID: return *reinterpret_cast<Uuid*>(value.v) + == *reinterpret_cast<Uuid*>(other.value.v); + case VAR_LIST: return equal(asList(), other.asList()); + case VAR_MAP: return equal(asMap(), other.asMap()); + } + } + return false; +} + +const Variant::Map& VariantImpl::asMap() const +{ + switch(type) { + case VAR_MAP: return *reinterpret_cast<Variant::Map*>(value.v); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_MAP))); + } +} + +Variant::Map& VariantImpl::asMap() +{ + switch(type) { + case VAR_MAP: return *reinterpret_cast<Variant::Map*>(value.v); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_MAP))); + } +} + +const Variant::List& VariantImpl::asList() const +{ + switch(type) { + case VAR_LIST: return *reinterpret_cast<Variant::List*>(value.v); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_LIST))); + } +} + +Variant::List& VariantImpl::asList() +{ + switch(type) { + case VAR_LIST: return *reinterpret_cast<Variant::List*>(value.v); + default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_LIST))); + } +} + +std::string& VariantImpl::getString() +{ + switch(type) { + case VAR_STRING: return *reinterpret_cast<std::string*>(value.v); + default: throw InvalidConversion(QPID_MSG("Variant is not a string; use asString() if conversion is required.")); + } +} + +const std::string& VariantImpl::getString() const +{ + switch(type) { + case VAR_STRING: return *reinterpret_cast<std::string*>(value.v); + default: throw InvalidConversion(QPID_MSG("Variant is not a string; use asString() if conversion is required.")); + } +} + +void VariantImpl::setEncoding(const std::string& s) { encoding = s; } +const std::string& VariantImpl::getEncoding() const { return encoding; } + +std::string getTypeName(VariantType type) +{ + switch (type) { + case VAR_VOID: return "void"; + case VAR_BOOL: return "bool"; + case VAR_UINT8: return "uint8"; + case VAR_UINT16: return "uint16"; + case VAR_UINT32: return "uint32"; + case VAR_UINT64: return "uint64"; + case VAR_INT8: return "int8"; + case VAR_INT16: return "int16"; + case VAR_INT32: return "int32"; + case VAR_INT64: return "int64"; + case VAR_FLOAT: return "float"; + case VAR_DOUBLE: return "double"; + case VAR_STRING: return "string"; + case VAR_MAP: return "map"; + case VAR_LIST: return "list"; + case VAR_UUID: return "uuid"; + } + return "<unknown>";//should never happen +} + +VariantImpl* VariantImpl::create(const Variant& v) +{ + switch (v.getType()) { + case VAR_BOOL: return new VariantImpl(v.asBool()); + case VAR_UINT8: return new VariantImpl(v.asUint8()); + case VAR_UINT16: return new VariantImpl(v.asUint16()); + case VAR_UINT32: return new VariantImpl(v.asUint32()); + case VAR_UINT64: return new VariantImpl(v.asUint64()); + case VAR_INT8: return new VariantImpl(v.asInt8()); + case VAR_INT16: return new VariantImpl(v.asInt16()); + case VAR_INT32: return new VariantImpl(v.asInt32()); + case VAR_INT64: return new VariantImpl(v.asInt64()); + case VAR_FLOAT: return new VariantImpl(v.asFloat()); + case VAR_DOUBLE: return new VariantImpl(v.asDouble()); + case VAR_STRING: return new VariantImpl(v.asString(), v.getEncoding()); + case VAR_MAP: return new VariantImpl(v.asMap()); + case VAR_LIST: return new VariantImpl(v.asList()); + case VAR_UUID: return new VariantImpl(v.asUuid()); + default: return new VariantImpl(); + } +} + +Variant::Variant() : impl(0) {} +Variant::Variant(bool b) : impl(new VariantImpl(b)) {} +Variant::Variant(uint8_t i) : impl(new VariantImpl(i)) {} +Variant::Variant(uint16_t i) : impl(new VariantImpl(i)) {} +Variant::Variant(uint32_t i) : impl(new VariantImpl(i)) {} +Variant::Variant(uint64_t i) : impl(new VariantImpl(i)) {} +Variant::Variant(int8_t i) : impl(new VariantImpl(i)) {} +Variant::Variant(int16_t i) : impl(new VariantImpl(i)) {} +Variant::Variant(int32_t i) : impl(new VariantImpl(i)) {} +Variant::Variant(int64_t i) : impl(new VariantImpl(i)) {} +Variant::Variant(float f) : impl(new VariantImpl(f)) {} +Variant::Variant(double d) : impl(new VariantImpl(d)) {} +Variant::Variant(const std::string& s) : impl(new VariantImpl(s)) {} +Variant::Variant(const char* s) : impl(new VariantImpl(std::string(s))) {} +Variant::Variant(const Map& m) : impl(new VariantImpl(m)) {} +Variant::Variant(const List& l) : impl(new VariantImpl(l)) {} +Variant::Variant(const Variant& v) : impl(VariantImpl::create(v)) {} +Variant::Variant(const Uuid& u) : impl(new VariantImpl(u)) {} + +Variant::~Variant() { if (impl) delete impl; } + +void Variant::reset() +{ + if (impl) delete impl; + impl = 0; +} + + +Variant& Variant::operator=(bool b) +{ + if (impl) delete impl; + impl = new VariantImpl(b); + return *this; +} + +Variant& Variant::operator=(uint8_t i) +{ + if (impl) delete impl; + impl = new VariantImpl(i); + return *this; +} +Variant& Variant::operator=(uint16_t i) +{ + if (impl) delete impl; + impl = new VariantImpl(i); + return *this; +} +Variant& Variant::operator=(uint32_t i) +{ + if (impl) delete impl; + impl = new VariantImpl(i); + return *this; +} +Variant& Variant::operator=(uint64_t i) +{ + if (impl) delete impl; + impl = new VariantImpl(i); + return *this; +} + +Variant& Variant::operator=(int8_t i) +{ + if (impl) delete impl; + impl = new VariantImpl(i); + return *this; +} +Variant& Variant::operator=(int16_t i) +{ + if (impl) delete impl; + impl = new VariantImpl(i); + return *this; +} +Variant& Variant::operator=(int32_t i) +{ + if (impl) delete impl; + impl = new VariantImpl(i); + return *this; +} +Variant& Variant::operator=(int64_t i) +{ + if (impl) delete impl; + impl = new VariantImpl(i); + return *this; +} + +Variant& Variant::operator=(float f) +{ + if (impl) delete impl; + impl = new VariantImpl(f); + return *this; +} +Variant& Variant::operator=(double d) +{ + if (impl) delete impl; + impl = new VariantImpl(d); + return *this; +} + +Variant& Variant::operator=(const std::string& s) +{ + if (impl) delete impl; + impl = new VariantImpl(s); + return *this; +} + +Variant& Variant::operator=(const char* s) +{ + if (impl) delete impl; + impl = new VariantImpl(std::string(s)); + return *this; +} + +Variant& Variant::operator=(const Uuid& u) +{ + if (impl) delete impl; + impl = new VariantImpl(u); + return *this; +} + +Variant& Variant::operator=(const Map& m) +{ + if (impl) delete impl; + impl = new VariantImpl(m); + return *this; +} + +Variant& Variant::operator=(const List& l) +{ + if (impl) delete impl; + impl = new VariantImpl(l); + return *this; +} + +Variant& Variant::operator=(const Variant& v) +{ + if (impl) delete impl; + impl = VariantImpl::create(v); + return *this; +} + +Variant& Variant::parse(const std::string& s) +{ + operator=(s); + try { + return operator=(asInt64()); + } catch (const InvalidConversion&) {} + try { + return operator=(asDouble()); + } catch (const InvalidConversion&) {} + try { + return operator=(asBool()); + } catch (const InvalidConversion&) {} + return *this; +} + + +VariantType Variant::getType() const { return impl ? impl->getType() : VAR_VOID; } +bool Variant::isVoid() const { return getType() == VAR_VOID; } +bool Variant::asBool() const { return impl && impl->asBool(); } +uint8_t Variant::asUint8() const { return impl ? impl->asUint8() : 0; } +uint16_t Variant::asUint16() const { return impl ? impl->asUint16() : 0; } +uint32_t Variant::asUint32() const { return impl ? impl->asUint32() : 0; } +uint64_t Variant::asUint64() const { return impl ? impl->asUint64() : 0; } +int8_t Variant::asInt8() const { return impl ? impl->asInt8() : 0; } +int16_t Variant::asInt16() const { return impl ? impl->asInt16() : 0; } +int32_t Variant::asInt32() const { return impl ? impl->asInt32(): 0; } +int64_t Variant::asInt64() const { return impl ? impl->asInt64() : 0; } +float Variant::asFloat() const { return impl ? impl->asFloat() : 0; } +double Variant::asDouble() const { return impl ? impl->asDouble() : 0; } +std::string Variant::asString() const { return impl ? impl->asString() : EMPTY; } +Uuid Variant::asUuid() const { return impl ? impl->asUuid() : Uuid(); } +const Variant::Map& Variant::asMap() const { if (!impl) throw InvalidConversion("Can't convert VOID to MAP"); return impl->asMap(); } +Variant::Map& Variant::asMap() { if (!impl) throw InvalidConversion("Can't convert VOID to MAP"); return impl->asMap(); } +const Variant::List& Variant::asList() const { if (!impl) throw InvalidConversion("Can't convert VOID to LIST"); return impl->asList(); } +Variant::List& Variant::asList() { if (!impl) throw InvalidConversion("Can't convert VOID to LIST"); return impl->asList(); } +const std::string& Variant::getString() const { if (!impl) throw InvalidConversion("Can't convert VOID to STRING"); return impl->getString(); } +std::string& Variant::getString() { if (!impl) throw InvalidConversion("Can't convert VOID to STRING"); return impl->getString(); } +void Variant::setEncoding(const std::string& s) { + if (!impl) impl = new VariantImpl(); + impl->setEncoding(s); +} +const std::string& Variant::getEncoding() const { return impl ? impl->getEncoding() : EMPTY; } + +Variant::operator bool() const { return asBool(); } +Variant::operator uint8_t() const { return asUint8(); } +Variant::operator uint16_t() const { return asUint16(); } +Variant::operator uint32_t() const { return asUint32(); } +Variant::operator uint64_t() const { return asUint64(); } +Variant::operator int8_t() const { return asInt8(); } +Variant::operator int16_t() const { return asInt16(); } +Variant::operator int32_t() const { return asInt32(); } +Variant::operator int64_t() const { return asInt64(); } +Variant::operator float() const { return asFloat(); } +Variant::operator double() const { return asDouble(); } +Variant::operator std::string() const { return asString(); } +Variant::operator Uuid() const { return asUuid(); } + +std::ostream& operator<<(std::ostream& out, const Variant::Map& map) +{ + out << "{"; + for (Variant::Map::const_iterator i = map.begin(); i != map.end(); ++i) { + if (i != map.begin()) out << ", "; + out << i->first << ":" << i->second; + } + out << "}"; + return out; +} + +std::ostream& operator<<(std::ostream& out, const Variant::List& list) +{ + out << "["; + for (Variant::List::const_iterator i = list.begin(); i != list.end(); ++i) { + if (i != list.begin()) out << ", "; + out << *i; + } + out << "]"; + return out; +} + +std::ostream& operator<<(std::ostream& out, const Variant& value) +{ + switch (value.getType()) { + case VAR_MAP: + out << value.asMap(); + break; + case VAR_LIST: + out << value.asList(); + break; + case VAR_VOID: + out << "<void>"; + break; + default: + out << value.asString(); + break; + } + return out; +} + +bool operator==(const Variant& a, const Variant& b) +{ + return a.isEqualTo(b); +} + +bool Variant::isEqualTo(const Variant& other) const +{ + return impl && impl->isEqualTo(*other.impl); +} + +}} // namespace qpid::types diff --git a/qpid/cpp/src/qpid/xml/XmlExchange.cpp b/qpid/cpp/src/qpid/xml/XmlExchange.cpp new file mode 100644 index 0000000000..b7ff5d211d --- /dev/null +++ b/qpid/cpp/src/qpid/xml/XmlExchange.cpp @@ -0,0 +1,408 @@ +/* + * + * 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 "config.h" + +#include "qpid/xml/XmlExchange.h" + +#include "qpid/broker/DeliverableMessage.h" + +#include "qpid/log/Statement.h" +#include "qpid/broker/FedOps.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/framing/FieldValue.h" +#include "qpid/framing/reply_exceptions.h" + +#include "qpid/Plugin.h" + +#include <xercesc/framework/MemBufInputSource.hpp> + +#ifdef XQ_EFFECTIVE_BOOLEAN_VALUE_HPP +#include <xqilla/ast/XQEffectiveBooleanValue.hpp> +#endif + +#include <xqilla/ast/XQGlobalVariable.hpp> + +#include <xqilla/context/ItemFactory.hpp> +#include <xqilla/xqilla-simple.hpp> + +#include <boost/bind.hpp> +#include <functional> +#include <algorithm> +#include <iostream> +#include <sstream> + +using namespace qpid::framing; +using namespace qpid::sys; +using qpid::management::Manageable; +namespace _qmf = qmf::org::apache::qpid::broker; + +namespace qpid { +namespace broker { + +XQilla XmlBinding::xqilla; + +XmlBinding::XmlBinding(const std::string& key, const Queue::shared_ptr queue, const std::string& _fedOrigin, Exchange* parent, + const ::qpid::framing::FieldTable& _arguments, const std::string& queryText ) + : Binding(key, queue, parent, _arguments), + xquery(), + parse_message_content(true), + fedOrigin(_fedOrigin) +{ + startManagement(); + + QPID_LOG(trace, "Creating binding with query: " << queryText ); + + try { + Query q(xqilla.parse(X(queryText.c_str()))); + xquery = q; + + QPID_LOG(trace, "Bound successfully with query: " << queryText ); + + parse_message_content = false; + + if (xquery->getQueryBody()->getStaticAnalysis().areContextFlagsUsed()) { + parse_message_content = true; + } + else { + GlobalVariables &vars = const_cast<GlobalVariables&>(xquery->getVariables()); + for (GlobalVariables::iterator it = vars.begin(); it != vars.end(); ++it) { + if ((*it)->getStaticAnalysis().areContextFlagsUsed()) { + parse_message_content = true; + break; + } + } + } + } + catch (XQException& e) { + throw InternalErrorException(QPID_MSG("Could not parse xquery:"+ queryText)); + } + catch (...) { + throw InternalErrorException(QPID_MSG("Unexpected error - Could not parse xquery:"+ queryText)); + } +} + + +XmlExchange::XmlExchange(const string& _name, Manageable* _parent, Broker* b) : Exchange(_name, _parent, b) +{ + if (mgmtExchange != 0) + mgmtExchange->set_type (typeName); +} + +XmlExchange::XmlExchange(const std::string& _name, bool _durable, + const FieldTable& _args, Manageable* _parent, Broker* b) : + Exchange(_name, _durable, _args, _parent, b) +{ + if (mgmtExchange != 0) + mgmtExchange->set_type (typeName); +} + +bool XmlExchange::bind(Queue::shared_ptr queue, const string& bindingKey, const FieldTable* args) +{ + + // Federation uses bind for unbind and reorigin comands as well as for binds. + // + // Both federated and local binds are done in this method. Other + // federated requests are done by calling the relevent methods. + + string fedOp; + string fedTags; + string fedOrigin; + + if (args) + fedOp = args->getAsString(qpidFedOp); + if (! fedOp.empty()) { + fedTags = args->getAsString(qpidFedTags); + fedOrigin = args->getAsString(qpidFedOrigin); + } + + if (fedOp == fedOpUnbind) { + return fedUnbind(fedOrigin, fedTags, queue, bindingKey, args); + } + else if (fedOp == fedOpReorigin) { + fedReorigin(); + return true; + } + + // OK, looks like we're really going to bind + + else if (fedOp.empty() || fedOp == fedOpBind) { + + string queryText = args->getAsString("xquery"); + + RWlock::ScopedWlock l(lock); + + XmlBinding::vector& bindings(bindingsMap[bindingKey]); + XmlBinding::vector::ConstPtr p = bindings.snapshot(); + + if (!p || std::find_if(p->begin(), p->end(), MatchQueueAndOrigin(queue, fedOrigin)) == p->end()) { + + XmlBinding::shared_ptr binding(new XmlBinding (bindingKey, queue, fedOrigin, this, *args, queryText)); + bindings.add(binding); + + if (mgmtExchange != 0) { + mgmtExchange->inc_bindingCount(); + } + } else { + return false; + } + } + else { + QPID_LOG(warning, "Unknown Federation Op: " << fedOp); + } + + routeIVE(); + propagateFedOp(bindingKey, fedTags, fedOp, fedOrigin, args); + + return true; +} + +bool XmlExchange::unbind(Queue::shared_ptr queue, const string& bindingKey, const FieldTable* args) +{ + /* + * When called directly, no qpidFedOrigin argument will be + * present. When called from federation, it will be present. + * + * This is a bit of a hack - the binding needs the origin, but + * this interface, as originally defined, would not supply one. + */ + string fedOrigin; + if (args) fedOrigin = args->getAsString(qpidFedOrigin); + + RWlock::ScopedWlock l(lock); + if (bindingsMap[bindingKey].remove_if(MatchQueueAndOrigin(queue, fedOrigin))) { + if (mgmtExchange != 0) { + mgmtExchange->dec_bindingCount(); + } + return true; + } else { + return false; + } +} + +bool XmlExchange::matches(Query& query, Deliverable& msg, const qpid::framing::FieldTable* args, bool parse_message_content) +{ + string msgContent; + + try { + QPID_LOG(trace, "matches: query is [" << UTF8(query->getQueryText()) << "]"); + + boost::scoped_ptr<DynamicContext> context(query->createDynamicContext()); + if (!context.get()) { + throw InternalErrorException(QPID_MSG("Query context looks munged ...")); + } + + if (parse_message_content) { + + msg.getMessage().getFrames().getContent(msgContent); + + QPID_LOG(trace, "matches: message content is [" << msgContent << "]"); + + XERCES_CPP_NAMESPACE::MemBufInputSource xml((const XMLByte*) msgContent.c_str(), + msgContent.length(), "input" ); + + // This will parse the document using either Xerces or FastXDM, depending + // on your XQilla configuration. FastXDM can be as much as 10x faster. + + Sequence seq(context->parseDocument(xml)); + + if(!seq.isEmpty() && seq.first()->isNode()) { + context->setContextItem(seq.first()); + context->setContextPosition(1); + context->setContextSize(1); + } + } + + if (args) { + FieldTable::ValueMap::const_iterator v = args->begin(); + for(; v != args->end(); ++v) { + + if (v->second->convertsTo<double>()) { + QPID_LOG(trace, "XmlExchange, external variable (double): " << v->first << " = " << v->second->get<double>()); + Item::Ptr value = context->getItemFactory()->createDouble(v->second->get<double>(), context.get()); + context->setExternalVariable(X(v->first.c_str()), value); + } + else if (v->second->convertsTo<int>()) { + QPID_LOG(trace, "XmlExchange, external variable (int):" << v->first << " = " << v->second->getData().getInt()); + Item::Ptr value = context->getItemFactory()->createInteger(v->second->get<int>(), context.get()); + context->setExternalVariable(X(v->first.c_str()), value); + } + else if (v->second->convertsTo<std::string>()) { + QPID_LOG(trace, "XmlExchange, external variable (string):" << v->first << " = " << v->second->getData().getString().c_str()); + Item::Ptr value = context->getItemFactory()->createString(X(v->second->get<std::string>().c_str()), context.get()); + context->setExternalVariable(X(v->first.c_str()), value); + } + + } + } + + Result result = query->execute(context.get()); +#ifdef XQ_EFFECTIVE_BOOLEAN_VALUE_HPP + Item::Ptr first_ = result->next(context.get()); + Item::Ptr second_ = result->next(context.get()); + return XQEffectiveBooleanValue::get(first_, second_, context.get(), 0); +#else + return result->getEffectiveBooleanValue(context.get(), 0); +#endif + } + catch (XQException& e) { + QPID_LOG(warning, "Could not parse XML content (or message headers):" << msgContent); + } + catch (...) { + QPID_LOG(warning, "Unexpected error routing message: " << msgContent); + } + return 0; +} + +// Future optimization: If any query in a binding for a given routing key requires +// message content, parse the message once, and use that parsed form for all bindings. +// +// Future optimization: XQilla does not currently do document projection for data +// accessed via the context item. If there is a single query for a given routing key, +// and it accesses document data, this could be a big win. +// +// Document projection often is not a win if you have multiple queries on the same data. +// But for very large messages, if all these queries are on the first part of the data, +// it could still be a big win. + +void XmlExchange::route(Deliverable& msg, const string& routingKey, const FieldTable* args) +{ + PreRoute pr(msg, this); + try { + XmlBinding::vector::ConstPtr p; + BindingList b(new std::vector<boost::shared_ptr<qpid::broker::Exchange::Binding> >); + { + RWlock::ScopedRlock l(lock); + p = bindingsMap[routingKey].snapshot(); + if (!p.get()) return; + } + + for (std::vector<XmlBinding::shared_ptr>::const_iterator i = p->begin(); i != p->end(); i++) { + if (matches((*i)->xquery, msg, args, (*i)->parse_message_content)) { + b->push_back(*i); + } + } + doRoute(msg, b); + } catch (...) { + QPID_LOG(warning, "XMLExchange " << getName() << ": exception routing message with query " << routingKey); + } +} + + +bool XmlExchange::isBound(Queue::shared_ptr queue, const string* const bindingKey, const FieldTable* const) +{ + RWlock::ScopedRlock l(lock); + if (bindingKey) { + XmlBindingsMap::iterator i = bindingsMap.find(*bindingKey); + + if (i == bindingsMap.end()) + return false; + if (!queue) + return true; + XmlBinding::vector::ConstPtr p = i->second.snapshot(); + return p && std::find_if(p->begin(), p->end(), MatchQueue(queue)) != p->end(); + } else if (!queue) { + //if no queue or routing key is specified, just report whether any bindings exist + return bindingsMap.size() > 0; + } else { + for (XmlBindingsMap::iterator i = bindingsMap.begin(); i != bindingsMap.end(); i++) { + XmlBinding::vector::ConstPtr p = i->second.snapshot(); + if (p && std::find_if(p->begin(), p->end(), MatchQueue(queue)) != p->end()) return true; + } + return false; + } + +} + +XmlExchange::~XmlExchange() +{ + bindingsMap.clear(); +} + +void XmlExchange::propagateFedOp(const std::string& bindingKey, const std::string& fedTags, const std::string& fedOp, const std::string& fedOrigin, const qpid::framing::FieldTable* args) +{ + FieldTable nonFedArgs; + + if (args) { + for (qpid::framing::FieldTable::ValueMap::const_iterator i=args->begin(); i != args->end(); ++i) { + const string& name(i->first); + if (name != qpidFedOp && + name != qpidFedTags && + name != qpidFedOrigin) { + nonFedArgs.insert((*i)); + } + } + } + + FieldTable* propArgs = (nonFedArgs.count() > 0 ? &nonFedArgs : 0); + Exchange::propagateFedOp(bindingKey, fedTags, fedOp, fedOrigin, propArgs); +} + +bool XmlExchange::fedUnbind(const string& fedOrigin, const string& fedTags, Queue::shared_ptr queue, const string& bindingKey, const FieldTable* args) +{ + RWlock::ScopedRlock l(lock); + + if (unbind(queue, bindingKey, args)) { + propagateFedOp(bindingKey, fedTags, fedOpUnbind, fedOrigin); + return true; + } + return false; +} + +void XmlExchange::fedReorigin() +{ + std::vector<std::string> keys2prop; + { + RWlock::ScopedRlock l(lock); + for (XmlBindingsMap::iterator i = bindingsMap.begin(); i != bindingsMap.end(); ++i) { + XmlBinding::vector::ConstPtr p = i->second.snapshot(); + if (std::find_if(p->begin(), p->end(), MatchOrigin(string())) != p->end()) { + keys2prop.push_back(i->first); + } + } + } /* lock dropped */ + for (std::vector<std::string>::const_iterator key = keys2prop.begin(); + key != keys2prop.end(); key++) { + propagateFedOp( *key, string(), fedOpBind, string()); + } +} + + +XmlExchange::MatchOrigin::MatchOrigin(const string& _origin) : origin(_origin) {} + +bool XmlExchange::MatchOrigin::operator()(XmlBinding::shared_ptr b) +{ + return b->fedOrigin == origin; +} + + +XmlExchange::MatchQueueAndOrigin::MatchQueueAndOrigin(Queue::shared_ptr _queue, const string& _origin) : queue(_queue), origin(_origin) {} + +bool XmlExchange::MatchQueueAndOrigin::operator()(XmlBinding::shared_ptr b) +{ + return b->queue == queue and b->fedOrigin == origin; +} + + +const std::string XmlExchange::typeName("xml"); + +} +} diff --git a/qpid/cpp/src/qpid/xml/XmlExchange.h b/qpid/cpp/src/qpid/xml/XmlExchange.h new file mode 100644 index 0000000000..958bad4931 --- /dev/null +++ b/qpid/cpp/src/qpid/xml/XmlExchange.h @@ -0,0 +1,119 @@ +/* + * + * 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 _XmlExchange_ +#define _XmlExchange_ + +#include "qpid/broker/Exchange.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/sys/CopyOnWriteArray.h" +#include "qpid/sys/Monitor.h" +#include "qpid/broker/Queue.h" + +#include <xqilla/xqilla-simple.hpp> + +#include <boost/scoped_ptr.hpp> + +#include <map> +#include <vector> +#include <string> + +using namespace std; + +namespace qpid { +namespace broker { + +class Broker; + +typedef boost::shared_ptr<XQQuery> Query; + +struct XmlBinding : public Exchange::Binding { + + static XQilla xqilla; + + typedef boost::shared_ptr<XmlBinding> shared_ptr; + typedef qpid::sys::CopyOnWriteArray<XmlBinding::shared_ptr> vector; + + Query xquery; + bool parse_message_content; + const std::string fedOrigin; // empty for local bindings + + XmlBinding(const std::string& key, const Queue::shared_ptr queue, const std::string& fedOrigin, Exchange* parent, + const ::qpid::framing::FieldTable& _arguments, const std::string& ); + +}; + +class XmlExchange : public virtual Exchange { + + typedef std::map<string, XmlBinding::vector> XmlBindingsMap; + XmlBindingsMap bindingsMap; + + qpid::sys::RWlock lock; + + bool matches(Query& query, Deliverable& msg, const qpid::framing::FieldTable* args, bool parse_message_content); + + public: + static const std::string typeName; + + XmlExchange(const std::string& name, management::Manageable* parent = 0, Broker* broker = 0); + XmlExchange(const std::string& _name, bool _durable, + const qpid::framing::FieldTable& _args, management::Manageable* parent = 0, Broker* broker = 0); + + virtual std::string getType() const { return typeName; } + + virtual bool bind(Queue::shared_ptr queue, const std::string& routingKey, const qpid::framing::FieldTable* args); + + virtual bool unbind(Queue::shared_ptr queue, const std::string& routingKey, const qpid::framing::FieldTable* args); + + virtual void route(Deliverable& msg, const std::string& routingKey, const qpid::framing::FieldTable* args); + + virtual bool isBound(Queue::shared_ptr queue, const std::string* const routingKey, const qpid::framing::FieldTable* const args); + + virtual void propagateFedOp(const std::string& bindingKey, const std::string& fedTags, const std::string& fedOp, const std::string& fedOrigin, const qpid::framing::FieldTable* args=0); + + virtual bool fedUnbind(const std::string& fedOrigin, const std::string& fedTags, Queue::shared_ptr queue, const std::string& bindingKey, const qpid::framing::FieldTable* args); + + virtual void fedReorigin(); + + virtual bool supportsDynamicBinding() { return true; } + + virtual ~XmlExchange(); + + struct MatchOrigin { + const std::string origin; + MatchOrigin(const std::string& origin); + bool operator()(XmlBinding::shared_ptr b); + }; + + struct MatchQueueAndOrigin { + const Queue::shared_ptr queue; + const std::string origin; + MatchQueueAndOrigin(Queue::shared_ptr queue, const std::string& origin); + bool operator()(XmlBinding::shared_ptr b); + }; + +}; + + +} +} + + +#endif diff --git a/qpid/cpp/src/qpid/xml/XmlExchangePlugin.cpp b/qpid/cpp/src/qpid/xml/XmlExchangePlugin.cpp new file mode 100644 index 0000000000..742b878e86 --- /dev/null +++ b/qpid/cpp/src/qpid/xml/XmlExchangePlugin.cpp @@ -0,0 +1,69 @@ +/* + * + * 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. + * + */ + +#include <sstream> +#include "qpid/acl/Acl.h" +#include "qpid/broker/Broker.h" +#include "qpid/Plugin.h" +#include "qpid/log/Statement.h" + +#include <boost/shared_ptr.hpp> +#include <boost/utility/in_place_factory.hpp> + +#include "qpid/xml/XmlExchange.h" + +namespace qpid { +namespace broker { // ACL uses the acl namespace here - should I? + +using namespace std; +class Broker; + +Exchange::shared_ptr create(const std::string& name, bool durable, + const framing::FieldTable& args, + management::Manageable* parent, + Broker* broker) +{ + Exchange::shared_ptr e(new XmlExchange(name, durable, args, parent, broker)); + return e; +} + + +class XmlExchangePlugin : public Plugin +{ +public: + void earlyInitialize(Plugin::Target& target); + void initialize(Plugin::Target& target); +}; + + +void XmlExchangePlugin::earlyInitialize(Plugin::Target& target) +{ + Broker* broker = dynamic_cast<broker::Broker*>(&target); + if (broker) { + broker->getExchanges().registerType(XmlExchange::typeName, &create); + QPID_LOG(info, "Registered xml exchange"); + } +} + +void XmlExchangePlugin::initialize(Target&) {} + + +static XmlExchangePlugin matchingPlugin; + + +}} // namespace qpid::acl diff --git a/qpid/cpp/src/qpidd.cpp b/qpid/cpp/src/qpidd.cpp new file mode 100644 index 0000000000..a7c1dbe8a6 --- /dev/null +++ b/qpid/cpp/src/qpidd.cpp @@ -0,0 +1,86 @@ +/* + * + * 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 "./qpidd.h" +#include "qpid/Plugin.h" +#include "qpid/Version.h" +#include "qpid/log/Logger.h" +#include "qpid/log/Statement.h" + +#include <iostream> +#include <memory> +using namespace std; + +auto_ptr<QpiddOptions> options; + +int main(int argc, char* argv[]) +{ + try + { + BootstrapOptions bootOptions(argv[0]); + string defaultPath (bootOptions.module.loadDir); + // Parse only the common, load, and log options to see which + // modules need to be loaded. Once the modules are loaded, + // the command line will be re-parsed with all of the + // module-supplied options. + try { + bootOptions.parse (argc, argv, bootOptions.common.config, true); + qpid::log::Logger::instance().configure(bootOptions.log); + } catch (const std::exception& e) { + // Couldn't configure logging so write the message direct to stderr. + cerr << "Unexpected error: " << e.what() << endl; + return 1; + } + + for (vector<string>::iterator iter = bootOptions.module.load.begin(); + iter != bootOptions.module.load.end(); + iter++) + qpid::tryShlib (iter->data(), false); + + if (!bootOptions.module.noLoad) { + bool isDefault = defaultPath == bootOptions.module.loadDir; + qpid::loadModuleDir (bootOptions.module.loadDir, isDefault); + } + + // Parse options + options.reset(new QpiddOptions(argv[0])); + options->parse(argc, argv, options->common.config); + + // Options that just print information. + if (options->common.help || options->common.version) { + if (options->common.version) + cout << "qpidd (" << qpid::product << ") version " + << qpid::version << endl; + else if (options->common.help) + options->usage(); + return 0; + } + + // Everything else is driven by the platform-specific broker + // logic. + QpiddBroker broker; + return broker.execute(options.get()); + } + catch(const exception& e) { + QPID_LOG(critical, "Unexpected error: " << e.what()); + } + return 1; +} diff --git a/qpid/cpp/src/qpidd.h b/qpid/cpp/src/qpidd.h new file mode 100644 index 0000000000..c702270e80 --- /dev/null +++ b/qpid/cpp/src/qpidd.h @@ -0,0 +1,70 @@ +#ifndef QPID_H +#define QPID_H + +/* + * + * 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. + * + */ + +#include "qpid/Modules.h" +#include "qpid/Options.h" +#include "qpid/broker/Broker.h" +#include "qpid/log/Options.h" + +#include <memory> + +// BootstrapOptions is a minimal subset of options used for a pre-parse +// of the command line to discover which plugin modules need to be loaded. +// The pre-parse is necessary because plugin modules may supply their own +// set of options. CommonOptions is needed to properly support loading +// from a configuration file. +struct BootstrapOptions : public qpid::Options { + qpid::CommonOptions common; + qpid::ModuleOptions module; + qpid::log::Options log; + + BootstrapOptions(const char *argv0); +}; + +// Each platform derives an options struct from QpiddOptionsPrivate, adding +// platform-specific option types. QpiddOptions needs to allocation one of +// these derived structs from its constructor. +struct QpiddOptions; +struct QpiddOptionsPrivate { + QpiddOptions *options; + QpiddOptionsPrivate(QpiddOptions *parent) : options(parent) {} + virtual ~QpiddOptionsPrivate() {} +protected: + QpiddOptionsPrivate() {} +}; + +struct QpiddOptions : public qpid::Options { + qpid::CommonOptions common; + qpid::ModuleOptions module; + qpid::broker::Broker::Options broker; + qpid::log::Options log; + std::auto_ptr<QpiddOptionsPrivate> platform; + + QpiddOptions(const char *argv0); + void usage() const; +}; + +class QpiddBroker { +public: + int execute (QpiddOptions *options); +}; + +#endif /*!QPID_H*/ diff --git a/qpid/cpp/src/rdma.cmake b/qpid/cpp/src/rdma.cmake new file mode 100644 index 0000000000..e020cb84a9 --- /dev/null +++ b/qpid/cpp/src/rdma.cmake @@ -0,0 +1,118 @@ +# +# 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. +# +# +# RDMA (Remote DMA) wrapper CMake fragment, to be included in CMakeLists.txt +# + +# Optional RDMA support. Requires ibverbs and rdma_cm. + +include(CheckIncludeFiles) +include(CheckLibraryExists) + +CHECK_LIBRARY_EXISTS (ibverbs ibv_create_qp "" HAVE_IBVERBS) +CHECK_LIBRARY_EXISTS (rdmacm rdma_create_id "" HAVE_RDMACM) +CHECK_INCLUDE_FILES (infiniband/verbs.h HAVE_IBVERBS_H) +CHECK_INCLUDE_FILES (rdma/rdma_cma.h HAVE_RDMACM_H) + +set (rdma_default ${rdma_force}) +if (HAVE_IBVERBS AND HAVE_IBVERBS_H) + if (HAVE_RDMACM AND HAVE_RDMACM_H) + set (rdma_default ON) + endif (HAVE_RDMACM AND HAVE_RDMACM_H) +endif (HAVE_IBVERBS AND HAVE_IBVERBS_H) + +option(BUILD_RDMA "Build with support for Remote DMA protocols" ${rdma_default}) +if (BUILD_RDMA) + if (NOT HAVE_IBVERBS) + message(FATAL_ERROR "libibverbs not found, required for RDMA support") + endif (NOT HAVE_IBVERBS) + if (NOT HAVE_RDMACM) + message(FATAL_ERROR "librdmacm not found, required for RDMA support") + endif (NOT HAVE_RDMACM) + if (NOT HAVE_IBVERBS_H) + message(FATAL_ERROR "ibverbs headers not found, required for RDMA support") + endif (NOT HAVE_IBVERBS_H) + if (NOT HAVE_RDMACM_H) + message(FATAL_ERROR "rdmacm headers not found, required for RDMA support") + endif (NOT HAVE_RDMACM_H) + + set (rdma_SOURCES + qpid/sys/rdma/rdma_exception.h + qpid/sys/rdma/rdma_factories.cpp + qpid/sys/rdma/rdma_factories.h + qpid/sys/rdma/RdmaIO.cpp + qpid/sys/rdma/RdmaIO.h + qpid/sys/rdma/rdma_wrap.cpp + qpid/sys/rdma/rdma_wrap.h + ) + + add_library (rdmawrap SHARED ${rdma_SOURCES}) + target_link_libraries (rdmawrap qpidcommon rdmacm ibverbs) + set_target_properties (rdmawrap PROPERTIES VERSION ${qpidc_version}) + if (CMAKE_COMPILER_IS_GNUCXX) + set_target_properties(rdmawrap PROPERTIES + COMPILE_FLAGS -Wno-missing-field-initializers + LINK_FLAGS -Wl,--no-undefined) + endif (CMAKE_COMPILER_IS_GNUCXX) + + install (TARGETS rdmawrap + DESTINATION ${QPID_INSTALL_LIBDIR} + COMPONENT ${QPID_COMPONENT_COMMON}) + + add_library (rdma MODULE qpid/sys/RdmaIOPlugin.cpp) + target_link_libraries (rdma qpidbroker rdmawrap) + set_target_properties (rdma PROPERTIES + PREFIX "") + + if (CMAKE_COMPILER_IS_GNUCXX) + set_target_properties(rdma PROPERTIES + COMPILE_FLAGS -Wno-missing-field-initializers + LINK_FLAGS -Wl,--no-undefined) + endif (CMAKE_COMPILER_IS_GNUCXX) + + install (TARGETS rdma + DESTINATION ${QPIDD_MODULE_DIR} + COMPONENT ${QPID_COMPONENT_BROKER}) + + add_library (rdmaconnector MODULE qpid/client/RdmaConnector.cpp) + target_link_libraries (rdmaconnector qpidclient rdmawrap) + set_target_properties (rdmaconnector PROPERTIES + PREFIX "") + + if (CMAKE_COMPILER_IS_GNUCXX) + set_target_properties(rdmaconnector PROPERTIES + COMPILE_FLAGS -Wno-missing-field-initializers + LINK_FLAGS -Wl,--no-undefined) + endif (CMAKE_COMPILER_IS_GNUCXX) + + install (TARGETS rdmaconnector + DESTINATION ${QPIDC_MODULE_DIR} + COMPONENT ${QPID_COMPONENT_CLIENT}) + + # RDMA test/sample programs + add_executable (RdmaServer qpid/sys/rdma/RdmaServer.cpp) + target_link_libraries (RdmaServer rdmawrap qpidcommon) + add_executable (RdmaClient qpid/sys/rdma/RdmaClient.cpp) + target_link_libraries (RdmaClient rdmawrap qpidcommon) + if (CMAKE_COMPILER_IS_GNUCXX) + set_target_properties(RdmaClient PROPERTIES + COMPILE_FLAGS -Wno-missing-field-initializers) + endif (CMAKE_COMPILER_IS_GNUCXX) + +endif (BUILD_RDMA) diff --git a/qpid/cpp/src/replication.mk b/qpid/cpp/src/replication.mk new file mode 100644 index 0000000000..e5da32f88b --- /dev/null +++ b/qpid/cpp/src/replication.mk @@ -0,0 +1,52 @@ +# +# 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. + +# Make file for building two plugins for asynchronously replicating +# queues. + +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 + +replicating_listener_la_LIBADD = libqpidbroker.la +if SUNOS + replicating_listener_la_LIBADD += libqpidcommon.la -lboost_program_options -luuid $(SUNCC_RUNTIME_LIBS) +endif +replicating_listener_la_LDFLAGS = $(PLUGINLDFLAGS) + +# a custom exchange plugin that allows an exchange to be created that +# can process the messages from a replication queue (populated on the +# source system by the replicating listener plugin above) and take the +# corresponding action on the local queues +replication_exchange_la_SOURCES = \ + qpid/replication/constants.h \ + qpid/replication/ReplicationExchange.cpp \ + qpid/replication/ReplicationExchange.h + +replication_exchange_la_LIBADD = libqpidbroker.la + +if SUNOS + replication_exchange_la_LIBADD += libqpidcommon.la -lboost_program_options $(SUNCC_RUNTIME_LIBS) -luuid +endif +replication_exchange_la_LDFLAGS = $(PLUGINLDFLAGS) + diff --git a/qpid/cpp/src/ssl.cmake b/qpid/cpp/src/ssl.cmake new file mode 100644 index 0000000000..c205845388 --- /dev/null +++ b/qpid/cpp/src/ssl.cmake @@ -0,0 +1,117 @@ +# +# 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. +# +# +# SSL/TLS CMake fragment, to be included in CMakeLists.txt +# + +# Optional SSL/TLS support. Requires Netscape Portable Runtime on Linux. + +include(FindPkgConfig) + +# According to some cmake docs this is not a reliable way to detect +# pkg-configed libraries, but it's no worse than what we did under +# autotools +pkg_check_modules(NSS nss) + +set (ssl_default ${ssl_force}) +if (CMAKE_SYSTEM_NAME STREQUAL Windows) + set (ssl_default ON) +else (CMAKE_SYSTEM_NAME STREQUAL Windows) + if (NSS_FOUND) + set (ssl_default ON) + endif (NSS_FOUND) +endif (CMAKE_SYSTEM_NAME STREQUAL Windows) + +option(BUILD_SSL "Build with support for SSL" ${ssl_default}) +if (BUILD_SSL) + if (CMAKE_SYSTEM_NAME STREQUAL Windows) + set (sslclient_windows_SOURCES qpid/client/windows/SslConnector.cpp) + set (sslbroker_windows_SOURCES qpid/broker/windows/SslProtocolFactory.cpp) + set (sslcommon_windows_SOURCES + qpid/sys/windows/SslAsynchIO.cpp + ) + set (windows_ssl_libs Secur32.lib) + set (windows_ssl_server_libs Crypt32.lib) + else (CMAKE_SYSTEM_NAME STREQUAL Windows) + + if (NOT NSS_FOUND) + message(FATAL_ERROR "nss/nspr not found, required for ssl support") + endif (NOT NSS_FOUND) + + foreach(f ${NSS_CFLAGS}) + set (NSS_COMPILE_FLAGS "${NSS_COMPILE_FLAGS} ${f}") + endforeach(f) + + foreach(f ${NSS_LDFLAGS}) + set (NSS_LINK_FLAGS "${NSS_LINK_FLAGS} ${f}") + endforeach(f) + + set (sslcommon_SOURCES + qpid/sys/ssl/check.h + qpid/sys/ssl/check.cpp + qpid/sys/ssl/util.h + qpid/sys/ssl/util.cpp + qpid/sys/ssl/SslSocket.h + qpid/sys/ssl/SslSocket.cpp + qpid/sys/ssl/SslIo.h + qpid/sys/ssl/SslIo.cpp + ) + + add_library (sslcommon SHARED ${sslcommon_SOURCES}) + target_link_libraries (sslcommon qpidcommon) + set_target_properties (sslcommon PROPERTIES + VERSION ${qpidc_version} + COMPILE_FLAGS ${NSS_COMPILE_FLAGS} + LINK_FLAGS ${NSS_LINK_FLAGS}) + + set (ssl_SOURCES + qpid/sys/SslPlugin.cpp + qpid/sys/ssl/SslHandler.h + qpid/sys/ssl/SslHandler.cpp + ) + add_library (ssl MODULE ${ssl_SOURCES}) + target_link_libraries (ssl qpidbroker sslcommon ${Boost_PROGRAM_OPTIONS_LIBRARY}) + set_target_properties (ssl PROPERTIES + PREFIX "" + COMPILE_FLAGS ${NSS_COMPILE_FLAGS}) + if (CMAKE_COMPILER_IS_GNUCXX) + set_target_properties(ssl PROPERTIES + LINK_FLAGS -Wl,--no-undefined) + endif (CMAKE_COMPILER_IS_GNUCXX) + + install (TARGETS ssl + DESTINATION ${QPIDD_MODULE_DIR} + COMPONENT ${QPID_COMPONENT_BROKER}) + + add_library (sslconnector MODULE qpid/client/SslConnector.cpp) + target_link_libraries (sslconnector qpidclient sslcommon) + set_target_properties (sslconnector PROPERTIES + PREFIX "" + COMPILE_FLAGS ${NSS_COMPILE_FLAGS}) + if (CMAKE_COMPILER_IS_GNUCXX) + set_target_properties(sslconnector PROPERTIES + LINK_FLAGS -Wl,--no-undefined) + endif (CMAKE_COMPILER_IS_GNUCXX) + + install (TARGETS sslconnector + DESTINATION ${QPIDC_MODULE_DIR} + COMPONENT ${QPID_COMPONENT_CLIENT}) + endif (CMAKE_SYSTEM_NAME STREQUAL Windows) + +endif (BUILD_SSL) diff --git a/qpid/cpp/src/ssl.mk b/qpid/cpp/src/ssl.mk new file mode 100644 index 0000000000..4dba9bb61c --- /dev/null +++ b/qpid/cpp/src/ssl.mk @@ -0,0 +1,64 @@ +# +# 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. +# +# +# Makefile fragment, conditionally included in Makefile.am +# +libsslcommon_la_SOURCES = \ + qpid/sys/ssl/check.h \ + qpid/sys/ssl/check.cpp \ + qpid/sys/ssl/util.h \ + qpid/sys/ssl/util.cpp \ + qpid/sys/ssl/SslSocket.h \ + qpid/sys/ssl/SslSocket.cpp \ + qpid/sys/ssl/SslIo.h \ + qpid/sys/ssl/SslIo.cpp + +SSLCOMMON_VERSION_INFO = 2:0:0 +libsslcommon_la_LDFLAGS = -version-info $(SSLCOMMON_VERSION_INFO) +libsslcommon_la_LIBADD= -lnss3 -lssl3 -lnspr4 libqpidcommon.la +libsslcommon_la_CXXFLAGS=$(AM_CXXFLAGS) $(SSL_CFLAGS) + +lib_LTLIBRARIES += libsslcommon.la + +ssl_la_SOURCES = \ + qpid/sys/SslPlugin.cpp \ + qpid/sys/ssl/SslHandler.h \ + qpid/sys/ssl/SslHandler.cpp + +ssl_la_LIBADD= libqpidbroker.la libsslcommon.la + +ssl_la_CXXFLAGS=$(AM_CXXFLAGS) $(SSL_CFLAGS) + +ssl_la_LDFLAGS = $(PLUGINLDFLAGS) + +dmoduleexec_LTLIBRARIES += ssl.la + +sslconnector_la_SOURCES = \ + qpid/client/SslConnector.cpp + +sslconnector_la_LIBADD = \ + libqpidclient.la \ + libsslcommon.la + +sslconnector_la_CXXFLAGS = $(AM_CXXFLAGS) -DQPIDC_CONF_FILE=\"$(confdir)/qpidc.conf\" $(SSL_CFLAGS) + +sslconnector_la_LDFLAGS = $(PLUGINLDFLAGS) + +cmoduleexec_LTLIBRARIES += \ + sslconnector.la diff --git a/qpid/cpp/src/tests/.valgrind.supp b/qpid/cpp/src/tests/.valgrind.supp new file mode 100644 index 0000000000..2c6a1509ff --- /dev/null +++ b/qpid/cpp/src/tests/.valgrind.supp @@ -0,0 +1,202 @@ +{ + Leak in TCPConnector: https://bugzilla.redhat.com/show_bug.cgi?id=520600 + Memcheck:Leak + fun:_vgrZU_libcZdsoZa_calloc + fun:_dl_allocate_tls + fun:* + fun:* + fun:* + fun:_ZN4qpid6client12TCPConnector7connectERKSsi +} + +{ + Leak in TCPConnector: https://bugzilla.redhat.com/show_bug.cgi?id=520600 + Memcheck:Leak + fun:_vgrZU_libcZdsoZa_calloc + fun:_dl_allocate_tls + fun:* + fun:* + fun:_ZN4qpid6client12TCPConnector7connectERKSsi +} + +{ + Reported on FC5 and RHEL5 when md5 sasl libs are installed + Memcheck:Leak + fun:* + fun:_dl_map_object_from_fd + fun:_dl_map_object + fun:openaux + fun:_dl_catch_error + fun:_dl_map_object_deps + fun:dl_open_worker + fun:_dl_catch_error + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_error + fun:_dlerror_run + fun:* + fun:_sasl_get_plugin + fun:_sasl_load_plugins + fun:sasl_client_init +} +{ + Benign leak in CPG - patched version. + Memcheck:Leak + fun:* + fun:openais_service_connect + fun:cpg_initialize +} + +{ + Benign error in libcpg. + Memcheck:Param + socketcall.sendmsg(msg.msg_iov[i]) + obj:*/libpthread-2.5.so + obj:*/libcpg.so.2.0.0 +} + +{ + Uninitialised value problem in _dl_relocate (F7, F8) + Memcheck:Cond + fun:_dl_relocate_object + fun:*dl_* +} + +{ + False "possibly leaked" in boost program_options - global std::string var. + Memcheck:Leak + fun:_Znwj + fun:_ZNSs4_Rep9_S_createEjjRKSaIcE + obj:/usr/lib/libstdc++.so.6.0.8 + fun:_ZNSsC1EPKcRKSaIcE + obj:/usr/lib/libboost_program_options.so.1.33.1 +} + +{ + INVESTIGATE + Memcheck:Leak + fun:calloc + fun:_dl_allocate_tls + fun:pthread_create@@GLIBC_2.2.5 + fun:_ZN4qpid6client9Connector4initEv + fun:_ZN4qpid6client14ConnectionImpl4openERKSsiS3_S3_S3_ +} + +{ + INVESTIGATE + Memcheck:Param + write(buf) + obj:/lib64/tls/libc-2.3.4.so + fun:_ZNK4qpid3sys6Socket5writeEPKvm + fun:_ZN4qpid3sys8AsynchIO9writeableERNS0_14DispatchHandleE +} + +{ + "Conditional jump or move depends on uninitialised value(s)" from Xerces parser + Memcheck:Cond + fun:_ZN11xercesc_2_717XMLUTF8Transcoder13transcodeFromEPKhjPtjRjPh + fun:_ZN11xercesc_2_79XMLReader14xcodeMoreCharsEPtPhj + fun:_ZN11xercesc_2_79XMLReader17refreshCharBufferEv +} + +{ + INVESTIGATE + Memcheck:Param + socketcall.sendto(msg) + fun:send + fun:get_mapping + fun:__nscd_get_map_ref + fun:nscd_gethst_r + fun:__nscd_gethostbyname_r + fun:gethostbyname_r@@GLIBC_2.2.5 + fun:gethostbyname + fun:_ZNK4qpid3sys6Socket7connectERKSsi +} + +{ + INVESTIGATE + 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:_ZN20ClientSessionFixtureC1Ev + 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 + fun:_dl_allocate_tls + fun:pthread_create@@GLIBC_2.2.5 + fun:_ZN4qpid6client9Connector4initEv +} + +{ + MICK -- FIX + Memcheck:Leak + fun:_Znam + fun:_ZN4qpid7Options5parseEiPPcRKSsb +} + +{ + MICK -- FIX + Memcheck:Leak + fun:malloc + fun:strdup + fun:_ZN4qpid7Options5parseEiPPcRKSsb +} + +{ + CPG error - seems benign. + Memcheck:Param + socketcall.sendmsg(msg.msg_iov[i]) + obj:* + obj:*/libcpg.so.2.0.0 +} + +{ + Known leak in boost.thread 1.33.1. Wildcards for 64/32 bit diffs. + Memcheck:Leak + fun:* + obj:/usr/*/libboost_thread.so.1.33.1 + fun:_ZN5boost6detail3tss3setEPv +} + +{ + Shows up on RHEL5: believed benign + Memcheck:Cond + fun:__strcpy_chk + fun:_sasl_load_plugins + fun:sasl_client_init +} + +{ + Seems like a use after delete issue in boost unit_test + Memcheck:Addr8 + fun:_ZN5boost9unit_test14framework_implD1Ev + fun:exit + fun:(below main) +} + +{ + Seems like a use after delete issue in boost unit_test + Memcheck:Addr4 + fun:_ZN5boost9unit_test14framework_implD1Ev + fun:exit + fun:(below main) +} + diff --git a/qpid/cpp/src/tests/AccumulatedAckTest.cpp b/qpid/cpp/src/tests/AccumulatedAckTest.cpp new file mode 100644 index 0000000000..c736a519d2 --- /dev/null +++ b/qpid/cpp/src/tests/AccumulatedAckTest.cpp @@ -0,0 +1,237 @@ + +/* + * + * 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/AccumulatedAck.h" +#include "unit_test.h" +#include <iostream> +#include <list> + +using std::list; +using namespace qpid::framing; + + +namespace qpid { +namespace tests { + +bool covers(const AccumulatedAck& ack, int i) +{ + return ack.covers(SequenceNumber(i)); +} + +void update(AccumulatedAck& ack, int start, int end) +{ + ack.update(SequenceNumber(start), SequenceNumber(end)); +} + +QPID_AUTO_TEST_SUITE(AccumulatedAckTestSuite) + +QPID_AUTO_TEST_CASE(testGeneral) +{ + AccumulatedAck ack(0); + ack.clear(); + update(ack, 3,3); + update(ack, 7,7); + update(ack, 9,9); + update(ack, 1,2); + update(ack, 4,5); + update(ack, 6,6); + + for(int i = 1; i <= 7; i++) BOOST_CHECK(covers(ack, i)); + BOOST_CHECK(covers(ack, 9)); + + BOOST_CHECK(!covers(ack, 8)); + BOOST_CHECK(!covers(ack, 10)); + + ack.consolidate(); + + for(int i = 1; i <= 7; i++) BOOST_CHECK(covers(ack, i)); + BOOST_CHECK(covers(ack, 9)); + + BOOST_CHECK(!covers(ack, 8)); + BOOST_CHECK(!covers(ack, 10)); +} + +QPID_AUTO_TEST_CASE(testCovers) +{ + AccumulatedAck ack(5); + update(ack, 7, 7); + update(ack, 9, 9); + + BOOST_CHECK(covers(ack, 1)); + BOOST_CHECK(covers(ack, 2)); + BOOST_CHECK(covers(ack, 3)); + BOOST_CHECK(covers(ack, 4)); + BOOST_CHECK(covers(ack, 5)); + BOOST_CHECK(covers(ack, 7)); + BOOST_CHECK(covers(ack, 9)); + + BOOST_CHECK(!covers(ack, 6)); + BOOST_CHECK(!covers(ack, 8)); + BOOST_CHECK(!covers(ack, 10)); +} + +QPID_AUTO_TEST_CASE(testUpdateFromCompletionData) +{ + AccumulatedAck ack(0); + SequenceNumber mark(2); + SequenceNumberSet ranges; + ranges.addRange(SequenceNumber(5), SequenceNumber(8)); + ranges.addRange(SequenceNumber(10), SequenceNumber(15)); + ranges.addRange(SequenceNumber(9), SequenceNumber(9)); + ranges.addRange(SequenceNumber(3), SequenceNumber(4)); + + ack.update(mark, ranges); + + for(int i = 0; i <= 15; i++) { + BOOST_CHECK(covers(ack, i)); + } + BOOST_CHECK(!covers(ack, 16)); + BOOST_CHECK_EQUAL((uint32_t) 15, ack.mark.getValue()); +} + +QPID_AUTO_TEST_CASE(testCase1) +{ + AccumulatedAck ack(3); + update(ack, 1,2); + for(int i = 1; i <= 3; i++) BOOST_CHECK(covers(ack, i)); + BOOST_CHECK(!covers(ack, 4)); +} + +QPID_AUTO_TEST_CASE(testCase2) +{ + AccumulatedAck ack(3); + update(ack, 3,6); + for(int i = 1; i <= 6; i++) BOOST_CHECK(covers(ack, i)); + BOOST_CHECK(!covers(ack, 7)); +} + +QPID_AUTO_TEST_CASE(testCase3) +{ + AccumulatedAck ack(3); + update(ack, 4,6); + for(int i = 1; i <= 6; i++) { + BOOST_CHECK(covers(ack, i)); + } + BOOST_CHECK(!covers(ack, 7)); +} + +QPID_AUTO_TEST_CASE(testCase4) +{ + AccumulatedAck ack(3); + update(ack, 5,6); + for(int i = 1; i <= 6; i++) { + if (i == 4) BOOST_CHECK(!covers(ack, i)); + else BOOST_CHECK(covers(ack, i)); + } + BOOST_CHECK(!covers(ack, 7)); +} + +QPID_AUTO_TEST_CASE(testConsolidation1) +{ + AccumulatedAck ack(3); + update(ack, 7,7); + BOOST_CHECK_EQUAL((uint32_t) 3, ack.mark.getValue()); + BOOST_CHECK_EQUAL((size_t) 1, ack.ranges.size()); + + update(ack, 8,9); + BOOST_CHECK_EQUAL((uint32_t) 3, ack.mark.getValue()); + BOOST_CHECK_EQUAL((size_t) 1, ack.ranges.size()); + + update(ack, 1,2); + BOOST_CHECK_EQUAL((uint32_t) 3, ack.mark.getValue()); + BOOST_CHECK_EQUAL((size_t) 1, ack.ranges.size()); + + update(ack, 4,5); + BOOST_CHECK_EQUAL((uint32_t) 5, ack.mark.getValue()); + BOOST_CHECK_EQUAL((size_t) 1, ack.ranges.size()); + + update(ack, 6,6); + BOOST_CHECK_EQUAL((uint32_t) 9, ack.mark.getValue()); + BOOST_CHECK_EQUAL((size_t) 0, ack.ranges.size()); + + for(int i = 1; i <= 9; i++) BOOST_CHECK(covers(ack, i)); + BOOST_CHECK(!covers(ack, 10)); +} + +QPID_AUTO_TEST_CASE(testConsolidation2) +{ + AccumulatedAck ack(0); + update(ack, 10,12); + BOOST_CHECK_EQUAL((uint32_t) 0, ack.mark.getValue()); + BOOST_CHECK_EQUAL((size_t) 1, ack.ranges.size()); + + update(ack, 7,9); + BOOST_CHECK_EQUAL((uint32_t) 0, ack.mark.getValue()); + BOOST_CHECK_EQUAL((size_t) 1, ack.ranges.size()); + BOOST_CHECK_EQUAL((uint32_t) 7, ack.ranges.front().start.getValue()); + BOOST_CHECK_EQUAL((uint32_t) 12, ack.ranges.front().end.getValue()); + + update(ack, 5,7); + BOOST_CHECK_EQUAL((uint32_t) 0, ack.mark.getValue()); + BOOST_CHECK_EQUAL((size_t) 1, ack.ranges.size()); + BOOST_CHECK_EQUAL((uint32_t) 5, ack.ranges.front().start.getValue()); + BOOST_CHECK_EQUAL((uint32_t) 12, ack.ranges.front().end.getValue()); + + update(ack, 3,4); + BOOST_CHECK_EQUAL((uint32_t) 0, ack.mark.getValue()); + BOOST_CHECK_EQUAL((size_t) 1, ack.ranges.size()); + BOOST_CHECK_EQUAL((uint32_t) 3, ack.ranges.front().start.getValue()); + BOOST_CHECK_EQUAL((uint32_t) 12, ack.ranges.front().end.getValue()); + + update(ack, 1,2); + BOOST_CHECK_EQUAL((uint32_t) 12, ack.mark.getValue()); + BOOST_CHECK_EQUAL((size_t) 0, ack.ranges.size()); + + for(int i = 1; i <= 12; i++) BOOST_CHECK(covers(ack, i)); + BOOST_CHECK(!covers(ack, 13)); +} + +QPID_AUTO_TEST_CASE(testConsolidation3) +{ + AccumulatedAck ack(0); + update(ack, 10,12); + update(ack, 6,7); + update(ack, 3,4); + update(ack, 1,15); + BOOST_CHECK_EQUAL((uint32_t) 15, ack.mark.getValue()); + BOOST_CHECK_EQUAL((size_t) 0, ack.ranges.size()); +} + +QPID_AUTO_TEST_CASE(testConsolidation4) +{ + AccumulatedAck ack(0); + ack.update(SequenceNumber(0), SequenceNumber(2)); + ack.update(SequenceNumber(5), SequenceNumber(8)); + ack.update(SequenceNumber(10), SequenceNumber(15)); + ack.update(SequenceNumber(9), SequenceNumber(9)); + ack.update(SequenceNumber(3), SequenceNumber(4)); + + for(int i = 0; i <= 15; i++) { + BOOST_CHECK(covers(ack, i)); + } + BOOST_CHECK(!covers(ack, 16)); + BOOST_CHECK_EQUAL((uint32_t) 15, ack.mark.getValue()); +} + +QPID_AUTO_TEST_SUITE_END() + + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/Address.cpp b/qpid/cpp/src/tests/Address.cpp new file mode 100644 index 0000000000..f41f27b6df --- /dev/null +++ b/qpid/cpp/src/tests/Address.cpp @@ -0,0 +1,124 @@ +/* + * + * 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 "qpid/messaging/Address.h" +#include "qpid/types/Variant.h" + +#include "unit_test.h" + +using namespace qpid::messaging; +using namespace qpid::types; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(AddressSuite) + +QPID_AUTO_TEST_CASE(testParseNameOnly) +{ + Address address("my-topic"); + BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName()); +} + +QPID_AUTO_TEST_CASE(testParseSubject) +{ + Address address("my-topic/my-subject"); + BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName()); + BOOST_CHECK_EQUAL(std::string("my-subject"), address.getSubject()); +} + +QPID_AUTO_TEST_CASE(testParseOptions) +{ + Address address("my-topic; {a:bc, x:101, y:'a string'}"); + BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName()); + + BOOST_CHECK_EQUAL(std::string("bc"), address.getOptions()["a"]); + BOOST_CHECK_EQUAL(101, static_cast<int>(address.getOptions()["x"])); + BOOST_CHECK_EQUAL(std::string("a string"), address.getOptions()["y"]); + + // Test asString() and asInt64() once here + + BOOST_CHECK_EQUAL(std::string("bc"), address.getOptions()["a"].asString()); + BOOST_CHECK_EQUAL(static_cast<uint16_t>(101), address.getOptions()["x"].asInt64()); + BOOST_CHECK_EQUAL(std::string("a string"), address.getOptions()["y"].asString()); +} + +QPID_AUTO_TEST_CASE(testParseSubjectAndOptions) +{ + Address address("my-topic/my-subject; {a:bc, x:101, y:'a string'}"); + BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName()); + BOOST_CHECK_EQUAL(std::string("my-subject"), address.getSubject()); + + BOOST_CHECK_EQUAL(std::string("bc"), address.getOptions()["a"]); + BOOST_CHECK_EQUAL(101, static_cast<int>(address.getOptions()["x"])); + BOOST_CHECK_EQUAL(std::string("a string"), address.getOptions()["y"]); +} + +QPID_AUTO_TEST_CASE(testParseNestedOptions) +{ + Address address("my-topic; {a:{p:202, q:'another string'}, x:101, y:'a string'}"); + BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName()); + BOOST_CHECK_EQUAL(202, static_cast<int>(address.getOptions()["a"].asMap()["p"])); + BOOST_CHECK_EQUAL(std::string("another string"), address.getOptions()["a"].asMap()["q"]); + BOOST_CHECK_EQUAL(std::string("a string"), address.getOptions()["y"]); +} + +QPID_AUTO_TEST_CASE(testParseOptionsWithList) +{ + Address address("my-topic; {a:[202, 'another string'], x:101}"); + BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName()); + Variant::List& list = address.getOptions()["a"].asList(); + Variant::List::const_iterator i = list.begin(); + BOOST_CHECK(i != list.end()); + BOOST_CHECK_EQUAL((uint16_t) 202, i->asInt64()); + BOOST_CHECK(++i != list.end()); + BOOST_CHECK_EQUAL(std::string("another string"), i->asString()); + BOOST_CHECK_EQUAL((uint16_t) 101, address.getOptions()["x"].asInt64()); +} + +QPID_AUTO_TEST_CASE(testParseOptionsWithEmptyList) +{ + Address address("my-topic; {a:[], x:101}"); + BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName()); + Variant::List& list = address.getOptions()["a"].asList(); + BOOST_CHECK_EQUAL(list.size(), (size_t) 0); + BOOST_CHECK_EQUAL((uint16_t) 101, address.getOptions()["x"].asInt64()); +} + +QPID_AUTO_TEST_CASE(testParseOptionsWithEmptyMap) +{ + Address address("my-topic; {a:{}, x:101}"); + BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName()); + Variant::Map& map = address.getOptions()["a"].asMap(); + BOOST_CHECK_EQUAL(map.size(), (size_t) 0); + BOOST_CHECK_EQUAL((uint16_t) 101, address.getOptions()["x"].asInt64()); +} + +QPID_AUTO_TEST_CASE(testParseQuotedNameAndSubject) +{ + Address address("'my topic with / in it'/'my subject with ; in it'"); + BOOST_CHECK_EQUAL(std::string("my topic with / in it"), address.getName()); + BOOST_CHECK_EQUAL(std::string("my subject with ; in it"), address.getSubject()); +} + +QPID_AUTO_TEST_SUITE_END() + +}} diff --git a/qpid/cpp/src/tests/Array.cpp b/qpid/cpp/src/tests/Array.cpp new file mode 100644 index 0000000000..7622b89d15 --- /dev/null +++ b/qpid/cpp/src/tests/Array.cpp @@ -0,0 +1,84 @@ +/* + * + * 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/framing/Array.h" +#include "qpid/framing/FieldValue.h" + +#include "unit_test.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(ArrayTestSuite) + +using namespace qpid::framing; + +void populate(std::vector<std::string>& data, int count = 10) +{ + for (int i = 0; i < count; i++) { + std::stringstream out; + out << "item-" << i; + data.push_back(out.str()); + } +} + +QPID_AUTO_TEST_CASE(testEncodeDecode) +{ + std::vector<std::string> data; + populate(data); + + Array a(data); + + char buff[200]; + Buffer wbuffer(buff, 200); + a.encode(wbuffer); + + Array b; + Buffer rbuffer(buff, 200); + b.decode(rbuffer); + BOOST_CHECK_EQUAL(a, b); + + std::vector<std::string> data2; + b.collect(data2); + //BOOST_CHECK_EQUAL(data, data2); + BOOST_CHECK(data == data2); +} + +QPID_AUTO_TEST_CASE(testArrayAssignment) +{ + std::vector<std::string> data; + populate(data); + Array b; + { + Array a(data); + b = a; + BOOST_CHECK_EQUAL(a, b); + } + std::vector<std::string> data2; + b.collect(data2); + //BOOST_CHECK_EQUAL(data, data2); + BOOST_CHECK(data == data2); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/AsyncCompletion.cpp b/qpid/cpp/src/tests/AsyncCompletion.cpp new file mode 100644 index 0000000000..e32097106f --- /dev/null +++ b/qpid/cpp/src/tests/AsyncCompletion.cpp @@ -0,0 +1,120 @@ +/* + * + * 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. + * + */ + + +#include "unit_test.h" +#include "test_tools.h" +#include "BrokerFixture.h" +#include "qpid/broker/NullMessageStore.h" +#include "qpid/sys/BlockingQueue.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/sys/Time.h" +#include "qpid/framing/QueueQueryResult.h" +#include "qpid/client/TypedResult.h" + +using namespace std; +using namespace qpid; +using namespace client; +using namespace framing; + +namespace qpid { namespace broker { +class TransactionContext; +class PersistableQueue; +}} + +using broker::PersistableMessage; +using broker::NullMessageStore; +using broker::TransactionContext; +using broker::PersistableQueue; +using sys::TIME_SEC; +using boost::intrusive_ptr; + +/** @file Unit tests for async completion. + * Using a dummy store, verify that the broker indicates async completion of + * message enqueues at the correct time. + */ + +namespace qpid { +namespace tests { + +class AsyncCompletionMessageStore : public NullMessageStore { + public: + sys::BlockingQueue<boost::intrusive_ptr<PersistableMessage> > enqueued; + + AsyncCompletionMessageStore() : NullMessageStore() {} + ~AsyncCompletionMessageStore(){} + + void enqueue(TransactionContext*, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& ) + { + enqueued.push(msg); + } +}; + +QPID_AUTO_TEST_SUITE(AsyncCompletionTestSuite) + +QPID_AUTO_TEST_CASE(testWaitTillComplete) { + SessionFixture fix; + AsyncCompletionMessageStore* store = new AsyncCompletionMessageStore; + boost::shared_ptr<qpid::broker::MessageStore> p; + p.reset(store); + fix.broker->setStore(p); + AsyncSession s = fix.session; + + static const int count = 3; + + s.queueDeclare("q", arg::durable=true); + Completion transfers[count]; + for (int i = 0; i < count; ++i) { + Message msg(boost::lexical_cast<string>(i), "q"); + msg.getDeliveryProperties().setDeliveryMode(PERSISTENT); + transfers[i] = s.messageTransfer(arg::content=msg); + } + + // Get hold of the broker-side messages. + typedef vector<intrusive_ptr<PersistableMessage> > BrokerMessages; + BrokerMessages enqueued; + for (int j = 0; j < count; ++j) + enqueued.push_back(store->enqueued.pop(TIME_SEC)); + + // Send a sync, make sure it does not complete till all messages are complete. + // In reverse order for fun. + Completion sync = s.executionSync(arg::sync=true); + for (int k = count-1; k >= 0; --k) { + BOOST_CHECK(!transfers[k].isComplete()); // Should not be complete yet. + BOOST_CHECK(!sync.isComplete()); // Should not be complete yet. + enqueued[k]->enqueueComplete(); + } + sync.wait(); // Should complete now, all messages are completed. +} + +QPID_AUTO_TEST_CASE(testGetResult) { + SessionFixture fix; + AsyncSession s = fix.session; + + s.queueDeclare("q", arg::durable=true); + TypedResult<QueueQueryResult> tr = s.queueQuery("q"); + QueueQueryResult qq = tr.get(); + BOOST_CHECK_EQUAL(qq.getQueue(), "q"); + BOOST_CHECK_EQUAL(qq.getMessageCount(), 0U); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/AtomicValue.cpp b/qpid/cpp/src/tests/AtomicValue.cpp new file mode 100644 index 0000000000..d855d993a7 --- /dev/null +++ b/qpid/cpp/src/tests/AtomicValue.cpp @@ -0,0 +1,54 @@ +/* + * + * 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. + * + */ + + +#include "unit_test.h" +#include "test_tools.h" +#include "qpid/sys/AtomicValue.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(AtomicValueTestSuite) + +QPID_AUTO_TEST_CASE(test) { + qpid::sys::AtomicValue<int> x(0); + BOOST_CHECK_EQUAL(++x, 1); + BOOST_CHECK_EQUAL(--x,0); + BOOST_CHECK_EQUAL(x+=5,5); + BOOST_CHECK_EQUAL(x-=10,-5); + BOOST_CHECK_EQUAL(x.fetchAndAdd(7), -5); + BOOST_CHECK_EQUAL(x.get(),2); + BOOST_CHECK_EQUAL(x.fetchAndSub(3), 2); + BOOST_CHECK_EQUAL(x.get(),-1); + + BOOST_CHECK_EQUAL(x.valueCompareAndSwap(-1,10), -1); + BOOST_CHECK_EQUAL(x.get(), 10); + BOOST_CHECK_EQUAL(x.valueCompareAndSwap(5, 6), 10); + BOOST_CHECK_EQUAL(x.get(), 10); + + BOOST_CHECK(!x.boolCompareAndSwap(5, 6)); + BOOST_CHECK_EQUAL(x.get(), 10); + BOOST_CHECK(x.boolCompareAndSwap(10, 6)); + BOOST_CHECK_EQUAL(x.get(), 6); +} + + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/Blob.cpp b/qpid/cpp/src/tests/Blob.cpp new file mode 100644 index 0000000000..9878d92fe4 --- /dev/null +++ b/qpid/cpp/src/tests/Blob.cpp @@ -0,0 +1,21 @@ +/* + * + * 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. + * + */ + diff --git a/qpid/cpp/src/tests/BrokerFixture.h b/qpid/cpp/src/tests/BrokerFixture.h new file mode 100644 index 0000000000..672d954572 --- /dev/null +++ b/qpid/cpp/src/tests/BrokerFixture.h @@ -0,0 +1,154 @@ +#ifndef TESTS_BROKERFIXTURE_H +#define TESTS_BROKERFIXTURE_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 "SocketProxy.h" + +#include "qpid/broker/Broker.h" +#include "qpid/client/Connection.h" +#include "qpid/client/ConnectionImpl.h" +#include "qpid/client/Session.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/client/LocalQueue.h" +#include "qpid/log/Logger.h" +#include "qpid/log/Options.h" +#include "qpid/sys/Thread.h" +#include <boost/noncopyable.hpp> + +namespace qpid { +namespace tests { + +/** + * A fixture with an in-process broker. + */ +struct BrokerFixture : private boost::noncopyable { + typedef qpid::broker::Broker Broker; + typedef boost::intrusive_ptr<Broker> BrokerPtr; + + BrokerPtr broker; + qpid::sys::Thread brokerThread; + + BrokerFixture(Broker::Options opts=Broker::Options(), bool enableMgmt=false) { + // Keep the tests quiet unless logging env. vars have been set by user. + if (!::getenv("QPID_LOG_ENABLE") && !::getenv("QPID_TRACE")) { + qpid::log::Options logOpts; + logOpts.selectors.clear(); + logOpts.selectors.push_back("error+"); + qpid::log::Logger::instance().configure(logOpts); + } + opts.port=0; + // Management doesn't play well with multiple in-process brokers. + opts.enableMgmt=enableMgmt; + opts.workerThreads=1; + opts.dataDir=""; + opts.auth=false; + broker = Broker::create(opts); + // TODO aconway 2007-12-05: At one point BrokerFixture + // tests could hang in Connection ctor if the following + // line is removed. This may not be an issue anymore. + broker->accept(); + broker->getPort(qpid::broker::Broker::TCP_TRANSPORT); + brokerThread = qpid::sys::Thread(*broker); + }; + + void shutdownBroker() + { + broker->shutdown(); + broker = BrokerPtr(); + } + + ~BrokerFixture() { + if (broker) broker->shutdown(); + brokerThread.join(); + } + + /** Open a connection to the broker. */ + void open(qpid::client::Connection& c) { + c.open("localhost", broker->getPort(qpid::broker::Broker::TCP_TRANSPORT)); + } + + uint16_t getPort() { return broker->getPort(qpid::broker::Broker::TCP_TRANSPORT); } +}; + +/** Connection that opens in its constructor */ +struct LocalConnection : public qpid::client::Connection { + LocalConnection(uint16_t port) { open("localhost", port); } + LocalConnection(const qpid::client::ConnectionSettings& s) { open(s); } + ~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. + */ +template <class ConnectionType=LocalConnection, class SessionType=qpid::client::Session> +struct ClientT { + ConnectionType connection; + SessionType session; + qpid::client::SubscriptionManager subs; + qpid::client::LocalQueue lq; + std::string name; + + ClientT(uint16_t port, const std::string& name_=std::string(), int timeout=0) + : connection(port), session(connection.newSession(name_,timeout)), subs(session), name(name_) {} + ClientT(const qpid::client::ConnectionSettings& settings, const std::string& name_=std::string(), int timeout=0) + : connection(settings), session(connection.newSession(name_, timeout)), subs(session), name(name_) {} + + ~ClientT() { close(); } + void close() { session.close(); connection.close(); } +}; + +typedef ClientT<> Client; + +/** + * A BrokerFixture and ready-connected BrokerFixture::Client all in one. + */ +template <class ConnectionType, class SessionType=qpid::client::Session> +struct SessionFixtureT : BrokerFixture, ClientT<ConnectionType,SessionType> { + + SessionFixtureT(Broker::Options opts=Broker::Options()) : + BrokerFixture(opts), + ClientT<ConnectionType,SessionType>(broker->getPort(qpid::broker::Broker::TCP_TRANSPORT)) + {} + +}; + +typedef SessionFixtureT<LocalConnection> SessionFixture; +typedef SessionFixtureT<ProxyConnection> ProxySessionFixture; + +}} // namespace qpid::tests + +#endif /*!TESTS_BROKERFIXTURE_H*/ diff --git a/qpid/cpp/src/tests/BrokerMgmtAgent.cpp b/qpid/cpp/src/tests/BrokerMgmtAgent.cpp new file mode 100644 index 0000000000..d0c6668b72 --- /dev/null +++ b/qpid/cpp/src/tests/BrokerMgmtAgent.cpp @@ -0,0 +1,792 @@ +/* + * + * 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 "unit_test.h" +#include "MessagingFixture.h" +#include "qpid/management/Buffer.h" +#include "qpid/messaging/Message.h" +#include "qpid/amqp_0_10/Codecs.h" +#include "qpid/log/Logger.h" +#include "qpid/log/Options.h" + +#include "qmf/org/apache/qpid/broker/mgmt/test/TestObject.h" + +#include <iomanip> + + +using qpid::management::Mutex; +using qpid::management::Manageable; +using qpid::management::Buffer; +using namespace qpid::messaging; +using namespace qpid::types; + + + +namespace qpid { + namespace tests { + + namespace _qmf = qmf::org::apache::qpid::broker::mgmt::test; + namespace { + + typedef boost::shared_ptr<_qmf::TestObject> TestObjectPtr; + typedef std::vector<TestObjectPtr> TestObjectVector; + + // Instantiates a broker and its internal management agent. Provides + // factories for constructing Receivers for object indication messages. + // + class AgentFixture + { + MessagingFixture *mFix; + + public: + AgentFixture( unsigned int pubInterval=10, + bool qmfV2=false, + qpid::broker::Broker::Options opts = qpid::broker::Broker::Options()) + { + opts.enableMgmt=true; + opts.qmf2Support=qmfV2; + opts.mgmtPubInterval=pubInterval; + mFix = new MessagingFixture(opts, true); + + _qmf::TestObject::registerSelf(getBrokerAgent()); + }; + ~AgentFixture() + { + delete mFix; + }; + ::qpid::management::ManagementAgent *getBrokerAgent() { return mFix->broker->getManagementAgent(); } + Receiver createV1DataIndRcvr( const std::string package, const std::string klass ) + { + return mFix->session.createReceiver(std::string("kqueue; {create: always, delete: always, " + "node: {type: queue, " + "x-bindings: [{exchange: qpid.management, " + "key: 'console.obj.1.0.") + + package + std::string(".") + klass + + std::string("'}]}}")); + }; + Receiver createV2DataIndRcvr( const std::string package, const std::string klass ) + { + std::string p(package); + std::replace(p.begin(), p.end(), '.', '_'); + std::string k(klass); + std::replace(k.begin(), k.end(), '.', '_'); + + return mFix->session.createReceiver(std::string("kqueue; {create: always, delete: always, " + "node: {type: queue, " + "x-bindings: [{exchange: qmf.default.topic, " + "key: 'agent.ind.data.") + + p + std::string(".") + k + + std::string("'}]}}")); + }; + }; + + + // A "management object" that supports the TestObject + // + class TestManageable : public qpid::management::Manageable + { + management::ManagementObject* mgmtObj; + const std::string key; + public: + TestManageable(management::ManagementAgent *agent, std::string _key) + : key(_key) + { + _qmf::TestObject *tmp = new _qmf::TestObject(agent, this); + + // seed it with some default values... + tmp->set_string1(key); + tmp->set_bool1(true); + qpid::types::Variant::Map vMap; + vMap["one"] = qpid::types::Variant(1); + vMap["two"] = qpid::types::Variant("two"); + vMap["three"] = qpid::types::Variant("whatever"); + tmp->set_map1(vMap); + + mgmtObj = tmp; + }; + ~TestManageable() { mgmtObj = 0; /* deleted by agent on shutdown */ }; + management::ManagementObject* GetManagementObject() const { return mgmtObj; }; + static void validateTestObjectProperties(_qmf::TestObject& to) + { + // verify the default values are as expected. We don't check 'string1', + // as it is the object key, and is unique for each object (no default value). + BOOST_CHECK(to.get_bool1() == true); + BOOST_CHECK(to.get_map1().size() == 3); + qpid::types::Variant::Map mappy = to.get_map1(); + BOOST_CHECK(1 == (unsigned int)mappy["one"]); + BOOST_CHECK(mappy["two"].asString() == std::string("two")); + BOOST_CHECK(mappy["three"].asString() == std::string("whatever")); + }; + }; + + + // decode a V1 Content Indication message + // + void decodeV1ObjectUpdates(const Message& inMsg, TestObjectVector& objs, const size_t objLen) + { + const size_t MAX_BUFFER_SIZE=65536; + char tmp[MAX_BUFFER_SIZE]; + + objs.clear(); + + BOOST_CHECK(inMsg.getContent().size() <= MAX_BUFFER_SIZE); + + ::memcpy(tmp, inMsg.getContent().data(), inMsg.getContent().size()); + Buffer buf(tmp, inMsg.getContent().size()); + + while (buf.available() > 8) { // 8 == qmf v1 header size + BOOST_CHECK_EQUAL(buf.getOctet(), 'A'); + BOOST_CHECK_EQUAL(buf.getOctet(), 'M'); + BOOST_CHECK_EQUAL(buf.getOctet(), '2'); + BOOST_CHECK_EQUAL(buf.getOctet(), 'c'); // opcode == content indication + // @@todo: kag: how do we skip 'i' entries??? + buf.getLong(); // ignore sequence + + std::string str1; // decode content body as string + buf.getRawData(str1, objLen); + + TestObjectPtr fake(new _qmf::TestObject(0,0)); + fake->readProperties( str1 ); + objs.push_back(fake); + } + } + + + // decode a V2 Content Indication message + // + void decodeV2ObjectUpdates(const qpid::messaging::Message& inMsg, TestObjectVector& objs) + { + objs.clear(); + + BOOST_CHECK_EQUAL(inMsg.getContentType(), std::string("amqp/list")); + + const ::qpid::types::Variant::Map& m = inMsg.getProperties(); + Variant::Map::const_iterator iter = m.find(std::string("qmf.opcode")); + BOOST_CHECK(iter != m.end()); + BOOST_CHECK_EQUAL(iter->second.asString(), std::string("_data_indication")); + + Variant::List vList; + ::qpid::amqp_0_10::ListCodec::decode(inMsg.getContent(), vList); + + for (Variant::List::iterator lIter = vList.begin(); lIter != vList.end(); lIter++) { + TestObjectPtr fake(new _qmf::TestObject(0,0)); + fake->readTimestamps(lIter->asMap()); + fake->mapDecodeValues((lIter->asMap())["_values"].asMap()); + objs.push_back(fake); + } + } + } + + QPID_AUTO_TEST_SUITE(BrokerMgmtAgent) + + // verify that an object that is added to the broker's management database is + // published correctly. Furthermore, verify that it is published once after + // it has been deleted. + // + QPID_AUTO_TEST_CASE(v1ObjPublish) + { + AgentFixture* fix = new AgentFixture(3); + management::ManagementAgent* agent; + agent = fix->getBrokerAgent(); + + // create a manageable test object + TestManageable *tm = new TestManageable(agent, std::string("obj1")); + uint32_t objLen = tm->GetManagementObject()->writePropertiesSize(); + + Receiver r1 = fix->createV1DataIndRcvr("org.apache.qpid.broker.mgmt.test", "#"); + + agent->addObject(tm->GetManagementObject(), 1); + + // wait for the object to be published + Message m1; + BOOST_CHECK(r1.fetch(m1, Duration::SECOND * 6)); + + TestObjectVector objs; + decodeV1ObjectUpdates(m1, objs, objLen); + BOOST_CHECK(objs.size() > 0); + + for (TestObjectVector::iterator oIter = objs.begin(); oIter != objs.end(); oIter++) { + + TestManageable::validateTestObjectProperties(**oIter); + + qpid::types::Variant::Map mappy; + (*oIter)->writeTimestamps(mappy); + BOOST_CHECK(0 == mappy["_delete_ts"].asUint64()); // not deleted + } + + // destroy the object + + tm->GetManagementObject()->resourceDestroy(); + + // wait for the deleted object to be published + + bool isDeleted = false; + while (!isDeleted && r1.fetch(m1, Duration::SECOND * 6)) { + + decodeV1ObjectUpdates(m1, objs, objLen); + BOOST_CHECK(objs.size() > 0); + + for (TestObjectVector::iterator oIter = objs.begin(); oIter != objs.end(); oIter++) { + + TestManageable::validateTestObjectProperties(**oIter); + + qpid::types::Variant::Map mappy; + (*oIter)->writeTimestamps(mappy); + if (mappy["_delete_ts"].asUint64() != 0) + isDeleted = true; + } + } + + BOOST_CHECK(isDeleted); + + r1.close(); + delete fix; + delete tm; + } + + // Repeat the previous test, but with V2-based object support + // + QPID_AUTO_TEST_CASE(v2ObjPublish) + { + AgentFixture* fix = new AgentFixture(3, true); + management::ManagementAgent* agent; + agent = fix->getBrokerAgent(); + + TestManageable *tm = new TestManageable(agent, std::string("obj2")); + + Receiver r1 = fix->createV2DataIndRcvr(tm->GetManagementObject()->getPackageName(), "#"); + + agent->addObject(tm->GetManagementObject(), "testobj-1"); + + // wait for the object to be published + Message m1; + BOOST_CHECK(r1.fetch(m1, Duration::SECOND * 6)); + + TestObjectVector objs; + decodeV2ObjectUpdates(m1, objs); + BOOST_CHECK(objs.size() > 0); + + for (TestObjectVector::iterator oIter = objs.begin(); oIter != objs.end(); oIter++) { + + TestManageable::validateTestObjectProperties(**oIter); + + qpid::types::Variant::Map mappy; + (*oIter)->writeTimestamps(mappy); + BOOST_CHECK(0 == mappy["_delete_ts"].asUint64()); + } + + // destroy the object + + tm->GetManagementObject()->resourceDestroy(); + + // wait for the deleted object to be published + + bool isDeleted = false; + while (!isDeleted && r1.fetch(m1, Duration::SECOND * 6)) { + + decodeV2ObjectUpdates(m1, objs); + BOOST_CHECK(objs.size() > 0); + + for (TestObjectVector::iterator oIter = objs.begin(); oIter != objs.end(); oIter++) { + + TestManageable::validateTestObjectProperties(**oIter); + + qpid::types::Variant::Map mappy; + (*oIter)->writeTimestamps(mappy); + if (mappy["_delete_ts"].asUint64() != 0) + isDeleted = true; + } + } + + BOOST_CHECK(isDeleted); + + r1.close(); + delete fix; + delete tm; + } + + + // verify that a deleted object is exported correctly using the + // exportDeletedObjects() method. V1 testcase. + // + QPID_AUTO_TEST_CASE(v1ExportDelObj) + { + AgentFixture* fix = new AgentFixture(3); + management::ManagementAgent* agent; + agent = fix->getBrokerAgent(); + + // create a manageable test object + TestManageable *tm = new TestManageable(agent, std::string("myObj")); + uint32_t objLen = tm->GetManagementObject()->writePropertiesSize(); + + Receiver r1 = fix->createV1DataIndRcvr("org.apache.qpid.broker.mgmt.test", "#"); + + agent->addObject(tm->GetManagementObject(), 1); + + // wait for the object to be published + Message m1; + BOOST_CHECK(r1.fetch(m1, Duration::SECOND * 6)); + + TestObjectVector objs; + decodeV1ObjectUpdates(m1, objs, objLen); + BOOST_CHECK(objs.size() > 0); + + // destroy the object, then immediately export (before the next poll cycle) + + ::qpid::management::ManagementAgent::DeletedObjectList delObjs; + tm->GetManagementObject()->resourceDestroy(); + agent->exportDeletedObjects( delObjs ); + BOOST_CHECK(delObjs.size() == 1); + + // wait for the deleted object to be published + + bool isDeleted = false; + while (!isDeleted && r1.fetch(m1, Duration::SECOND * 6)) { + + decodeV1ObjectUpdates(m1, objs, objLen); + BOOST_CHECK(objs.size() > 0); + + for (TestObjectVector::iterator oIter = objs.begin(); oIter != objs.end(); oIter++) { + + TestManageable::validateTestObjectProperties(**oIter); + + qpid::types::Variant::Map mappy; + (*oIter)->writeTimestamps(mappy); + if (mappy["_delete_ts"].asUint64() != 0) + isDeleted = true; + } + } + + BOOST_CHECK(isDeleted); + + // verify there are no deleted objects to export now. + + agent->exportDeletedObjects( delObjs ); + BOOST_CHECK(delObjs.size() == 0); + + r1.close(); + delete fix; + delete tm; + } + + + // verify that a deleted object is imported correctly using the + // importDeletedObjects() method. V1 testcase. + // + QPID_AUTO_TEST_CASE(v1ImportDelObj) + { + AgentFixture* fix = new AgentFixture(3); + management::ManagementAgent* agent; + agent = fix->getBrokerAgent(); + + // create a manageable test object + TestManageable *tm = new TestManageable(agent, std::string("anObj")); + uint32_t objLen = tm->GetManagementObject()->writePropertiesSize(); + + Receiver r1 = fix->createV1DataIndRcvr("org.apache.qpid.broker.mgmt.test", "#"); + + agent->addObject(tm->GetManagementObject(), 1); + + // wait for the object to be published + Message m1; + BOOST_CHECK(r1.fetch(m1, Duration::SECOND * 6)); + + TestObjectVector objs; + decodeV1ObjectUpdates(m1, objs, objLen); + BOOST_CHECK(objs.size() > 0); + + // destroy the object, then immediately export (before the next poll cycle) + + ::qpid::management::ManagementAgent::DeletedObjectList delObjs; + tm->GetManagementObject()->resourceDestroy(); + agent->exportDeletedObjects( delObjs ); + BOOST_CHECK(delObjs.size() == 1); + + // destroy the broker, and reinistantiate a new one without populating it + // with a TestObject. + + r1.close(); + delete fix; + delete tm; // should no longer be necessary + + fix = new AgentFixture(3); + r1 = fix->createV1DataIndRcvr("org.apache.qpid.broker.mgmt.test", "#"); + agent = fix->getBrokerAgent(); + agent->importDeletedObjects( delObjs ); + + // wait for the deleted object to be published + + bool isDeleted = false; + while (!isDeleted && r1.fetch(m1, Duration::SECOND * 6)) { + + decodeV1ObjectUpdates(m1, objs, objLen); + BOOST_CHECK(objs.size() > 0); + + for (TestObjectVector::iterator oIter = objs.begin(); oIter != objs.end(); oIter++) { + + TestManageable::validateTestObjectProperties(**oIter); + + qpid::types::Variant::Map mappy; + (*oIter)->writeTimestamps(mappy); + if (mappy["_delete_ts"].asUint64() != 0) + isDeleted = true; + } + } + + BOOST_CHECK(isDeleted); + + // verify there are no deleted objects to export now. + + agent->exportDeletedObjects( delObjs ); + BOOST_CHECK(delObjs.size() == 0); + + r1.close(); + delete fix; + } + + + // verify that an object that is added and deleted prior to the + // first poll cycle is accounted for by the export + // + QPID_AUTO_TEST_CASE(v1ExportFastDelObj) + { + AgentFixture* fix = new AgentFixture(3); + management::ManagementAgent* agent; + agent = fix->getBrokerAgent(); + + // create a manageable test object + TestManageable *tm = new TestManageable(agent, std::string("objectifyMe")); + + // add, then immediately delete and export the object... + + ::qpid::management::ManagementAgent::DeletedObjectList delObjs; + agent->addObject(tm->GetManagementObject(), 999); + tm->GetManagementObject()->resourceDestroy(); + agent->exportDeletedObjects( delObjs ); + BOOST_CHECK(delObjs.size() == 1); + + delete fix; + delete tm; + } + + + // Verify that we can export and import multiple deleted objects correctly. + // + QPID_AUTO_TEST_CASE(v1ImportMultiDelObj) + { + AgentFixture* fix = new AgentFixture(3); + management::ManagementAgent* agent; + agent = fix->getBrokerAgent(); + + Receiver r1 = fix->createV1DataIndRcvr("org.apache.qpid.broker.mgmt.test", "#"); + + // 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-" << std::setfill('x') << std::setw(4) << i; + // (no, seriously, I didn't just do that.) + // Note well: we have to keep the key string length EXACTLY THE SAME + // FOR ALL OBJECTS, so objLen will be the same. Otherwise the + // decodeV1ObjectUpdates() will fail (v1 lacks explict encoded length). + TestManageable *tm = new TestManageable(agent, key.str()); + objLen = tm->GetManagementObject()->writePropertiesSize(); + agent->addObject(tm->GetManagementObject(), i + 1); + tmv.push_back(tm); + } + + // wait for the objects to be published + Message m1; + uint32_t msgCount = 0; + while(r1.fetch(m1, Duration::SECOND * 6)) { + TestObjectVector objs; + decodeV1ObjectUpdates(m1, objs, objLen); + msgCount += objs.size(); + } + + BOOST_CHECK_EQUAL(msgCount, objCount); + + // destroy some of the objects, then immediately export (before the next poll cycle) + + uint32_t delCount = 0; + for (size_t i = 0; i < objCount; i += 2) { + tmv[i]->GetManagementObject()->resourceDestroy(); + delCount++; + } + + ::qpid::management::ManagementAgent::DeletedObjectList delObjs; + agent->exportDeletedObjects( delObjs ); + BOOST_CHECK_EQUAL(delObjs.size(), delCount); + + // destroy the broker, and reinistantiate a new one without populating it + // with TestObjects. + + r1.close(); + delete fix; + while (tmv.size()) { + delete tmv.back(); + tmv.pop_back(); + } + + fix = new AgentFixture(3); + r1 = fix->createV1DataIndRcvr("org.apache.qpid.broker.mgmt.test", "#"); + agent = fix->getBrokerAgent(); + agent->importDeletedObjects( delObjs ); + + // wait for the deleted object to be published, verify the count + + uint32_t countDels = 0; + while (r1.fetch(m1, Duration::SECOND * 6)) { + TestObjectVector objs; + decodeV1ObjectUpdates(m1, objs, objLen); + BOOST_CHECK(objs.size() > 0); + + + for (TestObjectVector::iterator oIter = objs.begin(); oIter != objs.end(); oIter++) { + + TestManageable::validateTestObjectProperties(**oIter); + + qpid::types::Variant::Map mappy; + (*oIter)->writeTimestamps(mappy); + if (mappy["_delete_ts"].asUint64() != 0) + countDels++; + } + } + + // make sure we get the correct # of deleted objects + BOOST_CHECK_EQUAL(countDels, delCount); + + // verify there are no deleted objects to export now. + + agent->exportDeletedObjects( delObjs ); + BOOST_CHECK(delObjs.size() == 0); + + r1.close(); + delete fix; + } + + // Verify that we can export and import multiple deleted objects correctly. + // QMF V2 variant + QPID_AUTO_TEST_CASE(v2ImportMultiDelObj) + { + AgentFixture* fix = new AgentFixture(3, true); + management::ManagementAgent* agent; + agent = fix->getBrokerAgent(); + + Receiver r1 = fix->createV2DataIndRcvr("org.apache.qpid.broker.mgmt.test", "#"); + + // 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(); + agent->addObject(tm->GetManagementObject(), key.str()); + tmv.push_back(tm); + } + + // wait for the objects to be published + Message m1; + uint32_t msgCount = 0; + while(r1.fetch(m1, Duration::SECOND * 6)) { + TestObjectVector objs; + decodeV2ObjectUpdates(m1, objs); + msgCount += objs.size(); + } + + BOOST_CHECK_EQUAL(msgCount, objCount); + + // destroy some of the objects, then immediately export (before the next poll cycle) + + uint32_t delCount = 0; + for (size_t i = 0; i < objCount; i += 2) { + tmv[i]->GetManagementObject()->resourceDestroy(); + delCount++; + } + + ::qpid::management::ManagementAgent::DeletedObjectList delObjs; + agent->exportDeletedObjects( delObjs ); + BOOST_CHECK_EQUAL(delObjs.size(), delCount); + + // destroy the broker, and reinistantiate a new one without populating it + // with TestObjects. + + r1.close(); + delete fix; + while (tmv.size()) { + delete tmv.back(); + tmv.pop_back(); + } + + fix = new AgentFixture(3, true); + r1 = fix->createV2DataIndRcvr("org.apache.qpid.broker.mgmt.test", "#"); + agent = fix->getBrokerAgent(); + agent->importDeletedObjects( delObjs ); + + // wait for the deleted object to be published, verify the count + + uint32_t countDels = 0; + while (r1.fetch(m1, Duration::SECOND * 6)) { + TestObjectVector objs; + decodeV2ObjectUpdates(m1, objs); + BOOST_CHECK(objs.size() > 0); + + for (TestObjectVector::iterator oIter = objs.begin(); oIter != objs.end(); oIter++) { + + TestManageable::validateTestObjectProperties(**oIter); + + qpid::types::Variant::Map mappy; + (*oIter)->writeTimestamps(mappy); + if (mappy["_delete_ts"].asUint64() != 0) + countDels++; + } + } + + // make sure we get the correct # of deleted objects + BOOST_CHECK_EQUAL(countDels, delCount); + + // verify there are no deleted objects to export now. + + agent->exportDeletedObjects( delObjs ); + BOOST_CHECK(delObjs.size() == 0); + + r1.close(); + delete fix; + } + + // See QPID-2997 + QPID_AUTO_TEST_CASE(v2RapidRestoreObj) + { + AgentFixture* fix = new AgentFixture(3, true); + management::ManagementAgent* agent; + agent = fix->getBrokerAgent(); + + // two objects, same ObjID + TestManageable *tm1 = new TestManageable(agent, std::string("obj2")); + TestManageable *tm2 = new TestManageable(agent, std::string("obj2")); + + Receiver r1 = fix->createV2DataIndRcvr(tm1->GetManagementObject()->getPackageName(), "#"); + + // add, then immediately delete and re-add a copy of the object + agent->addObject(tm1->GetManagementObject(), "testobj-1"); + tm1->GetManagementObject()->resourceDestroy(); + agent->addObject(tm2->GetManagementObject(), "testobj-1"); + + // expect: a delete notification, then an update notification + TestObjectVector objs; + bool isDeleted = false; + bool isAdvertised = false; + size_t count = 0; + Message m1; + while (r1.fetch(m1, Duration::SECOND * 6)) { + + decodeV2ObjectUpdates(m1, objs); + BOOST_CHECK(objs.size() > 0); + + for (TestObjectVector::iterator oIter = objs.begin(); oIter != objs.end(); oIter++) { + count++; + TestManageable::validateTestObjectProperties(**oIter); + + qpid::types::Variant::Map mappy; + (*oIter)->writeTimestamps(mappy); + if (mappy["_delete_ts"].asUint64() != 0) { + isDeleted = true; + BOOST_CHECK(isAdvertised == false); // delete must be first + } else { + isAdvertised = true; + BOOST_CHECK(isDeleted == true); // delete must be first + } + } + } + + BOOST_CHECK(isDeleted); + BOOST_CHECK(isAdvertised); + BOOST_CHECK(count == 2); + + r1.close(); + delete fix; + delete tm1; + delete tm2; + } + + // See QPID-2997 + QPID_AUTO_TEST_CASE(v2DuplicateErrorObj) + { + AgentFixture* fix = new AgentFixture(3, true); + management::ManagementAgent* agent; + agent = fix->getBrokerAgent(); + + // turn off the expected error log message + qpid::log::Options logOpts; + logOpts.selectors.clear(); + logOpts.selectors.push_back("critical+"); + qpid::log::Logger::instance().configure(logOpts); + + // two objects, same ObjID + TestManageable *tm1 = new TestManageable(agent, std::string("obj2")); + TestManageable *tm2 = new TestManageable(agent, std::string("obj2")); + // Keep a pointer to the ManagementObject. This test simulates a user-caused error + // case (duplicate objects) where the broker has no choice but to leak a management + // object (safest assumption). To prevent valgrind from flagging this leak, we + // manually clean up the object at the end of the test. + management::ManagementObject *save = tm2->GetManagementObject(); + + Receiver r1 = fix->createV2DataIndRcvr(tm1->GetManagementObject()->getPackageName(), "#"); + + // add, then immediately delete and re-add a copy of the object + agent->addObject(tm1->GetManagementObject(), "testobj-1"); + agent->addObject(tm2->GetManagementObject(), "testobj-1"); + + TestObjectVector objs; + size_t count = 0; + Message m1; + while (r1.fetch(m1, Duration::SECOND * 6)) { + + decodeV2ObjectUpdates(m1, objs); + BOOST_CHECK(objs.size() > 0); + + for (TestObjectVector::iterator oIter = objs.begin(); oIter != objs.end(); oIter++) { + count++; + TestManageable::validateTestObjectProperties(**oIter); + } + } + + BOOST_CHECK(count == 1); // only one should be accepted. + + r1.close(); + delete fix; + delete tm1; + delete tm2; + delete save; + } + + QPID_AUTO_TEST_SUITE_END() + } +} + + diff --git a/qpid/cpp/src/tests/BrokerMgmtAgent.xml b/qpid/cpp/src/tests/BrokerMgmtAgent.xml new file mode 100644 index 0000000000..202b8debf3 --- /dev/null +++ b/qpid/cpp/src/tests/BrokerMgmtAgent.xml @@ -0,0 +1,38 @@ +<schema package="org.apache.qpid.broker.mgmt.test"> + +<!-- + 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. +--> + + <!-- + =============================================================== + TestObject + =============================================================== + --> + <class name="TestObject"> + + A test object defined for the BrokerMgmtAgent unit test. + + <property name="string1" type="lstr" access="RW" index="y"/> + <property name="bool1" type="bool" access="RW"/> + <property name="map1" type="map" access="RW"/> + + </class> + +</schema> + diff --git a/qpid/cpp/src/tests/CMakeLists.txt b/qpid/cpp/src/tests/CMakeLists.txt new file mode 100644 index 0000000000..405718f12b --- /dev/null +++ b/qpid/cpp/src/tests/CMakeLists.txt @@ -0,0 +1,356 @@ +# +# 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. +# + +# Enable dashboard reporting. +include (CTest) + +# Make sure that everything get built before the tests +# Need to create a var with all the necessary top level targets + +add_definitions(-DBOOST_TEST_DYN_LINK) +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ) + +include (FindPythonInterp) + +# Create the environment scripts for tests +set (abs_srcdir ${CMAKE_CURRENT_SOURCE_DIR}) +set (abs_builddir ${CMAKE_CURRENT_BINARY_DIR}) +set (abs_top_srcdir ${CMAKE_SOURCE_DIR}) +set (abs_top_builddir ${CMAKE_BINARY_DIR}) +set (builddir_lib_suffix "") +configure_file (${CMAKE_CURRENT_SOURCE_DIR}/test_env.sh.in + ${CMAKE_CURRENT_BINARY_DIR}/test_env.sh) + + +# If valgrind is selected in the configuration step, set up the path to it +# for CTest. +if (ENABLE_VALGRIND) + set (MEMORYCHECK_COMMAND ${VALGRIND}) + set (MEMORYCHECK_COMMAND_OPTIONS "--gen-suppressions=all +--leak-check=full +--demangle=yes +--suppressions=${CMAKE_CURRENT_SOURCE_DIR}/.valgrind.supp +--num-callers=25 +--log-file=ctest_valgrind.vglog") +endif (ENABLE_VALGRIND) + +# Using the Boost DLLs triggers warning 4275 on Visual Studio +# (non dll-interface class used as base for dll-interface class). +# This is ok, so suppress the warning. +# Also, boost lengthy names trigger warning 4503, decorated name length exceeded +# and using getenv() triggers insecure CRT warnings which we can silence in the +# test environment. +if (MSVC) + add_definitions( /wd4275 /wd4503 /D_CRT_SECURE_NO_WARNINGS) +endif (MSVC) + +# Like this to work with cmake 2.4 on Unix +set (qpid_test_boost_libs + ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} ${Boost_SYSTEM_LIBRARY}) + +# Macro to make it easier to remember where the tests are built +macro(remember_location testname) + set (${testname}_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/${testname}${CMAKE_EXECUTABLE_SUFFIX}) +endmacro(remember_location) + +# Windows uses some process-startup calls to ensure that errors, etc. don't +# result in error boxes being thrown up. Since it's expected that most test +# runs will be in scripts, the default is to force these outputs to stderr +# instead of windows. If you want to remove this code, build without the +# QPID_WINDOWS_DEFAULT_TEST_OUTPUTS ON. +if (CMAKE_SYSTEM_NAME STREQUAL Windows) + option(QPID_WINDOWS_DEFAULT_TEST_OUTPUTS "Use default error-handling on Windows tests" OFF) + if (NOT QPID_WINDOWS_DEFAULT_TEST_OUTPUTS) + set(platform_test_additions windows/DisableWin32ErrorWindows.cpp) + endif (NOT QPID_WINDOWS_DEFAULT_TEST_OUTPUTS) +endif (CMAKE_SYSTEM_NAME STREQUAL Windows) + +# +# Unit test program +# +# Unit tests are built as a single program to reduce valgrind overhead +# when running the tests. If you want to build a subset of the tests run +# ccmake and set unit_tests_to_build to the set you want to build. + +set(unit_tests_to_build + exception_test + RefCounted + SessionState + logging + AsyncCompletion + Url + Uuid + Shlib + FieldValue + FieldTable + Array + QueueOptionsTest + InlineAllocator + InlineVector + ClientSessionTest + MessagingSessionTests + SequenceSet + StringUtils + RangeSet + AtomicValue + QueueTest + AccumulatedAckTest + DtxWorkRecordTest + DeliveryRecordTest + ExchangeTest + HeadersExchangeTest + MessageTest + QueueRegistryTest + QueuePolicyTest + QueueFlowLimitTest + FramingTest + HeaderTest + SequenceNumberTest + TimerTest + TopicExchangeTest + TxBufferTest + TxPublishTest + MessageBuilderTest + ManagementTest + MessageReplayTracker + ConsoleTest + QueueEvents + ProxyTest + RetryList + RateFlowcontrolTest + FrameDecoder + ReplicationTest + ClientMessageTest + PollableCondition + Variant + ClientMessage + ${xml_tests} + CACHE STRING "Which unit tests to build" + ) + +mark_as_advanced(unit_tests_to_build) + +# Disabled till we move to amqp_0_10 codec. +# amqp_0_10/serialize.cpp allSegmentTypes.h \ +# amqp_0_10/ProxyTemplate.cpp \ +# amqp_0_10/apply.cpp \ +# amqp_0_10/Map.cpp \ +# amqp_0_10/handlers.cpp + +add_executable (unit_test unit_test + ${unit_tests_to_build} ${platform_test_additions}) +target_link_libraries (unit_test + ${qpid_test_boost_libs} + qpidmessaging qpidbroker qmfconsole) +remember_location(unit_test) + +add_library (shlibtest MODULE shlibtest.cpp) + +if (BUILD_CLUSTER) + include (cluster.cmake) +endif (BUILD_CLUSTER) + +# FIXME aconway 2009-11-30: enable SSL +#if SSL +#include ssl.mk +#endif + +# +# Other test programs +# +add_executable (qpid-perftest qpid-perftest.cpp ${platform_test_additions}) +target_link_libraries (qpid-perftest qpidclient) +#qpid_perftest_SOURCES=qpid-perftest.cpp test_tools.h TestOptions.h ConnectionOptions.h +remember_location(qpid-perftest) + +add_executable (qpid-txtest qpid-txtest.cpp ${platform_test_additions}) +target_link_libraries (qpid-txtest qpidclient) +#qpid_txtest_SOURCES=qpid-txtest.cpp TestOptions.h ConnectionOptions.h +remember_location(qpid-txtest) + +add_executable (qpid-latency-test qpid-latency-test.cpp ${platform_test_additions}) +target_link_libraries (qpid-latency-test qpidclient) +#qpid_latencytest_SOURCES=qpid-latency-test.cpp TestOptions.h ConnectionOptions.h +remember_location(qpid-latency-test) + +add_executable (echotest echotest.cpp ${platform_test_additions}) +target_link_libraries (echotest qpidclient) +#echotest_SOURCES=echotest.cpp TestOptions.h ConnectionOptions.h +remember_location(echotest) + +add_executable (qpid-client-test qpid-client-test.cpp ${platform_test_additions}) +target_link_libraries (qpid-client-test qpidclient) +#qpid_client_test_SOURCES=qpid-client-test.cpp TestOptions.h ConnectionOptions.h +remember_location(qpid-client-test) + +add_executable (qpid-topic-listener qpid-topic-listener.cpp ${platform_test_additions}) +target_link_libraries (qpid-topic-listener qpidclient) +#qpid_topic_listener_SOURCES=qpid-topic-listener.cpp TestOptions.h ConnectionOptions.h +remember_location(qpid-topic-listener) + +add_executable (qpid-topic-publisher qpid-topic-publisher.cpp ${platform_test_additions}) +target_link_libraries (qpid-topic-publisher qpidclient) +#qpid_topic_publisher_SOURCES=qpid-topic-publisher.cpp TestOptions.h ConnectionOptions.h +remember_location(qpid-topic-publisher) + +add_executable (publish publish.cpp ${platform_test_additions}) +target_link_libraries (publish qpidclient) +#publish_SOURCES=publish.cpp TestOptions.h ConnectionOptions.h +remember_location(publish) + +add_executable (consume consume.cpp ${platform_test_additions}) +target_link_libraries (consume qpidclient) +#consume_SOURCES=consume.cpp TestOptions.h ConnectionOptions.h +remember_location(consume) + +add_executable (header_test header_test.cpp ${platform_test_additions}) +target_link_libraries (header_test qpidclient) +#header_test_SOURCES=header_test.cpp TestOptions.h ConnectionOptions.h +remember_location(header_test) + +add_executable (declare_queues declare_queues.cpp ${platform_test_additions}) +target_link_libraries (declare_queues qpidclient) +remember_location(declare_queues) + +add_executable (replaying_sender replaying_sender.cpp ${platform_test_additions}) +target_link_libraries (replaying_sender qpidclient) +remember_location(replaying_sender) + +add_executable (resuming_receiver resuming_receiver.cpp ${platform_test_additions}) +target_link_libraries (resuming_receiver qpidclient) +remember_location(resuming_receiver) + +add_executable (txshift txshift.cpp ${platform_test_additions}) +target_link_libraries (txshift qpidclient) +#txshift_SOURCES=txshift.cpp TestOptions.h ConnectionOptions.h +remember_location(txshift) + +add_executable (txjob txjob.cpp ${platform_test_additions}) +target_link_libraries (txjob qpidclient) +#txjob_SOURCES=txjob.cpp TestOptions.h ConnectionOptions.h +remember_location(txjob) + +add_executable (receiver receiver.cpp ${platform_test_additions}) +target_link_libraries (receiver qpidclient) +#receiver_SOURCES=receiver.cpp TestOptions.h ConnectionOptions.h +remember_location(receiver) + +add_executable (sender sender.cpp Statistics.cpp ${platform_test_additions}) +target_link_libraries (sender qpidmessaging) +#sender_SOURCES=sender.cpp TestOptions.h ConnectionOptions.h +remember_location(sender) + +add_executable (qpid-receive qpid-receive.cpp Statistics.cpp ${platform_test_additions}) +target_link_libraries (qpid-receive qpidmessaging) +remember_location(qpid-receive) + +add_executable (qpid-send qpid-send.cpp Statistics.cpp ${platform_test_additions}) +target_link_libraries (qpid-send qpidmessaging) +remember_location(qpid-send) + +# qpid-perftest and qpid-latency-test are generally useful so install them +install (TARGETS qpid-perftest qpid-latency-test RUNTIME + DESTINATION ${QPID_INSTALL_BINDIR}) + +if (CMAKE_SYSTEM_NAME STREQUAL Windows) + set (ENV{OUTDIR} ${EXECUTABLE_OUTPUT_PATH}) + set (test_script_suffix ".ps1") + set (shell "powershell") +endif (CMAKE_SYSTEM_NAME STREQUAL Windows) + +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 (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) +if (PYTHON_EXECUTABLE) + add_test (run_header_test ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_header_test${test_script_suffix}) + add_test (python_tests ${test_wrap} ${CMAKE_CURRENT_SOURCE_DIR}/python_tests${test_script_suffix}) +endif (PYTHON_EXECUTABLE) +add_test (stop_broker ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/stop_broker${test_script_suffix}) +if (PYTHON_EXECUTABLE) + 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}) +endif (BUILD_ACL) +add_test (dynamic_log_level_test ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/dynamic_log_level_test${test_script_suffix}) +if (BUILD_MSSQL) + add_test (store_tests ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_store_tests${test_script_suffix} MSSQL) +endif (BUILD_MSSQL) +if (BUILD_MSCLFS) + add_test (store_tests ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_store_tests${test_script_suffix} MSSQL-CLFS) +endif (BUILD_MSCLFS) +endif (PYTHON_EXECUTABLE) + +add_library(test_store MODULE test_store.cpp) +target_link_libraries (test_store qpidbroker qpidcommon) +set_target_properties (test_store PROPERTIES PREFIX "") + +#EXTRA_DIST += \ +# run_test vg_check \ +# run-unit-tests start_broker python_tests stop_broker \ +# quick_topictest \ +# quick_perftest \ +# quick_txtest \ +# topictest \ +# run_header_test \ +# header_test.py \ +# ssl_test \ +# config.null \ +# ais_check \ +# run_federation_tests \ +# run_acl_tests \ +# .valgrind.supp \ +# MessageUtils.h \ +# TestMessageStore.h \ +# TxMocks.h \ +# start_cluster stop_cluster restart_cluster + +add_library (dlclose_noop MODULE dlclose_noop.c) +#libdlclose_noop_la_LDFLAGS = -module -rpath $(abs_builddir) + +#CLEANFILES+=valgrind.out *.log *.vglog* dummy_test $(unit_wrappers) +# +## FIXME aconway 2008-05-23: Disabled interop_runner because it uses +## the obsolete Channel class. Convert to Session and re-enable. +## +## check_PROGRAMS += interop_runner +# +## interop_runner_SOURCES = \ +## interop_runner.cpp \ +## SimpleTestCaseBase.cpp \ +## BasicP2PTest.cpp \ +## BasicPubSubTest.cpp \ +## SimpleTestCaseBase.h \ +## BasicP2PTest.h \ +## BasicPubSubTest.h \ +## TestCase.h \ +## TestOptions.h ConnectionOptions.h +## interop_runner_LDADD = $(lib_client) $(lib_common) $(extra_libs) +# +# +## Longer running stability tests, not run by default check: target. +## Not run under valgrind, too slow +#LONG_TESTS=fanout_perftest shared_perftest multiq_perftest topic_perftest run_failover_soak +#EXTRA_DIST+=$(LONG_TESTS) run_perftest +#check-long: +# $(MAKE) check TESTS="start_broker $(LONG_TESTS) stop_broker" VALGRIND= diff --git a/qpid/cpp/src/tests/ClientMessage.cpp b/qpid/cpp/src/tests/ClientMessage.cpp new file mode 100644 index 0000000000..994c46552c --- /dev/null +++ b/qpid/cpp/src/tests/ClientMessage.cpp @@ -0,0 +1,46 @@ +/* + * + * 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 "qpid/messaging/Message.h" + +#include "unit_test.h" + +using namespace qpid::messaging; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(ClientMessageSuite) + +QPID_AUTO_TEST_CASE(testCopyConstructor) +{ + Message m("my-data"); + m.setSubject("my-subject"); + m.getProperties()["a"] = "ABC"; + Message c(m); + BOOST_CHECK_EQUAL(m.getContent(), c.getContent()); + BOOST_CHECK_EQUAL(m.getSubject(), c.getSubject()); + BOOST_CHECK_EQUAL(m.getProperties()["a"], c.getProperties()["a"]); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/ClientMessageTest.cpp b/qpid/cpp/src/tests/ClientMessageTest.cpp new file mode 100644 index 0000000000..f925f1c234 --- /dev/null +++ b/qpid/cpp/src/tests/ClientMessageTest.cpp @@ -0,0 +1,51 @@ +/* + * + * 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. + * + */ + + +/**@file Unit tests for the client::Message class. */ + +#include "unit_test.h" +#include "qpid/client/Message.h" + +using namespace qpid::client; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(ClientMessageTestSuite) + +QPID_AUTO_TEST_CASE(MessageCopyAssign) { + // Verify that message has normal copy semantics. + Message m("foo"); + BOOST_CHECK_EQUAL("foo", m.getData()); + Message c(m); + BOOST_CHECK_EQUAL("foo", c.getData()); + Message a; + BOOST_CHECK_EQUAL("", a.getData()); + a = m; + BOOST_CHECK_EQUAL("foo", a.getData()); + a.setData("a"); + BOOST_CHECK_EQUAL("a", a.getData()); + c.setData("c"); + BOOST_CHECK_EQUAL("c", c.getData()); + BOOST_CHECK_EQUAL("foo", m.getData()); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/ClientSessionTest.cpp b/qpid/cpp/src/tests/ClientSessionTest.cpp new file mode 100644 index 0000000000..3c0cff7350 --- /dev/null +++ b/qpid/cpp/src/tests/ClientSessionTest.cpp @@ -0,0 +1,682 @@ +/* + * + * 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 "unit_test.h" +#include "test_tools.h" +#include "BrokerFixture.h" +#include "qpid/client/QueueOptions.h" +#include "qpid/client/MessageListener.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/sys/Monitor.h" +#include "qpid/sys/Thread.h" +#include "qpid/sys/Runnable.h" +#include "qpid/sys/Time.h" +#include "qpid/client/Session.h" +#include "qpid/client/Message.h" +#include "qpid/framing/reply_exceptions.h" + +#include <boost/optional.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/bind.hpp> +#include <boost/ptr_container/ptr_vector.hpp> + +#include <vector> + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(ClientSessionTest) + +using namespace qpid::client; +using namespace qpid::framing; +using namespace qpid; +using qpid::sys::Monitor; +using qpid::sys::Thread; +using qpid::sys::TIME_SEC; +using qpid::broker::Broker; +using std::string; +using std::cout; +using std::endl; + + +struct DummyListener : public sys::Runnable, public MessageListener { + std::vector<Message> messages; + string name; + uint expected; + SubscriptionManager submgr; + + DummyListener(Session& session, const string& n, uint ex) : + name(n), expected(ex), submgr(session) {} + + void run() + { + submgr.subscribe(*this, name); + submgr.run(); + } + + void received(Message& msg) + { + messages.push_back(msg); + if (--expected == 0) { + submgr.stop(); + } + } +}; + +struct SimpleListener : public MessageListener +{ + Monitor lock; + std::vector<Message> messages; + + void received(Message& msg) + { + Monitor::ScopedLock l(lock); + messages.push_back(msg); + lock.notifyAll(); + } + + void waitFor(const uint n) + { + Monitor::ScopedLock l(lock); + while (messages.size() < n) { + lock.wait(); + } + } +}; + +struct ClientSessionFixture : public ProxySessionFixture +{ + ClientSessionFixture(Broker::Options opts = Broker::Options()) : ProxySessionFixture(opts) { + session.queueDeclare(arg::queue="my-queue"); + } +}; + +QPID_AUTO_TEST_CASE(testQueueQuery) { + ClientSessionFixture fix; + fix.session = fix.connection.newSession(); + fix.session.queueDeclare(arg::queue="q", arg::alternateExchange="amq.fanout", + arg::exclusive=true, arg::autoDelete=true); + QueueQueryResult result = fix.session.queueQuery("q"); + BOOST_CHECK_EQUAL(false, result.getDurable()); + BOOST_CHECK_EQUAL(true, result.getExclusive()); + BOOST_CHECK_EQUAL("amq.fanout", result.getAlternateExchange()); +} + +QPID_AUTO_TEST_CASE(testDispatcher) +{ + ClientSessionFixture fix; + fix.session =fix.connection.newSession(); + size_t count = 100; + for (size_t i = 0; i < count; ++i) + fix.session.messageTransfer(arg::content=Message(boost::lexical_cast<string>(i), "my-queue")); + DummyListener listener(fix.session, "my-queue", count); + listener.run(); + BOOST_CHECK_EQUAL(count, listener.messages.size()); + for (size_t i = 0; i < count; ++i) + BOOST_CHECK_EQUAL(boost::lexical_cast<string>(i), listener.messages[i].getData()); +} + +QPID_AUTO_TEST_CASE(testDispatcherThread) +{ + ClientSessionFixture fix; + fix.session =fix.connection.newSession(); + size_t count = 10; + DummyListener listener(fix.session, "my-queue", count); + sys::Thread t(listener); + for (size_t i = 0; i < count; ++i) { + fix.session.messageTransfer(arg::content=Message(boost::lexical_cast<string>(i), "my-queue")); + } + t.join(); + BOOST_CHECK_EQUAL(count, listener.messages.size()); + for (size_t i = 0; i < count; ++i) + 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; + fix.session.timeout(60); + fix.session.suspend(); + try { + fix.session.exchangeQuery(arg::exchange="amq.fanout"); + BOOST_FAIL("Expected session suspended exception"); + } 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; + fix.session.queueDeclare(arg::queue="myq", arg::exclusive=true, arg::autoDelete=true); + fix.subs.subscribe(mylistener, "myq"); + sys::Thread runner(fix.subs);//start dispatcher thread + string data("msg"); + Message msg(data, "myq"); + const uint count=10; + for (uint i = 0; i < count; ++i) { + fix.session.messageTransfer(arg::content=msg); + } + mylistener.waitFor(count); + fix.subs.cancel("myq"); + fix.subs.stop(); + runner.join(); + fix.session.close(); + BOOST_CHECK_EQUAL(mylistener.messages.size(), count); + for (uint j = 0; j < count; ++j) { + BOOST_CHECK_EQUAL(mylistener.messages[j].getData(), data); + } +} + +QPID_AUTO_TEST_CASE(testLocalQueue) { + ClientSessionFixture fix; + fix.session.queueDeclare(arg::queue="lq", arg::exclusive=true, arg::autoDelete=true); + LocalQueue lq; + fix.subs.subscribe(lq, "lq", FlowControl(2, FlowControl::UNLIMITED, false)); + fix.session.messageTransfer(arg::content=Message("foo0", "lq")); + fix.session.messageTransfer(arg::content=Message("foo1", "lq")); + fix.session.messageTransfer(arg::content=Message("foo2", "lq")); + BOOST_CHECK_EQUAL("foo0", lq.pop().getData()); + BOOST_CHECK_EQUAL("foo1", lq.pop().getData()); + BOOST_CHECK(lq.empty()); // Credit exhausted. + fix.subs.getSubscription("lq").setFlowControl(FlowControl::unlimited()); + BOOST_CHECK_EQUAL("foo2", lq.pop().getData()); +} + +struct DelayedTransfer : sys::Runnable +{ + ClientSessionFixture& fixture; + + DelayedTransfer(ClientSessionFixture& f) : fixture(f) {} + + void run() + { + qpid::sys::sleep(1); + fixture.session.messageTransfer(arg::content=Message("foo2", "getq")); + } +}; + +QPID_AUTO_TEST_CASE(testGet) { + ClientSessionFixture fix; + fix.session.queueDeclare(arg::queue="getq", arg::exclusive=true, arg::autoDelete=true); + fix.session.messageTransfer(arg::content=Message("foo0", "getq")); + fix.session.messageTransfer(arg::content=Message("foo1", "getq")); + Message got; + BOOST_CHECK(fix.subs.get(got, "getq", TIME_SEC)); + BOOST_CHECK_EQUAL("foo0", got.getData()); + BOOST_CHECK(fix.subs.get(got, "getq", TIME_SEC)); + BOOST_CHECK_EQUAL("foo1", got.getData()); + BOOST_CHECK(!fix.subs.get(got, "getq")); + DelayedTransfer sender(fix); + Thread t(sender); + //test timed get where message shows up after a short delay + BOOST_CHECK(fix.subs.get(got, "getq", 5*TIME_SEC)); + BOOST_CHECK_EQUAL("foo2", got.getData()); + t.join(); +} + +QPID_AUTO_TEST_CASE(testOpenFailure) { + BrokerFixture b; + Connection c; + string host("unknowable-host"); + try { + c.open(host); + } catch (const Exception&) { + BOOST_CHECK(!c.isOpen()); + } + b.open(c); + BOOST_CHECK(c.isOpen()); + c.close(); + BOOST_CHECK(!c.isOpen()); +} + +QPID_AUTO_TEST_CASE(testPeriodicExpiration) { + Broker::Options opts; + opts.queueCleanInterval = 1; + opts.queueFlowStopRatio = 0; + opts.queueFlowResumeRatio = 0; + ClientSessionFixture fix(opts); + 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"); + if (i % 2) m.getDeliveryProperties().setTtl(500); + fix.session.messageTransfer(arg::content=m); + } + + 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) { + ClientSessionFixture fix; + fix.session.queueDeclare(arg::queue="my-queue", arg::exclusive=true, arg::autoDelete=true); + + for (uint i = 0; i < 10; i++) { + Message m((boost::format("Message_%1%") % (i+1)).str(), "my-queue"); + if (i % 2) m.getDeliveryProperties().setTtl(200); + fix.session.messageTransfer(arg::content=m); + } + + qpid::sys::usleep(300* 1000); + + for (uint i = 0; i < 10; i++) { + if (i % 2) continue; + Message m; + BOOST_CHECK(fix.subs.get(m, "my-queue", TIME_SEC)); + BOOST_CHECK_EQUAL((boost::format("Message_%1%") % (i+1)).str(), m.getData()); + } +} + +QPID_AUTO_TEST_CASE(testRelease) { + ClientSessionFixture fix; + + const uint count=10; + for (uint i = 0; i < count; i++) { + Message m((boost::format("Message_%1%") % (i+1)).str(), "my-queue"); + fix.session.messageTransfer(arg::content=m); + } + + fix.subs.setAutoStop(false); + fix.subs.start(); + SubscriptionSettings settings; + settings.autoAck = 0; + + SimpleListener l1; + Subscription s1 = fix.subs.subscribe(l1, "my-queue", settings); + l1.waitFor(count); + s1.cancel(); + + for (uint i = 0; i < count; i++) { + BOOST_CHECK_EQUAL((boost::format("Message_%1%") % (i+1)).str(), l1.messages[i].getData()); + } + s1.release(s1.getUnaccepted()); + + //check that released messages are redelivered + settings.autoAck = 1; + SimpleListener l2; + Subscription s2 = fix.subs.subscribe(l2, "my-queue", settings); + l2.waitFor(count); + for (uint i = 0; i < count; i++) { + BOOST_CHECK_EQUAL((boost::format("Message_%1%") % (i+1)).str(), l2.messages[i].getData()); + } + + fix.subs.stop(); + fix.subs.wait(); + fix.session.close(); +} + +QPID_AUTO_TEST_CASE(testCompleteOnAccept) { + ClientSessionFixture fix; + const uint count = 8; + const uint chunk = 4; + for (uint i = 0; i < count; i++) { + Message m((boost::format("Message_%1%") % (i+1)).str(), "my-queue"); + fix.session.messageTransfer(arg::content=m); + } + + SubscriptionSettings settings; + settings.autoAck = 0; + settings.completionMode = COMPLETE_ON_ACCEPT; + settings.flowControl = FlowControl::messageWindow(chunk); + + LocalQueue q; + Subscription s = fix.subs.subscribe(q, "my-queue", settings); + fix.session.messageFlush(arg::destination=s.getName()); + SequenceSet accepted; + for (uint i = 0; i < chunk; i++) { + Message m; + BOOST_CHECK(q.get(m)); + BOOST_CHECK_EQUAL((boost::format("Message_%1%") % (i+1)).str(), m.getData()); + accepted.add(m.getId()); + } + Message m; + BOOST_CHECK(!q.get(m)); + + s.accept(accepted); + fix.session.messageFlush(arg::destination=s.getName()); + accepted.clear(); + + for (uint i = chunk; i < count; i++) { + Message m; + BOOST_CHECK(q.get(m)); + BOOST_CHECK_EQUAL((boost::format("Message_%1%") % (i+1)).str(), m.getData()); + accepted.add(m.getId()); + } + fix.session.messageAccept(accepted); +} + +namespace +{ +struct Publisher : qpid::sys::Runnable +{ + AsyncSession session; + Message message; + uint count; + Thread thread; + + Publisher(Connection& con, Message m, uint c) : session(con.newSession()), message(m), count(c) {} + + void start() + { + thread = Thread(*this); + } + + void join() + { + thread.join(); + } + + void run() + { + for (uint i = 0; i < count; i++) { + session.messageTransfer(arg::content=message); + } + session.sync(); + session.close(); + } +}; +} + +QPID_AUTO_TEST_CASE(testConcurrentSenders) +{ + //Ensure concurrent publishing sessions on a connection don't + //cause assertions, deadlocks or other undesirables: + BrokerFixture fix; + Connection connection; + ConnectionSettings settings; + settings.maxFrameSize = 1024; + settings.port = fix.broker->getPort(qpid::broker::Broker::TCP_TRANSPORT); + connection.open(settings); + AsyncSession session = connection.newSession(); + Message message(string(512, 'X')); + + boost::ptr_vector<Publisher> publishers; + for (size_t i = 0; i < 5; i++) { + publishers.push_back(new Publisher(connection, message, 100)); + } + std::for_each(publishers.begin(), publishers.end(), boost::bind(&Publisher::start, _1)); + std::for_each(publishers.begin(), publishers.end(), boost::bind(&Publisher::join, _1)); + connection.close(); +} + + +QPID_AUTO_TEST_CASE(testExclusiveSubscribe) +{ + ClientSessionFixture fix; + fix.session.queueDeclare(arg::queue="myq", arg::exclusive=true, arg::autoDelete=true); + SubscriptionSettings settings; + settings.exclusive = true; + LocalQueue q; + fix.subs.subscribe(q, "myq", settings, "first"); + //attempt to create new subscriber should fail + ScopedSuppressLogging sl; + BOOST_CHECK_THROW(fix.subs.subscribe(q, "myq", "second"), ResourceLockedException); + ; + +} + +QPID_AUTO_TEST_CASE(testExclusiveBinding) { + FieldTable options; + options.setString("qpid.exclusive-binding", "anything"); + ClientSessionFixture fix; + fix.session.queueDeclare(arg::queue="queue-1", arg::exclusive=true, arg::autoDelete=true); + fix.session.queueDeclare(arg::queue="queue-2", arg::exclusive=true, arg::autoDelete=true); + fix.session.exchangeBind(arg::exchange="amq.direct", arg::queue="queue-1", arg::bindingKey="my-key", arg::arguments=options); + fix.session.messageTransfer(arg::destination="amq.direct", arg::content=Message("message1", "my-key")); + fix.session.exchangeBind(arg::exchange="amq.direct", arg::queue="queue-2", arg::bindingKey="my-key", arg::arguments=options); + fix.session.messageTransfer(arg::destination="amq.direct", arg::content=Message("message2", "my-key")); + + Message got; + BOOST_CHECK(fix.subs.get(got, "queue-1")); + BOOST_CHECK_EQUAL("message1", got.getData()); + BOOST_CHECK(!fix.subs.get(got, "queue-1")); + + BOOST_CHECK(fix.subs.get(got, "queue-2")); + BOOST_CHECK_EQUAL("message2", got.getData()); + BOOST_CHECK(!fix.subs.get(got, "queue-2")); +} + +QPID_AUTO_TEST_CASE(testResubscribeWithLocalQueue) { + ClientSessionFixture fix; + fix.session.queueDeclare(arg::queue="some-queue", arg::exclusive=true, arg::autoDelete=true); + LocalQueue p, q; + fix.subs.subscribe(p, "some-queue"); + fix.subs.cancel("some-queue"); + fix.subs.subscribe(q, "some-queue"); + + fix.session.messageTransfer(arg::content=Message("some-data", "some-queue")); + fix.session.messageFlush(arg::destination="some-queue"); + + Message got; + BOOST_CHECK(!p.get(got)); + + BOOST_CHECK(q.get(got)); + BOOST_CHECK_EQUAL("some-data", got.getData()); + BOOST_CHECK(!q.get(got)); +} + +QPID_AUTO_TEST_CASE(testReliableDispatch) { + ClientSessionFixture fix; + std::string queue("a-queue"); + fix.session.queueDeclare(arg::queue=queue, arg::autoDelete=true); + + ConnectionSettings settings; + settings.port = fix.broker->getPort(qpid::broker::Broker::TCP_TRANSPORT); + + Connection c1; + c1.open(settings); + Session s1 = c1.newSession(); + SubscriptionManager subs1(s1); + LocalQueue q1; + subs1.subscribe(q1, queue, FlowControl());//first subscriber has no credit + + Connection c2; + c2.open(settings); + Session s2 = c2.newSession(); + SubscriptionManager subs2(s2); + LocalQueue q2; + subs2.subscribe(q2, queue);//second subscriber has credit + + fix.session.messageTransfer(arg::content=Message("my-message", queue)); + + //check that the second consumer gets the message + Message got; + BOOST_CHECK(q2.get(got, 1*TIME_SEC)); + BOOST_CHECK_EQUAL("my-message", got.getData()); + + c1.close(); + c2.close(); +} + +QPID_AUTO_TEST_CASE(testSessionCloseOnInvalidSession) { + Session session; + session.close(); +} + +QPID_AUTO_TEST_CASE(testLVQVariedSize) { + ClientSessionFixture fix; + std::string queue("my-lvq"); + QueueOptions args; + args.setOrdering(LVQ_NO_BROWSE); + fix.session.queueDeclare(arg::queue=queue, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); + + std::string key; + args.getLVQKey(key); + + for (size_t i = 0; i < 10; i++) { + std::ostringstream data; + size_t size = 100 - ((i % 10) * 10); + data << std::string(size, 'x'); + + Message m(data.str(), queue); + m.getHeaders().setString(key, "abc"); + fix.session.messageTransfer(arg::content=m); + } +} + +QPID_AUTO_TEST_CASE(testSessionManagerSetFlowControl) { + ClientSessionFixture fix; + std::string name("dummy"); + LocalQueue queue; + SubscriptionSettings settings; + settings.flowControl = FlowControl(); + fix.session.queueDeclare(arg::queue=name, arg::exclusive=true, arg::autoDelete=true); + fix.subs.subscribe(queue, name, settings); + fix.session.messageTransfer(arg::content=Message("my-message", name)); + fix.subs.setFlowControl(name, 1, FlowControl::UNLIMITED, false); + fix.session.messageFlush(name); + Message got; + BOOST_CHECK(queue.get(got, 0)); + BOOST_CHECK_EQUAL("my-message", got.getData()); +} + +QPID_AUTO_TEST_CASE(testGetThenSubscribe) { + ClientSessionFixture fix; + std::string name("myqueue"); + fix.session.queueDeclare(arg::queue=name, arg::exclusive=true, arg::autoDelete=true); + fix.session.messageTransfer(arg::content=Message("one", name)); + fix.session.messageTransfer(arg::content=Message("two", name)); + Message got; + BOOST_CHECK(fix.subs.get(got, name)); + BOOST_CHECK_EQUAL("one", got.getData()); + + DummyListener listener(fix.session, name, 1); + listener.run(); + BOOST_CHECK_EQUAL(1u, listener.messages.size()); + if (!listener.messages.empty()) { + BOOST_CHECK_EQUAL("two", listener.messages[0].getData()); + } +} + +QPID_AUTO_TEST_CASE(testSessionIsValid) { + ClientSessionFixture fix; + BOOST_CHECK(fix.session.isValid()); + Session session; + BOOST_CHECK(!session.isValid()); +} + +QPID_AUTO_TEST_CASE(testExpirationNotAltered) { + ClientSessionFixture fix; + fix.session.queueDeclare(arg::queue="my-queue", arg::exclusive=true, arg::autoDelete=true); + + Message m("my-message", "my-queue"); + m.getDeliveryProperties().setTtl(60000); + m.getDeliveryProperties().setExpiration(12345); + fix.session.messageTransfer(arg::content=m); + Message got; + BOOST_CHECK(fix.subs.get(got, "my-queue")); + BOOST_CHECK_EQUAL("my-message", got.getData()); + BOOST_CHECK_EQUAL(12345u, got.getDeliveryProperties().getExpiration()); +} + +QPID_AUTO_TEST_CASE(testGetConnectionFromSession) { + ClientSessionFixture fix; + FieldTable options; + options.setInt("no-local", 1); + fix.session.queueDeclare(arg::queue="a", arg::exclusive=true, arg::autoDelete=true, arg::arguments=options); + fix.session.queueDeclare(arg::queue="b", arg::exclusive=true, arg::autoDelete=true); + + Connection c = fix.session.getConnection(); + Session s = c.newSession(); + //If this new session was created as expected on the same connection as + //fix.session, then the no-local behaviour means that queue 'a' + //will not enqueue messages from this new session but queue 'b' + //will. + s.messageTransfer(arg::content=Message("a", "a")); + s.messageTransfer(arg::content=Message("b", "b")); + + Message got; + BOOST_CHECK(fix.subs.get(got, "b")); + BOOST_CHECK_EQUAL("b", got.getData()); + BOOST_CHECK(!fix.subs.get(got, "a")); +} + + +QPID_AUTO_TEST_CASE(testQueueDeleted) +{ + ClientSessionFixture fix; + fix.session.queueDeclare(arg::queue="my-queue"); + LocalQueue queue; + fix.subs.subscribe(queue, "my-queue"); + + ScopedSuppressLogging sl; + fix.session.queueDelete(arg::queue="my-queue"); + BOOST_CHECK_THROW(queue.get(1*qpid::sys::TIME_SEC), qpid::framing::ResourceDeletedException); +} + +QPID_AUTO_TEST_CASE(testTtl) +{ + const uint64_t ms = 1000ULL; // convert sec to ms + const uint64_t us = 1000ULL * 1000ULL; // convert sec to us + + ClientSessionFixture fix; + fix.session.queueDeclare(arg::queue="ttl-test", arg::exclusive=true, arg::autoDelete=true); + Message msg1 = Message("AAA", "ttl-test"); + uint64_t ttl = 2 * ms; // 2 sec + msg1.getDeliveryProperties().setTtl(ttl); + Connection c = fix.session.getConnection(); + Session s = c.newSession(); + s.messageTransfer(arg::content=msg1); + + Message msg2 = Message("BBB", "ttl-test"); + ttl = 10 * ms; // 10 sec + msg2.getDeliveryProperties().setTtl(ttl); + s.messageTransfer(arg::content=msg2); + + qpid::sys::usleep(5 * us); // 5 sec + + // Message "AAA" should be expired and never be delivered + // Check "BBB" has ttl somewhere between 1 and 5 secs + Message got; + BOOST_CHECK(fix.subs.get(got, "ttl-test")); + BOOST_CHECK_EQUAL("BBB", got.getData()); + BOOST_CHECK(got.getDeliveryProperties().getTtl() > 1 * ms); + BOOST_CHECK(got.getDeliveryProperties().getTtl() < ttl - (5 * ms)); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/ClusterFailover.cpp b/qpid/cpp/src/tests/ClusterFailover.cpp new file mode 100644 index 0000000000..bf5c147f19 --- /dev/null +++ b/qpid/cpp/src/tests/ClusterFailover.cpp @@ -0,0 +1,115 @@ +/* + * + * 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. + * + */ + +/**@file Tests for partial failure in a cluster. + * Partial failure means some nodes experience a failure while others do not. + * In this case the failed nodes must shut down. + */ + +#include "test_tools.h" +#include "unit_test.h" +#include "ClusterFixture.h" +#include "qpid/client/FailoverManager.h" +#include <boost/assign.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/bind.hpp> + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(ClusterFailoverTestSuite) + +using namespace std; +using namespace qpid; +using namespace qpid::cluster; +using namespace qpid::framing; +using namespace qpid::client; +using namespace qpid::client::arg; +using namespace boost::assign; +using broker::Broker; +using boost::shared_ptr; + +// Timeout for tests that wait for messages +const sys::Duration TIMEOUT=sys::TIME_SEC/4; + +ClusterFixture::Args getArgs(bool durable=std::getenv("STORE_LIB")) +{ + ClusterFixture::Args args; + args += "--auth", "no", "--no-module-dir", + "--load-module", getLibPath("CLUSTER_LIB"); + if (durable) + args += "--load-module", getLibPath("STORE_LIB"), "TMP_DATA_DIR"; + else + args += "--no-data-dir"; + return args; +} + +// Test re-connecting with same session name after a failure. +QPID_AUTO_TEST_CASE(testReconnectSameSessionName) { + ClusterFixture cluster(2, getArgs(), -1); + // Specify a timeout to make sure it is ignored, session resume is + // not implemented so sessions belonging to dead brokers should + // not be kept. + Client c0(cluster[0], "foo", 5); + BOOST_CHECK_EQUAL(2u, knownBrokerPorts(c0.connection, 2).size()); // wait for both. + c0.session.queueDeclare("q"); + c0.session.messageTransfer(arg::content=Message("sendme", "q")); + BOOST_CHECK_EQUAL(c0.subs.get("q").getData(), "sendme"); + cluster.killWithSilencer(0, c0.connection, 9); + Client c1(cluster[1], "foo", 5); + c1.session.queueQuery(); // Try to use the session. +} + +QPID_AUTO_TEST_CASE(testReconnectExclusiveQueue) { + // Regresion test. Session timeouts should be ignored + // by the broker as session resume is not implemented. + ClusterFixture cluster(2, getArgs(), -1); + Client c0(cluster[0], "foo", 5); + c0.session.queueDeclare("exq", arg::exclusive=true); + SubscriptionSettings settings; + settings.exclusive = true; + settings.autoAck = 0; + Subscription s0 = c0.subs.subscribe(c0.lq, "exq", settings, "exsub"); + c0.session.messageTransfer(arg::content=Message("sendme", "exq")); + BOOST_CHECK_EQUAL(c0.lq.get().getData(), "sendme"); + + // Regression: core dump on exit if unacked messages were left in + // a session with a timeout. + cluster.killWithSilencer(0, c0.connection); + + // Regression: session timeouts prevented re-connecting to + // exclusive queue. + Client c1(cluster[1]); + c1.session.queueDeclare("exq", arg::exclusive=true); + Subscription s1 = c1.subs.subscribe(c1.lq, "exq", settings, "exsub"); + s1.cancel(); + + // Regression: session timeouts prevented new member joining + // cluster with exclusive queues. + cluster.add(); + Client c2(cluster[2]); + c2.session.queueQuery(); +} + + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/ClusterFixture.cpp b/qpid/cpp/src/tests/ClusterFixture.cpp new file mode 100644 index 0000000000..6b62cb6fc7 --- /dev/null +++ b/qpid/cpp/src/tests/ClusterFixture.cpp @@ -0,0 +1,160 @@ +/* + * + * 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. + * + */ + +#include "test_tools.h" +#include "unit_test.h" +#include "ForkedBroker.h" +#include "BrokerFixture.h" + +#include "qpid/client/Connection.h" +#include "qpid/client/ConnectionAccess.h" +#include "qpid/client/Session.h" +#include "qpid/client/FailoverListener.h" +#include "qpid/cluster/Cluster.h" +#include "qpid/cluster/Cpg.h" +#include "qpid/cluster/UpdateClient.h" +#include "qpid/framing/AMQBody.h" +#include "qpid/framing/Uuid.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/enum.h" +#include "qpid/log/Logger.h" + +#include <boost/bind.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/assign.hpp> + +#include <string> +#include <iostream> +#include <iterator> +#include <vector> +#include <set> +#include <algorithm> +#include <iterator> + + +using namespace std; +using namespace qpid; +using namespace qpid::cluster; +using namespace qpid::framing; +using namespace qpid::client; +using qpid::sys::TIME_SEC; +using qpid::broker::Broker; +using boost::shared_ptr; +using qpid::cluster::Cluster; +using boost::assign::list_of; + + +#include "ClusterFixture.h" + +namespace qpid { +namespace tests { + +ClusterFixture::ClusterFixture(size_t n, const Args& args_, int localIndex_) + : name(Uuid(true).str()), localIndex(localIndex_), userArgs(args_) +{ + add(n); +} + +ClusterFixture::ClusterFixture(size_t n, boost::function<void (Args&, size_t)> updateArgs_, int localIndex_) + : name(Uuid(true).str()), localIndex(localIndex_), updateArgs(updateArgs_) +{ + add(n); +} + +ClusterFixture::Args ClusterFixture::makeArgs(const std::string& prefix, size_t index) { + Args args = list_of<string>("qpidd ") + ("--cluster-name")(name) + ("--log-prefix")(prefix); + args.insert(args.end(), userArgs.begin(), userArgs.end()); + if (updateArgs) updateArgs(args, index); + return args; +} + +void ClusterFixture::add() { + if (size() != size_t(localIndex)) { // fork a broker process. + std::ostringstream os; os << "fork" << size(); + std::string prefix = os.str(); + forkedBrokers.push_back(shared_ptr<ForkedBroker>(new ForkedBroker(makeArgs(prefix, size())))); + push_back(forkedBrokers.back()->getPort()); + } + else { // Run in this process + addLocal(); + } +} + +namespace { +/** Parse broker & cluster options */ +Broker::Options parseOpts(size_t argc, const char* argv[]) { + Broker::Options opts; + Plugin::addOptions(opts); // Pick up cluster options. + opts.parse(argc, argv, "", true); // Allow-unknown for --load-module + return opts; +} +} + +void ClusterFixture::addLocal() { + assert(int(size()) == localIndex); + ostringstream os; os << "local" << localIndex; + string prefix = os.str(); + Args args(makeArgs(prefix, localIndex)); + vector<const char*> argv(args.size()); + transform(args.begin(), args.end(), argv.begin(), boost::bind(&string::c_str, _1)); + qpid::log::Logger::instance().setPrefix(prefix); + localBroker.reset(new BrokerFixture(parseOpts(argv.size(), &argv[0]))); + push_back(localBroker->getPort()); + forkedBrokers.push_back(shared_ptr<ForkedBroker>()); +} + +bool ClusterFixture::hasLocal() const { return localIndex >= 0 && size_t(localIndex) < size(); } + +/** Kill a forked broker with sig, or shutdown localBroker if n==0. */ +void ClusterFixture::kill(size_t n, int sig) { + if (n == size_t(localIndex)) + localBroker->broker->shutdown(); + else + forkedBrokers[n]->kill(sig); +} + +/** Kill a broker and suppress errors from closing connection c. */ +void ClusterFixture::killWithSilencer(size_t n, client::Connection& c, int sig) { + ScopedSuppressLogging sl; + try { c.close(); } catch(...) {} + kill(n,sig); +} + +/** + * Get the known broker ports from a Connection. + *@param n if specified wait for the cluster size to be n, up to a timeout. + */ +std::set<int> knownBrokerPorts(qpid::client::Connection& c, int n) { + FailoverListener fl(c, false); + std::vector<qpid::Url> urls = fl.getKnownBrokers(); + if (n >= 0 && unsigned(n) != urls.size()) { + // Retry up to 10 secs in .1 second intervals. + for (size_t retry=100; urls.size() != unsigned(n) && retry != 0; --retry) { + qpid::sys::usleep(1000*100); // 0.1 secs + urls = fl.getKnownBrokers(); + } + } + std::set<int> s; + for (std::vector<qpid::Url>::const_iterator i = urls.begin(); i != urls.end(); ++i) + s.insert((*i)[0].port); + return s; +} + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/ClusterFixture.h b/qpid/cpp/src/tests/ClusterFixture.h new file mode 100644 index 0000000000..f548ff9376 --- /dev/null +++ b/qpid/cpp/src/tests/ClusterFixture.h @@ -0,0 +1,115 @@ +#ifndef CLUSTER_FIXTURE_H +#define CLUSTER_FIXTURE_H + +/* + * + * 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. + * + */ + +#include "test_tools.h" +#include "unit_test.h" +#include "ForkedBroker.h" +#include "BrokerFixture.h" + +#include "qpid/client/Connection.h" +#include "qpid/client/ConnectionAccess.h" +#include "qpid/client/Session.h" +#include "qpid/client/FailoverListener.h" +#include "qpid/cluster/Cluster.h" +#include "qpid/cluster/Cpg.h" +#include "qpid/cluster/UpdateClient.h" +#include "qpid/framing/AMQBody.h" +#include "qpid/framing/Uuid.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/enum.h" +#include "qpid/log/Logger.h" + +#include <boost/bind.hpp> +#include <boost/function.hpp> +#include <boost/shared_ptr.hpp> + +#include <string> +#include <iostream> +#include <iterator> +#include <vector> +#include <set> +#include <algorithm> +#include <iterator> + + +using namespace std; +using namespace qpid; +using namespace qpid::cluster; +using namespace qpid::framing; +using namespace qpid::client; +using qpid::sys::TIME_SEC; +using qpid::broker::Broker; +using boost::shared_ptr; +using qpid::cluster::Cluster; + +namespace qpid { +namespace tests { + +/** Cluster fixture is a vector of ports for the replicas. + * + * At most one replica (by default replica 0) is in the current + * process, all others are forked as children. + */ +class ClusterFixture : public vector<uint16_t> { + public: + typedef std::vector<std::string> Args; + + /** @param localIndex can be -1 meaning don't automatically start a local broker. + * A local broker can be started with addLocal(). + */ + ClusterFixture(size_t n, const Args& args, int localIndex=-1); + + /**@param updateArgs function is passed the index of the cluster member and can update the arguments. */ + ClusterFixture(size_t n, boost::function<void (Args&, size_t)> updateArgs, int localIndex=-1); + + void add(size_t n) { for (size_t i=0; i < n; ++i) add(); } + void add(); // Add a broker. + void setup(); + + bool hasLocal() const; + + /** Kill a forked broker with sig, or shutdown localBroker. */ + void kill(size_t n, int sig=SIGINT); + + /** Kill a broker and suppress errors from closing connection c. */ + void killWithSilencer(size_t n, client::Connection& c, int sig=SIGINT); + + private: + + void addLocal(); // Add a local broker. + Args makeArgs(const std::string& prefix, size_t index); + string name; + std::auto_ptr<BrokerFixture> localBroker; + int localIndex; + std::vector<shared_ptr<ForkedBroker> > forkedBrokers; + Args userArgs; + boost::function<void (Args&, size_t)> updateArgs; +}; + +/** + * Get the known broker ports from a Connection. + *@param n if specified wait for the cluster size to be n, up to a timeout. + */ +std::set<int> knownBrokerPorts(qpid::client::Connection& source, int n=-1); + +}} // namespace qpid::tests + +#endif /*!CLUSTER_FIXTURE_H*/ diff --git a/qpid/cpp/src/tests/ConnectionOptions.h b/qpid/cpp/src/tests/ConnectionOptions.h new file mode 100644 index 0000000000..fe945e9ddd --- /dev/null +++ b/qpid/cpp/src/tests/ConnectionOptions.h @@ -0,0 +1,62 @@ +#ifndef QPID_CLIENT_CONNECTIONOPTIONS_H +#define QPID_CLIENT_CONNECTIONOPTIONS_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/client/ConnectionSettings.h" +#include "qpid/Options.h" + +namespace qpid { + +/** + * Options parser for ConnectionOptions. + */ +struct ConnectionOptions : public qpid::Options, + public qpid::client::ConnectionSettings +{ + ConnectionOptions() : qpid::Options("Connection Settings") + { + using namespace qpid; + addOptions() + ("broker,b", optValue(host, "HOST"), "Broker host to connect to") + ("port,p", optValue(port, "PORT"), "Broker port to connect to") + ("protocol,P", optValue(protocol, "tcp|ssl|rdma"), "Protocol to use for broker connection") + ("virtualhost,v", optValue(virtualhost, "VHOST"), "virtual host") + ("username", optValue(username, "USER"), "user name for broker log in.") + ("password", optValue(password, "PASSWORD"), "password for broker log in.") + ("mechanism", optValue(mechanism, "MECH"), "SASL mechanism to use when authenticating.") + ("locale", optValue(locale, "LOCALE"), "locale to use.") + ("max-channels", optValue(maxChannels, "N"), "the maximum number of channels the client requires.") + ("heartbeat", optValue(heartbeat, "N"), "Desired heartbeat interval in seconds.") + ("max-frame-size", optValue(maxFrameSize, "N"), "the maximum frame size to request.") + ("bounds-multiplier", optValue(bounds, "N"), + "bound size of write queue (as a multiple of the max frame size).") + ("tcp-nodelay", optValue(tcpNoDelay), "Turn on tcp-nodelay") + ("service", optValue(service, "SERVICE-NAME"), "SASL service name.") + ("min-ssf", optValue(minSsf, "N"), "Minimum acceptable strength for SASL security layer") + ("max-ssf", optValue(maxSsf, "N"), "Maximum acceptable strength for SASL security layer"); + } +}; + +} // namespace qpid + +#endif /*!QPID_CLIENT_CONNECTIONOPTIONS_H*/ diff --git a/qpid/cpp/src/tests/ConsoleTest.cpp b/qpid/cpp/src/tests/ConsoleTest.cpp new file mode 100644 index 0000000000..107472ed9e --- /dev/null +++ b/qpid/cpp/src/tests/ConsoleTest.cpp @@ -0,0 +1,46 @@ +/* + * + * 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/console/Package.h" +#include "qpid/console/ClassKey.h" +#include "unit_test.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(ConsoleTestSuite) + +using namespace qpid::framing; +using namespace qpid::console; + +QPID_AUTO_TEST_CASE(testClassKey) { + uint8_t hash[16] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; + ClassKey k("com.redhat.test", "class", hash); + + BOOST_CHECK_EQUAL(k.getPackageName(), "com.redhat.test"); + BOOST_CHECK_EQUAL(k.getClassName(), "class"); + BOOST_CHECK_EQUAL(k.getHashString(), "00010203-04050607-08090a0b-0c0d0e0f"); + BOOST_CHECK_EQUAL(k.str(), "com.redhat.test:class(00010203-04050607-08090a0b-0c0d0e0f)"); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/DeliveryRecordTest.cpp b/qpid/cpp/src/tests/DeliveryRecordTest.cpp new file mode 100644 index 0000000000..f7013014ff --- /dev/null +++ b/qpid/cpp/src/tests/DeliveryRecordTest.cpp @@ -0,0 +1,67 @@ + +/* + * + * 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/DeliveryRecord.h" +#include "qpid/broker/Queue.h" +#include "unit_test.h" +#include <iostream> +#include <memory> +#include <boost/format.hpp> + +using namespace qpid::broker; +using namespace qpid::sys; +using namespace qpid::framing; +using boost::dynamic_pointer_cast; +using std::list; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(DeliveryRecordTestSuite) + +QPID_AUTO_TEST_CASE(testSort) +{ + list<SequenceNumber> ids; + ids.push_back(SequenceNumber(6)); + ids.push_back(SequenceNumber(2)); + ids.push_back(SequenceNumber(4)); + ids.push_back(SequenceNumber(5)); + ids.push_back(SequenceNumber(1)); + ids.push_back(SequenceNumber(3)); + + list<DeliveryRecord> records; + for (list<SequenceNumber>::iterator i = ids.begin(); i != ids.end(); i++) { + DeliveryRecord r(QueuedMessage(0), Queue::shared_ptr(), "tag", false, false, false); + r.setId(*i); + records.push_back(r); + } + records.sort(); + + SequenceNumber expected(0); + for (list<DeliveryRecord>::iterator i = records.begin(); i != records.end(); i++) { + BOOST_CHECK(i->getId() == ++expected); + } +} + + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/DispatcherTest.cpp b/qpid/cpp/src/tests/DispatcherTest.cpp new file mode 100644 index 0000000000..e1691db584 --- /dev/null +++ b/qpid/cpp/src/tests/DispatcherTest.cpp @@ -0,0 +1,241 @@ +/* + * + * 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/Poller.h" +#include "qpid/sys/IOHandle.h" +#include "qpid/sys/Dispatcher.h" +#include "qpid/sys/DispatchHandle.h" +#include "qpid/sys/posix/PrivatePosix.h" +#include "qpid/sys/Thread.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <signal.h> + +#include <iostream> +#include <boost/bind.hpp> + +using namespace std; +using namespace qpid::sys; + +namespace qpid { +namespace tests { + +int writeALot(int fd, const string& s) { + int bytesWritten = 0; + do { + errno = 0; + int lastWrite = ::write(fd, s.c_str(), s.size()); + if ( lastWrite >= 0) { + bytesWritten += lastWrite; + } + } while (errno != EAGAIN); + return bytesWritten; +} + +int readALot(int fd) { + int bytesRead = 0; + char buf[10240]; + + do { + errno = 0; + int lastRead = ::read(fd, buf, sizeof(buf)); + if ( lastRead >= 0) { + bytesRead += lastRead; + } + } while (errno != EAGAIN); + return bytesRead; +} + +int64_t writtenBytes = 0; +int64_t readBytes = 0; + +void writer(DispatchHandle& h, int fd, const string& s) { + writtenBytes += writeALot(fd, s); + h.rewatch(); +} + +void reader(DispatchHandle& h, int fd) { + readBytes += readALot(fd); + h.rewatch(); +} + +void rInterrupt(DispatchHandle&) { + cerr << "R"; +} + +void wInterrupt(DispatchHandle&) { + cerr << "W"; +} + +DispatchHandle::Callback rcb = rInterrupt; +DispatchHandle::Callback wcb = wInterrupt; + +DispatchHandleRef *volatile rh = 0; +DispatchHandleRef *volatile wh = 0; + +volatile bool stopWait = false; +volatile bool phase1finished = false; + +timer_t timer; + +void stop_handler(int /*signo*/, siginfo_t* /*info*/, void* /*context*/) { + stopWait = true; +} + +void timer_handler(int /*signo*/, siginfo_t* /*info*/, void* /*context*/) { + static int count = 0; + if (count++ < 10) { + rh->call(rcb); + wh->call(wcb); + } else { + phase1finished = true; + assert(::timer_delete(timer) == 0); + } +} + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int /*argc*/, char** /*argv*/) +{ + // Create poller + Poller::shared_ptr poller(new Poller); + + // Create dispatcher thread + Thread dt(*poller); + Thread dt1(*poller); + Thread dt2(*poller); + Thread dt3(*poller); + + // Setup sender and receiver + int sv[2]; + int rc = ::socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + assert(rc >= 0); + + // Set non-blocking + rc = ::fcntl(sv[0], F_SETFL, O_NONBLOCK); + assert(rc >= 0); + + rc = ::fcntl(sv[1], F_SETFL, O_NONBLOCK); + assert(rc >= 0); + + // Make up a large string + string testString = "This is only a test ... 1,2,3,4,5,6,7,8,9,10;"; + for (int i = 0; i < 8; i++) + testString += testString; + + PosixIOHandle f0(sv[0]); + PosixIOHandle f1(sv[1]); + + rh = new DispatchHandleRef(f0, boost::bind(reader, _1, sv[0]), 0, 0); + wh = new DispatchHandleRef(f1, 0, boost::bind(writer, _1, sv[1], testString), 0); + + rh->startWatch(poller); + wh->startWatch(poller); + + // Set up a regular itimer interupt + // We assume that this thread will handle the signals whilst sleeping + // as the Poller threads have signal handling blocked + + // Signal handling + struct ::sigaction sa; + sa.sa_sigaction = timer_handler; + sa.sa_flags = SA_RESTART | SA_SIGINFO; + ::sigemptyset(&sa.sa_mask); + rc = ::sigaction(SIGRTMIN, &sa,0); + assert(rc == 0); + + ::sigevent se; + ::memset(&se, 0, sizeof(se)); // Clear to make valgrind happy (this *is* the neatest way to do this portably - sigh) + se.sigev_notify = SIGEV_SIGNAL; + se.sigev_signo = SIGRTMIN; + rc = ::timer_create(CLOCK_REALTIME, &se, &timer); + assert(rc == 0); + + itimerspec ts = { + /*.it_value = */ {2, 0}, // s, ns + /*.it_interval = */ {2, 0}}; // s, ns + + rc = ::timer_settime(timer, 0, &ts, 0); + assert(rc == 0); + + // wait + while (!phase1finished) { + ::sleep(1); + } + + // Now test deleting/creating DispatchHandles in tight loop, so that we are likely to still be using the + // attached PollerHandles after deleting the DispatchHandle + DispatchHandleRef* t = wh; + wh = 0; + delete t; + t = rh; + rh = 0; + delete t; + + sa.sa_sigaction = stop_handler; + rc = ::sigaction(SIGRTMIN, &sa,0); + assert(rc == 0); + + itimerspec nts = { + /*.it_value = */ {30, 0}, // s, ns + /*.it_interval = */ {30, 0}}; // s, ns + + rc = ::timer_create(CLOCK_REALTIME, &se, &timer); + assert(rc == 0); + rc = ::timer_settime(timer, 0, &nts, 0); + assert(rc == 0); + + DispatchHandleRef* rh1; + DispatchHandleRef* wh1; + + struct timespec w = {0, 1000000}; + while (!stopWait) { + rh1 = new DispatchHandleRef(f0, boost::bind(reader, _1, sv[0]), 0, 0); + wh1 = new DispatchHandleRef(f1, 0, boost::bind(writer, _1, sv[1], testString), 0); + rh1->startWatch(poller); + wh1->startWatch(poller); + + ::nanosleep(&w, 0); + + delete wh1; + delete rh1; + } + + rc = ::timer_delete(timer); + assert(rc == 0); + + poller->shutdown(); + dt.join(); + dt1.join(); + dt2.join(); + dt3.join(); + + cout << "\nWrote: " << writtenBytes << "\n"; + cout << "Read: " << readBytes << "\n"; + + return 0; +} diff --git a/qpid/cpp/src/tests/DtxWorkRecordTest.cpp b/qpid/cpp/src/tests/DtxWorkRecordTest.cpp new file mode 100644 index 0000000000..9d7666dca4 --- /dev/null +++ b/qpid/cpp/src/tests/DtxWorkRecordTest.cpp @@ -0,0 +1,193 @@ +/* + * + * 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/DtxWorkRecord.h" +#include "unit_test.h" +#include <iostream> +#include <vector> +#include "TxMocks.h" + +using namespace qpid::broker; +using boost::static_pointer_cast; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(DtxWorkRecordTestSuite) + +QPID_AUTO_TEST_CASE(testOnePhaseCommit){ + MockTransactionalStore store; + store.expectBegin().expectCommit(); + + MockTxOp::shared_ptr opA(new MockTxOp()); + opA->expectPrepare().expectCommit(); + MockTxOp::shared_ptr opB(new MockTxOp()); + opB->expectPrepare().expectCommit(); + + DtxBuffer::shared_ptr bufferA(new DtxBuffer()); + bufferA->enlist(static_pointer_cast<TxOp>(opA)); + bufferA->markEnded(); + DtxBuffer::shared_ptr bufferB(new DtxBuffer()); + bufferB->enlist(static_pointer_cast<TxOp>(opB)); + bufferB->markEnded(); + + DtxWorkRecord work("my-xid", &store); + work.add(bufferA); + work.add(bufferB); + + work.commit(true); + + store.check(); + BOOST_CHECK(store.isCommitted()); + opA->check(); + opB->check(); +} + +QPID_AUTO_TEST_CASE(testFailOnOnePhaseCommit){ + MockTransactionalStore store; + store.expectBegin().expectAbort(); + + MockTxOp::shared_ptr opA(new MockTxOp()); + opA->expectPrepare().expectRollback(); + MockTxOp::shared_ptr opB(new MockTxOp(true)); + opB->expectPrepare().expectRollback(); + MockTxOp::shared_ptr opC(new MockTxOp()); + opC->expectRollback(); + + DtxBuffer::shared_ptr bufferA(new DtxBuffer()); + bufferA->enlist(static_pointer_cast<TxOp>(opA)); + bufferA->markEnded(); + DtxBuffer::shared_ptr bufferB(new DtxBuffer()); + bufferB->enlist(static_pointer_cast<TxOp>(opB)); + bufferB->markEnded(); + DtxBuffer::shared_ptr bufferC(new DtxBuffer()); + bufferC->enlist(static_pointer_cast<TxOp>(opC)); + bufferC->markEnded(); + + DtxWorkRecord work("my-xid", &store); + work.add(bufferA); + work.add(bufferB); + work.add(bufferC); + + work.commit(true); + + BOOST_CHECK(store.isAborted()); + store.check(); + + opA->check(); + opB->check(); + opC->check(); +} + +QPID_AUTO_TEST_CASE(testTwoPhaseCommit){ + MockTransactionalStore store; + store.expectBegin2PC().expectPrepare().expectCommit(); + + MockTxOp::shared_ptr opA(new MockTxOp()); + opA->expectPrepare().expectCommit(); + MockTxOp::shared_ptr opB(new MockTxOp()); + opB->expectPrepare().expectCommit(); + + DtxBuffer::shared_ptr bufferA(new DtxBuffer()); + bufferA->enlist(static_pointer_cast<TxOp>(opA)); + bufferA->markEnded(); + DtxBuffer::shared_ptr bufferB(new DtxBuffer()); + bufferB->enlist(static_pointer_cast<TxOp>(opB)); + bufferB->markEnded(); + + DtxWorkRecord work("my-xid", &store); + work.add(bufferA); + work.add(bufferB); + + BOOST_CHECK(work.prepare()); + BOOST_CHECK(store.isPrepared()); + work.commit(false); + store.check(); + BOOST_CHECK(store.isCommitted()); + opA->check(); + opB->check(); +} + +QPID_AUTO_TEST_CASE(testFailOnTwoPhaseCommit){ + MockTransactionalStore store; + store.expectBegin2PC().expectAbort(); + + MockTxOp::shared_ptr opA(new MockTxOp()); + opA->expectPrepare().expectRollback(); + MockTxOp::shared_ptr opB(new MockTxOp(true)); + opB->expectPrepare().expectRollback(); + MockTxOp::shared_ptr opC(new MockTxOp()); + opC->expectRollback(); + + DtxBuffer::shared_ptr bufferA(new DtxBuffer()); + bufferA->enlist(static_pointer_cast<TxOp>(opA)); + bufferA->markEnded(); + DtxBuffer::shared_ptr bufferB(new DtxBuffer()); + bufferB->enlist(static_pointer_cast<TxOp>(opB)); + bufferB->markEnded(); + DtxBuffer::shared_ptr bufferC(new DtxBuffer()); + bufferC->enlist(static_pointer_cast<TxOp>(opC)); + bufferC->markEnded(); + + DtxWorkRecord work("my-xid", &store); + work.add(bufferA); + work.add(bufferB); + work.add(bufferC); + + BOOST_CHECK(!work.prepare()); + BOOST_CHECK(store.isAborted()); + store.check(); + opA->check(); + opB->check(); + opC->check(); +} + +QPID_AUTO_TEST_CASE(testRollback){ + MockTransactionalStore store; + store.expectBegin2PC().expectPrepare().expectAbort(); + + MockTxOp::shared_ptr opA(new MockTxOp()); + opA->expectPrepare().expectRollback(); + MockTxOp::shared_ptr opB(new MockTxOp()); + opB->expectPrepare().expectRollback(); + + DtxBuffer::shared_ptr bufferA(new DtxBuffer()); + bufferA->enlist(static_pointer_cast<TxOp>(opA)); + bufferA->markEnded(); + DtxBuffer::shared_ptr bufferB(new DtxBuffer()); + bufferB->enlist(static_pointer_cast<TxOp>(opB)); + bufferB->markEnded(); + + DtxWorkRecord work("my-xid", &store); + work.add(bufferA); + work.add(bufferB); + + BOOST_CHECK(work.prepare()); + BOOST_CHECK(store.isPrepared()); + work.rollback(); + store.check(); + BOOST_CHECK(store.isAborted()); + opA->check(); + opB->check(); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/ExchangeTest.cpp b/qpid/cpp/src/tests/ExchangeTest.cpp new file mode 100644 index 0000000000..88a1cd99c2 --- /dev/null +++ b/qpid/cpp/src/tests/ExchangeTest.cpp @@ -0,0 +1,289 @@ +/* + * + * 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/Exception.h" +#include "qpid/broker/Exchange.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/DeliverableMessage.h" +#include "qpid/broker/DirectExchange.h" +#include "qpid/broker/ExchangeRegistry.h" +#include "qpid/broker/FanOutExchange.h" +#include "qpid/broker/HeadersExchange.h" +#include "qpid/broker/TopicExchange.h" +#include "qpid/framing/reply_exceptions.h" +#include "unit_test.h" +#include <iostream> +#include "MessageUtils.h" + +using boost::intrusive_ptr; +using namespace qpid::broker; +using namespace qpid::framing; +using namespace qpid::sys; +using namespace qpid; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(ExchangeTestSuite) + +QPID_AUTO_TEST_CASE(testMe) +{ + Queue::shared_ptr queue(new Queue("queue", true)); + Queue::shared_ptr queue2(new Queue("queue2", true)); + + TopicExchange topic("topic"); + topic.bind(queue, "abc", 0); + topic.bind(queue2, "abc", 0); + + DirectExchange direct("direct"); + direct.bind(queue, "abc", 0); + direct.bind(queue2, "abc", 0); + + queue.reset(); + queue2.reset(); + + intrusive_ptr<Message> msgPtr(MessageUtils::createMessage("exchange", "key", false, "id")); + DeliverableMessage msg(msgPtr); + topic.route(msg, "abc", 0); + direct.route(msg, "abc", 0); + +} + +QPID_AUTO_TEST_CASE(testIsBound) +{ + Queue::shared_ptr a(new Queue("a", true)); + Queue::shared_ptr b(new Queue("b", true)); + Queue::shared_ptr c(new Queue("c", true)); + Queue::shared_ptr d(new Queue("d", true)); + + string k1("abc"); + string k2("def"); + string k3("xyz"); + + FanOutExchange fanout("fanout"); + BOOST_CHECK(fanout.bind(a, "", 0)); + BOOST_CHECK(fanout.bind(b, "", 0)); + BOOST_CHECK(fanout.bind(c, "", 0)); + + BOOST_CHECK(fanout.isBound(a, 0, 0)); + BOOST_CHECK(fanout.isBound(b, 0, 0)); + BOOST_CHECK(fanout.isBound(c, 0, 0)); + BOOST_CHECK(!fanout.isBound(d, 0, 0)); + + DirectExchange direct("direct"); + BOOST_CHECK(direct.bind(a, k1, 0)); + BOOST_CHECK(direct.bind(a, k3, 0)); + BOOST_CHECK(direct.bind(b, k2, 0)); + BOOST_CHECK(direct.bind(c, k1, 0)); + + BOOST_CHECK(direct.isBound(a, 0, 0)); + BOOST_CHECK(direct.isBound(a, &k1, 0)); + BOOST_CHECK(direct.isBound(a, &k3, 0)); + BOOST_CHECK(!direct.isBound(a, &k2, 0)); + BOOST_CHECK(direct.isBound(b, 0, 0)); + BOOST_CHECK(direct.isBound(b, &k2, 0)); + BOOST_CHECK(direct.isBound(c, &k1, 0)); + BOOST_CHECK(!direct.isBound(d, 0, 0)); + BOOST_CHECK(!direct.isBound(d, &k1, 0)); + BOOST_CHECK(!direct.isBound(d, &k2, 0)); + BOOST_CHECK(!direct.isBound(d, &k3, 0)); + + TopicExchange topic("topic"); + BOOST_CHECK(topic.bind(a, k1, 0)); + BOOST_CHECK(topic.bind(a, k3, 0)); + BOOST_CHECK(topic.bind(b, k2, 0)); + BOOST_CHECK(topic.bind(c, k1, 0)); + + BOOST_CHECK(topic.isBound(a, 0, 0)); + BOOST_CHECK(topic.isBound(a, &k1, 0)); + BOOST_CHECK(topic.isBound(a, &k3, 0)); + BOOST_CHECK(!topic.isBound(a, &k2, 0)); + BOOST_CHECK(topic.isBound(b, 0, 0)); + BOOST_CHECK(topic.isBound(b, &k2, 0)); + BOOST_CHECK(topic.isBound(c, &k1, 0)); + BOOST_CHECK(!topic.isBound(d, 0, 0)); + BOOST_CHECK(!topic.isBound(d, &k1, 0)); + BOOST_CHECK(!topic.isBound(d, &k2, 0)); + BOOST_CHECK(!topic.isBound(d, &k3, 0)); + + HeadersExchange headers("headers"); + FieldTable args1; + args1.setString("x-match", "all"); + args1.setString("a", "A"); + args1.setInt("b", 1); + FieldTable args2; + args2.setString("x-match", "any"); + args2.setString("a", "A"); + args2.setInt("b", 1); + FieldTable args3; + args3.setString("x-match", "any"); + args3.setString("c", "C"); + args3.setInt("b", 6); + + headers.bind(a, "", &args1); + headers.bind(a, "", &args3); + headers.bind(b, "", &args2); + headers.bind(c, "", &args1); + + BOOST_CHECK(headers.isBound(a, 0, 0)); + BOOST_CHECK(headers.isBound(a, 0, &args1)); + BOOST_CHECK(headers.isBound(a, 0, &args3)); + BOOST_CHECK(!headers.isBound(a, 0, &args2)); + BOOST_CHECK(headers.isBound(b, 0, 0)); + BOOST_CHECK(headers.isBound(b, 0, &args2)); + BOOST_CHECK(headers.isBound(c, 0, &args1)); + BOOST_CHECK(!headers.isBound(d, 0, 0)); + BOOST_CHECK(!headers.isBound(d, 0, &args1)); + BOOST_CHECK(!headers.isBound(d, 0, &args2)); + BOOST_CHECK(!headers.isBound(d, 0, &args3)); +} + +QPID_AUTO_TEST_CASE(testDeleteGetAndRedeclare) +{ + ExchangeRegistry exchanges; + exchanges.declare("my-exchange", "direct", false, FieldTable()); + exchanges.destroy("my-exchange"); + try { + exchanges.get("my-exchange"); + } catch (const NotFoundException&) {} + std::pair<Exchange::shared_ptr, bool> response = exchanges.declare("my-exchange", "direct", false, FieldTable()); + BOOST_CHECK_EQUAL(string("direct"), response.first->getType()); +} + +intrusive_ptr<Message> cmessage(std::string exchange, std::string routingKey) { + 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); + return msg; +} + +QPID_AUTO_TEST_CASE(testSequenceOptions) +{ + FieldTable args; + args.setInt("qpid.msg_sequence",1); + char* buff = new char[10000]; + framing::Buffer buffer(buff,10000); + { + DirectExchange direct("direct1", false, args); + + intrusive_ptr<Message> msg1 = cmessage("e", "A"); + intrusive_ptr<Message> msg2 = cmessage("e", "B"); + intrusive_ptr<Message> msg3 = cmessage("e", "C"); + + DeliverableMessage dmsg1(msg1); + DeliverableMessage dmsg2(msg2); + DeliverableMessage dmsg3(msg3); + + direct.route(dmsg1, "abc", 0); + direct.route(dmsg2, "abc", 0); + direct.route(dmsg3, "abc", 0); + + BOOST_CHECK_EQUAL(1, msg1->getApplicationHeaders()->getAsInt64("qpid.msg_sequence")); + BOOST_CHECK_EQUAL(2, msg2->getApplicationHeaders()->getAsInt64("qpid.msg_sequence")); + BOOST_CHECK_EQUAL(3, msg3->getApplicationHeaders()->getAsInt64("qpid.msg_sequence")); + + FanOutExchange fanout("fanout1", false, args); + HeadersExchange header("headers1", false, args); + TopicExchange topic ("topic1", false, args); + + // check other exchanges, that they preroute + intrusive_ptr<Message> msg4 = cmessage("e", "A"); + intrusive_ptr<Message> msg5 = cmessage("e", "B"); + intrusive_ptr<Message> msg6 = cmessage("e", "C"); + + DeliverableMessage dmsg4(msg4); + DeliverableMessage dmsg5(msg5); + DeliverableMessage dmsg6(msg6); + + fanout.route(dmsg4, "abc", 0); + BOOST_CHECK_EQUAL(1, msg4->getApplicationHeaders()->getAsInt64("qpid.msg_sequence")); + + FieldTable headers; + header.route(dmsg5, "abc", &headers); + BOOST_CHECK_EQUAL(1, msg5->getApplicationHeaders()->getAsInt64("qpid.msg_sequence")); + + topic.route(dmsg6, "abc", 0); + BOOST_CHECK_EQUAL(1, msg6->getApplicationHeaders()->getAsInt64("qpid.msg_sequence")); + direct.encode(buffer); + } + { + + ExchangeRegistry exchanges; + buffer.reset(); + DirectExchange::shared_ptr exch_dec = Exchange::decode(exchanges, buffer); + + intrusive_ptr<Message> msg1 = cmessage("e", "A"); + DeliverableMessage dmsg1(msg1); + exch_dec->route(dmsg1, "abc", 0); + + BOOST_CHECK_EQUAL(4, msg1->getApplicationHeaders()->getAsInt64("qpid.msg_sequence")); + + } + delete [] buff; +} + +QPID_AUTO_TEST_CASE(testIVEOption) +{ + FieldTable args; + args.setInt("qpid.ive",1); + DirectExchange direct("direct1", false, args); + FanOutExchange fanout("fanout1", false, args); + HeadersExchange header("headers1", false, args); + TopicExchange topic ("topic1", false, args); + + intrusive_ptr<Message> msg1 = cmessage("direct1", "abc"); + msg1->getProperties<MessageProperties>()->getApplicationHeaders().setString("a", "abc"); + DeliverableMessage dmsg1(msg1); + + FieldTable args2; + args2.setString("x-match", "any"); + args2.setString("a", "abc"); + + direct.route(dmsg1, "abc", 0); + fanout.route(dmsg1, "abc", 0); + header.route(dmsg1, "abc", &args2); + topic.route(dmsg1, "abc", 0); + Queue::shared_ptr queue(new Queue("queue", true)); + Queue::shared_ptr queue1(new Queue("queue1", true)); + Queue::shared_ptr queue2(new Queue("queue2", true)); + Queue::shared_ptr queue3(new Queue("queue3", true)); + + BOOST_CHECK(HeadersExchange::match(args2, msg1->getProperties<MessageProperties>()->getApplicationHeaders())); + + BOOST_CHECK(direct.bind(queue, "abc", 0)); + BOOST_CHECK(fanout.bind(queue1, "abc", 0)); + BOOST_CHECK(header.bind(queue2, "", &args2)); + BOOST_CHECK(topic.bind(queue3, "abc", 0)); + + BOOST_CHECK_EQUAL(1u,queue->getMessageCount()); + BOOST_CHECK_EQUAL(1u,queue1->getMessageCount()); + BOOST_CHECK_EQUAL(1u,queue2->getMessageCount()); + BOOST_CHECK_EQUAL(1u,queue3->getMessageCount()); + +} + + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/FieldTable.cpp b/qpid/cpp/src/tests/FieldTable.cpp new file mode 100644 index 0000000000..fe2a14ec03 --- /dev/null +++ b/qpid/cpp/src/tests/FieldTable.cpp @@ -0,0 +1,213 @@ +/* + * + * 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 "qpid/framing/Array.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/framing/FieldValue.h" +#include "qpid/framing/List.h" +#include "qpid/sys/alloca.h" + +#include "unit_test.h" + +using namespace qpid::framing; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(FieldTableTestSuite) + +QPID_AUTO_TEST_CASE(testMe) +{ + FieldTable ft; + ft.setString("A", "BCDE"); + BOOST_CHECK(string("BCDE") == ft.getAsString("A")); + + char buff[100]; + Buffer wbuffer(buff, 100); + wbuffer.put(ft); + + Buffer rbuffer(buff, 100); + FieldTable ft2; + rbuffer.get(ft2); + BOOST_CHECK(string("BCDE") == ft2.getAsString("A")); + +} + +QPID_AUTO_TEST_CASE(testAssignment) +{ + FieldTable a; + FieldTable b; + + a.setString("A", "BBBB"); + a.setInt("B", 1234); + b = a; + a.setString("A", "CCCC"); + + BOOST_CHECK(string("CCCC") == a.getAsString("A")); + BOOST_CHECK(string("BBBB") == b.getAsString("A")); + BOOST_CHECK_EQUAL(1234, a.getAsInt("B")); + BOOST_CHECK_EQUAL(1234, b.getAsInt("B")); + BOOST_CHECK(IntegerValue(1234) == *a.get("B")); + BOOST_CHECK(IntegerValue(1234) == *b.get("B")); + + FieldTable d; + { + FieldTable c; + c = a; + + char* buff = static_cast<char*>(::alloca(c.encodedSize())); + Buffer wbuffer(buff, c.encodedSize()); + wbuffer.put(c); + + Buffer rbuffer(buff, c.encodedSize()); + rbuffer.get(d); + BOOST_CHECK_EQUAL(c, d); + BOOST_CHECK(string("CCCC") == c.getAsString("A")); + BOOST_CHECK(IntegerValue(1234) == *c.get("B")); + } + BOOST_CHECK(string("CCCC") == d.getAsString("A")); + BOOST_CHECK(IntegerValue(1234) == *d.get("B")); +} + + +QPID_AUTO_TEST_CASE(testNestedValues) +{ + double d = 1.2345; + uint32_t u = 101; + char buff[1000]; + { + FieldTable a; + FieldTable b; + std::vector<std::string> items; + items.push_back("one"); + items.push_back("two"); + Array c(items); + List list; + list.push_back(List::ValuePtr(new Str16Value("red"))); + list.push_back(List::ValuePtr(new Unsigned32Value(u))); + list.push_back(List::ValuePtr(new Str8Value("yellow"))); + list.push_back(List::ValuePtr(new DoubleValue(d))); + + a.setString("id", "A"); + b.setString("id", "B"); + a.setTable("B", b); + a.setArray("C", c); + a.set("my-list", FieldTable::ValuePtr(new ListValue(list))); + + + Buffer wbuffer(buff, 100); + wbuffer.put(a); + } + { + Buffer rbuffer(buff, 100); + FieldTable a; + FieldTable b; + Array c; + rbuffer.get(a); + BOOST_CHECK(string("A") == a.getAsString("id")); + a.getTable("B", b); + BOOST_CHECK(string("B") == b.getAsString("id")); + a.getArray("C", c); + std::vector<std::string> items; + c.collect(items); + BOOST_CHECK((uint) 2 == items.size()); + BOOST_CHECK(string("one") == items[0]); + BOOST_CHECK(string("two") == items[1]); + + List list; + BOOST_CHECK(a.get("my-list")->get<List>(list)); + List::const_iterator i = list.begin(); + BOOST_CHECK(i != list.end()); + BOOST_CHECK_EQUAL(std::string("red"), (*i)->get<std::string>()); + + i++; + BOOST_CHECK(i != list.end()); + BOOST_CHECK_EQUAL(u, (uint32_t) (*i)->get<int>()); + + i++; + BOOST_CHECK(i != list.end()); + BOOST_CHECK_EQUAL(std::string("yellow"), (*i)->get<std::string>()); + + i++; + BOOST_CHECK(i != list.end()); + BOOST_CHECK_EQUAL(d, (*i)->get<double>()); + + i++; + BOOST_CHECK(i == list.end()); + } +} + +QPID_AUTO_TEST_CASE(testFloatAndDouble) +{ + char buff[100]; + float f = 5.672f; + double d = 56.720001; + { + FieldTable a; + a.setString("string", "abc"); + a.setInt("int", 5672); + a.setFloat("float", f); + a.setDouble("double", d); + + Buffer wbuffer(buff, 100); + wbuffer.put(a); + } + { + Buffer rbuffer(buff, 100); + FieldTable a; + rbuffer.get(a); + BOOST_CHECK(string("abc") == a.getAsString("string")); + BOOST_CHECK(5672 == a.getAsInt("int")); + float f2; + BOOST_CHECK(!a.getFloat("string", f2)); + BOOST_CHECK(!a.getFloat("int", f2)); + BOOST_CHECK(a.getFloat("float", f2)); + BOOST_CHECK(f2 == f); + + double d2; + BOOST_CHECK(!a.getDouble("string", d2)); + BOOST_CHECK(!a.getDouble("int", d2)); + BOOST_CHECK(a.getDouble("double", d2)); + BOOST_CHECK(d2 == d); + } +} + +QPID_AUTO_TEST_CASE(test64GetAndSetConverts) +{ + FieldTable args; + args.setInt64("a",100); + args.setInt64("b",-(int64_t) ((int64_t) 1<<34)); + + args.setUInt64("c",1u); + args.setUInt64("d",(uint64_t) ((uint64_t) 1<<34)); + BOOST_CHECK_EQUAL(1u, args.getAsUInt64("c")); + BOOST_CHECK_EQUAL(100u, args.getAsUInt64("a")); + BOOST_CHECK_EQUAL(1, args.getAsInt64("c")); + BOOST_CHECK_EQUAL(100, args.getAsInt64("a")); + BOOST_CHECK_EQUAL(-(int64_t) ((int64_t) 1<<34), args.getAsInt64("b")); + BOOST_CHECK_EQUAL((uint64_t) ((uint64_t) 1<<34), args.getAsUInt64("d")); + BOOST_CHECK_EQUAL((int64_t) ((int64_t) 1<<34), args.getAsInt64("d")); + +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/FieldValue.cpp b/qpid/cpp/src/tests/FieldValue.cpp new file mode 100644 index 0000000000..0ebd0d7d44 --- /dev/null +++ b/qpid/cpp/src/tests/FieldValue.cpp @@ -0,0 +1,95 @@ +/* + * 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/FieldValue.h" + +#include "unit_test.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(FieldValueTestSuite) + +using namespace qpid::framing; + +Str16Value s("abc"); +IntegerValue i(42); +//DecimalValue d(1234,2); +//FieldTableValue ft; +//EmptyValue e; + +QPID_AUTO_TEST_CASE(testStr16ValueEquals) +{ + + BOOST_CHECK(Str16Value("abc") == s); + BOOST_CHECK(Str16Value("foo") != s); + BOOST_CHECK(s != i); + BOOST_CHECK(s.convertsTo<std::string>() == true); + BOOST_CHECK(s.convertsTo<int>() == false); + BOOST_CHECK(s.get<std::string>() == "abc"); + BOOST_CHECK_THROW(s.get<int>(), InvalidConversionException); +// BOOST_CHECK(s != ft); + +} + +QPID_AUTO_TEST_CASE(testIntegerValueEquals) +{ + BOOST_CHECK(IntegerValue(42) == i); + BOOST_CHECK(IntegerValue(5) != i); + BOOST_CHECK(i != s); + BOOST_CHECK(i.convertsTo<std::string>() == false); + BOOST_CHECK(i.convertsTo<int>() == true); + BOOST_CHECK_THROW(i.get<std::string>(), InvalidConversionException); + BOOST_CHECK(i.get<int>() == 42); +// BOOST_CHECK(i != ft); +} + +#if 0 +QPID_AUTO_TEST_CASE(testDecimalValueEquals) +{ + BOOST_CHECK(DecimalValue(1234, 2) == d); + BOOST_CHECK(DecimalValue(12345, 2) != d); + BOOST_CHECK(DecimalValue(1234, 3) != d); + BOOST_CHECK(d != s); +} + +QPID_AUTO_TEST_CASE(testFieldTableValueEquals) +{ + ft.getValue().setString("foo", "FOO"); + ft.getValue().setInt("magic", 7); + + BOOST_CHECK_EQUAL(std::string("FOO"), + ft.getValue().getString("foo")); + BOOST_CHECK_EQUAL(7, ft.getValue().getInt("magic")); + + FieldTableValue f2; + BOOST_CHECK(ft != f2); + f2.getValue().setString("foo", "FOO"); + BOOST_CHECK(ft != f2); + f2.getValue().setInt("magic", 7); + BOOST_CHECK_EQUAL(ft,f2); + BOOST_CHECK(ft == f2); + f2.getValue().setString("foo", "BAR"); + BOOST_CHECK(ft != f2); + BOOST_CHECK(ft != i); +} +#endif + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/ForkedBroker.cpp b/qpid/cpp/src/tests/ForkedBroker.cpp new file mode 100644 index 0000000000..53eaa7e1ce --- /dev/null +++ b/qpid/cpp/src/tests/ForkedBroker.cpp @@ -0,0 +1,158 @@ +/* + * + * 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 "ForkedBroker.h" +#include "qpid/log/Statement.h" +#include <boost/bind.hpp> +#include <algorithm> +#include <stdlib.h> +#include <sys/types.h> +#include <signal.h> + +using namespace std; +using qpid::ErrnoException; + +namespace std { +static ostream& operator<<(ostream& o, const qpid::tests::ForkedBroker::Args& a) { +copy(a.begin(), a.end(), ostream_iterator<string>(o, " ")); +return o; +} +} + +namespace qpid { +namespace tests { + +ForkedBroker::ForkedBroker(const Args& constArgs) : running(false), exitStatus(0) { + Args args(constArgs); + // Substitute the special value "TMP_DATA_DIR" with a temporary data dir. + Args::iterator i = find(args.begin(), args.end(), string("TMP_DATA_DIR")); + if (i != args.end()) { + args.erase(i); + char dd[] = "/tmp/ForkedBroker.XXXXXX"; + if (!mkdtemp(dd)) + throw qpid::ErrnoException("Can't create data dir"); + dataDir = dd; + args.push_back("--data-dir"); + args.push_back(dataDir); + } + // Never use the default data directory, set --no-data-dir if no other data-dir arg. + Args::iterator j = find(args.begin(), args.end(), string("--data-dir")); + Args::iterator k = find(args.begin(), args.end(), string("--no-data-dir")); + if (j == args.end() && k == args.end()) + args.push_back("--no-data-dir"); + init(args); +} + +ForkedBroker::~ForkedBroker() { + try { kill(); } + catch (const std::exception& e) { + QPID_LOG(error, QPID_MSG("Killing forked broker: " << e.what())); + } + if (!dataDir.empty()) + { + int unused_ret; // Suppress warnings about ignoring return value. + unused_ret = ::system(("rm -rf "+dataDir).c_str()); + } +} + +void ForkedBroker::kill(int sig) { + if (pid == 0) return; + int savePid = pid; + pid = 0; // Reset pid here in case of an exception. + using qpid::ErrnoException; + if (::kill(savePid, sig) < 0) + throw ErrnoException("kill failed"); + int status; + if (::waitpid(savePid, &status, 0) < 0 && sig != 9) + throw ErrnoException("wait for forked process failed"); + if (WEXITSTATUS(status) != 0 && sig != 9) + throw qpid::Exception(QPID_MSG("Forked broker exited with: " << WEXITSTATUS(status))); + running = false; + exitStatus = status; +} + +bool isLogOption(const std::string& s) { + const char * log_enable = "--log-enable", + * trace = "--trace"; + return( (! strncmp(s.c_str(), log_enable, strlen(log_enable))) || + (! strncmp(s.c_str(), trace, strlen(trace))) + ); +} + +namespace { + void ignore_signal(int) + { + } +} + +void ForkedBroker::init(const Args& userArgs) { + using qpid::ErrnoException; + port = 0; + int pipeFds[2]; + if(::pipe(pipeFds) < 0) throw ErrnoException("Can't create pipe"); + + // Ignore the SIGCHLD signal generated by an exitting child + // We will clean up any exitting children in the waitpid above + // This should really be neater (like only once not per fork) + struct ::sigaction sa; + sa.sa_handler = ignore_signal; + ::sigemptyset(&sa.sa_mask); + ::sigaddset(&sa.sa_mask, SIGCHLD); + sa.sa_flags = SA_NOCLDSTOP | SA_RESTART; + ::sigaction(SIGCHLD, &sa, 0); + + pid = ::fork(); + if (pid < 0) throw ErrnoException("Fork failed"); + if (pid) { // parent + ::close(pipeFds[1]); + FILE* f = ::fdopen(pipeFds[0], "r"); + if (!f) throw ErrnoException("fopen failed"); + if (::fscanf(f, "%d", &port) != 1) { + if (ferror(f)) throw ErrnoException("Error reading port number from child."); + else throw qpid::Exception("EOF reading port number from child."); + } + ::fclose(f); + running = true; + } + else { // child + ::close(pipeFds[0]); + int fd = ::dup2(pipeFds[1], 1); // pipe stdout to the parent. + if (fd < 0) throw ErrnoException("dup2 failed"); + const char* prog = ::getenv("QPIDD_EXEC"); + if (!prog) prog = "../qpidd"; // This only works from within svn checkout + Args args(userArgs); + args.push_back("--port=0"); + // Keep quiet except for errors. + if (!::getenv("QPID_TRACE") && !::getenv("QPID_LOG_ENABLE") + && find_if(userArgs.begin(), userArgs.end(), isLogOption) == userArgs.end()) + args.push_back("--log-enable=error+"); + std::vector<const char*> argv(args.size()); + std::transform(args.begin(), args.end(), argv.begin(), boost::bind(&std::string::c_str, _1)); + argv.push_back(0); + QPID_LOG(debug, "ForkedBroker exec " << prog << ": " << args); + + execv(prog, const_cast<char* const*>(&argv[0])); + QPID_LOG(critical, "execv failed to start broker: prog=\"" << prog << "\"; args=\"" << args << "\"; errno=" << errno << " (" << std::strerror(errno) << ")"); + ::exit(1); + } +} + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/ForkedBroker.h b/qpid/cpp/src/tests/ForkedBroker.h new file mode 100644 index 0000000000..87e141a425 --- /dev/null +++ b/qpid/cpp/src/tests/ForkedBroker.h @@ -0,0 +1,82 @@ +#ifndef TESTS_FORKEDBROKER_H +#define TESTS_FORKEDBROKER_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/Exception.h" +#include "qpid/log/Statement.h" +#include "qpid/broker/Broker.h" +#include <boost/lexical_cast.hpp> +#include <string> +#include <stdio.h> +#include <sys/wait.h> + +namespace qpid { +namespace tests { + +/** + * Class to fork a broker child process. + * + * For most tests a BrokerFixture may be more convenient as it starts + * a broker in the same process which allows you to easily debug into + * the broker. + * + * This useful for tests that need to start multiple brokers where + * those brokers can't coexist in the same process (e.g. for cluster + * tests where CPG doesn't allow multiple group members in a single + * process.) + * + */ +class ForkedBroker { + public: + typedef std::vector<std::string> Args; + + // argv args are passed to broker. + // + // Special value "TMP_DATA_DIR" is substituted with a temporary + // data directory for the broker. + // + ForkedBroker(const Args& argv); + ~ForkedBroker(); + + void kill(int sig=SIGINT); + int wait(); // Wait for exit, return exit status. + uint16_t getPort() { return port; } + pid_t getPID() { return pid; } + bool isRunning() { return running; } + + private: + + void init(const Args& args); + + bool running; + int exitStatus; + + pid_t pid; + int port; + std::string dataDir; +}; + +}} // namespace qpid::tests + +#endif /*!TESTS_FORKEDBROKER_H*/ diff --git a/qpid/cpp/src/tests/Frame.cpp b/qpid/cpp/src/tests/Frame.cpp new file mode 100644 index 0000000000..1270eabba3 --- /dev/null +++ b/qpid/cpp/src/tests/Frame.cpp @@ -0,0 +1,85 @@ +/* + * + * 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/Frame.h" + +#include <boost/lexical_cast.hpp> +#include "unit_test.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(FrameTestSuite) + +using namespace std; +using namespace qpid::framing; +using namespace boost; + +QPID_AUTO_TEST_CASE(testContentBody) { + Frame f(42, AMQContentBody("foobar")); + AMQBody* body=f.getBody(); + BOOST_CHECK(dynamic_cast<AMQContentBody*>(body)); + Buffer b(f.encodedSize(); + f.encode(b); + b.flip(); + Frame g; + g.decode(b); + AMQContentBody* content=dynamic_cast<AMQContentBody*>(g.getBody()); + BOOST_REQUIRE(content); + BOOST_CHECK_EQUAL(content->getData(), "foobar"); +} + +QPID_AUTO_TEST_CASE(testMethodBody) { + FieldTable args; + args.setString("foo", "bar"); + Frame f( + 42, QueueDeclareBody(ProtocolVersion(), 1, "q", "altex", + true, false, true, false, true, args)); + BOOST_CHECK_EQUAL(f.getChannel(), 42); + Buffer b(f.encodedSize(); + f.encode(b); + b.flip(); + Frame g; + g.decode(b); + BOOST_CHECK_EQUAL(f.getChannel(), g.getChannel()); + QueueDeclareBody* declare=dynamic_cast<QueueDeclareBody*>(g.getBody()); + BOOST_REQUIRE(declare); + BOOST_CHECK_EQUAL(declare->getAlternateExchange(), "altex"); + BOOST_CHECK_EQUAL(lexical_cast<string>(*f.getBody()), lexical_cast<string>(*g.getBody())); +} + +QPID_AUTO_TEST_CASE(testLoop) { + // Run in a loop so heap profiler can spot any allocations. + Buffer b(1024); + for (int i = 0; i < 100; ++i) { + Frame ctor(2, AccessRequestOkBody(ProtocolVersion(), 42)); + Frame assign(3); + assign.body = AccessRequestOkBody(ProtocolVersion(), 42); + assign.encode(b); + b.flip(); + Frame g; + g.decode(b); + BOOST_REQUIRE(dynamic_cast<AccessRequestOkBody*>(g.getBody())->getTicket() == 42); + } +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/FrameDecoder.cpp b/qpid/cpp/src/tests/FrameDecoder.cpp new file mode 100644 index 0000000000..9eeff2a41e --- /dev/null +++ b/qpid/cpp/src/tests/FrameDecoder.cpp @@ -0,0 +1,78 @@ +/* + * + * 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 "unit_test.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/FrameDecoder.h" +#include "qpid/framing/AMQContentBody.h" +#include "qpid/framing/Buffer.h" +#include <string> + + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(FrameDecoderTest) + +using namespace std; +using namespace qpid::framing; + + +string makeData(int size) { + string data; + data.resize(size); + for (int i =0; i < size; ++i) + data[i] = 'a' + (i%26); + return data; +} +string encodeFrame(string data) { + AMQFrame f((AMQContentBody(data))); + string encoded; + encoded.resize(f.encodedSize()); + Buffer b(&encoded[0], encoded.size()); + f.encode(b); + return encoded; +} + +string getData(const AMQFrame& frame) { + const AMQContentBody* content = dynamic_cast<const AMQContentBody*>(frame.getBody()); + BOOST_CHECK(content); + return content->getData(); +} + +QPID_AUTO_TEST_CASE(testByteFragments) { + string data = makeData(42); + string encoded = encodeFrame(data); + FrameDecoder decoder; + for (size_t i = 0; i < encoded.size()-1; ++i) { + Buffer buf(&encoded[i], 1); + BOOST_CHECK(!decoder.decode(buf)); + } + Buffer buf(&encoded[encoded.size()-1], 1); + BOOST_CHECK(decoder.decode(buf)); + BOOST_CHECK_EQUAL(data, getData(decoder.getFrame())); +} + + + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/FramingTest.cpp b/qpid/cpp/src/tests/FramingTest.cpp new file mode 100644 index 0000000000..f8795316cc --- /dev/null +++ b/qpid/cpp/src/tests/FramingTest.cpp @@ -0,0 +1,167 @@ +/* + * + * 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/Connector.h" +#include "qpid/framing/AMQP_HighestVersion.h" +#include "qpid/framing/ProtocolVersion.h" +#include "qpid/framing/all_method_bodies.h" +#include "qpid/framing/amqp_framing.h" +#include "qpid/framing/reply_exceptions.h" +#include "unit_test.h" + +#include <boost/bind.hpp> +#include <boost/lexical_cast.hpp> +#include <iostream> + +#include <memory> +#include <sstream> +#include <typeinfo> + +using namespace qpid; +using namespace qpid::framing; +using namespace std; + +namespace qpid { +namespace tests { + +template <class T> +std::string tostring(const T& x) +{ + std::ostringstream out; + out << x; + return out.str(); +} + +QPID_AUTO_TEST_SUITE(FramingTestSuite) + +QPID_AUTO_TEST_CASE(testMessageTransferBody) +{ + char buffer[1024]; + ProtocolVersion version(highestProtocolVersion); + Buffer wbuff(buffer, sizeof(buffer)); + MessageTransferBody in(version, "my-exchange", 1, 1); + in.encode(wbuff); + + Buffer rbuff(buffer, sizeof(buffer)); + MessageTransferBody out(version); + out.decode(rbuff); + BOOST_CHECK_EQUAL(tostring(in), tostring(out)); +} + +QPID_AUTO_TEST_CASE(testConnectionSecureBody) +{ + char buffer[1024]; + ProtocolVersion version(highestProtocolVersion); + Buffer wbuff(buffer, sizeof(buffer)); + std::string s = "security credential"; + ConnectionSecureBody in(version, s); + in.encode(wbuff); + + Buffer rbuff(buffer, sizeof(buffer)); + ConnectionSecureBody out(version); + out.decode(rbuff); + BOOST_CHECK_EQUAL(tostring(in), tostring(out)); +} + +QPID_AUTO_TEST_CASE(testConnectionRedirectBody) +{ + char buffer[1024]; + ProtocolVersion version(highestProtocolVersion); + Buffer wbuff(buffer, sizeof(buffer)); + std::string a = "hostA"; + std::string b = "hostB"; + Array hosts(0x95); + hosts.add(boost::shared_ptr<FieldValue>(new Str16Value(a))); + hosts.add(boost::shared_ptr<FieldValue>(new Str16Value(b))); + + ConnectionRedirectBody in(version, a, hosts); + in.encode(wbuff); + + Buffer rbuff(buffer, sizeof(buffer)); + ConnectionRedirectBody out(version); + out.decode(rbuff); + BOOST_CHECK_EQUAL(tostring(in), tostring(out)); +} + +QPID_AUTO_TEST_CASE(testQueueDeclareBody) +{ + char buffer[1024]; + ProtocolVersion version(highestProtocolVersion); + Buffer wbuff(buffer, sizeof(buffer)); + QueueDeclareBody in(version, "name", "dlq", true, false, true, false, FieldTable()); + in.encode(wbuff); + + Buffer rbuff(buffer, sizeof(buffer)); + QueueDeclareBody out(version); + out.decode(rbuff); + BOOST_CHECK_EQUAL(tostring(in), tostring(out)); +} + +QPID_AUTO_TEST_CASE(testConnectionRedirectBodyFrame) +{ + char buffer[1024]; + ProtocolVersion version(highestProtocolVersion); + Buffer wbuff(buffer, sizeof(buffer)); + std::string a = "hostA"; + std::string b = "hostB"; + Array hosts(0x95); + hosts.add(boost::shared_ptr<FieldValue>(new Str16Value(a))); + hosts.add(boost::shared_ptr<FieldValue>(new Str16Value(b))); + + AMQFrame in((ConnectionRedirectBody(version, a, hosts))); + in.setChannel(999); + in.encode(wbuff); + + Buffer rbuff(buffer, sizeof(buffer)); + AMQFrame out; + out.decode(rbuff); + BOOST_CHECK_EQUAL(tostring(in), tostring(out)); +} + +QPID_AUTO_TEST_CASE(testMessageCancelBodyFrame) +{ + char buffer[1024]; + ProtocolVersion version(highestProtocolVersion); + Buffer wbuff(buffer, sizeof(buffer)); + AMQFrame in((MessageCancelBody(version, "tag"))); + in.setChannel(999); + in.encode(wbuff); + + Buffer rbuff(buffer, sizeof(buffer)); + AMQFrame out; + out.decode(rbuff); + BOOST_CHECK_EQUAL(tostring(in), tostring(out)); +} + +QPID_AUTO_TEST_CASE(badStrings) { + char data[(65535 + 2) + (255 + 1)]; + Buffer b(data, sizeof(data)); + BOOST_CHECK_THROW(b.putShortString(std::string(256, 'X')), + Exception); + BOOST_CHECK_THROW(b.putMediumString(std::string(65536, 'X')), + Exception); + b.putShortString(std::string(255, 'X')); + b.putMediumString(std::string(65535, 'X')); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/HeaderTest.cpp b/qpid/cpp/src/tests/HeaderTest.cpp new file mode 100644 index 0000000000..4b16f3c793 --- /dev/null +++ b/qpid/cpp/src/tests/HeaderTest.cpp @@ -0,0 +1,114 @@ +/* + * + * 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 "qpid/framing/amqp_framing.h" +#include "qpid/framing/FieldValue.h" +#include "unit_test.h" + +using namespace qpid::framing; +using namespace std; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(HeaderTestSuite) + +QPID_AUTO_TEST_CASE(testGenericProperties) +{ + AMQHeaderBody body; + body.get<MessageProperties>(true)->getApplicationHeaders().setString( + "A", "BCDE"); + char buff[100]; + Buffer wbuffer(buff, 100); + body.encode(wbuffer); + + Buffer rbuffer(buff, 100); + AMQHeaderBody body2; + body2.decode(rbuffer, body.encodedSize()); + MessageProperties* props = + body2.get<MessageProperties>(true); + BOOST_CHECK_EQUAL( + string("BCDE"), + props->getApplicationHeaders().get("A")->get<string>()); +} + +QPID_AUTO_TEST_CASE(testMessageProperties) +{ + AMQFrame out((AMQHeaderBody())); + MessageProperties* props1 = + out.castBody<AMQHeaderBody>()->get<MessageProperties>(true); + + props1->setContentLength(42); + props1->setMessageId(Uuid(true)); + props1->setCorrelationId("correlationId"); + props1->setReplyTo(ReplyTo("ex","key")); + props1->setContentType("contentType"); + props1->setContentEncoding("contentEncoding"); + props1->setUserId("userId"); + props1->setAppId("appId"); + + char buff[10000]; + Buffer wbuffer(buff, 10000); + out.encode(wbuffer); + + Buffer rbuffer(buff, 10000); + AMQFrame in; + in.decode(rbuffer); + MessageProperties* props2 = + in.castBody<AMQHeaderBody>()->get<MessageProperties>(true); + + BOOST_CHECK_EQUAL(props1->getContentLength(), props2->getContentLength()); + BOOST_CHECK_EQUAL(props1->getMessageId(), props2->getMessageId()); + BOOST_CHECK_EQUAL(props1->getCorrelationId(), props2->getCorrelationId()); + BOOST_CHECK_EQUAL(props1->getContentType(), props2->getContentType()); + BOOST_CHECK_EQUAL(props1->getContentEncoding(), props2->getContentEncoding()); + BOOST_CHECK_EQUAL(props1->getUserId(), props2->getUserId()); + BOOST_CHECK_EQUAL(props1->getAppId(), props2->getAppId()); + +} + +QPID_AUTO_TEST_CASE(testDeliveryProperies) +{ + AMQFrame out((AMQHeaderBody())); + DeliveryProperties* props1 = + out.castBody<AMQHeaderBody>()->get<DeliveryProperties>(true); + + props1->setDiscardUnroutable(true); + props1->setExchange("foo"); + + char buff[10000]; + Buffer wbuffer(buff, 10000); + out.encode(wbuffer); + + Buffer rbuffer(buff, 10000); + AMQFrame in; + in.decode(rbuffer); + DeliveryProperties* props2 = + in.castBody<AMQHeaderBody>()->get<DeliveryProperties>(true); + + BOOST_CHECK(props2->getDiscardUnroutable()); + BOOST_CHECK_EQUAL(string("foo"), props2->getExchange()); + BOOST_CHECK(!props2->hasTimestamp()); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/HeadersExchangeTest.cpp b/qpid/cpp/src/tests/HeadersExchangeTest.cpp new file mode 100644 index 0000000000..40deb59c86 --- /dev/null +++ b/qpid/cpp/src/tests/HeadersExchangeTest.cpp @@ -0,0 +1,120 @@ +/* + * + * 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/Exception.h" +#include "qpid/broker/HeadersExchange.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/framing/FieldValue.h" +#include "unit_test.h" + +using namespace qpid::broker; +using namespace qpid::framing; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(HeadersExchangeTestSuite) + +QPID_AUTO_TEST_CASE(testMatchAll) +{ + FieldTable b, m, n; + b.setString("x-match", "all"); + b.setString("foo", "FOO"); + b.setInt("n", 42); + m.setString("foo", "FOO"); + m.setInt("n", 42); + BOOST_CHECK(HeadersExchange::match(b, m)); + + // Ignore extras. + m.setString("extra", "x"); + BOOST_CHECK(HeadersExchange::match(b, m)); + + // Fail mismatch, wrong value. + m.setString("foo", "NotFoo"); + BOOST_CHECK(!HeadersExchange::match(b, m)); + + // Fail mismatch, missing value + n.setInt("n", 42); + n.setString("extra", "x"); + BOOST_CHECK(!HeadersExchange::match(b, n)); +} + +QPID_AUTO_TEST_CASE(testMatchAny) +{ + FieldTable b, m, n; + b.setString("x-match", "any"); + b.setString("foo", "FOO"); + b.setInt("n", 42); + m.setString("foo", "FOO"); + BOOST_CHECK(!HeadersExchange::match(b, n)); + BOOST_CHECK(HeadersExchange::match(b, m)); + m.setInt("n", 42); + BOOST_CHECK(HeadersExchange::match(b, m)); +} + +QPID_AUTO_TEST_CASE(testMatchEmptyValue) +{ + FieldTable b, m; + b.setString("x-match", "all"); + b.set("foo", FieldTable::ValuePtr()); + b.set("n", FieldTable::ValuePtr()); + BOOST_CHECK(!HeadersExchange::match(b, m)); + m.setString("foo", "blah"); + m.setInt("n", 123); +} + +QPID_AUTO_TEST_CASE(testMatchEmptyArgs) +{ + FieldTable b, m; + m.setString("foo", "FOO"); + + b.setString("x-match", "all"); + BOOST_CHECK(HeadersExchange::match(b, m)); + b.setString("x-match", "any"); + BOOST_CHECK(!HeadersExchange::match(b, m)); +} + + +QPID_AUTO_TEST_CASE(testMatchNoXMatch) +{ + FieldTable b, m; + b.setString("foo", "FOO"); + m.setString("foo", "FOO"); + BOOST_CHECK(!HeadersExchange::match(b, m)); +} + +QPID_AUTO_TEST_CASE(testBindNoXMatch) +{ + HeadersExchange exchange("test"); + Queue::shared_ptr queue; + std::string key; + FieldTable args; + try { + //just checking this doesn't cause assertion etc + exchange.bind(queue, key, &args); + } catch(qpid::Exception&) { + //expected + } +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/InitialStatusMap.cpp b/qpid/cpp/src/tests/InitialStatusMap.cpp new file mode 100644 index 0000000000..ecbe2d4161 --- /dev/null +++ b/qpid/cpp/src/tests/InitialStatusMap.cpp @@ -0,0 +1,235 @@ +/* + * + * 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. + * + */ + + +#include "unit_test.h" +#include "test_tools.h" +#include "qpid/cluster/InitialStatusMap.h" +#include "qpid/framing/Uuid.h" +#include <boost/assign.hpp> + +using namespace std; +using namespace qpid::cluster; +using namespace qpid::framing; +using namespace qpid::framing::cluster; +using namespace boost::assign; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(InitialStatusMapTestSuite) + +typedef InitialStatusMap::Status Status; + +Status activeStatus(const Uuid& id=Uuid(), const MemberSet& ms=MemberSet()) { + return Status(ProtocolVersion(), 0, true, id, STORE_STATE_NO_STORE, Uuid(), + encodeMemberSet(ms)); +} + +Status newcomerStatus(const Uuid& id=Uuid(), const MemberSet& ms=MemberSet()) { + return Status(ProtocolVersion(), 0, false, id, STORE_STATE_NO_STORE, Uuid(), + encodeMemberSet(ms)); +} + +Status storeStatus(bool active, StoreState state, Uuid start=Uuid(), Uuid stop=Uuid(), + const MemberSet& ms=MemberSet()) +{ + return Status(ProtocolVersion(), 0, active, start, state, stop, + encodeMemberSet(ms)); +} + +QPID_AUTO_TEST_CASE(testFirstInCluster) { + // Single member is first in cluster. + InitialStatusMap map(MemberId(0), 1); + Uuid id(true); + BOOST_CHECK(!map.isComplete()); + MemberSet members = list_of(MemberId(0)); + map.configChange(members); + BOOST_CHECK(!map.isComplete()); + map.received(MemberId(0), newcomerStatus(id, list_of<MemberId>(0))); + BOOST_CHECK(map.isComplete()); + BOOST_CHECK(map.transitionToComplete()); + BOOST_CHECK(map.getElders().empty()); + BOOST_CHECK(!map.isUpdateNeeded()); + BOOST_CHECK_EQUAL(id, map.getClusterId()); +} + +QPID_AUTO_TEST_CASE(testJoinExistingCluster) { + // Single member 0 joins existing cluster 1,2 + InitialStatusMap map(MemberId(0), 1); + Uuid id(true); + MemberSet members = list_of(MemberId(0))(MemberId(1))(MemberId(2)); + map.configChange(members); + BOOST_CHECK(map.isResendNeeded()); + BOOST_CHECK(!map.isComplete()); + map.received(MemberId(0), newcomerStatus()); + map.received(MemberId(1), activeStatus(id)); + BOOST_CHECK(!map.isComplete()); + map.received(MemberId(2), activeStatus(id)); + BOOST_CHECK(map.isComplete()); + BOOST_CHECK(map.transitionToComplete()); + BOOST_CHECK_EQUAL(map.getElders(), list_of<MemberId>(1)(2)); + BOOST_CHECK(map.isUpdateNeeded()); + BOOST_CHECK_EQUAL(map.getClusterId(), id); + + // Check that transitionToComplete is reset. + map.configChange(list_of<MemberId>(0)(1)); + BOOST_CHECK(!map.transitionToComplete()); +} + +QPID_AUTO_TEST_CASE(testMultipleFirstInCluster) { + // Multiple members 0,1,2 join at same time. + InitialStatusMap map(MemberId(1), 1); // self is 1 + Uuid id(true); + MemberSet members = list_of(MemberId(0))(MemberId(1))(MemberId(2)); + map.configChange(members); + BOOST_CHECK(map.isResendNeeded()); + + // All new members + map.received(MemberId(0), newcomerStatus(id, list_of<MemberId>(0)(1)(2))); + map.received(MemberId(1), newcomerStatus(id, list_of<MemberId>(0)(1)(2))); + map.received(MemberId(2), newcomerStatus(id, list_of<MemberId>(0)(1)(2))); + BOOST_CHECK(!map.isResendNeeded()); + BOOST_CHECK(map.isComplete()); + BOOST_CHECK(map.transitionToComplete()); + BOOST_CHECK_EQUAL(map.getElders(), list_of(MemberId(2))); + BOOST_CHECK(!map.isUpdateNeeded()); + BOOST_CHECK_EQUAL(map.getClusterId(), id); +} + +QPID_AUTO_TEST_CASE(testMultipleJoinExisting) { + // Multiple members 2,3 join simultaneously a cluster containing 0,1. + InitialStatusMap map(MemberId(2), 1); // self is 2 + Uuid id(true); + MemberSet members = list_of(MemberId(0))(MemberId(1))(MemberId(2))(MemberId(3)); + map.configChange(members); + BOOST_CHECK(map.isResendNeeded()); + map.received(MemberId(0), activeStatus(id, list_of<MemberId>(0))); + map.received(MemberId(1), newcomerStatus(id, list_of<MemberId>(0)(1))); + map.received(MemberId(2), newcomerStatus(id, list_of<MemberId>(0)(1)(2)(3))); + map.received(MemberId(3), newcomerStatus(id, list_of<MemberId>(0)(1)(2)(3))); + BOOST_CHECK(!map.isResendNeeded()); + BOOST_CHECK(map.isComplete()); + BOOST_CHECK(map.transitionToComplete()); + BOOST_CHECK_EQUAL(map.getElders(), list_of<MemberId>(0)(1)(3)); + BOOST_CHECK(map.isUpdateNeeded()); + BOOST_CHECK_EQUAL(map.getClusterId(), id); +} + +QPID_AUTO_TEST_CASE(testMembersLeave) { + // Test that map completes if members leave rather than send status. + InitialStatusMap map(MemberId(0), 1); + Uuid id(true); + map.configChange(list_of(MemberId(0))(MemberId(1))(MemberId(2))); + map.received(MemberId(0), newcomerStatus()); + map.received(MemberId(1), activeStatus(id)); + BOOST_CHECK(!map.isComplete()); + map.configChange(list_of(MemberId(0))(MemberId(1))); // 2 left + BOOST_CHECK(map.isComplete()); + BOOST_CHECK(map.transitionToComplete()); + BOOST_CHECK_EQUAL(map.getElders(), list_of(MemberId(1))); + BOOST_CHECK_EQUAL(map.getClusterId(), id); +} + +QPID_AUTO_TEST_CASE(testInteveningConfig) { + // Multiple config changes arrives before we complete the map. + InitialStatusMap map(MemberId(0), 1); + Uuid id(true); + + map.configChange(list_of<MemberId>(0)(1)); + BOOST_CHECK(map.isResendNeeded()); + map.received(MemberId(0), newcomerStatus()); + BOOST_CHECK(!map.isComplete()); + BOOST_CHECK(!map.isResendNeeded()); + // New member 2 joins before we receive 1 + map.configChange(list_of<MemberId>(0)(1)(2)); + BOOST_CHECK(!map.isComplete()); + BOOST_CHECK(map.isResendNeeded()); + map.received(1, activeStatus(id)); + map.received(2, newcomerStatus()); + // We should not be complete as we haven't received 0 since new member joined + BOOST_CHECK(!map.isComplete()); + BOOST_CHECK(!map.isResendNeeded()); + + map.received(0, newcomerStatus()); + BOOST_CHECK(map.isComplete()); + BOOST_CHECK(map.transitionToComplete()); + BOOST_CHECK_EQUAL(map.getElders(), list_of<MemberId>(1)); + BOOST_CHECK_EQUAL(map.getClusterId(), id); +} + +QPID_AUTO_TEST_CASE(testAllCleanNoUpdate) { + InitialStatusMap map(MemberId(0), 3); + map.configChange(list_of<MemberId>(0)(1)(2)); + map.received(MemberId(0), storeStatus(false, STORE_STATE_CLEAN_STORE)); + map.received(MemberId(1), storeStatus(false, STORE_STATE_CLEAN_STORE)); + map.received(MemberId(2), storeStatus(false, STORE_STATE_CLEAN_STORE)); + BOOST_CHECK(!map.isUpdateNeeded()); +} + +QPID_AUTO_TEST_CASE(testAllEmptyNoUpdate) { + InitialStatusMap map(MemberId(0), 3); + map.configChange(list_of<MemberId>(0)(1)(2)); + map.received(MemberId(0), storeStatus(false, STORE_STATE_EMPTY_STORE)); + map.received(MemberId(1), storeStatus(false, STORE_STATE_EMPTY_STORE)); + map.received(MemberId(2), storeStatus(false, STORE_STATE_EMPTY_STORE)); + BOOST_CHECK(map.isComplete()); + BOOST_CHECK(!map.isUpdateNeeded()); +} + +QPID_AUTO_TEST_CASE(testAllNoStoreNoUpdate) { + InitialStatusMap map(MemberId(0), 3); + map.configChange(list_of<MemberId>(0)(1)(2)); + map.received(MemberId(0), storeStatus(false, STORE_STATE_NO_STORE)); + map.received(MemberId(1), storeStatus(false, STORE_STATE_NO_STORE)); + map.received(MemberId(2), storeStatus(false, STORE_STATE_NO_STORE)); + BOOST_CHECK(map.isComplete()); + BOOST_CHECK(!map.isUpdateNeeded()); +} + +QPID_AUTO_TEST_CASE(testDirtyNeedUpdate) { + InitialStatusMap map(MemberId(0), 3); + map.configChange(list_of<MemberId>(0)(1)(2)); + map.received(MemberId(0), storeStatus(false, STORE_STATE_DIRTY_STORE)); + map.received(MemberId(1), storeStatus(false, STORE_STATE_CLEAN_STORE)); + map.received(MemberId(2), storeStatus(false, STORE_STATE_CLEAN_STORE)); + BOOST_CHECK(map.transitionToComplete()); + BOOST_CHECK(map.isUpdateNeeded()); +} + +QPID_AUTO_TEST_CASE(testEmptyNeedUpdate) { + InitialStatusMap map(MemberId(0), 3); + map.configChange(list_of<MemberId>(0)(1)(2)); + map.received(MemberId(0), storeStatus(false, STORE_STATE_EMPTY_STORE)); + map.received(MemberId(1), storeStatus(false, STORE_STATE_CLEAN_STORE)); + map.received(MemberId(2), storeStatus(false, STORE_STATE_CLEAN_STORE)); + BOOST_CHECK(map.transitionToComplete()); + BOOST_CHECK(map.isUpdateNeeded()); +} + +QPID_AUTO_TEST_CASE(testEmptyAlone) { + InitialStatusMap map(MemberId(0), 1); + map.configChange(list_of<MemberId>(0)); + map.received(MemberId(0), storeStatus(false, STORE_STATE_EMPTY_STORE)); + BOOST_CHECK(map.transitionToComplete()); + BOOST_CHECK(!map.isUpdateNeeded()); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/InlineAllocator.cpp b/qpid/cpp/src/tests/InlineAllocator.cpp new file mode 100644 index 0000000000..a4c4d64cea --- /dev/null +++ b/qpid/cpp/src/tests/InlineAllocator.cpp @@ -0,0 +1,68 @@ +/* + * + * 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/InlineAllocator.h" +#include "unit_test.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(InlineAllocatorTestSuite) + +using namespace qpid; +using namespace std; + +QPID_AUTO_TEST_CASE(testAllocate) { + InlineAllocator<std::allocator<char>, 2> alloc; + + char* p = alloc.allocate(1); + BOOST_CHECK(p == (char*)&alloc); + alloc.deallocate(p,1); + + p = alloc.allocate(2); + BOOST_CHECK(p == (char*)&alloc); + alloc.deallocate(p,2); + + p = alloc.allocate(3); + BOOST_CHECK(p != (char*)&alloc); + alloc.deallocate(p,3); +} + +QPID_AUTO_TEST_CASE(testAllocateFull) { + InlineAllocator<std::allocator<char>, 1> alloc; + + char* p = alloc.allocate(1); + BOOST_CHECK(p == (char*)&alloc); + + char* q = alloc.allocate(1); + BOOST_CHECK(q != (char*)&alloc); + + alloc.deallocate(p,1); + p = alloc.allocate(1); + BOOST_CHECK(p == (char*)&alloc); + + alloc.deallocate(p,1); + alloc.deallocate(q,1); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/InlineVector.cpp b/qpid/cpp/src/tests/InlineVector.cpp new file mode 100644 index 0000000000..ba5165886d --- /dev/null +++ b/qpid/cpp/src/tests/InlineVector.cpp @@ -0,0 +1,128 @@ +/* + * + * 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/InlineVector.h" +#include "unit_test.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(InlineVectorTestSuite) + +using namespace qpid; +using namespace std; + +typedef InlineVector<int, 3> Vec; + +bool isInline(const Vec& v) { + // If nothing, give it the benefit of the doubt; + // can't take address of nothing. + if (v.size() <= 0) + return true; + return (const char*)&v <= (const char*)(&v[0]) && + (const char*)(&v[0]) < (const char*)&v+sizeof(v); +} + +QPID_AUTO_TEST_CASE(testCtor) { + { + Vec v; + BOOST_CHECK(isInline(v)); + BOOST_CHECK(v.empty()); + } + { + Vec v(3, 42); + BOOST_CHECK(isInline(v)); + BOOST_CHECK_EQUAL(3u, v.size()); + BOOST_CHECK_EQUAL(v[0], 42); + BOOST_CHECK_EQUAL(v[2], 42); + + Vec u(v); + BOOST_CHECK(isInline(u)); + BOOST_CHECK_EQUAL(3u, u.size()); + BOOST_CHECK_EQUAL(u[0], 42); + BOOST_CHECK_EQUAL(u[2], 42); + } + + { + Vec v(4, 42); + + BOOST_CHECK_EQUAL(v.size(), 4u); + BOOST_CHECK(!isInline(v)); + Vec u(v); + BOOST_CHECK_EQUAL(u.size(), 4u); + BOOST_CHECK(!isInline(u)); + } +} + +QPID_AUTO_TEST_CASE(testInsert) { + { + Vec v; + v.push_back(1); + BOOST_CHECK_EQUAL(v.size(), 1u); + BOOST_CHECK_EQUAL(v.back(), 1); + BOOST_CHECK(isInline(v)); + + v.insert(v.begin(), 2); + BOOST_CHECK_EQUAL(v.size(), 2u); + BOOST_CHECK_EQUAL(v.back(), 1); + BOOST_CHECK(isInline(v)); + + v.push_back(3); + BOOST_CHECK(isInline(v)); + + v.push_back(4); + + BOOST_CHECK(!isInline(v)); + } + { + Vec v(3,42); + v.insert(v.begin(), 9); + BOOST_CHECK_EQUAL(v.size(), 4u); + BOOST_CHECK(!isInline(v)); + } + { + Vec v(3,42); + v.insert(v.begin()+1, 9); + BOOST_CHECK(!isInline(v)); + BOOST_CHECK_EQUAL(v.size(), 4u); + } +} + +QPID_AUTO_TEST_CASE(testAssign) { + Vec v(3,42); + Vec u; + u = v; + BOOST_CHECK(isInline(u)); + u.push_back(4); + BOOST_CHECK(!isInline(u)); + v = u; + BOOST_CHECK(!isInline(v)); +} + +QPID_AUTO_TEST_CASE(testResize) { + Vec v; + v.resize(5); + BOOST_CHECK(!isInline(v)); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/Makefile.am b/qpid/cpp/src/tests/Makefile.am new file mode 100644 index 0000000000..ed97c41bff --- /dev/null +++ b/qpid/cpp/src/tests/Makefile.am @@ -0,0 +1,398 @@ +# +# 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. +# + +AM_CXXFLAGS = $(WARNING_CFLAGS) -DBOOST_TEST_DYN_LINK +INCLUDES = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_srcdir)/src -I$(top_builddir)/src +PUBLIC_INCLUDES = -I$(top_srcdir)/include -I$(top_builddir)/include # Use public API only +QMF_GEN=$(top_srcdir)/managementgen/qmf-gen + +abs_builddir=@abs_builddir@ +abs_srcdir=@abs_srcdir@ + +extra_libs = +lib_client = $(abs_builddir)/../libqpidclient.la +lib_messaging = $(abs_builddir)/../libqpidmessaging.la +lib_common = $(abs_builddir)/../libqpidcommon.la +lib_broker = $(abs_builddir)/../libqpidbroker.la +lib_console = $(abs_builddir)/../libqmfconsole.la +lib_qmf2 = $(abs_builddir)/../libqmf2.la +# lib_amqp_0_10 = $(abs_builddir)/../libqpidamqp_0_10.la + +# +# Initialize variables that are incremented with += +# +check_PROGRAMS= +check_LTLIBRARIES= +TESTS= +EXTRA_DIST= +CLEANFILES= +LONG_TESTS= +CLEAN_LOCAL= + +# +# Destination for intalled programs and tests defined here +# +qpidexecdir = $(libexecdir)/qpid +qpidexec_PROGRAMS = +qpidexec_SCRIPTS = +qpidtestdir = $(qpidexecdir)/tests +qpidtest_PROGRAMS = +qpidtest_SCRIPTS = +tmoduledir = $(libdir)/qpid/tests +tmodule_LTLIBRARIES= + +# +# Unit test program +# +# Unit tests are built as a single program to reduce valgrind overhead +# when running the tests. If you want to build a subset of the tests do +# rm -f unit_test; make unit_test unit_test_OBJECTS="unit_test.o SelectedTest.o" +# + +TESTS+=unit_test +check_PROGRAMS+=unit_test +unit_test_LDADD=-lboost_unit_test_framework \ + $(lib_messaging) $(lib_broker) $(lib_console) $(lib_qmf2) + +unit_test_SOURCES= unit_test.cpp unit_test.h \ + MessagingSessionTests.cpp \ + MessagingThreadTests.cpp \ + MessagingFixture.h \ + ClientSessionTest.cpp \ + BrokerFixture.h SocketProxy.h \ + exception_test.cpp \ + RefCounted.cpp \ + SessionState.cpp logging.cpp \ + AsyncCompletion.cpp \ + Url.cpp Uuid.cpp \ + Shlib.cpp FieldValue.cpp FieldTable.cpp Array.cpp \ + QueueOptionsTest.cpp \ + InlineAllocator.cpp \ + InlineVector.cpp \ + SequenceSet.cpp \ + StringUtils.cpp \ + RangeSet.cpp \ + AtomicValue.cpp \ + QueueTest.cpp \ + AccumulatedAckTest.cpp \ + DtxWorkRecordTest.cpp \ + DeliveryRecordTest.cpp \ + ExchangeTest.cpp \ + HeadersExchangeTest.cpp \ + MessageTest.cpp \ + QueueRegistryTest.cpp \ + QueuePolicyTest.cpp \ + QueueFlowLimitTest.cpp \ + FramingTest.cpp \ + HeaderTest.cpp \ + SequenceNumberTest.cpp \ + TimerTest.cpp \ + TopicExchangeTest.cpp \ + TxBufferTest.cpp \ + TxPublishTest.cpp \ + MessageBuilderTest.cpp \ + ConnectionOptions.h \ + ForkedBroker.h \ + ForkedBroker.cpp \ + ManagementTest.cpp \ + MessageReplayTracker.cpp \ + ConsoleTest.cpp \ + QueueEvents.cpp \ + ProxyTest.cpp \ + RetryList.cpp \ + RateFlowcontrolTest.cpp \ + FrameDecoder.cpp \ + ReplicationTest.cpp \ + ClientMessageTest.cpp \ + PollableCondition.cpp \ + Variant.cpp \ + Address.cpp \ + ClientMessage.cpp \ + Qmf2.cpp + +if HAVE_XML +unit_test_SOURCES+= XmlClientSessionTest.cpp +endif + +TESTLIBFLAGS = -module -rpath $(abs_builddir) + +check_LTLIBRARIES += libshlibtest.la +libshlibtest_la_LDFLAGS = $(TESTLIBFLAGS) +libshlibtest_la_SOURCES = shlibtest.cpp + +tmodule_LTLIBRARIES += test_store.la +test_store_la_SOURCES = test_store.cpp +test_store_la_LIBADD = $(lib_broker) +test_store_la_LDFLAGS = -module + +include cluster.mk +include sasl.mk +if SSL +include ssl.mk +endif + +# Test programs that are installed and therefore built as part of make, not make check + +qpidtest_SCRIPTS += qpid-cpp-benchmark install_env.sh +EXTRA_DIST += qpid-cpp-benchmark install_env.sh + +qpidtest_PROGRAMS += receiver +receiver_SOURCES = \ + receiver.cpp \ + TestOptions.h \ + ConnectionOptions.h +receiver_LDADD = $(lib_client) + +qpidtest_PROGRAMS += sender +sender_SOURCES = \ + sender.cpp \ + TestOptions.h \ + ConnectionOptions.h \ + Statistics.cpp +sender_LDADD = $(lib_messaging) + +qpidtest_PROGRAMS += qpid-receive +qpid_receive_SOURCES = \ + qpid-receive.cpp \ + TestOptions.h \ + ConnectionOptions.h \ + Statistics.h \ + Statistics.cpp +qpid_receive_LDADD = $(lib_messaging) + +qpidtest_PROGRAMS += qpid-send +qpid_send_SOURCES = \ + qpid-send.cpp \ + TestOptions.h \ + ConnectionOptions.h \ + Statistics.h \ + Statistics.cpp +qpid_send_LDADD = $(lib_messaging) + +qpidtest_PROGRAMS+=qpid-perftest +qpid_perftest_SOURCES=qpid-perftest.cpp test_tools.h TestOptions.h ConnectionOptions.h +qpid_perftest_INCLUDES=$(PUBLIC_INCLUDES) +qpid_perftest_LDADD=$(lib_client) + +qpidtest_PROGRAMS+=qpid-txtest +qpid_txtest_INCLUDES=$(PUBLIC_INCLUDES) +qpid_txtest_SOURCES=qpid-txtest.cpp TestOptions.h ConnectionOptions.h +qpid_txtest_LDADD=$(lib_client) + +qpidtest_PROGRAMS+=qpid-latency-test +qpid_latency_test_INCLUDES=$(PUBLIC_INCLUDES) +qpid_latency_test_SOURCES=qpid-latency-test.cpp TestOptions.h ConnectionOptions.h +qpid_latency_test_LDADD=$(lib_client) + +qpidtest_PROGRAMS+=qpid-client-test +qpid_client_test_INCLUDES=$(PUBLIC_INCLUDES) +qpid_client_test_SOURCES=qpid-client-test.cpp TestOptions.h ConnectionOptions.h +qpid_client_test_LDADD=$(lib_client) + +qpidtest_PROGRAMS+=qpid-topic-listener +qpid_topic_listener_INCLUDES=$(PUBLIC_INCLUDES) +qpid_topic_listener_SOURCES=qpid-topic-listener.cpp TestOptions.h ConnectionOptions.h +qpid_topic_listener_LDADD=$(lib_client) + +qpidtest_PROGRAMS+=qpid-topic-publisher +qpid_topic_publisher_INCLUDES=$(PUBLIC_INCLUDES) +qpid_topic_publisher_SOURCES=qpid-topic-publisher.cpp TestOptions.h ConnectionOptions.h +qpid_topic_publisher_LDADD=$(lib_client) + +qpidtest_PROGRAMS+=qpid-ping +qpid_ping_INCLUDES=$(PUBLIC_INCLUDES) +qpid_ping_SOURCES=qpid-ping.cpp test_tools.h TestOptions.h ConnectionOptions.h +qpid_ping_LDADD=$(lib_client) + +# +# Other test programs +# + +check_PROGRAMS+=echotest +echotest_INCLUDES=$(PUBLIC_INCLUDES) +echotest_SOURCES=echotest.cpp TestOptions.h ConnectionOptions.h +echotest_LDADD=$(lib_client) + +check_PROGRAMS+=publish +publish_INCLUDES=$(PUBLIC_INCLUDES) +publish_SOURCES=publish.cpp TestOptions.h ConnectionOptions.h +publish_LDADD=$(lib_client) + +check_PROGRAMS+=consume +consume_INCLUDES=$(PUBLIC_INCLUDES) +consume_SOURCES=consume.cpp TestOptions.h ConnectionOptions.h +consume_LDADD=$(lib_client) + +check_PROGRAMS+=header_test +header_test_INCLUDES=$(PUBLIC_INCLUDES) +header_test_SOURCES=header_test.cpp TestOptions.h ConnectionOptions.h +header_test_LDADD=$(lib_client) + +check_PROGRAMS+=failover_soak +failover_soak_INCLUDES=$(PUBLIC_INCLUDES) +failover_soak_SOURCES=failover_soak.cpp ForkedBroker.h ForkedBroker.cpp +failover_soak_LDADD=$(lib_client) $(lib_broker) + +check_PROGRAMS+=declare_queues +declare_queues_INCLUDES=$(PUBLIC_INCLUDES) +declare_queues_SOURCES=declare_queues.cpp +declare_queues_LDADD=$(lib_client) + +check_PROGRAMS+=replaying_sender +replaying_sender_INCLUDES=$(PUBLIC_INCLUDES) +replaying_sender_SOURCES=replaying_sender.cpp +replaying_sender_LDADD=$(lib_client) + +check_PROGRAMS+=resuming_receiver +resuming_receiver_INCLUDES=$(PUBLIC_INCLUDES) +resuming_receiver_SOURCES=resuming_receiver.cpp +resuming_receiver_LDADD=$(lib_client) + +check_PROGRAMS+=txshift +txshift_INCLUDES=$(PUBLIC_INCLUDES) +txshift_SOURCES=txshift.cpp TestOptions.h ConnectionOptions.h +txshift_LDADD=$(lib_client) + +check_PROGRAMS+=txjob +txjob_INCLUDES=$(PUBLIC_INCLUDES) +txjob_SOURCES=txjob.cpp TestOptions.h ConnectionOptions.h +txjob_LDADD=$(lib_client) + +check_PROGRAMS+=PollerTest +PollerTest_SOURCES=PollerTest.cpp +PollerTest_LDADD=$(lib_common) $(lib_client) $(SOCKLIBS) + +check_PROGRAMS+=DispatcherTest +DispatcherTest_SOURCES=DispatcherTest.cpp +DispatcherTest_LDADD=$(lib_common) $(lib_client) $(SOCKLIBS) + +check_PROGRAMS+=datagen +datagen_SOURCES=datagen.cpp +datagen_LDADD=$(lib_common) $(lib_client) + +check_PROGRAMS+=qpid-stream +qpid_stream_INCLUDES=$(PUBLIC_INCLUDES) +qpid_stream_SOURCES=qpid-stream.cpp +qpid_stream_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 \ + run_queue_flow_limit_tests + +EXTRA_DIST += \ + run_test vg_check \ + run-unit-tests start_broker python_tests stop_broker \ + quick_topictest \ + quick_perftest \ + quick_txtest \ + topictest \ + run_header_test \ + header_test.py \ + ssl_test \ + config.null \ + ais_check \ + run_federation_tests \ + run_cli_tests \ + run_acl_tests \ + .valgrind.supp \ + MessageUtils.h \ + TestMessageStore.h \ + TxMocks.h \ + replication_test \ + run_perftest \ + ring_queue_test \ + run_ring_queue_test \ + dynamic_log_level_test \ + qpid-ctrl \ + CMakeLists.txt \ + cluster.cmake \ + windows/DisableWin32ErrorWindows.cpp \ + background.ps1 \ + find_prog.ps1 \ + python_tests.ps1 \ + quick_topictest.ps1 \ + run_federation_tests.ps1 \ + run_header_test.ps1 \ + run_test.ps1 \ + start_broker.ps1 \ + stop_broker.ps1 \ + topictest.ps1 \ + run_queue_flow_limit_tests + +check_LTLIBRARIES += libdlclose_noop.la +libdlclose_noop_la_LDFLAGS = -module -rpath $(abs_builddir) +libdlclose_noop_la_SOURCES = dlclose_noop.c + +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 \ + run_failover_soak reliable_replication_test \ + federated_cluster_test_with_node_failure + +EXTRA_DIST+= \ + fanout_perftest \ + shared_perftest \ + multiq_perftest \ + topic_perftest \ + run_failover_soak \ + reliable_replication_test \ + federated_cluster_test_with_node_failure \ + sasl_test_setup.sh + +check-long: + $(MAKE) check TESTS="$(LONG_TESTS)" VALGRIND= + +# Things that should be built before the check target runs. +check-am: python_prep test_env.sh install_env.sh sasl_config + +PYTHON_SRC_DIR=$(abs_srcdir)/../../../python +PYTHON_BLD_DIR=$(abs_builddir)/python + +# Generate python client as part of the all-am target so it gets built before tests. +all-am: python_prep + +python_prep: + if test -d $(PYTHON_SRC_DIR); \ + then cd $(PYTHON_SRC_DIR) && python $(PYTHON_SRC_DIR)/setup.py install \ + --prefix=$(PYTHON_BLD_DIR) --install-lib=$(PYTHON_BLD_DIR) \ + --install-scripts=$(PYTHON_BLD_DIR)/commands; \ + else echo "WARNING: python client not built, missing $(PYTHON_SRC_DIR)"; fi + +sasl_config: sasl_test_setup.sh + sh $(srcdir)/sasl_test_setup.sh + touch sasl_config + +CLEAN_LOCAL += sasl_config + +clean-local: + rm -rf $(CLEAN_LOCAL) + +include testagent.mk +include brokermgmt.mk + diff --git a/qpid/cpp/src/tests/ManagementTest.cpp b/qpid/cpp/src/tests/ManagementTest.cpp new file mode 100644 index 0000000000..8944c084c0 --- /dev/null +++ b/qpid/cpp/src/tests/ManagementTest.cpp @@ -0,0 +1,124 @@ +/* + * + * 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/management/ManagementObject.h" +#include "qpid/framing/Buffer.h" +#include "qpid/console/ObjectId.h" +#include "unit_test.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(ManagementTestSuite) + +using namespace qpid::framing; +using namespace qpid::management; + +QPID_AUTO_TEST_CASE(testObjectIdSerializeStream) { + std::string text("0-10-4-2500-80000000000()"); + std::stringstream input(text); + + ObjectId oid(input); + + std::stringstream output; + output << oid; + + BOOST_CHECK_EQUAL(text, output.str()); +} + +QPID_AUTO_TEST_CASE(testObjectIdSerializeString) { + std::string text("0-10-4-2500-80000000000()"); + + ObjectId oid(text); + + std::stringstream output; + output << oid; + + BOOST_CHECK_EQUAL(text, output.str()); +} + +QPID_AUTO_TEST_CASE(testObjectIdEncode) { + qpid::types::Variant::Map oidMap; + + ObjectId oid(1, 2, 3, 9999); + oid.setV2Key("testkey"); + oid.setAgentName("myAgent"); + + std::stringstream out1; + out1 << oid; + + BOOST_CHECK_EQUAL(out1.str(), "1-2-3-myAgent-9999(testkey)"); +} + +QPID_AUTO_TEST_CASE(testObjectIdAttach) { + AgentAttachment agent; + ObjectId oid(&agent, 10, 20); + oid.setV2Key("GabbaGabbaHey"); + oid.setAgentName("MrSmith"); + + std::stringstream out1; + out1 << oid; + + BOOST_CHECK_EQUAL(out1.str(), "10-20-0-MrSmith-0(GabbaGabbaHey)"); + + agent.setBanks(30, 40); + std::stringstream out2; + out2 << oid; + + BOOST_CHECK_EQUAL(out2.str(), "10-20-30-MrSmith-0(GabbaGabbaHey)"); +} + +QPID_AUTO_TEST_CASE(testObjectIdCreate) { + ObjectId oid("some-agent-name", "an-object-name"); + + BOOST_CHECK_EQUAL(oid.getAgentName(), "some-agent-name"); + BOOST_CHECK_EQUAL(oid.getV2Key(), "an-object-name"); +} + +QPID_AUTO_TEST_CASE(testConsoleObjectId) { + qpid::console::ObjectId oid1, oid2; + + oid1.setValue(1, 2); + oid2.setValue(3, 4); + + BOOST_CHECK(oid1 < oid2); + BOOST_CHECK(oid1 <= oid2); + BOOST_CHECK(oid2 > oid1); + BOOST_CHECK(oid2 >= oid1); + BOOST_CHECK(oid1 != oid2); + BOOST_CHECK(oid1 == oid1); + + oid1.setValue(3, 6); + oid2.setValue(3, 4); + + BOOST_CHECK(oid1 > oid2); + BOOST_CHECK(oid1 >= oid2); + BOOST_CHECK(oid2 < oid1); + BOOST_CHECK(oid2 <= oid1); + BOOST_CHECK(oid1 != oid2); + + oid2.setValue(3, 6); + BOOST_CHECK(oid1 == oid2); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/MessageBuilderTest.cpp b/qpid/cpp/src/tests/MessageBuilderTest.cpp new file mode 100644 index 0000000000..c3d40ed88a --- /dev/null +++ b/qpid/cpp/src/tests/MessageBuilderTest.cpp @@ -0,0 +1,190 @@ +/* + * + * 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/Message.h" +#include "qpid/broker/MessageBuilder.h" +#include "qpid/broker/NullMessageStore.h" +#include "qpid/framing/frame_functors.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/TypeFilter.h" +#include "unit_test.h" +#include <list> + +using namespace qpid::broker; +using namespace qpid::framing; +using namespace qpid::sys; + +namespace qpid { +namespace tests { + +class MockMessageStore : public NullMessageStore +{ + enum Op {STAGE=1, APPEND=2}; + + uint64_t id; + boost::intrusive_ptr<PersistableMessage> expectedMsg; + string expectedData; + std::list<Op> ops; + + void checkExpectation(Op actual) + { + BOOST_CHECK_EQUAL(ops.front(), actual); + ops.pop_front(); + } + + public: + MockMessageStore() : id(0), expectedMsg(0) {} + + void expectStage(PersistableMessage& msg) + { + expectedMsg = &msg; + ops.push_back(STAGE); + } + + void expectAppendContent(PersistableMessage& msg, const string& data) + { + expectedMsg = &msg; + expectedData = data; + ops.push_back(APPEND); + } + + void stage(const boost::intrusive_ptr<PersistableMessage>& msg) + { + checkExpectation(STAGE); + BOOST_CHECK_EQUAL(expectedMsg, msg); + msg->setPersistenceId(++id); + } + + void appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg, + const string& data) + { + checkExpectation(APPEND); + BOOST_CHECK_EQUAL(boost::static_pointer_cast<const PersistableMessage>(expectedMsg), msg); + BOOST_CHECK_EQUAL(expectedData, data); + } + + bool expectationsMet() + { + return ops.empty(); + } + + //don't treat this store as a null impl + bool isNull() const + { + return false; + } + +}; + +QPID_AUTO_TEST_SUITE(MessageBuilderTestSuite) + +QPID_AUTO_TEST_CASE(testHeaderOnly) +{ + MessageBuilder builder(0); + builder.start(SequenceNumber()); + + std::string exchange("builder-exchange"); + std::string key("builder-exchange"); + + AMQFrame method((MessageTransferBody(ProtocolVersion(), exchange, 0, 0))); + AMQFrame header((AMQHeaderBody())); + + header.castBody<AMQHeaderBody>()->get<MessageProperties>(true)->setContentLength(0); + header.castBody<AMQHeaderBody>()->get<DeliveryProperties>(true)->setRoutingKey(key); + + builder.handle(method); + builder.handle(header); + + BOOST_CHECK(builder.getMessage()); + BOOST_CHECK_EQUAL(exchange, builder.getMessage()->getExchangeName()); + BOOST_CHECK_EQUAL(key, builder.getMessage()->getRoutingKey()); + BOOST_CHECK(builder.getMessage()->getFrames().isComplete()); +} + +QPID_AUTO_TEST_CASE(test1ContentFrame) +{ + MessageBuilder builder(0); + builder.start(SequenceNumber()); + + std::string data("abcdefg"); + std::string exchange("builder-exchange"); + std::string key("builder-exchange"); + + AMQFrame method((MessageTransferBody(ProtocolVersion(), exchange, 0, 0))); + AMQFrame header((AMQHeaderBody())); + AMQFrame content((AMQContentBody(data))); + method.setEof(false); + header.setBof(false); + header.setEof(false); + content.setBof(false); + + header.castBody<AMQHeaderBody>()->get<MessageProperties>(true)->setContentLength(data.size()); + header.castBody<AMQHeaderBody>()->get<DeliveryProperties>(true)->setRoutingKey(key); + + builder.handle(method); + BOOST_CHECK(builder.getMessage()); + BOOST_CHECK(!builder.getMessage()->getFrames().isComplete()); + + builder.handle(header); + BOOST_CHECK(builder.getMessage()); + BOOST_CHECK(!builder.getMessage()->getFrames().isComplete()); + + builder.handle(content); + BOOST_CHECK(builder.getMessage()); + BOOST_CHECK(builder.getMessage()->getFrames().isComplete()); +} + +QPID_AUTO_TEST_CASE(test2ContentFrames) +{ + MessageBuilder builder(0); + builder.start(SequenceNumber()); + + std::string data1("abcdefg"); + std::string data2("hijklmn"); + std::string exchange("builder-exchange"); + std::string key("builder-exchange"); + + AMQFrame method((MessageTransferBody(ProtocolVersion(), exchange, 0, 0))); + AMQFrame header((AMQHeaderBody())); + AMQFrame content1((AMQContentBody(data1))); + AMQFrame content2((AMQContentBody(data2))); + method.setEof(false); + header.setBof(false); + header.setEof(false); + content1.setBof(false); + content1.setEof(false); + content2.setBof(false); + + header.castBody<AMQHeaderBody>()->get<MessageProperties>(true)->setContentLength(data1.size() + data2.size()); + header.castBody<AMQHeaderBody>()->get<DeliveryProperties>(true)->setRoutingKey(key); + + builder.handle(method); + builder.handle(header); + builder.handle(content1); + BOOST_CHECK(builder.getMessage()); + BOOST_CHECK(!builder.getMessage()->getFrames().isComplete()); + + builder.handle(content2); + BOOST_CHECK(builder.getMessage()); + BOOST_CHECK(builder.getMessage()->getFrames().isComplete()); +} +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/MessageReplayTracker.cpp b/qpid/cpp/src/tests/MessageReplayTracker.cpp new file mode 100644 index 0000000000..3d79ee53c2 --- /dev/null +++ b/qpid/cpp/src/tests/MessageReplayTracker.cpp @@ -0,0 +1,102 @@ +/* + * + * 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 "unit_test.h" +#include "BrokerFixture.h" +#include "qpid/client/MessageReplayTracker.h" +#include "qpid/sys/Time.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(MessageReplayTrackerTests) + +using namespace qpid::client; +using namespace qpid::sys; +using std::string; + +class ReplayBufferChecker +{ + public: + + ReplayBufferChecker(uint from, uint to) : end(to), i(from) {} + + void operator()(const Message& m) + { + if (i > end) BOOST_FAIL("Extra message found: " + m.getData()); + BOOST_CHECK_EQUAL((boost::format("Message_%1%") % (i++)).str(), m.getData()); + } + private: + const uint end; + uint i; + +}; + +QPID_AUTO_TEST_CASE(testReplay) +{ + ProxySessionFixture fix; + fix.session.queueDeclare(arg::queue="my-queue", arg::exclusive=true, arg::autoDelete=true); + + MessageReplayTracker tracker(10); + tracker.init(fix.session); + for (uint i = 0; i < 5; i++) { + Message message((boost::format("Message_%1%") % (i+1)).str(), "my-queue"); + tracker.send(message); + } + ReplayBufferChecker checker(1, 10); + tracker.foreach(checker); + + tracker.replay(fix.session); + for (uint j = 0; j < 2; j++) {//each message should have been sent twice + for (uint i = 0; i < 5; i++) { + Message m; + BOOST_CHECK(fix.subs.get(m, "my-queue", TIME_SEC)); + BOOST_CHECK_EQUAL((boost::format("Message_%1%") % (i+1)).str(), m.getData()); + } + } + Message m; + BOOST_CHECK(!fix.subs.get(m, "my-queue")); +} + +QPID_AUTO_TEST_CASE(testCheckCompletion) +{ + ProxySessionFixture fix; + fix.session.queueDeclare(arg::queue="my-queue", arg::exclusive=true, arg::autoDelete=true); + + MessageReplayTracker tracker(10); + tracker.init(fix.session); + for (uint i = 0; i < 5; i++) { + Message message((boost::format("Message_%1%") % (i+1)).str(), "my-queue"); + tracker.send(message); + } + fix.session.sync();//ensures all messages are complete + tracker.checkCompletion(); + tracker.replay(fix.session); + Message received; + for (uint i = 0; i < 5; i++) { + BOOST_CHECK(fix.subs.get(received, "my-queue")); + BOOST_CHECK_EQUAL((boost::format("Message_%1%") % (i+1)).str(), received.getData()); + } + BOOST_CHECK(!fix.subs.get(received, "my-queue")); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/MessageTest.cpp b/qpid/cpp/src/tests/MessageTest.cpp new file mode 100644 index 0000000000..7d67c92b37 --- /dev/null +++ b/qpid/cpp/src/tests/MessageTest.cpp @@ -0,0 +1,92 @@ +/* + * + * 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/Message.h" +#include "qpid/framing/AMQP_HighestVersion.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/FieldValue.h" +#include "qpid/framing/Uuid.h" +#include "qpid/sys/alloca.h" + +#include "unit_test.h" + +#include <iostream> + +using namespace qpid::broker; +using namespace qpid::framing; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(MessageTestSuite) + +QPID_AUTO_TEST_CASE(testEncodeDecode) +{ + string exchange = "MyExchange"; + string routingKey = "MyRoutingKey"; + Uuid messageId(true); + string data1("abcdefg"); + string data2("hijklmn"); + + boost::intrusive_ptr<Message> msg(new Message()); + + AMQFrame method((MessageTransferBody(ProtocolVersion(), exchange, 0, 0))); + AMQFrame header((AMQHeaderBody())); + AMQFrame content1((AMQContentBody(data1))); + AMQFrame content2((AMQContentBody(data2))); + + msg->getFrames().append(method); + msg->getFrames().append(header); + msg->getFrames().append(content1); + msg->getFrames().append(content2); + + MessageProperties* mProps = msg->getFrames().getHeaders()->get<MessageProperties>(true); + mProps->setContentLength(data1.size() + data2.size()); + mProps->setMessageId(messageId); + FieldTable applicationHeaders; + applicationHeaders.setString("abc", "xyz"); + mProps->setApplicationHeaders(applicationHeaders); + DeliveryProperties* dProps = msg->getFrames().getHeaders()->get<DeliveryProperties>(true); + dProps->setRoutingKey(routingKey); + dProps->setDeliveryMode(PERSISTENT); + BOOST_CHECK(msg->isPersistent()); + + char* buff = static_cast<char*>(::alloca(msg->encodedSize())); + Buffer wbuffer(buff, msg->encodedSize()); + msg->encode(wbuffer); + + Buffer rbuffer(buff, msg->encodedSize()); + msg = new Message(); + msg->decodeHeader(rbuffer); + msg->decodeContent(rbuffer); + BOOST_CHECK_EQUAL(exchange, msg->getExchangeName()); + BOOST_CHECK_EQUAL(routingKey, msg->getRoutingKey()); + BOOST_CHECK_EQUAL((uint64_t) data1.size() + data2.size(), msg->contentSize()); + BOOST_CHECK_EQUAL((uint64_t) data1.size() + data2.size(), msg->getProperties<MessageProperties>()->getContentLength()); + BOOST_CHECK_EQUAL(messageId, msg->getProperties<MessageProperties>()->getMessageId()); + BOOST_CHECK_EQUAL(string("xyz"), msg->getProperties<MessageProperties>()->getApplicationHeaders().getAsString("abc")); + BOOST_CHECK_EQUAL((uint8_t) PERSISTENT, msg->getProperties<DeliveryProperties>()->getDeliveryMode()); + BOOST_CHECK(msg->isPersistent()); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/MessageUtils.h b/qpid/cpp/src/tests/MessageUtils.h new file mode 100644 index 0000000000..a1b140d484 --- /dev/null +++ b/qpid/cpp/src/tests/MessageUtils.h @@ -0,0 +1,63 @@ +/* + * + * 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/Message.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/Uuid.h" + +using namespace qpid; +using namespace broker; +using namespace framing; + +namespace qpid { +namespace tests { + +struct MessageUtils +{ + static boost::intrusive_ptr<Message> createMessage(const string& exchange="", const string& routingKey="", + const bool durable = false, const Uuid& messageId=Uuid(true), + uint64_t contentSize = 0) + { + boost::intrusive_ptr<broker::Message> msg(new broker::Message()); + + AMQFrame method(( MessageTransferBody(ProtocolVersion(), exchange, 0, 0))); + AMQFrame header((AMQHeaderBody())); + + msg->getFrames().append(method); + msg->getFrames().append(header); + MessageProperties* props = msg->getFrames().getHeaders()->get<MessageProperties>(true); + props->setContentLength(contentSize); + props->setMessageId(messageId); + msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setRoutingKey(routingKey); + if (durable) + msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setDeliveryMode(2); + return msg; + } + + static void addContent(boost::intrusive_ptr<Message> msg, const string& data) + { + AMQFrame content((AMQContentBody(data))); + msg->getFrames().append(content); + } +}; + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/MessagingFixture.h b/qpid/cpp/src/tests/MessagingFixture.h new file mode 100644 index 0000000000..2312a87e9d --- /dev/null +++ b/qpid/cpp/src/tests/MessagingFixture.h @@ -0,0 +1,345 @@ +#ifndef TESTS_MESSAGINGFIXTURE_H +#define TESTS_MESSAGINGFIXTURE_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 "BrokerFixture.h" +#include "unit_test.h" +#include "test_tools.h" +#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; + qpid::client::Session session; + + BrokerAdmin(uint16_t port) + { + connection.open("localhost", port); + session = connection.newSession(); + } + + void createQueue(const std::string& name) + { + session.queueDeclare(qpid::client::arg::queue=name); + } + + void deleteQueue(const std::string& name) + { + session.queueDelete(qpid::client::arg::queue=name); + } + + void createExchange(const std::string& name, const std::string& type) + { + session.exchangeDeclare(qpid::client::arg::exchange=name, qpid::client::arg::type=type); + } + + void deleteExchange(const std::string& name) + { + session.exchangeDelete(qpid::client::arg::exchange=name); + } + + bool checkQueueExists(const std::string& name) + { + return session.queueQuery(name).getQueue() == name; + } + + bool checkExchangeExists(const std::string& name, std::string& type) + { + qpid::framing::ExchangeQueryResult result = session.exchangeQuery(name); + type = result.getType(); + return !result.getNotFound(); + } + + void send(qpid::client::Message& message, const std::string& exchange=std::string()) + { + session.messageTransfer(qpid::client::arg::destination=exchange, qpid::client::arg::content=message); + } + + ~BrokerAdmin() + { + session.close(); + connection.close(); + } +}; + +struct MessagingFixture : public BrokerFixture +{ + messaging::Connection connection; + messaging::Session session; + BrokerAdmin admin; + + MessagingFixture(Broker::Options opts = Broker::Options(), bool mgmtEnabled=false) : + BrokerFixture(opts, mgmtEnabled), + connection(open(broker->getPort(Broker::TCP_TRANSPORT))), + session(connection.createSession()), + admin(broker->getPort(Broker::TCP_TRANSPORT)) + { + } + + static messaging::Connection open(uint16_t port) + { + messaging::Connection connection( + (boost::format("amqp:tcp:localhost:%1%") % (port)).str()); + connection.open(); + return connection; + } + + /** Open a connection to the broker. */ + qpid::messaging::Connection newConnection() + { + qpid::messaging::Connection connection( + (boost::format("amqp:tcp:localhost:%1%") % (broker->getPort(qpid::broker::Broker::TCP_TRANSPORT))).str()); + return connection; + } + + void ping(const qpid::messaging::Address& address) + { + messaging::Receiver r = session.createReceiver(address); + messaging::Sender s = session.createSender(address); + messaging::Message out(framing::Uuid(true).str()); + s.send(out); + messaging::Message in; + BOOST_CHECK(r.fetch(in, 5*messaging::Duration::SECOND)); + BOOST_CHECK_EQUAL(out.getContent(), in.getContent()); + r.close(); + s.close(); + } + + ~MessagingFixture() + { + session.close(); + connection.close(); + } +}; + +struct QueueFixture : MessagingFixture +{ + std::string queue; + + QueueFixture(const std::string& name = "test-queue") : queue(name) + { + admin.createQueue(queue); + } + + ~QueueFixture() + { + admin.deleteQueue(queue); + } + +}; + +struct TopicFixture : MessagingFixture +{ + std::string topic; + + TopicFixture(const std::string& name = "test-topic", const std::string& type="fanout") : topic(name) + { + admin.createExchange(topic, type); + } + + ~TopicFixture() + { + admin.deleteExchange(topic); + } + +}; + +struct MultiQueueFixture : MessagingFixture +{ + typedef std::vector<std::string>::const_iterator const_iterator; + std::vector<std::string> queues; + + MultiQueueFixture(const std::vector<std::string>& names = boost::assign::list_of<std::string>("q1")("q2")("q3")) : queues(names) + { + for (const_iterator i = queues.begin(); i != queues.end(); ++i) { + admin.createQueue(*i); + } + } + + ~MultiQueueFixture() + { + connection.close(); + for (const_iterator i = queues.begin(); i != queues.end(); ++i) { + admin.deleteQueue(*i); + } + } + +}; + +inline std::vector<std::string> fetch(messaging::Receiver& receiver, int count, messaging::Duration timeout=messaging::Duration::SECOND*5) +{ + std::vector<std::string> data; + messaging::Message message; + for (int i = 0; i < count && receiver.fetch(message, timeout); i++) { + data.push_back(message.getContent()); + } + return data; +} + + +inline void send(messaging::Sender& sender, uint count = 1, uint start = 1, + const std::string& base = "Message") +{ + for (uint i = start; i < start + count; ++i) { + sender.send(messaging::Message((boost::format("%1%_%2%") % base % i).str())); + } +} + +inline void receive(messaging::Receiver& receiver, uint count = 1, uint start = 1, + const std::string& base = "Message", + messaging::Duration timeout=messaging::Duration::SECOND*5) +{ + for (uint i = start; i < start + count; ++i) { + BOOST_CHECK_EQUAL(receiver.fetch(timeout).getContent(), (boost::format("%1%_%2%") % base % i).str()); + } +} + + +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/qpid/cpp/src/tests/MessagingSessionTests.cpp b/qpid/cpp/src/tests/MessagingSessionTests.cpp new file mode 100644 index 0000000000..6aa4c63ed7 --- /dev/null +++ b/qpid/cpp/src/tests/MessagingSessionTests.cpp @@ -0,0 +1,997 @@ +/* + * + * 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 "MessagingFixture.h" +#include "unit_test.h" +#include "test_tools.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" +#include "qpid/client/Connection.h" +#include "qpid/client/Session.h" +#include "qpid/framing/ExchangeQueryResult.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/Uuid.h" +#include "qpid/sys/Time.h" +#include <boost/assign.hpp> +#include <boost/format.hpp> +#include <string> +#include <vector> + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(MessagingSessionTests) + +using namespace qpid::messaging; +using namespace qpid::types; +using namespace qpid; +using qpid::broker::Broker; +using qpid::framing::Uuid; + + +QPID_AUTO_TEST_CASE(testSimpleSendReceive) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message out("test-message"); + out.setSubject("test-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(testSyncSendReceive) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message out("test-message"); + sender.send(out, true); + Receiver receiver = fix.session.createReceiver(fix.queue); + Message in = receiver.fetch(Duration::IMMEDIATE); + fix.session.acknowledge(true); + BOOST_CHECK_EQUAL(in.getContent(), out.getContent()); +} + +QPID_AUTO_TEST_CASE(testSendReceiveHeaders) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message out("test-message"); + for (uint i = 0; i < 10; ++i) { + out.getProperties()["a"] = i; + out.setProperty("b", i + 100); + sender.send(out); + } + Receiver receiver = fix.session.createReceiver(fix.queue); + Message in; + for (uint i = 0; i < 10; ++i) { + BOOST_CHECK(receiver.fetch(in, Duration::SECOND * 5)); + BOOST_CHECK_EQUAL(in.getContent(), out.getContent()); + BOOST_CHECK_EQUAL(in.getProperties()["a"].asUint32(), i); + BOOST_CHECK_EQUAL(in.getProperties()["b"].asUint32(), i + 100); + fix.session.acknowledge(); + } +} + +QPID_AUTO_TEST_CASE(testSenderError) +{ + MessagingFixture fix; + ScopedSuppressLogging sl; + BOOST_CHECK_THROW(fix.session.createSender("NonExistentAddress"), qpid::messaging::NotFound); + fix.session = fix.connection.createSession(); + BOOST_CHECK_THROW(fix.session.createSender("NonExistentAddress; {create:receiver}"), + qpid::messaging::NotFound); +} + +QPID_AUTO_TEST_CASE(testReceiverError) +{ + MessagingFixture fix; + ScopedSuppressLogging sl; + BOOST_CHECK_THROW(fix.session.createReceiver("NonExistentAddress"), qpid::messaging::NotFound); + fix.session = fix.connection.createSession(); + BOOST_CHECK_THROW(fix.session.createReceiver("NonExistentAddress; {create:sender}"), + qpid::messaging::NotFound); +} + +QPID_AUTO_TEST_CASE(testSimpleTopic) +{ + TopicFixture fix; + + Sender sender = fix.session.createSender(fix.topic); + Message msg("one"); + sender.send(msg); + Receiver sub1 = fix.session.createReceiver(fix.topic); + sub1.setCapacity(10u); + msg.setContent("two"); + sender.send(msg); + Receiver sub2 = fix.session.createReceiver(fix.topic); + sub2.setCapacity(10u); + msg.setContent("three"); + sender.send(msg); + Receiver sub3 = fix.session.createReceiver(fix.topic); + sub3.setCapacity(10u); + msg.setContent("four"); + sender.send(msg); + BOOST_CHECK_EQUAL(fetch(sub2, 2), boost::assign::list_of<std::string>("three")("four")); + sub2.close(); + + msg.setContent("five"); + sender.send(msg); + BOOST_CHECK_EQUAL(fetch(sub1, 4), boost::assign::list_of<std::string>("two")("three")("four")("five")); + BOOST_CHECK_EQUAL(fetch(sub3, 2), boost::assign::list_of<std::string>("four")("five")); + Message in; + BOOST_CHECK(!sub2.fetch(in, Duration::IMMEDIATE));//TODO: or should this raise an error? + + + //TODO: check pending messages... +} + +QPID_AUTO_TEST_CASE(testNextReceiver) +{ + MultiQueueFixture fix; + + for (uint i = 0; i < fix.queues.size(); i++) { + Receiver r = fix.session.createReceiver(fix.queues[i]); + r.setCapacity(10u); + } + + for (uint i = 0; i < fix.queues.size(); i++) { + Sender s = fix.session.createSender(fix.queues[i]); + Message msg((boost::format("Message_%1%") % (i+1)).str()); + s.send(msg); + } + + for (uint i = 0; i < fix.queues.size(); i++) { + Message msg; + BOOST_CHECK(fix.session.nextReceiver().fetch(msg, Duration::SECOND)); + BOOST_CHECK_EQUAL(msg.getContent(), (boost::format("Message_%1%") % (i+1)).str()); + } +} + +QPID_AUTO_TEST_CASE(testMapMessage) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message out; + Variant::Map content; + content["abc"] = "def"; + content["pi"] = 3.14f; + Variant utf8("A utf 8 string"); + utf8.setEncoding("utf8"); + content["utf8"] = utf8; + Variant utf16("\x00\x61\x00\x62\x00\x63"); + utf16.setEncoding("utf16"); + content["utf16"] = utf16; + encode(content, out); + sender.send(out); + Receiver receiver = fix.session.createReceiver(fix.queue); + Message in = receiver.fetch(5 * Duration::SECOND); + Variant::Map view; + decode(in, view); + BOOST_CHECK_EQUAL(view["abc"].asString(), "def"); + BOOST_CHECK_EQUAL(view["pi"].asFloat(), 3.14f); + BOOST_CHECK_EQUAL(view["utf8"].asString(), utf8.asString()); + BOOST_CHECK_EQUAL(view["utf8"].getEncoding(), utf8.getEncoding()); + BOOST_CHECK_EQUAL(view["utf16"].asString(), utf16.asString()); + BOOST_CHECK_EQUAL(view["utf16"].getEncoding(), utf16.getEncoding()); + fix.session.acknowledge(); +} + +QPID_AUTO_TEST_CASE(testMapMessageWithInitial) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message out; + Variant::Map imap; + imap["abc"] = "def"; + imap["pi"] = 3.14f; + encode(imap, out); + sender.send(out); + Receiver receiver = fix.session.createReceiver(fix.queue); + Message in = receiver.fetch(5 * Duration::SECOND); + Variant::Map view; + decode(in, view); + BOOST_CHECK_EQUAL(view["abc"].asString(), "def"); + BOOST_CHECK_EQUAL(view["pi"].asFloat(), 3.14f); + fix.session.acknowledge(); +} + +QPID_AUTO_TEST_CASE(testListMessage) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message out; + Variant::List content; + content.push_back(Variant("abc")); + content.push_back(Variant(1234)); + content.push_back(Variant("def")); + content.push_back(Variant(56.789)); + encode(content, out); + sender.send(out); + Receiver receiver = fix.session.createReceiver(fix.queue); + Message in = receiver.fetch(5 * Duration::SECOND); + Variant::List view; + decode(in, view); + BOOST_CHECK_EQUAL(view.size(), content.size()); + BOOST_CHECK_EQUAL(view.front().asString(), "abc"); + BOOST_CHECK_EQUAL(view.back().asDouble(), 56.789); + + Variant::List::const_iterator i = view.begin(); + BOOST_CHECK(i != view.end()); + BOOST_CHECK_EQUAL(i->asString(), "abc"); + BOOST_CHECK(++i != view.end()); + BOOST_CHECK_EQUAL(i->asInt64(), 1234); + BOOST_CHECK(++i != view.end()); + BOOST_CHECK_EQUAL(i->asString(), "def"); + BOOST_CHECK(++i != view.end()); + BOOST_CHECK_EQUAL(i->asDouble(), 56.789); + BOOST_CHECK(++i == view.end()); + + fix.session.acknowledge(); +} + +QPID_AUTO_TEST_CASE(testListMessageWithInitial) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message out; + Variant::List ilist; + ilist.push_back(Variant("abc")); + ilist.push_back(Variant(1234)); + ilist.push_back(Variant("def")); + ilist.push_back(Variant(56.789)); + encode(ilist, out); + sender.send(out); + Receiver receiver = fix.session.createReceiver(fix.queue); + Message in = receiver.fetch(5 * Duration::SECOND); + Variant::List view; + decode(in, view); + BOOST_CHECK_EQUAL(view.size(), ilist.size()); + BOOST_CHECK_EQUAL(view.front().asString(), "abc"); + BOOST_CHECK_EQUAL(view.back().asDouble(), 56.789); + + Variant::List::const_iterator i = view.begin(); + BOOST_CHECK(i != view.end()); + BOOST_CHECK_EQUAL(i->asString(), "abc"); + BOOST_CHECK(++i != view.end()); + BOOST_CHECK_EQUAL(i->asInt64(), 1234); + BOOST_CHECK(++i != view.end()); + BOOST_CHECK_EQUAL(i->asString(), "def"); + BOOST_CHECK(++i != view.end()); + BOOST_CHECK_EQUAL(i->asDouble(), 56.789); + BOOST_CHECK(++i == view.end()); + + fix.session.acknowledge(); +} + +QPID_AUTO_TEST_CASE(testReject) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message m1("reject-me"); + sender.send(m1); + Message m2("accept-me"); + sender.send(m2); + Receiver receiver = fix.session.createReceiver(fix.queue); + Message in = receiver.fetch(5 * Duration::SECOND); + BOOST_CHECK_EQUAL(in.getContent(), m1.getContent()); + fix.session.reject(in); + in = receiver.fetch(5 * Duration::SECOND); + BOOST_CHECK_EQUAL(in.getContent(), m2.getContent()); + fix.session.acknowledge(); +} + +QPID_AUTO_TEST_CASE(testAvailable) +{ + MultiQueueFixture fix; + + Receiver r1 = fix.session.createReceiver(fix.queues[0]); + r1.setCapacity(100); + + Receiver r2 = fix.session.createReceiver(fix.queues[1]); + r2.setCapacity(100); + + Sender s1 = fix.session.createSender(fix.queues[0]); + Sender s2 = fix.session.createSender(fix.queues[1]); + + for (uint i = 0; i < 10; ++i) { + s1.send(Message((boost::format("A_%1%") % (i+1)).str())); + } + for (uint i = 0; i < 5; ++i) { + s2.send(Message((boost::format("B_%1%") % (i+1)).str())); + } + qpid::sys::sleep(1);//is there any avoid an arbitrary sleep while waiting for messages to be dispatched? + for (uint i = 0; i < 5; ++i) { + BOOST_CHECK_EQUAL(fix.session.getReceivable(), 15u - 2*i); + BOOST_CHECK_EQUAL(r1.getAvailable(), 10u - i); + BOOST_CHECK_EQUAL(r1.fetch().getContent(), (boost::format("A_%1%") % (i+1)).str()); + BOOST_CHECK_EQUAL(r2.getAvailable(), 5u - i); + BOOST_CHECK_EQUAL(r2.fetch().getContent(), (boost::format("B_%1%") % (i+1)).str()); + fix.session.acknowledge(); + } + for (uint i = 5; i < 10; ++i) { + BOOST_CHECK_EQUAL(fix.session.getReceivable(), 10u - i); + BOOST_CHECK_EQUAL(r1.getAvailable(), 10u - i); + BOOST_CHECK_EQUAL(r1.fetch().getContent(), (boost::format("A_%1%") % (i+1)).str()); + } +} + +QPID_AUTO_TEST_CASE(testUnsettledAcks) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + for (uint i = 0; i < 10; ++i) { + sender.send(Message((boost::format("Message_%1%") % (i+1)).str())); + } + Receiver receiver = fix.session.createReceiver(fix.queue); + for (uint i = 0; i < 10; ++i) { + BOOST_CHECK_EQUAL(receiver.fetch().getContent(), (boost::format("Message_%1%") % (i+1)).str()); + } + BOOST_CHECK_EQUAL(fix.session.getUnsettledAcks(), 0u); + fix.session.acknowledge(); + BOOST_CHECK_EQUAL(fix.session.getUnsettledAcks(), 10u); + fix.session.sync(); + BOOST_CHECK_EQUAL(fix.session.getUnsettledAcks(), 0u); +} + +QPID_AUTO_TEST_CASE(testUnsettledSend) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + send(sender, 10); + //Note: this test relies on 'inside knowledge' of the sender + //implementation and the fact that the simple test case makes it + //possible to predict when completion information will be sent to + //the client. TODO: is there a better way of testing this? + BOOST_CHECK_EQUAL(sender.getUnsettled(), 10u); + fix.session.sync(); + BOOST_CHECK_EQUAL(sender.getUnsettled(), 0u); + + Receiver receiver = fix.session.createReceiver(fix.queue); + receive(receiver, 10); + fix.session.acknowledge(); +} + +QPID_AUTO_TEST_CASE(testBrowse) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + send(sender, 10); + Receiver browser1 = fix.session.createReceiver(fix.queue + "; {mode:browse}"); + receive(browser1, 10); + Receiver browser2 = fix.session.createReceiver(fix.queue + "; {mode:browse}"); + receive(browser2, 10); + Receiver consumer = fix.session.createReceiver(fix.queue); + receive(consumer, 10); + fix.session.acknowledge(); +} + +struct QueueCreatePolicyFixture : public MessagingFixture +{ + qpid::messaging::Address address; + + QueueCreatePolicyFixture(const std::string& a) : address(a) {} + + void test() + { + ping(address); + BOOST_CHECK(admin.checkQueueExists(address.getName())); + } + + ~QueueCreatePolicyFixture() + { + admin.deleteQueue(address.getName()); + } +}; + +QPID_AUTO_TEST_CASE(testCreatePolicyQueueAlways) +{ + QueueCreatePolicyFixture fix("#; {create:always, node:{type:queue}}"); + fix.test(); +} + +QPID_AUTO_TEST_CASE(testCreatePolicyQueueReceiver) +{ + QueueCreatePolicyFixture fix("#; {create:receiver, node:{type:queue}}"); + Receiver r = fix.session.createReceiver(fix.address); + fix.test(); + r.close(); +} + +QPID_AUTO_TEST_CASE(testCreatePolicyQueueSender) +{ + QueueCreatePolicyFixture fix("#; {create:sender, node:{type:queue}}"); + Sender s = fix.session.createSender(fix.address); + fix.test(); + s.close(); +} + +struct ExchangeCreatePolicyFixture : public MessagingFixture +{ + qpid::messaging::Address address; + const std::string exchangeType; + + ExchangeCreatePolicyFixture(const std::string& a, const std::string& t) : + address(a), exchangeType(t) {} + + void test() + { + ping(address); + std::string actualType; + BOOST_CHECK(admin.checkExchangeExists(address.getName(), actualType)); + BOOST_CHECK_EQUAL(exchangeType, actualType); + } + + ~ExchangeCreatePolicyFixture() + { + admin.deleteExchange(address.getName()); + } +}; + +QPID_AUTO_TEST_CASE(testCreatePolicyTopic) +{ + ExchangeCreatePolicyFixture fix("#; {create:always, node:{type:topic}}", + "topic"); + fix.test(); +} + +QPID_AUTO_TEST_CASE(testCreatePolicyTopicReceiverFanout) +{ + ExchangeCreatePolicyFixture fix("#/my-subject; {create:receiver, node:{type:topic, x-declare:{type:fanout}}}", "fanout"); + Receiver r = fix.session.createReceiver(fix.address); + fix.test(); + r.close(); +} + +QPID_AUTO_TEST_CASE(testCreatePolicyTopicSenderDirect) +{ + ExchangeCreatePolicyFixture fix("#/my-subject; {create:sender, node:{type:topic, x-declare:{type:direct}}}", "direct"); + Sender s = fix.session.createSender(fix.address); + fix.test(); + s.close(); +} + +struct DeletePolicyFixture : public MessagingFixture +{ + enum Mode {RECEIVER, SENDER, ALWAYS, NEVER}; + + std::string getPolicy(Mode mode) + { + switch (mode) { + case SENDER: + return "{delete:sender}"; + case RECEIVER: + return "{delete:receiver}"; + case ALWAYS: + return "{delete:always}"; + case NEVER: + return "{delete:never}"; + } + return ""; + } + + void testAll() + { + test(RECEIVER); + test(SENDER); + test(ALWAYS); + test(NEVER); + } + + virtual ~DeletePolicyFixture() {} + virtual void create(const qpid::messaging::Address&) = 0; + virtual void destroy(const qpid::messaging::Address&) = 0; + virtual bool exists(const qpid::messaging::Address&) = 0; + + void test(Mode mode) + { + qpid::messaging::Address address("#; " + getPolicy(mode)); + create(address); + + Sender s = session.createSender(address); + Receiver r = session.createReceiver(address); + switch (mode) { + case RECEIVER: + s.close(); + BOOST_CHECK(exists(address)); + r.close(); + BOOST_CHECK(!exists(address)); + break; + case SENDER: + r.close(); + BOOST_CHECK(exists(address)); + s.close(); + BOOST_CHECK(!exists(address)); + break; + case ALWAYS: + s.close(); + BOOST_CHECK(!exists(address)); + break; + case NEVER: + r.close(); + BOOST_CHECK(exists(address)); + s.close(); + BOOST_CHECK(exists(address)); + destroy(address); + } + } +}; + +struct QueueDeletePolicyFixture : DeletePolicyFixture +{ + void create(const qpid::messaging::Address& address) + { + admin.createQueue(address.getName()); + } + void destroy(const qpid::messaging::Address& address) + { + admin.deleteQueue(address.getName()); + } + bool exists(const qpid::messaging::Address& address) + { + return admin.checkQueueExists(address.getName()); + } +}; + +struct ExchangeDeletePolicyFixture : DeletePolicyFixture +{ + const std::string exchangeType; + ExchangeDeletePolicyFixture(const std::string type = "topic") : exchangeType(type) {} + + void create(const qpid::messaging::Address& address) + { + admin.createExchange(address.getName(), exchangeType); + } + void destroy(const qpid::messaging::Address& address) + { + admin.deleteExchange(address.getName()); + } + bool exists(const qpid::messaging::Address& address) + { + std::string actualType; + return admin.checkExchangeExists(address.getName(), actualType) && actualType == exchangeType; + } +}; + +QPID_AUTO_TEST_CASE(testDeletePolicyQueue) +{ + QueueDeletePolicyFixture fix; + fix.testAll(); +} + +QPID_AUTO_TEST_CASE(testDeletePolicyExchange) +{ + ExchangeDeletePolicyFixture fix; + fix.testAll(); +} + +QPID_AUTO_TEST_CASE(testAssertPolicyQueue) +{ + MessagingFixture fix; + std::string a1 = "q; {create:always, assert:always, node:{type:queue, durable:false, x-declare:{arguments:{qpid.max-count:100}}}}"; + Sender s1 = fix.session.createSender(a1); + s1.close(); + Receiver r1 = fix.session.createReceiver(a1); + r1.close(); + + std::string a2 = "q; {assert:receiver, node:{durable:true, x-declare:{arguments:{qpid.max-count:100}}}}"; + Sender s2 = fix.session.createSender(a2); + s2.close(); + BOOST_CHECK_THROW(fix.session.createReceiver(a2), qpid::messaging::AssertionFailed); + + std::string a3 = "q; {assert:sender, node:{x-declare:{arguments:{qpid.max-count:99}}}}"; + BOOST_CHECK_THROW(fix.session.createSender(a3), qpid::messaging::AssertionFailed); + Receiver r3 = fix.session.createReceiver(a3); + r3.close(); + + fix.admin.deleteQueue("q"); +} + +QPID_AUTO_TEST_CASE(testGetSender) +{ + QueueFixture fix; + std::string name = fix.session.createSender(fix.queue).getName(); + Sender sender = fix.session.getSender(name); + BOOST_CHECK_EQUAL(name, sender.getName()); + Message out(Uuid(true).str()); + sender.send(out); + Message in; + BOOST_CHECK(fix.session.createReceiver(fix.queue).fetch(in)); + BOOST_CHECK_EQUAL(out.getContent(), in.getContent()); + BOOST_CHECK_THROW(fix.session.getSender("UnknownSender"), qpid::messaging::KeyError); +} + +QPID_AUTO_TEST_CASE(testGetReceiver) +{ + QueueFixture fix; + std::string name = fix.session.createReceiver(fix.queue).getName(); + Receiver receiver = fix.session.getReceiver(name); + BOOST_CHECK_EQUAL(name, receiver.getName()); + Message out(Uuid(true).str()); + fix.session.createSender(fix.queue).send(out); + Message in; + BOOST_CHECK(receiver.fetch(in)); + BOOST_CHECK_EQUAL(out.getContent(), in.getContent()); + BOOST_CHECK_THROW(fix.session.getReceiver("UnknownReceiver"), qpid::messaging::KeyError); +} + +QPID_AUTO_TEST_CASE(testGetSessionFromConnection) +{ + QueueFixture fix; + fix.connection.createSession("my-session"); + Session session = fix.connection.getSession("my-session"); + Message out(Uuid(true).str()); + session.createSender(fix.queue).send(out); + Message in; + BOOST_CHECK(session.createReceiver(fix.queue).fetch(in)); + BOOST_CHECK_EQUAL(out.getContent(), in.getContent()); + BOOST_CHECK_THROW(fix.connection.getSession("UnknownSession"), qpid::messaging::KeyError); +} + +QPID_AUTO_TEST_CASE(testGetConnectionFromSession) +{ + QueueFixture fix; + Message out(Uuid(true).str()); + Sender sender = fix.session.createSender(fix.queue); + sender.send(out); + Message in; + sender.getSession().getConnection().createSession("incoming"); + BOOST_CHECK(fix.connection.getSession("incoming").createReceiver(fix.queue).fetch(in)); + BOOST_CHECK_EQUAL(out.getContent(), in.getContent()); +} + +QPID_AUTO_TEST_CASE(testTx) +{ + QueueFixture fix; + Session ssn1 = fix.connection.createTransactionalSession(); + Session ssn2 = fix.connection.createTransactionalSession(); + Sender sender1 = ssn1.createSender(fix.queue); + Sender sender2 = ssn2.createSender(fix.queue); + Receiver receiver1 = ssn1.createReceiver(fix.queue); + Receiver receiver2 = ssn2.createReceiver(fix.queue); + Message in; + + send(sender1, 5, 1, "A"); + send(sender2, 5, 1, "B"); + ssn2.commit(); + receive(receiver1, 5, 1, "B");//(only those from sender2 should be received) + BOOST_CHECK(!receiver1.fetch(in, Duration::IMMEDIATE));//check there are no more messages + ssn1.rollback(); + receive(receiver2, 5, 1, "B"); + BOOST_CHECK(!receiver2.fetch(in, Duration::IMMEDIATE));//check there are no more messages + ssn2.rollback(); + receive(receiver1, 5, 1, "B"); + BOOST_CHECK(!receiver1.fetch(in, Duration::IMMEDIATE));//check there are no more messages + ssn1.commit(); + //check neither receiver gets any more messages: + BOOST_CHECK(!receiver1.fetch(in, Duration::IMMEDIATE)); + BOOST_CHECK(!receiver2.fetch(in, Duration::IMMEDIATE)); +} + +QPID_AUTO_TEST_CASE(testRelease) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message out("test-message"); + sender.send(out, true); + Receiver receiver = fix.session.createReceiver(fix.queue); + Message m1 = receiver.fetch(Duration::IMMEDIATE); + fix.session.release(m1); + Message m2 = receiver.fetch(Duration::SECOND * 1); + BOOST_CHECK_EQUAL(m1.getContent(), out.getContent()); + BOOST_CHECK_EQUAL(m1.getContent(), m2.getContent()); + fix.session.acknowledge(true); +} + +QPID_AUTO_TEST_CASE(testOptionVerification) +{ + MessagingFixture fix; + fix.session.createReceiver("my-queue; {create: always, assert: always, delete: always, node: {type: queue, durable: false, x-declare: {arguments: {a: b}}, x-bindings: [{exchange: amq.fanout}]}, link: {name: abc, durable: false, reliability: exactly-once, x-subscribe: {arguments:{a:b}}, x-bindings:[{exchange: amq.fanout}]}, mode: browse}"); + BOOST_CHECK_THROW(fix.session.createReceiver("my-queue; {invalid-option:blah}"), qpid::messaging::AddressError); +} + +QPID_AUTO_TEST_CASE(testReceiveSpecialProperties) +{ + QueueFixture fix; + + qpid::client::Message out; + out.getDeliveryProperties().setRoutingKey(fix.queue); + out.getMessageProperties().setAppId("my-app-id"); + out.getMessageProperties().setMessageId(qpid::framing::Uuid(true)); + out.getMessageProperties().setContentEncoding("my-content-encoding"); + fix.admin.send(out); + + Receiver receiver = fix.session.createReceiver(fix.queue); + Message in = receiver.fetch(Duration::SECOND * 5); + BOOST_CHECK_EQUAL(in.getProperties()["x-amqp-0-10.routing-key"].asString(), out.getDeliveryProperties().getRoutingKey()); + BOOST_CHECK_EQUAL(in.getProperties()["x-amqp-0-10.app-id"].asString(), out.getMessageProperties().getAppId()); + BOOST_CHECK_EQUAL(in.getProperties()["x-amqp-0-10.content-encoding"].asString(), out.getMessageProperties().getContentEncoding()); + BOOST_CHECK_EQUAL(in.getMessageId(), out.getMessageProperties().getMessageId().str()); + fix.session.acknowledge(true); +} + +QPID_AUTO_TEST_CASE(testSendSpecialProperties) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message out("test-message"); + std::string appId = "my-app-id"; + std::string contentEncoding = "my-content-encoding"; + out.getProperties()["x-amqp-0-10.app-id"] = appId; + out.getProperties()["x-amqp-0-10.content-encoding"] = contentEncoding; + out.setMessageId(qpid::framing::Uuid(true).str()); + sender.send(out, true); + + qpid::client::LocalQueue q; + qpid::client::SubscriptionManager subs(fix.admin.session); + qpid::client::Subscription s = subs.subscribe(q, fix.queue); + qpid::client::Message in = q.get(); + s.cancel(); + fix.admin.session.sync(); + + BOOST_CHECK_EQUAL(in.getMessageProperties().getAppId(), appId); + BOOST_CHECK_EQUAL(in.getMessageProperties().getContentEncoding(), contentEncoding); + BOOST_CHECK_EQUAL(in.getMessageProperties().getMessageId().str(), out.getMessageId()); +} + +QPID_AUTO_TEST_CASE(testExclusiveSubscriber) +{ + QueueFixture fix; + std::string address = (boost::format("%1%; { link: { x-subscribe : { exclusive:true } } }") % fix.queue).str(); + Receiver receiver = 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(testExclusiveQueueSubscriberAndBrowser) +{ + MessagingFixture fix; + + std::string address = "exclusive-queue; { create: receiver, node : { x-declare : { auto-delete: true, exclusive: true } } }"; + std::string browseAddress = "exclusive-queue; { mode: browse }"; + + Receiver receiver = fix.session.createReceiver(address); + fix.session.sync(); + + Connection c2 = fix.newConnection(); + c2.open(); + Session s2 = c2.createSession(); + + BOOST_CHECK_NO_THROW(Receiver browser = s2.createReceiver(browseAddress)); + c2.close(); +} + + +QPID_AUTO_TEST_CASE(testDeleteQueueWithUnackedMessages) +{ + MessagingFixture fix; + const uint capacity = 5; + + Sender sender = fix.session.createSender("test.ex;{create:always,node:{type:topic}}"); + Receiver receiver2 = fix.session.createReceiver("alternate.ex;{create:always,node:{type:topic}}"); + Receiver receiver1 = fix.session.createReceiver("test.q;{create:always, delete:always,node:{type:queue, x-declare:{alternate-exchange:alternate.ex}},link:{x-bindings:[{exchange:test.ex,queue:test.q,key:#}]}}"); + + receiver1.setCapacity(capacity); + receiver2.setCapacity(capacity*2); + + Message out("test-message"); + for (uint i = 0; i < capacity*2; ++i) { + sender.send(out); + } + + receiver1.close(); + + // Make sure all pending messages were sent to the alternate + // exchange when the queue was deleted. + Message in; + for (uint i = 0; i < capacity*2; ++i) { + in = receiver2.fetch(Duration::SECOND * 5); + BOOST_CHECK_EQUAL(in.getContent(), out.getContent()); + } +} + +QPID_AUTO_TEST_CASE(testAuthenticatedUsername) +{ + MessagingFixture fix; + Connection connection = fix.newConnection(); + connection.setOption("sasl-mechanism", "PLAIN"); + connection.setOption("username", "test-user"); + connection.setOption("password", "ignored"); + connection.open(); + BOOST_CHECK_EQUAL(connection.getAuthenticatedUsername(), std::string("test-user")); +} + +QPID_AUTO_TEST_CASE(testExceptionOnClosedConnection) +{ + MessagingFixture fix; + fix.connection.close(); + BOOST_CHECK_THROW(fix.connection.createSession(), MessagingException); + Connection connection("blah"); + BOOST_CHECK_THROW(connection.createSession(), MessagingException); +} + +QPID_AUTO_TEST_CASE(testAcknowledge) +{ + 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); //acknowledge first 10 messages only + for (uint i = 0; i < batch; ++i) { + other.acknowledge(messages[i]); + } + messages.clear(); + other.sync(); + other.close(); + + other = fix.connection.createSession(); + receiver = other.createReceiver(fix.queue); + for (uint i = 0; i < (count-batch); ++i) { + Message msg = receiver.fetch(); + BOOST_CHECK_EQUAL(msg.getContent(), (boost::format("Message_%1%") % (i+1+batch)).str()); + if (i % 2) other.acknowledge(msg); //acknowledge every other message + } + other.sync(); + other.close(); + + //check unacknowledged messages are still enqueued + other = fix.connection.createSession(); + receiver = other.createReceiver(fix.queue); + for (uint i = 0; i < ((count-batch)/2); ++i) { + Message msg = receiver.fetch(); + BOOST_CHECK_EQUAL(msg.getContent(), (boost::format("Message_%1%") % ((i*2)+1+batch)).str()); + } + other.acknowledge();//acknowledge all messages + 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(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_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/MessagingThreadTests.cpp b/qpid/cpp/src/tests/MessagingThreadTests.cpp new file mode 100644 index 0000000000..48264735b1 --- /dev/null +++ b/qpid/cpp/src/tests/MessagingThreadTests.cpp @@ -0,0 +1,144 @@ +/* + * + * 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 "MessagingFixture.h" +#include "qpid/sys/Runnable.h" +#include "qpid/sys/Thread.h" +#include <boost/lexical_cast.hpp> + +namespace qpid { +namespace tests { +QPID_AUTO_TEST_SUITE(MessagingThreadTests) + +using namespace messaging; +using namespace boost::assign; +using namespace std; + +struct ReceiveThread : public sys::Runnable { + Receiver receiver; + vector<string> received; + string error; + + ReceiveThread(Receiver s) : receiver(s) {} + void run() { + try { + while(true) { + Message m = receiver.fetch(Duration::SECOND*5); + if (m.getContent() == "END") break; + received.push_back(m.getContent()); + } + } catch (const NoMessageAvailable& e) { + // Indicates that fetch timed out OR receiver was closed by other thread. + if (!receiver.isClosed()) // timeout + error = e.what(); + } catch (const std::exception& e) { + error = e.what(); + } + } +}; + +struct NextReceiverThread : public sys::Runnable { + Session session; + vector<string> received; + string error; + + NextReceiverThread(Session s) : session(s) {} + void run() { + try { + while(true) { + Message m = session.nextReceiver(Duration::SECOND*5).fetch(); + if (m.getContent() == "END") break; + received.push_back(m.getContent()); + } + } catch (const std::exception& e) { + error = e.what(); + } + } +}; + + +QPID_AUTO_TEST_CASE(testConcurrentSendReceive) { + MessagingFixture fix; + Sender s = fix.session.createSender("concurrent;{create:always}"); + Receiver r = fix.session.createReceiver("concurrent;{create:always,link:{reliability:unreliable}}"); + ReceiveThread rt(r); + sys::Thread thread(rt); + const size_t COUNT=100; + for (size_t i = 0; i < COUNT; ++i) { + s.send(Message()); + } + s.send(Message("END")); + thread.join(); + BOOST_CHECK_EQUAL(rt.error, string()); + BOOST_CHECK_EQUAL(COUNT, rt.received.size()); +} + +QPID_AUTO_TEST_CASE(testCloseBusyReceiver) { + MessagingFixture fix; + Receiver r = fix.session.createReceiver("closeReceiver;{create:always}"); + ReceiveThread rt(r); + sys::Thread thread(rt); + sys::usleep(1000); // Give the receive thread time to block. + r.close(); + thread.join(); + BOOST_CHECK_EQUAL(rt.error, string()); + + // Fetching on closed receiver should fail. + Message m; + BOOST_CHECK(!r.fetch(m, Duration(0))); + BOOST_CHECK_THROW(r.fetch(Duration(0)), NoMessageAvailable); +} + +QPID_AUTO_TEST_CASE(testCloseSessionBusyReceiver) { + MessagingFixture fix; + Receiver r = fix.session.createReceiver("closeSession;{create:always}"); + ReceiveThread rt(r); + sys::Thread thread(rt); + sys::usleep(1000); // Give the receive thread time to block. + fix.session.close(); + thread.join(); + BOOST_CHECK_EQUAL(rt.error, string()); + + // Fetching on closed receiver should fail. + Message m; + BOOST_CHECK(!r.fetch(m, Duration(0))); + BOOST_CHECK_THROW(r.fetch(Duration(0)), NoMessageAvailable); +} + +QPID_AUTO_TEST_CASE(testConcurrentSendNextReceiver) { + MessagingFixture fix; + Receiver r = fix.session.createReceiver("concurrent;{create:always,link:{reliability:unreliable}}"); + const size_t COUNT=100; + r.setCapacity(COUNT); + NextReceiverThread rt(fix.session); + sys::Thread thread(rt); + sys::usleep(1000); // Give the receive thread time to block. + Sender s = fix.session.createSender("concurrent;{create:always}"); + for (size_t i = 0; i < COUNT; ++i) { + s.send(Message()); + } + s.send(Message("END")); + thread.join(); + BOOST_CHECK_EQUAL(rt.error, string()); + BOOST_CHECK_EQUAL(COUNT, rt.received.size()); +} + +QPID_AUTO_TEST_SUITE_END() +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/PartialFailure.cpp b/qpid/cpp/src/tests/PartialFailure.cpp new file mode 100644 index 0000000000..63ee28017a --- /dev/null +++ b/qpid/cpp/src/tests/PartialFailure.cpp @@ -0,0 +1,291 @@ +/* + * + * 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. + * + */ + +/**@file Tests for partial failure in a cluster. + * Partial failure means some nodes experience a failure while others do not. + * In this case the failed nodes must shut down. + */ + +#include "test_tools.h" +#include "unit_test.h" +#include "ClusterFixture.h" +#include <boost/assign.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/bind.hpp> + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(PartialFailureTestSuite) + +using namespace std; +using namespace qpid; +using namespace qpid::cluster; +using namespace qpid::framing; +using namespace qpid::client; +using namespace qpid::client::arg; +using namespace boost::assign; +using broker::Broker; +using boost::shared_ptr; + +// Timeout for tests that wait for messages +const sys::Duration TIMEOUT=sys::TIME_SEC/4; + +static bool isLogOption(const std::string& s) { return boost::starts_with(s, "--log-enable"); } + +void updateArgs(ClusterFixture::Args& args, size_t index) { + ostringstream clusterLib, testStoreLib, storeName; + clusterLib << getLibPath("CLUSTER_LIB"); + testStoreLib << getLibPath("TEST_STORE_LIB"); + storeName << "s" << index; + args.push_back("--auth"); + args.push_back("no"); + args.push_back("--no-module-dir"); + args.push_back("--load-module"); + args.push_back(clusterLib.str()); + args.push_back("--load-module"); + args.push_back(testStoreLib.str()); + args.push_back("--test-store-name"); + args.push_back(storeName.str()); + args.push_back("TMP_DATA_DIR"); + + // These tests generate errors deliberately, disable error logging unless a log env var is set. + if (!::getenv("QPID_TRACE") && !::getenv("QPID_LOG_ENABLE")) { + remove_if(args.begin(), args.end(), isLogOption); + args.push_back("--log-enable=critical+:DISABLED"); // hacky way to disable logs. + } +} + +Message pMessage(string data, string q) { + Message msg(data, q); + msg.getDeliveryProperties().setDeliveryMode(PERSISTENT); + return msg; +} + +void queueAndSub(Client& c) { + c.session.queueDeclare(c.name, durable=true); + c.subs.subscribe(c.lq, c.name); +} + +// Handle near-simultaneous errors +QPID_AUTO_TEST_CASE(testCoincidentErrors) { + ClusterFixture cluster(2, updateArgs, -1); + Client c0(cluster[0], "c0"); + Client c1(cluster[1], "c1"); + + c0.session.queueDeclare("q", durable=true); + { + ScopedSuppressLogging allQuiet; + async(c0.session).messageTransfer(content=pMessage("TEST_STORE_DO: s0[exception]", "q")); + async(c1.session).messageTransfer(content=pMessage("TEST_STORE_DO: s1[exception]", "q")); + + int alive=0; + try { Client c00(cluster[0], "c00"); ++alive; c00.close(); } catch (...) {} + try { Client c11(cluster[1], "c11"); ++alive; c11.close(); } catch (...) {} + + BOOST_CHECK_EQUAL(alive, 1); + + // Close inside ScopedSuppressLogging to avoid warnings + c0.close(); + c1.close(); + } +} + +// Verify normal cluster-wide errors. +QPID_AUTO_TEST_CASE(testNormalErrors) { + // FIXME aconway 2009-04-10: Would like to put a scope just around + // the statements expected to fail (in BOOST_CHECK_yTHROW) but that + // sproadically lets out messages, possibly because they're in + // Connection thread. + + ClusterFixture cluster(3, updateArgs, -1); + Client c0(cluster[0], "c0"); + Client c1(cluster[1], "c1"); + Client c2(cluster[2], "c2"); + + { + ScopedSuppressLogging allQuiet; + queueAndSub(c0); + c0.session.messageTransfer(content=Message("x", "c0")); + BOOST_CHECK_EQUAL(c0.lq.get(TIMEOUT).getData(), "x"); + + // Session error. + BOOST_CHECK_THROW(c0.session.exchangeBind(), SessionException); + c1.session.messageTransfer(content=Message("stay", "c0")); // Will stay on queue, session c0 is dead. + + // Connection error, kill c1 on all members. + queueAndSub(c1); + BOOST_CHECK_THROW( + c1.session.messageTransfer( + content=pMessage("TEST_STORE_DO: s0[exception] s1[exception] s2[exception] testNormalErrors", "c1")), + ConnectionException); + c2.session.messageTransfer(content=Message("stay", "c1")); // Will stay on queue, session/connection c1 is dead. + + BOOST_CHECK_EQUAL(3u, knownBrokerPorts(c2.connection, 3).size()); + BOOST_CHECK_EQUAL(c2.subs.get("c0", TIMEOUT).getData(), "stay"); + BOOST_CHECK_EQUAL(c2.subs.get("c1", TIMEOUT).getData(), "stay"); + + // Close inside ScopedSuppressLogging to avoid warnings + c0.close(); + c1.close(); + c2.close(); + } +} + + +// Test errors after a new member joins to verify frame-sequence-numbers are ok in update. +QPID_AUTO_TEST_CASE(testErrorAfterJoin) { + ClusterFixture cluster(1, updateArgs, -1); + Client c0(cluster[0]); + { + ScopedSuppressLogging allQuiet; + + c0.session.queueDeclare("q", durable=true); + c0.session.messageTransfer(content=pMessage("a", "q")); + + // Kill the new guy + cluster.add(); + Client c1(cluster[1]); + c0.session.messageTransfer(content=pMessage("TEST_STORE_DO: s1[exception] testErrorAfterJoin", "q")); + BOOST_CHECK_THROW(c1.session.messageTransfer(content=pMessage("xxx", "q")), TransportFailure); + BOOST_CHECK_EQUAL(1u, knownBrokerPorts(c0.connection, 1).size()); + + // Kill the old guy + cluster.add(); + Client c2(cluster[2]); + c2.session.messageTransfer(content=pMessage("TEST_STORE_DO: s0[exception] testErrorAfterJoin2", "q")); + BOOST_CHECK_THROW(c0.session.messageTransfer(content=pMessage("xxx", "q")), TransportFailure); + + BOOST_CHECK_EQUAL(1u, knownBrokerPorts(c2.connection, 1).size()); + + // Close inside ScopedSuppressLogging to avoid warnings + c0.close(); + c1.close(); + c2.close(); + } +} + +// Test that if one member fails and others do not, the failure leaves the cluster. +QPID_AUTO_TEST_CASE(testSinglePartialFailure) { + ClusterFixture cluster(3, updateArgs, -1); + Client c0(cluster[0], "c0"); + Client c1(cluster[1], "c1"); + Client c2(cluster[2], "c2"); + + { + ScopedSuppressLogging allQuiet; + + c0.session.queueDeclare("q", durable=true); + c0.session.messageTransfer(content=pMessage("a", "q")); + // Cause partial failure on c1 + c0.session.messageTransfer(content=pMessage("TEST_STORE_DO: s1[exception] testSinglePartialFailure", "q")); + BOOST_CHECK_THROW(c1.session.queueQuery("q"), TransportFailure); + + c0.session.messageTransfer(content=pMessage("b", "q")); + BOOST_CHECK_EQUAL(c0.session.queueQuery("q").getMessageCount(), 3u); + BOOST_CHECK_EQUAL(2u, knownBrokerPorts(c0.connection, 2).size()); + + // Cause partial failure on c2 + c0.session.messageTransfer(content=pMessage("TEST_STORE_DO: s2[exception] testSinglePartialFailure2", "q")); + BOOST_CHECK_THROW(c2.session.queueQuery("q"), TransportFailure); + + c0.session.messageTransfer(content=pMessage("c", "q")); + BOOST_CHECK_EQUAL(c0.session.queueQuery("q").getMessageCount(), 5u); + BOOST_CHECK_EQUAL(1u, knownBrokerPorts(c0.connection, 1).size()); + + // Close inside ScopedSuppressLogging to avoid warnings + c0.close(); + c1.close(); + c2.close(); + } +} + +// Test multiple partial falures: 2 fail 2 pass +QPID_AUTO_TEST_CASE(testMultiPartialFailure) { + ClusterFixture cluster(4, updateArgs, -1); + Client c0(cluster[0], "c0"); + Client c1(cluster[1], "c1"); + Client c2(cluster[2], "c2"); + Client c3(cluster[3], "c3"); + + { + ScopedSuppressLogging allQuiet; + + c0.session.queueDeclare("q", durable=true); + c0.session.messageTransfer(content=pMessage("a", "q")); + + // Cause partial failure on c1, c2 + c0.session.messageTransfer(content=pMessage("TEST_STORE_DO: s1[exception] s2[exception] testMultiPartialFailure", "q")); + BOOST_CHECK_THROW(c1.session.queueQuery("q"), TransportFailure); + BOOST_CHECK_THROW(c2.session.queueQuery("q"), TransportFailure); + + c0.session.messageTransfer(content=pMessage("b", "q")); + c3.session.messageTransfer(content=pMessage("c", "q")); + BOOST_CHECK_EQUAL(c3.session.queueQuery("q").getMessageCount(), 4u); + // FIXME aconway 2009-06-30: This check fails sporadically with 2 != 3. + // It should pass reliably. + // BOOST_CHECK_EQUAL(2u, knownBrokerPorts(c0.connection, 2).size()); + + // Close inside ScopedSuppressLogging to avoid warnings + c0.close(); + c1.close(); + c2.close(); + c3.close(); + } +} + +/** FIXME aconway 2009-04-10: + * The current approach to shutting down a process in test_store + * sometimes leads to assertion failures and errors in the shut-down + * process. Need a cleaner solution + */ +#if 0 +QPID_AUTO_TEST_CASE(testPartialFailureMemberLeaves) { + ClusterFixture cluster(2, updateArgs, -1); + Client c0(cluster[0], "c0"); + Client c1(cluster[1], "c1"); + + { + ScopedSuppressLogging allQuiet; + + c0.session.queueDeclare("q", durable=true); + c0.session.messageTransfer(content=pMessage("a", "q")); + + // Cause failure on member 0 and simultaneous crash on member 1. + BOOST_CHECK_THROW( + c0.session.messageTransfer( + content=pMessage("TEST_STORE_DO: s0[exception] s1[exit_process] testPartialFailureMemberLeaves", "q")), + ConnectionException); + cluster.wait(1); + + Client c00(cluster[0], "c00"); // Old connection is dead. + BOOST_CHECK_EQUAL(c00.session.queueQuery("q").getMessageCount(), 1u); + BOOST_CHECK_EQUAL(1u, knownBrokerPorts(c00.connection, 1).size()); + + // Close inside ScopedSuppressLogging to avoid warnings + c0.close(); + } +} +#endif + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/PollableCondition.cpp b/qpid/cpp/src/tests/PollableCondition.cpp new file mode 100644 index 0000000000..f9b3c25c93 --- /dev/null +++ b/qpid/cpp/src/tests/PollableCondition.cpp @@ -0,0 +1,109 @@ +/* + * + * 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 "test_tools.h" +#include "unit_test.h" +#include "qpid/sys/Poller.h" +#include "qpid/sys/PollableCondition.h" +#include "qpid/sys/Monitor.h" +#include "qpid/sys/Time.h" +#include "qpid/sys/Thread.h" +#include <boost/bind.hpp> + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(PollableConditionTest) + +using namespace qpid::sys; + +const Duration SHORT = TIME_SEC/100; +const Duration LONG = TIME_SEC/10; + +class Callback { + public: + enum Action { NONE, CLEAR }; + + Callback() : count(), action(NONE) {} + + void call(PollableCondition& pc) { + Mutex::ScopedLock l(lock); + ++count; + switch(action) { + case NONE: break; + case CLEAR: pc.clear(); break; + } + action = NONE; + lock.notify(); + } + + bool isCalling() { Mutex::ScopedLock l(lock); return wait(LONG); } + + bool isNotCalling() { Mutex::ScopedLock l(lock); return !wait(SHORT); } + + bool nextCall(Action a=NONE) { + Mutex::ScopedLock l(lock); + action = a; + return wait(LONG); + } + + private: + bool wait(Duration timeout) { + int n = count; + AbsTime deadline(now(), timeout); + while (n == count && lock.wait(deadline)) + ; + return n != count; + } + + Monitor lock; + int count; + Action action; +}; + +QPID_AUTO_TEST_CASE(testPollableCondition) { + boost::shared_ptr<Poller> poller(new Poller()); + Callback callback; + PollableCondition pc(boost::bind(&Callback::call, &callback, _1), poller); + + Thread runner = Thread(*poller); + + BOOST_CHECK(callback.isNotCalling()); // condition is not set. + + pc.set(); + BOOST_CHECK(callback.isCalling()); // Set. + BOOST_CHECK(callback.isCalling()); // Still set. + + callback.nextCall(Callback::CLEAR); + BOOST_CHECK(callback.isNotCalling()); // Cleared + + pc.set(); + BOOST_CHECK(callback.isCalling()); // Set. + callback.nextCall(Callback::CLEAR); + BOOST_CHECK(callback.isNotCalling()); // Cleared. + + poller->shutdown(); + runner.join(); +} + +QPID_AUTO_TEST_SUITE_END() + +}} //namespace qpid::tests diff --git a/qpid/cpp/src/tests/PollerTest.cpp b/qpid/cpp/src/tests/PollerTest.cpp new file mode 100644 index 0000000000..9fa5689c5f --- /dev/null +++ b/qpid/cpp/src/tests/PollerTest.cpp @@ -0,0 +1,263 @@ +/* + * + * 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. + * + */ + +/** + * Use socketpair to test the poller + */ + +#include "qpid/sys/IOHandle.h" +#include "qpid/sys/Poller.h" +#include "qpid/sys/posix/PrivatePosix.h" + +#include <string> +#include <iostream> +#include <memory> +#include <exception> + +#include <assert.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +using namespace std; +using namespace qpid::sys; + +int writeALot(int fd, const string& s) { + int bytesWritten = 0; + do { + errno = 0; + int lastWrite = ::write(fd, s.c_str(), s.size()); + if ( lastWrite >= 0) { + bytesWritten += lastWrite; + } + } while (errno != EAGAIN); + return bytesWritten; +} + +int readALot(int fd) { + int bytesRead = 0; + char buf[1024]; + + do { + errno = 0; + int lastRead = ::read(fd, buf, sizeof(buf)); + if ( lastRead >= 0) { + bytesRead += lastRead; + } + } while (errno != EAGAIN); + return bytesRead; +} + +void makesocketpair(int (&sv)[2]) { + int rc = ::socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + assert(rc >= 0); + + // Set non-blocking + rc = ::fcntl(sv[0], F_SETFL, O_NONBLOCK); + assert(rc >= 0); + + rc = ::fcntl(sv[1], F_SETFL, O_NONBLOCK); + assert(rc >= 0); +} + +int main(int /*argc*/, char** /*argv*/) +{ + try + { + int sv[2]; + makesocketpair(sv); + + // Make up a large string + string testString = "This is only a test ... 1,2,3,4,5,6,7,8,9,10;"; + for (int i = 0; i < 6; i++) + testString += testString; + + // Read as much as we can from socket 0 + int bytesRead = readALot(sv[0]); + assert(bytesRead == 0); + + // Write as much as we can to socket 0 + int bytesWritten = writeALot(sv[0], testString); + + // Read as much as we can from socket 1 + bytesRead = readALot(sv[1]); + assert(bytesRead == bytesWritten); + + auto_ptr<Poller> poller(new Poller); + + PosixIOHandle f0(sv[0]); + PosixIOHandle f1(sv[1]); + + PollerHandle h0(f0); + PollerHandle h1(f1); + + poller->registerHandle(h0); + poller->monitorHandle(h0, Poller::INOUT); + + // h0 should be writable + Poller::Event event = poller->wait(); + assert(event.handle == &h0); + assert(event.type == Poller::WRITABLE); + + // Write as much as we can to socket 0 + bytesWritten = writeALot(sv[0], testString); + + // Wait for 500ms - h0 no longer writable + event = poller->wait(500000000); + assert(event.handle == 0); + + // Test we can read it all now + poller->registerHandle(h1); + poller->monitorHandle(h1, Poller::INOUT); + event = poller->wait(); + assert(event.handle == &h1); + assert(event.type == Poller::READ_WRITABLE); + + bytesRead = readALot(sv[1]); + assert(bytesRead == bytesWritten); + + // Test poller interrupt + assert(poller->interrupt(h0) == true); + event = poller->wait(); + assert(event.handle == &h0); + assert(event.type == Poller::INTERRUPTED); + + // Test multiple interrupts + assert(poller->interrupt(h0) == true); + assert(poller->interrupt(h1) == true); + + // Make sure we can interrupt them again + assert(poller->interrupt(h0) == true); + assert(poller->interrupt(h1) == true); + + // Make sure that they both come out + event = poller->wait(); + assert(event.type == Poller::INTERRUPTED); + assert(event.handle == &h0 || event.handle == &h1); + if (event.handle == &h0) { + event = poller->wait(); + assert(event.type == Poller::INTERRUPTED); + assert(event.handle == &h1); + } else { + event = poller->wait(); + assert(event.type == Poller::INTERRUPTED); + assert(event.handle == &h0); + } + + poller->unmonitorHandle(h1, Poller::INOUT); + + event = poller->wait(); + assert(event.handle == &h0); + assert(event.type == Poller::WRITABLE); + + // We didn't write anything so it should still be writable + event = poller->wait(); + assert(event.handle == &h0); + assert(event.type == Poller::WRITABLE); + + poller->unmonitorHandle(h0, Poller::INOUT); + + event = poller->wait(500000000); + assert(event.handle == 0); + + poller->unregisterHandle(h1); + assert(poller->interrupt(h1) == false); + + // close the other end to force a disconnect + ::close(sv[1]); + + // Now make sure that we are readable followed by disconnected + // and after that we never return again + poller->monitorHandle(h0, Poller::INOUT); + event = poller->wait(500000000); + assert(event.handle == &h0); + assert(event.type == Poller::READABLE); + event = poller->wait(500000000); + assert(event.handle == &h0); + assert(event.type == Poller::DISCONNECTED); + event = poller->wait(1500000000); + assert(event.handle == 0); + + // Now we're disconnected monitoring should have no effect at all + poller->unmonitorHandle(h0, Poller::INOUT); + event = poller->wait(1500000000); + assert(event.handle == 0); + + poller->unregisterHandle(h0); + assert(poller->interrupt(h0) == false); + + // Test shutdown + poller->shutdown(); + event = poller->wait(); + assert(event.handle == 0); + assert(event.type == Poller::SHUTDOWN); + + event = poller->wait(); + assert(event.handle == 0); + assert(event.type == Poller::SHUTDOWN); + + ::close(sv[0]); + + // Test for correct interaction of shutdown and interrupts - need to have new poller + // etc. for this + makesocketpair(sv); + + auto_ptr<Poller> poller1(new Poller); + + PosixIOHandle f2(sv[0]); + PosixIOHandle f3(sv[1]); + + PollerHandle h2(f2); + PollerHandle h3(f3); + + poller1->registerHandle(h2); + poller1->monitorHandle(h2, Poller::INOUT); + event = poller1->wait(); + assert(event.handle == &h2); + assert(event.type == Poller::WRITABLE); + + // Shutdown + poller1->shutdown(); + event = poller1->wait(); + assert(event.handle == 0); + assert(event.type == Poller::SHUTDOWN); + + assert(poller1->interrupt(h2) == true); + event = poller1->wait(); + assert(event.handle == &h2); + assert(event.type == Poller::INTERRUPTED); + poller1->unmonitorHandle(h2, Poller::INOUT); + + event = poller1->wait(); + assert(event.handle == 0); + assert(event.type == Poller::SHUTDOWN); + + poller1->unregisterHandle(h2); + return 0; + } catch (exception& e) { + cout << "Caught exception " << e.what() << "\n"; + } +} + + diff --git a/qpid/cpp/src/tests/ProxyTest.cpp b/qpid/cpp/src/tests/ProxyTest.cpp new file mode 100644 index 0000000000..a926b28395 --- /dev/null +++ b/qpid/cpp/src/tests/ProxyTest.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 <iostream> +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/AMQMethodBody.h" +#include "qpid/framing/ExecutionSyncBody.h" +#include "qpid/framing/Proxy.h" + +#include "unit_test.h" + +using namespace qpid::framing; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(ProxyTestSuite) + + +QPID_AUTO_TEST_CASE(testScopedSync) +{ + struct DummyHandler : FrameHandler + { + void handle(AMQFrame& f) { + AMQMethodBody* m = f.getMethod(); + BOOST_CHECK(m); + BOOST_CHECK(m->isA<ExecutionSyncBody>()); + BOOST_CHECK(m->isSync()); + } + }; + DummyHandler f; + Proxy p(f); + Proxy::ScopedSync s(p); + p.send(ExecutionSyncBody(p.getVersion())); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/Qmf2.cpp b/qpid/cpp/src/tests/Qmf2.cpp new file mode 100644 index 0000000000..66c774accd --- /dev/null +++ b/qpid/cpp/src/tests/Qmf2.cpp @@ -0,0 +1,320 @@ +/* + * + * 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 "qpid/types/Variant.h" +#include "qmf/QueryImpl.h" +#include "qmf/SchemaImpl.h" +#include "qmf/exceptions.h" + +#include "unit_test.h" + +using namespace qpid::types; +using namespace qmf; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(Qmf2Suite) + +QPID_AUTO_TEST_CASE(testQuery) +{ + Query query(QUERY_OBJECT, "class_name", "package_name", "[and, [eq, name, [quote, smith]], [lt, age, [quote, 27]]]"); + Query newQuery(new QueryImpl(QueryImplAccess::get(query).asMap())); + + BOOST_CHECK_EQUAL(newQuery.getTarget(), QUERY_OBJECT); + BOOST_CHECK_EQUAL(newQuery.getSchemaId().getName(), "class_name"); + BOOST_CHECK_EQUAL(newQuery.getSchemaId().getPackageName(), "package_name"); + + Variant::List pred(newQuery.getPredicate()); + BOOST_CHECK_EQUAL(pred.size(), size_t(3)); + + Variant::List::iterator iter(pred.begin()); + BOOST_CHECK_EQUAL(iter->asString(), "and"); + iter++; + BOOST_CHECK_EQUAL(iter->getType(), VAR_LIST); + iter++; + BOOST_CHECK_EQUAL(iter->getType(), VAR_LIST); + iter = iter->asList().begin(); + BOOST_CHECK_EQUAL(iter->asString(), "lt"); + iter++; + BOOST_CHECK_EQUAL(iter->asString(), "age"); + iter++; + BOOST_CHECK_EQUAL(iter->getType(), VAR_LIST); + iter = iter->asList().begin(); + BOOST_CHECK_EQUAL(iter->asString(), "quote"); + iter++; + BOOST_CHECK_EQUAL(iter->asUint32(), uint32_t(27)); + + Query query2(QUERY_OBJECT_ID); + Query newQuery2(new QueryImpl(QueryImplAccess::get(query2).asMap())); + BOOST_CHECK_EQUAL(newQuery2.getTarget(), QUERY_OBJECT_ID); + + Query query3(QUERY_SCHEMA); + Query newQuery3(new QueryImpl(QueryImplAccess::get(query3).asMap())); + BOOST_CHECK_EQUAL(newQuery3.getTarget(), QUERY_SCHEMA); + + Query query4(QUERY_SCHEMA_ID); + Query newQuery4(new QueryImpl(QueryImplAccess::get(query4).asMap())); + BOOST_CHECK_EQUAL(newQuery4.getTarget(), QUERY_SCHEMA_ID); + + DataAddr addr("name", "agent_name", 34); + Query query5(addr); + Query newQuery5(new QueryImpl(QueryImplAccess::get(query5).asMap())); + BOOST_CHECK_EQUAL(newQuery5.getTarget(), QUERY_OBJECT); + BOOST_CHECK_EQUAL(newQuery5.getDataAddr().getName(), "name"); + BOOST_CHECK_EQUAL(newQuery5.getDataAddr().getAgentName(), "agent_name"); + BOOST_CHECK_EQUAL(newQuery5.getDataAddr().getAgentEpoch(), uint32_t(34)); +} + +QPID_AUTO_TEST_CASE(testQueryPredicateErrors) +{ + Query query; + Variant::Map map; + + BOOST_CHECK_THROW(Query(QUERY_OBJECT, "INVALID"), QmfException); + query = Query(QUERY_OBJECT, "[unknown, one, two]"); + BOOST_CHECK_THROW(query.matchesPredicate(map), QmfException); + + query = Query(QUERY_OBJECT, "[eq, first]"); + BOOST_CHECK_THROW(query.matchesPredicate(map), QmfException); + + query = Query(QUERY_OBJECT, "[exists]"); + BOOST_CHECK_THROW(query.matchesPredicate(map), QmfException); + + query = Query(QUERY_OBJECT, "[eq, first, [quote, 1, 2, 3]]]"); + BOOST_CHECK_THROW(query.matchesPredicate(map), QmfException); + + query = Query(QUERY_OBJECT, "[eq, first, [unexpected, 3]]]"); + BOOST_CHECK_THROW(query.matchesPredicate(map), QmfException); + + query = Query(QUERY_OBJECT, "[eq, first, {}]]"); + BOOST_CHECK_THROW(query.matchesPredicate(map), QmfException); + + query = Query(QUERY_OBJECT, "[eq, first, second, third]"); + BOOST_CHECK_THROW(query.matchesPredicate(map), QmfException); + + query = Query(QUERY_OBJECT, "[and, first, second, third]"); + BOOST_CHECK_THROW(query.matchesPredicate(map), QmfException); +} + +QPID_AUTO_TEST_CASE(testQueryPredicate) +{ + Query query; + Variant::Map map; + + map["forty"] = 40; + map["fifty"] = 50; + map["minus_ten"] = -10; + map["pos_float"] = 100.05; + map["neg_float"] = -1000.33; + map["name"] = "jones"; + map["bool_t"] = true; + map["bool_f"] = false; + + BOOST_CHECK_THROW(Query(QUERY_OBJECT, "INVALID"), QmfException); + + query = Query(QUERY_OBJECT); + BOOST_CHECK_EQUAL(query.matchesPredicate(Variant::Map()), true); + + query = Query(QUERY_OBJECT, "[eq, forty, [quote, 40]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[eq, forty, [quote, 41]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), false); + + query = Query(QUERY_OBJECT, "[le, forty, fifty]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[and, [eq, forty, [quote, 40]], [eq, name, [quote, jones]]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[and, [eq, forty, [quote, 40]], [eq, name, [quote, smith]]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), false); + + query = Query(QUERY_OBJECT, "[or, [eq, forty, [quote, 40]], [eq, name, [quote, smith]]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[or, [eq, forty, [quote, 41]], [eq, name, [quote, smith]]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), false); + + query = Query(QUERY_OBJECT, "[not, [le, forty, [quote, 40]]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), false); + + query = Query(QUERY_OBJECT, "[le, forty, [quote, 40]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[ge, forty, [quote, 40]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[lt, forty, [quote, 45]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[lt, [quote, 45], forty]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), false); + + query = Query(QUERY_OBJECT, "[gt, forty, [quote, 45]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), false); + + query = Query(QUERY_OBJECT, "[gt, [quote, 45], forty]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[eq, bool_t, [quote, True]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[eq, bool_t, [quote, False]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), false); + + query = Query(QUERY_OBJECT, "[eq, bool_f, [quote, True]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), false); + + query = Query(QUERY_OBJECT, "[eq, bool_f, [quote, False]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[eq, minus_ten, [quote, -10]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[lt, minus_ten, [quote, -20]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), false); + + query = Query(QUERY_OBJECT, "[lt, [quote, -20], minus_ten]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[exists, name]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[exists, nonexfield]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), false); + + query = Query(QUERY_OBJECT, "[eq, pos_float, [quote, 100.05]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); +} + +QPID_AUTO_TEST_CASE(testSchema) +{ + Schema in(SCHEMA_TYPE_DATA, "package", "class"); + in.addProperty(SchemaProperty("prop1", SCHEMA_DATA_BOOL, "{desc:'Property One'}")); + in.addProperty(SchemaProperty("prop2", SCHEMA_DATA_INT, "{desc:'Property Two',unit:'Furlong'}")); + in.addProperty(SchemaProperty("prop3", SCHEMA_DATA_STRING, "{desc:'Property Three'}")); + + SchemaMethod method1("method1", "{desc:'Method One'}"); + method1.addArgument(SchemaProperty("arg1", SCHEMA_DATA_BOOL, "{desc:'Argument One',dir:IN}")); + method1.addArgument(SchemaProperty("arg2", SCHEMA_DATA_INT, "{desc:'Argument Two',dir:OUT}")); + method1.addArgument(SchemaProperty("arg3", SCHEMA_DATA_FLOAT, "{desc:'Argument Three',dir:INOUT}")); + in.addMethod(method1); + + SchemaMethod method2("method2", "{desc:'Method Two'}"); + method2.addArgument(SchemaProperty("arg21", SCHEMA_DATA_BOOL, "{desc:'Argument One',dir:IN}")); + method2.addArgument(SchemaProperty("arg22", SCHEMA_DATA_INT, "{desc:'Argument Two',dir:OUT}")); + method2.addArgument(SchemaProperty("arg23", SCHEMA_DATA_FLOAT, "{desc:'Argument Three',dir:INOUT}")); + in.addMethod(method2); + + BOOST_CHECK(!in.isFinalized()); + in.finalize(); + BOOST_CHECK(in.isFinalized()); + + Variant::Map map(SchemaImplAccess::get(in).asMap()); + Schema out(new SchemaImpl(map)); + + BOOST_CHECK(out.isFinalized()); + BOOST_CHECK_EQUAL(out.getSchemaId().getType(), SCHEMA_TYPE_DATA); + BOOST_CHECK_EQUAL(out.getSchemaId().getPackageName(), "package"); + BOOST_CHECK_EQUAL(out.getSchemaId().getName(), "class"); + BOOST_CHECK_EQUAL(out.getSchemaId().getHash(), in.getSchemaId().getHash()); + + BOOST_CHECK_EQUAL(out.getPropertyCount(), uint32_t(3)); + SchemaProperty prop; + + prop = out.getProperty(0); + BOOST_CHECK_EQUAL(prop.getName(), "prop1"); + BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_BOOL); + BOOST_CHECK_EQUAL(prop.getDesc(), "Property One"); + + prop = out.getProperty(1); + BOOST_CHECK_EQUAL(prop.getName(), "prop2"); + BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_INT); + BOOST_CHECK_EQUAL(prop.getDesc(), "Property Two"); + BOOST_CHECK_EQUAL(prop.getUnit(), "Furlong"); + BOOST_CHECK(!prop.isIndex()); + + prop = out.getProperty(2); + BOOST_CHECK_EQUAL(prop.getName(), "prop3"); + BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_STRING); + BOOST_CHECK_EQUAL(prop.getDesc(), "Property Three"); + + BOOST_CHECK_THROW(out.getProperty(3), QmfException); + + BOOST_CHECK_EQUAL(out.getMethodCount(), uint32_t(2)); + SchemaMethod method; + + method = out.getMethod(0); + BOOST_CHECK_EQUAL(method.getName(), "method1"); + BOOST_CHECK_EQUAL(method.getDesc(), "Method One"); + BOOST_CHECK_EQUAL(method.getArgumentCount(), uint32_t(3)); + + prop = method.getArgument(0); + BOOST_CHECK_EQUAL(prop.getName(), "arg1"); + BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_BOOL); + BOOST_CHECK_EQUAL(prop.getDesc(), "Argument One"); + BOOST_CHECK_EQUAL(prop.getDirection(), DIR_IN); + + prop = method.getArgument(1); + BOOST_CHECK_EQUAL(prop.getName(), "arg2"); + BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_INT); + BOOST_CHECK_EQUAL(prop.getDesc(), "Argument Two"); + BOOST_CHECK_EQUAL(prop.getDirection(), DIR_OUT); + + prop = method.getArgument(2); + BOOST_CHECK_EQUAL(prop.getName(), "arg3"); + BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_FLOAT); + BOOST_CHECK_EQUAL(prop.getDesc(), "Argument Three"); + BOOST_CHECK_EQUAL(prop.getDirection(), DIR_IN_OUT); + + BOOST_CHECK_THROW(method.getArgument(3), QmfException); + + method = out.getMethod(1); + BOOST_CHECK_EQUAL(method.getName(), "method2"); + BOOST_CHECK_EQUAL(method.getDesc(), "Method Two"); + BOOST_CHECK_EQUAL(method.getArgumentCount(), uint32_t(3)); + + prop = method.getArgument(0); + BOOST_CHECK_EQUAL(prop.getName(), "arg21"); + BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_BOOL); + BOOST_CHECK_EQUAL(prop.getDesc(), "Argument One"); + BOOST_CHECK_EQUAL(prop.getDirection(), DIR_IN); + + prop = method.getArgument(1); + BOOST_CHECK_EQUAL(prop.getName(), "arg22"); + BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_INT); + BOOST_CHECK_EQUAL(prop.getDesc(), "Argument Two"); + BOOST_CHECK_EQUAL(prop.getDirection(), DIR_OUT); + + prop = method.getArgument(2); + BOOST_CHECK_EQUAL(prop.getName(), "arg23"); + BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_FLOAT); + BOOST_CHECK_EQUAL(prop.getDesc(), "Argument Three"); + BOOST_CHECK_EQUAL(prop.getDirection(), DIR_IN_OUT); + + BOOST_CHECK_THROW(method.getArgument(3), QmfException); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/QueueEvents.cpp b/qpid/cpp/src/tests/QueueEvents.cpp new file mode 100644 index 0000000000..bd18fa45fb --- /dev/null +++ b/qpid/cpp/src/tests/QueueEvents.cpp @@ -0,0 +1,238 @@ +/* + * + * 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 "MessageUtils.h" +#include "unit_test.h" +#include "BrokerFixture.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/QueueEvents.h" +#include "qpid/client/QueueOptions.h" +#include "qpid/framing/SequenceNumber.h" +#include "qpid/sys/Dispatcher.h" +#include <boost/bind.hpp> +#include <boost/format.hpp> + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(QueueEventsSuite) + +using namespace qpid::client; +using namespace qpid::broker; +using namespace qpid::sys; +using qpid::framing::SequenceNumber; + +struct EventChecker +{ + typedef std::deque<QueueEvents::Event> Events; + + Events events; + boost::shared_ptr<Poller> poller; + + void handle(QueueEvents::Event e) + { + if (events.empty()) { + BOOST_FAIL("Unexpected event received"); + } else { + BOOST_CHECK_EQUAL(events.front().type, e.type); + BOOST_CHECK_EQUAL(events.front().msg.queue, e.msg.queue); + BOOST_CHECK_EQUAL(events.front().msg.payload, e.msg.payload); + BOOST_CHECK_EQUAL(events.front().msg.position, e.msg.position); + events.pop_front(); + } + if (events.empty() && poller) poller->shutdown(); + } + + void expect(QueueEvents::Event e) + { + events.push_back(e); + } +}; + +QPID_AUTO_TEST_CASE(testBasicEventProcessing) +{ + boost::shared_ptr<Poller> poller(new Poller()); + sys::Dispatcher dispatcher(poller); + Thread dispatchThread(dispatcher); + QueueEvents events(poller); + EventChecker listener; + listener.poller = poller; + events.registerListener("dummy", boost::bind(&EventChecker::handle, &listener, _1)); + //signal occurence of some events: + Queue queue("queue1"); + SequenceNumber id; + QueuedMessage event1(&queue, MessageUtils::createMessage(), id); + QueuedMessage event2(&queue, MessageUtils::createMessage(), ++id); + + //define events expected by listener: + listener.expect(QueueEvents::Event(QueueEvents::ENQUEUE, event1)); + listener.expect(QueueEvents::Event(QueueEvents::ENQUEUE, event2)); + listener.expect(QueueEvents::Event(QueueEvents::DEQUEUE, event1)); + + events.enqueued(event1); + events.enqueued(event2); + events.dequeued(event1); + + dispatchThread.join(); + events.shutdown(); + events.unregisterListener("dummy"); +} + + +struct EventRecorder +{ + struct EventRecord + { + QueueEvents::EventType type; + std::string queue; + std::string content; + SequenceNumber position; + }; + + typedef std::deque<EventRecord> Events; + + Events events; + + void handle(QueueEvents::Event event) + { + EventRecord record; + record.type = event.type; + record.queue = event.msg.queue->getName(); + event.msg.payload->getFrames().getContent(record.content); + record.position = event.msg.position; + events.push_back(record); + } + + void check(QueueEvents::EventType type, const std::string& queue, const std::string& content, const SequenceNumber& position) + { + if (events.empty()) { + BOOST_FAIL("Missed event"); + } else { + BOOST_CHECK_EQUAL(events.front().type, type); + BOOST_CHECK_EQUAL(events.front().queue, queue); + BOOST_CHECK_EQUAL(events.front().content, content); + BOOST_CHECK_EQUAL(events.front().position, position); + events.pop_front(); + } + } + void checkEnqueue(const std::string& queue, const std::string& data, const SequenceNumber& position) + { + check(QueueEvents::ENQUEUE, queue, data, position); + } + + void checkDequeue(const std::string& queue, const std::string& data, const SequenceNumber& position) + { + check(QueueEvents::DEQUEUE, queue, data, position); + } +}; + +QPID_AUTO_TEST_CASE(testSystemLevelEventProcessing) +{ + ProxySessionFixture fixture; + //register dummy event listener to broker + EventRecorder listener; + fixture.broker->getQueueEvents().registerListener("recorder", boost::bind(&EventRecorder::handle, &listener, _1)); + + //declare queue with event options specified + QueueOptions options; + options.enableQueueEvents(false); + std::string q("queue-events-test"); + fixture.session.queueDeclare(arg::queue=q, arg::arguments=options); + //send and consume some messages + LocalQueue incoming; + Subscription sub = fixture.subs.subscribe(incoming, q); + for (int i = 0; i < 5; i++) { + fixture.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q)); + } + for (int i = 0; i < 3; i++) { + BOOST_CHECK_EQUAL(incoming.pop().getData(), (boost::format("%1%_%2%") % "Message" % (i+1)).str()); + } + for (int i = 5; i < 10; i++) { + fixture.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q)); + } + for (int i = 3; i < 10; i++) { + BOOST_CHECK_EQUAL(incoming.pop().getData(), (boost::format("%1%_%2%") % "Message" % (i+1)).str()); + } + fixture.connection.close(); + fixture.broker->getQueueEvents().shutdown(); + + //check listener was notified of all events, and in correct order + SequenceNumber enqueueId(1); + SequenceNumber dequeueId(1); + for (int i = 0; i < 5; i++) { + listener.checkEnqueue(q, (boost::format("%1%_%2%") % "Message" % (i+1)).str(), enqueueId++); + } + for (int i = 0; i < 3; i++) { + listener.checkDequeue(q, (boost::format("%1%_%2%") % "Message" % (i+1)).str(), dequeueId++); + } + for (int i = 5; i < 10; i++) { + listener.checkEnqueue(q, (boost::format("%1%_%2%") % "Message" % (i+1)).str(), enqueueId++); + } + for (int i = 3; i < 10; i++) { + listener.checkDequeue(q, (boost::format("%1%_%2%") % "Message" % (i+1)).str(), dequeueId++); + } +} + +QPID_AUTO_TEST_CASE(testSystemLevelEventProcessing_enqueuesOnly) +{ + ProxySessionFixture fixture; + //register dummy event listener to broker + EventRecorder listener; + fixture.broker->getQueueEvents().registerListener("recorder", boost::bind(&EventRecorder::handle, &listener, _1)); + + //declare queue with event options specified + QueueOptions options; + options.enableQueueEvents(true); + std::string q("queue-events-test"); + fixture.session.queueDeclare(arg::queue=q, arg::arguments=options); + //send and consume some messages + LocalQueue incoming; + Subscription sub = fixture.subs.subscribe(incoming, q); + for (int i = 0; i < 5; i++) { + fixture.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q)); + } + for (int i = 0; i < 3; i++) { + BOOST_CHECK_EQUAL(incoming.pop().getData(), (boost::format("%1%_%2%") % "Message" % (i+1)).str()); + } + for (int i = 5; i < 10; i++) { + fixture.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q)); + } + for (int i = 3; i < 10; i++) { + BOOST_CHECK_EQUAL(incoming.pop().getData(), (boost::format("%1%_%2%") % "Message" % (i+1)).str()); + } + fixture.connection.close(); + fixture.broker->getQueueEvents().shutdown(); + + //check listener was notified of all events, and in correct order + SequenceNumber enqueueId(1); + SequenceNumber dequeueId(1); + for (int i = 0; i < 5; i++) { + listener.checkEnqueue(q, (boost::format("%1%_%2%") % "Message" % (i+1)).str(), enqueueId++); + } + for (int i = 5; i < 10; i++) { + listener.checkEnqueue(q, (boost::format("%1%_%2%") % "Message" % (i+1)).str(), enqueueId++); + } +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/QueueFlowLimitTest.cpp b/qpid/cpp/src/tests/QueueFlowLimitTest.cpp new file mode 100644 index 0000000000..8a6923fb09 --- /dev/null +++ b/qpid/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/qpid/cpp/src/tests/QueueOptionsTest.cpp b/qpid/cpp/src/tests/QueueOptionsTest.cpp new file mode 100644 index 0000000000..f2fbaba2c1 --- /dev/null +++ b/qpid/cpp/src/tests/QueueOptionsTest.cpp @@ -0,0 +1,102 @@ +/* + * + * 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 "qpid/framing/Array.h" +#include "qpid/client/QueueOptions.h" + +#include "unit_test.h" + +using namespace qpid::client; + + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(QueueOptionsTestSuite) + +QPID_AUTO_TEST_CASE(testSizePolicy) +{ + QueueOptions ft; + + ft.setSizePolicy(REJECT,1,2); + + BOOST_CHECK(QueueOptions::strREJECT == ft.getAsString(QueueOptions::strTypeKey)); + BOOST_CHECK(1 == ft.getAsInt(QueueOptions::strMaxSizeKey)); + BOOST_CHECK(2 == ft.getAsInt(QueueOptions::strMaxCountKey)); + + ft.setSizePolicy(FLOW_TO_DISK,0,2); + BOOST_CHECK(QueueOptions::strFLOW_TO_DISK == ft.getAsString(QueueOptions::strTypeKey)); + BOOST_CHECK(1 == ft.getAsInt(QueueOptions::strMaxSizeKey)); + BOOST_CHECK(2 == ft.getAsInt(QueueOptions::strMaxCountKey)); + + ft.setSizePolicy(RING,1,0); + BOOST_CHECK(QueueOptions::strRING == ft.getAsString(QueueOptions::strTypeKey)); + + ft.setSizePolicy(RING_STRICT,1,0); + BOOST_CHECK(QueueOptions::strRING_STRICT == ft.getAsString(QueueOptions::strTypeKey)); + + ft.clearSizePolicy(); + BOOST_CHECK(!ft.isSet(QueueOptions::strTypeKey)); + BOOST_CHECK(!ft.isSet(QueueOptions::strMaxSizeKey)); + BOOST_CHECK(!ft.isSet(QueueOptions::strMaxCountKey)); +} + +QPID_AUTO_TEST_CASE(testFlags) +{ + QueueOptions ft; + + ft.setPersistLastNode(); + ft.setOrdering(LVQ); + + BOOST_CHECK(1 == ft.getAsInt(QueueOptions::strPersistLastNode)); + BOOST_CHECK(1 == ft.getAsInt(QueueOptions::strLastValueQueue)); + + ft.clearPersistLastNode(); + ft.setOrdering(FIFO); + + BOOST_CHECK(!ft.isSet(QueueOptions::strPersistLastNode)); + BOOST_CHECK(!ft.isSet(QueueOptions::strLastValueQueue)); + +} + +QPID_AUTO_TEST_CASE(testSetOrdering) +{ + //ensure setOrdering(FIFO) works even if not preceded by a call to + //setOrdering(LVQ) + QueueOptions ft; + ft.setOrdering(FIFO); + BOOST_CHECK(!ft.isSet(QueueOptions::strLastValueQueue)); + +} + +QPID_AUTO_TEST_CASE(testClearPersistLastNode) +{ + //ensure clear works even if not preceded by the setting on the + //option + QueueOptions ft; + ft.clearPersistLastNode(); + BOOST_CHECK(!ft.isSet(QueueOptions::strPersistLastNode)); +} + + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/QueuePolicyTest.cpp b/qpid/cpp/src/tests/QueuePolicyTest.cpp new file mode 100644 index 0000000000..5455105078 --- /dev/null +++ b/qpid/cpp/src/tests/QueuePolicyTest.cpp @@ -0,0 +1,406 @@ +/* + * + * 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 "unit_test.h" +#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" +#include "MessageUtils.h" +#include "BrokerFixture.h" + +using namespace qpid::broker; +using namespace qpid::client; +using namespace qpid::framing; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(QueuePolicyTestSuite) + +namespace { +QueuedMessage createMessage(uint32_t size) +{ + QueuedMessage msg; + msg.payload = MessageUtils::createMessage(); + MessageUtils::addContent(msg.payload, std::string (size, 'x')); + return msg; +} +} + +QPID_AUTO_TEST_CASE(testCount) +{ + std::auto_ptr<QueuePolicy> policy(QueuePolicy::createQueuePolicy("test", 5, 0)); + BOOST_CHECK_EQUAL((uint64_t) 0, policy->getMaxSize()); + BOOST_CHECK_EQUAL((uint32_t) 5, policy->getMaxCount()); + + QueuedMessage msg = createMessage(10); + for (size_t i = 0; i < 5; i++) { + policy->tryEnqueue(msg.payload); + } + try { + policy->tryEnqueue(msg.payload); + BOOST_FAIL("Policy did not fail on enqueuing sixth message"); + } catch (const ResourceLimitExceededException&) {} + + policy->dequeued(msg); + policy->tryEnqueue(msg.payload); + + try { + policy->tryEnqueue(msg.payload); + BOOST_FAIL("Policy did not fail on enqueuing sixth message (after dequeue)"); + } catch (const ResourceLimitExceededException&) {} +} + +QPID_AUTO_TEST_CASE(testSize) +{ + std::auto_ptr<QueuePolicy> policy(QueuePolicy::createQueuePolicy("test", 0, 50)); + QueuedMessage msg = createMessage(10); + + for (size_t i = 0; i < 5; i++) { + policy->tryEnqueue(msg.payload); + } + try { + policy->tryEnqueue(msg.payload); + BOOST_FAIL("Policy did not fail on aggregate size exceeding 50. " << *policy); + } catch (const ResourceLimitExceededException&) {} + + policy->dequeued(msg); + policy->tryEnqueue(msg.payload); + + try { + policy->tryEnqueue(msg.payload); + BOOST_FAIL("Policy did not fail on aggregate size exceeding 50 (after dequeue). " << *policy); + } catch (const ResourceLimitExceededException&) {} +} + +QPID_AUTO_TEST_CASE(testBoth) +{ + std::auto_ptr<QueuePolicy> policy(QueuePolicy::createQueuePolicy("test", 5, 50)); + try { + QueuedMessage msg = createMessage(51); + policy->tryEnqueue(msg.payload); + BOOST_FAIL("Policy did not fail on single message exceeding 50. " << *policy); + } catch (const ResourceLimitExceededException&) {} + + std::vector<QueuedMessage> messages; + messages.push_back(createMessage(15)); + messages.push_back(createMessage(10)); + messages.push_back(createMessage(11)); + messages.push_back(createMessage(2)); + messages.push_back(createMessage(7)); + for (size_t i = 0; i < messages.size(); i++) { + policy->tryEnqueue(messages[i].payload); + } + //size = 45 at this point, count = 5 + try { + QueuedMessage msg = createMessage(5); + policy->tryEnqueue(msg.payload); + BOOST_FAIL("Policy did not fail on count exceeding 6. " << *policy); + } catch (const ResourceLimitExceededException&) {} + try { + QueuedMessage msg = createMessage(10); + policy->tryEnqueue(msg.payload); + BOOST_FAIL("Policy did not fail on aggregate size exceeding 50. " << *policy); + } catch (const ResourceLimitExceededException&) {} + + + policy->dequeued(messages[0]); + try { + QueuedMessage msg = createMessage(20); + policy->tryEnqueue(msg.payload); + } catch (const ResourceLimitExceededException&) { + BOOST_FAIL("Policy failed incorrectly after dequeue. " << *policy); + } +} + +QPID_AUTO_TEST_CASE(testSettings) +{ + //test reading and writing the policy from/to field table + std::auto_ptr<QueuePolicy> a(QueuePolicy::createQueuePolicy("test", 101, 303)); + FieldTable settings; + a->update(settings); + std::auto_ptr<QueuePolicy> b(QueuePolicy::createQueuePolicy("test", settings)); + BOOST_CHECK_EQUAL(a->getMaxCount(), b->getMaxCount()); + BOOST_CHECK_EQUAL(a->getMaxSize(), b->getMaxSize()); +} + +QPID_AUTO_TEST_CASE(testRingPolicyCount) +{ + FieldTable args; + std::auto_ptr<QueuePolicy> policy = QueuePolicy::createQueuePolicy("test", 5, 0, QueuePolicy::RING); + policy->update(args); + + ProxySessionFixture 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++) { + f.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q)); + } + client::Message msg; + for (int i = 5; i < 10; i++) { + BOOST_CHECK(f.subs.get(msg, q, qpid::sys::TIME_SEC)); + BOOST_CHECK_EQUAL((boost::format("%1%_%2%") % "Message" % (i+1)).str(), msg.getData()); + } + BOOST_CHECK(!f.subs.get(msg, q)); + + for (int i = 10; i < 20; i++) { + f.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q)); + } + for (int i = 15; i < 20; i++) { + BOOST_CHECK(f.subs.get(msg, q, qpid::sys::TIME_SEC)); + BOOST_CHECK_EQUAL((boost::format("%1%_%2%") % "Message" % (i+1)).str(), msg.getData()); + } + BOOST_CHECK(!f.subs.get(msg, q)); +} + +QPID_AUTO_TEST_CASE(testRingPolicySize) +{ + std::string hundredBytes = std::string(100, 'h'); + std::string fourHundredBytes = std::string (400, 'f'); + std::string thousandBytes = std::string(1000, 't'); + + // Ring queue, 500 bytes maxSize + + FieldTable args; + std::auto_ptr<QueuePolicy> policy = QueuePolicy::createQueuePolicy("test", 0, 500, QueuePolicy::RING); + policy->update(args); + + ProxySessionFixture f; + std::string q("my-ring-queue"); + f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); + + // A. Send messages 0 .. 5, each 100 bytes + + client::Message m(hundredBytes, q); + + for (int i = 0; i < 6; i++) { + std::stringstream id; + id << i; + m.getMessageProperties().setCorrelationId(id.str()); + f.session.messageTransfer(arg::content=m); + } + + // should find 1 .. 5 on the queue, 0 is displaced by 5 + client::Message msg; + for (int i = 1; i < 6; i++) { + std::stringstream id; + id << i; + BOOST_CHECK(f.subs.get(msg, q, qpid::sys::TIME_SEC)); + BOOST_CHECK_EQUAL(msg.getMessageProperties().getCorrelationId(), id.str()); + } + BOOST_CHECK(!f.subs.get(msg, q)); + + // B. Now make sure that one 400 byte message displaces four 100 byte messages + + // Send messages 0 .. 5, each 100 bytes + for (int i = 0; i < 6; i++) { + client::Message m(hundredBytes, q); + std::stringstream id; + id << i; + m.getMessageProperties().setCorrelationId(id.str()); + f.session.messageTransfer(arg::content=m); + } + + // Now send one 400 byte message + client::Message m2(fourHundredBytes, q); + m2.getMessageProperties().setCorrelationId("6"); + f.session.messageTransfer(arg::content=m2); + + // expect to see 5, 6 on the queue + for (int i = 5; i < 7; i++) { + std::stringstream id; + id << i; + BOOST_CHECK(f.subs.get(msg, q, qpid::sys::TIME_SEC)); + BOOST_CHECK_EQUAL(msg.getMessageProperties().getCorrelationId(), id.str()); + } + BOOST_CHECK(!f.subs.get(msg, q)); + + + // C. Try sending a 1000-byte message, should fail - exceeds maxSize of queue + + client::Message m3(thousandBytes, q); + m3.getMessageProperties().setCorrelationId("6"); + try { + ScopedSuppressLogging sl; + f.session.messageTransfer(arg::content=m3); + BOOST_FAIL("Ooops - successfully added a 1000 byte message to a 512 byte ring queue ..."); + } + catch (...) { + } + +} + + +QPID_AUTO_TEST_CASE(testStrictRingPolicy) +{ + FieldTable args; + std::auto_ptr<QueuePolicy> policy = QueuePolicy::createQueuePolicy("test", 5, 0, QueuePolicy::RING_STRICT); + policy->update(args); + + ProxySessionFixture f; + std::string q("my-ring-queue"); + f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); + LocalQueue incoming; + SubscriptionSettings settings(FlowControl::unlimited()); + settings.autoAck = 0; // no auto ack. + Subscription sub = f.subs.subscribe(incoming, q, settings); + for (int i = 0; i < 5; i++) { + f.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q)); + } + for (int i = 0; i < 5; i++) { + BOOST_CHECK_EQUAL(incoming.pop().getData(), (boost::format("%1%_%2%") % "Message" % (i+1)).str()); + } + try { + ScopedSuppressLogging sl; // Suppress messages for expected errors. + f.session.messageTransfer(arg::content=client::Message("Message_6", q)); + BOOST_FAIL("expecting ResourceLimitExceededException."); + } catch (const ResourceLimitExceededException&) {} +} + +QPID_AUTO_TEST_CASE(testPolicyWithDtx) +{ + FieldTable args; + std::auto_ptr<QueuePolicy> policy = QueuePolicy::createQueuePolicy("test", 5, 0, QueuePolicy::REJECT); + policy->update(args); + + ProxySessionFixture f; + std::string q("my-policy-queue"); + f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); + LocalQueue incoming; + SubscriptionSettings settings(FlowControl::unlimited()); + settings.autoAck = 0; // no auto ack. + Subscription sub = f.subs.subscribe(incoming, q, settings); + f.session.dtxSelect(); + Xid tx1(1, "test-dtx-mgr", "tx1"); + f.session.dtxStart(arg::xid=tx1); + for (int i = 0; i < 5; i++) { + f.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q)); + } + f.session.dtxEnd(arg::xid=tx1); + f.session.dtxCommit(arg::xid=tx1, arg::onePhase=true); + + Xid tx2(1, "test-dtx-mgr", "tx2"); + f.session.dtxStart(arg::xid=tx2); + for (int i = 0; i < 5; i++) { + BOOST_CHECK_EQUAL(incoming.pop().getData(), (boost::format("%1%_%2%") % "Message" % (i+1)).str()); + } + SequenceSet accepting=sub.getUnaccepted(); + f.session.messageAccept(accepting); + f.session.dtxEnd(arg::xid=tx2); + f.session.dtxPrepare(arg::xid=tx2); + f.session.dtxRollback(arg::xid=tx2); + f.session.messageRelease(accepting); + + Xid tx3(1, "test-dtx-mgr", "tx3"); + f.session.dtxStart(arg::xid=tx3); + for (int i = 0; i < 5; i++) { + incoming.pop(); + } + accepting=sub.getUnaccepted(); + f.session.messageAccept(accepting); + f.session.dtxEnd(arg::xid=tx3); + f.session.dtxPrepare(arg::xid=tx3); + + Session other = f.connection.newSession(); + try { + ScopedSuppressLogging sl; // Suppress messages for expected errors. + other.messageTransfer(arg::content=client::Message("Message_6", q)); + BOOST_FAIL("expecting ResourceLimitExceededException."); + } catch (const ResourceLimitExceededException&) {} + + f.session.dtxCommit(arg::xid=tx3); + //now retry and this time should succeed + other = f.connection.newSession(); + other.messageTransfer(arg::content=client::Message("Message_6", q)); +} + +QPID_AUTO_TEST_CASE(testFlowToDiskWithNoStore) +{ + //Ensure that with no store loaded, we don't flow to disk but + //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; + std::string q("my-queue"); + f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); + LocalQueue incoming; + SubscriptionSettings settings(FlowControl::unlimited()); + settings.autoAck = 0; // no auto ack. + Subscription sub = f.subs.subscribe(incoming, q, settings); + for (int i = 0; i < 5; i++) { + f.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q)); + } + for (int i = 0; i < 5; i++) { + BOOST_CHECK_EQUAL(incoming.pop().getData(), (boost::format("%1%_%2%") % "Message" % (i+1)).str()); + } + try { + ScopedSuppressLogging sl; // Suppress messages for expected errors. + f.session.messageTransfer(arg::content=client::Message("Message_6", q)); + BOOST_FAIL("expecting ResourceLimitExceededException."); + } catch (const ResourceLimitExceededException&) {} +} + +QPID_AUTO_TEST_CASE(testPolicyFailureOnCommit) +{ + FieldTable args; + std::auto_ptr<QueuePolicy> policy = QueuePolicy::createQueuePolicy("test", 5, 0, QueuePolicy::REJECT); + policy->update(args); + + ProxySessionFixture f; + std::string q("q"); + f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); + f.session.txSelect(); + for (int i = 0; i < 10; i++) { + f.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q)); + } + ScopedSuppressLogging sl; // Suppress messages for expected errors. + BOOST_CHECK_THROW(f.session.txCommit(), InternalErrorException); +} + +QPID_AUTO_TEST_CASE(testCapacityConversion) +{ + FieldTable args; + args.setString("qpid.max_count", "5"); + args.setString("qpid.flow_stop_count", "0"); + + ProxySessionFixture 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++) { + f.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q)); + } + try { + ScopedSuppressLogging sl; // Suppress messages for expected errors. + f.session.messageTransfer(arg::content=client::Message("Message_6", q)); + BOOST_FAIL("expecting ResourceLimitExceededException."); + } catch (const ResourceLimitExceededException&) {} +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/QueueRegistryTest.cpp b/qpid/cpp/src/tests/QueueRegistryTest.cpp new file mode 100644 index 0000000000..ae555539a4 --- /dev/null +++ b/qpid/cpp/src/tests/QueueRegistryTest.cpp @@ -0,0 +1,100 @@ +/* + * 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/QueueRegistry.h" +#include "qpid/broker/Queue.h" +#include "unit_test.h" +#include <string> + +using namespace qpid::broker; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(QueueRegistryTest) + +QPID_AUTO_TEST_CASE(testDeclare) +{ + std::string foo("foo"); + std::string bar("bar"); + QueueRegistry reg; + std::pair<Queue::shared_ptr, bool> qc; + + qc = reg.declare(foo, false, 0, 0); + Queue::shared_ptr q = qc.first; + BOOST_CHECK(q); + BOOST_CHECK(qc.second); // New queue + BOOST_CHECK_EQUAL(foo, q->getName()); + + qc = reg.declare(foo, false, 0, 0); + BOOST_CHECK_EQUAL(q, qc.first); + BOOST_CHECK(!qc.second); + + qc = reg.declare(bar, false, 0, 0); + q = qc.first; + BOOST_CHECK(q); + BOOST_CHECK_EQUAL(true, qc.second); + BOOST_CHECK_EQUAL(bar, q->getName()); +} + +QPID_AUTO_TEST_CASE(testDeclareTmp) +{ + QueueRegistry reg; + std::pair<Queue::shared_ptr, bool> qc; + + qc = reg.declare(std::string(), false, 0, 0); + BOOST_CHECK(qc.second); + BOOST_CHECK_EQUAL(std::string("tmp_1"), qc.first->getName()); +} + +QPID_AUTO_TEST_CASE(testFind) +{ + std::string foo("foo"); + std::string bar("bar"); + QueueRegistry reg; + std::pair<Queue::shared_ptr, bool> qc; + + BOOST_CHECK(reg.find(foo) == 0); + + reg.declare(foo, false, 0, 0); + reg.declare(bar, false, 0, 0); + Queue::shared_ptr q = reg.find(bar); + BOOST_CHECK(q); + BOOST_CHECK_EQUAL(bar, q->getName()); +} + +QPID_AUTO_TEST_CASE(testDestroy) +{ + std::string foo("foo"); + QueueRegistry reg; + std::pair<Queue::shared_ptr, bool> qc; + + qc = reg.declare(foo, false, 0, 0); + reg.destroy(foo); + // Queue is gone from the registry. + BOOST_CHECK(reg.find(foo) == 0); + // Queue is not actually destroyed till we drop our reference. + BOOST_CHECK_EQUAL(foo, qc.first->getName()); + // We shoud be the only reference. + BOOST_CHECK_EQUAL(1L, qc.first.use_count()); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/QueueTest.cpp b/qpid/cpp/src/tests/QueueTest.cpp new file mode 100644 index 0000000000..34e4592a15 --- /dev/null +++ b/qpid/cpp/src/tests/QueueTest.cpp @@ -0,0 +1,1124 @@ + /* + * + * 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 "MessageUtils.h" +#include "unit_test.h" +#include "test_tools.h" +#include "qpid/Exception.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/DeliverableMessage.h" +#include "qpid/broker/FanOutExchange.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/Deliverable.h" +#include "qpid/broker/ExchangeRegistry.h" +#include "qpid/broker/QueueRegistry.h" +#include "qpid/broker/NullMessageStore.h" +#include "qpid/broker/ExpiryPolicy.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/client/QueueOptions.h" +#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" + +using boost::intrusive_ptr; +using namespace qpid; +using namespace qpid::broker; +using namespace qpid::client; +using namespace qpid::framing; +using namespace qpid::sys; + +namespace qpid { +namespace tests { + +class TestConsumer : public virtual Consumer{ +public: + typedef boost::shared_ptr<TestConsumer> shared_ptr; + + intrusive_ptr<Message> last; + bool received; + TestConsumer(bool acquire = true):Consumer(acquire), received(false) {}; + + virtual bool deliver(QueuedMessage& msg){ + last = msg.payload; + received = true; + return true; + }; + void notify() {} + OwnershipToken* getSession() { return 0; } +}; + +class FailOnDeliver : public Deliverable +{ + boost::intrusive_ptr<Message> msg; +public: + FailOnDeliver() : msg(MessageUtils::createMessage()) {} + void deliverTo(const boost::shared_ptr<Queue>& queue) + { + throw Exception(QPID_MSG("Invalid delivery to " << queue->getName())); + } + Message& getMessage() { return *(msg.get()); } +}; + +intrusive_ptr<Message> create_message(std::string exchange, std::string routingKey) { + 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); + return msg; +} + +QPID_AUTO_TEST_SUITE(QueueTestSuite) + +QPID_AUTO_TEST_CASE(testAsyncMessage) { + Queue::shared_ptr queue(new Queue("my_test_queue", true)); + intrusive_ptr<Message> received; + + TestConsumer::shared_ptr c1(new TestConsumer()); + queue->consume(c1); + + + //Test basic delivery: + intrusive_ptr<Message> msg1 = create_message("e", "A"); + msg1->enqueueAsync(queue, 0);//this is done on enqueue which is not called from process + queue->process(msg1); + sleep(2); + + BOOST_CHECK(!c1->received); + msg1->enqueueComplete(); + + received = queue->get().payload; + BOOST_CHECK_EQUAL(msg1.get(), received.get()); +} + + +QPID_AUTO_TEST_CASE(testAsyncMessageCount){ + Queue::shared_ptr queue(new Queue("my_test_queue", true)); + intrusive_ptr<Message> msg1 = create_message("e", "A"); + msg1->enqueueAsync(queue, 0);//this is done on enqueue which is not called from process + + queue->process(msg1); + sleep(2); + uint32_t compval=0; + BOOST_CHECK_EQUAL(compval, queue->getEnqueueCompleteMessageCount()); + msg1->enqueueComplete(); + compval=1; + BOOST_CHECK_EQUAL(compval, queue->getEnqueueCompleteMessageCount()); + BOOST_CHECK_EQUAL(compval, queue->getMessageCount()); +} + +QPID_AUTO_TEST_CASE(testConsumers){ + Queue::shared_ptr queue(new Queue("my_queue", true)); + + //Test adding consumers: + TestConsumer::shared_ptr c1(new TestConsumer()); + TestConsumer::shared_ptr c2(new TestConsumer()); + queue->consume(c1); + queue->consume(c2); + + BOOST_CHECK_EQUAL(uint32_t(2), queue->getConsumerCount()); + + //Test basic delivery: + intrusive_ptr<Message> msg1 = create_message("e", "A"); + intrusive_ptr<Message> msg2 = create_message("e", "B"); + intrusive_ptr<Message> msg3 = create_message("e", "C"); + + queue->deliver(msg1); + BOOST_CHECK(queue->dispatch(c1)); + BOOST_CHECK_EQUAL(msg1.get(), c1->last.get()); + + queue->deliver(msg2); + BOOST_CHECK(queue->dispatch(c2)); + BOOST_CHECK_EQUAL(msg2.get(), c2->last.get()); + + c1->received = false; + queue->deliver(msg3); + BOOST_CHECK(queue->dispatch(c1)); + BOOST_CHECK_EQUAL(msg3.get(), c1->last.get()); + + //Test cancellation: + queue->cancel(c1); + BOOST_CHECK_EQUAL(uint32_t(1), queue->getConsumerCount()); + queue->cancel(c2); + BOOST_CHECK_EQUAL(uint32_t(0), queue->getConsumerCount()); +} + +QPID_AUTO_TEST_CASE(testRegistry){ + //Test use of queues in registry: + QueueRegistry registry; + registry.declare("queue1", true, true); + registry.declare("queue2", true, true); + registry.declare("queue3", true, true); + + BOOST_CHECK(registry.find("queue1")); + BOOST_CHECK(registry.find("queue2")); + BOOST_CHECK(registry.find("queue3")); + + registry.destroy("queue1"); + registry.destroy("queue2"); + registry.destroy("queue3"); + + BOOST_CHECK(!registry.find("queue1")); + BOOST_CHECK(!registry.find("queue2")); + BOOST_CHECK(!registry.find("queue3")); +} + +QPID_AUTO_TEST_CASE(testDequeue){ + 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"); + intrusive_ptr<Message> received; + + queue->deliver(msg1); + queue->deliver(msg2); + queue->deliver(msg3); + + BOOST_CHECK_EQUAL(uint32_t(3), queue->getMessageCount()); + + received = queue->get().payload; + BOOST_CHECK_EQUAL(msg1.get(), received.get()); + BOOST_CHECK_EQUAL(uint32_t(2), queue->getMessageCount()); + + received = queue->get().payload; + BOOST_CHECK_EQUAL(msg2.get(), received.get()); + BOOST_CHECK_EQUAL(uint32_t(1), queue->getMessageCount()); + + TestConsumer::shared_ptr consumer(new TestConsumer()); + queue->consume(consumer); + queue->dispatch(consumer); + if (!consumer->received) + sleep(2); + + BOOST_CHECK_EQUAL(msg3.get(), consumer->last.get()); + BOOST_CHECK_EQUAL(uint32_t(0), queue->getMessageCount()); + + received = queue->get().payload; + BOOST_CHECK(!received); + BOOST_CHECK_EQUAL(uint32_t(0), queue->getMessageCount()); + +} + +QPID_AUTO_TEST_CASE(testBound){ + //test the recording of bindings, and use of those to allow a queue to be unbound + string key("my-key"); + FieldTable args; + + Queue::shared_ptr queue(new Queue("my-queue", true)); + ExchangeRegistry exchanges; + //establish bindings from exchange->queue and notify the queue as it is bound: + Exchange::shared_ptr exchange1 = exchanges.declare("my-exchange-1", "direct").first; + exchange1->bind(queue, key, &args); + queue->bound(exchange1->getName(), key, args); + + Exchange::shared_ptr exchange2 = exchanges.declare("my-exchange-2", "fanout").first; + exchange2->bind(queue, key, &args); + queue->bound(exchange2->getName(), key, args); + + Exchange::shared_ptr exchange3 = exchanges.declare("my-exchange-3", "topic").first; + exchange3->bind(queue, key, &args); + queue->bound(exchange3->getName(), key, args); + + //delete one of the exchanges: + exchanges.destroy(exchange2->getName()); + exchange2.reset(); + + //unbind the queue from all exchanges it knows it has been bound to: + queue->unbind(exchanges); + + //ensure the remaining exchanges don't still have the queue bound to them: + FailOnDeliver deliverable; + exchange1->route(deliverable, key, &args); + exchange3->route(deliverable, key, &args); +} + +QPID_AUTO_TEST_CASE(testPersistLastNodeStanding){ + client::QueueOptions args; + args.setPersistLastNode(); + + 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 + queue->deliver(msg1); + queue->deliver(msg2); + + //change mode + queue->setLastNodeFailure(); + + //enqueue 1 message + queue->deliver(msg3); + + //check all have persistent ids. + BOOST_CHECK(msg1->isPersistent()); + BOOST_CHECK(msg2->isPersistent()); + BOOST_CHECK(msg3->isPersistent()); + +} + + +QPID_AUTO_TEST_CASE(testSeek){ + + 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 + queue->deliver(msg1); + queue->deliver(msg2); + queue->deliver(msg3); + + TestConsumer::shared_ptr consumer(new TestConsumer(false)); + SequenceNumber seq(2); + consumer->position = seq; + + QueuedMessage qm; + queue->dispatch(consumer); + + BOOST_CHECK_EQUAL(msg3.get(), consumer->last.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)); + + 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 + queue->deliver(msg1); + queue->deliver(msg2); + queue->deliver(msg3); + + SequenceNumber seq(2); + QueuedMessage qm = queue->find(seq); + + BOOST_CHECK_EQUAL(seq.getValue(), qm.position.getValue()); + + queue->acquire(qm); + BOOST_CHECK_EQUAL(queue->getMessageCount(), 2u); + SequenceNumber seq1(3); + QueuedMessage qm1 = queue->find(seq1); + BOOST_CHECK_EQUAL(seq1.getValue(), qm1.position.getValue()); + +} +const std::string nullxid = ""; + +class SimpleDummyCtxt : public TransactionContext {}; + +class DummyCtxt : public TPCTransactionContext +{ + const std::string xid; + public: + DummyCtxt(const std::string& _xid) : xid(_xid) {} + static std::string getXid(TransactionContext& ctxt) + { + DummyCtxt* c(dynamic_cast<DummyCtxt*>(&ctxt)); + return c ? c->xid : nullxid; + } +}; + +class TestMessageStoreOC : public MessageStore +{ + std::set<std::string> prepared; + uint64_t nextPersistenceId; + public: + + uint enqCnt; + uint deqCnt; + bool error; + + TestMessageStoreOC() : MessageStore(),nextPersistenceId(1),enqCnt(0),deqCnt(0),error(false) {} + ~TestMessageStoreOC(){} + + virtual void dequeue(TransactionContext*, + const boost::intrusive_ptr<PersistableMessage>& /*msg*/, + const PersistableQueue& /*queue*/) + { + if (error) throw Exception("Dequeue error test"); + deqCnt++; + } + + virtual void enqueue(TransactionContext*, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& /* queue */) + { + if (error) throw Exception("Enqueue error test"); + enqCnt++; + msg->enqueueComplete(); + } + + void createError() + { + error=true; + } + + bool init(const Options*) { return true; } + void truncateInit(const bool) {} + void create(PersistableQueue& queue, const framing::FieldTable&) { queue.setPersistenceId(nextPersistenceId++); } + void destroy(PersistableQueue&) {} + void create(const PersistableExchange& exchange, const framing::FieldTable&) { exchange.setPersistenceId(nextPersistenceId++); } + void destroy(const PersistableExchange&) {} + void bind(const PersistableExchange&, const PersistableQueue&, const std::string&, const framing::FieldTable&) {} + void unbind(const PersistableExchange&, const PersistableQueue&, const std::string&, const framing::FieldTable&) {} + void create(const PersistableConfig& config) { config.setPersistenceId(nextPersistenceId++); } + void destroy(const PersistableConfig&) {} + void stage(const boost::intrusive_ptr<PersistableMessage>&) {} + void destroy(PersistableMessage&) {} + void appendContent(const boost::intrusive_ptr<const PersistableMessage>&, const std::string&) {} + void loadContent(const qpid::broker::PersistableQueue&, const boost::intrusive_ptr<const PersistableMessage>&, + std::string&, uint64_t, uint32_t) { throw qpid::framing::InternalErrorException("Can't load content; persistence not enabled"); } + void flush(const qpid::broker::PersistableQueue&) {} + uint32_t outstandingQueueAIO(const PersistableQueue&) { return 0; } + + std::auto_ptr<TransactionContext> begin() { return std::auto_ptr<TransactionContext>(new SimpleDummyCtxt()); } + std::auto_ptr<TPCTransactionContext> begin(const std::string& xid) { return std::auto_ptr<TPCTransactionContext>(new DummyCtxt(xid)); } + void prepare(TPCTransactionContext& ctxt) { prepared.insert(DummyCtxt::getXid(ctxt)); } + void commit(TransactionContext& ctxt) { prepared.erase(DummyCtxt::getXid(ctxt)); } + void abort(TransactionContext& ctxt) { prepared.erase(DummyCtxt::getXid(ctxt)); } + void collectPreparedXids(std::set<std::string>& out) { out.insert(prepared.begin(), prepared.end()); } + + void recover(RecoveryManager&) {} +}; + + +QPID_AUTO_TEST_CASE(testLVQOrdering){ + + client::QueueOptions args; + // set queue mode + args.setOrdering(client::LVQ); + + 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"); + intrusive_ptr<Message> msg4 = create_message("e", "D"); + intrusive_ptr<Message> received; + + //set deliever match for LVQ a,b,c,a + + string 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"); + + //enqueue 4 message + queue->deliver(msg1); + queue->deliver(msg2); + queue->deliver(msg3); + queue->deliver(msg4); + + BOOST_CHECK_EQUAL(queue->getMessageCount(), 3u); + + received = queue->get().payload; + BOOST_CHECK_EQUAL(msg4.get(), received.get()); + + received = queue->get().payload; + BOOST_CHECK_EQUAL(msg2.get(), received.get()); + + received = queue->get().payload; + BOOST_CHECK_EQUAL(msg3.get(), received.get()); + + 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"); + queue->deliver(msg5); + queue->deliver(msg6); + queue->deliver(msg7); + + BOOST_CHECK_EQUAL(queue->getMessageCount(), 3u); + + received = queue->get().payload; + BOOST_CHECK_EQUAL(msg5.get(), received.get()); + + received = queue->get().payload; + BOOST_CHECK_EQUAL(msg6.get(), received.get()); + + received = queue->get().payload; + BOOST_CHECK_EQUAL(msg7.get(), received.get()); + +} + +QPID_AUTO_TEST_CASE(testLVQEmptyKey){ + + client::QueueOptions args; + // set queue mode + args.setOrdering(client::LVQ); + + 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"); + + string key; + args.getLVQKey(key); + BOOST_CHECK_EQUAL(key, "qpid.LVQ_key"); + + + msg1->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"a"); + queue->deliver(msg1); + queue->deliver(msg2); + BOOST_CHECK_EQUAL(queue->getMessageCount(), 2u); + +} + +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); + + intrusive_ptr<Message> msg1 = create_message("e", "A"); + intrusive_ptr<Message> msg2 = create_message("e", "B"); + intrusive_ptr<Message> msg3 = create_message("e", "C"); + intrusive_ptr<Message> msg4 = create_message("e", "D"); + intrusive_ptr<Message> msg5 = create_message("e", "F"); + intrusive_ptr<Message> msg6 = create_message("e", "G"); + + //set deliever match for LVQ a,b,c,a + + string 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"); + msg5->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"b"); + msg6->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"c"); + + //enqueue 4 message + queue->deliver(msg1); + queue->deliver(msg2); + queue->deliver(msg3); + queue->deliver(msg4); + + BOOST_CHECK_EQUAL(queue->getMessageCount(), 3u); + + framing::SequenceNumber sequence(1); + QueuedMessage qmsg(queue.get(), msg1, sequence); + QueuedMessage qmsg2(queue.get(), msg2, ++sequence); + framing::SequenceNumber sequence1(10); + QueuedMessage qmsg3(queue.get(), 0, sequence1); + + BOOST_CHECK(!queue->acquire(qmsg)); + BOOST_CHECK(queue->acquire(qmsg2)); + // Acquire the massage again to test failure case. + BOOST_CHECK(!queue->acquire(qmsg2)); + BOOST_CHECK(!queue->acquire(qmsg3)); + + BOOST_CHECK_EQUAL(queue->getMessageCount(), 2u); + + queue->deliver(msg5); + BOOST_CHECK_EQUAL(queue->getMessageCount(), 3u); + + // set mode to no browse and check + args.setOrdering(client::LVQ_NO_BROWSE); + queue->configure(args); + TestConsumer::shared_ptr c1(new TestConsumer(false)); + + queue->dispatch(c1); + queue->dispatch(c1); + queue->dispatch(c1); + + queue->deliver(msg6); + BOOST_CHECK_EQUAL(queue->getMessageCount(), 3u); + + intrusive_ptr<Message> received; + received = queue->get().payload; + BOOST_CHECK_EQUAL(msg4.get(), received.get()); + +} + +QPID_AUTO_TEST_CASE(testLVQMultiQueue){ + + client::QueueOptions args; + // set queue mode + args.setOrdering(client::LVQ); + + Queue::shared_ptr queue1(new Queue("my-queue", true )); + Queue::shared_ptr queue2(new Queue("my-queue", true )); + intrusive_ptr<Message> received; + queue1->configure(args); + queue2->configure(args); + + intrusive_ptr<Message> msg1 = create_message("e", "A"); + intrusive_ptr<Message> msg2 = create_message("e", "A"); + + string key; + args.getLVQKey(key); + BOOST_CHECK_EQUAL(key, "qpid.LVQ_key"); + + msg1->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"a"); + msg2->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"a"); + + queue1->deliver(msg1); + queue2->deliver(msg1); + queue1->deliver(msg2); + + received = queue1->get().payload; + BOOST_CHECK_EQUAL(msg2.get(), received.get()); + + received = queue2->get().payload; + BOOST_CHECK_EQUAL(msg1.get(), received.get()); + +} + +QPID_AUTO_TEST_CASE(testLVQRecover){ + +/* simulate this + 1. start 2 nodes + 2. create cluster durable lvq + 3. send a transient message to the queue + 4. kill one of the nodes (to trigger force persistent behaviour)... + 5. then restart it (to turn off force persistent behaviour) + 6. send another transient message with same lvq key as in 3 + 7. kill the second node again (retrigger force persistent) + 8. stop and recover the first node +*/ + TestMessageStoreOC testStore; + client::QueueOptions args; + // set queue mode + args.setOrdering(client::LVQ); + args.setPersistLastNode(); + + Queue::shared_ptr queue1(new Queue("my-queue", true, &testStore)); + intrusive_ptr<Message> received; + queue1->create(args); + + intrusive_ptr<Message> msg1 = create_message("e", "A"); + intrusive_ptr<Message> msg2 = create_message("e", "A"); + // 2 + string key; + 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 + queue1->deliver(msg1); + // 4 + queue1->setLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 1u); + // 5 + queue1->clearLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 1u); + // 6 + queue1->deliver(msg2); + BOOST_CHECK_EQUAL(testStore.enqCnt, 1u); + queue1->setLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 2u); + BOOST_CHECK_EQUAL(testStore.deqCnt, 1u); +} + +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); + queue.deliver(m); + } +} + +QPID_AUTO_TEST_CASE(testPurgeExpired) { + Queue queue("my-queue"); + addMessagesToQueue(10, queue); + BOOST_CHECK_EQUAL(queue.getMessageCount(), 10u); + ::usleep(300*1000); + queue.purgeExpired(); + BOOST_CHECK_EQUAL(queue.getMessageCount(), 5u); +} + +QPID_AUTO_TEST_CASE(testQueueCleaner) { + Timer timer; + QueueRegistry queues; + Queue::shared_ptr queue = queues.declare("my-queue").first; + addMessagesToQueue(10, *queue, 200, 400); + BOOST_CHECK_EQUAL(queue->getMessageCount(), 10u); + + QueueCleaner cleaner(queues, timer); + cleaner.start(100 * qpid::sys::TIME_MSEC); + ::usleep(300*1000); + BOOST_CHECK_EQUAL(queue->getMessageCount(), 5u); + ::usleep(300*1000); + BOOST_CHECK_EQUAL(queue->getMessageCount(), 0u); +} + +QPID_AUTO_TEST_CASE(testMultiQueueLastNode){ + + TestMessageStoreOC testStore; + client::QueueOptions args; + args.setPersistLastNode(); + + Queue::shared_ptr queue1(new Queue("queue1", true, &testStore )); + queue1->create(args); + Queue::shared_ptr queue2(new Queue("queue2", true, &testStore )); + queue2->create(args); + + intrusive_ptr<Message> msg1 = create_message("e", "A"); + + queue1->deliver(msg1); + queue2->deliver(msg1); + + //change mode + queue1->setLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 1u); + queue2->setLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 2u); + + // check they don't get stored twice + queue1->setLastNodeFailure(); + queue2->setLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 2u); + + intrusive_ptr<Message> msg2 = create_message("e", "B"); + queue1->deliver(msg2); + queue2->deliver(msg2); + + queue1->clearLastNodeFailure(); + queue2->clearLastNodeFailure(); + // check only new messages get forced + queue1->setLastNodeFailure(); + queue2->setLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 4u); + + // check no failure messages are stored + queue1->clearLastNodeFailure(); + queue2->clearLastNodeFailure(); + + intrusive_ptr<Message> msg3 = create_message("e", "B"); + queue1->deliver(msg3); + queue2->deliver(msg3); + BOOST_CHECK_EQUAL(testStore.enqCnt, 4u); + queue1->setLastNodeFailure(); + queue2->setLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 6u); + + // check requeue 1 + intrusive_ptr<Message> msg4 = create_message("e", "C"); + intrusive_ptr<Message> msg5 = create_message("e", "D"); + + framing::SequenceNumber sequence(1); + QueuedMessage qmsg1(queue1.get(), msg4, sequence); + QueuedMessage qmsg2(queue2.get(), msg5, ++sequence); + + queue1->requeue(qmsg1); + BOOST_CHECK_EQUAL(testStore.enqCnt, 7u); + + // check requeue 2 + queue2->clearLastNodeFailure(); + queue2->requeue(qmsg2); + BOOST_CHECK_EQUAL(testStore.enqCnt, 7u); + queue2->setLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 8u); + + queue2->clearLastNodeFailure(); + queue2->setLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 8u); +} + +QPID_AUTO_TEST_CASE(testLastNodeRecoverAndFail){ +/* +simulate this: + 1. start two nodes + 2. create cluster durable queue and add some messages + 3. kill one node (trigger force-persistent behaviour) + 4. stop and recover remaining node + 5. add another node + 6. kill that new node again +make sure that an attempt to re-enqueue a message does not happen which will +result in the last man standing exiting with an error. + +we need to make sure that recover is safe, i.e. messages are +not requeued to the store. +*/ + TestMessageStoreOC testStore; + client::QueueOptions args; + // set queue mode + args.setPersistLastNode(); + + Queue::shared_ptr queue1(new Queue("my-queue", true, &testStore)); + intrusive_ptr<Message> received; + queue1->create(args); + + // check requeue 1 + intrusive_ptr<Message> msg1 = create_message("e", "C"); + intrusive_ptr<Message> msg2 = create_message("e", "D"); + + queue1->recover(msg1); + + queue1->setLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 0u); + + queue1->clearLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 0u); + + queue1->deliver(msg2); + BOOST_CHECK_EQUAL(testStore.enqCnt, 0u); + queue1->setLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 1u); + +} + +QPID_AUTO_TEST_CASE(testLastNodeJournalError){ +/* +simulate store exception going into last node standing + +*/ + TestMessageStoreOC testStore; + client::QueueOptions args; + // set queue mode + args.setPersistLastNode(); + + Queue::shared_ptr queue1(new Queue("my-queue", true, &testStore)); + intrusive_ptr<Message> received; + queue1->configure(args); + + // check requeue 1 + intrusive_ptr<Message> msg1 = create_message("e", "C"); + + queue1->deliver(msg1); + testStore.createError(); + + ScopedSuppressLogging sl; // Suppress messages for expected errors. + queue1->setLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 0u); + +} + +intrusive_ptr<Message> mkMsg(MessageStore& store, std::string content = "", bool durable = false) +{ + intrusive_ptr<Message> msg = MessageUtils::createMessage("", "", durable); + if (content.size()) MessageUtils::addContent(msg, content); + msg->setStore(&store); + return msg; +} + +QPID_AUTO_TEST_CASE(testFlowToDiskBlocking){ + + TestMessageStoreOC testStore; + client::QueueOptions args0; // No size policy + client::QueueOptions args1; + args1.setSizePolicy(FLOW_TO_DISK, 0, 1); + client::QueueOptions args2; + args2.setSizePolicy(FLOW_TO_DISK, 0, 2); + + // --- Fanout exchange bound to single transient queue ------------------------------------------------------------- + + FanOutExchange sbtFanout1("sbtFanout1", false, args0); // single binding to transient queue + Queue::shared_ptr tq1(new Queue("tq1", true)); // transient w/ limit + tq1->configure(args1); + sbtFanout1.bind(tq1, "", 0); + + intrusive_ptr<Message> msg01 = mkMsg(testStore, std::string(5, 'X')); // transient w/ content + DeliverableMessage dmsg01(msg01); + sbtFanout1.route(dmsg01, "", 0); // Brings queue 1 to capacity limit + msg01->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg01->isContentReleased(), false); + BOOST_CHECK_EQUAL(1u, tq1->getMessageCount()); + + intrusive_ptr<Message> msg02 = mkMsg(testStore, std::string(5, 'X')); // transient w/ content + DeliverableMessage dmsg02(msg02); + { + 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); + { + 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); + { + 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); + { + 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()); + + // --- Fanout exchange bound to single durable queue --------------------------------------------------------------- + + FanOutExchange sbdFanout2("sbdFanout2", false, args0); // single binding to durable queue + Queue::shared_ptr dq2(new Queue("dq2", true, &testStore)); // durable w/ limit + dq2->configure(args1); + sbdFanout2.bind(dq2, "", 0); + + intrusive_ptr<Message> msg06 = mkMsg(testStore, std::string(5, 'X')); // transient w/ content + DeliverableMessage dmsg06(msg06); + sbdFanout2.route(dmsg06, "", 0); // Brings queue 2 to capacity limit + msg06->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg06->isContentReleased(), false); + BOOST_CHECK_EQUAL(1u, dq2->getMessageCount()); + + intrusive_ptr<Message> msg07 = mkMsg(testStore, std::string(5, 'X')); // transient w/ content + DeliverableMessage dmsg07(msg07); + sbdFanout2.route(dmsg07, "", 0); + msg07->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg07->isContentReleased(), true); + BOOST_CHECK_EQUAL(2u, dq2->getMessageCount()); + + intrusive_ptr<Message> msg08 = mkMsg(testStore, std::string(5, 'X'), true); // durable w/ content + DeliverableMessage dmsg08(msg08); + sbdFanout2.route(dmsg08, "", 0); + msg08->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg08->isContentReleased(), true); + BOOST_CHECK_EQUAL(3u, dq2->getMessageCount()); + + intrusive_ptr<Message> msg09 = mkMsg(testStore); // transient no content + DeliverableMessage dmsg09(msg09); + sbdFanout2.route(dmsg09, "", 0); + msg09->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg09->isContentReleased(), true); + BOOST_CHECK_EQUAL(4u, dq2->getMessageCount()); + + intrusive_ptr<Message> msg10 = mkMsg(testStore, "", true); // durable no content + DeliverableMessage dmsg10(msg10); + sbdFanout2.route(dmsg10, "", 0); + msg10->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg10->isContentReleased(), true); + BOOST_CHECK_EQUAL(5u, dq2->getMessageCount()); + + // --- Fanout exchange bound to multiple durable queues ------------------------------------------------------------ + + FanOutExchange mbdFanout3("mbdFanout3", false, args0); // multiple bindings to durable queues + Queue::shared_ptr dq3(new Queue("dq3", true, &testStore)); // durable w/ limit 2 + dq3->configure(args2); + mbdFanout3.bind(dq3, "", 0); + Queue::shared_ptr dq4(new Queue("dq4", true, &testStore)); // durable w/ limit 1 + dq4->configure(args1); + mbdFanout3.bind(dq4, "", 0); + Queue::shared_ptr dq5(new Queue("dq5", true, &testStore)); // durable no limit + dq5->configure(args0); + mbdFanout3.bind(dq5, "", 0); + + intrusive_ptr<Message> msg11 = mkMsg(testStore, std::string(5, 'X')); // transient w/ content + DeliverableMessage dmsg11(msg11); + mbdFanout3.route(dmsg11, "", 0); // Brings queues 3 and 4 to capacity limit + msg11->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg11->isContentReleased(), false); + BOOST_CHECK_EQUAL(1u, dq3->getMessageCount()); + BOOST_CHECK_EQUAL(1u, dq4->getMessageCount()); + BOOST_CHECK_EQUAL(1u, dq5->getMessageCount()); + + intrusive_ptr<Message> msg12 = mkMsg(testStore, std::string(5, 'X')); // transient w/ content + DeliverableMessage dmsg12(msg12); + mbdFanout3.route(dmsg12, "", 0); + msg12->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg12->isContentReleased(), false); // XXXX - consequence of transient msg multi-queue ftd policy-handling limitations, fix in broker at some point! + BOOST_CHECK_EQUAL(2u, dq3->getMessageCount()); + BOOST_CHECK_EQUAL(2u, dq4->getMessageCount()); + BOOST_CHECK_EQUAL(2u, dq5->getMessageCount()); + + intrusive_ptr<Message> msg13 = mkMsg(testStore, std::string(5, 'X'), true); // durable w/ content + DeliverableMessage dmsg13(msg13); + mbdFanout3.route(dmsg13, "", 0); + msg13->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg13->isContentReleased(), true); + BOOST_CHECK_EQUAL(3u, dq3->getMessageCount()); + BOOST_CHECK_EQUAL(3u, dq4->getMessageCount()); + BOOST_CHECK_EQUAL(3u, dq5->getMessageCount()); + + intrusive_ptr<Message> msg14 = mkMsg(testStore); // transient no content + DeliverableMessage dmsg14(msg14); + mbdFanout3.route(dmsg14, "", 0); + msg14->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg14->isContentReleased(), false); // XXXX - consequence of transient msg multi-queue ftd policy-handling limitations, fix in broker at some point! + BOOST_CHECK_EQUAL(4u, dq3->getMessageCount()); + BOOST_CHECK_EQUAL(4u, dq4->getMessageCount()); + BOOST_CHECK_EQUAL(4u, dq5->getMessageCount()); + + intrusive_ptr<Message> msg15 = mkMsg(testStore, "", true); // durable no content + DeliverableMessage dmsg15(msg15); + mbdFanout3.route(dmsg15, "", 0); + msg15->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg15->isContentReleased(), true); + BOOST_CHECK_EQUAL(5u, dq3->getMessageCount()); + BOOST_CHECK_EQUAL(5u, dq4->getMessageCount()); + BOOST_CHECK_EQUAL(5u, dq5->getMessageCount()); + + // Bind a transient queue, this should block the release of any further messages. + // Note: this will result in a violation of the count policy of dq3 and dq4 - but this + // is expected until a better overall multi-queue design is implemented. Similarly + // for the other tests in this section. + + Queue::shared_ptr tq6(new Queue("tq6", true)); // transient no limit + tq6->configure(args0); + mbdFanout3.bind(tq6, "", 0); + + intrusive_ptr<Message> msg16 = mkMsg(testStore, std::string(5, 'X')); // transient w/ content + DeliverableMessage dmsg16(msg16); + mbdFanout3.route(dmsg16, "", 0); + msg16->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg16->isContentReleased(), false); + BOOST_CHECK_EQUAL(6u, dq3->getMessageCount()); + BOOST_CHECK_EQUAL(6u, dq4->getMessageCount()); + BOOST_CHECK_EQUAL(6u, dq5->getMessageCount()); + + intrusive_ptr<Message> msg17 = mkMsg(testStore, std::string(5, 'X'), true); // durable w/ content + DeliverableMessage dmsg17(msg17); + mbdFanout3.route(dmsg17, "", 0); + msg17->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg17->isContentReleased(), false); + BOOST_CHECK_EQUAL(7u, dq3->getMessageCount()); + BOOST_CHECK_EQUAL(7u, dq4->getMessageCount()); + BOOST_CHECK_EQUAL(7u, dq5->getMessageCount()); + + intrusive_ptr<Message> msg18 = mkMsg(testStore); // transient no content + DeliverableMessage dmsg18(msg18); + mbdFanout3.route(dmsg18, "", 0); + msg18->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg18->isContentReleased(), false); + BOOST_CHECK_EQUAL(8u, dq3->getMessageCount()); + BOOST_CHECK_EQUAL(8u, dq4->getMessageCount()); + BOOST_CHECK_EQUAL(8u, dq5->getMessageCount()); + + intrusive_ptr<Message> msg19 = mkMsg(testStore, "", true); // durable no content + DeliverableMessage dmsg19(msg19); + mbdFanout3.route(dmsg19, "", 0); + msg19->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg19->isContentReleased(), false); + BOOST_CHECK_EQUAL(9u, dq3->getMessageCount()); + BOOST_CHECK_EQUAL(9u, dq4->getMessageCount()); + BOOST_CHECK_EQUAL(9u, dq5->getMessageCount()); + + + // --- Fanout exchange bound to multiple durable and transient queues ---------------------------------------------- + + FanOutExchange mbmFanout4("mbmFanout4", false, args0); // multiple bindings to durable/transient queues + Queue::shared_ptr dq7(new Queue("dq7", true, &testStore)); // durable no limit + dq7->configure(args0); + mbmFanout4.bind(dq7, "", 0); + Queue::shared_ptr dq8(new Queue("dq8", true, &testStore)); // durable w/ limit + dq8->configure(args1); + mbmFanout4.bind(dq8, "", 0); + Queue::shared_ptr tq9(new Queue("tq9", true)); // transient no limit + tq9->configure(args0); + mbmFanout4.bind(tq9, "", 0); + + intrusive_ptr<Message> msg20 = mkMsg(testStore, std::string(5, 'X')); // transient w/ content + DeliverableMessage dmsg20(msg20); + mbmFanout4.route(dmsg20, "", 0); // Brings queue 7 to capacity limit + msg20->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg20->isContentReleased(), false); + BOOST_CHECK_EQUAL(1u, dq7->getMessageCount()); + BOOST_CHECK_EQUAL(1u, dq8->getMessageCount()); + BOOST_CHECK_EQUAL(1u, tq9->getMessageCount()); + + intrusive_ptr<Message> msg21 = mkMsg(testStore, std::string(5, 'X')); // transient w/ content + DeliverableMessage dmsg21(msg21); + mbmFanout4.route(dmsg21, "", 0); + msg21->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg21->isContentReleased(), false); + BOOST_CHECK_EQUAL(2u, dq7->getMessageCount()); // over limit + BOOST_CHECK_EQUAL(2u, dq8->getMessageCount()); + BOOST_CHECK_EQUAL(2u, tq9->getMessageCount()); + + intrusive_ptr<Message> msg22 = mkMsg(testStore, std::string(5, 'X'), true); // durable w/ content + DeliverableMessage dmsg22(msg22); + mbmFanout4.route(dmsg22, "", 0); + msg22->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg22->isContentReleased(), false); + BOOST_CHECK_EQUAL(3u, dq7->getMessageCount()); // over limit + BOOST_CHECK_EQUAL(3u, dq8->getMessageCount()); // over limit + BOOST_CHECK_EQUAL(3u, tq9->getMessageCount()); + + intrusive_ptr<Message> msg23 = mkMsg(testStore); // transient no content + DeliverableMessage dmsg23(msg23); + mbmFanout4.route(dmsg23, "", 0); + msg23->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg23->isContentReleased(), false); + BOOST_CHECK_EQUAL(4u, dq7->getMessageCount()); // over limit + BOOST_CHECK_EQUAL(4u, dq8->getMessageCount()); // over limit + BOOST_CHECK_EQUAL(4u, tq9->getMessageCount()); + + intrusive_ptr<Message> msg24 = mkMsg(testStore, "", true); // durable no content + DeliverableMessage dmsg24(msg24); + mbmFanout4.route(dmsg24, "", 0); + msg24->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg24->isContentReleased(), false); + BOOST_CHECK_EQUAL(5u, dq7->getMessageCount()); // over limit + BOOST_CHECK_EQUAL(5u, dq8->getMessageCount()); // over limit + BOOST_CHECK_EQUAL(5u, tq9->getMessageCount()); +} + + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/README.txt b/qpid/cpp/src/tests/README.txt new file mode 100644 index 0000000000..0f4edee493 --- /dev/null +++ b/qpid/cpp/src/tests/README.txt @@ -0,0 +1,54 @@ += Running Qpid C++ tests = + +General philosophy is that "make check" run all tests by default, but +developers can run tests selectively as explained below. + +== Valgrind == + +By default we run tests under valgrind to detect memory errors if valgrind +is present. ./configure --disable-valgrind will disable it. + +Default valgrind options are specified in .valgrindrc-default, which a +checked-in file. The actual options used are in .valgrindrc which is a +local file. Normally it is a copy of valgrindrc-default but you can +modify at will. + +Supressed errors are listed in .valgrind.supp. If you want to change +suppressions for local testing, just modify .valgrindrc to point to a +different file. Do NOT add suppressions to .valgrindrc.supp unless +they are known problems outside of Qpid that can't reasonably be +worked around in Qpid. + + +== Unit Tests == +Unit tests use the boost test framework, and are compiled to rogram + unit_test +There are several options to control how test results are displayed, +see + http://www.boost.org/doc/libs/1_35_0/libs/test/doc/components/utf/parameters/index.html + +NOTE: some unit tests are written as CppUnit plug-ins, we are moving away +from CppUnit so new tests should use the boost framework. + +CppUnit tests are run by the script run-unit-tests. + +== System Tests == + +System tests are self contained AMQP client executables or scripts. +They are listed in the TESTS make variable, which can be over-ridden. + +The ./start_broker "test" launches the broker, ./stop_broker" stops it. +Tests in between assume the broker is running. + +./python_tests: runs ../python/run_tests. This is the main set of +system testss for the broker. + +Other C++ client test executables and scripts under client/test are +system tests for the client. + +By setting TESTS in a make command you can run a different subset of tests +against an already-running broker. + + + + diff --git a/qpid/cpp/src/tests/RangeSet.cpp b/qpid/cpp/src/tests/RangeSet.cpp new file mode 100644 index 0000000000..db3a964086 --- /dev/null +++ b/qpid/cpp/src/tests/RangeSet.cpp @@ -0,0 +1,146 @@ +/* + * + * 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. + * + */ + + +#include "unit_test.h" +#include "test_tools.h" +#include "qpid/RangeSet.h" + +using namespace std; +using namespace qpid; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(RangeSetTestSuite) + +typedef qpid::Range<int> TestRange; +typedef qpid::RangeSet<int> TestRangeSet; + +QPID_AUTO_TEST_CASE(testEmptyRange) { + TestRange r; + BOOST_CHECK(r.empty()); + BOOST_CHECK(!r.contains(0)); + // BOOST_CHECK(r.contiguous(0)); +} + +QPID_AUTO_TEST_CASE(testRangeSetAddPoint) { + TestRangeSet r; + BOOST_CHECK(r.empty()); + r += 3; + BOOST_CHECK_MESSAGE(r.contains(3), r); + BOOST_CHECK_MESSAGE(r.contains(TestRange(3,4)), r); + BOOST_CHECK(!r.empty()); + r += 5; + BOOST_CHECK_MESSAGE(r.contains(5), r); + BOOST_CHECK_MESSAGE(r.contains(TestRange(5,6)), r); + BOOST_CHECK_MESSAGE(!r.contains(TestRange(3,6)), r); + r += 4; + BOOST_CHECK_MESSAGE(r.contains(TestRange(3,6)), r); +} + +QPID_AUTO_TEST_CASE(testRangeSetAddRange) { + TestRangeSet r; + r += TestRange(0,3); + BOOST_CHECK(r.contains(TestRange(0,3))); + r += TestRange(4,6); + BOOST_CHECK_MESSAGE(r.contains(TestRange(4,6)), r); + r += 3; + BOOST_CHECK_MESSAGE(r.contains(TestRange(0,6)), r); + BOOST_CHECK(r.front() == 0); + BOOST_CHECK(r.back() == 6); +} + +QPID_AUTO_TEST_CASE(testRangeSetAddSet) { + TestRangeSet r; + TestRangeSet s = TestRangeSet(0,3)+TestRange(5,10); + r += s; + BOOST_CHECK_EQUAL(r,s); + r += TestRangeSet(3,5) + TestRange(7,12) + 15; + BOOST_CHECK_EQUAL(r, TestRangeSet(0,12) + 15); + + r.clear(); + BOOST_CHECK(r.empty()); + r += TestRange::makeClosed(6,10); + BOOST_CHECK_EQUAL(r, TestRangeSet(6,11)); + r += TestRangeSet(2,6)+8; + BOOST_CHECK_EQUAL(r, TestRangeSet(2,11)); +} + +QPID_AUTO_TEST_CASE(testRangeSetIterate) { + TestRangeSet r; + (((r += 1) += 10) += TestRange(4,7)) += 2; + BOOST_MESSAGE(r); + std::vector<int> actual; + std::copy(r.begin(), r.end(), std::back_inserter(actual)); + std::vector<int> expect = boost::assign::list_of(1)(2)(4)(5)(6)(10); + BOOST_CHECK_EQUAL(expect, actual); +} + +QPID_AUTO_TEST_CASE(testRangeSetRemove) { + // points + BOOST_CHECK_EQUAL(TestRangeSet(0,5)-3, TestRangeSet(0,3)+TestRange(4,5)); + BOOST_CHECK_EQUAL(TestRangeSet(1,5)-5, TestRangeSet(1,5)); + BOOST_CHECK_EQUAL(TestRangeSet(1,5)-0, TestRangeSet(1,5)); + + TestRangeSet r(TestRangeSet(0,5)+TestRange(10,15)+TestRange(20,25)); + + // TestRanges + BOOST_CHECK_EQUAL(r-TestRange(0,5), TestRangeSet(10,15)+TestRange(20,25)); + BOOST_CHECK_EQUAL(r-TestRange(10,15), TestRangeSet(0,5)+TestRange(20,25)); + BOOST_CHECK_EQUAL(r-TestRange(20,25), TestRangeSet(0,5)+TestRange(10,15)); + + BOOST_CHECK_EQUAL(r-TestRange(-5, 30), TestRangeSet()); + + BOOST_CHECK_EQUAL(r-TestRange(-5, 7), TestRangeSet(10,15)+TestRange(20,25)); + BOOST_CHECK_EQUAL(r-TestRange(8,19), TestRangeSet(0,5)+TestRange(20,25)); + BOOST_CHECK_EQUAL(r-TestRange(17,30), TestRangeSet(0,5)+TestRange(10,15)); + BOOST_CHECK_EQUAL(r-TestRange(17,30), TestRangeSet(0,5)+TestRange(10,15)); + + BOOST_CHECK_EQUAL(r-TestRange(-5, 5), TestRangeSet(10,15)+TestRange(20,25)); + BOOST_CHECK_EQUAL(r-TestRange(10,19), TestRangeSet(0,5)+TestRange(20,25)); + BOOST_CHECK_EQUAL(r-TestRange(18,25), TestRangeSet(0,5)+TestRange(10,15)); + BOOST_CHECK_EQUAL(r-TestRange(23,25), TestRangeSet(0,5)+TestRange(10,15)+TestRange(20,23)); + + BOOST_CHECK_EQUAL(r-TestRange(-3, 3), TestRangeSet(3,5)+TestRange(10,15)+TestRange(20,25)); + BOOST_CHECK_EQUAL(r-TestRange(3, 7), TestRangeSet(0,2)+TestRange(10,15)+TestRange(20,25)); + BOOST_CHECK_EQUAL(r-TestRange(3, 12), TestRangeSet(0,3)+TestRange(12,15)+TestRange(20,25)); + BOOST_CHECK_EQUAL(r-TestRange(3, 22), TestRangeSet(12,15)+TestRange(22,25)); + BOOST_CHECK_EQUAL(r-TestRange(12, 22), TestRangeSet(0,5)+TestRange(10,11)+TestRange(22,25)); + + // Sets + BOOST_CHECK_EQUAL(r-(TestRangeSet(-1,6)+TestRange(11,14)+TestRange(23,25)), + TestRangeSet(10,11)+TestRange(14,15)+TestRange(20,23)); +} + +QPID_AUTO_TEST_CASE(testRangeContaining) { + TestRangeSet r; + (((r += 1) += TestRange(3,5)) += 7); + BOOST_CHECK_EQUAL(r.rangeContaining(0), TestRange(0,0)); + BOOST_CHECK_EQUAL(r.rangeContaining(1), TestRange(1,2)); + BOOST_CHECK_EQUAL(r.rangeContaining(2), TestRange(2,2)); + BOOST_CHECK_EQUAL(r.rangeContaining(3), TestRange(3,5)); + BOOST_CHECK_EQUAL(r.rangeContaining(4), TestRange(3,5)); + BOOST_CHECK_EQUAL(r.rangeContaining(5), TestRange(5,5)); + BOOST_CHECK_EQUAL(r.rangeContaining(6), TestRange(6,6)); + BOOST_CHECK_EQUAL(r.rangeContaining(7), TestRange(7,8)); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/RateFlowcontrolTest.cpp b/qpid/cpp/src/tests/RateFlowcontrolTest.cpp new file mode 100644 index 0000000000..80ad06af8c --- /dev/null +++ b/qpid/cpp/src/tests/RateFlowcontrolTest.cpp @@ -0,0 +1,71 @@ +/* + * + * 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 "unit_test.h" + +#include "qpid/broker/RateFlowcontrol.h" +#include "qpid/sys/Time.h" + +using namespace qpid::broker; +using namespace qpid::sys; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(RateFlowcontrolTestSuite) + +QPID_AUTO_TEST_CASE(RateFlowcontrolTest) +{ + // BOOST_CHECK(predicate); + // BOOST_CHECK_EQUAL(a, b); + + RateFlowcontrol fc(100); + AbsTime n=AbsTime::now(); + + BOOST_CHECK_EQUAL( fc.receivedMessage(n, 0), 0U ); + + fc.sentCredit(n, 0); + + BOOST_CHECK_EQUAL( fc.receivedMessage(n, 0), 0U ); + fc.sentCredit(n, 50); + + Duration d=250*TIME_MSEC; + + n = AbsTime(n,d); + BOOST_CHECK_EQUAL( fc.receivedMessage(n, 25), 0U ); + BOOST_CHECK_EQUAL( fc.availableCredit(n), 25U ); + + n = AbsTime(n,d); + BOOST_CHECK_EQUAL( fc.receivedMessage(n, 23), 48U ); + BOOST_CHECK_EQUAL( fc.availableCredit(n), 48U ); + fc.sentCredit(n, 48); + BOOST_CHECK_EQUAL( fc.receivedMessage(n, 50), 0U); + BOOST_CHECK(fc.flowStopped()); + + n = AbsTime(n,d); + BOOST_CHECK_EQUAL( fc.receivedMessage(n, 0), 25U); + n = AbsTime(n,d); + BOOST_CHECK_EQUAL( fc.receivedMessage(n, 0), 50U); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/RefCounted.cpp b/qpid/cpp/src/tests/RefCounted.cpp new file mode 100644 index 0000000000..e4c1da5696 --- /dev/null +++ b/qpid/cpp/src/tests/RefCounted.cpp @@ -0,0 +1,55 @@ +/* + * + * 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. + * + */ + +#include "qpid/RefCounted.h" +#include <boost/intrusive_ptr.hpp> + +#include "unit_test.h" + +QPID_AUTO_TEST_SUITE(RefCountedTestSuiteTestSuite) + +using boost::intrusive_ptr; +using namespace std; +using namespace qpid; + +namespace qpid { +namespace tests { + +struct CountMe : public RefCounted { + static int instances; + CountMe() { ++instances; } + ~CountMe() { --instances; } +}; + +int CountMe::instances=0; + +QPID_AUTO_TEST_CASE(testRefCounted) { + BOOST_CHECK_EQUAL(0, CountMe::instances); + intrusive_ptr<CountMe> p(new CountMe()); + BOOST_CHECK_EQUAL(1, CountMe::instances); + intrusive_ptr<CountMe> q(p); + BOOST_CHECK_EQUAL(1, CountMe::instances); + q=0; + BOOST_CHECK_EQUAL(1, CountMe::instances); + p=0; + BOOST_CHECK_EQUAL(0, CountMe::instances); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/ReplicationTest.cpp b/qpid/cpp/src/tests/ReplicationTest.cpp new file mode 100644 index 0000000000..7310a3fe20 --- /dev/null +++ b/qpid/cpp/src/tests/ReplicationTest.cpp @@ -0,0 +1,144 @@ +/* + * + * 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 "unit_test.h" +#include "test_tools.h" +#include "config.h" +#include "BrokerFixture.h" + +#include "qpid/Plugin.h" +#include "qpid/broker/Broker.h" +#include "qpid/client/QueueOptions.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/SequenceNumber.h" +#include "qpid/replication/constants.h" +#include "qpid/sys/Shlib.h" + +#include <string> +#include <sstream> +#include <vector> + +#include <boost/assign.hpp> +#include <boost/bind.hpp> + +using namespace qpid::client; +using namespace qpid::framing; +using namespace qpid::replication::constants; +using boost::assign::list_of; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(ReplicationTestSuite) + +// FIXME aconway 2009-11-26: clean this up. +// The CMake-based build passes in the module suffix; if it's not there, this +// is a Linux/UNIX libtool-based build. +#if defined (QPID_MODULE_PREFIX) && defined (QPID_MODULE_SUFFIX) +static const char *default_shlib = + QPID_MODULE_PREFIX "replicating_listener" QPID_MODULE_POSTFIX QPID_MODULE_SUFFIX; +#else +static const char *default_shlib = ".libs/replicating_listener.so"; +#endif +qpid::sys::Shlib plugin(getLibPath("REPLICATING_LISTENER_LIB", default_shlib)); + +qpid::broker::Broker::Options getBrokerOpts(const std::vector<std::string>& args) +{ + std::vector<const char*> argv(args.size()); + transform(args.begin(), args.end(), argv.begin(), boost::bind(&string::c_str, _1)); + + qpid::broker::Broker::Options opts; + qpid::Plugin::addOptions(opts); + opts.parse(argv.size(), &argv[0], "", true); + return opts; +} + +QPID_AUTO_TEST_CASE(testReplicationExchange) +{ + qpid::broker::Broker::Options brokerOpts(getBrokerOpts(list_of<string>("qpidd") + ("--replication-exchange-name=qpid.replication"))); + ProxySessionFixture f(brokerOpts); + + + std::string dataQ("queue-1"); + std::string eventQ("event-queue-1"); + std::string dataQ2("queue-2"); + std::string eventQ2("event-queue-2"); + FieldTable eventQopts; + eventQopts.setString("qpid.insert_sequence_numbers", REPLICATION_EVENT_SEQNO); + + f.session.queueDeclare(arg::queue=eventQ, arg::exclusive=true, arg::autoDelete=true, arg::arguments=eventQopts); + f.session.exchangeBind(arg::exchange="qpid.replication", arg::queue=eventQ, arg::bindingKey=dataQ); + + f.session.queueDeclare(arg::queue=eventQ2, arg::exclusive=true, arg::autoDelete=true, arg::arguments=eventQopts); + f.session.exchangeBind(arg::exchange="qpid.replication", arg::queue=eventQ2, arg::bindingKey=dataQ2); + + QueueOptions args; + args.enableQueueEvents(false); + f.session.queueDeclare(arg::queue=dataQ, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); + f.session.queueDeclare(arg::queue=dataQ2, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); + for (int i = 0; i < 10; i++) { + f.session.messageTransfer(arg::content=Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), dataQ)); + f.session.messageTransfer(arg::content=Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), dataQ2)); + } + Message msg; + LocalQueue incoming; + Subscription sub = f.subs.subscribe(incoming, dataQ); + for (int i = 0; i < 10; i++) { + BOOST_CHECK(incoming.get(msg, qpid::sys::TIME_SEC)); + BOOST_CHECK_EQUAL((boost::format("%1%_%2%") % "Message" % (i+1)).str(), msg.getData()); + } + BOOST_CHECK(!f.subs.get(msg, dataQ)); + + sub.cancel(); + sub = f.subs.subscribe(incoming, eventQ); + //check that we received enqueue events for first queue: + for (int i = 0; i < 10; i++) { + BOOST_CHECK(incoming.get(msg, qpid::sys::TIME_SEC)); + BOOST_CHECK_EQUAL(msg.getHeaders().getAsString(REPLICATION_TARGET_QUEUE), dataQ); + BOOST_CHECK_EQUAL(msg.getHeaders().getAsInt(REPLICATION_EVENT_TYPE), ENQUEUE); + BOOST_CHECK_EQUAL(msg.getHeaders().getAsInt64(REPLICATION_EVENT_SEQNO), (i+1)); + BOOST_CHECK_EQUAL((boost::format("%1%_%2%") % "Message" % (i+1)).str(), msg.getData()); + } + //check that we received dequeue events for first queue: + for (int i = 0; i < 10; i++) { + BOOST_CHECK(incoming.get(msg, qpid::sys::TIME_SEC)); + BOOST_CHECK_EQUAL(msg.getHeaders().getAsString(REPLICATION_TARGET_QUEUE), dataQ); + BOOST_CHECK_EQUAL(msg.getHeaders().getAsInt(REPLICATION_EVENT_TYPE), DEQUEUE); + BOOST_CHECK_EQUAL(msg.getHeaders().getAsInt(DEQUEUED_MESSAGE_POSITION), (i+1)); + BOOST_CHECK_EQUAL(msg.getHeaders().getAsInt64(REPLICATION_EVENT_SEQNO), (i+11)); + } + + sub.cancel(); + sub = f.subs.subscribe(incoming, eventQ2); + //check that we received enqueue events for second queue: + for (int i = 0; i < 10; i++) { + BOOST_CHECK(incoming.get(msg, qpid::sys::TIME_SEC)); + BOOST_CHECK_EQUAL(msg.getHeaders().getAsString(REPLICATION_TARGET_QUEUE), dataQ2); + BOOST_CHECK_EQUAL(msg.getHeaders().getAsInt(REPLICATION_EVENT_TYPE), ENQUEUE); + BOOST_CHECK_EQUAL(msg.getHeaders().getAsInt64(REPLICATION_EVENT_SEQNO), (i+1)); + BOOST_CHECK_EQUAL((boost::format("%1%_%2%") % "Message" % (i+1)).str(), msg.getData()); + } +} + + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/RetryList.cpp b/qpid/cpp/src/tests/RetryList.cpp new file mode 100644 index 0000000000..50cd5edfe8 --- /dev/null +++ b/qpid/cpp/src/tests/RetryList.cpp @@ -0,0 +1,111 @@ +/* + * + * 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. + * + */ + + +#include "unit_test.h" +#include "test_tools.h" +#include "qpid/broker/RetryList.h" + +using namespace qpid; +using namespace qpid::broker; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(RetryListTestSuite) + +struct RetryListFixture +{ + RetryList list; + std::vector<Url> urls; + std::vector<Address> expected; + + void addUrl(const std::string& s) + { + urls.push_back(Url(s)); + } + + void addExpectation(const std::string& host, uint16_t port) + { + expected.push_back(Address("tcp", host, port)); + } + + void check() + { + list.reset(urls); + for (int t = 0; t < 2; t++) { + Address next; + for (std::vector<Address>::const_iterator i = expected.begin(); i != expected.end(); ++i) { + BOOST_CHECK(list.next(next)); + BOOST_CHECK_EQUAL(i->host, next.host); + BOOST_CHECK_EQUAL(i->port, next.port); + } + BOOST_CHECK(!list.next(next)); + } + } +}; + +QPID_AUTO_TEST_CASE(testWithSingleAddress) +{ + RetryListFixture test; + test.addUrl("amqp:host:5673"); + test.addExpectation("host", 5673); + test.check(); +} + +QPID_AUTO_TEST_CASE(testWithSingleUrlOfMultipleAddresses) +{ + RetryListFixture test; + test.addUrl("amqp:host1,host2:2222,tcp:host3:5673,host4:1"); + + test.addExpectation("host1", 5672); + test.addExpectation("host2", 2222); + test.addExpectation("host3", 5673); + test.addExpectation("host4", 1); + + test.check(); +} + +QPID_AUTO_TEST_CASE(testWithMultipleUrlsOfMultipleAddresses) +{ + RetryListFixture test; + test.addUrl("amqp:my-host"); + test.addUrl("amqp:host1:6666,host2:2222,tcp:host3:5673,host4:1"); + test.addUrl("amqp:host5,host6:2222,tcp:host7:5673"); + + test.addExpectation("my-host", 5672); + test.addExpectation("host1", 6666); + test.addExpectation("host2", 2222); + test.addExpectation("host3", 5673); + test.addExpectation("host4", 1); + test.addExpectation("host5", 5672); + test.addExpectation("host6", 2222); + test.addExpectation("host7", 5673); + + test.check(); +} + +QPID_AUTO_TEST_CASE(testEmptyList) +{ + RetryListFixture test; + test.check(); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/SequenceNumberTest.cpp b/qpid/cpp/src/tests/SequenceNumberTest.cpp new file mode 100644 index 0000000000..f3c934e3ca --- /dev/null +++ b/qpid/cpp/src/tests/SequenceNumberTest.cpp @@ -0,0 +1,209 @@ +/* + * + * 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 "unit_test.h" +#include <iostream> +#include "qpid/framing/SequenceNumber.h" +#include "qpid/framing/SequenceNumberSet.h" + +using namespace qpid::framing; + +namespace qpid { +namespace tests { + +void checkDifference(SequenceNumber& a, SequenceNumber& b, int gap) +{ + BOOST_CHECK_EQUAL(gap, a - b); + BOOST_CHECK_EQUAL(-gap, b - a); + + //increment until b wraps around + for (int i = 0; i < (gap + 2); i++, ++a, ++b) { + BOOST_CHECK_EQUAL(gap, a - b); + } + //keep incrementing until a also wraps around + for (int i = 0; i < (gap + 2); i++, ++a, ++b) { + BOOST_CHECK_EQUAL(gap, a - b); + } + //let b catch up and overtake + for (int i = 0; i < (gap*2); i++, ++b) { + BOOST_CHECK_EQUAL(gap - i, a - b); + BOOST_CHECK_EQUAL(i - gap, b - a); + } +} + +void checkComparison(SequenceNumber& a, SequenceNumber& b, int gap) +{ + //increment until b wraps around + for (int i = 0; i < (gap + 2); i++) { + BOOST_CHECK(++a < ++b);//test prefix + } + //keep incrementing until a also wraps around + for (int i = 0; i < (gap + 2); i++) { + BOOST_CHECK(a++ < b++);//test postfix + } + //let a 'catch up' + for (int i = 0; i < gap; i++) { + a++; + } + BOOST_CHECK(a == b); + BOOST_CHECK(++a > b); +} + + +QPID_AUTO_TEST_SUITE(SequenceNumberTestSuite) + +QPID_AUTO_TEST_CASE(testIncrementPostfix) +{ + SequenceNumber a; + SequenceNumber b; + BOOST_CHECK(!(a > b)); + BOOST_CHECK(!(b < a)); + BOOST_CHECK(a == b); + + SequenceNumber c = a++; + BOOST_CHECK(a > b); + BOOST_CHECK(b < a); + BOOST_CHECK(a != b); + BOOST_CHECK(c < a); + BOOST_CHECK(a != c); + + b++; + BOOST_CHECK(!(a > b)); + BOOST_CHECK(!(b < a)); + BOOST_CHECK(a == b); + BOOST_CHECK(c < b); + BOOST_CHECK(b != c); +} + +QPID_AUTO_TEST_CASE(testIncrementPrefix) +{ + SequenceNumber a; + SequenceNumber b; + BOOST_CHECK(!(a > b)); + BOOST_CHECK(!(b < a)); + BOOST_CHECK(a == b); + + SequenceNumber c = ++a; + BOOST_CHECK(a > b); + BOOST_CHECK(b < a); + BOOST_CHECK(a != b); + BOOST_CHECK(a == c); + + ++b; + BOOST_CHECK(!(a > b)); + BOOST_CHECK(!(b < a)); + BOOST_CHECK(a == b); +} + +QPID_AUTO_TEST_CASE(testWrapAround) +{ + const uint32_t max = 0xFFFFFFFF; + SequenceNumber a(max - 10); + SequenceNumber b(max - 5); + checkComparison(a, b, 5); + + const uint32_t max_signed = 0x7FFFFFFF; + SequenceNumber c(max_signed - 10); + SequenceNumber d(max_signed - 5); + checkComparison(c, d, 5); +} + +QPID_AUTO_TEST_CASE(testCondense) +{ + SequenceNumberSet set; + for (uint i = 0; i < 6; i++) { + set.push_back(SequenceNumber(i)); + } + set.push_back(SequenceNumber(7)); + for (uint i = 9; i < 13; i++) { + set.push_back(SequenceNumber(i)); + } + set.push_back(SequenceNumber(13)); + SequenceNumberSet actual = set.condense(); + + SequenceNumberSet expected; + expected.addRange(SequenceNumber(0), SequenceNumber(5)); + expected.addRange(SequenceNumber(7), SequenceNumber(7)); + expected.addRange(SequenceNumber(9), SequenceNumber(13)); + BOOST_CHECK_EQUAL(expected, actual); +} + +QPID_AUTO_TEST_CASE(testCondenseSingleRange) +{ + SequenceNumberSet set; + for (uint i = 0; i < 6; i++) { + set.push_back(SequenceNumber(i)); + } + SequenceNumberSet actual = set.condense(); + + SequenceNumberSet expected; + expected.addRange(SequenceNumber(0), SequenceNumber(5)); + BOOST_CHECK_EQUAL(expected, actual); +} + +QPID_AUTO_TEST_CASE(testCondenseSingleItem) +{ + SequenceNumberSet set; + set.push_back(SequenceNumber(1)); + SequenceNumberSet actual = set.condense(); + + SequenceNumberSet expected; + expected.addRange(SequenceNumber(1), SequenceNumber(1)); + BOOST_CHECK_EQUAL(expected, actual); +} + +QPID_AUTO_TEST_CASE(testDifference) +{ + SequenceNumber a; + SequenceNumber b; + + for (int i = 0; i < 10; i++, ++a) { + BOOST_CHECK_EQUAL(i, a - b); + BOOST_CHECK_EQUAL(-i, b - a); + } + + b = a; + + for (int i = 0; i < 10; i++, ++b) { + BOOST_CHECK_EQUAL(-i, a - b); + BOOST_CHECK_EQUAL(i, b - a); + } +} + +QPID_AUTO_TEST_CASE(testDifferenceWithWrapAround1) +{ + const uint32_t max = 0xFFFFFFFF; + SequenceNumber a(max - 5); + SequenceNumber b(max - 10); + checkDifference(a, b, 5); +} + +QPID_AUTO_TEST_CASE(testDifferenceWithWrapAround2) +{ + const uint32_t max_signed = 0x7FFFFFFF; + SequenceNumber c(max_signed - 5); + SequenceNumber d(max_signed - 10); + checkDifference(c, d, 5); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/SequenceSet.cpp b/qpid/cpp/src/tests/SequenceSet.cpp new file mode 100644 index 0000000000..bc0a8ea509 --- /dev/null +++ b/qpid/cpp/src/tests/SequenceSet.cpp @@ -0,0 +1,187 @@ +/* + * + * 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. + * + */ + +#include "qpid/framing/SequenceSet.h" +#include "unit_test.h" +#include <list> + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(SequenceSetTestSuite) + +using namespace qpid::framing; + +struct RangeExpectations +{ + typedef std::pair<SequenceNumber, SequenceNumber> Range; + typedef std::list<Range> Ranges; + + Ranges ranges; + + RangeExpectations& expect(const SequenceNumber& start, const SequenceNumber& end) { + ranges.push_back(Range(start, end)); + return *this; + } + + void operator()(const SequenceNumber& start, const SequenceNumber& end) { + BOOST_CHECK(!ranges.empty()); + if (!ranges.empty()) { + BOOST_CHECK_EQUAL(start, ranges.front().first); + BOOST_CHECK_EQUAL(end, ranges.front().second); + ranges.pop_front(); + } + } + + void check(SequenceSet& set) { + set.for_each(*this); + BOOST_CHECK(ranges.empty()); + } +}; + +QPID_AUTO_TEST_CASE(testAdd) { + SequenceSet s; + s.add(2); + s.add(8,8); + s.add(3,5); + + for (uint32_t i = 0; i <= 1; i++) + BOOST_CHECK(!s.contains(i)); + + for (uint32_t i = 2; i <= 5; i++) + BOOST_CHECK(s.contains(i)); + + for (uint32_t i = 6; i <= 7; i++) + BOOST_CHECK(!s.contains(i)); + + BOOST_CHECK(s.contains(8)); + + for (uint32_t i = 9; i <= 10; i++) + BOOST_CHECK(!s.contains(i)); + + RangeExpectations().expect(2, 5).expect(8, 8).check(s); + + SequenceSet t; + t.add(6, 10); + t.add(s); + + for (uint32_t i = 0; i <= 1; i++) + BOOST_CHECK(!t.contains(i)); + + for (uint32_t i = 2; i <= 10; i++) + BOOST_CHECK_MESSAGE(t.contains(i), t << " contains " << i); + + RangeExpectations().expect(2, 10).check(t); +} + +QPID_AUTO_TEST_CASE(testAdd2) { + SequenceSet s; + s.add(7,6); + s.add(4,4); + s.add(3,10); + s.add(2); + RangeExpectations().expect(2, 10).check(s); +} + +QPID_AUTO_TEST_CASE(testRemove) { + SequenceSet s; + SequenceSet t; + s.add(0, 10); + t.add(0, 10); + + s.remove(7); + s.remove(3, 5); + s.remove(9, 10); + + t.remove(s); + + for (uint32_t i = 0; i <= 2; i++) { + BOOST_CHECK(s.contains(i)); + BOOST_CHECK(!t.contains(i)); + } + + for (uint32_t i = 3; i <= 5; i++) { + BOOST_CHECK(!s.contains(i)); + BOOST_CHECK(t.contains(i)); + } + + BOOST_CHECK(s.contains(6)); + BOOST_CHECK(!t.contains(6)); + + BOOST_CHECK(!s.contains(7)); + BOOST_CHECK(t.contains(7)); + + BOOST_CHECK(s.contains(8)); + BOOST_CHECK(!t.contains(8)); + + for (uint32_t i = 9; i <= 10; i++) { + BOOST_CHECK(!s.contains(i)); + BOOST_CHECK(t.contains(i)); + } + + RangeExpectations().expect(0, 2).expect(6, 6).expect(8, 8).check(s); + RangeExpectations().expect(3, 5).expect(7, 7).expect(9, 10).check(t); +} + + +QPID_AUTO_TEST_CASE(testOutOfOrderRemove) { + + SequenceSet s(2, 20); + + // test remove from middle: + s.remove(7); + RangeExpectations().expect(2, 6).expect(8, 20).check(s); + s.remove(14); + RangeExpectations().expect(2, 6).expect(8, 13).expect(15, 20).check(s); + + // remove from front of subrange: + s.remove(8, 8); + RangeExpectations().expect(2, 6).expect(9, 13).expect(15, 20).check(s); + + // remove from tail of subrange: + s.remove(6); + RangeExpectations().expect(2, 5).expect(9, 13).expect(15, 20).check(s); + + // remove across subrange: + s.remove(13, 15); + RangeExpectations().expect(2, 5).expect(9, 12).expect(16, 20).check(s); + + // remove within subrange: + s.remove(6, 8); + RangeExpectations().expect(2, 5).expect(9, 12).expect(16, 20).check(s); + + // remove overlap subrange tail: + s.remove(11, 15); + RangeExpectations().expect(2, 5).expect(9, 10).expect(16, 20).check(s); + + // remove overlap subrange head: + s.remove(14, 17); + RangeExpectations().expect(2, 5).expect(9, 10).expect(18, 20).check(s); + + // remove overlap sequence tail: + s.remove(20, 22); + RangeExpectations().expect(2, 5).expect(9, 10).expect(18, 19).check(s); + + // remove overlap sequence head: + s.remove(1, 3); + RangeExpectations().expect(4, 5).expect(9, 10).expect(18, 19).check(s); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/SessionState.cpp b/qpid/cpp/src/tests/SessionState.cpp new file mode 100644 index 0000000000..157cabfb63 --- /dev/null +++ b/qpid/cpp/src/tests/SessionState.cpp @@ -0,0 +1,304 @@ +/* + * + * 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. + * + */ + +#include "unit_test.h" + +#include "qpid/SessionState.h" +#include "qpid/Exception.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/SessionFlushBody.h" + +#include <boost/bind.hpp> +#include <algorithm> +#include <functional> +#include <numeric> + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(SessionStateTestSuite) + +using namespace std; +using namespace boost; +using namespace qpid::framing; + +// ================================================================ +// Utility functions. + +// 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))); +} + +// Create a frame with a one-char string. +AMQFrame& frame(char s) { + static AMQFrame frame((AMQContentBody(string(&s, 1)))); + return frame; +} + +// Simple string representation of a frame. +string str(const AMQFrame& f) { + if (f.getMethod()) return "C"; // Command or Control + const AMQContentBody* c = dynamic_cast<const AMQContentBody*>(f.getBody()); + if (c) return c->getData(); // Return data for content frames. + return "H"; // Must be a header. +} +// Make a string from a range of frames. +string str(const boost::iterator_range<vector<AMQFrame>::const_iterator>& frames) { + string (*strFrame)(const AMQFrame&) = str; + return applyAccumulate(frames.begin(), frames.end(), string(), ptr_fun(strFrame)); +} +// Make a transfer command frame. +AMQFrame transferFrame(bool hasContent) { + AMQFrame t((MessageTransferBody())); + t.setFirstFrame(true); + t.setLastFrame(true); + t.setFirstSegment(true); + t.setLastSegment(!hasContent); + return t; +} +// Make a content frame +AMQFrame contentFrame(string content, bool isLast=true) { + AMQFrame f((AMQContentBody(content))); + f.setFirstFrame(true); + f.setLastFrame(true); + f.setFirstSegment(false); + f.setLastSegment(isLast); + return f; +} +AMQFrame contentFrameChar(char content, bool isLast=true) { + return contentFrame(string(1, content), isLast); +} + +// Send frame & return size of frame. +size_t send(qpid::SessionState& s, const AMQFrame& f) { s.senderRecord(f); return f.encodedSize(); } +// Send transfer command with no content. +size_t transfer0(qpid::SessionState& s) { return send(s, transferFrame(false)); } +// Send transfer frame with single content frame. +size_t transfer1(qpid::SessionState& s, string content) { + return send(s,transferFrame(true)) + send(s,contentFrame(content)); +} +size_t transfer1Char(qpid::SessionState& s, char content) { + return transfer1(s, string(1,content)); +} + +// Send transfer frame with multiple single-byte content frames. +size_t transferN(qpid::SessionState& s, string content) { + size_t size=send(s, transferFrame(!content.empty())); + if (!content.empty()) { + 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))); + size += send(s, contentFrameChar(last, true)); + } + return size; +} + +// 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)); +} + +size_t contentFrameSize(size_t n=1) { return AMQFrame(( AMQContentBody())).encodedSize() + n; } +size_t transferFrameSize() { return AMQFrame((MessageTransferBody())).encodedSize(); } + +// ==== qpid::SessionState test classes + +using qpid::SessionId; +using qpid::SessionPoint; + + +QPID_AUTO_TEST_CASE(testSendGetReplyList) { + qpid::SessionState s; + s.setTimeout(1); + s.senderGetCommandPoint(); + transfer1(s, "abc"); + transfers(s, "def"); + transferN(s, "xyz"); + BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(0,0))),"CabcCdCeCfCxyz"); + // Ignore controls. + s.senderRecord(AMQFrame(new SessionFlushBody())); + BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(2,0))),"CeCfCxyz"); +} + +QPID_AUTO_TEST_CASE(testNeedFlush) { + qpid::SessionState::Configuration c; + // sync after 2 1-byte transfers or equivalent bytes. + c.replayFlushLimit = 2*(transferFrameSize()+contentFrameSize()); + qpid::SessionState s(SessionId(), c); + s.setTimeout(1); + s.senderGetCommandPoint(); + transfers(s, "a"); + BOOST_CHECK(!s.senderNeedFlush()); + transfers(s, "b"); + BOOST_CHECK(s.senderNeedFlush()); + s.senderRecordFlush(); + BOOST_CHECK(!s.senderNeedFlush()); + transfers(s, "c"); + BOOST_CHECK(!s.senderNeedFlush()); + transfers(s, "d"); + BOOST_CHECK(s.senderNeedFlush()); + BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint())), "CaCbCcCd"); +} + +QPID_AUTO_TEST_CASE(testPeerConfirmed) { + qpid::SessionState::Configuration c; + // sync after 2 1-byte transfers or equivalent bytes. + c.replayFlushLimit = 2*(transferFrameSize()+contentFrameSize()); + qpid::SessionState s(SessionId(), c); + s.setTimeout(1); + s.senderGetCommandPoint(); + transfers(s, "ab"); + BOOST_CHECK(s.senderNeedFlush()); + transfers(s, "cd"); + BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(0,0))), "CaCbCcCd"); + s.senderConfirmed(SessionPoint(3)); + BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(3,0))), "Cd"); + BOOST_CHECK(!s.senderNeedFlush()); + + // Multi-frame transfer. + transfer1(s, "efg"); + transfers(s, "xy"); + BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(3,0))), "CdCefgCxCy"); + BOOST_CHECK(s.senderNeedFlush()); + + s.senderConfirmed(SessionPoint(4)); + BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(4,0))), "CefgCxCy"); + BOOST_CHECK(s.senderNeedFlush()); + + s.senderConfirmed(SessionPoint(5)); + BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(5,0))), "CxCy"); + BOOST_CHECK(s.senderNeedFlush()); + + s.senderConfirmed(SessionPoint(6)); + BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(6,0))), "Cy"); + BOOST_CHECK(!s.senderNeedFlush()); +} + +QPID_AUTO_TEST_CASE(testPeerCompleted) { + qpid::SessionState s; + s.setTimeout(1); + s.senderGetCommandPoint(); + // Completion implies confirmation + transfers(s, "abc"); + BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(0,0))), "CaCbCc"); + SequenceSet set(SequenceSet() + 0 + 1); + s.senderCompleted(set); + BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(2,0))), "Cc"); + + transfers(s, "def"); + // We dont do out-of-order confirmation, so this will only confirm up to 3: + set = SequenceSet(SequenceSet() + 2 + 3 + 5); + s.senderCompleted(set); + BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(4,0))), "CeCf"); +} + +QPID_AUTO_TEST_CASE(testReceive) { + // Advance expected/received correctly + qpid::SessionState s; + s.receiverSetCommandPoint(SessionPoint()); + BOOST_CHECK_EQUAL(s.receiverGetExpected(), SessionPoint(0)); + BOOST_CHECK_EQUAL(s.receiverGetReceived(), SessionPoint(0)); + + BOOST_CHECK(s.receiverRecord(transferFrame(false))); + BOOST_CHECK_EQUAL(s.receiverGetExpected(), SessionPoint(1)); + BOOST_CHECK_EQUAL(s.receiverGetReceived(), SessionPoint(1)); + + BOOST_CHECK(s.receiverRecord(transferFrame(true))); + SessionPoint point = SessionPoint(1, transferFrameSize()); + BOOST_CHECK_EQUAL(s.receiverGetExpected(), point); + BOOST_CHECK_EQUAL(s.receiverGetReceived(), point); + BOOST_CHECK(s.receiverRecord(contentFrame("", false))); + point.offset += contentFrameSize(0); + BOOST_CHECK_EQUAL(s.receiverGetExpected(), point); + BOOST_CHECK_EQUAL(s.receiverGetReceived(), point); + BOOST_CHECK(s.receiverRecord(contentFrame("", true))); + BOOST_CHECK_EQUAL(s.receiverGetExpected(), SessionPoint(2)); + BOOST_CHECK_EQUAL(s.receiverGetReceived(), SessionPoint(2)); + + // Idempotence barrier, rewind expected & receive some duplicates. + s.receiverSetCommandPoint(SessionPoint(1)); + BOOST_CHECK(!s.receiverRecord(transferFrame(false))); + BOOST_CHECK_EQUAL(s.receiverGetExpected(), SessionPoint(2)); + BOOST_CHECK_EQUAL(s.receiverGetReceived(), SessionPoint(2)); + BOOST_CHECK(s.receiverRecord(transferFrame(false))); + BOOST_CHECK_EQUAL(s.receiverGetExpected(), SessionPoint(3)); + BOOST_CHECK_EQUAL(s.receiverGetReceived(), SessionPoint(3)); +} + +QPID_AUTO_TEST_CASE(testCompleted) { + // completed & unknownCompleted + qpid::SessionState s; + s.receiverSetCommandPoint(SessionPoint()); + s.receiverRecord(transferFrame(false)); + s.receiverRecord(transferFrame(false)); + s.receiverRecord(transferFrame(false)); + s.receiverCompleted(1); + BOOST_CHECK_EQUAL(s.receiverGetUnknownComplete(), SequenceSet(SequenceSet()+1)); + s.receiverCompleted(0); + BOOST_CHECK_EQUAL(s.receiverGetUnknownComplete(), + SequenceSet(SequenceSet() + qpid::Range<SequenceNumber>(0,2))); + s.receiverKnownCompleted(SequenceSet(SequenceSet()+1)); + BOOST_CHECK_EQUAL(s.receiverGetUnknownComplete(), SequenceSet(SequenceSet()+2)); + // TODO aconway 2008-04-30: missing tests for known-completed. +} + +QPID_AUTO_TEST_CASE(testNeedKnownCompleted) { + size_t flushInterval= 2*(transferFrameSize()+contentFrameSize())+1; + qpid::SessionState::Configuration c(flushInterval); + qpid::SessionState s(qpid::SessionId(), c); + s.senderGetCommandPoint(); + transfers(s, "a"); + SequenceSet set(SequenceSet() + 0); + s.senderCompleted(set); + BOOST_CHECK(!s.senderNeedKnownCompleted()); + + transfers(s, "b"); + set += 1; + s.senderCompleted(set); + BOOST_CHECK(!s.senderNeedKnownCompleted()); + + transfers(s, "c"); + set += 2; + s.senderCompleted(set); + BOOST_CHECK(s.senderNeedKnownCompleted()); + s.senderRecordKnownCompleted(); + BOOST_CHECK(!s.senderNeedKnownCompleted()); + + transfers(s, "de"); + set += 3; + set += 4; + s.senderCompleted(set); + BOOST_CHECK(!s.senderNeedKnownCompleted()); + + transfers(s, "f"); + set += 2; + s.senderCompleted(set); + BOOST_CHECK(s.senderNeedKnownCompleted()); + s.senderRecordKnownCompleted(); + BOOST_CHECK(!s.senderNeedKnownCompleted()); +} + + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/Shlib.cpp b/qpid/cpp/src/tests/Shlib.cpp new file mode 100644 index 0000000000..d8ad4c14d8 --- /dev/null +++ b/qpid/cpp/src/tests/Shlib.cpp @@ -0,0 +1,77 @@ +/* + * 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 "test_tools.h" +#include "config.h" +#include "qpid/sys/Shlib.h" +#include "qpid/Exception.h" + +#include "unit_test.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(ShlibTestSuite) + +using namespace qpid::sys; +typedef void (*CallMe)(int*); + + +QPID_AUTO_TEST_CASE(testShlib) { + // The CMake-based build passes in the shared lib suffix; if it's not + // there, this is a Linux/UNIX libtool-based build. +#if defined (QPID_SHLIB_PREFIX) && defined (QPID_SHLIB_SUFFIX) + Shlib sh("./" QPID_SHLIB_PREFIX "shlibtest" QPID_SHLIB_POSTFIX QPID_SHLIB_SUFFIX); +#else + Shlib sh(".libs/libshlibtest.so"); +#endif + // Double cast to avoid ISO warning. + CallMe callMe=sh.getSymbol<CallMe>("callMe"); + BOOST_REQUIRE(callMe != 0); + int unloaded=0; + callMe(&unloaded); + sh.unload(); + BOOST_CHECK_EQUAL(42, unloaded); + try { + sh.getSymbol("callMe"); + BOOST_FAIL("Expected exception"); + } + catch (const qpid::Exception&) {} +} + +QPID_AUTO_TEST_CASE(testAutoShlib) { + int unloaded = 0; + { +#if defined (QPID_SHLIB_PREFIX) && defined (QPID_SHLIB_SUFFIX) + AutoShlib sh("./" QPID_SHLIB_PREFIX "shlibtest" QPID_SHLIB_POSTFIX QPID_SHLIB_SUFFIX); +#else + AutoShlib sh(".libs/libshlibtest.so"); +#endif + CallMe callMe=sh.getSymbol<CallMe>("callMe"); + BOOST_REQUIRE(callMe != 0); + callMe(&unloaded); + } + BOOST_CHECK_EQUAL(42, unloaded); +} + + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/SocketProxy.h b/qpid/cpp/src/tests/SocketProxy.h new file mode 100644 index 0000000000..d195f11aa9 --- /dev/null +++ b/qpid/cpp/src/tests/SocketProxy.h @@ -0,0 +1,183 @@ +#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" + +#include <boost/lexical_cast.hpp> + +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, boost::lexical_cast<std::string>(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/qpid/cpp/src/tests/Statistics.cpp b/qpid/cpp/src/tests/Statistics.cpp new file mode 100644 index 0000000000..19531762b1 --- /dev/null +++ b/qpid/cpp/src/tests/Statistics.cpp @@ -0,0 +1,131 @@ +/* + * + * 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 "Statistics.h" +#include <qpid/messaging/Message.h> +#include <ostream> +#include <iomanip> + +namespace qpid { +namespace tests { + +using namespace std; + +Statistic::~Statistic() {} + +Throughput::Throughput() : messages(0), started(false) {} + +void Throughput::message(const messaging::Message&) { + ++messages; + if (!started) { + start = sys::now(); + started = true; + } +} + +void Throughput::header(ostream& o) const { + o << "tp(m/s)"; +} + +void Throughput::report(ostream& o) const { + double elapsed(int64_t(sys::Duration(start, sys::now()))/double(sys::TIME_SEC)); + o << fixed << setprecision(0) << messages/elapsed; +} + +ThroughputAndLatency::ThroughputAndLatency() : + total(0), + min(numeric_limits<double>::max()), + max(numeric_limits<double>::min()), + samples(0) +{} + +const std::string TS = "ts"; + +void ThroughputAndLatency::message(const messaging::Message& m) { + Throughput::message(m); + types::Variant::Map::const_iterator i = m.getProperties().find(TS); + if (i != m.getProperties().end()) { + ++samples; + int64_t start(i->second.asInt64()); + int64_t end(sys::Duration(sys::EPOCH, sys::now())); + double latency = double(end - start)/sys::TIME_MSEC; + if (latency > 0) { + total += latency; + if (latency < min) min = latency; + if (latency > max) max = latency; + } + } +} + +void ThroughputAndLatency::header(ostream& o) const { + Throughput::header(o); + o << '\t' << "l-min" << '\t' << "l-max" << '\t' << "l-avg"; +} + +void ThroughputAndLatency::report(ostream& o) const { + Throughput::report(o); + if (samples) { + o << fixed << setprecision(2) + << '\t' << min << '\t' << max << '\t' << total/samples; + } +} + +ReporterBase::ReporterBase(ostream& o, int batch, bool wantHeader) + : batchSize(batch), batchCount(0), headerPrinted(!wantHeader), out(o) +{} + +ReporterBase::~ReporterBase() {} + +/** Count message in the statistics */ +void ReporterBase::message(const messaging::Message& m) { + if (!overall.get()) overall = create(); + overall->message(m); + if (batchSize) { + if (!batch.get()) batch = create(); + batch->message(m); + if (++batchCount == batchSize) { + header(); + batch->report(out); + out << endl; + batch = create(); + batchCount = 0; + } + } +} + +/** Print overall report. */ +void ReporterBase::report() { + if (!overall.get()) overall = create(); + header(); + overall->report(out); + out << endl; +} + +void ReporterBase::header() { + if (!headerPrinted) { + if (!overall.get()) overall = create(); + overall->header(out); + out << endl; + headerPrinted = true; + } +} + + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/Statistics.h b/qpid/cpp/src/tests/Statistics.h new file mode 100644 index 0000000000..091046a17f --- /dev/null +++ b/qpid/cpp/src/tests/Statistics.h @@ -0,0 +1,111 @@ +#ifndef TESTS_STATISTICS_H +#define TESTS_STATISTICS_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> +#include <limits> +#include <iosfwd> +#include <memory> + +namespace qpid { + +namespace messaging { +class Message; +} + +namespace tests { + +class Statistic { + public: + virtual ~Statistic(); + virtual void message(const messaging::Message&) = 0; + virtual void report(std::ostream&) const = 0; + virtual void header(std::ostream&) const = 0; +}; + +class Throughput : public Statistic { + public: + Throughput(); + virtual void message(const messaging::Message&); + virtual void report(std::ostream&) const; + virtual void header(std::ostream&) const; + + protected: + int messages; + + private: + bool started; + sys::AbsTime start; +}; + +class ThroughputAndLatency : public Throughput { + public: + ThroughputAndLatency(); + virtual void message(const messaging::Message&); + virtual void report(std::ostream&) const; + virtual void header(std::ostream&) const; + + private: + double total, min, max; // Milliseconds + int samples; +}; + +/** Report batch and overall statistics */ +class ReporterBase { + public: + virtual ~ReporterBase(); + + /** Count message in the statistics */ + void message(const messaging::Message& m); + + /** Print overall report. */ + void report(); + + protected: + ReporterBase(std::ostream& o, int batchSize, bool wantHeader); + virtual std::auto_ptr<Statistic> create() = 0; + + private: + void header(); + void report(const Statistic& s); + std::auto_ptr<Statistic> overall; + std::auto_ptr<Statistic> batch; + int batchSize, batchCount; + bool stopped, headerPrinted; + std::ostream& out; +}; + +template <class Stats> class Reporter : public ReporterBase { + public: + Reporter(std::ostream& o, int batchSize, bool wantHeader) + : ReporterBase(o, batchSize, wantHeader) {} + + virtual std::auto_ptr<Statistic> create() { + return std::auto_ptr<Statistic>(new Stats); + } +}; + +}} // namespace qpid::tests + +#endif /*!TESTS_STATISTICS_H*/ diff --git a/qpid/cpp/src/tests/StoreStatus.cpp b/qpid/cpp/src/tests/StoreStatus.cpp new file mode 100644 index 0000000000..43d4cfd920 --- /dev/null +++ b/qpid/cpp/src/tests/StoreStatus.cpp @@ -0,0 +1,117 @@ +/* + * + * 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. + * + */ + + +#include "unit_test.h" +#include "test_tools.h" +#include "qpid/cluster/StoreStatus.h" +#include "qpid/framing/Uuid.h" +#include <boost/assign.hpp> +#include <boost/filesystem/operations.hpp> + +using namespace std; +using namespace qpid::cluster; +using namespace qpid::framing; +using namespace qpid::framing::cluster; +using namespace boost::assign; +using namespace boost::filesystem; + + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(StoreStatusTestSuite) + +const char* TEST_DIR = "StoreStatus.tmp"; + +struct TestDir { + TestDir() { + remove_all(TEST_DIR); + create_directory(TEST_DIR); + } + ~TestDir() { + remove_all(TEST_DIR); + } +}; + +QPID_AUTO_TEST_CASE(testLoadEmpty) { + TestDir td; + StoreStatus ss(TEST_DIR); + BOOST_CHECK_EQUAL(ss.getState(), STORE_STATE_NO_STORE); + BOOST_CHECK(!ss.getClusterId()); + BOOST_CHECK(!ss.getShutdownId()); + ss.load(); + BOOST_CHECK_EQUAL(ss.getState(), STORE_STATE_EMPTY_STORE); + BOOST_CHECK(!ss.getShutdownId()); +} + +QPID_AUTO_TEST_CASE(testSaveLoadDirty) { + TestDir td; + Uuid clusterId = Uuid(true); + StoreStatus ss(TEST_DIR); + ss.load(); + ss.setClusterId(clusterId); + ss.dirty(); + BOOST_CHECK_EQUAL(ss.getState(), STORE_STATE_DIRTY_STORE); + + StoreStatus ss2(TEST_DIR); + ss2.load(); + BOOST_CHECK_EQUAL(ss2.getState(), STORE_STATE_DIRTY_STORE); + BOOST_CHECK_EQUAL(ss2.getClusterId(), clusterId); + BOOST_CHECK(!ss2.getShutdownId()); +} + +QPID_AUTO_TEST_CASE(testSaveLoadClean) { + TestDir td; + Uuid clusterId = Uuid(true); + Uuid shutdownId = Uuid(true); + StoreStatus ss(TEST_DIR); + ss.load(); + ss.setClusterId(clusterId); + ss.clean(shutdownId); + BOOST_CHECK_EQUAL(ss.getState(), STORE_STATE_CLEAN_STORE); + + StoreStatus ss2(TEST_DIR); + ss2.load(); + BOOST_CHECK_EQUAL(ss2.getState(), STORE_STATE_CLEAN_STORE); + BOOST_CHECK_EQUAL(ss2.getClusterId(), clusterId); + BOOST_CHECK_EQUAL(ss2.getShutdownId(), shutdownId); +} + +QPID_AUTO_TEST_CASE(testMarkDirty) { + // Save clean then mark to dirty. + TestDir td; + Uuid clusterId = Uuid(true); + Uuid shutdownId = Uuid(true); + StoreStatus ss(TEST_DIR); + ss.load(); + ss.setClusterId(clusterId); + ss.dirty(); + ss.clean(shutdownId); + ss.dirty(); + + StoreStatus ss2(TEST_DIR); + ss2.load(); + BOOST_CHECK_EQUAL(ss2.getState(), STORE_STATE_DIRTY_STORE); + BOOST_CHECK_EQUAL(ss2.getClusterId(), clusterId); + BOOST_CHECK(!ss2.getShutdownId()); +} + +QPID_AUTO_TEST_SUITE_END() + + }} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/StringUtils.cpp b/qpid/cpp/src/tests/StringUtils.cpp new file mode 100644 index 0000000000..6a19119288 --- /dev/null +++ b/qpid/cpp/src/tests/StringUtils.cpp @@ -0,0 +1,77 @@ +/* + * + * 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 "qpid/StringUtils.h" + +#include "unit_test.h" + +QPID_AUTO_TEST_SUITE(StringUtilsTestSuite) + +using namespace qpid; +using std::string; + +QPID_AUTO_TEST_CASE(testSplit_general) +{ + std::vector<std::string> results = split("a bbbb, car,d123,,,e", ", "); + BOOST_CHECK_EQUAL(5u, results.size()); + BOOST_CHECK_EQUAL(string("a"), results[0]); + BOOST_CHECK_EQUAL(string("bbbb"), results[1]); + BOOST_CHECK_EQUAL(string("car"), results[2]); + BOOST_CHECK_EQUAL(string("d123"), results[3]); + BOOST_CHECK_EQUAL(string("e"), results[4]); +} + +QPID_AUTO_TEST_CASE(testSplit_noDelims) +{ + std::vector<std::string> results = split("abc", ", "); + BOOST_CHECK_EQUAL(1u, results.size()); + BOOST_CHECK_EQUAL(string("abc"), results[0]); +} + +QPID_AUTO_TEST_CASE(testSplit_delimAtEnd) +{ + std::vector<std::string> results = split("abc def,,", ", "); + BOOST_CHECK_EQUAL(2u, results.size()); + BOOST_CHECK_EQUAL(string("abc"), results[0]); + BOOST_CHECK_EQUAL(string("def"), results[1]); +} + +QPID_AUTO_TEST_CASE(testSplit_delimAtStart) +{ + std::vector<std::string> results = split(",,abc def", ", "); + BOOST_CHECK_EQUAL(2u, results.size()); + BOOST_CHECK_EQUAL(string("abc"), results[0]); + BOOST_CHECK_EQUAL(string("def"), results[1]); +} + +QPID_AUTO_TEST_CASE(testSplit_onlyDelims) +{ + std::vector<std::string> results = split(",, , ", ", "); + BOOST_CHECK_EQUAL(0u, results.size()); +} + +QPID_AUTO_TEST_CASE(testSplit_empty) +{ + std::vector<std::string> results = split("", ", "); + BOOST_CHECK_EQUAL(0u, results.size()); +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/TestMessageStore.h b/qpid/cpp/src/tests/TestMessageStore.h new file mode 100644 index 0000000000..20e0b755b2 --- /dev/null +++ b/qpid/cpp/src/tests/TestMessageStore.h @@ -0,0 +1,63 @@ +#ifndef _tests_TestMessageStore_h +#define _tests_TestMessageStore_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/NullMessageStore.h" +#include <vector> + +using namespace qpid; +using namespace qpid::broker; +using namespace qpid::framing; + +namespace qpid { +namespace tests { + +typedef std::pair<string, boost::intrusive_ptr<PersistableMessage> > msg_queue_pair; + +class TestMessageStore : public NullMessageStore +{ + public: + std::vector<boost::intrusive_ptr<PersistableMessage> > dequeued; + std::vector<msg_queue_pair> enqueued; + + void dequeue(TransactionContext*, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& /*queue*/) + { + dequeued.push_back(msg); + } + + void enqueue(TransactionContext*, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue) + { + msg->enqueueComplete(); + enqueued.push_back(msg_queue_pair(queue.getName(), msg)); + } + + TestMessageStore() : NullMessageStore() {} + ~TestMessageStore(){} +}; + +}} // namespace qpid::tests + +#endif diff --git a/qpid/cpp/src/tests/TestOptions.h b/qpid/cpp/src/tests/TestOptions.h new file mode 100644 index 0000000000..f8da0f59cf --- /dev/null +++ b/qpid/cpp/src/tests/TestOptions.h @@ -0,0 +1,79 @@ +#ifndef _TestOptions_ +#define _TestOptions_ +/* + * + * 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/Options.h" +#include "qpid/log/Options.h" +#include "qpid/Url.h" +#include "qpid/log/Logger.h" +#include "qpid/client/Connection.h" +#include "ConnectionOptions.h" + +#include <iostream> +#include <exception> + +namespace qpid { + +struct TestOptions : public qpid::Options +{ + TestOptions(const std::string& helpText_=std::string(), + const std::string& argv0=std::string()) + : Options("Test Options"), help(false), log(argv0), helpText(helpText_) + { + addOptions() + ("help", optValue(help), "print this usage statement"); + add(con); + add(log); + } + + /** As well as parsing, throw help message if requested. */ + void parse(int argc, char** argv) { + try { + qpid::Options::parse(argc, argv); + } catch (const std::exception& e) { + std::ostringstream msg; + msg << *this << std::endl << std::endl << e.what() << std::endl; + throw qpid::Options::Exception(msg.str()); + } + qpid::log::Logger::instance().configure(log); + if (help) { + std::ostringstream msg; + msg << *this << std::endl << std::endl << helpText << std::endl; + throw qpid::Options::Exception(msg.str()); + } + } + + /** Open a connection using option values */ + void open(qpid::client::Connection& connection) { + connection.open(con); + } + + + bool help; + ConnectionOptions con; + qpid::log::Options log; + std::string helpText; +}; + +} + +#endif diff --git a/qpid/cpp/src/tests/TimerTest.cpp b/qpid/cpp/src/tests/TimerTest.cpp new file mode 100644 index 0000000000..6a0a196f4e --- /dev/null +++ b/qpid/cpp/src/tests/TimerTest.cpp @@ -0,0 +1,130 @@ + +/* + * + * 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/Timer.h" +#include "qpid/sys/Monitor.h" +#include "unit_test.h" +#include <math.h> +#include <iostream> +#include <memory> +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> + +using namespace qpid::sys; +using boost::intrusive_ptr; +using boost::dynamic_pointer_cast; + +namespace qpid { +namespace tests { + +class Counter +{ + Mutex lock; + uint counter; + public: + Counter() : counter(0) {} + uint next() + { + Mutex::ScopedLock l(lock); + return ++counter; + } +}; + +class TestTask : public TimerTask +{ + const AbsTime start; + const Duration expected; + AbsTime end; + bool fired; + uint position; + Monitor monitor; + Counter& counter; + + public: + TestTask(Duration timeout, Counter& _counter) + : TimerTask(timeout, "Test"), start(now()), expected(timeout), end(start), fired(false), counter(_counter) {} + + void fire() + { + Monitor::ScopedLock l(monitor); + fired = true; + position = counter.next(); + end = now(); + monitor.notify(); + } + + void check(uint expected_position, uint64_t tolerance = 500 * TIME_MSEC) + { + Monitor::ScopedLock l(monitor); + BOOST_CHECK(fired); + BOOST_CHECK_EQUAL(expected_position, position); + Duration actual(start, end); +#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 + std::string msg(boost::lexical_cast<std::string>(boost::format("tolerance = %1%, difference = %2%") % tolerance % difference)); + BOOST_CHECK_MESSAGE(difference < tolerance, msg); + } + + void wait(Duration d) + { + Monitor::ScopedLock l(monitor); + monitor.wait(AbsTime(now(), d)); + } +}; + +class DummyRunner : public Runnable +{ + public: + void run() {} +}; + +QPID_AUTO_TEST_SUITE(TimerTestSuite) + +QPID_AUTO_TEST_CASE(testGeneral) +{ + Counter counter; + Timer timer; + intrusive_ptr<TestTask> task1(new TestTask(Duration(3 * TIME_SEC), counter)); + intrusive_ptr<TestTask> task2(new TestTask(Duration(1 * TIME_SEC), counter)); + intrusive_ptr<TestTask> task3(new TestTask(Duration(4 * TIME_SEC), counter)); + intrusive_ptr<TestTask> task4(new TestTask(Duration(2 * TIME_SEC), counter)); + + timer.add(task1); + timer.add(task2); + timer.add(task3); + timer.add(task4); + + dynamic_pointer_cast<TestTask>(task3)->wait(Duration(6 * TIME_SEC)); + + dynamic_pointer_cast<TestTask>(task1)->check(3); + dynamic_pointer_cast<TestTask>(task2)->check(1); + dynamic_pointer_cast<TestTask>(task3)->check(4); + dynamic_pointer_cast<TestTask>(task4)->check(2); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/TopicExchangeTest.cpp b/qpid/cpp/src/tests/TopicExchangeTest.cpp new file mode 100644 index 0000000000..ff8931f9c9 --- /dev/null +++ b/qpid/cpp/src/tests/TopicExchangeTest.cpp @@ -0,0 +1,406 @@ +/* + * 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/TopicExchange.h" +#include "unit_test.h" +#include "test_tools.h" + +using namespace qpid::broker; +using namespace std; + + +namespace qpid { +namespace broker { + +// Class for exercising the pattern match code in the TopicExchange +class TopicExchange::TopicExchangeTester { + +public: + typedef std::vector<std::string> BindingVec; + +private: + // binding node iterator that collects all routes that are bound + class TestFinder : public TopicExchange::BindingNode::TreeIterator { + public: + TestFinder(BindingVec& m) : bv(m) {}; + ~TestFinder() {}; + bool visit(BindingNode& node) { + if (!node.bindings.bindingVector.empty()) + bv.push_back(node.routePattern); + return true; + } + + BindingVec& bv; + }; + +public: + TopicExchangeTester() {}; + ~TopicExchangeTester() {}; + bool addBindingKey(const std::string& bKey) { + string routingPattern = normalize(bKey); + BindingKey *bk = bindingTree.addBindingKey(routingPattern); + if (bk) { + // push a dummy binding to mark this node as "non-leaf" + bk->bindingVector.push_back(Binding::shared_ptr()); + return true; + } + return false; + } + + bool removeBindingKey(const std::string& bKey){ + string routingPattern = normalize(bKey); + BindingKey *bk = bindingTree.getBindingKey(routingPattern); + if (bk) { + bk->bindingVector.pop_back(); + if (bk->bindingVector.empty()) { + // no more bindings - remove this node + bindingTree.removeBindingKey(routingPattern); + } + return true; + } + return false; + } + + void findMatches(const std::string& rKey, BindingVec& matches) { + TestFinder testFinder(matches); + bindingTree.iterateMatch( rKey, testFinder ); + } + + void getAll(BindingVec& bindings) { + TestFinder testFinder(bindings); + bindingTree.iterateAll( testFinder ); + } + +private: + TopicExchange::BindingNode bindingTree; +}; +} // namespace broker + + +namespace tests { + +QPID_AUTO_TEST_SUITE(TopicExchangeTestSuite) + +#define CHECK_NORMALIZED(expect, pattern) BOOST_CHECK_EQUAL(expect, TopicExchange::normalize(pattern)); + +namespace { + // return the count of bindings that match 'pattern' + int match(TopicExchange::TopicExchangeTester &tt, + const std::string& pattern) + { + TopicExchange::TopicExchangeTester::BindingVec bv; + tt.findMatches(pattern, bv); + return int(bv.size()); + } + + // return true if expected contains exactly all bindings that match + // against pattern. + bool compare(TopicExchange::TopicExchangeTester& tt, + const std::string& pattern, + const TopicExchange::TopicExchangeTester::BindingVec& expected) + { + TopicExchange::TopicExchangeTester::BindingVec bv; + tt.findMatches(pattern, bv); + if (expected.size() != bv.size()) { + // std::cout << "match failed 1 f=[" << bv << "]" << std::endl; + // std::cout << "match failed 1 e=[" << expected << "]" << std::endl; + return false; + } + TopicExchange::TopicExchangeTester::BindingVec::const_iterator i; + for (i = expected.begin(); i != expected.end(); i++) { + TopicExchange::TopicExchangeTester::BindingVec::iterator j; + for (j = bv.begin(); j != bv.end(); j++) { + // std::cout << "matched [" << *j << "]" << std::endl; + if (*i == *j) break; + } + if (j == bv.end()) { + // std::cout << "match failed 2 [" << bv << "]" << std::endl; + return false; + } + } + return true; + } +} + + +QPID_AUTO_TEST_CASE(testNormalize) +{ + CHECK_NORMALIZED("", ""); + CHECK_NORMALIZED("a.b.c", "a.b.c"); + CHECK_NORMALIZED("a.*.c", "a.*.c"); + CHECK_NORMALIZED("#", "#"); + CHECK_NORMALIZED("#", "#.#.#.#"); + CHECK_NORMALIZED("*.*.*.#", "#.*.#.*.#.#.*"); + CHECK_NORMALIZED("a.*.*.*.#", "a.*.#.*.#.*.#"); + CHECK_NORMALIZED("a.*.*.*.#", "a.*.#.*.#.*"); + CHECK_NORMALIZED("*.*.*.#", "*.#.#.*.*.#"); +} + +QPID_AUTO_TEST_CASE(testPlain) +{ + TopicExchange::TopicExchangeTester tt; + string pattern("ab.cd.e"); + + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, "ab.cd.e")); + BOOST_CHECK_EQUAL(0, match(tt, "abx.cd.e")); + BOOST_CHECK_EQUAL(0, match(tt, "ab.cd")); + BOOST_CHECK_EQUAL(0, match(tt, "ab.cd..e.")); + BOOST_CHECK_EQUAL(0, match(tt, "ab.cd.e.")); + BOOST_CHECK_EQUAL(0, match(tt, ".ab.cd.e")); + BOOST_CHECK(tt.removeBindingKey(pattern)); + + pattern = ""; + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, "")); + BOOST_CHECK(tt.removeBindingKey(pattern)); + + pattern = "."; + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, ".")); + BOOST_CHECK(tt.removeBindingKey(pattern)); +} + + +QPID_AUTO_TEST_CASE(testStar) +{ + TopicExchange::TopicExchangeTester tt; + string pattern("a.*.b"); + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, "a.xx.b")); + BOOST_CHECK_EQUAL(0, match(tt, "a.b")); + BOOST_CHECK(tt.removeBindingKey(pattern)); + + pattern = "*.x"; + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, "y.x")); + BOOST_CHECK_EQUAL(1, match(tt, ".x")); + BOOST_CHECK_EQUAL(0, match(tt, "x")); + BOOST_CHECK(tt.removeBindingKey(pattern)); + + pattern = "x.x.*"; + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, "x.x.y")); + BOOST_CHECK_EQUAL(1, match(tt, "x.x.")); + BOOST_CHECK_EQUAL(0, match(tt, "x.x")); + BOOST_CHECK_EQUAL(0, match(tt, "q.x.y")); + BOOST_CHECK(tt.removeBindingKey(pattern)); +} + +QPID_AUTO_TEST_CASE(testHash) +{ + TopicExchange::TopicExchangeTester tt; + string pattern("a.#.b"); + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, "a.b")); + BOOST_CHECK_EQUAL(1, match(tt, "a.x.b")); + BOOST_CHECK_EQUAL(1, match(tt, "a..x.y.zz.b")); + BOOST_CHECK_EQUAL(0, match(tt, "a.b.")); + BOOST_CHECK_EQUAL(0, match(tt, "q.x.b")); + BOOST_CHECK(tt.removeBindingKey(pattern)); + + pattern = "a.#"; + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, "a")); + BOOST_CHECK_EQUAL(1, match(tt, "a.b")); + BOOST_CHECK_EQUAL(1, match(tt, "a.b.c")); + BOOST_CHECK(tt.removeBindingKey(pattern)); + + pattern = "#.a"; + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, "a")); + BOOST_CHECK_EQUAL(1, match(tt, "x.y.a")); + BOOST_CHECK(tt.removeBindingKey(pattern)); + + pattern = "a.#.b.#.c"; + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, "a.b.c")); + BOOST_CHECK_EQUAL(1, match(tt, "a.x.b.y.c")); + BOOST_CHECK_EQUAL(1, match(tt, "a.x.x.b.y.y.c")); + BOOST_CHECK(tt.removeBindingKey(pattern)); +} + +QPID_AUTO_TEST_CASE(testMixed) +{ + TopicExchange::TopicExchangeTester tt; + string pattern("*.x.#.y"); + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, "a.x.y")); + BOOST_CHECK_EQUAL(1, match(tt, "a.x.p.qq.y")); + BOOST_CHECK_EQUAL(0, match(tt, "a.a.x.y")); + BOOST_CHECK_EQUAL(0, match(tt, "aa.x.b.c")); + BOOST_CHECK(tt.removeBindingKey(pattern)); + + pattern = "a.#.b.*"; + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, "a.b.x")); + BOOST_CHECK_EQUAL(1, match(tt, "a.x.x.x.b.x")); + BOOST_CHECK(tt.removeBindingKey(pattern)); + + pattern = "*.*.*.#"; + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, "x.y.z")); + BOOST_CHECK_EQUAL(1, match(tt, "x.y.z.a.b.c")); + BOOST_CHECK_EQUAL(0, match(tt, "x.y")); + BOOST_CHECK_EQUAL(0, match(tt, "x")); + BOOST_CHECK(tt.removeBindingKey(pattern)); +} + + +QPID_AUTO_TEST_CASE(testMultiple) +{ + TopicExchange::TopicExchangeTester tt; + const std::string bindings[] = + { "a", "b", + "a.b", "b.c", + "a.b.c.d", "b.c.d.e", + "a.*", "a.#", "a.*.#", + "#.b", "*.b", "*.#.b", + "a.*.b", "a.#.b", "a.*.#.b", + "*.b.*", "#.b.#", + }; + const size_t nBindings = sizeof(bindings)/sizeof(bindings[0]); + + // setup bindings + for (size_t idx = 0; idx < nBindings; idx++) { + BOOST_CHECK(tt.addBindingKey(bindings[idx])); + } + + { + // read all bindings, and verify all are present + TopicExchange::TopicExchangeTester::BindingVec b; + tt.getAll(b); + BOOST_CHECK_EQUAL(b.size(), nBindings); + for (size_t idx = 0; idx < nBindings; idx++) { + bool found = false; + for (TopicExchange::TopicExchangeTester::BindingVec::iterator i = b.begin(); + i != b.end(); i++) { + if (*i == bindings[idx]) { + found = true; + break; + } + } + BOOST_CHECK(found); + } + } + + { // test match on pattern "a" + const std::string matches[] = { "a", "a.#" }; + const size_t nMatches = 2; + TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); + BOOST_CHECK(compare(tt, "a", expected)); + } + + { // test match on pattern "a.z" + const std::string matches[] = { "a.*", "a.#", "a.*.#" }; + const size_t nMatches = 3; + TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); + BOOST_CHECK(compare(tt, "a.z", expected)); + } + + { // test match on pattern "a.b" + const std::string matches[] = { + "a.b", "a.*", "a.#", "a.*.#", + "#.b", "#.b.#", "*.#.b", "*.b", + "a.#.b" + }; + const size_t nMatches = 9; + TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); + BOOST_CHECK(compare(tt, "a.b", expected)); + } + + { // test match on pattern "a.c.c.b" + + const std::string matches[] = { + "#.b", "#.b.#", "*.#.b", "a.#.b", + "a.#", "a.*.#.b", "a.*.#" + }; + const size_t nMatches = 7; + TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); + BOOST_CHECK(compare(tt, "a.c.c.b", expected)); + } + + { // test match on pattern "a.b.c" + + const std::string matches[] = { + "#.b.#", "*.b.*", "a.#", "a.*.#" + }; + const size_t nMatches = 4; + TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); + BOOST_CHECK(compare(tt, "a.b.c", expected)); + } + + { // test match on pattern "b" + + const std::string matches[] = { + "#.b", "#.b.#", "b" + }; + const size_t nMatches = 3; + TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); + BOOST_CHECK(compare(tt, "b", expected)); + } + + { // test match on pattern "x.b" + + const std::string matches[] = { + "#.b", "#.b.#", "*.#.b", "*.b" + }; + const size_t nMatches = 4; + TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); + BOOST_CHECK(compare(tt, "x.b", expected)); + } + + { // test match on pattern "x.y.z.b" + + const std::string matches[] = { + "#.b", "#.b.#", "*.#.b" + }; + const size_t nMatches = 3; + TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); + BOOST_CHECK(compare(tt, "x.y.z.b", expected)); + } + + { // test match on pattern "x.y.z.b.a.b.c" + + const std::string matches[] = { + "#.b.#", "#.b.#" + }; + const size_t nMatches = 2; + TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); + BOOST_CHECK(compare(tt, "x.y.z.b.a.b.c", expected)); + } + + { // test match on pattern "a.b.c.d" + + const std::string matches[] = { + "#.b.#", "a.#", "a.*.#", "a.b.c.d", + }; + const size_t nMatches = 4; + TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); + BOOST_CHECK(compare(tt, "a.b.c.d", expected)); + } + + // cleanup bindings + for (size_t idx = 0; idx < nBindings; idx++) { + BOOST_CHECK(tt.removeBindingKey(bindings[idx])); + } +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/TxBufferTest.cpp b/qpid/cpp/src/tests/TxBufferTest.cpp new file mode 100644 index 0000000000..4807026ab7 --- /dev/null +++ b/qpid/cpp/src/tests/TxBufferTest.cpp @@ -0,0 +1,181 @@ +/* + * + * 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/TxBuffer.h" +#include "unit_test.h" +#include <iostream> +#include <vector> +#include "TxMocks.h" + +using namespace qpid::broker; +using boost::static_pointer_cast; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(TxBufferTestSuite) + +QPID_AUTO_TEST_CASE(testCommitLocal) +{ + MockTransactionalStore store; + store.expectBegin().expectCommit(); + + MockTxOp::shared_ptr opA(new MockTxOp()); + opA->expectPrepare().expectCommit(); + MockTxOp::shared_ptr opB(new MockTxOp()); + opB->expectPrepare().expectPrepare().expectCommit().expectCommit();//opB enlisted twice to test relative order + MockTxOp::shared_ptr opC(new MockTxOp()); + opC->expectPrepare().expectCommit(); + + TxBuffer buffer; + buffer.enlist(static_pointer_cast<TxOp>(opA)); + buffer.enlist(static_pointer_cast<TxOp>(opB)); + buffer.enlist(static_pointer_cast<TxOp>(opB));//opB enlisted twice + buffer.enlist(static_pointer_cast<TxOp>(opC)); + + BOOST_CHECK(buffer.commitLocal(&store)); + store.check(); + BOOST_CHECK(store.isCommitted()); + opA->check(); + opB->check(); + opC->check(); +} + +QPID_AUTO_TEST_CASE(testFailOnCommitLocal) +{ + MockTransactionalStore store; + store.expectBegin().expectAbort(); + + MockTxOp::shared_ptr opA(new MockTxOp()); + opA->expectPrepare().expectRollback(); + MockTxOp::shared_ptr opB(new MockTxOp(true)); + opB->expectPrepare().expectRollback(); + MockTxOp::shared_ptr opC(new MockTxOp());//will never get prepare as b will fail + opC->expectRollback(); + + TxBuffer buffer; + buffer.enlist(static_pointer_cast<TxOp>(opA)); + buffer.enlist(static_pointer_cast<TxOp>(opB)); + buffer.enlist(static_pointer_cast<TxOp>(opC)); + + BOOST_CHECK(!buffer.commitLocal(&store)); + BOOST_CHECK(store.isAborted()); + store.check(); + opA->check(); + opB->check(); + opC->check(); +} + +QPID_AUTO_TEST_CASE(testPrepare) +{ + MockTxOp::shared_ptr opA(new MockTxOp()); + opA->expectPrepare(); + MockTxOp::shared_ptr opB(new MockTxOp()); + opB->expectPrepare(); + MockTxOp::shared_ptr opC(new MockTxOp()); + opC->expectPrepare(); + + TxBuffer buffer; + buffer.enlist(static_pointer_cast<TxOp>(opA)); + buffer.enlist(static_pointer_cast<TxOp>(opB)); + buffer.enlist(static_pointer_cast<TxOp>(opC)); + + BOOST_CHECK(buffer.prepare(0)); + opA->check(); + opB->check(); + opC->check(); +} + +QPID_AUTO_TEST_CASE(testFailOnPrepare) +{ + MockTxOp::shared_ptr opA(new MockTxOp()); + opA->expectPrepare(); + MockTxOp::shared_ptr opB(new MockTxOp(true)); + opB->expectPrepare(); + MockTxOp::shared_ptr opC(new MockTxOp());//will never get prepare as b will fail + + TxBuffer buffer; + buffer.enlist(static_pointer_cast<TxOp>(opA)); + buffer.enlist(static_pointer_cast<TxOp>(opB)); + buffer.enlist(static_pointer_cast<TxOp>(opC)); + + BOOST_CHECK(!buffer.prepare(0)); + opA->check(); + opB->check(); + opC->check(); +} + +QPID_AUTO_TEST_CASE(testRollback) +{ + MockTxOp::shared_ptr opA(new MockTxOp()); + opA->expectRollback(); + MockTxOp::shared_ptr opB(new MockTxOp(true)); + opB->expectRollback(); + MockTxOp::shared_ptr opC(new MockTxOp()); + opC->expectRollback(); + + TxBuffer buffer; + buffer.enlist(static_pointer_cast<TxOp>(opA)); + buffer.enlist(static_pointer_cast<TxOp>(opB)); + buffer.enlist(static_pointer_cast<TxOp>(opC)); + + buffer.rollback(); + opA->check(); + opB->check(); + opC->check(); +} + +QPID_AUTO_TEST_CASE(testBufferIsClearedAfterRollback) +{ + MockTxOp::shared_ptr opA(new MockTxOp()); + opA->expectRollback(); + MockTxOp::shared_ptr opB(new MockTxOp()); + opB->expectRollback(); + + TxBuffer buffer; + buffer.enlist(static_pointer_cast<TxOp>(opA)); + buffer.enlist(static_pointer_cast<TxOp>(opB)); + + buffer.rollback(); + buffer.commit();//second call should not reach ops + opA->check(); + opB->check(); +} + +QPID_AUTO_TEST_CASE(testBufferIsClearedAfterCommit) +{ + MockTxOp::shared_ptr opA(new MockTxOp()); + opA->expectCommit(); + MockTxOp::shared_ptr opB(new MockTxOp()); + opB->expectCommit(); + + TxBuffer buffer; + buffer.enlist(static_pointer_cast<TxOp>(opA)); + buffer.enlist(static_pointer_cast<TxOp>(opB)); + + buffer.commit(); + buffer.rollback();//second call should not reach ops + opA->check(); + opB->check(); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/TxMocks.h b/qpid/cpp/src/tests/TxMocks.h new file mode 100644 index 0000000000..72cb50cd21 --- /dev/null +++ b/qpid/cpp/src/tests/TxMocks.h @@ -0,0 +1,235 @@ +/* + * + * 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 _tests_TxMocks_h +#define _tests_TxMocks_h + + +#include "qpid/Exception.h" +#include "qpid/Msg.h" +#include "qpid/broker/TransactionalStore.h" +#include "qpid/broker/TxOp.h" +#include <iostream> +#include <vector> + +using namespace qpid::broker; +using boost::static_pointer_cast; +using std::string; + +namespace qpid { +namespace tests { + +template <class T> void assertEqualVector(std::vector<T>& expected, std::vector<T>& actual){ + unsigned int i = 0; + while(i < expected.size() && i < actual.size()){ + BOOST_CHECK_EQUAL(expected[i], actual[i]); + i++; + } + if (i < expected.size()) { + throw qpid::Exception(QPID_MSG("Missing " << expected[i])); + } else if (i < actual.size()) { + throw qpid::Exception(QPID_MSG("Extra " << actual[i])); + } + BOOST_CHECK_EQUAL(expected.size(), actual.size()); +} + +class TxOpConstants{ +protected: + const string PREPARE; + const string COMMIT; + const string ROLLBACK; + + TxOpConstants() : PREPARE("PREPARE"), COMMIT("COMMIT"), ROLLBACK("ROLLBACK") {} +}; + +class MockTxOp : public TxOp, public TxOpConstants{ + std::vector<string> expected; + std::vector<string> actual; + bool failOnPrepare; + string debugName; +public: + typedef boost::shared_ptr<MockTxOp> shared_ptr; + + MockTxOp() : failOnPrepare(false) {} + MockTxOp(bool _failOnPrepare) : failOnPrepare(_failOnPrepare) {} + + void setDebugName(string name){ + debugName = name; + } + + void printExpected(){ + std::cout << std::endl << "MockTxOp[" << debugName << "] expects: "; + for (std::vector<string>::iterator i = expected.begin(); i < expected.end(); i++) { + if(i != expected.begin()) std::cout << ", "; + std::cout << *i; + } + std::cout << std::endl; + } + + void printActual(){ + std::cout << std::endl << "MockTxOp[" << debugName << "] actual: "; + for (std::vector<string>::iterator i = actual.begin(); i < actual.end(); i++) { + if(i != actual.begin()) std::cout << ", "; + std::cout << *i; + } + std::cout << std::endl; + } + + bool prepare(TransactionContext*) throw(){ + actual.push_back(PREPARE); + return !failOnPrepare; + } + void commit() throw(){ + actual.push_back(COMMIT); + } + void rollback() throw(){ + if(!debugName.empty()) std::cout << std::endl << "MockTxOp[" << debugName << "]::rollback()" << std::endl; + actual.push_back(ROLLBACK); + } + MockTxOp& expectPrepare(){ + expected.push_back(PREPARE); + return *this; + } + MockTxOp& expectCommit(){ + expected.push_back(COMMIT); + return *this; + } + MockTxOp& expectRollback(){ + expected.push_back(ROLLBACK); + return *this; + } + void check(){ + assertEqualVector(expected, actual); + } + + void accept(TxOpConstVisitor&) const {} + + ~MockTxOp(){} +}; + +class MockTransactionalStore : public TransactionalStore{ + const string BEGIN; + const string BEGIN2PC; + const string PREPARE; + const string COMMIT; + const string ABORT; + std::vector<string> expected; + std::vector<string> actual; + + enum states {OPEN = 1, PREPARED = 2, COMMITTED = 3, ABORTED = 4}; + int state; + + class TestTransactionContext : public TPCTransactionContext{ + MockTransactionalStore* store; + public: + TestTransactionContext(MockTransactionalStore* _store) : store(_store) {} + void prepare(){ + if(!store->isOpen()) throw "txn already completed"; + store->state = PREPARED; + } + + void commit(){ + if(!store->isOpen() && !store->isPrepared()) throw "txn already completed"; + store->state = COMMITTED; + } + + void abort(){ + if(!store->isOpen() && !store->isPrepared()) throw "txn already completed"; + store->state = ABORTED; + } + ~TestTransactionContext(){} + }; + +public: + MockTransactionalStore() : + BEGIN("BEGIN"), BEGIN2PC("BEGIN2PC"), PREPARE("PREPARE"), COMMIT("COMMIT"), ABORT("ABORT"), state(OPEN){} + + void collectPreparedXids(std::set<std::string>&) + { + throw "Operation not supported"; + } + + std::auto_ptr<TPCTransactionContext> begin(const std::string&){ + actual.push_back(BEGIN2PC); + std::auto_ptr<TPCTransactionContext> txn(new TestTransactionContext(this)); + return txn; + } + std::auto_ptr<TransactionContext> begin(){ + actual.push_back(BEGIN); + std::auto_ptr<TransactionContext> txn(new TestTransactionContext(this)); + return txn; + } + void prepare(TPCTransactionContext& ctxt){ + actual.push_back(PREPARE); + dynamic_cast<TestTransactionContext&>(ctxt).prepare(); + } + void commit(TransactionContext& ctxt){ + actual.push_back(COMMIT); + dynamic_cast<TestTransactionContext&>(ctxt).commit(); + } + void abort(TransactionContext& ctxt){ + actual.push_back(ABORT); + dynamic_cast<TestTransactionContext&>(ctxt).abort(); + } + MockTransactionalStore& expectBegin(){ + expected.push_back(BEGIN); + return *this; + } + MockTransactionalStore& expectBegin2PC(){ + expected.push_back(BEGIN2PC); + return *this; + } + MockTransactionalStore& expectPrepare(){ + expected.push_back(PREPARE); + return *this; + } + MockTransactionalStore& expectCommit(){ + expected.push_back(COMMIT); + return *this; + } + MockTransactionalStore& expectAbort(){ + expected.push_back(ABORT); + return *this; + } + void check(){ + assertEqualVector(expected, actual); + } + + bool isPrepared(){ + return state == PREPARED; + } + + bool isCommitted(){ + return state == COMMITTED; + } + + bool isAborted(){ + return state == ABORTED; + } + + bool isOpen() const{ + return state == OPEN; + } + ~MockTransactionalStore(){} +}; + +}} // namespace qpid::tests + +#endif diff --git a/qpid/cpp/src/tests/TxPublishTest.cpp b/qpid/cpp/src/tests/TxPublishTest.cpp new file mode 100644 index 0000000000..210abf0a5b --- /dev/null +++ b/qpid/cpp/src/tests/TxPublishTest.cpp @@ -0,0 +1,99 @@ +/* + * + * 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/NullMessageStore.h" +#include "qpid/broker/RecoveryManager.h" +#include "qpid/broker/TxPublish.h" +#include "unit_test.h" +#include <iostream> +#include <list> +#include <vector> +#include "MessageUtils.h" +#include "TestMessageStore.h" + +using std::list; +using std::pair; +using std::vector; +using boost::intrusive_ptr; +using namespace qpid::broker; +using namespace qpid::framing; + +namespace qpid { +namespace tests { + +struct TxPublishTest +{ + + TestMessageStore store; + Queue::shared_ptr queue1; + Queue::shared_ptr queue2; + intrusive_ptr<Message> msg; + TxPublish op; + + TxPublishTest() : + queue1(new Queue("queue1", false, &store, 0)), + queue2(new Queue("queue2", false, &store, 0)), + msg(MessageUtils::createMessage("exchange", "routing_key", false, "id")), + op(msg) + { + msg->getProperties<DeliveryProperties>()->setDeliveryMode(PERSISTENT); + op.deliverTo(queue1); + op.deliverTo(queue2); + } +}; + + +QPID_AUTO_TEST_SUITE(TxPublishTestSuite) + +QPID_AUTO_TEST_CASE(testPrepare) +{ + TxPublishTest t; + + intrusive_ptr<PersistableMessage> pmsg = boost::static_pointer_cast<PersistableMessage>(t.msg); + //ensure messages are enqueued in store + t.op.prepare(0); + BOOST_CHECK_EQUAL((size_t) 2, t.store.enqueued.size()); + BOOST_CHECK_EQUAL(string("queue1"), t.store.enqueued[0].first); + 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))->isIngressComplete()); +} + +QPID_AUTO_TEST_CASE(testCommit) +{ + TxPublishTest t; + + //ensure messages are delivered to queue + t.op.prepare(0); + t.op.commit(); + 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))->isIngressComplete()); + BOOST_CHECK_EQUAL(t.msg, msg_dequeue); + + BOOST_CHECK_EQUAL((uint32_t) 1, t.queue2->getMessageCount()); + BOOST_CHECK_EQUAL(t.msg, t.queue2->get().payload); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/Url.cpp b/qpid/cpp/src/tests/Url.cpp new file mode 100644 index 0000000000..234a62ee91 --- /dev/null +++ b/qpid/cpp/src/tests/Url.cpp @@ -0,0 +1,90 @@ +/* + * + * 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. + * + */ + + +#include "unit_test.h" +#include "test_tools.h" +#include "qpid/Url.h" +#include <boost/assign.hpp> + +using namespace std; +using namespace qpid; +using namespace boost::assign; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(UrlTestSuite) + +#define URL_CHECK_STR(STR) BOOST_CHECK_EQUAL(Url(STR).str(), STR) +#define URL_CHECK_INVALID(STR) BOOST_CHECK_THROW(Url(STR), Url::Invalid) + +QPID_AUTO_TEST_CASE(TestParseTcp) { + URL_CHECK_STR("amqp:tcp:host:42"); + URL_CHECK_STR("amqp:tcp:host-._~%ff%23:42"); // unreserved chars and pct encoded hex. + // Check defaults + BOOST_CHECK_EQUAL(Url("amqp:host:42").str(), "amqp:tcp:host:42"); + BOOST_CHECK_EQUAL(Url("amqp:tcp:host").str(), "amqp:tcp:host:5672"); + BOOST_CHECK_EQUAL(Url("host").str(), "amqp:tcp:host:5672"); +} + +QPID_AUTO_TEST_CASE(TestParseInvalid) { + //host is required: + URL_CHECK_INVALID("amqp:tcp:"); + URL_CHECK_INVALID("amqp:"); + URL_CHECK_INVALID("amqp::42"); + URL_CHECK_INVALID(""); + + // Port must be numeric + URL_CHECK_INVALID("host:badPort"); +} + +QPID_AUTO_TEST_CASE(TestParseXyz) { + Url::addProtocol("xyz"); + URL_CHECK_STR("amqp:xyz:host:123"); + BOOST_CHECK_EQUAL(Url("xyz:host").str(), "amqp:xyz:host:5672"); +} + +QPID_AUTO_TEST_CASE(TestParseMultiAddress) { + Url::addProtocol("xyz"); + URL_CHECK_STR("amqp:tcp:host:0,xyz:foo:123,tcp:foo:0,xyz:bar:1"); + URL_CHECK_STR("amqp:xyz:foo:222,tcp:foo:0"); + URL_CHECK_INVALID("amqp:tcp:h:0,"); + URL_CHECK_INVALID(",amqp:tcp:h"); +} + +QPID_AUTO_TEST_CASE(TestParseUserPass) { + URL_CHECK_STR("amqp:user/pass@tcp:host:123"); + URL_CHECK_STR("amqp:user@tcp:host:123"); + BOOST_CHECK_EQUAL(Url("user/pass@host").str(), "amqp:user/pass@tcp:host:5672"); + BOOST_CHECK_EQUAL(Url("user@host").str(), "amqp:user@tcp:host:5672"); + + Url u("user/pass@host"); + BOOST_CHECK_EQUAL(u.getUser(), "user"); + BOOST_CHECK_EQUAL(u.getPass(), "pass"); + Url v("foo@host"); + BOOST_CHECK_EQUAL(v.getUser(), "foo"); + BOOST_CHECK_EQUAL(v.getPass(), ""); + u = v; + BOOST_CHECK_EQUAL(u.getUser(), "foo"); + BOOST_CHECK_EQUAL(u.getPass(), ""); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/Uuid.cpp b/qpid/cpp/src/tests/Uuid.cpp new file mode 100644 index 0000000000..0195455ca3 --- /dev/null +++ b/qpid/cpp/src/tests/Uuid.cpp @@ -0,0 +1,137 @@ +/* + * + * 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. + * + */ + +#include "qpid/framing/Uuid.h" +#include "qpid/framing/Buffer.h" +#include "qpid/types/Uuid.h" +#include "qpid/sys/alloca.h" + +#include "unit_test.h" + +#include <set> + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(UuidTestSuite) + +using namespace std; +using namespace qpid::framing; + +struct UniqueSet : public std::set<Uuid> { + void operator()(const Uuid& uuid) { + BOOST_REQUIRE(find(uuid) == end()); + insert(uuid); + } +}; + +QPID_AUTO_TEST_CASE(testUuidCtor) { + // Uniqueness + boost::array<Uuid,1000> uuids; + for_each(uuids.begin(), uuids.end(), mem_fun_ref(&Uuid::generate)); + UniqueSet unique; + for_each(uuids.begin(), uuids.end(), unique); +} + +boost::array<uint8_t, 16> sample = {{'\x1b', '\x4e', '\x28', '\xba', '\x2f', '\xa1', '\x11', '\xd2', '\x88', '\x3f', '\xb9', '\xa7', '\x61', '\xbd', '\xe3', '\xfb'}}; +const string sampleStr("1b4e28ba-2fa1-11d2-883f-b9a761bde3fb"); +const string zeroStr("00000000-0000-0000-0000-000000000000"); + +QPID_AUTO_TEST_CASE(testUuidIstream) { + Uuid uuid; + istringstream in(sampleStr); + in >> uuid; + BOOST_CHECK(!in.fail()); + BOOST_CHECK(uuid == sample); + + istringstream is(zeroStr); + Uuid zero; + is >> zero; + BOOST_CHECK(!in.fail()); + BOOST_CHECK_EQUAL(zero, Uuid()); +} + +QPID_AUTO_TEST_CASE(testUuidOstream) { + Uuid uuid(sample.c_array()); + ostringstream out; + out << uuid; + BOOST_CHECK(out.good()); + BOOST_CHECK_EQUAL(out.str(), sampleStr); + + ostringstream os; + os << Uuid(); + BOOST_CHECK(out.good()); + BOOST_CHECK_EQUAL(os.str(), zeroStr); +} + +QPID_AUTO_TEST_CASE(testUuidIOstream) { + Uuid a(true), b(true); + ostringstream os; + os << a << endl << b; + Uuid aa, bb; + istringstream is(os.str()); + is >> aa >> ws >> bb; + BOOST_CHECK(os.good()); + BOOST_CHECK_EQUAL(a, aa); + BOOST_CHECK_EQUAL(b, bb); +} + +QPID_AUTO_TEST_CASE(testUuidEncodeDecode) { + char* buff = static_cast<char*>(::alloca(Uuid::size())); + Buffer wbuf(buff, Uuid::size()); + Uuid uuid(sample.c_array()); + uuid.encode(wbuf); + + Buffer rbuf(buff, Uuid::size()); + Uuid decoded; + decoded.decode(rbuf); + BOOST_CHECK_EQUAL(string(sample.begin(), sample.end()), + string(decoded.begin(), decoded.end())); +} + +QPID_AUTO_TEST_CASE(testTypesUuid) +{ + //tests for the Uuid class in the types namespace (introduced + //to avoid pulling in dependencies from framing) + types::Uuid a; + types::Uuid b(true); + types::Uuid c(true); + types::Uuid d(b); + types::Uuid e; + e = c; + + BOOST_CHECK(!a); + BOOST_CHECK(b); + + BOOST_CHECK(a != b); + BOOST_CHECK(b != c); + + BOOST_CHECK_EQUAL(b, d); + BOOST_CHECK_EQUAL(c, e); + + ostringstream out; + out << b; + istringstream in(out.str()); + in >> a; + BOOST_CHECK(!in.fail()); + BOOST_CHECK_EQUAL(a, b); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/Variant.cpp b/qpid/cpp/src/tests/Variant.cpp new file mode 100644 index 0000000000..40f1c0cf75 --- /dev/null +++ b/qpid/cpp/src/tests/Variant.cpp @@ -0,0 +1,775 @@ +/* + * + * 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 "qpid/types/Variant.h" +#include "qpid/amqp_0_10/Codecs.h" + +#include "unit_test.h" + +using namespace qpid::types; +using namespace qpid::amqp_0_10; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(VariantSuite) + +QPID_AUTO_TEST_CASE(testConversions) +{ + Variant value; + + //string to float/double + value = "1.5"; + BOOST_CHECK_EQUAL((float) 1.5, value.asFloat()); + BOOST_CHECK_EQUAL((double) 1.5, value.asDouble()); + + //float to string or double + value = 1.5f; + BOOST_CHECK_EQUAL((float) 1.5, value.asFloat()); + BOOST_CHECK_EQUAL((double) 1.5, value.asDouble()); + BOOST_CHECK_EQUAL(std::string("1.5"), value.asString()); + + //double to string (conversion to float not valid) + value = 1.5; + BOOST_CHECK_THROW(value.asFloat(), InvalidConversion); + BOOST_CHECK_EQUAL((double) 1.5, value.asDouble()); + BOOST_CHECK_EQUAL(std::string("1.5"), value.asString()); + + //uint8 to larger unsigned ints and string + value = (uint8_t) 7; + BOOST_CHECK_EQUAL((uint8_t) 7, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 7, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 7, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 7, value.asUint64()); + BOOST_CHECK_EQUAL(std::string("7"), value.asString()); + + value = (uint16_t) 8; + BOOST_CHECK_EQUAL(std::string("8"), value.asString()); + value = (uint32_t) 9; + BOOST_CHECK_EQUAL(std::string("9"), value.asString()); + + //uint32 to larger unsigned ints and string + value = (uint32_t) 9999999; + BOOST_CHECK_EQUAL((uint32_t) 9999999, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 9999999, value.asUint64()); + BOOST_CHECK_EQUAL(std::string("9999999"), value.asString()); + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + + value = "true"; + BOOST_CHECK(value.asBool()); + value = "false"; + BOOST_CHECK(!value.asBool()); + value = "1"; + BOOST_CHECK(value.asBool()); + value = "0"; + BOOST_CHECK(!value.asBool()); + value = "other"; + 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; + + //uint8 (in 7 bits) to other uints, ints + value = (uint8_t) 7; + BOOST_CHECK_EQUAL((uint8_t) 7, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 7, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 7, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 7, value.asUint64()); + BOOST_CHECK_EQUAL((int8_t) 7, value.asInt8()); + BOOST_CHECK_EQUAL((int16_t) 7, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 7, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 7, value.asInt64()); + + //uint8 (in 8 bits) to other uints, ints + value = (uint8_t) 200; + BOOST_CHECK_EQUAL((uint8_t) 200, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 200, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 200, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 200, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 200, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 200, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 200, value.asInt64()); + + + + //uint16 (in 7 bits) to other uints, ints + value = (uint16_t) 120; + BOOST_CHECK_EQUAL((uint8_t) 120, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 120, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 120, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 120, value.asUint64()); + BOOST_CHECK_EQUAL((int8_t) 120, value.asInt8()); + BOOST_CHECK_EQUAL((int16_t) 120, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 120, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 120, value.asInt64()); + + //uint16 (more than 7 bits) to other uints, ints + value = (uint16_t) 240; + BOOST_CHECK_EQUAL((uint8_t) 240, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 240, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 240, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 240, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 240, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 240, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 240, value.asInt64()); + + //uint16 (more than 8 bits) to other uints, ints + value = (uint16_t) 1000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_EQUAL((uint16_t) 1000, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 1000, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 1000, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 1000, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 1000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 1000, value.asInt64()); + + //uint16 (more than 15 bits) to other uints, ints + value = (uint16_t) 32770; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_EQUAL((uint16_t) 32770, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 32770, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 32770, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_EQUAL((int32_t) 32770, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 32770, value.asInt64()); + + + + //uint32 (in 7 bits) to other uints, ints + value = (uint32_t) 120; + BOOST_CHECK_EQUAL((uint8_t) 120, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 120, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 120, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 120, value.asUint64()); + BOOST_CHECK_EQUAL((int8_t) 120, value.asInt8()); + BOOST_CHECK_EQUAL((int16_t) 120, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 120, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 120, value.asInt64()); + + //uint32 (more than 7 bits) to other uints, ints + value = (uint32_t) 240; + BOOST_CHECK_EQUAL((uint8_t) 240, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 240, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 240, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 240, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 240, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 240, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 240, value.asInt64()); + + //uint32 (more than 8 bits) to other uints, ints + value = (uint32_t) 1000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_EQUAL((uint16_t) 1000, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 1000, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 1000, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 1000, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 1000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 1000, value.asInt64()); + + //uint32 (more than 15 bits) to other uints, ints + value = (uint32_t) 32770; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_EQUAL((uint16_t) 32770, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 32770, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 32770, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_EQUAL((int32_t) 32770, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 32770, value.asInt64()); + + //uint32 (more than 16 bits) to other uints, ints + value = (uint32_t) 66000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_EQUAL((uint32_t) 66000, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 66000, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_EQUAL((int32_t) 66000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 66000, value.asInt64()); + + + + //uint64 (in 7 bits) to other uints, ints + value = (uint64_t) 120; + BOOST_CHECK_EQUAL((uint8_t) 120, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 120, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 120, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 120, value.asUint64()); + BOOST_CHECK_EQUAL((int8_t) 120, value.asInt8()); + BOOST_CHECK_EQUAL((int16_t) 120, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 120, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 120, value.asInt64()); + + //uint64 (more than 7 bits) to other uints, ints + value = (uint64_t) 240; + BOOST_CHECK_EQUAL((uint8_t) 240, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 240, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 240, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 240, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 240, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 240, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 240, value.asInt64()); + + //uint64 (more than 8 bits) to other uints, ints + value = (uint64_t) 1000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_EQUAL((uint16_t) 1000, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 1000, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 1000, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 1000, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 1000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 1000, value.asInt64()); + + //uint64 (more than 15 bits) to other uints, ints + value = (uint64_t) 32770; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_EQUAL((uint16_t) 32770, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 32770, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 32770, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_EQUAL((int32_t) 32770, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 32770, value.asInt64()); + + //uint64 (more than 16 bits) to other uints, ints + value = (uint64_t) 66000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_EQUAL((uint32_t) 66000, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 66000, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_EQUAL((int32_t) 66000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 66000, value.asInt64()); + + //uint64 (more than 31 bits) to other uints, ints + value = (uint64_t) 3000000000ul; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_EQUAL((uint32_t) 3000000000ul, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 3000000000ul, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt32(), InvalidConversion); + BOOST_CHECK_EQUAL((int64_t) 3000000000ul, value.asInt64()); + + //uint64 (more than 32 bits) to other uints, ints + value = (uint64_t) 7000000000ull; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_EQUAL((uint64_t) 7000000000ull, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt32(), InvalidConversion); + BOOST_CHECK_EQUAL((int64_t) 7000000000ull, value.asInt64()); + + //uint64 (more than 63 bits) to other uints, ints + value = (uint64_t) 0x8000000000000000ull; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_EQUAL((uint64_t) 0x8000000000000000ull, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt32(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt64(), InvalidConversion); +} + +QPID_AUTO_TEST_CASE(testSizeConversionsInt) +{ + Variant value; + + //int8 (positive in 7 bits) + value = (int8_t) 100; + BOOST_CHECK_EQUAL((uint8_t) 100, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 100, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 100, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 100, value.asUint64()); + BOOST_CHECK_EQUAL((int8_t) 100, value.asInt8()); + BOOST_CHECK_EQUAL((int16_t) 100, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 100, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 100, value.asInt64()); + + //int8 (negative) + value = (int8_t) -100; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint64(), InvalidConversion); + BOOST_CHECK_EQUAL((int8_t) -100, value.asInt8()); + BOOST_CHECK_EQUAL((int16_t) -100, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) -100, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) -100, value.asInt64()); + + + + //int16 (positive in 7 bits) + value = (int16_t) 100; + BOOST_CHECK_EQUAL((uint8_t) 100, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 100, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 100, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 100, value.asUint64()); + BOOST_CHECK_EQUAL((int8_t) 100, value.asInt8()); + BOOST_CHECK_EQUAL((int16_t) 100, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 100, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 100, value.asInt64()); + + //int16 (positive in 8 bits) + value = (int16_t) 200; + BOOST_CHECK_EQUAL((uint8_t) 200, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 200, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 200, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 200, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 200, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 200, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 200, value.asInt64()); + + //int16 (positive in more than 8 bits) + value = (int16_t) 1000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_EQUAL((uint16_t) 1000, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 1000, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 1000, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 1000, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 1000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 1000, value.asInt64()); + + //int16 (negative in 7 bits) + value = (int16_t) -100; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint64(), InvalidConversion); + BOOST_CHECK_EQUAL((int8_t) -100, value.asInt8()); + BOOST_CHECK_EQUAL((int16_t) -100, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) -100, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) -100, value.asInt64()); + + //int16 (negative in more than 7 bits) + value = (int16_t) -1000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint64(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) -1000, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) -1000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) -1000, value.asInt64()); + + + + //int32 (positive in 7 bits) + value = (int32_t) 100; + BOOST_CHECK_EQUAL((uint8_t) 100, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 100, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 100, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 100, value.asUint64()); + BOOST_CHECK_EQUAL((int8_t) 100, value.asInt8()); + BOOST_CHECK_EQUAL((int16_t) 100, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 100, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 100, value.asInt64()); + + //int32 (positive in 8 bits) + value = (int32_t) 200; + BOOST_CHECK_EQUAL((uint8_t) 200, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 200, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 200, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 200, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 200, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 200, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 200, value.asInt64()); + + //int32 (positive in more than 8 bits) + value = (int32_t) 1000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_EQUAL((uint16_t) 1000, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 1000, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 1000, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 1000, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 1000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 1000, value.asInt64()); + + //int32 (positive in more than 15 bits) + value = (int32_t) 40000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_EQUAL((uint16_t) 40000, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 40000, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 40000, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_EQUAL((int32_t) 40000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 40000, value.asInt64()); + + //int32 (negative in 7 bits) + value = (int32_t) -100; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint64(), InvalidConversion); + BOOST_CHECK_EQUAL((int8_t) -100, value.asInt8()); + BOOST_CHECK_EQUAL((int16_t) -100, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) -100, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) -100, value.asInt64()); + + //int32 (negative in more than 7 bits) + value = (int32_t) -1000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint64(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) -1000, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) -1000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) -1000, value.asInt64()); + + //int32 (negative in more than 15 bits) + value = (int32_t) -40000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint64(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_EQUAL((int32_t) -40000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) -40000, value.asInt64()); + + + + //int64 (positive in 7 bits) + value = (int64_t) 100; + BOOST_CHECK_EQUAL((uint8_t) 100, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 100, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 100, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 100, value.asUint64()); + BOOST_CHECK_EQUAL((int8_t) 100, value.asInt8()); + BOOST_CHECK_EQUAL((int16_t) 100, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 100, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 100, value.asInt64()); + + //int64 (positive in 8 bits) + value = (int64_t) 200; + BOOST_CHECK_EQUAL((uint8_t) 200, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 200, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 200, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 200, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 200, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 200, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 200, value.asInt64()); + + //int64 (positive in more than 8 bits) + value = (int64_t) 1000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_EQUAL((uint16_t) 1000, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 1000, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 1000, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 1000, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 1000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 1000, value.asInt64()); + + //int64 (positive in more than 15 bits) + value = (int64_t) 40000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_EQUAL((uint16_t) 40000, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 40000, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 40000, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_EQUAL((int32_t) 40000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 40000, value.asInt64()); + + //int64 (positive in more than 31 bits) + value = (int64_t) 3000000000ll; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_EQUAL((uint32_t) 3000000000ll, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 3000000000ll, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt32(), InvalidConversion); + BOOST_CHECK_EQUAL((int64_t) 3000000000ll, value.asInt64()); + + //int64 (positive in more than 32 bits) + value = (int64_t) 5000000000ll; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_EQUAL((uint64_t) 5000000000ll, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt32(), InvalidConversion); + BOOST_CHECK_EQUAL((int64_t) 5000000000ll, value.asInt64()); + + //int64 (negative in 7 bits) + value = (int64_t) -100; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint64(), InvalidConversion); + BOOST_CHECK_EQUAL((int8_t) -100, value.asInt8()); + BOOST_CHECK_EQUAL((int16_t) -100, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) -100, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) -100, value.asInt64()); + + //int64 (negative in more than 7 bits) + value = (int64_t) -1000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint64(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) -1000, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) -1000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) -1000, value.asInt64()); + + //int64 (negative in more than 15 bits) + value = (int64_t) -40000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint64(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_EQUAL((int32_t) -40000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) -40000, value.asInt64()); + + //int64 (negative in more than 31 bits) + value = (int64_t) -3000000000ll; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint64(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt32(), InvalidConversion); + BOOST_CHECK_EQUAL((int64_t) -3000000000ll, value.asInt64()); +} + +QPID_AUTO_TEST_CASE(testAssignment) +{ + Variant value("abc"); + Variant other = value; + BOOST_CHECK_EQUAL(VAR_STRING, value.getType()); + BOOST_CHECK_EQUAL(other.getType(), value.getType()); + BOOST_CHECK_EQUAL(other.asString(), value.asString()); + + const uint32_t i(1000); + value = i; + BOOST_CHECK_EQUAL(VAR_UINT32, value.getType()); + BOOST_CHECK_EQUAL(VAR_STRING, other.getType()); +} + +QPID_AUTO_TEST_CASE(testList) +{ + const std::string s("abc"); + const float f(9.876f); + const int16_t x(1000); + + Variant value = Variant::List(); + value.asList().push_back(Variant(s)); + value.asList().push_back(Variant(f)); + value.asList().push_back(Variant(x)); + BOOST_CHECK_EQUAL(3u, value.asList().size()); + Variant::List::const_iterator i = value.asList().begin(); + + BOOST_CHECK(i != value.asList().end()); + BOOST_CHECK_EQUAL(VAR_STRING, i->getType()); + BOOST_CHECK_EQUAL(s, i->asString()); + i++; + + BOOST_CHECK(i != value.asList().end()); + BOOST_CHECK_EQUAL(VAR_FLOAT, i->getType()); + BOOST_CHECK_EQUAL(f, i->asFloat()); + i++; + + BOOST_CHECK(i != value.asList().end()); + BOOST_CHECK_EQUAL(VAR_INT16, i->getType()); + BOOST_CHECK_EQUAL(x, i->asInt16()); + i++; + + BOOST_CHECK(i == value.asList().end()); +} + +QPID_AUTO_TEST_CASE(testMap) +{ + const std::string red("red"); + const float pi(3.14f); + const int16_t x(1000); + const Uuid u(true); + + Variant value = Variant::Map(); + value.asMap()["colour"] = red; + value.asMap()["pi"] = pi; + value.asMap()["my-key"] = x; + value.asMap()["id"] = u; + BOOST_CHECK_EQUAL(4u, value.asMap().size()); + + BOOST_CHECK_EQUAL(VAR_STRING, value.asMap()["colour"].getType()); + BOOST_CHECK_EQUAL(red, value.asMap()["colour"].asString()); + + BOOST_CHECK_EQUAL(VAR_FLOAT, value.asMap()["pi"].getType()); + BOOST_CHECK_EQUAL(pi, value.asMap()["pi"].asFloat()); + + BOOST_CHECK_EQUAL(VAR_INT16, value.asMap()["my-key"].getType()); + BOOST_CHECK_EQUAL(x, value.asMap()["my-key"].asInt16()); + + BOOST_CHECK_EQUAL(VAR_UUID, value.asMap()["id"].getType()); + BOOST_CHECK_EQUAL(u, value.asMap()["id"].asUuid()); + + value.asMap()["my-key"] = "now it's a string"; + BOOST_CHECK_EQUAL(VAR_STRING, value.asMap()["my-key"].getType()); + BOOST_CHECK_EQUAL(std::string("now it's a string"), value.asMap()["my-key"].asString()); +} + +QPID_AUTO_TEST_CASE(testIsEqualTo) +{ + BOOST_CHECK_EQUAL(Variant("abc"), Variant("abc")); + BOOST_CHECK_EQUAL(Variant(1234), Variant(1234)); + + Variant a = Variant::Map(); + a.asMap()["colour"] = "red"; + a.asMap()["pi"] = 3.14f; + a.asMap()["my-key"] = 1234; + Variant b = Variant::Map(); + b.asMap()["colour"] = "red"; + b.asMap()["pi"] = 3.14f; + b.asMap()["my-key"] = 1234; + BOOST_CHECK_EQUAL(a, b); +} + +QPID_AUTO_TEST_CASE(testEncoding) +{ + Variant a("abc"); + a.setEncoding("utf8"); + Variant b = a; + Variant map = Variant::Map(); + map.asMap()["a"] = a; + map.asMap()["b"] = b; + BOOST_CHECK_EQUAL(a.getEncoding(), std::string("utf8")); + BOOST_CHECK_EQUAL(a.getEncoding(), b.getEncoding()); + BOOST_CHECK_EQUAL(a.getEncoding(), map.asMap()["a"].getEncoding()); + BOOST_CHECK_EQUAL(b.getEncoding(), map.asMap()["b"].getEncoding()); + BOOST_CHECK_EQUAL(map.asMap()["a"].getEncoding(), map.asMap()["b"].getEncoding()); +} + +QPID_AUTO_TEST_CASE(testBufferEncoding) +{ + Variant a("abc"); + a.setEncoding("utf8"); + std::string buffer; + + Variant::Map inMap, outMap; + inMap["a"] = a; + + MapCodec::encode(inMap, buffer); + MapCodec::decode(buffer, outMap); + BOOST_CHECK_EQUAL(inMap, outMap); + + inMap["b"] = Variant(std::string(65535, 'X')); + inMap["b"].setEncoding("utf16"); + MapCodec::encode(inMap, buffer); + MapCodec::decode(buffer, outMap); + BOOST_CHECK_EQUAL(inMap, outMap); + + inMap["fail"] = Variant(std::string(65536, 'X')); + inMap["fail"].setEncoding("utf16"); + BOOST_CHECK_THROW(MapCodec::encode(inMap, buffer), std::exception); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/XmlClientSessionTest.cpp b/qpid/cpp/src/tests/XmlClientSessionTest.cpp new file mode 100644 index 0000000000..b3b7f12b53 --- /dev/null +++ b/qpid/cpp/src/tests/XmlClientSessionTest.cpp @@ -0,0 +1,297 @@ +/* + * + * Licensed to the Apachef 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 "unit_test.h" +#include "test_tools.h" +#include "BrokerFixture.h" +#include "qpid/sys/Shlib.h" +#include "qpid/sys/Monitor.h" +#include "qpid/sys/Thread.h" +#include "qpid/sys/Runnable.h" +#include "qpid/client/Message.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/client/Connection.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/client/LocalQueue.h" +#include "qpid/client/Session.h" +#include "qpid/client/SubscriptionManager.h" + +#include <boost/optional.hpp> +#include <boost/lexical_cast.hpp> + +#include <vector> + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(XmlClientSessionTest) + +using namespace qpid::client; + +using namespace qpid::client::arg; +using namespace qpid::framing; +using namespace qpid; +using qpid::sys::Shlib; +using qpid::sys::Monitor; +using std::string; +using std::cout; +using std::endl; + + +Shlib shlib(getLibPath("XML_LIB")); + +class SubscribedLocalQueue : public LocalQueue { + private: + SubscriptionManager& subscriptions; + public: + SubscribedLocalQueue(SubscriptionManager& subs) : subscriptions(subs) {} + Message get () { return pop(); } + Message get (sys::Duration timeout) { return pop(timeout); } + virtual ~SubscribedLocalQueue() {} +}; + + +struct SimpleListener : public MessageListener +{ + Monitor lock; + std::vector<Message> messages; + + void received(Message& msg) + { + Monitor::ScopedLock l(lock); + messages.push_back(msg); + lock.notifyAll(); + } + + void waitFor(const uint n) + { + Monitor::ScopedLock l(lock); + while (messages.size() < n) { + lock.wait(); + } + } +}; + +struct ClientSessionFixture : public ProxySessionFixture +{ + void declareSubscribe(const string& q="odd_blue", + const string& dest="xml") + { + session.queueDeclare(queue=q); + session.messageSubscribe(queue=q, destination=dest, acquireMode=1); + session.messageFlow(destination=dest, unit=0, value=0xFFFFFFFF);//messages + session.messageFlow(destination=dest, unit=1, value=0xFFFFFFFF);//bytes + } +}; + +// ########### START HERE #################################### + +QPID_AUTO_TEST_CASE(testXmlBinding) { + ClientSessionFixture f; + + SubscriptionManager subscriptions(f.session); + SubscribedLocalQueue localQueue(subscriptions); + + f.session.exchangeDeclare(qpid::client::arg::exchange="xml", qpid::client::arg::type="xml"); + f.session.queueDeclare(qpid::client::arg::queue="odd_blue"); + subscriptions.subscribe(localQueue, "odd_blue"); + + FieldTable binding; + binding.setString("xquery", "declare variable $color external;" + "(./message/id mod 2 = 1) and ($color = 'blue')"); + f.session.exchangeBind(qpid::client::arg::exchange="xml", qpid::client::arg::queue="odd_blue", qpid::client::arg::bindingKey="query_name", qpid::client::arg::arguments=binding); + + Message message; + message.getDeliveryProperties().setRoutingKey("query_name"); + + message.getHeaders().setString("color", "blue"); + string m = "<message><id>1</id></message>"; + message.setData(m); + + f.session.messageTransfer(qpid::client::arg::content=message, qpid::client::arg::destination="xml"); + + Message m2 = localQueue.get(1*qpid::sys::TIME_SEC); + BOOST_CHECK_EQUAL(m, m2.getData()); +} + +/** + * Ensure that multiple queues can be bound using the same routing key + */ +QPID_AUTO_TEST_CASE(testXMLBindMultipleQueues) { + ClientSessionFixture f; + + + f.session.exchangeDeclare(arg::exchange="xml", arg::type="xml"); + f.session.queueDeclare(arg::queue="blue", arg::exclusive=true, arg::autoDelete=true); + f.session.queueDeclare(arg::queue="red", arg::exclusive=true, arg::autoDelete=true); + + FieldTable blue; + blue.setString("xquery", "./colour = 'blue'"); + f.session.exchangeBind(arg::exchange="xml", arg::queue="blue", arg::bindingKey="by-colour", arg::arguments=blue); + FieldTable red; + red.setString("xquery", "./colour = 'red'"); + f.session.exchangeBind(arg::exchange="xml", arg::queue="red", arg::bindingKey="by-colour", arg::arguments=red); + + Message sent1("<colour>blue</colour>", "by-colour"); + f.session.messageTransfer(arg::content=sent1, arg::destination="xml"); + + Message sent2("<colour>red</colour>", "by-colour"); + f.session.messageTransfer(arg::content=sent2, arg::destination="xml"); + + Message received; + BOOST_CHECK(f.subs.get(received, "blue")); + BOOST_CHECK_EQUAL(sent1.getData(), received.getData()); + BOOST_CHECK(f.subs.get(received, "red")); + BOOST_CHECK_EQUAL(sent2.getData(), received.getData()); +} + +//### Test: Bad XML does not kill the server - and does not even +// raise an exception, the content is not required to be XML. + +QPID_AUTO_TEST_CASE(testXMLSendBadXML) { + ClientSessionFixture f; + + f.session.exchangeDeclare(arg::exchange="xml", arg::type="xml"); + f.session.queueDeclare(arg::queue="blue", arg::exclusive=true, arg::autoDelete=true)\ + ; + f.session.queueDeclare(arg::queue="red", arg::exclusive=true, arg::autoDelete=true); + + FieldTable blue; + blue.setString("xquery", "./colour = 'blue'"); + f.session.exchangeBind(arg::exchange="xml", arg::queue="blue", arg::bindingKey="by-c\ +olour", arg::arguments=blue); + FieldTable red; + red.setString("xquery", "./colour = 'red'"); + f.session.exchangeBind(arg::exchange="xml", arg::queue="red", arg::bindingKey="by-co\ +lour", arg::arguments=red); + + Message sent1("<>colour>blue</colour>", "by-colour"); + f.session.messageTransfer(arg::content=sent1, arg::destination="xml"); + + BOOST_CHECK_EQUAL(1, 1); +} + + +//### Test: Bad XQuery does not kill the server, but does raise an exception + +QPID_AUTO_TEST_CASE(testXMLBadXQuery) { + ClientSessionFixture f; + + f.session.exchangeDeclare(arg::exchange="xml", arg::type="xml"); + f.session.queueDeclare(arg::queue="blue", arg::exclusive=true, arg::autoDelete=true)\ + ; + + try { + ScopedSuppressLogging sl; // Supress logging of error messages for expected error. + FieldTable blue; + blue.setString("xquery", "./colour $=! 'blue'"); + f.session.exchangeBind(arg::exchange="xml", arg::queue="blue", arg::bindingKey="by-c\ +olour", arg::arguments=blue); + } + catch (const InternalErrorException& e) { + return; + } + BOOST_ERROR("A bad XQuery must raise an exception when used in an XML Binding."); + +} + + +//### Test: double, string, and integer field values can all be bound to queries + +QPID_AUTO_TEST_CASE(testXmlBindingUntyped) { + ClientSessionFixture f; + + SubscriptionManager subscriptions(f.session); + SubscribedLocalQueue localQueue(subscriptions); + + f.session.exchangeDeclare(qpid::client::arg::exchange="xml", qpid::client::arg::type="xml"); + f.session.queueDeclare(qpid::client::arg::queue="odd_blue"); + subscriptions.subscribe(localQueue, "odd_blue"); + + FieldTable binding; + binding.setString("xquery", + "declare variable $s external;" + "declare variable $i external;" + "declare variable $d external;" + "$s = 'string' and $i = 1 and $d < 1"); + f.session.exchangeBind(qpid::client::arg::exchange="xml", qpid::client::arg::queue="odd_blue", qpid::client::arg::bindingKey="query_name", qpid::client::arg::arguments=binding); + + Message message; + message.getDeliveryProperties().setRoutingKey("query_name"); + + message.getHeaders().setString("s", "string"); + message.getHeaders().setInt("i", 1); + message.getHeaders().setDouble("d", 0.5); + string m = "<message>Hi, Mom!</message>"; + message.setData(m); + + f.session.messageTransfer(qpid::client::arg::content=message, qpid::client::arg::destination="xml"); + + Message m2 = localQueue.get(1*qpid::sys::TIME_SEC); + BOOST_CHECK_EQUAL(m, m2.getData()); +} + + +//### Test: double, string, and integer field values can all be bound to queries + +QPID_AUTO_TEST_CASE(testXmlBindingTyped) { + ClientSessionFixture f; + + SubscriptionManager subscriptions(f.session); + SubscribedLocalQueue localQueue(subscriptions); + + f.session.exchangeDeclare(qpid::client::arg::exchange="xml", qpid::client::arg::type="xml"); + f.session.queueDeclare(qpid::client::arg::queue="odd_blue"); + subscriptions.subscribe(localQueue, "odd_blue"); + + FieldTable binding; + binding.setString("xquery", + "declare variable $s as xs:string external;" + "declare variable $i as xs:integer external;" + "declare variable $d external;" // XQilla bug when declaring xs:float, xs:double types? Fine if untyped, acts as float. + "$s = 'string' and $i = 1 and $d < 1"); + f.session.exchangeBind(qpid::client::arg::exchange="xml", qpid::client::arg::queue="odd_blue", qpid::client::arg::bindingKey="query_name", qpid::client::arg::arguments=binding); + + Message message; + message.getDeliveryProperties().setRoutingKey("query_name"); + + message.getHeaders().setString("s", "string"); + message.getHeaders().setInt("i", 1); + message.getHeaders().setDouble("d", 0.5); + string m = "<message>Hi, Mom!</message>"; + message.setData(m); + + f.session.messageTransfer(qpid::client::arg::content=message, qpid::client::arg::destination="xml"); + + Message m2 = localQueue.get(1*qpid::sys::TIME_SEC); + BOOST_CHECK_EQUAL(m, m2.getData()); +} + + +//### Test: Each session can provide its own definition for a query name + + + +//### Test: Bindings persist, surviving broker restart + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/acl.py b/qpid/cpp/src/tests/acl.py new file mode 100755 index 0000000000..5e9a150d8f --- /dev/null +++ b/qpid/cpp/src/tests/acl.py @@ -0,0 +1,1077 @@ +#!/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 +import qpid +from qpid.util import connect +from qpid.connection import Connection +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, policy='data_dir/policy.acl'): + self.f = open(policy,'w') + + def write(self,line): + self.f.write(line) + + def close(self): + self.f.close() + +class ACLTests(TestBase010): + + def get_session(self, user, passwd): + socket = connect(self.broker.host, self.broker.port) + connection = Connection (sock=socket, username=user, password=passwd, + mechanism="PLAIN") + connection.start() + return connection.session(str(uuid4())) + + def reload_acl(self): + 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 = 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 + #===================================== + + def test_deny_mode(self): + """ + Test the deny all mode + """ + 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') + aclf.close() + + result = self.reload_acl() + if (result.text.find("format error",0,len(result.text)) != -1): + self.fail(result) + + session = self.get_session('bob','bob') + try: + session.queue_declare(queue="deny_queue") + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request"); + self.fail("Error during queue create request"); + + try: + session.exchange_bind(exchange="amq.direct", queue="deny_queue", binding_key="routing_key") + self.fail("ACL should deny queue bind request"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + + def test_allow_mode(self): + """ + Test the allow all mode + """ + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID bind exchange\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result.text.find("format error",0,len(result.text)) != -1): + self.fail(result) + + session = self.get_session('bob','bob') + try: + session.queue_declare(queue="allow_queue") + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request"); + self.fail("Error during queue create request"); + + try: + session.exchange_bind(exchange="amq.direct", queue="allow_queue", binding_key="routing_key") + self.fail("ACL should deny queue bind request"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + + + #===================================== + # ACL file format tests + #===================================== + + def test_empty_groups(self): + """ + Test empty groups + """ + 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') + aclf.close() + + result = self.reload_acl() + if (result.text.find("Insufficient tokens for acl definition",0,len(result.text)) == -1): + self.fail("ACL Reader should reject the acl file due to empty group name") + + def test_illegal_acl_formats(self): + """ + Test illegal acl formats + """ + aclf = self.get_acl_file() + aclf.write('acl group admins bob@QPID joe@QPID\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result.text.find("Unknown ACL permission",0,len(result.text)) == -1): + self.fail(result) + + def test_illegal_extension_lines(self): + """ + Test illegal extension lines + """ + + aclf = self.get_acl_file() + aclf.write('group admins bob@QPID \n') + aclf.write(' \ \n') + aclf.write('joe@QPID \n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result.text.find("contains an illegal extension",0,len(result.text)) == -1): + self.fail(result) + + if (result.text.find("Non-continuation line must start with \"group\" or \"acl\"",0,len(result.text)) == -1): + self.fail(result) + + def test_llegal_extension_lines(self): + """ + Test proper extention lines + """ + 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 + aclf.write('host/123.example.com@TEST.COM\n') # should be allowed + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result.text.find("ACL format error",0,len(result.text)) != -1): + self.fail(result) + + def test_user_realm(self): + """ + Test a user defined without a realm + Ex. group admin rajith + """ + 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') + aclf.close() + + result = self.reload_acl() + if (result.text.find("Username 'bob' must contain a realm",0,len(result.text)) == -1): + self.fail(result) + + def test_allowed_chars_for_username(self): + """ + Test a user defined without a realm + Ex. group admin rajith + """ + 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 + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result.text.find("ACL format error",0,len(result.text)) != -1): + self.fail(result) + + 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() + + result = self.reload_acl() + if (result.text.find("Username \"joe$H@EXAMPLE.com\" contains illegal characters",0,len(result.text)) == -1): + self.fail(result) + + #===================================== + # ACL validation tests + #===================================== + + def test_illegal_queue_policy(self): + """ + Test illegal queue policy + """ + + 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() + + result = self.reload_acl() + expected = "ding is not a valid value for 'policytype', possible values are one of" \ + " { 'ring' 'ring_strict' 'flow_to_disk' 'reject' }"; + if (result.text != expected): + self.fail(result) + + def test_illegal_queue_size(self): + """ + Test illegal queue policy + """ + + 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() + + result = self.reload_acl() + expected = "-1 is not a valid value for 'maxqueuesize', " \ + "values should be between 0 and 9223372036854775807"; + if (result.text != expected): + self.fail(result) + + 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() + + result = self.reload_acl() + expected = "9223372036854775808 is not a valid value for 'maxqueuesize', " \ + "values should be between 0 and 9223372036854775807"; + if (result.text != expected): + self.fail(result) + + + def test_illegal_queue_count(self): + """ + Test illegal queue policy + """ + + 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() + + result = self.reload_acl() + expected = "-1 is not a valid value for 'maxqueuecount', " \ + "values should be between 0 and 9223372036854775807"; + if (result.text != expected): + self.fail(result) + + 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() + + result = self.reload_acl() + expected = "9223372036854775808 is not a valid value for 'maxqueuecount', " \ + "values should be between 0 and 9223372036854775807"; + if (result.text != expected): + self.fail(result) + + + #===================================== + # ACL queue tests + #===================================== + + def test_queue_allow_mode(self): + """ + Test cases for queue acl in allow mode + """ + 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') + aclf.write('acl deny bob@QPID purge queue name=q3\n') + aclf.write('acl deny bob@QPID delete queue name=q4\n') + aclf.write('acl deny bob@QPID create queue name=q5 maxqueuesize=1000 maxqueuecount=100\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result.text.find("format error",0,len(result.text)) != -1): + self.fail(result) + + session = self.get_session('bob','bob') + + try: + session.queue_declare(queue="q1", durable=True, passive=True) + self.fail("ACL should deny queue create request with name=q1 durable=true passive=true"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.policy_type"] = "ring" + session.queue_declare(queue="q2", exclusive=True, arguments=queue_options) + self.fail("ACL should deny queue create request with name=q2 exclusive=true qpid.policy_type=ring"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.policy_type"] = "ring_strict" + session.queue_declare(queue="q2", exclusive=True, arguments=queue_options) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request with name=q2 exclusive=true qpid.policy_type=ring_strict"); + + try: + queue_options = {} + queue_options["qpid.max_count"] = 200 + queue_options["qpid.max_size"] = 500 + session.queue_declare(queue="q5", exclusive=True, arguments=queue_options) + self.fail("ACL should deny queue create request with name=q2, qpid.max_size=500 and qpid.max_count=200"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.max_count"] = 200 + queue_options["qpid.max_size"] = 100 + session.queue_declare(queue="q2", exclusive=True, arguments=queue_options) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request with name=q2, qpid.max_size=100 and qpid.max_count=200 "); + try: + session.queue_declare(queue="q3", exclusive=True) + session.queue_declare(queue="q4", durable=True) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request for q3 and q4 with any parameter"); + + try: + session.queue_query(queue="q3") + self.fail("ACL should deny queue query request for q3"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.queue_purge(queue="q3") + self.fail("ACL should deny queue purge request for q3"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.queue_purge(queue="q4") + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue purge request for q4"); + + try: + session.queue_delete(queue="q4") + self.fail("ACL should deny queue delete request for q4"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.queue_delete(queue="q3") + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue delete request for q3"); + + + def test_queue_deny_mode(self): + """ + Test cases for queue acl in deny mode + """ + 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') + aclf.write('acl allow bob@QPID purge queue name=q3\n') + aclf.write('acl allow bob@QPID create queue name=q3\n') + aclf.write('acl allow bob@QPID create queue name=q4\n') + aclf.write('acl allow bob@QPID delete queue name=q4\n') + aclf.write('acl allow bob@QPID create queue name=q5 maxqueuesize=1000 maxqueuecount=100\n') + aclf.write('acl allow anonymous 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) + + session = self.get_session('bob','bob') + + try: + session.queue_declare(queue="q1", durable=True, passive=True) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request with name=q1 durable=true passive=true"); + + try: + session.queue_declare(queue="q1", durable=False, passive=False) + self.fail("ACL should deny queue create request with name=q1 durable=true passive=false"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.queue_declare(queue="q2", exclusive=False) + self.fail("ACL should deny queue create request with name=q2 exclusive=false"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.max_count"] = 200 + queue_options["qpid.max_size"] = 500 + session.queue_declare(queue="q5", arguments=queue_options) + self.fail("ACL should deny queue create request with name=q2 maxqueuesize=500 maxqueuecount=200"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.max_count"] = 100 + queue_options["qpid.max_size"] = 500 + session.queue_declare(queue="q5", arguments=queue_options) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request with name=q2 maxqueuesize=500 maxqueuecount=200"); + + try: + queue_options = {} + queue_options["qpid.policy_type"] = "ring" + session.queue_declare(queue="q2", exclusive=True, arguments=queue_options) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request for q2 with exclusive=true policytype=ring"); + + try: + session.queue_declare(queue="q3") + session.queue_declare(queue="q4") + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request for q3 and q4"); + + try: + session.queue_query(queue="q4") + self.fail("ACL should deny queue query request for q4"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.queue_purge(queue="q4") + self.fail("ACL should deny queue purge request for q4"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.queue_purge(queue="q3") + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue purge request for q3"); + + try: + session.queue_query(queue="q3") + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue query request for q3"); + + try: + session.queue_delete(queue="q3") + self.fail("ACL should deny queue delete request for q3"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.queue_delete(queue="q4") + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue delete request for q4"); + + #===================================== + # ACL exchange tests + #===================================== + + def test_exchange_acl_allow_mode(self): + session = self.get_session('bob','bob') + session.queue_declare(queue="baz") + + """ + Test cases for exchange acl in allow mode + """ + 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') + aclf.write('acl deny bob@QPID bind exchange name=myEx queuename=q1 routingkey=rk1\n') + aclf.write('acl deny bob@QPID unbind exchange name=myEx queuename=q1 routingkey=rk1\n') + aclf.write('acl deny bob@QPID delete exchange name=myEx\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result.text.find("format error",0,len(result.text)) != -1): + self.fail(result) + + session = self.get_session('bob','bob') + session.queue_declare(queue='q1') + session.queue_declare(queue='q2') + session.exchange_declare(exchange='myEx', type='direct') + + try: + session.exchange_declare(exchange='testEx', durable=True, passive=True) + self.fail("ACL should deny exchange create request with name=testEx durable=true passive=true"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_declare(exchange='testEx', type='direct', durable=True, passive=False) + except qpid.session.SessionException, e: + print e + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange create request for testEx with any parameter other than durable=true and passive=true"); + + try: + session.exchange_declare(exchange='ex1', type='direct') + self.fail("ACL should deny exchange create request with name=ex1 type=direct"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_declare(exchange='myXml', type='direct') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange create request for myXml with any parameter"); + + try: + session.exchange_query(name='myEx') + self.fail("ACL should deny exchange query request for myEx"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_bound(exchange='myEx', queue='q1', binding_key='rk1.*') + self.fail("ACL should deny exchange bound request for myEx with queuename=q1 and routing_key='rk1.*' "); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_query(name='amq.topic') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange query request for exchange='amq.topic'"); + + try: + session.exchange_bound(exchange='myEx', queue='q1', binding_key='rk2.*') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange bound request for myEx with queuename=q1 and binding_key='rk2.*'"); + + try: + session.exchange_bind(exchange='myEx', queue='q1', binding_key='rk1') + self.fail("ACL should deny exchange bind request with exchange='myEx' queuename='q1' bindingkey='rk1'"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_bind(exchange='myEx', queue='q1', binding_key='x') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange bind request for exchange='myEx', queue='q1', binding_key='x'"); + + try: + session.exchange_bind(exchange='myEx', queue='q2', binding_key='rk1') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange bind request for exchange='myEx', queue='q2', binding_key='rk1'"); + + try: + session.exchange_unbind(exchange='myEx', queue='q1', binding_key='rk1') + self.fail("ACL should deny exchange unbind request with exchange='myEx' queuename='q1' bindingkey='rk1'"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_unbind(exchange='myEx', queue='q1', binding_key='x') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange unbind request for exchange='myEx', queue='q1', binding_key='x'"); + + try: + session.exchange_unbind(exchange='myEx', queue='q2', binding_key='rk1') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange unbind request for exchange='myEx', queue='q2', binding_key='rk1'"); + + try: + session.exchange_delete(exchange='myEx') + self.fail("ACL should deny exchange delete request for myEx"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_delete(exchange='myXml') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange delete request for myXml"); + + + def test_exchange_acl_deny_mode(self): + session = self.get_session('bob','bob') + session.queue_declare(queue='bar') + + """ + Test cases for exchange acl in deny mode + """ + 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') + aclf.write('acl allow bob@QPID access exchange name=myEx queuename=q1 routingkey=rk1.*\n') + aclf.write('acl allow bob@QPID delete exchange name=myEx\n') + aclf.write('acl allow anonymous 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) + + session = self.get_session('bob','bob') + + try: + session.exchange_declare(exchange='myEx', type='direct', durable=True, passive=False) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange create request for myEx with durable=true and passive=false"); + + try: + session.exchange_declare(exchange='myEx', type='direct', durable=False) + self.fail("ACL should deny exchange create request with name=myEx durable=false"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_bind(exchange='amq.topic', queue='bar', binding_key='foo.bar') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange bind request for exchange='amq.topic', queue='bar', binding_key='foor.bar'"); + + try: + session.exchange_bind(exchange='amq.topic', queue='baz', binding_key='foo.bar') + self.fail("ACL should deny exchange bind request for exchange='amq.topic', queue='baz', binding_key='foo.bar'"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_bind(exchange='amq.topic', queue='bar', binding_key='fooz.bar') + self.fail("ACL should deny exchange bind request for exchange='amq.topic', queue='bar', binding_key='fooz.bar'"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_unbind(exchange='amq.topic', queue='bar', binding_key='foo.bar') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange unbind request for exchange='amq.topic', queue='bar', binding_key='foor.bar'"); + try: + session.exchange_unbind(exchange='amq.topic', queue='baz', binding_key='foo.bar') + self.fail("ACL should deny exchange unbind request for exchange='amq.topic', queue='baz', binding_key='foo.bar'"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_unbind(exchange='amq.topic', queue='bar', binding_key='fooz.bar') + self.fail("ACL should deny exchange unbind request for exchange='amq.topic', queue='bar', binding_key='fooz.bar'"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_query(name='amq.topic') + self.fail("ACL should deny exchange query request for amq.topic"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_bound(exchange='myEx', queue='q1', binding_key='rk2.*') + self.fail("ACL should deny exchange bound request for amq.topic with queuename=q1 and routing_key='rk2.*' "); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_query(name='myEx') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange query request for exchange='myEx'"); + + try: + session.exchange_bound(exchange='myEx', queue='q1', binding_key='rk1.*') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange bound request for myEx with queuename=q1 and binding_key='rk1.*'"); + + try: + session.exchange_delete(exchange='myXml') + self.fail("ACL should deny exchange delete request for myXml"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_delete(exchange='myEx') + except qpid.session.SessionException, e: + 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 + #===================================== + + def test_consume_allow_mode(self): + """ + Test cases for consume in allow mode + """ + 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') + aclf.close() + + result = self.reload_acl() + if (result.text.find("format error",0,len(result.text)) != -1): + self.fail(result) + + session = self.get_session('bob','bob') + + + try: + session.queue_declare(queue='q1') + session.queue_declare(queue='q2') + session.queue_declare(queue='q3') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow create queue request"); + + try: + session.message_subscribe(queue='q1', destination='myq1') + self.fail("ACL should deny subscription for queue='q1'"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.message_subscribe(queue='q2', destination='myq1') + self.fail("ACL should deny subscription for queue='q2'"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.message_subscribe(queue='q3', destination='myq1') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow subscription for q3"); + + + def test_consume_deny_mode(self): + """ + Test cases for consume in allow mode + """ + 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') + aclf.write('acl allow anonymous 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) + + session = self.get_session('bob','bob') + + + try: + session.queue_declare(queue='q1') + session.queue_declare(queue='q2') + session.queue_declare(queue='q3') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow create queue request"); + + try: + session.message_subscribe(queue='q1', destination='myq1') + session.message_subscribe(queue='q2', destination='myq2') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow subscription for q1 and q2"); + + try: + session.message_subscribe(queue='q3', destination='myq3') + self.fail("ACL should deny subscription for queue='q3'"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + + #===================================== + # ACL publish tests + #===================================== + + def test_publish_acl_allow_mode(self): + """ + Test various publish acl + """ + 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') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result.text.find("format error",0,len(result.text)) != -1): + self.fail(result) + + session = self.get_session('bob','bob') + + props = session.delivery_properties(routing_key="rk1") + + try: + session.message_transfer(destination="amq.direct", message=Message(props,"Test")) + self.fail("ACL should deny message transfer to name=amq.direct routingkey=rk1"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.message_transfer(destination="amq.topic", message=Message(props,"Test")) + self.fail("ACL should deny message transfer to name=amq.topic"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_declare(exchange='myEx', type='direct', durable=False) + session.message_transfer(destination="myEx", message=Message(props,"Test")) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow message transfer to exchange myEx with routing key rk1"); + + + props = session.delivery_properties(routing_key="rk2") + try: + session.message_transfer(destination="amq.direct", message=Message(props,"Test")) + 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 rk2"); + + + def test_publish_acl_deny_mode(self): + """ + Test various publish acl + """ + 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') + aclf.write('acl allow bob@QPID create exchange\n') + aclf.write('acl allow anonymous 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) + + session = self.get_session('bob','bob') + + props = session.delivery_properties(routing_key="rk2") + + try: + session.message_transfer(destination="amq.direct", message=Message(props,"Test")) + self.fail("ACL should deny message transfer to name=amq.direct routingkey=rk2"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.message_transfer(destination="amq.topic", message=Message(props,"Test")) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow message transfer to exchange amq.topic with any routing key"); + + try: + session.exchange_declare(exchange='myEx', type='direct', durable=False) + session.message_transfer(destination="myEx", message=Message(props,"Test")) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow message transfer to exchange myEx with routing key=rk2"); + + props = session.delivery_properties(routing_key="rk1") + + try: + session.message_transfer(destination="myEx", message=Message(props,"Test")) + self.fail("ACL should deny message transfer to name=myEx routingkey=rk1"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.message_transfer(destination="amq.direct", message=Message(props,"Test")) + 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"); + +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}) diff --git a/qpid/cpp/src/tests/ais_check b/qpid/cpp/src/tests/ais_check new file mode 100755 index 0000000000..92eaa9dd39 --- /dev/null +++ b/qpid/cpp/src/tests/ais_check @@ -0,0 +1,34 @@ +#!/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. +# + +srcdir=`dirname $0` + +# Check AIS requirements and run tests if found. +ps -u root | grep 'aisexec\|corosync' >/dev/null || { + echo WARNING: Skipping cluster tests, the aisexec or corosync daemon is not running. + exit 0; # A warning, not a failure. +} + +# Execute command with the ais group set if user is a member. +with_ais_group() { + if id -nG | grep '\<ais\>' >/dev/null; then sg ais -c "$*" + else "$@" + fi +} diff --git a/qpid/cpp/src/tests/ais_test.cpp b/qpid/cpp/src/tests/ais_test.cpp new file mode 100644 index 0000000000..00c61242e4 --- /dev/null +++ b/qpid/cpp/src/tests/ais_test.cpp @@ -0,0 +1,23 @@ +/* + * + * 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. + * + */ + +// Defines test_main function to link with actual unit test code. +#define BOOST_AUTO_TEST_MAIN // Boost 1.33 +#define BOOST_TEST_MAIN +#include "unit_test.h" + diff --git a/qpid/cpp/src/tests/allhosts b/qpid/cpp/src/tests/allhosts new file mode 100755 index 0000000000..e43571aed4 --- /dev/null +++ b/qpid/cpp/src/tests/allhosts @@ -0,0 +1,77 @@ +#!/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. +# + +usage() { + echo "Usage: $0 [options] command. +Run a command on each host in \$HOSTS. +Options: + -l USER passed to ssh - run as USER. + -t passed to ssh - create a terminal. + -b run in background, wait for commands to complete. + -d run in background, don't wait for commands to complete. + -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 +" + exit 1 +} + +while getopts "tl:bs:dqo:" opt; do + case $opt in + l) SSHOPTS="-l$OPTARG $SSHOPTS" ;; + t) SSHOPTS="-t $SSHOPTS" ;; + b) BACKGROUND=1 ;; + d) BACKGROUND=1; DISOWN=1 ;; + s) SLEEP="sleep $OPTARG" ;; + q) NOBANNER=1 ;; + o) SUFFIX=$OPTARG ;; + *) usage;; + esac +done +shift `expr $OPTIND - 1` +test "$*" || usage; + +OK_FILE=`mktemp` # Will be deleted if anything goes wrong. +trap "rm -f $OK_FILE" EXIT + +do_ssh() { + h=$1; shift + if test $SUFFIX ; then ssh $SSHOPTS $h "$@" &> $h.$SUFFIX + else ssh $SSHOPTS $h "$@"; fi || rm -rf $OK_FILE; +} + +for h in $HOSTS ; do + test "$NOBANNER" || echo "== ssh $SSHOPTS $h $@ ==" + if [ "$BACKGROUND" = 1 ]; then + do_ssh $h "$@" & + CHILDREN="$! $CHILDREN" + else + do_ssh $h "$@" + fi + $SLEEP +done + +if [ "$DISOWN" = 1 ]; then + for c in $CHILDREN; do disown $c; done +else + wait +fi + +test -f $OK_FILE diff --git a/qpid/cpp/src/tests/amqp_0_10/Map.cpp b/qpid/cpp/src/tests/amqp_0_10/Map.cpp new file mode 100644 index 0000000000..ffb235829e --- /dev/null +++ b/qpid/cpp/src/tests/amqp_0_10/Map.cpp @@ -0,0 +1,98 @@ +/* + * + * 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 "amqp_0_10/unit_test.h" +#include "qpid/amqp_0_10/Map.h" +#include "qpid/amqp_0_10/Array.h" +#include "qpid/amqp_0_10/Struct32.h" +#include "qpid/amqp_0_10/UnknownType.h" +#include "qpid/amqp_0_10/Codec.h" +#include <iostream> + +using namespace qpid::amqp_0_10; +using namespace std; + +QPID_AUTO_TEST_SUITE(MapTestSuite) + + QPID_AUTO_TEST_CASE(testGetSet) { + MapValue v; + v = Str8("foo"); + BOOST_CHECK(v.get<Str8>()); + BOOST_CHECK(!v.get<uint8_t>()); + BOOST_CHECK_EQUAL(*v.get<Str8>(), "foo"); + + v = uint8_t(42); + BOOST_CHECK(!v.get<Str8>()); + BOOST_CHECK(v.get<uint8_t>()); + BOOST_CHECK_EQUAL(*v.get<uint8_t>(), 42); + + v = uint16_t(12); + BOOST_CHECK(v.get<uint16_t>()); + BOOST_CHECK_EQUAL(*v.get<uint16_t>(), 12); +} + +template <class R> struct TestVisitor : public MapValue::Visitor<R> { + template <class T> R operator()(const T&) const { throw MapValue::BadTypeException(); } + R operator()(const R& r) const { return r; } +}; + +QPID_AUTO_TEST_CASE(testVisit) { + MapValue v; + v = Str8("foo"); + BOOST_CHECK_EQUAL(v.apply_visitor(TestVisitor<Str8>()), "foo"); + v = Uint16(42); + BOOST_CHECK_EQUAL(v.apply_visitor(TestVisitor<Uint16>()), 42); + try { + v.apply_visitor(TestVisitor<bool>()); + BOOST_FAIL("Expecting exception"); + } + catch(const MapValue::BadTypeException&) {} +} + + +QPID_AUTO_TEST_CASE(testEncodeMapValue) { + MapValue mv; + std::string data; + mv = Str8("hello"); + Codec::encode(back_inserter(data))(mv); + BOOST_CHECK_EQUAL(data.size(), Codec::size(mv)); + MapValue mv2; + Codec::decode(data.begin())(mv2); + BOOST_CHECK_EQUAL(mv2.getCode(), 0x85); + BOOST_REQUIRE(mv2.get<Str8>()); + BOOST_CHECK_EQUAL(*mv2.get<Str8>(), "hello"); +} + +QPID_AUTO_TEST_CASE(testEncode) { + Map map; + std::string data; + map["A"] = true; + map["b"] = Str8("hello"); + Codec::encode(back_inserter(data))(map); + BOOST_CHECK_EQUAL(Codec::size(map), data.size()); + Map map2; + Codec::decode(data.begin())(map2); + BOOST_CHECK_EQUAL(map.size(), 2u); + BOOST_CHECK(map["A"].get<bool>()); + BOOST_CHECK_EQUAL(*map["b"].get<Str8>(), "hello"); +} + + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/amqp_0_10/ProxyTemplate.cpp b/qpid/cpp/src/tests/amqp_0_10/ProxyTemplate.cpp new file mode 100644 index 0000000000..f54ee0da22 --- /dev/null +++ b/qpid/cpp/src/tests/amqp_0_10/ProxyTemplate.cpp @@ -0,0 +1,49 @@ +/* + * + * 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 "amqp_0_10/unit_test.h" +#include "qpid/amqp_0_10/ProxyTemplate.h" +#include <boost/any.hpp> + +QPID_AUTO_TEST_SUITE(ProxyTemplateTestSuite) + +using namespace qpid::amqp_0_10; + +struct ToAny { + template <class T> + boost::any operator()(const T& t) { return boost::any(t); } +}; + +struct AnyProxy : public ProxyTemplate<ToAny, boost::any> {}; + +QPID_AUTO_TEST_CASE(testAnyProxy) { + AnyProxy p; + boost::any a=p.connectionTune(1,2,3,4); + BOOST_CHECK_EQUAL(a.type().name(), typeid(connection::Tune).name()); + connection::Tune* tune=boost::any_cast<connection::Tune>(&a); + BOOST_REQUIRE(tune); + BOOST_CHECK_EQUAL(tune->channelMax, 1u); + BOOST_CHECK_EQUAL(tune->maxFrameSize, 2u); + BOOST_CHECK_EQUAL(tune->heartbeatMin, 3u); + BOOST_CHECK_EQUAL(tune->heartbeatMax, 4u); +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/amqp_0_10/apply.cpp b/qpid/cpp/src/tests/amqp_0_10/apply.cpp new file mode 100644 index 0000000000..0aa4421791 --- /dev/null +++ b/qpid/cpp/src/tests/amqp_0_10/apply.cpp @@ -0,0 +1,99 @@ +/* + * + * 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 "amqp_0_10/unit_test.h" +#include "qpid/amqp_0_10/specification.h" +#include "qpid/amqp_0_10/ApplyControl.h" + +QPID_AUTO_TEST_SUITE(VisitorTestSuite) + +using namespace qpid::amqp_0_10; + +struct GetCode : public ApplyFunctor<uint8_t> { + template <class T> uint8_t operator()(const T&) const { return T::CODE; } +}; + +struct SetChannelMax : ApplyFunctor<void> { + template <class T> void operator()(T&) const { BOOST_FAIL(""); } + void operator()(connection::Tune& t) const { t.channelMax=42; } +}; + +struct TestFunctor { + typedef bool result_type; + bool operator()(const connection::Tune& tune) { + BOOST_CHECK_EQUAL(tune.channelMax, 1u); + BOOST_CHECK_EQUAL(tune.maxFrameSize, 2u); + BOOST_CHECK_EQUAL(tune.heartbeatMin, 3u); + BOOST_CHECK_EQUAL(tune.heartbeatMax, 4u); + return true; + } + template <class T> + bool operator()(const T&) { return false; } +}; + +QPID_AUTO_TEST_CASE(testApply) { + connection::Tune tune(1,2,3,4); + Control* p = &tune; + + // boost oddity - without the cast we get undefined symbol errors. + BOOST_CHECK_EQUAL(apply(GetCode(), *p), (uint8_t)connection::Tune::CODE); + + TestFunctor tf; + BOOST_CHECK(apply(tf, *p)); + + connection::Start start; + p = &start; + BOOST_CHECK(!apply(tf, *p)); + + apply(SetChannelMax(), tune); + BOOST_CHECK_EQUAL(tune.channelMax, 42); +} + +struct VoidTestFunctor { + typedef void result_type; + + int code; + VoidTestFunctor() : code() {} + + void operator()(const connection::Tune& tune) { + BOOST_CHECK_EQUAL(tune.channelMax, 1u); + BOOST_CHECK_EQUAL(tune.maxFrameSize, 2u); + BOOST_CHECK_EQUAL(tune.heartbeatMin, 3u); + BOOST_CHECK_EQUAL(tune.heartbeatMax, 4u); + code=connection::Tune::CODE; + } + template <class T> + void operator()(const T&) { code=0xFF; } +}; + +QPID_AUTO_TEST_CASE(testApplyVoid) { + connection::Tune tune(1,2,3,4); + Control* p = &tune; + VoidTestFunctor tf; + apply(tf, *p); + BOOST_CHECK_EQUAL(uint8_t(connection::Tune::CODE), tf.code); + + connection::Start start; + p = &start; + apply(tf, *p); + BOOST_CHECK_EQUAL(0xFF, tf.code); +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/amqp_0_10/handlers.cpp b/qpid/cpp/src/tests/amqp_0_10/handlers.cpp new file mode 100644 index 0000000000..91bb304a17 --- /dev/null +++ b/qpid/cpp/src/tests/amqp_0_10/handlers.cpp @@ -0,0 +1,125 @@ +/* + * + * 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 "amqp_0_10/unit_test.h" +#include "qpid/Exception.h" +#include "qpid/amqp_0_10/Unit.h" +#include "qpid/amqp_0_10/ControlHolder.h" +#include "qpid/amqp_0_10/CommandHolder.h" +#include "qpid/amqp_0_10/handlers.h" +#include "qpid/amqp_0_10/specification.h" + +QPID_AUTO_TEST_SUITE(handler_tests) + +using namespace qpid::amqp_0_10; +using namespace std; + +string called; // Set by called handler function + +// Note on handlers: +// +// Control and Command handlers are separate, both behave the same way, +// so substitute "control or command" for command in the following. +// +// Command handlers derive from CommandHandler and implement functions +// for all the commands they handle. Handling an unimplemented command +// will raise NotImplementedException. +// +// Using virtual inheritance from CommandHandler allows multiple +// handlers to be aggregated into one with multiple inheritance, +// See test code for example. +// +// E.g. the existing broker model would have two control handlers: +// - ConnectionHandler: ControlHandler for connection controls. +// - SessionHandler: ControlHandler for session controls. +// It would have class-command handlers for each AMQP class: +// - QueueHandler, MessageHandler etc.. handle each class. +// And an aggregate handler in place of BrokerAdapter +// - BrokerCommandHandler: public QueueHandler, MessageHandler ... +// +// In other applications (e.g. cluster) any combination of commands +// can be handled by a given handler. It _might_ simplify the code +// to collaps ConnectionHandler and SessionHandler into a single +// ControlHandler (or it might not.) + +struct TestExecutionHandler : public virtual CommandHandler { + void executionSync() { called = "executionSync"; } + // ... etc. for all execution commands +}; + +struct TestMessageHandler : public virtual CommandHandler { + void messageCancel(const Str8&) { called="messageCancel"; } + // ... etc. +}; + +// Aggregate handler for all recognised commands. +struct TestCommandHandler : + public TestExecutionHandler, + public TestMessageHandler + // ... etc. handlers for all command classes. +{}; // Nothing to do. + + +// Sample unit handler, written as a static_visitor. +// Note it could equally be written with if/else statements +// in handle. +// +struct TestUnitHandler : public boost::static_visitor<void> { + TestCommandHandler handler; + void handle(const Unit& u) { u.applyVisitor(*this); } + + void operator()(const Body&) { called="Body"; } + void operator()(const Header&) { called="Header"; } + void operator()(const ControlHolder&) { throw qpid::Exception("I don't do controls."); } + void operator()(const CommandHolder& c) { c.invoke(handler); } +}; + +QPID_AUTO_TEST_CASE(testHandlers) { + TestUnitHandler handler; + Unit u; + + u = Body(); + handler.handle(u); + BOOST_CHECK_EQUAL("Body", called); + + u = Header(); + handler.handle(u); + BOOST_CHECK_EQUAL("Header", called); + + // in_place<Foo>(...) is equivalent to Foo(...) but + // constructs Foo directly in the holder, avoiding + // a copy. + + u = CommandHolder(in_place<execution::Sync>()); + handler.handle(u); + BOOST_CHECK_EQUAL("executionSync", called); + + u = ControlHolder(in_place<connection::Start>(Map(), Str16Array(), Str16Array())); + try { + handler.handle(u); + } catch (const qpid::Exception&) {} + + u = CommandHolder(in_place<message::Cancel>(Str8())); + handler.handle(u); + BOOST_CHECK_EQUAL("messageCancel", called); +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/amqp_0_10/serialize.cpp b/qpid/cpp/src/tests/amqp_0_10/serialize.cpp new file mode 100644 index 0000000000..975d6206ec --- /dev/null +++ b/qpid/cpp/src/tests/amqp_0_10/serialize.cpp @@ -0,0 +1,429 @@ +/* + * + * 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 "amqp_0_10/unit_test.h" +#include "amqp_0_10/allSegmentTypes.h" + +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/Buffer.h" + +#include "qpid/amqp_0_10/Packer.h" +#include "qpid/amqp_0_10/built_in_types.h" +#include "qpid/amqp_0_10/Codec.h" +#include "qpid/amqp_0_10/specification.h" +#include "qpid/amqp_0_10/ControlHolder.h" +#include "qpid/amqp_0_10/Struct32.h" +#include "qpid/amqp_0_10/FrameHeader.h" +#include "qpid/amqp_0_10/Map.h" +#include "qpid/amqp_0_10/Unit.h" +#include "allSegmentTypes.h" + +#include <boost/test/test_case_template.hpp> +#include <boost/type_traits/is_arithmetic.hpp> +#include <boost/utility/enable_if.hpp> +#include <boost/optional.hpp> +#include <boost/mpl/vector.hpp> +#include <boost/mpl/back_inserter.hpp> +#include <boost/mpl/copy.hpp> +#include <boost/mpl/empty_sequence.hpp> +#include <boost/current_function.hpp> +#include <iterator> +#include <string> +#include <sstream> +#include <iostream> +#include <netinet/in.h> + +// Missing operators needed for tests. +namespace boost { +template <class T, size_t N> +std::ostream& operator<<(std::ostream& out, const array<T,N>& a) { + std::ostream_iterator<T> o(out, " "); + std::copy(a.begin(), a.end(), o); + return out; +} +} // boost + +QPID_AUTO_TEST_SUITE(SerializeTestSuite) + +using namespace std; +namespace mpl=boost::mpl; +using namespace qpid::amqp_0_10; +using qpid::framing::in_place; + +template <class A, class B> struct concat2 { typedef typename mpl::copy<B, typename mpl::back_inserter<A> >::type type; }; +template <class A, class B, class C> struct concat3 { typedef typename concat2<A, typename concat2<B, C>::type>::type type; }; +template <class A, class B, class C, class D> struct concat4 { typedef typename concat2<A, typename concat3<B, C, D>::type>::type type; }; + +typedef mpl::vector<Boolean, Char, Int32, Int64, Int8, Uint16, CharUtf32, Uint32, Uint64, Bin8, Uint8>::type IntegralTypes; +typedef mpl::vector<Bin1024, Bin128, Bin16, Bin256, Bin32, Bin40, Bin512, Bin64, Bin72>::type BinTypes; +typedef mpl::vector<Double, Float>::type FloatTypes; +typedef mpl::vector<SequenceNo, Uuid, Datetime, Dec32, Dec64> FixedSizeClassTypes; +typedef mpl::vector<Map, Vbin8, Str8Latin, Str8, Str8Utf16, Vbin16, Str16Latin, Str16, Str16Utf16, Vbin32> VariableSizeTypes; + +typedef concat4<IntegralTypes, BinTypes, FloatTypes, FixedSizeClassTypes>::type FixedSizeTypes; +typedef concat2<FixedSizeTypes, VariableSizeTypes>::type AllTypes; + +// TODO aconway 2008-02-20: should test 64 bit integrals for order also. +QPID_AUTO_TEST_CASE(testNetworkByteOrder) { + string data; + + uint32_t l = 0x11223344; + Codec::encode(std::back_inserter(data))(l); + uint32_t enc=reinterpret_cast<const uint32_t&>(*data.data()); + uint32_t l2 = ntohl(enc); + BOOST_CHECK_EQUAL(l, l2); + + data.clear(); + uint16_t s = 0x1122; + Codec::encode(std::back_inserter(data))(s); + uint32_t s2 = ntohs(*reinterpret_cast<const uint32_t*>(data.data())); + BOOST_CHECK_EQUAL(s, s2); +} + +QPID_AUTO_TEST_CASE(testSetLimit) { + typedef Codec::Encoder<back_insert_iterator<string> > Encoder; + string data; + Encoder encode(back_inserter(data), 3); + encode('1')('2')('3'); + try { + encode('4'); + BOOST_FAIL("Expected exception"); + } catch (...) {} // FIXME aconway 2008-04-03: catch proper exception + BOOST_CHECK_EQUAL(data, "123"); +} + +QPID_AUTO_TEST_CASE(testScopedLimit) { + typedef Codec::Encoder<back_insert_iterator<string> > Encoder; + string data; + Encoder encode(back_inserter(data), 10); + encode(Str8("123")); // 4 bytes + { + Encoder::ScopedLimit l(encode, 3); + encode('a')('b')('c'); + try { + encode('d'); + BOOST_FAIL("Expected exception"); + } catch(...) {} // FIXME aconway 2008-04-03: catch proper exception + } + BOOST_CHECK_EQUAL(data, "\003123abc"); + encode('x')('y')('z'); + try { + encode('!'); + BOOST_FAIL("Expected exception"); + } catch(...) {} // FIXME aconway 2008-04-03: catch proper exception + BOOST_CHECK_EQUAL(data.size(), 10u); +} + +// Assign test values to the various types. +void testValue(bool& b) { b = true; } +void testValue(Bit&) { } +template <class T> typename boost::enable_if<boost::is_arithmetic<T> >::type testValue(T& n) { n=42; } +void testValue(CharUtf32& c) { c = 43; } +void testValue(long long& l) { l = 0x012345; } +void testValue(Datetime& dt) { dt = qpid::sys::now(); } +void testValue(Uuid& uuid) { uuid=Uuid(true); } +template <class E, class M> void testValue(Decimal<E,M>& d) { d.exponent=2; d.mantissa=0x1122; } +void testValue(SequenceNo& s) { s = 42; } +template <size_t N> void testValue(Bin<N>& a) { a.assign(42); } +template <class T, class S, int Unique> void testValue(SerializableString<T, S, Unique>& s) { + char msg[]="foobar"; + s.assign(msg, msg+sizeof(msg)); +} +void testValue(Str16& s) { s = "the quick brown fox jumped over the lazy dog"; } +void testValue(Str8& s) { s = "foobar"; } +void testValue(Map& m) { m["s"] = Str8("foobar"); m["b"] = true; m["c"] = uint16_t(42); } + +//typedef mpl::vector<Str8, Str16>::type TestTypes; +/*BOOST_AUTO_TEST_CASE_TEMPLATE(testEncodeDecode, T, AllTypes) +{ + string data; + T t; + testValue(t); + Codec::encode(std::back_inserter(data))(t); + + BOOST_CHECK_EQUAL(Codec::size(t), data.size()); + + T t2; + Codec::decode(data.begin())(t2); + BOOST_CHECK_EQUAL(t,t2); +} +*/ + +struct TestMe { + bool encoded, decoded; + char value; + TestMe(char v) : encoded(), decoded(), value(v) {} + template <class S> void encode(S& s) const { + const_cast<TestMe*>(this)->encoded=true; s(value); + } + template <class S> void decode(S& s) { decoded=true; s(value); } + template <class S> void serialize(S& s) { s.split(*this); } +}; + +QPID_AUTO_TEST_CASE(testSplit) { + string data; + TestMe t1('x'); + Codec::encode(std::back_inserter(data))(t1); + BOOST_CHECK(t1.encoded); + BOOST_CHECK(!t1.decoded); + BOOST_CHECK_EQUAL(data, "x"); + + TestMe t2('y'); + Codec::decode(data.begin())(t2); + BOOST_CHECK(!t2.encoded); + BOOST_CHECK(t2.decoded); + BOOST_CHECK_EQUAL(t2.value, 'x'); +} + +QPID_AUTO_TEST_CASE(testControlEncodeDecode) { + string data; + Control::Holder h(in_place<connection::Tune>(1,2,3,4)); + Codec::encode(std::back_inserter(data))(h); + + BOOST_CHECK_EQUAL(data.size(), Codec::size(h)); + + Codec::Decoder<string::iterator> decode(data.begin()); + Control::Holder h2; + decode(h2); + + BOOST_REQUIRE(h2.get()); + BOOST_CHECK_EQUAL(h2.get()->getClassCode(), connection::CODE); + BOOST_CHECK_EQUAL(h2.get()->getCode(), uint8_t(connection::Tune::CODE)); + connection::Tune& tune=static_cast<connection::Tune&>(*h2.get()); + BOOST_CHECK_EQUAL(tune.channelMax, 1u); + BOOST_CHECK_EQUAL(tune.maxFrameSize, 2u); + BOOST_CHECK_EQUAL(tune.heartbeatMin, 3u); + BOOST_CHECK_EQUAL(tune.heartbeatMax, 4u); +} + +QPID_AUTO_TEST_CASE(testStruct32) { + message::DeliveryProperties dp; + dp.priority=message::MEDIUM; + dp.routingKey="foo"; + Struct32 s(dp); + string data; + Codec::encode(back_inserter(data))(s); + + uint32_t structSize; // Starts with size + Codec::decode(data.begin())(structSize); + BOOST_CHECK_EQUAL(structSize, Codec::size(dp) + 2); // +2 for code + BOOST_CHECK_EQUAL(structSize, data.size()-4); // encoded body + + BOOST_CHECK_EQUAL(data.size(), Codec::size(s)); + Struct32 s2; + Codec::decode(data.begin())(s2); + message::DeliveryProperties* dp2 = s2.getIf<message::DeliveryProperties>(); + BOOST_REQUIRE(dp2); + BOOST_CHECK_EQUAL(dp2->priority, message::MEDIUM); + BOOST_CHECK_EQUAL(dp2->routingKey, "foo"); +} + +QPID_AUTO_TEST_CASE(testStruct32Unknown) { + // Verify we can recode an unknown struct unchanged. + Struct32 s; + string data; + Codec::encode(back_inserter(data))(uint32_t(10)); + data.append(10, 'X'); + Codec::decode(data.begin())(s); + string data2; + Codec::encode(back_inserter(data2))(s); + BOOST_CHECK_EQUAL(data.size(), data2.size()); + BOOST_CHECK_EQUAL(data, data2); +} + +struct DummyPacked { + static const uint8_t PACK=1; + boost::optional<char> i, j; + char k; + Bit l,m; + DummyPacked(char a=0, char b=0, char c=0) : i(a), j(b), k(c), l(), m() {} + template <class S> void serialize(S& s) { s(i)(j)(k)(l)(m); } +}; + +Packer<DummyPacked> serializable(DummyPacked& d) { return Packer<DummyPacked>(d); } + +QPID_AUTO_TEST_CASE(testPackBits) { + DummyPacked d('a','b','c'); + BOOST_CHECK_EQUAL(packBits(d), 7u); + d.j = boost::none; + BOOST_CHECK_EQUAL(packBits(d), 5u); + d.m = true; + BOOST_CHECK_EQUAL(packBits(d), 0x15u); +} + + +QPID_AUTO_TEST_CASE(testPacked) { + string data; + + Codec::encode(back_inserter(data))('a')(boost::optional<char>('b'))(boost::optional<char>())('c'); + BOOST_CHECK_EQUAL(data, "abc"); + data.clear(); + + DummyPacked dummy('a','b','c'); + + Codec::encode(back_inserter(data))(dummy); + BOOST_CHECK_EQUAL(data.size(), 4u); + BOOST_CHECK_EQUAL(data, string("\007abc")); + data.clear(); + + dummy.i = boost::none; + Codec::encode(back_inserter(data))(dummy); + BOOST_CHECK_EQUAL(data, string("\6bc")); + data.clear(); + + const char* missing = "\5xy"; + Codec::decode(missing)(dummy); + BOOST_CHECK(dummy.i); + BOOST_CHECK_EQUAL(*dummy.i, 'x'); + BOOST_CHECK(!dummy.j); + BOOST_CHECK_EQUAL(dummy.k, 'y'); +} + +QPID_AUTO_TEST_CASE(testUnitControl) { + string data; + Control::Holder h(in_place<connection::Tune>(1,2,3,4)); + Codec::encode(std::back_inserter(data))(h); + + Unit unit(FrameHeader(FIRST_FRAME|LAST_FRAME, CONTROL)); + Codec::decode(data.begin())(unit); + + BOOST_REQUIRE(unit.get<ControlHolder>()); + + string data2; + Codec::encode(back_inserter(data2))(unit); + + BOOST_CHECK_EQUAL(data, data2); +} + +QPID_AUTO_TEST_CASE(testArray) { + ArrayDomain<char> a; + a.resize(3, 'x'); + string data; + Codec::encode(back_inserter(data))(a); + + ArrayDomain<char> b; + Codec::decode(data.begin())(b); + BOOST_CHECK_EQUAL(b.size(), 3u); + string data3; + Codec::encode(back_inserter(data3))(a); + BOOST_CHECK_EQUAL(data, data3); + + Array x; + Codec::decode(data.begin())(x); + BOOST_CHECK_EQUAL(x.size(), 3u); + BOOST_CHECK_EQUAL(x[0].size(), 1u); + BOOST_CHECK_EQUAL(*x[0].begin(), 'x'); + BOOST_CHECK_EQUAL(*x[2].begin(), 'x'); + + string data2; + Codec::encode(back_inserter(data2))(x); + BOOST_CHECK_EQUAL(data,data2); +} + +QPID_AUTO_TEST_CASE(testStruct) { + string data; + + message::DeliveryProperties dp; + BOOST_CHECK(!dp.discardUnroutable); + dp.immediate = true; + dp.redelivered = false; + dp.priority = message::MEDIUM; + dp.exchange = "foo"; + + Codec::encode(back_inserter(data))(dp); + // Skip 4 bytes size, little-endian decode for pack bits. + uint16_t encodedBits=uint8_t(data[5]); + encodedBits <<= 8; + encodedBits += uint8_t(data[4]); + BOOST_CHECK_EQUAL(encodedBits, packBits(dp)); + + data.clear(); + Struct32 h(dp); + Codec::encode(back_inserter(data))(h); + + Struct32 h2; + Codec::decode(data.begin())(h2); + BOOST_CHECK_EQUAL(h2.getClassCode(), Uint8(message::DeliveryProperties::CLASS_CODE)); + BOOST_CHECK_EQUAL(h2.getCode(), Uint8(message::DeliveryProperties::CODE)); + message::DeliveryProperties* dp2 = + dynamic_cast<message::DeliveryProperties*>(h2.get()); + BOOST_CHECK(dp2); + BOOST_CHECK(!dp2->discardUnroutable); + BOOST_CHECK(dp2->immediate); + BOOST_CHECK(!dp2->redelivered); + BOOST_CHECK_EQUAL(dp2->priority, message::MEDIUM); + BOOST_CHECK_EQUAL(dp2->exchange, "foo"); +} + +struct RecodeUnit { + template <class T> + void operator() (const T& t) { + BOOST_MESSAGE(BOOST_CURRENT_FUNCTION << " called with: " << t); + using qpid::framing::Buffer; + using qpid::framing::AMQFrame; + + session::Header sh; + BOOST_CHECK_EQUAL(Codec::size(sh), 2u); + + // Encode unit. + Unit u(t); + string data; + Codec::encode(back_inserter(data))(u.getHeader())(u); + data.push_back(char(0xCE)); // Preview end-of-frame + + // Decode AMQFrame + Buffer buf(&data[0], data.size()); + AMQFrame f; + f.decode(buf); + BOOST_MESSAGE("AMQFrame decoded: " << f); + // Encode AMQFrame + string data2(f.size(), ' '); + Buffer buf2(&data2[0], data.size()); + f.encode(buf2); + + // Verify encoded by unit == encoded by AMQFrame + BOOST_CHECK_MESSAGE(data == data2, BOOST_CURRENT_FUNCTION); + + // Decode unit + // FIXME aconway 2008-04-15: must set limit to decode a header. + Codec::Decoder<string::iterator> decode(data2.begin(), data2.size()-1); + + FrameHeader h; + decode(h); + BOOST_CHECK_EQUAL(u.getHeader(), h); + Unit u2(h); + decode(u2); + + // Re-encode unit + string data3; + Codec::encode(back_inserter(data3))(u2.getHeader())(u2); + data3.push_back(char(0xCE)); // Preview end-of-frame + + BOOST_CHECK_MESSAGE(data3 == data2, BOOST_CURRENT_FUNCTION); + } +}; + +QPID_AUTO_TEST_CASE(testSerializeAllSegmentTypes) { + RecodeUnit recode; + allSegmentTypes(recode); +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/background.ps1 b/qpid/cpp/src/tests/background.ps1 new file mode 100644 index 0000000000..36e9e4e6e9 --- /dev/null +++ b/qpid/cpp/src/tests/background.ps1 @@ -0,0 +1,55 @@ +#
+# 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 PowerShell scriptblock in a background process.
+param(
+ [scriptblock] $script # scriptblock to run
+)
+
+# break out of the script on any errors
+trap { break }
+
+# In order to pass a scriptblock to another powershell instance, it must
+# be encoded to pass through the command line.
+$encodedScript = [convert]::ToBase64String(
+ [Text.Encoding]::Unicode.GetBytes([string] $script))
+
+#$p = new-object System.Diagnostics.Process
+$si = new-object System.Diagnostics.ProcessStartInfo
+$si.WorkingDirectory = $pwd
+$si.FileName = (get-command powershell.exe).Definition
+$si.Arguments = "-encodedCommand $encodedScript"
+
+###### debugging setup
+#$si.CreateNoWindow = $true
+# UseShellExecute false required for RedirectStandard(Error, Output)
+#$si.UseShellExecute = $false
+#$si.RedirectStandardError = $true
+#$si.RedirectStandardOutput = $true
+######
+$si.UseShellExecute = $true
+
+##### Debugging, instead of the plain Start() above.
+#$output = [io.File]::AppendText("start.out")
+#$error = [io.File]::AppendText("start.err")
+$p = [System.Diagnostics.Process]::Start($si)
+#$output.WriteLine($p.StandardOutput.ReadToEnd())
+#$error.WriteLine($p.StandardError.ReadToEnd())
+#$p.WaitForExit()
+#$output.Close()
diff --git a/qpid/cpp/src/tests/benchmark b/qpid/cpp/src/tests/benchmark new file mode 100755 index 0000000000..c075837847 --- /dev/null +++ b/qpid/cpp/src/tests/benchmark @@ -0,0 +1,95 @@ +#!/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. +# +# A basic "benchmark" to generate performacne samples of throughput +# and latency against a single cluster member while they are replicating. +# +# Must be run in the qpid src/tests build directory. +# + +usage() { +cat <<EOF +Usage: $0 [options] -- client hosts --- broker hosts +Read the script for options. +EOF +} +# Defaults +TESTDIR=${TESTDIR:-$PWD} # Absolute path to test exes on all hosts. +SCRIPTDIR=${SCRIPTDIR:-`dirname $0`} # Path to local test scripts directory. +SAMPLES=10 # Runs of each test. +COUNT=${COUNT:-10000} # Count for pub/sub tests. +SIZE=${SIZE:-600} # Size of messages +ECHO=${ECHO:-1000} # Count for echo test. +NSUBS=${NSUBS:-4} +NPUBS=${NPUBS:-4} + +collect() { eval $COLLECT=\""\$$COLLECT $*"\"; } +COLLECT=ARGS +while test $# -gt 0; do + case $1 in + --testdir) TESTDIR=$2 ; shift 2 ;; + --samples) SAMPLES=$2 ; shift 2 ;; + --count) COUNT=$2 ; shift 2 ;; + --echos) ECHO=$2 ; shift 2 ;; + --size) SIZE=$2 ; shift 2 ;; + --nsubs) NSUBS=$2 ; shift 2 ;; + --npubs) NPUBS=$2 ; shift 2 ;; + --) COLLECT=CLIENTARG; shift ;; + ---) COLLECT=BROKERARG; shift;; + *) collect $1; shift ;; + esac +done + +CLIENTS=${CLIENTARG:-$CLIENTS} +BROKERS=${BROKERARG:-$BROKERS} +test -z "$CLIENTS" && { echo "Must specify at least one client host."; exit 1; } +test -z "$BROKERS" && { echo "Must specify at least one broker host."; exit 1; } + +export TESTDIR # For perfdist +CLIENTS=($CLIENTS) # Convert to array +BROKERS=($BROKERS) +trap "rm -f $FILES" EXIT + +dosamples() { + FILE=`mktemp` + FILES="$FILES $FILE" + TABS=`echo "$HEADING" | sed s'/[^ ]//g'` + { + echo "\"$*\"$TABS" + echo "$HEADING" + for (( i=0; i<$SAMPLES; ++i)) ; do echo "`$*`" ; done + echo + } | tee $FILE +} + +HEADING="pub sub total Mb" +dosamples $SCRIPTDIR/perfdist --size $SIZE --count $COUNT --nsubs $NSUBS --npubs $NPUBS -s -- ${CLIENTS[*]} --- ${BROKERS[*]} +HEADING="pub" +dosamples ssh -A ${CLIENTS[0]} $TESTDIR/publish --routing-key perftest0 --size $SIZE --count $COUNT -s -b ${BROKERS[0]} +HEADING="sub" +dosamples ssh -A ${CLIENTS[0]} $TESTDIR/consume --queue perftest0 -s --count $COUNT -b ${BROKERS[0]} +HEADING="min max avg" +dosamples ssh -A ${CLIENTS[0]} $TESTDIR/echotest --count $ECHO -s -b ${BROKERS[0]} + +echo +echo "Tab separated spreadsheet (also saved as benchmark.tab):" +echo + +echo "benchmark -- ${CLIENTS[*]} --- ${BROKERS[*]} " | tee benchmark.tab +paste $FILES | tee -a benchmark.tab diff --git a/qpid/cpp/src/tests/brokermgmt.mk b/qpid/cpp/src/tests/brokermgmt.mk new file mode 100644 index 0000000000..cf9a47200c --- /dev/null +++ b/qpid/cpp/src/tests/brokermgmt.mk @@ -0,0 +1,44 @@ +# +# 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. +# + +# Build a unit test for the broker's internal management agent. + +BROKERMGMT_GEN_SRC= \ + brokermgmt_gen/qmf/org/apache/qpid/broker/mgmt/test/Package.cpp \ + brokermgmt_gen/qmf/org/apache/qpid/broker/mgmt/test/Package.h \ + brokermgmt_gen/qmf/org/apache/qpid/broker/mgmt/test/TestObject.h \ + brokermgmt_gen/qmf/org/apache/qpid/broker/mgmt/test/TestObject.cpp + +$(BROKERMGMT_GEN_SRC): brokermgmt_gen.timestamp + +if GENERATE +BROKERMGMT_DEPS=../mgen.timestamp +endif # GENERATE +brokermgmt_gen.timestamp: BrokerMgmtAgent.xml ${BROKERMGMT_DEPS} + $(QMF_GEN) -b -o brokermgmt_gen/qmf $(srcdir)/BrokerMgmtAgent.xml + touch $@ + +BrokerMgmtAgent.$(OBJEXT): $(BROKERMGMT_GEN_SRC) + +CLEANFILES+=$(BROKERMGMT_GEN_SRC) brokermgmt_gen.timestamp + +unit_test_SOURCES+=BrokerMgmtAgent.cpp ${BROKERMGMT_GEN_SRC} +INCLUDES+= -Ibrokermgmt_gen + +EXTRA_DIST+=BrokerMgmtAgent.xml diff --git a/qpid/cpp/src/tests/brokertest.py b/qpid/cpp/src/tests/brokertest.py new file mode 100644 index 0000000000..a19dd305e5 --- /dev/null +++ b/qpid/cpp/src/tests/brokertest.py @@ -0,0 +1,671 @@ +# +# 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. +# + +# Support library for tests that start multiple brokers, e.g. cluster +# or federation + +import os, signal, string, tempfile, subprocess, socket, threading, time, imp, re +import qpid, traceback, signal +from qpid import connection, messaging, util +from qpid.compat import format_exc +from qpid.harness import Skipped +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") + +# Values for expected outcome of process at end of test +EXPECT_EXIT_OK=1 # Expect to exit with 0 status before end of test. +EXPECT_EXIT_FAIL=2 # Expect to exit with non-0 status before end of test. +EXPECT_RUNNING=3 # Expect to still be running at end of test +EXPECT_UNKNOWN=4 # No expectation, don't check exit status. + +def find_exe(program): + """Find an executable in the system PATH""" + def is_exe(fpath): + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + mydir, name = os.path.split(program) + if mydir: + if is_exe(program): return program + else: + for path in os.environ["PATH"].split(os.pathsep): + exe_file = os.path.join(path, program) + if is_exe(exe_file): return exe_file + return None + +def is_running(pid): + try: + os.kill(pid, 0) + return True + except: + return False + +class BadProcessStatus(Exception): + pass + +def error_line(filename, n=1): + """Get the last n line(s) of filename for error messages""" + result = [] + try: + f = open(filename) + try: + for l in f: + if len(result) == n: result.pop(0) + result.append(" "+l) + finally: + f.close() + except: return "" + return ":\n" + "".join(result) + +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(): + remaining = deadline - time.time() + if remaining <= 0: return False + delay = min(delay, remaining) + time.sleep(delay) + 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. + """ + + 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. + 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.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, 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("%s\n%d"%(self.cmd_str(), self.pid)) + finally: f.close() + log.debug("Started process %s: %s" % (self.pname, " ".join(self.cmd))) + + def __str__(self): return "Popen<%s>"%(self.pname) + + 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()) + else: + retry(lambda: self.poll() is not None) + if self.returncode is None: # Still haven't stopped + self.kill() + self.unexpected("still running") + elif self.expect == EXPECT_EXIT_OK and self.returncode != 0: + self.unexpected("exit code %d" % self.returncode) + elif self.expect == EXPECT_EXIT_FAIL and self.returncode == 0: + self.unexpected("expected error") + finally: + self.wait() # Clean up the process. + + def communicate(self, input=None): + ret = subprocess.Popen.communicate(self, input) + self.cleanup() + return ret + + 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 wait(self): + ret = subprocess.Popen.wait(self) + self._cleanup() + return ret + + def terminate(self): + try: subprocess.Popen.terminate(self) + except AttributeError: # No terminate method + try: + 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 + try: + 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]) + +def checkenv(name): + value = os.getenv(name) + if not value: raise Exception("Environment variable %s is not set" % name) + return value + +def find_in_file(str, filename): + if not os.path.exists(filename): return False + f = open(filename) + try: return str in f.read() + finally: f.close() + +class Broker(Popen): + "A broker process. Takes care of start, stop and logging." + _broker_count = 0 + + def __str__(self): return "Broker<%s %s>"%(self.name, self.pname) + + def find_log(self): + self.log = "%s.log" % self.name + i = 1 + 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): + """Start a broker daemon. name determines the data-dir and log + file names.""" + + self.test = test + self._port=port + if BrokerTest.store_lib: + args = args + ['--load-module', BrokerTest.store_lib] + if BrokerTest.sql_store_lib: + args = args + ['--load-module', BrokerTest.sql_store_lib] + args = args + ['--catalog', BrokerTest.sql_catalog] + if BrokerTest.sql_clfs_store_lib: + args = args + ['--load-module', BrokerTest.sql_clfs_store_lib] + args = args + ['--catalog', BrokerTest.sql_catalog] + cmd = [BrokerTest.qpidd_exec, "--port", port, "--no-module-dir"] + args + if not "--auth" in args: cmd.append("--auth=no") + if wait != None: + cmd += ["--wait", str(wait)] + if name: self.name = name + else: + self.name = "broker%d" % Broker._broker_count + Broker._broker_count += 1 + self.find_log() + cmd += ["--log-to-file", self.log] + cmd += ["--log-to-stderr=no"] + if log_level != None: + cmd += ["--log-enable=%s" % log_level] + self.datadir = self.name + cmd += ["--data-dir", self.datadir] + 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): + # Read port from broker process stdout if not already read. + if (self._port == 0): + try: self._port = int(self.stdout.readline()) + except ValueError: + raise Exception("Can't get port for broker %s (%s)%s" % + (self.name, self.pname, error_line(self.log,5))) + return self._port + + def unexpected(self,msg): + raise BadProcessStatus("%s: %s (%s)" % (msg, self.name, self.pname)) + + def connect(self, **kwargs): + """New API connection to the broker.""" + return messaging.Connection.establish(self.host_port(), **kwargs) + + def connect_old(self): + """Old API connection to the broker.""" + socket = qpid.util.connect(self.host(),self.port()) + connection = qpid.connection.Connection (sock=socket) + connection.start() + return connection; + + def declare_queue(self, queue): + c = self.connect_old() + 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 + "}" + return s + "}}" + + def send_message(self, queue, message, durable=True, xprops=None, session=None): + if session == None: + s = self.connect().session() + else: + s = session + s.sender(self._prep_sender(queue, durable, xprops)).send(message) + if session == None: + s.connection.close() + + def send_messages(self, queue, messages, durable=True, xprops=None, session=None): + if session == None: + s = self.connect().session() + else: + s = session + sender = s.sender(self._prep_sender(queue, durable, xprops)) + for m in messages: sender.send(m) + if session == None: + s.connection.close() + + def get_message(self, queue): + s = self.connect().session() + m = s.receiver(queue+"; {create:always}", capacity=1).fetch(timeout=1) + s.acknowledge() + s.connection.close() + return m + + def get_messages(self, queue, n): + s = self.connect().session() + receiver = s.receiver(queue+"; {create:always}", capacity=n) + m = [receiver.fetch(timeout=1) for i in range(n)] + s.acknowledge() + s.connection.close() + return m + + def host_port(self): return "%s:%s" % (self.host(), self.port()) + + def log_ready(self): + """Return true if the log file exists and contains a broker ready message""" + 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=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 + # return after cluster init has finished. + try: + c = self.connect(**kwargs) + try: c.session() + finally: c.close() + except Exception,e: raise RethrownException( + "Broker %s not responding: (%s)%s"%(self.name,e,error_line(self.log, 5))) + + def store_state(self): + 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.""" + + _cluster_count = 0 + + def __init__(self, test, count=0, args=[], expect=EXPECT_RUNNING, wait=True): + self.test = test + self._brokers=[] + self.name = "cluster%d" % Cluster._cluster_count + Cluster._cluster_count += 1 + # Use unique cluster name + self.args = copy(args) + self.args += [ "--cluster-name", "%s-%s:%d" % (self.name, socket.gethostname(), os.getpid()) ] + 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) + + def start(self, name=None, expect=EXPECT_RUNNING, wait=True, args=[], port=0): + """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)) + 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) + + # Behave like a list of brokers. + def __len__(self): return len(self._brokers) + def __getitem__(self,index): return self._brokers[index] + def __iter__(self): return self._brokers.__iter__() + +class BrokerTest(TestCase): + """ + Tracks processes started by test and kills at end of test. + Provides a well-known working directory for each test. + """ + + # Environment settings. + qpidd_exec = os.path.abspath(checkenv("QPIDD_EXEC")) + cluster_lib = os.getenv("CLUSTER_LIB") + xml_lib = os.getenv("XML_LIB") + qpid_config_exec = os.getenv("QPID_CONFIG_EXEC") + qpid_route_exec = os.getenv("QPID_ROUTE_EXEC") + receiver_exec = os.getenv("RECEIVER_EXEC") + sender_exec = os.getenv("SENDER_EXEC") + sql_store_lib = os.getenv("STORE_SQL_LIB") + sql_clfs_store_lib = os.getenv("STORE_SQL_CLFS_LIB") + sql_catalog = os.getenv("STORE_CATALOG") + store_lib = os.getenv("STORE_LIB") + test_store_lib = os.getenv("TEST_STORE_LIB") + 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()) + os.makedirs(self.dir) + os.chdir(self.dir) + self.stopem = [] # things to stop at end of test + + def tearDown(self): + err = [] + for p in self.stopem: + try: p.stop() + except Exception, e: err.append(str(e)) + self.stopem = [] # reset in case more processes start + os.chdir(self.rootdir) + if err: raise Exception("Unexpected process status:\n "+"\n ".join(err)) + + def cleanup_stop(self, stopable): + """Call thing.stop at end of test""" + self.stopem.append(stopable) + + 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, 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): + """Create and return a broker ready for use""" + b = Broker(self, args=args, name=name, expect=expect, port=port, log_level=log_level) + 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): + """Create and return a cluster ready for use""" + cluster = Cluster(self, count, args, expect=expect, wait=wait) + return cluster + + def browse(self, session, queue, 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)) + try: + contents = [] + try: + while True: contents.append(r.fetch(timeout=timeout).content) + except messaging.Empty: pass + finally: pass #FIXME aconway 2011-04-14: 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""" + 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=""): + Exception.__init__(self, msg+"\n"+format_exc()) + +class StoppableThread(Thread): + """ + Base class for threads that do something in a loop and periodically check + to see if they have been stopped. + """ + def __init__(self): + self.stopped = False + self.error = None + Thread.__init__(self) + + def stop(self): + self.stopped = True + 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"): + """ + max_depth: enable flow control, ensure sent - received <= max_depth. + Requires self.notify_received(n) to be called each time messages are received. + """ + Thread.__init__(self) + self.sender = broker.test.popen( + ["qpid-send", + "--broker", "localhost:%s"%broker.port(), + "--address", "%s;{create:always}"%queue, + "--failover-updates", + "--content-stdin" + ], + expect=EXPECT_RUNNING, + stdin=PIPE) + self.condition = Condition() + self.max = max_depth + self.received = 0 + self.stopped = False + self.error = None + + def write_message(self, n): + self.sender.stdin.write(str(n)+"\n") + self.sender.stdin.flush() + + def run(self): + try: + self.sent = 0 + while not self.stopped: + if self.max: + self.condition.acquire() + while not self.stopped and self.sent - self.received > self.max: + self.condition.wait() + self.condition.release() + self.write_message(self.sent) + self.sent += 1 + except Exception: self.error = RethrownException(self.sender.pname) + + def notify_received(self, count): + """Called by receiver to enable flow control. count = messages received so far.""" + self.condition.acquire() + self.received = count + self.condition.notify() + self.condition.release() + + def stop(self): + self.condition.acquire() + try: + self.stopped = True + self.condition.notify() + finally: self.condition.release() + 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"): + """ + sender: enable flow control. Call sender.received(n) for each message received. + """ + Thread.__init__(self) + self.test = broker.test + self.receiver = self.test.popen( + ["qpid-receive", + "--broker", "localhost:%s"%broker.port(), + "--address", "%s;{create:always}"%queue, + "--failover-updates", + "--forever" + ], + expect=EXPECT_RUNNING, + stdout=PIPE) + self.lock = Lock() + self.error = None + self.sender = sender + + def read_message(self): + return int(self.receiver.stdout.readline()) + + def run(self): + try: + self.received = 0 + m = self.read_message() + while m != -1: + assert(m <= self.received) # Check for missing messages + if (m == self.received): # Ignore duplicates + self.received += 1 + if self.sender: + self.sender.notify_received(self.received) + m = self.read_message() + except Exception: + self.error = RethrownException(self.receiver.pname) + + def stop(self): + """Returns when termination message is received""" + join(self) + if self.error: raise self.error + +class ErrorGenerator(StoppableThread): + """ + Thread that continuously generates errors by trying to consume from + a non-existent queue. For cluster regression tests, error handling + caused issues in the past. + """ + + def __init__(self, broker): + StoppableThread.__init__(self) + self.broker=broker + broker.test.cleanup_stop(self) + self.start() + + def run(self): + c = self.broker.connect_old() + try: + while not self.stopped: + try: + c.session(str(qpid.datatypes.uuid4())).message_subscribe( + queue="non-existent-queue") + assert(False) + except qpid.session.SessionException: pass + time.sleep(0.01) + except: pass # Normal if broker is killed. + +def import_script(path): + """ + Import executable script at path as a module. + Requires some trickery as scripts are not in standard module format + """ + f = open(path) + try: + name=os.path.split(path)[1].replace("-","_") + return imp.load_module(name, f, path, ("", "r", imp.PY_SOURCE)) + finally: f.close() diff --git a/qpid/cpp/src/tests/cli_tests.py b/qpid/cpp/src/tests/cli_tests.py new file mode 100755 index 0000000000..6c75927461 --- /dev/null +++ b/qpid/cpp/src/tests/cli_tests.py @@ -0,0 +1,475 @@ +#!/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 +import os +import imp +from qpid.testlib import TestBase010 +# from brokertest import import_script, checkenv +from qpid.datatypes import Message +from qpid.queue import Empty +from time import sleep + +def import_script(path): + """ + Import executable script at path as a module. + Requires some trickery as scripts are not in standard module format + """ + f = open(path) + try: + name=os.path.split(path)[1].replace("-","_") + return imp.load_module(name, f, path, ("", "r", imp.PY_SOURCE)) + finally: f.close() + +def checkenv(name): + value = os.getenv(name) + if not value: raise Exception("Environment variable %s is not set" % name) + return value + +class CliTests(TestBase010): + + def remote_host(self): + return self.defines.get("remote-host", "localhost") + + def remote_port(self): + return int(self.defines["remote-port"]) + + def cli_dir(self): + return self.defines["cli-dir"] + + def makeQueue(self, qname, arguments, api=False): + if api: + ret = self.qpid_config_api(" add queue " + qname + " " + arguments) + else: + ret = os.system(self.qpid_config_command(" add queue " + qname + " " + arguments)) + + self.assertEqual(ret, 0) + queues = self.qmf.getObjects(_class="queue") + for queue in queues: + if queue.name == qname: + return queue + assert False + + def test_queue_params(self): + self.startQmf() + queue1 = self.makeQueue("test_queue_params1", "--limit-policy none") + queue2 = self.makeQueue("test_queue_params2", "--limit-policy reject") + queue3 = self.makeQueue("test_queue_params3", "--limit-policy flow-to-disk") + queue4 = self.makeQueue("test_queue_params4", "--limit-policy ring") + queue5 = self.makeQueue("test_queue_params5", "--limit-policy ring-strict") + + LIMIT = "qpid.policy_type" + assert LIMIT not in queue1.arguments + self.assertEqual(queue2.arguments[LIMIT], "reject") + self.assertEqual(queue3.arguments[LIMIT], "flow_to_disk") + self.assertEqual(queue4.arguments[LIMIT], "ring") + self.assertEqual(queue5.arguments[LIMIT], "ring_strict") + + queue6 = self.makeQueue("test_queue_params6", "--order fifo") + queue7 = self.makeQueue("test_queue_params7", "--order lvq") + queue8 = self.makeQueue("test_queue_params8", "--order lvq-no-browse") + + LVQ = "qpid.last_value_queue" + LVQNB = "qpid.last_value_queue_no_browse" + + assert LVQ not in queue6.arguments + assert LVQ in queue7.arguments + assert LVQ not in queue8.arguments + + assert LVQNB not in queue6.arguments + assert LVQNB not in queue7.arguments + assert LVQNB in queue8.arguments + + + def test_queue_params_api(self): + self.startQmf() + queue1 = self.makeQueue("test_queue_params1", "--limit-policy none", True) + queue2 = self.makeQueue("test_queue_params2", "--limit-policy reject", True) + queue3 = self.makeQueue("test_queue_params3", "--limit-policy flow-to-disk", True) + queue4 = self.makeQueue("test_queue_params4", "--limit-policy ring", True) + queue5 = self.makeQueue("test_queue_params5", "--limit-policy ring-strict", True) + + LIMIT = "qpid.policy_type" + assert LIMIT not in queue1.arguments + self.assertEqual(queue2.arguments[LIMIT], "reject") + self.assertEqual(queue3.arguments[LIMIT], "flow_to_disk") + self.assertEqual(queue4.arguments[LIMIT], "ring") + self.assertEqual(queue5.arguments[LIMIT], "ring_strict") + + queue6 = self.makeQueue("test_queue_params6", "--order fifo", True) + queue7 = self.makeQueue("test_queue_params7", "--order lvq", True) + queue8 = self.makeQueue("test_queue_params8", "--order lvq-no-browse", True) + + LVQ = "qpid.last_value_queue" + LVQNB = "qpid.last_value_queue_no_browse" + + assert LVQ not in queue6.arguments + assert LVQ in queue7.arguments + assert LVQ not in queue8.arguments + + assert LVQNB not in queue6.arguments + assert LVQNB not in queue7.arguments + assert LVQNB in queue8.arguments + + + def test_qpid_config(self): + self.startQmf(); + qmf = self.qmf + qname = "test_qpid_config" + + ret = os.system(self.qpid_config_command(" add queue " + qname)) + self.assertEqual(ret, 0) + queues = qmf.getObjects(_class="queue") + found = False + for queue in queues: + if queue.name == qname: + self.assertEqual(queue.durable, False) + found = True + self.assertEqual(found, True) + + ret = os.system(self.qpid_config_command(" del queue " + qname)) + self.assertEqual(ret, 0) + queues = qmf.getObjects(_class="queue") + found = False + for queue in queues: + if queue.name == qname: + found = True + self.assertEqual(found, False) + + def test_qpid_config_api(self): + self.startQmf(); + qmf = self.qmf + qname = "test_qpid_config_api" + + ret = self.qpid_config_api(" add queue " + qname) + self.assertEqual(ret, 0) + queues = qmf.getObjects(_class="queue") + found = False + for queue in queues: + if queue.name == qname: + self.assertEqual(queue.durable, False) + found = True + self.assertEqual(found, True) + + ret = self.qpid_config_api(" del queue " + qname) + self.assertEqual(ret, 0) + queues = qmf.getObjects(_class="queue") + found = False + for queue in queues: + if queue.name == qname: + found = True + self.assertEqual(found, False) + + + def test_qpid_config_sasl_plain_expect_succeed(self): + self.startQmf(); + qmf = self.qmf + qname = "test_qpid_config_sasl_plain_expect_succeed" + cmd = " --sasl-mechanism PLAIN -a guest/guest@localhost:"+str(self.broker.port) + " add queue " + qname + ret = self.qpid_config_api(cmd) + self.assertEqual(ret, 0) + + def test_qpid_config_sasl_plain_expect_fail(self): + """Fails because no user name and password is supplied""" + self.startQmf(); + qmf = self.qmf + qname = "test_qpid_config_sasl_plain_expect_succeed" + cmd = " --sasl-mechanism PLAIN -a localhost:"+str(self.broker.port) + " add queue " + qname + ret = self.qpid_config_api(cmd) + assert ret != 0 + + # helpers for some of the test methods + def helper_find_exchange(self, xchgname, typ, expected=True): + xchgs = self.qmf.getObjects(_class = "exchange") + found = False + for xchg in xchgs: + if xchg.name == xchgname: + if typ: + self.assertEqual(xchg.type, typ) + found = True + self.assertEqual(found, expected) + + def helper_create_exchange(self, xchgname, typ="direct", opts=""): + foo = self.qpid_config_command(opts + " add exchange " + typ + " " + xchgname) + # print foo + ret = os.system(foo) + self.assertEqual(ret, 0) + self.helper_find_exchange(xchgname, typ, True) + + def helper_destroy_exchange(self, xchgname): + foo = self.qpid_config_command(" del exchange " + xchgname) + # print foo + ret = os.system(foo) + self.assertEqual(ret, 0) + self.helper_find_exchange(xchgname, False, expected=False) + + def helper_find_queue(self, qname, expected=True): + queues = self.qmf.getObjects(_class="queue") + found = False + for queue in queues: + if queue.name == qname: + self.assertEqual(queue.durable, False) + found = True + self.assertEqual(found, expected) + + def helper_create_queue(self, qname): + foo = self.qpid_config_command(" add queue " + qname) + # print foo + ret = os.system(foo) + self.assertEqual(ret, 0) + self.helper_find_queue(qname, True) + + def helper_destroy_queue(self, qname): + foo = self.qpid_config_command(" del queue " + qname) + # print foo + ret = os.system(foo) + self.assertEqual(ret, 0) + self.helper_find_queue(qname, False) + + + # test the bind-queue-to-header-exchange functionality + def test_qpid_config_headers(self): + self.startQmf(); + qmf = self.qmf + qname = "test_qpid_config" + xchgname = "test_xchg" + + # first create a header xchg + self.helper_create_exchange(xchgname, typ="headers") + + # create the queue + self.helper_create_queue(qname) + + # now bind the queue to the xchg + foo = self.qpid_config_command(" bind " + xchgname + " " + qname + + " key all foo=bar baz=quux") + # print foo + ret = os.system(foo) + self.assertEqual(ret, 0) + + # he likes it, mikey. Ok, now tear it all down. first the binding + ret = os.system(self.qpid_config_command(" unbind " + xchgname + " " + qname + + " key")) + self.assertEqual(ret, 0) + + # then the queue + self.helper_destroy_queue(qname) + + # then the exchange + self.helper_destroy_exchange(xchgname) + + + def test_qpid_config_xml(self): + self.startQmf(); + qmf = self.qmf + qname = "test_qpid_config" + xchgname = "test_xchg" + + # first create a header xchg + self.helper_create_exchange(xchgname, typ="xml") + + # create the queue + self.helper_create_queue(qname) + + # now bind the queue to the xchg + foo = self.qpid_config_command("-f test.xquery bind " + xchgname + " " + qname) + # print foo + ret = os.system(foo) + self.assertEqual(ret, 0) + + # he likes it, mikey. Ok, now tear it all down. first the binding + ret = os.system(self.qpid_config_command(" unbind " + xchgname + " " + qname + + " key")) + self.assertEqual(ret, 0) + + # then the queue + self.helper_destroy_queue(qname) + + # then the exchange + self.helper_destroy_exchange(xchgname) + + def test_qpid_config_durable(self): + self.startQmf(); + qmf = self.qmf + qname = "test_qpid_config" + + ret = os.system(self.qpid_config_command(" add queue --durable " + qname)) + self.assertEqual(ret, 0) + queues = qmf.getObjects(_class="queue") + found = False + for queue in queues: + if queue.name == qname: + self.assertEqual(queue.durable, True) + found = True + self.assertEqual(found, True) + + ret = os.system(self.qpid_config_command(" del queue " + qname)) + self.assertEqual(ret, 0) + queues = qmf.getObjects(_class="queue") + found = False + for queue in queues: + if queue.name == qname: + found = True + self.assertEqual(found, False) + + def test_qpid_config_altex(self): + self.startQmf(); + qmf = self.qmf + exName = "testalt" + qName = "testqalt" + altName = "amq.direct" + + ret = os.system(self.qpid_config_command(" add exchange topic %s --alternate-exchange=%s" % (exName, altName))) + self.assertEqual(ret, 0) + + exchanges = qmf.getObjects(_class="exchange") + found = False + for exchange in exchanges: + if exchange.name == altName: + self.assertEqual(exchange.altExchange, None) + + if exchange.name == exName: + found = True + if not exchange.altExchange: + self.fail("Alternate exchange not set") + self.assertEqual(exchange._altExchange_.name, altName) + self.assertEqual(found, True) + + ret = os.system(self.qpid_config_command(" add queue %s --alternate-exchange=%s" % (qName, altName))) + self.assertEqual(ret, 0) + + queues = qmf.getObjects(_class="queue") + found = False + for queue in queues: + if queue.name == qName: + found = True + if not queue.altExchange: + self.fail("Alternate exchange not set") + 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 + + command = self.cli_dir() + "/qpid-route dynamic add guest/guest@localhost:%d %s:%d amq.topic" %\ + (self.broker.port, self.remote_host(), self.remote_port()) + ret = os.system(command) + self.assertEqual(ret, 0) + + links = qmf.getObjects(_class="link") + found = False + for link in links: + if link.port == self.remote_port(): + found = True + self.assertEqual(found, True) + + def test_qpid_route_api(self): + self.startQmf(); + qmf = self.qmf + + ret = self.qpid_route_api("dynamic add " + + "guest/guest@localhost:"+str(self.broker.port) + " " + + str(self.remote_host())+":"+str(self.remote_port()) + " " + +"amq.direct") + + self.assertEqual(ret, 0) + + links = qmf.getObjects(_class="link") + found = False + for link in links: + if link.port == self.remote_port(): + found = True + self.assertEqual(found, True) + + + def test_qpid_route_api(self): + self.startQmf(); + qmf = self.qmf + + ret = self.qpid_route_api("dynamic add " + + " --client-sasl-mechanism PLAIN " + + "guest/guest@localhost:"+str(self.broker.port) + " " + + str(self.remote_host())+":"+str(self.remote_port()) + " " + +"amq.direct") + + self.assertEqual(ret, 0) + + links = qmf.getObjects(_class="link") + found = False + for link in links: + if link.port == self.remote_port(): + found = True + self.assertEqual(found, True) + + def test_qpid_route_api_expect_fail(self): + self.startQmf(); + qmf = self.qmf + + ret = self.qpid_route_api("dynamic add " + + " --client-sasl-mechanism PLAIN " + + "localhost:"+str(self.broker.port) + " " + + str(self.remote_host())+":"+str(self.remote_port()) + " " + +"amq.direct") + assert ret != 0 + + + def getProperty(self, msg, name): + for h in msg.headers: + if hasattr(h, name): return getattr(h, name) + return None + + def getAppHeader(self, msg, name): + headers = self.getProperty(msg, "application_headers") + if headers: + return headers[name] + return None + + def qpid_config_command(self, arg = ""): + return self.cli_dir() + "/qpid-config -a localhost:%d" % self.broker.port + " " + arg + + def qpid_config_api(self, arg = ""): + script = import_script(checkenv("QPID_CONFIG_EXEC")) + broker = ["-a", "localhost:"+str(self.broker.port)] + return script.main(broker + arg.split()) + + def qpid_route_api(self, arg = ""): + script = import_script(checkenv("QPID_ROUTE_EXEC")) + return script.main(arg.split()) diff --git a/qpid/cpp/src/tests/cluster.cmake b/qpid/cpp/src/tests/cluster.cmake new file mode 100644 index 0000000000..3471173e97 --- /dev/null +++ b/qpid/cpp/src/tests/cluster.cmake @@ -0,0 +1,90 @@ +# +# 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. +# + +# +# Cluster tests cmake fragment, to be included in CMakeLists.txt +# + +add_executable (failover_soak failover_soak.cpp ForkedBroker.cpp ${platform_test_additions}) +target_link_libraries (failover_soak qpidclient) +remember_location(failover_soak) + +add_executable (cluster_authentication_soak cluster_authentication_soak.cpp ForkedBroker.cpp ${platform_test_additions}) +target_link_libraries (cluster_authentication_soak qpidclient) +remember_location(cluster_authentication_soak) + +set (cluster_test_SOURCES + cluster_test + unit_test + ClusterFixture + ForkedBroker + PartialFailure + ClusterFailover + InitialStatusMap + StoreStatus + ) +add_executable (cluster_test ${cluster_test_SOURCES} ${platform_test_additions}) +target_link_libraries (cluster_test ${qpid_test_boost_libs} qpidclient qpidbroker cluster_shared) +remember_location(cluster_test) + +add_test (cluster_test ${CMAKE_CURRENT_SOURCE_DIR}/run_cluster_test${test_script_suffix}) +add_test (cluster_tests ${CMAKE_CURRENT_SOURCE_DIR}/run_cluster_tests${test_script_suffix}) +add_test (cluster_read_credit ${CMAKE_CURRENT_SOURCE_DIR}/cluster_read_credit${test_script_suffix}) +add_test (cluster_test_watchdog ${CMAKE_CURRENT_SOURCE_DIR}/test_watchdog${test_script_suffix}) +add_test (federated_cluster_test ${CMAKE_CURRENT_SOURCE_DIR}/federated_cluster_test${test_script_suffix}) +add_test (clustered_replication_test ${CMAKE_CURRENT_SOURCE_DIR}/clustered_replication_test${test_script_suffix}) + +# FIXME aconway 2009-12-01: translate to cmake +# # Clean up after cluster_test and start_cluster +# CLEANFILES += cluster_test.acl cluster.ports + +# EXTRA_DIST += \ +# ais_check \ +# run_cluster_test \ +# cluster_read_credit \ +# test_watchdog \ +# start_cluster \ +# stop_cluster \ +# restart_cluster \ +# cluster_python_tests \ +# cluster_python_tests_failing.txt \ +# federated_cluster_test \ +# clustered_replication_test \ +# run_cluster_tests \ +# run_long_cluster_tests \ +# testlib.py \ +# cluster_tests.py \ +# long_cluster_tests.py \ +# cluster_tests.fail + +# LONG_TESTS += \ +# run_long_cluster_tests \ +# start_cluster \ +# cluster_python_tests \ +# stop_cluster + +# qpidtest_PROGRAMS += cluster_test + +# cluster_test_SOURCES = \ + +# cluster_test_LDADD=$(lib_client) $(lib_broker) ../cluster.la -lboost_unit_test_framework + +# qpidtest_SCRIPTS += run_cluster_tests cluster_tests.py run_long_cluster_tests long_cluster_tests.py testlib.py cluster_tests.fail + +# endif diff --git a/qpid/cpp/src/tests/cluster.mk b/qpid/cpp/src/tests/cluster.mk new file mode 100644 index 0000000000..7d17dd7bde --- /dev/null +++ b/qpid/cpp/src/tests/cluster.mk @@ -0,0 +1,100 @@ +# +# 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 cluster scripts and extra files in distribution even if +# we're not configured for cluster. + +# Useful scripts for doing cluster testing. +CLUSTER_TEST_SCRIPTS_LIST= \ + allhosts rsynchosts \ + qpid-build-rinstall qpid-src-rinstall \ + qpid-test-cluster \ + qpid-cluster-benchmark + +EXTRA_DIST += \ + $(CLUSTER_TEST_SCRIPTS_LIST) \ + ais_check \ + run_cluster_test \ + cluster_read_credit \ + test_watchdog \ + start_cluster \ + stop_cluster \ + restart_cluster \ + cluster_python_tests \ + cluster_python_tests_failing.txt \ + federated_cluster_test \ + clustered_replication_test \ + run_cluster_tests \ + run_long_cluster_tests \ + testlib.py \ + brokertest.py \ + cluster_tests.py \ + cluster_test_logs.py \ + long_cluster_tests.py \ + cluster_tests.fail + + +if HAVE_LIBCPG + +# +# Cluster tests makefile fragment, to be included in Makefile.am +# + +# NOTE: Programs using the openais library must be run with gid=ais +# You should do "newgrp ais" before running the tests to run these. +# + + +# ais_check checks pre-requisites for cluster tests and runs them if ok. +TESTS += \ + run_cluster_test \ + cluster_read_credit \ + test_watchdog \ + run_cluster_tests \ + federated_cluster_test \ + clustered_replication_test + +# Clean up after cluster_test and start_cluster +CLEANFILES += cluster_test.acl cluster.ports + +LONG_TESTS += \ + run_long_cluster_tests \ + start_cluster \ + cluster_python_tests \ + stop_cluster + +qpidtest_PROGRAMS += cluster_test + +cluster_test_SOURCES = \ + cluster_test.cpp \ + unit_test.cpp \ + ClusterFixture.cpp \ + ClusterFixture.h \ + ForkedBroker.h \ + ForkedBroker.cpp \ + PartialFailure.cpp \ + ClusterFailover.cpp + +cluster_test_LDADD=$(lib_client) $(lib_broker) ../cluster.la -lboost_unit_test_framework + +qpidtest_SCRIPTS += run_cluster_tests brokertest.py cluster_tests.py cluster_test_logs.py run_long_cluster_tests long_cluster_tests.py testlib.py cluster_tests.fail +qpidtest_SCRIPTS += $(CLUSTER_TEST_SCRIPTS_LIST) + +endif diff --git a/qpid/cpp/src/tests/cluster_authentication_soak.cpp b/qpid/cpp/src/tests/cluster_authentication_soak.cpp new file mode 100644 index 0000000000..b8e8a22693 --- /dev/null +++ b/qpid/cpp/src/tests/cluster_authentication_soak.cpp @@ -0,0 +1,310 @@ +/* + * + * 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 <stdlib.h> +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> + +#include <sys/wait.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> + +#include <string> +#include <iostream> +#include <sstream> +#include <vector> + +#include <boost/assign.hpp> + +#include "qpid/framing/Uuid.h" + +#include <ForkedBroker.h> +#include <qpid/client/Connection.h> + +#include <sasl/sasl.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + + + + +using namespace std; +using boost::assign::list_of; +using namespace qpid::framing; +using namespace qpid::client; + + +namespace qpid { +namespace tests { + +vector<pid_t> brokerPids; + +typedef vector<ForkedBroker *> brokerVector; + + + + + +int runSilent = 1; +int newbiePort = 0; + + +void +makeClusterName ( string & s ) { + stringstream ss; + ss << "authenticationSoakCluster_" << Uuid(true).str(); + s = ss.str(); +} + + + +void +startBroker ( brokerVector & brokers , int brokerNumber, string const & clusterName ) { + stringstream prefix, clusterArg; + prefix << "soak-" << brokerNumber; + clusterArg << "--cluster-name=" << clusterName; + + std::vector<std::string> argv; + + argv.push_back ("../qpidd"); + argv.push_back ("--no-module-dir"); + argv.push_back ("--load-module=../.libs/cluster.so"); + argv.push_back (clusterArg.str()); + argv.push_back ("--cluster-username=zig"); + argv.push_back ("--cluster-password=zig"); + argv.push_back ("--cluster-mechanism=ANONYMOUS"); + argv.push_back ("--sasl-config=./sasl_config"); + argv.push_back ("--auth=yes"); + argv.push_back ("--mgmt-enable=yes"); + argv.push_back ("--log-prefix"); + argv.push_back (prefix.str()); + argv.push_back ("--log-to-file"); + argv.push_back (prefix.str()+".log"); + argv.push_back ("TMP_DATA_DIR"); + + ForkedBroker * newbie = new ForkedBroker (argv); + newbiePort = newbie->getPort(); + brokers.push_back ( newbie ); +} + + + + +bool +runPerftest ( bool hangTest ) { + stringstream portSs; + portSs << newbiePort; + string portStr = portSs.str(); + char const * path = "./qpid-perftest"; + + vector<char const *> argv; + argv.push_back ( "./qpid-perftest" ); + argv.push_back ( "-p" ); + argv.push_back ( portStr.c_str() ); + argv.push_back ( "--username" ); + argv.push_back ( "zig" ); + argv.push_back ( "--password" ); + argv.push_back ( "zig" ); + argv.push_back ( "--mechanism" ); + argv.push_back ( "DIGEST-MD5" ); + argv.push_back ( "--count" ); + argv.push_back ( "20000" ); + argv.push_back ( 0 ); + + pid_t pid = fork(); + + if ( ! pid ) { + int i=open("/dev/null",O_RDWR); + dup2 ( i, fileno(stdout) ); + dup2 ( i, fileno(stderr) ); + + execv ( path, const_cast<char * const *>(&argv[0]) ); + // The exec failed: we are still in parent process. + perror ( "error running qpid-perftest: " ); + return false; + } + else { + if ( hangTest ) { + if ( ! runSilent ) + cerr << "Pausing perftest " << pid << endl; + kill ( pid, 19 ); + } + + struct timeval startTime, + currentTime, + duration; + + gettimeofday ( & startTime, 0 ); + + while ( 1 ) { + sleep ( 2 ); + int status; + int returned_pid = waitpid ( pid, &status, WNOHANG ); + if ( returned_pid == pid ) { + int exit_status = WEXITSTATUS(status); + if ( exit_status ) { + cerr << "qpid-perftest failed. exit_status was: " << exit_status << endl; + return false; + } + else { + return true; // qpid-perftest succeeded. + } + } + else { // qpid-perftest has not yet completed. + gettimeofday ( & currentTime, 0 ); + timersub ( & currentTime, & startTime, & duration ); + if ( duration.tv_sec > 60 ) { + kill ( pid, 9 ); + cerr << "qpid-perftest pid " << pid << " hanging: killed.\n"; + return false; + } + } + } + + } +} + + + +bool +allBrokersAreAlive ( brokerVector & brokers ) { + for ( unsigned int i = 0; i < brokers.size(); ++ i ) + if ( ! brokers[i]->isRunning() ) + return false; + + return true; +} + + + + + +void +killAllBrokers ( brokerVector & brokers ) { + for ( unsigned int i = 0; i < brokers.size(); ++ i ) { + brokers[i]->kill ( 9 ); + } +} + + + + +void +killOneBroker ( brokerVector & brokers ) { + int doomedBroker = getpid() % brokers.size(); + cout << "Killing broker " << brokers[doomedBroker]->getPID() << endl; + brokers[doomedBroker]->kill ( 9 ); + sleep ( 2 ); +} + + + + +}} // namespace qpid::tests + +using namespace qpid::tests; + + + +/* + * Please note that this test has self-test capability. + * It is intended to detect + * 1. perftest hangs. + * 2. broker deaths + * Both of these condtions can be forced when running manually + * to ensure that the test really does detect them. + * See command-line arguments 3 and 4. + */ +int +main ( int argc, char ** argv ) +{ + // I need the SASL_PATH_TYPE_CONFIG feature, which did not appear until SASL 2.1.22 +#if (SASL_VERSION_FULL < ((2<<16)|(1<<8)|22)) + cout << "Skipping SASL test, SASL version too low." << endl; + return 0; +#endif + + int n_iterations = argc > 1 ? atoi(argv[1]) : 1; + runSilent = argc > 2 ? atoi(argv[2]) : 1; // default to silent + int killBroker = argc > 3 ? atoi(argv[3]) : 0; // Force the kill of one broker. + int hangTest = argc > 4 ? atoi(argv[4]) : 0; // Force the first perftest to hang. + int n_brokers = 3; + brokerVector brokers; + + srand ( getpid() ); + string clusterName; + makeClusterName ( clusterName ); + for ( int i = 0; i < n_brokers; ++ i ) { + startBroker ( brokers, i, clusterName ); + } + + sleep ( 3 ); + + /* Run all qpid-perftest iterations, and only then check for brokers + * still being up. If you just want a quick check for the failure + * mode in which a single iteration would kill all brokers except + * the client-connected one, just run it with the iterations arg + * set to 1. + */ + for ( int iteration = 0; iteration < n_iterations; ++ iteration ) { + if ( ! runPerftest ( hangTest ) ) { + if ( ! runSilent ) + cerr << "qpid-perftest " << iteration << " failed.\n"; + return 1; + } + if ( ! ( iteration % 10 ) ) { + if ( ! runSilent ) + cerr << "qpid-perftest " << iteration << " complete. -------------- \n"; + } + } + if ( ! runSilent ) + cerr << "\nqpid-perftest " << n_iterations << " iterations complete. -------------- \n\n"; + + /* If the command-line tells us to kill a broker, do + * it now. Use this option to prove that this test + * really can detect broker-deaths. + */ + if ( killBroker ) { + killOneBroker ( brokers ); + } + + if ( ! allBrokersAreAlive ( brokers ) ) { + if ( ! runSilent ) + cerr << "not all brokers are alive.\n"; + killAllBrokers ( brokers ); + return 2; + } + + killAllBrokers ( brokers ); + if ( ! runSilent ) + cout << "success.\n"; + + return 0; +} + + + diff --git a/qpid/cpp/src/tests/cluster_python_tests b/qpid/cpp/src/tests/cluster_python_tests new file mode 100755 index 0000000000..9d9137ed57 --- /dev/null +++ b/qpid/cpp/src/tests/cluster_python_tests @@ -0,0 +1,27 @@ +#!/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. +# + +# Skip if cluster services not running. +. `dirname $0`/ais_check + +FAILING=`dirname $0`/cluster_python_tests_failing.txt +source `dirname $0`/python_tests + diff --git a/qpid/cpp/src/tests/cluster_python_tests_failing.txt b/qpid/cpp/src/tests/cluster_python_tests_failing.txt new file mode 100644 index 0000000000..7ba8089946 --- /dev/null +++ b/qpid/cpp/src/tests/cluster_python_tests_failing.txt @@ -0,0 +1,32 @@ +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/qpid/cpp/src/tests/cluster_read_credit b/qpid/cpp/src/tests/cluster_read_credit new file mode 100755 index 0000000000..370d4098c5 --- /dev/null +++ b/qpid/cpp/src/tests/cluster_read_credit @@ -0,0 +1,27 @@ +#!/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. +# + +# Regression test for http://issues.apache.org/jira/browse/QPID-2086 + +srcdir=`dirname $0` +. $srcdir/ais_check +$srcdir/start_cluster 1 --cluster-read-max=2 || exit 1 +trap $srcdir/stop_cluster EXIT +seq 1 10000 | ./sender --port `cat cluster.ports` --routing-key no-such-queue diff --git a/qpid/cpp/src/tests/cluster_test.cpp b/qpid/cpp/src/tests/cluster_test.cpp new file mode 100644 index 0000000000..f2ccd0ba84 --- /dev/null +++ b/qpid/cpp/src/tests/cluster_test.cpp @@ -0,0 +1,1231 @@ +/* + * + * 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. + * + */ + +#include "test_tools.h" +#include "unit_test.h" +#include "ForkedBroker.h" +#include "BrokerFixture.h" +#include "ClusterFixture.h" + +#include "qpid/client/Connection.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/client/ConnectionAccess.h" +#include "qpid/client/Session.h" +#include "qpid/client/FailoverListener.h" +#include "qpid/client/FailoverManager.h" +#include "qpid/client/QueueOptions.h" +#include "qpid/cluster/Cluster.h" +#include "qpid/cluster/Cpg.h" +#include "qpid/cluster/UpdateClient.h" +#include "qpid/framing/AMQBody.h" +#include "qpid/framing/Uuid.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/enum.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/log/Logger.h" +#include "qpid/sys/Monitor.h" +#include "qpid/sys/Thread.h" + +#include <boost/bind.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/assign.hpp> + +#include <string> +#include <iostream> +#include <fstream> +#include <iterator> +#include <vector> +#include <set> +#include <algorithm> +#include <iterator> + + +using namespace std; +using namespace qpid; +using namespace qpid::cluster; +using namespace qpid::framing; +using namespace qpid::client; +using namespace boost::assign; +using broker::Broker; +using boost::shared_ptr; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(cluster_test) + +bool durableFlag = std::getenv("STORE_LIB") != 0; + +void prepareArgs(ClusterFixture::Args& args, const bool durableFlag = false) { + ostringstream clusterLib; + clusterLib << getLibPath("CLUSTER_LIB"); + args += "--auth", "no", "--no-module-dir", "--load-module", clusterLib.str(); + if (durableFlag) + args += "--load-module", getLibPath("STORE_LIB"), "TMP_DATA_DIR"; + else + args += "--no-data-dir"; +} + +ClusterFixture::Args prepareArgs(const bool durableFlag = false) { + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + return args; +} + +// Timeout for tests that wait for messages +const sys::Duration TIMEOUT=2*sys::TIME_SEC; + + +ostream& operator<<(ostream& o, const cpg_name* n) { + return o << Cpg::str(*n); +} + +ostream& operator<<(ostream& o, const cpg_address& a) { + return o << "(" << a.nodeid <<","<<a.pid<<","<<a.reason<<")"; +} + +template <class T> +ostream& operator<<(ostream& o, const pair<T*, int>& array) { + o << "{ "; + ostream_iterator<cpg_address> i(o, " "); + copy(array.first, array.first+array.second, i); + o << "}"; + return o; +} + +template <class C> set<int> makeSet(const C& c) { + set<int> s; + copy(c.begin(), c.end(), inserter(s, s.begin())); + return s; +} + +class Sender { + public: + Sender(boost::shared_ptr<ConnectionImpl> ci, uint16_t ch) : connection(ci), channel(ch) {} + void send(const AMQBody& body, bool firstSeg, bool lastSeg, bool firstFrame, bool lastFrame) { + AMQFrame f(body); + f.setChannel(channel); + f.setFirstSegment(firstSeg); + f.setLastSegment(lastSeg); + f.setFirstFrame(firstFrame); + f.setLastFrame(lastFrame); + connection->expand(f.encodedSize(), false); + connection->handle(f); + } + + private: + boost::shared_ptr<ConnectionImpl> connection; + uint16_t channel; +}; + +int64_t getMsgSequence(const Message& m) { + return m.getMessageProperties().getApplicationHeaders().getAsInt64("qpid.msg_sequence"); +} + +Message ttlMessage(const string& data, const string& key, uint64_t ttl, bool durable = false) { + Message m(data, key); + m.getDeliveryProperties().setTtl(ttl); + if (durable) m.getDeliveryProperties().setDeliveryMode(framing::PERSISTENT); + return m; +} + +Message makeMessage(const string& data, const string& key, bool durable = false) { + Message m(data, key); + if (durable) m.getDeliveryProperties().setDeliveryMode(framing::PERSISTENT); + return m; +} + +vector<string> browse(Client& c, const string& q, int n) { + SubscriptionSettings browseSettings( + FlowControl::messageCredit(n), + ACCEPT_MODE_NONE, + ACQUIRE_MODE_NOT_ACQUIRED, + 0 // No auto-ack. + ); + LocalQueue lq; + c.subs.subscribe(lq, q, browseSettings); + c.session.messageFlush(q); + vector<string> result; + for (int i = 0; i < n; ++i) { + Message m; + if (!lq.get(m, TIMEOUT)) + break; + result.push_back(m.getData()); + } + c.subs.getSubscription(q).cancel(); + return result; +} + +ConnectionSettings aclSettings(int port, const std::string& id) { + ConnectionSettings settings; + settings.port = port; + settings.mechanism = "PLAIN"; + settings.username = id; + settings.password = id; + return settings; +} + +// An illegal frame body +struct PoisonPill : public AMQBody { + virtual uint8_t type() const { return 0xFF; } + virtual void encode(Buffer& ) const {} + virtual void decode(Buffer& , uint32_t=0) {} + virtual uint32_t encodedSize() const { return 0; } + + virtual void print(std::ostream&) const {}; + virtual void accept(AMQBodyConstVisitor&) const {}; + + virtual AMQMethodBody* getMethod() { return 0; } + virtual const AMQMethodBody* getMethod() const { return 0; } + + /** Match if same type and same class/method ID for methods */ + static bool match(const AMQBody& , const AMQBody& ) { return false; } + virtual boost::intrusive_ptr<AMQBody> clone() const { return new PoisonPill; } +}; + +QPID_AUTO_TEST_CASE(testBadClientData) { + // Ensure that bad data on a client connection closes the + // connection but does not stop the broker. + ClusterFixture::Args args; + prepareArgs(args, false); + args += "--log-enable=critical"; // Supress expected errors + ClusterFixture cluster(2, args, -1); + Client c0(cluster[0]); + Client c1(cluster[1]); + boost::shared_ptr<client::ConnectionImpl> ci = + client::ConnectionAccess::getImpl(c0.connection); + AMQFrame poison(boost::intrusive_ptr<AMQBody>(new PoisonPill)); + ci->expand(poison.encodedSize(), false); + ci->handle(poison); + { + ScopedSuppressLogging sl; + BOOST_CHECK_THROW(c0.session.queueQuery("q0"), Exception); + } + Client c00(cluster[0]); + BOOST_CHECK_EQUAL(c00.session.queueQuery("q00").getQueue(), ""); + BOOST_CHECK_EQUAL(c1.session.queueQuery("q1").getQueue(), ""); +} + +QPID_AUTO_TEST_CASE(testAcl) { + ofstream policyFile("cluster_test.acl"); + policyFile << "acl allow foo@QPID create queue name=foo" << endl + << "acl allow foo@QPID create queue name=foo2" << endl + << "acl deny foo@QPID create queue name=bar" << endl + << "acl allow all all" << endl; + policyFile.close(); + char cwd[1024]; + BOOST_CHECK(::getcwd(cwd, sizeof(cwd))); + ostringstream aclLib; + aclLib << getLibPath("ACL_LIB"); + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + args += "--log-enable=critical"; // Supress expected errors + args += "--acl-file", string(cwd) + "/cluster_test.acl", + "--cluster-mechanism", "PLAIN", + "--cluster-username", "cluster", + "--cluster-password", "cluster", + "--load-module", aclLib.str(); + ClusterFixture cluster(2, args, -1); + + Client c0(aclSettings(cluster[0], "c0"), "c0"); + Client c1(aclSettings(cluster[1], "c1"), "c1"); + Client foo(aclSettings(cluster[1], "foo"), "foo"); + + foo.session.queueDeclare("foo", arg::durable=durableFlag); + BOOST_CHECK_EQUAL(c0.session.queueQuery("foo").getQueue(), "foo"); + + { + ScopedSuppressLogging sl; + BOOST_CHECK_THROW(foo.session.queueDeclare("bar", arg::durable=durableFlag), framing::UnauthorizedAccessException); + } + BOOST_CHECK(c0.session.queueQuery("bar").getQueue().empty()); + BOOST_CHECK(c1.session.queueQuery("bar").getQueue().empty()); + + cluster.add(); + Client c2(aclSettings(cluster[2], "c2"), "c2"); + { + ScopedSuppressLogging sl; + BOOST_CHECK_THROW(foo.session.queueDeclare("bar", arg::durable=durableFlag), framing::UnauthorizedAccessException); + } + BOOST_CHECK(c2.session.queueQuery("bar").getQueue().empty()); +} + +QPID_AUTO_TEST_CASE(testMessageTimeToLive) { + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(2, args, -1); + Client c0(cluster[0], "c0"); + Client c1(cluster[1], "c1"); + c0.session.queueDeclare("p", arg::durable=durableFlag); + c0.session.queueDeclare("q", arg::durable=durableFlag); + c0.session.messageTransfer(arg::content=ttlMessage("a", "q", 200, durableFlag)); + c0.session.messageTransfer(arg::content=makeMessage("b", "q", durableFlag)); + c0.session.messageTransfer(arg::content=ttlMessage("x", "p", 100000, durableFlag)); + c0.session.messageTransfer(arg::content=makeMessage("y", "p", durableFlag)); + cluster.add(); + Client c2(cluster[1], "c2"); + + BOOST_CHECK_EQUAL(browse(c0, "p", 1), list_of<string>("x")); + BOOST_CHECK_EQUAL(browse(c1, "p", 1), list_of<string>("x")); + BOOST_CHECK_EQUAL(browse(c2, "p", 1), list_of<string>("x")); + + sys::usleep(200*1000); + BOOST_CHECK_EQUAL(browse(c0, "q", 1), list_of<string>("b")); + BOOST_CHECK_EQUAL(browse(c1, "q", 1), list_of<string>("b")); + BOOST_CHECK_EQUAL(browse(c2, "q", 1), list_of<string>("b")); +} + +QPID_AUTO_TEST_CASE(testSequenceOptions) { + // Make sure the exchange qpid.msg_sequence property is properly replicated. + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c0(cluster[0], "c0"); + FieldTable ftargs; + ftargs.setInt("qpid.msg_sequence", 1); + c0.session.queueDeclare(arg::queue="q", arg::durable=durableFlag); + c0.session.exchangeDeclare(arg::exchange="ex", arg::type="direct", arg::arguments=ftargs); + c0.session.exchangeBind(arg::exchange="ex", arg::queue="q", arg::bindingKey="k"); + c0.session.messageTransfer(arg::content=makeMessage("1", "k", durableFlag), arg::destination="ex"); + c0.session.messageTransfer(arg::content=makeMessage("2", "k", durableFlag), arg::destination="ex"); + BOOST_CHECK_EQUAL(1, getMsgSequence(c0.subs.get("q", TIMEOUT))); + BOOST_CHECK_EQUAL(2, getMsgSequence(c0.subs.get("q", TIMEOUT))); + + cluster.add(); + Client c1(cluster[1]); + c1.session.messageTransfer(arg::content=makeMessage("3", "k", durableFlag), arg::destination="ex"); + BOOST_CHECK_EQUAL(3, getMsgSequence(c1.subs.get("q", TIMEOUT))); +} + +QPID_AUTO_TEST_CASE(testTxTransaction) { + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c0(cluster[0], "c0"); + c0.session.queueDeclare(arg::queue="q", arg::durable=durableFlag); + c0.session.messageTransfer(arg::content=makeMessage("A", "q", durableFlag)); + c0.session.messageTransfer(arg::content=makeMessage("B", "q", durableFlag)); + + // Start a transaction that will commit. + Session commitSession = c0.connection.newSession("commit"); + SubscriptionManager commitSubs(commitSession); + commitSession.txSelect(); + commitSession.messageTransfer(arg::content=makeMessage("a", "q", durableFlag)); + commitSession.messageTransfer(arg::content=makeMessage("b", "q", durableFlag)); + BOOST_CHECK_EQUAL(commitSubs.get("q", TIMEOUT).getData(), "A"); + + // Start a transaction that will roll back. + Session rollbackSession = c0.connection.newSession("rollback"); + SubscriptionManager rollbackSubs(rollbackSession); + rollbackSession.txSelect(); + rollbackSession.messageTransfer(arg::content=makeMessage("1", "q", durableFlag)); + Message rollbackMessage = rollbackSubs.get("q", TIMEOUT); + BOOST_CHECK_EQUAL(rollbackMessage.getData(), "B"); + + BOOST_CHECK_EQUAL(c0.session.queueQuery("q").getMessageCount(), 0u); + // Add new member mid transaction. + cluster.add(); + Client c1(cluster[1], "c1"); + + // More transactional work + BOOST_CHECK_EQUAL(c1.session.queueQuery("q").getMessageCount(), 0u); + rollbackSession.messageTransfer(arg::content=makeMessage("2", "q", durableFlag)); + commitSession.messageTransfer(arg::content=makeMessage("c", "q", durableFlag)); + rollbackSession.messageTransfer(arg::content=makeMessage("3", "q", durableFlag)); + + BOOST_CHECK_EQUAL(c1.session.queueQuery("q").getMessageCount(), 0u); + + // Commit/roll back. + commitSession.txCommit(); + rollbackSession.txRollback(); + rollbackSession.messageRelease(rollbackMessage.getId()); + + // Verify queue status: just the comitted messages and dequeues should remain. + BOOST_CHECK_EQUAL(c1.session.queueQuery("q").getMessageCount(), 4u); + BOOST_CHECK_EQUAL(c1.subs.get("q", TIMEOUT).getData(), "B"); + BOOST_CHECK_EQUAL(c1.subs.get("q", TIMEOUT).getData(), "a"); + BOOST_CHECK_EQUAL(c1.subs.get("q", TIMEOUT).getData(), "b"); + BOOST_CHECK_EQUAL(c1.subs.get("q", TIMEOUT).getData(), "c"); + + commitSession.close(); + rollbackSession.close(); +} + +QPID_AUTO_TEST_CASE(testUnacked) { + // Verify replication of unacknowledged messages. + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c0(cluster[0], "c0"); + + Message m; + + // Create unacked message: acquired but not accepted. + SubscriptionSettings manualAccept(FlowControl::unlimited(), ACCEPT_MODE_EXPLICIT, ACQUIRE_MODE_PRE_ACQUIRED, 0); + c0.session.queueDeclare("q1", arg::durable=durableFlag); + c0.session.messageTransfer(arg::content=makeMessage("11","q1", durableFlag)); + LocalQueue q1; + c0.subs.subscribe(q1, "q1", manualAccept); + BOOST_CHECK_EQUAL(q1.get(TIMEOUT).getData(), "11"); // Acquired but not accepted + BOOST_CHECK_EQUAL(c0.session.queueQuery("q1").getMessageCount(), 0u); // Gone from queue + + // Create unacked message: not acquired, accepted or completeed. + SubscriptionSettings manualAcquire(FlowControl::unlimited(), ACCEPT_MODE_EXPLICIT, ACQUIRE_MODE_NOT_ACQUIRED, 0); + c0.session.queueDeclare("q2", arg::durable=durableFlag); + c0.session.messageTransfer(arg::content=makeMessage("21","q2", durableFlag)); + c0.session.messageTransfer(arg::content=makeMessage("22","q2", durableFlag)); + LocalQueue q2; + c0.subs.subscribe(q2, "q2", manualAcquire); + m = q2.get(TIMEOUT); // Not acquired or accepted, still on queue + BOOST_CHECK_EQUAL(m.getData(), "21"); + BOOST_CHECK_EQUAL(c0.session.queueQuery("q2").getMessageCount(), 2u); // Not removed + c0.subs.getSubscription("q2").acquire(m); // Acquire manually + BOOST_CHECK_EQUAL(c0.session.queueQuery("q2").getMessageCount(), 1u); // Removed + BOOST_CHECK_EQUAL(q2.get(TIMEOUT).getData(), "22"); // Not acquired or accepted, still on queue + BOOST_CHECK_EQUAL(c0.session.queueQuery("q2").getMessageCount(), 1u); // 1 not acquired. + + // Create empty credit record: acquire and accept but don't complete. + SubscriptionSettings manualComplete(FlowControl::messageWindow(1), ACCEPT_MODE_EXPLICIT, ACQUIRE_MODE_PRE_ACQUIRED, 1, MANUAL_COMPLETION); + c0.session.queueDeclare("q3", arg::durable=durableFlag); + c0.session.messageTransfer(arg::content=makeMessage("31", "q3", durableFlag)); + c0.session.messageTransfer(arg::content=makeMessage("32", "q3", durableFlag)); + LocalQueue q3; + c0.subs.subscribe(q3, "q3", manualComplete); + Message m31=q3.get(TIMEOUT); + BOOST_CHECK_EQUAL(m31.getData(), "31"); // Automatically acquired & accepted but not completed. + BOOST_CHECK_EQUAL(c0.session.queueQuery("q3").getMessageCount(), 1u); + + // Add new member while there are unacked messages. + cluster.add(); + Client c1(cluster[1], "c1"); + + // Check queue counts + BOOST_CHECK_EQUAL(c1.session.queueQuery("q1").getMessageCount(), 0u); + BOOST_CHECK_EQUAL(c1.session.queueQuery("q2").getMessageCount(), 1u); + BOOST_CHECK_EQUAL(c1.session.queueQuery("q3").getMessageCount(), 1u); + + // Complete the empty credit message, should unblock the message behind it. + BOOST_CHECK_THROW(q3.get(0), Exception); + c0.session.markCompleted(SequenceSet(m31.getId()), true); + BOOST_CHECK_EQUAL(q3.get(TIMEOUT).getData(), "32"); + BOOST_CHECK_EQUAL(c0.session.queueQuery("q3").getMessageCount(), 0u); + BOOST_CHECK_EQUAL(c1.session.queueQuery("q3").getMessageCount(), 0u); + + // Close the original session - unacked messages should be requeued. + c0.session.close(); + BOOST_CHECK_EQUAL(c1.session.queueQuery("q1").getMessageCount(), 1u); + BOOST_CHECK_EQUAL(c1.session.queueQuery("q2").getMessageCount(), 2u); + + BOOST_CHECK_EQUAL(c1.subs.get("q1", TIMEOUT).getData(), "11"); + BOOST_CHECK_EQUAL(c1.subs.get("q2", TIMEOUT).getData(), "21"); + BOOST_CHECK_EQUAL(c1.subs.get("q2", TIMEOUT).getData(), "22"); +} + +// FIXME aconway 2009-06-17: test for unimplemented feature, enable when implemented. +void testUpdateTxState() { + // Verify that we update transaction state correctly to new members. + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c0(cluster[0], "c0"); + + // Do work in a transaction. + c0.session.txSelect(); + c0.session.queueDeclare("q", arg::durable=durableFlag); + c0.session.messageTransfer(arg::content=makeMessage("1","q", durableFlag)); + c0.session.messageTransfer(arg::content=makeMessage("2","q", durableFlag)); + Message m; + BOOST_CHECK(c0.subs.get(m, "q", TIMEOUT)); + BOOST_CHECK_EQUAL(m.getData(), "1"); + + // New member, TX not comitted, c1 should see nothing. + cluster.add(); + Client c1(cluster[1], "c1"); + BOOST_CHECK_EQUAL(c1.session.queueQuery(arg::queue="q").getMessageCount(), 0u); + + // After commit c1 shoudl see results of tx. + c0.session.txCommit(); + BOOST_CHECK_EQUAL(c1.session.queueQuery(arg::queue="q").getMessageCount(), 1u); + BOOST_CHECK(c1.subs.get(m, "q", TIMEOUT)); + BOOST_CHECK_EQUAL(m.getData(), "2"); + + // Another transaction with both members active. + c0.session.messageTransfer(arg::content=makeMessage("3","q", durableFlag)); + BOOST_CHECK_EQUAL(c1.session.queueQuery(arg::queue="q").getMessageCount(), 0u); + c0.session.txCommit(); + BOOST_CHECK_EQUAL(c1.session.queueQuery(arg::queue="q").getMessageCount(), 1u); + BOOST_CHECK(c1.subs.get(m, "q", TIMEOUT)); + BOOST_CHECK_EQUAL(m.getData(), "3"); +} + +QPID_AUTO_TEST_CASE(testUpdateMessageBuilder) { + // Verify that we update a partially recieved message to a new member. + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c0(cluster[0], "c0"); + c0.session.queueDeclare("q", arg::durable=durableFlag); + Sender sender(ConnectionAccess::getImpl(c0.connection), c0.session.getChannel()); + + // Send first 2 frames of message. + MessageTransferBody transfer( + ProtocolVersion(), string(), // default exchange. + framing::message::ACCEPT_MODE_NONE, + framing::message::ACQUIRE_MODE_PRE_ACQUIRED); + sender.send(transfer, true, false, true, true); + AMQHeaderBody header; + header.get<DeliveryProperties>(true)->setRoutingKey("q"); + if (durableFlag) + header.get<DeliveryProperties>(true)->setDeliveryMode(DELIVERY_MODE_PERSISTENT); + else + header.get<DeliveryProperties>(true)->setDeliveryMode(DELIVERY_MODE_NON_PERSISTENT); + sender.send(header, false, false, true, true); + + // No reliable way to ensure the partial message has arrived + // before we start the new broker, so we sleep. + sys::usleep(2500); + cluster.add(); + + // Send final 2 frames of message. + sender.send(AMQContentBody("ab"), false, true, true, false); + sender.send(AMQContentBody("cd"), false, true, false, true); + + // Verify message is enqued correctly on second member. + Message m; + Client c1(cluster[1], "c1"); + BOOST_CHECK(c1.subs.get(m, "q", TIMEOUT)); + BOOST_CHECK_EQUAL(m.getData(), "abcd"); + BOOST_CHECK_EQUAL(2u, knownBrokerPorts(c1.connection, 2).size()); +} + +QPID_AUTO_TEST_CASE(testConnectionKnownHosts) { + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c0(cluster[0], "c0"); + set<int> kb0 = knownBrokerPorts(c0.connection, 1); + BOOST_CHECK_EQUAL(kb0.size(), 1u); + BOOST_CHECK_EQUAL(kb0, makeSet(cluster)); + + cluster.add(); + Client c1(cluster[1], "c1"); + set<int> kb1 = knownBrokerPorts(c1.connection, 2); + kb0 = knownBrokerPorts(c0.connection, 2); + BOOST_CHECK_EQUAL(kb1.size(), 2u); + BOOST_CHECK_EQUAL(kb1, makeSet(cluster)); + BOOST_CHECK_EQUAL(kb1,kb0); + + cluster.add(); + Client c2(cluster[2], "c2"); + set<int> kb2 = knownBrokerPorts(c2.connection, 3); + kb1 = knownBrokerPorts(c1.connection, 3); + kb0 = knownBrokerPorts(c0.connection, 3); + BOOST_CHECK_EQUAL(kb2.size(), 3u); + BOOST_CHECK_EQUAL(kb2, makeSet(cluster)); + BOOST_CHECK_EQUAL(kb2,kb0); + BOOST_CHECK_EQUAL(kb2,kb1); + + cluster.killWithSilencer(1,c1.connection,9); + kb0 = knownBrokerPorts(c0.connection, 2); + kb2 = knownBrokerPorts(c2.connection, 2); + BOOST_CHECK_EQUAL(kb0.size(), 2u); + BOOST_CHECK_EQUAL(kb0, kb2); +} + +QPID_AUTO_TEST_CASE(testUpdateConsumers) { + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + + Client c0(cluster[0], "c0"); + c0.session.queueDeclare("p", arg::durable=durableFlag); + c0.session.queueDeclare("q", arg::durable=durableFlag); + c0.subs.subscribe(c0.lq, "q", FlowControl::zero()); + LocalQueue lp; + c0.subs.subscribe(lp, "p", FlowControl::messageCredit(1)); + c0.session.sync(); + + // Start new members + cluster.add(); // Local + Client c1(cluster[1], "c1"); + cluster.add(); + Client c2(cluster[2], "c2"); + + // Transfer messages + c0.session.messageTransfer(arg::content=makeMessage("aaa", "q", durableFlag)); + + c0.session.messageTransfer(arg::content=makeMessage("bbb", "p", durableFlag)); + c0.session.messageTransfer(arg::content=makeMessage("ccc", "p", durableFlag)); + + // Activate the subscription, ensure message removed on all queues. + c0.subs.setFlowControl("q", FlowControl::unlimited()); + Message m; + BOOST_CHECK(c0.lq.get(m, TIMEOUT)); + BOOST_CHECK_EQUAL(m.getData(), "aaa"); + BOOST_CHECK_EQUAL(c0.session.queueQuery("q").getMessageCount(), 0u); + BOOST_CHECK_EQUAL(c1.session.queueQuery("q").getMessageCount(), 0u); + BOOST_CHECK_EQUAL(c2.session.queueQuery("q").getMessageCount(), 0u); + + // Check second subscription's flow control: gets first message, not second. + BOOST_CHECK(lp.get(m, TIMEOUT)); + BOOST_CHECK_EQUAL(m.getData(), "bbb"); + BOOST_CHECK_EQUAL(c0.session.queueQuery("p").getMessageCount(), 1u); + BOOST_CHECK_EQUAL(c1.session.queueQuery("p").getMessageCount(), 1u); + BOOST_CHECK_EQUAL(c2.session.queueQuery("p").getMessageCount(), 1u); + + BOOST_CHECK(c0.subs.get(m, "p", TIMEOUT)); + BOOST_CHECK_EQUAL(m.getData(), "ccc"); + + // Kill the subscribing member, ensure further messages are not removed. + cluster.killWithSilencer(0,c0.connection,9); + BOOST_REQUIRE_EQUAL(knownBrokerPorts(c1.connection, 2).size(), 2u); + for (int i = 0; i < 10; ++i) { + c1.session.messageTransfer(arg::content=makeMessage("xxx", "q", durableFlag)); + BOOST_REQUIRE(c1.subs.get(m, "q", TIMEOUT)); + BOOST_REQUIRE_EQUAL(m.getData(), "xxx"); + } +} + +// Test that message data and delivery properties are updated properly. +QPID_AUTO_TEST_CASE(testUpdateMessages) { + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c0(cluster[0], "c0"); + + // Create messages with different delivery properties + c0.session.queueDeclare("q", arg::durable=durableFlag); + c0.session.exchangeBind(arg::exchange="amq.fanout", arg::queue="q"); + c0.session.messageTransfer(arg::content=makeMessage("foo","q", durableFlag)); + c0.session.messageTransfer(arg::content=makeMessage("bar","q", durableFlag), + arg::destination="amq.fanout"); + + while (c0.session.queueQuery("q").getMessageCount() != 2) + sys::usleep(1000); // Wait for message to show up on broker 0. + + // Add a new broker, it will catch up. + cluster.add(); + + // Do some work post-add + c0.session.queueDeclare("p", arg::durable=durableFlag); + c0.session.messageTransfer(arg::content=makeMessage("pfoo","p", durableFlag)); + + // Do some work post-join + BOOST_REQUIRE_EQUAL(knownBrokerPorts(c0.connection, 2).size(), 2u); + c0.session.messageTransfer(arg::content=makeMessage("pbar","p", durableFlag)); + + // Verify new brokers have state. + Message m; + + Client c1(cluster[1], "c1"); + + BOOST_CHECK(c1.subs.get(m, "q", TIMEOUT)); + BOOST_CHECK_EQUAL(m.getData(), "foo"); + BOOST_CHECK(m.getDeliveryProperties().hasExchange()); + BOOST_CHECK_EQUAL(m.getDeliveryProperties().getExchange(), ""); + BOOST_CHECK(c1.subs.get(m, "q", TIMEOUT)); + BOOST_CHECK_EQUAL(m.getData(), "bar"); + BOOST_CHECK(m.getDeliveryProperties().hasExchange()); + BOOST_CHECK_EQUAL(m.getDeliveryProperties().getExchange(), "amq.fanout"); + BOOST_CHECK_EQUAL(c1.session.queueQuery("q").getMessageCount(), 0u); + + // Add another broker, don't wait for join - should be stalled till ready. + cluster.add(); + Client c2(cluster[2], "c2"); + BOOST_CHECK(c2.subs.get(m, "p", TIMEOUT)); + BOOST_CHECK_EQUAL(m.getData(), "pfoo"); + BOOST_CHECK(c2.subs.get(m, "p", TIMEOUT)); + BOOST_CHECK_EQUAL(m.getData(), "pbar"); + BOOST_CHECK_EQUAL(c2.session.queueQuery("p").getMessageCount(), 0u); +} + +QPID_AUTO_TEST_CASE(testWiringReplication) { + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(3, args, -1); + Client c0(cluster[0]); + BOOST_CHECK(c0.session.queueQuery("q").getQueue().empty()); + BOOST_CHECK(c0.session.exchangeQuery("ex").getType().empty()); + c0.session.queueDeclare("q", arg::durable=durableFlag); + c0.session.exchangeDeclare("ex", arg::type="direct"); + c0.session.close(); + c0.connection.close(); + // Verify all brokers get wiring update. + for (size_t i = 0; i < cluster.size(); ++i) { + BOOST_MESSAGE("i == "<< i); + Client c(cluster[i]); + BOOST_CHECK_EQUAL("q", c.session.queueQuery("q").getQueue()); + BOOST_CHECK_EQUAL("direct", c.session.exchangeQuery("ex").getType()); + } +} + +QPID_AUTO_TEST_CASE(testMessageEnqueue) { + // Enqueue on one broker, dequeue on another. + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(2, args, -1); + Client c0(cluster[0]); + c0.session.queueDeclare("q", arg::durable=durableFlag); + c0.session.messageTransfer(arg::content=makeMessage("foo", "q", durableFlag)); + c0.session.messageTransfer(arg::content=makeMessage("bar", "q", durableFlag)); + c0.session.close(); + Client c1(cluster[1]); + Message msg; + BOOST_CHECK(c1.subs.get(msg, "q", TIMEOUT)); + BOOST_CHECK_EQUAL(string("foo"), msg.getData()); + BOOST_CHECK(c1.subs.get(msg, "q", TIMEOUT)); + BOOST_CHECK_EQUAL(string("bar"), msg.getData()); +} + +QPID_AUTO_TEST_CASE(testMessageDequeue) { + // Enqueue on one broker, dequeue on two others. + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(3, args, -1); + Client c0(cluster[0], "c0"); + c0.session.queueDeclare("q", arg::durable=durableFlag); + c0.session.messageTransfer(arg::content=makeMessage("foo", "q", durableFlag)); + c0.session.messageTransfer(arg::content=makeMessage("bar", "q", durableFlag)); + + Message msg; + + // Dequeue on 2 others, ensure correct order. + Client c1(cluster[1], "c1"); + BOOST_CHECK(c1.subs.get(msg, "q")); + BOOST_CHECK_EQUAL("foo", msg.getData()); + + Client c2(cluster[2], "c2"); + BOOST_CHECK(c1.subs.get(msg, "q")); + BOOST_CHECK_EQUAL("bar", msg.getData()); + + // Queue should be empty on all cluster members. + BOOST_CHECK_EQUAL(0u, c0.session.queueQuery("q").getMessageCount()); + BOOST_CHECK_EQUAL(0u, c1.session.queueQuery("q").getMessageCount()); + BOOST_CHECK_EQUAL(0u, c2.session.queueQuery("q").getMessageCount()); +} + +QPID_AUTO_TEST_CASE(testDequeueWaitingSubscription) { + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(3, args, -1); + Client c0(cluster[0]); + BOOST_REQUIRE_EQUAL(knownBrokerPorts(c0.connection, 3).size(), 3u); // Wait for brokers. + + // First start a subscription. + c0.session.queueDeclare("q", arg::durable=durableFlag); + c0.subs.subscribe(c0.lq, "q", FlowControl::messageCredit(2)); + + // Now send messages + Client c1(cluster[1]); + c1.session.messageTransfer(arg::content=makeMessage("foo", "q", durableFlag)); + c1.session.messageTransfer(arg::content=makeMessage("bar", "q", durableFlag)); + + // Check they arrived + Message m; + BOOST_CHECK(c0.lq.get(m, TIMEOUT)); + BOOST_CHECK_EQUAL("foo", m.getData()); + BOOST_CHECK(c0.lq.get(m, TIMEOUT)); + BOOST_CHECK_EQUAL("bar", m.getData()); + + // Queue should be empty on all cluster members. + Client c2(cluster[2]); + BOOST_CHECK_EQUAL(0u, c0.session.queueQuery("q").getMessageCount()); + BOOST_CHECK_EQUAL(0u, c1.session.queueQuery("q").getMessageCount()); + BOOST_CHECK_EQUAL(0u, c2.session.queueQuery("q").getMessageCount()); +} + +QPID_AUTO_TEST_CASE(queueDurabilityPropagationToNewbie) +{ + /* + Start with a single broker. + Set up two queues: one durable, and one not. + Add a new broker to the cluster. + Make sure it has one durable and one non-durable queue. + */ + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c0(cluster[0]); + c0.session.queueDeclare("durable_queue", arg::durable=true); + c0.session.queueDeclare("non_durable_queue", arg::durable=false); + cluster.add(); + Client c1(cluster[1]); + QueueQueryResult durable_query = c1.session.queueQuery ( "durable_queue" ); + QueueQueryResult non_durable_query = c1.session.queueQuery ( "non_durable_queue" ); + BOOST_CHECK_EQUAL(durable_query.getQueue(), std::string("durable_queue")); + BOOST_CHECK_EQUAL(non_durable_query.getQueue(), std::string("non_durable_queue")); + + BOOST_CHECK_EQUAL ( durable_query.getDurable(), true ); + BOOST_CHECK_EQUAL ( non_durable_query.getDurable(), false ); +} + + +QPID_AUTO_TEST_CASE(testHeartbeatCancelledOnFailover) +{ + + struct Sender : FailoverManager::Command + { + std::string queue; + std::string content; + + Sender(const std::string& q, const std::string& c) : queue(q), content(c) {} + + void execute(AsyncSession& session, bool) + { + session.messageTransfer(arg::content=makeMessage(content, queue, durableFlag)); + } + }; + + struct Receiver : FailoverManager::Command, MessageListener, qpid::sys::Runnable + { + FailoverManager& mgr; + std::string queue; + std::string expectedContent; + qpid::client::Subscription subscription; + qpid::sys::Monitor lock; + bool ready, failed; + + Receiver(FailoverManager& m, const std::string& q, const std::string& c) : mgr(m), queue(q), expectedContent(c), ready(false), failed(false) {} + + void received(Message& message) + { + BOOST_CHECK_EQUAL(expectedContent, message.getData()); + subscription.cancel(); + } + + void execute(AsyncSession& session, bool) + { + session.queueDeclare(arg::queue=queue, arg::durable=durableFlag); + SubscriptionManager subs(session); + subscription = subs.subscribe(*this, queue); + session.sync(); + setReady(); + subs.run(); + //cleanup: + session.queueDelete(arg::queue=queue); + } + + void run() + { + try { + mgr.execute(*this); + } + catch (const std::exception& e) { + BOOST_MESSAGE("Exception in mgr.execute: " << e.what()); + failed = true; + } + } + + void waitForReady() + { + qpid::sys::Monitor::ScopedLock l(lock); + while (!ready) { + lock.wait(); + } + } + + void setReady() + { + qpid::sys::Monitor::ScopedLock l(lock); + ready = true; + lock.notify(); + } + }; + + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(2, args, -1); + ConnectionSettings settings; + settings.port = cluster[1]; + settings.heartbeat = 1; + FailoverManager fmgr(settings); + Sender sender("my-queue", "my-data"); + Receiver receiver(fmgr, "my-queue", "my-data"); + qpid::sys::Thread runner(receiver); + receiver.waitForReady(); + { + ScopedSuppressLogging allQuiet; // suppress connection closed messages + cluster.kill(1); + //sleep for 2 secs to allow the heartbeat task to fire on the now dead connection: + ::usleep(2*1000*1000); + } + fmgr.execute(sender); + runner.join(); + BOOST_CHECK(!receiver.failed); + fmgr.close(); +} + +QPID_AUTO_TEST_CASE(testPolicyUpdate) { + //tests that the policys internal state is accurate on newly + //joined nodes + ClusterFixture::Args args; + args += "--log-enable", "critical"; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c1(cluster[0], "c1"); + { + ScopedSuppressLogging allQuiet; + QueueOptions options; + options.setSizePolicy(REJECT, 0, 2); + c1.session.queueDeclare("q", arg::arguments=options, arg::durable=durableFlag); + c1.session.messageTransfer(arg::content=makeMessage("one", "q", durableFlag)); + cluster.add(); + Client c2(cluster[1], "c2"); + c2.session.messageTransfer(arg::content=makeMessage("two", "q", durableFlag)); + + BOOST_CHECK_THROW(c2.session.messageTransfer(arg::content=makeMessage("three", "q", durableFlag)), framing::ResourceLimitExceededException); + + Message received; + BOOST_CHECK(c1.subs.get(received, "q")); + BOOST_CHECK_EQUAL(received.getData(), std::string("one")); + BOOST_CHECK(c1.subs.get(received, "q")); + BOOST_CHECK_EQUAL(received.getData(), std::string("two")); + BOOST_CHECK(!c1.subs.get(received, "q")); + } +} + +QPID_AUTO_TEST_CASE(testExclusiveQueueUpdate) { + //tests that exclusive queues are accurately replicated on newly + //joined nodes + ClusterFixture::Args args; + args += "--log-enable", "critical"; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c1(cluster[0], "c1"); + { + ScopedSuppressLogging allQuiet; + c1.session.queueDeclare("q", arg::exclusive=true, arg::autoDelete=true, arg::alternateExchange="amq.fanout"); + cluster.add(); + Client c2(cluster[1], "c2"); + QueueQueryResult result = c2.session.queueQuery("q"); + BOOST_CHECK_EQUAL(result.getQueue(), std::string("q")); + BOOST_CHECK(result.getExclusive()); + BOOST_CHECK(result.getAutoDelete()); + BOOST_CHECK(!result.getDurable()); + BOOST_CHECK_EQUAL(result.getAlternateExchange(), std::string("amq.fanout")); + BOOST_CHECK_THROW(c2.session.queueDeclare(arg::queue="q", arg::exclusive=true, arg::passive=true), framing::ResourceLockedException); + c1.session.close(); + c1.connection.close(); + c2.session = c2.connection.newSession(); + BOOST_CHECK_THROW(c2.session.queueDeclare(arg::queue="q", arg::passive=true), framing::NotFoundException); + } +} + +/** + * Subscribes to specified queue and acquires up to the specified + * number of message but does not accept or release them. These + * message are therefore 'locked' by the clients session. + */ +Subscription lockMessages(Client& client, const std::string& queue, int count) +{ + LocalQueue q; + SubscriptionSettings settings(FlowControl::messageCredit(count)); + settings.autoAck = 0; + Subscription sub = client.subs.subscribe(q, queue, settings); + client.session.messageFlush(sub.getName()); + return sub; +} + +/** + * check that the specified queue contains the expected set of + * messages (matched on content) for all nodes in the cluster + */ +void checkQueue(ClusterFixture& cluster, const std::string& queue, const std::vector<std::string>& messages) +{ + for (size_t i = 0; i < cluster.size(); i++) { + Client client(cluster[i], (boost::format("%1%_%2%") % "c" % (i+1)).str()); + BOOST_CHECK_EQUAL(browse(client, queue, messages.size()), messages); + client.close(); + } +} + +void send(Client& client, const std::string& queue, int count, int start=1, const std::string& base="m", + const std::string& lvqKey="") +{ + for (int i = 0; i < count; i++) { + Message message = makeMessage((boost::format("%1%_%2%") % base % (i+start)).str(), queue, durableFlag); + if (!lvqKey.empty()) message.getHeaders().setString(QueueOptions::strLVQMatchProperty, lvqKey); + client.session.messageTransfer(arg::content=message); + } +} + +QPID_AUTO_TEST_CASE(testRingQueueUpdate) { + //tests that ring queues are accurately replicated on newly + //joined nodes + ClusterFixture::Args args; + args += "--log-enable", "critical"; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c1(cluster[0], "c1"); + { + ScopedSuppressLogging allQuiet; + QueueOptions options; + options.setSizePolicy(RING, 0, 5); + c1.session.queueDeclare("q", arg::arguments=options, arg::durable=durableFlag); + send(c1, "q", 5); + lockMessages(c1, "q", 1); + //add new node + cluster.add(); + BOOST_CHECK_EQUAL(2u, knownBrokerPorts(c1.connection, 2).size());//wait till joined + //send one more message + send(c1, "q", 1, 6); + //release locked message + c1.close(); + //check state of queue on both nodes + checkQueue(cluster, "q", list_of<string>("m_2")("m_3")("m_4")("m_5")("m_6")); + } +} + +QPID_AUTO_TEST_CASE(testRingQueueUpdate2) { + //tests that ring queues are accurately replicated on newly joined + //nodes; just like testRingQueueUpdate, but new node joins after + //the sixth message has been sent. + ClusterFixture::Args args; + args += "--log-enable", "critical"; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c1(cluster[0], "c1"); + { + ScopedSuppressLogging allQuiet; + QueueOptions options; + options.setSizePolicy(RING, 0, 5); + c1.session.queueDeclare("q", arg::arguments=options, arg::durable=durableFlag); + send(c1, "q", 5); + lockMessages(c1, "q", 1); + //send sixth message + send(c1, "q", 1, 6); + //add new node + cluster.add(); + BOOST_CHECK_EQUAL(2u, knownBrokerPorts(c1.connection, 2).size());//wait till joined + //release locked message + c1.close(); + //check state of queue on both nodes + checkQueue(cluster, "q", list_of<string>("m_2")("m_3")("m_4")("m_5")("m_6")); + } +} + +QPID_AUTO_TEST_CASE(testLvqUpdate) { + //tests that lvqs are accurately replicated on newly joined nodes + ClusterFixture::Args args; + args += "--log-enable", "critical"; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c1(cluster[0], "c1"); + { + ScopedSuppressLogging allQuiet; + QueueOptions options; + options.setOrdering(LVQ); + c1.session.queueDeclare("q", arg::arguments=options, arg::durable=durableFlag); + + send(c1, "q", 5, 1, "a", "a"); + send(c1, "q", 2, 1, "b", "b"); + send(c1, "q", 1, 1, "c", "c"); + send(c1, "q", 1, 3, "b", "b"); + + //add new node + cluster.add(); + BOOST_CHECK_EQUAL(2u, knownBrokerPorts(c1.connection, 2).size());//wait till joined + + //check state of queue on both nodes + checkQueue(cluster, "q", list_of<string>("a_5")("b_3")("c_1")); + } +} + + +QPID_AUTO_TEST_CASE(testBrowsedLvqUpdate) { + //tests that lvqs are accurately replicated on newly joined nodes + //if the lvq state has been affected by browsers + ClusterFixture::Args args; + args += "--log-enable", "critical"; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c1(cluster[0], "c1"); + { + ScopedSuppressLogging allQuiet; + QueueOptions options; + options.setOrdering(LVQ); + c1.session.queueDeclare("q", arg::arguments=options, arg::durable=durableFlag); + + send(c1, "q", 1, 1, "a", "a"); + send(c1, "q", 2, 1, "b", "b"); + send(c1, "q", 1, 1, "c", "c"); + checkQueue(cluster, "q", list_of<string>("a_1")("b_2")("c_1")); + send(c1, "q", 4, 2, "a", "a"); + send(c1, "q", 1, 3, "b", "b"); + + //add new node + cluster.add(); + BOOST_CHECK_EQUAL(2u, knownBrokerPorts(c1.connection, 2).size());//wait till joined + + //check state of queue on both nodes + checkQueue(cluster, "q", list_of<string>("a_1")("b_2")("c_1")("a_5")("b_3")); + } +} + +QPID_AUTO_TEST_CASE(testRelease) { + //tests that releasing a messages that was unacked when one node + //joined works correctly + ClusterFixture::Args args; + args += "--log-enable", "critical"; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c1(cluster[0], "c1"); + { + ScopedSuppressLogging allQuiet; + c1.session.queueDeclare("q", arg::durable=durableFlag); + for (int i = 0; i < 5; i++) { + c1.session.messageTransfer(arg::content=makeMessage((boost::format("%1%_%2%") % "m" % (i+1)).str(), "q", durableFlag)); + } + //receive but don't ack a message + LocalQueue lq; + SubscriptionSettings lqSettings(FlowControl::messageCredit(1)); + lqSettings.autoAck = 0; + Subscription lqSub = c1.subs.subscribe(lq, "q", lqSettings); + c1.session.messageFlush("q"); + Message received; + BOOST_CHECK(lq.get(received)); + BOOST_CHECK_EQUAL(received.getData(), std::string("m_1")); + + //add new node + cluster.add(); + + lqSub.release(lqSub.getUnaccepted()); + + //check state of queue on both nodes + vector<string> expected = list_of<string>("m_1")("m_2")("m_3")("m_4")("m_5"); + Client c3(cluster[0], "c3"); + BOOST_CHECK_EQUAL(browse(c3, "q", 5), expected); + Client c2(cluster[1], "c2"); + BOOST_CHECK_EQUAL(browse(c2, "q", 5), expected); + } +} + + +// Browse for 1 message with byte credit, return true if a message was +// received false if not. +bool browseByteCredit(Client& c, const string& q, int n, Message& m) { + SubscriptionSettings browseSettings( + FlowControl(1, n, false), // 1 message, n bytes credit, no window + ACCEPT_MODE_NONE, + ACQUIRE_MODE_NOT_ACQUIRED, + 0 // No auto-ack. + ); + LocalQueue lq; + Subscription s = c.subs.subscribe(lq, q, browseSettings); + c.session.messageFlush(arg::destination=q, arg::sync=true); + c.session.sync(); + c.subs.getSubscription(q).cancel(); + return lq.get(m, 0); // No timeout, flush should push message thru. +} + +// Ensure cluster update preserves exact message size, use byte credt as test. +QPID_AUTO_TEST_CASE(testExactByteCredit) { + ClusterFixture cluster(1, prepareArgs(), -1); + Client c0(cluster[0], "c0"); + c0.session.queueDeclare("q"); + c0.session.messageTransfer(arg::content=Message("MyMessage", "q")); + cluster.add(); + + int size=36; // Size of message on broker: headers+body + Client c1(cluster[1], "c1"); + Message m; + + // Ensure we get the message with exact credit. + BOOST_CHECK(browseByteCredit(c0, "q", size, m)); + BOOST_CHECK(browseByteCredit(c1, "q", size, m)); + // and not with one byte less. + BOOST_CHECK(!browseByteCredit(c0, "q", size-1, m)); + BOOST_CHECK(!browseByteCredit(c1, "q", size-1, m)); +} + +// Test that consumer positions are updated correctly. +// Regression test for https://bugzilla.redhat.com/show_bug.cgi?id=541927 +// +QPID_AUTO_TEST_CASE(testUpdateConsumerPosition) { + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c0(cluster[0], "c0"); + + c0.session.queueDeclare("q", arg::durable=durableFlag); + SubscriptionSettings settings; + settings.autoAck = 0; + // Set the acquire mode to 'not-acquired' the consumer moves along the queue + // but does not acquire (remove) messages. + settings.acquireMode = ACQUIRE_MODE_NOT_ACQUIRED; + Subscription s = c0.subs.subscribe(c0.lq, "q", settings); + c0.session.messageTransfer(arg::content=makeMessage("1", "q", durableFlag)); + BOOST_CHECK_EQUAL("1", c0.lq.get(TIMEOUT).getData()); + + // Add another member, send/receive another message and acquire + // the messages. With the bug, this creates an inconsistency + // because the browse position was not updated to the new member. + cluster.add(); + c0.session.messageTransfer(arg::content=makeMessage("2", "q", durableFlag)); + BOOST_CHECK_EQUAL("2", c0.lq.get(TIMEOUT).getData()); + s.acquire(s.getUnacquired()); + s.accept(s.getUnaccepted()); + + // In the bug we now have 0 messages on cluster[0] and 1 message on cluster[1] + // Subscribing on cluster[1] provokes an error that shuts down cluster[0] + Client c1(cluster[1], "c1"); + Subscription s1 = c1.subs.subscribe(c1.lq, "q"); // Default auto-ack=1 + Message m; + BOOST_CHECK(!c1.lq.get(m, TIMEOUT/10)); + BOOST_CHECK_EQUAL(c1.session.queueQuery("q").getMessageCount(), 0u); + BOOST_CHECK_EQUAL(c0.session.queueQuery("q").getMessageCount(), 0u); +} + +QPID_AUTO_TEST_CASE(testFairsharePriorityDelivery) { + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c0(cluster[0], "c0"); + + FieldTable arguments; + arguments.setInt("x-qpid-priorities", 10); + arguments.setInt("x-qpid-fairshare", 5); + c0.session.queueDeclare("q", arg::durable=durableFlag, arg::arguments=arguments); + + //send messages of different priorities + for (int i = 0; i < 20; i++) { + Message msg = makeMessage((boost::format("msg-%1%") % i).str(), "q", durableFlag); + msg.getDeliveryProperties().setPriority(i % 2 ? 9 : 5); + c0.session.messageTransfer(arg::content=msg); + } + + //pull off a couple of the messages (first four should be the top priority messages + for (int i = 0; i < 4; i++) { + BOOST_CHECK_EQUAL((boost::format("msg-%1%") % ((i*2)+1)).str(), c0.subs.get("q", TIMEOUT).getData()); + } + + // Add another member + cluster.add(); + Client c1(cluster[1], "c1"); + + //pull off some more messages + BOOST_CHECK_EQUAL((boost::format("msg-%1%") % 9).str(), c0.subs.get("q", TIMEOUT).getData()); + BOOST_CHECK_EQUAL((boost::format("msg-%1%") % 0).str(), c1.subs.get("q", TIMEOUT).getData()); + BOOST_CHECK_EQUAL((boost::format("msg-%1%") % 2).str(), c0.subs.get("q", TIMEOUT).getData()); + + //check queue has same content on both nodes + BOOST_CHECK_EQUAL(browse(c0, "q", 12), browse(c1, "q", 12)); +} + +QPID_AUTO_TEST_SUITE_END() +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/cluster_test_logs.py b/qpid/cpp/src/tests/cluster_test_logs.py new file mode 100755 index 0000000000..9f7d1e2f6c --- /dev/null +++ b/qpid/cpp/src/tests/cluster_test_logs.py @@ -0,0 +1,119 @@ +#!/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. +# + +# Functions for comparing broker log files, used by cluster_tests.py. + +import os, os.path, re, glob +from itertools import izip + +def split_log(log): + """Split a broker log at checkpoints where a member joins. + Return the set of checkpoints discovered.""" + checkpoint_re = re.compile("Member joined, frameSeq=([0-9]+), queue snapshot:") + outfile = None + checkpoints = [] + for l in open(log): + match = checkpoint_re.search(l) + if match: + checkpoint = match.groups()[0] + checkpoints.append(checkpoint) + if outfile: outfile.close() + outfile = open("%s.%s"%(log, checkpoint), 'w') + + if outfile: outfile.write(l) + if outfile: outfile.close() + return checkpoints + +def filter_log(log): + """Filter the contents of a log file to remove data that is expected + to differ between brokers in a cluster. Filtered log contents between + the same checkpoints should match across the cluster.""" + out = open("%s.filter"%(log), 'w') + # Lines to skip entirely, expected differences + skip = "|".join([ + 'local connection', # Only on local broker + 'UPDATER|UPDATEE', # Ignore update process + 'stall for update|unstall, ignore update|cancelled offer .* unstall', + 'caught up', + 'active for links|Passivating links|Activating links', + 'info Connection.* connected to', # UpdateClient connection + 'warning Connection [\d+ [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', + '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 + ]) + # 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 + subs = [ + (r'\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d ', ''), # Remove timestamp + (r'cluster\([0-9.: ]*', 'cluster('), # Remove cluster node id + (r' local\)| shadow\)', ')'), # Remove local/shadow indication + (r'CATCHUP', 'READY'), # Treat catchup as equivalent to ready. + (r'OFFER', 'READY'), # Treat offer as equivalent to ready. + # System UUID expected to be different + (r'(org.apache.qpid.broker:system[:(])%s(\)?)'%(uuid), r'\1UUID\2'), + + # TODO aconway 2010-12-20: review if these should be expected: + (r' len=\d+', ' len=NN'), # buffer lengths + (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) + out.write(l) + out.close() + +def verify_logs(): + """Compare log files from cluster brokers, verify that they correspond correctly.""" + for l in glob.glob("*.log"): filter_log(l) + checkpoints = set() + for l in glob.glob("*.filter"): checkpoints = checkpoints.union(set(split_log(l))) + errors=[] + for c in checkpoints: + fragments = glob.glob("*.filter.%s"%(c)) + fragments.sort(reverse=True, key=os.path.getsize) + while len(fragments) >= 2: + a = fragments.pop(0) + b = fragments[0] + for ab in izip(open(a), open(b)): + if ab[0] != ab[1]: + errors.append("\n %s %s"%(a, b)) + break + if errors: + raise Exception("Files differ in %s"%(os.getcwd())+"".join(errors)) + +# Can be run as a script. +if __name__ == "__main__": + verify_logs() diff --git a/qpid/cpp/src/tests/cluster_test_scripts/README.txt b/qpid/cpp/src/tests/cluster_test_scripts/README.txt new file mode 100644 index 0000000000..e861a2f397 --- /dev/null +++ b/qpid/cpp/src/tests/cluster_test_scripts/README.txt @@ -0,0 +1,20 @@ +Cluster test scripts. + +A set of scripts to start and stop cluster and test clients on +multiple hosts using ssh. + +Pre-requisites: You must be + - set up for password-free ssh access to the test hosts. + - a member of the ais group on all the test hosts. + +Configuration: + +Copy defaults.sh to config.sh and edit the values as necessary. + +Test scripts: + +Test scripts use the functions in functions.sh to start & monitor +cluster and clients. +A test script can collect other scripts. + + diff --git a/qpid/cpp/src/tests/cluster_test_scripts/cluster_check b/qpid/cpp/src/tests/cluster_test_scripts/cluster_check new file mode 100755 index 0000000000..05fcc1bcd2 --- /dev/null +++ b/qpid/cpp/src/tests/cluster_test_scripts/cluster_check @@ -0,0 +1,37 @@ +#!/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. +# + +# Check that all members of a cluster are running + +source config.sh + +HOSTS=(`cat $CLUSTER_HOME/hosts`) +PORTS=(`cat $CLUSTER_HOME/ports`) + +for ((i=0; i<${#HOSTS[*]}; ++i)); do + host=${HOSTS[$i]} + port=${PORTS[$i]} + ssh $host "$QPIDD -cp $port" > /dev/null || { + ret=1 + echo "ERROR: broker not running $host:$port" + } +done +exit $ret diff --git a/qpid/cpp/src/tests/cluster_test_scripts/cluster_start b/qpid/cpp/src/tests/cluster_test_scripts/cluster_start new file mode 100755 index 0000000000..8911358f7e --- /dev/null +++ b/qpid/cpp/src/tests/cluster_test_scripts/cluster_start @@ -0,0 +1,56 @@ +#!/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. +# + +# Start a cluster +# +# Arguments: NAME HOST [host...] +# Start a cluster called NAME with N nodes running on the given HOSTs +# repeat the host name to run multiple brokers on one host. Use dynamic +# ports. +# +# Log files, data directories and hosts/ports files are all stored under +# $HOME/cluster_test/$NAME +# + +source config.sh + +CLUSTER_NAME=`date +"${USER}_%F_%T"` +HOSTS=($BROKER_HOSTS) +for ((i = 0; i < ${#HOSTS[*]}; ++i)) ; do + host=${HOSTS[$i]} + datadir=$CLUSTER_HOME/broker$i + log=$datadir/qpidd.log + ssh $host "rm -rf $datadir; mkdir -p $datadir" || { + echo "ERROR: can't make data dir $datadir"; exit 1 + } + port=`ssh $host "echo $QPIDD -dp0 --cluster-name=$CLUSTER_NAME \ + --data-dir=$datadir \ + --log-to-file=$log --log-prefix=broker$i \ + $QPIDD_OPTS | newgrp ais"` || { + error "ERROR: can't start broker $i on $host"; exit 1; + } + PORTS="$PORTS $port" +done + +echo "$BROKER_HOSTS" > $CLUSTER_HOME/hosts +echo "$PORTS" > $CLUSTER_HOME/ports + +`dirname $0`/cluster_check $NAME diff --git a/qpid/cpp/src/tests/cluster_test_scripts/cluster_stop b/qpid/cpp/src/tests/cluster_test_scripts/cluster_stop new file mode 100755 index 0000000000..09aa8f3b21 --- /dev/null +++ b/qpid/cpp/src/tests/cluster_test_scripts/cluster_stop @@ -0,0 +1,38 @@ +#!/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. +# + +# Stop the cluster. + +source config.sh + +HOSTS=(`cat $CLUSTER_HOME/hosts`) +PORTS=(`cat $CLUSTER_HOME/ports`) + +for ((i=0; i<${#HOSTS[*]}; ++i)); do + host=${HOSTS[$i]} + port=${PORTS[$i]} + ssh $host "$QPIDD -qp $port" > /dev/null || { + ret=1 + echo "ERROR: stopping broker at $host:$port" + } +done + +exit $ret diff --git a/qpid/cpp/src/tests/cluster_test_scripts/config_example.sh b/qpid/cpp/src/tests/cluster_test_scripts/config_example.sh new file mode 100755 index 0000000000..d47c9a9c77 --- /dev/null +++ b/qpid/cpp/src/tests/cluster_test_scripts/config_example.sh @@ -0,0 +1,44 @@ +# Cluster configuration. + +# +# 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. +# + +# All output stored under $HOME/$CLUSTER_HOME. +CLUSTER_HOME=$HOME/cluster_test + +# Hosts where brokers will be run. Repeat hostname to run multiple brokers on 1 host. +BROKER_HOSTS="mrg22 mrg23 mrg24 mrg25 mrg26" + +# Hosts where clients will be run. +CLIENT_HOSTS="$BROKER_HOSTS" + +# Paths to executables +QPIDD=qpidd +PERFTEST=perftest + +# Directory containing tests +TESTDIR=/usr/bin + +# Options for qpidd, must be sufficient to load the cluster plugin. +# Scripts will add --cluster-name, --daemon, --port and --log-to-file options here. +QPIDD_OPTS=" \ +--auth=no \ +--log-enable=notice+ \ +--log-enable=debug+:cluster \ +" diff --git a/qpid/cpp/src/tests/cluster_test_scripts/perftest b/qpid/cpp/src/tests/cluster_test_scripts/perftest new file mode 100755 index 0000000000..984761eb5f --- /dev/null +++ b/qpid/cpp/src/tests/cluster_test_scripts/perftest @@ -0,0 +1,54 @@ +#!/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 a distributed perftest against a cluster. +# Args: npubs nsubs [perftest-options] + +source config.sh + +NPUBS=${1:-4} ; shift +NSUBS=${1:-4} ; shift +OPTS="--npubs $NPUBS --nsubs $NSUBS $*" + +CLIENTS=($CLIENT_HOSTS) +BROKERS=(`cat $CLUSTER_HOME/hosts`) +PORTS=(`cat $CLUSTER_HOME/ports`) + +start() { + client=${CLIENTS[i % ${#CLIENTS[*]}]} + broker=${BROKERS[i % ${#BROKERS[*]}]} + port=${PORTS[i % ${#PORTS[*]}]} + ssh -n $client $PERFTEST $OPTS $* -b $broker -p $port & + PIDS="$PIDS $!" +} + +ssh ${CLIENTS[0]} $PERFTEST $OPTS --setup -b ${BROKERS[0]} -p${PORTS[0]} +for (( i=0 ; i < $NPUBS ; ++i)); do start --publish; done +for (( ; i < $NPUBS+$NSUBS ; ++i)); do start --subscribe; done +ssh ${CLIENTS[0]} $PERFTEST $OPTS --control -b ${BROKERS[0]} -p${PORTS[0]} + +for pid in $PIDS; do + wait $pid || echo "ERROR: client process $pid failed" +done + +`dirname $0`/cluster_check + + diff --git a/qpid/cpp/src/tests/cluster_tests.fail b/qpid/cpp/src/tests/cluster_tests.fail new file mode 100644 index 0000000000..b28b04f643 --- /dev/null +++ b/qpid/cpp/src/tests/cluster_tests.fail @@ -0,0 +1,3 @@ + + + diff --git a/qpid/cpp/src/tests/cluster_tests.py b/qpid/cpp/src/tests/cluster_tests.py new file mode 100755 index 0000000000..593791297a --- /dev/null +++ b/qpid/cpp/src/tests/cluster_tests.py @@ -0,0 +1,1057 @@ +#!/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 os, signal, sys, time, imp, re, subprocess, glob, cluster_test_logs +from qpid import datatypes, messaging +from brokertest import * +from qpid.harness import Skipped +from qpid.messaging import Message, Empty, Disposition, REJECTED +from threading import Thread, Lock, Condition +from logging import getLogger +from itertools import chain +from tempfile import NamedTemporaryFile + +log = getLogger("qpid.cluster_tests") + +# Note: brokers that shut themselves down due to critical error during +# normal operation will still have an exit code of 0. Brokers that +# shut down because of an error found during initialize will exit with +# a non-0 code. Hence the apparently inconsistent use of EXPECT_EXIT_OK +# and EXPECT_EXIT_FAIL in some of the tests below. + +# TODO aconway 2010-03-11: resolve this - ideally any exit due to an error +# should give non-0 exit status. + +# Import scripts as modules +qpid_cluster=import_script(checkenv("QPID_CLUSTER_EXEC")) + +def readfile(filename): + """Returns te content of file named filename as a string""" + f = file(filename) + try: return f.read() + finally: f.close() + +class ShortTests(BrokerTest): + """Short cluster functionality tests.""" + + def test_message_replication(self): + """Test basic cluster message replication.""" + # Start a cluster, send some messages to member 0. + cluster = self.cluster(2) + s0 = cluster[0].connect().session() + s0.sender("q; {create:always}").send(Message("x")) + s0.sender("q; {create:always}").send(Message("y")) + s0.connection.close() + + # Verify messages available on member 1. + s1 = cluster[1].connect().session() + m = s1.receiver("q", capacity=1).fetch(timeout=1) + s1.acknowledge() + self.assertEqual("x", m.content) + s1.connection.close() + + # Start member 2 and verify messages available. + s2 = cluster.start().connect().session() + m = s2.receiver("q", capacity=1).fetch(timeout=1) + s2.acknowledge() + self.assertEqual("y", m.content) + s2.connection.close() + + def test_store_direct_update_match(self): + """Verify that brokers stores an identical message whether they receive it + direct from clients or during an update, no header or other differences""" + cluster = self.cluster(0, args=["--load-module", self.test_store_lib]) + cluster.start(args=["--test-store-dump", "direct.dump"]) + # Try messages with various headers + cluster[0].send_message("q", Message(durable=True, content="foobar", + subject="subject", + reply_to="reply_to", + properties={"n":10})) + # Try messages of different sizes + for size in range(0,10000,100): + cluster[0].send_message("q", Message(content="x"*size, durable=True)) + # Try sending via named exchange + c = cluster[0].connect_old() + s = c.session(str(qpid.datatypes.uuid4())) + s.exchange_bind(exchange="amq.direct", binding_key="foo", queue="q") + props = s.delivery_properties(routing_key="foo", delivery_mode=2) + s.message_transfer( + destination="amq.direct", + message=qpid.datatypes.Message(props, "content")) + + # 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") + + def test_sasl(self): + """Test SASL authentication and encryption in a cluster""" + sasl_config=os.path.join(self.rootdir, "sasl_config") + acl=os.path.join(os.getcwd(), "policy.acl") + aclf=file(acl,"w") + aclf.write(""" +acl deny zag@QPID create queue +acl allow all all +""") + aclf.close() + cluster = self.cluster(2, 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.close() + c = cluster[1].connect(username="zig", password="zig") + c.session().receiver("ziggy;{assert:always}") + c.close() + for b in cluster: b.ready() # Make sure all brokers still running. + + # Valid user, bad password + try: + cluster[0].connect(username="zig", password="foo").close() + self.fail("Expected exception") + except messaging.exceptions.ConnectionError: pass + for b in cluster: b.ready() # Make sure all brokers still running. + + # Bad user ID + try: + cluster[0].connect(username="foo", password="bar").close() + self.fail("Expected exception") + except messaging.exceptions.ConnectionError: pass + for b in cluster: b.ready() # Make sure all brokers still running. + + # Action disallowed by ACL + c = cluster[0].connect(username="zag", password="zag") + try: + s = c.session() + s.sender("zaggy;{create:always}") + s.close() + 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") + try: + s = c.session() + s.sender("zaggy;{assert:always}") + s.close() + self.fail("Expected exception") + except messaging.exceptions.NotFound: 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") + cluster = self.cluster(1, args=["--auth", "yes", "--sasl-config", sasl_config,]) + c = cluster[0].connect(username="zig", password="zig") + s = c.session().sender("q;{create:always}") + s.send(Message("x", user_id="zig")) # Message sent before start new broker + cluster.start() + s.send(Message("y", user_id="zig")) # Messsage sent after start of new broker + # Verify brokers are healthy and messages are on the queue. + self.assertEqual("x", cluster[0].get_message("q").content) + self.assertEqual("y", cluster[1].get_message("q").content) + + def test_link_events(self): + """Regression test for https://bugzilla.redhat.com/show_bug.cgi?id=611543""" + args = ["--mgmt-pub-interval", 1] # Publish management information every second. + broker1 = self.cluster(1, args)[0] + broker2 = self.cluster(1, args)[0] + qp = self.popen(["qpid-printevents", broker1.host_port()], EXPECT_RUNNING) + qr = self.popen(["qpid-route", "route", "add", + broker1.host_port(), broker2.host_port(), + "amq.fanout", "key" + ], EXPECT_EXIT_OK) + # Look for link event in printevents output. + retry(lambda: find_in_file("brokerLinkUp", qp.outfile("out"))) + broker1.ready() + broker2.ready() + + def test_queue_cleaner(self): + """ Regression test to ensure that cleanup of expired messages works correctly """ + cluster = self.cluster(2, args=["--queue-purge-interval", 3]) + + s0 = cluster[0].connect().session() + sender = s0.sender("my-lvq; {create: always, node:{x-declare:{arguments:{'qpid.last_value_queue':1}}}}") + #send 10 messages that will all expire and be cleaned up + for i in range(1, 10): + msg = Message("message-%s" % i) + msg.properties["qpid.LVQ_key"] = "a" + msg.ttl = 0.1 + sender.send(msg) + #wait for queue cleaner to run + time.sleep(3) + + #test all is ok by sending and receiving a message + msg = Message("non-expiring") + msg.properties["qpid.LVQ_key"] = "b" + sender.send(msg) + s0.connection.close() + s1 = cluster[1].connect().session() + m = s1.receiver("my-lvq", capacity=1).fetch(timeout=1) + s1.acknowledge() + self.assertEqual("non-expiring", m.content) + s1.connection.close() + + for b in cluster: b.ready() # Make sure all brokers still running. + + + def test_amqfailover_visible(self): + """Verify that the amq.failover exchange can be seen by + QMF-based tools - regression test for BZ615300.""" + broker1 = self.cluster(1)[0] + broker2 = self.cluster(1)[0] + qs = subprocess.Popen(["qpid-stat", "-e", broker1.host_port()], stdout=subprocess.PIPE) + out = qs.communicate()[0] + assert out.find("amq.failover") > 0 + + def evaluate_address(self, session, address): + """Create a receiver just to evaluate an address for its side effects""" + r = session.receiver(address) + r.close() + + def test_expire_fanout(self): + """Regression test for QPID-2874: Clustered broker crashes in assertion in + cluster/ExpiryPolicy.cpp. + Caused by a fan-out message being updated as separate messages""" + cluster = self.cluster(1) + session0 = cluster[0].connect().session() + # Create 2 queues bound to fanout exchange. + self.evaluate_address(session0, "q1;{create:always,node:{x-bindings:[{exchange:'amq.fanout',queue:q1}]}}") + self.evaluate_address(session0, "q2;{create:always,node:{x-bindings:[{exchange:'amq.fanout',queue:q2}]}}") + queues = ["q1", "q2"] + # Send a fanout message with a long timeout + s = session0.sender("amq.fanout") + s.send(Message("foo", ttl=100), sync=False) + # Start a new member, check the messages + cluster.start() + session1 = cluster[1].connect().session() + for q in queues: self.assert_browse(session1, "q1", ["foo"]) + + 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. + This meant extra management objects and caused an exit if a management + client was attached. + """ + args=["--mgmt-pub-interval=1","--log-enable=trace+:management"] + cluster0 = self.cluster(1, args=args) + cluster1 = self.cluster(1, args=args) + assert 0 == subprocess.call( + ["qpid-route", "route", "add", cluster0[0].host_port(), + cluster1[0].host_port(), "dummy-exchange", "dummy-key", "-d"]) + cluster0.start() + + # Wait for qpid-tool:list on cluster0[0] to generate expected output. + pattern = re.compile("org.apache.qpid.broker.*link") + qpid_tool = subprocess.Popen(["qpid-tool", cluster0[0].host_port()], + stdin=subprocess.PIPE, stdout=subprocess.PIPE) + class Scanner(Thread): + def __init__(self): self.found = False; Thread.__init__(self) + def run(self): + for l in qpid_tool.stdout: + if pattern.search(l): self.found = True; return + scanner = Scanner() + scanner.start() + start = time.time() + try: + # Wait up to 5 second timeout for scanner to find expected output + while not scanner.found and time.time() < start + 5: + qpid_tool.stdin.write("list\n") # Ask qpid-tool to list + for b in cluster0: b.ready() # Raise if any brokers are down + finally: + qpid_tool.stdin.write("quit\n") + 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].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={reconnect:true}" + ]) + self.receiver = self.popen( + ["qpid-receive", + "--broker", url, + "--address", queue, + "--ignore-duplicates", + "--check-redelivered", + "--connection-options={reconnect:true}", + "--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={reconnect:true}", + "--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={reconnect:true}", + "--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={reconnect:true}", + "--ack-frequency=1", + "--address=flq", + "--broker=%s" % cluster[2].host_port()]) + receiver.wait() + q_obj.update() + assert not q_obj.flowStopped + assert 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"]) + + +class LongTests(BrokerTest): + """Tests that can run for a long time if -DDURATION=<minutes> is set""" + def duration(self): + d = self.config.defines.get("DURATION") + if d: return float(d)*60 + else: return 3 # Default is to be quick + + def test_failover(self): + """Test fail-over during continuous send-receive with errors""" + + # Original cluster will all be killed so expect exit with failure + cluster = self.cluster(3, expect=EXPECT_EXIT_FAIL) + 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) + receiver.start() + sender.start() + + # Kill original brokers, start new ones for the duration. + endtime = time.time() + self.duration() + i = 0 + while time.time() < endtime: + cluster[i].kill() + i += 1 + b = cluster.start(expect=EXPECT_EXIT_FAIL) + ErrorGenerator(b) + time.sleep(5) + sender.stop() + receiver.stop() + for i in range(i, len(cluster)): cluster[i].kill() + + def test_management(self, args=[]): + """ + Stress test: Run management clients and other clients concurrently + while killing and restarting brokers. + """ + + class ClientLoop(StoppableThread): + """Run a client executable in a loop.""" + def __init__(self, broker, cmd): + StoppableThread.__init__(self) + self.broker=broker + self.cmd = cmd # Client command. + self.lock = Lock() + self.process = None # Client process. + self.start() + + def run(self): + try: + while True: + self.lock.acquire() + try: + if self.stopped: break + self.process = self.broker.test.popen( + self.cmd, expect=EXPECT_UNKNOWN) + finally: + self.lock.release() + try: + exit = self.process.wait() + except OSError, e: + # 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: + if self.stopped: break + if exit != 0: + self.process.unexpected( + "client of %s exit code %s"%(self.broker.name, exit)) + finally: + self.lock.release() + except Exception, e: + self.error = RethrownException("Error in ClientLoop.run") + + def stop(self): + """Stop the running client and wait for it to exit""" + self.lock.acquire() + try: + if self.stopped: return + self.stopped = True + if self.process: + try: self.process.kill() # Kill the client. + except OSError: pass # The client might not be running. + finally: self.lock.release() + StoppableThread.stop(self) + + # body of test_management() + + args += ["--mgmt-pub-interval", 1] + args += ["--log-enable=trace+:management"] + # Use store if present. + if BrokerTest.store_lib: args +=["--load-module", BrokerTest.store_lib] + cluster = self.cluster(3, args) + + clients = [] # Per-broker list of clients that only connect to one broker. + mclients = [] # Management clients that connect to every broker in the cluster. + + def start_clients(broker): + """Start ordinary clients for a broker.""" + cmds=[ + ["qpid-tool", "localhost:%s"%(broker.port())], + ["qpid-perftest", "--count=5000", "--durable=yes", + "--base-name", str(qpid.datatypes.uuid4()), "--port", broker.port()], + ["qpid-txtest", "--queue-base-name", "tx-%s"%str(qpid.datatypes.uuid4()), + "--port", broker.port()], + ["qpid-queue-stats", "-a", "localhost:%s" %(broker.port())], + ["testagent", "localhost", str(broker.port())] ] + clients.append([ClientLoop(broker, cmd) for cmd in cmds]) + + def start_mclients(broker): + """Start management clients that make multiple connections.""" + cmd = ["qpid-stat", "-b", "localhost:%s" %(broker.port())] + mclients.append(ClientLoop(broker, cmd)) + + endtime = time.time() + self.duration() + # For long duration, first run is a quarter of the duration. + runtime = max(5, self.duration() / 4.0) + alive = 0 # First live cluster member + for i in range(len(cluster)): start_clients(cluster[i]) + start_mclients(cluster[alive]) + + while time.time() < endtime: + time.sleep(runtime) + runtime = 5 # Remaining runs 5 seconds, frequent broker kills + 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.kill() + # Stop the brokers clients and all the mclients. + for c in clients[alive] + mclients: + try: c.stop() + except: pass # Ignore expected errors due to broker shutdown. + clients[alive] = [] + mclients = [] + # Start another broker and clients + alive += 1 + cluster.start() + start_clients(cluster[-1]) + start_mclients(cluster[alive]) + for c in chain(mclients, *clients): + c.stop() + # 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): + 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: ErrorGenerator(b) + + # 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[2]) + receiver.start() + senders = [NumberedSender(cluster[i]) for i in range(1,3)] + for s in senders: + s.start() + + # Kill original brokers, start new ones for the duration. + endtime = time.time() + self.duration(); + i = 0 + while time.time() < endtime: + cluster[i].kill() + i += 1 + b = cluster.start(expect=EXPECT_EXIT_FAIL) + #ErrorGenerator(b) + time.sleep(5) + #b = cluster[0] + #b.startQmf() + for s in senders: + s.stop() + receiver.stop() + for i in range(i, len(cluster)): cluster[i].kill() + + +class StoreTests(BrokerTest): + """ + Cluster tests that can only be run if there is a store available. + """ + def args(self): + assert BrokerTest.store_lib + return ["--load-module", BrokerTest.store_lib] + + def test_store_loaded(self): + """Ensure we are indeed loading a working store""" + broker = self.broker(self.args(), name="recoverme", expect=EXPECT_EXIT_FAIL) + m = Message("x", durable=True) + broker.send_message("q", m) + broker.kill() + broker = self.broker(self.args(), name="recoverme") + self.assertEqual("x", broker.get_message("q").content) + + def test_kill_restart(self): + """Verify we can kill/resetart a broker with store in a cluster""" + cluster = self.cluster(1, self.args()) + cluster.start("restartme", expect=EXPECT_EXIT_FAIL).kill() + + # Send a message, retrieve from the restarted broker + cluster[0].send_message("q", "x") + m = cluster.start("restartme").get_message("q") + self.assertEqual("x", m.content) + + def stop_cluster(self,broker): + """Clean shut-down of a cluster""" + self.assertEqual(0, qpid_cluster.main( + ["-kf", broker.host_port()])) + + def test_persistent_restart(self): + """Verify persistent cluster shutdown/restart scenarios""" + cluster = self.cluster(0, args=self.args() + ["--cluster-size=3"]) + a = cluster.start("a", expect=EXPECT_EXIT_OK, wait=False) + b = cluster.start("b", expect=EXPECT_EXIT_OK, wait=False) + c = cluster.start("c", expect=EXPECT_EXIT_FAIL, wait=True) + a.send_message("q", Message("1", durable=True)) + # Kill & restart one member. + c.kill() + self.assertEqual(a.get_message("q").content, "1") + a.send_message("q", Message("2", durable=True)) + c = cluster.start("c", expect=EXPECT_EXIT_OK) + self.assertEqual(c.get_message("q").content, "2") + # Shut down the entire cluster cleanly and bring it back up + a.send_message("q", Message("3", durable=True)) + self.stop_cluster(a) + a = cluster.start("a", wait=False) + b = cluster.start("b", wait=False) + c = cluster.start("c", wait=True) + self.assertEqual(a.get_message("q").content, "3") + + def test_persistent_partial_failure(self): + # Kill 2 members, shut down the last cleanly then restart + # Ensure we use the clean database + cluster = self.cluster(0, args=self.args() + ["--cluster-size=3"]) + a = cluster.start("a", expect=EXPECT_EXIT_FAIL, wait=False) + b = cluster.start("b", expect=EXPECT_EXIT_FAIL, wait=False) + c = cluster.start("c", expect=EXPECT_EXIT_OK, wait=True) + a.send_message("q", Message("4", durable=True)) + a.kill() + b.kill() + self.assertEqual(c.get_message("q").content, "4") + c.send_message("q", Message("clean", durable=True)) + self.stop_cluster(c) + a = cluster.start("a", wait=False) + b = cluster.start("b", wait=False) + c = cluster.start("c", wait=True) + self.assertEqual(a.get_message("q").content, "clean") + + def test_wrong_cluster_id(self): + # Start a cluster1 broker, then try to restart in cluster2 + cluster1 = self.cluster(0, args=self.args()) + a = cluster1.start("a", expect=EXPECT_EXIT_OK) + a.terminate() + cluster2 = self.cluster(1, args=self.args()) + try: + a = cluster2.start("a", expect=EXPECT_EXIT_FAIL) + a.ready() + self.fail("Expected exception") + except: pass + + def test_wrong_shutdown_id(self): + # Start 2 members and shut down. + cluster = self.cluster(0, args=self.args()+["--cluster-size=2"]) + a = cluster.start("a", expect=EXPECT_EXIT_OK, wait=False) + b = cluster.start("b", expect=EXPECT_EXIT_OK, wait=False) + self.stop_cluster(a) + self.assertEqual(a.wait(), 0) + self.assertEqual(b.wait(), 0) + + # Restart with a different member and shut down. + a = cluster.start("a", expect=EXPECT_EXIT_OK, wait=False) + c = cluster.start("c", expect=EXPECT_EXIT_OK, wait=False) + self.stop_cluster(a) + self.assertEqual(a.wait(), 0) + self.assertEqual(c.wait(), 0) + # Mix members from both shutdown events, they should fail + # TODO aconway 2010-03-11: can't predict the exit status of these + # as it depends on the order of delivery of initial-status messages. + # See comment at top of this file. + a = cluster.start("a", expect=EXPECT_UNKNOWN, wait=False) + b = cluster.start("b", expect=EXPECT_UNKNOWN, wait=False) + self.assertRaises(Exception, lambda: a.ready()) + self.assertRaises(Exception, lambda: b.ready()) + + def test_solo_store_clean(self): + # A single node cluster should always leave a clean store. + cluster = self.cluster(0, self.args()) + a = cluster.start("a", expect=EXPECT_EXIT_FAIL) + a.send_message("q", Message("x", durable=True)) + a.kill() + a = cluster.start("a") + self.assertEqual(a.get_message("q").content, "x") + + def test_last_store_clean(self): + # Verify that only the last node in a cluster to shut down has + # a clean store. Start with cluster of 3, reduce to 1 then + # increase again to ensure that a node that was once alone but + # finally did not finish as the last node does not get a clean + # store. + cluster = self.cluster(0, self.args()) + a = cluster.start("a", expect=EXPECT_EXIT_FAIL) + self.assertEqual(a.store_state(), "clean") + b = cluster.start("b", expect=EXPECT_EXIT_FAIL) + c = cluster.start("c", expect=EXPECT_EXIT_FAIL) + self.assertEqual(b.store_state(), "dirty") + self.assertEqual(c.store_state(), "dirty") + retry(lambda: a.store_state() == "dirty") + + a.send_message("q", Message("x", durable=True)) + a.kill() + b.kill() # c is last man, will mark store clean + retry(lambda: c.store_state() == "clean") + a = cluster.start("a", expect=EXPECT_EXIT_FAIL) # c no longer last man + retry(lambda: c.store_state() == "dirty") + c.kill() # a is now last man + retry(lambda: a.store_state() == "clean") + a.kill() + self.assertEqual(a.store_state(), "clean") + self.assertEqual(b.store_state(), "dirty") + self.assertEqual(c.store_state(), "dirty") + + def test_restart_clean(self): + """Verify that we can re-start brokers one by one in a + persistent cluster after a clean oshutdown""" + cluster = self.cluster(0, self.args()) + a = cluster.start("a", expect=EXPECT_EXIT_OK) + b = cluster.start("b", expect=EXPECT_EXIT_OK) + c = cluster.start("c", expect=EXPECT_EXIT_OK) + a.send_message("q", Message("x", durable=True)) + self.stop_cluster(a) + a = cluster.start("a") + b = cluster.start("b") + c = cluster.start("c") + self.assertEqual(c.get_message("q").content, "x") + + def test_join_sub_size(self): + """Verify that after starting a cluster with cluster-size=N, + we can join new members even if size < N-1""" + cluster = self.cluster(0, self.args()+["--cluster-size=3"]) + a = cluster.start("a", wait=False, expect=EXPECT_EXIT_FAIL) + b = cluster.start("b", wait=False, expect=EXPECT_EXIT_FAIL) + c = cluster.start("c") + a.send_message("q", Message("x", durable=True)) + a.send_message("q", Message("y", durable=True)) + a.kill() + b.kill() + a = cluster.start("a") + self.assertEqual(c.get_message("q").content, "x") + b = cluster.start("b") + self.assertEqual(c.get_message("q").content, "y") diff --git a/qpid/cpp/src/tests/clustered_replication_test b/qpid/cpp/src/tests/clustered_replication_test new file mode 100755 index 0000000000..d6c72d9d1b --- /dev/null +++ b/qpid/cpp/src/tests/clustered_replication_test @@ -0,0 +1,110 @@ +#!/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. +# + +# Test reliability of the replication feature in the face of link +# failures: +source ./test_env.sh + +trap stop_brokers INT EXIT + +fail() { + echo $1 + exit 1 +} + +stop_brokers() { + if [[ $PRIMARY1 ]] ; then + $QPIDD_EXEC --no-module-dir -q --port $PRIMARY1 + unset PRIMARY1 + fi + if [[ $PRIMARY2 ]] ; then + $QPIDD_EXEC --no-module-dir -q --port $PRIMARY2 + unset PRIMARY2 + fi + if [[ $DR1 ]] ; then + $QPIDD_EXEC --no-module-dir -q --port $DR1 + unset DR1 + fi + if [[ $DR2 ]] ; then + $QPIDD_EXEC --no-module-dir -q --port $DR2 + unset DR2 + fi +} + +if test -d $PYTHON_DIR; then + . $srcdir/ais_check + + #todo: these cluster names need to be unique to prevent clashes + PRIMARY_CLUSTER=PRIMARY_$(hostname)_$$ + DR_CLUSTER=DR_$(hostname)_$$ + + GENERAL_OPTS="--auth no --no-module-dir --no-data-dir --daemon --port 0 --log-to-stderr false" + PRIMARY_OPTS="--load-module $REPLICATING_LISTENER_LIB --create-replication-queue true --replication-queue REPLICATION_QUEUE --load-module $CLUSTER_LIB --cluster-name $PRIMARY_CLUSTER" + DR_OPTS="--load-module $REPLICATION_EXCHANGE_LIB --load-module $CLUSTER_LIB --cluster-name $DR_CLUSTER" + + rm -f repl*.tmp #cleanup any files left from previous run + + #start first node of primary cluster and set up test queue + echo Starting primary cluster + PRIMARY1=$(with_ais_group $QPIDD_EXEC $GENERAL_OPTS $PRIMARY_OPTS --log-to-file repl.primary.1.tmp) || fail "Could not start PRIMARY1" + $PYTHON_COMMANDS/qpid-config -a "localhost:$PRIMARY1" add queue test-queue --generate-queue-events 2 + $PYTHON_COMMANDS/qpid-config -a "localhost:$PRIMARY1" add queue control-queue --generate-queue-events 1 + + #send 10 messages, consume 5 of them + for i in `seq 1 10`; do echo Message$i; done | ./sender --port $PRIMARY1 + ./receiver --port $PRIMARY1 --messages 5 > /dev/null + + #add new node to primary cluster, testing correct transfer of state: + echo Adding node to primary cluster + PRIMARY2=$(with_ais_group $QPIDD_EXEC $GENERAL_OPTS $PRIMARY_OPTS --log-to-file repl.primary.2.tmp) || fail "Could not start PRIMARY2 " + + #start DR cluster, set up test queue there and establish replication bridge + echo Starting DR cluster + DR1=$(with_ais_group $QPIDD_EXEC $GENERAL_OPTS $DR_OPTS --log-to-file repl.dr.1.tmp) || fail "Could not start DR1" + DR2=$(with_ais_group $QPIDD_EXEC $GENERAL_OPTS $DR_OPTS --log-to-file repl.dr.2.tmp) || fail "Could not start DR2" + + $PYTHON_COMMANDS/qpid-config -a "localhost:$DR1" add queue test-queue + $PYTHON_COMMANDS/qpid-config -a "localhost:$DR1" add queue control-queue + $PYTHON_COMMANDS/qpid-config -a "localhost:$DR1" add exchange replication REPLICATION_EXCHANGE + $PYTHON_COMMANDS/qpid-route queue add localhost:$DR2 localhost:$PRIMARY2 REPLICATION_EXCHANGE REPLICATION_QUEUE || fail "Could not add route." + + #send more messages to primary + for i in `seq 11 20`; do echo Message$i; done | ./sender --port $PRIMARY1 --send-eos 1 + + #wait for replication events to all be processed: + echo Waiting for replication to complete + echo Done | ./sender --port $PRIMARY1 --routing-key control-queue --send-eos 1 + ./receiver --queue control-queue --port $DR1 > /dev/null + + #verify contents of test queue on dr cluster: + echo Verifying... + ./receiver --port $DR2 > repl.out.tmp + for i in `seq 6 20`; do echo Message$i; done | diff repl.out.tmp - || FAIL=1 + + if [[ $FAIL ]]; then + echo Clustered replication test failed: expectations not met! + exit 1 + else + echo Clustered replication test passed + rm -f repl*.tmp + fi + +fi diff --git a/qpid/cpp/src/tests/config.null b/qpid/cpp/src/tests/config.null new file mode 100644 index 0000000000..565c7da435 --- /dev/null +++ b/qpid/cpp/src/tests/config.null @@ -0,0 +1 @@ +# empty config diff --git a/qpid/cpp/src/tests/consume.cpp b/qpid/cpp/src/tests/consume.cpp new file mode 100644 index 0000000000..69110d151f --- /dev/null +++ b/qpid/cpp/src/tests/consume.cpp @@ -0,0 +1,131 @@ +/* + * + * 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 <algorithm> +#include <iostream> +#include <memory> +#include <sstream> +#include <vector> + +#include "TestOptions.h" +#include "qpid/client/Connection.h" +#include "qpid/client/Message.h" +#include "qpid/client/Session.h" +#include "qpid/client/SubscriptionManager.h" + +using namespace qpid; +using namespace qpid::client; +using namespace qpid::sys; +using namespace std; + +namespace qpid { +namespace tests { + +typedef vector<string> StringSet; + +struct Args : public qpid::TestOptions { + uint count; + uint ack; + string queue; + bool declare; + bool summary; + bool print; + bool durable; + + Args() : count(1000), ack(0), queue("publish-consume"), + declare(false), summary(false), print(false) + { + addOptions() + ("count", optValue(count, "N"), "number of messages to publish") + ("ack-frequency", optValue(ack, "N"), "ack every N messages (0 means use no-ack mode)") + ("queue", optValue(queue, "<queue name>"), "queue to consume from") + ("declare", optValue(declare), "declare the queue") + ("durable", optValue(durable), "declare the queue durable, use with declare") + ("print-data", optValue(print), "Print the recieved data at info level") + ("s,summary", optValue(summary), "Print undecorated rate."); + } +}; + +Args opts; + +struct Client +{ + Connection connection; + Session session; + + Client() + { + opts.open(connection); + session = connection.newSession(); + } + + void consume() + { + if (opts.declare) + session.queueDeclare(arg::queue=opts.queue, arg::durable=opts.durable); + SubscriptionManager subs(session); + LocalQueue lq; + SubscriptionSettings settings; + settings.acceptMode = opts.ack > 0 ? ACCEPT_MODE_EXPLICIT : ACCEPT_MODE_NONE; + settings.flowControl = FlowControl(opts.count, SubscriptionManager::UNLIMITED,false); + Subscription sub = subs.subscribe(lq, opts.queue, settings); + Message msg; + AbsTime begin=now(); + for (size_t i = 0; i < opts.count; ++i) { + msg=lq.pop(); + QPID_LOG(info, "Received: " << msg.getMessageProperties().getCorrelationId()); + if (opts.print) QPID_LOG(info, "Data: " << msg.getData()); + } + if (opts.ack != 0) + sub.accept(sub.getUnaccepted()); // Cumulative ack for final batch. + AbsTime end=now(); + double secs(double(Duration(begin,end))/TIME_SEC); + if (opts.summary) cout << opts.count/secs << endl; + else cout << "Time: " << secs << "s Rate: " << opts.count/secs << endl; + } + + ~Client() + { + try{ + session.close(); + connection.close(); + } catch(const exception& e) { + cout << e.what() << endl; + } + } +}; + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char** argv) +{ + try { + opts.parse(argc, argv); + Client client; + client.consume(); + return 0; + } catch(const exception& e) { + cout << e.what() << endl; + } + return 1; +} diff --git a/qpid/cpp/src/tests/datagen.cpp b/qpid/cpp/src/tests/datagen.cpp new file mode 100644 index 0000000000..acbc07d63c --- /dev/null +++ b/qpid/cpp/src/tests/datagen.cpp @@ -0,0 +1,103 @@ +/* + * + * 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 <exception> +#include <iostream> +#include <stdlib.h> +#include <time.h> +#include "qpid/Options.h" + +namespace qpid { +namespace tests { + +struct Args : public qpid::Options +{ + uint count; + uint minSize; + uint maxSize; + uint minChar; + uint maxChar; + bool help; + + Args() : qpid::Options("Random data generator"), + count(1), minSize(8), maxSize(4096), + minChar(32), maxChar(126),//safely printable ascii chars + help(false) + { + addOptions() + ("count", qpid::optValue(count, "N"), "number of data strings to generate") + ("min-size", qpid::optValue(minSize, "N"), "minimum size of data string") + ("max-size", qpid::optValue(maxSize, "N"), "maximum size of data string") + ("min-char", qpid::optValue(minChar, "N"), "minimum char value used in data string") + ("max-char", qpid::optValue(maxChar, "N"), "maximum char value used in data string") + ("help", qpid::optValue(help), "print this usage statement"); + } + + bool parse(int argc, char** argv) { + try { + qpid::Options::parse(argc, argv); + if (maxSize < minSize) throw qpid::Options::Exception("max-size must be greater than min-size"); + if (maxChar < minChar) throw qpid::Options::Exception("max-char must be greater than min-char"); + + if (help) { + std::cerr << *this << std::endl << std::endl; + } else { + return true; + } + } catch (const std::exception& e) { + std::cerr << *this << std::endl << std::endl << e.what() << std::endl; + } + return false; + } + +}; + +uint random(uint min, uint max) +{ + return (rand() % (max-min+1)) + min; +} + +std::string generateData(uint size, uint min, uint max) +{ + std::string data; + for (uint i = 0; i < size; i++) { + data += (char) random(min, max); + } + return data; +} + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char** argv) +{ + Args opts; + if (opts.parse(argc, argv)) { + srand(time(0)); + for (uint i = 0; i < opts.count; i++) { + std::cout << generateData(random(opts.minSize, opts.maxSize), opts.minChar, opts.maxChar) << std::endl; + } + return 0; + } else { + return 1; + } +} diff --git a/qpid/cpp/src/tests/declare_queues.cpp b/qpid/cpp/src/tests/declare_queues.cpp new file mode 100644 index 0000000000..bf85b9c04b --- /dev/null +++ b/qpid/cpp/src/tests/declare_queues.cpp @@ -0,0 +1,101 @@ +/* + * + * 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/FailoverManager.h> +#include <qpid/client/Session.h> +#include <qpid/sys/Time.h> +#include <qpid/Exception.h> + +#include <cstdlib> +#include <iostream> +#include <sstream> + +using namespace qpid::client; + +using namespace std; + +int +main(int argc, char ** argv) +{ + ConnectionSettings settings; + if ( argc != 6 ) + { + cerr << "Usage: declare_queues host port durability queue_name_prefix n_queues\n"; + return 1; + } + + settings.host = argv[1]; + settings.port = atoi(argv[2]); + int durability = atoi(argv[3]); + char const * queue_name_prefix = argv[4]; + int n_queues = atoi(argv[5]); + + FailoverManager connection(settings); + + int max_fail = 13; + for ( int i = 0; i < n_queues; ++ i ) { + stringstream queue_name; + queue_name << queue_name_prefix << '_' << i; + + bool queue_created = false; + int failure_count; + + // Any non-transport failure is Bad. + try + { + while ( ! queue_created ) { + Session session = connection.connect().newSession(); + // TransportFailures aren't too bad -- they might happen because + // we are doing a cluster failover test. But if we get too many, + // we will still bug out. + failure_count = 0; + try { + if ( durability ) + session.queueDeclare(arg::queue=queue_name.str(), arg::durable=true); + else + session.queueDeclare(arg::queue=queue_name.str()); + queue_created = true; + cout << "declare_queues: Created queue " << queue_name.str() << endl; + } + catch ( const qpid::TransportFailure& ) { + if ( ++ failure_count >= max_fail ) { + cerr << "declare_queues failed: too many transport errors.\n"; + cerr << " host: " << settings.host + << " port: " << settings.port << endl; + return 1; + } + qpid::sys::sleep ( 1 ); + } + } + } + catch ( const exception & error) { + cerr << "declare_queues failed:" << error.what() << endl; + cerr << " host: " << settings.host + << " port: " << settings.port << endl; + return 1; + } + } +} + + + + + diff --git a/qpid/cpp/src/tests/dlclose_noop.c b/qpid/cpp/src/tests/dlclose_noop.c new file mode 100644 index 0000000000..b78cf486d8 --- /dev/null +++ b/qpid/cpp/src/tests/dlclose_noop.c @@ -0,0 +1,30 @@ +/* + * + * 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. + * + */ + +/* + * Loaded via LD_PRELOAD this will turn dlclose into a no-op. + * + * Allows valgrind to generate useful reports from programs that + * dynamically unload libraries before exit, such as CppUnit's + * DllPlugInTester. + * + */ + +#include <stdio.h> +void* dlclose(void* handle) { return 0; } + diff --git a/qpid/cpp/src/tests/dynamic_log_level_test b/qpid/cpp/src/tests/dynamic_log_level_test new file mode 100755 index 0000000000..990e56b1b1 --- /dev/null +++ b/qpid/cpp/src/tests/dynamic_log_level_test @@ -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 a simple test to verify dynamic log level changes +source ./test_env.sh +test -d $PYTHON_DIR || { echo "Skipping python tests, no python dir."; exit 0; } + +LOG_FILE=log_test.log +trap cleanup EXIT + +cleanup() { + test -n "$PORT" && $QPIDD_EXEC --no-module-dir --quit --port $PORT +} + +error() { + echo $*; + exit 1; +} + +rm -rf $LOG_FILE +PORT=$($QPIDD_EXEC --auth=no --no-module-dir --daemon --port=0 --log-to-file $LOG_FILE) || error "Could not start broker" + +echo Broker for log level test started on $PORT, pid is $($QPIDD_EXEC --no-module-dir --check --port $PORT) + +$srcdir/qpid-ctrl -b localhost:$PORT setLogLevel level='notice+' > /dev/null +$srcdir/qpid-ctrl -b localhost:$PORT echo sequence=1 body=HIDDEN > /dev/null +$srcdir/qpid-ctrl -b localhost:$PORT setLogLevel level='debug+:Broker' > /dev/null +$srcdir/qpid-ctrl -b localhost:$PORT echo sequence=2 body=VISIBLE > /dev/null +$srcdir/qpid-ctrl -b localhost:$PORT setLogLevel level='notice+' > /dev/null + +#check log includes debug statement for last echo, but not the first +if [[ $(grep echo $LOG_FILE | wc -l) -ne 1 ]]; then + cat $LOG_FILE + error "Log contents not as expected" +else + rm -rf $LOG_FILE + echo OK +fi + diff --git a/qpid/cpp/src/tests/echotest.cpp b/qpid/cpp/src/tests/echotest.cpp new file mode 100644 index 0000000000..5114ab883d --- /dev/null +++ b/qpid/cpp/src/tests/echotest.cpp @@ -0,0 +1,158 @@ +/* + * + * 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/SubscriptionManager.h> +#include <qpid/client/AsyncSession.h> +#include <qpid/client/Message.h> +#include <qpid/client/MessageListener.h> +#include <qpid/sys/Time.h> +#include <qpid/Options.h> + +#include <iostream> + +using namespace qpid::client; +using namespace qpid::framing; +using namespace qpid::sys; +using namespace std; + +namespace qpid { +namespace tests { + +struct Args : public qpid::Options, + public qpid::client::ConnectionSettings +{ + bool help; + uint count; + uint size; + bool summary; + + Args() : qpid::Options("Simple latency test optins"), help(false), count(20), size(0), summary() + { + using namespace qpid; + addOptions() + ("help", optValue(help), "Print this usage statement") + ("count", optValue(count, "N"), "Number of messages to send") + ("size", optValue(count, "N"), "Size of messages") + ("broker,b", optValue(host, "HOST"), "Broker host to connect to") + ("port,p", optValue(port, "PORT"), "Broker port to connect to") + ("username", optValue(username, "USER"), "user name for broker log in.") + ("password", optValue(password, "PASSWORD"), "password for broker log in.") + ("mechanism", optValue(mechanism, "MECH"), "SASL mechanism to use when authenticating.") + ("tcp-nodelay", optValue(tcpNoDelay), "Turn on tcp-nodelay") + ("s,summary", optValue(summary), "Print only average latency."); + } +}; + +uint64_t current_time() +{ + Duration t(EPOCH, now()); + return t; +} + +class Listener : public MessageListener +{ + private: + Session session; + SubscriptionManager subscriptions; + uint counter; + const uint limit; + std::string queue; + Message request; + double total, min, max; + bool summary; + + public: + Listener(Session& session, uint limit, bool summary); + void start(uint size); + void received(Message& message); +}; + +Listener::Listener(Session& s, uint l, bool summary_) : + session(s), subscriptions(s), counter(0), limit(l), + queue(session.getId().getName()), total(), + min(std::numeric_limits<double>::max()), max(), summary(summary_) +{} + +void Listener::start(uint size) +{ + session.queueDeclare(arg::queue=queue, arg::exclusive=true, arg::autoDelete=true); + request.getDeliveryProperties().setRoutingKey(queue); + subscriptions.subscribe(*this, queue, SubscriptionSettings(FlowControl::unlimited(), ACCEPT_MODE_NONE)); + + request.getDeliveryProperties().setTimestamp(current_time()); + if (size) request.setData(std::string(size, 'X')); + async(session).messageTransfer(arg::content=request); + subscriptions.run(); +} + +void Listener::received(Message& response) +{ + //extract timestamp and compute latency: + uint64_t sentAt = response.getDeliveryProperties().getTimestamp(); + uint64_t receivedAt = current_time(); + + double latency = ((double) (receivedAt - sentAt)) / TIME_MSEC; + if (!summary) cout << "Latency: " << latency << "ms" << endl; + min = std::min(latency, min); + max = std::max(latency, max); + total += latency; + + if (++counter < limit) { + request.getDeliveryProperties().setTimestamp(current_time()); + async(session).messageTransfer(arg::content=request); + } else { + subscriptions.cancel(queue); + if (summary) cout << min << "\t" << max << "\t" << total/limit << endl; + else cout << "min: " << min << " max: " << max << " average: " << total/limit << endl; + } +} + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char** argv) +{ + Args opts; + opts.parse(argc, argv); + + if (opts.help) { + std::cout << opts << std::endl; + return 0; + } + + Connection connection; + try { + connection.open(opts); + Session session = connection.newSession(); + Listener listener(session, opts.count, opts.summary); + listener.start(opts.size); + + connection.close(); + return 0; + } catch(const std::exception& error) { + std::cout << error.what() << std::endl; + } + return 1; +} + + diff --git a/qpid/cpp/src/tests/exception_test.cpp b/qpid/cpp/src/tests/exception_test.cpp new file mode 100644 index 0000000000..3536ffddbe --- /dev/null +++ b/qpid/cpp/src/tests/exception_test.cpp @@ -0,0 +1,127 @@ +/* + * + * 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 "unit_test.h" +#include "test_tools.h" +#include "BrokerFixture.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/client/MessageListener.h" +#include "qpid/sys/Runnable.h" +#include "qpid/sys/Thread.h" +#include "qpid/framing/reply_exceptions.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(exception_test) + +// FIXME aconway 2008-06-12: need to update our exception handling to +// 0-10 handling and extend this test to provoke all the exceptional +// conditions we know of and verify the correct exception is thrown. + +using namespace std; +using namespace qpid; +using namespace sys; +using namespace client; +using namespace framing; + +using qpid::broker::Broker; +using boost::bind; +using boost::function; + +template <class Ex> +struct Catcher : public Runnable { + function<void ()> f; + bool caught; + Thread thread; + + Catcher(function<void ()> f_) : f(f_), caught(false), thread(this) {} + ~Catcher() { join(); } + + void run() { + try { + ScopedSuppressLogging sl; // Suppress messages for expected errors. + f(); + } + catch(const Ex& e) { + caught=true; + BOOST_MESSAGE(string("Caught expected exception: ")+e.what()+"["+typeid(e).name()+"]"); + } + catch(const std::exception& e) { + BOOST_ERROR(string("Bad exception: ")+e.what()+"["+typeid(e).name()+"] expected: "+typeid(Ex).name()); + } + catch(...) { + BOOST_ERROR(string("Bad exception: unknown")); + } + } + + bool join() { + if (thread) { + thread.join(); + thread=Thread(); + } + return caught; + } +}; + +QPID_AUTO_TEST_CASE(TestSessionBusy) { + SessionFixture f; + try { + ScopedSuppressLogging sl; // Suppress messages for expected errors. + f.connection.newSession(f.session.getId().getName()); + BOOST_FAIL("Expected SessionBusyException for " << f.session.getId().getName()); + } catch (const SessionBusyException&) {} // FIXME aconway 2008-09-22: client is not throwing correct exception. +} + +QPID_AUTO_TEST_CASE(DisconnectedPop) { + ProxySessionFixture fix; + ProxyConnection c(fix.broker->getPort(Broker::TCP_TRANSPORT)); + 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(); + BOOST_CHECK(pop.join()); +} + +QPID_AUTO_TEST_CASE(DisconnectedListen) { + ProxySessionFixture 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(); + BOOST_CHECK_THROW(fix.session.queueDeclare(arg::queue="x"), TransportFailure); +} + +QPID_AUTO_TEST_CASE(NoSuchQueueTest) { + ProxySessionFixture fix; + ScopedSuppressLogging sl; // Suppress messages for expected errors. + BOOST_CHECK_THROW(fix.subs.subscribe(fix.lq, "no such queue"), NotFoundException); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/failover_soak.cpp b/qpid/cpp/src/tests/failover_soak.cpp new file mode 100644 index 0000000000..c2ac36a757 --- /dev/null +++ b/qpid/cpp/src/tests/failover_soak.cpp @@ -0,0 +1,827 @@ +/* + * + * 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 <stdlib.h> +#include <unistd.h> +#include <sys/wait.h> +#include <sys/time.h> +#include <string.h> +#include <sys/types.h> +#include <signal.h> + +#include <string> +#include <iostream> +#include <sstream> +#include <vector> + +#include <boost/assign.hpp> + +#include "qpid/framing/Uuid.h" + +#include <ForkedBroker.h> +#include <qpid/client/Connection.h> + + + + + +using namespace std; +using boost::assign::list_of; +using namespace qpid::framing; +using namespace qpid::client; + + +namespace qpid { +namespace tests { + +vector<pid_t> pids; + +typedef vector<ForkedBroker *> brokerVector; + +typedef enum +{ + NO_STATUS, + RUNNING, + COMPLETED +} +childStatus; + + +typedef enum +{ + NO_TYPE, + DECLARING_CLIENT, + SENDING_CLIENT, + RECEIVING_CLIENT +} +childType; + + +ostream& operator<< ( ostream& os, const childType& ct ) { + switch ( ct ) { + case DECLARING_CLIENT: os << "Declaring Client"; break; + case SENDING_CLIENT: os << "Sending Client"; break; + case RECEIVING_CLIENT: os << "Receiving Client"; break; + default: os << "No Client"; break; + } + + return os; +} + + + + +struct child +{ + child ( string & name, pid_t pid, childType type ) + : name(name), pid(pid), retval(-999), status(RUNNING), type(type) + { + gettimeofday ( & startTime, 0 ); + } + + + void + done ( int _retval ) + { + retval = _retval; + status = COMPLETED; + gettimeofday ( & stopTime, 0 ); + } + + + void + setType ( childType t ) + { + type = t; + } + + + string name; + pid_t pid; + int retval; + childStatus status; + childType type; + struct timeval startTime, + stopTime; +}; + + + + +struct children : public vector<child *> +{ + + void + add ( string & name, pid_t pid, childType type ) + { + push_back ( new child ( name, pid, type ) ); + } + + + child * + get ( pid_t pid ) + { + vector<child *>::iterator i; + for ( i = begin(); i != end(); ++ i ) + if ( pid == (*i)->pid ) + return *i; + + return 0; + } + + + void + exited ( pid_t pid, int retval ) + { + child * kid = get ( pid ); + if(! kid) + { + if ( verbosity > 1 ) + { + cerr << "children::exited warning: Can't find child with pid " + << pid + << endl; + } + return; + } + + kid->done ( retval ); + } + + + int + unfinished ( ) + { + int count = 0; + + vector<child *>::iterator i; + for ( i = begin(); i != end(); ++ i ) + if ( COMPLETED != (*i)->status ) + ++ count; + + return count; + } + + + int + checkChildren ( ) + { + for ( unsigned int i = 0; i < pids.size(); ++ i ) + { + int pid = pids[i]; + int returned_pid; + int status; + + child * kid = get ( pid ); + + if ( kid->status != COMPLETED ) + { + returned_pid = waitpid ( pid, &status, WNOHANG ); + + if ( returned_pid == pid ) + { + int exit_status = WEXITSTATUS(status); + exited ( pid, exit_status ); + if ( exit_status ) // this is a child error. + return exit_status; + } + } + } + + return 0; + } + + + void + killEverybody ( ) + { + vector<child *>::iterator i; + for ( i = begin(); i != end(); ++ i ) + kill ( (*i)->pid, 9 ); + } + + + + void + print ( ) + { + cout << "--- status of all children --------------\n"; + vector<child *>::iterator i; + for ( i = begin(); i != end(); ++ i ) + cout << "child: " << (*i)->name + << " status: " << (*i)->status + << endl; + cout << "\n\n\n\n"; + } + + int verbosity; +}; + + +children allMyChildren; + + +void +childExit ( int ) +{ + int childReturnCode; + pid_t pid = waitpid ( 0, & childReturnCode, WNOHANG); + + if ( pid > 0 ) + allMyChildren.exited ( pid, childReturnCode ); +} + + + +int +mrand ( int maxDesiredVal ) { + double zeroToOne = (double) rand() / (double) RAND_MAX; + return (int) (zeroToOne * (double) maxDesiredVal); +} + + + +int +mrand ( int minDesiredVal, int maxDesiredVal ) { + int interval = maxDesiredVal - minDesiredVal; + return minDesiredVal + mrand ( interval ); +} + + + +void +makeClusterName ( string & s ) { + stringstream ss; + ss << "soakTestCluster_" << Uuid(true).str(); + s = ss.str(); +} + + + + + +void +printBrokers ( brokerVector & brokers ) +{ + cout << "Broker List ------------ size: " << brokers.size() << "\n"; + for ( brokerVector::iterator i = brokers.begin(); i != brokers.end(); ++ i) { + cout << "pid: " + << (*i)->getPID() + << " port: " + << (*i)->getPort() + << endl; + } + cout << "end Broker List ------------\n"; +} + + + + +ForkedBroker * newbie = 0; +int newbie_port = 0; + + + +bool +wait_for_newbie ( ) +{ + if ( ! newbie ) + return true; + + try + { + Connection connection; + connection.open ( "127.0.0.1", newbie_port ); + connection.close(); + newbie = 0; // He's no newbie anymore! + return true; + } + catch ( const std::exception& error ) + { + std::cerr << "wait_for_newbie error: " + << error.what() + << endl; + return false; + } +} + +bool endsWith(const char* str, const char* suffix) { + return (strlen(suffix) < strlen(str) && 0 == strcmp(str+strlen(str)-strlen(suffix), suffix)); +} + + +void +startNewBroker ( brokerVector & brokers, + char const * moduleOrDir, + string const clusterName, + int verbosity, + int durable ) +{ + static int brokerId = 0; + stringstream path, prefix; + prefix << "soak-" << brokerId; + std::vector<std::string> argv = list_of<string> + ("qpidd") + ("--cluster-name")(clusterName) + ("--auth=no") + ("--mgmt-enable=no") + ("--log-prefix")(prefix.str()) + ("--log-to-file")(prefix.str()+".log") + ("--log-enable=info+") + ("--log-enable=debug+:cluster") + ("TMP_DATA_DIR"); + + if (endsWith(moduleOrDir, "cluster.so")) { + // Module path specified, load only that module. + argv.push_back(string("--load-module=")+moduleOrDir); + argv.push_back("--no-module-dir"); + if ( durable ) { + std::cerr << "failover_soak warning: durable arg hass no effect. Use \"dir\" option of \"moduleOrDir\".\n"; + } + } + else { + // Module directory specified, load all modules in dir. + argv.push_back(string("--module-dir=")+moduleOrDir); + } + + newbie = new ForkedBroker (argv); + newbie_port = newbie->getPort(); + ForkedBroker * broker = newbie; + + if ( verbosity > 0 ) + std::cerr << "new broker created: pid == " + << broker->getPID() + << " log-prefix == " + << "soak-" << brokerId + << endl; + brokers.push_back ( broker ); + + ++ brokerId; +} + + + + + +bool +killFrontBroker ( brokerVector & brokers, int verbosity ) +{ + cerr << "killFrontBroker: waiting for newbie sync...\n"; + if ( ! wait_for_newbie() ) + return false; + cerr << "killFrontBroker: newbie synced.\n"; + + if ( verbosity > 0 ) + cout << "killFrontBroker pid: " << brokers[0]->getPID() << " on port " << brokers[0]->getPort() << endl; + try { brokers[0]->kill(9); } + catch ( const exception& error ) { + if ( verbosity > 0 ) + { + cout << "error killing broker: " + << error.what() + << endl; + } + + return false; + } + delete brokers[0]; + brokers.erase ( brokers.begin() ); + return true; +} + + + + + +/* + * The optional delay is to avoid killing newbie brokers that have just + * been added and are still in the process of updating. This causes + * spurious, test-generated errors that scare everybody. + */ +void +killAllBrokers ( brokerVector & brokers, int delay ) +{ + if ( delay > 0 ) + { + std::cerr << "Killing all brokers after delay of " << delay << endl; + sleep ( delay ); + } + + for ( uint i = 0; i < brokers.size(); ++ i ) + try { brokers[i]->kill(9); } + catch ( const exception& error ) + { + std::cerr << "killAllBrokers Warning: exception during kill on broker " + << i + << " " + << error.what() + << endl; + } +} + + + + + +pid_t +runDeclareQueuesClient ( brokerVector brokers, + char const * host, + char const * path, + int verbosity, + int durable, + char const * queue_prefix, + int n_queues + ) +{ + string name("declareQueues"); + int port = brokers[0]->getPort ( ); + + if ( verbosity > 1 ) + cout << "startDeclareQueuesClient: host: " + << host + << " port: " + << port + << endl; + stringstream portSs; + portSs << port; + + vector<const char*> argv; + argv.push_back ( "declareQueues" ); + argv.push_back ( host ); + string portStr = portSs.str(); + argv.push_back ( portStr.c_str() ); + if ( durable ) + argv.push_back ( "1" ); + else + argv.push_back ( "0" ); + + argv.push_back ( queue_prefix ); + + char n_queues_str[20]; + sprintf ( n_queues_str, "%d", n_queues ); + argv.push_back ( n_queues_str ); + + argv.push_back ( 0 ); + pid_t pid = fork(); + + if ( ! pid ) { + execv ( path, const_cast<char * const *>(&argv[0]) ); + perror ( "error executing declareQueues: " ); + return 0; + } + + allMyChildren.add ( name, pid, DECLARING_CLIENT ); + return pid; +} + + + + + +pid_t +startReceivingClient ( brokerVector brokers, + char const * host, + char const * receiverPath, + char const * reportFrequency, + int verbosity, + char const * queue_name + ) +{ + string name("receiver"); + int port = brokers[0]->getPort ( ); + + if ( verbosity > 1 ) + cout << "startReceivingClient: port " << port << endl; + + // verbosity has to be > 1 to let clients talk. + int client_verbosity = (verbosity > 1 ) ? 1 : 0; + + char portStr[100]; + char verbosityStr[100]; + sprintf(portStr, "%d", port); + sprintf(verbosityStr, "%d", client_verbosity); + + + vector<const char*> argv; + argv.push_back ( "resumingReceiver" ); + argv.push_back ( host ); + argv.push_back ( portStr ); + argv.push_back ( reportFrequency ); + argv.push_back ( verbosityStr ); + argv.push_back ( queue_name ); + argv.push_back ( 0 ); + + pid_t pid = fork(); + pids.push_back ( pid ); + + if ( ! pid ) { + execv ( receiverPath, const_cast<char * const *>(&argv[0]) ); + perror ( "error executing receiver: " ); + return 0; + } + + allMyChildren.add ( name, pid, RECEIVING_CLIENT ); + return pid; +} + + + + + +pid_t +startSendingClient ( brokerVector brokers, + char const * host, + char const * senderPath, + char const * nMessages, + char const * reportFrequency, + int verbosity, + int durability, + char const * queue_name + ) +{ + string name("sender"); + int port = brokers[0]->getPort ( ); + + if ( verbosity > 1) + cout << "startSenderClient: port " << port << endl; + char portStr[100]; + char verbosityStr[100]; + // + // verbosity has to be > 1 to let clients talk. + int client_verbosity = (verbosity > 1 ) ? 1 : 0; + + sprintf ( portStr, "%d", port); + sprintf ( verbosityStr, "%d", client_verbosity); + + vector<const char*> argv; + argv.push_back ( "replayingSender" ); + argv.push_back ( host ); + argv.push_back ( portStr ); + argv.push_back ( nMessages ); + argv.push_back ( reportFrequency ); + argv.push_back ( verbosityStr ); + if ( durability ) + argv.push_back ( "1" ); + else + argv.push_back ( "0" ); + argv.push_back ( queue_name ); + argv.push_back ( 0 ); + + pid_t pid = fork(); + pids.push_back ( pid ); + + if ( ! pid ) { + execv ( senderPath, const_cast<char * const *>(&argv[0]) ); + perror ( "error executing sender: " ); + return 0; + } + + allMyChildren.add ( name, pid, SENDING_CLIENT ); + return pid; +} + + + +#define HUNKY_DORY 0 +#define BAD_ARGS 1 +#define CANT_FORK_DQ 2 +#define CANT_FORK_RECEIVER 3 +#define CANT_FORK_SENDER 4 +#define DQ_FAILED 5 +#define ERROR_ON_CHILD 6 +#define HANGING 7 +#define ERROR_KILLING_BROKER 8 + +}} // namespace qpid::tests + +using namespace qpid::tests; + +// If you want durability, use the "dir" option of "moduleOrDir" . +int +main ( int argc, char const ** argv ) +{ + int brokerKills = 0; + if ( argc != 11 ) { + cerr << "Usage: " + << argv[0] + << "moduleOrDir declareQueuesPath senderPath receiverPath nMessages reportFrequency verbosity durable n_queues n_brokers" + << endl; + cerr << "\tverbosity is an integer, durable is 0 or 1\n"; + return BAD_ARGS; + } + signal ( SIGCHLD, childExit ); + + int i = 1; + char const * moduleOrDir = argv[i++]; + char const * declareQueuesPath = argv[i++]; + char const * senderPath = argv[i++]; + char const * receiverPath = argv[i++]; + char const * nMessages = argv[i++]; + char const * reportFrequency = argv[i++]; + int verbosity = atoi(argv[i++]); + int durable = atoi(argv[i++]); + int n_queues = atoi(argv[i++]); + int n_brokers = atoi(argv[i++]); + + char const * host = "127.0.0.1"; + + allMyChildren.verbosity = verbosity; + + string clusterName; + + srand ( getpid() ); + + makeClusterName ( clusterName ); + + brokerVector brokers; + + if ( verbosity > 1 ) + cout << "Starting initial cluster...\n"; + + for ( int i = 0; i < n_brokers; ++ i ) { + startNewBroker ( brokers, + moduleOrDir, + clusterName, + verbosity, + durable ); + } + + + if ( verbosity > 0 ) + printBrokers ( brokers ); + + // Get prefix for each queue name. + stringstream queue_prefix; + queue_prefix << "failover_soak_" << getpid(); + string queue_prefix_str(queue_prefix.str()); + + // Run the declareQueues child. + int childStatus; + pid_t dqClientPid = + runDeclareQueuesClient ( brokers, + host, + declareQueuesPath, + verbosity, + durable, + queue_prefix_str.c_str(), + n_queues + ); + if ( -1 == dqClientPid ) { + cerr << "END_OF_TEST ERROR_START_DECLARE_1\n"; + return CANT_FORK_DQ; + } + + // Don't continue until declareQueues is finished. + pid_t retval = waitpid ( dqClientPid, & childStatus, 0); + if ( retval != dqClientPid) { + cerr << "END_OF_TEST ERROR_START_DECLARE_2\n"; + return DQ_FAILED; + } + allMyChildren.exited ( dqClientPid, childStatus ); + + + /* + Start one receiving and one sending client for each queue. + */ + for ( int i = 0; i < n_queues; ++ i ) { + + stringstream queue_name; + queue_name << queue_prefix.str() << '_' << i; + string queue_name_str(queue_name.str()); + + // Receiving client --------------------------- + pid_t receivingClientPid = + startReceivingClient ( brokers, + host, + receiverPath, + reportFrequency, + verbosity, + queue_name_str.c_str() ); + if ( -1 == receivingClientPid ) { + cerr << "END_OF_TEST ERROR_START_RECEIVER\n"; + return CANT_FORK_RECEIVER; + } + + + // Sending client --------------------------- + pid_t sendingClientPid = + startSendingClient ( brokers, + host, + senderPath, + nMessages, + reportFrequency, + verbosity, + durable, + queue_name_str.c_str() ); + if ( -1 == sendingClientPid ) { + cerr << "END_OF_TEST ERROR_START_SENDER\n"; + return CANT_FORK_SENDER; + } + } + + + int minSleep = 2, + maxSleep = 6; + + int totalBrokers = n_brokers; + + int loop = 0; + + while ( 1 ) + { + ++ loop; + + /* + if ( verbosity > 1 ) + std::cerr << "------- loop " << loop << " --------\n"; + + if ( verbosity > 0 ) + cout << totalBrokers << " brokers have been added to the cluster.\n\n\n"; + */ + + // Sleep for a while. ------------------------- + int sleepyTime = mrand ( minSleep, maxSleep ); + sleep ( sleepyTime ); + + int bullet = mrand ( 100 ); + if ( bullet >= 95 ) + { + fprintf ( stderr, "Killing oldest broker...\n" ); + + // Kill the oldest broker. -------------------------- + if ( ! killFrontBroker ( brokers, verbosity ) ) + { + allMyChildren.killEverybody(); + killAllBrokers ( brokers, 5 ); + std::cerr << "END_OF_TEST ERROR_BROKER\n"; + return ERROR_KILLING_BROKER; + } + ++ brokerKills; + + // Start a new broker. -------------------------- + if ( verbosity > 0 ) + cout << "Starting new broker.\n\n"; + + startNewBroker ( brokers, + moduleOrDir, + clusterName, + verbosity, + durable ); + ++ totalBrokers; + printBrokers ( brokers ); + cerr << brokerKills << " brokers have been killed.\n\n\n"; + } + + int retval = allMyChildren.checkChildren(); + if ( retval ) + { + std::cerr << "END_OF_TEST ERROR_CLIENT\n"; + allMyChildren.killEverybody(); + killAllBrokers ( brokers, 5 ); + return ERROR_ON_CHILD; + } + + // If all children have exited, quit. + int unfinished = allMyChildren.unfinished(); + if ( unfinished == 0 ) { + killAllBrokers ( brokers, 5 ); + + if ( verbosity > 1 ) + cout << "failoverSoak: all children have exited.\n"; + + std::cerr << "END_OF_TEST SUCCESSFUL\n"; + return HUNKY_DORY; + } + + } + + allMyChildren.killEverybody(); + killAllBrokers ( brokers, 5 ); + + std::cerr << "END_OF_TEST SUCCESSFUL\n"; + + return HUNKY_DORY; +} + + + diff --git a/qpid/cpp/src/tests/fanout_perftest b/qpid/cpp/src/tests/fanout_perftest new file mode 100755 index 0000000000..d8a7661f49 --- /dev/null +++ b/qpid/cpp/src/tests/fanout_perftest @@ -0,0 +1,22 @@ +#!/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. +# + +exec `dirname $0`/run_perftest 10000 --mode fanout --npubs 16 --nsubs 16 --size 64 diff --git a/qpid/cpp/src/tests/federated_cluster_test b/qpid/cpp/src/tests/federated_cluster_test new file mode 100755 index 0000000000..70bec5e703 --- /dev/null +++ b/qpid/cpp/src/tests/federated_cluster_test @@ -0,0 +1,152 @@ +#!/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. +# + +# Test reliability of the replication feature in the face of link +# failures: +srcdir=`dirname $0` +source ./test_env.sh + +trap stop_brokers EXIT + +fail() { + echo $1 + exit 1 +} + +stop_brokers() { + if [[ $BROKER_A ]] ; then + ../qpidd --no-module-dir -q --port $BROKER_A + unset BROKER_A + fi + if [[ $NODE_1 ]] ; then + ../qpidd --no-module-dir -q --port $NODE_1 + unset NODE_1 + fi + if [[ $NODE_2 ]] ; then + ../qpidd --no-module-dir -q --port $NODE_2 + unset NODE_2 + fi + if [ -f cluster.ports ]; then + rm cluster.ports + fi +} + +start_brokers() { + #start single node... + BROKER_A=`../qpidd --daemon --port 0 --no-data-dir --no-module-dir --auth no --log-enable info+` || fail "BROKER_A failed to start" + + #...and start cluster + $srcdir/start_cluster 2 || fail "Could not start cluster" + NODE_1=$(head -1 cluster.ports) + NODE_2=$(tail -1 cluster.ports) + test -n "$NODE_1" || fail "NODE_1 failed to start" + test -n "$NODE_2" || fail "NODE_2 failed to start" +} + +setup() { + #create exchange on both cluster and single broker + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_A" add exchange direct test-exchange + $PYTHON_COMMANDS/qpid-config -a "localhost:$NODE_1" add exchange direct test-exchange + + #create dynamic routes for test exchange + $PYTHON_COMMANDS/qpid-route dynamic add "localhost:$NODE_2" "localhost:$BROKER_A" test-exchange + $PYTHON_COMMANDS/qpid-route dynamic add "localhost:$BROKER_A" "localhost:$NODE_2" test-exchange + + #create test queue on cluster and bind it to the test exchange + $PYTHON_COMMANDS/qpid-config -a "localhost:$NODE_1" add queue test-queue + $PYTHON_COMMANDS/qpid-config -a "localhost:$NODE_1" bind test-exchange test-queue to-cluster + + #create test queue on single broker and bind it to the test exchange + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_A" add queue test-queue + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_A" bind test-exchange test-queue from-cluster +} + +run_test_pull_to_cluster_two_consumers() { + #start consumers on each of the two nodes of the cluster + ./receiver --port $NODE_1 --queue test-queue --credit-window 1 > fed1.out.tmp & + ./receiver --port $NODE_2 --queue test-queue --credit-window 1 > fed2.out.tmp & + + #send stream of messages to test exchange on single broker + for i in `seq 1 1000`; do echo Message $i >> fed.in.tmp; done + ./sender --port $BROKER_A --exchange test-exchange --routing-key to-cluster --send-eos 2 < fed.in.tmp + + #combine output of the two consumers, sort it and compare with the expected stream + wait + sort -g -k 2 fed1.out.tmp fed2.out.tmp > fed.out.tmp + diff fed.in.tmp fed.out.tmp || fail "federated link to cluster failed: expectations not met!" + + rm -f fed*.tmp #cleanup +} + +run_test_pull_to_cluster() { + #send stream of messages to test exchange on single broker + for i in `seq 1 1000`; do echo Message $i >> fed.in.tmp; done + ./sender --port $BROKER_A --exchange test-exchange --routing-key to-cluster --send-eos 1 < fed.in.tmp + + #consume from remaining node of the cluster + ./receiver --port $NODE_2 --queue test-queue > fed.out.tmp + + #verify all messages are received + diff fed.in.tmp fed.out.tmp || fail "federated link to cluster failed: expectations not met!" + + rm -f fed*.tmp #cleanup +} + +run_test_pull_from_cluster() { + #start consumer on single broker + ./receiver --port $BROKER_A --queue test-queue --credit-window 1 > fed.out.tmp & + + #send stream of messages to test exchange on cluster + for i in `seq 1 1000`; do echo Message $i >> fed.in.tmp; done + ./sender --port $NODE_2 --exchange test-exchange --routing-key from-cluster --send-eos 1 < fed.in.tmp + + #verify all messages are received + wait + diff fed.in.tmp fed.out.tmp || fail "federated link from cluster failed: expectations not met!" + + rm -f fed*.tmp #cleanup +} + + +if test -d ${PYTHON_DIR}; then + . $srcdir/ais_check + + rm -f fed*.tmp #cleanup any files left from previous run + start_brokers + echo "brokers started" + setup + echo "setup completed" + run_test_pull_to_cluster_two_consumers + echo "federated link to cluster verified" + run_test_pull_from_cluster + echo "federated link from cluster verified" + if [[ $TEST_NODE_FAILURE ]] ; then + #kill first cluster node and retest + kill -9 $(../qpidd --check --port $NODE_1) && unset NODE_1 + echo "killed first cluster node; waiting for links to re-establish themselves..." + sleep 5 + echo "retesting..." + run_test_pull_to_cluster + echo "federated link to cluster verified" + run_test_pull_from_cluster + echo "federated link from cluster verified" + fi +fi diff --git a/qpid/cpp/src/tests/federated_cluster_test_with_node_failure b/qpid/cpp/src/tests/federated_cluster_test_with_node_failure new file mode 100755 index 0000000000..f144a676de --- /dev/null +++ b/qpid/cpp/src/tests/federated_cluster_test_with_node_failure @@ -0,0 +1,23 @@ +#!/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. +# + +srcdir=`dirname $0` +TEST_NODE_FAILURE=1 $srcdir/federated_cluster_test diff --git a/qpid/cpp/src/tests/federated_topic_test b/qpid/cpp/src/tests/federated_topic_test new file mode 100755 index 0000000000..b1063c7e8c --- /dev/null +++ b/qpid/cpp/src/tests/federated_topic_test @@ -0,0 +1,129 @@ +#!/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 the topic test on a federated setup + +# Clean up old log files +rm -f subscriber_*.log + +# Defaults values +SUBSCRIBERS=2 +MESSAGES=1000 +BATCHES=1 +VERBOSE=1 + +while getopts "s:m:b:" opt ; do + case $opt in + s) SUBSCRIBERS=$OPTARG ;; + m) MESSAGES=$OPTARG ;; + b) BATCHES=$OPTARG ;; + ?) + echo "Usage: %0 [-s <subscribers>] [-m <messages.] [-b <batches>]" + exit 1 + ;; + 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 +} + +start_brokers() { + start_broker + PORT_A=`cat qpidd.port` + start_broker + PORT_B=`cat qpidd.port` + start_broker + PORT_C=`cat qpidd.port` +} + +stop_brokers() { + for p in $PORT_A $PORT_B $PORT_C; do + $QPIDD_EXEC --no-module-dir -q --port $p + done +} + +subscribe() { + #which broker should we connect to? + if (( $1 % 2 )); then + MY_PORT=$PORT_C; + else + MY_PORT=$PORT_A; + fi + + echo Subscriber $1 connecting on $MY_PORT + LOG="subscriber_$1.log" + ${MY_DIR}/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 +} + +setup_routes() { + BROKER_A="localhost:$PORT_A" + BROKER_B="localhost:$PORT_B" + BROKER_C="localhost:$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 + 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 + 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 + 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 + done + fi +} + +if test -d ${PYTHON_DIR} ; then + start_brokers + if (($VERBOSE)); then + echo "Running federated topic test against brokers on ports $PORT_A $PORT_B $PORT_C" + fi + + for ((i=$SUBSCRIBERS ; i--; )); do + subscribe $i & + done + + setup_routes + + publish || exit 1 +fi diff --git a/qpid/cpp/src/tests/federation.py b/qpid/cpp/src/tests/federation.py new file mode 100755 index 0000000000..201b06a4a2 --- /dev/null +++ b/qpid/cpp/src/tests/federation.py @@ -0,0 +1,2091 @@ +#!/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.datatypes import Message +from qpid.queue import Empty +from qpid.util import URL +from time import sleep, time + + +class _FedBroker(object): + """ + A proxy object for a remote broker. Contains connection and management + state. + """ + def __init__(self, host, port, + conn=None, session=None, qmf_broker=None): + self.host = host + self.port = port + self.url = "%s:%d" % (host, port) + self.client_conn = None + self.client_session = None + self.qmf_broker = None + self.qmf_object = None + if conn is not None: + self.client_conn = conn + if session is not None: + self.client_session = session + if qmf_broker is not None: + self.qmf_broker = qmf_broker + + +class FederationTests(TestBase010): + + def remote_host(self): + return self.defines.get("remote-host", "localhost") + + def remote_port(self): + return int(self.defines["remote-port"]) + + def verify_cleanup(self): + attempts = 0 + total = len(self.qmf.getObjects(_class="bridge")) + len(self.qmf.getObjects(_class="link")) + while total > 0: + attempts += 1 + if attempts >= 10: + self.fail("Bridges and links didn't clean up") + return + sleep(1) + total = len(self.qmf.getObjects(_class="bridge")) + len(self.qmf.getObjects(_class="link")) + + def _setup_brokers(self): + ports = [self.remote_port()] + extra = self.defines.get("extra-brokers") + if extra: + for p in extra.split(): + ports.append(int(p)) + + # broker[0] has already been set up. + self._brokers = [_FedBroker(self.broker.host, + self.broker.port, + self.conn, + self.session, + self.qmf_broker)] + self._brokers[0].qmf_object = self.qmf.getObjects(_class="broker")[0] + + # setup remaining brokers + for _p in ports: + _b = _FedBroker(self.remote_host(), _p) + _b.client_conn = self.connect(host=self.remote_host(), port=_p) + _b.client_session = _b.client_conn.session("Fed_client_session_" + str(_p)) + _b.qmf_broker = self.qmf.addBroker(_b.url) + for _bo in self.qmf.getObjects(_class="broker"): + if _bo.getBroker().getUrl() == _b.qmf_broker.getUrl(): + _b.qmf_object = _bo + break + self._brokers.append(_b) + + def _teardown_brokers(self): + """ Un-does _setup_brokers() + """ + # broker[0] is configured at test setup, so it must remain configured + for _b in self._brokers[1:]: + self.qmf.delBroker(_b.qmf_broker) + if not _b.client_session.error(): + _b.client_session.close(timeout=10) + _b.client_conn.close(timeout=10) + + + def test_bridge_create_and_close(self): + 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) + + 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) + + bridge = qmf.getObjects(_class="bridge")[0] + result = bridge.close() + self.assertEqual(result.status, 0) + + result = link.close() + self.assertEqual(result.status, 0) + + self.verify_cleanup() + + def test_pull_from_exchange(self): + session = self.session + + 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) + + 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) + + bridge = qmf.getObjects(_class="bridge")[0] + + #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") + sleep(6) + + #send messages to remote broker and confirm it is routed to local broker + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_pull_from_exchange") + + for i in range(1, 11): + dp = r_session.delivery_properties(routing_key="my-key") + r_session.message_transfer(destination="amq.direct", message=Message(dp, "Message %d" % i)) + + for i in range(1, 11): + msg = queue.get(timeout=5) + self.assertEqual("Message %d" % i, msg.body) + 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) + + self.verify_cleanup() + + def test_push_to_exchange(self): + session = self.session + + 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) + + 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) + + bridge = qmf.getObjects(_class="bridge")[0] + + #setup queue to receive messages from remote broker + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_push_to_exchange") + r_session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + r_session.exchange_bind(queue="fed1", exchange="amq.fanout") + self.subscribe(session=r_session, queue="fed1", destination="f1") + queue = r_session.incoming("f1") + sleep(6) + + #send messages to local broker and confirm it is routed to remote broker + for i in range(1, 11): + dp = session.delivery_properties(routing_key="my-key") + session.message_transfer(destination="amq.direct", message=Message(dp, "Message %d" % i)) + + for i in range(1, 11): + msg = queue.get(timeout=5) + self.assertEqual("Message %d" % i, msg.body) + 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) + + self.verify_cleanup() + + def test_pull_from_queue(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") + 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) + + 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) + + bridge = qmf.getObjects(_class="bridge")[0] + sleep(3) + + #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) + + self.verify_cleanup() + + def test_tracing_automatic(self): + remoteUrl = "%s:%d" % (self.remote_host(), self.remote_port()) + self.startQmf() + l_broker = self.qmf_broker + r_broker = self.qmf.addBroker(remoteUrl) + + l_brokerObj = self.qmf.getObjects(_class="broker", _broker=l_broker)[0] + r_brokerObj = self.qmf.getObjects(_class="broker", _broker=r_broker)[0] + + l_res = l_brokerObj.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") + r_res = r_brokerObj.connect(self.broker.host, self.broker.port, False, "PLAIN", "guest", "guest", "tcp") + + self.assertEqual(l_res.status, 0) + self.assertEqual(r_res.status, 0) + + l_link = self.qmf.getObjects(_class="link", _broker=l_broker)[0] + r_link = self.qmf.getObjects(_class="link", _broker=r_broker)[0] + + l_res = l_link.bridge(False, "amq.direct", "amq.direct", "key", "", "", False, False, False, 0) + r_res = r_link.bridge(False, "amq.direct", "amq.direct", "key", "", "", False, False, False, 0) + + self.assertEqual(l_res.status, 0) + self.assertEqual(r_res.status, 0) + + count = 0 + while l_link.state != "Operational" or r_link.state != "Operational": + count += 1 + if count > 10: + self.fail("Fed links didn't become operational after 10 seconds") + sleep(1) + l_link = self.qmf.getObjects(_class="link", _broker=l_broker)[0] + r_link = self.qmf.getObjects(_class="link", _broker=r_broker)[0] + sleep(3) + + #setup queue to receive messages from local broker + session = self.session + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="amq.direct", binding_key="key") + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + #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_trace") + for i in range(1, 11): + dp = r_session.delivery_properties(routing_key="key") + r_session.message_transfer(destination="amq.direct", 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 + + def test_tracing(self): + session = self.session + + 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) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "amq.direct", "amq.fanout", "my-key", "my-bridge-id", + "exclude-me,also-exclude-me", False, False, False, 0) + self.assertEqual(result.status, 0) + bridge = qmf.getObjects(_class="bridge")[0] + + #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") + sleep(6) + + #send messages to remote broker and confirm it is routed to local broker + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_tracing") + + trace = [None, "exclude-me", "a,exclude-me,b", "also-exclude-me,c", "dont-exclude-me"] + body = ["yes", "first-bad", "second-bad", "third-bad", "yes"] + for b, t in zip(body, trace): + headers = {} + if (t): headers["x-qpid.trace"]=t + dp = r_session.delivery_properties(routing_key="my-key", ttl=1000*60*5) + mp = r_session.message_properties(application_headers=headers) + r_session.message_transfer(destination="amq.direct", message=Message(dp, mp, b)) + + for e in ["my-bridge-id", "dont-exclude-me,my-bridge-id"]: + msg = queue.get(timeout=5) + self.assertEqual("yes", msg.body) + self.assertEqual(e, self.getAppHeader(msg, "x-qpid.trace")) + assert(msg.get("delivery_properties").ttl > 0) + assert(msg.get("delivery_properties").ttl < 1000*60*50) + + 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) + + self.verify_cleanup() + + def test_dynamic_fanout(self): + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_fanout") + + session.exchange_declare(exchange="fed.fanout", type="fanout") + r_session.exchange_declare(exchange="fed.fanout", type="fanout") + + 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) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.fanout", "fed.fanout", "", "", "", False, False, True, 0) + self.assertEqual(result.status, 0) + bridge = qmf.getObjects(_class="bridge")[0] + sleep(5) + + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="fed.fanout") + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + for i in range(1, 11): + dp = r_session.delivery_properties() + r_session.message_transfer(destination="fed.fanout", message=Message(dp, "Message %d" % i)) + + for i in range(1, 11): + msg = queue.get(timeout=5) + self.assertEqual("Message %d" % i, msg.body) + 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) + + self.verify_cleanup() + + + def test_dynamic_direct(self): + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_direct") + + session.exchange_declare(exchange="fed.direct", type="direct") + r_session.exchange_declare(exchange="fed.direct", type="direct") + + 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) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.direct", "fed.direct", "", "", "", False, False, True, 0) + self.assertEqual(result.status, 0) + bridge = qmf.getObjects(_class="bridge")[0] + sleep(5) + + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="fed.direct", binding_key="fd-key") + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + for i in range(1, 11): + dp = r_session.delivery_properties(routing_key="fd-key") + r_session.message_transfer(destination="fed.direct", message=Message(dp, "Message %d" % i)) + + for i in range(1, 11): + msg = queue.get(timeout=5) + self.assertEqual("Message %d" % i, msg.body) + 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) + + self.verify_cleanup() + + def test_dynamic_topic(self): + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_topic") + + session.exchange_declare(exchange="fed.topic", type="topic") + r_session.exchange_declare(exchange="fed.topic", type="topic") + + 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) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.topic", "fed.topic", "", "", "", False, False, True, 0) + self.assertEqual(result.status, 0) + bridge = qmf.getObjects(_class="bridge")[0] + sleep(5) + + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="fed.topic", binding_key="ft-key.#") + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + for i in range(1, 11): + dp = r_session.delivery_properties(routing_key="ft-key.one.two") + r_session.message_transfer(destination="fed.topic", message=Message(dp, "Message %d" % i)) + + for i in range(1, 11): + msg = queue.get(timeout=5) + self.assertEqual("Message %d" % i, msg.body) + 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) + + self.verify_cleanup() + + def test_dynamic_topic_reorigin(self): + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_topic_reorigin") + + session.exchange_declare(exchange="fed.topic_reorigin", type="topic") + r_session.exchange_declare(exchange="fed.topic_reorigin", type="topic") + + session.exchange_declare(exchange="fed.topic_reorigin_2", type="topic") + r_session.exchange_declare(exchange="fed.topic_reorigin_2", type="topic") + + 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) + + session.queue_declare(queue="fed2", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed2", exchange="fed.topic_reorigin_2", binding_key="ft-key.one.#") + self.subscribe(queue="fed2", destination="f2") + queue2 = session.incoming("f2") + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.topic_reorigin", "fed.topic_reorigin", "", "", "", False, False, True, 0) + self.assertEqual(result.status, 0) + result = link.bridge(False, "fed.topic_reorigin_2", "fed.topic_reorigin_2", "", "", "", False, False, True, 0) + self.assertEqual(result.status, 0) + + bridge = qmf.getObjects(_class="bridge")[0] + bridge2 = qmf.getObjects(_class="bridge")[1] + sleep(5) + + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="fed.topic_reorigin", binding_key="ft-key.#") + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + for i in range(1, 11): + dp = r_session.delivery_properties(routing_key="ft-key.one.two") + r_session.message_transfer(destination="fed.topic_reorigin", message=Message(dp, "Message %d" % i)) + + for i in range(1, 11): + msg = queue.get(timeout=5) + self.assertEqual("Message %d" % i, msg.body) + 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 = bridge2.close() + self.assertEqual(result.status, 0) + + # extra check: verify we don't leak bridge objects - keep the link + # around and verify the bridge count has gone to zero + + attempts = 0 + bridgeCount = len(qmf.getObjects(_class="bridge")) + while bridgeCount > 0: + attempts += 1 + if attempts >= 5: + self.fail("Bridges didn't clean up") + return + sleep(1) + bridgeCount = len(qmf.getObjects(_class="bridge")) + + result = link.close() + self.assertEqual(result.status, 0) + + self.verify_cleanup() + + def test_dynamic_direct_reorigin(self): + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_direct_reorigin") + + session.exchange_declare(exchange="fed.direct_reorigin", type="direct") + r_session.exchange_declare(exchange="fed.direct_reorigin", type="direct") + + session.exchange_declare(exchange="fed.direct_reorigin_2", type="direct") + r_session.exchange_declare(exchange="fed.direct_reorigin_2", type="direct") + + 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) + + session.queue_declare(queue="fed2", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed2", exchange="fed.direct_reorigin_2", binding_key="ft-key.two") + self.subscribe(queue="fed2", destination="f2") + queue2 = session.incoming("f2") + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.direct_reorigin", "fed.direct_reorigin", "", "", "", False, False, True, 0) + self.assertEqual(result.status, 0) + result = link.bridge(False, "fed.direct_reorigin_2", "fed.direct_reorigin_2", "", "", "", False, False, True, 0) + self.assertEqual(result.status, 0) + + bridge = qmf.getObjects(_class="bridge")[0] + bridge2 = qmf.getObjects(_class="bridge")[1] + sleep(5) + + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="fed.direct_reorigin", binding_key="ft-key.one") + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + for i in range(1, 11): + dp = r_session.delivery_properties(routing_key="ft-key.one") + r_session.message_transfer(destination="fed.direct_reorigin", message=Message(dp, "Message %d" % i)) + + for i in range(1, 11): + msg = queue.get(timeout=5) + self.assertEqual("Message %d" % i, msg.body) + 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) + + # Extra test: don't explicitly close() bridge2. When the link is closed, + # it should clean up bridge2 automagically. verify_cleanup() will detect + # if bridge2 isn't cleaned up and will fail the test. + # + #result = bridge2.close() + #self.assertEqual(result.status, 0) + result = link.close() + self.assertEqual(result.status, 0) + + self.verify_cleanup() + + def test_dynamic_headers(self): + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_headers") + + session.exchange_declare(exchange="fed.headers", type="headers") + r_session.exchange_declare(exchange="fed.headers", type="headers") + + 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) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.headers", "fed.headers", "", "", "", False, False, True, 0) + self.assertEqual(result.status, 0) + bridge = qmf.getObjects(_class="bridge")[0] + 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'}) + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + props = r_session.message_properties(application_headers={'class':'first'}) + for i in range(1, 11): + r_session.message_transfer(destination="fed.headers", message=Message(props, "Message %d" % i)) + + for i in range(1, 11): + msg = queue.get(timeout=5) + content = msg.body + self.assertEqual("Message %d" % i, msg.body) + 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) + + self.verify_cleanup() + + def test_dynamic_headers_reorigin(self): + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_headers_reorigin") + + session.exchange_declare(exchange="fed.headers_reorigin", type="headers") + r_session.exchange_declare(exchange="fed.headers_reorigin", type="headers") + + session.exchange_declare(exchange="fed.headers_reorigin_2", type="headers") + r_session.exchange_declare(exchange="fed.headers_reorigin_2", type="headers") + + 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) + + session.queue_declare(queue="fed2", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed2", exchange="fed.headers_reorigin_2", binding_key="key2", arguments={'x-match':'any', 'class':'second'}) + self.subscribe(queue="fed2", destination="f2") + queue2 = session.incoming("f2") + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.headers_reorigin", "fed.headers_reorigin", "", "", "", False, False, True, 0) + self.assertEqual(result.status, 0) + result = link.bridge(False, "fed.headers_reorigin_2", "fed.headers_reorigin_2", "", "", "", False, False, True, 0) + self.assertEqual(result.status, 0) + + bridge = qmf.getObjects(_class="bridge")[0] + bridge2 = qmf.getObjects(_class="bridge")[1] + sleep(5) + + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="fed.headers_reorigin", binding_key="key1", arguments={'x-match':'any', 'class':'first'}) + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + props = r_session.message_properties(application_headers={'class':'first'}) + for i in range(1, 11): + r_session.message_transfer(destination="fed.headers_reorigin", message=Message(props, "Message %d" % i)) + + for i in range(1, 11): + msg = queue.get(timeout=5) + self.assertEqual("Message %d" % i, msg.body) + 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) + + # Extra test: don't explicitly close() bridge2. When the link is closed, + # it should clean up bridge2 automagically. verify_cleanup() will detect + # if bridge2 isn't cleaned up and will fail the test. + # + #result = bridge2.close() + #self.assertEqual(result.status, 0) + result = link.close() + self.assertEqual(result.status, 0) + + self.verify_cleanup() + + def test_dynamic_headers_unbind(self): + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_headers_unbind") + + session.exchange_declare(exchange="fed.headers_unbind", type="headers") + r_session.exchange_declare(exchange="fed.headers_unbind", type="headers") + + 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) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.headers_unbind", "fed.headers_unbind", "", "", "", False, False, True, 0) + self.assertEqual(result.status, 0) + bridge = qmf.getObjects(_class="bridge")[0] + sleep(5) + + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + queue = qmf.getObjects(_class="queue", name="fed1")[0] + queue.update() + self.assertEqual(queue.bindingCount, 1, + "bindings not accounted for (expected 1, got %d)" % queue.bindingCount) + + session.exchange_bind(queue="fed1", exchange="fed.headers_unbind", binding_key="key1", arguments={'x-match':'any', 'class':'first'}) + queue.update() + self.assertEqual(queue.bindingCount, 2, + "bindings not accounted for (expected 2, got %d)" % queue.bindingCount) + + session.exchange_unbind(queue="fed1", exchange="fed.headers_unbind", binding_key="key1") + queue.update() + self.assertEqual(queue.bindingCount, 1, + "bindings not accounted for (expected 1, got %d)" % queue.bindingCount) + + result = bridge.close() + self.assertEqual(result.status, 0) + result = link.close() + self.assertEqual(result.status, 0) + + self.verify_cleanup() + + + def test_dynamic_headers_xml(self): + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_headers_xml") + + session.exchange_declare(exchange="fed.xml", type="xml") + r_session.exchange_declare(exchange="fed.xml", type="xml") + + 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) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.xml", "fed.xml", "", "", "", False, False, True, 0) + + self.assertEqual(result.status, 0) + bridge = qmf.getObjects(_class="bridge")[0] + sleep(5) + + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="fed.xml", binding_key="key1", arguments={'xquery':'true()'}) + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + props = r_session.delivery_properties(routing_key="key1") + for i in range(1, 11): + r_session.message_transfer(destination="fed.xml", message=Message(props, "Message %d" % i)) + + for i in range(1, 11): + msg = queue.get(timeout=5) + content = msg.body + self.assertEqual("Message %d" % i, msg.body) + 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) + + self.verify_cleanup() + + def test_dynamic_headers_reorigin_xml(self): + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_headers_reorigin_xml") + + session.exchange_declare(exchange="fed.xml_reorigin", type="xml") + r_session.exchange_declare(exchange="fed.xml_reorigin", type="xml") + + session.exchange_declare(exchange="fed.xml_reorigin_2", type="xml") + r_session.exchange_declare(exchange="fed.xml_reorigin_2", type="xml") + + 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) + + session.queue_declare(queue="fed2", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed2", exchange="fed.xml_reorigin_2", binding_key="key2", arguments={'xquery':'true()'}) + self.subscribe(queue="fed2", destination="f2") + queue2 = session.incoming("f2") + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.xml_reorigin", "fed.xml_reorigin", "", "", "", False, False, True, 0) + + self.assertEqual(result.status, 0) + result = link.bridge(False, "fed.xml_reorigin_2", "fed.xml_reorigin_2", "", "", "", False, False, True, 0) + self.assertEqual(result.status, 0) + + bridge = qmf.getObjects(_class="bridge")[0] + bridge2 = qmf.getObjects(_class="bridge")[1] + sleep(5) + + foo=qmf.getObjects(_class="link") + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="fed.xml_reorigin", binding_key="key1", arguments={'xquery':'true()'}) + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + props = r_session.delivery_properties(routing_key="key1") + for i in range(1, 11): + r_session.message_transfer(destination="fed.xml_reorigin", message=Message(props, "Message %d" % i)) + + for i in range(1, 11): + msg = queue.get(timeout=5) + self.assertEqual("Message %d" % i, msg.body) + 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) + + # Extra test: don't explicitly close() bridge2. When the link is closed, + # it should clean up bridge2 automagically. verify_cleanup() will detect + # if bridge2 isn't cleaned up and will fail the test. + # + #result = bridge2.close() + #self.assertEqual(result.status, 0) + result = link.close() + self.assertEqual(result.status, 0) + + self.verify_cleanup() + + def test_dynamic_headers_unbind_xml(self): + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_xml_unbind") + + session.exchange_declare(exchange="fed.xml_unbind", type="xml") + r_session.exchange_declare(exchange="fed.xml_unbind", type="xml") + + 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) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.xml_unbind", "fed.xml_unbind", "", "", "", False, False, True, 0) + + self.assertEqual(result.status, 0) + bridge = qmf.getObjects(_class="bridge")[0] + sleep(5) + + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + queue = qmf.getObjects(_class="queue", name="fed1")[0] + queue.update() + self.assertEqual(queue.bindingCount, 1, + "bindings not accounted for (expected 1, got %d)" % queue.bindingCount) + + session.exchange_bind(queue="fed1", exchange="fed.xml_unbind", binding_key="key1", arguments={'xquery':'true()'}) + queue.update() + self.assertEqual(queue.bindingCount, 2, + "bindings not accounted for (expected 2, got %d)" % queue.bindingCount) + + session.exchange_unbind(queue="fed1", exchange="fed.xml_unbind", binding_key="key1") + queue.update() + self.assertEqual(queue.bindingCount, 1, + "bindings not accounted for (expected 1, got %d)" % queue.bindingCount) + + result = bridge.close() + self.assertEqual(result.status, 0) + result = link.close() + self.assertEqual(result.status, 0) + + self.verify_cleanup() + + + def test_dynamic_topic_nodup(self): + """Verify that a message whose routing key matches more than one + binding does not get duplicated to the same queue. + """ + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_topic_nodup") + + session.exchange_declare(exchange="fed.topic", type="topic") + r_session.exchange_declare(exchange="fed.topic", type="topic") + + 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) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.topic", "fed.topic", "", "", "", False, False, True, 0) + self.assertEqual(result.status, 0) + bridge = qmf.getObjects(_class="bridge")[0] + sleep(5) + + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="fed.topic", binding_key="red.*") + session.exchange_bind(queue="fed1", exchange="fed.topic", binding_key="*.herring") + + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + for i in range(1, 11): + dp = r_session.delivery_properties(routing_key="red.herring") + r_session.message_transfer(destination="fed.topic", message=Message(dp, "Message %d" % i)) + + for i in range(1, 11): + msg = queue.get(timeout=5) + self.assertEqual("Message %d" % i, msg.body) + 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) + + self.verify_cleanup() + + + def test_dynamic_direct_route_prop(self): + """ Set up a tree of uni-directional routes across the direct exchange. + Bind the same key to the same queues on the leaf nodes. Verify a + message sent with the routing key transverses the tree an arrives at + each leaf. Remove one leaf's queue, and verify that messages still + reach the other leaf. + + Route Topology: + + +---> B2 queue:"test-queue", binding key:"spudboy" + B0 --> B1 --+ + +---> B3 queue:"test-queue", binding key:"spudboy" + """ + session = self.session + + # create the federation + + self.startQmf() + qmf = self.qmf + + self._setup_brokers() + + # create direct exchange on each broker, and retrieve the corresponding + # management object for that exchange + + exchanges=[] + for _b in self._brokers: + _b.client_session.exchange_declare(exchange="fedX.direct", type="direct") + self.assertEqual(_b.client_session.exchange_query(name="fedX.direct").type, + "direct", "exchange_declare failed!") + # pull the exchange out of qmf... + retries = 0 + my_exchange = None + while my_exchange is None: + objs = qmf.getObjects(_broker=_b.qmf_broker, _class="exchange") + for ooo in objs: + if ooo.name == "fedX.direct": + my_exchange = ooo + break + if my_exchange is None: + retries += 1 + self.failIfEqual(retries, 10, + "QMF failed to find new exchange!") + sleep(1) + exchanges.append(my_exchange) + + self.assertEqual(len(exchanges), len(self._brokers), "Exchange creation failed!") + + # 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 --> B2 + result = self._brokers[2].qmf_object.connect(self._brokers[1].host, + self._brokers[1].port, + False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + # connect B1 --> B3 + result = self._brokers[3].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.direct" 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.direct", # src + "fedX.direct", # dst + "", # key + "", # tag + "", # excludes + False, # srcIsQueue + False, # srcIsLocal + True, # dynamic + 0) # sync + self.assertEqual(result.status, 0) + + # wait for the inter-broker links to become operational + retries = 0 + operational = False + while not operational: + 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 + if not operational: + retries += 1 + self.failIfEqual(retries, 10, + "inter-broker links failed to become operational.") + sleep(1) + + # @todo - There is no way to determine when the bridge objects become + # active. Hopefully, this is long enough! + sleep(6) + + # create a queue on B2, bound to "spudboy" + self._brokers[2].client_session.queue_declare(queue="fedX1", exclusive=True, auto_delete=True) + self._brokers[2].client_session.exchange_bind(queue="fedX1", exchange="fedX.direct", binding_key="spudboy") + + # create a queue on B3, bound to "spudboy" + self._brokers[3].client_session.queue_declare(queue="fedX1", exclusive=True, auto_delete=True) + self._brokers[3].client_session.exchange_bind(queue="fedX1", exchange="fedX.direct", binding_key="spudboy") + + # subscribe to messages arriving on B2's queue + self.subscribe(self._brokers[2].client_session, queue="fedX1", destination="f1") + queue_2 = self._brokers[2].client_session.incoming("f1") + + # subscribe to messages arriving on B3's queue + self.subscribe(self._brokers[3].client_session, queue="fedX1", destination="f1") + queue_3 = self._brokers[3].client_session.incoming("f1") + + # wait until the binding key has propagated to each broker (twice at + # broker B1). Work backwards from binding brokers. + + binding_counts = [1, 2, 1, 1] + self.assertEqual(len(binding_counts), len(exchanges), "Update Test!") + for i in range(3,-1,-1): + retries = 0 + exchanges[i].update() + while exchanges[i].bindingCount < binding_counts[i]: + retries += 1 + self.failIfEqual(retries, 10, + "binding failed to propagate to broker %d" + % i) + sleep(3) + exchanges[i].update() + + # send 10 msgs from B0 + for i in range(1, 11): + dp = self._brokers[0].client_session.delivery_properties(routing_key="spudboy") + self._brokers[0].client_session.message_transfer(destination="fedX.direct", message=Message(dp, "Message_drp %d" % i)) + + # wait for 10 messages to be forwarded from B0->B1, + # 10 messages from B1->B2, + # and 10 messages from B1->B3 + retries = 0 + for ex in exchanges: + ex.update() + while (exchanges[0].msgReceives != 10 or exchanges[0].msgRoutes != 10 or + exchanges[1].msgReceives != 10 or exchanges[1].msgRoutes != 20 or + exchanges[2].msgReceives != 10 or exchanges[2].msgRoutes != 10 or + exchanges[3].msgReceives != 10 or exchanges[3].msgRoutes != 10): + retries += 1 + self.failIfEqual(retries, 10, + "federation failed to route msgs %d:%d %d:%d %d:%d %d:%d" + % (exchanges[0].msgReceives, + exchanges[0].msgRoutes, + exchanges[1].msgReceives, + exchanges[1].msgRoutes, + exchanges[2].msgReceives, + exchanges[2].msgRoutes, + exchanges[3].msgReceives, + exchanges[3].msgRoutes)) + sleep(1) + for ex in exchanges: + ex.update() + + # get exactly 10 msgs on B2 and B3 + for i in range(1, 11): + msg = queue_2.get(timeout=5) + self.assertEqual("Message_drp %d" % i, msg.body) + msg = queue_3.get(timeout=5) + self.assertEqual("Message_drp %d" % i, msg.body) + + try: + extra = queue_2.get(timeout=1) + self.fail("Got unexpected message in queue_2: " + extra.body) + except Empty: None + + try: + extra = queue_3.get(timeout=1) + self.fail("Got unexpected message in queue_3: " + extra.body) + except Empty: None + + + # tear down the queue on B2 + self._brokers[2].client_session.exchange_unbind(queue="fedX1", exchange="fedX.direct", binding_key="spudboy") + self._brokers[2].client_session.message_cancel(destination="f1") + self._brokers[2].client_session.queue_delete(queue="fedX1") + + # @todo - restore code when QPID-2499 fixed!! + sleep(6) + # wait for the binding count on B1 to drop from 2 to 1 + retries = 0 + exchanges[1].update() + while exchanges[1].bindingCount != 1: + retries += 1 + self.failIfEqual(retries, 10, + "unbinding failed to propagate to broker B1: %d" + % exchanges[1].bindingCount) + sleep(1) + exchanges[1].update() + + # send 10 msgs from B0 + for i in range(11, 21): + dp = self._brokers[0].client_session.delivery_properties(routing_key="spudboy") + self._brokers[0].client_session.message_transfer(destination="fedX.direct", message=Message(dp, "Message_drp %d" % i)) + + # verify messages are forwarded to B3 only + retries = 0 + for ex in exchanges: + ex.update() + while (exchanges[0].msgReceives != 20 or exchanges[0].msgRoutes != 20 or + exchanges[1].msgReceives != 20 or exchanges[1].msgRoutes != 30 or + exchanges[2].msgReceives != 10 or exchanges[2].msgRoutes != 10 or + exchanges[3].msgReceives != 20 or exchanges[3].msgRoutes != 20): + retries += 1 + self.failIfEqual(retries, 10, + "federation failed to route more msgs %d:%d %d:%d %d:%d %d:%d" + % (exchanges[0].msgReceives, + exchanges[0].msgRoutes, + exchanges[1].msgReceives, + exchanges[1].msgRoutes, + exchanges[2].msgReceives, + exchanges[2].msgRoutes, + exchanges[3].msgReceives, + exchanges[3].msgRoutes)) + sleep(1) + for ex in exchanges: + ex.update() + + # get exactly 10 msgs on B3 only + for i in range(11, 21): + msg = queue_3.get(timeout=5) + self.assertEqual("Message_drp %d" % i, msg.body) + + try: + extra = queue_3.get(timeout=1) + self.fail("Got unexpected message in queue_3: " + extra.body) + except Empty: None + + # cleanup + + self._brokers[3].client_session.exchange_unbind(queue="fedX1", exchange="fedX.direct", binding_key="spudboy") + self._brokers[3].client_session.message_cancel(destination="f1") + self._brokers[3].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: + _b.client_session.exchange_delete(exchange="fedX.direct") + + self._teardown_brokers() + + self.verify_cleanup() + + def test_dynamic_topic_route_prop(self): + """ Set up a tree of uni-directional routes across a topic exchange. + Bind the same key to the same queues on the leaf nodes. Verify a + message sent with the routing key transverses the tree an arrives at + each leaf. Remove one leaf's queue, and verify that messages still + reach the other leaf. + + Route Topology: + + +---> B2 queue:"test-queue", binding key:"spud.*" + B0 --> B1 --+ + +---> B3 queue:"test-queue", binding key:"spud.*" + """ + 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: + _b.client_session.exchange_declare(exchange="fedX.topic", type="topic") + self.assertEqual(_b.client_session.exchange_query(name="fedX.topic").type, + "topic", "exchange_declare failed!") + # pull the exchange out of qmf... + retries = 0 + my_exchange = None + while my_exchange is None: + objs = qmf.getObjects(_broker=_b.qmf_broker, _class="exchange") + for ooo in objs: + if ooo.name == "fedX.topic": + my_exchange = ooo + break + if my_exchange is None: + retries += 1 + self.failIfEqual(retries, 10, + "QMF failed to find new exchange!") + sleep(1) + exchanges.append(my_exchange) + + self.assertEqual(len(exchanges), len(self._brokers), "Exchange creation failed!") + + # 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 --> B2 + result = self._brokers[2].qmf_object.connect(self._brokers[1].host, + self._brokers[1].port, + False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + # connect B1 --> B3 + result = self._brokers[3].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.topic" 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.topic", # src + "fedX.topic", # dst + "", # key + "", # tag + "", # excludes + False, # srcIsQueue + False, # srcIsLocal + True, # dynamic + 0) # sync + self.assertEqual(result.status, 0) + + # wait for the inter-broker links to become operational + retries = 0 + operational = False + while not operational: + 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 + if not operational: + retries += 1 + self.failIfEqual(retries, 10, + "inter-broker links failed to become operational.") + sleep(1) + + # @todo - There is no way to determine when the bridge objects become + # active. + sleep(6) + + # create a queue on B2, bound to "spudboy" + self._brokers[2].client_session.queue_declare(queue="fedX1", exclusive=True, auto_delete=True) + self._brokers[2].client_session.exchange_bind(queue="fedX1", exchange="fedX.topic", binding_key="spud.*") + + # create a queue on B3, bound to "spudboy" + self._brokers[3].client_session.queue_declare(queue="fedX1", exclusive=True, auto_delete=True) + self._brokers[3].client_session.exchange_bind(queue="fedX1", exchange="fedX.topic", binding_key="spud.*") + + # subscribe to messages arriving on B2's queue + self.subscribe(self._brokers[2].client_session, queue="fedX1", destination="f1") + queue_2 = self._brokers[2].client_session.incoming("f1") + + # subscribe to messages arriving on B3's queue + self.subscribe(self._brokers[3].client_session, queue="fedX1", destination="f1") + queue_3 = self._brokers[3].client_session.incoming("f1") + + # wait until the binding key has propagated to each broker (twice at + # broker B1). Work backwards from binding brokers. + + binding_counts = [1, 2, 1, 1] + self.assertEqual(len(binding_counts), len(exchanges), "Update Test!") + for i in range(3,-1,-1): + retries = 0 + exchanges[i].update() + while exchanges[i].bindingCount < binding_counts[i]: + retries += 1 + self.failIfEqual(retries, 10, + "binding failed to propagate to broker %d" + % i) + sleep(3) + exchanges[i].update() + + # send 10 msgs from B0 + for i in range(1, 11): + dp = self._brokers[0].client_session.delivery_properties(routing_key="spud.boy") + self._brokers[0].client_session.message_transfer(destination="fedX.topic", message=Message(dp, "Message_trp %d" % i)) + + # wait for 10 messages to be forwarded from B0->B1, + # 10 messages from B1->B2, + # and 10 messages from B1->B3 + retries = 0 + for ex in exchanges: + ex.update() + while (exchanges[0].msgReceives != 10 or exchanges[0].msgRoutes != 10 or + exchanges[1].msgReceives != 10 or exchanges[1].msgRoutes != 20 or + exchanges[2].msgReceives != 10 or exchanges[2].msgRoutes != 10 or + exchanges[3].msgReceives != 10 or exchanges[3].msgRoutes != 10): + retries += 1 + self.failIfEqual(retries, 10, + "federation failed to route msgs %d:%d %d:%d %d:%d %d:%d" + % (exchanges[0].msgReceives, + exchanges[0].msgRoutes, + exchanges[1].msgReceives, + exchanges[1].msgRoutes, + exchanges[2].msgReceives, + exchanges[2].msgRoutes, + exchanges[3].msgReceives, + exchanges[3].msgRoutes)) + sleep(1) + for ex in exchanges: + ex.update() + + # get exactly 10 msgs on B2 and B3 + for i in range(1, 11): + msg = queue_2.get(timeout=5) + self.assertEqual("Message_trp %d" % i, msg.body) + msg = queue_3.get(timeout=5) + self.assertEqual("Message_trp %d" % i, msg.body) + + try: + extra = queue_2.get(timeout=1) + self.fail("Got unexpected message in queue_2: " + extra.body) + except Empty: None + + try: + extra = queue_3.get(timeout=1) + self.fail("Got unexpected message in queue_3: " + extra.body) + except Empty: None + + # tear down the queue on B2 + self._brokers[2].client_session.exchange_unbind(queue="fedX1", exchange="fedX.topic", binding_key="spud.*") + self._brokers[2].client_session.message_cancel(destination="f1") + self._brokers[2].client_session.queue_delete(queue="fedX1") + + # wait for the binding count on B1 to drop from 2 to 1 + retries = 0 + exchanges[1].update() + while exchanges[1].bindingCount != 1: + retries += 1 + self.failIfEqual(retries, 10, + "unbinding failed to propagate to broker B1: %d" + % exchanges[1].bindingCount) + sleep(1) + exchanges[1].update() + + # send 10 msgs from B0 + for i in range(11, 21): + dp = self._brokers[0].client_session.delivery_properties(routing_key="spud.boy") + self._brokers[0].client_session.message_transfer(destination="fedX.topic", message=Message(dp, "Message_trp %d" % i)) + + # verify messages are forwarded to B3 only + retries = 0 + for ex in exchanges: + ex.update() + while (exchanges[0].msgReceives != 20 or exchanges[0].msgRoutes != 20 or + exchanges[1].msgReceives != 20 or exchanges[1].msgRoutes != 30 or + exchanges[2].msgReceives != 10 or exchanges[2].msgRoutes != 10 or + exchanges[3].msgReceives != 20 or exchanges[3].msgRoutes != 20): + retries += 1 + self.failIfEqual(retries, 10, + "federation failed to route more msgs %d:%d %d:%d %d:%d %d:%d" + % (exchanges[0].msgReceives, + exchanges[0].msgRoutes, + exchanges[1].msgReceives, + exchanges[1].msgRoutes, + exchanges[2].msgReceives, + exchanges[2].msgRoutes, + exchanges[3].msgReceives, + exchanges[3].msgRoutes)) + sleep(1) + for ex in exchanges: + ex.update() + + # get exactly 10 msgs on B3 only + for i in range(11, 21): + msg = queue_3.get(timeout=5) + self.assertEqual("Message_trp %d" % i, msg.body) + + try: + extra = queue_3.get(timeout=1) + self.fail("Got unexpected message in queue_3: " + extra.body) + except Empty: None + + # cleanup + + self._brokers[3].client_session.exchange_unbind(queue="fedX1", exchange="fedX.topic", binding_key="spud.*") + self._brokers[3].client_session.message_cancel(destination="f1") + self._brokers[3].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: + _b.client_session.exchange_delete(exchange="fedX.topic") + + self._teardown_brokers() + + self.verify_cleanup() + + + def test_dynamic_fanout_route_prop(self): + """ Set up a tree of uni-directional routes across a fanout exchange. + Bind the same key to the same queues on the leaf nodes. Verify a + message sent with the routing key transverses the tree an arrives at + each leaf. Remove one leaf's queue, and verify that messages still + reach the other leaf. + + Route Topology: + + +---> B2 queue:"test-queue", binding key:"spud.*" + B0 --> B1 --+ + +---> B3 queue:"test-queue", binding key:"spud.*" + """ + session = self.session + + # create the federation + + self.startQmf() + qmf = self.qmf + + self._setup_brokers() + + # create fanout exchange on each broker, and retrieve the corresponding + # management object for that exchange + + exchanges=[] + for _b in self._brokers: + _b.client_session.exchange_declare(exchange="fedX.fanout", type="fanout") + self.assertEqual(_b.client_session.exchange_query(name="fedX.fanout").type, + "fanout", "exchange_declare failed!") + # pull the exchange out of qmf... + retries = 0 + my_exchange = None + while my_exchange is None: + objs = qmf.getObjects(_broker=_b.qmf_broker, _class="exchange") + for ooo in objs: + if ooo.name == "fedX.fanout": + my_exchange = ooo + break + if my_exchange is None: + retries += 1 + self.failIfEqual(retries, 10, + "QMF failed to find new exchange!") + sleep(1) + exchanges.append(my_exchange) + + self.assertEqual(len(exchanges), len(self._brokers), "Exchange creation failed!") + + # 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 --> B2 + result = self._brokers[2].qmf_object.connect(self._brokers[1].host, + self._brokers[1].port, + False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + # connect B1 --> B3 + result = self._brokers[3].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.fanout" 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.fanout", # src + "fedX.fanout", # dst + "", # key + "", # tag + "", # excludes + False, # srcIsQueue + False, # srcIsLocal + True, # dynamic + 0) # sync + self.assertEqual(result.status, 0) + + # wait for the inter-broker links to become operational + retries = 0 + operational = False + while not operational: + 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 + if not operational: + retries += 1 + self.failIfEqual(retries, 10, + "inter-broker links failed to become operational.") + sleep(1) + + # @todo - There is no way to determine when the bridge objects become + # active. + sleep(6) + + # create a queue on B2, bound to the exchange + self._brokers[2].client_session.queue_declare(queue="fedX1", exclusive=True, auto_delete=True) + self._brokers[2].client_session.exchange_bind(queue="fedX1", exchange="fedX.fanout") + + # create a queue on B3, bound to the exchange + self._brokers[3].client_session.queue_declare(queue="fedX1", exclusive=True, auto_delete=True) + self._brokers[3].client_session.exchange_bind(queue="fedX1", exchange="fedX.fanout") + + # subscribe to messages arriving on B2's queue + self.subscribe(self._brokers[2].client_session, queue="fedX1", destination="f1") + queue_2 = self._brokers[2].client_session.incoming("f1") + + # subscribe to messages arriving on B3's queue + self.subscribe(self._brokers[3].client_session, queue="fedX1", destination="f1") + queue_3 = self._brokers[3].client_session.incoming("f1") + + # wait until the binding key has propagated to each broker (twice at + # broker B1). Work backwards from binding brokers. + + binding_counts = [1, 2, 1, 1] + self.assertEqual(len(binding_counts), len(exchanges), "Update Test!") + for i in range(3,-1,-1): + retries = 0 + exchanges[i].update() + while exchanges[i].bindingCount < binding_counts[i]: + retries += 1 + self.failIfEqual(retries, 10, + "binding failed to propagate to broker %d" + % i) + sleep(3) + exchanges[i].update() + + # send 10 msgs from B0 + for i in range(1, 11): + dp = self._brokers[0].client_session.delivery_properties() + self._brokers[0].client_session.message_transfer(destination="fedX.fanout", message=Message(dp, "Message_frp %d" % i)) + + # wait for 10 messages to be forwarded from B0->B1, + # 10 messages from B1->B2, + # and 10 messages from B1->B3 + retries = 0 + for ex in exchanges: + ex.update() + while (exchanges[0].msgReceives != 10 or exchanges[0].msgRoutes != 10 or + exchanges[1].msgReceives != 10 or exchanges[1].msgRoutes != 20 or + exchanges[2].msgReceives != 10 or exchanges[2].msgRoutes != 10 or + exchanges[3].msgReceives != 10 or exchanges[3].msgRoutes != 10): + retries += 1 + self.failIfEqual(retries, 10, + "federation failed to route msgs %d:%d %d:%d %d:%d %d:%d" + % (exchanges[0].msgReceives, + exchanges[0].msgRoutes, + exchanges[1].msgReceives, + exchanges[1].msgRoutes, + exchanges[2].msgReceives, + exchanges[2].msgRoutes, + exchanges[3].msgReceives, + exchanges[3].msgRoutes)) + sleep(1) + for ex in exchanges: + ex.update() + + # get exactly 10 msgs on B2 and B3 + for i in range(1, 11): + msg = queue_2.get(timeout=5) + self.assertEqual("Message_frp %d" % i, msg.body) + msg = queue_3.get(timeout=5) + self.assertEqual("Message_frp %d" % i, msg.body) + + try: + extra = queue_2.get(timeout=1) + self.fail("Got unexpected message in queue_2: " + extra.body) + except Empty: None + + try: + extra = queue_3.get(timeout=1) + self.fail("Got unexpected message in queue_3: " + extra.body) + except Empty: None + + # tear down the queue on B2 + self._brokers[2].client_session.exchange_unbind(queue="fedX1", exchange="fedX.fanout") + self._brokers[2].client_session.message_cancel(destination="f1") + self._brokers[2].client_session.queue_delete(queue="fedX1") + + # wait for the binding count on B1 to drop from 2 to 1 + retries = 0 + exchanges[1].update() + while exchanges[1].bindingCount != 1: + retries += 1 + self.failIfEqual(retries, 10, + "unbinding failed to propagate to broker B1: %d" + % exchanges[1].bindingCount) + sleep(1) + exchanges[1].update() + + # send 10 msgs from B0 + for i in range(11, 21): + dp = self._brokers[0].client_session.delivery_properties() + self._brokers[0].client_session.message_transfer(destination="fedX.fanout", message=Message(dp, "Message_frp %d" % i)) + + # verify messages are forwarded to B3 only + retries = 0 + for ex in exchanges: + ex.update() + while (exchanges[0].msgReceives != 20 or exchanges[0].msgRoutes != 20 or + exchanges[1].msgReceives != 20 or exchanges[1].msgRoutes != 30 or + exchanges[2].msgReceives != 10 or exchanges[2].msgRoutes != 10 or + exchanges[3].msgReceives != 20 or exchanges[3].msgRoutes != 20): + retries += 1 + self.failIfEqual(retries, 10, + "federation failed to route more msgs %d:%d %d:%d %d:%d %d:%d" + % (exchanges[0].msgReceives, + exchanges[0].msgRoutes, + exchanges[1].msgReceives, + exchanges[1].msgRoutes, + exchanges[2].msgReceives, + exchanges[2].msgRoutes, + exchanges[3].msgReceives, + exchanges[3].msgRoutes)) + sleep(1) + for ex in exchanges: + ex.update() + + # get exactly 10 msgs on B3 only + for i in range(11, 21): + msg = queue_3.get(timeout=5) + self.assertEqual("Message_frp %d" % i, msg.body) + + try: + extra = queue_3.get(timeout=1) + self.fail("Got unexpected message in queue_3: " + extra.body) + except Empty: None + + # cleanup + + self._brokers[3].client_session.exchange_unbind(queue="fedX1", exchange="fedX.fanout") + self._brokers[3].client_session.message_cancel(destination="f1") + self._brokers[3].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: + _b.client_session.exchange_delete(exchange="fedX.fanout") + + self._teardown_brokers() + + self.verify_cleanup() + + + def getProperty(self, msg, name): + for h in msg.headers: + if hasattr(h, name): return getattr(h, name) + return None + + def getAppHeader(self, msg, name): + headers = self.getProperty(msg, "application_headers") + 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/qpid/cpp/src/tests/find_prog.ps1 b/qpid/cpp/src/tests/find_prog.ps1 new file mode 100644 index 0000000000..5c482debbf --- /dev/null +++ b/qpid/cpp/src/tests/find_prog.ps1 @@ -0,0 +1,36 @@ +#
+# 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.
+#
+
+# Locate the subdirectory where the specified program resides; the program
+# must have a directory and a file name, even if the directory is .
+param(
+ [string] $prog # program to look for somewhere below cwd
+)
+
+$dir = Split-Path $prog
+$exe = Split-Path $prog -leaf
+$sub = ""
+$subs = "Debug","Release","MinSizeRel","RelWithDebInfo"
+foreach ($try in $subs) {
+ $prog = "$dir\$try\$exe"
+ if (Test-Path $prog) {
+ $sub = $try
+ break
+ }
+}
diff --git a/qpid/cpp/src/tests/header_test.cpp b/qpid/cpp/src/tests/header_test.cpp new file mode 100644 index 0000000000..c36b4f3bc3 --- /dev/null +++ b/qpid/cpp/src/tests/header_test.cpp @@ -0,0 +1,59 @@ +/* + * + * 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 "TestOptions.h" +#include "qpid/client/Connection.h" +#include "qpid/client/Message.h" +#include "qpid/client/Session.h" +#include "qpid/client/SubscriptionManager.h" + +using namespace qpid; +using namespace qpid::client; +using namespace std; + +int main(int argc, char** argv) +{ + TestOptions opts; + try { + opts.parse(argc, argv); + Connection connection; + connection.open(opts.con); + Session session = connection.newSession(); + std::string q("header_interop_test_queue"); + session.queueDeclare(arg::queue=q); + double pi = 3.14159265; + float e = 2.71828f; + Message msg("", q); + msg.getMessageProperties().getApplicationHeaders().setDouble("pi", pi); + msg.getMessageProperties().getApplicationHeaders().setFloat("e", e); + session.messageTransfer(arg::content=msg); + + session.close(); + connection.close(); + + return 0; + } catch(const exception& e) { + cout << e.what() << endl; + } + return 1; +} diff --git a/qpid/cpp/src/tests/header_test.py b/qpid/cpp/src/tests/header_test.py new file mode 100755 index 0000000000..d5a2c16c01 --- /dev/null +++ b/qpid/cpp/src/tests/header_test.py @@ -0,0 +1,86 @@ +#!/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 qpid +import sys +import os +from qpid.util import connect +from qpid.connection import Connection +from qpid.datatypes import Message, RangedSet, uuid4 +from qpid.queue import Empty +from math import fabs + +def getApplicationHeaders(msg): + for h in msg.headers: + if hasattr(h, 'application_headers'): return getattr(h, 'application_headers') + return None + +# Set parameters for login + +host="127.0.0.1" +port=5672 +user="guest" +password="guest" + +if len(sys.argv) > 1 : + host=sys.argv[1] +if len(sys.argv) > 2 : + port=int(sys.argv[2]) + +# Create a connection. +socket = connect(host, port) +connection = Connection (sock=socket) +connection.start() +session = connection.session(str(uuid4())) + +q = "header_interop_test_queue" +session.queue_declare(queue=q) + +session.message_subscribe(queue=q, destination="received") +queue = session.incoming("received") +queue.start() + +msg = queue.get(timeout=10) +pi = 3.14159265 +e = 2.71828 + +headers = getApplicationHeaders(msg) +pi_ = headers["pi"] +e_ = headers["e"] +session.close(timeout=10) + +failed = False + +if pi != pi_: + print "got incorrect value for pi: ", pi_, " expected:", pi + failed = True + +if fabs(e - e_) > 0.0001: + print "got incorrect value for e: ", e_, " expected:", e + failed = True + +if failed: + sys.exit(1) +else: + print "Correct header values received." + sys.exit(0) + + + diff --git a/qpid/cpp/src/tests/headers_federation.py b/qpid/cpp/src/tests/headers_federation.py new file mode 100644 index 0000000000..60cff1da54 --- /dev/null +++ b/qpid/cpp/src/tests/headers_federation.py @@ -0,0 +1,99 @@ +#!/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.datatypes import Message +from qpid.queue import Empty +from time import sleep + +class HeadersFederationTests(TestBase010): + + def remote_host(self): + return self.defines.get("remote-host", "localhost") + + def remote_port(self): + return int(self.defines["remote-port"]) + + def verify_cleanup(self): + attempts = 0 + total = len(self.qmf.getObjects(_class="bridge")) + len(self.qmf.getObjects(_class="link")) + while total > 0: + attempts += 1 + if attempts >= 10: + self.fail("Bridges and links didn't clean up") + return + sleep(1) + total = len(self.qmf.getObjects(_class="bridge")) + len(self.qmf.getObjects(_class="link")) + + def test_dynamic_headers_unbind(self): + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_headers_unbind") + + session.exchange_declare(exchange="fed.headers_unbind", type="headers") + r_session.exchange_declare(exchange="fed.headers_unbind", type="headers") + + 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) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.headers_unbind", "fed.headers_unbind", "", "", "", False, False, True, 0) + self.assertEqual(result.status, 0) + bridge = qmf.getObjects(_class="bridge")[0] + sleep(5) + + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + queue = qmf.getObjects(_class="queue", name="fed1")[0] + queue.update() + self.assertEqual(queue.bindingCount, 1, + "bindings not accounted for (expected 1, got %d)" % queue.bindingCount) + + session.exchange_bind(queue="fed1", exchange="fed.headers_unbind", binding_key="key1", arguments={'x-match':'any', 'class':'first'}) + queue.update() + self.assertEqual(queue.bindingCount, 2, + "bindings not accounted for (expected 2, got %d)" % queue.bindingCount) + + session.exchange_unbind(queue="fed1", exchange="fed.headers_unbind", binding_key="key1") + queue.update() + self.assertEqual(queue.bindingCount, 1, + "bindings not accounted for (expected 1, got %d)" % queue.bindingCount) + + result = bridge.close() + self.assertEqual(result.status, 0) + result = link.close() + self.assertEqual(result.status, 0) + + self.verify_cleanup() + + def getProperty(self, msg, name): + for h in msg.headers: + if hasattr(h, name): return getattr(h, name) + return None + + def getAppHeader(self, msg, name): + headers = self.getProperty(msg, "application_headers") + if headers: + return headers[name] + return None diff --git a/qpid/cpp/src/tests/install_env.sh.in b/qpid/cpp/src/tests/install_env.sh.in new file mode 100644 index 0000000000..2231954cb8 --- /dev/null +++ b/qpid/cpp/src/tests/install_env.sh.in @@ -0,0 +1,26 @@ +# +# 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. +# + +absdir() { echo `cd $1 && pwd`; } + +prefix=`absdir @prefix@` +export QPID_INSTALL_PREFIX=$prefix +export PATH=$prefix/bin:$prefix/sbin:$prefix/libexec/qpid/tests:$PATH +export LD_LIBRARY_PATH=$prefix/lib:$LD_LIBRARY_PATH +export PYTHONPATH=$prefix/lib/python2.4/site-packages:$PYTHONPATH diff --git a/qpid/cpp/src/tests/logging.cpp b/qpid/cpp/src/tests/logging.cpp new file mode 100644 index 0000000000..fc55d642c3 --- /dev/null +++ b/qpid/cpp/src/tests/logging.cpp @@ -0,0 +1,385 @@ +/* + * + * 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. + * + */ + +#include "test_tools.h" +#include "qpid/log/Logger.h" +#include "qpid/log/Options.h" +#include "qpid/log/OstreamOutput.h" +#include "qpid/memory.h" +#include "qpid/Options.h" +#if defined (_WIN32) +# include "qpid/log/windows/SinkOptions.h" +#else +# include "qpid/log/posix/SinkOptions.h" +#endif + +#include <boost/test/floating_point_comparison.hpp> +#include <boost/format.hpp> +#include "unit_test.h" + +#include <exception> +#include <fstream> +#include <time.h> + + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(loggingTestSuite) + +using namespace std; +using namespace boost; +using namespace qpid::log; + +QPID_AUTO_TEST_CASE(testStatementInit) { + Statement s=QPID_LOG_STATEMENT_INIT(debug); int line=__LINE__; + BOOST_CHECK(!s.enabled); + BOOST_CHECK_EQUAL(string(__FILE__), s.file); + BOOST_CHECK_EQUAL(line, s.line); + BOOST_CHECK_EQUAL(debug, s.level); +} + + +QPID_AUTO_TEST_CASE(testSelector_enable) { + Selector s; + // Simple enable + s.enable(debug,"foo"); + BOOST_CHECK(s.isEnabled(debug,"foo")); + BOOST_CHECK(!s.isEnabled(error,"foo")); + BOOST_CHECK(!s.isEnabled(error,"bar")); + + // Substring match + BOOST_CHECK(s.isEnabled(debug, "bazfoobar")); + BOOST_CHECK(!s.isEnabled(debug, "bazbar")); + + // Different levels for different substrings. + s.enable(info, "bar"); + BOOST_CHECK(s.isEnabled(debug, "foobar")); + BOOST_CHECK(s.isEnabled(info, "foobar")); + BOOST_CHECK(!s.isEnabled(debug, "bar")); + BOOST_CHECK(!s.isEnabled(info, "foo")); + + // Enable-strings + s.enable("notice:blob"); + BOOST_CHECK(s.isEnabled(notice, "blob")); + s.enable("error+:oops"); + BOOST_CHECK(s.isEnabled(error, "oops")); + BOOST_CHECK(s.isEnabled(critical, "oops")); +} + +QPID_AUTO_TEST_CASE(testStatementEnabled) { + // Verify that the singleton enables and disables static + // log statements. + Logger& l = Logger::instance(); + ScopedSuppressLogging ls(l); + l.select(Selector(debug)); + static Statement s=QPID_LOG_STATEMENT_INIT(debug); + BOOST_CHECK(!s.enabled); + static Statement::Initializer init(s); + BOOST_CHECK(s.enabled); + + static Statement s2=QPID_LOG_STATEMENT_INIT(warning); + static Statement::Initializer init2(s2); + BOOST_CHECK(!s2.enabled); + + l.select(Selector(warning)); + BOOST_CHECK(!s.enabled); + BOOST_CHECK(s2.enabled); +} + +struct TestOutput : public Logger::Output { + vector<string> msg; + vector<Statement> stmt; + + TestOutput(Logger& l) { + l.output(std::auto_ptr<Logger::Output>(this)); + } + + void log(const Statement& s, const string& m) { + msg.push_back(m); + stmt.push_back(s); + } + string last() { return msg.back(); } +}; + +using boost::assign::list_of; + +QPID_AUTO_TEST_CASE(testLoggerOutput) { + Logger l; + l.clear(); + l.select(Selector(debug)); + Statement s=QPID_LOG_STATEMENT_INIT(debug); + + TestOutput* out=new TestOutput(l); + + // Verify message is output. + l.log(s, "foo"); + vector<string> expect=list_of("foo\n"); + BOOST_CHECK_EQUAL(expect, out->msg); + + // Verify multiple outputs + TestOutput* out2=new TestOutput(l); + l.log(Statement(), "baz"); + expect.push_back("baz\n"); + BOOST_CHECK_EQUAL(expect, out->msg); + expect.erase(expect.begin()); + BOOST_CHECK_EQUAL(expect, out2->msg); +} + +QPID_AUTO_TEST_CASE(testMacro) { + Logger& l=Logger::instance(); + ScopedSuppressLogging ls(l); + l.select(Selector(info)); + TestOutput* out=new TestOutput(l); + QPID_LOG(info, "foo"); + vector<string> expect=list_of("foo\n"); + BOOST_CHECK_EQUAL(expect, out->msg); + BOOST_CHECK_EQUAL(__FILE__, out->stmt.front().file); + + // Not enabled: + QPID_LOG(debug, "bar"); + BOOST_CHECK_EQUAL(expect, out->msg); + + QPID_LOG(info, 42 << " bingo"); + expect.push_back("42 bingo\n"); + BOOST_CHECK_EQUAL(expect, out->msg); +} + +QPID_AUTO_TEST_CASE(testLoggerFormat) { + Logger& l = Logger::instance(); + ScopedSuppressLogging ls(l); + l.select(Selector(critical)); + TestOutput* out=new TestOutput(l); + + l.format(Logger::FILE); + QPID_LOG(critical, "foo"); + BOOST_CHECK_EQUAL(out->last(), string(__FILE__)+": foo\n"); + + l.format(Logger::FILE|Logger::LINE); + QPID_LOG(critical, "foo"); + BOOST_CHECK_EQUAL(out->last().find(__FILE__), 0u); + + l.format(Logger::FUNCTION); + QPID_LOG(critical, "foo"); + BOOST_CHECK_EQUAL(string(BOOST_CURRENT_FUNCTION) + ": foo\n", out->last()); + + l.format(Logger::LEVEL); + QPID_LOG(critical, "foo"); + BOOST_CHECK_EQUAL("critical foo\n", out->last()); +} + +QPID_AUTO_TEST_CASE(testOstreamOutput) { + Logger& l=Logger::instance(); + ScopedSuppressLogging ls(l); + l.select(Selector(error)); + ostringstream os; + l.output(qpid::make_auto_ptr<Logger::Output>(new OstreamOutput(os))); + QPID_LOG(error, "foo"); + QPID_LOG(error, "bar"); + QPID_LOG(error, "baz"); + BOOST_CHECK_EQUAL("foo\nbar\nbaz\n", os.str()); +} + +#if 0 // This test requires manual intervention. Normally disabled. +QPID_AUTO_TEST_CASE(testSyslogOutput) { + Logger& l=Logger::instance(); + Logger::StateSaver ls(l); + l.clear(); + l.select(Selector(info)); + l.syslog("qpid_test"); + QPID_LOG(info, "Testing QPID"); + BOOST_ERROR("Manually verify that /var/log/messages contains a recent line 'Testing QPID'"); +} +#endif // 0 + +int count() { + static int n = 0; + return n++; +} + +int loggedCount() { + static int n = 0; + QPID_LOG(debug, "counting: " << n); + return n++; +} + + +using namespace qpid::sys; + +// Measure CPU time. +clock_t timeLoop(int times, int (*fp)()) { + clock_t start=clock(); + while (times-- > 0) + (*fp)(); + return clock() - start; +} + +// Overhead test disabled because it consumes a ton of CPU and takes +// forever under valgrind. Not friendly for regular test runs. +// +#if 0 +QPID_AUTO_TEST_CASE(testOverhead) { + // Ensure that the ratio of CPU time for an incrementing loop + // with and without disabled log statements is in acceptable limits. + // + int times=100000000; + clock_t noLog=timeLoop(times, count); + clock_t withLog=timeLoop(times, loggedCount); + double ratio=double(withLog)/double(noLog); + + // NB: in initial tests the ratio was consistently below 1.5, + // 2.5 is reasonable and should avoid spurios failures + // due to machine load. + // + BOOST_CHECK_SMALL(ratio, 2.5); +} +#endif // 0 + +Statement statement( + Level level, const char* file="", int line=0, const char* fn=0) +{ + Statement s={0, file, line, fn, level}; + return s; +} + + +#define ARGC(argv) (sizeof(argv)/sizeof(char*)) + +QPID_AUTO_TEST_CASE(testOptionsParse) { + const char* argv[]={ + 0, + "--log-enable", "error+:foo", + "--log-enable", "debug:bar", + "--log-enable", "info", + "--log-to-stderr", "no", + "--log-to-file", "logout", + "--log-level", "yes", + "--log-source", "1", + "--log-thread", "true", + "--log-function", "YES" + }; + qpid::log::Options opts(""); +#ifdef _WIN32 + qpid::log::windows::SinkOptions sinks("test"); +#else + qpid::log::posix::SinkOptions sinks("test"); +#endif + opts.parse(ARGC(argv), const_cast<char**>(argv)); + sinks = *opts.sinkOptions; + vector<string> expect=list_of("error+:foo")("debug:bar")("info"); + BOOST_CHECK_EQUAL(expect, opts.selectors); + BOOST_CHECK(!sinks.logToStderr); + BOOST_CHECK(!sinks.logToStdout); + BOOST_CHECK(sinks.logFile == "logout"); + BOOST_CHECK(opts.level); + BOOST_CHECK(opts.source); + BOOST_CHECK(opts.function); + BOOST_CHECK(opts.thread); +} + +QPID_AUTO_TEST_CASE(testOptionsDefault) { + qpid::log::Options opts(""); +#ifdef _WIN32 + qpid::log::windows::SinkOptions sinks("test"); +#else + qpid::log::posix::SinkOptions sinks("test"); +#endif + sinks = *opts.sinkOptions; + BOOST_CHECK(sinks.logToStderr); + BOOST_CHECK(!sinks.logToStdout); + BOOST_CHECK(sinks.logFile.length() == 0); + vector<string> expect=list_of("notice+"); + BOOST_CHECK_EQUAL(expect, opts.selectors); + BOOST_CHECK(opts.time && opts.level); + BOOST_CHECK(!(opts.source || opts.function || opts.thread)); +} + +QPID_AUTO_TEST_CASE(testSelectorFromOptions) { + const char* argv[]={ + 0, + "--log-enable", "error+:foo", + "--log-enable", "debug:bar", + "--log-enable", "info" + }; + qpid::log::Options opts(""); + opts.parse(ARGC(argv), const_cast<char**>(argv)); + vector<string> expect=list_of("error+:foo")("debug:bar")("info"); + BOOST_CHECK_EQUAL(expect, opts.selectors); + Selector s(opts); + BOOST_CHECK(!s.isEnabled(warning, "x")); + BOOST_CHECK(!s.isEnabled(debug, "x")); + BOOST_CHECK(s.isEnabled(debug, "bar")); + BOOST_CHECK(s.isEnabled(error, "foo")); + BOOST_CHECK(s.isEnabled(critical, "foo")); +} + +QPID_AUTO_TEST_CASE(testLoggerStateure) { + Logger& l=Logger::instance(); + ScopedSuppressLogging ls(l); + qpid::log::Options opts("test"); + const char* argv[]={ + 0, + "--log-time", "no", + "--log-source", "yes", + "--log-to-stderr", "no", + "--log-to-file", "logging.tmp", + "--log-enable", "critical" + }; + opts.parse(ARGC(argv), const_cast<char**>(argv)); + l.configure(opts); + QPID_LOG(critical, "foo"); int srcline=__LINE__; + ifstream log("logging.tmp"); + string line; + getline(log, line); + string expect=(format("critical %s:%d: foo")%__FILE__%srcline).str(); + BOOST_CHECK_EQUAL(expect, line); + log.close(); + unlink("logging.tmp"); +} + +QPID_AUTO_TEST_CASE(testQuoteNonPrintable) { + Logger& l=Logger::instance(); + ScopedSuppressLogging ls(l); + qpid::log::Options opts("test"); + opts.time=false; +#ifdef _WIN32 + qpid::log::windows::SinkOptions *sinks = + dynamic_cast<qpid::log::windows::SinkOptions *>(opts.sinkOptions.get()); +#else + qpid::log::posix::SinkOptions *sinks = + dynamic_cast<qpid::log::posix::SinkOptions *>(opts.sinkOptions.get()); +#endif + sinks->logToStderr = false; + sinks->logFile = "logging.tmp"; + l.configure(opts); + + char s[] = "null\0tab\tspace newline\nret\r\x80\x99\xff"; + string str(s, sizeof(s)); + QPID_LOG(critical, str); + ifstream log("logging.tmp"); + string line; + getline(log, line, '\0'); + string expect="critical null\\x00tab\tspace newline\nret\r\\x80\\x99\\xFF\\x00\n"; + BOOST_CHECK_EQUAL(expect, line); + log.close(); + unlink("logging.tmp"); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/long_cluster_tests.py b/qpid/cpp/src/tests/long_cluster_tests.py new file mode 100755 index 0000000000..f77837f0c4 --- /dev/null +++ b/qpid/cpp/src/tests/long_cluster_tests.py @@ -0,0 +1,38 @@ +#!/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 os, signal, sys, unittest +from testlib import TestBaseCluster + +class LongClusterTests(TestBaseCluster): + """Long/Soak cluster tests with async store ability""" + + + def test_LongCluster_01_DummyTest(self): + """Dummy test - a placeholder for the first of the long/soak python cluster tests""" + pass + +# Start the test here + +if __name__ == '__main__': + if os.getenv("STORE_LIB") != None: + print "NOTE: Store enabled for the following tests:" + if not unittest.main(): sys.exit(1) + diff --git a/qpid/cpp/src/tests/multiq_perftest b/qpid/cpp/src/tests/multiq_perftest new file mode 100755 index 0000000000..10f9edd2a6 --- /dev/null +++ b/qpid/cpp/src/tests/multiq_perftest @@ -0,0 +1,22 @@ +#!/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. +# + +exec `dirname $0`/run_perftest 10000 --mode shared --qt 16 diff --git a/qpid/cpp/src/tests/perfdist b/qpid/cpp/src/tests/perfdist new file mode 100755 index 0000000000..59548b23f7 --- /dev/null +++ b/qpid/cpp/src/tests/perfdist @@ -0,0 +1,87 @@ +#!/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. +# + + +# +# Distributed perftest. +# Runs perftest clients on multiple hosts using ssh. +# + +set -e +usage() { +cat <<EOF +usage: $0 <perftest-args> -- <client-hosts ...> [ --- <broker hosts...> ] +Client & broker hosts can also be set in env vars CLIENTS and BROKERS. + +Run perftest clients on the client hosts against brokers on the broker +hosts Clients are assigned to client hosts round robin: publishers +first, then subscribers. If there are multiple brokers (for cluster +tests) clients connect to them round robin. + +Broker hosts can be listed with -b in perftest-args or after --- +at the end of the arguments. + +Error: $* +EOF +exit 1 +} + +TESTDIR=${TESTDIR:-$PWD} # Absolute path to test exes on all hosts. + +collect() { eval $COLLECT=\""\$$COLLECT $*"\"; } +NPUBS=1 +NSUBS=1 +COLLECT=ARGS +while test $# -gt 0; do + case $1 in + --publish|--subscribe|--setup|--control) usage "Don't pass perftest action flags: $1" ;; + --npubs) collect $1 $2; NPUBS=$2; shift 2 ;; + --nsubs) collect $1 $2; NSUBS=$2; shift 2 ;; + -s|--summary) collect $1; QUIET=yes; shift 1 ;; + -b|--broker) BROKERS="$BROKERS $2"; shift 2;; + --) COLLECT=CLIENTARG; shift ;; + ---) COLLECT=BROKERARG; shift;; + *) collect $1; shift ;; + esac +done + +CLIENTS=${CLIENTARG:-$CLIENTS} +if [ -z "$CLIENTS" ]; then usage "No client hosts listed after --"; fi +BROKERS=${BROKERARG:-$BROKERS} +if [ -z "$BROKERS" ]; then usage "No brokers specified"; fi + +PERFTEST="$TESTDIR/perftest $ARGS" + +CLIENTS=($CLIENTS) +BROKERS=($BROKERS) +start() { + CLIENT=${CLIENTS[i % ${#CLIENTS[*]}]} + BROKER=${BROKERS[i % ${#BROKERS[*]}]} + ARGS="$* --broker $BROKER" + cmd="ssh -n $CLIENT $PERFTEST $ARGS" + test -z "$QUIET" && echo "Client $i: $cmd" + $cmd & +} + +$PERFTEST --setup -b ${BROKERS[0]} +for (( i=0 ; i < $NPUBS ; ++i)); do start --publish; done +for (( ; i < $NPUBS+$NSUBS ; ++i)); do start --subscribe; done +$PERFTEST --control -b ${BROKERS[0]} diff --git a/qpid/cpp/src/tests/policy.acl b/qpid/cpp/src/tests/policy.acl new file mode 100644 index 0000000000..ef46026555 --- /dev/null +++ b/qpid/cpp/src/tests/policy.acl @@ -0,0 +1 @@ +acl allow all all diff --git a/qpid/cpp/src/tests/publish.cpp b/qpid/cpp/src/tests/publish.cpp new file mode 100644 index 0000000000..3f456e7588 --- /dev/null +++ b/qpid/cpp/src/tests/publish.cpp @@ -0,0 +1,135 @@ +/* + * + * 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 <algorithm> +#include <iostream> +#include <memory> +#include <sstream> +#include <vector> + +#include "TestOptions.h" +#include "qpid/client/Connection.h" +#include "qpid/client/Message.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/client/SubscriptionManager.h" + +using namespace qpid; +using namespace qpid::client; +using namespace qpid::sys; +using namespace std; + +namespace qpid { +namespace tests { + +typedef vector<string> StringSet; + +struct Args : public qpid::TestOptions { + uint size; + uint count; + bool durable; + string destination; + string routingKey; + bool summary; + bool id; + + Args() : size(256), count(1000), durable(true), routingKey("publish-consume"), summary(false), id(false) { + addOptions() + ("size", optValue(size, "N"), "message size") + ("count", optValue(count, "N"), "number of messages to publish") + ("durable", optValue(durable, "yes|no"), "use durable messages") + ("destination", optValue(destination, "<exchange name>"), "destination to publish to") + ("routing-key", optValue(routingKey, "<key>"), "routing key to publish with") + ("summary,s", optValue(summary), "Output only the rate.") + ("id", optValue(id), "Add unique correlation ID"); + } +}; + +Args opts; + +struct Client +{ + Connection connection; + AsyncSession session; + + Client() + { + opts.open(connection); + session = connection.newSession(); + } + + // Cheap hex calculation, avoid expensive ostrstream and string + // creation to generate correlation ids in message loop. + char hex(char i) { return i<10 ? '0'+i : 'A'+i-10; } + void hex(char i, string& s) { + s[0]=hex(i>>24); s[1]=hex(i>>16); s[2]=hex(i>>8); s[3]=i; + } + + void publish() + { + AbsTime begin=now(); + Message msg(string(opts.size, 'X'), opts.routingKey); + string correlationId = "0000"; + if (opts.durable) + msg.getDeliveryProperties().setDeliveryMode(framing::PERSISTENT); + + for (uint i = 0; i < opts.count; i++) { + if (opts.id) { + hex(i+1, correlationId); + msg.getMessageProperties().setCorrelationId(correlationId); + } + session.messageTransfer(arg::destination=opts.destination, + arg::content=msg, + arg::acceptMode=1); + } + session.sync(); + AbsTime end=now(); + double secs(double(Duration(begin,end))/TIME_SEC); + if (opts.summary) cout << opts.count/secs << endl; + else cout << "Time: " << secs << "s Rate: " << opts.count/secs << endl; + } + + ~Client() + { + try{ + session.close(); + connection.close(); + } catch(const exception& e) { + cout << e.what() << endl; + } + } +}; + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char** argv) +{ + try { + opts.parse(argc, argv); + Client client; + client.publish(); + return 0; + } catch(const exception& e) { + cout << e.what() << endl; + } + return 1; +} diff --git a/qpid/cpp/src/tests/python_tests b/qpid/cpp/src/tests/python_tests new file mode 100755 index 0000000000..0216b5ca7b --- /dev/null +++ b/qpid/cpp/src/tests/python_tests @@ -0,0 +1,29 @@ +#!/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 python tests. +source ./test_env.sh +test -d $PYTHON_DIR || { echo "Skipping python tests, no python dir."; exit 0; } +QPID_PORT=${QPID_PORT:-5672} +PYTHON_TESTS=${PYTHON_TESTS:-$*} +FAILING=${FAILING:-/dev/null} + +python $QPID_PYTHON_TEST -m qpid_tests.broker_0_10 -m qpid.tests -b localhost:$QPID_PORT -I $FAILING $PYTHON_TESTS || exit 1 diff --git a/qpid/cpp/src/tests/python_tests.ps1 b/qpid/cpp/src/tests/python_tests.ps1 new file mode 100644 index 0000000000..9f8b9890c4 --- /dev/null +++ b/qpid/cpp/src/tests/python_tests.ps1 @@ -0,0 +1,45 @@ +# +# 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 python tests; intended to be run by run_test.ps1 which sets up +# QPID_PORT +$srcdir = Split-Path $myInvocation.InvocationName +$PYTHON_DIR = "$srcdir\..\..\..\python" +if (!(Test-Path $PYTHON_DIR -pathType Container)) { + "Skipping python tests as python libs not found" + exit 1 +} + +$PYTHON_TEST_DIR = "$srcdir\..\..\..\tests\src\py" +$QMF_LIB = "$srcdir\..\..\..\extras\qmf\src\py" + +if (Test-Path env:FAILING) { + $fails = "-I $env:FAILING" +} +if (Test-Path env:PYTHON_TESTS) { + $tests = "$env:PYTHON_TESTS" +} +else { + $tests = "$args" +} + +#cd $PYTHON_DIR +$env:PYTHONPATH="$PYTHON_DIR;$PYTHON_TEST_DIR;$env:PYTHONPATH;$QMF_LIB" +python $PYTHON_DIR/qpid-python-test -m qpid_tests.broker_0_10 -m qpid.tests -b localhost:$env:QPID_PORT $fails $tests +exit $LASTEXITCODE diff --git a/qpid/cpp/src/tests/qpid-build-rinstall b/qpid/cpp/src/tests/qpid-build-rinstall new file mode 100755 index 0000000000..1a92f8750a --- /dev/null +++ b/qpid/cpp/src/tests/qpid-build-rinstall @@ -0,0 +1,28 @@ +#!/bin/sh +# +# Licensed to the Apache Software Foundation (ASF) under onemake +# 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 "make install"" locally then copy the install tree to each of $HOSTS +# Must be run in a configured qpid build directory. +# +test -f config.status || { echo "Not in a configured build directory."; usage; } +. src/tests/install_env.sh +set -ex +make && make -j1 install +rsynchosts $QPID_INSTALL_PREFIX diff --git a/qpid/cpp/src/tests/qpid-client-test.cpp b/qpid/cpp/src/tests/qpid-client-test.cpp new file mode 100644 index 0000000000..2f5e8e5afe --- /dev/null +++ b/qpid/cpp/src/tests/qpid-client-test.cpp @@ -0,0 +1,139 @@ +/* + * + * 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. + * + */ + +/** + * This file provides a simple test (and example) of basic + * functionality including declaring an exchange and a queue, binding + * these together, publishing a message and receiving that message + * asynchronously. + */ + +#include <iostream> + +#include "TestOptions.h" +#include "qpid/client/Connection.h" +#include "qpid/client/Message.h" +#include "qpid/client/Session.h" +#include "qpid/client/SubscriptionManager.h" + + +using namespace qpid; +using namespace qpid::client; +using namespace qpid::framing; +using std::string; + +namespace qpid { +namespace tests { + +struct Args : public TestOptions { + uint msgSize; + bool verbose; + + Args() : TestOptions("Simple test of Qpid c++ client; sends and receives a single message."), msgSize(26) + { + addOptions() + ("size", optValue(msgSize, "N"), "message size") + ("verbose", optValue(verbose), "print out some status messages"); + } +}; + +const std::string chars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); + +std::string generateData(uint size) +{ + if (size < chars.length()) { + return chars.substr(0, size); + } + std::string data; + for (uint i = 0; i < (size / chars.length()); i++) { + data += chars; + } + data += chars.substr(0, size % chars.length()); + return data; +} + +void print(const std::string& text, const Message& msg) +{ + std::cout << text; + if (msg.getData().size() > 16) { + std::cout << msg.getData().substr(0, 16) << "..."; + } else { + std::cout << msg.getData(); + } + std::cout << std::endl; +} + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char** argv) +{ + try { + Args opts; + opts.parse(argc, argv); + + //Connect to the broker: + Connection connection; + opts.open(connection); + if (opts.verbose) std::cout << "Opened connection." << std::endl; + + //Create and open a session on the connection through which + //most functionality is exposed: + Session session = connection.newSession(); + if (opts.verbose) std::cout << "Opened session." << std::endl; + + + //'declare' the exchange and the queue, which will create them + //as they don't exist + session.exchangeDeclare(arg::exchange="MyExchange", arg::type="direct"); + if (opts.verbose) std::cout << "Declared exchange." << std::endl; + session.queueDeclare(arg::queue="MyQueue", arg::autoDelete=true, arg::exclusive=true); + if (opts.verbose) std::cout << "Declared queue." << std::endl; + + //now bind the queue to the exchange + session.exchangeBind(arg::exchange="MyExchange", arg::queue="MyQueue", arg::bindingKey="MyKey"); + if (opts.verbose) std::cout << "Bound queue to exchange." << std::endl; + + //create and send a message to the exchange using the routing + //key we bound our queue with: + Message msgOut(generateData(opts.msgSize)); + msgOut.getDeliveryProperties().setRoutingKey("MyKey"); + session.messageTransfer(arg::destination="MyExchange", arg::content=msgOut, arg::acceptMode=1); + if (opts.verbose) print("Published message: ", msgOut); + + // Using the SubscriptionManager, get the message from the queue. + SubscriptionManager subs(session); + Message msgIn = subs.get("MyQueue"); + if (msgIn.getData() == msgOut.getData()) + if (opts.verbose) std::cout << "Received the exepected message." << std::endl; + + //close the session & connection + session.close(); + if (opts.verbose) std::cout << "Closed session." << std::endl; + connection.close(); + if (opts.verbose) std::cout << "Closed connection." << std::endl; + return 0; + } catch(const std::exception& e) { + std::cout << e.what() << std::endl; + } + return 1; +} diff --git a/qpid/cpp/src/tests/qpid-cluster-benchmark b/qpid/cpp/src/tests/qpid-cluster-benchmark new file mode 100755 index 0000000000..ff787a46dd --- /dev/null +++ b/qpid/cpp/src/tests/qpid-cluster-benchmark @@ -0,0 +1,58 @@ +#!/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. +# + +# Benchmark script for comparing cluster performance. + +# Default values +PORT="5672" +COUNT=10000 +FLOW=100 # Flow control limit on queue depth for latency. +REPEAT=10 +QUEUES=4 +CLIENTS=3 + +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 "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 "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/qpid/cpp/src/tests/qpid-cluster-lag.py b/qpid/cpp/src/tests/qpid-cluster-lag.py new file mode 100755 index 0000000000..5b24353241 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-cluster-lag.py @@ -0,0 +1,93 @@ +#!/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. +# + +"""%prog [options] broker... +Check for brokers that lag behind other brokers in a cluster.""" + +import os, os.path, sys, socket, time, re +from qpid.messaging import * +from optparse import OptionParser +from threading import Thread + +class Browser(Thread): + def __init__(self, broker, queue, timeout): + Thread.__init__(self) + self.broker = broker + self.queue = queue + self.timeout = timeout + self.error = None + self.time = None + + def run(self): + try: + self.connection = Connection(self.broker) + self.connection.open() + self.session = self.connection.session() + self.receiver = self.session.receiver("%s;{mode:browse}"%self.queue) + self.msg = self.receiver.fetch(timeout=self.timeout) + self.time = time.time() + if (self.msg.content != self.queue): + raise Exception("Wrong message content, expected '%s' found '%s'"% + (self.queue, self.msg.content)) + except Empty: + self.error = "No message on queue %s"%self.queue + except Exception, e: + self.error = "Error: %s"%e + +def main(argv): + op = OptionParser(usage=__doc__) + op.add_option("--timeout", type="float", default=None, metavar="TIMEOUT", + help="Give up after TIMEOUT milliseconds, default never timeout") + (opts, args) = op.parse_args(argv) + if (len(args) <= 1): op.error("No brokers were specified") + brokers = args[1:] + + # Put a message on a uniquely named queue. + queue = "%s:%s:%s"%(os.path.basename(args[0]), socket.gethostname(), os.getpid()) + connection = Connection(brokers[0]) + connection.open() + session = connection.session() + sender = session.sender( + "%s;{create:always,delete:always,node:{durable:False}}"%queue) + sender.send(Message(content=queue)) + start = time.time() + # Browse for the message on each broker + if opts.timeout: opts.timeout + threads = [Browser(b, queue, opts.timeout) for b in brokers] + for t in threads: t.start() + delays=[] + + for t in threads: + t.join() + if t.error: + delay=t.error + else: + delay = t.time-start + delays.append([delay, t.broker]) + print "%s: %s"%(t.broker,delay) + if delays: + delays.sort() + print "lag: %s (%s-%s)"%(delays[-1][0] - delays[0][0], delays[-1][1], delays[0][1]) + # Clean up + sender.close() + session.close() + connection.close() + +if __name__ == "__main__": sys.exit(main(sys.argv)) diff --git a/qpid/cpp/src/tests/qpid-cpp-benchmark b/qpid/cpp/src/tests/qpid-cpp-benchmark new file mode 100755 index 0000000000..6138108558 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-cpp-benchmark @@ -0,0 +1,260 @@ +#!/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 optparse, time, qpid.messaging, re +from threading import Thread +from subprocess import Popen, PIPE, STDOUT + +op = optparse.OptionParser(usage="usage: %prog [options]", + description="simple performance benchmarks") +op.add_option("-b", "--broker", default=[], action="append", type="str", + help="url of broker(s) to connect to, round robin on multiple brokers") +op.add_option("-c", "--client-host", default=[], action="append", type="str", + help="host(s) to run clients on via ssh, round robin on mulple hosts") +op.add_option("-q", "--queues", default=1, type="int", metavar="N", + help="create N queues (default %default)") +op.add_option("-s", "--senders", default=1, type="int", metavar="N", + help="start N senders per queue (default %default)") +op.add_option("-r", "--receivers", default=1, type="int", metavar="N", + help="start N receivers per queue (default %default)") +op.add_option("-m", "--messages", default=100000, type="int", metavar="N", + help="send N messages per sender (default %default)") +op.add_option("--queue-name", default="benchmark", metavar="NAME", + help="base name for queues (default %default)") +op.add_option("--send-rate", default=0, metavar="N", + help="send rate limited to N messages/second, 0 means no limit (default %default)") +op.add_option("--receive-rate", default=0, metavar="N", + help="receive rate limited to N messages/second, 0 means no limit (default %default)") +op.add_option("--content-size", default=1024, type="int", metavar="BYTES", + help="message size in bytes (default %default)") +op.add_option("--ack-frequency", default=100, metavar="N", type="int", + help="receiver ack's every N messages, 0 means unconfirmed (default %default)") +op.add_option("--no-report-header", dest="report_header", default=True, + action="store_false", help="don't print header on report") +op.add_option("--summarize", default=False, action="store_true", + help="print summary statistics for multiple senders/receivers: total throughput, average latency") +op.add_option("--repeat", default=1, metavar="N", help="repeat N times", type="int") +op.add_option("--send-option", default=[], action="append", type="str", + help="Additional option for sending addresses") +op.add_option("--receive-option", default=[], action="append", type="str", + help="Additional option for receiving addresses") +op.add_option("--send-arg", default=[], action="append", type="str", + help="Additional argument for qpid-send") +op.add_option("--receive-arg", default=[], action="append", type="str", + help="Additional argument for qpid-receive") +op.add_option("--no-timestamp", dest="timestamp", default=True, + action="store_false", help="don't add a timestamp, no latency results") +op.add_option("--connection-options", type="str", + help="Connection options for senders & receivers") +op.add_option("--flow-control", default=0, type="int", metavar="N", + help="Flow control each sender to limit queue depth to 2*N. 0 means no flow control.") +op.add_option("--durable", default=False, action="store_true", + help="Use durable queues and messages") + +single_quote_re = re.compile("'") +def posix_quote(string): + """ Quote a string for use as an argument in a posix shell""" + return "'" + single_quote_re.sub("\\'", string) + "'"; + +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}"] + address="%s;{%s}"%(queue,",".join(address_opts)) + msg_total=opts.senders*opts.messages + messages = msg_total/opts.receivers; + if (index < msg_total%opts.receivers): messages += 1 + if (messages == 0): return None + command = ["qpid-receive", + "-b", broker, + "-a", address, + "-m", str(messages), + "--forever", + "--print-content=no", + "--receive-rate", str(opts.receive_rate), + "--report-total", + "--ack-frequency", str(opts.ack_frequency), + "--ready-address", ready_queue, + "--report-header=no" + ] + command += opts.receive_arg + if opts.connection_options: + command += ["--connection-options",opts.connection_options] + if host: command = ssh_command(host, command) + return clients.add(Popen(command, stdout=PIPE)) + +def start_send(queue, opts, broker, host): + address="%s;{%s}"%(queue,",".join(opts.send_option)) + command = ["qpid-send", + "-b", broker, + "-a", address, + "--messages", str(opts.messages), + "--content-size", str(opts.content_size), + "--send-rate", str(opts.send_rate), + "--report-total", + "--report-header=no", + "--timestamp=%s"%(opts.timestamp and "yes" or "no"), + "--sequence=no", + "--flow-control", str(opts.flow_control), + "--durable", str(opts.durable) + ] + command += opts.send_arg + if opts.connection_options: + command += ["--connection-options",opts.connection_options] + if host: command = ssh_command(host, command) + return clients.add(Popen(command, stdout=PIPE)) + +def first_line(p): + out,err=p.communicate() + if p.returncode != 0: raise Exception("Process failed: %s"%(out.strip())) + return out.split("\n")[0] + +def delete_queues(queues, broker): + c = qpid.messaging.Connection(broker) + c.open() + for q in queues: + 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() + +def print_header(timestamp): + if timestamp: latency_header="\tl-min\tl-max\tl-avg" + else: latency_header="" + print "send-tp\t\trecv-tp%s"%latency_header + +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): + return parse([int],[first_line(p) for p in senders]) + +def parse_receivers(receivers): + return parse([int,float,float,float],[first_line(p) for p in receivers if p]) + +def print_data(send_stats, recv_stats): + for send,recv in map(None, send_stats, recv_stats): + line="" + if send: line += "%d"%send[0] + if recv: + 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) + send_tp = sum([l[0] for l in send_stats]) + recv_tp = sum([l[0] for l in recv_stats]) + summary = "%d\t\t%d"%(send_tp, recv_tp) + if recv_stats and len(recv_stats[0]) == 4: + l_min = sum(l[1] for l in recv_stats)/len(recv_stats) + l_max = sum(l[2] for l in recv_stats)/len(recv_stats) + l_avg = sum(l[3] for l in recv_stats)/len(recv_stats) + summary += "\t%.2f\t%.2f\t%.2f"%(l_min, l_max, l_avg) + print summary + + +class ReadyReceiver: + """A receiver for ready messages""" + def __init__(self, queue, broker): + delete_queues([queue], broker) + self.connection = qpid.messaging.Connection(broker) + self.connection.open() + self.receiver = self.connection.session().receiver( + "%s;{create:receiver,delete:receiver,node:{durable:false}}"%(queue)) + self.receiver.session.sync() + self.timeout=10 + + def wait(self, receivers): + try: + for i in receivers: self.receiver.fetch(self.timeout) + self.connection.close() + except qpid.messaging.Empty: + for r in receivers: + if (r.poll() is not None): + out,err=r.communicate() + 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),[]) + +class RoundRobin: + def __init__(self,items): + self.items = items + self.index = 0 + + def next(self): + if not self.items: return None + ret = self.items[self.index] + self.index = (self.index+1)%len(self.items) + return ret + +def main(): + opts, args = op.parse_args() + if not opts.broker: opts.broker = ["127.0.0.1"] # Deafult to local broker + opts.broker = flatten(opts.broker) + opts.client_host = flatten(opts.client_host) + brokers = RoundRobin(opts.broker) + client_hosts = RoundRobin(opts.client_host) + send_out = "" + receive_out = "" + ready_queue="%s-ready"%(opts.queue_name) + queues = ["%s-%s"%(opts.queue_name, i) for i in xrange(opts.queues)] + 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/qpid/cpp/src/tests/qpid-ctrl b/qpid/cpp/src/tests/qpid-ctrl new file mode 100755 index 0000000000..4246c57898 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-ctrl @@ -0,0 +1,120 @@ +#!/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 optparse +from qpid.messaging import * +from qpid.util import URL +from qpid.log import enable, DEBUG, WARN + +def nameval(st): + idx = st.find("=") + if idx >= 0: + name = st[0:idx] + value = st[idx+1:] + else: + name = st + value = None + return name, value + +def list_map_entries(m): + r = "" + for t in m: + r += "%s=%s " % (t, m[t]) + return r + +def get_qmfv2_result(m): + if m.properties['x-amqp-0-10.app-id'] == 'qmf2': + if m.properties['qmf.opcode'] == '_method_response': + return m.content['_arguments'] + elif m.properties['qmf.opcode'] == '_exception': + raise Exception("Error: %s" % list_map_entries(m.content['_values'])) + else: raise Exception("Invalid response received, unexpected opcode: %s" % m) + else: raise Exception("Invalid response received, not a qmfv2 method: %s" % m) + + +parser = optparse.OptionParser(usage="usage: %prog [options] COMMAND ...", + description="Invoke the specified command.") +parser.add_option("-b", "--broker", default="localhost", + help="connect to specified BROKER (default %default)") +parser.add_option("-c", "--class", dest="qmfclass", default="broker", + help="class of object on which command is being invoked (default %default)") +parser.add_option("-p", "--package", default="org.apache.qpid.broker", + help="package of object on which command is being invoked (default %default)") +parser.add_option("-i", "--id", default="amqp-broker", + help="identifier of object on which command is being invoked (default %default)") +parser.add_option("-a", "--address", default="qmf.default.direct/broker", + help="address to send commands to (default %default)") +parser.add_option("-t", "--timeout", type="float", default=5, + help="timeout in seconds to wait for response before exiting (default %default)") +parser.add_option("-v", dest="verbose", action="store_true", + help="enable logging") + +opts, args = parser.parse_args() + +if opts.verbose: + enable("qpid", DEBUG) +else: + enable("qpid", WARN) + +if args: + command = args.pop(0) +else: + parser.error("command is required") + + +conn = Connection(opts.broker) +try: + conn.open() + ssn = conn.session() + snd = ssn.sender(opts.address) + reply_to = "qmf.default.direct/%s; {node: {type: topic}}" % str(uuid4()) + rcv = ssn.receiver(reply_to) + + object_name = "%s:%s:%s" % (opts.package, opts.qmfclass, opts.id) + method_name = command + arguments = {} + for a in args: + name, val = nameval(a) + 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, + "_arguments": arguments + } + msg = Message(reply_to=reply_to, content=content) + msg.properties["x-amqp-0-10.app-id"] = "qmf2" + msg.properties["qmf.opcode"] = "_method_request" + snd.send(msg) + + try: + print list_map_entries(get_qmfv2_result(rcv.fetch(timeout=opts.timeout))) + except Empty: + print "No response received!" + except Exception, e: + print e +except ReceiverError, e: + print e +except KeyboardInterrupt: + pass + +conn.close() diff --git a/qpid/cpp/src/tests/qpid-latency-test.cpp b/qpid/cpp/src/tests/qpid-latency-test.cpp new file mode 100644 index 0000000000..20eb4568f3 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-latency-test.cpp @@ -0,0 +1,469 @@ +/* + * + * 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 <algorithm> +#include <limits> +#include <iostream> +#include <memory> +#include <sstream> +#include <vector> + +#include "TestOptions.h" +#include "qpid/sys/Thread.h" +#include "qpid/client/Connection.h" +#include "qpid/client/Message.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/sys/Time.h" + +using namespace qpid; +using namespace qpid::client; +using namespace qpid::sys; +using std::string; + +namespace qpid { +namespace tests { + +typedef std::vector<std::string> StringSet; + +struct Args : public qpid::TestOptions { + uint size; + uint count; + uint rate; + bool sync; + uint reportFrequency; + uint timeLimit; + uint concurrentConnections; + uint prefetch; + uint ack; + bool cumulative; + bool csv; + bool durable; + string base; + bool singleConnect; + + Args() : size(256), count(1000), rate(0), reportFrequency(1000), + timeLimit(0), concurrentConnections(1), + prefetch(100), ack(0), + durable(false), base("latency-test"), singleConnect(false) + + { + addOptions() + + ("size", optValue(size, "N"), "message size") + ("concurrentTests", optValue(concurrentConnections, "N"), "number of concurrent test setups, will create another publisher,\ + subcriber, queue, and connections") + ("single-connection", optValue(singleConnect, "yes|no"), "Use one connection for multiple sessions.") + ("count", optValue(count, "N"), "number of messages to send") + ("rate", optValue(rate, "N"), "target message rate (causes count to be ignored)") + ("sync", optValue(sync), "send messages synchronously") + ("report-frequency", optValue(reportFrequency, "N"), + "number of milliseconds to wait between reports (ignored unless rate specified)") + ("time-limit", optValue(timeLimit, "N"), + "test duration, in seconds") + ("prefetch", optValue(prefetch, "N"), "prefetch count (0 implies no flow control, and no acking)") + ("ack", optValue(ack, "N"), "Ack frequency in messages (defaults to half the prefetch value)") + ("durable", optValue(durable, "yes|no"), "use durable messages") + ("csv", optValue(csv), "print stats in csv format (rate,min,max,avg)") + ("cumulative", optValue(cumulative), "cumulative stats in csv format") + ("queue-base-name", optValue(base, "<name>"), "base name for queues"); + } +}; + +const std::string chars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); + +Args opts; +double c_min, c_avg, c_max; +Connection globalConnection; + +uint64_t current_time() +{ + Duration t(EPOCH, now()); + return t; +} + +struct Stats +{ + Mutex lock; + uint count; + double minLatency; + double maxLatency; + double totalLatency; + + Stats(); + void update(double l); + void print(); + void reset(); +}; + +class Client : public Runnable +{ +protected: + Connection* connection; + Connection localConnection; + AsyncSession session; + Thread thread; + string queue; + +public: + Client(const string& q); + virtual ~Client(); + + void start(); + void join(); + void run(); + virtual void test() = 0; +}; + +class Receiver : public Client, public MessageListener +{ + SubscriptionManager mgr; + uint count; + Stats& stats; + +public: + Receiver(const string& queue, Stats& stats); + void test(); + void received(Message& msg); + Stats getStats(); + uint getCount() { return count; } + void stop() { mgr.stop(); mgr.cancel(queue); } +}; + + +class Sender : public Client +{ + string generateData(uint size); + void sendByRate(); + void sendByCount(); + Receiver& receiver; + const string data; + +public: + Sender(const string& queue, Receiver& receiver); + void test(); +}; + + +class Test +{ + const string queue; + Stats stats; + Receiver receiver; + Sender sender; + AbsTime begin; + +public: + Test(const string& q) : queue(q), receiver(queue, stats), sender(queue, receiver), begin(now()) {} + void start(); + void join(); + void report(); +}; + + +Client::Client(const string& q) : queue(q) +{ + if (opts.singleConnect){ + connection = &globalConnection; + if (!globalConnection.isOpen()) opts.open(globalConnection); + }else{ + connection = &localConnection; + opts.open(localConnection); + } + session = connection->newSession(); +} + +void Client::start() +{ + thread = Thread(this); +} + +void Client::join() +{ + thread.join(); +} + +void Client::run() +{ + try{ + test(); + } catch(const std::exception& e) { + std::cout << "Error in receiver: " << e.what() << std::endl; + } +} + +Client::~Client() +{ + try{ + session.close(); + connection->close(); + } catch(const std::exception& e) { + std::cout << "Error in receiver: " << e.what() << std::endl; + } +} + +Receiver::Receiver(const string& q, Stats& s) : Client(q), mgr(session), count(0), stats(s) +{ + session.queueDeclare(arg::queue=queue, arg::durable=opts.durable, arg::autoDelete=true); + uint msgCount = session.queueQuery(arg::queue=queue).get().getMessageCount(); + if (msgCount) { + std::cout << "Warning: found " << msgCount << " msgs on " << queue << ". Purging..." << std::endl; + session.queuePurge(arg::queue=queue); + session.sync(); + } + SubscriptionSettings settings; + if (opts.prefetch) { + settings.autoAck = (opts.ack ? opts.ack : (opts.prefetch / 2)); + settings.flowControl = FlowControl::messageWindow(opts.prefetch); + } else { + settings.acceptMode = ACCEPT_MODE_NONE; + settings.flowControl = FlowControl::unlimited(); + } + mgr.subscribe(*this, queue, settings); +} + +void Receiver::test() +{ + mgr.run(); + mgr.cancel(queue); +} + +void Receiver::received(Message& msg) +{ + ++count; + uint64_t receivedAt = current_time(); + uint64_t sentAt = msg.getDeliveryProperties().getTimestamp(); + + stats.update(((double) (receivedAt - sentAt)) / TIME_MSEC); + + if (!opts.rate && count >= opts.count) { + mgr.stop(); + } +} + +void Stats::update(double latency) +{ + Mutex::ScopedLock l(lock); + count++; + minLatency = std::min(minLatency, latency); + maxLatency = std::max(maxLatency, latency); + totalLatency += latency; +} + +Stats::Stats() : count(0), minLatency(std::numeric_limits<double>::max()), maxLatency(0), totalLatency(0) {} + +void Stats::print() +{ + static bool already_have_stats = false; + uint value; + + if (opts.rate) + value = opts.rate; + else + value = opts.count; + Mutex::ScopedLock l(lock); + double aux_avg = (totalLatency / count); + if (!opts.cumulative) { + if (!opts.csv) { + if (count) { + std::cout << "Latency(ms): min=" << minLatency << ", max=" << + maxLatency << ", avg=" << aux_avg; + } else { + std::cout << "Stalled: no samples for interval"; + } + } else { + if (count) { + std::cout << value << "," << minLatency << "," << maxLatency << + "," << aux_avg; + } else { + std::cout << value << "," << minLatency << "," << maxLatency << + ", Stalled"; + } + } + } else { + if (count) { + if (already_have_stats) { + c_avg = (c_min + aux_avg) / 2; + if (c_min > minLatency) c_min = minLatency; + if (c_max < maxLatency) c_max = maxLatency; + } else { + c_avg = aux_avg; + c_min = minLatency; + c_max = maxLatency; + already_have_stats = true; + } + std::cout << value << "," << c_min << "," << c_max << + "," << c_avg; + } else { + std::cout << "Stalled: no samples for interval"; + } + } +} + +void Stats::reset() +{ + Mutex::ScopedLock l(lock); + count = 0; + totalLatency = maxLatency = 0; + minLatency = std::numeric_limits<double>::max(); +} + +Sender::Sender(const string& q, Receiver& receiver) : Client(q), receiver(receiver), data(generateData(opts.size)) {} + +void Sender::test() +{ + if (opts.rate) sendByRate(); + else sendByCount(); +} + +void Sender::sendByCount() +{ + Message msg(data, queue); + if (opts.durable) { + msg.getDeliveryProperties().setDeliveryMode(framing::PERSISTENT); + } + + for (uint i = 0; i < opts.count; i++) { + uint64_t sentAt(current_time()); + msg.getDeliveryProperties().setTimestamp(sentAt); + async(session).messageTransfer(arg::content=msg, arg::acceptMode=1); + if (opts.sync) session.sync(); + } + session.sync(); +} + +void Sender::sendByRate() +{ + Message msg(data, queue); + if (opts.durable) { + msg.getDeliveryProperties().setDeliveryMode(framing::PERSISTENT); + } + uint64_t interval = TIME_SEC/opts.rate; + int64_t timeLimit = opts.timeLimit * TIME_SEC; + uint64_t sent = 0, missedRate = 0; + AbsTime start = now(); + while (true) { + AbsTime sentAt=now(); + msg.getDeliveryProperties().setTimestamp(Duration(EPOCH, sentAt)); + async(session).messageTransfer(arg::content=msg, arg::acceptMode=1); + if (opts.sync) session.sync(); + ++sent; + AbsTime waitTill(start, sent*interval); + Duration delay(sentAt, waitTill); + if (delay < 0) + ++missedRate; + else + sys::usleep(delay / TIME_USEC); + if (timeLimit != 0 && Duration(start, now()) > timeLimit) { + session.sync(); + receiver.stop(); + break; + } + } +} + +string Sender::generateData(uint size) +{ + if (size < chars.length()) { + return chars.substr(0, size); + } + std::string data; + for (uint i = 0; i < (size / chars.length()); i++) { + data += chars; + } + data += chars.substr(0, size % chars.length()); + return data; +} + + +void Test::start() +{ + receiver.start(); + begin = AbsTime(now()); + sender.start(); +} + +void Test::join() +{ + sender.join(); + receiver.join(); + AbsTime end = now(); + Duration time(begin, end); + double msecs(time / TIME_MSEC); + if (!opts.csv) { + std::cout << "Sent " << receiver.getCount() << " msgs through " << queue + << " in " << msecs << "ms (" << (receiver.getCount() * 1000 / msecs) << " msgs/s) "; + } + stats.print(); + std::cout << std::endl; +} + +void Test::report() +{ + stats.print(); + std::cout << std::endl; + stats.reset(); +} + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char** argv) +{ + try { + opts.parse(argc, argv); + if (opts.cumulative) + opts.csv = true; + + Connection localConnection; + AsyncSession session; + + boost::ptr_vector<Test> tests(opts.concurrentConnections); + for (uint i = 0; i < opts.concurrentConnections; i++) { + std::ostringstream out; + out << opts.base << "-" << (i+1); + tests.push_back(new Test(out.str())); + } + for (boost::ptr_vector<Test>::iterator i = tests.begin(); i != tests.end(); i++) { + i->start(); + } + if (opts.rate && !opts.timeLimit) { + while (true) { + qpid::sys::usleep(opts.reportFrequency * 1000); + //print latency report: + for (boost::ptr_vector<Test>::iterator i = tests.begin(); i != tests.end(); i++) { + i->report(); + } + } + } else { + for (boost::ptr_vector<Test>::iterator i = tests.begin(); i != tests.end(); i++) { + i->join(); + } + } + + return 0; + } catch(const std::exception& e) { + std::cout << e.what() << std::endl; + } + return 1; +} diff --git a/qpid/cpp/src/tests/qpid-perftest.cpp b/qpid/cpp/src/tests/qpid-perftest.cpp new file mode 100644 index 0000000000..8a5cf05775 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-perftest.cpp @@ -0,0 +1,746 @@ +/* + * + * 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 "TestOptions.h" + +#include "qpid/client/AsyncSession.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/client/Connection.h" +#include "qpid/client/Completion.h" +#include "qpid/client/Message.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/sys/Time.h" +#include "qpid/sys/Thread.h" + +#include <boost/lexical_cast.hpp> +#include <boost/bind.hpp> +#include <boost/function.hpp> +#include <boost/ptr_container/ptr_vector.hpp> + +#include <iostream> +#include <sstream> +#include <numeric> +#include <algorithm> +#include <math.h> + + +using namespace std; +using namespace qpid; +using namespace client; +using namespace sys; +using boost::lexical_cast; +using boost::bind; + +namespace qpid { +namespace tests { + +enum Mode { SHARED, FANOUT, TOPIC }; +const char* modeNames[] = { "shared", "fanout", "topic" }; + +// istream/ostream ops so Options can read/display Mode. +istream& operator>>(istream& in, Mode& mode) { + string s; + in >> s; + int i = find(modeNames, modeNames+3, s) - modeNames; + if (i >= 3) throw Exception("Invalid mode: "+s); + mode = Mode(i); + return in; +} + +ostream& operator<<(ostream& out, Mode mode) { + return out << modeNames[mode]; +} + + +struct Opts : public TestOptions { + + // Actions + bool setup, control, publish, subscribe; + + // Queue policy + uint32_t queueMaxCount; + uint64_t queueMaxSize; + std::string baseName; + bool queueDurable; + + // Publisher + size_t pubs; + size_t count ; + size_t size; + bool confirm; + bool durable; + bool uniqueData; + bool syncPub; + + // Subscriber + size_t subs; + size_t ack; + + // General + size_t qt; + bool singleConnect; + size_t iterations; + Mode mode; + bool summary; + uint32_t intervalSub; + uint32_t intervalPub; + size_t tx; + size_t txPub; + size_t txSub; + bool commitAsync; + + static const std::string helpText; + + Opts() : + TestOptions(helpText), + setup(false), control(false), publish(false), subscribe(false), baseName("qpid-perftest"), + pubs(1), count(500000), size(1024), confirm(true), durable(false), uniqueData(false), syncPub(false), + subs(1), ack(0), + qt(1),singleConnect(false), iterations(1), mode(SHARED), summary(false), + intervalSub(0), intervalPub(0), tx(0), txPub(0), txSub(0), commitAsync(false) + { + addOptions() + ("setup", optValue(setup), "Create shared queues.") + ("control", optValue(control), "Run test, print report.") + ("publish", optValue(publish), "Publish messages.") + ("subscribe", optValue(subscribe), "Subscribe for messages.") + + ("mode", optValue(mode, "shared|fanout|topic"), "Test mode." + "\nshared: --qt queues, --npubs publishers and --nsubs subscribers per queue.\n" + "\nfanout: --npubs publishers, --nsubs subscribers, fanout exchange." + "\ntopic: --qt topics, --npubs publishers and --nsubs subscribers per topic.\n") + + ("npubs", optValue(pubs, "N"), "Create N publishers.") + ("count", optValue(count, "N"), "Each publisher sends N messages.") + ("size", optValue(size, "BYTES"), "Size of messages in bytes.") + ("pub-confirm", optValue(confirm, "yes|no"), "Publisher use confirm-mode.") + ("durable", optValue(durable, "yes|no"), "Publish messages as durable.") + ("unique-data", optValue(uniqueData, "yes|no"), "Make data for each message unique.") + ("sync-publish", optValue(syncPub, "yes|no"), "Wait for confirmation of each message before sending the next one.") + + ("nsubs", optValue(subs, "N"), "Create N subscribers.") + ("sub-ack", optValue(ack, "N"), "N>0: Subscriber acks batches of N.\n" + "N==0: Subscriber uses unconfirmed mode") + + ("qt", optValue(qt, "N"), "Create N queues or topics.") + ("single-connection", optValue(singleConnect, "yes|no"), "Use one connection for multiple sessions.") + + ("iterations", optValue(iterations, "N"), "Desired number of iterations of the test.") + ("summary,s", optValue(summary), "Summary output: pubs/sec subs/sec transfers/sec Mbytes/sec") + + ("queue-max-count", optValue(queueMaxCount, "N"), "queue policy: count to trigger 'flow to disk'") + ("queue-max-size", optValue(queueMaxSize, "N"), "queue policy: accumulated size to trigger 'flow to disk'") + ("base-name", optValue(baseName, "NAME"), "base name used for queues or topics") + ("queue-durable", optValue(queueDurable, "N"), "Make queue durable (implied if durable set)") + + ("interval_sub", optValue(intervalSub, "ms"), ">=0 delay between msg consume") + ("interval_pub", optValue(intervalPub, "ms"), ">=0 delay between msg publish") + + ("tx", optValue(tx, "N"), "if non-zero, the transaction batch size for publishing and consuming") + ("pub-tx", optValue(txPub, "N"), "if non-zero, the transaction batch size for publishing") + ("async-commit", optValue(commitAsync, "yes|no"), "Don't wait for completion of commit") + ("sub-tx", optValue(txSub, "N"), "if non-zero, the transaction batch size for consuming"); + } + + // Computed values + size_t totalPubs; + size_t totalSubs; + size_t transfers; + size_t subQuota; + + void parse(int argc, char** argv) { + TestOptions::parse(argc, argv); + switch (mode) { + case SHARED: + if (count % subs) { + count += subs - (count % subs); + cout << "WARNING: Adjusted --count to " << count + << " the nearest multiple of --nsubs" << endl; + } + totalPubs = pubs*qt; + totalSubs = subs*qt; + subQuota = (pubs*count)/subs; + break; + case FANOUT: + if (qt != 1) cerr << "WARNING: Fanout mode, ignoring --qt=" + << qt << endl; + qt=1; + totalPubs = pubs; + totalSubs = subs; + subQuota = totalPubs*count; + break; + case TOPIC: + totalPubs = pubs*qt; + totalSubs = subs*qt; + subQuota = pubs*count; + break; + } + transfers=(totalPubs*count) + (totalSubs*subQuota); + if (tx) { + if (txPub) { + cerr << "WARNING: Using overriden tx value for publishers: " << txPub << std::endl; + } else { + txPub = tx; + } + if (txSub) { + cerr << "WARNING: Using overriden tx value for subscribers: " << txSub << std::endl; + } else { + txSub = tx; + } + } + } +}; + +const std::string Opts::helpText= +"There are two ways to use qpid-perftest: single process or multi-process.\n\n" +"If none of the --setup, --publish, --subscribe or --control options\n" +"are given qpid-perftest will run a single-process test.\n" +"For a multi-process test first run:\n" +" qpid-perftest --setup <other options>\n" +"and wait for it to complete. The remaining process should run concurrently::\n" +"Run --npubs times: qpid-perftest --publish <other options>\n" +"Run --nsubs times: qpid-perftest --subscribe <other options>\n" +"Run once: qpid-perftest --control <other options>\n" +"Note the <other options> must be identical for all processes.\n"; + +Opts opts; +Connection globalConnection; + +std::string fqn(const std::string& name) +{ + ostringstream fqn; + fqn << opts.baseName << "_" << name; + return fqn.str(); +} + +struct Client : public Runnable { + Connection* connection; + Connection localConnection; + AsyncSession session; + Thread thread; + + Client() { + if (opts.singleConnect){ + connection = &globalConnection; + if (!globalConnection.isOpen()) opts.open(globalConnection); + }else{ + connection = &localConnection; + opts.open(localConnection); + } + session = connection->newSession(); + } + + ~Client() { + try { + if (connection->isOpen()) { + session.close(); + connection->close(); + } + } catch (const std::exception& e) { + std::cerr << "Error in shutdown: " << e.what() << std::endl; + } + } +}; + +struct Setup : public Client { + + void queueInit(string name, bool durable=false, const framing::FieldTable& settings=framing::FieldTable()) { + session.queueDeclare(arg::queue=name, arg::durable=durable, arg::arguments=settings); + session.queuePurge(arg::queue=name); + session.sync(); + } + + void run() { + queueInit(fqn("pub_start")); + queueInit(fqn("pub_done")); + queueInit(fqn("sub_ready")); + queueInit(fqn("sub_done")); + if (opts.iterations > 1) queueInit(fqn("sub_iteration")); + if (opts.mode==SHARED) { + framing::FieldTable settings;//queue policy settings + settings.setInt("qpid.max_count", opts.queueMaxCount); + settings.setInt("qpid.max_size", opts.queueMaxSize); + for (size_t i = 0; i < opts.qt; ++i) { + ostringstream qname; + qname << opts.baseName << i; + queueInit(qname.str(), opts.durable || opts.queueDurable, settings); + } + } + } +}; + +void expect(string actual, string expect) { + if (expect != actual) + throw Exception("Expecting "+expect+" but received "+actual); + +} + +double secs(Duration d) { return double(d)/TIME_SEC; } +double secs(AbsTime start, AbsTime finish) { + return secs(Duration(start,finish)); +} + + +// Collect rates & print stats. +class Stats { + vector<double> values; + double sum; + + public: + Stats() : sum(0) {} + + // Functor to collect rates. + void operator()(const string& data) { + try { + double d=lexical_cast<double>(data); + values.push_back(d); + sum += d; + } catch (const std::exception&) { + throw Exception("Bad report: "+data); + } + } + + double mean() const { + return sum/values.size(); + } + + double stdev() const { + if (values.size() <= 1) return 0; + double avg = mean(); + double ssq = 0; + for (vector<double>::const_iterator i = values.begin(); + i != values.end(); ++i) { + double x=*i; + x -= avg; + ssq += x*x; + } + return sqrt(ssq/(values.size()-1)); + } + + ostream& print(ostream& out) { + ostream_iterator<double> o(out, "\n"); + copy(values.begin(), values.end(), o); + out << "Average: " << mean(); + if (values.size() > 1) + out << " (std.dev. " << stdev() << ")"; + return out << endl; + } +}; + + +// Manage control queues, collect and print reports. +struct Controller : public Client { + + SubscriptionManager subs; + + Controller() : subs(session) {} + + /** Process messages from queue by applying a functor. */ + void process(size_t n, string queue, + boost::function<void (const string&)> msgFn) + { + if (!opts.summary) + cout << "Processing " << n << " messages from " + << queue << " " << flush; + LocalQueue lq; + subs.setFlowControl(n, SubscriptionManager::UNLIMITED, false); + subs.subscribe(lq, queue); + for (size_t i = 0; i < n; ++i) { + if (!opts.summary) cout << "." << flush; + msgFn(lq.pop().getData()); + } + if (!opts.summary) cout << " done." << endl; + } + + void process(size_t n, LocalQueue lq, string queue, + boost::function<void (const string&)> msgFn) + { + session.messageFlow(queue, 0, n); + if (!opts.summary) + cout << "Processing " << n << " messages from " + << queue << " " << flush; + for (size_t i = 0; i < n; ++i) { + if (!opts.summary) cout << "." << flush; + msgFn(lq.pop().getData()); + } + if (!opts.summary) cout << " done." << endl; + } + + void send(size_t n, string queue, string data) { + if (!opts.summary) + cout << "Sending " << data << " " << n << " times to " << queue + << endl; + Message msg(data, queue); + for (size_t i = 0; i < n; ++i) + session.messageTransfer(arg::content=msg, arg::acceptMode=1); + } + + void run() { // Controller + try { + // Wait for subscribers to be ready. + process(opts.totalSubs, fqn("sub_ready"), bind(expect, _1, "ready")); + + LocalQueue pubDone; + LocalQueue subDone; + subs.setFlowControl(0, SubscriptionManager::UNLIMITED, false); + subs.subscribe(pubDone, fqn("pub_done")); + subs.subscribe(subDone, fqn("sub_done")); + + double txrateTotal(0); + double mbytesTotal(0); + double pubRateTotal(0); + double subRateTotal(0); + + for (size_t j = 0; j < opts.iterations; ++j) { + 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 + } + + Stats pubRates; + Stats subRates; + + process(opts.totalPubs, pubDone, fqn("pub_done"), boost::ref(pubRates)); + 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); + + if (!opts.summary) { + cout << endl << "Total " << opts.transfers << " transfers of " + << opts.size << " bytes in " + << time << " seconds." << endl; + cout << endl << "Publish transfers/sec: " << endl; + pubRates.print(cout); + cout << endl << "Subscribe transfers/sec: " << endl; + subRates.print(cout); + cout << endl + << "Total transfers/sec: " << txrate << endl + << "Total Mbytes/sec: " << mbytes << endl; + } + else { + cout << pubRates.mean() << "\t" + << subRates.mean() << "\t" + << txrate << "\t" + << mbytes << endl; + } + + txrateTotal += txrate; + mbytesTotal += mbytes; + pubRateTotal += pubRates.mean(); + subRateTotal += subRates.mean(); + } + if (opts.iterations > 1) { + cout << "Averages: "<< endl + << (pubRateTotal / opts.iterations) << "\t" + << (subRateTotal / opts.iterations) << "\t" + << (txrateTotal / opts.iterations) << "\t" + << (mbytesTotal / opts.iterations) << endl; + } + } + catch (const std::exception& e) { + cout << "Controller exception: " << e.what() << endl; + } + } +}; + + +struct PublishThread : public Client { + string destination; + string routingKey; + + PublishThread() {}; + + PublishThread(string key, string dest=string()) { + destination=dest; + routingKey=key; + } + + void run() { // Publisher + try { + string data; + size_t offset(0); + if (opts.uniqueData) { + offset = 5; + data += "data:";//marker (requested for latency testing tool scripts) + data += string(sizeof(size_t), 'X');//space for seq no + data += session.getId().str(); + if (opts.size > data.size()) { + data += string(opts.size - data.size(), 'X'); + } else if(opts.size < data.size()) { + cout << "WARNING: Increased --size to " << data.size() + << " to honour --unique-data" << endl; + } + } else { + size_t msgSize=max(opts.size, sizeof(size_t)); + data = string(msgSize, 'X'); + } + + Message msg(data, routingKey); + if (opts.durable) + msg.getDeliveryProperties().setDeliveryMode(framing::PERSISTENT); + + + if (opts.txPub){ + session.txSelect(); + } + SubscriptionManager subs(session); + LocalQueue lq; + subs.setFlowControl(1, SubscriptionManager::UNLIMITED, true); + subs.subscribe(lq, fqn("pub_start")); + + for (size_t j = 0; j < opts.iterations; ++j) { + expect(lq.pop().getData(), "start"); + AbsTime start=now(); + for (size_t i=0; i<opts.count; i++) { + // Stamp the iteration into the message data, avoid + // any heap allocation. + const_cast<std::string&>(msg.getData()).replace(offset, sizeof(size_t), + reinterpret_cast<const char*>(&i), sizeof(size_t)); + if (opts.syncPub) { + sync(session).messageTransfer( + arg::destination=destination, + arg::content=msg, + arg::acceptMode=1); + } else { + session.messageTransfer( + arg::destination=destination, + arg::content=msg, + arg::acceptMode=1); + } + if (opts.txPub && ((i+1) % opts.txPub == 0)){ + if (opts.commitAsync){ + session.txCommit(); + } else { + sync(session).txCommit(); + } + } + if (opts.intervalPub) + qpid::sys::usleep(opts.intervalPub*1000); + } + 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")); + session.messageTransfer(arg::content=report, arg::acceptMode=1); + if (opts.txPub){ + sync(session).txCommit(); + } + } + session.close(); + } + catch (const std::exception& e) { + cout << "PublishThread exception: " << e.what() << endl; + } + } +}; + +struct SubscribeThread : public Client { + + string queue; + + SubscribeThread() {} + + SubscribeThread(string q) { queue = q; } + + SubscribeThread(string key, string ex) { + queue=session.getId().str(); // Unique name. + session.queueDeclare(arg::queue=queue, + arg::exclusive=true, + arg::autoDelete=true, + arg::durable=opts.durable); + session.exchangeBind(arg::queue=queue, + arg::exchange=ex, + arg::bindingKey=key); + } + + void verify(bool cond, const char* test, uint32_t expect, uint32_t actual) { + if (!cond) { + Message error( + QPID_MSG("Sequence error: expected n" << test << expect << " but got " << actual), + "sub_done"); + session.messageTransfer(arg::content=error, arg::acceptMode=1); + throw Exception(error.getData()); + } + } + + void run() { // Subscribe + try { + if (opts.txSub) sync(session).txSelect(); + SubscriptionManager subs(session); + SubscriptionSettings settings; + settings.autoAck = opts.txSub ? opts.txSub : opts.ack; + settings.acceptMode = (opts.txSub || opts.ack ? ACCEPT_MODE_EXPLICIT : ACCEPT_MODE_NONE); + settings.flowControl = FlowControl::messageCredit(opts.subQuota); + LocalQueue lq; + Subscription subscription = subs.subscribe(lq, queue, settings); + // Notify controller we are ready. + session.messageTransfer(arg::content=Message("ready", fqn("sub_ready")), arg::acceptMode=1); + if (opts.txSub) { + if (opts.commitAsync) session.txCommit(); + else sync(session).txCommit(); + } + + LocalQueue iterationControl; + if (opts.iterations > 1) { + subs.subscribe(iterationControl, fqn("sub_iteration"), SubscriptionSettings(FlowControl::messageCredit(0))); + } + + for (size_t j = 0; j < opts.iterations; ++j) { + if (j > 0) { + //need to wait here until all subs are done + session.messageFlow(fqn("sub_iteration"), 0, 1); + iterationControl.pop(); + + //need to allocate some more credit for subscription + session.messageFlow(queue, 0, opts.subQuota); + } + Message msg; + AbsTime start=now(); + size_t expect=0; + for (size_t i = 0; i < opts.subQuota; ++i) { + msg=lq.pop(); + if (opts.txSub && ((i+1) % opts.txSub == 0)) { + if (opts.commitAsync) session.txCommit(); + else sync(session).txCommit(); + } + if (opts.intervalSub) + qpid::sys::usleep(opts.intervalSub*1000); + // TODO aconway 2007-11-23: check message order for. + // multiple publishers. Need an array of counters, + // one per publisher and a publisher ID in the + // message. Careful not to introduce a lot of overhead + // here, e.g. no std::map, std::string etc. + // + // 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); + if (opts.pubs == 1) { + if (opts.subs == 1 || opts.mode == FANOUT) verify(n==expect, "==", expect, n); + else verify(n>=expect, ">=", expect, n); + expect = n+1; + } + } + if (opts.txSub || opts.ack) + subscription.accept(subscription.getUnaccepted()); + if (opts.txSub) { + if (opts.commitAsync) session.txCommit(); + else sync(session).txCommit(); + } + AbsTime end=now(); + + // Report to publisher. + Message result(lexical_cast<string>(opts.subQuota/secs(start,end)), + fqn("sub_done")); + session.messageTransfer(arg::content=result, arg::acceptMode=1); + if (opts.txSub) sync(session).txCommit(); + } + session.close(); + } + catch (const std::exception& e) { + cout << "SubscribeThread exception: " << e.what() << endl; + } + } +}; + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char** argv) { + int exitCode = 0; + boost::ptr_vector<Client> subs(opts.subs); + boost::ptr_vector<Client> pubs(opts.pubs); + + try { + opts.parse(argc, argv); + + string exchange; + switch (opts.mode) { + case FANOUT: exchange="amq.fanout"; break; + case TOPIC: exchange="amq.topic"; break; + case SHARED: break; + } + + bool singleProcess= + (!opts.setup && !opts.control && !opts.publish && !opts.subscribe); + if (singleProcess) + opts.setup = opts.control = opts.publish = opts.subscribe = true; + + if (opts.setup) Setup().run(); // Set up queues + + // Start pubs/subs for each queue/topic. + for (size_t i = 0; i < opts.qt; ++i) { + ostringstream key; + key << opts.baseName << i; // Queue or topic name. + if (opts.publish) { + size_t n = singleProcess ? opts.pubs : 1; + for (size_t j = 0; j < n; ++j) { + pubs.push_back(new PublishThread(key.str(), exchange)); + pubs.back().thread=Thread(pubs.back()); + } + } + if (opts.subscribe) { + size_t n = singleProcess ? opts.subs : 1; + for (size_t j = 0; j < n; ++j) { + if (opts.mode==SHARED) + subs.push_back(new SubscribeThread(key.str())); + else + subs.push_back(new SubscribeThread(key.str(),exchange)); + subs.back().thread=Thread(subs.back()); + } + } + } + + if (opts.control) Controller().run(); + } + catch (const std::exception& e) { + cout << endl << e.what() << endl; + exitCode = 1; + } + + // Wait for started threads. + if (opts.publish) { + for (boost::ptr_vector<Client>::iterator i=pubs.begin(); + i != pubs.end(); + ++i) + i->thread.join(); + } + + if (opts.subscribe) { + for (boost::ptr_vector<Client>::iterator i=subs.begin(); + i != subs.end(); + ++i) + i->thread.join(); + } + return exitCode; +} diff --git a/qpid/cpp/src/tests/qpid-ping.cpp b/qpid/cpp/src/tests/qpid-ping.cpp new file mode 100644 index 0000000000..0cb4afa0ee --- /dev/null +++ b/qpid/cpp/src/tests/qpid-ping.cpp @@ -0,0 +1,76 @@ +/* + * + * 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 "TestOptions.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/client/Connection.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/sys/Time.h" +#include "qpid/sys/Thread.h" +#include "qpid/sys/Runnable.h" +#include "qpid/sys/Monitor.h" +#include "qpid/framing/Uuid.h" +#include <string> +#include <iostream> + +using namespace std; +using namespace qpid::sys; +using namespace qpid::framing; +using namespace qpid::client; +using namespace qpid; + +struct PingOptions : public qpid::TestOptions { + int timeout; // Timeout in seconds. + bool quiet; // No output + PingOptions() : timeout(1), quiet(false) { + addOptions() + ("timeout,t", optValue(timeout, "SECONDS"), "Max time to wait.") + ("quiet,q", optValue(quiet), "Don't print anything to stderr/stdout."); + } +}; + +int main(int argc, char** argv) { + try { + PingOptions opts; + opts.parse(argc, argv); + opts.con.heartbeat = (opts.timeout+1)/2; + Connection connection; + opts.open(connection); + if (!opts.quiet) cout << "Opened connection." << endl; + AsyncSession s = connection.newSession(); + string qname(Uuid(true).str()); + s.queueDeclare(arg::queue=qname,arg::autoDelete=true,arg::exclusive=true); + s.messageTransfer(arg::content=Message("hello", qname)); + if (!opts.quiet) cout << "Sent message." << endl; + SubscriptionManager subs(s); + subs.get(qname); + if (!opts.quiet) cout << "Received message." << endl; + s.sync(); + s.close(); + connection.close(); + if (!opts.quiet) cout << "Success." << endl; + return 0; + } catch (const exception& e) { + cerr << "Error: " << e.what() << endl; + return 1; + } +} diff --git a/qpid/cpp/src/tests/qpid-receive.cpp b/qpid/cpp/src/tests/qpid-receive.cpp new file mode 100644 index 0000000000..9c713e872a --- /dev/null +++ b/qpid/cpp/src/tests/qpid-receive.cpp @@ -0,0 +1,269 @@ +/* + * + * 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/sys/Time.h" +#include "TestOptions.h" +#include "Statistics.h" + +#include <iostream> +#include <memory> + +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; + int64_t timeout; + bool forever; + uint messages; + bool ignoreDuplicates; + bool checkRedelivered; + uint capacity; + uint ackFrequency; + uint tx; + uint rollbackFrequency; + bool printContent; + bool printHeaders; + bool failoverUpdates; + qpid::log::Options log; + bool reportTotal; + uint reportEvery; + bool reportHeader; + string readyAddress; + uint receiveRate; + + Options(const std::string& argv0=std::string()) + : qpid::Options("Options"), + help(false), + url("amqp:tcp:127.0.0.1"), + timeout(0), + forever(false), + messages(0), + ignoreDuplicates(false), + checkRedelivered(false), + capacity(1000), + ackFrequency(100), + tx(0), + rollbackFrequency(0), + printContent(true), + printHeaders(false), + failoverUpdates(false), + log(argv0), + reportTotal(false), + reportEvery(0), + reportHeader(true), + receiveRate(0) + { + addOptions() + ("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", 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)") + ("rollback-frequency", qpid::optValue(rollbackFrequency, "N"), "rollback frequency (0 implies no transaction will be rolledback)") + ("print-content", qpid::optValue(printContent, "yes|no"), "print out message content") + ("print-headers", qpid::optValue(printHeaders, "yes|no"), "print out message headers") + ("failover-updates", qpid::optValue(failoverUpdates), "Listen for membership updates distributed via amq.failover") + ("report-total", qpid::optValue(reportTotal), "Report total throughput and latency statistics") + ("report-every", qpid::optValue(reportEvery,"N"), "Report throughput and latency statistics every N messages.") + ("report-header", qpid::optValue(reportHeader, "yes|no"), "Headers on report.") + ("ready-address", qpid::optValue(readyAddress, "ADDRESS"), "send a message to this address when ready to receive") + ("receive-rate", qpid::optValue(receiveRate,"N"), "Receive at rate of N messages/second. 0 means receive as fast as possible.") + ("help", qpid::optValue(help), "print this usage statement"); + add(log); + } + + Duration getTimeout() + { + if (forever) return Duration::FOREVER; + else return Duration::SECOND*timeout; + + } + 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 + << "Drains messages from the specified address" << 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 SequenceTracker +{ + uint lastSn; + public: + SequenceTracker() : lastSn(0) {} + + bool isDuplicate(Message& message) + { + uint sn = message.getProperties()[SN]; + if (lastSn < sn) { + lastSn = sn; + return false; + } else { + return true; + } + } +}; + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char ** argv) +{ + Connection connection; + try { + Options opts; + if (opts.parse(argc, argv)) { + connection = Connection(opts.url, opts.connectionOptions); + connection.open(); + std::auto_ptr<FailoverUpdates> updates(opts.failoverUpdates ? new FailoverUpdates(connection) : 0); + Session session = opts.tx ? connection.createTransactionalSession() : connection.createSession(); + Receiver receiver = session.createReceiver(opts.address); + receiver.setCapacity(opts.capacity); + Message msg; + uint count = 0; + uint txCount = 0; + SequenceTracker sequenceTracker; + Duration timeout = opts.getTimeout(); + bool done = false; + Reporter<ThroughputAndLatency> reporter(std::cout, opts.reportEvery, opts.reportHeader); + if (!opts.readyAddress.empty()) + session.createSender(opts.readyAddress).send(msg); + + // For receive rate calculation + qpid::sys::AbsTime start = qpid::sys::now(); + int64_t interval = 0; + if (opts.receiveRate) interval = qpid::sys::TIME_SEC/opts.receiveRate; + + std::map<std::string,Sender> replyTo; + + while (!done && receiver.fetch(msg, timeout)) { + reporter.message(msg); + if (!opts.ignoreDuplicates || !sequenceTracker.isDuplicate(msg)) { + if (msg.getContent() == EOS) { + done = true; + } else { + ++count; + if (opts.printHeaders) { + if (msg.getSubject().size()) std::cout << "Subject: " << msg.getSubject() << std::endl; + if (msg.getReplyTo()) std::cout << "ReplyTo: " << msg.getReplyTo() << std::endl; + if (msg.getCorrelationId().size()) std::cout << "CorrelationId: " << msg.getCorrelationId() << std::endl; + if (msg.getUserId().size()) std::cout << "UserId: " << msg.getUserId() << std::endl; + if (msg.getTtl().getMilliseconds()) std::cout << "TTL: " << msg.getTtl().getMilliseconds() << std::endl; + if (msg.getPriority()) std::cout << "Priority: " << msg.getPriority() << std::endl; + if (msg.getDurable()) std::cout << "Durable: true" << std::endl; + if (msg.getRedelivered()) std::cout << "Redelivered: true" << std::endl; + std::cout << "Properties: " << msg.getProperties() << std::endl; + std::cout << std::endl; + } + if (opts.printContent) + 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)) { + session.rollback(); + } else { + session.commit(); + } + } else if (opts.ackFrequency && (count % opts.ackFrequency == 0)) { + session.acknowledge(); + } + if (msg.getReplyTo()) { // Echo message back to reply-to address. + Sender& s = replyTo[msg.getReplyTo().str()]; + if (s.isNull()) { + s = session.createSender(msg.getReplyTo()); + s.setCapacity(opts.capacity); + } + s.send(msg); + } + if (opts.receiveRate) { + qpid::sys::AbsTime waitTill(start, count*interval); + int64_t delay = qpid::sys::Duration(qpid::sys::now(), waitTill); + if (delay > 0) qpid::sys::usleep(delay/qpid::sys::TIME_USEC); + } + // Clear out message properties & content for next iteration. + msg = Message(); // TODO aconway 2010-12-01: should be done by fetch + } + if (opts.reportTotal) reporter.report(); + if (opts.tx) { + if (opts.rollbackFrequency && (++txCount % opts.rollbackFrequency == 0)) { + session.rollback(); + } else { + session.commit(); + } + } else { + session.acknowledge(); + } + session.close(); + connection.close(); + return 0; + } + } catch(const std::exception& error) { + std::cerr << "qpid-receive: " << error.what() << std::endl; + connection.close(); + return 1; + } +} diff --git a/qpid/cpp/src/tests/qpid-send.cpp b/qpid/cpp/src/tests/qpid-send.cpp new file mode 100644 index 0000000000..ef5e98e2a0 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-send.cpp @@ -0,0 +1,375 @@ +/* + * + * 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/Message.h> +#include <qpid/messaging/Sender.h> +#include <qpid/messaging/Receiver.h> +#include <qpid/messaging/Session.h> +#include <qpid/messaging/FailoverUpdates.h> +#include <qpid/sys/Time.h> +#include <qpid/sys/Monitor.h> +#include "TestOptions.h" +#include "Statistics.h" + +#include <fstream> +#include <iostream> +#include <memory> + +using namespace std; +using namespace qpid::messaging; +using namespace qpid::types; + +typedef std::vector<std::string> string_vector; + +namespace qpid { +namespace tests { + +struct Options : public qpid::Options +{ + bool help; + std::string url; + std::string connectionOptions; + std::string address; + uint messages; + std::string id; + std::string replyto; + uint sendEos; + bool durable; + uint ttl; + uint priority; + std::string userid; + std::string correlationid; + string_vector properties; + string_vector entries; + std::string contentString; + uint contentSize; + bool contentStdin; + uint tx; + uint rollbackFrequency; + uint capacity; + bool failoverUpdates; + qpid::log::Options log; + bool reportTotal; + uint reportEvery; + bool reportHeader; + uint sendRate; + uint flowControl; + bool sequence; + bool timestamp; + + Options(const std::string& argv0=std::string()) + : qpid::Options("Options"), + help(false), + url("amqp:tcp:127.0.0.1"), + messages(1), + sendEos(0), + durable(false), + ttl(0), + priority(0), + contentString(), + contentSize(0), + contentStdin(false), + tx(0), + rollbackFrequency(0), + capacity(1000), + failoverUpdates(false), + log(argv0), + reportTotal(false), + reportEvery(0), + reportHeader(true), + sendRate(0), + flowControl(0), + sequence(true), + timestamp(true) + { + addOptions() + ("broker,b", qpid::optValue(url, "URL"), "url of broker to connect to") + ("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)") + ("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") + ("content-string", qpid::optValue(contentString, "CONTENT"), "use CONTENT as message content") + ("content-size", qpid::optValue(contentSize, "N"), "create an N-byte message content") + ("content-map,M", qpid::optValue(entries, "NAME=VALUE"), "specify entry for map content") + ("content-stdin", qpid::optValue(contentStdin), "read message content from stdin, one line per message") + ("capacity", qpid::optValue(capacity, "N"), "size of the senders outgoing message queue") + ("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)") + ("failover-updates", qpid::optValue(failoverUpdates), "Listen for membership updates distributed via amq.failover") + ("report-total", qpid::optValue(reportTotal), "Report total throughput statistics") + ("report-every", qpid::optValue(reportEvery,"N"), "Report throughput statistics every N messages") + ("report-header", qpid::optValue(reportHeader, "yes|no"), "Headers on report.") + ("send-rate", qpid::optValue(sendRate,"N"), "Send at rate of N messages/second. 0 means send as fast as possible.") + ("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)") + ("help", qpid::optValue(help), "print this usage statement"); + add(log); + } + + 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 + << "Drains messages from the specified address" << 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; + } + } + + static bool nameval(const std::string& in, std::string& name, std::string& value) + { + std::string::size_type i = in.find("="); + if (i == std::string::npos) { + name = in; + return false; + } else { + name = in.substr(0, i); + if (i+1 < in.size()) { + value = in.substr(i+1); + return true; + } else { + return false; + } + } + } + + static void setProperty(Message& message, const std::string& property) + { + std::string name; + std::string value; + if (nameval(property, name, value)) { + message.getProperties()[name] = value; + } else { + message.getProperties()[name] = Variant(); + } + } + + void setProperties(Message& message) const + { + for (string_vector::const_iterator i = properties.begin(); i != properties.end(); ++i) { + setProperty(message, *i); + } + } + + void setEntries(Variant::Map& content) const + { + for (string_vector::const_iterator i = entries.begin(); i != entries.end(); ++i) { + std::string name; + std::string value; + if (nameval(*i, name, value)) { + content[name] = value; + } else { + content[name] = Variant(); + } + } + } +}; + +const string EOS("eos"); +const string SN("sn"); +const string TS("ts"); + +}} // namespace qpid::tests + +using namespace qpid::tests; + +class ContentGenerator { + public: + virtual ~ContentGenerator() {} + virtual bool setContent(Message& msg) = 0; +}; + +class GetlineContentGenerator : public ContentGenerator { + public: + virtual bool setContent(Message& msg) { + string content; + bool got = getline(std::cin, content); + if (got) msg.setContent(content); + return got; + } +}; + +class FixedContentGenerator : public ContentGenerator { + public: + FixedContentGenerator(const string& s) : content(s) {} + virtual bool setContent(Message& msg) { + msg.setContent(content); + return true; + } + private: + std::string content; +}; + +class MapContentGenerator : public ContentGenerator { + public: + MapContentGenerator(const Options& opt) : opts(opt) {} + virtual bool setContent(Message& msg) { + Variant::Map map; + opts.setEntries(map); + encode(map, msg); + return true; + } + private: + const Options& opts; +}; + +int main(int argc, char ** argv) +{ + Connection connection; + Options opts; + try { + if (opts.parse(argc, argv)) { + connection = Connection(opts.url, opts.connectionOptions); + connection.open(); + std::auto_ptr<FailoverUpdates> updates(opts.failoverUpdates ? new FailoverUpdates(connection) : 0); + Session session = opts.tx ? connection.createTransactionalSession() : connection.createSession(); + Sender sender = session.createSender(opts.address); + if (opts.capacity) sender.setCapacity(opts.capacity); + Message msg; + msg.setDurable(opts.durable); + if (opts.ttl) { + msg.setTtl(Duration(opts.ttl)); + } + if (opts.priority) { + msg.setPriority(opts.priority); + } + if (!opts.replyto.empty()) { + if (opts.flowControl) + throw Exception("Can't use reply-to and flow-control together"); + msg.setReplyTo(Address(opts.replyto)); + } + if (!opts.userid.empty()) msg.setUserId(opts.userid); + if (!opts.correlationid.empty()) msg.setCorrelationId(opts.correlationid); + opts.setProperties(msg); + uint sent = 0; + uint txCount = 0; + Reporter<Throughput> reporter(std::cout, opts.reportEvery, opts.reportHeader); + + std::auto_ptr<ContentGenerator> contentGen; + if (opts.contentStdin) { + opts.messages = 0; // Don't limit # messages sent. + contentGen.reset(new GetlineContentGenerator); + } + else if (opts.entries.size() > 0) + contentGen.reset(new MapContentGenerator(opts)); + else if (opts.contentSize > 0) + contentGen.reset(new FixedContentGenerator(string(opts.contentSize, 'X'))); + else + contentGen.reset(new FixedContentGenerator(opts.contentString)); + + qpid::sys::AbsTime start = qpid::sys::now(); + int64_t interval = 0; + if (opts.sendRate) interval = qpid::sys::TIME_SEC/opts.sendRate; + + Receiver flowControlReceiver; + Address flowControlAddress("flow-"+Uuid(true).str()+";{create:always,delete:always}"); + uint flowSent = 0; + if (opts.flowControl) { + flowControlReceiver = session.createReceiver(flowControlAddress); + flowControlReceiver.setCapacity(2); + } + + while (contentGen->setContent(msg)) { + ++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); + ++flowSent; + } + else + msg.setReplyTo(Address()); // Clear the reply address. + } + sender.send(msg); + reporter.message(msg); + + if (opts.tx && (sent % opts.tx == 0)) { + if (opts.rollbackFrequency && + (++txCount % opts.rollbackFrequency == 0)) + session.rollback(); + else + session.commit(); + } + if (opts.messages && sent >= opts.messages) break; + + if (opts.flowControl && flowSent == 2) { + flowControlReceiver.get(Duration::SECOND); + --flowSent; + } + + 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); + } + } + for ( ; flowSent>0; --flowSent) + flowControlReceiver.get(Duration::SECOND); + if (opts.reportTotal) reporter.report(); + for (uint i = opts.sendEos; i > 0; --i) { + if (opts.sequence) + msg.getProperties()[SN] = ++sent; + msg.setContent(EOS); //TODO: add in ability to send digest or similar + sender.send(msg); + } + if (opts.tx) { + if (opts.rollbackFrequency && (++txCount % opts.rollbackFrequency == 0)) { + session.rollback(); + } else { + session.commit(); + } + } + session.sync(); + session.close(); + connection.close(); + return 0; + } + } catch(const std::exception& error) { + std::cerr << "qpid-send: " << error.what() << std::endl; + connection.close(); + return 1; + } +} diff --git a/qpid/cpp/src/tests/qpid-src-rinstall b/qpid/cpp/src/tests/qpid-src-rinstall new file mode 100755 index 0000000000..5e69e0ade1 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-src-rinstall @@ -0,0 +1,31 @@ +#!/bin/sh +# +# Licensed to the Apache Software Foundation (ASF) under onemake +# 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. +# + +# Copy the source tree and run "make install" on each of $HOSTS +# Must be run in a configured qpid build directory. + +absdir() { echo `cd $1 && pwd`; } + +test -f config.status || { echo "Not in a configured build directory."; } +CONFIGURE=`./config.status -V | grep '^configured by' | sed 's/^configured by \([^,]*\),.*$/\1/'` +CONFIG_OPTIONS=`./config.status -V | grep 'with options' | sed 's/^.*with options "\([^"]*\)".*$/\1/'` +set -ex +rsynchosts `absdir $(dirname $CONFIGURE)/..` # Copy cpp srcdir and siblings. +allhosts -bo rbuild.log "mkdir -p $PWD && cd $PWD && { test -f config.status || $CONFIGURE $CONFIG_OPTIONS; } && make && make -j1 install" diff --git a/qpid/cpp/src/tests/qpid-stream.cpp b/qpid/cpp/src/tests/qpid-stream.cpp new file mode 100644 index 0000000000..f02a484750 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-stream.cpp @@ -0,0 +1,193 @@ +/* + * + * 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/Connection.h> +#include <qpid/messaging/Message.h> +#include <qpid/messaging/Receiver.h> +#include <qpid/messaging/Sender.h> +#include <qpid/messaging/Session.h> +#include <qpid/sys/Runnable.h> +#include <qpid/sys/Thread.h> +#include <qpid/sys/Time.h> +#include <qpid/Options.h> +#include <iostream> +#include <string> + +using namespace qpid::messaging; +using namespace qpid::types; + +namespace qpid { +namespace tests { + +struct Args : public qpid::Options +{ + std::string url; + std::string address; + uint size; + uint rate; + bool durable; + uint receiverCapacity; + uint senderCapacity; + uint ackFrequency; + + Args() : + url("amqp:tcp:127.0.0.1:5672"), + address("test-queue"), + size(512), + rate(1000), + durable(false), + receiverCapacity(0), + senderCapacity(0), + ackFrequency(1) + { + addOptions() + ("url", qpid::optValue(url, "URL"), "Url to connect to.") + ("address", qpid::optValue(address, "ADDRESS"), "Address to stream messages through.") + ("size", qpid::optValue(size, "bytes"), "Message size in bytes (content only, not headers).") + ("rate", qpid::optValue(rate, "msgs/sec"), "Rate at which to stream messages.") + ("durable", qpid::optValue(durable, "true|false"), "Mark messages as durable.") + ("sender-capacity", qpid::optValue(senderCapacity, "N"), "Credit window (0 implies infinite window)") + ("receiver-capacity", qpid::optValue(receiverCapacity, "N"), "Credit window (0 implies infinite window)") + ("ack-frequency", qpid::optValue(ackFrequency, "N"), + "Ack frequency (0 implies none of the messages will get accepted)"); + } +}; + +Args opts; + +const std::string TS = "ts"; + +uint64_t timestamp(const qpid::sys::AbsTime& time) +{ + qpid::sys::Duration t(qpid::sys::EPOCH, time); + return t; +} + +struct Client : qpid::sys::Runnable +{ + virtual ~Client() {} + virtual void doWork(Session&) = 0; + + void run() + { + Connection connection(opts.url); + try { + connection.open(); + Session session = connection.createSession(); + doWork(session); + session.close(); + connection.close(); + } catch(const std::exception& error) { + std::cout << error.what() << std::endl; + connection.close(); + } + } + + qpid::sys::Thread thread; + + void start() { thread = qpid::sys::Thread(this); } + void join() { thread.join(); } +}; + +struct Publish : Client +{ + void doWork(Session& session) + { + Sender sender = session.createSender(opts.address); + if (opts.senderCapacity) sender.setCapacity(opts.senderCapacity); + Message msg(std::string(opts.size, 'X')); + uint64_t interval = qpid::sys::TIME_SEC / opts.rate; + uint64_t sent = 0, missedRate = 0; + qpid::sys::AbsTime start = qpid::sys::now(); + while (true) { + qpid::sys::AbsTime sentAt = qpid::sys::now(); + msg.getProperties()[TS] = timestamp(sentAt); + sender.send(msg); + ++sent; + qpid::sys::AbsTime waitTill(start, sent*interval); + qpid::sys::Duration delay(sentAt, waitTill); + if (delay < 0) { + ++missedRate; + } else { + qpid::sys::usleep(delay / qpid::sys::TIME_USEC); + } + } + } +}; + +struct Consume : Client +{ + void doWork(Session& session) + { + Message msg; + uint64_t received = 0; + double minLatency = std::numeric_limits<double>::max(); + double maxLatency = 0; + double totalLatency = 0; + Receiver receiver = session.createReceiver(opts.address); + if (opts.receiverCapacity) receiver.setCapacity(opts.receiverCapacity); + while (receiver.fetch(msg)) { + ++received; + if (opts.ackFrequency && (received % opts.ackFrequency == 0)) { + session.acknowledge(); + } + //calculate latency + uint64_t receivedAt = timestamp(qpid::sys::now()); + uint64_t sentAt = msg.getProperties()[TS].asUint64(); + double latency = ((double) (receivedAt - sentAt)) / qpid::sys::TIME_MSEC; + + //update avg, min & max + minLatency = std::min(minLatency, latency); + maxLatency = std::max(maxLatency, latency); + totalLatency += latency; + + if (received % opts.rate == 0) { + std::cout << "count=" << received + << ", avg=" << (totalLatency/received) + << ", min=" << minLatency + << ", max=" << maxLatency << std::endl; + } + } + } +}; + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char** argv) +{ + try { + opts.parse(argc, argv); + Publish publish; + Consume consume; + publish.start(); + consume.start(); + consume.join(); + publish.join(); + return 0; + } catch(const std::exception& error) { + std::cout << error.what() << std::endl; + } + return 1; +} + + diff --git a/qpid/cpp/src/tests/qpid-test-cluster b/qpid/cpp/src/tests/qpid-test-cluster new file mode 100755 index 0000000000..9887406ef9 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-test-cluster @@ -0,0 +1,109 @@ +#!/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. +# + +usage() { + echo "Usage: `basename $0` [options] start|stop|restart|check [hosts] +Start/stop/restart a cluster on specified hosts or on \$HOSTS via ssh. + +Options: + -l USER Run qpidd and copy files as USER. + -e SCRIPT Source SCRIPT for environment settings. Copies SCRIPT to each host. + Default is $DEFAULT_ENV. + -c CONFIG Use CONFIG as qpidd config file. Copies CONFIG to each host. + Default is $DEFAULT_CONF + -d Delete data-dir and log file before starting broker. +" + exit 1 +} + +DEFAULT_CONF=~/qpid-test-qpidd.conf +DEFAULT_ENV=~/qpid-test-env.sh + +test -f $DEFAULT_CONF && CONF_FILE=$DEFAULT_CONF +test -f $DEFAULT_ENV && ENV_FILE=$DEFAULT_ENV + +while getopts "l:e:c:d" opt; do + case $opt in + l) SSHOPTS="-l$OPTARG $SSHOPTS" ; RSYNC_USER="$OPTARG@" ;; + e) ENV_FILE=$OPTARG ;; + c) CONF_FILE=$OPTARG ;; + d) DO_DELETE=1 ;; + *) usage;; + esac +done +shift `expr $OPTIND - 1` +test "$*" || usage +CMD=$1; shift +HOSTS=${*:-$HOSTS} + +conf_value() { test -f "$CONF_FILE" && awk -F= "/^$1=/ {print \$2}" $CONF_FILE; } + +if test -n "$CONF_FILE"; then + test -f "$CONF_FILE" || { echo Config file not found: $CONF_FILE; exit 1; } + RSYNCFILES="$RSYNCFILES $CONF_FILE" + QPIDD_ARGS="$QPIDD_ARGS --config $CONF_FILE" + CONF_PORT=`conf_value port` + CONF_DATA_DIR=`conf_value data-dir` + CONF_LOG_FILE=`conf_value log-to-file` +fi + +if test -n "$ENV_FILE"; then + test -f "$ENV_FILE" || { echo Environment file not found: $ENV_FILE; exit 1; } + RSYNCFILES="$RSYNCFILES $ENV_FILE" + SOURCE_ENV="source $ENV_FILE ; " +fi + +test -n "$RSYNCFILES" && rsynchosts $RSYNCFILES # Copy conf/env files to all hosts + +do_start() { + for h in $HOSTS; do + COMMAND="qpidd -d $QPIDD_ARGS" + id -nG | grep '\<ais\>' >/dev/null && COMMAND="sg ais -c '$COMMAND'" + if test "$DO_DELETE"; then COMMAND="rm -rf $CONF_DATA_DIR $CONF_LOG_FILE; $COMMAND"; fi + ssh $h "$SOURCE_ENV $COMMAND" || { echo "Failed to start on $h"; exit 1; } + done +} + +do_stop() { + for h in $HOSTS; do + ssh $h "$SOURCE_ENV qpidd -q --no-module-dir --no-data-dir $QPIDD_ARGS" + done +} + +do_status() { + for h in $HOSTS; do + if ssh $h "$SOURCE_ENV qpidd -c --no-module-dir --no-data-dir $QPIDD_ARGS > /dev/null"; then + echo "$h ok" + else + echo "$h not running" + STATUS=1 + fi + done +} + +case $CMD in + start) do_start ;; + stop) do_stop ;; + restart) do_stop ; do_start ;; + status) do_status ;; + *) usage;; +esac + +exit $STATUS diff --git a/qpid/cpp/src/tests/qpid-topic-listener.cpp b/qpid/cpp/src/tests/qpid-topic-listener.cpp new file mode 100644 index 0000000000..c42e76d760 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-topic-listener.cpp @@ -0,0 +1,209 @@ +/* + * + * 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. + * + */ + +/** + * This file provides one half of a test and example of a pub-sub + * style of interaction. See qpid-topic-publisher.cpp for the other half, + * in which the logic for publishing is defined. + * + * This file contains the listener logic. A listener will subscribe to + * a logical 'topic'. It will count the number of messages it receives + * and the time elapsed between the first one and the last one. It + * recognises two types of 'special' message that tell it to (a) send + * a report containing this information, (b) shutdown (i.e. stop + * listening). + */ + +#include "TestOptions.h" +#include "qpid/client/Connection.h" +#include "qpid/client/MessageListener.h" +#include "qpid/client/Session.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/sys/SystemInfo.h" +#include "qpid/sys/Time.h" +#include "qpid/framing/FieldValue.h" +#include <iostream> +#include <sstream> + +using namespace qpid; +using namespace qpid::client; +using namespace qpid::sys; +using namespace qpid::framing; +using namespace std; + +namespace qpid { +namespace tests { + +/** + * A message listener implementation in which the runtime logic is + * defined. + */ +class Listener : public MessageListener{ + Session session; + SubscriptionManager& mgr; + const string responseQueue; + const bool transactional; + bool init; + int count; + AbsTime start; + + void shutdown(); + void report(); +public: + Listener(const Session& session, SubscriptionManager& mgr, const string& reponseQueue, bool tx); + virtual void received(Message& msg); + Subscription subscription; +}; + +/** + * A utility class for managing the options passed in. + */ +struct Args : public qpid::TestOptions { + int ack; + bool transactional; + bool durable; + int prefetch; + string statusqueue; + + Args() : ack(0), transactional(false), durable(false), prefetch(0) { + addOptions() + ("ack", optValue(ack, "MODE"), "Ack frequency in messages (defaults to half the prefetch value)") + ("transactional", optValue(transactional), "Use transactions") + ("durable", optValue(durable), "subscribers should use durable queues") + ("prefetch", optValue(prefetch, "N"), "prefetch count (0 implies no flow control, and no acking)") + ("status-queue", optValue(statusqueue, "QUEUE-NAME"), "Message queue to put status messages on"); + } +}; + +Listener::Listener(const Session& s, SubscriptionManager& m, const string& _responseq, bool tx) : + session(s), mgr(m), responseQueue(_responseq), transactional(tx), init(false), count(0){} + +void Listener::received(Message& message){ + if(!init){ + start = now(); + count = 0; + init = true; + cout << "Batch started." << endl; + } + string type = message.getHeaders().getAsString("TYPE"); + + if(string("TERMINATION_REQUEST") == type){ + shutdown(); + }else if(string("REPORT_REQUEST") == type){ + subscription.accept(subscription.getUnaccepted()); // Accept everything upto this point + cout <<"Batch ended, sending report." << endl; + //send a report: + report(); + init = false; + }else if (++count % 1000 == 0){ + cout <<"Received " << count << " messages." << endl; + } +} + +void Listener::shutdown(){ + mgr.stop(); +} + +void Listener::report(){ + AbsTime finish = now(); + Duration time(start, finish); + stringstream reportstr; + reportstr << "Received " << count << " messages in " + << time/TIME_MSEC << " ms."; + Message msg(reportstr.str(), responseQueue); + msg.getHeaders().setString("TYPE", "REPORT"); + session.messageTransfer(arg::destination="amq.direct", arg::content=msg, arg::acceptMode=1); + if(transactional){ + sync(session).txCommit(); + } +} + +}} // namespace qpid::tests + +using namespace qpid::tests; + +/** + * The main routine creates a Listener instance and sets it up to + * consume from a private queue bound to the exchange with the + * appropriate topic name. + */ +int main(int argc, char** argv){ + try{ + Args args; + args.parse(argc, argv); + if(args.help) + cout << args << endl; + else { + Connection connection; + args.open(connection); + AsyncSession session = connection.newSession(); + + //declare exchange, queue and bind them: + session.queueDeclare(arg::queue="response"); + std::string control = "control_" + session.getId().str(); + if (args.durable) { + session.queueDeclare(arg::queue=control, arg::durable=true); + } else { + session.queueDeclare(arg::queue=control, arg::exclusive=true, arg::autoDelete=true); + } + session.exchangeBind(arg::exchange="amq.topic", arg::queue=control, arg::bindingKey="topic_control"); + + //set up listener + SubscriptionManager mgr(session); + Listener listener(session, mgr, "response", args.transactional); + SubscriptionSettings settings; + if (args.prefetch) { + settings.autoAck = (args.ack ? args.ack : (args.prefetch / 2)); + settings.flowControl = FlowControl::messageCredit(args.prefetch); + } else { + settings.acceptMode = ACCEPT_MODE_NONE; + settings.flowControl = FlowControl::unlimited(); + } + listener.subscription = mgr.subscribe(listener, control, settings); + session.sync(); + + if( args.statusqueue.length() > 0 ) { + stringstream msg_str; + msg_str << "qpid-topic-listener: " << qpid::sys::SystemInfo::getProcessId(); + session.messageTransfer(arg::content=Message(msg_str.str(), args.statusqueue)); + cout << "Ready status put on queue '" << args.statusqueue << "'" << endl; + } + + if (args.transactional) { + session.txSelect(); + } + + cout << "qpid-topic-listener: listening..." << endl; + mgr.run(); + if (args.durable) { + session.queueDelete(arg::queue=control); + } + session.close(); + cout << "closing connection" << endl; + connection.close(); + } + return 0; + } catch (const std::exception& error) { + cout << "qpid-topic-listener: " << error.what() << endl; + } + return 1; +} diff --git a/qpid/cpp/src/tests/qpid-topic-publisher.cpp b/qpid/cpp/src/tests/qpid-topic-publisher.cpp new file mode 100644 index 0000000000..f9107b90d0 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-topic-publisher.cpp @@ -0,0 +1,230 @@ +/* + * + * 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. + * + */ + +/** + * This file provides one half of a test and example of a pub-sub + * style of interaction. See qpid-topic-listener.cpp for the other half, in + * which the logic for subscribers is defined. + * + * This file contains the publisher logic. The publisher will send a + * number of messages to the exchange with the appropriate routing key + * for the logical 'topic'. Once it has done this it will then send a + * request that each subscriber report back with the number of message + * it has received and the time that elapsed between receiving the + * first one and receiving the report request. Once the expected + * number of reports are received, it sends out a request that each + * subscriber shutdown. + */ + +#include "TestOptions.h" +#include "qpid/client/Connection.h" +#include "qpid/client/MessageListener.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/sys/Monitor.h" +#include "qpid/sys/Time.h" +#include <cstdlib> +#include <iostream> + +using namespace qpid; +using namespace qpid::client; +using namespace qpid::sys; +using namespace std; + +namespace qpid { +namespace tests { + +/** + * The publishing logic is defined in this class. It implements + * message listener and can therfore be used to receive messages sent + * back by the subscribers. + */ +class Publisher { + AsyncSession session; + SubscriptionManager mgr; + LocalQueue queue; + const string controlTopic; + const bool transactional; + const bool durable; + + string generateData(int size); + +public: + Publisher(const AsyncSession& session, const string& controlTopic, bool tx, bool durable); + int64_t publish(int msgs, int listeners, int size); + void terminate(); +}; + +/** + * A utility class for managing the options passed in to the test + */ +struct Args : public TestOptions { + int messages; + int subscribers; + bool transactional; + bool durable; + int batches; + int delay; + int size; + string statusqueue; + + Args() : messages(1000), subscribers(1), + transactional(false), durable(false), + batches(1), delay(0), size(256) + { + addOptions() + ("messages", optValue(messages, "N"), "how many messages to send") + ("subscribers", optValue(subscribers, "N"), "how many subscribers to expect reports from") + ("transactional", optValue(transactional), "client should use transactions") + ("durable", optValue(durable), "messages should be durable") + ("batches", optValue(batches, "N"), "how many batches to run") + ("delay", optValue(delay, "SECONDS"), "Causes a delay between each batch") + ("size", optValue(size, "BYTES"), "size of the published messages") + ("status-queue", optValue(statusqueue, "QUEUE-NAME"), "Message queue to read status messages from"); + } +}; + +Publisher::Publisher(const AsyncSession& _session, const string& _controlTopic, bool tx, bool d) : + session(_session), mgr(session), controlTopic(_controlTopic), transactional(tx), durable(d) +{ + mgr.subscribe(queue, "response"); +} + +int64_t Publisher::publish(int msgs, int listeners, int size){ + Message msg(generateData(size), controlTopic); + if (durable) { + msg.getDeliveryProperties().setDeliveryMode(framing::PERSISTENT); + } + AbsTime start = now(); + + for(int i = 0; i < msgs; i++){ + session.messageTransfer(arg::content=msg, arg::destination="amq.topic", arg::acceptMode=1); + } + //send report request + Message reportRequest("", controlTopic); + reportRequest.getHeaders().setString("TYPE", "REPORT_REQUEST"); + session.messageTransfer(arg::content=reportRequest, arg::destination="amq.topic", arg::acceptMode=1); + if(transactional){ + sync(session).txCommit(); + } + //wait for a response from each listener (TODO, could log these) + for (int i = 0; i < listeners; i++) { + Message report = queue.pop(); + } + + if(transactional){ + sync(session).txCommit(); + } + + AbsTime finish = now(); + return Duration(start, finish); +} + +string Publisher::generateData(int size){ + string data; + for(int i = 0; i < size; i++){ + data += ('A' + (i / 26)); + } + return data; +} + +void Publisher::terminate(){ + //send termination request + Message terminationRequest("", controlTopic); + terminationRequest.getHeaders().setString("TYPE", "TERMINATION_REQUEST"); + session.messageTransfer(arg::content=terminationRequest, arg::destination="amq.topic", arg::acceptMode=1); + if(transactional){ + session.txCommit(); + } +} + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char** argv) { + try{ + Args args; + args.parse(argc, argv); + if(args.help) + cout << args << endl; + else { + Connection connection; + args.open(connection); + AsyncSession session = connection.newSession(); + + // If status-queue is defined, wait for all expected listeners to join in before we start + if( args.statusqueue.length() > 0 ) { + cout << "Waiting for " << args.subscribers << " listeners..." << endl; + SubscriptionManager statusSubs(session); + LocalQueue statusQ; + statusSubs.subscribe(statusQ, args.statusqueue); + for (int i = 0; i < args.subscribers; i++) { + Message m = statusQ.get(); + if( m.getData().find("topic_listener: ", 0) == 0 ) { + cout << "Listener " << (i+1) << " of " << args.subscribers + << " is ready (pid " << m.getData().substr(16, m.getData().length() - 16) + << ")" << endl; + } else { + throw Exception(QPID_MSG("Unexpected message received on status queue: " << m.getData())); + } + } + } + + if (args.transactional) { + session.txSelect(); + } + session.queueDeclare(arg::queue="response"); + session.exchangeBind(arg::exchange="amq.direct", arg::queue="response", arg::bindingKey="response"); + + Publisher publisher(session, "topic_control", args.transactional, args.durable); + + int batchSize(args.batches); + int64_t max(0); + int64_t min(0); + int64_t sum(0); + for(int i = 0; i < batchSize; i++){ + if(i > 0 && args.delay) qpid::sys::sleep(args.delay); + int64_t msecs = + publisher.publish(args.messages, + args.subscribers, + args.size) / TIME_MSEC; + if(!max || msecs > max) max = msecs; + if(!min || msecs < min) min = msecs; + sum += msecs; + cout << "Completed " << (i+1) << " of " << batchSize + << " in " << msecs << "ms" << endl; + } + publisher.terminate(); + int64_t avg = sum / batchSize; + if(batchSize > 1){ + cout << batchSize << " batches completed. avg=" << avg << + ", max=" << max << ", min=" << min << endl; + } + session.close(); + connection.close(); + } + return 0; + }catch(exception& error) { + cout << error.what() << endl; + } + return 1; +} diff --git a/qpid/cpp/src/tests/qpid-txtest.cpp b/qpid/cpp/src/tests/qpid-txtest.cpp new file mode 100644 index 0000000000..d0ba2f1245 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-txtest.cpp @@ -0,0 +1,340 @@ +/* + * + * 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 <algorithm> +#include <iomanip> +#include <iostream> +#include <memory> +#include <sstream> +#include <vector> + +#include "TestOptions.h" +#include "qpid/client/Connection.h" +#include "qpid/client/Message.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/framing/Array.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/Uuid.h" +#include "qpid/sys/Thread.h" + +using namespace qpid; +using namespace qpid::client; +using namespace qpid::sys; +using std::string; + +namespace qpid { +namespace tests { + +typedef std::vector<std::string> StringSet; + +struct Args : public qpid::TestOptions { + bool init, transfer, check;//actions + uint size; + bool durable; + uint queues; + string base; + uint msgsPerTx; + uint txCount; + uint totalMsgCount; + bool dtx; + bool quiet; + + Args() : init(true), transfer(true), check(true), + size(256), durable(true), queues(2), + base("tx-test"), msgsPerTx(1), txCount(1), totalMsgCount(10), + dtx(false), quiet(false) + { + addOptions() + + ("init", optValue(init, "yes|no"), "Declare queues and populate one with the initial set of messages.") + ("transfer", optValue(transfer, "yes|no"), "'Move' messages from one queue to another using transactions to ensure no message loss.") + ("check", optValue(check, "yes|no"), "Check that the initial messages are all still available.") + ("size", optValue(size, "N"), "message size") + ("durable", optValue(durable, "yes|no"), "use durable messages") + ("queues", optValue(queues, "N"), "number of queues") + ("queue-base-name", optValue(base, "<name>"), "base name for queues") + ("messages-per-tx", optValue(msgsPerTx, "N"), "number of messages transferred per transaction") + ("tx-count", optValue(txCount, "N"), "number of transactions per 'agent'") + ("total-messages", optValue(totalMsgCount, "N"), "total number of messages in 'circulation'") + ("dtx", optValue(dtx, "yes|no"), "use distributed transactions") + ("quiet", optValue(quiet), "reduce output from test"); + } +}; + +const std::string chars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); + +std::string generateData(uint size) +{ + if (size < chars.length()) { + return chars.substr(0, size); + } + std::string data; + for (uint i = 0; i < (size / chars.length()); i++) { + data += chars; + } + data += chars.substr(0, size % chars.length()); + return data; +} + +void generateSet(const std::string& base, uint count, StringSet& collection) +{ + for (uint i = 0; i < count; i++) { + std::ostringstream out; + out << base << "-" << (i+1); + collection.push_back(out.str()); + } +} + +Args opts; + +struct Client +{ + Connection connection; + AsyncSession session; + + Client() + { + opts.open(connection); + session = connection.newSession(); + } + + ~Client() + { + try{ + session.close(); + connection.close(); + } catch(const std::exception& e) { + std::cout << e.what() << std::endl; + } + } +}; + +struct Transfer : public Client, public Runnable +{ + std::string src; + std::string dest; + Thread thread; + framing::Xid xid; + + Transfer(const std::string& to, const std::string& from) : src(to), dest(from), xid(0x4c414e47, "", from) {} + + void run() + { + try { + + if (opts.dtx) session.dtxSelect(); + else session.txSelect(); + SubscriptionManager subs(session); + + LocalQueue lq; + SubscriptionSettings settings(FlowControl::messageWindow(opts.msgsPerTx)); + settings.autoAck = 0; // Disabled + Subscription sub = subs.subscribe(lq, src, settings); + + for (uint t = 0; t < opts.txCount; t++) { + Message in; + Message out("", dest); + if (opts.dtx) { + setNewXid(xid); + session.dtxStart(arg::xid=xid); + } + for (uint m = 0; m < opts.msgsPerTx; m++) { + in = lq.pop(); + std::string& data = in.getData(); + if (data.size() != opts.size) { + std::ostringstream oss; + oss << "Message size incorrect: size=" << in.getData().size() << "; expected " << opts.size; + throw std::runtime_error(oss.str()); + } + out.setData(data); + out.getMessageProperties().setCorrelationId(in.getMessageProperties().getCorrelationId()); + out.getDeliveryProperties().setDeliveryMode(in.getDeliveryProperties().getDeliveryMode()); + session.messageTransfer(arg::content=out, arg::acceptMode=1); + } + sub.accept(sub.getUnaccepted()); + if (opts.dtx) { + session.dtxEnd(arg::xid=xid); + session.dtxPrepare(arg::xid=xid); + session.dtxCommit(arg::xid=xid); + } else { + session.txCommit(); + } + } + } catch(const std::exception& e) { + std::cout << "Transfer interrupted: " << e.what() << std::endl; + } + } + + void setNewXid(framing::Xid& xid) { + framing::Uuid uuid(true); + xid.setGlobalId(uuid.str()); + } +}; + +struct Controller : public Client +{ + StringSet ids; + StringSet queues; + + Controller() + { + generateSet(opts.base, opts.queues, queues); + generateSet("msg", opts.totalMsgCount, ids); + } + + void init() + { + //declare queues + for (StringSet::iterator i = queues.begin(); i != queues.end(); i++) { + session.queueDeclare(arg::queue=*i, arg::durable=opts.durable); + session.sync(); + } + + Message msg(generateData(opts.size), *queues.begin()); + if (opts.durable) { + msg.getDeliveryProperties().setDeliveryMode(framing::PERSISTENT); + } + + //publish messages + for (StringSet::iterator i = ids.begin(); i != ids.end(); i++) { + msg.getMessageProperties().setCorrelationId(*i); + session.messageTransfer(arg::content=msg, arg::acceptMode=1); + } + } + + void transfer() + { + boost::ptr_vector<Transfer> agents(opts.queues); + //launch transfer agents + for (StringSet::iterator i = queues.begin(); i != queues.end(); i++) { + StringSet::iterator next = i + 1; + if (next == queues.end()) next = queues.begin(); + + if (!opts.quiet) std::cout << "Transfering from " << *i << " to " << *next << std::endl; + agents.push_back(new Transfer(*i, *next)); + agents.back().thread = Thread(agents.back()); + } + + for (boost::ptr_vector<Transfer>::iterator i = agents.begin(); i != agents.end(); i++) { + i->thread.join(); + } + } + + int check() + { + SubscriptionManager subs(session); + + // Recover DTX transactions (if any) + if (opts.dtx) { + std::vector<std::string> inDoubtXids; + framing::DtxRecoverResult dtxRes = session.dtxRecover().get(); + const framing::Array& xidArr = dtxRes.getInDoubt(); + xidArr.collect(inDoubtXids); + + if (inDoubtXids.size()) { + if (!opts.quiet) std::cout << "Recovering DTX in-doubt transaction(s):" << std::endl; + framing::StructHelper decoder; + framing::Xid xid; + // abort even, commit odd transactions + for (unsigned i = 0; i < inDoubtXids.size(); i++) { + decoder.decode(xid, inDoubtXids[i]); + if (!opts.quiet) std::cout << (i%2 ? " * aborting " : " * committing "); + xid.print(std::cout); + std::cout << std::endl; + if (i%2) { + session.dtxRollback(arg::xid=xid); + } else { + session.dtxCommit(arg::xid=xid); + } + } + } + } + + StringSet drained; + //drain each queue and verify the correct set of messages are available + for (StringSet::iterator i = queues.begin(); i != queues.end(); i++) { + //subscribe, allocate credit and flushn + LocalQueue lq; + SubscriptionSettings settings(FlowControl::unlimited(), ACCEPT_MODE_NONE); + subs.subscribe(lq, *i, settings); + session.messageFlush(arg::destination=*i); + session.sync(); + + uint count(0); + while (!lq.empty()) { + Message m = lq.pop(); + //add correlation ids of received messages to drained + drained.push_back(m.getMessageProperties().getCorrelationId()); + ++count; + } + if (!opts.quiet) std::cout << "Drained " << count << " messages from " << *i << std::endl; + } + + sort(ids.begin(), ids.end()); + sort(drained.begin(), drained.end()); + + //check that drained == ids + StringSet missing; + set_difference(ids.begin(), ids.end(), drained.begin(), drained.end(), back_inserter(missing)); + + StringSet extra; + set_difference(drained.begin(), drained.end(), ids.begin(), ids.end(), back_inserter(extra)); + + if (missing.empty() && extra.empty()) { + std::cout << "All expected messages were retrieved." << std::endl; + return 0; + } else { + if (!missing.empty()) { + std::cout << "The following ids were missing:" << std::endl; + for (StringSet::iterator i = missing.begin(); i != missing.end(); i++) { + std::cout << " '" << *i << "'" << std::endl; + } + } + if (!extra.empty()) { + std::cout << "The following extra ids were encountered:" << std::endl; + for (StringSet::iterator i = extra.begin(); i != extra.end(); i++) { + std::cout << " '" << *i << "'" << std::endl; + } + } + return 1; + } + } +}; + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char** argv) +{ + try { + opts.parse(argc, argv); + Controller controller; + if (opts.init) controller.init(); + if (opts.transfer) controller.transfer(); + if (opts.check) return controller.check(); + return 0; + } catch(const std::exception& e) { + std::cout << e.what() << std::endl; + } + return 2; +} diff --git a/qpid/cpp/src/tests/queue_flow_limit_tests.py b/qpid/cpp/src/tests/queue_flow_limit_tests.py new file mode 100644 index 0000000000..dec7cfb3af --- /dev/null +++ b/qpid/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/qpid/cpp/src/tests/quick_perftest b/qpid/cpp/src/tests/quick_perftest new file mode 100755 index 0000000000..362f9ee96a --- /dev/null +++ b/qpid/cpp/src/tests/quick_perftest @@ -0,0 +1,22 @@ +#!/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. +# + +exec `dirname $0`/run_test ./qpid-perftest --summary --count 100 diff --git a/qpid/cpp/src/tests/quick_topictest b/qpid/cpp/src/tests/quick_topictest new file mode 100755 index 0000000000..0a6b29b33f --- /dev/null +++ b/qpid/cpp/src/tests/quick_topictest @@ -0,0 +1,30 @@ +#!/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. +# + + +# Quick and quiet topic test for make check. +test -z "$srcdir" && srcdir=`dirname $0` +$srcdir/topictest -s2 -m2 -b1 > topictest.log 2>&1 || { + echo $0 FAILED: + cat topictest.log + exit 1 +} +rm topictest.log diff --git a/qpid/cpp/src/tests/quick_topictest.ps1 b/qpid/cpp/src/tests/quick_topictest.ps1 new file mode 100644 index 0000000000..8f5b2caff7 --- /dev/null +++ b/qpid/cpp/src/tests/quick_topictest.ps1 @@ -0,0 +1,30 @@ +# +# 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. +# + +# Quick and quiet topic test for make check. +[string]$me = $myInvocation.InvocationName +$srcdir = Split-Path $me +Invoke-Expression "$srcdir\topictest.ps1 -subscribers 2 -messages 2 -batches 1" > topictest.log 2>&1 +if (!$?) { + "$me FAILED:" + cat topictest.log + exit 1 +} +Remove-Item topictest.log +exit 0 diff --git a/qpid/cpp/src/tests/quick_txtest b/qpid/cpp/src/tests/quick_txtest new file mode 100755 index 0000000000..c872fcec12 --- /dev/null +++ b/qpid/cpp/src/tests/quick_txtest @@ -0,0 +1,22 @@ +#!/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. +# + +exec `dirname $0`/run_test ./qpid-txtest --queues 4 --tx-count 10 --quiet diff --git a/qpid/cpp/src/tests/receiver.cpp b/qpid/cpp/src/tests/receiver.cpp new file mode 100644 index 0000000000..f1b462d6e4 --- /dev/null +++ b/qpid/cpp/src/tests/receiver.cpp @@ -0,0 +1,140 @@ +/* + * + * 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/FailoverManager.h> +#include <qpid/client/Session.h> +#include <qpid/client/Message.h> +#include <qpid/client/SubscriptionManager.h> +#include <qpid/client/SubscriptionSettings.h> +#include "TestOptions.h" + +#include <iostream> +#include <fstream> + + +using namespace qpid; +using namespace qpid::client; +using namespace qpid::framing; + +using namespace std; + +namespace qpid { +namespace tests { + +struct Args : public qpid::TestOptions +{ + string queue; + uint messages; + bool ignoreDuplicates; + uint creditWindow; + uint ackFrequency; + bool browse; + + Args() : queue("test-queue"), messages(0), ignoreDuplicates(false), creditWindow(0), ackFrequency(1), browse(false) + { + addOptions() + ("queue", qpid::optValue(queue, "QUEUE NAME"), "Queue from which to request messages") + ("messages", 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)") + ("credit-window", qpid::optValue(creditWindow, "N"), "Credit window (0 implies infinite window)") + ("ack-frequency", qpid::optValue(ackFrequency, "N"), "Ack frequency (0 implies none of the messages will get accepted)") + ("browse", qpid::optValue(browse), "Browse rather than consuming"); + } +}; + +const string EOS("eos"); +const string SN("sn"); + +class Receiver : public MessageListener, public FailoverManager::Command +{ + public: + Receiver(const string& queue, uint messages, bool ignoreDuplicates, uint creditWindow, uint ackFrequency, bool browse); + void received(Message& message); + void execute(AsyncSession& session, bool isRetry); + private: + const string queue; + const uint count; + const bool skipDups; + SubscriptionSettings settings; + Subscription subscription; + uint processed; + uint lastSn; + + bool isDuplicate(Message& message); +}; + +Receiver::Receiver(const string& q, uint messages, bool ignoreDuplicates, uint creditWindow, uint ackFrequency, bool browse) : + queue(q), count(messages), skipDups(ignoreDuplicates), processed(0), lastSn(0) +{ + if (browse) settings.acquireMode = ACQUIRE_MODE_NOT_ACQUIRED; + if (creditWindow) settings.flowControl = FlowControl::messageWindow(creditWindow); + settings.autoAck = ackFrequency; +} + +void Receiver::received(Message& message) +{ + if (!(skipDups && isDuplicate(message))) { + bool eos = message.getData() == EOS; + if (!eos) std::cout << message.getData() << std::endl; + if (eos || ++processed == count) subscription.cancel(); + } +} + +bool Receiver::isDuplicate(Message& message) +{ + uint sn = message.getHeaders().getAsInt(SN); + if (lastSn < sn) { + lastSn = sn; + return false; + } else { + return true; + } +} + +void Receiver::execute(AsyncSession& session, bool /*isRetry*/) +{ + SubscriptionManager subs(session); + subscription = subs.subscribe(*this, queue, settings); + subs.run(); + if (settings.autoAck) { + subscription.accept(subscription.getUnaccepted()); + } +} + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char ** argv) +{ + Args opts; + try { + opts.parse(argc, argv); + FailoverManager connection(opts.con); + Receiver receiver(opts.queue, opts.messages, opts.ignoreDuplicates, opts.creditWindow, opts.ackFrequency, opts.browse); + connection.execute(receiver); + connection.close(); + return 0; + } catch(const std::exception& error) { + std::cerr << "Failure: " << error.what() << std::endl; + } + return 1; +} diff --git a/qpid/cpp/src/tests/reliable_replication_test b/qpid/cpp/src/tests/reliable_replication_test new file mode 100755 index 0000000000..f57d11a263 --- /dev/null +++ b/qpid/cpp/src/tests/reliable_replication_test @@ -0,0 +1,93 @@ +#!/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. +# + +# Test reliability of the replication feature in the face of link +# failures: + +source ./test_env.sh + +trap stop_brokers EXIT + +stop_brokers() { + if [[ $BROKER_A ]] ; then + $QPIDD_EXEC --no-module-dir -q --port $BROKER_A + unset BROKER_A + fi + if [[ $BROKER_B ]] ; then + $QPIDD_EXEC --no-module-dir -q --port $BROKER_B + unset BROKER_B + fi +} + +setup() { + rm -f replication-source.log replication-dest.log + $QPIDD_EXEC --daemon --port 0 --no-data-dir --no-module-dir --auth no --load-module $REPLICATING_LISTENER_LIB --replication-queue replication --create-replication-queue true --log-enable trace+ --log-to-file replication-source.log --log-to-stderr 0 > qpidd-repl.port + BROKER_A=`cat qpidd-repl.port` + + $QPIDD_EXEC --daemon --port 0 --no-data-dir --no-module-dir --auth no --load-module $REPLICATION_EXCHANGE_LIB --log-enable info+ --log-to-file replication-dest.log --log-to-stderr 0 > qpidd-repl.port + BROKER_B=`cat qpidd-repl.port` + + echo "Testing replication from port $BROKER_A to port $BROKER_B" + + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_B" add exchange replication replication + $PYTHON_COMMANDS/qpid-route --ack 500 queue add "localhost:$BROKER_B" "localhost:$BROKER_A" replication replication + + #create test queue (only replicate enqueues for this test): + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_A" add queue queue-a --generate-queue-events 1 + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_B" add queue queue-a +} + +send() { + ./sender --port $BROKER_A --routing-key queue-a --send-eos 1 < replicated.expected +} + +receive() { + rm -f replicated.actual + ./receiver --port $BROKER_B --queue queue-a > replicated.actual +} + +bounce_link() { + echo "Destroying link..." + $PYTHON_COMMANDS/qpid-route link del "localhost:$BROKER_B" "localhost:$BROKER_A" + echo "Link destroyed; recreating route..." + sleep 2 + $PYTHON_COMMANDS/qpid-route --ack 500 queue add "localhost:$BROKER_B" "localhost:$BROKER_A" replication replication + echo "Route re-established" +} + +if test -d ${PYTHON_DIR} && test -e $REPLICATING_LISTENER_LIB && test -e $REPLICATION_EXCHANGE_LIB ; then + setup + for i in `seq 1 100000`; do echo Message $i; done > replicated.expected + send & + receive & + for i in `seq 1 5`; do sleep 10; bounce_link; done; + wait + #check that received list is identical to sent list + diff replicated.actual replicated.expected || FAIL=1 + if [[ $FAIL ]]; then + echo reliable replication test failed: expectations not met! + exit 1 + else + echo replication reliable in the face of link failures + rm -f replication.actual replication.expected replication-source.log replication-dest.log qpidd-repl.port + fi +fi + diff --git a/qpid/cpp/src/tests/replaying_sender.cpp b/qpid/cpp/src/tests/replaying_sender.cpp new file mode 100644 index 0000000000..a5549bfdf2 --- /dev/null +++ b/qpid/cpp/src/tests/replaying_sender.cpp @@ -0,0 +1,165 @@ +/* + * + * 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/FailoverManager.h> +#include <qpid/client/Session.h> +#include <qpid/client/AsyncSession.h> +#include <qpid/client/Message.h> +#include <qpid/client/MessageReplayTracker.h> +#include <qpid/Exception.h> + +#include <iostream> +#include <sstream> + +using namespace qpid; +using namespace qpid::client; +using namespace qpid::framing; + +using namespace std; + +namespace qpid { +namespace tests { + +class Sender : public FailoverManager::Command +{ + public: + Sender(const std::string& queue, uint count, uint reportFreq); + void execute(AsyncSession& session, bool isRetry); + uint getSent(); + + void setVerbosity ( int v ) { verbosity = v; } + void setPersistence ( int p ) { persistence = p; } + + private: + MessageReplayTracker sender; + const uint count; + uint sent; + const uint reportFrequency; + Message message; + int verbosity; + int persistence; + string queueName; +}; + +Sender::Sender(const std::string& queue, uint count_, uint reportFreq ) + : sender(10), + count(count_), + sent(0), + reportFrequency(reportFreq), + verbosity(0), + persistence(0), + queueName ( queue ) +{ + message.getDeliveryProperties().setRoutingKey(queueName.c_str()); +} + +const string SN("sn"); + +void Sender::execute(AsyncSession& session, bool isRetry) +{ + if (verbosity > 0) + std::cout << "replaying_sender " << (isRetry ? "first " : "re-") << "connect." << endl; + if (isRetry) sender.replay(session); + else sender.init(session); + while (sent < count) { + stringstream message_data; + message_data << ++sent; + message.setData(message_data.str()); + message.getHeaders().setInt(SN, sent); + if ( persistence ) + message.getDeliveryProperties().setDeliveryMode(PERSISTENT); + + sender.send(message); + if (count > reportFrequency && !(sent % reportFrequency)) { + if ( verbosity > 0 ) + std::cout << "Sender sent " + << sent + << " of " + << count + << " on queue " + << queueName + << std::endl; + } + } + message.setData("That's all, folks!"); + sender.send(message); + + if ( verbosity > 0 ) + std::cout << "SENDER COMPLETED\n"; +} + +uint Sender::getSent() +{ + return sent; +} + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char ** argv) +{ + ConnectionSettings settings; + + if ( argc != 8 ) + { + std::cerr << "Usage: replaying_sender host port n_messages report_frequency verbosity persistence queue_name\n"; + return 1; + } + + settings.host = argv[1]; + settings.port = atoi(argv[2]); + int n_messages = atoi(argv[3]); + int reportFrequency = atoi(argv[4]); + int verbosity = atoi(argv[5]); + int persistence = atoi(argv[6]); + char * queue_name = argv[7]; + + FailoverManager connection(settings); + Sender sender(queue_name, n_messages, reportFrequency ); + sender.setVerbosity ( verbosity ); + sender.setPersistence ( persistence ); + try { + connection.execute ( sender ); + if ( verbosity > 0 ) + { + std::cout << "Sender finished. Sent " + << sender.getSent() + << " messages on queue " + << queue_name + << endl; + } + connection.close(); + return 0; + } + catch(const std::exception& error) + { + cerr << "Sender (host: " + << settings.host + << " port: " + << settings.port + << " ) " + << " Failed: " + << error.what() + << std::endl; + } + return 1; +} diff --git a/qpid/cpp/src/tests/replication_test b/qpid/cpp/src/tests/replication_test new file mode 100755 index 0000000000..8c37568875 --- /dev/null +++ b/qpid/cpp/src/tests/replication_test @@ -0,0 +1,182 @@ +#!/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 test of the replication feature + +source ./test_env.sh + +trap stop_brokers INT TERM QUIT + +stop_brokers() { + if [ x$BROKER_A != x ]; then + $QPIDD_EXEC --no-module-dir -q --port $BROKER_A + unset BROKER_A + fi + if [ x$BROKER_B != x ]; then + $QPIDD_EXEC --no-module-dir -q --port $BROKER_B + unset BROKER_B + fi +} + +if test -d ${PYTHON_DIR} && test -f "$REPLICATING_LISTENER_LIB" && test -f "$REPLICATION_EXCHANGE_LIB"; then + rm -f queue-*.repl replication-*.log #cleanup from any earlier runs + + $QPIDD_EXEC --daemon --port 0 --no-data-dir --no-module-dir --auth no --load-module $REPLICATING_LISTENER_LIB --replication-queue replication --create-replication-queue true --log-enable info+ --log-to-file replication-source.log --log-to-stderr 0 > qpidd.port + BROKER_A=`cat qpidd.port` + + $QPIDD_EXEC --daemon --port 0 --no-data-dir --no-module-dir --auth no --load-module $REPLICATION_EXCHANGE_LIB --log-enable info+ --log-to-file replication-dest.log --log-to-stderr 0 > qpidd.port + BROKER_B=`cat qpidd.port` + echo "Running replication test between localhost:$BROKER_A and localhost:$BROKER_B" + + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_B" add exchange replication replication + $PYTHON_COMMANDS/qpid-route --ack 5 queue add "localhost:$BROKER_B" "localhost:$BROKER_A" replication replication + + #create test queues + + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_A" add queue queue-a --generate-queue-events 2 + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_A" add queue queue-b --generate-queue-events 2 + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_A" add queue queue-c --generate-queue-events 1 + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_A" add queue queue-d --generate-queue-events 2 + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_A" add queue queue-e --generate-queue-events 1 + + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_B" add queue queue-a + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_B" add queue queue-b + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_B" add queue queue-c + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_B" add queue queue-e + #queue-d deliberately not declared on DR; this error case should be handled + + #publish and consume from test queues on broker A: + i=1 + while [ $i -le 10 ]; do + echo Message $i for A >> queue-a-input.repl + i=`expr $i + 1` + done + i=1 + while [ $i -le 20 ]; do + echo Message $i for B >> queue-b-input.repl + i=`expr $i + 1` + done + i=1 + while [ $i -le 15 ]; do + echo Message $i for C >> queue-c-input.repl + i=`expr $i + 1` + done + + ./sender --port $BROKER_A --routing-key queue-a --send-eos 1 < queue-a-input.repl + ./sender --port $BROKER_A --routing-key queue-b --send-eos 1 < queue-b-input.repl + ./sender --port $BROKER_A --routing-key queue-c --send-eos 1 < queue-c-input.repl + echo dummy | ./sender --port $BROKER_A --routing-key queue-d --send-eos 1 + + ./receiver --port $BROKER_A --queue queue-a --messages 5 > /dev/null + ./receiver --port $BROKER_A --queue queue-b --messages 10 > /dev/null + ./receiver --port $BROKER_A --queue queue-c --messages 10 > /dev/null + ./receiver --port $BROKER_A --queue queue-d > /dev/null + + + # What we are doing is putting a message on the end of repliaction queue & waiting for it on remote side + # making sure all the messages have been flushed from the replication queue. + echo dummy | ./sender --port $BROKER_A --routing-key queue-e --send-eos 1 + ./receiver --port $BROKER_B --queue queue-e --messages 1 > /dev/null + + #shutdown broker A then check that broker Bs versions of the queues are as expected + $QPIDD_EXEC --no-module-dir -q --port $BROKER_A + unset BROKER_A + + #validate replicated queues: + ./receiver --port $BROKER_B --queue queue-a > queue-a-backup.repl + ./receiver --port $BROKER_B --queue queue-b > queue-b-backup.repl + ./receiver --port $BROKER_B --queue queue-c > queue-c-backup.repl + + + tail -5 queue-a-input.repl > queue-a-expected.repl + tail -10 queue-b-input.repl > queue-b-expected.repl + diff queue-a-backup.repl queue-a-expected.repl || FAIL=1 + diff queue-b-backup.repl queue-b-expected.repl || FAIL=1 + diff queue-c-backup.repl queue-c-input.repl || FAIL=1 + + grep 'queue-d does not exist' replication-dest.log > /dev/null || echo "WARNING: Expected error to be logged!" + + stop_brokers + + # now check offsets working (enqueue based on position being set, not queue abs position) + + $QPIDD_EXEC --daemon --port 0 --no-data-dir --no-module-dir --auth no --load-module $REPLICATING_LISTENER_LIB --replication-queue replication --create-replication-queue true --log-enable info+ --log-to-file replication-source.log --log-to-stderr 0 > qpidd.port + BROKER_A=`cat qpidd.port` + + $QPIDD_EXEC --daemon --port 0 --no-data-dir --no-module-dir --auth no --load-module $REPLICATION_EXCHANGE_LIB --log-enable info+ --log-to-file replication-dest.log --log-to-stderr 0 > qpidd.port + BROKER_B=`cat qpidd.port` + + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_B" add exchange replication replication + $PYTHON_COMMANDS/qpid-route --ack 5 queue add "localhost:$BROKER_B" "localhost:$BROKER_A" replication replication + + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_A" add queue queue-e --generate-queue-events 2 + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_B" add queue queue-e + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_A" add queue queue-d --generate-queue-events 1 + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_B" add queue queue-d + + i=1 + while [ $i -le 10 ]; do + echo Message $i for A >> queue-e-input.repl + i=`expr $i + 1` + done + + ./sender --port $BROKER_A --routing-key queue-e --send-eos 1 < queue-e-input.repl + ./receiver --port $BROKER_A --queue queue-e --messages 10 > /dev/null + + # What we are doing is putting a message on the end of repliaction queue & waiting for it on remote side + # making sure all the messages have been flushed from the replication queue. + echo dummy | ./sender --port $BROKER_A --routing-key queue-d --send-eos 1 + ./receiver --port $BROKER_B --queue queue-d --messages 1 > /dev/null + + # now check offsets working + $QPIDD_EXEC --no-module-dir -q --port $BROKER_B + unset BROKER_B + $QPIDD_EXEC --daemon --port 0 --no-data-dir --no-module-dir --auth no --load-module $REPLICATION_EXCHANGE_LIB --log-enable info+ --log-to-file replication-dest.log --log-to-stderr 0 > qpidd.port + BROKER_B=`cat qpidd.port` + + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_B" add queue queue-e + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_B" add exchange replication replication + $PYTHON_COMMANDS/qpid-route --ack 5 queue add "localhost:$BROKER_B" "localhost:$BROKER_A" replication replication + # now send another 15 + i=11 + while [ $i -le 15 ]; do + echo Message $i for A >> queue-e1-input.repl + i=`expr $i + 1` + done + ./sender --port $BROKER_A --routing-key queue-e --send-eos 1 < queue-e1-input.repl + + ./receiver --port $BROKER_B --queue queue-e > queue-e-backup.repl + diff queue-e-backup.repl queue-e1-input.repl || FAIL=1 + + stop_brokers + + if [ x$FAIL != x ]; then + echo replication test failed: expectations not met! + exit 1 + else + echo queue state replicated as expected + rm -f queue-*.repl replication-*.log + fi + +else + echo "Skipping replication test, plugins not built or python utils not located" +fi + diff --git a/qpid/cpp/src/tests/restart_cluster b/qpid/cpp/src/tests/restart_cluster new file mode 100755 index 0000000000..6a6abc8042 --- /dev/null +++ b/qpid/cpp/src/tests/restart_cluster @@ -0,0 +1,38 @@ +#!/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. +# + +# Re-start a cluster on the local host. + +srcdir=`dirname $0` +$srcdir/stop_cluster +exec $srcdir/start_cluster "$@" +#!/bin/sh +# Re-start a cluster on the local host. + +srcdir=`dirname $0` +$srcdir/stop_cluster +exec $srcdir/start_cluster "$@" +#!/bin/sh +# Re-start a cluster on the local host. + +srcdir=`dirname $0` +$srcdir/stop_cluster +exec $srcdir/start_cluster "$@" diff --git a/qpid/cpp/src/tests/resuming_receiver.cpp b/qpid/cpp/src/tests/resuming_receiver.cpp new file mode 100644 index 0000000000..2e22a7c572 --- /dev/null +++ b/qpid/cpp/src/tests/resuming_receiver.cpp @@ -0,0 +1,193 @@ +/* + * + * 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/FailoverManager.h> +#include <qpid/client/Session.h> +#include <qpid/client/Message.h> +#include <qpid/client/SubscriptionManager.h> + +#include <iostream> +#include <fstream> + +using namespace qpid; +using namespace qpid::client; +using namespace qpid::framing; + +using namespace std; + + + +namespace qpid { +namespace tests { + +class Listener : public MessageListener, + public FailoverManager::Command, + public FailoverManager::ReconnectionStrategy +{ + public: + Listener ( int report_frequency = 1000, + int verbosity = 0, + char const * queue_name = "message_queue" ); + void received(Message& message); + void execute(AsyncSession& session, bool isRetry); + void check(); + void editUrlList(vector<Url>& urls); + private: + Subscription subscription; + uint count; + vector<int> received_twice; + uint lastSn; + bool gaps; + uint reportFrequency; + int verbosity; + bool done; + string queueName; +}; + + +Listener::Listener ( int freq, int verbosity, char const * name ) + : count(0), + lastSn(0), + gaps(false), + reportFrequency(freq), + verbosity(verbosity), + done(false), + queueName ( name ) +{} + +const std::string SN("sn"); + +void Listener::received(Message & message) +{ + if (message.getData() == "That's all, folks!") + { + done = true; + if(verbosity > 0 ) + { + cout << "Shutting down listener for " + << message.getDestination() << endl; + + cout << "Listener received " + << count + << " messages (" + << received_twice.size() + << " received_twice)" + << endl; + + } + subscription.cancel(); + if ( verbosity > 0 ) + cout << "LISTENER COMPLETED\n"; + + if ( ! gaps ) { + cout << "no gaps were detected\n"; + cout << received_twice.size() << " messages were received twice.\n"; + } + else { + cout << "gaps detected\n"; + for ( unsigned int i = 0; i < received_twice.size(); ++ i ) + cout << "received_twice " + << received_twice[i] + << endl; + } + } else { + uint sn = message.getHeaders().getAsInt(SN); + if (lastSn < sn) { + if (sn - lastSn > 1) { + cerr << "Error: gap in sequence between " << lastSn << " and " << sn << endl; + gaps = true; + } + lastSn = sn; + ++count; + if ( ! ( count % reportFrequency ) ) { + if ( verbosity > 0 ) + cout << "Listener has received " + << count + << " messages on queue " + << queueName + << endl; + } + } else { + received_twice.push_back ( sn ); + } + } +} + +void Listener::check() +{ + if (gaps) throw Exception("Detected gaps in sequence; messages appear to have been lost."); +} + +void Listener::execute(AsyncSession& session, bool isRetry) { + if (verbosity > 0) + cout << "resuming_receiver " << (isRetry ? "first " : "re-") << "connect." << endl; + if (!done) { + SubscriptionManager subs(session); + subscription = subs.subscribe(*this, queueName); + subs.run(); + } +} + +void Listener::editUrlList(vector<Url>& urls) +{ + /** + * A more realistic algorithm would be to search through the list + * for prefered hosts and ensure they come first in the list. + */ + if (urls.size() > 1) rotate(urls.begin(), urls.begin() + 1, urls.end()); +} + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char ** argv) +{ + ConnectionSettings settings; + + if ( argc != 6 ) + { + cerr << "Usage: resuming_receiver host port report_frequency verbosity queue_name\n"; + return 1; + } + + settings.host = argv[1]; + settings.port = atoi(argv[2]); + int reportFrequency = atoi(argv[3]); + int verbosity = atoi(argv[4]); + char * queue_name = argv[5]; + + Listener listener ( reportFrequency, verbosity, queue_name ); + FailoverManager connection(settings, &listener); + + try { + connection.execute(listener); + connection.close(); + listener.check(); + return 0; + } catch(const exception& error) { + cerr << "Receiver failed: " << error.what() << endl; + } + return 1; +} + + + diff --git a/qpid/cpp/src/tests/ring_queue_test b/qpid/cpp/src/tests/ring_queue_test new file mode 100755 index 0000000000..553746eb49 --- /dev/null +++ b/qpid/cpp/src/tests/ring_queue_test @@ -0,0 +1,174 @@ +#!/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. +# + +# Test script for validating the behaviour of a ring queue + +QUEUE_NAME=ring-queue +LIMIT=100 +DURABLE=0 +MESSAGES=10000 +SENDERS=1 +RECEIVERS=1 +CONCURRENT=0 +BROKER_URL="-a ${QPID_BROKER:-localhost}:${QPID_PORT:-5672}" + +setup() { + if [[ $DURABLE -gt 0 ]]; then + EXTRA_ARGS=" --durable" + fi + qpid-config $BROKER_URL add queue $QUEUE_NAME --max-queue-count $LIMIT --limit-policy ring $EXTRA_ARGS +} + +send() { + datagen --count $MESSAGES | tee sender_${QUEUE_NAME}_${1} | sender --durable $DURABLE --routing-key $QUEUE_NAME +} + +receive() { + #TODO: allow a variety of receiver options to be passed in (ack-frequency, credit-window etc) + receiver --queue $QUEUE_NAME > receiver_${QUEUE_NAME}_${1} +} + +cleanup() { + rm -f sender_${QUEUE_NAME}_* receiver_${QUEUE_NAME}_* + qpid-config $BROKER_URL del queue $QUEUE_NAME --force +} + +log() { + echo $1 +} + +fail() { + echo $1 + FAILED=1 +} + +validate() { + if [[ $RECEIVERS -eq 0 ]]; then + #queue should have $LIMIT messages on it, but need to send an eos also + sender --routing-key $QUEUE_NAME --send-eos 1 < /dev/null + received=$(receiver --queue $QUEUE_NAME --browse | wc -l) + if [[ received -eq $(( $LIMIT - 1)) ]]; then + log "queue contains $LIMIT messages as expected" + else + fail "queue does not contain the expected $LIMIT messages (received $received)" + fi + elif [[ $CONCURRENT -eq 0 ]]; then + #sum of length of all output files should be equal to $LIMIT - $RECEIVERS (1 eos message each) + received=$(cat receiver_${QUEUE_NAME}_* | wc -l) + expected=$(( $LIMIT - $RECEIVERS )) + if [[ $received -eq $expected ]]; then + log "received $LIMIT messages as expected" + else + fail "received $received messages, expected $expected" + fi + #if there were only a single sender and receiver (executed serially) we can check the + #actual received contents + if [[ $RECEIVERS -eq 1 ]] && [[ $SENDERS -eq 1 ]]; then + tail -n $(($LIMIT - 1)) sender_${QUEUE_NAME}_1 | diff - receiver_${QUEUE_NAME}_1 || FAILED=1 + if [[ $FAILED -eq 1 ]]; then + fail "did not receive expected messages" + else + log "received messages matched expectations" + fi + fi + else + #multiple receivers, concurrent with senders; ring queue functionality cannot be validated in this case + if [[ $(cat receiver_${QUEUE_NAME}_* | wc -l) -le $(cat sender_${QUEUE_NAME}_* | wc -l) ]]; then + log "sent at least as many messages as were received" + else + #Note: if any receiver was browsing, this would be valid (would need to add 'sort | uniq') + # to pipeline above + fail "received more messages than were sent" + fi + fi + + if [[ $FAILED ]]; then + echo $(basename $0): FAILED + exit 1 + else + cleanup + fi +} + +run_test() { + if [[ $CONCURRENT -eq 0 ]]; then + echo "Starting $SENDERS senders followed by $RECEIVERS receivers " + else + echo "Starting $SENDERS senders concurrently with $RECEIVERS receivers" + fi + for ((i=1; i <= $SENDERS; i++)); do + send $i & + sender_pids[$i]=$! + done + if [[ $CONCURRENT -eq 0 ]] && [[ $RECEIVERS -gt 0 ]]; then + wait + sender --routing-key $QUEUE_NAME --send-eos $RECEIVERS < /dev/null + fi + for ((i=1; i <= $RECEIVERS; i++)); do + receive $i & + done + if [[ $CONCURRENT -gt 0 ]]; then + for ((i=1; i <= $SENDERS; i++)); do + wait ${sender_pids[$i]} + done + sender --routing-key $QUEUE_NAME --send-eos $RECEIVERS < /dev/null + fi + wait +} + +usage() { + cat <<EOF +$(basename $0): Test script for validating the behaviour of a ring queue + +Options: + -q <queue> the name of the queue to use + -s <senders> the number of senders to start + -r <receivers> the number of receivers to start + -l <limit> the limit for the ring queue + -m <messages> the number of messages to send + -c if specified, receivers will run concurrently with senders + -d if specified the queue and messages will be durable +EOF + exit 1 +} + +while getopts "s:r:m:u:dch" opt ; do + case $opt in + q) QUEUE_NAME=$OPTARG ;; + l) LIMIT=$OPTARG ;; + s) SENDERS=$OPTARG ;; + r) RECEIVERS=$OPTARG ;; + m) MESSAGES=$OPTARG ;; + d) DURABLE=1 ;; + c) CONCURRENT=1 ;; + h) usage;; + ?) usage;; + esac +done + +if [[ $SENDERS -gt 0 ]]; then + setup + run_test + validate +else + echo "Nothing can be done if there are no senders" +fi + diff --git a/qpid/cpp/src/tests/rsynchosts b/qpid/cpp/src/tests/rsynchosts new file mode 100755 index 0000000000..56ee57e898 --- /dev/null +++ b/qpid/cpp/src/tests/rsynchosts @@ -0,0 +1,49 @@ +#!/bin/sh +# +# Licensed to the Apache Software Foundation (ASF) under onemake +# 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. +# + +absdir() { echo `cd $1 && pwd`; } + +abspath() { + if test -d "$1"; then absdir "$1"; + else echo $(absdir $(dirname "$1"))/$(basename "$1") + fi +} + +usage() { + echo "Usage: $(basename $0) file [file...] +Synchronize the contents of each file or directory to the same absolute path on +each host in \$HOSTS. +" + exit 1 +} + +test "$*" || usage + +for f in $*; do FILES="$FILES $(abspath $f)" || exit 1; done + +OK_FILE=`mktemp` # Will be deleted if anything goes wrong. +trap "rm -f $OK_FILE" EXIT + +for h in $HOSTS; do + rsync -aRO --delete $FILES $h:/ || { echo "rsync to $h failed"; rm -f $OK_FILE; } & +done +wait +test -f $OK_FILE + diff --git a/qpid/cpp/src/tests/run-unit-tests b/qpid/cpp/src/tests/run-unit-tests new file mode 100755 index 0000000000..862a76c4f5 --- /dev/null +++ b/qpid/cpp/src/tests/run-unit-tests @@ -0,0 +1,48 @@ +#!/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. +# + +# +# Library names (without path or .so) and CppUnit test paths can be +# specified on the command line or in env var UNIT_TESTS. For example: +# +# Selected test classes: +# ./run-unit-tests ValueTest ClientChannelTest +# +# Individual test method +# ./run-unit-tests ValueTest :ValueTest::testStringValueEquals +# +# Build and run selected tests: +# make check TESTS=run-unit-tests UNIT_TESTS=ClientChannelTest +# + +for u in $* $UNIT_TESTS ; do + case $u in + :*) TEST_ARGS="$TEST_ARGS $u" ;; # A test path. + *) TEST_ARGS="$TEST_ARGS .libs/$u.so" ;; # A test library. + esac +done +test -z "$TEST_ARGS" && TEST_ARGS=".libs/*Test.so" + +test -z "$srcdir" && srcdir=. + +# libdlclose_noop prevents unloading symbols needed for valgrind output. +export LD_PRELOAD=.libs/libdlclose_noop.so +source $srcdir/run_test DllPlugInTester -c -b $TEST_ARGS diff --git a/qpid/cpp/src/tests/run_acl_tests b/qpid/cpp/src/tests/run_acl_tests new file mode 100755 index 0000000000..41f41e20e1 --- /dev/null +++ b/qpid/cpp/src/tests/run_acl_tests @@ -0,0 +1,62 @@ +#!/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 acl tests. $srcdir is set by the Makefile. +source ./test_env.sh +DATA_DIR=`pwd`/data_dir + +trap stop_brokers INT TERM QUIT + +start_brokers() { + ../qpidd --daemon --port 0 --no-module-dir --data-dir $DATA_DIR --load-module $ACL_LIB --acl-file policy.acl --auth no > qpidd.port + LOCAL_PORT=`cat qpidd.port` +} + +stop_brokers() { + $QPIDD_EXEC --no-module-dir -q --port $LOCAL_PORT +} + +test_loading_acl_from_absolute_path(){ + POLICY_FILE=$srcdir/policy.acl + rm -f temp.log + PORT=`../qpidd --daemon --port 0 --no-module-dir --no-data-dir --auth no --load-module $ACL_LIB --acl-file $POLICY_FILE -t --log-to-file temp.log 2>/dev/null` + ACL_FILE=`grep "notice Read ACL file" temp.log | sed 's/^.*Read ACL file //'` + $QPIDD_EXEC --no-module-dir -q --port $PORT + if test "$ACL_FILE" != "\"$POLICY_FILE\""; then + echo "unable to load policy file from an absolute path"; + return 1; + fi + rm temp.log +} + +if test -d ${PYTHON_DIR} ; then + rm -rf $DATA_DIR + mkdir -p $DATA_DIR + cp $srcdir/policy.acl $DATA_DIR + start_brokers + echo "Running acl tests using brokers on ports $LOCAL_PORT" + $QPID_PYTHON_TEST -b localhost:$LOCAL_PORT -m acl || EXITCODE=1 + stop_brokers || EXITCODE=1 + test_loading_acl_from_absolute_path || EXITCODE=1 + rm -rf $DATA_DIR + exit $EXITCODE +fi + diff --git a/qpid/cpp/src/tests/run_acl_tests.ps1 b/qpid/cpp/src/tests/run_acl_tests.ps1 new file mode 100644 index 0000000000..a1848779c7 --- /dev/null +++ b/qpid/cpp/src/tests/run_acl_tests.ps1 @@ -0,0 +1,102 @@ +# +# 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 acl tests. + +$srcdir = Split-Path $myInvocation.InvocationName +$PYTHON_DIR = "$srcdir\..\..\..\python" +if (!(Test-Path $PYTHON_DIR -pathType Container)) { + "Skipping acl tests as python libs not found" + exit 1 +} + +$PYTHON_TEST_DIR = "$srcdir\..\..\..\tests\src\py" +$QMF_LIB = "$srcdir\..\..\..\extras\qmf\src\py" +$env:PYTHONPATH="$PYTHON_DIR;$srcdir;$PYTHON_TEST_DIR;$QMF_LIB" +$Global:BROKER_EXE = "" + +Function start_broker($acl_options) +{ + # Test runs from the tests directory but the broker executable is one level + # up, and most likely in a subdirectory from there based on what build type. + # Look around for it before trying to start it. + . $srcdir\find_prog.ps1 ..\qpidd.exe + if (!(Test-Path $prog)) { + "Cannot locate qpidd.exe" + exit 1 + } + $Global:BROKER_EXE = $prog + if (Test-Path qpidd.port) { + Remove-Item qpidd.port + } + $cmdline = "$prog --auth=no --no-module-dir --port=0 --log-to-file qpidd.log $acl_options | foreach { set-content qpidd.port `$_ }" + $cmdblock = $executioncontext.invokecommand.NewScriptBlock($cmdline) + . $srcdir\background.ps1 $cmdblock + # Wait for the broker to start + $wait_time = 0 + while (!(Test-Path qpidd.port) -and ($wait_time -lt 10)) { + Start-Sleep 2 + $wait_time += 2 + } + if (!(Test-Path qpidd.port)) { + "Timeout waiting for broker to start" + exit 1 + } + set-item -path env:BROKER_PORT -value (get-content -path qpidd.port -totalcount 1) +} + +Function stop_broker +{ + "Stopping $Global:BROKER_EXE" + Invoke-Expression "$Global:BROKER_EXE --no-module-dir -q --port $env:BROKER_PORT" | Write-Output + Remove-Item qpidd.port +} + +$DATA_DIR = [IO.Directory]::GetCurrentDirectory() + "\data_dir" +Remove-Item $DATA_DIR -recurse +New-Item $DATA_DIR -type directory +Copy-Item $srcdir\policy.acl $DATA_DIR +start_broker("--data-dir $DATA_DIR --acl-file policy.acl") +"Running acl tests using broker on port $env:BROKER_PORT" +Invoke-Expression "python $PYTHON_DIR/qpid-python-test -m acl -b localhost:$env:BROKER_PORT" | Out-Default +$RETCODE=$LASTEXITCODE +stop_broker + +# Now try reading the acl file from an absolute path. +Remove-Item qpidd.log +$policy_full_path = "$srcdir\policy.acl" +start_broker("--no-data-dir --acl-file $policy_full_path") +#test_loading_acl_from_absolute_path(){ +# POLICY_FILE=$srcdir/policy.acl +# rm -f temp.log +# PORT=`../qpidd --daemon --port 0 --no-module-dir --no-data-dir --auth no --load-module $ACL_LIB --acl-file $POLICY_FILE -t --log-to-file temp.log 2>/dev/null` +# ACL_FILE=`grep "notice Read ACL file" temp.log | sed 's/^.*Read ACL file //'` +# $QPIDD_EXEC --no-module-dir -q --port $PORT +# if test "$ACL_FILE" != "\"$POLICY_FILE\""; then +# echo "unable to load policy file from an absolute path"; +# return 1; +# fi +# rm temp.log +#} +# +# test_loading_acl_from_absolute_path || EXITCODE=1 +# rm -rf $DATA_DIR +# exit $EXITCODE +stop_broker +exit $RETCODE diff --git a/qpid/cpp/src/tests/run_cli_tests b/qpid/cpp/src/tests/run_cli_tests new file mode 100755 index 0000000000..ec5c71b646 --- /dev/null +++ b/qpid/cpp/src/tests/run_cli_tests @@ -0,0 +1,81 @@ +#!/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 cli-utility tests. + +source ./test_env.sh +CLI_DIR=$PYTHON_COMMANDS + +trap stop_brokers INT TERM QUIT + +# helper function to create test.xquery in the current directory, so +# that the python test program can find it. yes, it leaves a turd. +create_test_xquery() { + cat <<EOF > ./test.xquery + let \$w := ./weather + return \$w/station = 'Raleigh-Durham International Airport (KRDU)' + and \$w/temperature_f > 50 + and \$w/temperature_f - \$w/dewpoint > 5 + and \$w/wind_speed_mph > 7 + and \$w/wind_speed_mph < 20 +EOF +} + +start_brokers() { + # if the xml lib is present, use it. if not, disable any tests which + # look like they're xml related. + # if we start supporting xml on windows, it will need something similar + # here + if [ -f ../.libs/xml.so ] ; then + xargs="--load-module ../.libs/xml.so" + if [ ! -f test.xquery ] ; then + create_test_xquery + fi + targs="" + else + echo "Ignoring XML tests" + xargs="" + targs="--ignore=*xml*" + fi + + ../qpidd --daemon --port 0 --no-data-dir --no-module-dir --auth no $xargs > qpidd.port + LOCAL_PORT=`cat qpidd.port` + ../qpidd --daemon --port 0 --no-data-dir --no-module-dir --auth no $xargs > qpidd.port + REMOTE_PORT=`cat qpidd.port` +} + +stop_brokers() { + $QPIDD_EXEC --no-module-dir -q --port $LOCAL_PORT + $QPIDD_EXEC --no-module-dir -q --port $REMOTE_PORT +} + +if test -d ${PYTHON_DIR} ; then + start_brokers + echo "Running CLI tests using brokers on ports $LOCAL_PORT $REMOTE_PORT" + 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 + echo "FAIL CLI tests"; exit 1; + fi +fi + diff --git a/qpid/cpp/src/tests/run_cluster_authentication_soak b/qpid/cpp/src/tests/run_cluster_authentication_soak new file mode 100755 index 0000000000..7bc406c4ca --- /dev/null +++ b/qpid/cpp/src/tests/run_cluster_authentication_soak @@ -0,0 +1,26 @@ +#! /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 +source sasl_test_setup.sh + +with_ais_group ./cluster_authentication_soak 500 + diff --git a/qpid/cpp/src/tests/run_cluster_authentication_test b/qpid/cpp/src/tests/run_cluster_authentication_test new file mode 100755 index 0000000000..647200b869 --- /dev/null +++ b/qpid/cpp/src/tests/run_cluster_authentication_test @@ -0,0 +1,26 @@ +#! /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 +source sasl_test_setup.sh + +with_ais_group ./cluster_authentication_soak + diff --git a/qpid/cpp/src/tests/run_cluster_test b/qpid/cpp/src/tests/run_cluster_test new file mode 100755 index 0000000000..c022eea1fe --- /dev/null +++ b/qpid/cpp/src/tests/run_cluster_test @@ -0,0 +1,26 @@ +#!/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 tests +srcdir=`dirname $0` +. $srcdir/ais_check +with_ais_group $srcdir/run_test ./cluster_test diff --git a/qpid/cpp/src/tests/run_cluster_tests b/qpid/cpp/src/tests/run_cluster_tests new file mode 100755 index 0000000000..e136d3810a --- /dev/null +++ b/qpid/cpp/src/tests/run_cluster_tests @@ -0,0 +1,37 @@ +#!/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 + +test -x $QPID_PYTHON_TEST || { echo Skipping test, $QPID_PYTHON_TEST not found; exit 0; } + +# Delete old cluster test data +OUTDIR=${OUTDIR:-brokertest.tmp} +rm -rf $OUTDIR +mkdir -p $OUTDIR + +# Ignore tests requiring a store by default. +CLUSTER_TESTS_IGNORE=${CLUSTER_TESTS_IGNORE:--i cluster_tests.StoreTests.* -I $srcdir/cluster_tests.fail} +CLUSTER_TESTS=${CLUSTER_TESTS:-$*} + +with_ais_group $QPID_PYTHON_TEST -DOUTDIR=$OUTDIR -m cluster_tests $CLUSTER_TESTS_IGNORE $CLUSTER_TESTS || exit 1 +rm -rf $OUTDIR diff --git a/qpid/cpp/src/tests/run_failover_soak b/qpid/cpp/src/tests/run_failover_soak new file mode 100755 index 0000000000..cce8b07a26 --- /dev/null +++ b/qpid/cpp/src/tests/run_failover_soak @@ -0,0 +1,38 @@ +#!/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. +# + +source ./test_env.sh +. $srcdir/ais_check + +host=127.0.0.1 + +unset QPID_NO_MODULE_DIR # failover_soak uses --module-dir, dont want clash +MODULES=${MODULES:-$moduledir} +MESSAGES=${MESSAGES:-500000} +REPORT_FREQUENCY=${REPORT_FREQUENCY:-20000} +VERBOSITY=${VERBOSITY:-0} +DURABILITY=${DURABILITY:-0} +N_QUEUES=${N_QUEUES:-1} +N_BROKERS=${N_BROKERS:-4} + +rm -f soak-*.log +exec ./failover_soak $MODULES ./declare_queues ./replaying_sender ./resuming_receiver $MESSAGES $REPORT_FREQUENCY $VERBOSITY $DURABILITY $N_QUEUES $N_BROKERS + diff --git a/qpid/cpp/src/tests/run_federation_tests b/qpid/cpp/src/tests/run_federation_tests new file mode 100755 index 0000000000..590f74746e --- /dev/null +++ b/qpid/cpp/src/tests/run_federation_tests @@ -0,0 +1,64 @@ +#!/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 tests. + +source ./test_env.sh + +trap stop_brokers INT TERM QUIT + +if [ -f ../.libs/xml.so ] ; then + MODULES="--load-module xml" # Load the XML exchange and run XML exchange federation tests + SKIPTESTS="" +else + MODULES="--no-module-dir" + SKIPTESTS="-i *xml" +fi + +start_brokers() { + ../qpidd --daemon --port 0 --no-data-dir $MODULES --auth no > qpidd.port + LOCAL_PORT=`cat qpidd.port` + ../qpidd --daemon --port 0 --no-data-dir $MODULES --auth no > qpidd.port + REMOTE_PORT=`cat qpidd.port` + + ../qpidd --daemon --port 0 --no-data-dir $MODULES --auth no > qpidd.port + REMOTE_B1=`cat qpidd.port` + ../qpidd --daemon --port 0 --no-data-dir $MODULES --auth no > qpidd.port + REMOTE_B2=`cat qpidd.port` +} + +stop_brokers() { + $QPIDD_EXEC $MODULES -q --port $LOCAL_PORT + $QPIDD_EXEC $MODULES -q --port $REMOTE_PORT + $QPIDD_EXEC $MODULES -q --port $REMOTE_B1 + $QPIDD_EXEC $MODULES -q --port $REMOTE_B2 +} + +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" $@ + RETCODE=$? + stop_brokers + if test x$RETCODE != x0; then + echo "FAIL federation tests"; exit 1; + fi +fi diff --git a/qpid/cpp/src/tests/run_federation_tests.ps1 b/qpid/cpp/src/tests/run_federation_tests.ps1 new file mode 100644 index 0000000000..35353a870f --- /dev/null +++ b/qpid/cpp/src/tests/run_federation_tests.ps1 @@ -0,0 +1,84 @@ +# +# 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 tests. + +$srcdir = Split-Path $myInvocation.InvocationName +$PYTHON_DIR = "$srcdir\..\..\..\python" +if (!(Test-Path $PYTHON_DIR -pathType Container)) { + "Skipping federation tests as python libs not found" + exit 1 +} + +$PYTHON_TEST_DIR = "$srcdir\..\..\..\tests\src\py" +$QMF_LIB = "$srcdir\..\..\..\extras\qmf\src\py" + +# Test runs from the tests directory but the broker executable is one level +# up, and most likely in a subdirectory from there based on what build type. +# Look around for it before trying to start it. +$subs = "Debug","Release","MinSizeRel","RelWithDebInfo" +foreach ($sub in $subs) { + $prog = "..\$sub\qpidd.exe" + if (Test-Path $prog) { + break + } +} +if (!(Test-Path $prog)) { + "Cannot locate qpidd.exe" + exit 1 +} +$cmdline = "$prog --auth=no --no-module-dir --no-data-dir --port=0 --ssl-port=0 --log-to-file qpidd.log $args | foreach { set-content qpidd.port `$_ }" +$cmdblock = $executioncontext.invokecommand.NewScriptBlock($cmdline) + +function start_brokers { + # Start 2 brokers, saving the port numbers in LOCAL_PORT, REMOTE_PORT. + . $srcdir\background.ps1 $cmdblock + while (!(Test-Path qpidd.port)) { + Start-Sleep 2 + } + set-item -path env:LOCAL_PORT -value (get-content -path qpidd.port -totalcount 1) + Remove-Item qpidd.port + . $srcdir\background.ps1 $cmdblock + while (!(Test-Path qpidd.port)) { + Start-Sleep 2 + } + set-item -path env:REMOTE_PORT -value (get-content -path qpidd.port -totalcount 1) +} + +function stop_brokers { + Invoke-Expression "$prog -q --port $env:LOCAL_PORT" | Out-Default + Invoke-Expression "$prog -q --port $env:REMOTE_PORT" | Out-Default +} + +trap { + &stop_brokers + break +} + +&start_brokers +"Running federation tests using brokers on ports $env:LOCAL_PORT $env:REMOTE_PORT" +$env:PYTHONPATH="$srcdir;$PYTHON_DIR;$PYTHON_TEST_DIR;$env:PYTHONPATH;$QMF_LIB" +$tests = "*" +Invoke-Expression "python $PYTHON_DIR/qpid-python-test -m federation -b localhost:$env:LOCAL_PORT -Dremote-port=$env:REMOTE_PORT $tests" | Out-Default +$RETCODE=$LASTEXITCODE +&stop_brokers +if ($RETCODE -ne 0) { + "FAIL federation tests" + exit 1 +} diff --git a/qpid/cpp/src/tests/run_header_test b/qpid/cpp/src/tests/run_header_test new file mode 100755 index 0000000000..34008132cc --- /dev/null +++ b/qpid/cpp/src/tests/run_header_test @@ -0,0 +1,37 @@ +#!/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. +# + +# Simple test of encode/decode of a double in application headers +# TODO: this should be expanded to cover a wider set of types and go +# in both directions + +srcdir=`dirname $0` +source ./test_env.sh + +test -f qpidd.port && QPID_PORT=`cat qpidd.port` + +if test -d ${PYTHON_DIR} ; then + ./header_test -p $QPID_PORT + $srcdir/header_test.py "localhost" $QPID_PORT +else + echo "Skipping header test as python libs not found" +fi + diff --git a/qpid/cpp/src/tests/run_header_test.ps1 b/qpid/cpp/src/tests/run_header_test.ps1 new file mode 100644 index 0000000000..7d3e43a30f --- /dev/null +++ b/qpid/cpp/src/tests/run_header_test.ps1 @@ -0,0 +1,47 @@ +# +# 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. +# + +# Simple test of encode/decode of a double in application headers +# TODO: this should be expanded to cover a wider set of types and go +# in both directions + +$srcdir = Split-Path $myInvocation.InvocationName +$PYTHON_DIR = "$srcdir\..\..\..\python" +if (!(Test-Path $PYTHON_DIR -pathType Container)) { + "Skipping header test as python libs not found" + exit 0 +} + +if (Test-Path qpidd.port) { + set-item -path env:QPID_PORT -value (get-content -path qpidd.port -totalcount 1) +} + +# Test runs from the tests directory but the test executables are in a +# subdirectory based on the build type. Look around for it before trying +# to start it. +. $srcdir\find_prog.ps1 .\header_test.exe +if (!(Test-Path $prog)) { + "Cannot locate header_test.exe" + exit 1 +} + +Invoke-Expression "$prog -p $env:QPID_PORT" | Write-Output +$env:PYTHONPATH="$PYTHON_DIR;$env:PYTHONPATH" +Invoke-Expression "python $srcdir/header_test.py localhost $env:QPID_PORT" | Write-Output +exit $LASTEXITCODE diff --git a/qpid/cpp/src/tests/run_headers_federation_tests b/qpid/cpp/src/tests/run_headers_federation_tests new file mode 100644 index 0000000000..a4584e6884 --- /dev/null +++ b/qpid/cpp/src/tests/run_headers_federation_tests @@ -0,0 +1,49 @@ +#!/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 the federation tests for the Headers Exchange. + +source ./test_env.sh + +trap stop_brokers INT TERM QUIT + +start_brokers() { + ../qpidd --daemon --port 0 --no-data-dir --no-module-dir --auth no > qpidd.port + LOCAL_PORT=`cat qpidd.port` + ../qpidd --daemon --port 0 --no-data-dir --no-module-dir --auth no > qpidd.port + REMOTE_PORT=`cat qpidd.port` +} + +stop_brokers() { + $QPIDD_EXEC --no-module-dir -q --port $LOCAL_PORT + $QPIDD_EXEC --no-module-dir -q --port $REMOTE_PORT +} + +if test -d ${PYTHON_DIR} ; then + start_brokers + echo "Running HeadersExchange federation tests using brokers on ports $LOCAL_PORT $REMOTE_PORT" + $QPID_PYTHON_TEST -m headers_federation -b localhost:$LOCAL_PORT -Dremote-port=$REMOTE_PORT $@ + RETCODE=$? + stop_brokers + if test x$RETCODE != x0; then + echo "FAIL federation tests"; exit 1; + fi +fi diff --git a/qpid/cpp/src/tests/run_long_cluster_tests b/qpid/cpp/src/tests/run_long_cluster_tests new file mode 100755 index 0000000000..5dce0be585 --- /dev/null +++ b/qpid/cpp/src/tests/run_long_cluster_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. +# + +srcdir=`dirname $0` +$srcdir/run_cluster_tests 'cluster_tests.LongTests.*' -DDURATION=4 + diff --git a/qpid/cpp/src/tests/run_perftest b/qpid/cpp/src/tests/run_perftest new file mode 100755 index 0000000000..5ad7c1ff4f --- /dev/null +++ b/qpid/cpp/src/tests/run_perftest @@ -0,0 +1,28 @@ +#!/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. +# + +# Args: count [qpid-perftest options...] +# Run a qpid-perftest with count multiplied. +# +MULTIPLIER=3 +COUNT=`expr $1 \* $MULTIPLIER` +shift +exec `dirname $0`/run_test ./qpid-perftest --summary --count $COUNT "$@" diff --git a/qpid/cpp/src/tests/run_queue_flow_limit_tests b/qpid/cpp/src/tests/run_queue_flow_limit_tests new file mode 100755 index 0000000000..f921cf5e7e --- /dev/null +++ b/qpid/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/qpid/cpp/src/tests/run_ring_queue_test b/qpid/cpp/src/tests/run_ring_queue_test new file mode 100755 index 0000000000..7ca870841e --- /dev/null +++ b/qpid/cpp/src/tests/run_ring_queue_test @@ -0,0 +1,36 @@ +#!/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 ring queue tests via make + +#setup path to find qpid-config and sender/receiver 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` +export QPID_PORT + +ring_queue_test -c -s 4 -r 4 +ring_queue_test -s 4 -r 0 +ring_queue_test -s 1 -r 1 + + diff --git a/qpid/cpp/src/tests/run_store_tests.ps1 b/qpid/cpp/src/tests/run_store_tests.ps1 new file mode 100644 index 0000000000..76b46737f0 --- /dev/null +++ b/qpid/cpp/src/tests/run_store_tests.ps1 @@ -0,0 +1,133 @@ +# +# 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 store tests. +# There are two sets of tests: +# 1. A subset of the normal broker python tests, dtx and persistence, but +# run again with the desired store loaded. +# 2. store.py, which tests recovering things across broker restarts. + +$test_store = $args[0] +if ($test_store -ne "MSSQL" -and $test_store -ne "MSSQL-CLFS") { + "Invalid store test type $test_store - must be MSSQL or MSSQL-CLFS" + exit 1 +} + +$srcdir = Split-Path $myInvocation.InvocationName +$PYTHON_DIR = "$srcdir\..\..\..\python" +if (!(Test-Path $PYTHON_DIR -pathType Container)) { + "Skipping store tests as python libs not found" + exit 1 +} + +$QMF_LIB = "$srcdir\..\..\..\extras\qmf\src\py" + +# Test runs from the tests directory but the broker executable is one level +# up, and most likely in a subdirectory from there based on what build type. +# Look around for it before trying to start it. +$subs = "Debug","Release","MinSizeRel","RelWithDebInfo" +foreach ($sub in $subs) { + $prog = "..\$sub\qpidd.exe" + if (Test-Path $prog) { + break + } +} +if (!(Test-Path $prog)) { + "Cannot locate qpidd.exe" + exit 1 +} + +# The store to test is the same build type as the broker. +$store_dir = "..\qpid\store\$sub" +if (!([string]::Compare($sub, "Debug", $True))) { + $suffix = "d" +} + +$stamp = Get-Date -format %dMMMyyyy_HHmmss +$env:STORE_LIB="$store_dir\store$suffix.dll" +if ($test_store -eq "MSSQL") { + $test_store_module="$store_dir\mssql_store$suffix.dll" + $env:STORE_SQL_LIB=$test_store_module + $env:STORE_CATALOG="store_recovery_sql_test_$stamp" + $cat1="store_sql_test_$stamp" + $out = "sql_store_test_$stamp" +} +else { + $test_store_module="$store_dir\msclfs_store$suffix.dll" + $env:STORE_SQL_CLFS_LIB=$test_store_module + $env:STORE_CATALOG="store_recovery_clfs_test_$stamp" + $cat1="store_clfs_test_$stamp" + $out = "clfs_store_test_$stamp" +} + +$FAILCODE = 0 + +# Test 1... re-run some of the regular python broker tests against a broker +# with the store module loaded. +$cmdline = "$prog --auth=no --port=0 --log-to-file qpidd-store.log --no-module-dir --load-module $env:STORE_LIB --load-module $test_store_module --catalog $cat1 | foreach { set-content qpidd-store.port `$_ }" +$cmdblock = $executioncontext.invokecommand.NewScriptBlock($cmdline) +. $srcdir\background.ps1 $cmdblock + +$wait_time = 0 +while (!(Test-Path qpidd-store.port) -and ($wait_time -lt 90)) { + Start-Sleep 2 + $wait_time += 2 +} +if (!(Test-Path qpidd-store.port)) { + "Time out waiting for broker to start" + exit 1 +} +set-item -path env:QPID_PORT -value (get-content -path qpidd-store.port -totalcount 1) +Remove-Item qpidd-store.port + +$PYTHON_TEST_DIR = "$srcdir\..\..\..\tests\src\py\qpid_tests\broker_0_10" +$env:PYTHONPATH="$PYTHON_DIR;$PYTHON_TEST_DIR;$env:PYTHONPATH;$QMF_LIB" +python $PYTHON_DIR/qpid-python-test -m dtx -m persistence -b localhost:$env:QPID_PORT $fails $tests +$RETCODE=$LASTEXITCODE +if ($RETCODE -ne 0) { + $FAILCODE = 1 +} + +# Piping the output makes the script wait for qpidd to finish. +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:QPIDD_EXEC="$prog" +$env:STORE_LIB="$store_dir\store$suffix.dll" +if ($test_store -eq "MSSQL") { + $env:STORE_SQL_LIB="$store_dir\mssql_store$suffix.dll" + $env:STORE_CATALOG="store_recovery_sql_test_$stamp" + $out = "sql_store_test_$stamp" +} +else { + $env:STORE_SQL_CLFS_LIB="$store_dir\msclfs_store$suffix.dll" + $env:STORE_CATALOG="store_recovery_clfs_test_$stamp" + $out = "clfs_store_test_$stamp" +} +Invoke-Expression "python $PYTHON_DIR/qpid-python-test -m store -D OUTDIR=$out $tests" | Out-Default +$RETCODE=$LASTEXITCODE +if ($RETCODE -ne 0) { + "FAIL $test_store store tests" + $FAILCODE = 1 +} +exit $FAILCODE diff --git a/qpid/cpp/src/tests/run_test b/qpid/cpp/src/tests/run_test new file mode 100755 index 0000000000..6ec1fd892b --- /dev/null +++ b/qpid/cpp/src/tests/run_test @@ -0,0 +1,82 @@ +#!/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. +# + +# +# Set up environment and run a test executable or script. +# +# Output nothing if test passes, show the output if it fails and +# leave output in <test>.log for examination. +# +# If qpidd.port exists and is not empty run test with QPID_PORT=`cat qpidd.port` +# +# If $VALGRIND if is set run under valgrind. If there are valgrind +# erros show valgrind output, also leave it in <test>.valgrind for +# examination. +# + +srcdir=`dirname $0` +source ./test_env.sh +source $srcdir/vg_check + +# Export variables from makefile. +export srcdir + +# Set QPID_PORT if qpidd.port exists. +test -s qpidd.port && QPID_PORT=`cat qpidd.port` +export QPID_PORT + +# Avoid silly libtool error messages if these are not defined +test -z "$LC_ALL" && LC_ALL= +test -z "$LC_CTYPE" && LC_CTYPE= +test -z "$LC_COLLATE" && LC_COLLATE= +test -z "$LC_MESSAGES" && LC_MESSAGES= +export LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES + +VG_LOG="`basename $1`.vglog" +rm -f $VG_LOG* + +# Use VALGRIND_OPTS="--gen-suppressions=all" to generated suppressions +VALGRIND_OPTS="$VALGRIND_OPTS +--leak-check=full +--demangle=yes +--suppressions=$srcdir/.valgrind.supp +--num-callers=25 +--log-file=$VG_LOG -- +" +ERROR=0 +if grep -l "^# Generated by .*libtool" "$1" >/dev/null 2>&1; then + # This is a libtool "executable". Valgrind it if VALGRIND specified. + test -n "$VALGRIND" && VALGRIND="$VALGRIND $VALGRIND_OPTS" + # Hide output unless there's an error. + $LIBTOOL --mode=execute $VALGRIND "$@" 2>&1 || ERROR=1 + test -n "$VALGRIND" && { vg_check $VG_LOG* || ERROR=1 ; } +elif file $1 | grep -q text; then + # This is a non-libtool shell script, just execute it. + exec "$@" +else + # This is a real executable, valgrind it. + test -n "$VALGRIND" && VALGRIND="$VALGRIND $VALGRIND_OPTS" + # Hide output unless there's an error. + $VALGRIND "$@" 2>&1 || ERROR=1 + test -n "$VALGRIND" && { vg_check $VG_LOG* || ERROR=1 ; } +fi + +exit $ERROR + diff --git a/qpid/cpp/src/tests/run_test.ps1 b/qpid/cpp/src/tests/run_test.ps1 new file mode 100644 index 0000000000..ca990bc057 --- /dev/null +++ b/qpid/cpp/src/tests/run_test.ps1 @@ -0,0 +1,69 @@ +# +# 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. +# + +$srcdir = Split-Path $myInvocation.InvocationName + +# Set up environment and run a test executable or script. +$env:QPID_DATA_DIR = "" +$env:BOOST_TEST_SHOW_PROGRESS = "yes" + +# The test exe is probably not in the current binary dir - it's usually +# placed in a subdirectory based on the configuration built in Visual Studio. +# So check around to see where it is - when located, set the QPID_LIB_DIR +# and PATH to look in the corresponding configuration off the src directory, +# one level up. +$prog = $args[0] +$is_script = $prog -match ".ps1$" +if (!$is_script) { + . $srcdir\find_prog.ps1 $prog + $args[0] = $prog + $env:QPID_LIB_DIR = "..\$sub" + $env:PATH += ";$dir\$sub;..\$sub" +} + +# If qpidd.port exists and is not empty run test with QPID_PORT set. +if (Test-Path qpidd.port) { + set-item -path env:QPID_PORT -value (get-content -path qpidd.port -totalcount 1) +} + +$si = new-object System.Diagnostics.ProcessStartInfo +$si.WorkingDirectory = $pwd +$si.UseShellExecute = $false +$si.CreateNoWindow = $true +$si.RedirectStandardOutput = $true +if ($is_script) { + $si.FileName = (get-command powershell.exe).Definition + $si.Arguments = $args +} +else { + $si.FileName = $args[0] + if ($args.length -gt 1) { + $si.Arguments = $args[1..($args.length-1)] + } +} +$p = [System.Diagnostics.Process]::Start($si) +$line = "" +while (($line = $p.StandardOutput.ReadLine()) -ne $null) { + $line +} +# ReadToEnd() works, but doesn't show any output until the program exits. +#$p.StandardOutput.ReadToEnd() +$p.WaitForExit() +$status = $p.ExitCode +exit $status diff --git a/qpid/cpp/src/tests/sasl.mk b/qpid/cpp/src/tests/sasl.mk new file mode 100644 index 0000000000..20eaa7c7a5 --- /dev/null +++ b/qpid/cpp/src/tests/sasl.mk @@ -0,0 +1,49 @@ +# +# 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. +# + +# Test that are only relevant if SASL is enabled. +if HAVE_SASL + +check_PROGRAMS+=cluster_authentication_soak +cluster_authentication_soak_INCLUDES=$(PUBLIC_INCLUDES) +cluster_authentication_soak_SOURCES=cluster_authentication_soak.cpp ForkedBroker.h ForkedBroker.cpp +cluster_authentication_soak_LDADD=$(lib_client) $(lib_broker) + +# Note: sasl_version is not a test -- it is a tool used by tests. +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_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 +LONG_TESTS += 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 + + +endif # HAVE_SASL diff --git a/qpid/cpp/src/tests/sasl_fed b/qpid/cpp/src/tests/sasl_fed new file mode 100755 index 0000000000..884c44177c --- /dev/null +++ b/qpid/cpp/src/tests/sasl_fed @@ -0,0 +1,166 @@ +#! /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 + +# 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 +# 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 + +# In a distribution, the python tools will be absent. +if [ ! -f $QPID_CONFIG_EXEC ] || [ ! -f $QPID_ROUTE_EXEC ] ; then + echo "python tools absent - skipping sasl_fed." + exit 0 +fi + + +sasl_config_file=$builddir/sasl_config + +my_random_number=$RANDOM +tmp_root=/tmp/sasl_fed_$my_random_number +mkdir -p $tmp_root + + +#-------------------------------------------------- +#echo " Starting broker 1" +#-------------------------------------------------- +$QPIDD_EXEC \ + -p 0 \ + --data-dir $tmp_root/data_1 \ + --auth=yes \ + --mgmt-enable=yes \ + --log-enable info+ \ + --log-source yes \ + --log-to-file $tmp_root/qpidd_1.log \ + --sasl-config=$sasl_config_file \ + -d > $tmp_root/broker_1_port + +broker_1_port=`cat $tmp_root/broker_1_port` + + +#-------------------------------------------------- +#echo " Starting broker 2" +#-------------------------------------------------- +$QPIDD_EXEC \ + -p 0 \ + --data-dir $tmp_root/data_2 \ + --auth=yes \ + --mgmt-enable=yes \ + --log-enable info+ \ + --log-source yes \ + --log-to-file $tmp_root/qpidd_2.log \ + --sasl-config=$sasl_config_file \ + -d > $tmp_root/broker_2_port + +broker_2_port=`cat $tmp_root/broker_2_port` + +sleep 2 + +# I am not randomizing these names, because the test creates its own brokers. +QUEUE_NAME=sasl_fed_queue +ROUTING_KEY=sasl_fed_queue +EXCHANGE_NAME=sasl_fedex + +#-------------------------------------------------- +#echo " add exchanges" +#-------------------------------------------------- +$QPID_CONFIG_EXEC -a localhost:$broker_1_port add exchange direct $EXCHANGE_NAME +$QPID_CONFIG_EXEC -a localhost:$broker_2_port add exchange direct $EXCHANGE_NAME + + +#-------------------------------------------------- +#echo " add queues" +#-------------------------------------------------- +$QPID_CONFIG_EXEC -a localhost:$broker_1_port add queue $QUEUE_NAME +$QPID_CONFIG_EXEC -a localhost:$broker_2_port add queue $QUEUE_NAME + +sleep 5 + +#-------------------------------------------------- +#echo " create bindings" +#-------------------------------------------------- +$QPID_CONFIG_EXEC -a localhost:$broker_1_port bind $EXCHANGE_NAME $QUEUE_NAME $ROUTING_KEY +$QPID_CONFIG_EXEC -a localhost:$broker_2_port bind $EXCHANGE_NAME $QUEUE_NAME $ROUTING_KEY + +sleep 5 + + +#-------------------------------------------------- +#echo " qpid-route route add" +#-------------------------------------------------- +$QPID_ROUTE_EXEC route add zag/zag@localhost:$broker_2_port zag/zag@localhost:$broker_1_port $EXCHANGE_NAME $ROUTING_KEY "" "" DIGEST-MD5 + +sleep 5 + + +n_messages=100 +#-------------------------------------------------- +#echo " Sending 100 messages to $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 + +#-------------------------------------------------- +#echo " Examine Broker $broker_1_port" +#-------------------------------------------------- +broker_1_message_count=`$PYTHON_COMMANDS/qpid-stat -q localhost:$broker_1_port | grep sasl_fed_queue | awk '{print $2}'` +#echo " " + +#-------------------------------------------------- +#echo " Examine Broker $broker_2_port" +#-------------------------------------------------- +broker_2_message_count=`$PYTHON_COMMANDS/qpid-stat -q localhost:$broker_2_port | grep sasl_fed_queue | awk '{print $2}'` +#echo " " + +#-------------------------------------------------- +#echo " Asking brokers to quit." +#-------------------------------------------------- +$QPIDD_EXEC --port $broker_1_port --quit +$QPIDD_EXEC --port $broker_2_port --quit + + +#-------------------------------------------------- +#echo "Removing temporary directory $tmp_root" +#-------------------------------------------------- +rm -rf $tmp_root + +if [ "$broker_2_message_count" -eq "$n_messages" ]; then + # echo "good: |$broker_2_message_count| == |$n_messages|" + exit 0 +else + # echo "not ideal: |$broker_1_message_count| != |$n_messages|" + exit 1 +fi + + + + + + diff --git a/qpid/cpp/src/tests/sasl_fed_ex b/qpid/cpp/src/tests/sasl_fed_ex new file mode 100755 index 0000000000..716a806874 --- /dev/null +++ b/qpid/cpp/src/tests/sasl_fed_ex @@ -0,0 +1,361 @@ +#! /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. +# + +#=============================================================================== +# 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 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 + +# In a distribution, the python tools will be absent. +if [ ! -f $QPID_CONFIG_EXEC ] || [ ! -f $QPID_ROUTE_EXEC ] ; then + echo "python tools absent - skipping sasl_fed_ex." + exit 0 +fi + +CERT_DIR=`pwd`/test_cert_db +CERT_PW_FILE=`pwd`/cert.password +TEST_HOSTNAME=127.0.0.1 + +create_certs() { + #create certificate and key databases with single, simple, self-signed certificate in it + mkdir ${CERT_DIR} + certutil -N -d ${CERT_DIR} -f ${CERT_PW_FILE} + certutil -S -d ${CERT_DIR} -n ${TEST_HOSTNAME} -s "CN=${TEST_HOSTNAME}" -t "CT,," -x -f ${CERT_PW_FILE} -z /usr/bin/certutil 2> /dev/null +} + +delete_certs() { + if [[ -e ${CERT_DIR} ]] ; then + print "removing cert dir ${CERT_DIR}" + rm -rf ${CERT_DIR} + fi +} + + +CERTUTIL=$(type -p certutil) +if [[ !(-x $CERTUTIL) ]] ; then + echo "No certutil, skipping ssl test"; + exit 0; +fi + +delete_certs +create_certs 2> /dev/null +if [ ! $? ]; then + error "Could not create test certificate" + exit 1 +fi + +sasl_config_dir=$builddir/sasl_config + +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 + +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} + +export QPID_NO_MODULE_DIR=1 +export QPID_LOAD_MODULE=$SSLCONNECTOR_LIB +export QPID_SSL_CERT_DB=${CERT_DIR} +export QPID_SSL_CERT_PASSWORD_FILE=${CERT_PW_FILE} +export QPID_SSL_CERT_NAME=${TEST_HOSTNAME} + + + +####################################### +# Understanding this Plumbing +####################################### +# 1. when you establish the route with qpid-route, +# here is the best termiology to use: +# +# qpid-route route add DST SRC +# +# 2. DST will connect to SRC through the ssl port of SRC. +# +# 3. sender client connects to the tcp port of SRC. +# +# 4. sender specifies mechanism ANONYMOUS. +# +# 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 + +} + + +start_brokers $clustering_flag + + +# I am not randomizing these names, because this test creates its own brokers. +QUEUE_NAME=sasl_fed_queue +ROUTING_KEY=sasl_fed_queue +EXCHANGE_NAME=sasl_fedex + + +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 + + +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 + + +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 + + +# +# 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. + + + +#---------------------------------------------------------------- +# 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 + + +# 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}') + +halt_brokers + +sleep 1 + +if [ ! ${link_status} ]; then + print "link_status is empty" + print "result: fail" + exit 2 +fi + +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 +fi + +print "link_status has a bad value: ${link_status}" +print "result: fail" +exit 3 + + + diff --git a/qpid/cpp/src/tests/sasl_fed_ex_dynamic b/qpid/cpp/src/tests/sasl_fed_ex_dynamic new file mode 100755 index 0000000000..c20b8d69a0 --- /dev/null +++ b/qpid/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/qpid/cpp/src/tests/sasl_fed_ex_dynamic_cluster b/qpid/cpp/src/tests/sasl_fed_ex_dynamic_cluster new file mode 100755 index 0000000000..b0cceccecb --- /dev/null +++ b/qpid/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/qpid/cpp/src/tests/sasl_fed_ex_link b/qpid/cpp/src/tests/sasl_fed_ex_link new file mode 100755 index 0000000000..7b232d4874 --- /dev/null +++ b/qpid/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/qpid/cpp/src/tests/sasl_fed_ex_link_cluster b/qpid/cpp/src/tests/sasl_fed_ex_link_cluster new file mode 100755 index 0000000000..4139300b12 --- /dev/null +++ b/qpid/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/qpid/cpp/src/tests/sasl_fed_ex_queue b/qpid/cpp/src/tests/sasl_fed_ex_queue new file mode 100755 index 0000000000..be0c10cf63 --- /dev/null +++ b/qpid/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/qpid/cpp/src/tests/sasl_fed_ex_queue_cluster b/qpid/cpp/src/tests/sasl_fed_ex_queue_cluster new file mode 100755 index 0000000000..f251420e08 --- /dev/null +++ b/qpid/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/qpid/cpp/src/tests/sasl_fed_ex_route b/qpid/cpp/src/tests/sasl_fed_ex_route new file mode 100755 index 0000000000..dd5c4f3cac --- /dev/null +++ b/qpid/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/qpid/cpp/src/tests/sasl_fed_ex_route_cluster b/qpid/cpp/src/tests/sasl_fed_ex_route_cluster new file mode 100755 index 0000000000..a5d1542def --- /dev/null +++ b/qpid/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/qpid/cpp/src/tests/sasl_test_setup.sh b/qpid/cpp/src/tests/sasl_test_setup.sh new file mode 100755 index 0000000000..6395ba6ec3 --- /dev/null +++ b/qpid/cpp/src/tests/sasl_test_setup.sh @@ -0,0 +1,41 @@ +#! /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. +# + +SASL_PW=/usr/sbin/saslpasswd2 +test -x $SASL_PW || { echo Skipping SASL test, saslpasswd2 not found; exit 0; } + +mkdir -p sasl_config + +# Create configuration file. +cat > sasl_config/qpidd.conf <<EOF +pwcheck_method: auxprop +auxprop_plugin: sasldb +sasldb_path: $PWD/sasl_config/qpidd.sasldb +sql_select: dummy select +EOF + +# Populate temporary sasl db. +SASLTEST_DB=./sasl_config/qpidd.sasldb +rm -f $SASLTEST_DB +echo guest | $SASL_PW -c -p -f $SASLTEST_DB -u QPID guest +echo zig | $SASL_PW -c -p -f $SASLTEST_DB -u QPID zig +echo zag | $SASL_PW -c -p -f $SASLTEST_DB -u QPID zag + diff --git a/qpid/cpp/src/tests/sasl_version.cpp b/qpid/cpp/src/tests/sasl_version.cpp new file mode 100644 index 0000000000..db3efe4181 --- /dev/null +++ b/qpid/cpp/src/tests/sasl_version.cpp @@ -0,0 +1,48 @@ +/* + * + * 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 "sasl/sasl.h" + + +/* + Some tests need to distinguish between different versions of + SASL. This encodes and outputs the version number as an integer + for easy use in testing scripts. +*/ + +int +main ( ) +{ + // I assume that these are 8-bit quantities.... + int sasl_version = (SASL_VERSION_MAJOR << 16) + + (SASL_VERSION_MINOR << 8) + + SASL_VERSION_STEP; + + std::cout << sasl_version << std::endl; + + return 0; +} + + + + diff --git a/qpid/cpp/src/tests/sender.cpp b/qpid/cpp/src/tests/sender.cpp new file mode 100644 index 0000000000..063b5e87dc --- /dev/null +++ b/qpid/cpp/src/tests/sender.cpp @@ -0,0 +1,157 @@ +/* + * + * 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/FailoverManager.h> +#include <qpid/client/Session.h> +#include <qpid/client/AsyncSession.h> +#include <qpid/client/Message.h> +#include <qpid/client/MessageReplayTracker.h> +#include <qpid/client/QueueOptions.h> +#include <qpid/Exception.h> +#include "TestOptions.h" + +#include "qpid/messaging/Message.h" // Only for Statistics +#include "Statistics.h" + +#include <fstream> +#include <iostream> + +using namespace qpid; +using namespace qpid::client; +using namespace qpid::framing; + +using namespace std; + +namespace qpid { +namespace tests { + +struct Args : public qpid::TestOptions +{ + string destination; + string key; + uint sendEos; + bool durable; + uint ttl; + string lvqMatchValue; + string lvqMatchFile; + bool reportTotal; + int reportEvery; + bool reportHeader; + + Args() : + key("test-queue"), sendEos(0), durable(false), ttl(0), + reportTotal(false), + reportEvery(0), + reportHeader(true) + { + addOptions() + ("exchange", qpid::optValue(destination, "EXCHANGE"), "Exchange to send messages to") + ("routing-key", qpid::optValue(key, "KEY"), "Routing key to add to messages") + ("send-eos", qpid::optValue(sendEos, "N"), "Send N EOS messages to mark end of input") + ("durable", qpid::optValue(durable, "true|false"), "Mark messages as durable.") + ("ttl", qpid::optValue(ttl, "msecs"), "Time-to-live for messages, in milliseconds") + ("lvq-match-value", qpid::optValue(lvqMatchValue, "KEY"), "The value to set for the LVQ match key property") + ("lvq-match-file", qpid::optValue(lvqMatchFile, "FILE"), "A file containing values to set for the LVQ match key property") + ("report-total", qpid::optValue(reportTotal), "Report total throughput statistics") + ("report-every", qpid::optValue(reportEvery,"N"), "Report throughput statistics every N messages") + ("report-header", qpid::optValue(reportHeader, "yes|no"), "Headers on report.") + ; + } +}; + +const string EOS("eos"); + +class Sender : public FailoverManager::Command +{ + public: + Sender(Reporter<Throughput>& reporter, const std::string& destination, const std::string& key, uint sendEos, bool durable, uint ttl, + const std::string& lvqMatchValue, const std::string& lvqMatchFile); + void execute(AsyncSession& session, bool isRetry); + + private: + Reporter<Throughput>& reporter; + messaging::Message dummyMessage; + const std::string destination; + MessageReplayTracker sender; + Message message; + const uint sendEos; + uint sent; + std::ifstream lvqMatchValues; +}; + +Sender::Sender(Reporter<Throughput>& rep, const std::string& dest, const std::string& key, uint eos, bool durable, uint ttl, const std::string& lvqMatchValue, const std::string& lvqMatchFile) : + reporter(rep), destination(dest), sender(10), message("", key), sendEos(eos), sent(0) , lvqMatchValues(lvqMatchFile.c_str()) +{ + if (durable){ + message.getDeliveryProperties().setDeliveryMode(framing::PERSISTENT); + } + + if (ttl) { + message.getDeliveryProperties().setTtl(ttl); + } + + if (!lvqMatchValue.empty()) { + message.getHeaders().setString(QueueOptions::strLVQMatchProperty, lvqMatchValue); + } +} + +void Sender::execute(AsyncSession& session, bool isRetry) +{ + if (isRetry) sender.replay(session); + else sender.init(session); + string data; + while (getline(std::cin, data)) { + message.setData(data); + //message.getHeaders().setInt("SN", ++sent); + string matchKey; + if (lvqMatchValues && getline(lvqMatchValues, matchKey)) { + message.getHeaders().setString(QueueOptions::strLVQMatchProperty, matchKey); + } + reporter.message(dummyMessage); // For statistics + sender.send(message, destination); + } + for (uint i = sendEos; i > 0; --i) { + message.setData(EOS); + sender.send(message, destination); + } +} + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char ** argv) +{ + Args opts; + try { + opts.parse(argc, argv); + Reporter<Throughput> reporter(std::cout, opts.reportEvery, opts.reportHeader); + FailoverManager connection(opts.con); + Sender sender(reporter, opts.destination, opts.key, opts.sendEos, opts.durable, opts.ttl, opts.lvqMatchValue, opts.lvqMatchFile); + connection.execute(sender); + connection.close(); + if (opts.reportTotal) reporter.report(); + return 0; + } catch(const std::exception& error) { + std::cout << "Failed: " << error.what() << std::endl; + } + return 1; +} diff --git a/qpid/cpp/src/tests/shared_perftest b/qpid/cpp/src/tests/shared_perftest new file mode 100755 index 0000000000..cc192d25bd --- /dev/null +++ b/qpid/cpp/src/tests/shared_perftest @@ -0,0 +1,22 @@ +#!/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. +# + +exec `dirname $0`/run_perftest 100000 --mode shared --npubs 16 --nsubs 16 diff --git a/qpid/cpp/src/tests/shlibtest.cpp b/qpid/cpp/src/tests/shlibtest.cpp new file mode 100644 index 0000000000..5655eb7e64 --- /dev/null +++ b/qpid/cpp/src/tests/shlibtest.cpp @@ -0,0 +1,34 @@ +/* + * 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. + * + */ + +namespace qpid { +namespace tests { + +int* loaderData = 0; +extern "C" +#ifdef WIN32 +__declspec(dllexport) +#endif +void callMe(int *i) { loaderData=i; } + +struct OnUnload { ~OnUnload() { *loaderData=42; } }; +OnUnload unloader; // For destructor. + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/ssl.mk b/qpid/cpp/src/tests/ssl.mk new file mode 100644 index 0000000000..435db0c55b --- /dev/null +++ b/qpid/cpp/src/tests/ssl.mk @@ -0,0 +1,22 @@ +# +# 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. +# + +TESTS+=ssl_test +EXTRA_DIST+=ssl_test +CLEAN_LOCAL += test_cert_db cert.password diff --git a/qpid/cpp/src/tests/ssl_test b/qpid/cpp/src/tests/ssl_test new file mode 100755 index 0000000000..cbf75eb237 --- /dev/null +++ b/qpid/cpp/src/tests/ssl_test @@ -0,0 +1,142 @@ +#!/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 SSL +source ./test_env.sh + +CONFIG=$(dirname $0)/config.null +CERT_DIR=`pwd`/test_cert_db +CERT_PW_FILE=`pwd`/cert.password +TEST_HOSTNAME=127.0.0.1 +TEST_CLIENT_CERT=rumplestiltskin +COUNT=10 + +trap cleanup EXIT + +error() { echo $*; exit 1; } + +create_certs() { + #create certificate and key databases with single, simple, self-signed certificate in it + mkdir ${CERT_DIR} + certutil -N -d ${CERT_DIR} -f ${CERT_PW_FILE} + certutil -S -d ${CERT_DIR} -n ${TEST_HOSTNAME} -s "CN=${TEST_HOSTNAME}" -t "CT,," -x -f ${CERT_PW_FILE} -z /usr/bin/certutil + certutil -S -d ${CERT_DIR} -n ${TEST_CLIENT_CERT} -s "CN=${TEST_CLIENT_CERT}" -t "CT,," -x -f ${CERT_PW_FILE} -z /usr/bin/certutil +} + +delete_certs() { + if [[ -e ${CERT_DIR} ]] ; then + rm -rf ${CERT_DIR} + 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" +start_broker() { # $1 = extra opts + ../qpidd --transport ssl --port 0 --ssl-port 0 $COMMON_OPTS $1; +} + +stop_brokers() { + test -n "$PORT" && ../qpidd --no-module-dir -qp $PORT + test -n "$PORT2" && ../qpidd --no-module-dir -qp $PORT2 + PORT="" + PORT2="" +} + +cleanup() { + stop_brokers + delete_certs +} + +CERTUTIL=$(type -p certutil) +if [[ !(-x $CERTUTIL) ]] ; then + echo "No certutil, skipping ssl test"; + exit 0; +fi + +if [[ !(-e ${CERT_PW_FILE}) ]] ; then + echo password > ${CERT_PW_FILE} +fi +delete_certs +create_certs || error "Could not create test certificate" +PORT=`start_broker` || error "Could not start broker" +echo "Running SSL test on port $PORT" +export QPID_NO_MODULE_DIR=1 +export QPID_LOAD_MODULE=$SSLCONNECTOR_LIB +export QPID_SSL_CERT_DB=${CERT_DIR} +export QPID_SSL_CERT_PASSWORD_FILE=${CERT_PW_FILE} + +## Test connection via connection settings +./qpid-perftest --count ${COUNT} --port ${PORT} -P ssl -b $TEST_HOSTNAME --summary + +## Test connection with a URL +URL=amqp:ssl:$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; } + +#### Client Authentication tests + +PORT2=`start_broker --ssl-require-client-authentication` || error "Could not start broker" +echo "Running SSL client authentication test on port $PORT2" +URL=amqp:ssl:$TEST_HOSTNAME:$PORT2 + +## See if you can set the SSL cert-name for the connection +./qpid-send -b $URL --connection-options "{ssl-cert-name: $TEST_CLIENT_CERT }" --content-string=hello -a "bar;{create:always}" +MSG2=`./qpid-receive -b $URL --connection-options "{ssl-cert-name: $TEST_CLIENT_CERT }" -a "bar;{create:always}" --messages 1` +test "$MSG2" = "hello" || { echo "receive failed '$MSG2' != 'hello'"; exit 1; } + +## Make sure that connect fails with an invalid SSL cert-name +./qpid-send -b $URL --connection-options "{ssl-cert-name: pignose }" --content-string=hello -a "baz;{create:always}" 2>/dev/null 1>/dev/null +MSG3=`./qpid-receive -b $URL --connection-options "{ssl-cert-name: pignose }" -a "baz;{create:always}" --messages 1 2>/dev/null` +test "$MSG3" = "" || { echo "receive succeeded without valid ssl cert '$MSG3' != ''"; 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 + # 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" +} + +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:ssl:$TEST_HOSTNAME:$PORT1 -a "foo;{create:always}" -f | uniq > ssl_test_receive.tmp & +./qpid-send -b amqp:ssl:$TEST_HOSTNAME:$PORT2 --content-string=one -a "foo;{create:always}" +../qpidd --no-module-dir -qp $PORT1 # Kill broker 1 receiver should fail-over. +./qpid-send -b amqp:ssl:$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; } +rm -f ssl_test_receive.* + diff --git a/qpid/cpp/src/tests/start_broker b/qpid/cpp/src/tests/start_broker new file mode 100755 index 0000000000..093c44051a --- /dev/null +++ b/qpid/cpp/src/tests/start_broker @@ -0,0 +1,24 @@ +#!/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. +# + +# Start a test broker. +srcdir=`dirname $0` +exec $srcdir/run_test ../qpidd --auth=no --no-module-dir --daemon --port=0 --log-to-file qpidd.log "$@" > qpidd.port diff --git a/qpid/cpp/src/tests/start_broker.ps1 b/qpid/cpp/src/tests/start_broker.ps1 new file mode 100644 index 0000000000..9263262b9f --- /dev/null +++ b/qpid/cpp/src/tests/start_broker.ps1 @@ -0,0 +1,60 @@ +# +# 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. +# + +# Get the directory where this script resides. +function Get-ScriptPath + { Split-Path $myInvocation.ScriptName } + +# Start a test broker and capture it's port (from stdout) to qpidd.port +# This script will exit immediately after spawning the broker process. To avoid +# running more tests before the broker is initialized, wait for the qpidd.port +# file to appear before exiting. +if (Test-Path qpidd.port) { + Remove-Item qpidd.port +} + +# Test runs from the tests directory but the broker executable is one level +# up, and most likely in a subdirectory from there based on what build type. +# Look around for it before trying to start it. +$subs = "Debug","Release","MinSizeRel","RelWithDebInfo" +foreach ($sub in $subs) { + $prog = "..\$sub\qpidd.exe" + if (Test-Path $prog) { + break + } +} +if (!(Test-Path $prog)) { + "Cannot locate qpidd.exe" + exit 1 +} +$cmdline = "$prog --auth=no --no-module-dir --port=0 --log-to-file qpidd.log $args | foreach { set-content qpidd.port `$_ }" +$cmdblock = $executioncontext.invokecommand.NewScriptBlock($cmdline) +$srcdir = Get-ScriptPath +. $srcdir\background.ps1 $cmdblock + +$wait_time = 0 +while (!(Test-Path qpidd.port) -and ($wait_time -lt 10)) { + Start-Sleep 2 + $wait_time += 2 +} +if (Test-Path qpidd.port) { + exit 0 +} +"Time out waiting for broker to start" +exit 1 diff --git a/qpid/cpp/src/tests/start_cluster b/qpid/cpp/src/tests/start_cluster new file mode 100755 index 0000000000..bc35a2eddc --- /dev/null +++ b/qpid/cpp/src/tests/start_cluster @@ -0,0 +1,42 @@ +#!/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. +# + +# Start a cluster of brokers on local host, put the list of ports for cluster members in cluster.ports +# + +# Execute command with the ais group set. +source ./test_env.sh +. `dirname $0`/ais_check + +rm -f cluster*.log cluster.ports qpidd.port + +SIZE=${1:-3}; shift +CLUSTER=$HOSTNAME.$$ +OPTS="-d --no-module-dir --load-module $CLUSTER_LIB --cluster-name=$CLUSTER --auth=no --log-enable notice+ --log-enable debug+:cluster $@" + +for (( i=0; i<SIZE; ++i )); do + DDIR=`mktemp -d /tmp/start_cluster.XXXXXXXXXX` + PORT=`with_ais_group ../qpidd -p0 --log-to-file=cluster$i.log $OPTS --data-dir=$DDIR` || exit 1 + echo $PORT >> cluster.ports +done + +head -n 1 cluster.ports > qpidd.port # First member's port for tests. + diff --git a/qpid/cpp/src/tests/start_cluster_hosts b/qpid/cpp/src/tests/start_cluster_hosts new file mode 100755 index 0000000000..778b4248da --- /dev/null +++ b/qpid/cpp/src/tests/start_cluster_hosts @@ -0,0 +1,70 @@ +#!/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. +# + +# +# Start a cluster of brokers on local host, put the list of host port addresses +# in cluster.ports +# +# Arguments: [-k] [-p port] HOST [HOST...] +# -p port to start broker on, can be 0. Actual ports recorded in cluster.addr. +# -k kill any qpidd processes owned by this user before starting. +# +# Start a broker on each named host. Name a host twice to start multiple brokers. +# +# You must be able to ssh to each host and be in group ais. +# $QPIDD must be executable on each host. +# Logs go to syslog on each host, with a unique prefix per broker. +# + +QPIDD=${QPIDD:-$PWD/../qpidd} +LIBQPIDCLUSTER=${LIBQPIDCLUSTER:-$PWD/../.libs/cluster.so} +NAME=$USER # User name is default cluster name. +RESTART=NO + +while getopts "kp:n:q:r" ARG ; do + case $ARG in + k) KILL=yes ;; + p) PORT="$OPTARG" ;; + n) NAME=$OPTARG ;; + q) QPIDD=$OPTARG ;; + l) LIBQPIDCLUSTER=$OPTARG ;; + r) RESTART=yes ;; + *) echo "Error parsing options: $ARG"; exit 1 ;; + esac +done +shift `expr $OPTIND - 1` +test -n "$PORT" && PORTOPT="-p $PORT" +test "$KILL" = yes && KILL="$QPIDD --no-module-dir -q $PORTOPT ;" +CLUSTER=${*:-$CLUSTER} # Use args or env +test -z "$CLUSTER" && { echo Must specify at least one host; exit 1; } + + +OPTS="-d $PORTOPT --load-module $LIBQPIDCLUSTER --cluster-name=$NAME --no-data-dir --auth=no --log-to-syslog --log-enable=info+" + +num=0 +for h in $CLUSTER; do + num=`expr $num + 1` # Give a unique log prefix to each node. + cmd="$KILL $QPIDD $OPTS --log-prefix $num.$h" + out=`echo "$cmd" | ssh $h newgrp ais` || { echo == $h error: $out ; exit 1; } + if [ "$PORT" = 0 ] ; then p=$out; else p=$PORT; fi + echo "$h $p" +done + diff --git a/qpid/cpp/src/tests/stop_broker b/qpid/cpp/src/tests/stop_broker new file mode 100755 index 0000000000..248fd1fc5c --- /dev/null +++ b/qpid/cpp/src/tests/stop_broker @@ -0,0 +1,41 @@ +#!/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. +# + +# Stop the broker, check for errors. +# +QPID_PORT=`cat qpidd.port` +export QPID_PORT +rm -f qpidd.port + +../qpidd --no-module-dir --quit || ERROR=1 + +# Check qpidd.log. +egrep 'warning\|error\|critical' qpidd.log && { + echo "WARNING: Suspicious broker log entries in qpidd.log, above." +} + +# Check valgrind log. +if test -n "$VALGRIND"; then + . `dirname $0`/vg_check $VG_LOG* + vg_check qpidd.vglog* || ERROR=1 +fi + +exit $ERROR diff --git a/qpid/cpp/src/tests/stop_broker.ps1 b/qpid/cpp/src/tests/stop_broker.ps1 new file mode 100644 index 0000000000..4fdeb26e2b --- /dev/null +++ b/qpid/cpp/src/tests/stop_broker.ps1 @@ -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. +# + +# Stop the broker, check for errors. +Get-Content -path qpidd.port -totalCount 1 | Set-Variable -name qpid_port +Remove-Item qpidd.port + +# Test runs from the tests directory but the broker executable is one level +# up, and most likely in a subdirectory from there based on what build type. +# Look around for it before trying to start it. +$subs = "Debug","Release","MinSizeRel","RelWithDebInfo" +foreach ($sub in $subs) { + $prog = "..\$sub\qpidd.exe" + if (Test-Path $prog) { + break + } +} +if (!(Test-Path $prog)) { + "Cannot locate qpidd.exe" + exit 1 +} + +# Piping the output makes the script wait for qpidd to finish. +Invoke-Expression "$prog --quit --port $qpid_port" | Write-Output +$stopped = $? + +# Check qpidd.log. +filter bad_stuff { + $_ -match "( warning | error | critical )" +} + +$qpidd_errors = $false +Get-Content -path qpidd.log | where { bad_stuff } | Out-Default | Set-Variable -name qpidd_errors -value $true +if ($qpidd_errors -eq $true) { + "WARNING: Suspicious broker log entries in qpidd.log, above." +} +if ($stopped -eq $true) { + exit 0 +} +exit 1 diff --git a/qpid/cpp/src/tests/stop_cluster b/qpid/cpp/src/tests/stop_cluster new file mode 100755 index 0000000000..d598a2255a --- /dev/null +++ b/qpid/cpp/src/tests/stop_cluster @@ -0,0 +1,33 @@ +#!/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. +# + +# Stop brokers on ports listed in cluster.ports + +PORTS=`cat cluster.ports` +for PORT in $PORTS ; do + $QPIDD_EXEC --no-module-dir -qp $PORT || ERROR="$ERROR $PORT" +done +rm -f cluster.ports qpidd.port + +if [ -n "$ERROR" ]; then + echo "Errors stopping brokers on ports: $ERROR" + exit 1 +fi diff --git a/qpid/cpp/src/tests/store.py b/qpid/cpp/src/tests/store.py new file mode 100755 index 0000000000..77e8a78e5d --- /dev/null +++ b/qpid/cpp/src/tests/store.py @@ -0,0 +1,197 @@ +#!/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 errno, os, time +from brokertest import * +from qpid import compat, session +from qpid.util import connect +from qpid.connection import Connection +from qpid.datatypes import Message, uuid4 +from qpid.queue import Empty + +class StoreTests(BrokerTest): + + XA_RBROLLBACK = 1 + XA_RBTIMEOUT = 2 + XA_OK = 0 + tx_counter = 0 + + def configure(self, config): + self.config = config + self.defines = self.config.defines + BrokerTest.configure(self, config) + + def setup_connection(self): + socket = connect(self._broker.host(), self._broker.port()) + return Connection(sock=socket) + + def setup_session(self): + self.conn.start() + return self.conn.session(str(uuid4())) + + def start_session(self): + self.conn = self.setup_connection() + self.ssn = self.setup_session() + + def setUp(self): + BrokerTest.setUp(self) + self._broker = self.broker() + self.start_session() + + def cycle_broker(self): + # tearDown resets working dir; change it back after. + d = os.getcwd() + BrokerTest.tearDown(self) + os.chdir(d) + self._broker = None + self._broker = self.broker() + self.conn = self.setup_connection() + self.ssn = self.setup_session() + + def xid(self, txid): + StoreTests.tx_counter += 1 + branchqual = "v%s" % StoreTests.tx_counter + return self.ssn.xid(format=0, global_id=txid, branch_id=branchqual) + + def testDurableExchange(self): + try: + self.ssn.exchange_delete(exchange="DE1") + except: + # restart the session busted from the exception + self.start_session() + + self.ssn.exchange_declare(exchange="DE1", type="direct", durable=True) + response = self.ssn.exchange_query(name="DE1") + self.assert_(response.durable) + self.assert_(not response.not_found) + + # Cycle the broker and make sure the exchange recovers + self.cycle_broker() + response = self.ssn.exchange_query(name="DE1") + self.assert_(response.durable) + self.assert_(not response.not_found) + + self.ssn.exchange_delete(exchange="DE1") + + def testDurableQueues(self): + try: + self.ssn.queue_delete(queue="DQ1") + except: + self.start_session() + + self.ssn.queue_declare(queue="DQ1", durable=True) + response = self.ssn.queue_query(queue="DQ1") + self.assertEqual("DQ1", response.queue) + self.assert_(response.durable) + + # Cycle the broker and make sure the queue recovers + self.cycle_broker() + response = self.ssn.queue_query(queue="DQ1") + self.assertEqual("DQ1", response.queue) + self.assert_(response.durable) + + self.ssn.queue_delete(queue="DQ1") + + def testDurableBindings(self): + try: + self.ssn.exchange_unbind(queue="DB_Q1", exchange="DB_E1", binding_key="K1") + except: + self.start_session() + try: + self.ssn.exchange_delete(exchange="DB_E1") + except: + self.start_session() + try: + self.ssn.queue_delete(queue="DB_Q1") + except: + self.start_session() + + self.ssn.queue_declare(queue="DB_Q1", durable=True) + self.ssn.exchange_declare(exchange="DB_E1", type="direct", durable=True) + self.ssn.exchange_bind(exchange="DB_E1", queue="DB_Q1", binding_key="K1") + + # Cycle the broker and make sure the binding recovers + self.cycle_broker() + response = self.ssn.exchange_bound(exchange="DB_E1", queue="DB_Q1", binding_key="K1") + self.assert_(not response.exchange_not_found) + self.assert_(not response.queue_not_found) + self.assert_(not response.queue_not_matched) + self.assert_(not response.key_not_matched) + + self.ssn.exchange_unbind(queue="DB_Q1", exchange="DB_E1", binding_key="K1") + self.ssn.exchange_delete(exchange="DB_E1") + self.ssn.queue_delete(queue="DB_Q1") + + def testDtxRecoverPrepared(self): + try: + self.ssn.exchange_unbind(queue="Dtx_Q", exchange="Dtx_E", binding_key="Dtx") + except: + self.start_session() + try: + self.ssn.exchange_delete(exchange="Dtx_E") + except: + self.start_session() + try: + self.ssn.queue_delete(queue="Dtx_Q") + except: + self.start_session() + + self.ssn.queue_declare(queue="Dtx_Q", auto_delete=False, durable=True) + self.ssn.exchange_declare(exchange="Dtx_E", type="direct", durable=True) + self.ssn.exchange_bind(exchange="Dtx_E", queue="Dtx_Q", binding_key="Dtx") + txid = self.xid("DtxRecoverPrepared") + self.ssn.dtx_select() + self.ssn.dtx_start(xid=txid) + # 2 = delivery_mode.persistent + dp = self.ssn.delivery_properties(routing_key="Dtx_Q", delivery_mode=2) + self.ssn.message_transfer(message=Message(dp, "transactional message")) + self.ssn.dtx_end(xid=txid) + self.assertEqual(self.XA_OK, self.ssn.dtx_prepare(xid=txid).status) + # Cycle the broker and make sure the xid is there, the message is not + # queued. + self.cycle_broker() + # The txid should be recovered and in doubt + xids = self.ssn.dtx_recover().in_doubt + xid_matched = False + for x in xids: + self.assertEqual(txid.format, x.format) + self.assertEqual(txid.global_id, x.global_id) + self.assertEqual(txid.branch_id, x.branch_id) + xid_matched = True + self.assert_(xid_matched) + self.ssn.message_subscribe(destination="dtx_msgs", queue="Dtx_Q", accept_mode=1, acquire_mode=0) + self.ssn.message_flow(unit = 1, value = 0xFFFFFFFFL, destination = "dtx_msgs") + self.ssn.message_flow(unit = 0, value = 10, destination = "dtx_msgs") + message_arrivals = self.ssn.incoming("dtx_msgs") + try: + message_arrivals.get(timeout=1) + assert False, 'Message present in queue before commit' + except Empty: pass + self.ssn.dtx_select() + self.assertEqual(self.XA_OK, self.ssn.dtx_commit(xid=txid, one_phase=False).status) + try: + msg = message_arrivals.get(timeout=1) + self.assertEqual("transactional message", msg.body) + except Empty: + assert False, 'Message should be present after dtx commit but is not' + + self.ssn.exchange_unbind(queue="Dtx_Q", exchange="Dtx_E", binding_key="Dtx") + self.ssn.exchange_delete(exchange="Dtx_E") + self.ssn.queue_delete(queue="Dtx_Q") diff --git a/qpid/cpp/src/tests/test.xquery b/qpid/cpp/src/tests/test.xquery new file mode 100644 index 0000000000..4cfe3af02d --- /dev/null +++ b/qpid/cpp/src/tests/test.xquery @@ -0,0 +1,6 @@ + let $w := ./weather + return $w/station = 'Raleigh-Durham International Airport (KRDU)' + and $w/temperature_f > 50 + and $w/temperature_f - $w/dewpoint > 5 + and $w/wind_speed_mph > 7 + and $w/wind_speed_mph < 20 diff --git a/qpid/cpp/src/tests/test_env.sh.in b/qpid/cpp/src/tests/test_env.sh.in new file mode 100644 index 0000000000..842d7729cb --- /dev/null +++ b/qpid/cpp/src/tests/test_env.sh.in @@ -0,0 +1,79 @@ +# +# 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. +# + +absdir() { echo `cd $1 && pwd`; } + +# Environment variables substituted by configure/cmake. +srcdir=`absdir @abs_srcdir@` +builddir=`absdir @abs_builddir@` +top_srcdir=`absdir @abs_top_srcdir@` +top_builddir=`absdir @abs_top_builddir@` +moduledir=$top_builddir/src@builddir_lib_suffix@ +testmoduledir=$builddir@builddir_lib_suffix@ +export QPID_INSTALL_PREFIX=@prefix@ + +# Python paths and directories +export PYTHON_DIR=$builddir/python +export QPID_PYTHON_TEST=$PYTHON_DIR/commands/qpid-python-test +if [ ! -d $PYTHON_DIR -a -d $top_srcdir/../python ]; then + export PYTHON_DIR=$top_srcdir/../python + export QPID_PYTHON_TEST=$PYTHON_DIR/qpid-python-test +fi +export QPID_TESTS=$top_srcdir/../tests +export QPID_TESTS_PY=$QPID_TESTS/src/py +export QPID_TOOLS=$top_srcdir/../tools +export QMF_LIB=$top_srcdir/../extras/qmf/src/py +export PYTHON_COMMANDS=$QPID_TOOLS/src/py +export PYTHONPATH=$srcdir:$PYTHON_DIR:$PYTHON_COMMANDS:$QPID_TESTS_PY:$QMF_LIB:$PYTHONPATH +export QPID_CONFIG_EXEC=$PYTHON_COMMANDS/qpid-config +export QPID_ROUTE_EXEC=$PYTHON_COMMANDS/qpid-route +export QPID_CLUSTER_EXEC=$PYTHON_COMMANDS/qpid-cluster + +# Executables +export QPIDD_EXEC=$top_builddir/src/qpidd +export QPID_WATCHDOG_EXEC=$top_builddir/src/qpidd_watchdog + +# Test executables +export QPID_TEST_EXEC_DIR=$builddir +export RECEIVER_EXEC=$QPID_TEST_EXEC_DIR/receiver +export SENDER_EXEC=$QPID_TEST_EXEC_DIR/sender + +# Path +export PATH=$top_builddir/src:$builddir:$srcdir:$PYTHON_COMMANDS:$QPID_TEST_EXEC_DIR:$PATH + +# Modules +export TEST_STORE_LIB=$testmoduledir/test_store.so + +exportmodule() { test -f $moduledir/$2 && eval "export $1=$moduledir/$2"; } +exportmodule ACL_LIB acl.so +exportmodule CLUSTER_LIB cluster.so +exportmodule REPLICATING_LISTENER_LIB replicating_listener.so +exportmodule REPLICATION_EXCHANGE_LIB replication_exchange.so +exportmodule SSLCONNECTOR_LIB sslconnector.so +exportmodule SSL_LIB ssl.so +exportmodule WATCHDOG_LIB watchdog.so +exportmodule XML_LIB xml.so + +# Qpid options +export QPID_NO_MODULE_DIR=1 # Don't accidentally load installed modules +export QPID_DATA_DIR= # Default to no data dir, not ~/.qpidd + +# Options for boost test framework +export BOOST_TEST_SHOW_PROGRESS=yes +export BOOST_TEST_CATCH_SYSTEM_ERRORS=no diff --git a/qpid/cpp/src/tests/test_store.cpp b/qpid/cpp/src/tests/test_store.cpp new file mode 100644 index 0000000000..257e77b6b4 --- /dev/null +++ b/qpid/cpp/src/tests/test_store.cpp @@ -0,0 +1,178 @@ +/* + * + * 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. + * + */ + + +/**@file + * Plug-in message store for tests. + * + * Add functionality as required, build up a comprehensive set of + * features to support persistent behavior tests. + * + * Current features special "action" messages can: + * - raise exception from enqueue. + * - force host process to exit. + * - do async completion after a delay. + */ + +#include "qpid/broker/NullMessageStore.h" +#include "qpid/broker/Broker.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/log/Statement.h" +#include "qpid/Plugin.h" +#include "qpid/Options.h" +#include <boost/cast.hpp> +#include <boost/lexical_cast.hpp> +#include <memory> +#include <fstream> + +using namespace qpid; +using namespace broker; +using namespace std; +using namespace qpid::sys; + +namespace qpid { +namespace tests { + +struct TestStoreOptions : public Options { + + string name; + string dump; + + TestStoreOptions() : Options("Test Store Options") { + addOptions() + ("test-store-name", optValue(name, "NAME"), "Name of test store instance.") + ("test-store-dump", optValue(dump, "FILE"), "File to dump enqueued messages.") + ; + } +}; + +struct Completer : public Runnable { + boost::intrusive_ptr<PersistableMessage> message; + int usecs; + Completer(boost::intrusive_ptr<PersistableMessage> m, int u) : message(m), usecs(u) {} + void run() { + qpid::sys::usleep(usecs); + message->enqueueComplete(); + delete this; + } +}; + +class TestStore : public NullMessageStore { + public: + TestStore(const TestStoreOptions& opts, Broker& broker_) + : options(opts), name(opts.name), broker(broker_) + { + QPID_LOG(info, "TestStore name=" << name << " dump=" << options.dump); + if (!options.dump.empty()) + dump.reset(new ofstream(options.dump.c_str())); + } + + ~TestStore() { + for_each(threads.begin(), threads.end(), boost::bind(&Thread::join, _1)); + } + + virtual bool isNull() const { return false; } + + void enqueue(TransactionContext* , + const boost::intrusive_ptr<PersistableMessage>& pmsg, + const PersistableQueue& ) + { + Message* msg = dynamic_cast<Message*>(pmsg.get()); + assert(msg); + + // Dump the message if there is a dump file. + if (dump.get()) { + msg->getFrames().getMethod()->print(*dump); + *dump << endl << " "; + msg->getFrames().getHeaders()->print(*dump); + *dump << endl << " "; + *dump << msg->getFrames().getContentSize() << endl; + } + + // Check the message for special instructions. + string data = msg->getFrames().getContent(); + size_t i = string::npos; + size_t j = string::npos; + if (strncmp(data.c_str(), TEST_STORE_DO.c_str(), strlen(TEST_STORE_DO.c_str())) == 0 + && (i = data.find(name+"[")) != string::npos + && (j = data.find("]", i)) != string::npos) + { + size_t start = i+name.size()+1; + string action = data.substr(start, j-start); + + if (action == EXCEPTION) { + throw Exception(QPID_MSG("TestStore " << name << " throwing exception for: " << data)); + } + else if (action == EXIT_PROCESS) { + // FIXME aconway 2009-04-10: this is a dubious way to + // close the process at best, it can cause assertions or seg faults + // rather than clean exit. + QPID_LOG(critical, "TestStore " << name << " forcing process exit for: " << data); + exit(0); + } + else if (strncmp(action.c_str(), ASYNC.c_str(), strlen(ASYNC.c_str())) == 0) { + std::string delayStr(action.substr(ASYNC.size())); + int delay = boost::lexical_cast<int>(delayStr); + threads.push_back(Thread(*new Completer(msg, delay))); + } + else { + QPID_LOG(error, "TestStore " << name << " unknown action " << action); + msg->enqueueComplete(); + } + } + else + msg->enqueueComplete(); + } + + private: + static const string TEST_STORE_DO, EXCEPTION, EXIT_PROCESS, ASYNC; + TestStoreOptions options; + string name; + Broker& broker; + vector<Thread> threads; + std::auto_ptr<ofstream> dump; +}; + +const string TestStore::TEST_STORE_DO = "TEST_STORE_DO: "; +const string TestStore::EXCEPTION = "exception"; +const string TestStore::EXIT_PROCESS = "exit_process"; +const string TestStore::ASYNC="async "; + +struct TestStorePlugin : public Plugin { + + TestStoreOptions options; + + Options* getOptions() { return &options; } + + void earlyInitialize (Plugin::Target& target) + { + Broker* broker = dynamic_cast<Broker*>(&target); + if (!broker) return; + boost::shared_ptr<MessageStore> p(new TestStore(options, *broker)); + broker->setStore (p); + } + + void initialize(qpid::Plugin::Target&) {} +}; + +static TestStorePlugin pluginInstance; + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/test_tools.h b/qpid/cpp/src/tests/test_tools.h new file mode 100644 index 0000000000..de672f938a --- /dev/null +++ b/qpid/cpp/src/tests/test_tools.h @@ -0,0 +1,106 @@ +#ifndef TEST_TOOLS_H +#define TEST_TOOLS_H + +/* + * + * 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. + * + */ +#include "qpid/log/Logger.h" + +#include <limits.h> // Include before boost/test headers. +#include <boost/test/test_tools.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/assign/list_of.hpp> +#include <vector> +#include <set> +#include <ostream> +#include <sstream> +#include <exception> + +// Print a sequence +template <class T> std::ostream& seqPrint(std::ostream& o, const T& seq) { + std::copy(seq.begin(), seq.end(), std::ostream_iterator<typename T::value_type>(o, " ")); + return o; +} + +// Compare sequences +template <class T, class U> +bool seqEqual(const T& a, const U& b) { + typename T::const_iterator i = a.begin(); + typename U::const_iterator j = b.begin(); + while (i != a.end() && j != b.end() && *i == *j) { ++i; ++j; } + return (i == a.end()) && (j == b.end()); +} + +// ostream and == operators so we can compare vectors and sets with +// boost::assign::list_of with BOOST_CHECK_EQUALS +namespace std { // In namespace std so boost can find them. + +template <class T> +ostream& operator<<(ostream& o, const vector<T>& v) { return seqPrint(o, v); } + +template <class T> +ostream& operator<<(ostream& o, const set<T>& v) { return seqPrint(o, v); } + +template <class T> +ostream& operator<<(ostream& o, const boost::assign_detail::generic_list<T>& l) { return seqPrint(o, l); } + +template <class T> +bool operator == (const vector<T>& a, const boost::assign_detail::generic_list<T>& b) { return seqEqual(a, b); } + +template <class T> +bool operator == (const boost::assign_detail::generic_list<T>& b, const vector<T>& a) { return seqEqual(a, b); } + +template <class T> +bool operator == (const set<T>& a, const boost::assign_detail::generic_list<T>& b) { return seqEqual(a, b); } + +template <class T> +bool operator == (const boost::assign_detail::generic_list<T>& b, const set<T>& a) { return seqEqual(a, b); } +} + +namespace qpid { +namespace tests { + +/** Check if types of two objects (as given by typeinfo::name()) match. */ +#define BOOST_CHECK_TYPEID_EQUAL(a,b) BOOST_CHECK_EQUAL(typeid(a).name(),typeid(b).name()) + +/** + * Supress all logging in a scope, restore to previous configuration in destructor. + */ +struct ScopedSuppressLogging { + typedef qpid::log::Logger Logger; + ScopedSuppressLogging(Logger& l=Logger::instance()) : logger(l), opts(l.getOptions()) { l.clear(); } + ~ScopedSuppressLogging() { logger.configure(opts); } + Logger& logger; + qpid::log::Options opts; +}; + +inline std::string getLibPath(const char* envName, const char* defaultPath = 0) { + const char* p = std::getenv(envName); + if (p != 0) + return p; + if (defaultPath == 0) { + std::ostringstream msg; + msg << "Environment variable " << envName << " not set."; + throw std::runtime_error(msg.str()); + } + return defaultPath; +} + +}} // namespace qpid::tests + +#endif /*!TEST_TOOLS_H*/ + diff --git a/qpid/cpp/src/tests/test_watchdog b/qpid/cpp/src/tests/test_watchdog new file mode 100755 index 0000000000..2b4ae9246e --- /dev/null +++ b/qpid/cpp/src/tests/test_watchdog @@ -0,0 +1,36 @@ +#!/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. +# + +# Tests for the watchdog plug-in + +source ./test_env.sh +# Start a broker with watchdog, freeze it with kill -STOP, verify that it is killed. +PORT=`$QPIDD_EXEC -dp0 --no-data-dir --auth=no --no-module-dir --load-module $WATCHDOG_LIB --log-to-file=qpidd_watchdog.log --watchdog-interval 2` || exit 1 +PID=`$QPIDD_EXEC --no-module-dir -cp $PORT` || exit 1 +kill -STOP $PID +sleep 3 + +if kill -0 $PID 2>/dev/null; then + echo "Hung process did not die." + kill $PID +else + true +fi diff --git a/qpid/cpp/src/tests/test_wrap b/qpid/cpp/src/tests/test_wrap new file mode 100755 index 0000000000..dd43c5a2e2 --- /dev/null +++ b/qpid/cpp/src/tests/test_wrap @@ -0,0 +1,48 @@ +#!/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. +# + +# Read the started broker port, set appropriate env vars +# then run the program under test + +QPID_PORT=`cat qpidd.port` +export QPID_PORT + +program=$1 +shift + +QPID_LOG_TO_FILE=`basename $program`.log +export QPID_LOG_TO_FILE + +ERROR=0 +$program $* || ERROR=1 + +# Check qpidd.log. +egrep 'warning\|error\|critical' $QPID_LOG_TO_FILE && { + echo "WARNING: Suspicious broker log entries in $QPID_LOG_TO_FILE, above." +} + +# Check valgrind log. +#if test -n "$VALGRIND"; then +# . `dirname $0`/vg_check $VG_LOG* +# vg_check qpidd.vglog* || ERROR=1 +#fi + +exit $ERROR diff --git a/qpid/cpp/src/tests/testagent.cpp b/qpid/cpp/src/tests/testagent.cpp new file mode 100644 index 0000000000..98520b424a --- /dev/null +++ b/qpid/cpp/src/tests/testagent.cpp @@ -0,0 +1,203 @@ +/* + * + * 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/management/Manageable.h> +#include <qpid/management/ManagementObject.h> +#include <qpid/agent/ManagementAgent.h> +#include <qpid/sys/Mutex.h> +#include <qpid/sys/Time.h> +#include "qmf/org/apache/qpid/agent/example/Parent.h" +#include "qmf/org/apache/qpid/agent/example/Child.h" +#include "qmf/org/apache/qpid/agent/example/ArgsParentCreate_child.h" +#include "qmf/org/apache/qpid/agent/example/EventChildCreated.h" +#include "qmf/org/apache/qpid/agent/example/Package.h" + +#include <signal.h> +#include <cstdlib> +#include <iostream> + +#include <sstream> + +static bool running = true; + +using namespace std; +using qpid::management::ManagementAgent; +using qpid::management::ManagementObject; +using qpid::management::Manageable; +using qpid::management::Args; +using qpid::sys::Mutex; +namespace _qmf = qmf::org::apache::qpid::agent::example; + +class ChildClass; + +//============================================================== +// CoreClass is the operational class that corresponds to the +// "Parent" class in the management schema. +//============================================================== +class CoreClass : public Manageable +{ + string name; + ManagementAgent* agent; + _qmf::Parent* mgmtObject; + std::vector<ChildClass*> children; + Mutex vectorLock; + +public: + + CoreClass(ManagementAgent* agent, string _name); + ~CoreClass() { mgmtObject->resourceDestroy(); } + + ManagementObject* GetManagementObject(void) const + { return mgmtObject; } + + void doLoop(); + status_t ManagementMethod (uint32_t methodId, Args& args, string& text); +}; + +class ChildClass : public Manageable +{ + string name; + _qmf::Child* mgmtObject; + +public: + + ChildClass(ManagementAgent* agent, CoreClass* parent, string name); + ~ChildClass() { mgmtObject->resourceDestroy(); } + + ManagementObject* GetManagementObject(void) const + { return mgmtObject; } + + void doWork() + { + mgmtObject->inc_count(2); + } +}; + +CoreClass::CoreClass(ManagementAgent* _agent, string _name) : name(_name), agent(_agent) +{ + static uint64_t persistId = 0x111222333444555LL; + mgmtObject = new _qmf::Parent(agent, this, name); + + agent->addObject(mgmtObject, persistId++); + mgmtObject->set_state("IDLE"); +} + +void CoreClass::doLoop() +{ + // Periodically bump a counter to provide a changing statistical value + while (running) { + qpid::sys::sleep(1); + mgmtObject->inc_count(); + mgmtObject->set_state("IN_LOOP"); + + { + Mutex::ScopedLock _lock(vectorLock); + + for (std::vector<ChildClass*>::iterator iter = children.begin(); + iter != children.end(); + iter++) { + (*iter)->doWork(); + } + } + } +} + +Manageable::status_t CoreClass::ManagementMethod(uint32_t methodId, Args& args, string& /*text*/) +{ + Mutex::ScopedLock _lock(vectorLock); + + switch (methodId) { + case _qmf::Parent::METHOD_CREATE_CHILD: + _qmf::ArgsParentCreate_child& ioArgs = (_qmf::ArgsParentCreate_child&) args; + + ChildClass *child = new ChildClass(agent, this, ioArgs.i_name); + ioArgs.o_childRef = child->GetManagementObject()->getObjectId(); + + children.push_back(child); + + agent->raiseEvent(_qmf::EventChildCreated(ioArgs.i_name)); + + return STATUS_OK; + } + + return STATUS_NOT_IMPLEMENTED; +} + +ChildClass::ChildClass(ManagementAgent* agent, CoreClass* parent, string name) +{ + mgmtObject = new _qmf::Child(agent, this, parent, name); + + agent->addObject(mgmtObject); +} + + +//============================================================== +// Main program +//============================================================== + +ManagementAgent::Singleton* singleton; + +void shutdown(int) +{ + running = false; +} + +int main_int(int argc, char** argv) +{ + singleton = new ManagementAgent::Singleton(); + const char* host = argc>1 ? argv[1] : "127.0.0.1"; + int port = argc>2 ? atoi(argv[2]) : 5672; + + signal(SIGINT, shutdown); + + // Create the qmf management agent + ManagementAgent* agent = singleton->getInstance(); + + // Register the Qmf_example schema with the agent + _qmf::Package packageInit(agent); + + // Start the agent. It will attempt to make a connection to the + // management broker + agent->init(host, port, 5, false, ".magentdata"); + + // Allocate some core objects + CoreClass core1(agent, "Example Core Object #1"); + CoreClass core2(agent, "Example Core Object #2"); + CoreClass core3(agent, "Example Core Object #3"); + + core1.doLoop(); + + // done, cleanup and exit + delete singleton; + + return 0; +} + +int main(int argc, char** argv) +{ + try { + return main_int(argc, argv); + } catch(std::exception& e) { + cerr << "Top Level Exception: " << e.what() << endl; + return 1; + } +} + diff --git a/qpid/cpp/src/tests/testagent.mk b/qpid/cpp/src/tests/testagent.mk new file mode 100644 index 0000000000..19d91ccab9 --- /dev/null +++ b/qpid/cpp/src/tests/testagent.mk @@ -0,0 +1,51 @@ +# +# 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. +# + +# Build a simple qmf agent for test purposes. + +TESTAGENT_GEN_SRC= \ + testagent_gen/qmf/org/apache/qpid/agent/example/Parent.h \ + testagent_gen/qmf/org/apache/qpid/agent/example/Child.h \ + testagent_gen/qmf/org/apache/qpid/agent/example/Parent.cpp \ + testagent_gen/qmf/org/apache/qpid/agent/example/Child.cpp \ + testagent_gen/qmf/org/apache/qpid/agent/example/ArgsParentCreate_child.h \ + testagent_gen/qmf/org/apache/qpid/agent/example/EventChildCreated.h \ + testagent_gen/qmf/org/apache/qpid/agent/example/EventChildDestroyed.h \ + testagent_gen/qmf/org/apache/qpid/agent/example/EventChildCreated.cpp \ + testagent_gen/qmf/org/apache/qpid/agent/example/EventChildDestroyed.cpp \ + testagent_gen/qmf/org/apache/qpid/agent/example/Package.h \ + testagent_gen/qmf/org/apache/qpid/agent/example/Package.cpp + +$(TESTAGENT_GEN_SRC): testagent_gen.timestamp +if GENERATE +TESTAGENT_DEPS=../mgen.timestamp +endif # GENERATE +testagent_gen.timestamp: testagent.xml ${TESTAGENT_DEPS} + $(QMF_GEN) -o testagent_gen/qmf $(srcdir)/testagent.xml + touch $@ + +CLEANFILES+=$(TESTAGENT_GEN_SRC) testagent_gen.timestamp + +testagent-testagent.$(OBJEXT): $(TESTAGENT_GEN_SRC) +qpidtest_PROGRAMS+=testagent +testagent_CXXFLAGS=$(CXXFLAGS) -Itestagent_gen +testagent_SOURCES=testagent.cpp $(TESTAGENT_GEN_SRC) +testagent_LDADD=$(top_builddir)/src/libqmf.la + +EXTRA_DIST+=testagent.xml diff --git a/qpid/cpp/src/tests/testagent.xml b/qpid/cpp/src/tests/testagent.xml new file mode 100644 index 0000000000..0b1436f999 --- /dev/null +++ b/qpid/cpp/src/tests/testagent.xml @@ -0,0 +1,64 @@ +<schema package="org.apache.qpid.agent.example"> + +<!-- + 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. +--> + + <!-- + =============================================================== + Parent + =============================================================== + --> + <class name="Parent"> + + This class represents a parent object + + <property name="name" type="lstr" access="RC" index="y"/> + + <statistic name="state" type="sstr" desc="Operational state of the link"/> + <statistic name="count" type="count64" unit="tick" desc="Counter that increases monotonically"/> + + <method name="create_child" desc="Create child object"> + <arg name="name" dir="I" type="lstr"/> + <arg name="childRef" dir="O" type="objId"/> + </method> + </class> + + + <!-- + =============================================================== + Child + =============================================================== + --> + <class name="Child"> + <property name="ParentRef" type="objId" references="Parent" access="RC" index="y" parentRef="y"/> + <property name="name" type="lstr" access="RC" index="y"/> + + <statistic name="count" type="count64" unit="tick" desc="Counter that increases monotonically"/> + + <method name="delete"/> + </class> + + <eventArguments> + <arg name="childName" type="lstr"/> + </eventArguments> + + <event name="ChildCreated" args="childName"/> + <event name="ChildDestroyed" args="childName"/> +</schema> + diff --git a/qpid/cpp/src/tests/testlib.py b/qpid/cpp/src/tests/testlib.py new file mode 100644 index 0000000000..fe57a84a81 --- /dev/null +++ b/qpid/cpp/src/tests/testlib.py @@ -0,0 +1,766 @@ +# +# 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. +# + +# +# Support library for qpid python tests. +# + +import os, re, signal, subprocess, time, unittest + +class TestBase(unittest.TestCase): + """ + Base class for qpid tests. Provides broker start/stop/kill methods + """ + + """ + The following environment vars control if and how the test is run, and determine where many of the helper + executables/libs are to be found. + """ + _storeLib = os.getenv("STORE_LIB") + _storeEnable = _storeLib != None # Must be True for durability to be enabled during the test + _qpiddExec = os.getenv("QPIDD_EXEC", "/usr/sbin/qpidd") + _tempStoreDir = os.path.abspath(os.getenv("TMP_DATA_DIR", "/tmp/qpid")) + + """Global message counter ensures unique messages""" + _msgCnt = 0 + + # --- Helper functions for parameter handling --- + + def _paramBool(self, key, val, keyOnly = False): + if val == None: + return "" + if keyOnly: + if val: + return " --%s" % key + else: + return "" + else: + if val: + return " --%s yes" % key + else: + return " --%s no" % key + + # --- Helper functions for message creation --- + + def _makeMessage(self, msgSize): + msg = "Message-%04d" % self._msgCnt + self._msgCnt = self._msgCnt + 1 + msgLen = len(msg) + if msgSize > msgLen: + for i in range(msgLen, msgSize): + if i == msgLen: + msg += "-" + else: + msg += chr(ord('a') + (i % 26)) + return msg + + def _makeMessageList(self, numMsgs, msgSize): + if msgSize == None: + msgSize = 12 + msgs = "" + for m in range(0, numMsgs): + msgs += "%s\n" % self._makeMessage(msgSize) + return msgs + + # --- Starting and stopping a broker --- + + def startBroker(self, qpiddArgs, logFile = None): + """Start a single broker daemon, returns tuple (pid, port)""" + if self._qpiddExec == None: + raise Exception("Environment variable QPIDD is not set") + cmd = "%s --daemon --port=0 %s" % (self._qpiddExec, qpiddArgs) + portStr = os.popen(cmd).read() + if len(portStr) == 0: + err = "Broker daemon startup failed." + if logFile != None: + err += " See log file %s" % logFile + raise Exception(err) + port = int(portStr) + pidStr = os.popen("%s -p %d -c" % (self._qpiddExec, port)).read() + try: + pid = int(pidStr) + except: + raise Exception("Unable to get pid: \"%s -p %d -c\" returned %s" % (self._qpiddExec, port, pidStr)) + #print "started broker: pid=%d, port=%d args: %s" % (pid, port, qpiddArgs) + return (pid, port) + + def killBroker(self, nodeTuple, ignoreFailures = False): + """Kill a broker using kill -9""" + try: + os.kill(nodeTuple[self.PID], signal.SIGKILL) + try: + os.waitpid(nodeTuple[self.PID], 0) + except: + pass + #print "killed broker: port=%d pid=%d" % (nodeTuple[self.PORT], nodeTuple[self.PID]) + except: + if ignoreFailures: + print "WARNING: killBroker (port=%d pid=%d) failed - ignoring." % (nodeTuple[self.PORT], nodeTuple[self.PID]) + else: + raise + + def stopBroker(self, nodeTuple, ignoreFailures = False): + """Stop a broker using qpidd -q""" + try: + ret = os.spawnl(os.P_WAIT, self._qpiddExec, self._qpiddExec, "--port=%d" % nodeTuple[self.PORT], "--quit", "--no-module-dir") + if ret != 0: + raise Exception("stopBroker(): port=%d: qpidd -q returned %d" % (nodeTuple[self.PORT], ret)) + try: + os.waitpid(nodeTuple[self.PID], 0) + except: + pass + #print "stopped broker: port=%d pid=%d" % (nodeTuple[self.PORT], nodeTuple[self.PID]) + except: + if ignoreFailures: + print "WARNING: stopBroker (port=%d pid=%d) failed - ignoring." % (nodeTuple[self.PORT], nodeTuple[self.PID]) + else: + raise + + + +class TestBaseCluster(TestBase): + """ + Base class for cluster tests. Provides methods for starting and stopping clusters and cluster nodes. + """ + + """ + The following environment vars control if and how the test is run, and determine where many of the helper + executables/libs are to be found. + """ + _clusterLib = os.getenv("CLUSTER_LIB") + _clusterTestEnable = _clusterLib != None # Must be True for these cluster tests to run + _xmlLib = os.getenv("XML_LIB") + _xmlEnable = _xmlLib != None + _qpidConfigExec = os.getenv("QPID_CONFIG_EXEC", "/usr/bin/qpid-config") + _qpidRouteExec = os.getenv("QPID_ROUTE_EXEC", "/usr/bin/qpid-route") + _receiverExec = os.getenv("RECEIVER_EXEC", "/usr/libexec/qpid/test/receiver") + _senderExec = os.getenv("SENDER_EXEC", "/usr/libexec/qpid/test/sender") + + + """ + _clusterDict is a dictionary of clusters: + key = cluster name (string) + val = dictionary of node numbers: + key = integer node number + val = tuple containing (pid, port) + For example, two clusters "TestCluster0" and "TestCluster1" containing several nodes would look as follows: + {"TestCluster0": {0: (pid0-0, port0-0), 1: (pid0-1, port0-1), ...}, "TestCluster1": {0: (pid1-0, port1-0), 1: (pid1-1, port1-1), ...}} + where pidm-n and portm-n are the int pid and port for TestCluster m node n respectively. + """ + _clusterDict = {} + + """Index for (pid, port) tuple""" + PID = 0 + PORT = 1 + + def run(self, res): + """ Skip cluster testing if env var RUN_CLUSTER_TESTS is not defined.""" + if not self._clusterTestEnable: + return + unittest.TestCase.run(self, res) + + # --- Private helper / convenience functions --- + + def _checkPids(self, clusterName = None): + for pid, port in self.getTupleList(): + try: + os.kill(pid, 0) + except: + raise Exception("_checkPids(): Broker with pid %d expected but does not exist! (crashed?)" % pid) + + + # --- Starting cluster node(s) --- + + def createClusterNode(self, nodeNumber, clusterName): + """Create a node and add it to the named cluster""" + if self._tempStoreDir == None: + raise Exception("Environment variable TMP_DATA_DIR is not set") + if self._clusterLib == None: + raise Exception("Environment variable LIBCLUSTER is not set") + name = "%s-%d" % (clusterName, nodeNumber) + dataDir = os.path.join(self._tempStoreDir, "cluster", name) + logFile = "%s.log" % dataDir + args = "--no-module-dir --load-module=%s --data-dir=%s --cluster-name=%s --auth=no --log-enable=notice+ --log-to-file=%s" % \ + (self._clusterLib, dataDir, clusterName, logFile) + if self._storeEnable: + if self._storeLib == None: + raise Exception("Environment variable LIBSTORE is not set") + args += " --load-module %s" % self._storeLib + self._clusterDict[clusterName][nodeNumber] = self.startBroker(args, logFile) + + def createCluster(self, clusterName, numberNodes = 0): + """Create a cluster containing an initial number of nodes""" + self._clusterDict[clusterName] = {} + for n in range(0, numberNodes): + self.createClusterNode(n, clusterName) + + def waitForNodes(self, clusterName): + """Wait for all nodes to become active (ie finish cluster sync)""" + # TODO - connect to each known node in cluster + # Until this is done, wait a bit (hack) + time.sleep(1) + + # --- Cluster and node status --- + + def getTupleList(self, clusterName = None): + """Get list of (pid, port) tuples of all known cluster brokers""" + tList = [] + for c, l in self._clusterDict.iteritems(): + if clusterName == None or c == clusterName: + for t in l.itervalues(): + tList.append(t) + return tList + + def getNumBrokers(self): + """Get total number of brokers in all known clusters""" + return len(self.getTupleList()) + + def checkNumBrokers(self, expected = None, checkPids = True): + """Check that the total number of brokers in all known clusters is the expected value""" + if expected != None and self.getNumBrokers() != expected: + raise Exception("Unexpected number of brokers: expected %d, found %d" % (expected, self.getNumBrokers())) + if checkPids: + self._checkPids() + + def getClusterTupleList(self, clusterName): + """Get list of (pid, port) tuples of all nodes in named cluster""" + if clusterName in self._clusterDict: + return self._clusterDict[clusterName].values() + return [] + + def getNumClusterBrokers(self, clusterName): + """Get total number of brokers in named cluster""" + return len(self.getClusterTupleList(clusterName)) + + def getNodeTuple(self, nodeNumber, clusterName): + """Get the (pid, port) tuple for the given cluster node""" + return self._clusterDict[clusterName][nodeNumber] + + def checkNumClusterBrokers(self, clusterName, expected = None, checkPids = True, waitForNodes = True): + """Check that the total number of brokers in the named cluster is the expected value""" + if expected != None and self.getNumClusterBrokers(clusterName) != expected: + raise Exception("Unexpected number of brokers in cluster %s: expected %d, found %d" % \ + (clusterName, expected, self.getNumClusterBrokers(clusterName))) + if checkPids: + self._checkPids(clusterName) + if waitForNodes: + self.waitForNodes(clusterName) + + def clusterExists(self, clusterName): + """ Return True if clusterName exists, False otherwise""" + return clusterName in self._clusterDict.keys() + + def clusterNodeExists(self, clusterName, nodeNumber): + """ Return True if nodeNumber in clusterName exists, False otherwise""" + if clusterName in self._clusterDict.keys(): + return nodeNumber in self._clusterDict[nodeName] + return False + + def createCheckCluster(self, clusterName, size): + """Create a cluster using the given name and size, then check the number of brokers""" + self.createCluster(clusterName, size) + self.checkNumClusterBrokers(clusterName, size) + + # --- Kill cluster nodes using signal 9 --- + + def killNode(self, nodeNumber, clusterName, updateDict = True, ignoreFailures = False): + """Kill the given node in the named cluster using kill -9""" + self.killBroker(self.getNodeTuple(nodeNumber, clusterName), ignoreFailures) + if updateDict: + del(self._clusterDict[clusterName][nodeNumber]) + + def killCluster(self, clusterName, updateDict = True, ignoreFailures = False): + """Kill all nodes in the named cluster""" + for n in self._clusterDict[clusterName].iterkeys(): + self.killNode(n, clusterName, False, ignoreFailures) + if updateDict: + del(self._clusterDict[clusterName]) + + def killClusterCheck(self, clusterName): + """Kill the named cluster and check that the name is removed from the cluster dictionary""" + self.killCluster(clusterName) + if self.clusterExists(clusterName): + raise Exception("Unable to kill cluster %s; %d nodes still exist" % \ + (clusterName, self.getNumClusterBrokers(clusterName))) + + def killAllClusters(self, ignoreFailures = False): + """Kill all known clusters""" + for n in self._clusterDict.iterkeys(): + self.killCluster(n, False, ignoreFailures) + self._clusterDict.clear() + + def killAllClustersCheck(self, ignoreFailures = False): + """Kill all known clusters and check that the cluster dictionary is empty""" + self.killAllClusters(ignoreFailures) + self.checkNumBrokers(0) + + # --- Stop cluster nodes using qpidd -q --- + + def stopNode(self, nodeNumber, clusterName, updateDict = True, ignoreFailures = False): + """Stop the given node in the named cluster using qpidd -q""" + self.stopBroker(self.getNodeTuple(nodeNumber, clusterName), ignoreFailures) + if updateDict: + del(self._clusterDict[clusterName][nodeNumber]) + + def stopAllClusters(self, ignoreFailures = False): + """Stop all known clusters""" + for n in self._clusterDict.iterkeys(): + self.stopCluster(n, False, ignoreFailures) + self._clusterDict.clear() + + + def stopCluster(self, clusterName, updateDict = True, ignoreFailures = False): + """Stop all nodes in the named cluster""" + for n in self._clusterDict[clusterName].iterkeys(): + self.stopNode(n, clusterName, False, ignoreFailures) + if updateDict: + del(self._clusterDict[clusterName]) + + def stopCheckCluster(self, clusterName, ignoreFailures = False): + """Stop the named cluster and check that the name is removed from the cluster dictionary""" + self.stopCluster(clusterName, True, ignoreFailures) + if self.clusterExists(clusterName): + raise Exception("Unable to kill cluster %s; %d nodes still exist" % (clusterName, self.getNumClusterBrokers(clusterName))) + + def stopAllCheck(self, ignoreFailures = False): + """Kill all known clusters and check that the cluster dictionary is empty""" + self.stopAllClusters() + self.checkNumBrokers(0) + + # --- qpid-config functions --- + + def _qpidConfig(self, nodeNumber, clusterName, action): + """Configure some aspect of a qpid broker using the qpid_config executable""" + port = self.getNodeTuple(nodeNumber, clusterName)[self.PORT] + #print "%s -a localhost:%d %s" % (self._qpidConfigExec, port, action) + ret = os.spawnl(os.P_WAIT, self._qpidConfigExec, self._qpidConfigExec, "-a", "localhost:%d" % port, *action.split()) + if ret != 0: + raise Exception("_qpidConfig(): cluster=\"%s\" nodeNumber=%d port=%d action=\"%s\" returned %d" % \ + (clusterName, nodeNumber, port, action, ret)) + + def addExchange(self, nodeNumber, clusterName, exchangeType, exchangeName, durable = False, sequence = False, \ + ive = False): + """Add a named exchange.""" + action = "add exchange %s %s" % (exchangeType, exchangeName) + action += self._paramBool("durable", durable, True) + action += self._paramBool("sequence", sequence, True) + action += self._paramBool("ive", ive, True) + self._qpidConfig(nodeNumber, clusterName, action) + + def deleteExchange(self, nodeNumber, clusterName, exchangeName): + """Delete a named exchange""" + self._qpidConfig(nodeNumber, clusterName, "del exchange %s" % exchangeName) + + def addQueue(self, nodeNumber, clusterName, queueName, configArgs = None): + """Add a queue using qpid-config.""" + action = "add queue %s" % queueName + if self._storeEnable: + action += " --durable" + if configArgs != None: + action += " %s" % configArgs + self._qpidConfig(nodeNumber, clusterName, action) + + def delQueue(self, nodeNumber, clusterName, queueName): + """Delete a named queue using qpid-config.""" + self._qpidConfig(nodeNumber, clusterName, "del queue %s" % queueName) + + def bind(self, nodeNumber, clusterName, exchangeName, queueName, key): + """Create an exchange-queue binding using qpid-config.""" + self._qpidConfig(nodeNumber, clusterName, "bind %s %s %s" % (exchangeName, queueName, key)) + + def unbind(self, nodeNumber, clusterName, exchangeName, queueName, key): + """Remove an exchange-queue binding using qpid-config.""" + self._qpidConfig(nodeNumber, clusterName, "unbind %s %s %s" % (exchangeName, queueName, key)) + + # --- qpid-route functions (federation) --- + + def brokerDict(self, nodeNumber, clusterName, host = "localhost", user = None, password = None): + """Returns a dictionary containing the broker info to be passed to route functions""" + port = self.getNodeTuple(nodeNumber, clusterName)[self.PORT] + return {"cluster": clusterName, "node":nodeNumber, "port":port, "host":host, "user":user, "password":password} + + def _brokerStr(self, brokerDict): + """Set up a broker string in the format [user/password@]host:port""" + str = "" + if brokerDict["user"] !=None and brokerDict["password"] != None: + str = "%s@%s" % (brokerDict["user"], brokerDict["password"]) + str += "%s:%d" % (brokerDict["host"], brokerDict["port"]) + return str + + def _qpidRoute(self, action): + """Set up a route using qpid-route""" + #print "%s %s" % (self._qpidRouteExec, action) + ret = os.spawnl(os.P_WAIT, self._qpidRouteExec, self._qpidRouteExec, *action.split()) + if ret != 0: + raise Exception("_qpidRoute(): action=\"%s\" returned %d" % (action, ret)) + + def routeDynamicAdd(self, destBrokerDict, srcBrokerDict, exchangeName): + self._qpidRoute("dynamic add %s %s %s" % (self._brokerStr(destBrokerDict), self._brokerStr(srcBrokerDict), exchangeName)) + + def routeDynamicDelete(self, destBrokerDict, srcBrokerDict, exchangeName): + self._qpidRoute("dynamic del %s %s %s" % (self._brokerStr(destBrokerDict), self._brokerStr(srcBrokerDict), exchangeName)) + + def routeAdd(self, destBrokerDict, srcBrokerDict, exchangeName, routingKey): + self._qpidRoute("route add %s %s %s %s" % (self._brokerStr(destBrokerDict), self._brokerStr(srcBrokerDict), exchangeName, routingKey)) + + def routeDelete(self, destBrokerDict, srcBrokerDict, exchangeName, routingKey): + self._qpidRoute("route del %s %s %s %s" % (self._brokerStr(destBrokerDict), self._brokerStr(srcBrokerDict), exchangeName, routingKey)) + + def routeQueueAdd(self, destBrokerDict, srcBrokerDict, exchangeName, queueName): + self._qpidRoute("queue add %s %s %s %s" % (self._brokerStr(destBrokerDict), self._brokerStr(srcBrokerDict), exchangeName, queueName)) + + def routeQueueDelete(self, destBrokerDict, srcBrokerDict, exchangeName, queueName): + self._qpidRoute("queue del %s %s %s %s" % (self._brokerStr(destBrokerDict), self._brokerStr(srcBrokerDict), exchangeName, queueName)) + + def routeLinkAdd(self, destBrokerDict, srcBrokerDict): + self._qpidRoute("link add %s %s" % (self._brokerStr(destBrokerDict), self._brokerStr(srcBrokerDict))) + + def routeLinkDelete(self, destBrokerDict, srcBrokerDict): + self._qpidRoute("link del %s %s" % (self._brokerStr(destBrokerDict), self._brokerStr(srcBrokerDict))) + + # --- Message send and receive functions --- + + def _receiver(self, action): + if self._receiverExec == None: + raise Exception("Environment variable RECEIVER is not set") + cmd = "%s %s" % (self._receiverExec, action) + #print cmd + return subprocess.Popen(cmd.split(), stdout = subprocess.PIPE) + + def _sender(self, action): + if self._senderExec == None: + raise Exception("Environment variable SENDER is not set") + cmd = "%s %s" % (self._senderExec, action) + #print cmd + return subprocess.Popen(cmd.split(), stdin = subprocess.PIPE) + + def createReciever(self, nodeNumber, clusterName, queueName, numMsgs = None, receiverArgs = None): + port = self.getNodeTuple(nodeNumber, clusterName)[self.PORT] + action = "--port %d --queue %s" % (port, queueName) + if numMsgs != None: + action += " --messages %d" % numMsgs + if receiverArgs != None: + action += " %s" % receiverArgs + return self._receiver(action) + + def createSender(self, nodeNumber, clusterName, exchangeName, routingKey, senderArgs = None): + port = self.getNodeTuple(nodeNumber, clusterName)[self.PORT] + action = "--port %d --exchange %s" % (port, exchangeName) + if routingKey != None and len(routingKey) > 0: + action += " --routing-key %s" % routingKey + if self._storeEnable: + action += " --durable yes" + if senderArgs != None: + action += " %s" % senderArgs + return self._sender(action) + + def createBindDirectExchangeQueue(self, nodeNumber, clusterName, exchangeName, queueName): + self.addExchange(nodeNumber, clusterName, "direct", exchangeName) + self.addQueue(nodeNumber, clusterName, queueName) + self.bind(nodeNumber, clusterName, exchangeName, queueName, queueName) + + def createBindTopicExchangeQueues(self, nodeNumber, clusterName, exchangeName, queueNameKeyList): + self.addExchange(nodeNumber, clusterName, "topic", exchangeName) + for queueName, key in queueNameKeyList.iteritems(): + self.addQueue(nodeNumber, clusterName, queueName) + self.bind(nodeNumber, clusterName, exchangeName, queueName, key) + + def createBindFanoutExchangeQueues(self, nodeNumber, clusterName, exchangeName, queueNameList): + self.addExchange(nodeNumber, clusterName, "fanout", exchangeName) + for queueName in queueNameList: + self.addQueue(nodeNumber, clusterName, queueName) + self.bind(nodeNumber, clusterName, exchangeName, queueName, "") + + def sendMsgs(self, nodeNumber, clusterName, exchangeName, routingKey, numMsgs, msgSize = None, wait = True): + msgs = self._makeMessageList(numMsgs, msgSize) + sender = self.createSender(nodeNumber, clusterName, exchangeName, routingKey) + sender.stdin.write(msgs) + sender.stdin.close() + if wait: + sender.wait() + return msgs + + def receiveMsgs(self, nodeNumber, clusterName, queueName, numMsgs, wait = True): + receiver = self.createReciever(nodeNumber, clusterName, queueName, numMsgs) + cnt = 0 + msgs = "" + while cnt < numMsgs: + rx = receiver.stdout.readline() + if rx == "" and receiver.poll() != None: break + msgs += rx + cnt = cnt + 1 + if wait: + receiver.wait() + return msgs + + + # --- Exchange-specific helper inner classes --- + + class TestHelper: + """ + This is a "virtual" superclass for test helpers, and is not useful on its own, but the + per-exchange subclasses are designed to keep track of the messages sent to and received + from queues which have bindings to that exchange type. + """ + + def __init__(self, testBaseCluster, clusterName, numNodes, exchangeName, queueNameList): + + """Dictionary of queues and lists of messages sent to them.""" + self._txMsgs = {} + """Dictionary of queues and lists of messages received from them.""" + self._rxMsgs = {} + """List of node numbers currently in the cluster""" + self._nodes = [] + """List of node numbers which have been killed and can therefore be recovered""" + self._deadNodes = [] + """Last node to be used""" + self._lastNode = None + + self._testBaseCluster = testBaseCluster + self._clusterName = clusterName + self._exchangeName = exchangeName + self._queueNameList = queueNameList + self._addQueues(queueNameList) + self._testBaseCluster.createCheckCluster(clusterName, numNodes) + self._nodes.extend(range(0, numNodes)) + + def _addQueues(self, queueNameList): + for qn in queueNameList: + if not qn in self._txMsgs: + self._txMsgs[qn] = [] + if not qn in self._rxMsgs: + self._rxMsgs[qn] = [] + + def _bindQueue(self, queueName, bindingKey, nodeNumber = None): + """Bind a queue to an exchange using a binding key.""" + if nodeNumber == None: + nodeNumber = self._nodes[0] # first available node + self._testBaseCluster.addQueue(nodeNumber, self._clusterName, queueName) + self._testBaseCluster.bind(nodeNumber, self._clusterName, self._exchangeName, queueName, bindingKey) + + def _highestNodeNumber(self): + """Find the highest node number used so far between the current nodes and those stopped/killed.""" + highestNode = self._nodes[-1] + if len(self._deadNodes) == 0: + return highestNode + highestDeadNode = self._deadNodes[-1] + if highestNode > highestDeadNode: + return highestNode + return highestDeadNode + + def killCluster(self): + """Kill all nodes in the cluster""" + self._testBaseCluster.killCluster(self._clusterName) + self._testBaseCluster.checkNumClusterBrokers(self._clusterName, 0) + self._deadNodes.extend(self._nodes) + self._deadNodes.sort() + del self._nodes[:] + + def restoreCluster(self, lastNode = None, restoreNodes = True): + """Restore a previously killed cluster""" + self._testBaseCluster.createCluster(self._clusterName) + if restoreNodes: + numNodes = len(self._deadNodes) + self.restoreNodes(lastNode) + self._testBaseCluster.checkNumClusterBrokers(self._clusterName, numNodes) + + def addNodes(self, numberOfNodes = 1): + """Add a fixed number of nodes to the cluster.""" + nodeStart = self._highestNodeNumber() + 1 + for i in range(0, numberOfNodes): + nodeNumber = nodeStart + i + self._testBaseCluster.createClusterNode(nodeNumber, self._clusterName) + self._nodes.append(nodeNumber) + self._testBaseCluster.checkNumClusterBrokers(self._clusterName, len(self._nodes)) + self._testBaseCluster.waitForNodes(self._clusterName) + + def restoreNode(self, nodeNumber): + """Restore a cluster node that has been previously killed""" + if nodeNumber not in self._deadNodes: + raise Exception("restoreNode(): Node number %d not in dead node list %s" % (nodeNumber, self._deadNodes)) + self._testBaseCluster.createClusterNode(nodeNumber, self._clusterName) + self._deadNodes.remove(nodeNumber) + self._nodes.append(nodeNumber) + self._nodes.sort() + + def restoreNodes(self, lastNode = None): + """Restore all known cluster nodes that have been previously killed starting with a known last-used node""" + if len(self._nodes) == 0: # restore last-used node first + if lastNode == None: + lastNode = self._lastNode + self.restoreNode(lastNode) + while len(self._deadNodes) > 0: + self.restoreNode(self._deadNodes[0]) + self._testBaseCluster.waitForNodes(self._clusterName) + + def killNode(self, nodeNumber): + """Kill a cluster node (if it is in the _nodes list).""" + if nodeNumber not in self._nodes: + raise Exception("killNode(): Node number %d not in node list %s" % (nodeNumber, self._nodes)) + self._testBaseCluster.killNode(nodeNumber, self._clusterName) + self._nodes.remove(nodeNumber) + self._deadNodes.append(nodeNumber) + self._deadNodes.sort() + + def sendMsgs(self, routingKey, numMsgs, nodeNumber = None, msgSize = None, wait = True): + """Send a fixed number of messages using the given routing key.""" + if nodeNumber == None: + nodeNumber = self._nodes[0] # Use first available node + msgs = self._testBaseCluster._makeMessageList(numMsgs, msgSize) + sender = self._testBaseCluster.createSender(nodeNumber, self._clusterName, self._exchangeName, routingKey) + sender.stdin.write(msgs) + sender.stdin.close() + if wait: + sender.wait() + self._lastNode = nodeNumber + return msgs.split() + + # TODO - this i/f is messy: one mumMsgs can be given, but a list of queues + # so assuming numMsgs for each queue + # A mechanism is needed to specify a different numMsgs per queue + def receiveMsgs(self, numMsgs, nodeNumber = None, queueNameList = None, wait = True): + """Receive a fixed number of messages from a named queue. If numMsgs == None, get all remaining messages.""" + if nodeNumber == None: + nodeNumber = self._nodes[0] # Use first available node + if queueNameList == None: + queueNameList = self._txMsgs.iterkeys() + for qn in queueNameList: + nm = numMsgs + if nm == None: + nm = len(self._txMsgs[qn]) - len(self._rxMsgs[qn]) # get all remaining messages + if nm > 0: + while nm > 0: + receiver = self._testBaseCluster.createReciever(nodeNumber, self._clusterName, qn, nm) + cnt = 0 + while cnt < nm: + rx = receiver.stdout.readline().strip() + if rx == "": + if receiver.poll() != None: break + elif rx not in self._rxMsgs[qn]: + self._rxMsgs[qn].append(rx) + cnt = cnt + 1 + nm = nm - cnt + if wait: + receiver.wait() + self._rxMsgs[qn].sort() + self._lastNode = nodeNumber + + def receiveRemainingMsgs(self, nodeNumber = None, queueNameList = None, wait = True): + """Receive all remaining messages on named queue.""" + self.receiveMsgs(None, nodeNumber, queueNameList, wait) + + def checkMsgs(self): + """Return True if all expected messages have been received (ie the transmit and receive list are identical).""" + txMsgTot = 0 + rxMsgTot = 0 + for qn, txMsgList in self._txMsgs.iteritems(): + rxMsgList = self._rxMsgs[qn] + txMsgTot = txMsgTot + len(txMsgList) + rxMsgTot = rxMsgTot + len(rxMsgList) + if len(txMsgList) != len(rxMsgList): + return False + for i, m in enumerate(txMsgList): + if m != rxMsgList[i]: + return False + if txMsgTot == 0 and rxMsgTot == 0: + print "WARNING: No messages were either sent or received" + return True + + def finalizeTest(self): + """Recover all the remaining messages on all queues, then check that all expected messages were received.""" + self.receiveRemainingMsgs() + self._testBaseCluster.stopAllCheck() + if not self.checkMsgs(): + self.printMsgs() + self._testBaseCluster.fail("Send - receive message mismatch") + + def printMsgs(self, txMsgs = True, rxMsgs = True): + """Print all messages transmitted and received.""" + for qn, txMsgList in self._txMsgs.iteritems(): + print "Queue: %s" % qn + if txMsgs: + print " txMsgList = %s" % txMsgList + if rxMsgs: + rxMsgList = self._rxMsgs[qn] + print " rxMsgList = %s" % rxMsgList + + + class DirectExchangeTestHelper(TestHelper): + + def __init__(self, testBaseCluster, clusterName, numNodes, exchangeName, queueNameList): + TestBaseCluster.TestHelper.__init__(self, testBaseCluster, clusterName, numNodes, exchangeName, queueNameList) + self._testBaseCluster.addExchange(0, clusterName, "direct", exchangeName) + for qn in queueNameList: + self._bindQueue(qn, qn) + + def addQueues(self, queueNameList): + self._addQueues(queueNameList) + for qn in queueNameList: + self._bindQueue(qn, qn) + + def sendMsgs(self, numMsgs, nodeNumber = None, queueNameList = None, msgSize = None, wait = True): + if queueNameList == None: + queueNameList = self._txMsgs.iterkeys() + for qn in queueNameList: + self._txMsgs[qn].extend(TestBaseCluster.TestHelper.sendMsgs(self, qn, numMsgs, nodeNumber, msgSize, wait)) + + + class TopicExchangeTestHelper(TestHelper): + + def __init__(self, testBaseCluster, clusterName, numNodes, exchangeName, queueNameKeyList): + self._queueNameKeyList = queueNameKeyList + TestBaseCluster.TestHelper.__init__(self, testBaseCluster, clusterName, numNodes, exchangeName, queueNameKeyList.iterkeys()) + self._testBaseCluster.addExchange(0, clusterName, "topic", exchangeName) + for qn, bk in queueNameKeyList.iteritems(): + self._bindQueue(qn, bk) + + def addQueues(self, queueNameKeyList): + self._addQueues(queueNameKeyList.iterkeys()) + for qn, bk in queueNameKeyList.iteritems(): + self._bindQueue(qn, bk) + + def _prepareRegex(self, bk): + # This regex conversion is not very complete - there are other chars that should be escaped too + return "^%s$" % bk.replace(".", r"\.").replace("*", r"[^.]*").replace("#", ".*") + + def sendMsgs(self, routingKey, numMsgs, nodeNumber = None, msgSize = None, wait = True): + msgList = TestBaseCluster.TestHelper.sendMsgs(self, routingKey, numMsgs, nodeNumber, msgSize, wait) + for qn, bk in self._queueNameKeyList.iteritems(): + if re.match(self._prepareRegex(bk), routingKey): + self._txMsgs[qn].extend(msgList) + + + class FanoutExchangeTestHelper(TestHelper): + + def __init__(self, testBaseCluster, clusterName, numNodes, exchangeName, queueNameList): + TestBaseCluster.TestHelper.__init__(self, testBaseCluster, clusterName, numNodes, exchangeName, queueNameList) + self._testBaseCluster.addExchange(0, clusterName, "fanout", exchangeName) + for qn in queueNameList: + self._bindQueue(qn, "") + + def addQueues(self, queueNameList): + self._addQueues(queueNameList) + for qn in queueNameList: + self._bindQueue(qn, "") + + def sendMsgs(self, numMsgs, nodeNumber = None, msgSize = None, wait = True): + msgList = TestBaseCluster.TestHelper.sendMsgs(self, "", numMsgs, nodeNumber, msgSize, wait) + for ml in self._txMsgs.itervalues(): + ml.extend(msgList) + diff --git a/qpid/cpp/src/tests/topic_perftest b/qpid/cpp/src/tests/topic_perftest new file mode 100755 index 0000000000..cd440b2458 --- /dev/null +++ b/qpid/cpp/src/tests/topic_perftest @@ -0,0 +1,22 @@ +#!/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. +# + +exec `dirname $0`/run_perftest 10000 --mode topic --qt 16 diff --git a/qpid/cpp/src/tests/topictest b/qpid/cpp/src/tests/topictest new file mode 100755 index 0000000000..257c24bd81 --- /dev/null +++ b/qpid/cpp/src/tests/topictest @@ -0,0 +1,61 @@ +#!/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 C++ topic test + +# Clean up old log files +rm -f subscriber_*.log + +# Defaults values +SUBSCRIBERS=10 +MESSAGES=2000 +BATCHES=10 + +while getopts "s:m:b:h:t" opt ; do + case $opt in + s) SUBSCRIBERS=$OPTARG ;; + m) MESSAGES=$OPTARG ;; + b) BATCHES=$OPTARG ;; + h) HOST=-h$OPTARG ;; + t) TRANSACTIONAL="--transactional --durable" ;; + ?) + echo "Usage: %0 [-s <subscribers>] [-m <messages.] [-b <batches>]" + exit 1 + ;; + esac +done + +subscribe() { + echo Start subscriber $1 + LOG="subscriber_$1.log" + ./qpid-topic-listener $TRANSACTIONAL > $LOG 2>&1 && rm -f $LOG +} + +publish() { + ./qpid-topic-publisher --messages $MESSAGES --batches $BATCHES --subscribers $SUBSCRIBERS $HOST $TRANSACTIONAL +} + +for ((i=$SUBSCRIBERS ; i--; )); do + subscribe $i & +done +# FIXME aconway 2007-03-27: Hack around startup race. Fix topic test. +sleep 2 +publish 2>&1 || exit 1 diff --git a/qpid/cpp/src/tests/topictest.ps1 b/qpid/cpp/src/tests/topictest.ps1 new file mode 100644 index 0000000000..59a483c2d5 --- /dev/null +++ b/qpid/cpp/src/tests/topictest.ps1 @@ -0,0 +1,73 @@ +# +# 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. +# + +# Parameters with default values: s (subscribers) m (messages) b (batches) +# h (host) t (false; use transactions) +param ( + [int]$subscribers = 10, + [int]$message_count = 2000, + [int]$batches = 10, + [string]$broker, + [switch] $t # transactional +) + +# Run the C++ topic test +[string]$me = $myInvocation.InvocationName +$srcdir = Split-Path $me +#$srcdir = Split-Path $myInvocation.InvocationName + +# Clean up old log files +Get-Item subscriber_*.log | Remove-Item + +if ($t) { + $transactional = "--transactional --durable" +} + +# Find which subdir the exes are in +. $srcdir\find_prog.ps1 .\topic_listener.exe + +function subscribe { + param ([int]$num, [string]$sub) + "Start subscriber $num" + $LOG = "subscriber_$num.log" + $cmdline = ".\$sub\topic_listener $transactional > $LOG 2>&1 + if (`$LastExitCode -ne 0) { Remove-Item $LOG }" + $cmdblock = $executioncontext.invokecommand.NewScriptBlock($cmdline) + . $srcdir\background.ps1 $cmdblock +} + +function publish { + param ([string]$sub) + Invoke-Expression ".\$sub\topic_publisher --messages $message_count --batches $batches --subscribers $subscribers $host $transactional" 2>&1 +} + +if ($broker.length) { + $broker = "-h$broker" +} + +$i = $subscribers +while ($i -gt 0) { + subscribe $i $sub + $i-- +} + +# FIXME aconway 2007-03-27: Hack around startup race. Fix topic test. +Start-Sleep 2 +publish $sub +exit $LastExitCode diff --git a/qpid/cpp/src/tests/txjob.cpp b/qpid/cpp/src/tests/txjob.cpp new file mode 100644 index 0000000000..a7a905c1b7 --- /dev/null +++ b/qpid/cpp/src/tests/txjob.cpp @@ -0,0 +1,102 @@ +/* + * + * 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 <boost/bind.hpp> +#include <boost/ptr_container/ptr_vector.hpp> + +#include "TestOptions.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/client/FailoverManager.h" +#include "qpid/client/Message.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/sys/Thread.h" + +using namespace qpid::client; +using namespace qpid::sys; + +namespace qpid { +namespace tests { + +struct Args : public qpid::TestOptions +{ + string workQueue; + string source; + string dest; + uint messages; + uint jobs; + bool quit; + bool declareQueues; + + Args() : workQueue("txshift-control"), source("txshift-1"), dest("txshift-2"), messages(0), jobs(0), + quit(false), declareQueues(false) + { + addOptions() + ("messages", qpid::optValue(messages, "N"), "Number of messages to shift") + ("jobs", qpid::optValue(jobs, "N"), "Number of shift jobs to request") + ("source", qpid::optValue(source, "QUEUE NAME"), "source queue from which messages will be shifted") + ("dest", qpid::optValue(dest, "QUEUE NAME"), "dest queue to which messages will be shifted") + ("work-queue", qpid::optValue(workQueue, "QUEUE NAME"), "work queue from which to take instructions") + ("add-quit", qpid::optValue(quit), "add a 'quit' instruction to the queue (after any other jobs)") + ("declare-queues", qpid::optValue(declareQueues), "issue a declare for all queues"); + } +}; + +}} // namespace qpid::tests + +using namespace qpid::tests; + +//TODO: might be nice to make this capable of failover as well at some +//point; for now its just for the setup phase. +int main(int argc, char** argv) +{ + Args opts; + try { + opts.parse(argc, argv); + Connection connection; + connection.open(opts.con); + Session session = connection.newSession(); + if (opts.declareQueues) { + session.queueDeclare(arg::queue=opts.workQueue); + session.queueDeclare(arg::queue=opts.source); + session.queueDeclare(arg::queue=opts.dest); + } + for (uint i = 0; i < opts.jobs; ++i) { + Message job("transfer", opts.workQueue); + job.getHeaders().setString("src", opts.source); + job.getHeaders().setString("dest", opts.dest); + job.getHeaders().setInt("count", opts.messages); + async(session).messageTransfer(arg::content=job); + } + + if (opts.quit) { + async(session).messageTransfer(arg::content=Message("quit", opts.workQueue)); + } + + session.sync(); + session.close(); + + return 0; + } catch(const std::exception& e) { + std::cout << e.what() << std::endl; + return 1; + } +} diff --git a/qpid/cpp/src/tests/txshift.cpp b/qpid/cpp/src/tests/txshift.cpp new file mode 100644 index 0000000000..882d3716d8 --- /dev/null +++ b/qpid/cpp/src/tests/txshift.cpp @@ -0,0 +1,193 @@ +/* + * + * 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 <boost/bind.hpp> +#include <boost/ptr_container/ptr_vector.hpp> + +#include "TestOptions.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/client/FailoverManager.h" +#include "qpid/client/Message.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Thread.h" + +using namespace qpid::client; +using namespace qpid::sys; + +namespace qpid { +namespace tests { + +struct Args : public qpid::TestOptions +{ + string workQueue; + size_t workers; + + Args() : workQueue("txshift-control"), workers(1) + { + addOptions() + ("workers", qpid::optValue(workers, "N"), "Number of separate worker sessions to start") + ("work-queue", qpid::optValue(workQueue, "NAME"), "work queue from which to take instructions"); + } +}; + +struct Transfer : MessageListener +{ + std::string control; + std::string source; + std::string destination; + uint expected; + uint transfered; + SubscriptionSettings controlSettings; + Subscription controlSubscription; + SubscriptionSettings sourceSettings; + Subscription sourceSubscription; + + Transfer(const std::string control_) : control(control_), expected(0), transfered(0) {} + + void subscribeToSource(SubscriptionManager manager) + { + sourceSettings.autoAck = 0;//will accept once at the end of the batch + sourceSettings.flowControl = FlowControl::messageCredit(expected); + sourceSubscription = manager.subscribe(*this, source, sourceSettings); + QPID_LOG(info, "Subscribed to source: " << source << " expecting: " << expected); + } + + void subscribeToControl(SubscriptionManager manager) + { + controlSettings.flowControl = FlowControl::messageCredit(1); + controlSubscription = manager.subscribe(*this, control, controlSettings); + QPID_LOG(info, "Subscribed to job queue"); + } + + void received(Message& message) + { + QPID_LOG(debug, "received: " << message.getData() << " for " << message.getDestination()); + if (message.getDestination() == source) { + receivedFromSource(message); + } else if (message.getDestination() == control) { + receivedFromControl(message); + } else { + QPID_LOG(error, "Unexpected message: " << message.getData() << " to " << message.getDestination()); + } + } + + void receivedFromSource(Message& message) + { + QPID_LOG(debug, "transfering " << (transfered+1) << " of " << expected); + message.getDeliveryProperties().setRoutingKey(destination); + async(sourceSubscription.getSession()).messageTransfer(arg::content=message); + if (++transfered == expected) { + QPID_LOG(info, "completed job: " << transfered << " messages shifted from " << + source << " to " << destination); + sourceSubscription.accept(sourceSubscription.getUnaccepted()); + sourceSubscription.getSession().txCommit(); + sourceSubscription.cancel(); + //grant credit to allow broker to send us another control message + controlSubscription.grantMessageCredit(1); + } + } + + void receivedFromControl(Message& message) + { + if (message.getData() == "transfer") { + source = message.getHeaders().getAsString("src"); + destination = message.getHeaders().getAsString("dest"); + expected = message.getHeaders().getAsInt("count"); + transfered = 0; + QPID_LOG(info, "received transfer request: " << expected << " messages to be shifted from " << + source << " to " << destination); + subscribeToSource(controlSubscription.getSubscriptionManager()); + } else if (message.getData() == "quit") { + QPID_LOG(info, "received quit request"); + controlSubscription.cancel(); + } else { + std::cerr << "Rejecting invalid message: " << message.getData() << std::endl; + controlSubscription.getSession().messageReject(SequenceSet(message.getId())); + } + } + +}; + +struct Worker : FailoverManager::Command, Runnable +{ + FailoverManager& connection; + Transfer transfer; + Thread runner; + + Worker(FailoverManager& c, const std::string& controlQueue) : connection(c), transfer(controlQueue) {} + + void run() + { + connection.execute(*this); + } + + void start() + { + runner = Thread(this); + } + + void join() + { + runner.join(); + } + + void execute(AsyncSession& session, bool isRetry) + { + if (isRetry) QPID_LOG(info, "Retrying..."); + session.txSelect(); + SubscriptionManager subs(session); + transfer.subscribeToControl(subs); + subs.run(); + session.txCommit();//commit accept of control messages + } +}; + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char** argv) +{ + Args opts; + try { + opts.parse(argc, argv); + FailoverManager connection(opts.con); + connection.connect(); + if (opts.workers == 1) { + Worker worker(connection, opts.workQueue); + worker.run(); + } else { + boost::ptr_vector<Worker> workers; + for (size_t i = 0; i < opts.workers; i++) { + workers.push_back(new Worker(connection, opts.workQueue)); + } + std::for_each(workers.begin(), workers.end(), boost::bind(&Worker::start, _1)); + std::for_each(workers.begin(), workers.end(), boost::bind(&Worker::join, _1)); + } + + return 0; + } catch(const std::exception& e) { + std::cout << e.what() << std::endl; + return 1; + } +} diff --git a/qpid/cpp/src/tests/unit_test.cpp b/qpid/cpp/src/tests/unit_test.cpp new file mode 100644 index 0000000000..00c61242e4 --- /dev/null +++ b/qpid/cpp/src/tests/unit_test.cpp @@ -0,0 +1,23 @@ +/* + * + * 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. + * + */ + +// Defines test_main function to link with actual unit test code. +#define BOOST_AUTO_TEST_MAIN // Boost 1.33 +#define BOOST_TEST_MAIN +#include "unit_test.h" + diff --git a/qpid/cpp/src/tests/unit_test.h b/qpid/cpp/src/tests/unit_test.h new file mode 100644 index 0000000000..ed9623bcc0 --- /dev/null +++ b/qpid/cpp/src/tests/unit_test.h @@ -0,0 +1,70 @@ +#ifndef QPIPD_TEST_UNIT_TEST_H_ +#define QPIPD_TEST_UNIT_TEST_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. + * + */ + +// Workaround so we can build against boost 1.33 and boost 1.34. +// Remove when we no longer need to support 1.33. +// +#include <boost/version.hpp> +#include <limits.h> // Must be inclued beofre boost/test headers. + +// #include the correct header file. +// +#if (BOOST_VERSION < 103400) +# include <boost/test/auto_unit_test.hpp> +#else +# include <boost/test/unit_test.hpp> +#endif // BOOST_VERSION + +// Workarounds for BOOST_AUTO_TEST_CASE|SUITE|SUITE_END +// +#if (BOOST_VERSION < 103300) + +# define QPID_AUTO_TEST_SUITE(name) +# define QPID_AUTO_TEST_CASE(name) BOOST_AUTO_UNIT_TEST(name) +# define QPID_AUTO_TEST_SUITE_END() + +#elif (BOOST_VERSION < 103400) +// Note the trailing ';' +# define QPID_AUTO_TEST_SUITE(name) BOOST_AUTO_TEST_SUITE(name); +# define QPID_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END(); + +#endif // Workarounds for BOOST_AUTO_TEST_CASE|SUITE|SUITE_END + +// +// Default definitions for latest version of boost. +// + +#ifndef QPID_AUTO_TEST_SUITE +# define QPID_AUTO_TEST_SUITE(name) BOOST_AUTO_TEST_SUITE(name) +#endif + +#ifndef QPID_AUTO_TEST_CASE +# define QPID_AUTO_TEST_CASE(name) BOOST_AUTO_TEST_CASE(name) +#endif + +#ifndef QPID_AUTO_TEST_SUITE_END +# define QPID_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() +#endif + +#endif // !QPIPD_TEST_UNIT_TEST_H_ diff --git a/qpid/cpp/src/tests/verify_cluster_objects b/qpid/cpp/src/tests/verify_cluster_objects new file mode 100755 index 0000000000..94661cf6b9 --- /dev/null +++ b/qpid/cpp/src/tests/verify_cluster_objects @@ -0,0 +1,107 @@ +#!/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. +# + +# Verify managment objects are consistent in a cluster. +# Arguments: url of one broker in the cluster. + +import qmf.console, sys, re + +class Session(qmf.console.Session): + """A qmf.console.Session that caches useful values""" + + def __init__(self): + qmf.console.Session.__init__(self) + self.classes = None + + def all_classes(self): + if self.classes is None: + self.classes = [c for p in self.getPackages() for c in self.getClasses(p)] + return self.classes + +class Broker: + def __init__(self, url, qmf): + self.url = url + self.qmf = qmf + self.broker = self.qmf.addBroker(url) + self.broker._waitForStable() + self.objects = None + self.ignore_list = [ re.compile("org.apache.qpid.broker:system:") ] + + def get_objects(self): + def ignore(name): + for m in self.ignore_list: + if m.match(name): return True + if self.objects is None: + obj_list = [] + ignored=0 + for c in self.qmf.all_classes(): + for o in self.qmf.getObjects(_key=c, _broker=self.broker): + name=o.getObjectId().getObject() + if not ignore(name): obj_list.append(name) + else: ignored += 1 + self.objects = set(obj_list) + if (len(obj_list) != len(self.objects)): + raise Exception("Duplicates in object list for %s"%(self.url)) + print "%d objects on %s, ignored %d."%(len(self.objects), self.url, ignored) + return self.objects + + def compare(self,other): + def compare1(x,y): + diff = x.get_objects() - y.get_objects() + if diff: + print "ERROR: found on %s but not %s"%(x, y) + for o in diff: print " %s"%(o) + return False + return True + + so = compare1(self, other) + os = compare1(other, self) + return so and os + + def __str__(self): return self.url + + def get_cluster(self): + """Given one Broker, return list of all brokers in its cluster""" + clusters = self.qmf.getObjects(_class="cluster") + if not clusters: raise ("%s is not a cluster member"%(self.url)) + def first_address(url): + """Python doesn't understand the brokers URL syntax. Extract a simple addres""" + return re.compile("amqp:tcp:([^,]*)").match(url).group(1) + return [Broker(first_address(url), self.qmf) + for url in clusters[0].members.split(";")] + + def __del__(self): self.qmf.delBroker(self.broker) + +def main(argv=None): + if argv is None: argv = sys.argv + qmf = Session() + brokers = Broker(argv[1], qmf).get_cluster() + print "%d members in cluster."%(len(brokers)) + base = brokers.pop(0) + try: + for b in brokers: + if not base.compare(b): return 1 + print "No differences." + return 0 + finally: + del base + del brokers + +if __name__ == "__main__": sys.exit(main()) diff --git a/qpid/cpp/src/tests/vg_check b/qpid/cpp/src/tests/vg_check new file mode 100644 index 0000000000..462f4cb5e4 --- /dev/null +++ b/qpid/cpp/src/tests/vg_check @@ -0,0 +1,43 @@ +# +# 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. +# + +# Check for valgrind errors. Sourced by test scripts. + +vg_failed() { + echo "Valgrind error log in $VG_LOG." 1>&2 + cat $VG_LOG 1>&2 + echo $1 1>&2 + exit 1 +} + +vg_check() +{ + test -z "$1" || VG_LOG=$1 + test -f $VG_LOG || vg_failed Valgrind log file $VG_LOG missing. + # Ensure there is an ERROR SUMMARY line. + grep -E '^==[0-9]+== ERROR SUMMARY:' $VG_LOG > /dev/null || \ + vg_failed "No valgrind ERROR SUMMARY line in $VG_LOG." + # Ensure that the number of errors is 0. + grep -E '^==[0-9]+== ERROR SUMMARY: [^0]' $VG_LOG > /dev/null && \ + vg_failed "Valgrind reported errors in $VG_LOG; see above." + # Check for leaks. + grep -E '^==[0-9]+== +.* lost: [^0]' $VG_LOG && \ + vg_failed "Found memory leaks (see log file, $VG_LOG); see above." + true +} diff --git a/qpid/cpp/src/tests/windows/DisableWin32ErrorWindows.cpp b/qpid/cpp/src/tests/windows/DisableWin32ErrorWindows.cpp new file mode 100644 index 0000000000..024f20b147 --- /dev/null +++ b/qpid/cpp/src/tests/windows/DisableWin32ErrorWindows.cpp @@ -0,0 +1,76 @@ +/* + * + * 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. + * + */ + +// This file intends to prevent Windows from throwing up error boxes and +// offering to debug when serious errors happen. The errors are displayed +// on stderr instead. The purpose of this is to allow the tests to proceed +// scripted and catch the text for logging. If this behavior is desired, +// 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> + +namespace { + +// Instead of popping up a window for exceptions, just print something out +LONG _stdcall UnhandledExceptionFilter (PEXCEPTION_POINTERS pExceptionInfo) +{ + DWORD dwExceptionCode = pExceptionInfo->ExceptionRecord->ExceptionCode; + + if (dwExceptionCode == EXCEPTION_ACCESS_VIOLATION) + std::cerr << "\nERROR: ACCESS VIOLATION\n" << std::endl; + else + std::cerr << "\nERROR: UNHANDLED EXCEPTION\n" << std::endl; + + return EXCEPTION_EXECUTE_HANDLER; +} + +struct redirect_errors_to_stderr { + redirect_errors_to_stderr (); +}; + +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. + SetErrorMode(SEM_FAILCRITICALERRORS); + SetErrorMode(SEM_NOOPENFILEERRORBOX); + + // And this will catch all unhandled exceptions. + SetUnhandledExceptionFilter (&UnhandledExceptionFilter); +} + +} // namespace diff --git a/qpid/cpp/src/windows/QpiddBroker.cpp b/qpid/cpp/src/windows/QpiddBroker.cpp new file mode 100644 index 0000000000..50bb45979c --- /dev/null +++ b/qpid/cpp/src/windows/QpiddBroker.cpp @@ -0,0 +1,310 @@ +/* + * + * 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. + * + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#else +// These need to be made something sensible, like reading a value from +// the registry. But for now, get things going with a local definition. +namespace { +const char *QPIDD_CONF_FILE = "qpid_broker.conf"; +const char *QPIDD_MODULE_DIR = "."; +} +#endif +#include "qpidd.h" +#include "qpid/Exception.h" +#include "qpid/Options.h" +#include "qpid/Plugin.h" +#include "qpid/sys/IntegerTypes.h" +#include "qpid/sys/windows/check.h" +#include "qpid/broker/Broker.h" + +#include <iostream> +#include <windows.h> + +using namespace qpid::broker; + +BootstrapOptions::BootstrapOptions(const char* argv0) + : qpid::Options("Options"), + common("", QPIDD_CONF_FILE), + module(QPIDD_MODULE_DIR), + log(argv0) +{ + add(common); + add(module); + add(log); +} + +// Local functions to set and get the pid via a LockFile. +namespace { + +const std::string TCP = "tcp"; + +// ShutdownEvent maintains an event that can be used to ask the broker +// to stop. Analogous to sending SIGTERM/SIGINT to the posix broker. +// The signal() method signals the event. +class ShutdownEvent { + public: + ShutdownEvent(int port); + ~ShutdownEvent(); + + void create(); + void open(); + void signal(); + + private: + std::string eventName; + + protected: + HANDLE event; +}; + +class ShutdownHandler : public ShutdownEvent, public qpid::sys::Runnable { + public: + ShutdownHandler(int port, const boost::intrusive_ptr<Broker>& b) + : ShutdownEvent(port) { broker = b; } + + private: + virtual void run(); // Inherited from Runnable + boost::intrusive_ptr<Broker> broker; +}; + +ShutdownEvent::ShutdownEvent(int port) : event(NULL) { + std::ostringstream name; + name << "qpidd_" << port << std::ends; + eventName = name.str(); +} + +void ShutdownEvent::create() { + // Auto-reset event in case multiple processes try to signal a + // broker that doesn't respond for some reason. Initially not signaled. + event = ::CreateEvent(NULL, false, false, eventName.c_str()); + QPID_WINDOWS_CHECK_NULL(event); +} + +void ShutdownEvent::open() { + // TODO: Might need to search Global\\ name if unadorned name fails + event = ::OpenEvent(EVENT_MODIFY_STATE, false, eventName.c_str()); + QPID_WINDOWS_CHECK_NULL(event); +} + +ShutdownEvent::~ShutdownEvent() { + ::CloseHandle(event); + event = NULL; +} + +void ShutdownEvent::signal() { + QPID_WINDOWS_CHECK_NOT(::SetEvent(event), 0); +} + + +void ShutdownHandler::run() { + if (event == NULL) + return; + ::WaitForSingleObject(event, INFINITE); + if (broker.get()) { + broker->shutdown(); + broker = 0; // Release the broker reference + } +} + +// Console control handler to properly handle ctl-c. +int ourPort; +BOOL CtrlHandler(DWORD ctl) +{ + ShutdownEvent shutter(ourPort); // We have to have set up the port before interrupting + shutter.open(); + shutter.signal(); + return ((ctl == CTRL_C_EVENT || ctl == CTRL_CLOSE_EVENT) ? TRUE : FALSE); +} + +template <typename T> +class NamedSharedMemory { + std::string name; + HANDLE memory; + T* data; + +public: + NamedSharedMemory(const std::string&); + ~NamedSharedMemory(); + + T& create(); + T& get(); +}; + +template <typename T> +NamedSharedMemory<T>::NamedSharedMemory(const std::string& n) : + name(n), + memory(NULL), + data(0) +{} + +template <typename T> +NamedSharedMemory<T>::~NamedSharedMemory() { + if (data) + ::UnmapViewOfFile(data); + if (memory != NULL) + ::CloseHandle(memory); +} + +template <typename T> +T& NamedSharedMemory<T>::create() { + assert(memory == NULL); + + // Create named shared memory file + memory = ::CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(T), name.c_str()); + QPID_WINDOWS_CHECK_NULL(memory); + + // Map file into memory + data = static_cast<T*>(::MapViewOfFile(memory, FILE_MAP_WRITE, 0, 0, 0)); + QPID_WINDOWS_CHECK_NULL(data); + + return *data; +} + +template <typename T> +T& NamedSharedMemory<T>::get() { + if (memory == NULL) { + // TODO: Might need to search Global\\ name if unadorned name fails + memory = ::OpenFileMapping(FILE_MAP_WRITE, FALSE, name.c_str()); + QPID_WINDOWS_CHECK_NULL(memory); + + data = static_cast<T*>(::MapViewOfFile(memory, FILE_MAP_WRITE, 0, 0, 0)); + QPID_WINDOWS_CHECK_NULL(data); + } + + return *data; +} + +std::string brokerInfoName(uint16_t port) +{ + std::ostringstream path; + path << "qpidd_info_" << port; + return path.str(); +} + +struct BrokerInfo { + DWORD pid; +}; + +} + +struct ProcessControlOptions : public qpid::Options { + bool quit; + bool check; + std::string transport; + + ProcessControlOptions() + : qpid::Options("Process control options"), + quit(false), + check(false), + transport(TCP) + { + addOptions() + ("check,c", qpid::optValue(check), "Prints the broker's process ID to stdout and returns 0 if the broker is running, otherwise returns 1") + ("transport", qpid::optValue(transport, "TRANSPORT"), "The transport for which to return the port") + ("quit,q", qpid::optValue(quit), "Tells the broker to shut down"); + } +}; + +struct QpiddWindowsOptions : public QpiddOptionsPrivate { + ProcessControlOptions control; + QpiddWindowsOptions(QpiddOptions *parent) : QpiddOptionsPrivate(parent) { + parent->add(control); + } +}; + +QpiddOptions::QpiddOptions(const char* argv0) + : qpid::Options("Options"), + common("", QPIDD_CONF_FILE), + module(QPIDD_MODULE_DIR), + log(argv0) +{ + add(common); + add(module); + add(broker); + add(log); + + platform.reset(new QpiddWindowsOptions(this)); + qpid::Plugin::addOptions(*this); +} + +void QpiddOptions::usage() const { + std::cout << "Usage: qpidd [OPTIONS]" << std::endl << std::endl + << *this << std::endl; +} + +int QpiddBroker::execute (QpiddOptions *options) { + // Options that affect a running daemon. + QpiddWindowsOptions *myOptions = + reinterpret_cast<QpiddWindowsOptions *>(options->platform.get()); + if (myOptions == 0) + throw qpid::Exception("Internal error obtaining platform options"); + + if (myOptions->control.check || myOptions->control.quit) { + // Relies on port number being set via --port or QPID_PORT env variable. + NamedSharedMemory<BrokerInfo> info(brokerInfoName(options->broker.port)); + int pid = info.get().pid; + if (pid < 0) + return 1; + if (myOptions->control.check) + std::cout << pid << std::endl; + if (myOptions->control.quit) { + ShutdownEvent shutter(options->broker.port); + shutter.open(); + shutter.signal(); + HANDLE brokerHandle = ::OpenProcess(SYNCHRONIZE, false, pid); + QPID_WINDOWS_CHECK_NULL(brokerHandle); + ::WaitForSingleObject(brokerHandle, INFINITE); + ::CloseHandle(brokerHandle); + } + return 0; + } + + boost::intrusive_ptr<Broker> brokerPtr(new Broker(options->broker)); + + // Need the correct port number to use in the pid file name. + if (options->broker.port == 0) + options->broker.port = brokerPtr->getPort(myOptions->control.transport); + + BrokerInfo info; + info.pid = ::GetCurrentProcessId(); + + NamedSharedMemory<BrokerInfo> sharedInfo(brokerInfoName(options->broker.port)); + sharedInfo.create() = info; + + // Allow the broker to receive a shutdown request via a qpidd --quit + // command. Note that when the broker is run as a service this operation + // should not be allowed. + ourPort = options->broker.port; + ShutdownHandler waitShut(ourPort, brokerPtr); + waitShut.create(); + qpid::sys::Thread waitThr(waitShut); // Wait for shutdown event + ::SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE); + brokerPtr->accept(); + std::cout << options->broker.port << std::endl; + brokerPtr->run(); + waitShut.signal(); // In case we shut down some other way + waitThr.join(); + + // CloseHandle(h); + return 0; +} diff --git a/qpid/cpp/src/windows/resources/qpid-icon.ico b/qpid/cpp/src/windows/resources/qpid-icon.ico Binary files differnew file mode 100644 index 0000000000..112f5d8f1f --- /dev/null +++ b/qpid/cpp/src/windows/resources/qpid-icon.ico diff --git a/qpid/cpp/src/windows/resources/template-resource.rc b/qpid/cpp/src/windows/resources/template-resource.rc new file mode 100644 index 0000000000..725d1c9391 --- /dev/null +++ b/qpid/cpp/src/windows/resources/template-resource.rc @@ -0,0 +1,122 @@ +//
+// 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 "version-resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "afxres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "version-resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""afxres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION ${winverFileVersionBinary}
+ PRODUCTVERSION ${winverProductVersionBinary}
+ FILEFLAGSMASK 0x17L
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x4L
+ FILETYPE 0x2L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "FileDescription", "${winverFileDescription}"
+ VALUE "FileVersion", "${winverFileVersionString}"
+ VALUE "LegalCopyright", "${winverLegalCopyright}"
+ VALUE "InternalName", "${winverInternalName}"
+ VALUE "OriginalFilename", "${winverOriginalFilename}"
+ VALUE "ProductName", "${winverProductName}"
+ VALUE "ProductVersion", "${winverProductVersionString}"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_ICON1 ICON "qpid-icon.ico"
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/qpid/cpp/src/windows/resources/version-resource.h b/qpid/cpp/src/windows/resources/version-resource.h new file mode 100644 index 0000000000..bf942abbaf --- /dev/null +++ b/qpid/cpp/src/windows/resources/version-resource.h @@ -0,0 +1,35 @@ +//
+// 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.
+//
+
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Preserved for common usage by any Qpid exe/dll.
+
+#define IDI_ICON1 101
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 104
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/qpid/cpp/src/xml.mk b/qpid/cpp/src/xml.mk new file mode 100644 index 0000000000..baf3803647 --- /dev/null +++ b/qpid/cpp/src/xml.mk @@ -0,0 +1,29 @@ +# +# 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. +# +dmoduleexec_LTLIBRARIES += xml.la + +xml_la_SOURCES = \ + qpid/xml/XmlExchange.cpp \ + qpid/xml/XmlExchange.h \ + qpid/xml/XmlExchangePlugin.cpp + +xml_la_LIBADD = -lxerces-c -lxqilla libqpidbroker.la + +xml_la_LDFLAGS = $(PLUGINLDFLAGS) + |