diff options
author | Kim van der Riet <kpvdr@apache.org> | 2012-08-03 12:13:32 +0000 |
---|---|---|
committer | Kim van der Riet <kpvdr@apache.org> | 2012-08-03 12:13:32 +0000 |
commit | d43d1912b376322e27fdcda551a73f9ff5487972 (patch) | |
tree | ce493e10baa95f44be8beb5778ce51783463196d /cpp | |
parent | 04877fec0c6346edec67072d7f2d247740cf2af5 (diff) | |
download | qpid-python-d43d1912b376322e27fdcda551a73f9ff5487972.tar.gz |
QPID-3858: Updated branch - merged from trunk r.1368650
git-svn-id: https://svn.apache.org/repos/asf/qpid/branches/asyncstore@1368910 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'cpp')
266 files changed, 9018 insertions, 3081 deletions
diff --git a/cpp/bindings/qmf/ruby/CMakeLists.txt b/cpp/bindings/qmf/ruby/CMakeLists.txt index 4b6ba2c1c3..702606139b 100644 --- a/cpp/bindings/qmf/ruby/CMakeLists.txt +++ b/cpp/bindings/qmf/ruby/CMakeLists.txt @@ -21,13 +21,12 @@ ## Use Swig to generate a literal binding to the C++ API ##------------------------------------------------------ set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/ruby.i PROPERTIES CPLUSPLUS ON) -set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/ruby.i PROPERTIES SWIG_FLAGS "-I${qpid-cpp_SOURCE_DIR}/include") + +include_directories(${RUBY_INCLUDE_DIRS} ${qpid-cpp_SOURCE_DIR}/include) swig_add_module(qmfengine_ruby ruby ${CMAKE_CURRENT_SOURCE_DIR}/ruby.i) swig_link_libraries(qmfengine_ruby qmf qmfconsole ${RUBY_LIBRARY}) -set_source_files_properties(${swig_generated_file_fullname} PROPERTIES COMPILE_FLAGS "-I${RUBY_INCLUDE_DIR} -I${qpid-cpp_SOURCE_DIR}/include") - ##---------------------------------- ## Install the complete Ruby binding ##---------------------------------- diff --git a/cpp/bindings/qmf2/ruby/CMakeLists.txt b/cpp/bindings/qmf2/ruby/CMakeLists.txt index 3bc97cf35f..1cb969f7dc 100644 --- a/cpp/bindings/qmf2/ruby/CMakeLists.txt +++ b/cpp/bindings/qmf2/ruby/CMakeLists.txt @@ -21,13 +21,12 @@ ## Use Swig to generate a literal binding to the C++ API ##------------------------------------------------------ set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/ruby.i PROPERTIES CPLUSPLUS ON) -set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/ruby.i PROPERTIES SWIG_FLAGS "-I${qpid-cpp_SOURCE_DIR}/include") + +include_directories(${RUBY_INCLUDE_DIRS} ${qpid-cpp_SOURCE_DIR}/include) swig_add_module(cqmf2_ruby ruby ${CMAKE_CURRENT_SOURCE_DIR}/ruby.i) swig_link_libraries(cqmf2_ruby qmf2 ${RUBY_LIBRARY}) -set_source_files_properties(${swig_generated_file_fullname} PROPERTIES COMPILE_FLAGS "-I${RUBY_INCLUDE_DIR} -I${qpid-cpp_SOURCE_DIR}/include") - ##---------------------------------- ## Install the complete Ruby binding ##---------------------------------- diff --git a/cpp/bindings/qpid/python/python.i b/cpp/bindings/qpid/python/python.i index a53cf3b853..4d8a64b376 100644 --- a/cpp/bindings/qpid/python/python.i +++ b/cpp/bindings/qpid/python/python.i @@ -33,41 +33,68 @@ * names as in the C++ library. They get renamed to their Python * equivalents when brought into the Python wrapping */ +%define QPID_EXCEPTION(exception, parent) %{ -static PyObject* pNoMessageAvailable; -static PyObject* pTargetCapacityExceeded; -static PyObject* pNotFound; -static PyObject* pTransportFailure; +static PyObject* exception; %} - %init %{ - pNoMessageAvailable = PyErr_NewException( - "_cqpid.NoMessageAvailable", NULL, NULL); - Py_INCREF(pNoMessageAvailable); - PyModule_AddObject(m, "NoMessageAvailable", pNoMessageAvailable); - - pTargetCapacityExceeded = PyErr_NewException( - "_cqpid.TargetCapacityExceeded", NULL, NULL); - Py_INCREF(pTargetCapacityExceeded); - PyModule_AddObject(m, "TargetCapacityExceeded", pTargetCapacityExceeded); - - pNotFound = PyErr_NewException( - "_cqpid.NotFound", NULL, NULL); - Py_INCREF(pNotFound); - PyModule_AddObject(m, "NotFound", pNotFound); - - pTransportFailure = PyErr_NewException( - "_cqpid.TransportFailure", NULL, NULL); - Py_INCREF(pTransportFailure); - PyModule_AddObject(m, "TransportFailure", pTransportFailure); + exception = PyErr_NewException( + (char *) ("_cqpid." #exception), parent, NULL); + Py_INCREF(exception); + PyModule_AddObject(m, #exception, exception); %} - %pythoncode %{ - Empty = _cqpid.NoMessageAvailable - TargetCapacityExceeded = _cqpid.TargetCapacityExceeded - NotFound = _cqpid.NotFound - ConnectError = _cqpid.TransportFailure + exception = _cqpid. ## exception %} +%enddef + + /* Python equivalents of C++ exceptions. */ + /* */ + /* Commented out lines are exceptions in the Python library, but not */ + /* in the C++ library. */ + +QPID_EXCEPTION(MessagingError, NULL) + +QPID_EXCEPTION(LinkError, MessagingError) +QPID_EXCEPTION(AddressError, LinkError) +QPID_EXCEPTION(ResolutionError, AddressError) +QPID_EXCEPTION(AssertionFailed, ResolutionError) +QPID_EXCEPTION(NotFound, ResolutionError) +QPID_EXCEPTION(InvalidOption, LinkError) +QPID_EXCEPTION(MalformedAddress, LinkError) +QPID_EXCEPTION(ReceiverError, LinkError) +QPID_EXCEPTION(FetchError, ReceiverError) +QPID_EXCEPTION(Empty, FetchError) +/* QPID_EXCEPTION(InsufficientCapacity, LinkError) */ +/* QPID_EXCEPTION(LinkClosed, LinkError) */ +QPID_EXCEPTION(SenderError, LinkError) +QPID_EXCEPTION(SendError, SenderError) +QPID_EXCEPTION(TargetCapacityExceeded, SendError) + +QPID_EXCEPTION(ConnectionError, MessagingError) +QPID_EXCEPTION(ConnectError, ConnectionError) +/* QPID_EXCEPTION(AuthenticationFailure, ConnectError) */ +/* QPID_EXCEPTION(VersionError, ConnectError) */ +/* QPID_EXCEPTION(ConnectionClosed, ConnectionError) */ +/* QPID_EXCEPTION(HeartbeartTimeout, ConnectionError) */ + +QPID_EXCEPTION(SessionError, MessagingError) +/* QPID_EXCEPTION(Detached, SessionError) */ +/* QPID_EXCEPTION(NontransactionalSession, SessionError) */ +/* QPID_EXCEPTION(ServerError, SessionError) */ +/* QPID_EXCEPTION(SessionClosed, SessionError) */ +QPID_EXCEPTION(TransactionError, SessionError) +QPID_EXCEPTION(TransactionAborted, TransactionError) +QPID_EXCEPTION(UnauthorizedAccess, SessionError) + +/* QPID_EXCEPTION(InternalError, MessagingError) */ + +%define TRANSLATE_EXCEPTION(cpp_exception, py_exception) + catch ( cpp_exception & ex) { + pExceptionType = py_exception; + error = ex.what(); + } +%enddef /* Define the general-purpose exception handling */ %exception { @@ -76,22 +103,31 @@ static PyObject* pTransportFailure; Py_BEGIN_ALLOW_THREADS; try { $action - } catch (qpid::messaging::NoMessageAvailable & ex) { - pExceptionType = pNoMessageAvailable; - error = ex.what(); - } catch (qpid::messaging::TargetCapacityExceeded & ex) { - pExceptionType = pTargetCapacityExceeded; - error = ex.what(); - } catch (qpid::messaging::NotFound & ex) { - pExceptionType = pNotFound; - error = ex.what(); - } catch (qpid::messaging::TransportFailure & ex) { - pExceptionType = pTransportFailure; - error = ex.what(); - } catch (qpid::types::Exception& ex) { - pExceptionType = PyExc_RuntimeError; - error = ex.what(); } + /* Catch and translate exceptions. */ + TRANSLATE_EXCEPTION(qpid::messaging::NoMessageAvailable, Empty) + TRANSLATE_EXCEPTION(qpid::messaging::NotFound, NotFound) + TRANSLATE_EXCEPTION(qpid::messaging::AssertionFailed, AssertionFailed) + TRANSLATE_EXCEPTION(qpid::messaging::ResolutionError, ResolutionError) + TRANSLATE_EXCEPTION(qpid::messaging::TargetCapacityExceeded, + TargetCapacityExceeded) + TRANSLATE_EXCEPTION(qpid::messaging::TransportFailure, ConnectError) + TRANSLATE_EXCEPTION(qpid::messaging::MalformedAddress, MalformedAddress) + TRANSLATE_EXCEPTION(qpid::messaging::AddressError, AddressError) + TRANSLATE_EXCEPTION(qpid::messaging::FetchError, FetchError) + TRANSLATE_EXCEPTION(qpid::messaging::ReceiverError, ReceiverError) + TRANSLATE_EXCEPTION(qpid::messaging::SendError, SendError) + TRANSLATE_EXCEPTION(qpid::messaging::SenderError, SenderError) + TRANSLATE_EXCEPTION(qpid::messaging::InvalidOptionString, InvalidOption) + TRANSLATE_EXCEPTION(qpid::messaging::LinkError, LinkError) + TRANSLATE_EXCEPTION(qpid::messaging::TransactionAborted, TransactionAborted) + TRANSLATE_EXCEPTION(qpid::messaging::TransactionError, TransactionError) + TRANSLATE_EXCEPTION(qpid::messaging::UnauthorizedAccess, UnauthorizedAccess) + TRANSLATE_EXCEPTION(qpid::messaging::SessionError, SessionError) + TRANSLATE_EXCEPTION(qpid::messaging::ConnectionError, ConnectionError) + TRANSLATE_EXCEPTION(qpid::messaging::KeyError, PyExc_KeyError) + TRANSLATE_EXCEPTION(qpid::messaging::MessagingException, MessagingError) + TRANSLATE_EXCEPTION(qpid::types::Exception, PyExc_RuntimeError) Py_END_ALLOW_THREADS; if (!error.empty()) { PyErr_SetString(pExceptionType, error.c_str()); diff --git a/cpp/bindings/qpid/ruby/CMakeLists.txt b/cpp/bindings/qpid/ruby/CMakeLists.txt index 8a8c88b595..9b32ff5728 100644 --- a/cpp/bindings/qpid/ruby/CMakeLists.txt +++ b/cpp/bindings/qpid/ruby/CMakeLists.txt @@ -30,12 +30,13 @@ set(GEM_OUTPUT_FILE ${GEM_OUTPUT_PATH}/pkg/qpid-${qpidc_version}.0.gem) ## Use Swig to generate a literal binding to the C++ API ##------------------------------------------------------ set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/ruby.i PROPERTIES CPLUSPLUS ON) -set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/ruby.i PROPERTIES SWIG_FLAGS "-I${qpid-cpp_SOURCE_DIR}/include") + +include_directories(${RUBY_INCLUDE_DIRS} ${qpid-cpp_SOURCE_DIR}/include) swig_add_module(cqpid_ruby ruby ${CMAKE_CURRENT_SOURCE_DIR}/ruby.i) swig_link_libraries(cqpid_ruby qpidmessaging qpidtypes qmf2 ${RUBY_LIBRARY}) -set_source_files_properties(${swig_generated_file_fullname} PROPERTIES COMPILE_FLAGS "-fno-strict-aliasing -I${RUBY_INCLUDE_DIR} -I${qpid-cpp_SOURCE_DIR}/include") +set_source_files_properties(${swig_generated_file_fullname} PROPERTIES COMPILE_FLAGS "-fno-strict-aliasing") ##---------------------------------- ## Install the complete Ruby binding diff --git a/cpp/bindings/qpid/ruby/LICENSE b/cpp/bindings/qpid/ruby/LICENSE index cff2a5e25d..232fd660d6 100644 --- a/cpp/bindings/qpid/ruby/LICENSE +++ b/cpp/bindings/qpid/ruby/LICENSE @@ -203,32 +203,3 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - -========================================================================= -== Boost License == -========================================================================= - -Boost Software License - Version 1.0 - August 17th, 2003 - -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: - -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. - diff --git a/cpp/bindings/qpid/ruby/README.rdoc b/cpp/bindings/qpid/ruby/README.rdoc index 478fc939d9..5c60a15588 100644 --- a/cpp/bindings/qpid/ruby/README.rdoc +++ b/cpp/bindings/qpid/ruby/README.rdoc @@ -2,7 +2,7 @@ Qpid is an cross-platform enterprise messaging system. -Version :: 0.17.0 +Version :: 0.19.0 = Links diff --git a/cpp/bindings/qpid/ruby/features/creating_a_receiver.feature b/cpp/bindings/qpid/ruby/features/creating_a_receiver.feature index f509f49115..1f758153af 100644 --- a/cpp/bindings/qpid/ruby/features/creating_a_receiver.feature +++ b/cpp/bindings/qpid/ruby/features/creating_a_receiver.feature @@ -21,9 +21,9 @@ Feature: Creating a receiver Scenario: The address string is fine Given an open session - Then creating a receiver with "my-queue;{create:always}" succeeds + Then creating a receiver with "my-queue;{create:always,delete:always}" succeeds Scenario: Using an Address object Given an open session - And an Address with the name "create-receiver-test" and subject "foo" and option "create" set to "always" + And an Address with the name "create-receiver-test" and subject "foo" and option "create" set to "always" and "delete" set to "always" Then creating a receiver with an Address succeeds diff --git a/cpp/bindings/qpid/ruby/features/creating_a_sender.feature b/cpp/bindings/qpid/ruby/features/creating_a_sender.feature index ac75543c2d..1c09ff837d 100644 --- a/cpp/bindings/qpid/ruby/features/creating_a_sender.feature +++ b/cpp/bindings/qpid/ruby/features/creating_a_sender.feature @@ -5,11 +5,11 @@ Feature: Creating a sender Scenario: The session is closed Given a closed session - Then creating a sender with "my-queue;{create:always}" raises an exception + Then creating a sender with "my-queue;{create:always,delete:always}" raises an exception Scenario: The connection is closed Given an open session with a closed connection - Then creating a sender with "my-queue;{create:always}" raises an exception + Then creating a sender with "my-queue;{create:always,delete:always}" raises an exception Scenario: The address is malformed Given an open session @@ -17,7 +17,7 @@ Feature: Creating a sender Scenario: The address string is valid Given an open session - Then creating a sender with "my-queue;{create:always}" succeeds + Then creating a sender with "my-queue;{create:always,delete:always}" succeeds Scenario: Using an Address object Given an open session diff --git a/cpp/bindings/qpid/ruby/features/receiving_a_message.feature b/cpp/bindings/qpid/ruby/features/receiving_a_message.feature index b68a78c337..7b6db4a5ac 100644 --- a/cpp/bindings/qpid/ruby/features/receiving_a_message.feature +++ b/cpp/bindings/qpid/ruby/features/receiving_a_message.feature @@ -4,24 +4,26 @@ Feature: Receving a message I need to be able to receive messages Scenario: Receiving after the session is closed - Given a sender and receiver for "my-queue;{create:always}" + Given a sender and receiver for "my-queue;{create:always,delete:always}" And the message "this is a test" is sent And the session is closed Then getting the next message raises an error Scenario: Receiving after the connection is closed - Given a sender and receiver for "my-queue;{create:always}" + Given a sender and receiver for "my-queue;{create:always,delete:always}" And the message "this is a test" is sent And the connection is closed Then getting the next message raises an error Scenario: No message is received on an empty queue - Given an existing receiver for "my-queue;{create:always}" + Given an existing receiver for "my-queue;{create:always,delete:always}" And the receiver has no pending messages Then getting the next message raises an error Scenario: A message is pending - Given a sender and receiver for "my-queue;{create:always}" + Given an open session + And given a sender for "my-queue;{create:always}" + And given a receiver for "my-queue;{create:always,delete:always}" And the receiver has a capacity of 1 And the message "this is a test" is sent Then the receiver should have 1 message available diff --git a/cpp/bindings/qpid/ruby/features/sending_a_message.feature b/cpp/bindings/qpid/ruby/features/sending_a_message.feature index b1127d3664..45cbd42f06 100644 --- a/cpp/bindings/qpid/ruby/features/sending_a_message.feature +++ b/cpp/bindings/qpid/ruby/features/sending_a_message.feature @@ -5,17 +5,17 @@ Feature: Sending a message Scenario: The session is closed Given an open session - And creating a sender with "my-queue;{create:always}" succeeds + And creating a sender with "my-queue;{create:always,delete:always}" succeeds And the session is closed Then sending the message "This is a test" should raise an error Scenario: The connection is closed Given an open session - And creating a sender with "my-queue;{create:always}" succeeds + And creating a sender with "my-queue;{create:always,delete:always}" succeeds And the connection is closed Then sending the message "This is a test" should raise an error Scenario: The message sends successfully Given an open session - And creating a sender with "my-queue;{create:always}" succeeds + And creating a sender with "my-queue;{create:always,delete:always}" succeeds Then sending the message "This is a test" succeeds
\ No newline at end of file diff --git a/cpp/bindings/qpid/ruby/features/step_definitions/address_steps.rb b/cpp/bindings/qpid/ruby/features/step_definitions/address_steps.rb index 845cc2b116..0531e5ee69 100644 --- a/cpp/bindings/qpid/ruby/features/step_definitions/address_steps.rb +++ b/cpp/bindings/qpid/ruby/features/step_definitions/address_steps.rb @@ -22,3 +22,10 @@ Given /^an Address with the name "([^"]*)" and subject "([^"]*)" and option "([^ options["#{key}"] = "#{value}" @address = Qpid::Messaging::Address.new "#{name}", "#{subject}", options end + +Given /^an Address with the name "([^"]*)" and subject "([^"]*)" and option "([^"]*)" set to "([^"]*)" and "([^"]*)" set to "([^"]*)"$/ do |name, subject, key1, value1, key2, value2| + options = Hash.new + options["#{key1}"] = "#{value1}" + options["#{key2}"] = "#{value2}" + @address = Qpid::Messaging::Address.new "#{name}", "#{subject}", options +end diff --git a/cpp/bindings/qpid/ruby/features/step_definitions/receiver_steps.rb b/cpp/bindings/qpid/ruby/features/step_definitions/receiver_steps.rb index a8c8aa4a43..e454dac345 100644 --- a/cpp/bindings/qpid/ruby/features/step_definitions/receiver_steps.rb +++ b/cpp/bindings/qpid/ruby/features/step_definitions/receiver_steps.rb @@ -59,3 +59,11 @@ Then /^the receiver should have (\d+) message available$/ do |available| sleep 1 @receiver.available.should == available.to_i end + +Given /^given a sender for "([^"]*)"$/ do |address| + @sender = @session.create_sender "#{address}" +end + +Given /^given a receiver for "([^"]*)"$/ do |address| + @receiver = @session.create_receiver "#{address}" +end diff --git a/cpp/design_docs/log-model-category-for-correlation.txt b/cpp/design_docs/log-model-category-for-correlation.txt new file mode 100644 index 0000000000..69253a417a --- /dev/null +++ b/cpp/design_docs/log-model-category-for-correlation.txt @@ -0,0 +1,112 @@ +This documennt describes the new logging entries written for +"QPID-4079 C++ Broker needs log messages to track object life cycles for auditing". + +Please see https://issues.apache.org/jira/browse/QPID-4079 for an overview. + +The basic features are simple: + +* A new log category, [Model], is added and only the new log entries use it. + +* At 'debug' log level are log entries that mirror the corresponding management + events. Debug level statements include user names, remote host information, and + other references using the user-specified names for the referenced objects. + +* At 'trace' log level are log entries that track the construction and destruction + of managed resources. Trace level statements identify the objects using the + internal management keys. The trace statement for each deleted object includes the + management statistics for that object. + +Enabling the Model log + +* Use the switch: '--log-enable trace+:Model' to receive both flavors of log +* Use the switch: '--log-enable debug+:Model' for a less verbose log + +Managed Objects in the logs + +All managed objects are included in the trace log. +The debug log has information for: + Connection, Queue, Exchange, Binding, Subscription + +The following section lists actual log file data sorted and paired with the +corresponding management Event captured with qpid-printevents. + +1. Connection + +Create connection +event: Fri Jul 13 17:46:23 2012 org.apache.qpid.broker:clientConnect rhost=[::1]:5672-[::1]:34383 user=anonymous +debug: 2012-07-13 13:46:23 [Model] debug Create connection. user:anonymous rhost:[::1]:5672-[::1]:34383 +trace: 2012-07-13 13:46:23 [Model] trace Mgmt create connection. id:[::1]:5672-[::1]:34383 + +Delete connection +event: Fri Jul 13 17:46:23 2012 org.apache.qpid.broker:clientDisconnect rhost=[::1]:5672-[::1]:34383 user=anonymous +debug: 2012-07-13 13:46:23 [Model] debug Delete connection. user:anonymous rhost:[::1]:5672-[::1]:34383 +trace: 2012-07-13 13:46:29 [Model] trace Mgmt delete connection. id:[::1]:5672-[::1]:34383 + Statistics: {bytesFromClient:1451, bytesToClient:892, closing:False, framesFromClient:25, framesToClient:21, msgsFromClient:1, msgsToClient:1} + +2. Session + +Create session +event: TBD +debug: TBD +trace: 2012-07-13 13:46:09 [Model] trace Mgmt create session. id:18f52c22-efc5-4c2f-bd09-902d2a02b948:0 + +Delete session +event: TBD +debug: TBD +trace: 2012-07-13 13:47:13 [Model] trace Mgmt delete session. id:18f52c22-efc5-4c2f-bd09-902d2a02b948:0 + Statistics: {TxnCommits:0, TxnCount:0, TxnRejects:0, TxnStarts:0, clientCredit:0, unackedMessages:0} + + +3. Exchange + +Create exchange +event: Fri Jul 13 17:46:34 2012 org.apache.qpid.broker:exchangeDeclare disp=created exName=myE exType=topic durable=False args={} autoDel=False rhost=[::1]:5672-[::1]:34384 altEx= user=anonymous +debug: 2012-07-13 13:46:34 [Model] debug Create exchange. name:myE user:anonymous rhost:[::1]:5672-[::1]:34384 type:topic alternateExchange: durable:F +trace: 2012-07-13 13:46:34 [Model] trace Mgmt create exchange. id:myE + + +Delete exchange +event: Fri Jul 13 18:19:33 2012 org.apache.qpid.broker:exchangeDelete exName=myE rhost=[::1]:5672-[::1]:37199 user=anonymous +debug: 2012-07-13 14:19:33 [Model] debug Delete exchange. name:myE user:anonymous rhost:[::1]:5672-[::1]:37199 +trace: 2012-07-13 14:19:42 [Model] trace Mgmt delete exchange. id:myE + Statistics: {bindingCount:0, bindingCountHigh:0, bindingCountLow:0, byteDrops:0, byteReceives:0, byteRoutes:0, msgDrops:0, msgReceives:0, msgRoutes:0, producerCount:0, producerCountHigh:0, producerCountLow:0} + + +4. Queue + +Create queue +event: Fri Jul 13 18:19:35 2012 org.apache.qpid.broker:queueDeclare disp=created durable=False args={} qName=myQ autoDel=False rhost=[::1]:5672-[::1]:37200 altEx= excl=False user=anonymous +debug: 2012-07-13 14:19:35 [Model] debug Create queue. name:myQ user:anonymous rhost:[::1]:5672-[::1]:37200 durable:F owner:0 autodelete:F alternateExchange: +trace: 2012-07-13 14:19:35 [Model] trace Mgmt create queue. id:myQ + +Delete queue +event: Fri Jul 13 18:19:37 2012 org.apache.qpid.broker:queueDelete user=anonymous qName=myQ rhost=[::1]:5672-[::1]:37201 +debug: 2012-07-13 14:19:37 [Model] debug Delete queue. name:myQ user:anonymous rhost:[::1]:5672-[::1]:37201 +trace: 2012-07-13 14:19:42 [Model] trace Mgmt delete queue. id:myQ + Statistics: {acquires:0, bindingCount:0, bindingCountHigh:0, bindingCountLow:0, byteDepth:0, byteFtdDepth:0, byteFtdDequeues:0, byteFtdEnqueues:0, bytePersistDequeues:0, bytePersistEnqueues:0, byteTotalDequeues:0, byteTotalEnqueues:0, byteTxnDequeues:0, byteTxnEnqueues:0, consumerCount:0, consumerCountHigh:0, consumerCountLow:0, discardsLvq:0, discardsOverflow:0, discardsPurge:0, discardsRing:0, discardsSubscriber:0, discardsTtl:0, flowStopped:False, flowStoppedCount:0, messageLatencyAvg:0, messageLatencyCount:0, messageLatencyMax:0, messageLatencyMin:0, msgDepth:0, msgFtdDepth:0, msgFtdDequeues:0, msgFtdEnqueues:0, msgPersistDequeues:0, msgPersistEnqueues:0, msgTotalDequeues:0, msgTotalEnqueues:0, msgTxnDequeues:0, msgTxnEnqueues:0, releases:0, reroutes:0, unackedMessages:0, unackedMessagesHigh:0, unackedMessagesLow:0} + +5. Binding + +Create binding +event: Fri Jul 13 17:46:45 2012 org.apache.qpid.broker:bind exName=myE args={} qName=myQ user=anonymous key=myKey rhost=[::1]:5672-[::1]:34385 +debug: 2012-07-13 13:46:45 [Model] debug Create binding. exchange:myE queue:myQ key:myKey user:anonymous rhost:[::1]:5672-[::1]:34385 +trace: 2012-07-13 13:46:23 [Model] trace Mgmt create binding. id:org.apache.qpid.broker:exchange:,org.apache.qpid.broker:queue:myQ,myQ + +Delete binding +event: Fri Jul 13 17:47:06 2012 org.apache.qpid.broker:unbind user=anonymous exName=myE qName=myQ key=myKey rhost=[::1]:5672-[::1]:34386 +debug: 2012-07-13 13:47:06 [Model] debug Delete binding. exchange:myE queue:myQ key:myKey user:anonymous rhost:[::1]:5672-[::1]:34386 +trace: 2012-07-13 13:47:09 [Model] trace Mgmt delete binding. id:org.apache.qpid.broker:exchange:myE,org.apache.qpid.broker:queue:myQ,myKey + Statistics: {msgMatched:0} + +6, Subscription + +Create subscription +event: Fri Jul 13 18:19:28 2012 org.apache.qpid.broker:subscribe dest=0 args={} qName=b78b1818-7a20-4341-a253-76216b40ab4a:0.0 user=anonymous excl=False rhost=[::1]:5672-[::1]:37198 +debug: 2012-07-13 14:19:28 [Model] debug Create subscription. queue:b78b1818-7a20-4341-a253-76216b40ab4a:0.0 destination:0 user:anonymous rhost:[::1]:5672-[::1]:37198 exclusive:F +trace: 2012-07-13 14:19:28 [Model] trace Mgmt create subscription. id:org.apache.qpid.broker:session:b78b1818-7a20-4341-a253-76216b40ab4a:0,org.apache.qpid.broker:queue:b78b1818-7a20-4341-a253-76216b40ab4a:0.0,0 + +Delete subscription +event: Fri Jul 13 18:19:28 2012 org.apache.qpid.broker:unsubscribe dest=0 rhost=[::1]:5672-[::1]:37198 user=anonymous +debug: 2012-07-13 14:19:28 [Model] debug Delete subscription. destination:0 user:anonymous rhost:[::1]:5672-[::1]:37198 +trace: 2012-07-13 14:19:32 [Model] trace Mgmt delete subscription. id:org.apache.qpid.broker:session:b78b1818-7a20-4341-a253-76216b40ab4a:0,org.apache.qpid.broker:queue:b78b1818-7a20-4341-a253-76216b40ab4a:0.0,0 + Statistics: {delivered:1} diff --git a/cpp/etc/Makefile.am b/cpp/etc/Makefile.am index b154a105d4..aa41c65b37 100644 --- a/cpp/etc/Makefile.am +++ b/cpp/etc/Makefile.am @@ -20,8 +20,8 @@ SASL_CONF = sasl2/qpidd.conf EXTRA_DIST = \ $(SASL_CONF) \ - qpidd qpidd-primary qpidd.conf qpidc.conf CMakeLists.txt \ - cluster.conf-example.xml + qpidd.in qpidd-primary.in qpidd.conf qpidc.conf CMakeLists.txt \ + cluster.conf-example.xml.in confdir = $(sysconfdir)/qpid nobase_conf_DATA=\ diff --git a/cpp/src/qmf/BrokerImportExport.h b/cpp/include/qmf/BrokerImportExport.h index ee05788063..ee05788063 100644 --- a/cpp/src/qmf/BrokerImportExport.h +++ b/cpp/include/qmf/BrokerImportExport.h diff --git a/cpp/include/qmf/posix/EventNotifier.h b/cpp/include/qmf/posix/EventNotifier.h index ebc1cb5364..c8b90b6421 100644 --- a/cpp/include/qmf/posix/EventNotifier.h +++ b/cpp/include/qmf/posix/EventNotifier.h @@ -28,7 +28,7 @@ namespace qmf { class PosixEventNotifierImpl; - class PosixEventNotifierImplAccess; + struct PosixEventNotifierImplAccess; namespace posix { diff --git a/cpp/include/qpid/Options.h b/cpp/include/qpid/Options.h index 9860076195..0bbe7b704f 100644 --- a/cpp/include/qpid/Options.h +++ b/cpp/include/qpid/Options.h @@ -81,13 +81,12 @@ po::value_semantic* optValue(T& value, const char* name) { */ template <class T> po::value_semantic* optValue(std::vector<T>& value, const char* name) { - using namespace std; - ostringstream os; - copy(value.begin(), value.end(), ostream_iterator<T>(os, " ")); - string val=os.str(); + std::ostringstream os; + std::copy(value.begin(), value.end(), std::ostream_iterator<T>(os, " ")); + std::string val=os.str(); if (!val.empty()) val.erase(val.end()-1); // Remove trailing " " - return (new OptionValue<vector<T> >(value, prettyArg(name, val))); + return (new OptionValue<std::vector<T> >(value, prettyArg(name, val))); } /** Create a boolean switch value. Presence of the option sets the value. */ diff --git a/cpp/include/qpid/RangeSet.h b/cpp/include/qpid/RangeSet.h index 36991fd784..ef0ad032da 100644 --- a/cpp/include/qpid/RangeSet.h +++ b/cpp/include/qpid/RangeSet.h @@ -224,17 +224,15 @@ bool RangeSet<T>::contains(const Range<T>& r) const { template <class T> void RangeSet<T>::addRange(const Range<T>& r) { if (r.empty()) return; - typename Ranges::iterator i = - std::lower_bound(ranges.begin(), ranges.end(), r); + typename Ranges::iterator i = std::lower_bound(ranges.begin(), ranges.end(), r); if (i == ranges.end() || !i->touching(r)) - ranges.insert(i, r); + ranges.insert(i, r); // No overlap else { i->merge(r); typename Ranges::iterator j = i; - if (++j != ranges.end() && i->touching(*j)) { + while (++j != ranges.end() && i->touching(*j)) i->merge(*j); - ranges.erase(j); - } + ranges.erase(i+1,j); } } diff --git a/cpp/include/qpid/amqp_0_10/Codecs.h b/cpp/include/qpid/amqp_0_10/Codecs.h index 73846f33a8..d632a9f20a 100644 --- a/cpp/include/qpid/amqp_0_10/Codecs.h +++ b/cpp/include/qpid/amqp_0_10/Codecs.h @@ -69,6 +69,8 @@ class QPID_COMMON_CLASS_EXTERN ListCodec */ QPID_COMMON_EXTERN void translate(const qpid::types::Variant::Map& from, qpid::framing::FieldTable& to); +QPID_COMMON_EXTERN void translate(const qpid::types::Variant::Map& from, const std::string& efield, const qpid::types::Variant& evalue, + qpid::framing::FieldTable& to); QPID_COMMON_EXTERN void translate(const qpid::framing::FieldTable& from, qpid::types::Variant::Map& to); diff --git a/cpp/include/qpid/client/SessionBase_0_10.h b/cpp/include/qpid/client/SessionBase_0_10.h index ea50ab32f7..630987c11d 100644 --- a/cpp/include/qpid/client/SessionBase_0_10.h +++ b/cpp/include/qpid/client/SessionBase_0_10.h @@ -36,14 +36,13 @@ namespace client { class Connection; class SessionImpl; -using std::string; -using framing::Content; -using framing::FieldTable; -using framing::SequenceNumber; -using framing::SequenceSet; -using framing::SequenceNumberSet; +using qpid::framing::Content; +using qpid::framing::FieldTable; +using qpid::framing::SequenceNumber; +using qpid::framing::SequenceSet; +using qpid::framing::SequenceNumberSet; using qpid::SessionId; -using framing::Xid; +using qpid::framing::Xid; /** Unit of message credit: messages or bytes */ enum CreditUnit { MESSAGE_CREDIT=0, BYTE_CREDIT=1, UNLIMITED_CREDIT=0xFFFFFFFF }; diff --git a/cpp/include/qpid/client/SubscriptionSettings.h b/cpp/include/qpid/client/SubscriptionSettings.h index b4cb302b56..bee39f6816 100644 --- a/cpp/include/qpid/client/SubscriptionSettings.h +++ b/cpp/include/qpid/client/SubscriptionSettings.h @@ -28,7 +28,19 @@ namespace qpid { namespace client { /** Bring AMQP enum definitions for message class into this namespace. */ -using namespace qpid::framing::message; +using qpid::framing::message::AcceptMode; +using qpid::framing::message::AcquireMode; +using qpid::framing::message::ACCEPT_MODE_EXPLICIT; +using qpid::framing::message::ACCEPT_MODE_NONE; +using qpid::framing::message::ACQUIRE_MODE_NOT_ACQUIRED; +using qpid::framing::message::ACQUIRE_MODE_PRE_ACQUIRED; +using qpid::framing::message::CREDIT_UNIT_BYTE; +using qpid::framing::message::CREDIT_UNIT_MESSAGE; +using qpid::framing::message::DELIVERY_MODE_NON_PERSISTENT; +using qpid::framing::message::DELIVERY_MODE_PERSISTENT; +using qpid::framing::message::FLOW_MODE_CREDIT; +using qpid::framing::message::FLOW_MODE_WINDOW; + enum CompletionMode { MANUAL_COMPLETION = 0, diff --git a/cpp/include/qpid/framing/Array.h b/cpp/include/qpid/framing/Array.h index 4f82d4dbf0..6254f6271a 100644 --- a/cpp/include/qpid/framing/Array.h +++ b/cpp/include/qpid/framing/Array.h @@ -82,7 +82,7 @@ class QPID_COMMON_CLASS_EXTERN Array // For use in standard algorithms template <typename R, typename V> static R get(const V& v) { - return v->get<R>(); + return v->template get<R>(); } private: diff --git a/cpp/include/qpid/framing/Buffer.h b/cpp/include/qpid/framing/Buffer.h index 8b08e60762..2ccad3bd57 100644 --- a/cpp/include/qpid/framing/Buffer.h +++ b/cpp/include/qpid/framing/Buffer.h @@ -1,3 +1,6 @@ +#ifndef QPID_FRAMING_BUFFER_H +#define QPID_FRAMING_BUFFER_H + /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -18,13 +21,12 @@ * under the License. * */ -#include "qpid/framing/amqp_types.h" + #include "qpid/Exception.h" #include "qpid/CommonImportExport.h" -#include <boost/iterator/iterator_facade.hpp> +#include "qpid/sys/IntegerTypes.h" -#ifndef _Buffer_ -#define _Buffer_ +#include <string> namespace qpid { namespace framing { @@ -41,42 +43,18 @@ class QPID_COMMON_CLASS_EXTERN Buffer uint32_t size; char* data; uint32_t position; - uint32_t r_position; public: void checkAvailable(uint32_t count) { if (position + count > size) throw OutOfBounds(); } - /** Buffer input/output iterator. - * Supports using an amqp_0_10::Codec with a framing::Buffer. - */ - class Iterator : public boost::iterator_facade< - Iterator, char, boost::random_access_traversal_tag> - { - public: - Iterator(Buffer& b) : buffer(&b) {} - - private: - friend class boost::iterator_core_access; - char& dereference() const { return buffer->data[buffer->position]; } - void increment() { ++buffer->position; } - bool equal(const Iterator& x) const { return buffer == x.buffer; } - - Buffer* buffer; - }; - - friend class Iterator; - QPID_COMMON_EXTERN Buffer(char* data=0, uint32_t size=0); - QPID_COMMON_EXTERN void record(); - QPID_COMMON_EXTERN void restore(bool reRecord = false); QPID_COMMON_EXTERN void reset(); QPID_COMMON_INLINE_EXTERN uint32_t available() { return size - position; } QPID_COMMON_INLINE_EXTERN uint32_t getSize() { return size; } QPID_COMMON_INLINE_EXTERN uint32_t getPosition() { return position; } QPID_COMMON_INLINE_EXTERN void setPosition(uint32_t p) { position = p; } - QPID_COMMON_INLINE_EXTERN Iterator getIterator() { return Iterator(*this); } QPID_COMMON_INLINE_EXTERN char* getPointer() { return data; } QPID_COMMON_EXTERN void putOctet(uint8_t i); @@ -108,16 +86,16 @@ class QPID_COMMON_CLASS_EXTERN Buffer template <int n> QPID_COMMON_EXTERN void putUInt(uint64_t); - QPID_COMMON_EXTERN void putShortString(const string& s); - QPID_COMMON_EXTERN void putMediumString(const string& s); - QPID_COMMON_EXTERN void putLongString(const string& s); - QPID_COMMON_EXTERN void getShortString(string& s); - QPID_COMMON_EXTERN void getMediumString(string& s); - QPID_COMMON_EXTERN void getLongString(string& s); + QPID_COMMON_EXTERN void putShortString(const std::string& s); + QPID_COMMON_EXTERN void putMediumString(const std::string& s); + QPID_COMMON_EXTERN void putLongString(const std::string& s); + QPID_COMMON_EXTERN void getShortString(std::string& s); + QPID_COMMON_EXTERN void getMediumString(std::string& s); + QPID_COMMON_EXTERN void getLongString(std::string& s); QPID_COMMON_EXTERN void getBin128(uint8_t* b); - QPID_COMMON_EXTERN void putRawData(const string& s); - QPID_COMMON_EXTERN void getRawData(string& s, uint32_t size); + QPID_COMMON_EXTERN void putRawData(const std::string& s); + QPID_COMMON_EXTERN void getRawData(std::string& s, uint32_t size); QPID_COMMON_EXTERN void putRawData(const uint8_t* data, size_t size); QPID_COMMON_EXTERN void getRawData(uint8_t* data, size_t size); diff --git a/cpp/include/qpid/framing/ProtocolVersion.h b/cpp/include/qpid/framing/ProtocolVersion.h index 30094c165d..26d628e41c 100644 --- a/cpp/include/qpid/framing/ProtocolVersion.h +++ b/cpp/include/qpid/framing/ProtocolVersion.h @@ -24,6 +24,8 @@ #include "qpid/framing/amqp_types.h" #include "qpid/CommonImportExport.h" +#include <string> + namespace qpid { namespace framing diff --git a/cpp/include/qpid/framing/SequenceNumber.h b/cpp/include/qpid/framing/SequenceNumber.h index dd85d97a52..00fa2469c8 100644 --- a/cpp/include/qpid/framing/SequenceNumber.h +++ b/cpp/include/qpid/framing/SequenceNumber.h @@ -57,12 +57,18 @@ boost::equality_comparable< QPID_COMMON_EXTERN uint32_t encodedSize() const; template <class S> void serialize(S& s) { s(value); } - - friend inline int32_t operator-(const SequenceNumber& a, const SequenceNumber& b); }; inline int32_t operator-(const SequenceNumber& a, const SequenceNumber& b) { - return int32_t(a.value - b.value); + return int32_t(a.getValue() - b.getValue()); +} + +inline SequenceNumber operator+(const SequenceNumber& a, int32_t n) { + return SequenceNumber(a.getValue() + n); +} + +inline SequenceNumber operator-(const SequenceNumber& a, int32_t n) { + return SequenceNumber(a.getValue() - n); } struct Window diff --git a/cpp/include/qpid/framing/StructHelper.h b/cpp/include/qpid/framing/StructHelper.h index 21f9b91fa9..fe2fa64ce7 100644 --- a/cpp/include/qpid/framing/StructHelper.h +++ b/cpp/include/qpid/framing/StructHelper.h @@ -34,7 +34,7 @@ class QPID_COMMON_CLASS_EXTERN StructHelper { public: - template <class T> void encode(const T t, std::string& data) { + template <class T> void encode(const T& t, std::string& data) { uint32_t size = t.bodySize() + 2/*type*/; data.resize(size); Buffer wbuffer(const_cast<char*>(data.data()), size); diff --git a/cpp/include/qpid/framing/Uuid.h b/cpp/include/qpid/framing/Uuid.h index ccfd7e9534..e9e56ed7c9 100644 --- a/cpp/include/qpid/framing/Uuid.h +++ b/cpp/include/qpid/framing/Uuid.h @@ -48,6 +48,9 @@ struct Uuid : public boost::array<uint8_t, 16> { /** Copy from 16 bytes of data. */ QPID_COMMON_EXTERN Uuid(const uint8_t* data); + /** Parse format 1b4e28ba-2fa1-11d2-883f-b9a761bde3fb. */ + QPID_COMMON_EXTERN Uuid(const std::string&); + // Default op= and copy ctor are fine. // boost::array gives us ==, < etc. diff --git a/cpp/include/qpid/framing/amqp_types.h b/cpp/include/qpid/framing/amqp_types.h index d9088b7a12..2072a83904 100644 --- a/cpp/include/qpid/framing/amqp_types.h +++ b/cpp/include/qpid/framing/amqp_types.h @@ -27,12 +27,10 @@ */ #include "qpid/sys/IntegerTypes.h" -#include <string> namespace qpid { namespace framing { -using std::string; typedef uint8_t FrameType; typedef uint16_t ChannelId; typedef uint32_t BatchOffset; diff --git a/cpp/include/qpid/log/Logger.h b/cpp/include/qpid/log/Logger.h index d255b7e150..9464fa52dd 100644 --- a/cpp/include/qpid/log/Logger.h +++ b/cpp/include/qpid/log/Logger.h @@ -36,7 +36,7 @@ namespace log { class QPID_COMMON_CLASS_EXTERN Logger : private boost::noncopyable { public: /** Flags indicating what to include in the log output */ - enum FormatFlag { FILE=1, LINE=2, FUNCTION=4, LEVEL=8, TIME=16, THREAD=32, HIRES=64}; + enum FormatFlag { FILE=1, LINE=2, FUNCTION=4, LEVEL=8, TIME=16, THREAD=32, HIRES=64, CATEGORY=128}; /** * Logging output sink. diff --git a/cpp/include/qpid/log/Options.h b/cpp/include/qpid/log/Options.h index 17cbfde9bc..819f2c85f1 100644 --- a/cpp/include/qpid/log/Options.h +++ b/cpp/include/qpid/log/Options.h @@ -39,7 +39,7 @@ struct Options : public qpid::Options { std::string argv0; std::string name; std::vector<std::string> selectors; - bool time, level, thread, source, function, hiresTs; + bool time, level, thread, source, function, hiresTs, category; bool trace; std::string prefix; std::auto_ptr<SinkOptions> sinkOptions; diff --git a/cpp/include/qpid/log/Selector.h b/cpp/include/qpid/log/Selector.h index 061152d7e2..498d4a7342 100644 --- a/cpp/include/qpid/log/Selector.h +++ b/cpp/include/qpid/log/Selector.h @@ -35,17 +35,24 @@ struct Options; class Selector { public: /** Empty selector selects nothing */ - Selector() {} + Selector() { + reset(); + } /** Set selector from Options */ QPID_COMMON_EXTERN Selector(const Options&); /** Equavlient to: Selector s; s.enable(l, s) */ Selector(Level l, const std::string& s=std::string()) { + reset(); enable(l,s); } - Selector(const std::string& enableStr) { enable(enableStr); } + Selector(const std::string& enableStr) { + reset(); + enable(enableStr); + } + /** * Enable messages with level in levels where the file * name contains substring. Empty string matches all. @@ -54,14 +61,30 @@ class Selector { substrings[level].push_back(substring); } + /** + * Enable messages at this level for this category + */ + void enable(Level level, Category category) { + catFlags[level][category] = true; + } + /** Enable based on a 'level[+]:file' string */ QPID_COMMON_EXTERN void enable(const std::string& enableStr); /** True if level is enabled for file. */ QPID_COMMON_EXTERN bool isEnabled(Level level, const char* function); + QPID_COMMON_EXTERN bool isEnabled(Level level, const char* function, Category category); + + /** Reset the category enable flags */ + QPID_COMMON_EXTERN void reset() { + for (int lt = 0; lt < LevelTraits::COUNT; ++lt) + for (int ct = 0; ct < CategoryTraits::COUNT; ++ct) + catFlags[lt][ct] = false; + } private: std::vector<std::string> substrings[LevelTraits::COUNT]; + bool catFlags[LevelTraits::COUNT][CategoryTraits::COUNT]; }; diff --git a/cpp/include/qpid/log/Statement.h b/cpp/include/qpid/log/Statement.h index 7b3ab60b81..ad84d66db6 100644 --- a/cpp/include/qpid/log/Statement.h +++ b/cpp/include/qpid/log/Statement.h @@ -22,6 +22,7 @@ #include "qpid/Msg.h" #include "qpid/CommonImportExport.h" #include <boost/current_function.hpp> +#include <list> namespace qpid { namespace log { @@ -55,15 +56,119 @@ struct LevelTraits { static const char* name(Level); }; -/** POD struct representing a logging statement in source code. */ +/** Formal message categories + * https://issues.apache.org/jira/browse/QPID-3902 + * + * Category Source code directory + * -------- --------------------- + * Security acl ssl gssapi sasl cyrus + * Broker broker + * Management agent console qmf + * Protocol amqp_0_10 framing + * System log sys types xml thread mutex fork pipe time ... + * HA cluster ha replication + * Messaging messaging + * Client client + * Store store + * Network tcp rdma AsynchIO socket epoll + * Test + * Model <not related to a directory> + * Unspecified <must be last in enum> + */ +enum Category { security, broker, management, protocol, system, ha, messaging, + store, network, test, client, model, unspecified }; +struct CategoryTraits { + static const int COUNT=unspecified+1; + + /** Test if given name is a Category name + */ + static bool isCategory(const std::string& name); + + /** Get category from string name + * @exception if name invalid. + */ + static Category category(const char* name); + + /** Get category from string name. + * @exception if name invalid. + */ + static Category category(const std::string& name) { + return category(name.c_str()); + } + + /** String name of category */ + static const char* name(Category); +}; + + +class CategoryFileNameHints { +public: + CategoryFileNameHints(){ + hintList.push_back(std::make_pair("AsynchIo", network)); + hintList.push_back(std::make_pair("TCP", network)); + hintList.push_back(std::make_pair("epoll", network)); + hintList.push_back(std::make_pair("Pollable", network)); + hintList.push_back(std::make_pair("Socket", network)); + + hintList.push_back(std::make_pair("Sasl", security)); + hintList.push_back(std::make_pair("Ssl", security)); + hintList.push_back(std::make_pair("Acl", security)); + hintList.push_back(std::make_pair("acl", security)); + hintList.push_back(std::make_pair("cyrus", security)); + + hintList.push_back(std::make_pair("amqp_", protocol)); + hintList.push_back(std::make_pair("framing", protocol)); + + hintList.push_back(std::make_pair("management", management)); + hintList.push_back(std::make_pair("qmf", management)); + hintList.push_back(std::make_pair("console", management)); + hintList.push_back(std::make_pair("Management", management)); + + hintList.push_back(std::make_pair("cluster", ha)); + hintList.push_back(std::make_pair("qpid/ha", ha)); + hintList.push_back(std::make_pair("qpid\\ha", ha)); + hintList.push_back(std::make_pair("replication", ha)); + hintList.push_back(std::make_pair("ClusterSafe", ha)); + + hintList.push_back(std::make_pair("broker", broker)); + hintList.push_back(std::make_pair("SessionState",broker)); + hintList.push_back(std::make_pair("DataDir", broker)); + hintList.push_back(std::make_pair("qpidd", broker)); + hintList.push_back(std::make_pair("xml", broker)); + hintList.push_back(std::make_pair("QpidBroker", broker)); + + hintList.push_back(std::make_pair("store", store)); + + hintList.push_back(std::make_pair("assert", system)); + hintList.push_back(std::make_pair("Exception", system)); + hintList.push_back(std::make_pair("sys", system)); + hintList.push_back(std::make_pair("SCM", system)); + + hintList.push_back(std::make_pair("tests", test)); + + hintList.push_back(std::make_pair("messaging", messaging)); + hintList.push_back(std::make_pair("types", messaging)); + + hintList.push_back(std::make_pair("client", client)); + } + + static Category categoryOf(const char*const fName); + +private: + std::list<std::pair<const char* const, Category> > hintList; +}; + + /** POD struct representing a logging statement in source code. */ struct Statement { bool enabled; const char* file; int line; const char* function; Level level; + Category category; QPID_COMMON_EXTERN void log(const std::string& message); + QPID_COMMON_EXTERN static void categorize(Statement& s); struct Initializer { QPID_COMMON_EXTERN Initializer(Statement& s); @@ -72,8 +177,14 @@ struct Statement { }; ///@internal static initializer for a Statement. -#define QPID_LOG_STATEMENT_INIT(level) \ - { 0, __FILE__, __LINE__, BOOST_CURRENT_FUNCTION, (::qpid::log::level) } +#define QPID_LOG_STATEMENT_INIT_CAT(LEVEL, CATEGORY) \ +{ 0, __FILE__, __LINE__, BOOST_CURRENT_FUNCTION, (::qpid::log::LEVEL), \ +(::qpid::log::CATEGORY) } + + +///@internal static initializer for a Statement with unspecified category +#define QPID_LOG_STATEMENT_INIT(LEVEL) \ +QPID_LOG_STATEMENT_INIT_CAT ( LEVEL , unspecified ) /** * Like QPID_LOG but computes an additional boolean test expression @@ -96,13 +207,26 @@ struct Statement { } while(0) /** + * Line QPID_LOG_IF but with the additional specification of a category. + * @param CATEGORY message category. + */ +#define QPID_LOG_IF_CAT(LEVEL, CATEGORY, TEST, MESSAGE) \ + do { \ + using ::qpid::log::Statement; \ + static Statement stmt_= QPID_LOG_STATEMENT_INIT_CAT(LEVEL, CATEGORY); \ + static Statement::Initializer init_(stmt_); \ + if (stmt_.enabled && (TEST)) \ + stmt_.log(::qpid::Msg() << MESSAGE); \ + } while(0) + +/** * FLAG must be a boolean variable. Assigns FLAG to true iff logging * is enabled for LEVEL in the calling context. Use when extra * support code is needed to generate log messages, to ensure that it * is only run if the logging level is enabled. * e.g. * bool logWarning; - * QPID_LOG_TEST(LEVEL, logWarning); + * QPID_LOG_TEST(warning, logWarning); * if (logWarning) { do stuff needed for warning log messages } */ #define QPID_LOG_TEST(LEVEL, FLAG) \ @@ -113,12 +237,31 @@ struct Statement { FLAG = stmt_.enabled; \ } while(0) + /** + * FLAG must be a boolean variable. Assigns FLAG to true iff logging + * is enabled for LEVEL in the calling context. Use when extra + * support code is needed to generate log messages, to ensure that it + * is only run if the logging level is enabled. + * e.g. + * bool logWarning; + * QPID_LOG_TEST_CAT(warning, System, logWarning); + * if (logWarning) { do stuff needed for warning log messages } + */ + #define QPID_LOG_TEST_CAT(LEVEL, CATEGORY, FLAG) \ + do { \ + using ::qpid::log::Statement; \ + static Statement stmt_= QPID_LOG_STATEMENT_INIT_CAT(LEVEL, CATEGORY); \ + static Statement::Initializer init_(stmt_); \ + FLAG = stmt_.enabled; \ + } while(0) + /** * Macro for log statements. Example of use: * @code * QPID_LOG(debug, "There are " << foocount << " foos in the bar."); * QPID_LOG(error, boost::format("Dohickey %s exploded") % dohicky.name()); * @endcode + * Using QPID_LOG implies a category of Unspecified. * * You can subscribe to log messages by level, by component, by filename * or a combination @see Configuration. @@ -130,6 +273,25 @@ struct Statement { */ #define QPID_LOG(LEVEL, MESSAGE) QPID_LOG_IF(LEVEL, true, MESSAGE); +/** + * Macro for log statements. Example of use: + * @code + * QPID_LOG_CAT(debug, System, "There are " << foocount << " foos in the bar."); + * QPID_LOG_CAT(error, System, boost::format("Dohickey %s exploded") % dohicky.name()); + * @endcode + * Using QPID_LOG_CAT requires the specification of a category. + * + * You can subscribe to log messages by level, by component, by filename + * or a combination @see Configuration. + * + *@param LEVEL severity Level for message, should be one of: + * debug, info, notice, warning, error, critical. NB no qpid::log:: prefix. + *@param CATEGORY basic Category for the message. + *@param MESSAGE any object with an @eostream operator<<, or a sequence + * like of ostreamable objects separated by @e<<. + */ +#define QPID_LOG_CAT(LEVEL, CATEGORY, MESSAGE) QPID_LOG_IF_CAT(LEVEL, CATEGORY, true, MESSAGE); + }} // namespace qpid::log diff --git a/cpp/include/qpid/management/Buffer.h b/cpp/include/qpid/management/Buffer.h index c32494b8c0..1ac52bf276 100644 --- a/cpp/include/qpid/management/Buffer.h +++ b/cpp/include/qpid/management/Buffer.h @@ -46,13 +46,12 @@ public: QPID_COMMON_EXTERN Buffer(char* data=0, uint32_t size=0); QPID_COMMON_EXTERN ~Buffer(); - QPID_COMMON_EXTERN void record(); - QPID_COMMON_EXTERN void restore(bool reRecord = false); QPID_COMMON_EXTERN void reset(); QPID_COMMON_EXTERN uint32_t available(); QPID_COMMON_EXTERN uint32_t getSize(); QPID_COMMON_EXTERN uint32_t getPosition(); + QPID_COMMON_EXTERN void setPosition(uint32_t); QPID_COMMON_EXTERN char* getPointer(); QPID_COMMON_EXTERN void putOctet(uint8_t i); diff --git a/cpp/include/qpid/sys/SystemInfo.h b/cpp/include/qpid/sys/SystemInfo.h index 23594cf650..24bc099d75 100644 --- a/cpp/include/qpid/sys/SystemInfo.h +++ b/cpp/include/qpid/sys/SystemInfo.h @@ -34,51 +34,61 @@ namespace sys { * Results may be dependent on OS/hardware. */ namespace SystemInfo { - /** - * Estimate available concurrency, e.g. number of CPU cores. - * -1 means estimate not available on this platform. - */ - QPID_COMMON_EXTERN long concurrency(); +/** + * Estimate available concurrency, e.g. number of CPU cores. + * -1 means estimate not available on this platform. + */ +QPID_COMMON_EXTERN long concurrency(); - /** - * Get the local host name and set it in the specified. - * Returns false if it can't be obtained and sets errno to any error value. - */ - QPID_COMMON_EXTERN bool getLocalHostname (Address &address); +/** + * Get the local host name and set it in the specified. + * Returns false if it can't be obtained and sets errno to any error value. + */ +QPID_COMMON_EXTERN bool getLocalHostname (Address &address); - QPID_COMMON_EXTERN void getLocalIpAddresses (uint16_t port, std::vector<Address> &addrList); +/** + * Get the (possibly multiple) local IP addresses of this host + * using the specified port. + */ +QPID_COMMON_EXTERN void getLocalIpAddresses (uint16_t port, std::vector<Address> &addrList); + +/** + * Return true if host names an address of the local host. + *@param host host name or IP address. + */ +QPID_COMMON_EXTERN bool isLocalHost(const std::string& host); - /** - * Retrieve system identifiers and versions. This is information that can - * generally be retrieved via POSIX uname(). - * - * @param osName Receives the OS name; e.g., GNU/Linux or Windows - * @param nodeName Receives the nodename. This may or may not match the - * set hostname from getLocalHostname(). - * @param release Receives the OS release identifier. - * @param version Receives the OS release version (kernel, build, sp, etc.) - * @param machine Receives the hardware type. - */ - QPID_COMMON_EXTERN void getSystemId (std::string &osName, - std::string &nodeName, - std::string &release, - std::string &version, - std::string &machine); +/** + * Retrieve system identifiers and versions. This is information that can + * generally be retrieved via POSIX uname(). + * + * @param osName Receives the OS name; e.g., GNU/Linux or Windows + * @param nodeName Receives the nodename. This may or may not match the + * set hostname from getLocalHostname(). + * @param release Receives the OS release identifier. + * @param version Receives the OS release version (kernel, build, sp, etc.) + * @param machine Receives the hardware type. + */ +QPID_COMMON_EXTERN void getSystemId (std::string &osName, + std::string &nodeName, + std::string &release, + std::string &version, + std::string &machine); - /** - * Get the process ID of the current process. - */ - QPID_COMMON_EXTERN uint32_t getProcessId(); +/** + * Get the process ID of the current process. + */ +QPID_COMMON_EXTERN uint32_t getProcessId(); - /** - * Get the process ID of the parent of the current process. - */ - QPID_COMMON_EXTERN uint32_t getParentProcessId(); +/** + * Get the process ID of the parent of the current process. + */ +QPID_COMMON_EXTERN uint32_t getParentProcessId(); - /** - * Get the name of the current process (i.e. the name of the executable) - */ - QPID_COMMON_EXTERN std::string getProcessName(); +/** + * Get the name of the current process (i.e. the name of the executable) + */ +QPID_COMMON_EXTERN std::string getProcessName(); }}} // namespace qpid::sys::SystemInfo diff --git a/cpp/managementgen/qmf-gen b/cpp/managementgen/qmf-gen index 6e8f864256..2f0cc0d8fd 100755 --- a/cpp/managementgen/qmf-gen +++ b/cpp/managementgen/qmf-gen @@ -49,6 +49,8 @@ parser.add_option("-b", "--broker-plugin", dest="brokerplugin", default=False, a help="Generate code for use in a qpid broker plugin") parser.add_option("-2", "--v2-style", dest="v2_style", default=False, action="store_true", help="Generate code for use with the QMFv2 Agent API") +parser.add_option("-l", "--qpid-logs", dest="qpidlogs", default=False, action="store_true", + help="Generate code for QPID_LOG statements in classes constructor/destructor") (opts, args) = parser.parse_args() @@ -70,6 +72,11 @@ else: vargs["agentHeaderDir"] = "agent" vargs["genQmfV1"] = None +if opts.qpidlogs: + vargs["genLogs"] = True +else: + vargs["genLogs"] = False + for schemafile in args: package = SchemaPackage(typefile, schemafile, opts) diff --git a/cpp/managementgen/qmfgen/generate.py b/cpp/managementgen/qmfgen/generate.py index 4e688e3bc7..61111be01d 100755 --- a/cpp/managementgen/qmfgen/generate.py +++ b/cpp/managementgen/qmfgen/generate.py @@ -304,6 +304,9 @@ class Generator: def testGenQMFv1 (self, variables): return variables["genQmfV1"] + def testGenLogs (self, variables): + return variables["genLogs"] + def genDisclaimer (self, stream, variables): prefix = variables["commentPrefix"] stream.write (prefix + " This source file was created by a code generator.\n") diff --git a/cpp/managementgen/qmfgen/templates/Class.cpp b/cpp/managementgen/qmfgen/templates/Class.cpp index fc0b9c8177..d3033db7e1 100644 --- a/cpp/managementgen/qmfgen/templates/Class.cpp +++ b/cpp/managementgen/qmfgen/templates/Class.cpp @@ -7,9 +7,9 @@ // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -27,8 +27,12 @@ #include "qpid//*MGEN:Class.AgentHeaderLocation*//ManagementAgent.h" #include "/*MGEN:Class.NameCap*/.h" /*MGEN:Class.MethodArgIncludes*/ +/*MGEN:IF(Root.GenLogs)*/ +#include "qpid/log/Statement.h" +/*MGEN:ENDIF*/ #include <iostream> #include <sstream> +#include <string.h> using namespace qmf::/*MGEN:Class.Namespace*/; using qpid::management::ManagementAgent; @@ -58,10 +62,26 @@ uint8_t /*MGEN:Class.NameCap*/::md5Sum[MD5_LEN] = for (int idx = 0; idx < maxThreads; idx++) perThreadStatsArray[idx] = 0; /*MGEN:ENDIF*/ +/*MGEN:IF(Root.GenLogs)*/ + QPID_LOG_CAT(trace, model, "Mgmt create " << className + << ". id:" << getKey()); +/*MGEN:ENDIF*/ } /*MGEN:Class.NameCap*/::~/*MGEN:Class.NameCap*/ () { +/*MGEN:IF(Root.GenLogs)*/ + bool logEnabled; + QPID_LOG_TEST_CAT(trace, model, logEnabled); + if (logEnabled) + { + ::qpid::types::Variant::Map map; + mapEncodeValues(map, false, true); + QPID_LOG_CAT(trace, model, "Mgmt delete " << className + << ". id:" << getKey() + << " Statistics: " << map); + } +/*MGEN:ENDIF*/ /*MGEN:IF(Class.ExistPerThreadStats)*/ for (int idx = 0; idx < maxThreads; idx++) if (perThreadStatsArray[idx] != 0) @@ -274,7 +294,6 @@ std::string /*MGEN:Class.NameCap*/::getKey() const } - void /*MGEN:Class.NameCap*/::mapEncodeValues (::qpid::types::Variant::Map& _map, bool includeProperties, bool includeStatistics) diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt index 1f3c2a739d..526f191f84 100644 --- a/cpp/src/CMakeLists.txt +++ b/cpp/src/CMakeLists.txt @@ -220,7 +220,7 @@ execute_process(COMMAND ${RUBY_EXECUTABLE} -I ${rgen_dir} ${rgen_dir}/generate $ 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} +execute_process(COMMAND ${PYTHON_EXECUTABLE} ${mgen_dir}/qmf-gen -c managementgen.cmake -b -l -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") @@ -281,7 +281,7 @@ endif (CMAKE_COMPILER_IS_GNUCXX) if (CMAKE_CXX_COMPILER_ID STREQUAL SunPro) set (COMPILER_FLAGS "-library=stlport4 -mt") - set (WARNING_FLAGS "+w2") + set (WARNING_FLAGS "+w") endif (CMAKE_CXX_COMPILER_ID STREQUAL SunPro) option(ENABLE_WARNINGS "Enable lots of compiler warnings (recommended)" ON) @@ -626,27 +626,42 @@ set (ha_default ON) option(BUILD_HA "Build Active-Passive HA plugin" ${ha_default}) if (BUILD_HA) set (ha_SOURCES + qpid/ha/AlternateExchangeSetter.h + qpid/ha/BackupConnectionExcluder.h + qpid/ha/BrokerInfo.cpp + qpid/ha/BrokerInfo.h + qpid/ha/QueueGuard.cpp + qpid/ha/QueueGuard.h + qpid/ha/ReplicationTest.cpp + qpid/ha/ReplicationTest.h qpid/ha/Backup.cpp qpid/ha/Backup.h + qpid/ha/BrokerReplicator.cpp + qpid/ha/BrokerReplicator.h + qpid/ha/ConnectionObserver.cpp + qpid/ha/ConnectionObserver.h qpid/ha/HaBroker.cpp qpid/ha/HaBroker.h qpid/ha/HaPlugin.cpp - qpid/ha/Settings.h - qpid/ha/QueueReplicator.h + qpid/ha/Membership.cpp + qpid/ha/Membership.h + qpid/ha/Primary.cpp + qpid/ha/Primary.h + qpid/ha/QueueRange.h qpid/ha/QueueReplicator.cpp - qpid/ha/ReplicateLevel.h - qpid/ha/ReplicateLevel.cpp - qpid/ha/ReplicatingSubscription.h + qpid/ha/QueueReplicator.h qpid/ha/ReplicatingSubscription.cpp - qpid/ha/BrokerReplicator.cpp - qpid/ha/BrokerReplicator.h - qpid/ha/ConnectionExcluder.cpp - qpid/ha/ConnectionExcluder.h + qpid/ha/ReplicatingSubscription.h + qpid/ha/Settings.h + qpid/ha/types.cpp + qpid/ha/types.h + qpid/ha/RemoteBackup.cpp + qpid/ha/RemoteBackup.h ) add_library (ha MODULE ${ha_SOURCES}) set_target_properties (ha PROPERTIES PREFIX "") - target_link_libraries (ha qpidcommon qpidbroker ${Boost_PROGRAM_OPTIONS_LIBRARY}) + target_link_libraries (ha qpidtypes qpidcommon qpidbroker) if (CMAKE_COMPILER_IS_GNUCXX) set_target_properties (ha PROPERTIES PREFIX "" @@ -672,6 +687,11 @@ 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) + +# Set default Memory Status module (Null implementation) +set (qpid_memstat_module + qpid/sys/MemStat.cpp +) # Allow MSVC user to select 'WinXP-SP3/Windows Server 2003' as build target version set (win32_winnt_default OFF) @@ -734,7 +754,6 @@ if (CMAKE_SYSTEM_NAME STREQUAL Windows) qpid/sys/windows/SystemInfo.cpp qpid/sys/windows/Thread.cpp qpid/sys/windows/Time.cpp - qpid/sys/windows/MemStat.cpp qpid/client/windows/SaslFactory.cpp ${sslcommon_windows_SOURCES} ) @@ -780,41 +799,54 @@ else (CMAKE_SYSTEM_NAME STREQUAL Windows) ) endif (POLLER STREQUAL poll) + # Set default System Info module + set (qpid_system_module + qpid/sys/posix/SystemInfo.cpp + ) + if (CMAKE_SYSTEM_NAME STREQUAL Linux) - set (qpid_system_module - qpid/sys/posix/SystemInfo.cpp - ) add_definitions(-pthread) set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread") if (CMAKE_COMPILER_IS_GNUCXX) set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${GCC_CATCH_UNDEFINED} -pthread") + set (CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -pthread") endif (CMAKE_COMPILER_IS_GNUCXX) + # On Linux override memory status module + set (qpid_memstat_module + qpid/sys/posix/MemStat.cpp + ) endif (CMAKE_SYSTEM_NAME STREQUAL Linux) - set (qpidtypes_platform_SOURCES) - set (qpidtypes_platform_LIBS - uuid - ${Boost_SYSTEM_LIBRARY} - ) - if (CMAKE_SYSTEM_NAME STREQUAL SunOS) + # On Solaris override the system info module set (qpid_system_module qpid/sys/solaris/SystemInfo.cpp ) -# On Sun we want -lpthread -lthread as the 2nd last and last libs passed to linker + # On Sun we want -lpthread -lthread as the 2nd last and last libs passed to linker set (qpidtypes_platform_LIBS ${qpidtypes_platform_LIBS} pthread thread ) endif (CMAKE_SYSTEM_NAME STREQUAL SunOS) + if (CMAKE_CXX_COMPILER_ID STREQUAL SunPro) + # -lmalloc needed for mallinfo. + set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lmalloc") + set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -lmalloc") + endif (CMAKE_CXX_COMPILER_ID STREQUAL SunPro) + + set (qpidtypes_platform_SOURCES) + set (qpidtypes_platform_LIBS + uuid + ${Boost_SYSTEM_LIBRARY} + ) + 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/MemStat.cpp qpid/sys/posix/Mutex.cpp qpid/sys/posix/PipeHandle.cpp qpid/sys/posix/PollableCondition.cpp @@ -917,6 +949,7 @@ set (qpidcommon_SOURCES qpid/sys/Timer.cpp qpid/sys/TimerWarnings.cpp qpid/amqp_0_10/Codecs.cpp + ${qpid_memstat_module} ) add_msvc_version (qpidcommon library dll) @@ -1200,6 +1233,7 @@ set (qmf_SOURCES set (qmf_HEADERS ../include/qpid/agent/ManagementAgent.h ../include/qpid/agent/QmfAgentImportExport.h + ../include/qmf/BrokerImportExport.h ) add_msvc_version (qmf library dll) diff --git a/cpp/src/Makefile.am b/cpp/src/Makefile.am index 43e491a229..8a8e4b1928 100644 --- a/cpp/src/Makefile.am +++ b/cpp/src/Makefile.am @@ -28,6 +28,7 @@ windows_dist = \ qpid/log/windows/SinkOptions.cpp \ qpid/log/windows/SinkOptions.h \ ../include/qpid/sys/windows/check.h \ + qpid/sys/MemStat.cpp \ qpid/sys/windows/AsynchIO.cpp \ qpid/sys/windows/AsynchIoResult.h \ ../include/qpid/sys/windows/Condition.h \ @@ -53,7 +54,6 @@ windows_dist = \ ../include/qpid/sys/windows/Time.h \ qpid/sys/windows/uuid.cpp \ qpid/sys/windows/uuid.h \ - qpid/sys/windows/MemStat.cpp \ windows/QpiddBroker.cpp \ windows/SCM.h \ windows/SCM.cpp \ @@ -107,7 +107,7 @@ mgen_xml=$(top_srcdir)/../specs/management-schema.xml \ $(srcdir)/qpid/cluster/management-schema.xml \ $(srcdir)/qpid/ha/management-schema.xml mgen_cmd=$(mgen_dir)/qmf-gen -m $(srcdir)/managementgen.mk \ - -c $(srcdir)/managementgen.cmake -q -b -o qmf \ + -c $(srcdir)/managementgen.cmake -q -b -l -o qmf \ $(mgen_xml) $(srcdir)/managementgen.mk $(mgen_broker_cpp) $(dist_qpid_management_HEADERS): mgen.timestamp @@ -380,7 +380,6 @@ libqpidcommon_la_SOURCES += \ 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 \ @@ -553,6 +552,8 @@ libqpidbroker_la_SOURCES = \ qpid/broker/ConsumerFactory.h \ qpid/broker/ConnectionObserver.h \ qpid/broker/ConnectionObservers.h \ + qpid/broker/ConfigurationObserver.h \ + qpid/broker/ConfigurationObservers.h \ qpid/broker/Daemon.cpp \ qpid/broker/Daemon.h \ qpid/broker/Deliverable.h \ @@ -615,6 +616,7 @@ libqpidbroker_la_SOURCES = \ qpid/broker/NameGenerator.h \ qpid/broker/NullMessageStore.cpp \ qpid/broker/NullMessageStore.h \ + qpid/broker/Observers.h \ qpid/broker/OwnershipToken.h \ qpid/broker/Persistable.h \ qpid/broker/PersistableConfig.h \ @@ -684,6 +686,7 @@ libqpidbroker_la_SOURCES = \ qpid/broker/ThresholdAlerts.h \ qpid/broker/TopicExchange.cpp \ qpid/broker/TopicExchange.h \ + qpid/broker/TopicKeyNode.h \ qpid/broker/TransactionalStore.h \ qpid/broker/TxAccept.cpp \ qpid/broker/TxAccept.h \ diff --git a/cpp/src/ha.mk b/cpp/src/ha.mk index be1fb73e89..96a3d872e4 100644 --- a/cpp/src/ha.mk +++ b/cpp/src/ha.mk @@ -23,22 +23,37 @@ dmoduleexec_LTLIBRARIES += ha.la ha_la_SOURCES = \ + qpid/ha/AlternateExchangeSetter.h \ qpid/ha/Backup.cpp \ qpid/ha/Backup.h \ + qpid/ha/BackupConnectionExcluder.h \ + qpid/ha/BrokerInfo.cpp \ + qpid/ha/BrokerInfo.h \ qpid/ha/BrokerReplicator.cpp \ - qpid/ha/BrokerReplicator.h \ - qpid/ha/ConnectionExcluder.cpp \ - qpid/ha/ConnectionExcluder.h \ + qpid/ha/BrokerReplicator.h \ + qpid/ha/ConnectionObserver.cpp \ + qpid/ha/ConnectionObserver.h \ qpid/ha/HaBroker.cpp \ qpid/ha/HaBroker.h \ qpid/ha/HaPlugin.cpp \ + qpid/ha/Membership.cpp \ + qpid/ha/Membership.h \ + qpid/ha/Primary.cpp \ + qpid/ha/Primary.h \ + qpid/ha/QueueGuard.cpp \ + qpid/ha/QueueGuard.h \ + qpid/ha/QueueRange.h \ qpid/ha/QueueReplicator.cpp \ qpid/ha/QueueReplicator.h \ - qpid/ha/ReplicateLevel.cpp \ - qpid/ha/ReplicateLevel.h \ qpid/ha/ReplicatingSubscription.cpp \ qpid/ha/ReplicatingSubscription.h \ - qpid/ha/Settings.h + qpid/ha/ReplicationTest.cpp \ + qpid/ha/ReplicationTest.h \ + qpid/ha/Settings.h \ + qpid/ha/RemoteBackup.cpp \ + qpid/ha/RemoteBackup.h \ + qpid/ha/types.cpp \ + qpid/ha/types.h ha_la_LIBADD = libqpidbroker.la ha_la_LDFLAGS = $(PLUGINLDFLAGS) diff --git a/cpp/src/posix/QpiddBroker.cpp b/cpp/src/posix/QpiddBroker.cpp index fd2fb6184f..76e3bb6674 100644 --- a/cpp/src/posix/QpiddBroker.cpp +++ b/cpp/src/posix/QpiddBroker.cpp @@ -154,8 +154,14 @@ int QpiddBroker::execute (QpiddOptions *options) { 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); + pid_t pid; + try { + pid = Daemon::getPid(myOptions->daemon.piddir, options->broker.port); + } catch (const ErrnoException& e) { + // This is not a critical error, usually means broker is not running + QPID_LOG(notice, "Cannot stop broker: " << e.what()); + return 1; + } if (pid < 0) return 1; if (myOptions->daemon.check) diff --git a/cpp/src/qmf.mk b/cpp/src/qmf.mk index 9b5df6c808..6a4bce4087 100644 --- a/cpp/src/qmf.mk +++ b/cpp/src/qmf.mk @@ -30,7 +30,8 @@ lib_LTLIBRARIES += \ # QMF_API = \ ../include/qpid/agent/ManagementAgent.h \ - ../include/qpid/agent/QmfAgentImportExport.h + ../include/qpid/agent/QmfAgentImportExport.h \ + ../include/qmf/BrokerImportExport.h # # Public headers for the QMF2 API @@ -96,7 +97,6 @@ libqmf2_la_SOURCES = \ qmf/AgentSessionImpl.h \ qmf/AgentSubscription.cpp \ qmf/AgentSubscription.h \ - qmf/BrokerImportExport.h \ qmf/ConsoleEvent.cpp \ qmf/ConsoleEventImpl.h \ qmf/ConsoleSession.cpp \ diff --git a/cpp/src/qmf/AgentSession.cpp b/cpp/src/qmf/AgentSession.cpp index 3b5806aea4..4d7be33188 100644 --- a/cpp/src/qmf/AgentSession.cpp +++ b/cpp/src/qmf/AgentSession.cpp @@ -21,6 +21,22 @@ #include "qmf/AgentSessionImpl.h" +#include <iostream> +#include <memory> + +namespace qmf { + +using std::string; +using std::map; + +using qpid::messaging::Address; +using qpid::messaging::Connection; +using qpid::messaging::Duration; +using qpid::messaging::Message; +using qpid::messaging::Receiver; +using qpid::messaging::Sender; +using qpid::types::Variant; + 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); } @@ -332,7 +348,7 @@ void AgentSessionImpl::delData(const DataAddr& addr) void AgentSessionImpl::authAccept(AgentEvent& authEvent) { - auto_ptr<AgentEventImpl> eventImpl(new AgentEventImpl(AGENT_QUERY)); + std::auto_ptr<AgentEventImpl> eventImpl(new AgentEventImpl(AGENT_QUERY)); eventImpl->setQuery(authEvent.getQuery()); eventImpl->setUserId(authEvent.getUserId()); eventImpl->setReplyTo(AgentEventImplAccess::get(authEvent).getReplyTo()); @@ -593,7 +609,7 @@ void AgentSessionImpl::handleMethodRequest(const Variant::Map& content, const Me // // Construct an AgentEvent to be sent to the application. // - auto_ptr<AgentEventImpl> eventImpl(new AgentEventImpl(AGENT_METHOD)); + std::auto_ptr<AgentEventImpl> eventImpl(new AgentEventImpl(AGENT_METHOD)); eventImpl->setUserId(msg.getUserId()); eventImpl->setReplyTo(msg.getReplyTo()); eventImpl->setCorrelationId(msg.getCorrelationId()); @@ -655,8 +671,8 @@ void AgentSessionImpl::handleQueryRequest(const Variant::Map& content, const Mes // // 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)); + std::auto_ptr<QueryImpl> queryImpl(new QueryImpl(content)); + std::auto_ptr<AgentEventImpl> eventImpl(new AgentEventImpl(AGENT_AUTH_QUERY)); eventImpl->setUserId(msg.getUserId()); eventImpl->setReplyTo(msg.getReplyTo()); eventImpl->setCorrelationId(msg.getCorrelationId()); @@ -1012,3 +1028,4 @@ const AgentSessionImpl& AgentSessionImplAccess::get(const AgentSession& session) return *session.impl; } +} diff --git a/cpp/src/qmf/AgentSessionImpl.h b/cpp/src/qmf/AgentSessionImpl.h index ae512a4054..64a39ab2e8 100644 --- a/cpp/src/qmf/AgentSessionImpl.h +++ b/cpp/src/qmf/AgentSessionImpl.h @@ -57,17 +57,10 @@ #include <queue> #include <map> -#include <iostream> -#include <memory> - -using namespace std; -using namespace qpid::messaging; -using namespace qmf; -using qpid::types::Variant; - -typedef qmf::PrivateImplRef<AgentSession> PI; namespace qmf { + typedef qmf::PrivateImplRef<AgentSession> PI; + class AgentSessionImpl : public virtual qpid::RefCounted, public qpid::sys::Runnable { public: ~AgentSessionImpl(); @@ -75,29 +68,29 @@ namespace qmf { // // 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; } + AgentSessionImpl(qpid::messaging::Connection& c, const std::string& o); + void setDomain(const std::string& d) { checkOpen(); domain = d; } + void setVendor(const std::string& v) { checkOpen(); attributes["_vendor"] = v; } + void setProduct(const std::string& p) { checkOpen(); attributes["_product"] = p; } + void setInstance(const std::string& i) { checkOpen(); attributes["_instance"] = i; } + void setAttribute(const std::string& k, const qpid::types::Variant& v) { checkOpen(); attributes[k] = v; } + const std::string& getName() const { return agentName; } void open(); void closeAsync(); void close(); - bool nextEvent(AgentEvent& e, Duration t); + bool nextEvent(AgentEvent& e, qpid::messaging::Duration t); int pendingEvents() const; void setEventNotifier(EventNotifierImpl* eventNotifier); EventNotifierImpl* getEventNotifier() const; void registerSchema(Schema& s); - DataAddr addData(Data& d, const string& n, bool persist); + DataAddr addData(Data& d, const std::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 authReject(AgentEvent& e, const std::string& m); + void raiseException(AgentEvent& e, const std::string& s); void raiseException(AgentEvent& e, const Data& d); void response(AgentEvent& e, const Data& d); void complete(AgentEvent& e); @@ -106,21 +99,21 @@ namespace qmf { void raiseEvent(const Data& d, int s); private: - typedef map<DataAddr, Data, DataAddrCompare> DataIndex; - typedef map<SchemaId, Schema, SchemaIdCompare> SchemaMap; + typedef std::map<DataAddr, Data, DataAddrCompare> DataIndex; + typedef std::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; + qpid::messaging::Connection connection; + qpid::messaging::Session session; + qpid::messaging::Sender directSender; + qpid::messaging::Sender topicSender; + std::string domain; + qpid::types::Variant::Map attributes; + qpid::types::Variant::Map options; + std::string agentName; bool opened; - queue<AgentEvent> eventQueue; + std::queue<AgentEvent> eventQueue; EventNotifierImpl* eventNotifier; qpid::sys::Thread* thread; bool threadCanceled; @@ -140,25 +133,25 @@ namespace qmf { bool strictSecurity; uint32_t maxThreadWaitTime; uint64_t schemaUpdateTime; - string directBase; - string topicBase; + std::string directBase; + std::string topicBase; SchemaMap schemata; DataIndex globalIndex; - map<SchemaId, DataIndex, SchemaIdCompareNoHash> schemaIndex; + std::map<SchemaId, DataIndex, SchemaIdCompareNoHash> schemaIndex; void checkOpen(); void setAgentName(); void enqueueEvent(const AgentEvent&); void alertEventNotifierLH(bool readable); - void handleLocateRequest(const Variant::List& content, const Message& msg); - void handleMethodRequest(const Variant::Map& content, const Message& msg); - void handleQueryRequest(const Variant::Map& content, const Message& msg); + void handleLocateRequest(const qpid::types::Variant::List& content, const qpid::messaging::Message& msg); + void handleMethodRequest(const qpid::types::Variant::Map& content, const qpid::messaging::Message& msg); + void handleQueryRequest(const qpid::types::Variant::Map& content, const qpid::messaging::Message& msg); void handleSchemaRequest(AgentEvent&); - void handleV1SchemaRequest(qpid::management::Buffer&, uint32_t, const Message&); - void dispatch(Message); + void handleV1SchemaRequest(qpid::management::Buffer&, uint32_t, const qpid::messaging::Message&); + void dispatch(qpid::messaging::Message); void sendHeartbeat(); - void send(Message, const Address&); + void send(qpid::messaging::Message, const qpid::messaging::Address&); void flushResponses(AgentEvent&, bool); void periodicProcessing(uint64_t); void run(); diff --git a/cpp/src/qmf/ConsoleSessionImpl.h b/cpp/src/qmf/ConsoleSessionImpl.h index e2b30602fa..2c06df030c 100644 --- a/cpp/src/qmf/ConsoleSessionImpl.h +++ b/cpp/src/qmf/ConsoleSessionImpl.h @@ -47,8 +47,6 @@ #include <map> #include <queue> -using namespace std; - namespace qmf { class ConsoleSessionImpl : public virtual qpid::RefCounted, public qpid::sys::Runnable { public: diff --git a/cpp/src/qmf/EventNotifierImpl.cpp b/cpp/src/qmf/EventNotifierImpl.cpp index 20114aaa5e..81b6d637a3 100644 --- a/cpp/src/qmf/EventNotifierImpl.cpp +++ b/cpp/src/qmf/EventNotifierImpl.cpp @@ -21,6 +21,8 @@ #include "qmf/AgentSessionImpl.h" #include "qmf/ConsoleSessionImpl.h" +namespace qmf { + EventNotifierImpl::EventNotifierImpl(AgentSession& agentSession) : readable(false), agent(agentSession) { @@ -54,3 +56,5 @@ bool EventNotifierImpl::isReadable() const { return this->readable; } + +} diff --git a/cpp/src/qmf/PrivateImplRef.h b/cpp/src/qmf/PrivateImplRef.h index 960cbb2e09..c0c07d7e1b 100644 --- a/cpp/src/qmf/PrivateImplRef.h +++ b/cpp/src/qmf/PrivateImplRef.h @@ -76,15 +76,15 @@ template <class T> class PrivateImplRef { /** 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); + if (t.impl) intrusive_ptr_release(t.impl); t.impl = p.get(); - if (t.impl) boost::intrusive_ptr_add_ref(t.impl); + if (t.impl) 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 ctor(T& t, Impl* p) { t.impl = p; if (p) 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 void dtor(T& t) { if(t.impl) intrusive_ptr_release(t.impl); } static T& assign(T& t, const T& x) { set(t, get(x)); return t;} }; diff --git a/cpp/src/qpid/RefCounted.h b/cpp/src/qpid/RefCounted.h index f9e0107103..26e3e2c4ba 100644 --- a/cpp/src/qpid/RefCounted.h +++ b/cpp/src/qpid/RefCounted.h @@ -49,15 +49,11 @@ protected: }; -} // 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(); } -} +inline void intrusive_ptr_add_ref(const RefCounted* p) { p->addRef(); } +inline void intrusive_ptr_release(const RefCounted* p) { p->release(); } + +} // namespace qpid #endif /*!QPID_REFCOUNTED_H*/ diff --git a/cpp/src/qpid/acl/Acl.cpp b/cpp/src/qpid/acl/Acl.cpp index 917c2e3398..d941577f6a 100644 --- a/cpp/src/qpid/acl/Acl.cpp +++ b/cpp/src/qpid/acl/Acl.cpp @@ -51,7 +51,7 @@ 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), - connectionCounter(new ConnectionCounter(*this, aclValues.aclMaxConnectPerUser, aclValues.aclMaxConnectPerIp)) + connectionCounter(new ConnectionCounter(*this, aclValues.aclMaxConnectPerUser, aclValues.aclMaxConnectPerIp, aclValues.aclMaxConnectTotal)) { agent = broker->getManagementAgent(); @@ -60,11 +60,14 @@ Acl::Acl (AclValues& av, Broker& b): aclValues(av), broker(&b), transferAcl(fals _qmf::Package packageInit(agent); mgmtObject = new _qmf::Acl (agent, this, broker); agent->addObject (mgmtObject); + mgmtObject->set_maxConnections(aclValues.aclMaxConnectTotal); + mgmtObject->set_maxConnectionsPerIp(aclValues.aclMaxConnectPerIp); + mgmtObject->set_maxConnectionsPerUser(aclValues.aclMaxConnectPerUser); } std::string errorString; if (!readAclFile(errorString)){ - throw Exception("Could not read ACL file " + errorString); if (mgmtObject!=0) mgmtObject->set_enforcingAcl(0); + throw Exception("Could not read ACL file " + errorString); } broker->getConnectionObservers().add(connectionCounter); QPID_LOG(info, "ACL Plugin loaded"); @@ -121,6 +124,11 @@ bool Acl::authorise( } +bool Acl::approveConnection(const qpid::broker::Connection& conn) +{ + return connectionCounter->approveConnection(conn); +} + bool Acl::result( const AclResult& aclreslt, const std::string& id, diff --git a/cpp/src/qpid/acl/Acl.h b/cpp/src/qpid/acl/Acl.h index c3451018ef..4893f71ef2 100644 --- a/cpp/src/qpid/acl/Acl.h +++ b/cpp/src/qpid/acl/Acl.h @@ -38,6 +38,7 @@ namespace qpid { namespace broker { class Broker; +class Connection; } namespace acl { @@ -45,8 +46,9 @@ class ConnectionCounter; struct AclValues { std::string aclFile; - uint32_t aclMaxConnectPerUser; - uint32_t aclMaxConnectPerIp; + uint16_t aclMaxConnectPerUser; + uint16_t aclMaxConnectPerIp; + uint16_t aclMaxConnectTotal; }; @@ -66,6 +68,9 @@ private: public: Acl (AclValues& av, broker::Broker& b); + /** reportConnectLimit + * issue management counts and alerts for denied connections + */ void reportConnectLimit(const std::string user, const std::string addr); inline virtual bool doTransferAcl() { @@ -87,6 +92,8 @@ public: const std::string& ExchangeName, const std::string& RoutingKey); + virtual bool approveConnection(const broker::Connection& connection); + virtual ~Acl(); private: bool result( diff --git a/cpp/src/qpid/acl/AclConnectionCounter.cpp b/cpp/src/qpid/acl/AclConnectionCounter.cpp index 5d4e3c1544..052fa3c222 100644 --- a/cpp/src/qpid/acl/AclConnectionCounter.cpp +++ b/cpp/src/qpid/acl/AclConnectionCounter.cpp @@ -34,37 +34,82 @@ namespace acl { // // This module instantiates a broker::ConnectionObserver and limits client -// connections by counting connections per user name and per client IP address. +// connections by counting connections per user name, per client IP address +// and per total connection count. // // // // -ConnectionCounter::ConnectionCounter(Acl& a, uint32_t nl, uint32_t hl) : - acl(a), nameLimit(nl), hostLimit(hl) {} +ConnectionCounter::ConnectionCounter(Acl& a, uint16_t nl, uint16_t hl, uint16_t tl) : + acl(a), nameLimit(nl), hostLimit(hl), totalLimit(tl), totalCurrentConnections(0) {} ConnectionCounter::~ConnectionCounter() {} // -// limitCheckLH +// limitApproveLH +// +// Connection creation approver. Return true only if user is under limit. +// Called with lock held. +// +bool ConnectionCounter::limitApproveLH( + connectCountsMap_t& theMap, + const std::string& theName, + uint16_t theLimit, + bool emitLog) { + + bool result(true); + if (theLimit > 0) { + uint16_t count; + connectCountsMap_t::iterator eRef = theMap.find(theName); + if (eRef != theMap.end()) { + count = (uint16_t)(*eRef).second; + result = count <= theLimit; + } else { + // Not found + count = 0; + } + if (emitLog) { + QPID_LOG(trace, "ACL ConnectionApprover IP=" << theName + << " limit=" << theLimit + << " curValue=" << count + << " result=" << (result ? "allow" : "deny")); + } + } + return result; +} + + +// +// countConnectionLH // // Increment the name's count in map and return a comparison against the limit. // called with dataLock already taken // -bool ConnectionCounter::limitCheckLH( - connectCountsMap_t& theMap, const std::string& theName, uint32_t theLimit) { +bool ConnectionCounter::countConnectionLH( + connectCountsMap_t& theMap, + const std::string& theName, + uint16_t theLimit, + bool emitLog) { bool result(true); + uint16_t count(0); if (theLimit > 0) { connectCountsMap_t::iterator eRef = theMap.find(theName); if (eRef != theMap.end()) { - uint32_t count = (uint32_t)(*eRef).second + 1; + count = (uint16_t)(*eRef).second + 1; (*eRef).second = count; result = count <= theLimit; } else { - theMap[theName] = 1; + theMap[theName] = count = 1; + } + if (emitLog) { + QPID_LOG(trace, "ACL ConnectionApprover user=" << theName + << " limit=" << theLimit + << " curValue=" << count + << " result=" << (result ? "allow" : "deny")); } } return result; @@ -78,12 +123,12 @@ bool ConnectionCounter::limitCheckLH( // called with dataLock already taken // void ConnectionCounter::releaseLH( - connectCountsMap_t& theMap, const std::string& theName, uint32_t theLimit) { + connectCountsMap_t& theMap, const std::string& theName, uint16_t theLimit) { if (theLimit > 0) { connectCountsMap_t::iterator eRef = theMap.find(theName); if (eRef != theMap.end()) { - uint32_t count = (uint32_t) (*eRef).second; + uint16_t count = (uint16_t) (*eRef).second; assert (count > 0); if (1 == count) { theMap.erase (eRef); @@ -103,52 +148,20 @@ void ConnectionCounter::releaseLH( // connection - called during Connection's constructor // void ConnectionCounter::connection(broker::Connection& connection) { - QPID_LOG(trace, "ACL ConnectionCounter connection IP:" << connection.getMgmtId() - << ", userId:" << connection.getUserId()); + QPID_LOG(trace, "ACL ConnectionCounter new connection: " << connection.getMgmtId()); - Mutex::ScopedLock locker(dataLock); - - connectProgressMap[connection.getMgmtId()] = C_CREATED; -} - - -// -// opened - called when first AMQP frame is received over Connection -// -void ConnectionCounter::opened(broker::Connection& connection) { - QPID_LOG(trace, "ACL ConnectionCounter Opened IP:" << connection.getMgmtId() - << ", userId:" << connection.getUserId()); + const std::string& hostName(getClientHost(connection.getMgmtId())); Mutex::ScopedLock locker(dataLock); - const std::string& userName( connection.getUserId()); - const std::string& hostName(getClientHost(connection.getMgmtId())); + // Total connections goes up + totalCurrentConnections += 1; - // Bump state from CREATED to OPENED - (void) limitCheckLH(connectProgressMap, connection.getMgmtId(), C_OPENED); - - bool nameOk = limitCheckLH(connectByNameMap, userName, nameLimit); - bool hostOk = limitCheckLH(connectByHostMap, hostName, hostLimit); - - if (!nameOk) { - // User has too many - acl.reportConnectLimit(userName, hostName); - QPID_LOG(notice, "ACL ConnectionCounter User '" << userName - << "' exceeded maximum allowed connections"); - throw Exception( - QPID_MSG("User '" << userName - << "' exceeded maximum allowed connections")); - } + // Record the fact that this connection exists + connectProgressMap[connection.getMgmtId()] = C_CREATED; - if (!hostOk) { - // Host has too many - acl.reportConnectLimit(userName, hostName); - QPID_LOG(notice, "ACL ConnectionCounter Client host '" << hostName - << "' exceeded maximum allowed connections"); - throw Exception( - QPID_MSG("Client host '" << hostName - << "' exceeded maximum allowed connections")); - } + // Count the connection from this host. + (void) countConnectionLH(connectByHostMap, hostName, hostLimit, false); } @@ -156,7 +169,7 @@ void ConnectionCounter::opened(broker::Connection& connection) { // closed - called during Connection's destructor // void ConnectionCounter::closed(broker::Connection& connection) { - QPID_LOG(trace, "ACL ConnectionCounter Closed IP:" << connection.getMgmtId() + QPID_LOG(trace, "ACL ConnectionCounter closed: " << connection.getMgmtId() << ", userId:" << connection.getUserId()); Mutex::ScopedLock locker(dataLock); @@ -165,32 +178,129 @@ void ConnectionCounter::closed(broker::Connection& connection) { if (eRef != connectProgressMap.end()) { if ((*eRef).second == C_OPENED){ // Normal case: connection was created and opened. - // Decrement in-use counts + // Decrement user in-use counts releaseLH(connectByNameMap, connection.getUserId(), nameLimit); - - releaseLH(connectByHostMap, - getClientHost(connection.getMgmtId()), - hostLimit); } else { // Connection was created but not opened. - // Don't decrement any connection counts. + // Don't decrement user count. } + + // Decrement host in-use count. + releaseLH(connectByHostMap, + getClientHost(connection.getMgmtId()), + hostLimit); + + // destroy connection progress indicator connectProgressMap.erase(eRef); } else { // connection not found in progress map - QPID_LOG(notice, "ACL ConnectionCounter info for '" << connection.getMgmtId() + QPID_LOG(notice, "ACL ConnectionCounter closed info for '" << connection.getMgmtId() << "' not found in connection state pool"); } + + // total connections + totalCurrentConnections -= 1; } // +// approveConnection +// check total connections, connections from IP, connections by user and +// disallow if over any limit +// +bool ConnectionCounter::approveConnection(const broker::Connection& connection) +{ + const std::string& hostName(getClientHost(connection.getMgmtId())); + const std::string& userName( connection.getUserId()); + + Mutex::ScopedLock locker(dataLock); + + // Bump state from CREATED to OPENED + (void) countConnectionLH(connectProgressMap, connection.getMgmtId(), + C_OPENED, false); + + // Approve total connections + bool okTotal = true; + if (totalLimit > 0) { + okTotal = totalCurrentConnections <= totalLimit; + if (!connection.isShadow()) { + QPID_LOG(trace, "ACL ConnectionApprover totalLimit=" << totalLimit + << " curValue=" << totalCurrentConnections + << " result=" << (okTotal ? "allow" : "deny")); + } + } + + // Approve by IP host connections + bool okByIP = limitApproveLH(connectByHostMap, hostName, hostLimit, !connection.isShadow()); + + // Count and Approve the connection by the user + bool okByUser = countConnectionLH(connectByNameMap, userName, nameLimit, !connection.isShadow()); + + if (!connection.isShadow()) { + // Emit separate log for each disapproval + if (!okTotal) { + QPID_LOG(error, "Client max total connection count limit of " << totalLimit + << " exceeded by '" + << connection.getMgmtId() << "', user: '" + << userName << "'. Connection refused"); + } + if (!okByIP) { + QPID_LOG(error, "Client max per-host connection count limit of " + << hostLimit << " exceeded by '" + << connection.getMgmtId() << "', user: '" + << userName << "'. Connection refused."); + } + if (!okByUser) { + QPID_LOG(error, "Client max per-user connection count limit of " + << nameLimit << " exceeded by '" + << connection.getMgmtId() << "', user: '" + << userName << "'. Connection refused."); + } + + // Count/Event once for each disapproval + bool result = okTotal && okByIP && okByUser; + if (!result) { + acl.reportConnectLimit(userName, hostName); + } + + return result; + } else { + // Always allow shadow connections + if (!okTotal) { + QPID_LOG(warning, "Client max total connection count limit of " << totalLimit + << " exceeded by '" + << connection.getMgmtId() << "', user: '" + << userName << "' but still within tolerance. Cluster connection allowed"); + } + if (!okByIP) { + QPID_LOG(warning, "Client max per-host connection count limit of " + << hostLimit << " exceeded by '" + << connection.getMgmtId() << "', user: '" + << userName << "' but still within tolerance. Cluster connection allowed"); + } + if (!okByUser) { + QPID_LOG(warning, "Client max per-user connection count limit of " + << nameLimit << " exceeded by '" + << connection.getMgmtId() << "', user: '" + << userName << "' but still within tolerance. Cluster connection allowed"); + } + if (okTotal && okByIP && okByUser) { + QPID_LOG(debug, "Cluster client connection: '" + << connection.getMgmtId() << "', user '" + << userName << "' allowed"); + } + return true; + } +} + +// // getClientIp - given a connection's mgmtId return the client host part. // // TODO: Ideally this would be a method of the connection itself. +// TODO: Verify it works with rdma connection names. // std::string ConnectionCounter::getClientHost(const std::string mgmtId) { diff --git a/cpp/src/qpid/acl/AclConnectionCounter.h b/cpp/src/qpid/acl/AclConnectionCounter.h index 31d11540fd..eec8e90256 100644 --- a/cpp/src/qpid/acl/AclConnectionCounter.h +++ b/cpp/src/qpid/acl/AclConnectionCounter.h @@ -48,32 +48,52 @@ private: enum CONNECTION_PROGRESS { C_CREATED=1, C_OPENED=2 }; Acl& acl; - uint32_t nameLimit; - uint32_t hostLimit; + uint16_t nameLimit; + uint16_t hostLimit; + uint16_t totalLimit; + uint16_t totalCurrentConnections; qpid::sys::Mutex dataLock; + /** Records per-connection state */ connectCountsMap_t connectProgressMap; + + /** Records per-username counts */ connectCountsMap_t connectByNameMap; + + /** Records per-host counts */ connectCountsMap_t connectByHostMap; + /** Given a connection's management ID, return the client host name */ std::string getClientHost(const std::string mgmtId); - bool limitCheckLH(connectCountsMap_t& theMap, - const std::string& theName, - uint32_t theLimit); + /** Return approval for proposed connection */ + bool limitApproveLH(connectCountsMap_t& theMap, + const std::string& theName, + uint16_t theLimit, + bool emitLog); + + /** Record a connection. + * @return indication if user/host is over its limit */ + bool countConnectionLH(connectCountsMap_t& theMap, + const std::string& theName, + uint16_t theLimit, + bool emitLog); + /** Release a connection */ void releaseLH(connectCountsMap_t& theMap, const std::string& theName, - uint32_t theLimit); + uint16_t theLimit); public: - ConnectionCounter(Acl& acl, uint32_t nl, uint32_t hl); + ConnectionCounter(Acl& acl, uint16_t nl, uint16_t hl, uint16_t tl); ~ConnectionCounter(); + // ConnectionObserver interface void connection(broker::Connection& connection); - void opened(broker::Connection& connection); void closed(broker::Connection& connection); + // Connection counting + bool approveConnection(const broker::Connection& conn); }; }} // namespace qpid::ha diff --git a/cpp/src/qpid/acl/AclData.cpp b/cpp/src/qpid/acl/AclData.cpp index da7f240a9b..a07176dc76 100644 --- a/cpp/src/qpid/acl/AclData.cpp +++ b/cpp/src/qpid/acl/AclData.cpp @@ -305,7 +305,9 @@ namespace acl { // lookup // // The ACL main business logic function of matching rules and declaring - // an allow or deny result. + // an allow or deny result. This lookup is the fastpath per-message + // lookup to verify if a user is allowed to publish to an exchange with + // a given key. // AclResult AclData::lookup( const std::string& id, @@ -331,7 +333,8 @@ namespace acl { if (itrRule != actionList[action][objType]->end() ) { - //loop the vector + // Found a rule list for this user-action-object set. + // Search the rule list for a matching rule. ruleSetItr rsItr = itrRule->second.end(); for (int cnt = itrRule->second.size(); cnt != 0; cnt--) { @@ -339,56 +342,46 @@ namespace acl { QPID_LOG(debug, "ACL: checking rule " << rsItr->toString()); - // loop the names looking for match + // Search on exchange name and routing key only if specfied in rule. bool match =true; - for (specPropertyMapItr pMItr = rsItr->props.begin(); - (pMItr != rsItr->props.end()) && match; - pMItr++) + if (rsItr->pubExchNameInRule) { - //match name is exists first - switch (pMItr->first) + if (matchProp(rsItr->pubExchName, name)) { - case acl::SPECPROP_NAME: - if (matchProp(pMItr->second, name)) - { - QPID_LOG(debug, "ACL: lookup exchange name '" - << name << "' matched with rule name '" - << pMItr->second << "'"); - - } - else - { - match= false; - QPID_LOG(debug, "ACL: lookup exchange name '" - << name << "' did not match with rule name '" - << pMItr->second << "'"); - } - break; + QPID_LOG(debug, "ACL: Rule: " << rsItr->rawRuleNum << " lookup exchange name '" + << name << "' matched with rule name '" + << rsItr->pubExchName << "'"); - case acl::SPECPROP_ROUTINGKEY: - if (matchProp(pMItr->second, routingKey)) - { - QPID_LOG(debug, "ACL: lookup key name '" - << routingKey << "' matched with rule routing key '" - << pMItr->second << "'"); - } - else - { - match= false; - QPID_LOG(debug, "ACL: lookup key name '" - << routingKey << "' did not match with rule routing key '" - << pMItr->second << "'"); - } - break; + } + else + { + match= false; + QPID_LOG(debug, "ACL: Rule: " << rsItr->rawRuleNum << " lookup exchange name '" + << name << "' did not match with rule name '" + << rsItr->pubExchName << "'"); + } + } - default: - // Don't care - break; - }; + if (match && rsItr->pubRoutingKeyInRule) + { + if (rsItr->matchRoutingKey(routingKey)) + { + QPID_LOG(debug, "ACL: Rule: " << rsItr->rawRuleNum << " lookup key name '" + << routingKey << "' matched with rule routing key '" + << rsItr->pubRoutingKey << "'"); + } + else + { + QPID_LOG(debug, "ACL: Rule: " << rsItr->rawRuleNum << " lookup key name '" + << routingKey << "' did not match with rule routing key '" + << rsItr->pubRoutingKey << "'"); + match = false; + } } + if (match){ aclresult = rsItr->ruleMode; - QPID_LOG(debug,"ACL: Successful match, the decision is:" + QPID_LOG(debug,"ACL: Rule: " << rsItr->rawRuleNum << " Successful match, the decision is:" << AclHelper::getAclResultStr(aclresult)); return aclresult; } diff --git a/cpp/src/qpid/acl/AclData.h b/cpp/src/qpid/acl/AclData.h index 1c1cb3e9c6..ca0a676a1c 100644 --- a/cpp/src/qpid/acl/AclData.h +++ b/cpp/src/qpid/acl/AclData.h @@ -21,6 +21,9 @@ */ #include "qpid/broker/AclModule.h" +#include "AclTopicMatch.h" +#include "qpid/log/Statement.h" +#include "boost/shared_ptr.hpp" #include <vector> #include <sstream> @@ -48,18 +51,29 @@ public: // A single ACL file entry may create many rule entries in // many ruleset vectors. // - struct rule { + struct Rule { + typedef broker::TopicExchange::TopicExchangeTester topicTester; int rawRuleNum; // rule number in ACL file qpid::acl::AclResult ruleMode; // combined allow/deny log/nolog specPropertyMap props; // + bool pubRoutingKeyInRule; + std::string pubRoutingKey; + boost::shared_ptr<topicTester> pTTest; + bool pubExchNameInRule; + std::string pubExchName; - - rule (int ruleNum, qpid::acl::AclResult res, specPropertyMap& p) : + Rule (int ruleNum, qpid::acl::AclResult res, specPropertyMap& p) : rawRuleNum(ruleNum), ruleMode(res), - props(p) - {}; + props(p), + pubRoutingKeyInRule(false), + pubRoutingKey(), + pTTest(boost::shared_ptr<topicTester>(new topicTester())), + pubExchNameInRule(false), + pubExchName() + {} + std::string toString () const { std::ostringstream ruleStr; @@ -76,9 +90,21 @@ public: ruleStr << " }]"; return ruleStr.str(); } + + void addTopicTest(const std::string& pattern) { + pTTest->addBindingKey(broker::TopicExchange::normalize(pattern)); + } + + // Topic Exchange tester + // return true if any bindings match 'pattern' + bool matchRoutingKey(const std::string& pattern) const + { + topicTester::BindingVec bv; + return pTTest->findMatches(pattern, bv); + } }; - typedef std::vector<rule> ruleSet; + typedef std::vector<Rule> ruleSet; typedef ruleSet::const_iterator ruleSetItr; typedef std::map<std::string, ruleSet > actionObject; // user typedef actionObject::iterator actObjItr; diff --git a/cpp/src/qpid/acl/AclPlugin.cpp b/cpp/src/qpid/acl/AclPlugin.cpp index 6c18cd2749..ebf5e90afe 100644 --- a/cpp/src/qpid/acl/AclPlugin.cpp +++ b/cpp/src/qpid/acl/AclPlugin.cpp @@ -39,10 +39,13 @@ struct AclOptions : public Options { AclValues& values; AclOptions(AclValues& v) : Options("ACL Options"), values(v) { + values.aclMaxConnectTotal = 500; addOptions() ("acl-file", optValue(values.aclFile, "FILE"), "The policy file to load from, loaded from data dir") - ("acl-max-connect-per-user", optValue(values.aclMaxConnectPerUser, "N"), "The maximum number of connections allowed per user") - ("acl-max-connect-per-ip" , optValue(values.aclMaxConnectPerIp, "N"), "The maximum number of connections allowed per host IP address"); + ("max-connections" , optValue(values.aclMaxConnectTotal, "N"), "The maximum combined number of connections allowed. 0 implies no limit.") + ("max-connections-per-user", optValue(values.aclMaxConnectPerUser, "N"), "The maximum number of connections allowed per user. 0 implies no limit.") + ("max-connections-per-ip" , optValue(values.aclMaxConnectPerIp, "N"), "The maximum number of connections allowed per host IP address. 0 implies no limit.") + ; } }; @@ -69,7 +72,6 @@ struct AclPlugin : public Plugin { 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)); diff --git a/cpp/src/qpid/acl/AclReader.cpp b/cpp/src/qpid/acl/AclReader.cpp index 80debf1bd1..f9be49b88d 100644 --- a/cpp/src/qpid/acl/AclReader.cpp +++ b/cpp/src/qpid/acl/AclReader.cpp @@ -101,7 +101,7 @@ namespace acl { << AclHelper::getAclResultStr(d->decisionMode)); foundmode = true; } else { - AclData::rule rule(cnt, (*i)->res, (*i)->props); + AclData::Rule rule(cnt, (*i)->res, (*i)->props); // Action -> Object -> map<user -> set<Rule> > std::ostringstream actionstr; @@ -110,8 +110,27 @@ namespace acl { (*i)->actionAll ? acnt++ : acnt = acl::ACTIONSIZE) { if (acnt == acl::ACT_PUBLISH) + { d->transferAcl = true; // we have transfer ACL - + // For Publish the only object should be Exchange + // and the only property should be routingkey. + // Go through the rule properties and find the name and the key. + // If found then place them specially for the lookup engine. + for (pmCitr pItr=(*i)->props.begin(); pItr!=(*i)->props.end(); pItr++) { + if (acl::SPECPROP_ROUTINGKEY == pItr->first) + { + rule.pubRoutingKeyInRule = true; + rule.pubRoutingKey = (std::string)pItr->second; + rule.addTopicTest(rule.pubRoutingKey); + break; + } + if (acl::SPECPROP_NAME == pItr->first) + { + rule.pubExchNameInRule = true; + rule.pubExchName = pItr->second; + } + } + } actionstr << AclHelper::getActionStr((Action) acnt) << ","; //find the Action, create if not exist @@ -285,7 +304,7 @@ namespace acl { if (ws) { ret = true; } else { - errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber << ", Non-continuation line must start with \"group\" or \"acl\"."; ret = false; } @@ -314,13 +333,23 @@ namespace acl { if (contFlag) { gmCitr citr = groups.find(groupName); for (unsigned i = 0; i < toksSize; i++) { - if (!isValidUserName(toks[i])) return false; + if (isValidGroupName(toks[i])) { + if (toks[i] == groupName) { + QPID_LOG(debug, "ACL: Line: " << lineNumber + << ", Ignoring recursive sub-group \"" << toks[i] << "\"."); + continue; + } else if (groups.find(toks[i]) == groups.end()) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Sub-group \"" << toks[i] << "\" not defined yet."; + return false; + } + } else 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 + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber << ", Insufficient tokens for group definition."; return false; } @@ -332,7 +361,17 @@ namespace acl { gmCitr citr = addGroup(toks[1]); if (citr == groups.end()) return false; for (unsigned i = 2; i < toksSize; i++) { - if (!isValidUserName(toks[i])) return false; + if (isValidGroupName(toks[i])) { + if (toks[i] == groupName) { + QPID_LOG(debug, "ACL: Line: " << lineNumber + << ", Ignoring recursive sub-group \"" << toks[i] << "\"."); + continue; + } else if (groups.find(toks[i]) == groups.end()) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Sub-group \"" << toks[i] << "\" not defined yet."; + return false; + } + } else if (!isValidUserName(toks[i])) return false; addName(toks[i], citr->second); } } @@ -356,7 +395,7 @@ namespace acl { void AclReader::addName(const std::string& name, nameSetPtr groupNameSet) { gmCitr citr = groups.find(name); - if (citr != groups.end() && citr->first != name){ + if (citr != groups.end()) { // 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 { @@ -459,7 +498,7 @@ namespace acl { nvPair propNvp = splitNameValuePair(toks[i]); if (propNvp.second.size() == 0) { errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber - <<", Badly formed property name-value pair \"" + <<", Badly formed property name-value pair \"" << propNvp.first << "\". (Must be name=value)"; return false; } diff --git a/cpp/src/qpid/acl/AclReader.h b/cpp/src/qpid/acl/AclReader.h index 730013f4ed..6351c1e509 100644 --- a/cpp/src/qpid/acl/AclReader.h +++ b/cpp/src/qpid/acl/AclReader.h @@ -26,6 +26,7 @@ #include <string> #include <vector> #include <sstream> +#include <memory> #include "qpid/acl/AclData.h" #include "qpid/broker/AclModule.h" diff --git a/cpp/src/qpid/acl/AclTopicMatch.h b/cpp/src/qpid/acl/AclTopicMatch.h new file mode 100644 index 0000000000..486c229ad5 --- /dev/null +++ b/cpp/src/qpid/acl/AclTopicMatch.h @@ -0,0 +1,89 @@ +#ifndef QPID_ACL_TOPIC_MATCH_H +#define QPID_ACL_TOPIC_MATCH_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/TopicKeyNode.h" +#include "qpid/broker/TopicExchange.h" +#include "qpid/log/Statement.h" +#include "boost/shared_ptr.hpp" +#include <vector> +#include <sstream> + +namespace qpid { +namespace broker { + +// Class for executing topic exchange routing key matching rules in +// Acl code the allows or denies users publishing to an exchange. +class TopicExchange::TopicExchangeTester { + +class boundNode; + +public: + typedef std::vector<bool> BindingVec; + typedef TopicKeyNode<boundNode> TestBindingNode; + +private: + // Target class to be bound into topic key tree + class boundNode { + public: + BindingVec bindingVector; + }; + + // Acl binding trees contain only one node each. + // When the iterator sees it then the node matches the caller's spec. + class TestFinder : public TestBindingNode::TreeIterator { + public: + TestFinder(BindingVec& m) : bv(m), found(false) {}; + ~TestFinder() {}; + bool visit(TestBindingNode& /*node*/) { + assert(!found); + found = true; + return true; + } + BindingVec& bv; + bool found; + }; + +public: + TopicExchangeTester() {}; + ~TopicExchangeTester() {}; + bool addBindingKey(const std::string& bKey) { + std::string routingPattern = normalize(bKey); + boundNode *mbn = bindingTree.add(routingPattern); + if (mbn) { + // push a dummy binding to mark this node as "non-leaf" + mbn->bindingVector.push_back(true); + return true; + } + return false; + } + + bool findMatches(const std::string& rKey, BindingVec& matches) { + TestFinder testFinder(matches); + bindingTree.iterateMatch( rKey, testFinder ); + return testFinder.found; + } + +private: + TestBindingNode bindingTree; +}; +}} // namespace qpid::broker + +#endif // QPID_ACL_TOPIC_MATCH_H diff --git a/cpp/src/qpid/acl/AclValidator.cpp b/cpp/src/qpid/acl/AclValidator.cpp index 49bb65db4b..85f0f7c240 100644 --- a/cpp/src/qpid/acl/AclValidator.cpp +++ b/cpp/src/qpid/acl/AclValidator.cpp @@ -131,7 +131,7 @@ namespace acl { boost::bind(&AclValidator::validateRule, this, _1)); } - void AclValidator::validateRule(qpid::acl::AclData::rule& rule){ + void AclValidator::validateRule(qpid::acl::AclData::Rule& rule){ std::for_each(rule.props.begin(), rule.props.end(), boost::bind(&AclValidator::validateProperty, this, _1)); diff --git a/cpp/src/qpid/acl/AclValidator.h b/cpp/src/qpid/acl/AclValidator.h index f85c241b06..76eb222d2f 100644 --- a/cpp/src/qpid/acl/AclValidator.h +++ b/cpp/src/qpid/acl/AclValidator.h @@ -71,7 +71,7 @@ class AclValidator { public: void validateRuleSet(std::pair<const std::string, qpid::acl::AclData::ruleSet>& rules); - void validateRule(qpid::acl::AclData::rule& rule); + void validateRule(qpid::acl::AclData::Rule& rule); void validateProperty(std::pair<const qpid::acl::SpecProperty, std::string>& prop); void validate(boost::shared_ptr<AclData> d); AclValidator(); diff --git a/cpp/src/qpid/acl/management-schema.xml b/cpp/src/qpid/acl/management-schema.xml index 19fe37333c..f52c251bed 100644 --- a/cpp/src/qpid/acl/management-schema.xml +++ b/cpp/src/qpid/acl/management-schema.xml @@ -17,13 +17,16 @@ --> <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"/> - <statistic name="connectionDenyCount" type="count64" unit="connection" desc="Number of connections denied"/> + <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"/> + <property name="maxConnections" type="uint16" access="RO" desc="Maximum allowed connections"/> + <property name="maxConnectionsPerIp" type="uint16" access="RO" desc="Maximum allowed connections"/> + <property name="maxConnectionsPerUser" type="uint16" access="RO" desc="Maximum allowed connections"/> + <statistic name="aclDenyCount" type="count64" unit="request" desc="Number of ACL requests denied"/> + <statistic name="connectionDenyCount" type="count64" unit="connection" desc="Number of connections denied"/> <method name="reloadACLFile" desc="Reload the ACL file"/> diff --git a/cpp/src/qpid/amqp_0_10/Codecs.cpp b/cpp/src/qpid/amqp_0_10/Codecs.cpp index b976a5d09b..1288652ee1 100644 --- a/cpp/src/qpid/amqp_0_10/Codecs.cpp +++ b/cpp/src/qpid/amqp_0_10/Codecs.cpp @@ -52,9 +52,7 @@ template <class T, class U, class F> void convert(const T& from, U& to, F 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) { @@ -70,20 +68,6 @@ template <class T, class U, class F> T* toFieldValueCollection(const U& u, F 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){ @@ -151,7 +135,7 @@ Variant toVariant(boost::shared_ptr<FieldValue> in) 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: @@ -217,89 +201,254 @@ boost::shared_ptr<FieldValue> convertString(const std::string& value, const std: } } -boost::shared_ptr<FieldValue> toFieldValue(const Variant& in) +Variant::Map::value_type toVariantMapEntry(const FieldTable::value_type& 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())); + return Variant::Map::value_type(in.first, toVariant(in.second)); +} + +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 _decode(const std::string& data, U& value, F f) +{ + T t; + DecodeBuffer buffer(data); + buffer.decode(t); + convert(t, value, f); +} + +uint32_t encodedSize(const Variant::Map& values); +uint32_t encodedSize(const Variant::List& values); +uint32_t encodedSize(const std::string& value); + +uint32_t encodedSize(const Variant& value) +{ + switch (value.getType()) { + case VAR_VOID: + return 0; + case VAR_BOOL: + case VAR_UINT8: + case VAR_INT8: + return 1; + case VAR_UINT16: + case VAR_INT16: + return 2; break; + case VAR_UINT32: + case VAR_INT32: + case VAR_FLOAT: + return 4; + case VAR_UINT64: + case VAR_INT64: + case VAR_DOUBLE: + return 8; + case VAR_UUID: + return 16; + case VAR_MAP: + return encodedSize(value.asMap()); + case VAR_LIST: + return encodedSize(value.asList()); + case VAR_STRING: + return encodedSize(value.getString()); + default: + throw Exception("Couldn't encode Variant: Illegal type code"); } - return out; } -Variant::Map::value_type toVariantMapEntry(const FieldTable::value_type& in) +uint32_t encodedSize(const Variant::Map& values) { - return Variant::Map::value_type(in.first, toVariant(in.second)); + uint32_t size = 4/*size field*/ + 4/*count field*/; + for(Variant::Map::const_iterator i = values.begin(); i != values.end(); ++i) { + size += 1/*size of key*/ + (i->first).size() + 1/*typecode*/ + encodedSize(i->second); + } + return size; } -FieldTable::value_type toFieldTableEntry(const Variant::Map::value_type& in) +uint32_t encodedSize(const Variant::Map& values, const std::string& efield, const Variant& evalue) { - return FieldTable::value_type(in.first, toFieldValue(in.second)); + uint32_t size = 4/*size field*/ + 4/*count field*/; + for(Variant::Map::const_iterator i = values.begin(); i != values.end(); ++i) { + size += 1/*size of key*/ + (i->first).size() + 1/*typecode*/ + encodedSize(i->second); + } + size += 1/*size of key*/ + efield.size() + 1/*typecode*/ + encodedSize(evalue); + return size; } -struct EncodeBuffer +uint32_t encodedSize(const Variant::List& values) { - char* data; - Buffer buffer; + uint32_t size = 4/*size field*/ + 4/*count field*/; + for(Variant::List::const_iterator i = values.begin(); i != values.end(); ++i) { + size += 1/*typecode*/ + encodedSize(*i); + } + return size; +} + +uint32_t encodedSize(const std::string& value) +{ + uint32_t size = value.size(); + if (size > std::numeric_limits<uint16_t>::max()) { + return size + 4; /*Long size*/ + } else { + return size + 2; /*Short size*/ + } +} - EncodeBuffer(size_t size) : data(new char[size]), buffer(data, size) {} - ~EncodeBuffer() { delete[] data; } +void encode(const std::string& value, const std::string& encoding, qpid::framing::Buffer& buffer) +{ + uint32_t size = value.size(); + if (size > std::numeric_limits<uint16_t>::max()) { + if (encoding == utf8 || encoding == utf16 || encoding == iso885915) { + throw Exception(QPID_MSG("Could not encode " << encoding << " character string - too long (" << size << " bytes)")); + } else { + buffer.putOctet(0xa0); + buffer.putLong(size); + buffer.putRawData(value); + } + } else { + if (encoding == utf8) { + buffer.putOctet(0x95); + } else if (encoding == utf16) { + buffer.putOctet(0x96); + } else if (encoding == iso885915) { + buffer.putOctet(0x94); + } else { + buffer.putOctet(0x90); + } + buffer.putShort(size); + buffer.putRawData(value); + } +} - template <class T> void encode(T& t) { t.encode(buffer); } +void encode(const Variant::Map& map, uint32_t len, qpid::framing::Buffer& buffer); +void encode(const Variant::List& list, uint32_t len, qpid::framing::Buffer& buffer); - void getData(std::string& s) { - s.assign(data, buffer.getSize()); +void encode(const Variant& value, qpid::framing::Buffer& buffer) +{ + switch (value.getType()) { + case VAR_VOID: + buffer.putOctet(0xf0); + break; + case VAR_BOOL: + buffer.putOctet(0x08); + buffer.putOctet(value.asBool()); + break; + case VAR_INT8: + buffer.putOctet(0x01); + buffer.putInt8(value.asInt8()); + break; + case VAR_UINT8: + buffer.putOctet(0x02); + buffer.putOctet(value.asUint8()); + break; + case VAR_INT16: + buffer.putOctet(0x11); + buffer.putInt16(value.asInt16()); + break; + case VAR_UINT16: + buffer.putOctet(0x12); + buffer.putShort(value.asUint16()); + break; + case VAR_INT32: + buffer.putOctet(0x21); + buffer.putInt32(value.asInt32()); + break; + case VAR_UINT32: + buffer.putOctet(0x22); + buffer.putLong(value.asUint32()); + break; + case VAR_FLOAT: + buffer.putOctet(0x23); + buffer.putFloat(value.asFloat()); + break; + case VAR_INT64: + buffer.putOctet(0x31); + buffer.putInt64(value.asInt64()); + break; + case VAR_UINT64: + buffer.putOctet(0x32); + buffer.putLongLong(value.asUint64()); + break; + case VAR_DOUBLE: + buffer.putOctet(0x33); + buffer.putDouble(value.asDouble()); + break; + case VAR_UUID: + buffer.putOctet(0x48); + buffer.putBin128(value.asUuid().data()); + break; + case VAR_MAP: + buffer.putOctet(0xa8); + encode(value.asMap(), encodedSize(value.asMap()), buffer); + break; + case VAR_LIST: + buffer.putOctet(0xa9); + encode(value.asList(), encodedSize(value.asList()), buffer); + break; + case VAR_STRING: + encode(value.getString(), value.getEncoding(), buffer); + break; } -}; +} -struct DecodeBuffer +void encode(const Variant::Map& map, uint32_t len, qpid::framing::Buffer& buffer) { - Buffer buffer; + uint32_t s = buffer.getPosition(); + buffer.putLong(len - 4);//exclusive of the size field itself + buffer.putLong(map.size()); + for (Variant::Map::const_iterator i = map.begin(); i != map.end(); ++i) { + buffer.putShortString(i->first); + encode(i->second, buffer); + } + (void) s; assert(s + len == buffer.getPosition()); +} - DecodeBuffer(const std::string& s) : buffer(const_cast<char*>(s.data()), s.size()) {} +void encode(const Variant::Map& map, const std::string& efield, const Variant& evalue, uint32_t len, qpid::framing::Buffer& buffer) +{ + uint32_t s = buffer.getPosition(); + buffer.putLong(len - 4);//exclusive of the size field itself + buffer.putLong(map.size() + 1 /* The extra field */ ); + for (Variant::Map::const_iterator i = map.begin(); i != map.end(); ++i) { + buffer.putShortString(i->first); + encode(i->second, buffer); + } + buffer.putShortString(efield); + encode(evalue, buffer); - template <class T> void decode(T& t) { t.decode(buffer); } - -}; + (void) s; assert(s + len == buffer.getPosition()); +} -template <class T, class U, class F> void _encode(const U& value, std::string& data, F f) +void encode(const Variant::List& list, uint32_t len, qpid::framing::Buffer& buffer) { - T t; - convert(value, t, f); - EncodeBuffer buffer(t.encodedSize()); - buffer.encode(t); - buffer.getData(data); + uint32_t s = buffer.getPosition(); + buffer.putLong(len - 4);//exclusive of the size field itself + buffer.putLong(list.size()); + for (Variant::List::const_iterator i = list.begin(); i != list.end(); ++i) { + encode(*i, buffer); + } + (void) s; assert(s + len == buffer.getPosition()); } -template <class T, class U, class F> void _decode(const std::string& data, U& value, F f) +void decode(qpid::framing::Buffer&, Variant::Map&) { - 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); + uint32_t len = qpid::amqp_0_10::encodedSize(value); + std::vector<char> space(len); + qpid::framing::Buffer buff(&space[0], len); + + qpid::amqp_0_10::encode(value, len, buff); + assert( len == buff.getPosition() ); + data.assign(&space[0], len); } void MapCodec::decode(const std::string& data, Variant::Map& value) @@ -309,14 +458,18 @@ void MapCodec::decode(const std::string& data, Variant::Map& value) size_t MapCodec::encodedSize(const Variant::Map& value) { - std::string encoded; - encode(value, encoded); - return encoded.size(); + return qpid::amqp_0_10::encodedSize(value); } void ListCodec::encode(const Variant::List& value, std::string& data) { - _encode<List>(value, data, &toFieldValue); + uint32_t len = qpid::amqp_0_10::encodedSize(value); + std::vector<char> space(len); + qpid::framing::Buffer buff(&space[0], len); + + qpid::amqp_0_10::encode(value, len, buff); + assert( len == buff.getPosition() ); + data.assign(&space[0], len); } void ListCodec::decode(const std::string& data, Variant::List& value) @@ -326,14 +479,47 @@ void ListCodec::decode(const std::string& data, Variant::List& value) size_t ListCodec::encodedSize(const Variant::List& value) { - std::string encoded; - encode(value, encoded); - return encoded.size(); + return qpid::amqp_0_10::encodedSize(value); } void translate(const Variant::Map& from, FieldTable& to) { - convert(from, to, &toFieldTableEntry); + // Create buffer of correct size to encode Variant::Map + uint32_t len = encodedSize(from); + std::vector<char> space(len); + qpid::framing::Buffer buff(&space[0], len); + + // Encode Variant::Map into buffer directly - + // We pass the already calculated length in to avoid + // recalculating it. + encode(from, len, buff); + + // Give buffer to FieldTable + // Could speed this up a bit by avoiding copying + // the buffer we just created into the FieldTable + assert( len == buff.getPosition() ); + buff.reset(); + to.decode(buff); +} + +void translate(const Variant::Map& from, const std::string& efield, const Variant& evalue, FieldTable& to) +{ + // Create buffer of correct size to encode Variant::Map + uint32_t len = encodedSize(from, efield, evalue); + std::vector<char> space(len); + qpid::framing::Buffer buff(&space[0], len); + + // Encode Variant::Map into buffer directly - + // We pass the already calculated length in to avoid + // recalculating it. + encode(from, efield, evalue, len, buff); + + // Give buffer to FieldTable + // Could speed this up a bit by avoiding copying + // the buffer we just created into the FieldTable + assert( len == buff.getPosition() ); + buff.reset(); + to.decode(buff); } void translate(const FieldTable& from, Variant::Map& to) diff --git a/cpp/src/qpid/broker/AclModule.h b/cpp/src/qpid/broker/AclModule.h index ff9281b6fc..7c180439cf 100644 --- a/cpp/src/qpid/broker/AclModule.h +++ b/cpp/src/qpid/broker/AclModule.h @@ -113,6 +113,7 @@ namespace acl { namespace broker { + class Connection; class AclModule { @@ -139,6 +140,11 @@ namespace broker { // Add specialized authorise() methods as required. + /** Approve connection by counting connections total, per-IP, and + * per-user. + */ + virtual bool approveConnection (const Connection& connection)=0; + virtual ~AclModule() {}; }; } // namespace broker diff --git a/cpp/src/qpid/broker/Bridge.cpp b/cpp/src/qpid/broker/Bridge.cpp index 5b531e4636..d1706b5907 100644 --- a/cpp/src/qpid/broker/Bridge.cpp +++ b/cpp/src/qpid/broker/Bridge.cpp @@ -57,22 +57,25 @@ void Bridge::PushHandler::handle(framing::AMQFrame& frame) conn->received(frame); } -Bridge::Bridge(Link* _link, framing::ChannelId _id, CancellationListener l, - const _qmf::ArgsLinkBridge& _args, - InitializeCallback init) : - link(_link), id(_id), args(_args), mgmtObject(0), - listener(l), name(Uuid(true).str()), queueName("qpid.bridge_queue_"), persistenceId(0), - initialize(init), detached(false) +Bridge::Bridge(const std::string& _name, Link* _link, framing::ChannelId _id, + CancellationListener l, const _qmf::ArgsLinkBridge& _args, + InitializeCallback init, const std::string& _queueName, const string& ae) : + link(_link), channel(_id), args(_args), mgmtObject(0), + listener(l), name(_name), + queueName(_queueName.empty() ? "qpid.bridge_queue_" + name + "_" + link->getBroker()->getFederationTag() + : _queueName), + altEx(ae), persistenceId(0), + connState(0), conn(0), initialize(init), detached(false), + useExistingQueue(!_queueName.empty()), + sessionName("qpid.bridge_session_" + name + "_" + link->getBroker()->getFederationTag()) { - std::stringstream title; - title << id << "_" << name; - 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, + (agent, this, link, name, 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); + mgmtObject->set_channelId(channel); agent->addObject(mgmtObject); } QPID_LOG(debug, "Bridge " << name << " created from " << args.i_src << " to " << args.i_dest); @@ -90,23 +93,22 @@ void Bridge::create(Connection& c) conn = &c; FieldTable options; if (args.i_sync) options.setInt("qpid.sync_frequency", args.i_sync); - SessionHandler& sessionHandler = c.getChannel(id); - sessionHandler.setDetachedCallback( - boost::bind(&Bridge::sessionDetached, shared_from_this())); + SessionHandler& sessionHandler = c.getChannel(channel); + sessionHandler.setErrorListener(shared_from_this()); 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())); + channelHandler.reset(new framing::ChannelHandler(channel, pushHandler.get())); session.reset(new framing::AMQP_ServerProxy::Session(*channelHandler)); peer.reset(new framing::AMQP_ServerProxy(*channelHandler)); - session->attach(name, false); + session->attach(sessionName, false); session->commandPoint(0,0); } else { - sessionHandler.attachAs(name); + sessionHandler.attachAs(sessionName); // Point the bridging commands at the remote peer broker peer.reset(new framing::AMQP_ServerProxy(sessionHandler.out)); } @@ -115,7 +117,7 @@ void Bridge::create(Connection& c) if (initialize) initialize(*this, sessionHandler); else 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, 0, args.i_sync ? 2 * args.i_sync : 0xFFFFFFFF); peer->getMessage().flow(args.i_dest, 1, 0xFFFFFFFF); QPID_LOG(debug, "Activated bridge " << name << " for route from queue " << args.i_src << " to " << args.i_dest); } else { @@ -138,12 +140,13 @@ void Bridge::create(Connection& c) } 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); + bool exclusive = !useExistingQueue; // only exclusive if the queue is owned by the bridge + bool autoDelete = exclusive && !durable;//auto delete transient queues? + peer->getQueue().declare(queueName, altEx, false, durable, exclusive, 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().subscribe(queueName, args.i_dest, (useExistingQueue && args.i_sync) ? 0 : 1, 0, false, "", 0, options); + peer->getMessage().flow(args.i_dest, 0, (useExistingQueue && args.i_sync) ? 2 * args.i_sync : 0xFFFFFFFF); peer->getMessage().flow(args.i_dest, 1, 0xFFFFFFFF); if (args.i_dynamic) { @@ -163,11 +166,12 @@ void Bridge::cancel(Connection&) { if (resetProxy()) { peer->getMessage().cancel(args.i_dest); - peer->getSession().detach(name); + peer->getSession().detach(sessionName); } QPID_LOG(debug, "Cancelled bridge " << name); } +/** Notify the bridge that the connection has closed */ void Bridge::closed() { if (args.i_dynamic) { @@ -177,9 +181,10 @@ void Bridge::closed() QPID_LOG(debug, "Closed bridge " << name); } -void Bridge::destroy() +/** Shut down the bridge */ +void Bridge::close() { - listener(this); + listener(this); // ask the LinkRegistry to destroy us } void Bridge::setPersistenceId(uint64_t pId) const @@ -187,8 +192,21 @@ void Bridge::setPersistenceId(uint64_t pId) const persistenceId = pId; } + +const std::string Bridge::ENCODED_IDENTIFIER("bridge.v2"); +const std::string Bridge::ENCODED_IDENTIFIER_V1("bridge"); + +bool Bridge::isEncodedBridge(const std::string& key) +{ + return key == ENCODED_IDENTIFIER || key == ENCODED_IDENTIFIER_V1; +} + + Bridge::shared_ptr Bridge::decode(LinkRegistry& links, Buffer& buffer) { + string kind; + buffer.getShortString(kind); + string host; uint16_t port; string src; @@ -196,9 +214,33 @@ Bridge::shared_ptr Bridge::decode(LinkRegistry& links, Buffer& buffer) string key; string id; string excludes; + string name; + + Link::shared_ptr link; + if (kind == ENCODED_IDENTIFIER_V1) { + /** previous versions identified the bridge by host:port, not by name, and + * transport wasn't provided. Try to find a link using those paramters. + */ + buffer.getShortString(host); + port = buffer.getShort(); + + link = links.getLink(host, port); + if (!link) { + QPID_LOG(error, "Bridge::decode() failed: cannot find Link for host=" << host << ", port=" << port); + return Bridge::shared_ptr(); + } + } else { + string linkName; + + buffer.getShortString(name); + buffer.getShortString(linkName); + link = links.getLink(linkName); + if (!link) { + QPID_LOG(error, "Bridge::decode() failed: cannot find Link named='" << linkName << "'"); + return Bridge::shared_ptr(); + } + } - buffer.getShortString(host); - port = buffer.getShort(); bool durable(buffer.getOctet()); buffer.getShortString(src); buffer.getShortString(dest); @@ -210,15 +252,21 @@ Bridge::shared_ptr Bridge::decode(LinkRegistry& links, Buffer& buffer) 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; + if (kind == ENCODED_IDENTIFIER_V1) { + /** previous versions did not provide a name for the bridge, so create one + */ + name = createName(link->getName(), src, dest, key); + } + + return links.declare(name, *link, 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.putShortString(ENCODED_IDENTIFIER); + buffer.putShortString(name); + buffer.putShortString(link->getName()); buffer.putOctet(args.i_durable ? 1 : 0); buffer.putShortString(args.i_src); buffer.putShortString(args.i_dest); @@ -233,9 +281,9 @@ void Bridge::encode(Buffer& buffer) const uint32_t Bridge::encodedSize() const { - return link->getHost().size() + 1 // short-string (host) - + 7 // short-string ("bridge") - + 2 // port + return ENCODED_IDENTIFIER.size() + 1 // +1 byte length + + name.size() + 1 + + link->getName().size() + 1 + 1 // durable + args.i_src.size() + 1 + args.i_dest.size() + 1 @@ -259,7 +307,8 @@ management::Manageable::status_t Bridge::ManagementMethod(uint32_t methodId, { if (methodId == _qmf::Bridge::METHOD_CLOSE) { //notify that we are closed - destroy(); + QPID_LOG(debug, "Bridge::close() method called on bridge '" << name << "'"); + close(); return management::Manageable::STATUS_OK; } else { return management::Manageable::STATUS_UNKNOWN_METHOD; @@ -306,7 +355,7 @@ void Bridge::sendReorigin() } bool Bridge::resetProxy() { - SessionHandler& sessionHandler = conn->getChannel(id); + SessionHandler& sessionHandler = conn->getChannel(channel); if (!sessionHandler.getSession()) peer.reset(); else peer.reset(new framing::AMQP_ServerProxy(sessionHandler.out)); return peer.get(); @@ -318,7 +367,7 @@ void Bridge::ioThreadPropagateBinding(const string& queue, const string& exchang 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(); + close(); } } @@ -333,8 +382,38 @@ const string& Bridge::getLocalTag() const return link->getBroker()->getFederationTag(); } -void Bridge::sessionDetached() { +// SessionHandler::ErrorListener methods. +void Bridge::connectionException( + framing::connection::CloseCode code, const std::string& msg) +{ + if (errorListener) errorListener->connectionException(code, msg); +} + +void Bridge::channelException( + framing::session::DetachCode code, const std::string& msg) +{ + if (errorListener) errorListener->channelException(code, msg); +} + +void Bridge::executionException( + framing::execution::ErrorCode code, const std::string& msg) +{ + if (errorListener) errorListener->executionException(code, msg); +} + +void Bridge::detach() { detached = true; + if (errorListener) errorListener->detach(); +} + +std::string Bridge::createName(const std::string& linkName, + const std::string& src, + const std::string& dest, + const std::string& key) +{ + std::stringstream keystream; + keystream << linkName << "!" << src << "!" << dest << "!" << key; + return keystream.str(); } }} diff --git a/cpp/src/qpid/broker/Bridge.h b/cpp/src/qpid/broker/Bridge.h index 32b9fd1781..ee298afd45 100644 --- a/cpp/src/qpid/broker/Bridge.h +++ b/cpp/src/qpid/broker/Bridge.h @@ -29,6 +29,7 @@ #include "qpid/framing/FieldTable.h" #include "qpid/management/Manageable.h" #include "qpid/broker/Exchange.h" +#include "qpid/broker/SessionHandler.h" #include "qmf/org/apache/qpid/broker/ArgsLinkBridge.h" #include "qmf/org/apache/qpid/broker/Bridge.h" @@ -43,29 +44,31 @@ class Connection; class ConnectionState; class Link; class LinkRegistry; -class SessionHandler; class Bridge : public PersistableConfig, public management::Manageable, public Exchange::DynamicBridge, + public SessionHandler::ErrorListener, public boost::enable_shared_from_this<Bridge> { -public: + public: typedef boost::shared_ptr<Bridge> shared_ptr; typedef boost::function<void(Bridge*)> CancellationListener; typedef boost::function<void(Bridge&, SessionHandler&)> InitializeCallback; - Bridge(Link* link, framing::ChannelId id, CancellationListener l, + Bridge(const std::string& name, Link* link, framing::ChannelId id, CancellationListener l, const qmf::org::apache::qpid::broker::ArgsLinkBridge& args, - InitializeCallback init + InitializeCallback init, const std::string& queueName="", + const std::string& altExchange="" ); ~Bridge(); - void create(Connection& c); - void cancel(Connection& c); - void closed(); - void destroy(); + QPID_BROKER_EXTERN void close(); bool isDurable() { return args.i_durable; } + Link *getLink() const { return link; } + const std::string getSrc() const { return args.i_src; } + const std::string getDest() const { return args.i_dest; } + const std::string getKey() const { return args.i_key; } bool isDetached() const { return detached; } @@ -80,7 +83,11 @@ public: uint32_t encodedSize() const; void encode(framing::Buffer& buffer) const; const std::string& getName() const { return name; } + + static const std::string ENCODED_IDENTIFIER; + static const std::string ENCODED_IDENTIFIER_V1; static Bridge::shared_ptr decode(LinkRegistry& links, framing::Buffer& buffer); + static bool isEncodedBridge(const std::string& key); // 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); @@ -93,10 +100,20 @@ public: std::string getQueueName() const { return queueName; } const qmf::org::apache::qpid::broker::ArgsLinkBridge& getArgs() { return args; } -private: - // Callback when the bridge's session is detached. - void sessionDetached(); + /** create a name for a bridge (if none supplied by user config) */ + static std::string createName(const std::string& linkName, + const std::string& src, + const std::string& dest, + const std::string& key); + + // SessionHandler::ErrorListener methods. + void connectionException(framing::connection::CloseCode code, const std::string& msg); + void channelException(framing::session::DetachCode, const std::string& msg); + void executionException(framing::execution::ErrorCode, const std::string& msg); + void detach(); + void setErrorListener(boost::shared_ptr<ErrorListener> e) { errorListener = e; } + private: struct PushHandler : framing::FrameHandler { PushHandler(Connection* c) { conn = c; } void handle(framing::AMQFrame& frame); @@ -108,19 +125,30 @@ private: std::auto_ptr<framing::AMQP_ServerProxy::Session> session; std::auto_ptr<framing::AMQP_ServerProxy> peer; - Link* link; - framing::ChannelId id; + Link* const link; + const framing::ChannelId channel; qmf::org::apache::qpid::broker::ArgsLinkBridge args; qmf::org::apache::qpid::broker::Bridge* mgmtObject; CancellationListener listener; std::string name; std::string queueName; + std::string altEx; mutable uint64_t persistenceId; ConnectionState* connState; Connection* conn; InitializeCallback initialize; bool detached; // Set when session is detached. bool resetProxy(); + + // connection Management (called by owning Link) + void create(Connection& c); + void cancel(Connection& c); + void closed(); + friend class Link; // to call create, cancel, closed() + boost::shared_ptr<ErrorListener> errorListener; + + const bool useExistingQueue; + const std::string sessionName; }; diff --git a/cpp/src/qpid/broker/Broker.cpp b/cpp/src/qpid/broker/Broker.cpp index f20cce18a2..b763dd4119 100644 --- a/cpp/src/qpid/broker/Broker.cpp +++ b/cpp/src/qpid/broker/Broker.cpp @@ -108,7 +108,6 @@ Broker::Options::Options(const std::string& name) : noDataDir(0), port(DEFAULT_PORT), workerThreads(5), - maxConnections(500), connectionBacklog(10), enableMgmt(1), mgmtPublish(1), @@ -128,8 +127,10 @@ Broker::Options::Options(const std::string& name) : queueFlowResumeRatio(70), queueThresholdEventRatio(80), defaultMsgGroup("qpid.no-group"), - timestampRcvMsgs(false), // set the 0.10 timestamp delivery property - linkMaintenanceInterval(2) + timestampRcvMsgs(false), // set the 0.10 timestamp delivery property + linkMaintenanceInterval(2), + linkHeartbeatInterval(120), + maxNegotiateTime(2000) // 2s { int c = sys::SystemInfo::concurrency(); workerThreads=c+1; @@ -146,7 +147,6 @@ Broker::Options::Options(const std::string& name) : ("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-publish", optValue(mgmtPublish,"yes|no"), "Enable Publish of Management Data ('no' implies query-only)") @@ -171,6 +171,9 @@ Broker::Options::Options(const std::string& name) : ("default-message-group", optValue(defaultMsgGroup, "GROUP-IDENTIFER"), "Group identifier to assign to messages delivered to a message group queue that do not contain an identifier.") ("enable-timestamp", optValue(timestampRcvMsgs, "yes|no"), "Add current time to each received message.") ("link-maintenace-interval", optValue(linkMaintenanceInterval, "SECONDS")) + ("link-heartbeat-interval", optValue(linkHeartbeatInterval, "SECONDS")) + ("max-negotiate-time", optValue(maxNegotiateTime, "MilliSeconds"), "Maximum time a connection can take to send the initial protocol negotiation") + ("federation-tag", optValue(fedTag, "NAME"), "Override the federation tag") ; } @@ -208,7 +211,6 @@ Broker::Broker(const Broker::Options& conf) : inCluster(false), clusterUpdatee(false), expiryPolicy(new ExpiryPolicy), - connectionCounter(conf.maxConnections), getKnownBrokers(boost::bind(&Broker::getKnownBrokersImpl, this)), deferDelivery(boost::bind(&Broker::deferDeliveryImpl, this, _1, _2)) { @@ -227,7 +229,6 @@ Broker::Broker(const Broker::Options& conf) : 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_mgmtPublish(conf.mgmtPublish); @@ -244,8 +245,11 @@ Broker::Broker(const Broker::Options& conf) : // management schema correct. Vhost* vhost = new Vhost(this, this); vhostObject = Vhost::shared_ptr(vhost); - framing::Uuid uuid(managementAgent->getUuid()); - federationTag = uuid.str(); + if (conf.fedTag.empty()) { + framing::Uuid uuid(managementAgent->getUuid()); + federationTag = uuid.str(); + } else + federationTag = conf.fedTag; vhostObject->setFederationTag(federationTag); queues.setParent(vhost); @@ -254,8 +258,11 @@ Broker::Broker(const Broker::Options& conf) : } 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(); + if (conf.fedTag.empty()) { + framing::Uuid uuid(true); + federationTag = uuid.str(); + } else + federationTag = conf.fedTag; } QueuePolicy::setDefaultMaxSize(conf.queueLimit); @@ -346,7 +353,7 @@ Broker::Broker(const Broker::Options& conf) : knownBrokers.push_back(Url(conf.knownHosts)); } - } catch (const std::exception& /*e*/) { + } catch (const std::exception&) { finalize(); throw; } @@ -443,7 +450,7 @@ Manageable* Broker::GetVhostObject(void) const Manageable::status_t Broker::ManagementMethod (uint32_t methodId, Args& args, - string&) + string& text) { Manageable::status_t status = Manageable::STATUS_UNKNOWN_METHOD; @@ -458,6 +465,14 @@ Manageable::status_t Broker::ManagementMethod (uint32_t methodId, status = Manageable::STATUS_OK; break; case _qmf::Broker::METHOD_CONNECT : { + /** Management is creating a Link to a remote broker using the host and port of + * the remote. This (old) interface does not allow management to specify a name + * for the link, nor does it allow multiple Links to the same remote. Use the + * "create()" broker method if these features are needed. + * TBD: deprecate this interface. + */ + QPID_LOG(info, "The Broker::connect() method will be removed in a future release of QPID." + " Please use the Broker::create() method with type='link' instead."); _qmf::ArgsBrokerConnect& hp= dynamic_cast<_qmf::ArgsBrokerConnect&>(args); @@ -466,13 +481,24 @@ Manageable::status_t Broker::ManagementMethod (uint32_t methodId, "; durable=" << (hp.i_durable?"T":"F") << "; authMech=\"" << hp.i_authMechanism << "\""); if (!getProtocolFactory(transport)) { QPID_LOG(error, "Transport '" << transport << "' not supported"); + text = "transport type 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); + + // Does a link to the remote already exist? If so, re-use the existing link + // - this behavior is backward compatible with previous releases. + if (!links.getLink(hp.i_host, hp.i_port, transport)) { + // new link, need to generate a unique name for it + std::pair<Link::shared_ptr, bool> response = + links.declare(Link::createName(transport, hp.i_host, hp.i_port), + hp.i_host, hp.i_port, transport, + hp.i_durable, hp.i_authMechanism, hp.i_username, hp.i_password); + if (!response.first) { + text = "Unable to create Link"; + status = Manageable::STATUS_PARAMETER_INVALID; + break; + } + } status = Manageable::STATUS_OK; break; } @@ -543,6 +569,8 @@ 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 TYPE_LINK("link"); +const std::string TYPE_BRIDGE("bridge"); const std::string DURABLE("durable"); const std::string AUTO_DELETE("auto-delete"); const std::string ALTERNATE_EXCHANGE("alternate-exchange"); @@ -554,6 +582,26 @@ const std::string ATTRIBUTE_TIMESTAMP_0_10("timestamp-0.10"); const std::string _TRUE("true"); const std::string _FALSE("false"); + +// parameters for creating a Link object, see mgmt schema +const std::string HOST("host"); +const std::string PORT("port"); +const std::string TRANSPORT("transport"); +const std::string AUTH_MECHANISM("authMechanism"); +const std::string USERNAME("username"); +const std::string PASSWORD("password"); + +// parameters for creating a Bridge object, see mgmt schema +const std::string LINK("link"); +const std::string SRC("src"); +const std::string DEST("dest"); +const std::string KEY("key"); +const std::string TAG("tag"); +const std::string EXCLUDES("excludes"); +const std::string SRC_IS_QUEUE("srcIsQueue"); +const std::string SRC_IS_LOCAL("srcIsLocal"); +const std::string DYNAMIC("dynamic"); +const std::string SYNC("sync"); } struct InvalidBindingIdentifier : public qpid::Exception @@ -603,6 +651,25 @@ struct UnknownObjectType : public qpid::Exception std::string getPrefix() const { return "unknown object type"; } }; +struct ReservedObjectName : public qpid::Exception +{ + ReservedObjectName(const std::string& type) : qpid::Exception(type) {} + std::string getPrefix() const { return std::string("names prefixed with '") + + QPID_NAME_PREFIX + std::string("' are reserved"); } +}; + +struct UnsupportedTransport : public qpid::Exception +{ + UnsupportedTransport(const std::string& type) : qpid::Exception(type) {} + std::string getPrefix() const { return "transport is not supported"; } +}; + +struct InvalidParameter : public qpid::Exception +{ + InvalidParameter(const std::string& type) : qpid::Exception(type) {} + std::string getPrefix() const { return "invalid parameter to method call"; } +}; + void Broker::createObject(const std::string& type, const std::string& name, const Variant::Map& properties, bool /*strict*/, const ConnectionState* context) { @@ -674,6 +741,113 @@ void Broker::createObject(const std::string& type, const std::string& name, amqp_0_10::translate(extensions, arguments); bind(binding.queue, binding.exchange, binding.key, arguments, userId, connectionId); + + } else if (type == TYPE_LINK) { + + QPID_LOG (debug, "createObject: Link; name=" << name << "; args=" << properties ); + + if (name.compare(0, QPID_NAME_PREFIX.length(), QPID_NAME_PREFIX) == 0) { + QPID_LOG(error, "Link name='" << name << "' cannot use the reserved prefix '" << QPID_NAME_PREFIX << "'"); + throw ReservedObjectName(name); + } + + std::string host; + uint16_t port = 0; + std::string transport = TCP_TRANSPORT; + bool durable = false; + std::string authMech, username, password; + + for (Variant::Map::const_iterator i = properties.begin(); i != properties.end(); ++i) { + if (i->first == HOST) host = i->second.asString(); + else if (i->first == PORT) port = i->second.asUint16(); + else if (i->first == TRANSPORT) transport = i->second.asString(); + else if (i->first == DURABLE) durable = bool(i->second); + else if (i->first == AUTH_MECHANISM) authMech = i->second.asString(); + else if (i->first == USERNAME) username = i->second.asString(); + else if (i->first == PASSWORD) password = i->second.asString(); + else { + // TODO: strict checking here + } + } + + if (!getProtocolFactory(transport)) { + QPID_LOG(error, "Transport '" << transport << "' not supported."); + throw UnsupportedTransport(transport); + } + + std::pair<boost::shared_ptr<Link>, bool> rc; + rc = links.declare(name, host, port, transport, durable, authMech, username, password); + if (!rc.first) { + QPID_LOG (error, "Failed to create Link object, name=" << name << " remote=" << host << ":" << port << + "; transport=" << transport << "; durable=" << (durable?"T":"F") << "; authMech=\"" << authMech << "\""); + throw InvalidParameter(name); + } + if (!rc.second) { + QPID_LOG (error, "Failed to create a new Link object, name=" << name << " already exists."); + throw ObjectAlreadyExists(name); + } + + } else if (type == TYPE_BRIDGE) { + + QPID_LOG (debug, "createObject: Bridge; name=" << name << "; args=" << properties ); + + if (name.compare(0, QPID_NAME_PREFIX.length(), QPID_NAME_PREFIX) == 0) { + QPID_LOG(error, "Bridge name='" << name << "' cannot use the reserved prefix '" << QPID_NAME_PREFIX << "'"); + throw ReservedObjectName(name); + } + + std::string linkName; + std::string src; + std::string dest; + std::string key; + std::string id; + std::string excludes; + std::string queueName; + bool durable = false; + bool srcIsQueue = false; + bool srcIsLocal = false; + bool dynamic = false; + uint16_t sync = 0; + + for (Variant::Map::const_iterator i = properties.begin(); i != properties.end(); ++i) { + + if (i->first == LINK) linkName = i->second.asString(); + else if (i->first == SRC) src = i->second.asString(); + else if (i->first == DEST) dest = i->second.asString(); + else if (i->first == KEY) key = i->second.asString(); + else if (i->first == TAG) id = i->second.asString(); + else if (i->first == EXCLUDES) excludes = i->second.asString(); + else if (i->first == SRC_IS_QUEUE) srcIsQueue = bool(i->second); + else if (i->first == SRC_IS_LOCAL) srcIsLocal = bool(i->second); + else if (i->first == DYNAMIC) dynamic = bool(i->second); + else if (i->first == SYNC) sync = i->second.asUint16(); + else if (i->first == DURABLE) durable = bool(i->second); + else if (i->first == QUEUE_NAME) queueName = i->second.asString(); + else { + // TODO: strict checking here + } + } + + boost::shared_ptr<Link> link; + if (linkName.empty() || !(link = links.getLink(linkName))) { + QPID_LOG(error, "Link '" << linkName << "' not found; bridge create failed."); + throw InvalidParameter(name); + } + std::pair<Bridge::shared_ptr, bool> rc = + links.declare(name, *link, durable, src, dest, key, srcIsQueue, srcIsLocal, id, excludes, + dynamic, sync, + 0, + queueName); + + if (!rc.first) { + QPID_LOG (error, "Failed to create Bridge object, name=" << name << " link=" << linkName << + "; src=" << src << "; dest=" << dest << "; key=" << key); + throw InvalidParameter(name); + } + if (!rc.second) { + QPID_LOG (error, "Failed to create a new Bridge object, name=" << name << " already exists."); + throw ObjectAlreadyExists(name); + } } else { throw UnknownObjectType(type); } @@ -696,6 +870,16 @@ void Broker::deleteObject(const std::string& type, const std::string& name, } else if (type == TYPE_BINDING) { BindingIdentifier binding(name); unbind(binding.queue, binding.exchange, binding.key, userId, connectionId); + } else if (type == TYPE_LINK) { + boost::shared_ptr<Link> link = links.getLink(name); + if (link) { + link->close(); + } + } else if (type == TYPE_BRIDGE) { + boost::shared_ptr<Bridge> bridge = links.getBridge(name); + if (bridge) { + bridge->close(); + } } else { throw UnknownObjectType(type); } @@ -920,6 +1104,13 @@ std::pair<boost::shared_ptr<Queue>, bool> Broker::createQueue( ManagementAgent::toMap(arguments), "created")); } + QPID_LOG_CAT(debug, model, "Create queue. name:" << name + << " user:" << userId + << " rhost:" << connectionId + << " durable:" << (durable ? "T" : "F") + << " owner:" << owner + << " autodelete:" << (autodelete ? "T" : "F") + << " alternateExchange:" << alternateExchange ); } return result; } @@ -942,6 +1133,10 @@ void Broker::deleteQueue(const std::string& name, const std::string& userId, if (managementAgent.get()) managementAgent->raiseEvent(_qmf::EventQueueDelete(connectionId, userId, name)); + QPID_LOG_CAT(debug, model, "Delete queue. name:" << name + << " user:" << userId + << " rhost:" << connectionId + ); } @@ -993,6 +1188,12 @@ std::pair<Exchange::shared_ptr, bool> Broker::createExchange( ManagementAgent::toMap(arguments), "created")); } + QPID_LOG_CAT(debug, model, "Create exchange. name:" << name + << " user:" << userId + << " rhost:" << connectionId + << " type:" << type + << " alternateExchange:" << alternateExchange + << " durable:" << (durable ? "T" : "F")); } return result; } @@ -1017,7 +1218,9 @@ void Broker::deleteExchange(const std::string& name, const std::string& userId, if (managementAgent.get()) managementAgent->raiseEvent(_qmf::EventExchangeDelete(connectionId, userId, name)); - + QPID_LOG_CAT(debug, model, "Delete exchange. name:" << name + << " user:" << userId + << " rhost:" << connectionId); } void Broker::bind(const std::string& queueName, @@ -1047,10 +1250,16 @@ void Broker::bind(const std::string& queueName, throw framing::NotFoundException(QPID_MSG("Bind failed. No such exchange: " << exchangeName)); } else { if (queue->bind(exchange, key, arguments)) { + getConfigurationObservers().bind(exchange, queue, key, arguments); if (managementAgent.get()) { managementAgent->raiseEvent(_qmf::EventBind(connectionId, userId, exchangeName, queueName, key, ManagementAgent::toMap(arguments))); } + QPID_LOG_CAT(debug, model, "Create binding. exchange:" << exchangeName + << " queue:" << queueName + << " key:" << key + << " user:" << userId + << " rhost:" << connectionId); } } } @@ -1082,12 +1291,33 @@ void Broker::unbind(const std::string& queueName, if (exchange->isDurable() && queue->isDurable()) { store->unbind(*exchange, *queue, key, qpid::framing::FieldTable()); } + getConfigurationObservers().unbind( + exchange, queue, key, framing::FieldTable()); if (managementAgent.get()) { managementAgent->raiseEvent(_qmf::EventUnbind(connectionId, userId, exchangeName, queueName, key)); } + QPID_LOG_CAT(debug, model, "Delete binding. exchange:" << exchangeName + << " queue:" << queueName + << " key:" << key + << " user:" << userId + << " rhost:" << connectionId); } } } +// FIXME aconway 2012-04-27: access to linkClientProperties is +// not properly thread safe, you could lose fields if 2 threads +// attempt to add a field concurrently. + +framing::FieldTable Broker::getLinkClientProperties() const { + sys::Mutex::ScopedLock l(linkClientPropertiesLock); + return linkClientProperties; +} + +void Broker::setLinkClientProperties(const framing::FieldTable& ft) { + sys::Mutex::ScopedLock l(linkClientPropertiesLock); + linkClientProperties = ft; +} + }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/Broker.h b/cpp/src/qpid/broker/Broker.h index 135b9340f9..922d0558e5 100644 --- a/cpp/src/qpid/broker/Broker.h +++ b/cpp/src/qpid/broker/Broker.h @@ -40,6 +40,7 @@ #include "qpid/broker/ExpiryPolicy.h" #include "qpid/broker/ConsumerFactory.h" #include "qpid/broker/ConnectionObservers.h" +#include "qpid/broker/ConfigurationObservers.h" #include "qpid/management/Manageable.h" #include "qpid/management/ManagementAgent.h" #include "qmf/org/apache/qpid/broker/Broker.h" @@ -64,8 +65,8 @@ namespace qpid { namespace sys { - class ProtocolFactory; - class Poller; +class ProtocolFactory; +class Poller; } struct Url; @@ -91,7 +92,7 @@ class Broker : public sys::Runnable, public Plugin::Target, public management::Manageable, public RefCounted { -public: + public: struct Options : public qpid::Options { static const std::string DEFAULT_DATA_DIR_LOCATION; @@ -103,7 +104,6 @@ public: std::string dataDir; uint16_t port; int workerThreads; - int maxConnections; int connectionBacklog; bool enableMgmt; bool mgmtPublish; @@ -127,31 +127,14 @@ public: std::string defaultMsgGroup; bool timestampRcvMsgs; double linkMaintenanceInterval; // FIXME aconway 2012-02-13: consistent parsing of SECONDS values. + uint16_t linkHeartbeatInterval; + uint32_t maxNegotiateTime; // Max time in ms for connection with no negotiation + std::string fedTag; 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; @@ -183,6 +166,7 @@ public: AclModule* acl; DataDir dataDir; ConnectionObservers connectionObservers; + ConfigurationObservers configurationObservers; QueueRegistry queues; ExchangeRegistry exchanges; @@ -203,9 +187,11 @@ public: bool recovery; bool inCluster, clusterUpdatee; boost::intrusive_ptr<ExpiryPolicy> expiryPolicy; - ConnectionCounter connectionCounter; ConsumerFactories consumerFactories; + mutable sys::Mutex linkClientPropertiesLock; + framing::FieldTable linkClientProperties; + public: QPID_BROKER_EXTERN virtual ~Broker(); @@ -317,8 +303,6 @@ public: 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 @@ -377,6 +361,14 @@ public: ConsumerFactories& getConsumerFactories() { return consumerFactories; } ConnectionObservers& getConnectionObservers() { return connectionObservers; } + ConfigurationObservers& getConfigurationObservers() { return configurationObservers; } + + /** Properties to be set on outgoing link connections */ + QPID_BROKER_EXTERN framing::FieldTable getLinkClientProperties() const; + QPID_BROKER_EXTERN void setLinkClientProperties(const framing::FieldTable&); + + /** Information identifying this system */ + boost::shared_ptr<const System> getSystem() const { return systemObject; } }; }} diff --git a/cpp/src/qpid/broker/ConfigurationObserver.h b/cpp/src/qpid/broker/ConfigurationObserver.h new file mode 100644 index 0000000000..701043db40 --- /dev/null +++ b/cpp/src/qpid/broker/ConfigurationObserver.h @@ -0,0 +1,61 @@ +#ifndef QPID_BROKER_CONFIGURATIONOBSERVER_H +#define QPID_BROKER_CONFIGURATIONOBSERVER_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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 framing { +class FieldTable; +} + +namespace broker { +class Queue; +class Exchange; + + +/** + * Observer for changes to configuration (aka wiring) + */ +class ConfigurationObserver +{ + public: + virtual ~ConfigurationObserver() {} + virtual void queueCreate(const boost::shared_ptr<Queue>&) {} + virtual void queueDestroy(const boost::shared_ptr<Queue>&) {} + virtual void exchangeCreate(const boost::shared_ptr<Exchange>&) {} + virtual void exchangeDestroy(const boost::shared_ptr<Exchange>&) {} + virtual void bind(const boost::shared_ptr<Exchange>& , + const boost::shared_ptr<Queue>& , + const std::string& /*key*/, + const framing::FieldTable& /*args*/) {} + virtual void unbind(const boost::shared_ptr<Exchange>&, + const boost::shared_ptr<Queue>& , + const std::string& /*key*/, + const framing::FieldTable& /*args*/) {} +}; +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_CONFIGURATIONOBSERVER_H*/ diff --git a/cpp/src/qpid/broker/ConfigurationObservers.h b/cpp/src/qpid/broker/ConfigurationObservers.h new file mode 100644 index 0000000000..4c1159747d --- /dev/null +++ b/cpp/src/qpid/broker/ConfigurationObservers.h @@ -0,0 +1,72 @@ +#ifndef QPID_BROKER_CONFIGURATIONOBSERVERS_H +#define QPID_BROKER_CONFIGURATIONOBSERVERS_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "ConfigurationObserver.h" +#include "Observers.h" +#include "qpid/sys/Mutex.h" + +namespace qpid { +namespace broker { + +/** + * A configuration observer that delegates to a collection of + * configuration observers. + * + * THREAD SAFE + */ +class ConfigurationObservers : public ConfigurationObserver, + public Observers<ConfigurationObserver> +{ + public: + void queueCreate(const boost::shared_ptr<Queue>& q) { + each(boost::bind(&ConfigurationObserver::queueCreate, _1, q)); + } + void queueDestroy(const boost::shared_ptr<Queue>& q) { + each(boost::bind(&ConfigurationObserver::queueDestroy, _1, q)); + } + void exchangeCreate(const boost::shared_ptr<Exchange>& e) { + each(boost::bind(&ConfigurationObserver::exchangeCreate, _1, e)); + } + void exchangeDestroy(const boost::shared_ptr<Exchange>& e) { + each(boost::bind(&ConfigurationObserver::exchangeDestroy, _1, e)); + } + void bind(const boost::shared_ptr<Exchange>& exchange, + const boost::shared_ptr<Queue>& queue, + const std::string& key, + const framing::FieldTable& args) { + each(boost::bind( + &ConfigurationObserver::bind, _1, exchange, queue, key, args)); + } + void unbind(const boost::shared_ptr<Exchange>& exchange, + const boost::shared_ptr<Queue>& queue, + const std::string& key, + const framing::FieldTable& args) { + each(boost::bind( + &ConfigurationObserver::unbind, _1, exchange, queue, key, args)); + } +}; + +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_CONFIGURATIONOBSERVERS_H*/ diff --git a/cpp/src/qpid/broker/Connection.cpp b/cpp/src/qpid/broker/Connection.cpp index 5e339cec03..8d250a32e5 100644 --- a/cpp/src/qpid/broker/Connection.cpp +++ b/cpp/src/qpid/broker/Connection.cpp @@ -43,7 +43,7 @@ #include <iostream> #include <assert.h> - +using std::string; using namespace qpid::sys; using namespace qpid::framing; @@ -87,10 +87,14 @@ Connection::Connection(ConnectionOutputHandler* out_, bool link_, uint64_t objectId_, bool shadow_, - bool delayManagement) : + bool delayManagement, + bool authenticated_ +) : ConnectionState(out_, broker_), securitySettings(external), - adapter(*this, link_, shadow_), + shadow(shadow_), + authenticated(authenticated_), + adapter(*this, link_), link(link_), mgmtClosing(false), mgmtId(mgmtId_), @@ -100,14 +104,12 @@ Connection::Connection(ConnectionOutputHandler* out_, timer(broker_.getTimer()), errorListener(0), objectId(objectId_), - shadow(shadow_), outboundTracker(*this) { outboundTracker.wrap(out); broker.getConnectionObservers().connection(*this); // In a cluster, allow adding the management object to be delayed. if (!delayManagement) addManagementObject(); - if (!isShadow()) broker.getConnectionCounter().inc_connectionCount(); } void Connection::addManagementObject() { @@ -141,6 +143,8 @@ Connection::~Connection() // a cluster-unsafe context. Don't raise an event in that case. if (!link && isClusterSafe()) agent->raiseEvent(_qmf::EventClientDisconnect(mgmtId, ConnectionState::getUserId())); + QPID_LOG_CAT(debug, model, "Delete connection. user:" << ConnectionState::getUserId() + << " rhost:" << mgmtId ); } broker.getConnectionObservers().closed(*this); @@ -148,8 +152,9 @@ Connection::~Connection() heartbeatTimer->cancel(); if (timeoutTimer) timeoutTimer->cancel(); - - if (!isShadow()) broker.getConnectionCounter().dec_connectionCount(); + if (linkHeartbeatTimer) { + linkHeartbeatTimer->cancel(); + } } void Connection::received(framing::AMQFrame& frame) { @@ -284,6 +289,10 @@ void Connection::raiseConnectEvent() { mgmtObject->set_authIdentity(userId); agent->raiseEvent(_qmf::EventClientConnect(mgmtId, userId)); } + + QPID_LOG_CAT(debug, model, "Create connection. user:" << userId + << " rhost:" << mgmtId ); + } void Connection::setUserProxyAuth(bool b) @@ -300,6 +309,9 @@ void Connection::close(connection::CloseCode code, const string& text) heartbeatTimer->cancel(); if (timeoutTimer) timeoutTimer->cancel(); + if (linkHeartbeatTimer) { + linkHeartbeatTimer->cancel(); + } adapter.close(code, text); //make sure we delete dangling pointers from outputTasks before deleting sessions outputTasks.removeAll(); @@ -313,6 +325,9 @@ void Connection::sendClose() { heartbeatTimer->cancel(); if (timeoutTimer) timeoutTimer->cancel(); + if (linkHeartbeatTimer) { + linkHeartbeatTimer->cancel(); + } adapter.close(connection::CLOSE_CODE_NORMAL, "OK"); getOutput().close(); } @@ -326,6 +341,9 @@ void Connection::closed(){ // Physically closed, suspend open sessions. heartbeatTimer->cancel(); if (timeoutTimer) timeoutTimer->cancel(); + if (linkHeartbeatTimer) { + linkHeartbeatTimer->cancel(); + } try { while (!channels.empty()) ptr_map_ptr(channels.begin())->handleDetach(); @@ -435,6 +453,31 @@ struct ConnectionHeartbeatTask : public sys::TimerTask { } }; +class LinkHeartbeatTask : public qpid::sys::TimerTask { + sys::Timer& timer; + Connection& connection; + bool heartbeatSeen; + + void fire() { + if (!heartbeatSeen) { + QPID_LOG(error, "Federation link connection " << connection.getMgmtId() << " missed 2 heartbeats - closing connection"); + connection.abort(); + } else { + heartbeatSeen = false; + // Setup next firing + setupNextFire(); + timer.add(this); + } + } + +public: + LinkHeartbeatTask(sys::Timer& t, qpid::sys::Duration period, Connection& c) : + TimerTask(period, "LinkHeartbeatTask"), timer(t), connection(c), heartbeatSeen(false) {} + + void heartbeatReceived() { heartbeatSeen = true; } +}; + + void Connection::abort() { // Make sure that we don't try to send a heartbeat as we're @@ -460,10 +503,21 @@ void Connection::setHeartbeatInterval(uint16_t heartbeat) } } +void Connection::startLinkHeartbeatTimeoutTask() { + if (!linkHeartbeatTimer && heartbeat > 0) { + linkHeartbeatTimer = new LinkHeartbeatTask(timer, 2 * heartbeat * TIME_SEC, *this); + timer.add(linkHeartbeatTimer); + } +} + void Connection::restartTimeout() { if (timeoutTimer) timeoutTimer->touch(); + + if (linkHeartbeatTimer) { + static_cast<LinkHeartbeatTask*>(linkHeartbeatTimer.get())->heartbeatReceived(); + } } bool Connection::isOpen() { return adapter.isOpen(); } diff --git a/cpp/src/qpid/broker/Connection.h b/cpp/src/qpid/broker/Connection.h index 1b8bd83139..d01599ce54 100644 --- a/cpp/src/qpid/broker/Connection.h +++ b/cpp/src/qpid/broker/Connection.h @@ -27,8 +27,7 @@ #include <vector> #include <queue> -#include <boost/ptr_container/ptr_map.hpp> - +#include "qpid/broker/BrokerImportExport.h" #include "qpid/broker/ConnectionHandler.h" #include "qpid/broker/ConnectionState.h" #include "qpid/broker/SessionHandler.h" @@ -86,15 +85,22 @@ class Connection : public sys::ConnectionInputHandler, bool isLink = false, uint64_t objectId = 0, bool shadow=false, - bool delayManagement = false); + bool delayManagement = false, + bool authenticated=true); ~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); + /** Close the connection. Waits for the client to respond with close-ok + * before actually destroying the connection. + */ + QPID_BROKER_EXTERN void close( + framing::connection::CloseCode code, const std::string& text); + + /** Abort the connection. Close abruptly and immediately. */ + QPID_BROKER_EXTERN void abort(); // ConnectionInputHandler methods void received(framing::AMQFrame& frame); @@ -138,8 +144,7 @@ class Connection : public sys::ConnectionInputHandler, 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)); @@ -149,7 +154,10 @@ class Connection : public sys::ConnectionInputHandler, void setSecureConnection(SecureConnection* secured); /** True if this is a shadow connection in a cluster. */ - bool isShadow() { return shadow; } + bool isShadow() const { return shadow; } + + /** True if this connection is authenticated */ + bool isAuthenticated() const { return authenticated; } // Used by cluster to update connection status sys::AggregateOutput& getOutputTasks() { return outputTasks; } @@ -166,6 +174,7 @@ class Connection : public sys::ConnectionInputHandler, bool isOpen(); bool isLink() { return link; } + void startLinkHeartbeatTimeoutTask(); // Used by cluster during catch-up, see cluster::OutputInterceptor void doIoCallbacks(); @@ -179,6 +188,8 @@ class Connection : public sys::ConnectionInputHandler, ChannelMap channels; qpid::sys::SecuritySettings securitySettings; + bool shadow; + bool authenticated; ConnectionHandler adapter; const bool link; bool mgmtClosing; @@ -189,11 +200,10 @@ class Connection : public sys::ConnectionInputHandler, LinkRegistry& links; management::ManagementAgent* agent; sys::Timer& timer; - boost::intrusive_ptr<sys::TimerTask> heartbeatTimer; + boost::intrusive_ptr<sys::TimerTask> heartbeatTimer, linkHeartbeatTimer; boost::intrusive_ptr<ConnectionTimeoutTask> timeoutTimer; ErrorListener* errorListener; uint64_t objectId; - bool shadow; framing::FieldTable clientProperties; /** diff --git a/cpp/src/qpid/broker/ConnectionFactory.cpp b/cpp/src/qpid/broker/ConnectionFactory.cpp index 9e0020812b..d5d24ca629 100644 --- a/cpp/src/qpid/broker/ConnectionFactory.cpp +++ b/cpp/src/qpid/broker/ConnectionFactory.cpp @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -40,11 +40,6 @@ 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))); @@ -62,5 +57,5 @@ ConnectionFactory::create(sys::OutputControl& out, const std::string& id, return c.release(); } - + }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/ConnectionHandler.cpp b/cpp/src/qpid/broker/ConnectionHandler.cpp index 6894324117..06f442a47f 100644 --- a/cpp/src/qpid/broker/ConnectionHandler.cpp +++ b/cpp/src/qpid/broker/ConnectionHandler.cpp @@ -36,6 +36,9 @@ using namespace qpid; using namespace qpid::broker; + +using std::string; + using namespace qpid::framing; using qpid::sys::SecurityLayer; namespace _qmf = qmf::org::apache::qpid::broker; @@ -103,9 +106,10 @@ void ConnectionHandler::setSecureConnection(SecureConnection* secured) handler->secured = secured; } -ConnectionHandler::ConnectionHandler(Connection& connection, bool isClient, bool isShadow) : handler(new Handler(connection, isClient, isShadow)) {} +ConnectionHandler::ConnectionHandler(Connection& connection, bool isClient) : + handler(new Handler(connection, isClient)) {} -ConnectionHandler::Handler::Handler(Connection& c, bool isClient, bool isShadow) : +ConnectionHandler::Handler::Handler(Connection& c, bool isClient) : proxy(c.getOutput()), connection(c), serverMode(!isClient), secured(0), isOpen(false) @@ -116,14 +120,13 @@ ConnectionHandler::Handler::Handler(Connection& c, bool isClient, bool isShadow) properties.setString(QPID_FED_TAG, connection.getBroker().getFederationTag()); - authenticator = SaslAuthenticator::createAuthenticator(c, isShadow); + authenticator = SaslAuthenticator::createAuthenticator(c); 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; @@ -149,12 +152,20 @@ void ConnectionHandler::Handler::startOk(const ConnectionStartOkBody& body) authenticator->start(body.getMechanism(), body.hasResponse() ? &body.getResponse() : 0); } catch (std::exception& /*e*/) { management::ManagementAgent* agent = connection.getAgent(); - if (agent) { + bool logEnabled; + QPID_LOG_TEST_CAT(debug, model, logEnabled); + if (logEnabled || agent) + { string error; string uid; authenticator->getError(error); authenticator->getUid(uid); - agent->raiseEvent(_qmf::EventClientConnectFail(connection.getMgmtId(), uid, error)); + if (agent) { + agent->raiseEvent(_qmf::EventClientConnectFail(connection.getMgmtId(), uid, error)); + } + QPID_LOG_CAT(debug, model, "Failed connection. rhost:" << connection.getMgmtId() + << " user:" << uid + << " reason:" << error ); } throw; } @@ -169,7 +180,9 @@ void ConnectionHandler::Handler::startOk(const ConnectionStartOkBody& body) AclModule* acl = connection.getBroker().getAcl(); FieldTable properties; if (acl && !acl->authorise(connection.getUserId(),acl::ACT_CREATE,acl::OBJ_LINK,"")){ - proxy.close(framing::connection::CLOSE_CODE_CONNECTION_FORCED,"ACL denied creating a federation link"); + proxy.close(framing::connection::CLOSE_CODE_CONNECTION_FORCED, + QPID_MSG("ACL denied " << connection.getUserId() + << " creating a federation link")); return; } QPID_LOG(info, "Connection is a federation link"); @@ -195,12 +208,20 @@ void ConnectionHandler::Handler::secureOk(const string& response) authenticator->step(response); } catch (std::exception& /*e*/) { management::ManagementAgent* agent = connection.getAgent(); - if (agent) { + bool logEnabled; + QPID_LOG_TEST_CAT(debug, model, logEnabled); + if (logEnabled || agent) + { string error; string uid; authenticator->getError(error); authenticator->getUid(uid); - agent->raiseEvent(_qmf::EventClientConnectFail(connection.getMgmtId(), uid, error)); + if (agent) { + agent->raiseEvent(_qmf::EventClientConnectFail(connection.getMgmtId(), uid, error)); + } + QPID_LOG_CAT(debug, model, "Failed connection. rhost:" << connection.getMgmtId() + << " user:" << uid + << " reason:" << error ); } throw; } @@ -278,7 +299,7 @@ void ConnectionHandler::Handler::start(const FieldTable& serverProperties, service, host, 0, // TODO -- mgoulish Fri Sep 24 2010 - 256, + 256, false ); // disallow interaction } std::string supportedMechanismsList; @@ -318,7 +339,7 @@ void ConnectionHandler::Handler::start(const FieldTable& serverProperties, connection.setFederationPeerTag(serverProperties.getAsString(QPID_FED_TAG)); } - FieldTable ft; + FieldTable ft = connection.getBroker().getLinkClientProperties(); ft.setInt(QPID_FED_LINK,1); ft.setString(QPID_FED_TAG, connection.getBroker().getFederationTag()); @@ -367,8 +388,14 @@ void ConnectionHandler::Handler::tune(uint16_t channelMax, maxFrameSize = std::min(maxFrameSize, maxFrameSizeProposed); connection.setFrameMax(maxFrameSize); - connection.setHeartbeat(heartbeatMax); - proxy.tuneOk(channelMax, maxFrameSize, heartbeatMax); + // this method is only ever called when this Connection + // is a federation link where this Broker is acting as + // a client to another Broker + uint16_t hb = std::min(connection.getBroker().getOptions().linkHeartbeatInterval, heartbeatMax); + connection.setHeartbeat(hb); + connection.startLinkHeartbeatTimeoutTask(); + + proxy.tuneOk(channelMax, maxFrameSize, hb); proxy.open("/", Array(), true); } diff --git a/cpp/src/qpid/broker/ConnectionHandler.h b/cpp/src/qpid/broker/ConnectionHandler.h index 2e25543308..9346e7b1ac 100644 --- a/cpp/src/qpid/broker/ConnectionHandler.h +++ b/cpp/src/qpid/broker/ConnectionHandler.h @@ -61,7 +61,7 @@ class ConnectionHandler : public framing::FrameHandler SecureConnection* secured; bool isOpen; - Handler(Connection& connection, bool isClient, bool isShadow=false); + Handler(Connection& connection, bool isClient); ~Handler(); void startOk(const qpid::framing::ConnectionStartOkBody& body); void startOk(const qpid::framing::FieldTable& clientProperties, @@ -99,7 +99,7 @@ class ConnectionHandler : public framing::FrameHandler bool handle(const qpid::framing::AMQMethodBody& method); public: - ConnectionHandler(Connection& connection, bool isClient, bool isShadow=false ); + ConnectionHandler(Connection& connection, bool isClient ); void close(framing::connection::CloseCode code, const std::string& text); void heartbeat(); void handle(framing::AMQFrame& frame); diff --git a/cpp/src/qpid/broker/ConnectionObservers.h b/cpp/src/qpid/broker/ConnectionObservers.h index 07e515f3c9..e9014c80c3 100644 --- a/cpp/src/qpid/broker/ConnectionObservers.h +++ b/cpp/src/qpid/broker/ConnectionObservers.h @@ -23,9 +23,7 @@ */ #include "ConnectionObserver.h" -#include "qpid/sys/Mutex.h" -#include <set> -#include <algorithm> +#include "Observers.h" namespace qpid { namespace broker { @@ -35,18 +33,10 @@ namespace broker { * Calling a ConnectionObserver function will call that function on each observer. * THREAD SAFE. */ -class ConnectionObservers : public ConnectionObserver { +class ConnectionObservers : public ConnectionObserver, + public Observers<ConnectionObserver> +{ public: - void add(boost::shared_ptr<ConnectionObserver> observer) { - sys::Mutex::ScopedLock l(lock); - observers.insert(observer); - } - - void remove(boost::shared_ptr<ConnectionObserver> observer) { - sys::Mutex::ScopedLock l(lock); - observers.erase(observer); - } - void connection(Connection& c) { each(boost::bind(&ConnectionObserver::connection, _1, boost::ref(c))); } @@ -62,16 +52,6 @@ class ConnectionObservers : public ConnectionObserver { void forced(Connection& c, const std::string& text) { each(boost::bind(&ConnectionObserver::forced, _1, boost::ref(c), text)); } - - private: - typedef std::set<boost::shared_ptr<ConnectionObserver> > Observers; - sys::Mutex lock; - Observers observers; - - template <class F> void each(F f) { - sys::Mutex::ScopedLock l(lock); - std::for_each(observers.begin(), observers.end(), f); - } }; }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/Consumer.h b/cpp/src/qpid/broker/Consumer.h index 682c75ed4f..64073621be 100644 --- a/cpp/src/qpid/broker/Consumer.h +++ b/cpp/src/qpid/broker/Consumer.h @@ -54,7 +54,9 @@ class Consumer bool preAcquires() const { return acquires; } const std::string& getName() const { return name; } + /**@return the position of the last message seen by this consumer */ virtual framing::SequenceNumber getPosition() const { return position; } + virtual void setPosition(framing::SequenceNumber pos) { position = pos; } virtual bool deliver(QueuedMessage& msg) = 0; diff --git a/cpp/src/qpid/broker/Daemon.h b/cpp/src/qpid/broker/Daemon.h index a9cd98bce2..2bb9fc5577 100644 --- a/cpp/src/qpid/broker/Daemon.h +++ b/cpp/src/qpid/broker/Daemon.h @@ -74,7 +74,6 @@ class Daemon : private boost::noncopyable pid_t pid; int pipeFds[2]; - int lockFileFd; std::string lockFile; std::string pidDir; }; diff --git a/cpp/src/qpid/broker/DirectExchange.cpp b/cpp/src/qpid/broker/DirectExchange.cpp index 5d9aea7509..2fa7ce0fc5 100644 --- a/cpp/src/qpid/broker/DirectExchange.cpp +++ b/cpp/src/qpid/broker/DirectExchange.cpp @@ -26,6 +26,9 @@ #include <iostream> using namespace qpid::broker; + +using std::string; + using namespace qpid::framing; using namespace qpid::sys; using qpid::management::Manageable; diff --git a/cpp/src/qpid/broker/Exchange.cpp b/cpp/src/qpid/broker/Exchange.cpp index 8d20b0df81..82d4b4df15 100644 --- a/cpp/src/qpid/broker/Exchange.cpp +++ b/cpp/src/qpid/broker/Exchange.cpp @@ -35,6 +35,8 @@ namespace qpid { namespace broker { +using std::string; + using namespace qpid::framing; using qpid::framing::Buffer; using qpid::framing::FieldTable; @@ -167,7 +169,7 @@ void Exchange::routeIVE(){ Exchange::Exchange (const string& _name, Manageable* parent, Broker* b) : - name(_name), durable(false), persistenceId(0), sequence(false), + name(_name), durable(false), alternateUsers(0), persistenceId(0), sequence(false), sequenceNo(0), ive(false), mgmtExchange(0), brokerMgmtObject(0), broker(b), destroyed(false) { if (parent != 0 && broker != 0) diff --git a/cpp/src/qpid/broker/Exchange.h b/cpp/src/qpid/broker/Exchange.h index 7376f814ed..fba752210f 100644 --- a/cpp/src/qpid/broker/Exchange.h +++ b/cpp/src/qpid/broker/Exchange.h @@ -174,8 +174,9 @@ public: bool isDurable() { return durable; } qpid::framing::FieldTable& getArgs() { return args; } - Exchange::shared_ptr getAlternate() { return alternate; } - void setAlternate(Exchange::shared_ptr _alternate); + QPID_BROKER_EXTERN Exchange::shared_ptr getAlternate() { return alternate; } + QPID_BROKER_EXTERN void setAlternate(Exchange::shared_ptr _alternate); + void incAlternateUsers() { alternateUsers++; } void decAlternateUsers() { alternateUsers--; } bool inUseAsAlternate() { return alternateUsers > 0; } diff --git a/cpp/src/qpid/broker/ExchangeRegistry.cpp b/cpp/src/qpid/broker/ExchangeRegistry.cpp index 43d7268dfb..b31c7bd7b8 100644 --- a/cpp/src/qpid/broker/ExchangeRegistry.cpp +++ b/cpp/src/qpid/broker/ExchangeRegistry.cpp @@ -19,6 +19,7 @@ * */ +#include "qpid/broker/Broker.h" #include "qpid/broker/ExchangeRegistry.h" #include "qpid/broker/DirectExchange.h" #include "qpid/broker/FanOutExchange.h" @@ -42,38 +43,42 @@ pair<Exchange::shared_ptr, bool> ExchangeRegistry::declare(const string& name, c 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 if (type == Link::exchangeTypeName) { - exchange = Link::linkExchangeFactory(name); - }else{ - FunctionMap::iterator i = factory.find(type); - if (i == factory.end()) { - throw UnknownExchangeTypeException(); - } else { - exchange = i->second(name, durable, args, parent, broker); + Exchange::shared_ptr exchange; + std::pair<Exchange::shared_ptr, bool> result; + { + RWlock::ScopedWlock locker(lock); + ExchangeMap::iterator i = exchanges.find(name); + if (i == exchanges.end()) { + 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 if (type == Link::exchangeTypeName) { + exchange = Link::linkExchangeFactory(name); + }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; + result = std::pair<Exchange::shared_ptr, bool>(exchange, true); + } else { + result = std::pair<Exchange::shared_ptr, bool>(i->second, false); } - exchanges[name] = exchange; - return std::pair<Exchange::shared_ptr, bool>(exchange, true); - } else { - return std::pair<Exchange::shared_ptr, bool>(i->second, false); } + if (broker && exchange) broker->getConfigurationObservers().exchangeCreate(exchange); + return result; } void ExchangeRegistry::destroy(const string& name){ @@ -82,12 +87,17 @@ void ExchangeRegistry::destroy(const string& name){ (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 exchange; + { + RWlock::ScopedWlock locker(lock); + ExchangeMap::iterator i = exchanges.find(name); + if (i != exchanges.end()) { + exchange = i->second; + i->second->destroy(); + exchanges.erase(i); + } } + if (broker && exchange) broker->getConfigurationObservers().exchangeDestroy(exchange); } Exchange::shared_ptr ExchangeRegistry::find(const string& name){ diff --git a/cpp/src/qpid/broker/FanOutExchange.cpp b/cpp/src/qpid/broker/FanOutExchange.cpp index 2bce99b6fe..56c894c129 100644 --- a/cpp/src/qpid/broker/FanOutExchange.cpp +++ b/cpp/src/qpid/broker/FanOutExchange.cpp @@ -24,6 +24,9 @@ #include <algorithm> using namespace qpid::broker; + +using std::string; + using namespace qpid::framing; using namespace qpid::sys; namespace _qmf = qmf::org::apache::qpid::broker; diff --git a/cpp/src/qpid/broker/HeadersExchange.cpp b/cpp/src/qpid/broker/HeadersExchange.cpp index 6648ae0422..9975d26c72 100644 --- a/cpp/src/qpid/broker/HeadersExchange.cpp +++ b/cpp/src/qpid/broker/HeadersExchange.cpp @@ -26,6 +26,9 @@ using namespace qpid::broker; + +using std::string; + using namespace qpid::framing; using namespace qpid::sys; namespace _qmf = qmf::org::apache::qpid::broker; diff --git a/cpp/src/qpid/broker/Link.cpp b/cpp/src/qpid/broker/Link.cpp index f21c861149..84dd163ac3 100644 --- a/cpp/src/qpid/broker/Link.cpp +++ b/cpp/src/qpid/broker/Link.cpp @@ -125,18 +125,20 @@ boost::shared_ptr<Exchange> Link::linkExchangeFactory( const std::string& _name return Exchange::shared_ptr(new LinkExchange(_name)); } -Link::Link(LinkRegistry* _links, - MessageStore* _store, +Link::Link(const string& _name, + LinkRegistry* _links, const string& _host, uint16_t _port, const string& _transport, + DestroyedListener l, bool _durable, const string& _authMechanism, const string& _username, const string& _password, Broker* _broker, - Manageable* parent) - : links(_links), store(_store), + Manageable* parent, + bool failover_) + : name(_name), links(_links), configuredTransport(_transport), configuredHost(_host), configuredPort(_port), host(_host), port(_port), transport(_transport), durable(_durable), @@ -149,7 +151,9 @@ Link::Link(LinkRegistry* _links, channelCounter(1), connection(0), agent(0), + listener(l), timerTask(new LinkTimerTask(*this, broker->getTimer())), + failover(failover_), failoverChannel(0) { if (parent != 0 && broker != 0) @@ -157,7 +161,10 @@ Link::Link(LinkRegistry* _links, agent = broker->getManagementAgent(); if (agent != 0) { - mgmtObject = new _qmf::Link(agent, this, parent, _host, _port, _transport, _durable); + mgmtObject = new _qmf::Link(agent, this, parent, name, durable); + mgmtObject->set_host(host); + mgmtObject->set_port(port); + mgmtObject->set_transport(transport); agent->addObject(mgmtObject, 0, durable); } } @@ -169,13 +176,15 @@ Link::Link(LinkRegistry* _links, } broker->getTimer().add(timerTask); - stringstream _name; - _name << "qpid.link." << transport << ":" << host << ":" << port; - std::pair<Exchange::shared_ptr, bool> rc = broker->getExchanges().declare(_name.str(), - exchangeTypeName); - failoverExchange = boost::static_pointer_cast<LinkExchange>(rc.first); - assert(failoverExchange); - failoverExchange->setLink(this); + if (failover) { + stringstream exchangeName; + exchangeName << "qpid.link." << name; + std::pair<Exchange::shared_ptr, bool> rc = + broker->getExchanges().declare(exchangeName.str(), exchangeTypeName); + failoverExchange = boost::static_pointer_cast<LinkExchange>(rc.first); + assert(failoverExchange); + failoverExchange->setLink(this); + } } Link::~Link () @@ -187,7 +196,8 @@ Link::~Link () if (mgmtObject != 0) mgmtObject->resourceDestroy (); - broker->getExchanges().destroy(failoverExchange->getName()); + if (failover) + broker->getExchanges().destroy(failoverExchange->getName()); } void Link::setStateLH (int newState) @@ -239,16 +249,19 @@ void Link::established(Connection* c) if (!hideManagement() && agent) agent->raiseEvent(_qmf::EventBrokerLinkUp(addr.str())); - - Mutex::ScopedLock mutex(lock); - setStateLH(STATE_OPERATIONAL); - currentInterval = 1; - visitCount = 0; - connection = c; - if (closing) + bool isClosing = false; + { + Mutex::ScopedLock mutex(lock); + setStateLH(STATE_OPERATIONAL); + currentInterval = 1; + visitCount = 0; + connection = c; + isClosing = closing; + } + if (isClosing) destroy(); else // Process any IO tasks bridges added before established. - connection->requestIOProcessing (boost::bind(&Link::ioThreadProcessing, this)); + c->requestIOProcessing (boost::bind(&Link::ioThreadProcessing, this)); } @@ -261,16 +274,26 @@ void Link::setUrl(const Url& u) { namespace { - /** invoked when session used to subscribe to remote's amq.failover exchange detaches */ - void sessionDetached(Link *link) { - QPID_LOG(debug, "detached from 'amq.failover' for link: " << link->getName()); - } +class DetachedCallback : public SessionHandler::ErrorListener { + public: + DetachedCallback(const Link& link) : name(link.getName()) {} + void connectionException(framing::connection::CloseCode, const std::string&) {} + void channelException(framing::session::DetachCode, const std::string&) {} + void executionException(framing::execution::ErrorCode, const std::string&) {} + void detach() {} + private: + const std::string name; +}; } - void Link::opened() { Mutex::ScopedLock mutex(lock); if (!connection) return; + + if (!hideManagement() && connection->GetManagementObject()) { + mgmtObject->set_connectionRef(connection->GetManagementObject()->getObjectId()); + } + // Get default URL from known-hosts if not already set if (url.empty()) { const std::vector<Url>& known = connection->getKnownHosts(); @@ -282,80 +305,82 @@ void Link::opened() { QPID_LOG(debug, "Known hosts for peer of inter-broker link: " << url); } - // - // attempt to subscribe to failover exchange for updates from remote - // - - const std::string queueName = "qpid.link." + framing::Uuid(true).str(); - failoverChannel = nextChannel(); - - SessionHandler& sessionHandler = connection->getChannel(failoverChannel); - sessionHandler.setDetachedCallback( boost::bind(&sessionDetached, this) ); - failoverSession = queueName; - sessionHandler.attachAs(failoverSession); - - framing::AMQP_ServerProxy remoteBroker(sessionHandler.out); - - remoteBroker.getQueue().declare(queueName, - "", // alt-exchange - false, // passive - false, // durable - true, // exclusive - true, // auto-delete - FieldTable()); - remoteBroker.getExchange().bind(queueName, - FAILOVER_EXCHANGE, - "", // no key - FieldTable()); - remoteBroker.getMessage().subscribe(queueName, - failoverExchange->getName(), - 1, // implied-accept mode - 0, // pre-acquire mode - false, // exclusive - "", // resume-id - 0, // resume-ttl + if (failover) { + // + // attempt to subscribe to failover exchange for updates from remote + // + + const std::string queueName = "qpid.link." + framing::Uuid(true).str(); + failoverChannel = nextChannel(); + + SessionHandler& sessionHandler = connection->getChannel(failoverChannel); + sessionHandler.setErrorListener( + boost::shared_ptr<SessionHandler::ErrorListener>(new DetachedCallback(*this))); + failoverSession = queueName; + sessionHandler.attachAs(failoverSession); + + framing::AMQP_ServerProxy remoteBroker(sessionHandler.out); + + remoteBroker.getQueue().declare(queueName, + "", // alt-exchange + false, // passive + false, // durable + true, // exclusive + true, // auto-delete + FieldTable()); + remoteBroker.getExchange().bind(queueName, + FAILOVER_EXCHANGE, + "", // no key FieldTable()); - remoteBroker.getMessage().flow(failoverExchange->getName(), 0, 0xFFFFFFFF); - remoteBroker.getMessage().flow(failoverExchange->getName(), 1, 0xFFFFFFFF); + remoteBroker.getMessage().subscribe(queueName, + failoverExchange->getName(), + 1, // implied-accept mode + 0, // pre-acquire mode + false, // exclusive + "", // resume-id + 0, // resume-ttl + FieldTable()); + remoteBroker.getMessage().flow(failoverExchange->getName(), 0, 0xFFFFFFFF); + remoteBroker.getMessage().flow(failoverExchange->getName(), 1, 0xFFFFFFFF); + } } void Link::closed(int, std::string text) { - bool isClosing = false; - { - Mutex::ScopedLock mutex(lock); - QPID_LOG (info, "Inter-broker link disconnected from " << host << ":" << port << " " << text); + Mutex::ScopedLock mutex(lock); + QPID_LOG (info, "Inter-broker link disconnected from " << host << ":" << port << " " << text); - connection = 0; - if (state == STATE_OPERATIONAL) { + connection = 0; + + if (!hideManagement()) { + mgmtObject->set_connectionRef(qpid::management::ObjectId()); + if (state == STATE_OPERATIONAL && agent) { stringstream addr; addr << host << ":" << port; - if (!hideManagement() && agent) - agent->raiseEvent(_qmf::EventBrokerLinkDown(addr.str())); + agent->raiseEvent(_qmf::EventBrokerLinkDown(addr.str())); } + } - for (Bridges::iterator i = active.begin(); i != active.end(); i++) { - (*i)->closed(); - created.push_back(*i); - } - active.clear(); + for (Bridges::iterator i = active.begin(); i != active.end(); i++) { + (*i)->closed(); + created.push_back(*i); + } + active.clear(); - if (state != STATE_FAILED && state != STATE_PASSIVE) - { - setStateLH(STATE_WAITING); - if (!hideManagement()) - mgmtObject->set_lastError (text); - } + if (state != STATE_FAILED && state != STATE_PASSIVE) + { + setStateLH(STATE_WAITING); + if (!hideManagement()) + mgmtObject->set_lastError (text); } - // Call destroy outside of the lock, don't want to be deleted with lock held. - if (isClosing) - destroy(); } -// Called in connection IO thread. +// Called in connection IO thread, cleans up the connection before destroying Link void Link::destroy () { Bridges toDelete; + + timerTask->cancel(); // call prior to locking so maintenance visit can finish { Mutex::ScopedLock mutex(lock); @@ -374,14 +399,13 @@ void Link::destroy () for (Bridges::iterator i = created.begin(); i != created.end(); i++) toDelete.push_back(*i); created.clear(); - - timerTask->cancel(); } + // 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(); + (*i)->close(); toDelete.clear(); - links->destroy (configuredHost, configuredPort); + listener(this); // notify LinkRegistry that this Link has been destroyed } void Link::add(Bridge::shared_ptr bridge) @@ -423,7 +447,7 @@ void Link::ioThreadProcessing() { Mutex::ScopedLock mutex(lock); - if (state != STATE_OPERATIONAL) + if (state != STATE_OPERATIONAL || closing) return; // check for bridge session errors and recover @@ -460,7 +484,7 @@ void Link::ioThreadProcessing() void Link::maintenanceVisit () { Mutex::ScopedLock mutex(lock); - + if (closing) return; if (state == STATE_WAITING) { visitCount++; @@ -476,21 +500,27 @@ void Link::maintenanceVisit () } } } - else if (state == STATE_OPERATIONAL && (!active.empty() || !created.empty() || !cancellations.empty()) && connection != 0) + else if (state == STATE_OPERATIONAL && + (!active.empty() || !created.empty() || !cancellations.empty()) && + connection && connection->isOpen()) connection->requestIOProcessing (boost::bind(&Link::ioThreadProcessing, this)); - } +} void Link::reconnectLH(const Address& a) { host = a.host; port = a.port; transport = a.protocol; - startConnectionLH(); + if (!hideManagement()) { stringstream errorString; - errorString << "Failed over to " << a; + errorString << "Failing over to " << a; mgmtObject->set_lastError(errorString.str()); + mgmtObject->set_host(host); + mgmtObject->set_port(port); + mgmtObject->set_transport(transport); } + startConnectionLH(); } bool Link::tryFailoverLH() { @@ -499,15 +529,14 @@ bool Link::tryFailoverLH() { if (url.empty()) return false; Address next = url[reconnectNext++]; if (next.host != host || next.port != port || next.protocol != transport) { - links->changeAddress(Address(transport, host, port), next); - QPID_LOG(debug, "Inter-broker link failing over to " << next.host << ":" << next.port); + QPID_LOG(notice, "Inter-broker link '" << name << "' failing over to " << next); reconnectLH(next); return true; } return false; } -// Management updates for a linke are inconsistent in a cluster, so they are +// Management updates for a link are inconsistent in a cluster, so they are // suppressed. bool Link::hideManagement() const { return !mgmtObject || ( broker && broker->isInCluster()); @@ -536,18 +565,34 @@ void Link::setPersistenceId(uint64_t id) const const string& Link::getName() const { - return configuredHost; + return name; +} + +const std::string Link::ENCODED_IDENTIFIER("link.v2"); +const std::string Link::ENCODED_IDENTIFIER_V1("link"); + +bool Link::isEncodedLink(const std::string& key) +{ + return key == ENCODED_IDENTIFIER || key == ENCODED_IDENTIFIER_V1; } Link::shared_ptr Link::decode(LinkRegistry& links, Buffer& buffer) { + string kind; + buffer.getShortString(kind); + string host; uint16_t port; string transport; string authMechanism; string username; string password; + string name; + if (kind == ENCODED_IDENTIFIER) { + // newer version provides a link name. + buffer.getShortString(name); + } buffer.getShortString(host); port = buffer.getShort(); buffer.getShortString(transport); @@ -556,12 +601,21 @@ Link::shared_ptr Link::decode(LinkRegistry& links, Buffer& buffer) buffer.getShortString(username); buffer.getShortString(password); - return links.declare(host, port, transport, durable, authMechanism, username, password).first; + if (kind == ENCODED_IDENTIFIER_V1) { + /** previous versions identified the Link by host:port, there was no name + * assigned. So create a name for the new Link. + */ + name = createName(transport, host, port); + } + + return links.declare(name, host, port, transport, durable, authMechanism, + username, password).first; } void Link::encode(Buffer& buffer) const { - buffer.putShortString(string("link")); + buffer.putShortString(ENCODED_IDENTIFIER); + buffer.putShortString(name); buffer.putShortString(configuredHost); buffer.putShort(configuredPort); buffer.putShortString(configuredTransport); @@ -573,8 +627,9 @@ void Link::encode(Buffer& buffer) const uint32_t Link::encodedSize() const { - return configuredHost.size() + 1 // short-string (host) - + 5 // short-string ("link") + return ENCODED_IDENTIFIER.size() + 1 // +1 byte length + + name.size() + 1 + + configuredHost.size() + 1 // short-string (host) + 2 // port + configuredTransport.size() + 1 // short-string(transport) + 1 // durable @@ -589,6 +644,7 @@ ManagementObject* Link::GetManagementObject (void) const } void Link::close() { + QPID_LOG(debug, "Link::close(), link=" << name ); Mutex::ScopedLock mutex(lock); if (!closing) { closing = true; @@ -609,36 +665,31 @@ Manageable::status_t Link::ManagementMethod (uint32_t op, Args& args, string& te return Manageable::STATUS_OK; case _qmf::Link::METHOD_BRIDGE : + /* TBD: deprecate this interface in favor of the Broker::create() method. The + * Broker::create() method allows the user to assign a name to the bridge. + */ + QPID_LOG(info, "The Link::bridge() method will be removed in a future release of QPID." + " Please use the Broker::create() method with type='bridge' instead."); _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; + QPID_LOG(debug, "Link::bridge() request received; src=" << iargs.i_src << + "; dest=" << iargs.i_dest << "; key=" << iargs.i_key); + + // Does a bridge already exist that has the src/dest/key? If so, re-use the + // existing bridge - this behavior is backward compatible with previous releases. + Bridge::shared_ptr bridge = links->getBridge(*this, iargs.i_src, iargs.i_dest, iargs.i_key); + if (!bridge) { + // need to create a new bridge on this link. + std::pair<Bridge::shared_ptr, bool> rc = + links->declare( Bridge::createName(name, iargs.i_src, iargs.i_dest, iargs.i_key), + *this, 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 (!rc.first) { + text = "invalid parameters"; + return Manageable::STATUS_PARAMETER_INVALID; } } - - std::pair<Bridge::shared_ptr, bool> result = - links->declare (configuredHost, configuredPort, 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; } @@ -666,11 +717,13 @@ void Link::closeConnection( const std::string& reason) { if (connection != 0) { // cancel our subscription to the failover exchange - SessionHandler& sessionHandler = connection->getChannel(failoverChannel); - if (sessionHandler.getSession()) { - framing::AMQP_ServerProxy remoteBroker(sessionHandler.out); - remoteBroker.getMessage().cancel(failoverExchange->getName()); - remoteBroker.getSession().detach(failoverSession); + if (failover) { + SessionHandler& sessionHandler = connection->getChannel(failoverChannel); + if (sessionHandler.getSession()) { + framing::AMQP_ServerProxy remoteBroker(sessionHandler.out); + remoteBroker.getMessage().cancel(failoverExchange->getName()); + remoteBroker.getSession().detach(failoverSession); + } } connection->close(CLOSE_CODE_CONNECTION_FORCED, reason); connection = 0; @@ -716,6 +769,23 @@ void Link::setState(const framing::FieldTable& state) } } +std::string Link::createName(const std::string& transport, + const std::string& host, + uint16_t port) +{ + stringstream linkName; + linkName << QPID_NAME_PREFIX << transport << std::string(":") + << host << std::string(":") << port; + return linkName.str(); +} + + +bool Link::pendingConnection(const std::string& _host, uint16_t _port) const +{ + Mutex::ScopedLock mutex(lock); + return (isConnecting() && _port == port && _host == host); +} + const std::string Link::exchangeTypeName("qpid.LinkExchange"); diff --git a/cpp/src/qpid/broker/Link.h b/cpp/src/qpid/broker/Link.h index a97fa48664..f0cb90e73b 100644 --- a/cpp/src/qpid/broker/Link.h +++ b/cpp/src/qpid/broker/Link.h @@ -25,7 +25,6 @@ #include <boost/shared_ptr.hpp> #include "qpid/Url.h" #include "qpid/broker/BrokerImportExport.h" -#include "qpid/broker/MessageStore.h" #include "qpid/broker/PersistableConfig.h" #include "qpid/broker/Bridge.h" #include "qpid/broker/BrokerImportExport.h" @@ -52,8 +51,8 @@ class LinkExchange; class Link : public PersistableConfig, public management::Manageable { private: mutable sys::Mutex lock; + const std::string name; LinkRegistry* links; - MessageStore* store; // these remain constant across failover - used to identify this link const std::string configuredTransport; @@ -64,7 +63,8 @@ class Link : public PersistableConfig, public management::Manageable { uint16_t port; std::string transport; - bool durable; + bool durable; + std::string authMechanism; std::string username; std::string password; @@ -85,8 +85,10 @@ class Link : public PersistableConfig, public management::Manageable { uint channelCounter; Connection* connection; management::ManagementAgent* agent; + boost::function<void(Link*)> listener; boost::intrusive_ptr<sys::TimerTask> timerTask; boost::shared_ptr<broker::LinkExchange> failoverExchange; // subscribed to remote's amq.failover exchange + bool failover; // Do we subscribe to a failover exchange? uint failoverChannel; std::string failoverSession; @@ -101,33 +103,39 @@ class Link : public PersistableConfig, public management::Manageable { void setStateLH (int newState); void startConnectionLH(); // Start the IO Connection - void destroy(); // Called when mgmt deletes this link + void destroy(); // Cleanup connection before link goes away void ioThreadProcessing(); // Called on connection's IO thread by request bool tryFailoverLH(); // Called during maintenance visit bool hideManagement() const; + void reconnectLH(const Address&); //called by LinkRegistry - void established(Connection*); // Called when connection is create + // connection management (called by LinkRegistry) + void established(Connection*); // Called when connection is created void opened(); // Called when connection is open (after create) void closed(int, std::string); // Called when connection goes away - void reconnectLH(const Address&); //called by LinkRegistry + void notifyConnectionForced(const std::string text); void closeConnection(const std::string& reason); + bool pendingConnection(const std::string& host, uint16_t port) const; // is Link trying to connect to this remote? friend class LinkRegistry; // to call established, opened, closed public: typedef boost::shared_ptr<Link> shared_ptr; + typedef boost::function<void(Link*)> DestroyedListener; - Link(LinkRegistry* links, - MessageStore* store, + Link(const std::string& name, + LinkRegistry* links, const std::string& host, uint16_t port, const std::string& transport, + DestroyedListener l, bool durable, const std::string& authMechanism, const std::string& username, const std::string& password, Broker* broker, - management::Manageable* parent = 0); + management::Manageable* parent = 0, + bool failover=true); virtual ~Link(); /** these return the *configured* transport/host/port, which does not change over the @@ -139,7 +147,7 @@ class Link : public PersistableConfig, public management::Manageable { /** returns the current address of the remote, which may be different from the configured transport/host/port due to failover. Returns true if connection is active */ - bool getRemoteAddress(qpid::Address& addr) const; + QPID_BROKER_EXTERN bool getRemoteAddress(qpid::Address& addr) const; bool isDurable() { return durable; } void maintenanceVisit (); @@ -148,15 +156,17 @@ class Link : public PersistableConfig, public management::Manageable { void cancel(Bridge::shared_ptr); QPID_BROKER_EXTERN void setUrl(const Url&); // Set URL for reconnection. - QPID_BROKER_EXTERN void close(); // Close the link from within the broker. + + // Close the link. + QPID_BROKER_EXTERN void close(); 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); + bool isConnecting() const { return state == STATE_CONNECTING; } // PersistableConfig: void setPersistenceId(uint64_t id) const; @@ -165,7 +175,10 @@ class Link : public PersistableConfig, public management::Manageable { void encode(framing::Buffer& buffer) const; const std::string& getName() const; + static const std::string ENCODED_IDENTIFIER; + static const std::string ENCODED_IDENTIFIER_V1; static Link::shared_ptr decode(LinkRegistry& links, framing::Buffer& buffer); + static bool isEncodedLink(const std::string& key); // Manageable entry points management::ManagementObject* GetManagementObject(void) const; @@ -178,6 +191,16 @@ class Link : public PersistableConfig, public management::Manageable { // replicate internal state of this Link for clustering void getState(framing::FieldTable& state) const; void setState(const framing::FieldTable& state); + + /** create a name for a link (if none supplied by user config) */ + static std::string createName(const std::string& transport, + const std::string& host, + uint16_t port); + + /** The current connction for this link. Note returns 0 if the link is not + * presently connected. + */ + Connection* getConnection() { return connection; } }; } } diff --git a/cpp/src/qpid/broker/LinkRegistry.cpp b/cpp/src/qpid/broker/LinkRegistry.cpp index d89f220d1b..0507fe6521 100644 --- a/cpp/src/qpid/broker/LinkRegistry.cpp +++ b/cpp/src/qpid/broker/LinkRegistry.cpp @@ -68,54 +68,92 @@ LinkRegistry::LinkRegistry (Broker* _broker) : LinkRegistry::~LinkRegistry() {} +/** find link by the *configured* remote address */ +boost::shared_ptr<Link> LinkRegistry::getLink(const std::string& host, + uint16_t port, + const std::string& transport) +{ + Mutex::ScopedLock locker(lock); + for (LinkMap::iterator i = links.begin(); i != links.end(); ++i) { + Link::shared_ptr& link = i->second; + if (link->getHost() == host && + link->getPort() == port && + (transport.empty() || link->getTransport() == transport)) + return link; + } + return boost::shared_ptr<Link>(); +} -void LinkRegistry::changeAddress(const qpid::Address& oldAddress, const qpid::Address& newAddress) +/** find link by name */ +boost::shared_ptr<Link> LinkRegistry::getLink(const std::string& name) { Mutex::ScopedLock locker(lock); - std::string oldKey = createKey(oldAddress); - 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"); - } 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); - } else { - links[newKey] = i->second; - links.erase(oldKey); - QPID_LOG(info, "Updated link key from " << oldKey << " to " << newKey); - } - } + LinkMap::iterator l = links.find(name); + if (l != links.end()) + return l->second; + return boost::shared_ptr<Link>(); } -pair<Link::shared_ptr, bool> LinkRegistry::declare(const string& host, +pair<Link::shared_ptr, bool> LinkRegistry::declare(const string& name, + const string& host, uint16_t port, const string& transport, bool durable, const string& authMechanism, const string& username, - const string& password) + const string& password, + bool failover) { Mutex::ScopedLock locker(lock); - string key = createKey(host, port); - LinkMap::iterator i = links.find(key); + LinkMap::iterator i = links.find(name); 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)); - links[key] = link; + link = Link::shared_ptr ( + new Link (name, this, host, port, transport, + boost::bind(&LinkRegistry::linkDestroyed, this, _1), + durable, authMechanism, username, password, broker, + parent, failover)); + if (durable && store) store->create(*link); + links[name] = link; + QPID_LOG(debug, "Creating new link; name=" << name ); 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(const std::string& host, - uint16_t port, +/** find bridge by link & route info */ +Bridge::shared_ptr LinkRegistry::getBridge(const Link& link, + const std::string& src, + const std::string& dest, + const std::string& key) +{ + Mutex::ScopedLock locker(lock); + for (BridgeMap::iterator i = bridges.begin(); i != bridges.end(); ++i) { + if (i->second->getSrc() == src && i->second->getDest() == dest && + i->second->getKey() == key && i->second->getLink() && + i->second->getLink()->getName() == link.getName()) { + return i->second; + } + } + return Bridge::shared_ptr(); +} + +/** find bridge by name */ +Bridge::shared_ptr LinkRegistry::getBridge(const std::string& name) +{ + Mutex::ScopedLock locker(lock); + BridgeMap::iterator b = bridges.find(name); + if (b != bridges.end()) + return b->second; + return Bridge::shared_ptr(); +} + +pair<Bridge::shared_ptr, bool> LinkRegistry::declare(const std::string& name, + Link& link, bool durable, const std::string& src, const std::string& dest, @@ -126,22 +164,32 @@ pair<Bridge::shared_ptr, bool> LinkRegistry::declare(const std::string& host, const std::string& excludes, bool dynamic, uint16_t sync, - Bridge::InitializeCallback init + Bridge::InitializeCallback init, + const std::string& queueName, + const std::string& altExchange ) { 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(); + // Durable bridges are only valid on durable links + if (durable && !link.isDurable()) { + QPID_LOG(error, "Can't create a durable route '" << name << "' on a non-durable link '" << link.getName()); + return pair<Bridge::shared_ptr, bool>(Bridge::shared_ptr(), false); + } - LinkMap::iterator l = links.find(linkKey); - if (l == links.end()) - return pair<Bridge::shared_ptr, bool>(Bridge::shared_ptr(), false); + if (dynamic) { + Exchange::shared_ptr exchange = broker->getExchanges().get(src); + if (exchange.get() == 0) { + QPID_LOG(error, "Exchange not found, name='" << src << "'" ); + return pair<Bridge::shared_ptr, bool>(Bridge::shared_ptr(), false); + } + if (!exchange->supportsDynamicBinding()) { + QPID_LOG(error, "Exchange type does not support dynamic routing, name='" << src << "'"); + return pair<Bridge::shared_ptr, bool>(Bridge::shared_ptr(), false); + } + } - BridgeMap::iterator b = bridges.find(bridgeKey); + BridgeMap::iterator b = bridges.find(name); if (b == bridges.end()) { _qmf::ArgsLinkBridge args; @@ -159,23 +207,29 @@ pair<Bridge::shared_ptr, bool> LinkRegistry::declare(const std::string& host, 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, init)); - bridges[bridgeKey] = bridge; - l->second->add(bridge); + (new Bridge (name, &link, link.nextChannel(), + boost::bind(&LinkRegistry::destroyBridge, this, _1), + args, init, queueName, altExchange)); + bridges[name] = bridge; + link.add(bridge); + if (durable && store) + store->create(*bridge); + + QPID_LOG(debug, "Bridge '" << name <<"' declared on link '" << link.getName() << + "' from " << src << " to " << dest << " (" << key << ")"); + 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) +/** called back by the link when it has completed its cleanup and can be removed. */ +void LinkRegistry::linkDestroyed(Link *link) { + QPID_LOG(debug, "LinkRegistry::destroy(); link= " << link->getName()); Mutex::ScopedLock locker(lock); - string key = createKey(host, port); - LinkMap::iterator i = links.find(key); + LinkMap::iterator i = links.find(link->getName()); if (i != links.end()) { if (i->second->isDurable() && store) @@ -184,27 +238,20 @@ void LinkRegistry::destroy(const string& host, const uint16_t port) } } -void LinkRegistry::destroy(const std::string& host, - const uint16_t port, - const std::string& src, - const std::string& dest, - const std::string& key) +/** called back by bridge when its destruction has been requested */ +void LinkRegistry::destroyBridge(Bridge *bridge) { + QPID_LOG(debug, "LinkRegistry::destroy(); bridge= " << bridge->getName()); 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); + BridgeMap::iterator b = bridges.find(bridge->getName()); if (b == bridges.end()) return; - l->second->cancel(b->second); + Link *link = b->second->getLink(); + if (link) { + link->cancel(b->second); + } if (b->second->isDurable()) store->destroy(*(b->second)); bridges.erase(b); @@ -219,26 +266,71 @@ 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); +namespace { + void extractHostPort(const std::string& connId, std::string *host, uint16_t *port) + { + // Extract host and port of remote broker from connection id string. + // + // 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. + // KAG: current connection id format assumed: + // "localhost:port-remotehost:port". In the case of IpV6, the host addresses are + // contained within brackets "[...]", example: + // connId="[::1]:36859-[::1]:48603". Liberal use of "asserts" provided to alert us + // if this assumption changes! + size_t separator = connId.find('-'); + assert(separator != std::string::npos); + std::string remote = connId.substr(separator+1, std::string::npos); + separator = remote.rfind(":"); + assert(separator != std::string::npos); + *host = remote.substr(0, separator); + // IPv6 - host is bracketed by "[]", strip them + if ((*host)[0] == '[' && (*host)[host->length() - 1] == ']') { + *host = host->substr(1, host->length() - 2); + } + try { + *port = boost::lexical_cast<uint16_t>(remote.substr(separator+1, std::string::npos)); + } catch (const boost::bad_lexical_cast&) { + QPID_LOG(error, "Invalid format for connection identifier! '" << connId << "'"); + assert(false); + } + } +} +/** find the Link that corresponds to the given connection */ +Link::shared_ptr LinkRegistry::findLink(const std::string& connId) +{ Mutex::ScopedLock locker(lock); - LinkMap::iterator l = links.find(key); - if (l != links.end()) return l->second; - else return Link::shared_ptr(); + ConnectionMap::iterator c = connections.find(connId); + if (c != connections.end()) { + LinkMap::iterator l = links.find(c->second); + if (l != links.end()) + return l->second; + } + return Link::shared_ptr(); } void LinkRegistry::notifyConnection(const std::string& key, Connection* c) { - Link::shared_ptr link = findLink(key); + // find a link that is attempting to connect to the remote, and + // create a mapping from connection id to link + QPID_LOG(debug, "LinkRegistry::notifyConnection(); key=" << key ); + std::string host; + uint16_t port = 0; + extractHostPort( key, &host, &port ); + Link::shared_ptr link; + { + Mutex::ScopedLock locker(lock); + for (LinkMap::iterator l = links.begin(); l != links.end(); ++l) { + if (l->second->pendingConnection(host, port)) { + link = l->second; + connections[key] = link->getName(); + break; + } + } + } + if (link) { link->established(c); c->setUserId(str(format("%1%@%2%") % link->getUsername() % realm)); @@ -343,20 +435,6 @@ std::string LinkRegistry::getAuthIdentity(const std::string& key) } -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); @@ -369,10 +447,12 @@ void LinkRegistry::setPassive(bool p) } void LinkRegistry::eachLink(boost::function<void(boost::shared_ptr<Link>)> f) { + Mutex::ScopedLock locker(lock); for (LinkMap::iterator i = links.begin(); i != links.end(); ++i) f(i->second); } void LinkRegistry::eachBridge(boost::function<void(boost::shared_ptr<Bridge>)> f) { + Mutex::ScopedLock locker(lock); for (BridgeMap::iterator i = bridges.begin(); i != bridges.end(); ++i) f(i->second); } diff --git a/cpp/src/qpid/broker/LinkRegistry.h b/cpp/src/qpid/broker/LinkRegistry.h index 8e9d2f4b0d..5a39b62bd1 100644 --- a/cpp/src/qpid/broker/LinkRegistry.h +++ b/cpp/src/qpid/broker/LinkRegistry.h @@ -42,9 +42,11 @@ namespace broker { class LinkRegistry { typedef std::map<std::string, boost::shared_ptr<Link> > LinkMap; typedef std::map<std::string, Bridge::shared_ptr> BridgeMap; + typedef std::map<std::string, std::string> ConnectionMap; - LinkMap links; - BridgeMap bridges; + LinkMap links; /** indexed by name of Link */ + BridgeMap bridges; /** indexed by name of Bridge */ + ConnectionMap connections; /** indexed by connection identifier, gives link name */ qpid::sys::Mutex lock; Broker* broker; @@ -54,15 +56,18 @@ namespace broker { std::string realm; 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); - // Methods called by the connection observer. + // Methods called by the connection observer, key is connection identifier void notifyConnection (const std::string& key, Connection* c); void notifyOpened (const std::string& key); void notifyClosed (const std::string& key); void notifyConnectionForced (const std::string& key, const std::string& text); - friend class LinkRegistryConnectionObserver; + friend class LinkRegistryConnectionObserver; + + /** Notify the registry that a Link has been destroyed */ + void linkDestroyed(Link*); + /** Request to destroy a Bridge */ + void destroyBridge(Bridge*); public: QPID_BROKER_EXTERN LinkRegistry (); // Only used in store tests @@ -70,17 +75,29 @@ namespace broker { QPID_BROKER_EXTERN ~LinkRegistry(); QPID_BROKER_EXTERN std::pair<boost::shared_ptr<Link>, bool> - declare(const std::string& host, + declare(const std::string& name, + const std::string& host, uint16_t port, const std::string& transport, bool durable, const std::string& authMechanism, const std::string& username, - const std::string& password); + const std::string& password, + bool failover=true); + + /** determine if Link exists */ + QPID_BROKER_EXTERN boost::shared_ptr<Link> + getLink(const std::string& name); + /** host,port,transport will be matched against the configured values, which may + be different from the current values due to failover */ + QPID_BROKER_EXTERN boost::shared_ptr<Link> + getLink(const std::string& configHost, + uint16_t configPort, + const std::string& configTransport = std::string()); QPID_BROKER_EXTERN std::pair<Bridge::shared_ptr, bool> - declare(const std::string& host, - uint16_t port, + declare(const std::string& name, + Link& link, bool durable, const std::string& src, const std::string& dest, @@ -91,16 +108,18 @@ namespace broker { const std::string& excludes, bool dynamic, uint16_t sync, - Bridge::InitializeCallback=0 + Bridge::InitializeCallback=0, + const std::string& queueName="", + const std::string& altExchange="" ); - - QPID_BROKER_EXTERN void destroy(const std::string& host, const uint16_t port); - - QPID_BROKER_EXTERN void destroy(const std::string& host, - const uint16_t port, - const std::string& src, - const std::string& dest, - const std::string& key); + /** determine if Bridge exists */ + QPID_BROKER_EXTERN Bridge::shared_ptr + getBridge(const std::string& name); + QPID_BROKER_EXTERN Bridge::shared_ptr + getBridge(const Link& link, + const std::string& src, + const std::string& dest, + const std::string& key); /** * Register the manageable parent for declared queues @@ -126,11 +145,6 @@ namespace broker { QPID_BROKER_EXTERN 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 diff --git a/cpp/src/qpid/broker/Message.cpp b/cpp/src/qpid/broker/Message.cpp index 40dfba39f4..4dd8a349dd 100644 --- a/cpp/src/qpid/broker/Message.cpp +++ b/cpp/src/qpid/broker/Message.cpp @@ -384,6 +384,18 @@ void Message::addTraceId(const std::string& id) } } +void Message::clearTrace() +{ + sys::Mutex::ScopedLock l(lock); + if (isA<MessageTransferBody>()) { + FieldTable& headers = getModifiableProperties<MessageProperties>()->getApplicationHeaders(); + std::string trace = headers.getAsString(X_QPID_TRACE); + if (!trace.empty()) { + headers.setString(X_QPID_TRACE, ""); + } + } +} + void Message::setTimestamp() { sys::Mutex::ScopedLock l(lock); diff --git a/cpp/src/qpid/broker/Message.h b/cpp/src/qpid/broker/Message.h index dda45d73e6..90e4eec889 100644 --- a/cpp/src/qpid/broker/Message.h +++ b/cpp/src/qpid/broker/Message.h @@ -161,6 +161,7 @@ public: bool isExcluded(const std::vector<std::string>& excludes) const; void addTraceId(const std::string& id); + void clearTrace(); void forcePersistent(); bool isForcedPersistent(); diff --git a/cpp/src/qpid/broker/MessageDeque.cpp b/cpp/src/qpid/broker/MessageDeque.cpp index f70c996975..83c8ca6868 100644 --- a/cpp/src/qpid/broker/MessageDeque.cpp +++ b/cpp/src/qpid/broker/MessageDeque.cpp @@ -40,13 +40,16 @@ size_t MessageDeque::index(const framing::SequenceNumber& position) bool MessageDeque::deleted(const QueuedMessage& m) { size_t i = index(m.position); - if (i < messages.size() && messages[i].status != QueuedMessage::DELETED) { - messages[i].status = QueuedMessage::DELETED; - clean(); - return true; - } else { - return false; + if (i < messages.size()) { + QueuedMessage *qm = &messages[i]; + if (qm->status != QueuedMessage::DELETED) { + qm->status = QueuedMessage::DELETED; + qm->payload = 0; // message no longer needed + clean(); + return true; + } } + return false; } size_t MessageDeque::size() @@ -144,6 +147,7 @@ QueuedMessage* MessageDeque::pushPtr(const QueuedMessage& added) { messages.back().status = QueuedMessage::AVAILABLE; if (head >= messages.size()) head = messages.size() - 1; ++available; + clean(); // QPID-4046: let producer help clean the backlog of deleted messages return &messages.back(); } @@ -173,12 +177,37 @@ void MessageDeque::updateAcquired(const QueuedMessage& acquired) } } +namespace { +bool isNotDeleted(const QueuedMessage& qm) { return qm.status != QueuedMessage::DELETED; } +} // namespace + +void MessageDeque::setPosition(const framing::SequenceNumber& n) { + size_t i = index(n+1); + if (i >= messages.size()) return; // Nothing to do. + + // Assertion to verify the precondition: no messaages after n. + assert(std::find_if(messages.begin()+i, messages.end(), &isNotDeleted) == + messages.end()); + messages.erase(messages.begin()+i, messages.end()); + if (head >= messages.size()) head = messages.size() - 1; + // Re-count the available messages + available = 0; + for (Deque::iterator i = messages.begin(); i != messages.end(); ++i) { + if (i->status == QueuedMessage::AVAILABLE) ++available; + } +} + void MessageDeque::clean() { - while (messages.size() && messages.front().status == QueuedMessage::DELETED) { + // QPID-4046: If a queue has multiple consumers, then it is possible for a large + // collection of deleted messages to build up. Limit the number of messages cleaned + // up on each call to clean(). + size_t count = 0; + while (messages.size() && messages.front().status == QueuedMessage::DELETED && count < 10) { messages.pop_front(); - if (head) --head; + count += 1; } + head = (head > count) ? head - count : 0; } void MessageDeque::foreach(Functor f) diff --git a/cpp/src/qpid/broker/MessageDeque.h b/cpp/src/qpid/broker/MessageDeque.h index 9b53716d4e..c5670b2a72 100644 --- a/cpp/src/qpid/broker/MessageDeque.h +++ b/cpp/src/qpid/broker/MessageDeque.h @@ -44,7 +44,7 @@ class MessageDeque : public Messages bool consume(QueuedMessage&); bool push(const QueuedMessage& added, QueuedMessage& removed); void updateAcquired(const QueuedMessage& acquired); - + void setPosition(const framing::SequenceNumber&); void foreach(Functor); void removeIf(Predicate); diff --git a/cpp/src/qpid/broker/MessageMap.cpp b/cpp/src/qpid/broker/MessageMap.cpp index 9b164d4e5c..592f3fefde 100644 --- a/cpp/src/qpid/broker/MessageMap.cpp +++ b/cpp/src/qpid/broker/MessageMap.cpp @@ -21,6 +21,7 @@ #include "qpid/broker/MessageMap.h" #include "qpid/broker/QueuedMessage.h" #include "qpid/log/Statement.h" +#include <algorithm> namespace qpid { namespace broker { @@ -130,18 +131,24 @@ bool MessageMap::push(const QueuedMessage& added, QueuedMessage& removed) QueuedMessage& a = messages[added.position]; a = added; a.status = QueuedMessage::AVAILABLE; - QPID_LOG(debug, "Added message at " << a.position); + QPID_LOG(debug, "Added message " << a); 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); result.first->second.status = QueuedMessage::AVAILABLE; - QPID_LOG(debug, "Displaced message at " << removed.position << " with " << result.first->second.position << ": " << result.first->first); + QPID_LOG(debug, "Displaced message " << removed << " with " << result.first->second << ": " << result.first->first); return true; } } +void MessageMap::setPosition(const framing::SequenceNumber& seq) { + // Nothing to do, just assert that the precondition is respected and there + // are no undeleted messages after seq. + (void) seq; assert(messages.empty() || (--messages.end())->first <= seq); +} + void MessageMap::foreach(Functor f) { for (Ordering::iterator i = messages.begin(); i != messages.end(); ++i) { diff --git a/cpp/src/qpid/broker/MessageMap.h b/cpp/src/qpid/broker/MessageMap.h index a668450250..1f0481cb6b 100644 --- a/cpp/src/qpid/broker/MessageMap.h +++ b/cpp/src/qpid/broker/MessageMap.h @@ -6,7 +6,7 @@ * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file +o * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at @@ -50,6 +50,7 @@ class MessageMap : public Messages virtual bool browse(const framing::SequenceNumber&, QueuedMessage&, bool); bool consume(QueuedMessage&); virtual bool push(const QueuedMessage& added, QueuedMessage& removed); + void setPosition(const framing::SequenceNumber&); void foreach(Functor); virtual void removeIf(Predicate); diff --git a/cpp/src/qpid/broker/Messages.h b/cpp/src/qpid/broker/Messages.h index 61e9fa110a..45f5e6cd81 100644 --- a/cpp/src/qpid/broker/Messages.h +++ b/cpp/src/qpid/broker/Messages.h @@ -21,6 +21,7 @@ * under the License. * */ +#include "qpid/framing/SequenceNumber.h" #include <boost/function.hpp> namespace qpid { @@ -101,14 +102,22 @@ class Messages virtual void updateAcquired(const QueuedMessage&) { } /** + * Set the position of the back of the queue. Next message enqueued will be n+1. + *@pre Any messages with seq > n must already be dequeued. + */ + virtual void setPosition(const framing::SequenceNumber& /*n*/) = 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 diff --git a/cpp/src/qpid/broker/NameGenerator.h b/cpp/src/qpid/broker/NameGenerator.h index 6ea25c9797..2e9f7febe2 100644 --- a/cpp/src/qpid/broker/NameGenerator.h +++ b/cpp/src/qpid/broker/NameGenerator.h @@ -32,6 +32,7 @@ namespace qpid { NameGenerator(const std::string& base); std::string generate(); }; + const std::string QPID_NAME_PREFIX("qpid."); // reserved for private names } } diff --git a/cpp/src/qpid/broker/Observers.h b/cpp/src/qpid/broker/Observers.h new file mode 100644 index 0000000000..c62f75d6d0 --- /dev/null +++ b/cpp/src/qpid/broker/Observers.h @@ -0,0 +1,69 @@ +#ifndef QPID_BROKER_OBSERVERS_H +#define QPID_BROKER_OBSERVERS_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR 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 <vector> +#include <algorithm> + +namespace qpid { +namespace broker { + +/** + * Base class for collections of observers with thread-safe add/remove and traversal. + */ +template <class Observer> +class Observers +{ + public: + void add(boost::shared_ptr<Observer> observer) { + sys::Mutex::ScopedLock l(lock); + observers.push_back(observer); + } + + void remove(boost::shared_ptr<Observer> observer) { + sys::Mutex::ScopedLock l(lock); + typename List::iterator i = std::find(observers.begin(), observers.end(), observer); + observers.erase(i); + } + + protected: + typedef std::vector<boost::shared_ptr<Observer> > List; + + sys::Mutex lock; + List observers; + + template <class F> void each(F f) { + List copy; + { + sys::Mutex::ScopedLock l(lock); + copy = observers; + } + std::for_each(copy.begin(), copy.end(), f); + } +}; + +}} // namespace qpid::broker + +#endif /*!QPID_BROKER_OBSERVERS_H*/ diff --git a/cpp/src/qpid/broker/PriorityQueue.cpp b/cpp/src/qpid/broker/PriorityQueue.cpp index ab5ec7235a..9a0fead744 100644 --- a/cpp/src/qpid/broker/PriorityQueue.cpp +++ b/cpp/src/qpid/broker/PriorityQueue.cpp @@ -121,6 +121,10 @@ void PriorityQueue::updateAcquired(const QueuedMessage& acquired) { fifo.updateAcquired(acquired); } +void PriorityQueue::setPosition(const framing::SequenceNumber& n) { + fifo.setPosition(n); +} + void PriorityQueue::foreach(Functor f) { fifo.foreach(f); diff --git a/cpp/src/qpid/broker/PriorityQueue.h b/cpp/src/qpid/broker/PriorityQueue.h index 8628745db1..301367358b 100644 --- a/cpp/src/qpid/broker/PriorityQueue.h +++ b/cpp/src/qpid/broker/PriorityQueue.h @@ -52,6 +52,7 @@ class PriorityQueue : public Messages bool consume(QueuedMessage&); bool push(const QueuedMessage& added, QueuedMessage& removed); void updateAcquired(const QueuedMessage& acquired); + void setPosition(const framing::SequenceNumber&); void foreach(Functor); void removeIf(Predicate); diff --git a/cpp/src/qpid/broker/PrivateImplRef.h b/cpp/src/qpid/broker/PrivateImplRef.h index 5932ab882b..d20c2f4608 100644 --- a/cpp/src/qpid/broker/PrivateImplRef.h +++ b/cpp/src/qpid/broker/PrivateImplRef.h @@ -88,15 +88,15 @@ template <class T> class PrivateImplRef { /** 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); + if (t.impl) intrusive_ptr_release(t.impl); t.impl = p.get(); - if (t.impl) boost::intrusive_ptr_add_ref(t.impl); + if (t.impl) 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 ctor(T& t, Impl* p) { t.impl = p; if (p) 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 void dtor(T& t) { if(t.impl) intrusive_ptr_release(t.impl); } static T& assign(T& t, const T& x) { set(t, get(x)); return t;} }; diff --git a/cpp/src/qpid/broker/Queue.cpp b/cpp/src/qpid/broker/Queue.cpp index e7305c021d..d5267c78dc 100644 --- a/cpp/src/qpid/broker/Queue.cpp +++ b/cpp/src/qpid/broker/Queue.cpp @@ -49,6 +49,7 @@ #include "qpid/types/Variant.h" #include "qmf/org/apache/qpid/broker/ArgsQueuePurge.h" #include "qmf/org/apache/qpid/broker/ArgsQueueReroute.h" +#include "qmf/org/apache/qpid/broker/EventQueueDelete.h" #include <iostream> #include <algorithm> @@ -67,6 +68,7 @@ using qpid::management::ManagementAgent; using qpid::management::ManagementObject; using qpid::management::Manageable; using qpid::management::Args; +using std::string; using std::for_each; using std::mem_fun; namespace _qmf = qmf::org::apache::qpid::broker; @@ -176,7 +178,8 @@ Queue::Queue(const string& _name, bool _autodelete, ManagementAgent* agent = broker->getManagementAgent(); if (agent != 0) { - mgmtObject = new _qmf::Queue(agent, this, parent, _name, _store != 0, _autodelete, _owner != 0); + mgmtObject = new _qmf::Queue(agent, this, parent, _name, _store != 0, _autodelete); + mgmtObject->set_exclusive(_owner != 0); agent->addObject(mgmtObject, 0, store != 0); brokerMgmtObject = (qmf::org::apache::qpid::broker::Broker*) broker->GetManagementObject(); if (brokerMgmtObject) @@ -587,21 +590,51 @@ QueuedMessage Queue::get(){ return msg; } -bool collect_if_expired(std::deque<QueuedMessage>& expired, QueuedMessage& message) +namespace { +bool collectIf(QueuedMessage& qm, Messages::Predicate predicate, + std::deque<QueuedMessage>& collection) { - if (message.payload->hasExpired()) { - expired.push_back(message); + if (predicate(qm)) { + collection.push_back(qm); return true; } else { return false; } } +bool isExpired(const QueuedMessage& qm) { return qm.payload->hasExpired(); } +} // namespace + +void Queue::dequeueIf(Messages::Predicate predicate, + std::deque<QueuedMessage>& dequeued) +{ + { + Mutex::ScopedLock locker(messageLock); + messages->removeIf(boost::bind(&collectIf, _1, predicate, boost::ref(dequeued))); + } + if (!dequeued.empty()) { + if (mgmtObject) { + mgmtObject->inc_acquires(dequeued.size()); + if (brokerMgmtObject) + brokerMgmtObject->inc_acquires(dequeued.size()); + } + for (std::deque<QueuedMessage>::const_iterator i = dequeued.begin(); + i != dequeued.end(); ++i) { + { + // KAG: should be safe to retake lock after the removeIf, since + // no other thread can touch these messages after the removeIf() call + Mutex::ScopedLock locker(messageLock); + observeAcquire(*i, locker); + } + dequeue( 0, *i ); + } + } +} + /** *@param lapse: time since the last purgeExpired */ -void Queue::purgeExpired(qpid::sys::Duration lapse) -{ +void Queue::purgeExpired(sys::Duration lapse) { //As expired messages are discarded during dequeue also, only //bother explicitly expiring if the rate of dequeues since last //attempt is less than one per second. @@ -609,37 +642,18 @@ void Queue::purgeExpired(qpid::sys::Duration lapse) dequeueSincePurge -= count; int seconds = int64_t(lapse)/qpid::sys::TIME_SEC; if (seconds == 0 || count / seconds < 1) { - std::deque<QueuedMessage> expired; - { - Mutex::ScopedLock locker(messageLock); - messages->removeIf(boost::bind(&collect_if_expired, boost::ref(expired), _1)); - } - - if (!expired.empty()) { + std::deque<QueuedMessage> dequeued; + dequeueIf(boost::bind(&isExpired, _1), dequeued); + if (dequeued.size()) { if (mgmtObject) { - mgmtObject->inc_acquires(expired.size()); - mgmtObject->inc_discardsTtl(expired.size()); - if (brokerMgmtObject) { - brokerMgmtObject->inc_acquires(expired.size()); - brokerMgmtObject->inc_discardsTtl(expired.size()); - } - } - - for (std::deque<QueuedMessage>::const_iterator i = expired.begin(); - i != expired.end(); ++i) { - { - // KAG: should be safe to retake lock after the removeIf, since - // no other thread can touch these messages after the removeIf() call - Mutex::ScopedLock locker(messageLock); - observeAcquire(*i, locker); - } - dequeue( 0, *i ); + mgmtObject->inc_discardsTtl(dequeued.size()); + if (brokerMgmtObject) + brokerMgmtObject->inc_discardsTtl(dequeued.size()); } } } } - namespace { // for use with purge/move below - collect messages that match a given filter // @@ -797,6 +811,7 @@ uint32_t Queue::purge(const uint32_t purge_request, boost::shared_ptr<Exchange> // now reroute if necessary if (dest.get()) { assert(qmsg->payload); + qmsg->payload->clearTrace(); DeliverableMessage dmsg(qmsg->payload); dest->routeWithAlternate(dmsg); } @@ -888,9 +903,10 @@ void Queue::push(boost::intrusive_ptr<Message>& msg, bool isRecovery){ if (mgmtObject) { mgmtObject->inc_acquires(); mgmtObject->inc_discardsLvq(); - if (brokerMgmtObject) + if (brokerMgmtObject) { brokerMgmtObject->inc_acquires(); brokerMgmtObject->inc_discardsLvq(); + } } if (isRecovery) { //can't issue new requests for the store until @@ -1470,12 +1486,18 @@ boost::shared_ptr<Exchange> Queue::getAlternateExchange() return alternateExchange; } -void tryAutoDeleteImpl(Broker& broker, Queue::shared_ptr queue) +void tryAutoDeleteImpl(Broker& broker, Queue::shared_ptr queue, const std::string& connectionId, const std::string& userId) { if (broker.getQueues().destroyIf(queue->getName(), boost::bind(boost::mem_fn(&Queue::canAutoDelete), queue))) { QPID_LOG(debug, "Auto-deleting " << queue->getName()); queue->destroyed(); + + if (broker.getManagementAgent()) + broker.getManagementAgent()->raiseEvent(_qmf::EventQueueDelete(connectionId, userId, queue->getName())); + QPID_LOG_CAT(debug, model, "Delete queue. name:" << queue->getName() + << " user:" << userId + << " rhost:" << connectionId ); } } @@ -1483,9 +1505,11 @@ struct AutoDeleteTask : qpid::sys::TimerTask { Broker& broker; Queue::shared_ptr queue; + std::string connectionId; + std::string userId; - AutoDeleteTask(Broker& b, Queue::shared_ptr q, AbsTime fireTime) - : qpid::sys::TimerTask(fireTime, "DelayedAutoDeletion:"+q->getName()), broker(b), queue(q) {} + AutoDeleteTask(Broker& b, Queue::shared_ptr q, const std::string& cId, const std::string& uId, AbsTime fireTime) + : qpid::sys::TimerTask(fireTime, "DelayedAutoDeletion:"+q->getName()), broker(b), queue(q), connectionId(cId), userId(uId) {} void fire() { @@ -1493,19 +1517,19 @@ struct AutoDeleteTask : qpid::sys::TimerTask //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); + tryAutoDeleteImpl(broker, queue, connectionId, userId); } }; -void Queue::tryAutoDelete(Broker& broker, Queue::shared_ptr queue) +void Queue::tryAutoDelete(Broker& broker, Queue::shared_ptr queue, const std::string& connectionId, const std::string& userId) { 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)); + queue->autoDeleteTask = boost::intrusive_ptr<qpid::sys::TimerTask>(new AutoDeleteTask(broker, queue, connectionId, userId, time)); broker.getClusterTimer().add(queue->autoDeleteTask); QPID_LOG(debug, "Timed auto-delete for " << queue->getName() << " initiated"); } else { - tryAutoDeleteImpl(broker, queue); + tryAutoDeleteImpl(broker, queue, connectionId, userId); } } @@ -1659,13 +1683,28 @@ void Queue::query(qpid::types::Variant::Map& results) const if (allocator) allocator->query(results); } +namespace { +struct After { + framing::SequenceNumber seq; + After(framing::SequenceNumber s) : seq(s) {} + bool operator()(const QueuedMessage& qm) { return qm.position > seq; } +}; +} // namespace + + void Queue::setPosition(SequenceNumber n) { Mutex::ScopedLock locker(messageLock); + if (n < sequence) { + std::deque<QueuedMessage> dequeued; + dequeueIf(After(n), dequeued); + messages->setPosition(n); + } sequence = n; QPID_LOG(trace, "Set position to " << sequence << " on " << getName()); } SequenceNumber Queue::getPosition() { + Mutex::ScopedLock locker(messageLock); return sequence; } diff --git a/cpp/src/qpid/broker/Queue.h b/cpp/src/qpid/broker/Queue.h index 9869a698c1..a31e0002ea 100644 --- a/cpp/src/qpid/broker/Queue.h +++ b/cpp/src/qpid/broker/Queue.h @@ -175,6 +175,7 @@ class Queue : public boost::enable_shared_from_this<Queue>, void configureImpl(const qpid::framing::FieldTable& settings); void checkNotDeleted(const Consumer::shared_ptr& c); void notifyDeleted(); + void dequeueIf(Messages::Predicate predicate, std::deque<QueuedMessage>& dequeued); public: @@ -343,7 +344,7 @@ class Queue : public boost::enable_shared_from_this<Queue>, * exclusive owner */ static Queue::shared_ptr restore(QueueRegistry& queues, framing::Buffer& buffer); - static void tryAutoDelete(Broker& broker, Queue::shared_ptr); + static void tryAutoDelete(Broker& broker, Queue::shared_ptr, const std::string& connectionId, const std::string& userId); virtual void setExternalQueueStore(ExternalQueueStore* inst); @@ -375,12 +376,21 @@ class Queue : public boost::enable_shared_from_this<Queue>, 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. + /** + * Set the sequence number for the back of the queue, the + * next message enqueued will be pos+1. + * If pos > getPosition() this creates a gap in the sequence numbers. + * if pos < getPosition() the back of the queue is reset to pos, + * + * The _caller_ must ensure that any messages after pos have been dequeued. + * + * Used by HA/cluster code for queue replication. */ QPID_BROKER_EXTERN void setPosition(framing::SequenceNumber pos); - /** return current position sequence number for the next message on the queue. + + /** + *@return sequence number for the back of the queue. The next message pushed + * will be at getPosition+1 */ QPID_BROKER_EXTERN framing::SequenceNumber getPosition(); QPID_BROKER_EXTERN void addObserver(boost::shared_ptr<QueueObserver>); diff --git a/cpp/src/qpid/broker/QueueFlowLimit.cpp b/cpp/src/qpid/broker/QueueFlowLimit.cpp index f15bb45c01..14fe5f4022 100644 --- a/cpp/src/qpid/broker/QueueFlowLimit.cpp +++ b/cpp/src/qpid/broker/QueueFlowLimit.cpp @@ -75,8 +75,8 @@ namespace { 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>()); + } else if (v->convertsTo<std::string>()) { + std::string s(v->get<std::string>()); QPID_LOG(debug, "Got string value for " << key << ": " << s); std::istringstream convert(s); if (convert >> result && result >= 0) return result; diff --git a/cpp/src/qpid/broker/QueuePolicy.cpp b/cpp/src/qpid/broker/QueuePolicy.cpp index d5b4c1ae86..3978420f4e 100644 --- a/cpp/src/qpid/broker/QueuePolicy.cpp +++ b/cpp/src/qpid/broker/QueuePolicy.cpp @@ -133,8 +133,8 @@ T getCapacity(const FieldTable& settings, const std::string& key, T defaultValue result = v->get<T>(); QPID_LOG(debug, "Got integer value for " << key << ": " << result); if (result >= 0) return result; - } else if (v->convertsTo<string>()) { - string s(v->get<string>()); + } else if (v->convertsTo<std::string>()) { + std::string s(v->get<std::string>()); QPID_LOG(debug, "Got string value for " << key << ": " << s); std::istringstream convert(s); if (convert >> result && result >= 0 && convert.eof()) return result; diff --git a/cpp/src/qpid/broker/QueueRegistry.cpp b/cpp/src/qpid/broker/QueueRegistry.cpp index 236d5ae34c..1401356444 100644 --- a/cpp/src/qpid/broker/QueueRegistry.cpp +++ b/cpp/src/qpid/broker/QueueRegistry.cpp @@ -18,6 +18,7 @@ * under the License. * */ +#include "qpid/broker/Broker.h" #include "qpid/broker/Queue.h" #include "qpid/broker/QueueRegistry.h" #include "qpid/broker/QueueEvents.h" @@ -46,40 +47,49 @@ QueueRegistry::declare(const string& declareName, bool durable, definition from persistente record*/) { - RWlock::ScopedWlock locker(lock); - string name = declareName.empty() ? generateName() : declareName; - assert(!name.empty()); - QueueMap::iterator i = queues.find(name); + Queue::shared_ptr queue; + std::pair<Queue::shared_ptr, bool> result; + { + 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); + if (i == queues.end()) { + queue.reset(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(); + result = std::pair<Queue::shared_ptr, bool>(queue, true); } else { - //i.e. recovering a queue for which we already have a persistent record - queue->configure(arguments); + result = std::pair<Queue::shared_ptr, bool>(i->second, false); } - 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); } + if (broker && queue) broker->getConfigurationObservers().queueCreate(queue); + return result; } -void QueueRegistry::destroyLH (const string& name){ - queues.erase(name); -} - -void QueueRegistry::destroy (const string& name){ - RWlock::ScopedWlock locker(lock); - destroyLH (name); +void QueueRegistry::destroy(const string& name) { + Queue::shared_ptr q; + { + qpid::sys::RWlock::ScopedWlock locker(lock); + QueueMap::iterator i = queues.find(name); + if (i != queues.end()) { + Queue::shared_ptr q = i->second; + queues.erase(i); + } + } + if (broker && q) broker->getConfigurationObservers().queueDestroy(q); } Queue::shared_ptr QueueRegistry::find(const string& name){ diff --git a/cpp/src/qpid/broker/QueueRegistry.h b/cpp/src/qpid/broker/QueueRegistry.h index f724e6b10c..a354513c5f 100644 --- a/cpp/src/qpid/broker/QueueRegistry.h +++ b/cpp/src/qpid/broker/QueueRegistry.h @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -61,7 +61,7 @@ class QueueRegistry { QPID_BROKER_EXTERN std::pair<boost::shared_ptr<Queue>, bool> declare( const std::string& name, bool durable = false, - bool autodelete = false, + bool autodelete = false, const OwnershipToken* owner = 0, boost::shared_ptr<Exchange> alternateExchange = boost::shared_ptr<Exchange>(), const qpid::framing::FieldTable& args = framing::FieldTable(), @@ -82,9 +82,8 @@ class QueueRegistry { 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); + destroy(name); return true; } else { return false; @@ -127,13 +126,13 @@ class QueueRegistry { 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; @@ -144,12 +143,9 @@ private: 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 diff --git a/cpp/src/qpid/broker/RecoveryManagerImpl.cpp b/cpp/src/qpid/broker/RecoveryManagerImpl.cpp index d08409695e..858535637a 100644 --- a/cpp/src/qpid/broker/RecoveryManagerImpl.cpp +++ b/cpp/src/qpid/broker/RecoveryManagerImpl.cpp @@ -144,11 +144,13 @@ RecoverableTransaction::shared_ptr RecoveryManagerImpl::recoverTransaction(const RecoverableConfig::shared_ptr RecoveryManagerImpl::recoverConfig(framing::Buffer& buffer) { string kind; - + uint32_t p = buffer.getPosition(); buffer.getShortString (kind); - if (kind == "link") + buffer.setPosition(p); + + if (Link::isEncodedLink(kind)) return RecoverableConfig::shared_ptr(new RecoverableConfigImpl(Link::decode (links, buffer))); - else if (kind == "bridge") + else if (Bridge::isEncodedBridge(kind)) return RecoverableConfig::shared_ptr(new RecoverableConfigImpl(Bridge::decode (links, buffer))); return RecoverableConfig::shared_ptr(); // TODO: raise an exception instead diff --git a/cpp/src/qpid/broker/SaslAuthenticator.cpp b/cpp/src/qpid/broker/SaslAuthenticator.cpp index 80fa5e1c0e..2d7c820b63 100644 --- a/cpp/src/qpid/broker/SaslAuthenticator.cpp +++ b/cpp/src/qpid/broker/SaslAuthenticator.cpp @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -23,6 +23,7 @@ # include "config.h" #endif +#include "qpid/broker/AclModule.h" #include "qpid/broker/Connection.h" #include "qpid/log/Statement.h" #include "qpid/framing/reply_exceptions.h" @@ -37,6 +38,7 @@ using qpid::sys::cyrus::CyrusSecurityLayer; #endif +using std::string; using namespace qpid::framing; using qpid::sys::SecurityLayer; using qpid::sys::SecuritySettings; @@ -164,13 +166,17 @@ void SaslAuthenticator::fini(void) #endif -std::auto_ptr<SaslAuthenticator> SaslAuthenticator::createAuthenticator(Connection& c, bool isShadow ) +std::auto_ptr<SaslAuthenticator> SaslAuthenticator::createAuthenticator(Connection& c ) { 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)); + // The cluster creates non-authenticated connections for internal shadow connections + // that are never connected to an external client. + if ( !c.isAuthenticated() ) + 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)); @@ -178,7 +184,7 @@ std::auto_ptr<SaslAuthenticator> SaslAuthenticator::createAuthenticator(Connecti } -NullAuthenticator::NullAuthenticator(Connection& c, bool e) : connection(c), client(c.getOutput()), +NullAuthenticator::NullAuthenticator(Connection& c, bool e) : connection(c), client(c.getOutput()), realm(c.getBroker().getOptions().realm), encrypt(e) {} NullAuthenticator::~NullAuthenticator() {} @@ -214,7 +220,7 @@ void NullAuthenticator::start(const string& mechanism, const string* response) } 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? + }//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); @@ -226,7 +232,12 @@ void NullAuthenticator::start(const string& mechanism, const string* response) } } else { connection.setUserId("anonymous"); - } + } + AclModule* acl = connection.getBroker().getAcl(); + if (acl && !acl->approveConnection(connection)) + { + throw ConnectionForcedException("User connection denied by configured limit"); + } client.tune(framing::CHANNEL_MAX, connection.getFrameMax(), 0, connection.getHeartbeatMax()); } @@ -240,7 +251,7 @@ std::auto_ptr<SecurityLayer> NullAuthenticator::getSecurityLayer(uint16_t) #if HAVE_SASL -CyrusAuthenticator::CyrusAuthenticator(Connection& c, bool _encrypt) : +CyrusAuthenticator::CyrusAuthenticator(Connection& c, bool _encrypt) : sasl_conn(0), connection(c), client(c.getOutput()), encrypt(_encrypt) { init(); @@ -271,17 +282,17 @@ void CyrusAuthenticator::init() 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; @@ -319,14 +330,14 @@ void CyrusAuthenticator::init() 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: + * 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; + 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)); @@ -371,10 +382,10 @@ void CyrusAuthenticator::getMechanisms(Array& mechanisms) "", 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"); @@ -382,17 +393,17 @@ void CyrusAuthenticator::getMechanisms(Array& mechanisms) 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++; @@ -404,20 +415,20 @@ void CyrusAuthenticator::start(const string& mechanism, const string* response) { const char *challenge; unsigned int challenge_len; - + // This should be at same debug level as mech list in getMechanisms(). QPID_LOG(info, "SASL: Starting authentication with mechanism: " << mechanism); int code = sasl_server_start(sasl_conn, mechanism.c_str(), (response ? response->c_str() : 0), (response ? response->size() : 0), &challenge, &challenge_len); - + processAuthenticationStep(code, challenge, challenge_len); qmf::org::apache::qpid::broker::Connection* cnxMgmt = connection.getMgmtObject(); - if ( cnxMgmt ) + if ( cnxMgmt ) cnxMgmt->set_saslMechanism(mechanism); } - + void CyrusAuthenticator::step(const string& response) { const char *challenge; @@ -439,10 +450,17 @@ void CyrusAuthenticator::processAuthenticationStep(int code, const char *challen // authentication failure, when one is available throw ConnectionForcedException("Authenticated username unavailable"); } - QPID_LOG(info, connection.getMgmtId() << " SASL: Authentication succeeded for: " << uid); connection.setUserId(uid); + AclModule* acl = connection.getBroker().getAcl(); + if (acl && !acl->approveConnection(connection)) + { + throw ConnectionForcedException("User connection denied by configured limit"); + } + + QPID_LOG(info, connection.getMgmtId() << " SASL: Authentication succeeded for: " << uid); + client.tune(framing::CHANNEL_MAX, connection.getFrameMax(), 0, connection.getHeartbeatMax()); } else if (SASL_CONTINUE == code) { string challenge_str(challenge, challenge_len); @@ -490,7 +508,7 @@ std::auto_ptr<SecurityLayer> CyrusAuthenticator::getSecurityLayer(uint16_t maxFr securityLayer = std::auto_ptr<SecurityLayer>(new CyrusSecurityLayer(sasl_conn, maxFrameSize)); } qmf::org::apache::qpid::broker::Connection* cnxMgmt = connection.getMgmtObject(); - if ( cnxMgmt ) + if ( cnxMgmt ) cnxMgmt->set_saslSsf(ssf); return securityLayer; } diff --git a/cpp/src/qpid/broker/SaslAuthenticator.h b/cpp/src/qpid/broker/SaslAuthenticator.h index 4e5d43214c..e5ecc9f6ec 100644 --- a/cpp/src/qpid/broker/SaslAuthenticator.h +++ b/cpp/src/qpid/broker/SaslAuthenticator.h @@ -54,7 +54,7 @@ public: 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); + static std::auto_ptr<SaslAuthenticator> createAuthenticator(Connection& connection); virtual void callUserIdCallbacks() { } }; diff --git a/cpp/src/qpid/broker/SecureConnectionFactory.cpp b/cpp/src/qpid/broker/SecureConnectionFactory.cpp index 754b443c22..757f6efc59 100644 --- a/cpp/src/qpid/broker/SecureConnectionFactory.cpp +++ b/cpp/src/qpid/broker/SecureConnectionFactory.cpp @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -41,11 +41,6 @@ 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)); @@ -71,5 +66,5 @@ SecureConnectionFactory::create(sys::OutputControl& out, const std::string& id, return sc.release(); } - + }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/SemanticState.cpp b/cpp/src/qpid/broker/SemanticState.cpp index 64924bdd4c..9a84db547c 100644 --- a/cpp/src/qpid/broker/SemanticState.cpp +++ b/cpp/src/qpid/broker/SemanticState.cpp @@ -72,7 +72,8 @@ SemanticState::SemanticState(DeliveryAdapter& da, SessionContext& ss) dtxSelected(false), authMsg(getSession().getBroker().getOptions().auth && !getSession().getConnection().isUserProxyAuth()), userID(getSession().getConnection().getUserId()), - closeComplete(false) + closeComplete(false), + connectionId(getSession().getConnection().getUrl()) {} SemanticState::~SemanticState() { @@ -142,6 +143,7 @@ bool SemanticState::cancel(const string& tag) DeliveryRecords::iterator removed = remove_if(unacked.begin(), unacked.end(), bind(&DeliveryRecord::isRedundant, _1)); unacked.erase(removed, unacked.end()); + getSession().setUnackedCount(unacked.size()); return true; } else { return false; @@ -270,6 +272,7 @@ void SemanticState::checkDtxTimeout() void SemanticState::record(const DeliveryRecord& delivery) { unacked.push_back(delivery); + getSession().setUnackedCount(unacked.size()); } const std::string QPID_SYNC_FREQUENCY("qpid.sync_frequency"); @@ -426,7 +429,7 @@ void SemanticState::cancel(ConsumerImpl::shared_ptr c) if(queue) { queue->cancel(c); if (queue->canAutoDelete() && !queue->hasExclusiveOwner()) { - Queue::tryAutoDelete(session.getBroker(), queue); + Queue::tryAutoDelete(session.getBroker(), queue, connectionId, userID); } } c->cancel(); @@ -555,6 +558,7 @@ void SemanticState::recover(bool requeue) //w.r.t id is lost sort(unacked.begin(), unacked.end()); } + getSession().setUnackedCount(unacked.size()); } void SemanticState::deliver(DeliveryRecord& msg, bool sync) @@ -712,6 +716,7 @@ void SemanticState::release(DeliveryId first, DeliveryId last, bool setRedeliver DeliveryRecords::iterator removed = remove_if(range.start, range.end, bind(&DeliveryRecord::isRedundant, _1)); unacked.erase(removed, range.end); + getSession().setUnackedCount(unacked.size()); } void SemanticState::reject(DeliveryId first, DeliveryId last) @@ -723,6 +728,7 @@ void SemanticState::reject(DeliveryId first, DeliveryId last) if (i->isRedundant()) i = unacked.erase(i); else i++; } + getSession().setUnackedCount(unacked.size()); } bool SemanticState::ConsumerImpl::doOutput() @@ -810,6 +816,7 @@ void SemanticState::accepted(const SequenceSet& commands) { (TransactionContext*) 0))); unacked.erase(removed, unacked.end()); } + getSession().setUnackedCount(unacked.size()); } void SemanticState::completed(const SequenceSet& commands) { @@ -819,6 +826,7 @@ void SemanticState::completed(const SequenceSet& commands) { bind(&SemanticState::complete, this, _1))); unacked.erase(removed, unacked.end()); requestDispatch(); + getSession().setUnackedCount(unacked.size()); } void SemanticState::attached() diff --git a/cpp/src/qpid/broker/SemanticState.h b/cpp/src/qpid/broker/SemanticState.h index e5e1d2da16..15928ce599 100644 --- a/cpp/src/qpid/broker/SemanticState.h +++ b/cpp/src/qpid/broker/SemanticState.h @@ -146,6 +146,8 @@ class SemanticState : private boost::noncopyable { std::string getResumeId() const { return resumeId; }; const std::string& getTag() const { return tag; } uint64_t getResumeTtl() const { return resumeTtl; } + uint32_t getDeliveryCount() const { return deliveryCount; } + void setDeliveryCount(uint32_t _deliveryCount) { deliveryCount = _deliveryCount; } const framing::FieldTable& getArguments() const { return arguments; } SemanticState& getParent() { return *parent; } @@ -180,6 +182,8 @@ class SemanticState : private boost::noncopyable { const bool authMsg; const std::string userID; bool closeComplete; + //needed for queue delete events in auto-delete: + const std::string connectionId; void route(boost::intrusive_ptr<Message> msg, Deliverable& strategy); void checkDtxTimeout(); diff --git a/cpp/src/qpid/broker/SessionAdapter.cpp b/cpp/src/qpid/broker/SessionAdapter.cpp index 78f2e43ce0..ae994a6bd5 100644 --- a/cpp/src/qpid/broker/SessionAdapter.cpp +++ b/cpp/src/qpid/broker/SessionAdapter.cpp @@ -41,6 +41,8 @@ namespace qpid { namespace broker { +using std::string; + using namespace qpid; using namespace qpid::framing; using namespace qpid::framing::dtx; @@ -107,6 +109,12 @@ void SessionAdapter::ExchangeHandlerImpl::declare(const string& exchange, const false, ManagementAgent::toMap(args), "existing")); + QPID_LOG_CAT(debug, model, "Create exchange. name:" << exchange + << " user:" << getConnection().getUserId() + << " rhost:" << getConnection().getUrl() + << " type:" << type + << " alternateExchange:" << alternateExchange + << " durable:" << (durable ? "T" : "F")); } }catch(UnknownExchangeTypeException& /*e*/){ throw NotFoundException(QPID_MSG("Exchange type not implemented: " << type)); @@ -204,7 +212,10 @@ ExchangeBoundResult SessionAdapter::ExchangeHandlerImpl::bound(const std::string } } -SessionAdapter::QueueHandlerImpl::QueueHandlerImpl(SemanticState& session) : HandlerHelper(session), broker(getBroker()) +SessionAdapter::QueueHandlerImpl::QueueHandlerImpl(SemanticState& session) + : HandlerHelper(session), broker(getBroker()), + //record connection id and userid for deleting exclsuive queues after session has ended: + connectionId(getConnection().getUrl()), userId(getConnection().getUserId()) {} @@ -223,7 +234,7 @@ void SessionAdapter::QueueHandlerImpl::destroyExclusiveQueues() Queue::shared_ptr q(exclusiveQueues.front()); q->releaseExclusiveOwnership(); if (q->canAutoDelete()) { - Queue::tryAutoDelete(broker, q); + Queue::tryAutoDelete(broker, q, connectionId, userId); } exclusiveQueues.erase(exclusiveQueues.begin()); } @@ -307,6 +318,14 @@ void SessionAdapter::QueueHandlerImpl::declare(const string& name, const string& agent->raiseEvent(_qmf::EventQueueDeclare(getConnection().getUrl(), getConnection().getUserId(), name, durable, exclusive, autoDelete, alternateExchange, ManagementAgent::toMap(arguments), "existing")); + QPID_LOG_CAT(debug, model, "Create queue. name:" << name + << " user:" << getConnection().getUserId() + << " rhost:" << getConnection().getUrl() + << " durable:" << (durable ? "T" : "F") + << " exclusive:" << (exclusive ? "T" : "F") + << " autodelete:" << (autoDelete ? "T" : "F") + << " alternateExchange:" << alternateExchange + ); } } @@ -411,6 +430,12 @@ SessionAdapter::MessageHandlerImpl::subscribe(const string& queueName, if (agent) agent->raiseEvent(_qmf::EventSubscribe(getConnection().getUrl(), getConnection().getUserId(), queueName, destination, exclusive, ManagementAgent::toMap(arguments))); + QPID_LOG_CAT(debug, model, "Create subscription. queue:" << queueName + << " destination:" << destination + << " user:" << getConnection().getUserId() + << " rhost:" << getConnection().getUrl() + << " exclusive:" << (exclusive ? "T" : "F") + ); } void @@ -423,6 +448,9 @@ SessionAdapter::MessageHandlerImpl::cancel(const string& destination ) ManagementAgent* agent = getBroker().getManagementAgent(); if (agent) agent->raiseEvent(_qmf::EventUnsubscribe(getConnection().getUrl(), getConnection().getUserId(), destination)); + QPID_LOG_CAT(debug, model, "Delete subscription. destination:" << destination + << " user:" << getConnection().getUserId() + << " rhost:" << getConnection().getUrl() ); } void diff --git a/cpp/src/qpid/broker/SessionAdapter.h b/cpp/src/qpid/broker/SessionAdapter.h index bc056538b1..3cc745f96c 100644 --- a/cpp/src/qpid/broker/SessionAdapter.h +++ b/cpp/src/qpid/broker/SessionAdapter.h @@ -121,6 +121,9 @@ class Queue; { Broker& broker; std::vector< boost::shared_ptr<Queue> > exclusiveQueues; + //connectionId and userId are needed for queue-delete events for auto deleted, exclusive queues + std::string connectionId; + std::string userId; public: QueueHandlerImpl(SemanticState& session); diff --git a/cpp/src/qpid/broker/SessionContext.h b/cpp/src/qpid/broker/SessionContext.h index 253ce8dcf2..ee98da1878 100644 --- a/cpp/src/qpid/broker/SessionContext.h +++ b/cpp/src/qpid/broker/SessionContext.h @@ -47,6 +47,7 @@ class SessionContext : public OwnershipToken, public sys::OutputControl virtual uint16_t getChannel() const = 0; virtual const SessionId& getSessionId() const = 0; virtual void addPendingExecutionSync() = 0; + virtual void setUnackedCount(uint64_t) {} }; }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/SessionHandler.cpp b/cpp/src/qpid/broker/SessionHandler.cpp index b58c7c01c5..23fa2ee0ca 100644 --- a/cpp/src/qpid/broker/SessionHandler.cpp +++ b/cpp/src/qpid/broker/SessionHandler.cpp @@ -35,23 +35,39 @@ 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) + clusterOrderProxy(c.getClusterOrderOutput() ? + new SetChannelProxy(ch, c.getClusterOrderOutput()) : 0) {} SessionHandler::~SessionHandler() {} -void SessionHandler::connectionException(framing::connection::CloseCode code, const std::string& msg) { +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); + if (connection.getErrorListener()) + connection.getErrorListener()->connectionError(msg); + if (errorListener) + errorListener->connectionException(code, 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::channelException( + framing::session::DetachCode code, const std::string& msg) +{ + if (connection.getErrorListener()) + connection.getErrorListener()->sessionError(getChannel(), msg); + if (errorListener) + errorListener->channelException(code, msg); } -void SessionHandler::executionException(framing::execution::ErrorCode, const std::string& msg) { - if (connection.getErrorListener()) connection.getErrorListener()->sessionError(getChannel(), msg); +void SessionHandler::executionException( + framing::execution::ErrorCode code, const std::string& msg) +{ + if (connection.getErrorListener()) + connection.getErrorListener()->sessionError(getChannel(), msg); + if (errorListener) + errorListener->executionException(code, msg); } ConnectionState& SessionHandler::getConnection() { return connection; } @@ -64,7 +80,7 @@ void SessionHandler::handleDetach() { if (session.get()) connection.getBroker().getSessionManager().detach(session); assert(!session.get()); - if (detachedCallback) detachedCallback(); + if (errorListener) errorListener->detach(); connection.closeChannel(channel.get()); } @@ -118,8 +134,4 @@ void SessionHandler::attached(const std::string& name) } } -void SessionHandler::setDetachedCallback(boost::function<void()> cb) { - detachedCallback = cb; -} - }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/SessionHandler.h b/cpp/src/qpid/broker/SessionHandler.h index 4e2cfaa963..ab87cf41a4 100644 --- a/cpp/src/qpid/broker/SessionHandler.h +++ b/cpp/src/qpid/broker/SessionHandler.h @@ -25,7 +25,7 @@ #include "qpid/amqp_0_10/SessionHandler.h" #include "qpid/broker/SessionHandler.h" #include "qpid/framing/AMQP_ClientProxy.h" -#include <boost/function.hpp> +#include <boost/shared_ptr.hpp> namespace qpid { class SessionState; @@ -43,6 +43,21 @@ class SessionState; */ class SessionHandler : public amqp_0_10::SessionHandler { public: + class ErrorListener { + public: + virtual ~ErrorListener() {} + 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; + /** Called when it is safe to delete the ErrorListener. */ + virtual void detach() = 0; + }; + + /** + *@param e must not be deleted until ErrorListener::detach has been called */ SessionHandler(Connection&, framing::ChannelId); ~SessionHandler(); @@ -71,7 +86,7 @@ class SessionHandler : public amqp_0_10::SessionHandler { void attached(const std::string& name);//used by 'pushing' inter-broker bridges void attachAs(const std::string& name);//used by 'pulling' inter-broker bridges - void setDetachedCallback(boost::function<void()> cb); + void setErrorListener(boost::shared_ptr<ErrorListener> e) { errorListener = e; } protected: virtual void setState(const std::string& sessionName, bool force); @@ -94,7 +109,7 @@ class SessionHandler : public amqp_0_10::SessionHandler { framing::AMQP_ClientProxy proxy; std::auto_ptr<SessionState> session; std::auto_ptr<SetChannelProxy> clusterOrderProxy; - boost::function<void ()> detachedCallback; + boost::shared_ptr<ErrorListener> errorListener; }; }} // namespace qpid::broker diff --git a/cpp/src/qpid/broker/SessionState.cpp b/cpp/src/qpid/broker/SessionState.cpp index 99407bc3a6..cc02d9ec94 100644 --- a/cpp/src/qpid/broker/SessionState.cpp +++ b/cpp/src/qpid/broker/SessionState.cpp @@ -156,7 +156,7 @@ ManagementObject* SessionState::GetManagementObject (void) const Manageable::status_t SessionState::ManagementMethod (uint32_t methodId, Args& /*args*/, - string& /*text*/) + std::string& /*text*/) { Manageable::status_t status = Manageable::STATUS_UNKNOWN_METHOD; diff --git a/cpp/src/qpid/broker/SessionState.h b/cpp/src/qpid/broker/SessionState.h index 8db232a2d6..a8ff7feff9 100644 --- a/cpp/src/qpid/broker/SessionState.h +++ b/cpp/src/qpid/broker/SessionState.h @@ -126,6 +126,11 @@ class SessionState : public qpid::SessionState, // the SessionState of a received Execution.Sync command. void addPendingExecutionSync(); + void setUnackedCount(uint64_t count) { + if (mgmtObject) + mgmtObject->set_unackedMessages(count); + } + // Used to delay creation of management object for sessions // belonging to inter-broker bridges void addManagementObject(); diff --git a/cpp/src/qpid/broker/System.cpp b/cpp/src/qpid/broker/System.cpp index 8cd2edda76..fa8df6406b 100644 --- a/cpp/src/qpid/broker/System.cpp +++ b/cpp/src/qpid/broker/System.cpp @@ -37,7 +37,6 @@ System::System (string _dataDir, Broker* broker) : mgmtObject(0) if (agent != 0) { - framing::Uuid systemId; if (_dataDir.empty ()) { @@ -66,14 +65,13 @@ System::System (string _dataDir, Broker* broker) : mgmtObject(0) } mgmtObject = new _qmf::System(agent, this, types::Uuid(systemId.c_array())); - std::string sysname, nodename, release, version, machine; - qpid::sys::SystemInfo::getSystemId (sysname, - nodename, + qpid::sys::SystemInfo::getSystemId (osName, + nodeName, release, version, machine); - mgmtObject->set_osName (sysname); - mgmtObject->set_nodeName (nodename); + mgmtObject->set_osName (osName); + mgmtObject->set_nodeName (nodeName); mgmtObject->set_release (release); mgmtObject->set_version (version); mgmtObject->set_machine (machine); diff --git a/cpp/src/qpid/broker/System.h b/cpp/src/qpid/broker/System.h index 0fc2c2bd88..6847c662ae 100644 --- a/cpp/src/qpid/broker/System.h +++ b/cpp/src/qpid/broker/System.h @@ -21,6 +21,7 @@ // #include "qpid/management/Manageable.h" +#include "qpid/framing/Uuid.h" #include "qmf/org/apache/qpid/broker/System.h" #include <boost/shared_ptr.hpp> #include <string> @@ -35,6 +36,8 @@ class System : public management::Manageable private: qmf::org::apache::qpid::broker::System* mgmtObject; + framing::Uuid systemId; + std::string osName, nodeName, release, version, machine; public: @@ -44,6 +47,20 @@ class System : public management::Manageable management::ManagementObject* GetManagementObject (void) const { return mgmtObject; } + + + /** Persistent UUID assigned by the management system to this broker. */ + framing::Uuid getSystemId() const { return systemId; } + /** Returns the OS name; e.g., GNU/Linux or Windows */ + std::string getOsName() const { return osName; } + /** Returns the node name. Usually the same as the host name. */ + std::string getNodeName() const { return nodeName; } + /** Returns the OS release identifier. */ + std::string getRelease() const { return release; } + /** Returns the OS release version (kernel, build, sp, etc.) */ + std::string getVersion() const { return version; } + /** Returns the hardware type. */ + std::string getMachine() const { return machine; } }; }} diff --git a/cpp/src/qpid/broker/TopicExchange.cpp b/cpp/src/qpid/broker/TopicExchange.cpp index dd3ec13019..c11389bb17 100644 --- a/cpp/src/qpid/broker/TopicExchange.cpp +++ b/cpp/src/qpid/broker/TopicExchange.cpp @@ -32,20 +32,8 @@ 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 { +class TopicExchange::ReOriginIter : public BindingNode::TreeIterator { public: ReOriginIter() {}; ~ReOriginIter() {}; @@ -61,7 +49,7 @@ public: // match iterator used by route(): builds BindingList of all unique queues // that match the routing key. -class TopicExchange::BindingsFinderIter : public TopicExchange::BindingNode::TreeIterator { +class TopicExchange::BindingsFinderIter : public BindingNode::TreeIterator { public: BindingsFinderIter(BindingList &bl) : b(bl) {}; ~BindingsFinderIter() {}; @@ -85,7 +73,7 @@ public: // Iterator to visit all bindings until a given queue is found -class TopicExchange::QueueFinderIter : public TopicExchange::BindingNode::TreeIterator { +class TopicExchange::QueueFinderIter : public BindingNode::TreeIterator { public: QueueFinderIter(Queue::shared_ptr queue) : queue(queue), found(false) {}; ~QueueFinderIter() {}; @@ -107,58 +95,7 @@ public: }; -// 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 { +class TopicExchange::Normalizer : public TokenIterator { public: Normalizer(string& p) : TokenIterator(&p[0], &p[0]+p.size()), pattern(p) @@ -230,7 +167,7 @@ bool TopicExchange::bind(Queue::shared_ptr queue, const string& routingKey, cons if (args == 0 || fedOp.empty() || fedOp == fedOpBind) { RWlock::ScopedWlock l(lock); - BindingKey *bk = bindingTree.addBindingKey(routingPattern); + BindingKey *bk = bindingTree.add(routingPattern); if (bk) { Binding::vector& qv(bk->bindingVector); Binding::vector::iterator q; @@ -324,7 +261,7 @@ bool TopicExchange::deleteBinding(Queue::shared_ptr queue, nBindings--; if(qv.empty()) { - bindingTree.removeBindingKey(routingKey); + bindingTree.remove(routingKey); } if (mgmtExchange != 0) { mgmtExchange->dec_bindingCount(); @@ -340,7 +277,7 @@ bool TopicExchange::deleteBinding(Queue::shared_ptr queue, 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 + BindingKey *bk = bindingTree.get(pattern); // Exact match against binding pattern if (!bk) return 0; Binding::vector& qv(bk->bindingVector); Binding::vector::iterator q; @@ -385,7 +322,7 @@ bool TopicExchange::isBound(Queue::shared_ptr queue, const string* const routing } else if (!routingKey && !queue) { return nBindings > 0; } else if (routingKey) { - if (bindingTree.getBindingKey(*routingKey)) { + if (bindingTree.get(*routingKey)) { return true; } } else { @@ -400,294 +337,4 @@ 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/cpp/src/qpid/broker/TopicExchange.h b/cpp/src/qpid/broker/TopicExchange.h index cc24e1411e..46871a1c6b 100644 --- a/cpp/src/qpid/broker/TopicExchange.h +++ b/cpp/src/qpid/broker/TopicExchange.h @@ -28,6 +28,7 @@ #include "qpid/framing/FieldTable.h" #include "qpid/sys/Monitor.h" #include "qpid/broker/Queue.h" +#include "qpid/broker/TopicKeyNode.h" namespace qpid { @@ -35,7 +36,6 @@ namespace broker { class TopicExchange : public virtual Exchange { - struct TokenIterator; class Normalizer; struct BindingKey { // binding for this node @@ -43,129 +43,44 @@ class TopicExchange : public virtual Exchange { 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); - }; + typedef TopicKeyNode<BindingKey> BindingNode; - // Special case: ("#" token) Node in the tree for a match zero or more - class HashNode : public BindingNode { - public: - HashNode(); - ~HashNode() {}; + BindingKey *getQueueBinding(Queue::shared_ptr queue, const std::string& pattern); + bool deleteBinding(Queue::shared_ptr queue, + const std::string& routingKey, + BindingKey *bk); - protected: - virtual bool iterateMatch(TokenIterator& key, TreeIterator& iter); - }; + class ReOriginIter; + class BindingsFinderIter; + class QueueFinderIter; 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; + bool cleared; public: - ClearCache(qpid::sys::RWlock* l, std::map<std::string, BindingList>* bc): cacheLock(l), - bindingCache(bc),cleared(false) {}; + 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; - } + qpid::sys::RWlock::ScopedWlock l(*cacheLock); + if (!cleared) { + bindingCache->clear(); + cleared =true; + } }; ~ClearCache(){ - 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: +public: static const std::string typeName; static QPID_BROKER_EXTERN std::string normalize(const std::string& pattern); @@ -199,7 +114,6 @@ class TopicExchange : public virtual Exchange { }; - } } diff --git a/cpp/src/qpid/broker/TopicKeyNode.h b/cpp/src/qpid/broker/TopicKeyNode.h new file mode 100644 index 0000000000..7671ed069d --- /dev/null +++ b/cpp/src/qpid/broker/TopicKeyNode.h @@ -0,0 +1,371 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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_BROKER_TOPIC_KEY_NODE_ +#define _QPID_BROKER_TOPIC_KEY_NODE_ + +#include "qpid/broker/BrokerImportExport.h" +#include <boost/shared_ptr.hpp> +#include <map> +#include <string> +#include <string.h> + + +namespace qpid { +namespace broker { + +static const std::string STAR("*"); +static const std::string HASH("#"); + + +// Iterate over a string of '.'-separated tokens. +struct TokenIterator { + typedef std::pair<const char*,const char*> Token; + + TokenIterator(const char* b, const char* e) : end(e), token(std::make_pair(b, std::find(b,e,'.'))) {} + + TokenIterator(const std::string& key) : end(&key[0]+key.size()), token(std::make_pair(&key[0], std::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=(std::find(token.first, end, '.')); + } + } + + void pop(std::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 std::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; +}; + + +// 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-->... +// +template <class T> +class QPID_BROKER_CLASS_EXTERN TopicKeyNode { + + public: + + typedef boost::shared_ptr<TopicKeyNode> shared_ptr; + + // for database transversal (visit a node). + class TreeIterator { + public: + TreeIterator() {}; + virtual ~TreeIterator() {}; + virtual bool visit(TopicKeyNode& node) = 0; + }; + + TopicKeyNode() : isStar(false), isHash(false) {} + TopicKeyNode(const std::string& _t) : token(_t), isStar(_t == STAR), isHash(_t == HASH) {} + QPID_BROKER_EXTERN virtual ~TopicKeyNode() { + childTokens.clear(); + } + + // add normalizedRoute to tree, return associated T + QPID_BROKER_EXTERN T* add(const std::string& normalizedRoute) { + TokenIterator bKey(normalizedRoute); + return add(bKey, normalizedRoute); + } + + // return T associated with normalizedRoute + QPID_BROKER_EXTERN T* get(const std::string& normalizedRoute) { + TokenIterator bKey(normalizedRoute); + return get(bKey); + } + + // remove T associated with normalizedRoute + QPID_BROKER_EXTERN void remove(const std::string& normalizedRoute) { + TokenIterator bKey2(normalizedRoute); + remove(bKey2, normalizedRoute); + } + + // applies iter against each node in tree until iter returns false + QPID_BROKER_EXTERN bool iterateAll(TreeIterator& iter) { + if (!iter.visit(*this)) return false; + if (starChild && !starChild->iterateAll(iter)) return false; + if (hashChild && !hashChild->iterateAll(iter)) return false; + for (typename 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 + QPID_BROKER_EXTERN bool iterateMatch(const std::string& routingKey, TreeIterator& iter) { + TokenIterator rKey(routingKey); + return iterateMatch( rKey, iter ); + } + + std::string routePattern; // normalized binding that matches this node + T bindings; // for matches against this node + + private: + + std::string token; // portion of pattern represented by this node + bool isStar; + bool isHash; + + // children + typedef std::map<const std::string, typename TopicKeyNode::shared_ptr> ChildMap; + ChildMap childTokens; + typename TopicKeyNode::shared_ptr starChild; // "*" subtree + typename TopicKeyNode::shared_ptr hashChild; // "#" subtree + + unsigned int getChildCount() { return childTokens.size() + + (starChild ? 1 : 0) + (hashChild ? 1 : 0); } + + T* add(TokenIterator& bKey, const std::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 TopicKeyNode<T>(STAR)); + } + bKey.next(); + return starChild->add(bKey, fullPattern); + + } else if (bKey.match(HASH)) { + if (!hashChild) { + hashChild.reset(new TopicKeyNode<T>(HASH)); + } + bKey.next(); + return hashChild->add(bKey, fullPattern); + + } else { + typename ChildMap::iterator ptr; + std::string next_token; + bKey.pop(next_token); + ptr = childTokens.find(next_token); + if (ptr != childTokens.end()) { + return ptr->second->add(bKey, fullPattern); + } else { + typename TopicKeyNode::shared_ptr child(new TopicKeyNode<T>(next_token)); + childTokens[next_token] = child; + return child->add(bKey, fullPattern); + } + } + } + } + + + bool remove(TokenIterator& bKey, const std::string& fullPattern) { + bool remove; + if (!bKey.finished()) { + if (bKey.match(STAR)) { + bKey.next(); + if (starChild) { + remove = starChild->remove(bKey, fullPattern); + if (remove) { + starChild.reset(); + } + } + } else if (bKey.match(HASH)) { + bKey.next(); + if (hashChild) { + remove = hashChild->remove(bKey, fullPattern); + if (remove) { + hashChild.reset(); + } + } + } else { + typename ChildMap::iterator ptr; + std::string next_token; + bKey.pop(next_token); + ptr = childTokens.find(next_token); + if (ptr != childTokens.end()) { + remove = ptr->second->remove(bKey, fullPattern); + if (remove) { + childTokens.erase(ptr); + } + } + } + } + + // no bindings and no children == parent can delete this node. + return getChildCount() == 0 && bindings.bindingVector.empty(); + } + + + T* get(TokenIterator& bKey) { + if (bKey.finished()) { + return &bindings; + } + + std::string next_token; + bKey.pop(next_token); + + if (next_token == STAR) { + if (starChild) + return starChild->get(bKey); + } else if (next_token == HASH) { + if (hashChild) + return hashChild->get(bKey); + } else { + typename ChildMap::iterator ptr; + ptr = childTokens.find(next_token); + if (ptr != childTokens.end()) { + return ptr->second->get(bKey); + } + } + + return 0; + } + + + bool iterateMatch(TokenIterator& rKey, TreeIterator& iter) { + if (isStar) return iterateMatchStar(rKey, iter); + if (isHash) return iterateMatchHash(rKey, iter); + return iterateMatchString(rKey, iter); + } + + + bool iterateMatchString(TokenIterator& rKey, TreeIterator& iter){ + // invariant: key has matched all previous tokens up to this node. + if (rKey.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(rKey, iter); + } + + + bool iterateMatchStar(TokenIterator& rKey, TreeIterator& iter) { + // must match one token: + if (rKey.finished()) + return true; // match failed, but continue iteration on siblings + + // pop the topmost token + rKey.next(); + + if (rKey.finished()) { + // exact match this node: visit if bound + if (!bindings.bindingVector.empty()) + if (!iter.visit(*this)) return false; + } + + return iterateMatchChildren(rKey, iter); + } + + + bool iterateMatchHash(TokenIterator& rKey, TreeIterator& iter) { + // consume each token and look for a match on the + // remaining key. + while (!rKey.finished()) { + if (!iterateMatchChildren(rKey, iter)) return false; + rKey.next(); + } + + if (!bindings.bindingVector.empty()) + return iter.visit(*this); + + return true; + } + + + bool iterateMatchChildren(const TokenIterator& key, 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); + + typename ChildMap::iterator ptr = childTokens.find(next_token); + if (ptr != childTokens.end()) { + return ptr->second->iterateMatch(newKey, iter); + } + } + } + + return true; + } +}; + +} +} + +#endif diff --git a/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp b/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp index a38e6ac12a..40e74be018 100644 --- a/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp +++ b/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp @@ -32,6 +32,8 @@ using namespace qpid::framing; using qpid::sys::SecurityLayer; +using std::string; + namespace qpid { namespace broker { @@ -79,7 +81,7 @@ void SaslAuthenticator::fini(void) return; } -std::auto_ptr<SaslAuthenticator> SaslAuthenticator::createAuthenticator(Connection& c, bool) +std::auto_ptr<SaslAuthenticator> SaslAuthenticator::createAuthenticator(Connection& c) { if (c.getBroker().getOptions().auth) { return std::auto_ptr<SaslAuthenticator>(new SspiAuthenticator(c)); diff --git a/cpp/src/qpid/broker/windows/SslProtocolFactory.cpp b/cpp/src/qpid/broker/windows/SslProtocolFactory.cpp index 1dff1ddc8f..fb59d058f8 100644 --- a/cpp/src/qpid/broker/windows/SslProtocolFactory.cpp +++ b/cpp/src/qpid/broker/windows/SslProtocolFactory.cpp @@ -44,26 +44,34 @@ namespace qpid { namespace sys { + +class Timer; + namespace windows { struct SslServerOptions : qpid::Options { std::string certStore; + std::string certStoreLocation; std::string certName; uint16_t port; bool clientAuth; SslServerOptions() : qpid::Options("SSL Options"), - certStore("My"), port(5671), clientAuth(false) + certStore("My"), + certStoreLocation("CurrentUser"), + certName("localhost"), + 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-store-location", optValue(certStoreLocation, "NAME"), + "Local store name location for certificates ( CurrentUser | LocalMachine | CurrentService )") ("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), @@ -72,10 +80,12 @@ struct SslServerOptions : qpid::Options }; class SslProtocolFactory : public qpid::sys::ProtocolFactory { - const bool tcpNoDelay; boost::ptr_vector<Socket> listeners; boost::ptr_vector<AsynchAcceptor> acceptors; + Timer& brokerTimer; + uint32_t maxNegotiateTime; uint16_t listeningPort; + const bool tcpNoDelay; std::string brokerHost; const bool clientAuthSelected; std::auto_ptr<qpid::sys::AsynchAcceptor> acceptor; @@ -83,7 +93,9 @@ class SslProtocolFactory : public qpid::sys::ProtocolFactory { CredHandle credHandle; public: - SslProtocolFactory(const SslServerOptions&, const std::string& host, const std::string& port, int backlog, bool nodelay); + SslProtocolFactory(const SslServerOptions&, const std::string& host, const std::string& port, + int backlog, bool nodelay, + Timer& timer, uint32_t maxTime); ~SslProtocolFactory(); void accept(sys::Poller::shared_ptr, sys::ConnectionCodec::Factory*); void connect(sys::Poller::shared_ptr, const std::string& host, const std::string& port, @@ -120,8 +132,8 @@ static struct SslPlugin : public Plugin { const broker::Broker::Options& opts = broker->getOptions(); ProtocolFactory::shared_ptr protocol(new SslProtocolFactory(options, "", boost::lexical_cast<std::string>(options.port), - opts.connectionBacklog, - opts.tcpNoDelay)); + opts.connectionBacklog, opts.tcpNoDelay, + broker->getTimer(), opts.maxNegotiateTime)); QPID_LOG(notice, "Listening for SSL connections on TCP port " << protocol->getPort()); broker->registerProtocolFactory("ssl", protocol); } catch (const std::exception& e) { @@ -132,9 +144,12 @@ static struct SslPlugin : public Plugin { } sslPlugin; SslProtocolFactory::SslProtocolFactory(const SslServerOptions& options, - const std::string& host, const std::string& port, int backlog, - bool nodelay) - : tcpNoDelay(nodelay), + const std::string& host, const std::string& port, + int backlog, bool nodelay, + Timer& timer, uint32_t maxTime) + : brokerTimer(timer), + maxNegotiateTime(maxTime), + tcpNoDelay(nodelay), clientAuthSelected(options.clientAuth) { // Make sure that certificate store is good before listening to sockets @@ -142,11 +157,25 @@ SslProtocolFactory::SslProtocolFactory(const SslServerOptions& options, SecInvalidateHandle(&credHandle); // Get the certificate for this server. + DWORD flags = 0; + std::string certStoreLocation = options.certStoreLocation; + std::transform(certStoreLocation.begin(), certStoreLocation.end(), certStoreLocation.begin(), ::tolower);
+ if (certStoreLocation == "currentuser") { + flags = CERT_SYSTEM_STORE_CURRENT_USER; + } else if (certStoreLocation == "localmachine") { + flags = CERT_SYSTEM_STORE_LOCAL_MACHINE; + } else if (certStoreLocation == "currentservice") { + flags = CERT_SYSTEM_STORE_CURRENT_SERVICE; + } else { + QPID_LOG(error, "Unrecognised SSL certificate store location: " << options.certStoreLocation + << " - Using default location"); + } HCERTSTORE certStoreHandle; certStoreHandle = ::CertOpenStore(CERT_STORE_PROV_SYSTEM_A, X509_ASN_ENCODING, 0, - CERT_SYSTEM_STORE_LOCAL_MACHINE, + flags | + CERT_STORE_READONLY_FLAG, options.certStore.c_str()); if (!certStoreHandle) throw qpid::Exception(QPID_MSG("Opening store " << options.certStore << " " << qpid::sys::strError(GetLastError()))); @@ -252,7 +281,7 @@ void SslProtocolFactory::established(sys::Poller::shared_ptr poller, boost::bind(&AsynchIOHandler::idle, async, _1)); } - async->init(aio, 4); + async->init(aio, brokerTimer, maxNegotiateTime, 4); aio->start(poller); } diff --git a/cpp/src/qpid/client/Connection.cpp b/cpp/src/qpid/client/Connection.cpp index 83a4a35b53..8b4eafccaa 100644 --- a/cpp/src/qpid/client/Connection.cpp +++ b/cpp/src/qpid/client/Connection.cpp @@ -136,7 +136,7 @@ const ConnectionSettings& Connection::getNegotiatedSettings() Session Connection::newSession(const std::string& name, uint32_t timeout) { if (!isOpen()) - throw Exception(QPID_MSG("Connection has not yet been opened")); + throw TransportFailure("Can't create session, connection is not open"); Session s; SessionBase_0_10Access(s).set(impl->newSession(name, timeout)); return s; diff --git a/cpp/src/qpid/client/ConnectionHandler.cpp b/cpp/src/qpid/client/ConnectionHandler.cpp index 94561f8079..91838d8e8b 100644 --- a/cpp/src/qpid/client/ConnectionHandler.cpp +++ b/cpp/src/qpid/client/ConnectionHandler.cpp @@ -258,7 +258,7 @@ void ConnectionHandler::start(const FieldTable& /*serverProps*/, const Array& me } if (sasl.get()) { - string response; + std::string response; if (sasl->start(join(mechlist), response, getSecuritySettings ? getSecuritySettings() : 0)) { proxy.startOk(properties, sasl->getMechanism(), response, locale); } else { @@ -272,7 +272,7 @@ void ConnectionHandler::start(const FieldTable& /*serverProps*/, const Array& me } } else { //TODO: verify that desired mechanism and locale are supported - string response = ((char)0) + username + ((char)0) + password; + std::string response = ((char)0) + username + ((char)0) + password; proxy.startOk(properties, mechanism, response, locale); } } @@ -280,7 +280,7 @@ void ConnectionHandler::start(const FieldTable& /*serverProps*/, const Array& me void ConnectionHandler::secure(const std::string& challenge) { if (sasl.get()) { - string response = sasl->step(challenge); + std::string response = sasl->step(challenge); proxy.secureOk(response); } else { throw NotImplementedException("Challenge-response cycle not yet implemented in client"); diff --git a/cpp/src/qpid/client/PrivateImplRef.h b/cpp/src/qpid/client/PrivateImplRef.h index 503a383c31..fa89b1bfa0 100644 --- a/cpp/src/qpid/client/PrivateImplRef.h +++ b/cpp/src/qpid/client/PrivateImplRef.h @@ -77,15 +77,15 @@ template <class T> class PrivateImplRef { static void set(T& t, const intrusive_ptr& p) { if (t.impl == p) return; - if (t.impl) boost::intrusive_ptr_release(t.impl); + if (t.impl) intrusive_ptr_release(t.impl); t.impl = p.get(); - if (t.impl) boost::intrusive_ptr_add_ref(t.impl); + if (t.impl) 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 ctor(T& t, Impl* p) { t.impl = p; if (p) 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 void dtor(T& t) { if(t.impl) intrusive_ptr_release(t.impl); } static T& assign(T& t, const T& x) { set(t, get(x)); return t;} }; diff --git a/cpp/src/qpid/client/SessionImpl.cpp b/cpp/src/qpid/client/SessionImpl.cpp index 3f3ad617f4..91e728d5ae 100644 --- a/cpp/src/qpid/client/SessionImpl.cpp +++ b/cpp/src/qpid/client/SessionImpl.cpp @@ -426,7 +426,7 @@ void SessionImpl::sendContent(const MethodContent& content) 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)); + std::string frag(content.getData().substr(offset, length)); AMQFrame frame((AMQContentBody(frag))); frame.setFirstSegment(false); frame.setLastSegment(true); diff --git a/cpp/src/qpid/client/SslConnector.cpp b/cpp/src/qpid/client/SslConnector.cpp index ab0c5c4957..4c6fadd28a 100644 --- a/cpp/src/qpid/client/SslConnector.cpp +++ b/cpp/src/qpid/client/SslConnector.cpp @@ -94,8 +94,6 @@ class SslConnector : public Connector sys::ShutdownHandler* shutdownHandler; framing::InputHandler* input; - framing::InitiationHandler* initialiser; - framing::OutputHandler* output; Writer writer; @@ -176,6 +174,7 @@ SslConnector::SslConnector(Poller::shared_ptr p, initiated(false), closed(true), shutdownHandler(0), + input(0), writer(maxFrameSize, cimpl), aio(0), poller(p) diff --git a/cpp/src/qpid/client/SubscriptionManagerImpl.cpp b/cpp/src/qpid/client/SubscriptionManagerImpl.cpp index a558d90be8..7dead112e5 100644 --- a/cpp/src/qpid/client/SubscriptionManagerImpl.cpp +++ b/cpp/src/qpid/client/SubscriptionManagerImpl.cpp @@ -39,6 +39,16 @@ SubscriptionManagerImpl::SubscriptionManagerImpl(const Session& s) : dispatcher(s), session(s), autoStop(true) {} +SubscriptionManagerImpl::~SubscriptionManagerImpl() +{ + sys::Mutex::ScopedLock l(lock); + for (std::map<std::string, Subscription>::iterator i = subscriptions.begin(); i != subscriptions.end(); ++i) { + boost::intrusive_ptr<SubscriptionImpl> s = PrivateImplRef<Subscription>::get(i->second); + if (s) s->cancelDiversion(); + } + subscriptions.clear(); +} + Subscription SubscriptionManagerImpl::subscribe( MessageListener& listener, const std::string& q, const SubscriptionSettings& ss, const std::string& n) { diff --git a/cpp/src/qpid/client/SubscriptionManagerImpl.h b/cpp/src/qpid/client/SubscriptionManagerImpl.h index 6376a05c45..64d922e387 100644 --- a/cpp/src/qpid/client/SubscriptionManagerImpl.h +++ b/cpp/src/qpid/client/SubscriptionManagerImpl.h @@ -99,7 +99,8 @@ class SubscriptionManagerImpl : public sys::Runnable, public RefCounted public: /** Create a new SubscriptionManagerImpl associated with a session */ SubscriptionManagerImpl(const Session& session); - + ~SubscriptionManagerImpl(); + /** * Subscribe a MessagesListener to receive messages from queue. * diff --git a/cpp/src/qpid/client/TCPConnector.cpp b/cpp/src/qpid/client/TCPConnector.cpp index 4660a41c07..1dd951d339 100644 --- a/cpp/src/qpid/client/TCPConnector.cpp +++ b/cpp/src/qpid/client/TCPConnector.cpp @@ -76,6 +76,7 @@ TCPConnector::TCPConnector(Poller::shared_ptr p, initiated(false), closed(true), shutdownHandler(0), + input(0), connector(0), aio(0), poller(p) @@ -265,7 +266,7 @@ size_t TCPConnector::encode(const char* buffer, size_t size) return bytesWritten; } -bool TCPConnector::readbuff(AsynchIO& aio, AsynchIO::BufferBase* buff) +void 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); @@ -280,10 +281,9 @@ bool TCPConnector::readbuff(AsynchIO& aio, AsynchIO::BufferBase* buff) // Give whole buffer back to aio subsystem aio.queueReadBuffer(buff); } - return true; } -size_t TCPConnector::decode(const char* buffer, size_t size) +size_t TCPConnector::decode(const char* buffer, size_t size) { framing::Buffer in(const_cast<char*>(buffer), size); if (!initiated) { diff --git a/cpp/src/qpid/client/TCPConnector.h b/cpp/src/qpid/client/TCPConnector.h index eb3f696013..c87d544816 100644 --- a/cpp/src/qpid/client/TCPConnector.h +++ b/cpp/src/qpid/client/TCPConnector.h @@ -66,8 +66,6 @@ class TCPConnector : public Connector, public sys::Codec sys::ShutdownHandler* shutdownHandler; framing::InputHandler* input; - framing::InitiationHandler* initialiser; - framing::OutputHandler* output; sys::Socket socket; @@ -102,7 +100,7 @@ protected: void start(sys::AsynchIO* aio_); void initAmqp(); virtual void connectFailed(const std::string& msg); - bool readbuff(qpid::sys::AsynchIO&, qpid::sys::AsynchIOBufferBase*); + void readbuff(qpid::sys::AsynchIO&, qpid::sys::AsynchIOBufferBase*); void writebuff(qpid::sys::AsynchIO&); void eof(qpid::sys::AsynchIO&); void disconnected(qpid::sys::AsynchIO&); diff --git a/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp b/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp index a8f4fb5237..2627c178f9 100644 --- a/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp +++ b/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp @@ -241,6 +241,7 @@ class Subscription : public Exchange, public MessageSource const std::string actualType; const bool exclusiveQueue; const bool exclusiveSubscription; + const std::string alternateExchange; FieldTable queueOptions; FieldTable subscriptionOptions; Bindings bindings; @@ -507,7 +508,8 @@ Subscription::Subscription(const Address& address, const std::string& type) durable(Opt(address)/LINK/DURABLE), actualType(type.empty() ? (specifiedType.empty() ? TOPIC_EXCHANGE : specifiedType) : type), exclusiveQueue((Opt(address)/LINK/X_DECLARE/EXCLUSIVE).asBool(true)), - exclusiveSubscription((Opt(address)/LINK/X_SUBSCRIBE/EXCLUSIVE).asBool(exclusiveQueue)) + exclusiveSubscription((Opt(address)/LINK/X_SUBSCRIBE/EXCLUSIVE).asBool(exclusiveQueue)), + alternateExchange((Opt(address)/LINK/X_DECLARE/ALTERNATE_EXCHANGE).str()) { (Opt(address)/LINK/X_DECLARE/ARGUMENTS).collect(queueOptions); (Opt(address)/LINK/X_SUBSCRIBE/ARGUMENTS).collect(subscriptionOptions); @@ -568,7 +570,9 @@ void Subscription::subscribe(qpid::client::AsyncSession& session, const std::str //create subscription queue: session.queueDeclare(arg::queue=queue, arg::exclusive=exclusiveQueue, - arg::autoDelete=!reliable, arg::durable=durable, arg::arguments=queueOptions); + arg::autoDelete=!reliable, arg::durable=durable, + arg::alternateExchange=alternateExchange, + arg::arguments=queueOptions); //'default' binding: bindings.bind(session); //any explicit bindings: diff --git a/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp b/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp index 2ea4dc0c61..aaebec0720 100644 --- a/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp +++ b/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp @@ -78,7 +78,7 @@ bool expired(const sys::AbsTime& start, double timeout) if (timeout == 0) return true; if (timeout == FOREVER) return false; sys::Duration used(start, sys::now()); - sys::Duration allowed(int64_t(timeout*sys::TIME_SEC)); + sys::Duration allowed((int64_t)(timeout*sys::TIME_SEC)); return allowed < used; } diff --git a/cpp/src/qpid/client/amqp0_10/OutgoingMessage.cpp b/cpp/src/qpid/client/amqp0_10/OutgoingMessage.cpp index d93416da75..dd14d11c4c 100644 --- a/cpp/src/qpid/client/amqp0_10/OutgoingMessage.cpp +++ b/cpp/src/qpid/client/amqp0_10/OutgoingMessage.cpp @@ -58,7 +58,12 @@ void OutgoingMessage::convert(const qpid::messaging::Message& from) if (address) { message.getMessageProperties().setReplyTo(AddressResolution::convert(address)); } - translate(from.getProperties(), message.getMessageProperties().getApplicationHeaders()); + if (!subject.empty()) { + Variant v(subject); v.setEncoding("utf8"); + translate(from.getProperties(), SUBJECT, v, message.getMessageProperties().getApplicationHeaders()); + } else { + translate(from.getProperties(), message.getMessageProperties().getApplicationHeaders()); + } if (from.getTtl().getMilliseconds()) { message.getDeliveryProperties().setTtl(from.getTtl().getMilliseconds()); } @@ -89,16 +94,14 @@ void OutgoingMessage::convert(const qpid::messaging::Message& from) } } -void OutgoingMessage::setSubject(const std::string& subject) +void OutgoingMessage::setSubject(const std::string& s) { - if (!subject.empty()) { - message.getMessageProperties().getApplicationHeaders().setString(SUBJECT, subject); - } + subject = s; } std::string OutgoingMessage::getSubject() const { - return message.getMessageProperties().getApplicationHeaders().getAsString(SUBJECT); + return subject; } }}} // namespace qpid::client::amqp0_10 diff --git a/cpp/src/qpid/client/amqp0_10/OutgoingMessage.h b/cpp/src/qpid/client/amqp0_10/OutgoingMessage.h index 0cdd2a2336..2191f45546 100644 --- a/cpp/src/qpid/client/amqp0_10/OutgoingMessage.h +++ b/cpp/src/qpid/client/amqp0_10/OutgoingMessage.h @@ -35,6 +35,7 @@ struct OutgoingMessage { qpid::client::Message message; qpid::client::Completion status; + std::string subject; void convert(const qpid::messaging::Message&); void setSubject(const std::string& subject); diff --git a/cpp/src/qpid/client/amqp0_10/ReceiverImpl.h b/cpp/src/qpid/client/amqp0_10/ReceiverImpl.h index 5693b7b71f..76da4f31a9 100644 --- a/cpp/src/qpid/client/amqp0_10/ReceiverImpl.h +++ b/cpp/src/qpid/client/amqp0_10/ReceiverImpl.h @@ -78,7 +78,6 @@ class ReceiverImpl : public qpid::messaging::ReceiverImpl 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 diff --git a/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp b/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp index f2f0f1a9e5..b275db38d7 100644 --- a/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp +++ b/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp @@ -37,10 +37,10 @@ SenderImpl::SenderImpl(SessionImpl& _parent, const std::string& _name, void SenderImpl::send(const qpid::messaging::Message& message, bool sync) { if (unreliable) { // immutable, don't need lock - UnreliableSend f(*this, &message); + UnreliableSend f(*this, message); parent->execute(f); } else { - Send f(*this, &message); + Send f(*this, message); while (f.repeat) parent->execute(f); } if (sync) parent->sync(true); @@ -117,8 +117,8 @@ 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()); + msg->convert(m); outgoing.push_back(msg.release()); sink->send(session, name, outgoing.back()); } @@ -127,8 +127,8 @@ 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()); + msg.convert(m); sink->send(session, name, msg); } diff --git a/cpp/src/qpid/client/amqp0_10/SenderImpl.h b/cpp/src/qpid/client/amqp0_10/SenderImpl.h index c10c77ae18..d75863c743 100644 --- a/cpp/src/qpid/client/amqp0_10/SenderImpl.h +++ b/cpp/src/qpid/client/amqp0_10/SenderImpl.h @@ -99,32 +99,32 @@ class SenderImpl : public qpid::messaging::SenderImpl struct Send : Command { - const qpid::messaging::Message* message; + const qpid::messaging::Message& message; bool repeat; - Send(SenderImpl& i, const qpid::messaging::Message* m) : Command(i), message(m), repeat(true) {} + 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); + impl.sendImpl(message); } }; struct UnreliableSend : Command { - const qpid::messaging::Message* message; + const qpid::messaging::Message& message; - UnreliableSend(SenderImpl& i, const qpid::messaging::Message* m) : Command(i), message(m) {} + 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); + impl.sendUnreliable(message); } }; diff --git a/cpp/src/qpid/client/windows/SslConnector.cpp b/cpp/src/qpid/client/windows/SslConnector.cpp index 785c817928..2aa31e8202 100644 --- a/cpp/src/qpid/client/windows/SslConnector.cpp +++ b/cpp/src/qpid/client/windows/SslConnector.cpp @@ -68,7 +68,7 @@ class SslConnector : public qpid::client::TCPConnector // 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 redirectReadbuff(qpid::sys::AsynchIO&, qpid::sys::AsynchIOBufferBase*); void redirectWritebuff(qpid::sys::AsynchIO&); void redirectEof(qpid::sys::AsynchIO&); @@ -111,9 +111,9 @@ void SslConnector::negotiationDone(SECURITY_STATUS status) connectFailed(QPID_MSG(qpid::sys::strError(status))); } -bool SslConnector::redirectReadbuff(qpid::sys::AsynchIO& a, +void SslConnector::redirectReadbuff(qpid::sys::AsynchIO& a, qpid::sys::AsynchIOBufferBase* b) { - return readbuff(a, b); + readbuff(a, b); } void SslConnector::redirectWritebuff(qpid::sys::AsynchIO& a) { diff --git a/cpp/src/qpid/cluster/Connection.cpp b/cpp/src/qpid/cluster/Connection.cpp index 512e0f03cb..ff855eef18 100644 --- a/cpp/src/qpid/cluster/Connection.cpp +++ b/cpp/src/qpid/cluster/Connection.cpp @@ -58,6 +58,8 @@ namespace qpid { namespace cluster { +using std::string; + using namespace framing; using namespace framing::cluster; using amqp_0_10::ListCodec; @@ -83,7 +85,9 @@ 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), + connectionCtor(&output, cluster.getBroker(), mgmtId, external, + false/*isLink*/, 0/*objectId*/, true/*shadow*/, false/*delayManagement*/, + false/*authenticated*/), expectProtocolHeader(false), mcastFrameHandler(cluster.getMulticast(), self), updateIn(c.getUpdateReceiver()), @@ -100,9 +104,10 @@ Connection::Connection(Cluster& c, sys::ConnectionOutputHandler& out, external, isLink, isCatchUp ? ++catchUpId : 0, - // The first catch-up connection is not considered a shadow - // as it needs to be authenticated. - isCatchUp && self.second > 1), + // The first catch-up connection is not a shadow + isCatchUp && self.second > 1, + false, // delayManagement + true), // catch up connecytions are authenticated expectProtocolHeader(isLink), mcastFrameHandler(cluster.getMulticast(), self), updateIn(c.getUpdateReceiver()), @@ -272,6 +277,8 @@ void Connection::closed() { if (announced) cluster.getMulticast().mcastControl( ClusterConnectionDeliverCloseBody(), self); + else + close(); } } catch (const std::exception& e) { @@ -404,11 +411,12 @@ void Connection::shadowSetUser(const std::string& userId) { } void Connection::consumerState(const string& name, bool blocked, bool notifyEnabled, const SequenceNumber& position, - uint32_t usedMsgCredit, uint32_t usedByteCredit) + uint32_t usedMsgCredit, uint32_t usedByteCredit, const uint32_t deliveryCount) { broker::SemanticState::ConsumerImpl::shared_ptr c = semanticState().find(name); c->setPosition(position); c->setBlocked(blocked); + c->setDeliveryCount(deliveryCount); if (c->getCredit().isWindowMode()) c->getCredit().consume(usedMsgCredit, usedByteCredit); if (notifyEnabled) c->enableNotify(); else c->disableNotify(); updateIn.consumerNumbering.add(c); @@ -522,6 +530,7 @@ broker::QueuedMessage Connection::getUpdateMessage() { boost::shared_ptr<broker::Queue> updateq = findQueue(UpdateClient::UPDATE); assert(!updateq->isDurable()); broker::QueuedMessage m = updateq->get(); + updateq->dequeue(0, m); if (!m.payload) throw Exception(QPID_MSG(cluster << " empty update queue")); return m; } @@ -782,16 +791,18 @@ void Connection::managementSetupState( void Connection::config(const std::string& encoded) { Buffer buf(const_cast<char*>(encoded.data()), encoded.size()); string kind; + uint32_t p = buf.getPosition(); buf.getShortString (kind); - if (kind == "link") { + buf.setPosition(p); + if (broker::Link::isEncodedLink(kind)) { broker::Link::shared_ptr link = - broker::Link::decode(cluster.getBroker().getLinks(), buf); + broker::Link::decode(cluster.getBroker().getLinks(), buf); QPID_LOG(debug, cluster << " updated link " << link->getHost() << ":" << link->getPort()); } - else if (kind == "bridge") { + else if (broker::Bridge::isEncodedBridge(kind)) { broker::Bridge::shared_ptr bridge = - broker::Bridge::decode(cluster.getBroker().getLinks(), buf); + 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)); diff --git a/cpp/src/qpid/cluster/Connection.h b/cpp/src/qpid/cluster/Connection.h index 26514c76e2..b0e7b3bd9e 100644 --- a/cpp/src/qpid/cluster/Connection.h +++ b/cpp/src/qpid/cluster/Connection.h @@ -110,7 +110,7 @@ class Connection : void deliveredFrame(const EventFrame&); void consumerState(const std::string& name, bool blocked, bool notifyEnabled, const qpid::framing::SequenceNumber& position, - uint32_t usedMsgCredit, uint32_t usedByteCredit); + uint32_t usedMsgCredit, uint32_t usedByteCredit, const uint32_t deliveryCount); // ==== Used in catch-up mode to build initial state. // @@ -228,6 +228,7 @@ class Connection : uint64_t objectId; bool shadow; bool delayManagement; + bool authenticated; ConnectionCtor( sys::ConnectionOutputHandler* out_, @@ -237,17 +238,18 @@ class Connection : bool isLink_=false, uint64_t objectId_=0, bool shadow_=false, - bool delayManagement_=false + bool delayManagement_=false, + bool authenticated_=true ) : out(out_), broker(broker_), mgmtId(mgmtId_), external(external_), isLink(isLink_), objectId(objectId_), shadow(shadow_), - delayManagement(delayManagement_) + delayManagement(delayManagement_), authenticated(authenticated_) {} std::auto_ptr<broker::Connection> construct() { return std::auto_ptr<broker::Connection>( new broker::Connection( out, broker, mgmtId, external, isLink, objectId, - shadow, delayManagement) + shadow, delayManagement, authenticated) ); } }; diff --git a/cpp/src/qpid/cluster/ConnectionCodec.cpp b/cpp/src/qpid/cluster/ConnectionCodec.cpp index d0ba8abfb3..54327fbfe2 100644 --- a/cpp/src/qpid/cluster/ConnectionCodec.cpp +++ b/cpp/src/qpid/cluster/ConnectionCodec.cpp @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -22,6 +22,7 @@ #include "qpid/cluster/Connection.h" #include "qpid/cluster/Cluster.h" #include "qpid/cluster/ProxyInputHandler.h" +#include "qpid/broker/AclModule.h" #include "qpid/broker/Connection.h" #include "qpid/framing/ConnectionCloseBody.h" #include "qpid/framing/ConnectionCloseOkBody.h" @@ -40,17 +41,10 @@ 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 new ConnectionCodec(v, out, id, cluster, true, false, external); return 0; } diff --git a/cpp/src/qpid/cluster/Cpg.cpp b/cpp/src/qpid/cluster/Cpg.cpp index 0856bcd824..6e9e22a42f 100644 --- a/cpp/src/qpid/cluster/Cpg.cpp +++ b/cpp/src/qpid/cluster/Cpg.cpp @@ -32,7 +32,7 @@ // 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))) + if ((RESULT) != CS_OK) throw Exception(errorStr((RESULT), (MSG))) namespace qpid { namespace cluster { @@ -50,13 +50,13 @@ Cpg* Cpg::cpgFromHandle(cpg_handle_t handle) { // Applies the same retry-logic to all cpg calls that need it. void Cpg::callCpg ( CpgOp & c ) { - cpg_error_t result; + cs_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))) { + if ( CS_OK == (result = c.op(handle, & group))) { break; } - else if ( result == CPG_ERR_TRY_AGAIN ) { + else if ( result == CS_ERR_TRY_AGAIN ) { QPID_LOG(info, "Retrying " << c.opName ); sys::usleep ( snooze ); snooze *= 10; @@ -65,7 +65,7 @@ void Cpg::callCpg ( CpgOp & c ) { else break; // Don't retry unless CPG tells us to. } - if ( result != CPG_OK ) + if ( result != CS_OK ) CPG_CHECK(result, c.msg(group)); } @@ -127,9 +127,9 @@ Cpg::Cpg(Handler& h) : IOHandle(new sys::IOHandlePrivate), handler(h), isShutdow callbacks.cpg_confchg_fn = &globalConfigChange; QPID_LOG(notice, "Initializing CPG"); - cpg_error_t err = cpg_initialize(&handle, &callbacks); + cs_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) { + while (err == CS_ERR_TRY_AGAIN && --retries) { QPID_LOG(notice, "Re-trying CPG initialization."); sys::sleep(5); err = cpg_initialize(&handle, &callbacks); @@ -169,11 +169,11 @@ bool Cpg::mcast(const iovec* iov, int iovLen) { if (flowState == CPG_FLOW_CONTROL_ENABLED) return false; - cpg_error_t result; + cs_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); + if (result != CS_ERR_TRY_AGAIN) CPG_CHECK(result, cantMcastMsg(group)); + } while(result == CS_ERR_TRY_AGAIN); return true; } @@ -187,34 +187,34 @@ void Cpg::shutdown() { } void Cpg::dispatchOne() { - CPG_CHECK(cpg_dispatch(handle,CPG_DISPATCH_ONE), "Error in CPG dispatch"); + CPG_CHECK(cpg_dispatch(handle,CS_DISPATCH_ONE), "Error in CPG dispatch"); } void Cpg::dispatchAll() { - CPG_CHECK(cpg_dispatch(handle,CPG_DISPATCH_ALL), "Error in CPG dispatch"); + CPG_CHECK(cpg_dispatch(handle,CS_DISPATCH_ALL), "Error in CPG dispatch"); } void Cpg::dispatchBlocking() { - CPG_CHECK(cpg_dispatch(handle,CPG_DISPATCH_BLOCKING), "Error in CPG dispatch"); + CPG_CHECK(cpg_dispatch(handle,CS_DISPATCH_BLOCKING), "Error in CPG dispatch"); } -string Cpg::errorStr(cpg_error_t err, const std::string& msg) { +string Cpg::errorStr(cs_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; + case CS_OK: os << "ok"; break; + case CS_ERR_LIBRARY: os << "library"; break; + case CS_ERR_TIMEOUT: os << "timeout"; break; + case CS_ERR_TRY_AGAIN: os << "try again"; break; + case CS_ERR_INVALID_PARAM: os << "invalid param"; break; + case CS_ERR_NO_MEMORY: os << "no memory"; break; + case CS_ERR_BAD_HANDLE: os << "bad handle"; break; + case CS_ERR_ACCESS: os << "access denied. You may need to set your group ID to 'ais'"; break; + case CS_ERR_NOT_EXIST: os << "not exist"; break; + case CS_ERR_EXIST: os << "exist"; break; + case CS_ERR_NOT_SUPPORTED: os << "not supported"; break; + case CS_ERR_SECURITY: os << "security"; break; + case CS_ERR_TOO_MANY_GROUPS: os << "too many groups"; break; default: os << ": unknown cpg error " << err; }; os << " (" << err << ")"; diff --git a/cpp/src/qpid/cluster/Cpg.h b/cpp/src/qpid/cluster/Cpg.h index 6b81c602bd..1afbce8d75 100644 --- a/cpp/src/qpid/cluster/Cpg.h +++ b/cpp/src/qpid/cluster/Cpg.h @@ -131,7 +131,7 @@ class Cpg : public sys::IOHandle { CpgOp ( std::string opName ) : opName(opName) { } - virtual cpg_error_t op ( cpg_handle_t handle, struct cpg_name * ) = 0; + virtual cs_error_t op ( cpg_handle_t handle, struct cpg_name * ) = 0; virtual std::string msg(const Name&) = 0; virtual ~CpgOp ( ) { } }; @@ -141,7 +141,7 @@ class Cpg : public sys::IOHandle { CpgJoinOp ( ) : CpgOp ( std::string("cpg_join") ) { } - cpg_error_t op(cpg_handle_t handle, struct cpg_name * group) { + cs_error_t op(cpg_handle_t handle, struct cpg_name * group) { return cpg_join ( handle, group ); } @@ -152,7 +152,7 @@ class Cpg : public sys::IOHandle { CpgLeaveOp ( ) : CpgOp ( std::string("cpg_leave") ) { } - cpg_error_t op(cpg_handle_t handle, struct cpg_name * group) { + cs_error_t op(cpg_handle_t handle, struct cpg_name * group) { return cpg_leave ( handle, group ); } @@ -163,7 +163,7 @@ class Cpg : public sys::IOHandle { CpgFinalizeOp ( ) : CpgOp ( std::string("cpg_finalize") ) { } - cpg_error_t op(cpg_handle_t handle, struct cpg_name *) { + cs_error_t op(cpg_handle_t handle, struct cpg_name *) { return cpg_finalize ( handle ); } @@ -177,7 +177,7 @@ class Cpg : public sys::IOHandle { CpgLeaveOp cpgLeaveOp; CpgFinalizeOp cpgFinalizeOp; - static std::string errorStr(cpg_error_t err, const std::string& msg); + static std::string errorStr(cs_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&); diff --git a/cpp/src/qpid/cluster/UpdateClient.cpp b/cpp/src/qpid/cluster/UpdateClient.cpp index 20684fd8a7..8737418570 100644 --- a/cpp/src/qpid/cluster/UpdateClient.cpp +++ b/cpp/src/qpid/cluster/UpdateClient.cpp @@ -74,6 +74,8 @@ namespace qpid { namespace cluster { +using std::string; + using amqp_0_10::ListCodec; using broker::Broker; using broker::Exchange; @@ -87,6 +89,8 @@ using namespace framing; namespace arg=client::arg; using client::SessionBase_0_10Access; +namespace _qmf = qmf::org::apache::qpid::broker; + // Reserved exchange/queue name for catch-up, avoid clashes with user queues/exchanges. const std::string UpdateClient::UPDATE("x-qpid.cluster-update"); // Name for header used to carry expiration information. @@ -226,14 +230,6 @@ template <class T> std::string encode(const T& t) { 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 @@ -377,13 +373,14 @@ class MessageUpdater { void UpdateClient::updateQueue(client::AsyncSession& s, const boost::shared_ptr<Queue>& q) { broker::Exchange::shared_ptr alternateExchange = q->getAlternateExchange(); + _qmf::Queue* mgmtQueue = dynamic_cast<_qmf::Queue*>(q->GetManagementObject()); 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() + arg::exclusive = mgmtQueue && mgmtQueue->get_exclusive() ); MessageUpdater updater(q->getName(), s, expiry); q->eachMessage(boost::bind(&MessageUpdater::updateQueuedMessage, &updater, _1)); @@ -545,7 +542,8 @@ void UpdateClient::updateConsumer( ci->isNotifyEnabled(), ci->getPosition(), ci->getCredit().used().messages, - ci->getCredit().used().bytes + ci->getCredit().used().bytes, + ci->getDeliveryCount() ); consumerNumbering.add(ci.get()); diff --git a/cpp/src/qpid/cluster/types.h b/cpp/src/qpid/cluster/types.h index bfb4fd5b9e..c8ffb0b804 100644 --- a/cpp/src/qpid/cluster/types.h +++ b/cpp/src/qpid/cluster/types.h @@ -34,6 +34,29 @@ extern "C" { #if defined (HAVE_OPENAIS_CPG_H) # include <openais/cpg.h> + +// Provide translations back to the deprecated definitions in openais +typedef cpg_error_t cs_error_t; +#define CS_DISPATCH_ONE CPG_DISPATCH_ONE +#define CS_DISPATCH_ALL CPG_DISPATCH_ALL +#define CS_DISPATCH_BLOCKING CPG_DISPATCH_BLOCKING +#define CS_FLOW_CONTROL_DISABLED CPG_FLOW_CONTROL_DISABLED +#define CS_FLOW_CONTROL_ENABLED CPG_FLOW_CONTROL_ENABLED +#define CS_OK CPG_OK +#define CS_ERR_LIBRARY CPG_ERR_LIBRARY +#define CS_ERR_TIMEOUT CPG_ERR_TIMEOUT +#define CS_ERR_TRY_AGAIN CPG_ERR_TRY_AGAIN +#define CS_ERR_INVALID_PARAM CPG_ERR_INVALID_PARAM +#define CS_ERR_NO_MEMORY CPG_ERR_NO_MEMORY +#define CS_ERR_BAD_HANDLE CPG_ERR_BAD_HANDLE +#define CS_ERR_BUSY CPG_ERR_BUSY +#define CS_ERR_ACCESS CPG_ERR_ACCESS +#define CS_ERR_NOT_EXIST CPG_ERR_NOT_EXIST +#define CS_ERR_EXIST CPG_ERR_EXIST +#define CS_ERR_NOT_SUPPORTED CPG_ERR_NOT_SUPPORTED +#define CS_ERR_SECURITY CPG_ERR_SECURITY +#define CS_ERR_TOO_MANY_GROUPS CPG_ERR_TOO_MANY_GROUPS + #elif defined (HAVE_COROSYNC_CPG_H) # include <corosync/cpg.h> #else diff --git a/cpp/src/qpid/console/ClassKey.cpp b/cpp/src/qpid/console/ClassKey.cpp index 7a16113bae..d4b59fc413 100644 --- a/cpp/src/qpid/console/ClassKey.cpp +++ b/cpp/src/qpid/console/ClassKey.cpp @@ -21,6 +21,7 @@ #include "qpid/console/ClassKey.h" #include <string.h> +#include <iostream> #include <cstdio> using namespace std; diff --git a/cpp/src/qpid/framing/AMQCommandControlBody.h b/cpp/src/qpid/framing/AMQCommandControlBody.h deleted file mode 100644 index d12b70a168..0000000000 --- a/cpp/src/qpid/framing/AMQCommandControlBody.h +++ /dev/null @@ -1,70 +0,0 @@ -#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/cpp/src/qpid/framing/AMQContentBody.cpp b/cpp/src/qpid/framing/AMQContentBody.cpp index 72f7d9978e..18f6994f8f 100644 --- a/cpp/src/qpid/framing/AMQContentBody.cpp +++ b/cpp/src/qpid/framing/AMQContentBody.cpp @@ -24,7 +24,7 @@ qpid::framing::AMQContentBody::AMQContentBody(){ } -qpid::framing::AMQContentBody::AMQContentBody(const string& _data) : data(_data){ +qpid::framing::AMQContentBody::AMQContentBody(const std::string& _data) : data(_data){ } uint32_t qpid::framing::AMQContentBody::encodedSize() const{ diff --git a/cpp/src/qpid/framing/AMQContentBody.h b/cpp/src/qpid/framing/AMQContentBody.h index e25451e354..148b293a2f 100644 --- a/cpp/src/qpid/framing/AMQContentBody.h +++ b/cpp/src/qpid/framing/AMQContentBody.h @@ -31,15 +31,15 @@ namespace framing { class QPID_COMMON_CLASS_EXTERN AMQContentBody : public AMQBody { - string data; + std::string data; public: QPID_COMMON_EXTERN AMQContentBody(); - QPID_COMMON_EXTERN AMQContentBody(const string& data); + QPID_COMMON_EXTERN AMQContentBody(const std::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; } + inline const std::string& getData() const { return data; } + inline std::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); diff --git a/cpp/src/qpid/framing/AMQFrame.cpp b/cpp/src/qpid/framing/AMQFrame.cpp index 5b9673f0d0..5e065d598c 100644 --- a/cpp/src/qpid/framing/AMQFrame.cpp +++ b/cpp/src/qpid/framing/AMQFrame.cpp @@ -85,7 +85,7 @@ bool AMQFrame::decode(Buffer& buffer) { if(buffer.available() < frameOverhead()) return false; - buffer.record(); + uint32_t pos = buffer.getPosition(); uint8_t flags = buffer.getOctet(); uint8_t framing_version = (flags & 0xc0) >> 6; @@ -115,7 +115,7 @@ bool AMQFrame::decode(Buffer& buffer) // B,E,b,e flags uint16_t body_size = frame_size - frameOverhead(); if (buffer.available() < body_size){ - buffer.restore(); + buffer.setPosition(pos); return false; } diff --git a/cpp/src/qpid/framing/Buffer.cpp b/cpp/src/qpid/framing/Buffer.cpp index 5a5bc0325e..b71915aeb7 100644 --- a/cpp/src/qpid/framing/Buffer.cpp +++ b/cpp/src/qpid/framing/Buffer.cpp @@ -23,27 +23,17 @@ #include "qpid/Msg.h" #include <string.h> #include <boost/format.hpp> + namespace qpid { namespace framing { +using std::string; + 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; } diff --git a/cpp/src/qpid/framing/FieldTable.cpp b/cpp/src/qpid/framing/FieldTable.cpp index 0f7140b627..cd38db1ee8 100644 --- a/cpp/src/qpid/framing/FieldTable.cpp +++ b/cpp/src/qpid/framing/FieldTable.cpp @@ -196,7 +196,7 @@ void FieldTable::setFloat(const std::string& name, const float value){ flushRawCache(); } -void FieldTable::setDouble(const std::string& name, double value){ +void FieldTable::setDouble(const std::string& name, const double value){ realDecode(); values[name] = ValuePtr(new DoubleValue(value)); flushRawCache(); diff --git a/cpp/src/qpid/framing/ProtocolInitiation.cpp b/cpp/src/qpid/framing/ProtocolInitiation.cpp index e617015d64..00ddb55a3b 100644 --- a/cpp/src/qpid/framing/ProtocolInitiation.cpp +++ b/cpp/src/qpid/framing/ProtocolInitiation.cpp @@ -20,6 +20,8 @@ */ #include "qpid/framing/ProtocolInitiation.h" +#include <iostream> + namespace qpid { namespace framing { diff --git a/cpp/src/qpid/framing/Uuid.cpp b/cpp/src/qpid/framing/Uuid.cpp index b3d1e2e1e4..e377c1172d 100644 --- a/cpp/src/qpid/framing/Uuid.cpp +++ b/cpp/src/qpid/framing/Uuid.cpp @@ -43,6 +43,13 @@ Uuid::Uuid(const uint8_t* data) { assign(data); } +Uuid::Uuid(const std::string& s) { + if (s.size() != UNPARSED_SIZE) + throw IllegalArgumentException(QPID_MSG("Invalid UUID: " << s)); + if (uuid_parse(const_cast<char *>(&s[0]), c_array()) != 0) + throw IllegalArgumentException(QPID_MSG("Invalid UUID: " << s)); +} + 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 diff --git a/cpp/src/qpid/ha/AlternateExchangeSetter.h b/cpp/src/qpid/ha/AlternateExchangeSetter.h new file mode 100644 index 0000000000..08690e68bc --- /dev/null +++ b/cpp/src/qpid/ha/AlternateExchangeSetter.h @@ -0,0 +1,73 @@ +#ifndef QPID_HA_ALTERNATEEXCHANGESETTER_H +#define QPID_HA_ALTERNATEEXCHANGESETTER_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR 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/Exchange.h" +#include "qpid/broker/ExchangeRegistry.h" +#include "boost/function.hpp" +#include <map> + +namespace qpid { +namespace ha { + +/** + * Sets the alternate exchange on queues and exchanges. + * Holds onto queues/exchanges if necessary till the alternate exchange is available. + * THREAD UNSAFE + */ +class AlternateExchangeSetter +{ + public: + typedef boost::function<void(boost::shared_ptr<broker::Exchange>)> SetFunction; + + AlternateExchangeSetter(broker::ExchangeRegistry& er) : exchanges(er) {} + + void setAlternate(const std::string& altEx, const SetFunction& setter) { + broker::Exchange::shared_ptr ex = exchanges.find(altEx); + if (ex) setter(ex); // Set immediately. + else setters.insert(Setters::value_type(altEx, setter)); // Save for later. + } + + void addExchange(boost::shared_ptr<broker::Exchange> exchange) { + // Update the setters for this exchange + std::pair<Setters::iterator, Setters::iterator> range = setters.equal_range(exchange->getName()); + for (Setters::iterator i = range.first; i != range.second; ++i) + i->second(exchange); + setters.erase(range.first, range.second); + } + + void clear() { + if (!setters.empty()) + QPID_LOG(warning, "Some alternate exchanges were not resolved."); + setters.clear(); + } + + private: + typedef std::multimap<std::string, SetFunction> Setters; + broker::ExchangeRegistry& exchanges; + Setters setters; +}; +}} // namespace qpid::ha + +#endif /*!QPID_HA_ALTERNATEEXCHANGESETTER_H*/ diff --git a/cpp/src/qpid/ha/Backup.cpp b/cpp/src/qpid/ha/Backup.cpp index 3f3fa87a01..bac6fd23c8 100644 --- a/cpp/src/qpid/ha/Backup.cpp +++ b/cpp/src/qpid/ha/Backup.cpp @@ -20,7 +20,6 @@ */ #include "Backup.h" #include "BrokerReplicator.h" -#include "ConnectionExcluder.h" #include "HaBroker.h" #include "ReplicatingSubscription.h" #include "Settings.h" @@ -34,6 +33,7 @@ #include "qpid/framing/AMQFrame.h" #include "qpid/framing/FieldTable.h" #include "qpid/framing/MessageTransferBody.h" +#include "qpid/sys/SystemInfo.h" #include "qpid/types/Variant.h" namespace qpid { @@ -45,47 +45,85 @@ using types::Variant; using std::string; Backup::Backup(HaBroker& hb, const Settings& s) : - haBroker(hb), broker(hb.getBroker()), settings(s), excluder(new ConnectionExcluder()) + logPrefix("Backup: "), haBroker(hb), broker(hb.getBroker()), settings(s) { - // Exclude client connections before starting the link to avoid self-connection. - broker.getConnectionObservers().add(excluder); - // Empty brokerUrl means delay initialization until setUrl() is called. + // Empty brokerUrl means delay initialization until seBrokertUrl() is called. if (!s.brokerUrl.empty()) initialize(Url(s.brokerUrl)); } -void Backup::initialize(const Url& url) { - if (url.empty()) throw Url::Invalid("HA broker URL is empty"); - QPID_LOG(notice, "HA: Backup initialized: " << url); +bool Backup::isSelf(const Address& a) const { + return sys::SystemInfo::isLocalHost(a.host) && + a.port == haBroker.getBroker().getPort(a.protocol); +} + +// Remove my own address from the URL if possible. +// This isn't 100% reliable given the many ways to specify a host, +// but should work in most cases. We have additional measures to prevent +// self-connection in ConnectionObserver +Url Backup::removeSelf(const Url& brokers) const { + Url url; + for (Url::const_iterator i = brokers.begin(); i != brokers.end(); ++i) + if (!isSelf(*i)) url.push_back(*i); + if (url.empty()) + throw Url::Invalid(logPrefix+"Failover URL is empty"); + QPID_LOG(debug, logPrefix << "Failover URL (excluding self): " << url); + return url; +} + +void Backup::initialize(const Url& brokers) { + if (brokers.empty()) throw Url::Invalid("HA broker URL is empty"); + QPID_LOG(info, logPrefix << "Connecting to cluster, broker URL: " << brokers); + Url url = removeSelf(brokers); string protocol = url[0].protocol.empty() ? "tcp" : url[0].protocol; + types::Uuid uuid(true); // Declare the link std::pair<Link::shared_ptr, bool> result = broker.getLinks().declare( + broker::QPID_NAME_PREFIX + string("ha.link.") + uuid.str(), url[0].host, url[0].port, protocol, - false, // durable - settings.mechanism, settings.username, settings.password); - link = result.first; - link->setUrl(url); - replicator.reset(new BrokerReplicator(haBroker, link)); - broker.getExchanges().registerExchange(replicator); + false, // durable + settings.mechanism, settings.username, settings.password, + false); // no amq.failover - don't want to use client URL. + { + sys::Mutex::ScopedLock l(lock); + link = result.first; + replicator.reset(new BrokerReplicator(haBroker, link)); + replicator->initialize(); + broker.getExchanges().registerExchange(replicator); + } + link->setUrl(url); // Outside the lock, once set link doesn't change. } Backup::~Backup() { if (link) link->close(); if (replicator.get()) broker.getExchanges().destroy(replicator->getName()); - replicator.reset(); - broker.getConnectionObservers().remove(excluder); // This allows client connections. } - +// Called via management. void Backup::setBrokerUrl(const Url& url) { // Ignore empty URLs seen during start-up for some tests. if (url.empty()) return; - sys::Mutex::ScopedLock l(lock); - if (link) { // URL changed after we initialized. - QPID_LOG(info, "HA: Backup broker URL set to " << url); - link->setUrl(url); + bool linkSet = false; + { + sys::Mutex::ScopedLock l(lock); + linkSet = link; + } + if (linkSet) { + QPID_LOG(info, logPrefix << "Broker URL set to: " << url); + link->setUrl(removeSelf(url)); // Outside lock, once set link doesn't change } - else { + else initialize(url); // Deferred initialization +} + +void Backup::setStatus(BrokerStatus status) { + switch (status) { + case READY: + QPID_LOG(notice, logPrefix << "Ready to become primary."); + break; + case CATCHUP: + QPID_LOG(notice, logPrefix << "Catching up on primary, cannot be promoted."); + default: + assert(0); } } diff --git a/cpp/src/qpid/ha/Backup.h b/cpp/src/qpid/ha/Backup.h index 6c36996914..1233a473ec 100644 --- a/cpp/src/qpid/ha/Backup.h +++ b/cpp/src/qpid/ha/Backup.h @@ -36,7 +36,6 @@ class Link; namespace ha { class Settings; -class ConnectionExcluder; class BrokerReplicator; class HaBroker; @@ -51,9 +50,13 @@ class Backup Backup(HaBroker&, const Settings&); ~Backup(); void setBrokerUrl(const Url&); + void setStatus(BrokerStatus); private: + bool isSelf(const Address& a) const; + Url removeSelf(const Url&) const; void initialize(const Url&); + std::string logPrefix; sys::Mutex lock; HaBroker& haBroker; @@ -61,7 +64,6 @@ class Backup Settings settings; boost::shared_ptr<broker::Link> link; boost::shared_ptr<BrokerReplicator> replicator; - boost::shared_ptr<ConnectionExcluder> excluder; }; }} // namespace qpid::ha diff --git a/cpp/src/qpid/ha/ConnectionExcluder.h b/cpp/src/qpid/ha/BackupConnectionExcluder.h index f8f2843a0c..ef537ab90a 100644 --- a/cpp/src/qpid/ha/ConnectionExcluder.h +++ b/cpp/src/qpid/ha/BackupConnectionExcluder.h @@ -1,5 +1,5 @@ -#ifndef QPID_HA_CONNECTIONEXCLUDER_H -#define QPID_HA_CONNECTIONEXCLUDER_H +#ifndef QPID_HA_BACKUPCONNECTIONEXCLUDER_H +#define QPID_HA_BACKUPCONNECTIONEXCLUDER_H /* * @@ -23,32 +23,26 @@ */ #include "qpid/broker/ConnectionObserver.h" -#include <boost/function.hpp> +#include "qpid/broker/Connection.h" +#include "qpid/log/Statement.h" namespace qpid { - -namespace broker { -class Connection; -} - namespace ha { /** - * Exclude normal connections to a backup broker. - * Admin connections are identified by a special flag in client-properties - * during connection negotiation. + * Exclude connections to a backup broker. */ -class ConnectionExcluder : public broker::ConnectionObserver +class BackupConnectionExcluder : public broker::ConnectionObserver { public: - ConnectionExcluder(); - - void opened(broker::Connection& connection); + void opened(broker::Connection& connection) { + QPID_LOG(debug, "Backup broker rejected connection "+connection.getMgmtId()); + connection.abort(); + } - private: - static const std::string ADMIN_TAG; + void closed(broker::Connection&) {} }; }} // namespace qpid::ha -#endif /*!QPID_HA_CONNECTIONEXCLUDER_H*/ +#endif /*!QPID_HA_BACKUPCONNECTIONEXCLUDER_H*/ diff --git a/cpp/src/qpid/ha/BrokerInfo.cpp b/cpp/src/qpid/ha/BrokerInfo.cpp new file mode 100644 index 0000000000..c8bd1a14be --- /dev/null +++ b/cpp/src/qpid/ha/BrokerInfo.cpp @@ -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. + * + */ + +#include "BrokerInfo.h" +#include "qpid/amqp_0_10/Codecs.h" +#include "qpid/Exception.h" +#include "qpid/log/Statement.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/framing/FieldValue.h" +#include <iostream> +#include <iterator> +#include <sstream> + +namespace qpid { +namespace ha { + +namespace { +std::string SYSTEM_ID="system-id"; +std::string HOST_NAME="host-name"; +std::string PORT="port"; +std::string STATUS="status"; +} + +using types::Uuid; +using types::Variant; +using framing::FieldTable; + +BrokerInfo::BrokerInfo(const std::string& host, uint16_t port_, const types::Uuid& id) : + hostName(host), port(port_), systemId(id) +{ + updateLogId(); +} + +void BrokerInfo::updateLogId() { + std::ostringstream o; + o << hostName << ":" << port; + logId = o.str(); +} + +FieldTable BrokerInfo::asFieldTable() const { + Variant::Map m = asMap(); + FieldTable ft; + amqp_0_10::translate(m, ft); + return ft; +} + +Variant::Map BrokerInfo::asMap() const { + Variant::Map m; + m[SYSTEM_ID] = systemId; + m[HOST_NAME] = hostName; + m[PORT] = port; + m[STATUS] = status; + return m; +} + +void BrokerInfo::assign(const FieldTable& ft) { + Variant::Map m; + amqp_0_10::translate(ft, m); + assign(m); +} + +namespace { +const Variant& get(const Variant::Map& m, const std::string& k) { + Variant::Map::const_iterator i = m.find(k); + if (i == m.end()) throw Exception( + QPID_MSG("Missing field '" << k << "' in broker information")); + return i->second; +} +} + +void BrokerInfo::assign(const Variant::Map& m) { + systemId = get(m, SYSTEM_ID).asUuid(); + hostName = get(m, HOST_NAME).asString(); + port = get(m, PORT).asUint16(); + status = BrokerStatus(get(m, STATUS).asUint8()); + updateLogId(); +} + +std::ostream& operator<<(std::ostream& o, const BrokerInfo& b) { + return o << b.getHostName() << ":" << b.getPort() << "(" + << printable(b.getStatus()) << ")"; +} + +std::ostream& operator<<(std::ostream& o, const BrokerInfo::Set& infos) { + std::ostream_iterator<BrokerInfo> out(o, " "); + copy(infos.begin(), infos.end(), out); + return o; +} + +std::ostream& operator<<(std::ostream& o, const BrokerInfo::Map::value_type& v) { + return o << v.second; +} + +std::ostream& operator<<(std::ostream& o, const BrokerInfo::Map& infos) { + std::ostream_iterator<BrokerInfo::Map::value_type> out(o, " "); + copy(infos.begin(), infos.end(), out); + return o; +} + +}} diff --git a/cpp/src/qpid/ha/BrokerInfo.h b/cpp/src/qpid/ha/BrokerInfo.h new file mode 100644 index 0000000000..642f7c1361 --- /dev/null +++ b/cpp/src/qpid/ha/BrokerInfo.h @@ -0,0 +1,84 @@ +#ifndef QPID_HA_BROKERINFO_H +#define QPID_HA_BROKERINFO_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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 "qpid/Url.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/types/Uuid.h" +#include "qpid/types/Variant.h" +#include <string> +#include <iosfwd> +#include <vector> + +namespace qpid { +namespace ha { + +/** + * Information about a cluster broker, maintained by the cluster primary. + */ +class BrokerInfo +{ + public: + typedef std::set<BrokerInfo> Set; + typedef std::map<types::Uuid, BrokerInfo> Map; + + BrokerInfo() {} + BrokerInfo(const std::string& host, uint16_t port_, const types::Uuid& id); + BrokerInfo(const framing::FieldTable& ft) { assign(ft); } + BrokerInfo(const types::Variant::Map& m) { assign(m); } + + types::Uuid getSystemId() const { return systemId; } + std::string getHostName() const { return hostName; } + BrokerStatus getStatus() const { return status; } + uint16_t getPort() const { return port; } + std::string getLogId() const { return logId; } + + void setStatus(BrokerStatus s) { status = s; } + + framing::FieldTable asFieldTable() const; + types::Variant::Map asMap() const; + + void assign(const framing::FieldTable&); + void assign(const types::Variant::Map&); + + // So it can be put in a set. + bool operator<(const BrokerInfo x) const { return systemId < x.systemId; } + + private: + void updateLogId(); + std::string logId; + std::string hostName; + uint16_t port; + types::Uuid systemId; + BrokerStatus status; +}; + +std::ostream& operator<<(std::ostream&, const BrokerInfo&); +std::ostream& operator<<(std::ostream&, const BrokerInfo::Set&); +std::ostream& operator<<(std::ostream&, const BrokerInfo::Map::value_type&); +std::ostream& operator<<(std::ostream&, const BrokerInfo::Map&); + +}} // namespace qpid::ha + +#endif /*!QPID_HA_BROKERINFO_H*/ diff --git a/cpp/src/qpid/ha/BrokerReplicator.cpp b/cpp/src/qpid/ha/BrokerReplicator.cpp index d0c99cbdb6..c8c4a42d72 100644 --- a/cpp/src/qpid/ha/BrokerReplicator.cpp +++ b/cpp/src/qpid/ha/BrokerReplicator.cpp @@ -22,6 +22,7 @@ #include "HaBroker.h" #include "QueueReplicator.h" #include "qpid/broker/Broker.h" +#include "qpid/broker/Connection.h" #include "qpid/broker/Queue.h" #include "qpid/broker/Link.h" #include "qpid/framing/FieldTable.h" @@ -37,8 +38,11 @@ #include "qmf/org/apache/qpid/broker/EventQueueDeclare.h" #include "qmf/org/apache/qpid/broker/EventQueueDelete.h" #include "qmf/org/apache/qpid/broker/EventSubscribe.h" +#include "qmf/org/apache/qpid/ha/EventMembersUpdate.h" #include <algorithm> #include <sstream> +#include <iostream> +#include <assert.h> namespace qpid { namespace ha { @@ -50,15 +54,16 @@ using qmf::org::apache::qpid::broker::EventExchangeDelete; using qmf::org::apache::qpid::broker::EventQueueDeclare; using qmf::org::apache::qpid::broker::EventQueueDelete; using qmf::org::apache::qpid::broker::EventSubscribe; +using qmf::org::apache::qpid::ha::EventMembersUpdate; using namespace framing; using std::string; +using std::ostream; using types::Variant; using namespace broker; namespace { const string QPID_CONFIGURATION_REPLICATOR("qpid.configuration-replicator"); -const string QPID_REPLICATE("qpid.replicate"); const string CLASS_NAME("_class_name"); const string EVENT("_event"); @@ -69,10 +74,13 @@ const string SCHEMA_ID("_schema_id"); const string VALUES("_values"); const string ALTEX("altEx"); +const string ALTEXCHANGE("altExchange"); const string ARGS("args"); const string ARGUMENTS("arguments"); const string AUTODEL("autoDel"); const string AUTODELETE("autoDelete"); +const string EXCL("excl"); +const string EXCLUSIVE("exclusive"); const string BIND("bind"); const string UNBIND("unbind"); const string BINDING("binding"); @@ -86,12 +94,12 @@ const string KEY("key"); const string NAME("name"); const string QNAME("qName"); const string QUEUE("queue"); -const string RHOST("rhost"); const string TYPE("type"); -const string USER("user"); const string HA_BROKER("habroker"); +const string PARTIAL("partial"); -const string AGENT_IND_EVENT_ORG_APACHE_QPID_BROKER("agent.ind.event.org_apache_qpid_broker.#"); +const string AGENT_EVENT_BROKER("agent.ind.event.org_apache_qpid_broker.#"); +const string AGENT_EVENT_HA("agent.ind.event.org_apache_qpid_ha.#"); const string QMF2("qmf2"); const string QMF_CONTENT("qmf.content"); const string QMF_DEFAULT_TOPIC("qmf.default.topic"); @@ -107,6 +115,7 @@ const string ORG_APACHE_QPID_HA("org.apache.qpid.ha"); const string QMF_DEFAULT_DIRECT("qmf.default.direct"); const string _QUERY_REQUEST("_query_request"); const string BROKER("broker"); +const string MEMBERS("members"); bool isQMFv2(const Message& message) { const framing::MessageProperties* props = message.getProperties<framing::MessageProperties>(); @@ -117,7 +126,9 @@ template <class T> bool match(Variant::Map& schema) { return T::match(schema[CLASS_NAME], schema[PACKAGE_NAME]); } -void sendQuery(const string& packageName, const string& className, const string& queueName, SessionHandler& sessionHandler) { +void sendQuery(const string& packageName, const string& className, const string& queueName, + SessionHandler& sessionHandler) +{ framing::AMQP_ServerProxy peer(sessionHandler.out); Variant::Map request; request[_WHAT] = OBJECT; @@ -137,6 +148,7 @@ void sendQuery(const string& packageName, const string& className, const string& props->setAppId(QMF2); props->getApplicationHeaders().setString(QMF_OPCODE, _QUERY_REQUEST); headerBody.get<qpid::framing::DeliveryProperties>(true)->setRoutingKey(BROKER); + headerBody.get<qpid::framing::MessageProperties>(true)->setCorrelationId(className); AMQFrame header(headerBody); header.setBof(false); header.setEof(false); @@ -159,39 +171,23 @@ Variant::Map asMapVoid(const Variant& value) { if (!value.isVoid()) return value.asMap(); else return Variant::Map(); } - } // namespace - -ReplicateLevel BrokerReplicator::replicateLevel(const std::string& str) { - ReplicateLevel rl; - if (qpid::ha::replicateLevel(str, rl)) return rl; - else return haBroker.getSettings().replicateDefault; -} - -ReplicateLevel BrokerReplicator::replicateLevel(const framing::FieldTable& f) { - if (f.isSet(QPID_REPLICATE)) - return replicateLevel(f.getAsString(QPID_REPLICATE)); - else - return haBroker.getSettings().replicateDefault; -} - -ReplicateLevel BrokerReplicator::replicateLevel(const Variant::Map& m) { - Variant::Map::const_iterator i = m.find(QPID_REPLICATE); - if (i != m.end()) - return replicateLevel(i->second.asString()); - else - return haBroker.getSettings().replicateDefault; -} - -BrokerReplicator::~BrokerReplicator() {} - BrokerReplicator::BrokerReplicator(HaBroker& hb, const boost::shared_ptr<Link>& l) : Exchange(QPID_CONFIGURATION_REPLICATOR), - haBroker(hb), broker(hb.getBroker()), link(l) -{ + logPrefix("Backup: "), replicationTest(hb.getReplicationTest()), + haBroker(hb), broker(hb.getBroker()), link(l), + initialized(false), + alternates(hb.getBroker().getExchanges()) +{} + +void BrokerReplicator::initialize() { + // Can't do this in the constructor because we need a shared_ptr to this. + types::Uuid uuid(true); + const std::string name(QPID_CONFIGURATION_REPLICATOR + ".bridge." + uuid.str()); broker.getLinks().declare( - link->getHost(), link->getPort(), + name, // name for bridge + *link, // parent false, // durable QPID_CONFIGURATION_REPLICATOR, // src QPID_CONFIGURATION_REPLICATOR, // dest @@ -202,21 +198,41 @@ BrokerReplicator::BrokerReplicator(HaBroker& hb, const boost::shared_ptr<Link>& "", // excludes false, // dynamic 0, // sync? - boost::bind(&BrokerReplicator::initializeBridge, this, _1, _2) + // shared_ptr keeps this in memory until outstanding initializeBridge + // calls are run. + boost::bind(&BrokerReplicator::initializeBridge, shared_from_this(), _1, _2) ); } +BrokerReplicator::~BrokerReplicator() { } + // This is called in the connection IO thread when the bridge is started. void BrokerReplicator::initializeBridge(Bridge& bridge, SessionHandler& sessionHandler) { - framing::AMQP_ServerProxy peer(sessionHandler.out); + // Use the credentials of the outgoing Link connection for creating queues, + // exchanges etc. We know link->getConnection() is non-zero because we are + // being called in the connections thread context. + // + assert(link->getConnection()); + userId = link->getConnection()->getUserId(); + remoteHost = link->getConnection()->getUrl(); + + link->getRemoteAddress(primary); string queueName = bridge.getQueueName(); + + QPID_LOG(info, logPrefix << (initialized ? "Connecting" : "Failing over") + << " to primary " << primary + << " status:" << printable(haBroker.getStatus())); + initialized = true; + + framing::AMQP_ServerProxy peer(sessionHandler.out); const qmf::org::apache::qpid::broker::ArgsLinkBridge& args(bridge.getArgs()); //declare and bind an event queue FieldTable declareArgs; - declareArgs.setString(QPID_REPLICATE, str(RL_NONE)); + declareArgs.setString(QPID_REPLICATE, printable(NONE).str()); peer.getQueue().declare(queueName, "", false, false, true, true, declareArgs); - peer.getExchange().bind(queueName, QMF_DEFAULT_TOPIC, AGENT_IND_EVENT_ORG_APACHE_QPID_BROKER, FieldTable()); + peer.getExchange().bind(queueName, QMF_DEFAULT_TOPIC, AGENT_EVENT_BROKER, FieldTable()); + peer.getExchange().bind(queueName, QMF_DEFAULT_TOPIC, AGENT_EVENT_HA, FieldTable()); //subscribe to the queue peer.getMessage().subscribe(queueName, args.i_dest, 1, 0, false, "", 0, FieldTable()); peer.getMessage().flow(args.i_dest, 0, 0xFFFFFFFF); @@ -228,23 +244,30 @@ void BrokerReplicator::initializeBridge(Bridge& bridge, SessionHandler& sessionH sendQuery(ORG_APACHE_QPID_BROKER, QUEUE, queueName, sessionHandler); sendQuery(ORG_APACHE_QPID_BROKER, EXCHANGE, queueName, sessionHandler); sendQuery(ORG_APACHE_QPID_BROKER, BINDING, queueName, sessionHandler); - QPID_LOG(debug, "HA: Backup configuration bridge: " << queueName); } void BrokerReplicator::route(Deliverable& msg) { + // We transition from JOINING->CATCHUP on the first message received from the primary. + // Until now we couldn't be sure if we had a good connection to the primary. + if (haBroker.getStatus() == JOINING) { + haBroker.setStatus(CATCHUP); + QPID_LOG(notice, logPrefix << "Connected to primary " << primary); + } + const framing::FieldTable* headers = msg.getMessage().getApplicationHeaders(); + const MessageProperties* messageProperties = msg.getMessage().getProperties<MessageProperties>(); Variant::List list; try { - if (!isQMFv2(msg.getMessage()) || !headers) + if (!isQMFv2(msg.getMessage()) || !headers || !messageProperties) throw Exception("Unexpected message, not QMF2 event or query response."); // decode as list string content = msg.getMessage().getFrames().getContent(); amqp_0_10::ListCodec::decode(content, list); - + QPID_LOG(trace, "Broker replicator received: " << *messageProperties); if (headers->getAsString(QMF_CONTENT) == EVENT) { for (Variant::List::iterator i = list.begin(); i != list.end(); ++i) { Variant::Map& map = i->asMap(); - QPID_LOG(trace, "HA: Backup received event: " << map); + QPID_LOG(trace, "Broker replicator event: " << map); Variant::Map& schema = map[SCHEMA_ID].asMap(); Variant::Map& values = map[VALUES].asMap(); if (match<EventQueueDeclare>(schema)) doEventQueueDeclare(values); @@ -253,12 +276,13 @@ void BrokerReplicator::route(Deliverable& msg) { else if (match<EventExchangeDelete>(schema)) doEventExchangeDelete(values); else if (match<EventBind>(schema)) doEventBind(values); else if (match<EventUnbind>(schema)) doEventUnbind(values); + else if (match<EventMembersUpdate>(schema)) doEventMembersUpdate(values); } } else if (headers->getAsString(QMF_OPCODE) == QUERY_RESPONSE) { for (Variant::List::iterator i = list.begin(); i != list.end(); ++i) { Variant::Map& map = i->asMap(); - QPID_LOG(trace, "HA: Backup received event: " << map); - string type = map[SCHEMA_ID].asMap()[CLASS_NAME]; + QPID_LOG(trace, "Broker replicator response: " << map); + string type = map[SCHEMA_ID].asMap()[CLASS_NAME].asString(); Variant::Map& values = map[VALUES].asMap(); framing::FieldTable args; amqp_0_10::translate(asMapVoid(values[ARGUMENTS]), args); @@ -267,85 +291,82 @@ void BrokerReplicator::route(Deliverable& msg) { else if (type == BINDING) doResponseBind(values); else if (type == HA_BROKER) doResponseHaBroker(values); } + if (messageProperties->getCorrelationId() == EXCHANGE && !headers->isSet(PARTIAL)) { + // We have received all of the exchange response. + alternates.clear(); + } } } catch (const std::exception& e) { - QPID_LOG(critical, "HA: Backup configuration failed: " << e.what() + QPID_LOG(critical, logPrefix << "Configuration failed: " << e.what() << ": while handling: " << list); + haBroker.shutdown(); throw; } } + void BrokerReplicator::doEventQueueDeclare(Variant::Map& values) { - string name = values[QNAME].asString(); Variant::Map argsMap = asMapVoid(values[ARGS]); - if (values[DISP] == CREATED && replicateLevel(argsMap)) { + bool autoDel = values[AUTODEL].asBool(); + bool excl = values[EXCL].asBool(); + if (values[DISP] == CREATED && + replicationTest.isReplicated(CONFIGURATION, argsMap, autoDel, excl)) + { + string name = values[QNAME].asString(); + QPID_LOG(debug, logPrefix << "Queue declare event: " << name); framing::FieldTable args; amqp_0_10::translate(argsMap, args); - std::pair<boost::shared_ptr<Queue>, bool> result = - broker.createQueue( - name, - values[DURABLE].asBool(), - values[AUTODEL].asBool(), - 0 /*i.e. no owner regardless of exclusivity on master*/, - values[ALTEX].asString(), - args, - values[USER].asString(), - values[RHOST].asString()); - if (result.second) { - QPID_LOG(debug, "HA: Backup queue declare event: " << name); - startQueueReplicator(result.first); - } else { - // FIXME aconway 2011-12-02: what's the right way to handle this? - // Should we delete the old & re-create form the event? Responses - // may be old but events are always up-to-date. - QPID_LOG(warning, "HA: Backup queue declare event, already exists: " << name); + // If we already have a queue with this name, replace it. + // The queue was definitely created on the primary. + if (broker.getQueues().find(name)) { + QPID_LOG(warning, logPrefix << "Replacing exsiting queue: " << name); + broker.getQueues().destroy(name); + stopQueueReplicator(name); } + boost::shared_ptr<Queue> queue = createQueue( + name, values[DURABLE].asBool(), autoDel, args, values[ALTEX].asString()); + assert(queue); // Should be created since we destroed the previous queue above. + if (queue) startQueueReplicator(queue); } } +boost::shared_ptr<QueueReplicator> BrokerReplicator::findQueueReplicator( + const std::string& qname) +{ + string rname = QueueReplicator::replicatorName(qname); + boost::shared_ptr<broker::Exchange> ex = broker.getExchanges().find(rname); + return boost::dynamic_pointer_cast<QueueReplicator>(ex); +} + void BrokerReplicator::doEventQueueDelete(Variant::Map& values) { // The remote queue has already been deleted so replicator // sessions may be closed by a "queue deleted" exception. string name = values[QNAME].asString(); boost::shared_ptr<Queue> queue = broker.getQueues().find(name); - if (!queue) { - QPID_LOG(warning, "HA: Backup queue delete event, does not exist: " << name); - } else if (!replicateLevel(queue->getSettings())) { - QPID_LOG(warning, "HA: Backup queue delete event, not replicated: " << name); - } else { - string rname = QueueReplicator::replicatorName(name); - boost::shared_ptr<broker::Exchange> ex = broker.getExchanges().find(rname); - boost::shared_ptr<QueueReplicator> qr = boost::dynamic_pointer_cast<QueueReplicator>(ex); - if (qr) qr->deactivate(); - // QueueReplicator's bridge is now queued for destruction but may not - // actually be destroyed, deleting the exhange - broker.getExchanges().destroy(rname); - broker.deleteQueue(name, values[USER].asString(), values[RHOST].asString()); - QPID_LOG(debug, "HA: Backup queue delete event: " << name); + if (queue && replicationTest.replicateLevel(queue->getSettings())) { + QPID_LOG(debug, logPrefix << "Queue delete event: " << name); + stopQueueReplicator(name); + broker.deleteQueue(name, userId, remoteHost); } } void BrokerReplicator::doEventExchangeDeclare(Variant::Map& values) { Variant::Map argsMap(asMapVoid(values[ARGS])); - if (values[DISP] == CREATED && replicateLevel(argsMap)) { + if (!replicationTest.replicateLevel(argsMap)) return; // Not a replicated exchange. + if (values[DISP] == CREATED && replicationTest.replicateLevel(argsMap)) { string name = values[EXNAME].asString(); + QPID_LOG(debug, logPrefix << "Exchange declare event: " << name); framing::FieldTable args; amqp_0_10::translate(argsMap, args); - if (broker.createExchange( - name, - values[EXTYPE].asString(), - values[DURABLE].asBool(), - values[ALTEX].asString(), - args, - values[USER].asString(), - values[RHOST].asString()).second) - { - QPID_LOG(debug, "HA: Backup exchange declare event: " << name); - } else { - // FIXME aconway 2011-11-22: should delete pre-existing exchange - // and re-create from event. See comment in doEventQueueDeclare. - QPID_LOG(debug, "HA: Backup exchange declare event, already exists: " << name); + // If we already have a exchange with this name, replace it. + // The exchange was definitely created on the primary. + if (broker.getExchanges().find(name)) { + broker.getExchanges().destroy(name); + QPID_LOG(warning, logPrefix << "Replaced exsiting exchange: " << name); } + boost::shared_ptr<Exchange> exchange = + createExchange(name, values[EXTYPE].asString(), values[DURABLE].asBool(), args, values[ALTEX].asString()); + assert(exchange); } } @@ -353,15 +374,12 @@ void BrokerReplicator::doEventExchangeDelete(Variant::Map& values) { string name = values[EXNAME].asString(); boost::shared_ptr<Exchange> exchange = broker.getExchanges().find(name); if (!exchange) { - QPID_LOG(warning, "HA: Backup exchange delete event, does not exist: " << name); - } else if (!replicateLevel(exchange->getArgs())) { - QPID_LOG(warning, "HA: Backup exchange delete event, not replicated: " << name); + QPID_LOG(warning, logPrefix << "Exchange delete event, does not exist: " << name); + } else if (!replicationTest.replicateLevel(exchange->getArgs())) { + QPID_LOG(warning, logPrefix << "Exchange delete event, not replicated: " << name); } else { - QPID_LOG(debug, "HA: Backup exchange delete event:" << name); - broker.deleteExchange( - name, - values[USER].asString(), - values[RHOST].asString()); + QPID_LOG(debug, logPrefix << "Exchange delete event:" << name); + broker.deleteExchange(name, userId, remoteHost); } } @@ -372,16 +390,16 @@ void BrokerReplicator::doEventBind(Variant::Map& values) { broker.getQueues().find(values[QNAME].asString()); // We only replicate binds for a replicated queue to replicated // exchange that both exist locally. - if (exchange && replicateLevel(exchange->getArgs()) && - queue && replicateLevel(queue->getSettings())) + if (exchange && replicationTest.replicateLevel(exchange->getArgs()) && + queue && replicationTest.replicateLevel(queue->getSettings())) { framing::FieldTable args; amqp_0_10::translate(asMapVoid(values[ARGS]), args); string key = values[KEY].asString(); - exchange->bind(queue, key, &args); - QPID_LOG(debug, "HA: Backup bind event: exchange=" << exchange->getName() + QPID_LOG(debug, logPrefix << "Bind event: exchange=" << exchange->getName() << " queue=" << queue->getName() << " key=" << key); + exchange->bind(queue, key, &args); } } @@ -392,64 +410,71 @@ void BrokerReplicator::doEventUnbind(Variant::Map& values) { broker.getQueues().find(values[QNAME].asString()); // We only replicate unbinds for a replicated queue to replicated // exchange that both exist locally. - if (exchange && replicateLevel(exchange->getArgs()) && - queue && replicateLevel(queue->getSettings())) + if (exchange && replicationTest.replicateLevel(exchange->getArgs()) && + queue && replicationTest.replicateLevel(queue->getSettings())) { framing::FieldTable args; amqp_0_10::translate(asMapVoid(values[ARGS]), args); string key = values[KEY].asString(); - exchange->unbind(queue, key, &args); - QPID_LOG(debug, "HA: Backup unbind event: exchange=" << exchange->getName() + QPID_LOG(debug, logPrefix << "Unbind event: exchange=" << exchange->getName() << " queue=" << queue->getName() << " key=" << key); + exchange->unbind(queue, key, &args); } } +void BrokerReplicator::doEventMembersUpdate(Variant::Map& values) { + Variant::List members = values[MEMBERS].asList(); + haBroker.setMembership(members); +} + +namespace { + +// Get the alternate exchange from the exchange field of a queue or exchange response. +static const string EXCHANGE_KEY_PREFIX("org.apache.qpid.broker:exchange:"); + +string getAltExchange(const types::Variant& var) { + if (!var.isVoid()) { + management::ObjectId oid(var); + string key = oid.getV2Key(); + if (key.find(EXCHANGE_KEY_PREFIX) != 0) throw Exception("Invalid exchange reference: "+key); + return key.substr(EXCHANGE_KEY_PREFIX.size()); + } + else return string(); +} +} + void BrokerReplicator::doResponseQueue(Variant::Map& values) { Variant::Map argsMap(asMapVoid(values[ARGUMENTS])); - if (!replicateLevel(argsMap)) return; + if (!replicationTest.isReplicated( + CONFIGURATION, + values[ARGUMENTS].asMap(), + values[AUTODELETE].asBool(), + values[EXCLUSIVE].asBool())) + return; + string name(values[NAME].asString()); + QPID_LOG(debug, logPrefix << "Queue response: " << name); framing::FieldTable args; amqp_0_10::translate(argsMap, args); - string name(values[NAME].asString()); - std::pair<boost::shared_ptr<Queue>, bool> result = - broker.createQueue( - name, - values[DURABLE].asBool(), - values[AUTODELETE].asBool(), - 0 /*i.e. no owner regardless of exclusivity on master*/, - ""/*TODO: need to include alternate-exchange*/, - args, - ""/*TODO: who is the user?*/, - ""/*TODO: what should we use as connection id?*/); - if (result.second) { - QPID_LOG(debug, "HA: Backup queue response: " << name); - startQueueReplicator(result.first); - } else { - // FIXME aconway 2011-11-22: Normal to find queue already - // exists if we're failing over. - QPID_LOG(warning, "HA: Backup queue response, already exists: " << name); - } + boost::shared_ptr<Queue> queue = + createQueue(name, values[DURABLE].asBool(), values[AUTODELETE].asBool(), args, + getAltExchange(values[ALTEXCHANGE])); + // It is normal for the queue to already exist if we are failing over. + if (queue) startQueueReplicator(queue); + else QPID_LOG(debug, logPrefix << "Queue already replicated: " << name); } void BrokerReplicator::doResponseExchange(Variant::Map& values) { Variant::Map argsMap(asMapVoid(values[ARGUMENTS])); - if (!replicateLevel(argsMap)) return; + if (!replicationTest.replicateLevel(argsMap)) return; + string name = values[NAME].asString(); + QPID_LOG(debug, logPrefix << "Exchange response: " << name); framing::FieldTable args; amqp_0_10::translate(argsMap, args); - if (broker.createExchange( - values[NAME].asString(), - values[TYPE].asString(), - values[DURABLE].asBool(), - ""/*TODO: need to include alternate-exchange*/, - args, - ""/*TODO: who is the user?*/, - ""/*TODO: what should we use as connection id?*/).second) - { - QPID_LOG(debug, "HA: Backup exchange response: " << values[NAME].asString()); - } else { - QPID_LOG(warning, "HA: Backup exchange query, already exists: " << - values[QNAME].asString()); - } + boost::shared_ptr<Exchange> exchange = createExchange( + name, values[TYPE].asString(), values[DURABLE].asBool(), args, + getAltExchange(values[ALTEXCHANGE])); + QPID_LOG_IF(debug, !exchange, logPrefix << "Exchange already exists: " << name); } namespace { @@ -480,16 +505,16 @@ void BrokerReplicator::doResponseBind(Variant::Map& values) { boost::shared_ptr<Queue> queue = broker.getQueues().find(qName); // Automatically replicate binding if queue and exchange exist and are replicated - if (exchange && replicateLevel(exchange->getArgs()) && - queue && replicateLevel(queue->getSettings())) + if (exchange && replicationTest.replicateLevel(exchange->getArgs()) && + queue && replicationTest.replicateLevel(queue->getSettings())) { + string key = values[KEY].asString(); + QPID_LOG(debug, logPrefix << "Bind response: exchange:" << exName + << " queue:" << qName + << " key:" << key); framing::FieldTable args; amqp_0_10::translate(asMapVoid(values[ARGUMENTS]), args); - string key = values[KEY].asString(); exchange->bind(queue, key, &args); - QPID_LOG(debug, "HA: Backup bind response: exchange=" << exchange->getName() - << " queue=" << queue->getName() - << " key=" << key); } } @@ -500,30 +525,97 @@ const string REPLICATE_DEFAULT="replicateDefault"; // Received the ha-broker configuration object for the primary broker. void BrokerReplicator::doResponseHaBroker(Variant::Map& values) { try { - ReplicateLevel mine = haBroker.getSettings().replicateDefault; - ReplicateLevel primary = replicateLevel(values[REPLICATE_DEFAULT].asString()); - if (mine != primary) { - std::ostringstream os; - os << "Replicate default on backup (" << mine - << ") does not match primary (" << primary << ")"; - haBroker.shutdown(os.str()); - } + QPID_LOG(trace, logPrefix << "HA Broker response: " << values); + ReplicateLevel mine = haBroker.getSettings().replicateDefault.get(); + ReplicateLevel primary = replicationTest.replicateLevel( + values[REPLICATE_DEFAULT].asString()); + if (mine != primary) + throw Exception(QPID_MSG("Replicate default on backup (" << mine + << ") does not match primary (" << primary << ")")); + haBroker.setMembership(values[MEMBERS].asList()); } catch (const std::exception& e) { - std::ostringstream os; - os << "Received invalid replicate default from primary: " << e.what(); - haBroker.shutdown(os.str()); + QPID_LOG(critical, logPrefix << "Invalid HA Broker response: " << e.what() + << ": " << values); + haBroker.shutdown(); + throw; } } -void BrokerReplicator::startQueueReplicator(const boost::shared_ptr<Queue>& queue) { - if (replicateLevel(queue->getSettings()) == RL_ALL) { - boost::shared_ptr<QueueReplicator> qr(new QueueReplicator(queue, link)); +void BrokerReplicator::startQueueReplicator(const boost::shared_ptr<Queue>& queue) +{ + if (replicationTest.replicateLevel(queue->getSettings()) == ALL) { + boost::shared_ptr<QueueReplicator> qr( + new QueueReplicator(haBroker, queue, link)); if (!broker.getExchanges().registerExchange(qr)) throw Exception(QPID_MSG("Duplicate queue replicator " << qr->getName())); qr->activate(); } } +void BrokerReplicator::stopQueueReplicator(const std::string& name) { + boost::shared_ptr<QueueReplicator> qr = findQueueReplicator(name); + if (qr) { + qr->deactivate(); + // QueueReplicator's bridge is now queued for destruction but may not + // actually be destroyed. + broker.getExchanges().destroy(qr->getName()); + } +} + +boost::shared_ptr<Queue> BrokerReplicator::createQueue( + const std::string& name, + bool durable, + bool autodelete, + const qpid::framing::FieldTable& arguments, + const std::string& alternateExchange) +{ + std::pair<boost::shared_ptr<Queue>, bool> result = + broker.createQueue( + name, + durable, + autodelete, + 0, // no owner regardless of exclusivity on primary + string(), // Set alternate exchange below + arguments, + userId, + remoteHost); + if (result.second) { + if (!alternateExchange.empty()) { + alternates.setAlternate( + alternateExchange, boost::bind(&Queue::setAlternateExchange, result.first, _1)); + } + return result.first; + } + else return boost::shared_ptr<Queue>(); +} + +boost::shared_ptr<Exchange> BrokerReplicator::createExchange( + const std::string& name, + const std::string& type, + bool durable, + const qpid::framing::FieldTable& args, + const std::string& alternateExchange) +{ + std::pair<boost::shared_ptr<Exchange>, bool> result = + broker.createExchange( + name, + type, + durable, + string(), // Set alternate exchange below + args, + userId, + remoteHost); + if (result.second) { + alternates.addExchange(result.first); + if (!alternateExchange.empty()) { + alternates.setAlternate( + alternateExchange, boost::bind(&Exchange::setAlternate, result.first, _1)); + } + return result.first; + } + else return boost::shared_ptr<Exchange>(); +} + bool BrokerReplicator::bind(boost::shared_ptr<Queue>, const string&, const framing::FieldTable*) { return false; } bool BrokerReplicator::unbind(boost::shared_ptr<Queue>, const string&, const framing::FieldTable*) { return false; } bool BrokerReplicator::isBound(boost::shared_ptr<Queue>, const string* const, const framing::FieldTable* const) { return false; } diff --git a/cpp/src/qpid/ha/BrokerReplicator.h b/cpp/src/qpid/ha/BrokerReplicator.h index c9d7b9f74c..69653b876a 100644 --- a/cpp/src/qpid/ha/BrokerReplicator.h +++ b/cpp/src/qpid/ha/BrokerReplicator.h @@ -22,10 +22,15 @@ * */ -#include "ReplicateLevel.h" +#include "types.h" +#include "ReplicationTest.h" +#include "AlternateExchangeSetter.h" +#include "qpid/Address.h" #include "qpid/broker/Exchange.h" #include "qpid/types/Variant.h" +#include "qpid/management/ManagementObject.h" #include <boost/shared_ptr.hpp> +#include <boost/enable_shared_from_this.hpp> namespace qpid { @@ -42,6 +47,7 @@ class FieldTable; namespace ha { class HaBroker; +class QueueReplicator; /** * Replicate configuration on a backup broker. @@ -51,28 +57,29 @@ class HaBroker; * exchanges and bindings to replicate the primary. * It also creates QueueReplicators for newly replicated queues. * - * THREAD SAFE: Has no mutable state. + * THREAD UNSAFE: Only called in Link connection thread, no need for locking. * */ -class BrokerReplicator : public broker::Exchange +class BrokerReplicator : public broker::Exchange, + public boost::enable_shared_from_this<BrokerReplicator> { public: BrokerReplicator(HaBroker&, const boost::shared_ptr<broker::Link>&); ~BrokerReplicator(); - std::string getType() const; + + void initialize(); // Exchange methods + std::string getType() const; bool bind(boost::shared_ptr<broker::Queue>, const std::string&, const framing::FieldTable*); bool unbind(boost::shared_ptr<broker::Queue>, const std::string&, const framing::FieldTable*); void route(broker::Deliverable&); bool isBound(boost::shared_ptr<broker::Queue>, const std::string* const, const framing::FieldTable* const); private: - void initializeBridge(broker::Bridge&, broker::SessionHandler&); + typedef boost::shared_ptr<QueueReplicator> QueueReplicatorPtr; - ReplicateLevel replicateLevel(const std::string&); - ReplicateLevel replicateLevel(const framing::FieldTable& args); - ReplicateLevel replicateLevel(const types::Variant::Map& args); + void initializeBridge(broker::Bridge&, broker::SessionHandler&); void doEventQueueDeclare(types::Variant::Map& values); void doEventQueueDelete(types::Variant::Map& values); @@ -80,17 +87,40 @@ class BrokerReplicator : public broker::Exchange void doEventExchangeDelete(types::Variant::Map& values); void doEventBind(types::Variant::Map&); void doEventUnbind(types::Variant::Map&); + void doEventMembersUpdate(types::Variant::Map&); void doResponseQueue(types::Variant::Map& values); void doResponseExchange(types::Variant::Map& values); void doResponseBind(types::Variant::Map& values); void doResponseHaBroker(types::Variant::Map& values); + QueueReplicatorPtr findQueueReplicator(const std::string& qname); void startQueueReplicator(const boost::shared_ptr<broker::Queue>&); + void stopQueueReplicator(const std::string& name); + + boost::shared_ptr<broker::Queue> createQueue( + const std::string& name, + bool durable, + bool autodelete, + const qpid::framing::FieldTable& arguments, + const std::string& alternateExchange); + + boost::shared_ptr<broker::Exchange> createExchange( + const std::string& name, + const std::string& type, + bool durable, + const qpid::framing::FieldTable& args, + const std::string& alternateExchange); + std::string logPrefix; + std::string userId, remoteHost; + ReplicationTest replicationTest; HaBroker& haBroker; broker::Broker& broker; boost::shared_ptr<broker::Link> link; + bool initialized; + AlternateExchangeSetter alternates; + qpid::Address primary; }; }} // namespace qpid::broker diff --git a/cpp/src/qpid/ha/ConnectionExcluder.cpp b/cpp/src/qpid/ha/ConnectionExcluder.cpp deleted file mode 100644 index 67ad7202d6..0000000000 --- a/cpp/src/qpid/ha/ConnectionExcluder.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -#include "ConnectionExcluder.h" -#include "qpid/broker/Connection.h" -#include <boost/function.hpp> -#include <sstream> - -namespace qpid { -namespace ha { - -ConnectionExcluder::ConnectionExcluder() {} - -void ConnectionExcluder::opened(broker::Connection& connection) { - if (!connection.isLink() && !connection.getClientProperties().isSet(ADMIN_TAG)) - throw Exception( - QPID_MSG("HA: Backup broker rejected connection " << connection.getMgmtId())); -} - -const std::string ConnectionExcluder::ADMIN_TAG="qpid.ha-admin"; - -}} // namespace qpid::ha diff --git a/cpp/src/qpid/ha/ConnectionObserver.cpp b/cpp/src/qpid/ha/ConnectionObserver.cpp new file mode 100644 index 0000000000..3f7a1710d9 --- /dev/null +++ b/cpp/src/qpid/ha/ConnectionObserver.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 "ConnectionObserver.h" +#include "BrokerInfo.h" +#include "HaBroker.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/broker/Connection.h" +#include "qpid/log/Statement.h" + +namespace qpid { +namespace ha { + +ConnectionObserver::ConnectionObserver(HaBroker& hb, const types::Uuid& uuid) + : haBroker(hb), logPrefix("Connections: "), self(uuid) {} + +bool ConnectionObserver::getBrokerInfo(broker::Connection& connection, BrokerInfo& info) { + framing::FieldTable ft; + if (connection.getClientProperties().getTable(ConnectionObserver::BACKUP_TAG, ft)) { + info = BrokerInfo(ft); + return true; + } + return false; +} + +void ConnectionObserver::setObserver(const ObserverPtr& o){ + sys::Mutex::ScopedLock l(lock); + observer = o; +} + +ConnectionObserver::ObserverPtr ConnectionObserver::getObserver() { + sys::Mutex::ScopedLock l(lock); + return observer; +} + +void ConnectionObserver::opened(broker::Connection& connection) { + try { + if (connection.isLink()) return; // Allow outgoing links. + if (connection.getClientProperties().isSet(ADMIN_TAG)) { + QPID_LOG(debug, logPrefix << "Allowing admin connection: " + << connection.getMgmtId()); + return; // No need to call observer, always allow admins. + } + BrokerInfo info; // Avoid self connections. + if (getBrokerInfo(connection, info)) { + if (info.getSystemId() == self) { + QPID_LOG(debug, "HA broker rejected self connection "+connection.getMgmtId()); + connection.abort(); + } + + } + ObserverPtr o(getObserver()); + if (o) o->opened(connection); + } + catch (const std::exception& e) { + QPID_LOG(error, logPrefix << "Open error: " << e.what()); + throw; + } +} + +void ConnectionObserver::closed(broker::Connection& connection) { + try { + BrokerInfo info; + ObserverPtr o(getObserver()); + if (o) o->closed(connection); + } + catch (const std::exception& e) { + QPID_LOG(error, logPrefix << "Close error: " << e.what()); + throw; + } +} + +const std::string ConnectionObserver::ADMIN_TAG="qpid.ha-admin"; +const std::string ConnectionObserver::BACKUP_TAG="qpid.ha-backup"; + +}} // namespace qpid::ha diff --git a/cpp/src/qpid/ha/ConnectionObserver.h b/cpp/src/qpid/ha/ConnectionObserver.h new file mode 100644 index 0000000000..5c1dabe8f8 --- /dev/null +++ b/cpp/src/qpid/ha/ConnectionObserver.h @@ -0,0 +1,74 @@ +#ifndef QPID_HA_CONNECTIONOBSERVER_H +#define QPID_HA_CONNECTIONOBSERVER_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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 "qpid/broker/ConnectionObserver.h" +#include "qpid/types/Uuid.h" +#include "qpid/sys/Mutex.h" +#include "boost/shared_ptr.hpp" + +namespace qpid { +namespace ha { +class BrokerInfo; +class HaBroker; + +/** + * Observes connections, delegates to another ConnectionObserver for + * actions specific to primary or backup. + * + * THREAD SAFE: called in arbitrary connection threads. + * + * Main role of this class is to provide a continuous observer object + * on the connection so we can't lose observations between removing + * one observer and adding another. + */ +class ConnectionObserver : public broker::ConnectionObserver +{ + public: + typedef boost::shared_ptr<broker::ConnectionObserver> ObserverPtr; + + static const std::string ADMIN_TAG; + static const std::string BACKUP_TAG; + + static bool getBrokerInfo(broker::Connection& connection, BrokerInfo& info); + + ConnectionObserver(HaBroker& haBroker, const types::Uuid& self); + + void setObserver(const ObserverPtr&); + ObserverPtr getObserver(); + + void opened(broker::Connection& connection); + void closed(broker::Connection& connection); + + private: + sys::Mutex lock; + HaBroker& haBroker; + std::string logPrefix; + ObserverPtr observer; + types::Uuid self; +}; + +}} // namespace qpid::ha + +#endif /*!QPID_HA_CONNECTIONOBSERVER_H*/ diff --git a/cpp/src/qpid/ha/HaBroker.cpp b/cpp/src/qpid/ha/HaBroker.cpp index 7d82fb63bd..d126639813 100644 --- a/cpp/src/qpid/ha/HaBroker.cpp +++ b/cpp/src/qpid/ha/HaBroker.cpp @@ -19,22 +19,31 @@ * */ #include "Backup.h" -#include "ConnectionExcluder.h" +#include "BackupConnectionExcluder.h" +#include "ConnectionObserver.h" #include "HaBroker.h" -#include "Settings.h" +#include "Primary.h" +#include "QueueReplicator.h" #include "ReplicatingSubscription.h" +#include "Settings.h" +#include "qpid/amqp_0_10/Codecs.h" #include "qpid/Exception.h" #include "qpid/broker/Broker.h" #include "qpid/broker/Link.h" #include "qpid/broker/Queue.h" #include "qpid/broker/SignalHandler.h" +#include "qpid/framing/FieldTable.h" #include "qpid/management/ManagementAgent.h" +#include "qpid/sys/SystemInfo.h" +#include "qpid/types/Uuid.h" +#include "qpid/framing/Uuid.h" #include "qmf/org/apache/qpid/ha/Package.h" #include "qmf/org/apache/qpid/ha/ArgsHaBrokerReplicate.h" -#include "qmf/org/apache/qpid/ha/ArgsHaBrokerSetBrokers.h" -#include "qmf/org/apache/qpid/ha/ArgsHaBrokerSetPublicBrokers.h" -#include "qmf/org/apache/qpid/ha/ArgsHaBrokerSetExpectedBackups.h" +#include "qmf/org/apache/qpid/ha/ArgsHaBrokerSetBrokersUrl.h" +#include "qmf/org/apache/qpid/ha/ArgsHaBrokerSetPublicUrl.h" +#include "qmf/org/apache/qpid/ha/EventMembersUpdate.h" #include "qpid/log/Statement.h" +#include <boost/shared_ptr.hpp> namespace qpid { namespace ha { @@ -42,91 +51,149 @@ namespace ha { namespace _qmf = ::qmf::org::apache::qpid::ha; using namespace management; using namespace std; +using types::Variant; +using types::Uuid; +using sys::Mutex; -namespace { - -const std::string STANDALONE="standalone"; -const std::string CATCH_UP="catch-up"; -const std::string BACKUP="backup"; -const std::string PRIMARY="primary"; - -} // namespace - - +// Called in Plugin::earlyInitialize HaBroker::HaBroker(broker::Broker& b, const Settings& s) - : broker(b), + : logPrefix("Broker: "), + broker(b), + systemId(broker.getSystem()->getSystemId().data()), settings(s), - mgmtObject(0) + observer(new ConnectionObserver(*this, systemId)), + mgmtObject(0), + status(STANDALONE), + membership(systemId), + replicationTest(s.replicateDefault.get()) { - // Register a factory for replicating subscriptions. - broker.getConsumerFactories().add( - boost::shared_ptr<ReplicatingSubscription::Factory>( - new ReplicatingSubscription::Factory())); + // If we are joining a cluster we must start excluding clients now, + // otherwise there's a window for a client to connect before we get to + // initialize() + if (settings.cluster) { + QPID_LOG(debug, logPrefix << "Rejecting client connections."); + observer->setObserver(boost::shared_ptr<broker::ConnectionObserver>( + new BackupConnectionExcluder)); + broker.getConnectionObservers().add(observer); + } +} - broker.getKnownBrokers = boost::bind(&HaBroker::getKnownBrokers, this); +// Called in Plugin::initialize +void HaBroker::initialize() { + // FIXME aconway 2012-07-19: assumes there's a TCP transport with a meaningful port. + brokerInfo = BrokerInfo( + broker.getSystem()->getNodeName(), broker.getPort(broker::Broker::TCP_TRANSPORT), systemId); + + // Set up the management object. ManagementAgent* ma = broker.getManagementAgent(); - if (!ma) + if (settings.cluster && !ma) throw Exception("Cannot start HA: management is disabled"); _qmf::Package packageInit(ma); mgmtObject = new _qmf::HaBroker(ma, this, "ha-broker"); - mgmtObject->set_status(settings.cluster ? BACKUP : STANDALONE); - mgmtObject->set_replicateDefault(str(settings.replicateDefault)); + mgmtObject->set_replicateDefault(settings.replicateDefault.str()); + mgmtObject->set_systemId(systemId); ma->addObject(mgmtObject); - // NOTE: lock is not needed in a constructor but we created it just to pass - // to the set functions. - sys::Mutex::ScopedLock l(lock); - if (!settings.clientUrl.empty()) setClientUrl(Url(settings.clientUrl), l); - if (!settings.brokerUrl.empty()) setBrokerUrl(Url(settings.brokerUrl), l); + // Register a factory for replicating subscriptions. + broker.getConsumerFactories().add( + boost::shared_ptr<ReplicatingSubscription::Factory>( + new ReplicatingSubscription::Factory())); + + // If we are in a cluster, start as backup in joining state. + if (settings.cluster) { + status = JOINING; + backup.reset(new Backup(*this, settings)); + broker.getKnownBrokers = boost::bind(&HaBroker::getKnownBrokers, this); + } + + if (!settings.clientUrl.empty()) setClientUrl(Url(settings.clientUrl)); + if (!settings.brokerUrl.empty()) setBrokerUrl(Url(settings.brokerUrl)); + + + QPID_LOG(notice, logPrefix << "Initializing: " << brokerInfo); - // If we are in a cluster, we start in backup mode. - if (settings.cluster) backup.reset(new Backup(*this, s)); + // NOTE: lock is not needed in a constructor, but create one + // to pass to functions that have a ScopedLock parameter. + Mutex::ScopedLock l(lock); + statusChanged(l); } -HaBroker::~HaBroker() {} +HaBroker::~HaBroker() { + QPID_LOG(notice, logPrefix << "Shut down: " << brokerInfo); + broker.getConnectionObservers().remove(observer); +} + +void HaBroker::recover() { + auto_ptr<Backup> b; + { + Mutex::ScopedLock l(lock); + // No longer replicating, close link. Note: link must be closed before we + // setStatus(RECOVERING) as that will remove our broker info from the + // outgoing link properties so we won't recognize self-connects. + b = backup; + } + b.reset(); // Call destructor outside of lock. + BrokerInfo::Set backups; + { + Mutex::ScopedLock l(lock); + setStatus(RECOVERING, l); + backups = membership.otherBackups(); + membership.reset(brokerInfo); + // Drop the lock, new Primary may call back on activate. + } + // Outside of lock, may call back on activate() + primary.reset(new Primary(*this, backups)); // Starts primary-ready check. +} + +// Called back from Primary active check. +void HaBroker::activate() { setStatus(ACTIVE); } Manageable::status_t HaBroker::ManagementMethod (uint32_t methodId, Args& args, string&) { - sys::Mutex::ScopedLock l(lock); switch (methodId) { case _qmf::HaBroker::METHOD_PROMOTE: { - if (backup.get()) { // I am a backup - // NOTE: resetting backup allows client connections, so any - // primary state should be set up here before backup.reset() - backup.reset(); - QPID_LOG(notice, "HA: Promoted to primary"); - mgmtObject->set_status(PRIMARY); + switch (getStatus()) { + case JOINING: recover(); break; + case CATCHUP: + QPID_LOG(error, logPrefix << "Still catching up, cannot be promoted."); + throw Exception("Still catching up, cannot be promoted."); + break; + case READY: recover(); break; + case RECOVERING: break; + case ACTIVE: break; + case STANDALONE: break; } break; } - case _qmf::HaBroker::METHOD_SETBROKERS: { - setBrokerUrl(Url(dynamic_cast<_qmf::ArgsHaBrokerSetBrokers&>(args).i_url), l); + case _qmf::HaBroker::METHOD_SETBROKERSURL: { + setBrokerUrl(Url(dynamic_cast<_qmf::ArgsHaBrokerSetBrokersUrl&>(args).i_url)); break; } - case _qmf::HaBroker::METHOD_SETPUBLICBROKERS: { - setClientUrl(Url(dynamic_cast<_qmf::ArgsHaBrokerSetPublicBrokers&>(args).i_url), l); + case _qmf::HaBroker::METHOD_SETPUBLICURL: { + setClientUrl(Url(dynamic_cast<_qmf::ArgsHaBrokerSetPublicUrl&>(args).i_url)); break; } - case _qmf::HaBroker::METHOD_SETEXPECTEDBACKUPS: { - setExpectedBackups(dynamic_cast<_qmf::ArgsHaBrokerSetExpectedBackups&>(args).i_expectedBackups, l); - break; - } case _qmf::HaBroker::METHOD_REPLICATE: { _qmf::ArgsHaBrokerReplicate& bq_args = dynamic_cast<_qmf::ArgsHaBrokerReplicate&>(args); - QPID_LOG(debug, "HA replicating individual queue "<< bq_args.i_queue << " from " << bq_args.i_broker); + QPID_LOG(debug, logPrefix << "Replicate individual queue " + << bq_args.i_queue << " from " << bq_args.i_broker); boost::shared_ptr<broker::Queue> queue = broker.getQueues().get(bq_args.i_queue); Url url(bq_args.i_broker); string protocol = url[0].protocol.empty() ? "tcp" : url[0].protocol; + Uuid uuid(true); std::pair<broker::Link::shared_ptr, bool> result = broker.getLinks().declare( + broker::QPID_NAME_PREFIX + string("ha.link.") + uuid.str(), url[0].host, url[0].port, protocol, false, // durable - settings.mechanism, settings.username, settings.password); + settings.mechanism, settings.username, settings.password, + false); // no amq.failover - don't want to use client URL. boost::shared_ptr<broker::Link> link = result.first; link->setUrl(url); // Create a queue replicator - boost::shared_ptr<QueueReplicator> qr(new QueueReplicator(queue, link)); + boost::shared_ptr<QueueReplicator> qr( + new QueueReplicator(*this, queue, link)); qr->activate(); broker.getExchanges().registerExchange(qr); break; @@ -138,43 +205,146 @@ Manageable::status_t HaBroker::ManagementMethod (uint32_t methodId, Args& args, return Manageable::STATUS_OK; } -void HaBroker::setClientUrl(const Url& url, const sys::Mutex::ScopedLock& l) { +void HaBroker::setClientUrl(const Url& url) { + Mutex::ScopedLock l(lock); if (url.empty()) throw Exception("Invalid empty URL for HA client failover"); clientUrl = url; updateClientUrl(l); } -void HaBroker::updateClientUrl(const sys::Mutex::ScopedLock&) { +void HaBroker::updateClientUrl(Mutex::ScopedLock&) { Url url = clientUrl.empty() ? brokerUrl : clientUrl; if (url.empty()) throw Url::Invalid("HA client URL is empty"); - mgmtObject->set_publicBrokers(url.str()); + mgmtObject->set_publicUrl(url.str()); knownBrokers.clear(); knownBrokers.push_back(url); - QPID_LOG(debug, "HA: Setting client URL to: " << url); + QPID_LOG(debug, logPrefix << "Setting client URL to: " << url); } -void HaBroker::setBrokerUrl(const Url& url, const sys::Mutex::ScopedLock& l) { +void HaBroker::setBrokerUrl(const Url& url) { + Mutex::ScopedLock l(lock); if (url.empty()) throw Url::Invalid("HA broker URL is empty"); - QPID_LOG(debug, "HA: Setting broker URL to: " << url); brokerUrl = url; - mgmtObject->set_brokers(brokerUrl.str()); + mgmtObject->set_brokersUrl(brokerUrl.str()); if (backup.get()) backup->setBrokerUrl(brokerUrl); // Updating broker URL also updates defaulted client URL: if (clientUrl.empty()) updateClientUrl(l); } -void HaBroker::setExpectedBackups(size_t n, const sys::Mutex::ScopedLock&) { - expectedBackups = n; - mgmtObject->set_expectedBackups(n); -} - std::vector<Url> HaBroker::getKnownBrokers() const { + Mutex::ScopedLock l(lock); return knownBrokers; } -void HaBroker::shutdown(const std::string& message) { - QPID_LOG(critical, "Shutting down: " << message); +void HaBroker::shutdown() { + QPID_LOG(critical, logPrefix << "Critical error, shutting down."); broker.shutdown(); } +BrokerStatus HaBroker::getStatus() const { + Mutex::ScopedLock l(lock); + return status; +} + +void HaBroker::setStatus(BrokerStatus newStatus) { + Mutex::ScopedLock l(lock); + setStatus(newStatus, l); +} + +namespace { +bool checkTransition(BrokerStatus from, BrokerStatus to) { + // Legal state transitions. Initial state is JOINING, ACTIVE is terminal. + static const BrokerStatus TRANSITIONS[][2] = { + { JOINING, CATCHUP }, // Connected to primary + { JOINING, RECOVERING }, // Chosen as initial primary. + { CATCHUP, READY }, // Caught up all queues, ready to take over. + { READY, RECOVERING }, // Chosen as new primary + { READY, CATCHUP }, // Timed out failing over, demoted to catch-up. + { RECOVERING, ACTIVE } // All expected backups are ready + }; + static const size_t N = sizeof(TRANSITIONS)/sizeof(TRANSITIONS[0]); + for (size_t i = 0; i < N; ++i) { + if (TRANSITIONS[i][0] == from && TRANSITIONS[i][1] == to) + return true; + } + return false; +} +} // namespace + +void HaBroker::setStatus(BrokerStatus newStatus, Mutex::ScopedLock& l) { + QPID_LOG(info, logPrefix << "Status change: " + << printable(status) << " -> " << printable(newStatus)); + bool legal = checkTransition(status, newStatus); + assert(legal); + if (!legal) { + QPID_LOG(critical, logPrefix << "Illegal state transition: " + << printable(status) << " -> " << printable(newStatus)); + shutdown(); + } + status = newStatus; + statusChanged(l); +} + +void HaBroker::statusChanged(Mutex::ScopedLock& l) { + mgmtObject->set_status(printable(status).str()); + brokerInfo.setStatus(status); + setLinkProperties(l); +} + +void HaBroker::membershipUpdated(Mutex::ScopedLock&) { + Variant::List brokers = membership.asList(); + mgmtObject->set_members(brokers); + broker.getManagementAgent()->raiseEvent(_qmf::EventMembersUpdate(brokers)); +} + +void HaBroker::setMembership(const Variant::List& brokers) { + Mutex::ScopedLock l(lock); + membership.assign(brokers); + QPID_LOG(info, logPrefix << "Membership update: " << membership); + BrokerInfo info; + // Update my status to what the primary says it is. The primary can toggle + // status between READY and CATCHUP based on the state of our subscriptions. + if (membership.get(systemId, info) && status != info.getStatus()) { + setStatus(info.getStatus(), l); + if (backup.get()) backup->setStatus(status); + } + membershipUpdated(l); +} + +void HaBroker::resetMembership(const BrokerInfo& b) { + Mutex::ScopedLock l(lock); + membership.reset(b); + QPID_LOG(debug, logPrefix << "Membership reset to: " << membership); + membershipUpdated(l); +} + +void HaBroker::addBroker(const BrokerInfo& b) { + Mutex::ScopedLock l(lock); + membership.add(b); + QPID_LOG(debug, logPrefix << "Membership add: " << b << " now: " << membership); + membershipUpdated(l); +} + +void HaBroker::removeBroker(const Uuid& id) { + Mutex::ScopedLock l(lock); + membership.remove(id); + QPID_LOG(debug, logPrefix << "Membership remove: " << id << " now: " << membership); + membershipUpdated(l); +} + +void HaBroker::setLinkProperties(Mutex::ScopedLock&) { + framing::FieldTable linkProperties = broker.getLinkClientProperties(); + if (isBackup(status)) { + // If this is a backup then any outgoing links are backup + // links and need to be tagged. + linkProperties.setTable(ConnectionObserver::BACKUP_TAG, brokerInfo.asFieldTable()); + } + else { + // If this is a primary then any outgoing links are federation links + // and should not be tagged. + linkProperties.erase(ConnectionObserver::BACKUP_TAG); + } + broker.setLinkClientProperties(linkProperties); +} + }} // namespace qpid::ha diff --git a/cpp/src/qpid/ha/HaBroker.h b/cpp/src/qpid/ha/HaBroker.h index 99b30fd36b..0ffc152097 100644 --- a/cpp/src/qpid/ha/HaBroker.h +++ b/cpp/src/qpid/ha/HaBroker.h @@ -22,31 +22,54 @@ * */ +#include "BrokerInfo.h" +#include "Membership.h" +#include "types.h" +#include "ReplicationTest.h" #include "Settings.h" #include "qpid/Url.h" #include "qpid/sys/Mutex.h" #include "qmf/org/apache/qpid/ha/HaBroker.h" #include "qpid/management/Manageable.h" +#include "qpid/types/Variant.h" #include <memory> +#include <set> +#include <boost/shared_ptr.hpp> namespace qpid { + +namespace types { +class Variant; +} + namespace broker { class Broker; +class Queue; +} +namespace framing { +class FieldTable; } + namespace ha { class Backup; +class ConnectionObserver; +class Primary; /** - * HA state and actions associated with a broker. + * HA state and actions associated with a HA broker. Holds all the management info. * * THREAD SAFE: may be called in arbitrary broker IO or timer threads. */ class HaBroker : public management::Manageable { public: + /** HaBroker is constructed during earlyInitialize */ HaBroker(broker::Broker&, const Settings&); ~HaBroker(); + /** Called during plugin initialization */ + void initialize(); + // Implement Manageable. qpid::management::ManagementObject* GetManagementObject() const { return mgmtObject; } management::Manageable::status_t ManagementMethod ( @@ -55,26 +78,57 @@ class HaBroker : public management::Manageable broker::Broker& getBroker() { return broker; } const Settings& getSettings() const { return settings; } - // Log a critical error message and shut down the broker. - void shutdown(const std::string& message); + /** Shut down the broker. Caller should log a critical error message. */ + void shutdown(); + + BrokerStatus getStatus() const; + void setStatus(BrokerStatus); + void activate(); + + Backup* getBackup() { return backup.get(); } + ReplicationTest getReplicationTest() const { return replicationTest; } + + boost::shared_ptr<ConnectionObserver> getObserver() { return observer; } + + const BrokerInfo& getBrokerInfo() const { return brokerInfo; } + + void setMembership(const types::Variant::List&); // Set membership from list. + void resetMembership(const BrokerInfo& b); // Reset to contain just one member. + void addBroker(const BrokerInfo& b); // Add a broker to the membership. + void removeBroker(const types::Uuid& id); // Remove a broker from membership. private: - void setClientUrl(const Url&, const sys::Mutex::ScopedLock&); - void setBrokerUrl(const Url&, const sys::Mutex::ScopedLock&); - void setExpectedBackups(size_t, const sys::Mutex::ScopedLock&); - void updateClientUrl(const sys::Mutex::ScopedLock&); - bool isPrimary(const sys::Mutex::ScopedLock&) { return !backup.get(); } + void setClientUrl(const Url&); + void setBrokerUrl(const Url&); + void updateClientUrl(sys::Mutex::ScopedLock&); + + bool isPrimary(sys::Mutex::ScopedLock&) { return !backup.get(); } + + void setStatus(BrokerStatus, sys::Mutex::ScopedLock&); + void recover(); + void statusChanged(sys::Mutex::ScopedLock&); + void setLinkProperties(sys::Mutex::ScopedLock&); + std::vector<Url> getKnownBrokers() const; + void membershipUpdated(sys::Mutex::ScopedLock&); + + std::string logPrefix; broker::Broker& broker; + types::Uuid systemId; const Settings settings; - sys::Mutex lock; + mutable sys::Mutex lock; + boost::shared_ptr<ConnectionObserver> observer; // Used by Backup and Primary std::auto_ptr<Backup> backup; + std::auto_ptr<Primary> primary; qmf::org::apache::qpid::ha::HaBroker* mgmtObject; Url clientUrl, brokerUrl; std::vector<Url> knownBrokers; - size_t expectedBackups; + BrokerStatus status; + BrokerInfo brokerInfo; + Membership membership; + ReplicationTest replicationTest; }; }} // namespace qpid::ha diff --git a/cpp/src/qpid/ha/HaPlugin.cpp b/cpp/src/qpid/ha/HaPlugin.cpp index 4da3b0d7d2..f7fe553d9b 100644 --- a/cpp/src/qpid/ha/HaPlugin.cpp +++ b/cpp/src/qpid/ha/HaPlugin.cpp @@ -20,7 +20,7 @@ #include "qpid/Plugin.h" #include "qpid/Options.h" #include "qpid/broker/Broker.h" - +#include <boost/bind.hpp> namespace qpid { namespace ha { @@ -33,21 +33,21 @@ struct Options : public qpid::Options { addOptions() ("ha-cluster", optValue(settings.cluster, "yes|no"), "Join a HA active/passive cluster.") - ("ha-brokers", optValue(settings.brokerUrl,"URL"), - "URL that backup brokers use to connect and fail over.") - ("ha-public-brokers", optValue(settings.clientUrl,"URL"), - "URL that clients use to connect and fail over, defaults to ha-brokers.") + ("ha-brokers-url", optValue(settings.brokerUrl,"URL"), + "URL with address of each broker in the cluster.") + ("ha-public-url", optValue(settings.clientUrl,"URL"), + "URL advertized to clients to connect to the cluster.") ("ha-replicate", optValue(settings.replicateDefault, "LEVEL"), "Replication level for creating queues and exchanges if there is no qpid.replicate argument supplied. LEVEL is 'none', 'configuration' or 'all'") - ("ha-expected-backups", optValue(settings.expectedBackups, "N"), - "Number of backups expected to be active in the HA cluster.") ("ha-username", optValue(settings.username, "USER"), "Username for connections between HA brokers") ("ha-password", optValue(settings.password, "PASS"), "Password for connections between HA brokers") ("ha-mechanism", optValue(settings.mechanism, "MECH"), "Authentication mechanism for connections between HA brokers") + ("ha-backup-timeout", optValue(settings.backupTimeout, "SECONDS"), + "Maximum time to wait for an expected backup to connect and become ready.") ; } }; @@ -62,14 +62,26 @@ struct HaPlugin : public Plugin { Options* getOptions() { return &options; } - void earlyInitialize(Plugin::Target& ) {} + void earlyInitialize(Plugin::Target& target) { + broker::Broker* broker = dynamic_cast<broker::Broker*>(&target); + if (broker) { + // Must create the HaBroker in earlyInitialize so it can set up its + // connection observer before clients start connecting. + haBroker.reset(new ha::HaBroker(*broker, settings)); + broker->addFinalizer(boost::bind(&HaPlugin::finalize, this)); + } + } void initialize(Plugin::Target& target) { broker::Broker* broker = dynamic_cast<broker::Broker*>(&target); - if (broker) haBroker.reset(new ha::HaBroker(*broker, settings)); + if (broker) haBroker->initialize(); + } + + void finalize() { + haBroker.reset(); } }; -static HaPlugin instance; // Static initialization. +HaPlugin instance; // Static initialization. }} // namespace qpid::ha diff --git a/cpp/src/qpid/ha/Membership.cpp b/cpp/src/qpid/ha/Membership.cpp new file mode 100644 index 0000000000..cc2906dd8f --- /dev/null +++ b/cpp/src/qpid/ha/Membership.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 "Membership.h" +#include <boost/bind.hpp> +#include <iostream> +#include <iterator> + +namespace qpid { +namespace ha { + + +void Membership::reset(const BrokerInfo& b) { + brokers.clear(); + brokers[b.getSystemId()] = b; +} + +void Membership::add(const BrokerInfo& b) { + brokers[b.getSystemId()] = b; +} + + +void Membership::remove(const types::Uuid& id) { + BrokerInfo::Map::iterator i = brokers.find(id); + if (i != brokers.end()) { + brokers.erase(i); + } +} + +bool Membership::contains(const types::Uuid& id) { + return brokers.find(id) != brokers.end(); +} + +void Membership::assign(const types::Variant::List& list) { + brokers.clear(); + for (types::Variant::List::const_iterator i = list.begin(); i != list.end(); ++i) { + BrokerInfo b(i->asMap()); + brokers[b.getSystemId()] = b; + } +} + +types::Variant::List Membership::asList() const { + types::Variant::List list; + for (BrokerInfo::Map::const_iterator i = brokers.begin(); i != brokers.end(); ++i) + list.push_back(i->second.asMap()); + return list; +} + +BrokerInfo::Set Membership::otherBackups() const { + BrokerInfo::Set result; + for (BrokerInfo::Map::const_iterator i = brokers.begin(); i != brokers.end(); ++i) + if (isBackup(i->second.getStatus()) && i->second.getSystemId() != self) + result.insert(i->second); + return result; +} + +bool Membership::get(const types::Uuid& id, BrokerInfo& result) { + BrokerInfo::Map::iterator i = brokers.find(id); + if (i == brokers.end()) return false; + result = i->second; + return true; +} + +std::ostream& operator<<(std::ostream& o, const Membership& members) { + return o << members.brokers; +} + +}} // namespace qpid::ha diff --git a/cpp/src/qpid/ha/Membership.h b/cpp/src/qpid/ha/Membership.h new file mode 100644 index 0000000000..3bd8653a64 --- /dev/null +++ b/cpp/src/qpid/ha/Membership.h @@ -0,0 +1,68 @@ +#ifndef QPID_HA_MEMBERSHIP_H +#define QPID_HA_MEMBERSHIP_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "BrokerInfo.h" +#include "types.h" +#include "qpid/framing/Uuid.h" +#include "qpid/log/Statement.h" +#include "qpid/types/Variant.h" +#include <boost/function.hpp> +#include <set> +#include <vector> +#include <iosfwd> +namespace qpid { +namespace ha { + +/** + * Keep track of the brokers in the membership. + * THREAD UNSAFE: caller must serialize + */ +class Membership +{ + public: + Membership(const types::Uuid& self_) : self(self_) {} + + void reset(const BrokerInfo& b); ///< Reset to contain just one member. + void add(const BrokerInfo& b); + void remove(const types::Uuid& id); + bool contains(const types::Uuid& id); + /** Return IDs of all backups other than self */ + BrokerInfo::Set otherBackups() const; + + void assign(const types::Variant::List&); + types::Variant::List asList() const; + + bool get(const types::Uuid& id, BrokerInfo& result); + + private: + types::Uuid self; + BrokerInfo::Map brokers; + friend std::ostream& operator<<(std::ostream&, const Membership&); +}; + +std::ostream& operator<<(std::ostream&, const Membership&); + +}} // namespace qpid::ha + +#endif /*!QPID_HA_MEMBERSHIP_H*/ diff --git a/cpp/src/qpid/ha/Primary.cpp b/cpp/src/qpid/ha/Primary.cpp new file mode 100644 index 0000000000..69c94bfc7d --- /dev/null +++ b/cpp/src/qpid/ha/Primary.cpp @@ -0,0 +1,252 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "Backup.h" +#include "HaBroker.h" +#include "Primary.h" +#include "ReplicationTest.h" +#include "ReplicatingSubscription.h" +#include "RemoteBackup.h" +#include "ConnectionObserver.h" +#include "qpid/assert.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/ConfigurationObserver.h" +#include "qpid/broker/Connection.h" +#include "qpid/broker/Queue.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Timer.h" +#include <boost/bind.hpp> + +namespace qpid { +namespace ha { + +using sys::Mutex; + +namespace { + +class PrimaryConnectionObserver : public broker::ConnectionObserver +{ + public: + PrimaryConnectionObserver(Primary& p) : primary(p) {} + void opened(broker::Connection& c) { primary.opened(c); } + void closed(broker::Connection& c) { primary.closed(c); } + private: + Primary& primary; +}; + +class PrimaryConfigurationObserver : public broker::ConfigurationObserver +{ + public: + PrimaryConfigurationObserver(Primary& p) : primary(p) {} + void queueCreate(const Primary::QueuePtr& q) { primary.queueCreate(q); } + void queueDestroy(const Primary::QueuePtr& q) { primary.queueDestroy(q); } + private: + Primary& primary; +}; + +class ExpectedBackupTimerTask : public sys::TimerTask { + public: + ExpectedBackupTimerTask(Primary& p, sys::AbsTime deadline) + : TimerTask(deadline, "ExpectedBackupTimerTask"), primary(p) {} + void fire() { primary.timeoutExpectedBackups(); } + private: + Primary& primary; +}; + +} // namespace + +Primary* Primary::instance = 0; + +Primary::Primary(HaBroker& hb, const BrokerInfo::Set& expect) : + haBroker(hb), logPrefix("Primary: "), active(false) +{ + assert(instance == 0); + instance = this; // Let queue replicators find us. + if (expect.empty()) { + QPID_LOG(notice, logPrefix << "Promoted to primary. No expected backups."); + } + else { + // NOTE: RemoteBackups must be created before we set the ConfigurationObserver + // or ConnectionObserver so that there is no client activity while + // the QueueGuards are created. + QPID_LOG(notice, logPrefix << "Promoted to primary. Expected backups: " << expect); + for (BrokerInfo::Set::const_iterator i = expect.begin(); i != expect.end(); ++i) { + boost::shared_ptr<RemoteBackup> backup( + new RemoteBackup(*i, haBroker.getReplicationTest(), false)); + backups[i->getSystemId()] = backup; + if (!backup->isReady()) expectedBackups.insert(backup); + backup->setInitialQueues(hb.getBroker().getQueues(), true); // Create guards + } + // Set timeout for expected brokers to connect and become ready. + sys::Duration timeout(int64_t(hb.getSettings().backupTimeout*sys::TIME_SEC)); + sys::AbsTime deadline(sys::now(), timeout); + timerTask = new ExpectedBackupTimerTask(*this, deadline); + hb.getBroker().getTimer().add(timerTask); + } + + configurationObserver.reset(new PrimaryConfigurationObserver(*this)); + haBroker.getBroker().getConfigurationObservers().add(configurationObserver); + + Mutex::ScopedLock l(lock); // We are now active as a configurationObserver + checkReady(l); + // Allow client connections + connectionObserver.reset(new PrimaryConnectionObserver(*this)); + haBroker.getObserver()->setObserver(connectionObserver); +} + +Primary::~Primary() { + if (timerTask) timerTask->cancel(); + haBroker.getBroker().getConfigurationObservers().remove(configurationObserver); +} + +void Primary::checkReady(Mutex::ScopedLock&) { + if (!active && expectedBackups.empty()) { + active = true; + Mutex::ScopedUnlock u(lock); // Don't hold lock across callback + QPID_LOG(notice, logPrefix << "Finished waiting for backups, primary is active."); + haBroker.activate(); + } +} + +void Primary::checkReady(BackupMap::iterator i, Mutex::ScopedLock& l) { + if (i != backups.end() && i->second->reportReady()) { + BrokerInfo info = i->second->getBrokerInfo(); + info.setStatus(READY); + haBroker.addBroker(info); + if (expectedBackups.erase(i->second)) { + QPID_LOG(info, logPrefix << "Expected backup is ready: " << info); + checkReady(l); + } + else + QPID_LOG(info, logPrefix << "New backup is ready: " << info); + } +} + +void Primary::timeoutExpectedBackups() { + try { + sys::Mutex::ScopedLock l(lock); + if (active) return; // Already activated + // Remove records for any expectedBackups that are not yet connected + // Allow backups that are connected to continue becoming ready. + for (BackupSet::iterator i = expectedBackups.begin(); i != expectedBackups.end();) + { + boost::shared_ptr<RemoteBackup> rb = *i; + if (!rb->isConnected()) { + BrokerInfo info = rb->getBrokerInfo(); + QPID_LOG(error, logPrefix << "Expected backup timed out: " << info); + expectedBackups.erase(i++); + backups.erase(info.getSystemId()); + rb->cancel(); + // Downgrade the broker to CATCHUP + info.setStatus(CATCHUP); + haBroker.addBroker(info); + } + else ++i; + } + checkReady(l); + } + catch(const std::exception& e) { + QPID_LOG(error, logPrefix << "Error timing out backups: " << e.what()); + // No-where for this exception to go. + } +} + +void Primary::readyReplica(const ReplicatingSubscription& rs) { + sys::Mutex::ScopedLock l(lock); + BackupMap::iterator i = backups.find(rs.getBrokerInfo().getSystemId()); + if (i != backups.end()) { + i->second->ready(rs.getQueue()); + checkReady(i, l); + } +} + +void Primary::queueCreate(const QueuePtr& q) { + // Throw if there is an invalid replication level in the queue settings. + haBroker.getReplicationTest().replicateLevel(q->getSettings()); + Mutex::ScopedLock l(lock); + for (BackupMap::iterator i = backups.begin(); i != backups.end(); ++i) { + i->second->queueCreate(q); + checkReady(i, l); + } +} + +void Primary::queueDestroy(const QueuePtr& q) { + Mutex::ScopedLock l(lock); + for (BackupMap::iterator i = backups.begin(); i != backups.end(); ++i) + i->second->queueDestroy(q); + checkReady(l); +} + +void Primary::opened(broker::Connection& connection) { + BrokerInfo info; + if (ha::ConnectionObserver::getBrokerInfo(connection, info)) { + Mutex::ScopedLock l(lock); + BackupMap::iterator i = backups.find(info.getSystemId()); + if (i == backups.end()) { + boost::shared_ptr<RemoteBackup> backup( + new RemoteBackup(info, haBroker.getReplicationTest(), true)); + { + // Avoid deadlock with queue registry lock. + Mutex::ScopedUnlock u(lock); + backup->setInitialQueues(haBroker.getBroker().getQueues(), false); + } + backups[info.getSystemId()] = backup; + QPID_LOG(debug, logPrefix << "New backup connected: " << info); + } + else { + QPID_LOG(debug, logPrefix << "Known backup connected: " << info); + i->second->setConnected(true); + checkReady(i, l); + } + if (info.getStatus() == JOINING) info.setStatus(CATCHUP); + haBroker.addBroker(info); + } + else + QPID_LOG(debug, logPrefix << "Accepted client connection " + << connection.getMgmtId()); +} + +void Primary::closed(broker::Connection& connection) { + Mutex::ScopedLock l(lock); + BrokerInfo info; + if (ha::ConnectionObserver::getBrokerInfo(connection, info)) { + QPID_LOG(debug, logPrefix << "Backup disconnected: " << info); + haBroker.removeBroker(info.getSystemId()); + BackupMap::iterator i = backups.find(info.getSystemId()); + if (i != backups.end()) i->second->setConnected(false); + } + // NOTE: we do not remove from the backups map here, the backups map holds + // all the backups we know about whether connected or not. + // + // It is possible for a backup connection to be rejected while we are a backup, + // but the closed is seen after we have become primary. Removing the entry + // from backups in this case would be incorrect. +} + + +boost::shared_ptr<QueueGuard> Primary::getGuard(const QueuePtr& q, const BrokerInfo& info) +{ + Mutex::ScopedLock l(lock); + BackupMap::iterator i = backups.find(info.getSystemId()); + return i == backups.end() ? boost::shared_ptr<QueueGuard>() : i->second->guard(q); +} + +}} // namespace qpid::ha diff --git a/cpp/src/qpid/ha/Primary.h b/cpp/src/qpid/ha/Primary.h new file mode 100644 index 0000000000..26883f4416 --- /dev/null +++ b/cpp/src/qpid/ha/Primary.h @@ -0,0 +1,115 @@ +#ifndef QPID_HA_PRIMARY_H +#define QPID_HA_PRIMARY_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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 "BrokerInfo.h" +#include "qpid/sys/Mutex.h" +#include <boost/shared_ptr.hpp> +#include <boost/intrusive_ptr.hpp> +#include <map> +#include <string> + +namespace qpid { + +namespace broker { +class Queue; +class Connection; +class ConnectionObserver; +class ConfigurationObserver; +} + +namespace sys { +class TimerTask; +} + +namespace ha { +class HaBroker; +class ReplicatingSubscription; +class RemoteBackup; +class QueueGuard; + +/** + * State associated with a primary broker: + * - sets queue guards and tracks readiness of initial backups till active. + * - sets queue guards on new queues for each backup. + * + * THREAD SAFE: called concurrently in arbitrary connection threads. + */ +class Primary +{ + public: + typedef boost::shared_ptr<broker::Queue> QueuePtr; + + static Primary* get() { return instance; } + + Primary(HaBroker& hb, const BrokerInfo::Set& expectedBackups); + ~Primary(); + + void readyReplica(const ReplicatingSubscription&); + void removeReplica(const std::string& q); + + // Called via ConfigurationObserver + void queueCreate(const QueuePtr&); + void queueDestroy(const QueuePtr&); + + // Called via ConnectionObserver + void opened(broker::Connection& connection); + void closed(broker::Connection& connection); + + boost::shared_ptr<QueueGuard> getGuard(const QueuePtr& q, const BrokerInfo&); + + // Called in timer thread when the deadline for expected backups expires. + void timeoutExpectedBackups(); + + private: + typedef std::map<types::Uuid, boost::shared_ptr<RemoteBackup> > BackupMap; + typedef std::set<boost::shared_ptr<RemoteBackup> > BackupSet; + + void checkReady(sys::Mutex::ScopedLock&); + void checkReady(BackupMap::iterator, sys::Mutex::ScopedLock&); + + sys::Mutex lock; + HaBroker& haBroker; + std::string logPrefix; + bool active; + /** + * Set of expected backups that must be ready before we declare ourselves + * active + */ + BackupSet expectedBackups; + /** + * Map of all the remote backups we know about: any expected backups plus + * all actual backups that have connected. We do not remove entries when a + * backup disconnects. @see Primary::closed() + */ + BackupMap backups; + boost::shared_ptr<broker::ConnectionObserver> connectionObserver; + boost::shared_ptr<broker::ConfigurationObserver> configurationObserver; + boost::intrusive_ptr<sys::TimerTask> timerTask; + + static Primary* instance; +}; +}} // namespace qpid::ha + +#endif /*!QPID_HA_PRIMARY_H*/ diff --git a/cpp/src/qpid/ha/QueueGuard.cpp b/cpp/src/qpid/ha/QueueGuard.cpp new file mode 100644 index 0000000000..a30ab1f73c --- /dev/null +++ b/cpp/src/qpid/ha/QueueGuard.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. + * + */ +#include "QueueGuard.h" +#include "ReplicatingSubscription.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/QueuedMessage.h" +#include "qpid/broker/QueueObserver.h" +#include "qpid/log/Statement.h" +#include <boost/bind.hpp> +#include <sstream> + +namespace qpid { +namespace ha { + +using namespace broker; +using sys::Mutex; +using framing::SequenceNumber; +using framing::SequenceSet; + +class QueueGuard::QueueObserver : public broker::QueueObserver +{ + public: + QueueObserver(QueueGuard& g) : guard(g) {} + void enqueued(const broker::QueuedMessage& qm) { guard.enqueued(qm); } + void dequeued(const broker::QueuedMessage& qm) { guard.dequeued(qm); } + void acquired(const broker::QueuedMessage&) {} + void requeued(const broker::QueuedMessage&) {} + private: + QueueGuard& guard; +}; + + + +QueueGuard::QueueGuard(broker::Queue& q, const BrokerInfo& info) + : queue(q), subscription(0) +{ + std::ostringstream os; + os << "Primary guard " << queue.getName() << "@" << info.getLogId() << ": "; + logPrefix = os.str(); + observer.reset(new QueueObserver(*this)); + queue.addObserver(observer); + // Set range after addObserver so we know that range.back+1 is a guarded position. + range = QueueRange(q); +} + +QueueGuard::~QueueGuard() { cancel(); } + +// NOTE: Called with message lock held. +void QueueGuard::enqueued(const QueuedMessage& qm) { + assert(qm.queue == &queue); + // Delay completion + QPID_LOG(trace, logPrefix << "Delayed completion of " << qm); + qm.payload->getIngressCompletion().startCompleter(); + { + Mutex::ScopedLock l(lock); + assert(!delayed.contains(qm.position)); + delayed += qm.position; + } +} + +// NOTE: Called with message lock held. +void QueueGuard::dequeued(const QueuedMessage& qm) { + assert(qm.queue == &queue); + QPID_LOG(trace, logPrefix << "Dequeued " << qm); + ReplicatingSubscription* rs=0; + { + Mutex::ScopedLock l(lock); + rs = subscription; + } + if (rs) rs->dequeued(qm); + complete(qm); +} + +void QueueGuard::cancel() { + queue.removeObserver(observer); + { + Mutex::ScopedLock l(lock); + if (delayed.empty()) return; // No need if no delayed messages. + } + // FIXME aconway 2012-06-15: optimize, only messages in delayed set. + queue.eachMessage(boost::bind(&QueueGuard::complete, this, _1)); +} + +void QueueGuard::attach(ReplicatingSubscription& rs) { + Mutex::ScopedLock l(lock); + subscription = &rs; +} + +namespace { +void completeBefore(QueueGuard* guard, SequenceNumber position, const QueuedMessage& qm) { + if (qm.position <= position) guard->complete(qm); +} +} + +bool QueueGuard::subscriptionStart(SequenceNumber position) { + // Complete any messages before or at the ReplicatingSubscription start position. + // Those messages are already on the backup. + if (!delayed.empty() && delayed.front() <= position) { + // FIXME aconway 2012-06-15: queue iteration, only messages in delayed + queue.eachMessage(boost::bind(&completeBefore, this, position, _1)); + } + return position >= range.back; +} + +void QueueGuard::complete(const QueuedMessage& qm) { + assert(qm.queue == &queue); + { + Mutex::ScopedLock l(lock); + // The same message can be completed twice, by + // ReplicatingSubscription::acknowledged and dequeued. Remove it + // from the set so we only call finishCompleter() once + if (delayed.contains(qm.position)) + delayed -= qm.position; + else + return; + } + QPID_LOG(trace, logPrefix << "Completed " << qm); + qm.payload->getIngressCompletion().finishCompleter(); +} + +}} // namespaces qpid::ha diff --git a/cpp/src/qpid/ha/QueueGuard.h b/cpp/src/qpid/ha/QueueGuard.h new file mode 100644 index 0000000000..bc8f40b65f --- /dev/null +++ b/cpp/src/qpid/ha/QueueGuard.h @@ -0,0 +1,118 @@ +#ifndef QPID_HA_QUEUEGUARD_H +#define QPID_HA_QUEUEGUARD_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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 "QueueRange.h" +#include "qpid/framing/SequenceNumber.h" +#include "qpid/framing/SequenceSet.h" +#include "qpid/types/Uuid.h" +#include "qpid/sys/Mutex.h" +#include <boost/shared_ptr.hpp> +#include <deque> +#include <set> + +namespace qpid { +namespace broker { +class Queue; +struct QueuedMessage; +class Message; +} + +namespace ha { +class BrokerInfo; +class ReplicatingSubscription; + +/** + * A queue guard is a QueueObserver that delays completion of new messages + * arriving on a queue. It works as part of a ReplicatingSubscription to ensure + * messages are not acknowledged till they have been replicated. + * + * The guard can be created before the ReplicatingSubscription to protect + * messages arriving before the creation of the subscription. + * + * THREAD SAFE: Concurrent calls: + * - enqueued() via QueueObserver in arbitrary connection threads. + * - attach(), cancel(), complete() from ReplicatingSubscription in subscription thread. + */ +class QueueGuard { + public: + QueueGuard(broker::Queue& q, const BrokerInfo&); + ~QueueGuard(); + + /** QueueObserver override. Delay completion of the message. + * NOTE: Called under the queues message lock. + */ + void enqueued(const broker::QueuedMessage&); + + /** QueueObserver override: Complete a delayed message. + * NOTE: Called under the queues message lock. + */ + void dequeued(const broker::QueuedMessage&); + + /** Complete a delayed message. */ + void complete(const broker::QueuedMessage&); + + /** Complete all delayed messages. */ + void cancel(); + + void attach(ReplicatingSubscription&); + + /** + * Return the un-guarded queue range at the time the QueueGuard was created. + * + * The first position guaranteed to be protected by the guard is + * getRange().getBack()+1. It is possible that the guard has protected some + * messages before that point. Any such messages are dealt with in subscriptionStart + * + * The QueueGuard is created in 3 situations + * - when a backup is promoted, guards are created for expected backups. + * - when a new queue is created on the primary + * - when a new backup joins. + * + * In the last situation the queue is active while the guard is being + * created. + * + */ + const QueueRange& getRange() const { return range; } // range is immutable, no lock needed. + + /** Inform the guard of the stating position for the attached subscription. + * Complete messages that will not be seen by the subscription. + *@return true if the subscription has already advanced to a guarded position. + */ + bool subscriptionStart(framing::SequenceNumber position); + + private: + class QueueObserver; + + sys::Mutex lock; + std::string logPrefix; + broker::Queue& queue; + framing::SequenceSet delayed; + ReplicatingSubscription* subscription; + boost::shared_ptr<QueueObserver> observer; + QueueRange range; +}; +}} // namespace qpid::ha + +#endif /*!QPID_HA_QUEUEGUARD_H*/ diff --git a/cpp/src/qpid/ha/QueueRange.h b/cpp/src/qpid/ha/QueueRange.h new file mode 100644 index 0000000000..d734326910 --- /dev/null +++ b/cpp/src/qpid/ha/QueueRange.h @@ -0,0 +1,85 @@ +#ifndef QPID_HA_QUEUERANGE_H +#define QPID_HA_QUEUERANGE_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "ReplicatingSubscription.h" +#include "qpid/broker/Queue.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/framing/SequenceNumber.h" +#include <iostream> + +namespace qpid { +namespace ha { + +/** + * Get the front/back range of a queue or from a ReplicatingSubscription arguments table. + * + * The *back* of the queue is the position of the latest (most recently pushed) + * message on the queue or, if the queue is empty, the back is n-1 where n is + * the position that will be assigned to the next message pushed onto the queue. + * + * The *front* of the queue is the position of the oldest (next to be consumed) message + * on the queue or, if the queue is empty, it is the position that will be occupied + * by the next message pushed onto the queue. + * + * This leads to the slightly surprising conclusion that for an empty queue + * front = back+1 + */ +struct QueueRange { + public: + framing::SequenceNumber front, back; + + QueueRange() : front(1), back(0) { } // Empty range. + + QueueRange(broker::Queue& q) { + if (ReplicatingSubscription::getFront(q, front)) + back = q.getPosition(); + else { + back = q.getPosition(); + front = back+1; // empty + } + assert(front <= back + 1); + } + + QueueRange(const framing::FieldTable& args) { + back = args.getAsInt(ReplicatingSubscription::QPID_BACK); + front = back+1; + if (args.isSet(ReplicatingSubscription::QPID_FRONT)) + front = args.getAsInt(ReplicatingSubscription::QPID_FRONT); + if (back+1 < front) + throw Exception(QPID_MSG("Invalid range [" << front << "," << back <<"]")); + } + + bool empty() const { return front == back+1; } +}; + + +inline std::ostream& operator<<(std::ostream& o, const QueueRange& qr) { + if (qr.front > qr.back) return o << "[-," << qr.back << "]"; + else return o << "[" << qr.front << "," << qr.back << "]"; +} + + +}} // namespace qpid::ha + +#endif /*!QPID_HA_QUEUERANGE_H*/ diff --git a/cpp/src/qpid/ha/QueueReplicator.cpp b/cpp/src/qpid/ha/QueueReplicator.cpp index 633619be13..be910a087f 100644 --- a/cpp/src/qpid/ha/QueueReplicator.cpp +++ b/cpp/src/qpid/ha/QueueReplicator.cpp @@ -19,6 +19,7 @@ * */ +#include "HaBroker.h" #include "QueueReplicator.h" #include "ReplicatingSubscription.h" #include "qpid/broker/Bridge.h" @@ -44,25 +45,39 @@ namespace ha { using namespace broker; using namespace framing; -const std::string QueueReplicator::DEQUEUE_EVENT_KEY("qpid.dequeue-event"); -const std::string QueueReplicator::POSITION_EVENT_KEY("qpid.position-event"); +const std::string QPID_HA_EVENT_PREFIX("qpid.ha-event:"); +const std::string QueueReplicator::DEQUEUE_EVENT_KEY(QPID_HA_EVENT_PREFIX+"dequeue"); +const std::string QueueReplicator::POSITION_EVENT_KEY(QPID_HA_EVENT_PREFIX+"position"); std::string QueueReplicator::replicatorName(const std::string& queueName) { return QPID_REPLICATOR_ + queueName; } -QueueReplicator::QueueReplicator(boost::shared_ptr<Queue> q, boost::shared_ptr<Link> l) - : Exchange(replicatorName(q->getName()), 0, q->getBroker()), queue(q), link(l) +bool QueueReplicator::isEventKey(const std::string key) { + const std::string& prefix = QPID_HA_EVENT_PREFIX; + bool ret = key.size() > prefix.size() && key.compare(0, prefix.size(), prefix) == 0; + return ret; +} + +QueueReplicator::QueueReplicator(HaBroker& hb, + boost::shared_ptr<Queue> q, + boost::shared_ptr<Link> l) + : Exchange(replicatorName(q->getName()), 0, q->getBroker()), + haBroker(hb), + logPrefix("Backup queue "+q->getName()+": "), + queue(q), link(l), brokerInfo(hb.getBrokerInfo()) { - logPrefix = "HA: Backup of " + queue->getName() + ": "; - QPID_LOG(info, logPrefix << "Created"); + Uuid uuid(true); + bridgeName = replicatorName(q->getName()) + std::string(".") + uuid.str(); } // This must be separate from the constructor so we can call shared_from_this. void QueueReplicator::activate() { - // Note this may create a new bridge or use an existing one. + sys::Mutex::ScopedLock l(lock); + std::pair<Bridge::shared_ptr, bool> result = queue->getBroker()->getLinks().declare( - link->getHost(), link->getPort(), + bridgeName, + *link, false, // durable queue->getName(), // src getName(), // dest @@ -77,43 +92,47 @@ void QueueReplicator::activate() { // before initializeBridge is called. boost::bind(&QueueReplicator::initializeBridge, shared_from_this(), _1, _2) ); + bridge = result.first; } -QueueReplicator::~QueueReplicator() {} +QueueReplicator::~QueueReplicator() { deactivate(); } void QueueReplicator::deactivate() { + // destroy the route sys::Mutex::ScopedLock l(lock); - queue->getBroker()->getLinks().destroy( - link->getHost(), link->getPort(), queue->getName(), getName(), string()); - QPID_LOG(debug, logPrefix << "Deactivated bridge " << bridgeName); + if (bridge) { + bridge->close(); + bridge.reset(); + QPID_LOG(debug, logPrefix << "Deactivated bridge " << bridgeName); + } } // Called in a broker connection thread when the bridge is created. void QueueReplicator::initializeBridge(Bridge& bridge, SessionHandler& sessionHandler) { sys::Mutex::ScopedLock l(lock); - bridgeName = bridge.getName(); - framing::AMQP_ServerProxy peer(sessionHandler.out); + AMQP_ServerProxy peer(sessionHandler.out); const qmf::org::apache::qpid::broker::ArgsLinkBridge& args(bridge.getArgs()); - framing::FieldTable settings; - - // FIXME aconway 2011-12-09: Failover optimization removed. - // There was code here to re-use messages already on the backup - // during fail-over. This optimization was removed to simplify - // the logic till we get the basic replication stable, it - // can be re-introduced later. Last revision with the optimization: - // r1213258 | QPID-3603: Fix QueueReplicator subscription parameters. - - // Clear out any old messages, reset the queue to start replicating fresh. - queue->purge(); - queue->setPosition(0); - + FieldTable settings; settings.setInt(ReplicatingSubscription::QPID_REPLICATING_SUBSCRIPTION, 1); - // TODO aconway 2011-12-19: optimize. - settings.setInt(QPID_SYNC_FREQUENCY, 1); - peer.getMessage().subscribe(args.i_src, args.i_dest, 0/*accept-explicit*/, 1/*not-acquired*/, false/*exclusive*/, "", 0, settings); + settings.setInt(QPID_SYNC_FREQUENCY, 1); // FIXME aconway 2012-05-22: optimize? + settings.setInt(ReplicatingSubscription::QPID_BACK, + queue->getPosition()); + settings.setTable(ReplicatingSubscription::QPID_BROKER_INFO, + brokerInfo.asFieldTable()); + SequenceNumber front; + if (ReplicatingSubscription::getFront(*queue, front)) + settings.setInt(ReplicatingSubscription::QPID_FRONT, front); + peer.getMessage().subscribe( + args.i_src, args.i_dest, 0/*accept-explicit*/, 1/*not-acquired*/, + false/*exclusive*/, "", 0, settings); + // FIXME aconway 2012-05-22: use a finite credit window? peer.getMessage().flow(getName(), 0, 0xFFFFFFFF); peer.getMessage().flow(getName(), 1, 0xFFFFFFFF); - QPID_LOG(debug, logPrefix << "Activated bridge " << bridgeName); + + qpid::Address primary; + link->getRemoteAddress(primary); + QPID_LOG(info, logPrefix << "Connected to " << primary << "(" << bridgeName << ")"); + QPID_LOG(trace, logPrefix << "Subscription settings: " << settings); } namespace { @@ -127,13 +146,11 @@ template <class T> T decodeContent(Message& m) { } } -void QueueReplicator::dequeue(SequenceNumber n, const sys::Mutex::ScopedLock&) { +void QueueReplicator::dequeue(SequenceNumber n, sys::Mutex::ScopedLock&) { // Thread safe: only calls thread safe Queue functions. - if (queue->getPosition() >= n) { // Ignore messages we haven't reached yet - QueuedMessage message; - if (queue->acquireMessageAt(n, message)) - queue->dequeue(0, message); - } + QueuedMessage message; + if (queue->acquireMessageAt(n, message)) + queue->dequeue(0, message); } // Called in connection thread of the queues bridge to primary. @@ -142,29 +159,33 @@ void QueueReplicator::route(Deliverable& msg) try { const std::string& key = msg.getMessage().getRoutingKey(); sys::Mutex::ScopedLock l(lock); - if (key == DEQUEUE_EVENT_KEY) { + if (!isEventKey(key)) { + msg.deliverTo(queue); + // We are on a backup so the queue is not modified except via this. + QPID_LOG(trace, logPrefix << "Enqueued message " << queue->getPosition()); + } + else if (key == DEQUEUE_EVENT_KEY) { SequenceSet dequeues = decodeContent<SequenceSet>(msg.getMessage()); QPID_LOG(trace, logPrefix << "Dequeue: " << dequeues); //TODO: should be able to optimise the following for (SequenceSet::iterator i = dequeues.begin(); i != dequeues.end(); i++) dequeue(*i, l); - } else if (key == POSITION_EVENT_KEY) { + } + else if (key == POSITION_EVENT_KEY) { SequenceNumber position = decodeContent<SequenceNumber>(msg.getMessage()); QPID_LOG(trace, logPrefix << "Position moved from " << queue->getPosition() << " to " << position); - if (queue->getPosition() > position) { - throw Exception( - QPID_MSG(logPrefix << "Invalid position update from " - << queue->getPosition() << " to " << position)); - } + // Verify that there are no messages after the new position in the queue. + SequenceNumber next; + if (ReplicatingSubscription::getNext(*queue, position, next)) + throw Exception("Invalid position move, preceeds messages"); queue->setPosition(position); - } else { - msg.deliverTo(queue); - QPID_LOG(trace, logPrefix << "Enqueued message " << queue->getPosition()); } + // Ignore unknown event keys, may be introduced in later versions. } catch (const std::exception& e) { QPID_LOG(critical, logPrefix << "Replication failed: " << e.what()); + haBroker.shutdown(); throw; } } diff --git a/cpp/src/qpid/ha/QueueReplicator.h b/cpp/src/qpid/ha/QueueReplicator.h index bcbac988fa..f8a68ea38f 100644 --- a/cpp/src/qpid/ha/QueueReplicator.h +++ b/cpp/src/qpid/ha/QueueReplicator.h @@ -21,6 +21,8 @@ * under the License. * */ + +#include "BrokerInfo.h" #include "qpid/broker/Exchange.h" #include "qpid/framing/SequenceSet.h" #include <boost/enable_shared_from_this.hpp> @@ -38,6 +40,7 @@ class Deliverable; } namespace ha { +class HaBroker; /** * Exchange created on a backup broker to replicate a queue on the primary. @@ -55,8 +58,13 @@ class QueueReplicator : public broker::Exchange, static const std::string DEQUEUE_EVENT_KEY; static const std::string POSITION_EVENT_KEY; static std::string replicatorName(const std::string& queueName); + /** Test if a string is an event key */ + static bool isEventKey(const std::string key); + + QueueReplicator(HaBroker&, + boost::shared_ptr<broker::Queue> q, + boost::shared_ptr<broker::Link> l); - QueueReplicator(boost::shared_ptr<broker::Queue> q, boost::shared_ptr<broker::Link> l); ~QueueReplicator(); void activate(); // Call after ctor @@ -71,13 +79,16 @@ class QueueReplicator : public broker::Exchange, private: void initializeBridge(broker::Bridge& bridge, broker::SessionHandler& sessionHandler); - void dequeue(framing::SequenceNumber, const sys::Mutex::ScopedLock&); + void dequeue(framing::SequenceNumber, sys::Mutex::ScopedLock&); + HaBroker& haBroker; std::string logPrefix; std::string bridgeName; sys::Mutex lock; boost::shared_ptr<broker::Queue> queue; boost::shared_ptr<broker::Link> link; + boost::shared_ptr<broker::Bridge> bridge; + BrokerInfo brokerInfo; }; }} // namespace qpid::ha diff --git a/cpp/src/qpid/ha/RemoteBackup.cpp b/cpp/src/qpid/ha/RemoteBackup.cpp new file mode 100644 index 0000000000..a5693fd14e --- /dev/null +++ b/cpp/src/qpid/ha/RemoteBackup.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 "RemoteBackup.h" +#include "QueueGuard.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/QueueRegistry.h" +#include <boost/bind.hpp> + +namespace qpid { +namespace ha { + +using sys::Mutex; +using boost::bind; + +RemoteBackup::RemoteBackup(const BrokerInfo& info, ReplicationTest rt, bool con) : + logPrefix("Primary remote backup "+info.getLogId()+": "), + brokerInfo(info), replicationTest(rt), connected(con), reportedReady(false) +{} + +void RemoteBackup::setInitialQueues(broker::QueueRegistry& queues, bool createGuards) +{ + QPID_LOG(debug, logPrefix << "Setting initial queues" << (createGuards ? " and guards" : "")); + queues.eachQueue(boost::bind(&RemoteBackup::initialQueue, this, _1, createGuards)); +} + +RemoteBackup::~RemoteBackup() { cancel(); } + +void RemoteBackup::cancel() { + for (GuardMap::iterator i = guards.begin(); i != guards.end(); ++i) + i->second->cancel(); + guards.clear(); +} + +bool RemoteBackup::isReady() { + return connected && initialQueues.empty(); +} + +void RemoteBackup::initialQueue(const QueuePtr& q, bool createGuard) { + if (replicationTest.isReplicated(ALL, *q)) { + initialQueues.insert(q); + if (createGuard) guards[q].reset(new QueueGuard(*q, brokerInfo)); + } +} + +RemoteBackup::GuardPtr RemoteBackup::guard(const QueuePtr& q) { + GuardMap::iterator i = guards.find(q); + GuardPtr guard; + if (i != guards.end()) { + guard = i->second; + guards.erase(i); + } + return guard; +} + +namespace { +typedef std::set<boost::shared_ptr<broker::Queue> > QS; +struct QueueSetPrinter { + const QS& qs; + std::string prefix; + QueueSetPrinter(const std::string& p, const QS& q) : qs(q), prefix(p) {} +}; +std::ostream& operator<<(std::ostream& o, const QueueSetPrinter& qp) { + if (!qp.qs.empty()) o << qp.prefix; + for (QS::const_iterator i = qp.qs.begin(); i != qp.qs.end(); ++i) + o << (*i)->getName() << " "; + return o; +} +} + +void RemoteBackup::ready(const QueuePtr& q) { + initialQueues.erase(q); + QPID_LOG(debug, logPrefix << "Queue ready: " << q->getName() + << QueueSetPrinter(", waiting for: ", initialQueues)); + if (isReady()) QPID_LOG(debug, logPrefix << "All queues ready"); +} + +// Called via ConfigurationObserver::queueCreate and from initialQueue +void RemoteBackup::queueCreate(const QueuePtr& q) { + if (replicationTest.isReplicated(ALL, *q)) + guards[q].reset(new QueueGuard(*q, brokerInfo)); +} + +// Called via ConfigurationObserver +void RemoteBackup::queueDestroy(const QueuePtr& q) { + initialQueues.erase(q); + GuardMap::iterator i = guards.find(q); + if (i != guards.end()) { + i->second->cancel(); + guards.erase(i); + } +} + +bool RemoteBackup::reportReady() { + if (!reportedReady && isReady()) { + reportedReady = true; + return true; + } + return false; +} + +}} // namespace qpid::ha diff --git a/cpp/src/qpid/ha/RemoteBackup.h b/cpp/src/qpid/ha/RemoteBackup.h new file mode 100644 index 0000000000..8ee776e90b --- /dev/null +++ b/cpp/src/qpid/ha/RemoteBackup.h @@ -0,0 +1,111 @@ +#ifndef QPID_HA_REMOTEBACKUP_H +#define QPID_HA_REMOTEBACKUP_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "ReplicationTest.h" +#include "BrokerInfo.h" +#include "types.h" +#include <set> +#include <map> + +namespace qpid { + +namespace broker { +class Queue; +class QueueRegistry; +} + +namespace ha { +class QueueGuard; + +/** + * Track readiness for a remote broker. + * Creates queue guards on behalf of the remote broker to keep + * queues safe till the ReplicatingSubscription is ready. + * + * THREAD UNSAFE: Caller must serialize. + */ +class RemoteBackup +{ + public: + typedef boost::shared_ptr<QueueGuard> GuardPtr; + typedef boost::shared_ptr<broker::Queue> QueuePtr; + + /** Note: isReady() can be true after construction + *@param connected true if the backup is already connected. + */ + RemoteBackup(const BrokerInfo& info, ReplicationTest, bool connected); + ~RemoteBackup(); + + /** Set the initial queues for all queues in the registry. + *@createGuards if true create guards also, if false guards will be created on demand. + */ + void setInitialQueues(broker::QueueRegistry&, bool createGuards); + + /** Return guard associated with a queue. Used to create ReplicatingSubscription. */ + GuardPtr guard(const QueuePtr&); + + /** Is the remote backup connected? */ + void setConnected(bool b) { connected=b; } + bool isConnected() const { return connected; } + + /** ReplicatingSubscription associated with queue is ready. + * Note: may set isReady() + */ + void ready(const QueuePtr& queue); + + /** Called via ConfigurationObserver */ + void queueCreate(const QueuePtr&); + + /** Called via ConfigurationObserver. Note: may set isReady() */ + void queueDestroy(const QueuePtr&); + + /**@return true when all initial queues for this backup are ready. */ + bool isReady(); + + /**@return true if isReady() and this is the first call to reportReady */ + bool reportReady(); + + /**Cancel all queue guards, called if we are timed out. */ + void cancel(); + + BrokerInfo getBrokerInfo() const { return brokerInfo; } + private: + typedef std::map<QueuePtr, GuardPtr> GuardMap; + typedef std::set<QueuePtr> QueueSet; + + /** Add queue to guard as an initial queue */ + void initialQueue(const QueuePtr&, bool createGuard); + + std::string logPrefix; + BrokerInfo brokerInfo; + ReplicationTest replicationTest; + GuardMap guards; + QueueSet initialQueues; + bool connected; + bool reportedReady; +}; + +}} // namespace qpid::ha + +#endif /*!QPID_HA_REMOTEBACKUP_H*/ diff --git a/cpp/src/qpid/ha/ReplicateLevel.cpp b/cpp/src/qpid/ha/ReplicateLevel.cpp deleted file mode 100644 index 4981577225..0000000000 --- a/cpp/src/qpid/ha/ReplicateLevel.cpp +++ /dev/null @@ -1,72 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -#include "ReplicateLevel.h" -#include "qpid/Exception.h" -#include "qpid/Msg.h" -#include <iostream> -#include <assert.h> - -namespace qpid { -namespace ha { - -using namespace std; - -// Note replicateLevel is called during plugin-initialization which -// happens in the static construction phase so these constants need -// to be POD, they can't be class objects -// -namespace { -const char* S_NONE="none"; -const char* S_CONFIGURATION="configuration"; -const char* S_ALL="all"; -} - -bool replicateLevel(const string& level, ReplicateLevel& out) { - if (level == S_NONE) { out = RL_NONE; return true; } - if (level == S_CONFIGURATION) { out = RL_CONFIGURATION; return true; } - if (level == S_ALL) { out = RL_ALL; return true; } - return false; -} - -ReplicateLevel replicateLevel(const string& level) { - ReplicateLevel rl; - if (!replicateLevel(level, rl)) - throw Exception("Invalid value for replication level: "+level); - return rl; -} - -string str(ReplicateLevel l) { - const char* names[] = { S_NONE, S_CONFIGURATION, S_ALL }; - if (l > RL_ALL) - throw Exception(QPID_MSG("Invalid value for replication level: " << l)); - return names[l]; -} - -ostream& operator<<(ostream& o, ReplicateLevel rl) { return o << str(rl); } - -istream& operator>>(istream& i, ReplicateLevel& rl) { - string str; - i >> str; - rl = replicateLevel(str); - return i; -} - -}} // namespace qpid::ha diff --git a/cpp/src/qpid/ha/ReplicateLevel.h b/cpp/src/qpid/ha/ReplicateLevel.h deleted file mode 100644 index c11e03f0ce..0000000000 --- a/cpp/src/qpid/ha/ReplicateLevel.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef QPID_HA_REPLICATELEVEL_H -#define QPID_HA_REPLICATELEVEL_H - -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES 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 <iosfwd> - -namespace qpid { -namespace ha { - -enum ReplicateLevel { RL_NONE, RL_CONFIGURATION, RL_ALL }; - -/** - * If str is a valid replicate level, set out and return true. - */ -bool replicateLevel(const std::string& str, ReplicateLevel& out); - -/** - *@return enum corresponding to string level. - *@throw qpid::Exception if level is not a valid replication level. - */ -ReplicateLevel replicateLevel(const std::string& level); - -/**@return string form of replicate level */ -std::string str(ReplicateLevel l); - -std::ostream& operator<<(std::ostream&, ReplicateLevel); -std::istream& operator>>(std::istream&, ReplicateLevel&); - -}} // namespaces qpid::ha - -#endif /*!QPID_HA_REPLICATELEVEL_H*/ diff --git a/cpp/src/qpid/ha/ReplicatingSubscription.cpp b/cpp/src/qpid/ha/ReplicatingSubscription.cpp index 91a4538bc4..c960758eaf 100644 --- a/cpp/src/qpid/ha/ReplicatingSubscription.cpp +++ b/cpp/src/qpid/ha/ReplicatingSubscription.cpp @@ -19,13 +19,18 @@ * */ +#include "QueueGuard.h" +#include "QueueRange.h" +#include "QueueReplicator.h" #include "ReplicatingSubscription.h" +#include "Primary.h" #include "qpid/broker/Queue.h" #include "qpid/broker/SessionContext.h" #include "qpid/broker/ConnectionState.h" #include "qpid/framing/AMQFrame.h" #include "qpid/framing/MessageTransferBody.h" #include "qpid/log/Statement.h" +#include "qpid/types/Uuid.h" #include <sstream> namespace qpid { @@ -34,19 +39,90 @@ namespace ha { using namespace framing; using namespace broker; using namespace std; +using sys::Mutex; -const string ReplicatingSubscription::QPID_REPLICATING_SUBSCRIPTION("qpid.replicating-subscription"); +const string ReplicatingSubscription::QPID_REPLICATING_SUBSCRIPTION("qpid.ha-replicating-subscription"); +const string ReplicatingSubscription::QPID_BACK("qpid.ha-back"); +const string ReplicatingSubscription::QPID_FRONT("qpid.ha-front"); +const string ReplicatingSubscription::QPID_BROKER_INFO("qpid.ha-broker-info"); namespace { const string DOLLAR("$"); const string INTERNAL("-internal"); } // namespace +// Scan the queue for gaps and add them to the subscriptions dequed set. +class DequeueScanner +{ + public: + DequeueScanner( + ReplicatingSubscription& rs, + SequenceNumber front_, + SequenceNumber back_ // Inclusive + ) : subscription(rs), front(front_), back(back_) + { + assert(front <= back); + // INVARIANT deques have been added for positions <= at. + at = front - 1; + } + + void operator()(const QueuedMessage& qm) { + if (qm.position >= front && qm.position <= back) { + if (qm.position > at+1) subscription.dequeued(at+1, qm.position-1); + at = qm.position; + } + } + + // Must call after scanning the queue. + void finish() { + if (at < back) subscription.dequeued(at+1, back); + } + + private: + ReplicatingSubscription& subscription; + SequenceNumber front; + SequenceNumber back; + SequenceNumber at; +}; + string mask(const string& in) { return DOLLAR + in + INTERNAL; } + +/** Dummy consumer used to get the front position on the queue */ +class GetPositionConsumer : public Consumer +{ + public: + GetPositionConsumer() : + Consumer("ha.GetPositionConsumer."+types::Uuid(true).str(), false) {} + bool deliver(broker::QueuedMessage& ) { return true; } + void notify() {} + bool filter(boost::intrusive_ptr<broker::Message>) { return true; } + bool accept(boost::intrusive_ptr<broker::Message>) { return true; } + void cancel() {} + void acknowledged(const broker::QueuedMessage&) {} + bool browseAcquired() const { return true; } + broker::OwnershipToken* getSession() { return 0; } +}; + + +bool ReplicatingSubscription::getNext( + broker::Queue& q, SequenceNumber from, SequenceNumber& result) +{ + boost::shared_ptr<Consumer> c(new GetPositionConsumer); + c->setPosition(from); + if (!q.dispatch(c)) return false; + result = c->getPosition(); + return true; +} + +bool ReplicatingSubscription::getFront(broker::Queue& q, SequenceNumber& front) { + // FIXME aconway 2012-05-23: won't wrap, assumes 0 is < all messages in queue. + return getNext(q, 0, front); +} + /* Called by SemanticState::consume to create a consumer */ boost::shared_ptr<broker::SemanticState::ConsumerImpl> ReplicatingSubscription::Factory::create( @@ -66,7 +142,7 @@ ReplicatingSubscription::Factory::create( rs.reset(new ReplicatingSubscription( parent, name, queue, ack, acquire, exclusive, tag, resumeId, resumeTtl, arguments)); - queue->addObserver(rs); + rs->initialize(); } return rs; } @@ -84,179 +160,223 @@ ReplicatingSubscription::ReplicatingSubscription( const framing::FieldTable& arguments ) : ConsumerImpl(parent, name, queue, ack, acquire, exclusive, tag, resumeId, resumeTtl, arguments), - events(new Queue(mask(name))), - consumer(new DelegatingConsumer(*this)) + dummy(new Queue(mask(name))), + ready(false) { - // Separate the remote part from a "local-remote" address. - string address = parent->getSession().getConnection().getUrl(); - size_t i = address.find('-'); - if (i != string::npos) address = address.substr(i+1); - logPrefix = "HA: Primary "; - stringstream ss; - logSuffix = " (" + address + ")"; - - // FIXME aconway 2011-12-09: Failover optimization removed. - // There was code here to re-use messages already on the backup - // during fail-over. This optimization was removed to simplify - // the logic till we get the basic replication stable, it - // can be re-introduced later. Last revision with the optimization: - // r1213258 | QPID-3603: Fix QueueReplicator subscription parameters. - - QPID_LOG(debug, logPrefix << "created backup subscription " << getName() << logSuffix); - - // FIXME aconway 2011-12-15: ConsumerImpl::position is left at 0 - // so we will start consuming from the lowest numbered message. - // This is incorrect if the sequence number wraps around, but - // this is what all consumers currently do. -} - -// Message is delivered in the subscription's connection thread. -bool ReplicatingSubscription::deliver(QueuedMessage& m) { try { - // Add position events for the subscribed queue, not for the internal event queue. - if (m.queue && m.queue == getQueue().get()) { - sys::Mutex::ScopedLock l(lock); - if (position != m.position) - throw Exception( - QPID_MSG("Expected position " << position - << " but got " << m.position)); - // m.position is the position of the newly enqueued m on the local queue. - // backupPosition is latest position on the backup queue (before enqueueing m.) - if (m.position <= backupPosition) - throw Exception( - QPID_MSG("Expected position > " << backupPosition - << " but got " << m.position)); - - if (m.position - backupPosition > 1) { - // Position has advanced because of messages dequeued ahead of us. - SequenceNumber send(m.position); - --send; // Send the position before m was enqueued. - sendPositionEvent(send, l); + FieldTable ft; + if (!arguments.getTable(ReplicatingSubscription::QPID_BROKER_INFO, ft)) + throw Exception("Replicating subscription does not have broker info: " + tag); + info.assign(ft); + + // Set a log prefix message that identifies the remote broker. + ostringstream os; + os << "Primary " << queue->getName() << "@" << info.getLogId() << ": "; + logPrefix = os.str(); + + // NOTE: Once the guard is attached we can have concurrent + // calls to dequeued so we need to lock use of this->dequeues. + // + // However we must attach the guard _before_ we scan for + // initial dequeues to be sure we don't miss any dequeues + // between the scan and attaching the guard. + if (Primary::get()) guard = Primary::get()->getGuard(queue, info); + if (!guard) guard.reset(new QueueGuard(*queue, info)); + guard->attach(*this); + + QueueRange backup(arguments); // Remote backup range. + QueueRange primary(guard->getRange()); // Unguarded range when the guard was set. + backupPosition = backup.back; + + // Sync backup and primary queues, don't send messages already on the backup + + if (backup.front > primary.front || // Missing messages at front + backup.back < primary.front || // No overlap + primary.empty() || backup.empty()) // Empty + { + // No useful overlap - erase backup and start from the beginning + if (!backup.empty()) dequeued(backup.front, backup.back); + position = primary.front-1; + } + else { // backup and primary do overlap. + // Remove messages from backup that are not in primary. + if (primary.back < backup.back) { + dequeued(primary.back+1, backup.back); // Trim excess messages at back + backup.back = primary.back; } - backupPosition = m.position; - QPID_LOG(trace, logPrefix << "replicating " << m << logSuffix); + if (backup.front < primary.front) { + dequeued(backup.front, primary.front-1); // Trim excess messages at front + backup.front = primary.front; + } + DequeueScanner scan(*this, backup.front, backup.back); + // FIXME aconway 2012-06-15: Optimize queue traversal, only in range. + queue->eachMessage(boost::ref(scan)); // Remove missing messages in between. + scan.finish(); + position = backup.back; } - return ConsumerImpl::deliver(m); - } catch (const std::exception& e) { - QPID_LOG(critical, logPrefix << "error replicating " << getQueue()->getName() - << logSuffix << ": " << e.what()); + // NOTE: we are assuming that the messages that are on the backup are + // consistent with those on the primary. If the backup is a replica + // queue and hasn't been tampered with then that will be the case. + + QPID_LOG(debug, logPrefix << "Subscribed:" + << " backup:" << backup + << " primary:" << primary + << " catch-up: " << position << "-" << primary.back + << "(" << primary.back-position << ")"); + + // Check if we are ready yet. + if (guard->subscriptionStart(position)) setReady(); + } + catch (const std::exception& e) { + QPID_LOG(error, logPrefix << "Creation error: " << e.what() + << ": arguments=" << getArguments()); throw; } } -ReplicatingSubscription::~ReplicatingSubscription() {} - +ReplicatingSubscription::~ReplicatingSubscription() { + QPID_LOG(debug, logPrefix << "Detroyed replicating subscription"); +} -// INVARIANT: delayed contains msg <=> we have outstanding startCompletion on msg +// Called in subscription's connection thread when the subscription is created. +// Called separate from ctor because sending events requires +// shared_from_this +// +void ReplicatingSubscription::initialize() { + try { + Mutex::ScopedLock l(lock); // Note dequeued() can be called concurrently. -// Mark a message completed. May be called by acknowledge or dequeued -void ReplicatingSubscription::complete( - const QueuedMessage& qm, const sys::Mutex::ScopedLock&) -{ - // Handle completions for the subscribed queue, not the internal event queue. - if (qm.queue && qm.queue == getQueue().get()) { - QPID_LOG(trace, logPrefix << "completed " << qm << logSuffix); - Delayed::iterator i= delayed.find(qm.position); - // The same message can be completed twice, by acknowledged and - // dequeued, remove it from the set so it only gets completed - // once. - if (i != delayed.end()) { - assert(i->second.payload == qm.payload); - qm.payload->getIngressCompletion().finishCompleter(); - delayed.erase(i); - } + // Send initial dequeues and position to the backup. + // There must be a shared_ptr(this) when sending. + sendDequeueEvent(l); + sendPositionEvent(position, l); + backupPosition = position; + } + catch (const std::exception& e) { + QPID_LOG(error, logPrefix << "Initialization error: " << e.what() + << ": arguments=" << getArguments()); + throw; } } -// Called before we get notified of the message being available and -// under the message lock in the queue. Called in arbitrary connection thread. -void ReplicatingSubscription::enqueued(const QueuedMessage& qm) { - sys::Mutex::ScopedLock l(lock); - // Delay completion - QPID_LOG(trace, logPrefix << "delaying completion of " << qm << logSuffix); - qm.payload->getIngressCompletion().startCompleter(); - assert(delayed.find(qm.position) == delayed.end()); - delayed[qm.position] = qm; +// Message is delivered in the subscription's connection thread. +bool ReplicatingSubscription::deliver(QueuedMessage& qm) { + try { + // Add position events for the subscribed queue, not the internal event queue. + if (qm.queue == getQueue().get()) { + QPID_LOG(trace, logPrefix << "Replicating " << qm); + { + Mutex::ScopedLock l(lock); + assert(position == qm.position); + // qm.position is the position of the newly enqueued qm on local queue. + // backupPosition is latest position on backup queue before enqueueing + if (qm.position <= backupPosition) + throw Exception( + QPID_MSG("Expected position > " << backupPosition + << " but got " << qm.position)); + if (qm.position - backupPosition > 1) { + // Position has advanced because of messages dequeued ahead of us. + // Send the position before qm was enqueued. + sendPositionEvent(qm.position-1, l); + } + // Backup will automatically advance by 1 on delivery of message. + backupPosition = qm.position; + } + } + return ConsumerImpl::deliver(qm); + } catch (const std::exception& e) { + QPID_LOG(critical, logPrefix << "Error replicating " << qm + << ": " << e.what()); + throw; + } } - -// Function to complete a delayed message, called by cancel() -void ReplicatingSubscription::cancelComplete( - const Delayed::value_type& v, const sys::Mutex::ScopedLock&) -{ - QPID_LOG(trace, logPrefix << "cancel completed " << v.second << logSuffix); - v.second.payload->getIngressCompletion().finishCompleter(); +void ReplicatingSubscription::setReady() { + { + Mutex::ScopedLock l(lock); + if (ready) return; + ready = true; + } + // Notify Primary that a subscription is ready. + QPID_LOG(debug, logPrefix << "Caught up"); + if (Primary::get()) Primary::get()->readyReplica(*this); } // Called in the subscription's connection thread. void ReplicatingSubscription::cancel() { - getQueue()->removeObserver( - boost::dynamic_pointer_cast<QueueObserver>(shared_from_this())); - { - sys::Mutex::ScopedLock l(lock); - QPID_LOG(debug, logPrefix << "cancel backup subscription " << getName() << logSuffix); - for_each(delayed.begin(), delayed.end(), - boost::bind(&ReplicatingSubscription::cancelComplete, this, _1, boost::ref(l))); - delayed.clear(); - } + guard->cancel(); ConsumerImpl::cancel(); } -// Called on primary in the backups IO thread. -void ReplicatingSubscription::acknowledged(const QueuedMessage& msg) { - sys::Mutex::ScopedLock l(lock); - // Finish completion of message, it has been acknowledged by the backup. - complete(msg, l); +// Consumer override, called on primary in the backup's IO thread. +void ReplicatingSubscription::acknowledged(const QueuedMessage& qm) { + if (qm.queue == getQueue().get()) { // Don't complete messages on the internal queue + // Finish completion of message, it has been acknowledged by the backup. + QPID_LOG(trace, logPrefix << "Acknowledged " << qm); + guard->complete(qm); + // If next message is protected by the guard then we are ready + if (qm.position >= guard->getRange().back) setReady(); + } + ConsumerImpl::acknowledged(qm); } -// Hide the "queue deleted" error for a ReplicatingSubscription when a -// queue is deleted, this is normal and not an error. -bool ReplicatingSubscription::hideDeletedError() { return true; } - // Called with lock held. Called in subscription's connection thread. -void ReplicatingSubscription::sendDequeueEvent(const sys::Mutex::ScopedLock& l) +void ReplicatingSubscription::sendDequeueEvent(Mutex::ScopedLock&) { - QPID_LOG(trace, logPrefix << "sending dequeues " << dequeues - << " from " << getQueue()->getName() << logSuffix); + if (dequeues.empty()) return; + QPID_LOG(trace, logPrefix << "Sending dequeues " << dequeues); string buf(dequeues.encodedSize(),'\0'); framing::Buffer buffer(&buf[0], buf.size()); dequeues.encode(buffer); dequeues.clear(); buffer.reset(); - sendEvent(QueueReplicator::DEQUEUE_EVENT_KEY, buffer, l); + { + Mutex::ScopedUnlock u(lock); + sendEvent(QueueReplicator::DEQUEUE_EVENT_KEY, buffer); + } } -// Called after the message has been removed from the deque and under -// the messageLock in the queue. Called in arbitrary connection threads. +// Called via QueueObserver::dequeued override on guard. +// Called after the message has been removed +// from the deque and under the messageLock in the queue. Called in +// arbitrary connection threads. void ReplicatingSubscription::dequeued(const QueuedMessage& qm) { + assert (qm.queue == getQueue().get()); + QPID_LOG(trace, logPrefix << "Dequeued " << qm); { - sys::Mutex::ScopedLock l(lock); - QPID_LOG(trace, logPrefix << "dequeued " << qm << logSuffix); + Mutex::ScopedLock l(lock); dequeues.add(qm.position); - // If we have not yet sent this message to the backup, then - // complete it now as it will never be accepted. - if (qm.position > position) complete(qm, l); } notify(); // Ensure a call to doDispatch } +// Called during construction while scanning for initial dequeues. +void ReplicatingSubscription::dequeued(SequenceNumber first, SequenceNumber last) { + QPID_LOG(trace, logPrefix << "Initial dequeue [" << first << ", " << last << "]"); + { + Mutex::ScopedLock l(lock); + dequeues.add(first,last); + } +} + // Called with lock held. Called in subscription's connection thread. -void ReplicatingSubscription::sendPositionEvent( - SequenceNumber position, const sys::Mutex::ScopedLock&l ) +void ReplicatingSubscription::sendPositionEvent(SequenceNumber pos, Mutex::ScopedLock&) { - QPID_LOG(trace, logPrefix << "sending position " << position - << ", was " << backupPosition << logSuffix); - string buf(backupPosition.encodedSize(),'\0'); + if (pos == backupPosition) return; // No need to send. + QPID_LOG(trace, logPrefix << "Sending position " << pos << ", was " << backupPosition); + string buf(pos.encodedSize(),'\0'); framing::Buffer buffer(&buf[0], buf.size()); - position.encode(buffer); + pos.encode(buffer); buffer.reset(); - sendEvent(QueueReplicator::POSITION_EVENT_KEY, buffer, l); + { + Mutex::ScopedUnlock u(lock); + sendEvent(QueueReplicator::POSITION_EVENT_KEY, buffer); + } } -void ReplicatingSubscription::sendEvent(const std::string& key, framing::Buffer& buffer, - const sys::Mutex::ScopedLock&) +void ReplicatingSubscription::sendEvent(const std::string& key, framing::Buffer& buffer) { //generate event message boost::intrusive_ptr<Message> event = new Message(); @@ -276,15 +396,14 @@ void ReplicatingSubscription::sendEvent(const std::string& key, framing::Buffer& event->getFrames().append(header); event->getFrames().append(content); - DeliveryProperties* props = event->getFrames().getHeaders()->get<DeliveryProperties>(true); + DeliveryProperties* props = + event->getFrames().getHeaders()->get<DeliveryProperties>(true); props->setRoutingKey(key); - // Send the event using the events queue. Consumer is a - // DelegatingConsumer that delegates to *this for everything but - // has an independnet position. We put an event on events and - // dispatch it through ourselves to send it in line with the - // normal browsing messages. - events->deliver(event); - events->dispatch(consumer); + // Send the event directly to the base consumer implementation. + // We don't really need a queue here but we pass a dummy queue + // to conform to the consumer API. + QueuedMessage qm(dummy.get(), event); + ConsumerImpl::deliver(qm); } @@ -292,19 +411,10 @@ void ReplicatingSubscription::sendEvent(const std::string& key, framing::Buffer& bool ReplicatingSubscription::doDispatch() { { - sys::Mutex::ScopedLock l(lock); + Mutex::ScopedLock l(lock); if (!dequeues.empty()) sendDequeueEvent(l); } return ConsumerImpl::doDispatch(); } -ReplicatingSubscription::DelegatingConsumer::DelegatingConsumer(ReplicatingSubscription& c) : Consumer(c.getName(), true), delegate(c) {} -ReplicatingSubscription::DelegatingConsumer::~DelegatingConsumer() {} -bool ReplicatingSubscription::DelegatingConsumer::deliver(QueuedMessage& m) { return delegate.deliver(m); } -void ReplicatingSubscription::DelegatingConsumer::notify() { delegate.notify(); } -bool ReplicatingSubscription::DelegatingConsumer::filter(boost::intrusive_ptr<Message> msg) { return delegate.filter(msg); } -bool ReplicatingSubscription::DelegatingConsumer::accept(boost::intrusive_ptr<Message> msg) { return delegate.accept(msg); } -bool ReplicatingSubscription::DelegatingConsumer::browseAcquired() const { return delegate.browseAcquired(); } -OwnershipToken* ReplicatingSubscription::DelegatingConsumer::getSession() { return delegate.getSession(); } - }} // namespace qpid::ha diff --git a/cpp/src/qpid/ha/ReplicatingSubscription.h b/cpp/src/qpid/ha/ReplicatingSubscription.h index f9176915f6..a80141a6c2 100644 --- a/cpp/src/qpid/ha/ReplicatingSubscription.h +++ b/cpp/src/qpid/ha/ReplicatingSubscription.h @@ -22,10 +22,10 @@ * */ -#include "QueueReplicator.h" // For DEQUEUE_EVENT_KEY +#include "BrokerInfo.h" #include "qpid/broker/SemanticState.h" -#include "qpid/broker/QueueObserver.h" #include "qpid/broker/ConsumerFactory.h" +#include "qpid/types/Uuid.h" #include <iosfwd> namespace qpid { @@ -42,18 +42,27 @@ class Buffer; } namespace ha { +class QueueGuard; /** - * A susbcription that represents a backup replicating a queue. + * A susbcription that replicates to a remote backup. * - * Runs on the primary. Delays completion of messages till the backup - * has acknowledged, informs backup of locally dequeued messages. + * Runs on the primary. In conjunction with a QueueGuard, delays completion of + * messages till the backup has acknowledged, informs backup of locally dequeued + * messages. + * + * A ReplicatingSubscription is "ready" when all the messages on the queue have + * either been acknowledged by the backup, or are protected by the queue guard. + * On a primary broker the ReplicatingSubscription calls Primary::readyReplica + * when it is ready. + * + * THREAD SAFE: Called in subscription's connection thread but also in arbitrary + * connection threads via dequeued. + * + * Lifecycle: broker::Queue holds shared_ptrs to this as a consumer. * - * THREAD SAFE: Used as a consumer in subscription's connection - * thread, and as a QueueObserver in arbitrary connection threads. */ -class ReplicatingSubscription : public broker::SemanticState::ConsumerImpl, - public broker::QueueObserver +class ReplicatingSubscription : public broker::SemanticState::ConsumerImpl { public: struct Factory : public broker::ConsumerFactory { @@ -67,6 +76,20 @@ class ReplicatingSubscription : public broker::SemanticState::ConsumerImpl, // Argument names for consume command. static const std::string QPID_REPLICATING_SUBSCRIPTION; + static const std::string QPID_BACK; + static const std::string QPID_FRONT; + static const std::string QPID_BROKER_INFO; + + // TODO aconway 2012-05-23: these don't belong on ReplicatingSubscription + /** Get position of front message on queue. + *@return false if queue is empty. + */ + static bool getFront(broker::Queue&, framing::SequenceNumber& result); + /** Get next message after from in queue. + *@return false if none found. + */ + static bool getNext(broker::Queue&, framing::SequenceNumber from, + framing::SequenceNumber& result); ReplicatingSubscription(broker::SemanticState* parent, const std::string& name, boost::shared_ptr<broker::Queue> , @@ -76,56 +99,46 @@ class ReplicatingSubscription : public broker::SemanticState::ConsumerImpl, ~ReplicatingSubscription(); - // QueueObserver overrides. - bool deliver(broker::QueuedMessage& msg); - void enqueued(const broker::QueuedMessage&); - void dequeued(const broker::QueuedMessage&); - void acquired(const broker::QueuedMessage&) {} - void requeued(const broker::QueuedMessage&) {} + // Called via QueueGuard::dequeued. + //@return true if the message requires completion. + void dequeued(const broker::QueuedMessage& qm); + + // Called during initial scan for dequeues. + void dequeued(framing::SequenceNumber first, framing::SequenceNumber last); // Consumer overrides. + bool deliver(broker::QueuedMessage& msg); void cancel(); void acknowledged(const broker::QueuedMessage&); bool browseAcquired() const { return true; } + // Hide the "queue deleted" error for a ReplicatingSubscription when a + // queue is deleted, this is normal and not an error. + bool hideDeletedError() { return true; } + + /** Initialization that must be done separately from construction + * because it requires a shared_ptr to this to exist. + */ + void initialize(); - bool hideDeletedError(); + BrokerInfo getBrokerInfo() const { return info; } protected: bool doDispatch(); + private: - typedef std::map<framing::SequenceNumber, broker::QueuedMessage> Delayed; - std::string logPrefix, logSuffix; - boost::shared_ptr<broker::Queue> events; - boost::shared_ptr<broker::Consumer> consumer; - Delayed delayed; + std::string logPrefix; + boost::shared_ptr<broker::Queue> dummy; // Used to send event messages framing::SequenceSet dequeues; framing::SequenceNumber backupPosition; - - void complete(const broker::QueuedMessage&, const sys::Mutex::ScopedLock&); - void cancelComplete(const Delayed::value_type& v, const sys::Mutex::ScopedLock&); - void sendDequeueEvent(const sys::Mutex::ScopedLock&); - void sendPositionEvent(framing::SequenceNumber, const sys::Mutex::ScopedLock&); - void sendEvent(const std::string& key, framing::Buffer&, - const sys::Mutex::ScopedLock&); - - class DelegatingConsumer : public Consumer - { - public: - DelegatingConsumer(ReplicatingSubscription&); - ~DelegatingConsumer(); - bool deliver(broker::QueuedMessage& msg); - void notify(); - bool filter(boost::intrusive_ptr<broker::Message>); - bool accept(boost::intrusive_ptr<broker::Message>); - void cancel() {} - void acknowledged(const broker::QueuedMessage&) {} - bool browseAcquired() const; - - broker::OwnershipToken* getSession(); - - private: - ReplicatingSubscription& delegate; - }; + bool ready; + BrokerInfo info; + boost::shared_ptr<QueueGuard> guard; + + void sendDequeueEvent(sys::Mutex::ScopedLock&); + void sendPositionEvent(framing::SequenceNumber, sys::Mutex::ScopedLock&); + void setReady(); + void sendEvent(const std::string& key, framing::Buffer&); + friend struct Factory; }; diff --git a/cpp/src/qpid/ha/ReplicationTest.cpp b/cpp/src/qpid/ha/ReplicationTest.cpp new file mode 100644 index 0000000000..18e0953930 --- /dev/null +++ b/cpp/src/qpid/ha/ReplicationTest.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 "ReplicationTest.h" +#include "qpid/broker/Queue.h" +#include "qpid/framing/FieldTable.h" + +namespace qpid { +namespace ha { + +using types::Variant; + +ReplicateLevel ReplicationTest::replicateLevel(const std::string& str) { + Enum<ReplicateLevel> rl(replicateDefault); + if (!str.empty()) rl.parse(str); + return rl.get(); +} + +ReplicateLevel ReplicationTest::replicateLevel(const framing::FieldTable& f) { + if (f.isSet(QPID_REPLICATE)) + return replicateLevel(f.getAsString(QPID_REPLICATE)); + else + return replicateDefault; +} + +ReplicateLevel ReplicationTest::replicateLevel(const Variant::Map& m) { + Variant::Map::const_iterator i = m.find(QPID_REPLICATE); + if (i != m.end()) + return replicateLevel(i->second.asString()); + else + return replicateDefault; +} + +namespace { +const std::string AUTO_DELETE_TIMEOUT("qpid.auto_delete_timeout"); +} + +bool ReplicationTest::isReplicated( + ReplicateLevel level, const Variant::Map& args, bool autodelete, bool exclusive) +{ + bool ignore = autodelete && exclusive && args.find(AUTO_DELETE_TIMEOUT) == args.end(); + return !ignore && replicateLevel(args) >= level; +} + +bool ReplicationTest::isReplicated( + ReplicateLevel level, const framing::FieldTable& args, bool autodelete, bool exclusive) +{ + bool ignore = autodelete && exclusive && !args.isSet(AUTO_DELETE_TIMEOUT); + return !ignore && replicateLevel(args) >= level; +} + +bool ReplicationTest::isReplicated(ReplicateLevel level, const broker::Queue& q) +{ + return isReplicated(level, q.getSettings(), q.isAutoDelete(), q.hasExclusiveOwner()); +} + + +}} // namespace qpid::ha diff --git a/cpp/src/qpid/ha/ReplicationTest.h b/cpp/src/qpid/ha/ReplicationTest.h new file mode 100644 index 0000000000..9f6976a8e4 --- /dev/null +++ b/cpp/src/qpid/ha/ReplicationTest.h @@ -0,0 +1,67 @@ +#ifndef QPID_HA_REPLICATIONTEST_H +#define QPID_HA_REPLICATIONTEST_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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 "qpid/types/Variant.h" +#include <string> + +namespace qpid { + +namespace broker { +class Queue; +} + +namespace framing { +class FieldTable; +} + +namespace ha { +/** + * Test whether something is replicated, taking into account the + * default replication level. + */ +class ReplicationTest +{ + public: + ReplicationTest(ReplicateLevel replicateDefault_) : + replicateDefault(replicateDefault_) {} + + // Return the simple replication level, accounting for defaults. + ReplicateLevel replicateLevel(const std::string& str); + ReplicateLevel replicateLevel(const framing::FieldTable& f); + ReplicateLevel replicateLevel(const types::Variant::Map& m); + + // Return true if replication for a queue is enabled at level or higher, + // taking account of default level and queue settings. + bool isReplicated(ReplicateLevel level, + const types::Variant::Map& args, bool autodelete, bool exclusive); + bool isReplicated(ReplicateLevel level, + const framing::FieldTable& args, bool autodelete, bool exclusive); + bool isReplicated(ReplicateLevel level, const broker::Queue&); + private: + ReplicateLevel replicateDefault; +}; +}} // namespace qpid::ha + +#endif /*!QPID_HA_REPLICATIONTEST_H*/ diff --git a/cpp/src/qpid/ha/Settings.h b/cpp/src/qpid/ha/Settings.h index bf70c3f3f7..37235b5c79 100644 --- a/cpp/src/qpid/ha/Settings.h +++ b/cpp/src/qpid/ha/Settings.h @@ -22,7 +22,7 @@ * */ -#include "ReplicateLevel.h" +#include "types.h" #include <string> namespace qpid { @@ -34,13 +34,15 @@ namespace ha { class Settings { public: - Settings() : cluster(false), expectedBackups(0), replicateDefault(RL_NONE) {} + Settings() : cluster(false), replicateDefault(NONE), backupTimeout(5) + {} + bool cluster; // True if we are a cluster member. std::string clientUrl; std::string brokerUrl; - size_t expectedBackups; - ReplicateLevel replicateDefault; + Enum<ReplicateLevel> replicateDefault; std::string username, password, mechanism; + double backupTimeout; private: }; }} // namespace qpid::ha diff --git a/cpp/src/qpid/ha/management-schema.xml b/cpp/src/qpid/ha/management-schema.xml index 363dff61fb..3da482e732 100644 --- a/cpp/src/qpid/ha/management-schema.xml +++ b/cpp/src/qpid/ha/management-schema.xml @@ -25,37 +25,39 @@ <property name="status" type="sstr" desc="HA status: primary or backup"/> - <property name="brokers" type="sstr" - desc="Multiple-address URL used by HA brokers to connect to each other."/> + <property name="brokersUrl" type="sstr" + desc="URL with address of each broker in the cluster."/> - <property name="publicBrokers" type="sstr" - desc="Multiple-address URL used by clients to connect to the HA brokers."/> + <property name="publicUrl" type="sstr" + desc="URL advertized to clients to connect to the cluster."/> - <property name="expectedBackups" type="uint16" - desc="Number of HA backup brokers expected."/> + <property name="replicateDefault" type="sstr" + desc="Replication for queues/exchanges with no qpid.replicate argument"/> - <property - name="replicateDefault" type="sstr" - desc="Replicate value for queues/exchanges without a qpid.replicate argument"/> + <property name="members" type="list" desc="List of brokers in the cluster"/> + + <property name="systemId" type="uuid" desc="Identifies the system."/> <method name="promote" desc="Promote a backup broker to primary."/> - <method name="setBrokers" desc="Set URL for HA brokers to connect to each other."> + <method name="setBrokersUrl" desc="URL listing each broker in the cluster."> <arg name="url" type="sstr" dir="I"/> </method> - <method name="setPublicBrokers" desc="Set URL for clients to connect to HA brokers"> + <method name="setPublicUrl" desc="URL advertized to clients."> <arg name="url" type="sstr" dir="I"/> </method> - <method name="setExpectedBackups" desc="Set number of backups expected"> - <arg name="expectedBackups" type="uint16" dir="I"/> - </method> - <method name="replicate" desc="Replicate individual queue from remote broker."> <arg name="broker" type="sstr" dir="I"/> <arg name="queue" type="sstr" dir="I"/> </method> </class> + <eventArguments> + <arg name="members" type="list" desc="List of broker information maps"/> + </eventArguments> + + <event name="membersUpdate" sev="inform" args="members"/> + </schema> diff --git a/cpp/src/qpid/ha/types.cpp b/cpp/src/qpid/ha/types.cpp new file mode 100644 index 0000000000..53e2056213 --- /dev/null +++ b/cpp/src/qpid/ha/types.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 "types.h" +#include "qpid/Msg.h" +#include "qpid/Exception.h" +#include <algorithm> +#include <iostream> +#include <iterator> +#include <assert.h> + +namespace qpid { +namespace ha { + +using namespace std; + +const string QPID_REPLICATE("qpid.replicate"); + +string EnumBase::str() const { + assert(value < count); + return names[value]; +} + +void EnumBase::parse(const string& s) { + if (!parseNoThrow(s)) + throw Exception(QPID_MSG("Invalid " << name << " value: " << s)); +} + +bool EnumBase::parseNoThrow(const string& s) { + const char** i = find(names, names+count, s); + value = i - names; + return value < count; +} + +template <> const char* Enum<ReplicateLevel>::NAME = "replication"; +template <> const char* Enum<ReplicateLevel>::NAMES[] = { "none", "configuration", "all" }; +template <> const size_t Enum<ReplicateLevel>::N = 3; + +template <> const char* Enum<BrokerStatus>::NAME = "HA broker status"; +template <> const char* Enum<BrokerStatus>::NAMES[] = { + "joining", "catchup", "ready", "recovering", "active", "standalone" +}; +template <> const size_t Enum<BrokerStatus>::N = 6; + +ostream& operator<<(ostream& o, EnumBase e) { + return o << e.str(); +} + +istream& operator>>(istream& i, EnumBase& e) { + string s; + i >> s; + e.parse(s); + return i; +} + +ostream& operator<<(ostream& o, const IdSet& ids) { + ostream_iterator<qpid::types::Uuid> out(o, " "); + copy(ids.begin(), ids.end(), out); + return o; +} + +}} // namespace qpid::ha diff --git a/cpp/src/qpid/ha/types.h b/cpp/src/qpid/ha/types.h new file mode 100644 index 0000000000..35faf9f624 --- /dev/null +++ b/cpp/src/qpid/ha/types.h @@ -0,0 +1,109 @@ +#ifndef QPID_HA_ENUM_H +#define QPID_HA_ENUM_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR 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/types/Uuid.h" +#include <string> +#include <set> +#include <iosfwd> + +namespace qpid { + +namespace framing { +class FieldTable; +} + +namespace ha { + +/** Base class for enums with string conversion */ +class EnumBase { + public: + EnumBase(const char* name_, const char* names_[], size_t count_, unsigned value) + : name(name_), names(names_), count(count_), value(value) {} + + /** Convert to string */ + std::string str() const; + /** Parse from string, throw if unsuccessful */ + void parse(const std::string&); + /** Parse from string, return false if unsuccessful. */ + bool parseNoThrow(const std::string&); + + protected: + const char* name; + const char** names; + size_t count; + unsigned value; +}; + +std::ostream& operator<<(std::ostream&, EnumBase); +std::istream& operator>>(std::istream&, EnumBase&); + +/** Wrapper template for enums with string conversion */ +template <class T> class Enum : public EnumBase { + public: + Enum(T x=T()) : EnumBase(NAME, NAMES, N, x) {} + T get() const { return T(value); } + void operator=(T x) { value = x; } + + private: + static const size_t N; // Number of enum values. + static const char* NAMES[]; // Names of enum values. + static const char* NAME; // Descriptive name for the enum type. +}; + +/** To print an enum x: o << printable(x) */ +template <class T> Enum<T> printable(T x) { return Enum<T>(x); } + +enum ReplicateLevel { + NONE, ///< Nothing is replicated + CONFIGURATION, ///< Wiring is replicated but not messages + ALL ///< Everything is replicated +}; + +/** State of a broker: see HaBroker::setStatus for state diagram */ +enum BrokerStatus { + JOINING, ///< New broker, looking for primary + CATCHUP, ///< Backup: Connected to primary, catching up on state. + READY, ///< Backup: Caught up, ready to take over. + RECOVERING, ///< Primary: waiting for backups to connect & sync + ACTIVE, ///< Primary: actively serving clients. + STANDALONE ///< Not part of a cluster. +}; + +inline bool isPrimary(BrokerStatus s) { + return s == RECOVERING || s == ACTIVE || s == STANDALONE; +} + +inline bool isBackup(BrokerStatus s) { return !isPrimary(s); } + +// String constants. +extern const std::string QPID_REPLICATE; + +/** Define IdSet type, not a typedef so we can overload operator << */ +class IdSet : public std::set<types::Uuid> {}; + +std::ostream& operator<<(std::ostream& o, const IdSet& ids); + +}} // qpid::ha +#endif /*!QPID_HA_ENUM_H*/ diff --git a/cpp/src/qpid/log/Logger.cpp b/cpp/src/qpid/log/Logger.cpp index 800881c077..7c2e807ca9 100644 --- a/cpp/src/qpid/log/Logger.cpp +++ b/cpp/src/qpid/log/Logger.cpp @@ -47,7 +47,7 @@ using namespace std; typedef sys::Mutex::ScopedLock ScopedLock; inline void Logger::enable_unlocked(Statement* s) { - s->enabled=selector.isEnabled(s->level, s->function); + s->enabled=selector.isEnabled(s->level, s->function, s->category); } Logger& Logger::instance() { @@ -95,6 +95,8 @@ void Logger::log(const Statement& s, const std::string& msg) { else qpid::sys::outputFormattedNow(os); } + if (flags&CATEGORY) + os << "[" << CategoryTraits::name(s.category) << "] "; if (flags&LEVEL) os << LevelTraits::name(s.level) << " "; if (flags&THREAD) @@ -144,7 +146,8 @@ int Logger::format(const Options& opts) { bitIf(opts.source, (FILE|LINE)) | bitIf(opts.function, FUNCTION) | bitIf(opts.thread, THREAD) | - bitIf(opts.hiresTs, HIRES); + bitIf(opts.hiresTs, HIRES) | + bitIf(opts.category, CATEGORY); format(flags); return flags; } diff --git a/cpp/src/qpid/log/Options.cpp b/cpp/src/qpid/log/Options.cpp index 1259244297..10422bbb1e 100644 --- a/cpp/src/qpid/log/Options.cpp +++ b/cpp/src/qpid/log/Options.cpp @@ -39,6 +39,7 @@ Options::Options(const std::string& argv0_, const std::string& name_) : source(false), function(false), hiresTs(false), + category(true), trace(false), sinkOptions (SinkOptions::create(argv0_)) { @@ -49,15 +50,23 @@ Options::Options(const std::string& argv0_, const std::string& name_) : for (int i = 1; i < LevelTraits::COUNT; ++i) levels << " " << LevelTraits::name(Level(i)); + ostringstream categories; + categories << CategoryTraits::name(Category(0)); + for (int i = 1; i < CategoryTraits::COUNT; ++i) + categories << " " << CategoryTraits::name(Category(i)); + addOptions() ("trace,t", optValue(trace), "Enables all logging" ) ("log-enable", optValue(selectors, "RULE"), - ("Enables logging for selected levels and components. " + ("Enables logging for selected levels and components. " "RULE is in the form 'LEVEL[+][:PATTERN]' " - "Levels are one of: \n\t "+levels.str()+"\n" + "LEVEL is one of: \n\t "+levels.str()+"\n" + "PATTERN is a function name or a catogory: \n\t "+categories.str()+"\n" "For example:\n" "\t'--log-enable warning+' " "logs all warning, error and critical messages.\n" + "\t'--log-enable trace+:Broker' " + "logs all category 'Broker' messages.\n" "\t'--log-enable debug:framing' " "logs debug messages from the framing namespace. " "This option can be used multiple times").c_str()) @@ -67,6 +76,7 @@ Options::Options(const std::string& argv0_, const std::string& name_) : ("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 hi-resolution timestamps in log messages") + ("log-category", optValue(category,"yes|no"), "Include category in log messages") ("log-prefix", optValue(prefix,"STRING"), "Prefix to append to all log messages") ; add(*sinkOptions); @@ -83,6 +93,7 @@ Options::Options(const Options &o) : source(o.source), function(o.function), hiresTs(o.hiresTs), + category(o.category), trace(o.trace), prefix(o.prefix), sinkOptions (SinkOptions::create(o.argv0)) @@ -101,11 +112,12 @@ Options& Options::operator=(const Options& x) { source = x.source; function = x.function; hiresTs = x.hiresTs; + category = x.category; trace = x.trace; prefix = x.prefix; *sinkOptions = *x.sinkOptions; } return *this; } - + }} // namespace qpid::log diff --git a/cpp/src/qpid/log/Selector.cpp b/cpp/src/qpid/log/Selector.cpp index a4bc580470..8757486d88 100644 --- a/cpp/src/qpid/log/Selector.cpp +++ b/cpp/src/qpid/log/Selector.cpp @@ -37,18 +37,29 @@ void Selector::enable(const string& enableStr) { level=enableStr.substr(0,c); pattern=enableStr.substr(c+1); } + bool isCat = CategoryTraits::isCategory(pattern); 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); + ++i) { + if (isCat) { + enable(Level(i), CategoryTraits::category(pattern)); + } else { + enable(Level(i), pattern); + } + } } else { - enable(LevelTraits::level(level), pattern); + if (isCat) { + enable(LevelTraits::level(level), CategoryTraits::category(pattern)); + } else { + enable(LevelTraits::level(level), pattern); + } } } Selector::Selector(const Options& opt){ + reset(); for_each(opt.selectors.begin(), opt.selectors.end(), boost::bind(&Selector::enable, this, _1)); } @@ -58,11 +69,17 @@ bool Selector::isEnabled(Level level, const char* 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; - } + { + if (std::search(function, functionEnd, i->begin(), i->end()) != functionEnd) + return true; + } return false; } +bool Selector::isEnabled(Level level, const char* function, Category category) { + if (catFlags[level][category]) + return true; + return isEnabled(level, function); +} + }} // namespace qpid::log diff --git a/cpp/src/qpid/log/Statement.cpp b/cpp/src/qpid/log/Statement.cpp index 79f7a28100..09ef458547 100644 --- a/cpp/src/qpid/log/Statement.cpp +++ b/cpp/src/qpid/log/Statement.cpp @@ -36,7 +36,7 @@ std::string quote(const std::string& str) { 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. + ret.reserve(str.size()+3*n); // Avoid extra allocations. for (std::string::const_iterator i = str.begin(); i != str.end(); ++i) { if (nonPrint(*i)) { ret.push_back('\\'); @@ -50,10 +50,42 @@ std::string quote(const std::string& str) { } } +// +// Instance of name hints +// +static CategoryFileNameHints filenameHints; + + +Category CategoryFileNameHints::categoryOf(const char* const fName) { + for (std::list<std::pair<const char* const, Category> >::iterator + it = filenameHints.hintList.begin(); + it != filenameHints.hintList.end(); + ++it) { + if (strstr(fName, (const char* const)it->first) != 0) { + return it->second; + } + } + return unspecified; +} + + +void Statement::categorize(Statement& s) { + // given a statement and it's category + // if the category is Unspecified then try to find a + // better category based on the path and file name. + if (s.category == log::unspecified) { + s.category = CategoryFileNameHints::categoryOf(s.file); + } else { + // already has a category so leave it alone + } +} + + void Statement::log(const std::string& message) { Logger::instance().log(*this, quote(message)); } + Statement::Initializer::Initializer(Statement& s) : statement(s) { // QPID-3891 // From the given BOOST_CURRENT_FUNCTION name extract only the @@ -99,16 +131,22 @@ Statement::Initializer::Initializer(Statement& s) : statement(s) { // no function-name pointer to process } + Statement::categorize(s); Logger::instance().add(s); } + namespace { const char* names[LevelTraits::COUNT] = { "trace", "debug", "info", "notice", "warning", "error", "critical" }; -} // namespace +const char* catNames[CategoryTraits::COUNT] = { + "Security", "Broker", "Management", "Protocol", "System", "HA", "Messaging", + "Store", "Network", "Test", "Client", "Model", "Unspecified" +}; +} // namespace Level LevelTraits::level(const char* name) { for (int i =0; i < LevelTraits::COUNT; ++i) { if (strcmp(names[i], name)==0) @@ -121,4 +159,23 @@ const char* LevelTraits::name(Level l) { return names[l]; } +bool CategoryTraits::isCategory(const std::string& name) { + for (int i =0; i < CategoryTraits::COUNT; ++i) { + if (strcmp(catNames[i], name.c_str())==0) + return true; + } + return false; +} + +Category CategoryTraits::category(const char* name) { + for (int i =0; i < CategoryTraits::COUNT; ++i) { + if (strcmp(catNames[i], name)==0) + return Category(i); + } + throw std::runtime_error(std::string("Invalid log category name: ")+name); +} + +const char* CategoryTraits::name(Category c) { + return catNames[c]; +} }} // namespace qpid::log diff --git a/cpp/src/qpid/log/posix/SinkOptions.cpp b/cpp/src/qpid/log/posix/SinkOptions.cpp index 8459938e5c..d3db5f3cdf 100644 --- a/cpp/src/qpid/log/posix/SinkOptions.cpp +++ b/cpp/src/qpid/log/posix/SinkOptions.cpp @@ -22,11 +22,14 @@ #include "qpid/log/OstreamOutput.h" #include "qpid/memory.h" #include "qpid/Exception.h" + #include <iostream> #include <map> #include <string> #include <syslog.h> +#include <boost/lexical_cast.hpp> + using std::string; using qpid::Exception; @@ -90,7 +93,7 @@ public: string name(int value) const { ByValue::const_iterator i = byValue.find(value); if (i == byValue.end()) - throw Exception("Not a valid syslog value: " + value); + throw Exception("Not a valid syslog value: " + boost::lexical_cast<string>(value)); return i->second; } diff --git a/cpp/src/qpid/management/Buffer.cpp b/cpp/src/qpid/management/Buffer.cpp index 7556b2a243..0ad6e9a851 100644 --- a/cpp/src/qpid/management/Buffer.cpp +++ b/cpp/src/qpid/management/Buffer.cpp @@ -29,12 +29,11 @@ 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(); } +void Buffer::setPosition(uint32_t p) { impl->setPosition(p); } char* Buffer::getPointer() { return impl->getPointer(); } void Buffer::putOctet(uint8_t i) { impl->putOctet(i); } void Buffer::putShort(uint16_t i) { impl->putShort(i); } diff --git a/cpp/src/qpid/management/ManagementAgent.cpp b/cpp/src/qpid/management/ManagementAgent.cpp index 062a530706..7d90ed99d0 100644 --- a/cpp/src/qpid/management/ManagementAgent.cpp +++ b/cpp/src/qpid/management/ManagementAgent.cpp @@ -1344,18 +1344,19 @@ void ManagementAgent::handleMethodRequestLH(Buffer& inBuffer, const string& repl outBuffer.putLong (Manageable::STATUS_PARAMETER_INVALID); outBuffer.putMediumString(Manageable::StatusText (Manageable::STATUS_PARAMETER_INVALID)); } - else + else { + uint32_t pos = outBuffer.getPosition(); 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.setPosition(pos);; outBuffer.putLong(Manageable::STATUS_EXCEPTION); outBuffer.putMediumString(e.what()); } + } } outLen = MA_BUFFER_SIZE - outBuffer.available(); @@ -1662,11 +1663,11 @@ void ManagementAgent::handleSchemaResponseLH(Buffer& inBuffer, const string& /*r string packageName; SchemaClassKey key; - inBuffer.record(); + uint32_t pos = inBuffer.getPosition(); inBuffer.getOctet(); inBuffer.getShortString(packageName); key.decode(inBuffer); - inBuffer.restore(); + inBuffer.setPosition(pos);; QPID_LOG(debug, "RECV SchemaResponse class=" << packageName << ":" << key.name << "(" << Uuid(key.hash) << ")" << " seq=" << sequence); @@ -2426,7 +2427,6 @@ size_t ManagementAgent::validateTableSchema(Buffer& inBuffer) uint8_t hash[16]; try { - inBuffer.record(); uint8_t kind = inBuffer.getOctet(); if (kind != ManagementItem::CLASS_KIND_TABLE) return 0; @@ -2468,7 +2468,7 @@ size_t ManagementAgent::validateTableSchema(Buffer& inBuffer) } end = inBuffer.getPosition(); - inBuffer.restore(); // restore original position + inBuffer.setPosition(start); // restore original position return end - start; } @@ -2480,7 +2480,6 @@ size_t ManagementAgent::validateEventSchema(Buffer& inBuffer) uint8_t hash[16]; try { - inBuffer.record(); uint8_t kind = inBuffer.getOctet(); if (kind != ManagementItem::CLASS_KIND_EVENT) return 0; @@ -2507,7 +2506,7 @@ size_t ManagementAgent::validateEventSchema(Buffer& inBuffer) } end = inBuffer.getPosition(); - inBuffer.restore(); // restore original position + inBuffer.setPosition(start); // restore original position return end - start; } diff --git a/cpp/src/qpid/management/ManagementDirectExchange.cpp b/cpp/src/qpid/management/ManagementDirectExchange.cpp index 312eacf462..9432a21b3a 100644 --- a/cpp/src/qpid/management/ManagementDirectExchange.cpp +++ b/cpp/src/qpid/management/ManagementDirectExchange.cpp @@ -28,7 +28,7 @@ using namespace qpid::broker; using namespace qpid::framing; using namespace qpid::sys; -ManagementDirectExchange::ManagementDirectExchange(const string& _name, Manageable* _parent, Broker* b) : +ManagementDirectExchange::ManagementDirectExchange(const std::string& _name, Manageable* _parent, Broker* b) : Exchange (_name, _parent, b), DirectExchange(_name, _parent, b), managementAgent(0) {} @@ -43,7 +43,7 @@ ManagementDirectExchange::ManagementDirectExchange(const std::string& _name, void ManagementDirectExchange::route(Deliverable& msg) { bool routeIt = true; - const string& routingKey = msg.getMessage().getRoutingKey(); + const std::string& routingKey = msg.getMessage().getRoutingKey(); const FieldTable* args = msg.getMessage().getApplicationHeaders(); if (managementAgent) diff --git a/cpp/src/qpid/management/ManagementTopicExchange.cpp b/cpp/src/qpid/management/ManagementTopicExchange.cpp index 587cc660df..e5b659f217 100644 --- a/cpp/src/qpid/management/ManagementTopicExchange.cpp +++ b/cpp/src/qpid/management/ManagementTopicExchange.cpp @@ -27,7 +27,7 @@ using namespace qpid::broker; using namespace qpid::framing; using namespace qpid::sys; -ManagementTopicExchange::ManagementTopicExchange(const string& _name, Manageable* _parent, Broker* b) : +ManagementTopicExchange::ManagementTopicExchange(const std::string& _name, Manageable* _parent, Broker* b) : Exchange (_name, _parent, b), TopicExchange(_name, _parent, b), managementAgent(0) {} @@ -42,7 +42,7 @@ ManagementTopicExchange::ManagementTopicExchange(const std::string& _name, void ManagementTopicExchange::route(Deliverable& msg) { bool routeIt = true; - const string& routingKey = msg.getMessage().getRoutingKey(); + const std::string& routingKey = msg.getMessage().getRoutingKey(); const FieldTable* args = msg.getMessage().getApplicationHeaders(); // Intercept management agent commands @@ -54,7 +54,7 @@ void ManagementTopicExchange::route(Deliverable& msg) } bool ManagementTopicExchange::bind(Queue::shared_ptr queue, - const string& routingKey, + const std::string& routingKey, const qpid::framing::FieldTable* args) { if (qmfVersion == 1) diff --git a/cpp/src/qpid/messaging/PrivateImplRef.h b/cpp/src/qpid/messaging/PrivateImplRef.h index 718faef475..47afe30919 100644 --- a/cpp/src/qpid/messaging/PrivateImplRef.h +++ b/cpp/src/qpid/messaging/PrivateImplRef.h @@ -77,15 +77,15 @@ template <class T> class PrivateImplRef { /** 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); + if (t.impl) intrusive_ptr_release(t.impl); t.impl = p.get(); - if (t.impl) boost::intrusive_ptr_add_ref(t.impl); + if (t.impl) 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 ctor(T& t, Impl* p) { t.impl = p; if (p) 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 void dtor(T& t) { if(t.impl) intrusive_ptr_release(t.impl); } static T& assign(T& t, const T& x) { set(t, get(x)); return t;} }; diff --git a/cpp/src/qpid/replication/ReplicationExchange.cpp b/cpp/src/qpid/replication/ReplicationExchange.cpp index 66f4f14d0c..bcb7c7f293 100644 --- a/cpp/src/qpid/replication/ReplicationExchange.cpp +++ b/cpp/src/qpid/replication/ReplicationExchange.cpp @@ -184,7 +184,7 @@ bool ReplicationExchange::unbind(Queue::shared_ptr /*queue*/, const std::string& throw NotImplementedException("Replication exchange does not support unbind operation"); } -bool ReplicationExchange::isBound(Queue::shared_ptr /*queue*/, const string* const /*routingKey*/, const FieldTable* const /*args*/) +bool ReplicationExchange::isBound(Queue::shared_ptr /*queue*/, const std::string* const /*routingKey*/, const FieldTable* const /*args*/) { return false; } diff --git a/cpp/src/qpid/sys/AsynchIOHandler.cpp b/cpp/src/qpid/sys/AsynchIOHandler.cpp index 5233002850..8a485db72d 100644 --- a/cpp/src/qpid/sys/AsynchIOHandler.cpp +++ b/cpp/src/qpid/sys/AsynchIOHandler.cpp @@ -23,6 +23,7 @@ #include "qpid/sys/AsynchIO.h" #include "qpid/sys/Socket.h" #include "qpid/sys/SecuritySettings.h" +#include "qpid/sys/Timer.h" #include "qpid/framing/AMQP_HighestVersion.h" #include "qpid/framing/ProtocolInitiation.h" #include "qpid/log/Statement.h" @@ -41,11 +42,30 @@ struct Buff : public AsynchIO::BufferBase { { delete [] bytes;} }; -AsynchIOHandler::AsynchIOHandler(std::string id, ConnectionCodec::Factory* f) : +struct ProtocolTimeoutTask : public sys::TimerTask { + AsynchIOHandler& handler; + std::string id; + + ProtocolTimeoutTask(const std::string& i, const Duration& timeout, AsynchIOHandler& h) : + TimerTask(timeout, "ProtocolTimeout"), + handler(h), + id(i) + {} + + void fire() { + // If this fires it means that we didn't negotiate the connection in the timeout period + // Schedule closing the connection for the io thread + QPID_LOG(error, "Connection " << id << " No protocol received closing"); + handler.abort(); + } +}; + +AsynchIOHandler::AsynchIOHandler(const std::string& id, ConnectionCodec::Factory* f) : identifier(id), aio(0), factory(f), codec(0), + reads(0), readError(false), isClient(false), readCredit(InfiniteCredit) @@ -54,12 +74,18 @@ AsynchIOHandler::AsynchIOHandler(std::string id, ConnectionCodec::Factory* f) : AsynchIOHandler::~AsynchIOHandler() { if (codec) codec->closed(); + if (timeoutTimerTask) + timeoutTimerTask->cancel(); delete codec; } -void AsynchIOHandler::init(AsynchIO* a, int numBuffs) { +void AsynchIOHandler::init(qpid::sys::AsynchIO* a, qpid::sys::Timer& timer, uint32_t maxTime, int numBuffs) { aio = a; + // Start timer for this connection + timeoutTimerTask = new ProtocolTimeoutTask(identifier, maxTime*TIME_MSEC, *this); + timer.add(timeoutTimerTask); + // Give connection some buffers to use for (int i = 0; i < numBuffs; i++) { aio->queueReadBuffer(new Buff); @@ -129,10 +155,18 @@ void AsynchIOHandler::readbuff(AsynchIO& , AsynchIO::BufferBase* buff) { } } + ++reads; size_t decoded = 0; if (codec) { // Already initiated try { decoded = codec->decode(buff->bytes+buff->dataStart, buff->dataCount); + // When we've decoded 3 reads (probably frames) we will have authenticated and + // started heartbeats, if specified, in many (but not all) cases so now we will cancel + // the idle connection timeout - this is really hacky, and would be better implemented + // in the connection, but that isn't actually created until the first decode. + if (reads == 3) { + timeoutTimerTask->cancel(); + } }catch(const std::exception& e){ QPID_LOG(error, e.what()); readError = true; @@ -143,6 +177,7 @@ void AsynchIOHandler::readbuff(AsynchIO& , AsynchIO::BufferBase* buff) { 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()); @@ -202,6 +237,10 @@ void AsynchIOHandler::idle(AsynchIO&){ if (isClient && codec == 0) { codec = factory->create(*this, identifier, SecuritySettings()); write(framing::ProtocolInitiation(codec->getVersion())); + // We've just sent the protocol negotiation so we can cancel the timeout for that + // This is not ideal, because we've not received anything yet, but heartbeats will + // be active soon + timeoutTimerTask->cancel(); return; } if (codec == 0) return; diff --git a/cpp/src/qpid/sys/AsynchIOHandler.h b/cpp/src/qpid/sys/AsynchIOHandler.h index b9867606c4..307aad5b85 100644 --- a/cpp/src/qpid/sys/AsynchIOHandler.h +++ b/cpp/src/qpid/sys/AsynchIOHandler.h @@ -27,6 +27,8 @@ #include "qpid/sys/Mutex.h" #include "qpid/CommonImportExport.h" +#include <boost/intrusive_ptr.hpp> + namespace qpid { namespace framing { @@ -38,24 +40,28 @@ namespace sys { class AsynchIO; struct AsynchIOBufferBase; class Socket; +class Timer; +class TimerTask; class AsynchIOHandler : public OutputControl { std::string identifier; AsynchIO* aio; ConnectionCodec::Factory* factory; ConnectionCodec* codec; + uint32_t reads; bool readError; bool isClient; AtomicValue<int32_t> readCredit; static const int32_t InfiniteCredit = -1; Mutex creditLock; + boost::intrusive_ptr<sys::TimerTask> timeoutTimerTask; void write(const framing::ProtocolInitiation&); public: - QPID_COMMON_EXTERN AsynchIOHandler(std::string id, ConnectionCodec::Factory* f); + QPID_COMMON_EXTERN AsynchIOHandler(const std::string& id, qpid::sys::ConnectionCodec::Factory* f ); QPID_COMMON_EXTERN ~AsynchIOHandler(); - QPID_COMMON_EXTERN void init(AsynchIO* a, int numBuffs); + QPID_COMMON_EXTERN void init(AsynchIO* a, Timer& timer, uint32_t maxTime, int numBuffs); QPID_COMMON_INLINE_EXTERN void setClient() { isClient = true; } diff --git a/cpp/src/qpid/sys/windows/MemStat.cpp b/cpp/src/qpid/sys/MemStat.cpp index b1d25c5fc5..c71fba785c 100644 --- a/cpp/src/qpid/sys/windows/MemStat.cpp +++ b/cpp/src/qpid/sys/MemStat.cpp @@ -21,9 +21,11 @@ #include "qpid/sys/MemStat.h" +// Null memory stats provider: +// This is for platforms that do not have a way to get allocated +// memory status void qpid::sys::MemStat::loadMemInfo(qmf::org::apache::qpid::broker::Memory*) { - // TODO: Add Windows-specific memory stats to the object and load them here. } diff --git a/cpp/src/qpid/sys/SslPlugin.cpp b/cpp/src/qpid/sys/SslPlugin.cpp index 48baef9042..3b50527c0a 100644 --- a/cpp/src/qpid/sys/SslPlugin.cpp +++ b/cpp/src/qpid/sys/SslPlugin.cpp @@ -39,6 +39,8 @@ namespace qpid { namespace sys { +class Timer; + using namespace qpid::sys::ssl; struct SslServerOptions : ssl::SslOptions @@ -68,6 +70,8 @@ class SslProtocolFactoryTmpl : public ProtocolFactory { typedef SslAcceptorTmpl<T> SslAcceptor; + Timer& brokerTimer; + uint32_t maxNegotiateTime; const bool tcpNoDelay; T listener; const uint16_t listeningPort; @@ -75,7 +79,7 @@ class SslProtocolFactoryTmpl : public ProtocolFactory { bool nodict; public: - SslProtocolFactoryTmpl(const SslServerOptions&, int backlog, bool nodelay); + SslProtocolFactoryTmpl(const SslServerOptions&, int backlog, bool nodelay, Timer& timer, uint32_t maxTime); void accept(Poller::shared_ptr, ConnectionCodec::Factory*); void connect(Poller::shared_ptr, const std::string& host, const std::string& port, ConnectionCodec::Factory*, @@ -132,16 +136,18 @@ static struct SslPlugin : public Plugin { try { ssl::initNSS(options, true); nssInitialized = true; - + const broker::Broker::Options& opts = broker->getOptions(); ProtocolFactory::shared_ptr protocol(options.multiplex ? static_cast<ProtocolFactory*>(new SslMuxProtocolFactory(options, opts.connectionBacklog, - opts.tcpNoDelay)) : + opts.tcpNoDelay, + broker->getTimer(), opts.maxNegotiateTime)) : static_cast<ProtocolFactory*>(new SslProtocolFactory(options, opts.connectionBacklog, - opts.tcpNoDelay))); + opts.tcpNoDelay, + broker->getTimer(), opts.maxNegotiateTime))); QPID_LOG(notice, "Listening for " << (options.multiplex ? "SSL or TCP" : "SSL") << " connections on TCP port " << @@ -156,14 +162,16 @@ static struct SslPlugin : public Plugin { } sslPlugin; template <class T> -SslProtocolFactoryTmpl<T>::SslProtocolFactoryTmpl(const SslServerOptions& options, int backlog, bool nodelay) : +SslProtocolFactoryTmpl<T>::SslProtocolFactoryTmpl(const SslServerOptions& options, int backlog, bool nodelay, Timer& timer, uint32_t maxTime) : + brokerTimer(timer), + maxNegotiateTime(maxTime), tcpNoDelay(nodelay), listeningPort(listener.listen(options.port, backlog, options.certName, options.clientAuth)), nodict(options.nodict) {} void SslEstablished(Poller::shared_ptr poller, const qpid::sys::SslSocket& s, ConnectionCodec::Factory* f, bool isClient, - bool tcpNoDelay, bool nodict) { + Timer& timer, uint32_t maxTime, bool tcpNoDelay, bool nodict) { qpid::sys::ssl::SslHandler* async = new qpid::sys::ssl::SslHandler(s.getFullAddress(), f, nodict); if (tcpNoDelay) { @@ -183,7 +191,7 @@ void SslEstablished(Poller::shared_ptr poller, const qpid::sys::SslSocket& s, boost::bind(&qpid::sys::ssl::SslHandler::nobuffs, async, _1), boost::bind(&qpid::sys::ssl::SslHandler::idle, async, _1)); - async->init(aio, 4); + async->init(aio,timer, maxTime, 4); aio->start(poller); } @@ -192,7 +200,7 @@ void SslProtocolFactory::established(Poller::shared_ptr poller, const Socket& s, ConnectionCodec::Factory* f, bool isClient) { const SslSocket *sslSock = dynamic_cast<const SslSocket*>(&s); - SslEstablished(poller, *sslSock, f, isClient, tcpNoDelay, nodict); + SslEstablished(poller, *sslSock, f, isClient, brokerTimer, maxNegotiateTime, tcpNoDelay, nodict); } template <class T> @@ -216,7 +224,7 @@ void SslMuxProtocolFactory::established(Poller::shared_ptr poller, const Socket& const SslSocket *sslSock = dynamic_cast<const SslSocket*>(&s); if (sslSock) { - SslEstablished(poller, *sslSock, f, isClient, tcpNoDelay, nodict); + SslEstablished(poller, *sslSock, f, isClient, brokerTimer, maxNegotiateTime, tcpNoDelay, nodict); return; } @@ -239,7 +247,7 @@ void SslMuxProtocolFactory::established(Poller::shared_ptr poller, const Socket& boost::bind(&AsynchIOHandler::nobuffs, async, _1), boost::bind(&AsynchIOHandler::idle, async, _1)); - async->init(aio, 4); + async->init(aio, brokerTimer, maxNegotiateTime, 4); aio->start(poller); } diff --git a/cpp/src/qpid/sys/TCPIOPlugin.cpp b/cpp/src/qpid/sys/TCPIOPlugin.cpp index bd10a5555a..551440f954 100644 --- a/cpp/src/qpid/sys/TCPIOPlugin.cpp +++ b/cpp/src/qpid/sys/TCPIOPlugin.cpp @@ -36,14 +36,21 @@ namespace qpid { namespace sys { +class Timer; + class AsynchIOProtocolFactory : public ProtocolFactory { - const bool tcpNoDelay; boost::ptr_vector<Socket> listeners; boost::ptr_vector<AsynchAcceptor> acceptors; + Timer& brokerTimer; + uint32_t maxNegotiateTime; uint16_t listeningPort; + const bool tcpNoDelay; public: - AsynchIOProtocolFactory(const std::string& host, const std::string& port, int backlog, bool nodelay, bool shouldListen); + AsynchIOProtocolFactory(const std::string& host, const std::string& port, + int backlog, bool nodelay, + Timer& timer, uint32_t maxTime, + bool shouldListen); void accept(Poller::shared_ptr, ConnectionCodec::Factory*); void connect(Poller::shared_ptr, const std::string& host, const std::string& port, ConnectionCodec::Factory*, @@ -90,6 +97,7 @@ static class TCPIOPlugin : public Plugin { "", boost::lexical_cast<std::string>(opts.port), opts.connectionBacklog, opts.tcpNoDelay, + broker->getTimer(), opts.maxNegotiateTime, shouldListen)); if (shouldListen) { @@ -101,7 +109,12 @@ static class TCPIOPlugin : public Plugin { } } tcpPlugin; -AsynchIOProtocolFactory::AsynchIOProtocolFactory(const std::string& host, const std::string& port, int backlog, bool nodelay, bool shouldListen) : +AsynchIOProtocolFactory::AsynchIOProtocolFactory(const std::string& host, const std::string& port, + int backlog, bool nodelay, + Timer& timer, uint32_t maxTime, + bool shouldListen) : + brokerTimer(timer), + maxNegotiateTime(maxTime), tcpNoDelay(nodelay) { if (!shouldListen) { @@ -153,7 +166,7 @@ void AsynchIOProtocolFactory::established(Poller::shared_ptr poller, const Socke boost::bind(&AsynchIOHandler::nobuffs, async, _1), boost::bind(&AsynchIOHandler::idle, async, _1)); - async->init(aio, 4); + async->init(aio, brokerTimer, maxNegotiateTime, 4); aio->start(poller); } diff --git a/cpp/src/qpid/sys/Timer.cpp b/cpp/src/qpid/sys/Timer.cpp index 47752e4584..973c6bd8b7 100644 --- a/cpp/src/qpid/sys/Timer.cpp +++ b/cpp/src/qpid/sys/Timer.cpp @@ -35,7 +35,7 @@ TimerTask::TimerTask(Duration timeout, const std::string& n) : sortTime(AbsTime::FarFuture()), period(timeout), nextFireTime(AbsTime::now(), timeout), - cancelled(false) + state(WAITING) {} TimerTask::TimerTask(AbsTime time, const std::string& n) : @@ -43,7 +43,7 @@ TimerTask::TimerTask(AbsTime time, const std::string& n) : sortTime(AbsTime::FarFuture()), period(0), nextFireTime(time), - cancelled(false) + state(WAITING) {} TimerTask::~TimerTask() {} @@ -52,27 +52,48 @@ bool TimerTask::readyToFire() const { return !(nextFireTime > AbsTime::now()); } +bool TimerTask::prepareToFire() { + Monitor::ScopedLock l(stateMonitor); + if (state != CANCELLED) { + state = CALLING; + return true; + } else { + return false; + } +} + void TimerTask::fireTask() { - cancelled = true; fire(); } +void TimerTask::finishFiring() { + Monitor::ScopedLock l(stateMonitor); + if (state != CANCELLED) { + state = WAITING; + stateMonitor.notifyAll(); + } +} + // 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::restart() { + nextFireTime = max(nextFireTime, AbsTime(AbsTime::now(), period)); +} void TimerTask::cancel() { - ScopedLock<Mutex> l(callbackLock); - cancelled = true; + Monitor::ScopedLock l(stateMonitor); + while (state == CALLING) { + stateMonitor.wait(); + } + state = CANCELLED; } void TimerTask::setFired() { @@ -96,6 +117,22 @@ Timer::~Timer() stop(); } +class TimerTaskCallbackScope { + TimerTask& tt; +public: + explicit TimerTaskCallbackScope(TimerTask& t) : + tt(t) + {} + + operator bool() { + return !tt.prepareToFire(); + } + + ~TimerTaskCallbackScope() { + tt.finishFiring(); + } +}; + // TODO AStitcher 21/08/09 The threshholds for emitting warnings are a little arbitrary void Timer::run() { @@ -112,8 +149,8 @@ void Timer::run() AbsTime start(AbsTime::now()); Duration delay(t->sortTime, start); { - ScopedLock<Mutex> l(t->callbackLock); - if (t->cancelled) { + TimerTaskCallbackScope s(*t); + if (s) { { Monitor::ScopedUnlock u(monitor); drop(t); diff --git a/cpp/src/qpid/sys/Timer.h b/cpp/src/qpid/sys/Timer.h index fccb17dbc2..5731b8d977 100644 --- a/cpp/src/qpid/sys/Timer.h +++ b/cpp/src/qpid/sys/Timer.h @@ -40,6 +40,7 @@ class Timer; class TimerTask : public RefCounted { friend class Timer; + friend class TimerTaskCallbackScope; friend bool operator<(const boost::intrusive_ptr<TimerTask>&, const boost::intrusive_ptr<TimerTask>&); @@ -47,9 +48,11 @@ class TimerTask : public RefCounted { AbsTime sortTime; Duration period; AbsTime nextFireTime; - Mutex callbackLock; - volatile bool cancelled; + qpid::sys::Monitor stateMonitor; + enum {WAITING, CALLING, CANCELLED} state; + bool prepareToFire(); + void finishFiring(); bool readyToFire() const; void fireTask(); diff --git a/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.cpp b/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.cpp index 249b769051..29b91f3e7a 100644 --- a/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.cpp +++ b/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.cpp @@ -18,6 +18,7 @@ * under the License. * */ +#include <unistd.h> #include "qpid/sys/cyrus/CyrusSecurityLayer.h" #include <algorithm> #include "qpid/framing/reply_exceptions.h" diff --git a/cpp/src/qpid/sys/posix/LockFile.cpp b/cpp/src/qpid/sys/posix/LockFile.cpp index c1f1c37b47..9fdf83f1bd 100755 --- a/cpp/src/qpid/sys/posix/LockFile.cpp +++ b/cpp/src/qpid/sys/posix/LockFile.cpp @@ -46,7 +46,7 @@ LockFile::LockFile(const std::string& path_, bool 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 (fd < 0) throw ErrnoException("Cannot open lock file " + path, errno); if (::lockf(fd, F_TLOCK, 0) < 0) { ::close(fd); throw ErrnoException("Cannot lock " + path, errno); diff --git a/cpp/src/qpid/sys/posix/MemStat.cpp b/cpp/src/qpid/sys/posix/MemStat.cpp index 72c53e5886..2fbf119cab 100644 --- a/cpp/src/qpid/sys/posix/MemStat.cpp +++ b/cpp/src/qpid/sys/posix/MemStat.cpp @@ -20,6 +20,7 @@ */ #include "qpid/sys/MemStat.h" + #include <malloc.h> void qpid::sys::MemStat::loadMemInfo(qmf::org::apache::qpid::broker::Memory* object) @@ -35,4 +36,3 @@ void qpid::sys::MemStat::loadMemInfo(qmf::org::apache::qpid::broker::Memory* obj object->set_malloc_keepcost(info.keepcost); } - diff --git a/cpp/src/qpid/sys/posix/SocketAddress.cpp b/cpp/src/qpid/sys/posix/SocketAddress.cpp index a7049c1851..344bd28669 100644 --- a/cpp/src/qpid/sys/posix/SocketAddress.cpp +++ b/cpp/src/qpid/sys/posix/SocketAddress.cpp @@ -35,14 +35,16 @@ namespace sys { SocketAddress::SocketAddress(const std::string& host0, const std::string& port0) : host(host0), port(port0), - addrInfo(0) + addrInfo(0), + currentAddrInfo(0) { } SocketAddress::SocketAddress(const SocketAddress& sa) : host(sa.host), port(sa.port), - addrInfo(0) + addrInfo(0), + currentAddrInfo(0) { } diff --git a/cpp/src/qpid/sys/posix/SystemInfo.cpp b/cpp/src/qpid/sys/posix/SystemInfo.cpp index 540cc8bc91..2b1bbb97df 100755 --- a/cpp/src/qpid/sys/posix/SystemInfo.cpp +++ b/cpp/src/qpid/sys/posix/SystemInfo.cpp @@ -18,10 +18,11 @@ * */ +#include "qpid/log/Statement.h" #include "qpid/sys/SystemInfo.h" - #include "qpid/sys/posix/check.h" - +#include <set> +#include <arpa/inet.h> #include <sys/ioctl.h> #include <sys/utsname.h> #include <sys/types.h> // For FreeBSD @@ -33,6 +34,7 @@ #include <fstream> #include <sstream> #include <netdb.h> +#include <string.h> #ifndef HOST_NAME_MAX # define HOST_NAME_MAX 256 @@ -59,48 +61,100 @@ bool SystemInfo::getLocalHostname (Address &address) { return true; } -static const string LOCALHOST("127.0.0.1"); +static const string LOOPBACK("127.0.0.1"); static const string TCP("tcp"); +// Test IPv4 address for loopback +inline bool IN_IS_ADDR_LOOPBACK(const ::in_addr* a) { + return ((ntohl(a->s_addr) & 0xff000000) == 0x7f000000); +} + +inline bool isLoopback(const ::sockaddr* addr) { + switch (addr->sa_family) { + case AF_INET: return IN_IS_ADDR_LOOPBACK(&((const ::sockaddr_in*)(const void*)addr)->sin_addr); + case AF_INET6: return IN6_IS_ADDR_LOOPBACK(&((const ::sockaddr_in6*)(const void*)addr)->sin6_addr); + default: return false; + } +} + 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; - + if (isLoopback(ifap->ifa_addr)) 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); + case AF_INET6: { + // Ignore link local addresses as: + // * The scope id is illegal in URL syntax + // * Clients won't be able to use a link local address + // without adding their own (potentially different) scope id + sockaddr_in6* sa6 = (sockaddr_in6*)(ifap->ifa_addr); + if (IN6_IS_ADDR_LINKLOCAL(&sa6->sin6_addr)) break; + // Fallthrough } - 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: + 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); + addrList.push_back(Address(TCP, addr, port)); + break; + } + default: continue; } } - freeifaddrs(ifaddr); + ::freeifaddrs(ifaddr); if (addrList.empty()) { - addrList.push_back(Address(TCP, LOCALHOST, port)); + addrList.push_back(Address(TCP, LOOPBACK, port)); + } +} + +namespace { +struct AddrInfo { + struct addrinfo* ptr; + AddrInfo(const std::string& host) : ptr(0) { + ::addrinfo hints; + ::memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; // Allow both IPv4 and IPv6 + if (::getaddrinfo(host.c_str(), NULL, &hints, &ptr) != 0) + ptr = 0; + } + ~AddrInfo() { if (ptr) ::freeaddrinfo(ptr); } +}; +} + +bool SystemInfo::isLocalHost(const std::string& host) { + std::vector<Address> myAddrs; + getLocalIpAddresses(0, myAddrs); + std::set<string> localHosts; + for (std::vector<Address>::const_iterator i = myAddrs.begin(); i != myAddrs.end(); ++i) + localHosts.insert(i->host); + // Resolve host + AddrInfo ai(host); + if (!ai.ptr) return false; + for (struct addrinfo *res = ai.ptr; res != NULL; res = res->ai_next) { + if (isLoopback(res->ai_addr)) return true; + // Get string form of IP addr + char addr[NI_MAXHOST] = ""; + int error = ::getnameinfo(res->ai_addr, res->ai_addrlen, addr, NI_MAXHOST, NULL, 0, + NI_NUMERICHOST | NI_NUMERICSERV); + if (error) return false; + if (localHosts.find(addr) != localHosts.end()) return true; } + return false; } void SystemInfo::getSystemId (std::string &osName, diff --git a/cpp/src/qpid/sys/ssl/SslHandler.cpp b/cpp/src/qpid/sys/ssl/SslHandler.cpp index 67bf4ea893..8613059f28 100644 --- a/cpp/src/qpid/sys/ssl/SslHandler.cpp +++ b/cpp/src/qpid/sys/ssl/SslHandler.cpp @@ -19,9 +19,9 @@ * */ #include "qpid/sys/ssl/SslHandler.h" - #include "qpid/sys/ssl/SslIo.h" #include "qpid/sys/ssl/SslSocket.h" +#include "qpid/sys/Timer.h" #include "qpid/framing/AMQP_HighestVersion.h" #include "qpid/framing/ProtocolInitiation.h" #include "qpid/log/Statement.h" @@ -42,6 +42,24 @@ struct Buff : public SslIO::BufferBase { { delete [] bytes;} }; +struct ProtocolTimeoutTask : public sys::TimerTask { + SslHandler& handler; + std::string id; + + ProtocolTimeoutTask(const std::string& i, const Duration& timeout, SslHandler& h) : + TimerTask(timeout, "ProtocolTimeout"), + handler(h), + id(i) + {} + + void fire() { + // If this fires it means that we didn't negotiate the connection in the timeout period + // Schedule closing the connection for the io thread + QPID_LOG(error, "Connection " << id << " No protocol received closing"); + handler.abort(); + } +}; + SslHandler::SslHandler(std::string id, ConnectionCodec::Factory* f, bool _nodict) : identifier(id), aio(0), @@ -55,12 +73,18 @@ SslHandler::SslHandler(std::string id, ConnectionCodec::Factory* f, bool _nodict SslHandler::~SslHandler() { if (codec) codec->closed(); + if (timeoutTimerTask) + timeoutTimerTask->cancel(); delete codec; } -void SslHandler::init(SslIO* a, int numBuffs) { +void SslHandler::init(SslIO* a, Timer& timer, uint32_t maxTime, int numBuffs) { aio = a; + // Start timer for this connection + timeoutTimerTask = new ProtocolTimeoutTask(identifier, maxTime*TIME_MSEC, *this); + timer.add(timeoutTimerTask); + // Give connection some buffers to use for (int i = 0; i < numBuffs; i++) { aio->queueReadBuffer(new Buff); @@ -80,8 +104,10 @@ void SslHandler::write(const framing::ProtocolInitiation& data) } void SslHandler::abort() { - // TODO: can't implement currently as underlying functionality not implemented - // aio->requestCallback(boost::bind(&SslHandler::eof, this, _1)); + // Don't disconnect if we're already disconnecting + if (!readError) { + aio->requestCallback(boost::bind(&SslHandler::eof, this, _1)); + } } void SslHandler::activateOutput() { aio->notifyPendingWrite(); @@ -109,6 +135,9 @@ void SslHandler::readbuff(SslIO& , SslIO::BufferBase* buff) { framing::Buffer in(buff->bytes+buff->dataStart, buff->dataCount); framing::ProtocolInitiation protocolInit; if (protocolInit.decode(in)) { + // We've just got the protocol negotiation so we can cancel the timeout for that + timeoutTimerTask->cancel(); + decoded = in.getPosition(); QPID_LOG(debug, "RECV [" << identifier << "]: INIT(" << protocolInit << ")"); try { @@ -169,6 +198,10 @@ void SslHandler::idle(SslIO&){ if (isClient && codec == 0) { codec = factory->create(*this, identifier, getSecuritySettings(aio)); write(framing::ProtocolInitiation(codec->getVersion())); + // We've just sent the protocol negotiation so we can cancel the timeout for that + // This is not ideal, because we've not received anything yet, but heartbeats will + // be active soon + timeoutTimerTask->cancel(); return; } if (codec == 0) return; diff --git a/cpp/src/qpid/sys/ssl/SslHandler.h b/cpp/src/qpid/sys/ssl/SslHandler.h index 400fa317fd..74df2b7fb0 100644 --- a/cpp/src/qpid/sys/ssl/SslHandler.h +++ b/cpp/src/qpid/sys/ssl/SslHandler.h @@ -25,6 +25,8 @@ #include "qpid/sys/ConnectionCodec.h" #include "qpid/sys/OutputControl.h" +#include <boost/intrusive_ptr.hpp> + namespace qpid { namespace framing { @@ -32,6 +34,10 @@ namespace framing { } namespace sys { + +class Timer; +class TimerTask; + namespace ssl { class SslIO; @@ -46,6 +52,7 @@ class SslHandler : public OutputControl { bool readError; bool isClient; bool nodict; + boost::intrusive_ptr<sys::TimerTask> timeoutTimerTask; void write(const framing::ProtocolInitiation&); qpid::sys::SecuritySettings getSecuritySettings(SslIO* aio); @@ -53,7 +60,7 @@ class SslHandler : public OutputControl { public: SslHandler(std::string id, ConnectionCodec::Factory* f, bool nodict); ~SslHandler(); - void init(SslIO* a, int numBuffs); + void init(SslIO* a, Timer& timer, uint32_t maxTime, int numBuffs); void setClient() { isClient = true; } diff --git a/cpp/src/qpid/sys/ssl/SslIo.cpp b/cpp/src/qpid/sys/ssl/SslIo.cpp index 2a7cf16923..789c205ead 100644 --- a/cpp/src/qpid/sys/ssl/SslIo.cpp +++ b/cpp/src/qpid/sys/ssl/SslIo.cpp @@ -257,6 +257,18 @@ void SslIO::queueWriteClose() { DispatchHandle::rewatchWrite(); } +void SslIO::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(&SslIO::requestedCall, this, callback)); +} + +void SslIO::requestedCall(RequestCallback callback) { + assert(callback); + callback(*this); +} + /** Return a queued buffer if there are enough * to spare */ diff --git a/cpp/src/qpid/sys/ssl/SslIo.h b/cpp/src/qpid/sys/ssl/SslIo.h index c980d73831..b795594cd9 100644 --- a/cpp/src/qpid/sys/ssl/SslIo.h +++ b/cpp/src/qpid/sys/ssl/SslIo.h @@ -125,6 +125,7 @@ public: typedef boost::function2<void, SslIO&, const SslSocket&> ClosedCallback; typedef boost::function1<void, SslIO&> BuffersEmptyCallback; typedef boost::function1<void, SslIO&> IdleCallback; + typedef boost::function1<void, SslIO&> RequestCallback; private: @@ -159,6 +160,7 @@ public: void notifyPendingWrite(); void queueWriteClose(); bool writeQueueEmpty() { return writeQueue.empty(); } + void requestCallback(RequestCallback); BufferBase* getQueuedBuffer(); qpid::sys::SecuritySettings getSecuritySettings(); @@ -168,6 +170,7 @@ private: void readable(qpid::sys::DispatchHandle& handle); void writeable(qpid::sys::DispatchHandle& handle); void disconnected(qpid::sys::DispatchHandle& handle); + void requestedCall(RequestCallback); void close(qpid::sys::DispatchHandle& handle); }; diff --git a/cpp/src/qpid/sys/unordered_map.h b/cpp/src/qpid/sys/unordered_map.h index 5f7f9567c5..7ae71c3daa 100644 --- a/cpp/src/qpid/sys/unordered_map.h +++ b/cpp/src/qpid/sys/unordered_map.h @@ -23,6 +23,8 @@ #ifdef _MSC_VER # include <unordered_map> +#elif defined(__SUNPRO_CC) +# include <boost/tr1/unordered_map.hpp> #else # include <tr1/unordered_map> #endif /* _MSC_VER */ diff --git a/cpp/src/qpid/sys/windows/IocpPoller.cpp b/cpp/src/qpid/sys/windows/IocpPoller.cpp index 1805dd2cd8..c81cef87b0 100755 --- a/cpp/src/qpid/sys/windows/IocpPoller.cpp +++ b/cpp/src/qpid/sys/windows/IocpPoller.cpp @@ -96,6 +96,7 @@ void Poller::shutdown() { // Allow sloppy code to shut us down more than once. if (impl->isShutdown) return; + impl->isShutdown = true; ULONG_PTR key = 1; // Tell wait() it's a shutdown, not I/O PostQueuedCompletionStatus(impl->iocp, 0, key, 0); } @@ -110,7 +111,7 @@ bool Poller::interrupt(PollerHandle&) { } void Poller::run() { - do { + while (!impl->isShutdown) { Poller::Event event = this->wait(); // Handle shutdown @@ -124,7 +125,7 @@ void Poller::run() { // This should be impossible assert(false); } - } while (true); + } } void Poller::monitorHandle(PollerHandle& handle, Direction dir) { diff --git a/cpp/src/qpid/sys/windows/Socket.cpp b/cpp/src/qpid/sys/windows/Socket.cpp index b085f67539..a4374260cc 100644 --- a/cpp/src/qpid/sys/windows/Socket.cpp +++ b/cpp/src/qpid/sys/windows/Socket.cpp @@ -266,14 +266,17 @@ int Socket::getError() const void Socket::setTcpNoDelay() const { - int flag = 1; - int result = setsockopt(impl->fd, - IPPROTO_TCP, - TCP_NODELAY, - (char *)&flag, - sizeof(flag)); - QPID_WINSOCK_CHECK(result); + SOCKET& socket = impl->fd; nodelay = true; + if (socket != INVALID_SOCKET) { + int flag = 1; + int result = setsockopt(impl->fd, + IPPROTO_TCP, + TCP_NODELAY, + (char *)&flag, + sizeof(flag)); + QPID_WINSOCK_CHECK(result); + } } inline IOHandlePrivate* IOHandlePrivate::getImpl(const qpid::sys::IOHandle &h) diff --git a/cpp/src/qpid/sys/windows/SystemInfo.cpp b/cpp/src/qpid/sys/windows/SystemInfo.cpp index 4da440bdd4..cef78dcc60 100755 --- a/cpp/src/qpid/sys/windows/SystemInfo.cpp +++ b/cpp/src/qpid/sys/windows/SystemInfo.cpp @@ -23,9 +23,11 @@ # define _WIN32_WINNT 0x0501 #endif -#include "qpid/sys/IntegerTypes.h" #include "qpid/sys/SystemInfo.h" +#include "qpid/sys/IntegerTypes.h" +#include "qpid/Exception.h"
+#include <assert.h> #include <winsock2.h> #include <ws2tcpip.h> #include <windows.h> @@ -93,6 +95,12 @@ void SystemInfo::getLocalIpAddresses (uint16_t port, } } +bool SystemInfo::isLocalHost(const std::string& candidateHost) { + // FIXME aconway 2012-05-03: not implemented. + assert(0); + throw Exception("Not implemented: isLocalHost"); +} + void SystemInfo::getSystemId (std::string &osName, std::string &nodeName, std::string &release, diff --git a/cpp/src/qpid/types/Variant.cpp b/cpp/src/qpid/types/Variant.cpp index 6af06ede5d..d332fffa5e 100644 --- a/cpp/src/qpid/types/Variant.cpp +++ b/cpp/src/qpid/types/Variant.cpp @@ -103,31 +103,27 @@ class VariantImpl int64_t i64; float f; double d; - void* v;//variable width data + Uuid* uuid; + Variant::Map* map; + Variant::List* list; + std::string* string; } 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) { + const std::string& s = *value.string; + try { + T r = boost::lexical_cast<T>(s); //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 + //So check that and allow special case of negative zero + //else its a non-zero negative number so throw exception at end of function + if (std::numeric_limits<T>::is_signed || s.find('-') != 0 || r == 0) { + return r; } + } catch(const boost::bad_lexical_cast&) { } - throw InvalidConversion(QPID_MSG("Cannot convert " << *s)); + throw InvalidConversion(QPID_MSG("Cannot convert " << s)); } }; @@ -145,24 +141,24 @@ 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); } + : type(VAR_STRING), encoding(e) { value.string = new std::string(s); } +VariantImpl::VariantImpl(const Variant::Map& m) : type(VAR_MAP) { value.map = new Variant::Map(m); } +VariantImpl::VariantImpl(const Variant::List& l) : type(VAR_LIST) { value.list = new Variant::List(l); } +VariantImpl::VariantImpl(const Uuid& u) : type(VAR_UUID) { value.uuid = new Uuid(u); } VariantImpl::~VariantImpl() { switch (type) { case VAR_STRING: - delete reinterpret_cast<std::string*>(value.v); + delete value.string; break; case VAR_MAP: - delete reinterpret_cast<Variant::Map*>(value.v); + delete value.map; break; case VAR_LIST: - delete reinterpret_cast<Variant::List*>(value.v); + delete value.list; break; case VAR_UUID: - delete reinterpret_cast<Uuid*>(value.v); + delete value.uuid; break; default: break; @@ -221,7 +217,7 @@ bool VariantImpl::asBool() const 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)); + case VAR_STRING: return toBool(*value.string); default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_BOOL))); } } @@ -500,8 +496,8 @@ std::string VariantImpl::asString() const 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_STRING: return *value.string; + case VAR_UUID: return value.uuid->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))); @@ -510,7 +506,7 @@ std::string VariantImpl::asString() const Uuid VariantImpl::asUuid() const { switch(type) { - case VAR_UUID: return *reinterpret_cast<Uuid*>(value.v); + case VAR_UUID: return *value.uuid; default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_UUID))); } } @@ -531,10 +527,8 @@ bool VariantImpl::isEqualTo(VariantImpl& other) const 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_STRING: return *value.string == *other.value.string; + case VAR_UUID: return *value.uuid == *other.value.uuid; case VAR_LIST: return equal(asList(), other.asList()); case VAR_MAP: return equal(asMap(), other.asMap()); } @@ -545,7 +539,7 @@ bool VariantImpl::isEqualTo(VariantImpl& other) const const Variant::Map& VariantImpl::asMap() const { switch(type) { - case VAR_MAP: return *reinterpret_cast<Variant::Map*>(value.v); + case VAR_MAP: return *value.map; default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_MAP))); } } @@ -553,7 +547,7 @@ const Variant::Map& VariantImpl::asMap() const Variant::Map& VariantImpl::asMap() { switch(type) { - case VAR_MAP: return *reinterpret_cast<Variant::Map*>(value.v); + case VAR_MAP: return *value.map; default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_MAP))); } } @@ -561,7 +555,7 @@ Variant::Map& VariantImpl::asMap() const Variant::List& VariantImpl::asList() const { switch(type) { - case VAR_LIST: return *reinterpret_cast<Variant::List*>(value.v); + case VAR_LIST: return *value.list; default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_LIST))); } } @@ -569,7 +563,7 @@ const Variant::List& VariantImpl::asList() const Variant::List& VariantImpl::asList() { switch(type) { - case VAR_LIST: return *reinterpret_cast<Variant::List*>(value.v); + case VAR_LIST: return *value.list; default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_LIST))); } } @@ -577,7 +571,7 @@ Variant::List& VariantImpl::asList() std::string& VariantImpl::getString() { switch(type) { - case VAR_STRING: return *reinterpret_cast<std::string*>(value.v); + case VAR_STRING: return *value.string; default: throw InvalidConversion(QPID_MSG("Variant is not a string; use asString() if conversion is required.")); } } @@ -585,7 +579,7 @@ std::string& VariantImpl::getString() const std::string& VariantImpl::getString() const { switch(type) { - case VAR_STRING: return *reinterpret_cast<std::string*>(value.v); + case VAR_STRING: return *value.string; default: throw InvalidConversion(QPID_MSG("Variant is not a string; use asString() if conversion is required.")); } } diff --git a/cpp/src/qpid/xml/XmlExchange.cpp b/cpp/src/qpid/xml/XmlExchange.cpp index 01770e22a6..3fb11394d0 100644 --- a/cpp/src/qpid/xml/XmlExchange.cpp +++ b/cpp/src/qpid/xml/XmlExchange.cpp @@ -101,7 +101,7 @@ XmlBinding::XmlBinding(const std::string& key, const Queue::shared_ptr queue, co } -XmlExchange::XmlExchange(const string& _name, Manageable* _parent, Broker* b) : Exchange(_name, _parent, b) +XmlExchange::XmlExchange(const std::string& _name, Manageable* _parent, Broker* b) : Exchange(_name, _parent, b) { if (mgmtExchange != 0) mgmtExchange->set_type (typeName); @@ -115,7 +115,7 @@ XmlExchange::XmlExchange(const std::string& _name, bool _durable, mgmtExchange->set_type (typeName); } -bool XmlExchange::bind(Queue::shared_ptr queue, const string& bindingKey, const FieldTable* args) +bool XmlExchange::bind(Queue::shared_ptr queue, const std::string& bindingKey, const FieldTable* args) { // Federation uses bind for unbind and reorigin comands as well as for binds. @@ -123,9 +123,9 @@ bool XmlExchange::bind(Queue::shared_ptr queue, const string& bindingKey, const // 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; + std::string fedOp; + std::string fedTags; + std::string fedOrigin; if (args) fedOp = args->getAsString(qpidFedOp); @@ -146,7 +146,7 @@ bool XmlExchange::bind(Queue::shared_ptr queue, const string& bindingKey, const else if (fedOp.empty() || fedOp == fedOpBind) { - string queryText = args->getAsString("xquery"); + std::string queryText = args->getAsString("xquery"); RWlock::ScopedWlock l(lock); @@ -175,7 +175,7 @@ bool XmlExchange::bind(Queue::shared_ptr queue, const string& bindingKey, const return true; } -bool XmlExchange::unbind(Queue::shared_ptr queue, const string& bindingKey, const FieldTable* args) +bool XmlExchange::unbind(Queue::shared_ptr queue, const std::string& bindingKey, const FieldTable* args) { /* * When called directly, no qpidFedOrigin argument will be @@ -184,7 +184,7 @@ bool XmlExchange::unbind(Queue::shared_ptr queue, const string& bindingKey, cons * This is a bit of a hack - the binding needs the origin, but * this interface, as originally defined, would not supply one. */ - string fedOrigin; + std::string fedOrigin; if (args) fedOrigin = args->getAsString(qpidFedOrigin); RWlock::ScopedWlock l(lock); @@ -200,7 +200,7 @@ bool XmlExchange::unbind(Queue::shared_ptr queue, const string& bindingKey, cons bool XmlExchange::matches(Query& query, Deliverable& msg, const qpid::framing::FieldTable* args, bool parse_message_content) { - string msgContent; + std::string msgContent; try { QPID_LOG(trace, "matches: query is [" << UTF8(query->getQueryText()) << "]"); @@ -285,7 +285,7 @@ bool XmlExchange::matches(Query& query, Deliverable& msg, const qpid::framing::F void XmlExchange::route(Deliverable& msg) { - const string& routingKey = msg.getMessage().getRoutingKey(); + const std::string& routingKey = msg.getMessage().getRoutingKey(); const FieldTable* args = msg.getMessage().getApplicationHeaders(); PreRoute pr(msg, this); try { @@ -309,7 +309,7 @@ void XmlExchange::route(Deliverable& msg) } -bool XmlExchange::isBound(Queue::shared_ptr queue, const string* const bindingKey, const FieldTable* const) +bool XmlExchange::isBound(Queue::shared_ptr queue, const std::string* const bindingKey, const FieldTable* const) { RWlock::ScopedRlock l(lock); if (bindingKey) { @@ -345,7 +345,7 @@ void XmlExchange::propagateFedOp(const std::string& bindingKey, const std::strin if (args) { for (qpid::framing::FieldTable::ValueMap::const_iterator i=args->begin(); i != args->end(); ++i) { - const string& name(i->first); + const std::string& name(i->first); if (name != qpidFedOp && name != qpidFedTags && name != qpidFedOrigin) { @@ -358,7 +358,7 @@ void XmlExchange::propagateFedOp(const std::string& bindingKey, const std::strin 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) +bool XmlExchange::fedUnbind(const std::string& fedOrigin, const std::string& fedTags, Queue::shared_ptr queue, const std::string& bindingKey, const FieldTable* args) { RWlock::ScopedRlock l(lock); @@ -376,19 +376,19 @@ void XmlExchange::fedReorigin() 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()) { + if (std::find_if(p->begin(), p->end(), MatchOrigin(std::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()); + propagateFedOp( *key, std::string(), fedOpBind, std::string()); } } -XmlExchange::MatchOrigin::MatchOrigin(const string& _origin) : origin(_origin) {} +XmlExchange::MatchOrigin::MatchOrigin(const std::string& _origin) : origin(_origin) {} bool XmlExchange::MatchOrigin::operator()(XmlBinding::shared_ptr b) { @@ -396,7 +396,7 @@ bool XmlExchange::MatchOrigin::operator()(XmlBinding::shared_ptr b) } -XmlExchange::MatchQueueAndOrigin::MatchQueueAndOrigin(Queue::shared_ptr _queue, const string& _origin) : queue(_queue), origin(_origin) {} +XmlExchange::MatchQueueAndOrigin::MatchQueueAndOrigin(Queue::shared_ptr _queue, const std::string& _origin) : queue(_queue), origin(_origin) {} bool XmlExchange::MatchQueueAndOrigin::operator()(XmlBinding::shared_ptr b) { diff --git a/cpp/src/qpid/xml/XmlExchange.h b/cpp/src/qpid/xml/XmlExchange.h index 9ef389d9bf..1d4723f9c4 100644 --- a/cpp/src/qpid/xml/XmlExchange.h +++ b/cpp/src/qpid/xml/XmlExchange.h @@ -35,8 +35,6 @@ #include <vector> #include <string> -using namespace std; - namespace qpid { namespace broker { @@ -62,7 +60,7 @@ struct XmlBinding : public Exchange::Binding { class XmlExchange : public virtual Exchange { - typedef std::map<string, XmlBinding::vector> XmlBindingsMap; + typedef std::map<std::string, XmlBinding::vector> XmlBindingsMap; XmlBindingsMap bindingsMap; qpid::sys::RWlock lock; diff --git a/cpp/src/tests/CMakeLists.txt b/cpp/src/tests/CMakeLists.txt index 637442e128..29dfe3634f 100644 --- a/cpp/src/tests/CMakeLists.txt +++ b/cpp/src/tests/CMakeLists.txt @@ -149,6 +149,7 @@ set(unit_tests_to_build PollableCondition Variant ClientMessage + SystemInfo ${xml_tests} CACHE STRING "Which unit tests to build" ) diff --git a/cpp/src/tests/ExchangeTest.cpp b/cpp/src/tests/ExchangeTest.cpp index 2fb284741a..66a16b9178 100644 --- a/cpp/src/tests/ExchangeTest.cpp +++ b/cpp/src/tests/ExchangeTest.cpp @@ -33,6 +33,8 @@ #include <iostream> #include "MessageUtils.h" +using std::string; + using boost::intrusive_ptr; using namespace qpid::broker; using namespace qpid::framing; diff --git a/cpp/src/tests/FieldTable.cpp b/cpp/src/tests/FieldTable.cpp index c79d110ae4..8aeeb031b3 100644 --- a/cpp/src/tests/FieldTable.cpp +++ b/cpp/src/tests/FieldTable.cpp @@ -20,6 +20,7 @@ */ #include <iostream> #include <algorithm> +#include "qpid/sys/alloca.h" #include "qpid/framing/Array.h" #include "qpid/framing/FieldTable.h" #include "qpid/framing/FieldValue.h" @@ -29,6 +30,8 @@ using namespace qpid::framing; +using std::string; + namespace qpid { namespace tests { @@ -73,11 +76,11 @@ QPID_AUTO_TEST_CASE(testAssignment) FieldTable c; c = a; - char* buff = static_cast<char*>(::alloca(c.encodedSize())); - Buffer wbuffer(buff, c.encodedSize()); + std::vector<char> buff(c.encodedSize()); + Buffer wbuffer(&buff[0], c.encodedSize()); wbuffer.put(c); - Buffer rbuffer(buff, c.encodedSize()); + Buffer rbuffer(&buff[0], c.encodedSize()); rbuffer.get(d); BOOST_CHECK_EQUAL(c, d); BOOST_CHECK(string("CCCC") == c.getAsString("A")); diff --git a/cpp/src/tests/Makefile.am b/cpp/src/tests/Makefile.am index 66d2cdd5d5..cdc7429f3b 100644 --- a/cpp/src/tests/Makefile.am +++ b/cpp/src/tests/Makefile.am @@ -67,7 +67,7 @@ tmodule_LTLIBRARIES= TESTS+=unit_test check_PROGRAMS+=unit_test -unit_test_LDADD=-lboost_unit_test_framework \ +unit_test_LDADD=-lboost_unit_test_framework -lpthread \ $(lib_messaging) $(lib_broker) $(lib_console) $(lib_qmf2) unit_test_SOURCES= unit_test.cpp unit_test.h \ @@ -124,7 +124,8 @@ unit_test_SOURCES= unit_test.cpp unit_test.h \ Address.cpp \ ClientMessage.cpp \ Qmf2.cpp \ - BrokerOptions.cpp + BrokerOptions.cpp \ + SystemInfo.cpp if HAVE_XML unit_test_SOURCES+= XmlClientSessionTest.cpp @@ -150,7 +151,7 @@ endif # Test programs that are installed and therefore built as part of make, not make check qpidexectest_SCRIPTS += qpid-cpp-benchmark qpid-cluster-benchmark install_env.sh -EXTRA_DIST += qpid-cpp-benchmark install_env.sh +EXTRA_DIST += qpid-cpp-benchmark qpid-cluster-benchmark install_env.sh qpidexectest_PROGRAMS += receiver receiver_SOURCES = \ @@ -305,7 +306,7 @@ TESTS_ENVIRONMENT = \ system_tests = qpid-client-test quick_perftest quick_topictest run_header_test quick_txtest \ run_msg_group_tests TESTS += start_broker $(system_tests) python_tests stop_broker \ - ha_tests.py run_federation_tests run_federation_sys_tests \ + run_ha_tests run_federation_tests run_federation_sys_tests \ run_acl_tests run_cli_tests replication_test dynamic_log_level_test \ run_queue_flow_limit_tests ipv6_test @@ -352,7 +353,8 @@ EXTRA_DIST += \ run_queue_flow_limit_tests \ run_msg_group_tests \ ipv6_test \ - ha_tests.py \ + run_ha_tests \ + ha_tests.py \ test_env.ps1.in check_LTLIBRARIES += libdlclose_noop.la diff --git a/cpp/src/tests/MessageBuilderTest.cpp b/cpp/src/tests/MessageBuilderTest.cpp index c3d40ed88a..9adb133d40 100644 --- a/cpp/src/tests/MessageBuilderTest.cpp +++ b/cpp/src/tests/MessageBuilderTest.cpp @@ -40,7 +40,7 @@ class MockMessageStore : public NullMessageStore uint64_t id; boost::intrusive_ptr<PersistableMessage> expectedMsg; - string expectedData; + std::string expectedData; std::list<Op> ops; void checkExpectation(Op actual) @@ -58,7 +58,7 @@ class MockMessageStore : public NullMessageStore ops.push_back(STAGE); } - void expectAppendContent(PersistableMessage& msg, const string& data) + void expectAppendContent(PersistableMessage& msg, const std::string& data) { expectedMsg = &msg; expectedData = data; @@ -73,7 +73,7 @@ class MockMessageStore : public NullMessageStore } void appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg, - const string& data) + const std::string& data) { checkExpectation(APPEND); BOOST_CHECK_EQUAL(boost::static_pointer_cast<const PersistableMessage>(expectedMsg), msg); diff --git a/cpp/src/tests/MessageTest.cpp b/cpp/src/tests/MessageTest.cpp index 7d67c92b37..3a3ed061f9 100644 --- a/cpp/src/tests/MessageTest.cpp +++ b/cpp/src/tests/MessageTest.cpp @@ -24,7 +24,6 @@ #include "qpid/framing/MessageTransferBody.h" #include "qpid/framing/FieldValue.h" #include "qpid/framing/Uuid.h" -#include "qpid/sys/alloca.h" #include "unit_test.h" @@ -33,6 +32,8 @@ using namespace qpid::broker; using namespace qpid::framing; +using std::string; + namespace qpid { namespace tests { @@ -69,11 +70,11 @@ QPID_AUTO_TEST_CASE(testEncodeDecode) dProps->setDeliveryMode(PERSISTENT); BOOST_CHECK(msg->isPersistent()); - char* buff = static_cast<char*>(::alloca(msg->encodedSize())); - Buffer wbuffer(buff, msg->encodedSize()); + std::vector<char> buff(msg->encodedSize()); + Buffer wbuffer(&buff[0], msg->encodedSize()); msg->encode(wbuffer); - Buffer rbuffer(buff, msg->encodedSize()); + Buffer rbuffer(&buff[0], msg->encodedSize()); msg = new Message(); msg->decodeHeader(rbuffer); msg->decodeContent(rbuffer); diff --git a/cpp/src/tests/MessageUtils.h b/cpp/src/tests/MessageUtils.h index a1b140d484..991e2a2714 100644 --- a/cpp/src/tests/MessageUtils.h +++ b/cpp/src/tests/MessageUtils.h @@ -33,7 +33,7 @@ namespace tests { struct MessageUtils { - static boost::intrusive_ptr<Message> createMessage(const string& exchange="", const string& routingKey="", + static boost::intrusive_ptr<Message> createMessage(const std::string& exchange="", const std::string& routingKey="", const bool durable = false, const Uuid& messageId=Uuid(true), uint64_t contentSize = 0) { @@ -53,7 +53,7 @@ struct MessageUtils return msg; } - static void addContent(boost::intrusive_ptr<Message> msg, const string& data) + static void addContent(boost::intrusive_ptr<Message> msg, const std::string& data) { AMQFrame content((AMQContentBody(data))); msg->getFrames().append(content); diff --git a/cpp/src/tests/MessagingSessionTests.cpp b/cpp/src/tests/MessagingSessionTests.cpp index 968d55fd45..c8ee3aa401 100644 --- a/cpp/src/tests/MessagingSessionTests.cpp +++ b/cpp/src/tests/MessagingSessionTests.cpp @@ -1146,6 +1146,24 @@ QPID_AUTO_TEST_CASE(testLargeRoutingKey) BOOST_CHECK_THROW(fix.session.createReceiver(address), qpid::messaging::MessagingException); } +QPID_AUTO_TEST_CASE(testAlternateExchangeInLinkDeclare) +{ + MessagingFixture fix; + Sender s = fix.session.createSender("amq.direct/key"); + Receiver r1 = fix.session.createReceiver("amq.direct/key;{link:{x-declare:{alternate-exchange:'amq.fanout'}}}"); + Receiver r2 = fix.session.createReceiver("amq.fanout"); + + for (uint i = 0; i < 10; ++i) { + s.send(Message((boost::format("Message_%1%") % (i+1)).str()), true); + } + r1.close();//orphans all messages in subscription queue, which should then be routed through alternate exchange + for (uint i = 0; i < 10; ++i) { + Message received; + BOOST_CHECK(r2.fetch(received, Duration::SECOND)); + BOOST_CHECK_EQUAL(received.getContent(), (boost::format("Message_%1%") % (i+1)).str()); + } +} + QPID_AUTO_TEST_SUITE_END() }} // namespace qpid::tests diff --git a/cpp/src/tests/QueueTest.cpp b/cpp/src/tests/QueueTest.cpp index fb429ca981..3b4f74620f 100644 --- a/cpp/src/tests/QueueTest.cpp +++ b/cpp/src/tests/QueueTest.cpp @@ -31,6 +31,8 @@ #include "qpid/broker/QueueRegistry.h" #include "qpid/broker/NullMessageStore.h" #include "qpid/broker/ExpiryPolicy.h" +#include "qpid/framing/DeliveryProperties.h" +#include "qpid/framing/FieldTable.h" #include "qpid/framing/MessageTransferBody.h" #include "qpid/client/QueueOptions.h" #include "qpid/framing/AMQFrame.h" @@ -40,8 +42,11 @@ #include "qpid/broker/QueueFlowLimit.h" #include <iostream> -#include "boost/format.hpp" +#include <vector> +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> +using namespace std; using boost::intrusive_ptr; using namespace qpid; using namespace qpid::broker; @@ -83,7 +88,7 @@ public: Message& getMessage() { return *(msg.get()); } }; -intrusive_ptr<Message> create_message(std::string exchange, std::string routingKey, uint64_t ttl = 0) { +intrusive_ptr<Message> createMessage(std::string exchange, std::string routingKey, uint64_t ttl = 0) { intrusive_ptr<Message> msg(new Message()); AMQFrame method((MessageTransferBody(ProtocolVersion(), exchange, 0, 0))); AMQFrame header((AMQHeaderBody())); @@ -94,6 +99,16 @@ intrusive_ptr<Message> create_message(std::string exchange, std::string routingK return msg; } +intrusive_ptr<Message> contentMessage(string content) { + intrusive_ptr<Message> m(MessageUtils::createMessage()); + MessageUtils::addContent(m, content); + return m; +} + +string getContent(intrusive_ptr<Message> m) { + return m->getFrames().getContent(); +} + QPID_AUTO_TEST_SUITE(QueueTestSuite) QPID_AUTO_TEST_CASE(testAsyncMessage) { @@ -105,7 +120,7 @@ QPID_AUTO_TEST_CASE(testAsyncMessage) { //Test basic delivery: - intrusive_ptr<Message> msg1 = create_message("e", "A"); + intrusive_ptr<Message> msg1 = createMessage("e", "A"); msg1->enqueueAsync(queue, (MessageStore*)0);//this is done on enqueue which is not called from process queue->process(msg1); sleep(2); @@ -120,7 +135,7 @@ QPID_AUTO_TEST_CASE(testAsyncMessage) { QPID_AUTO_TEST_CASE(testAsyncMessageCount){ Queue::shared_ptr queue(new Queue("my_test_queue", true)); - intrusive_ptr<Message> msg1 = create_message("e", "A"); + intrusive_ptr<Message> msg1 = createMessage("e", "A"); msg1->enqueueAsync(queue, (MessageStore*)0);//this is done on enqueue which is not called from process queue->process(msg1); @@ -145,9 +160,9 @@ QPID_AUTO_TEST_CASE(testConsumers){ 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"); + intrusive_ptr<Message> msg1 = createMessage("e", "A"); + intrusive_ptr<Message> msg2 = createMessage("e", "B"); + intrusive_ptr<Message> msg3 = createMessage("e", "C"); queue->deliver(msg1); BOOST_CHECK(queue->dispatch(c1)); @@ -191,9 +206,9 @@ QPID_AUTO_TEST_CASE(testRegistry){ 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> msg1 = createMessage("e", "A"); + intrusive_ptr<Message> msg2 = createMessage("e", "B"); + intrusive_ptr<Message> msg3 = createMessage("e", "C"); intrusive_ptr<Message> received; queue->deliver(msg1); @@ -265,9 +280,9 @@ QPID_AUTO_TEST_CASE(testPersistLastNodeStanding){ 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> msg1 = createMessage("e", "A"); + intrusive_ptr<Message> msg2 = createMessage("e", "B"); + intrusive_ptr<Message> msg3 = createMessage("e", "C"); //enqueue 2 messages queue->deliver(msg1); @@ -291,9 +306,9 @@ 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"); + intrusive_ptr<Message> msg1 = createMessage("e", "A"); + intrusive_ptr<Message> msg2 = createMessage("e", "B"); + intrusive_ptr<Message> msg3 = createMessage("e", "C"); //enqueue 2 messages queue->deliver(msg1); @@ -317,9 +332,9 @@ 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"); + intrusive_ptr<Message> msg1 = createMessage("e", "A"); + intrusive_ptr<Message> msg2 = createMessage("e", "B"); + intrusive_ptr<Message> msg3 = createMessage("e", "C"); //enqueue 2 messages queue->deliver(msg1); @@ -431,10 +446,10 @@ QPID_AUTO_TEST_CASE(testLVQOrdering){ 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> msg1 = createMessage("e", "A"); + intrusive_ptr<Message> msg2 = createMessage("e", "B"); + intrusive_ptr<Message> msg3 = createMessage("e", "C"); + intrusive_ptr<Message> msg4 = createMessage("e", "D"); intrusive_ptr<Message> received; //set deliever match for LVQ a,b,c,a @@ -466,9 +481,9 @@ QPID_AUTO_TEST_CASE(testLVQOrdering){ 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"); + intrusive_ptr<Message> msg5 = createMessage("e", "A"); + intrusive_ptr<Message> msg6 = createMessage("e", "B"); + intrusive_ptr<Message> msg7 = createMessage("e", "C"); msg5->insertCustomProperty(key,"a"); msg6->insertCustomProperty(key,"b"); msg7->insertCustomProperty(key,"c"); @@ -498,8 +513,8 @@ QPID_AUTO_TEST_CASE(testLVQEmptyKey){ 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> msg1 = createMessage("e", "A"); + intrusive_ptr<Message> msg2 = createMessage("e", "B"); string key; args.getLVQKey(key); @@ -524,12 +539,12 @@ QPID_AUTO_TEST_CASE(testLVQAcquire){ 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"); + intrusive_ptr<Message> msg1 = createMessage("e", "A"); + intrusive_ptr<Message> msg2 = createMessage("e", "B"); + intrusive_ptr<Message> msg3 = createMessage("e", "C"); + intrusive_ptr<Message> msg4 = createMessage("e", "D"); + intrusive_ptr<Message> msg5 = createMessage("e", "F"); + intrusive_ptr<Message> msg6 = createMessage("e", "G"); //set deliever match for LVQ a,b,c,a @@ -601,8 +616,8 @@ QPID_AUTO_TEST_CASE(testLVQMultiQueue){ queue1->configure(args); queue2->configure(args); - intrusive_ptr<Message> msg1 = create_message("e", "A"); - intrusive_ptr<Message> msg2 = create_message("e", "A"); + intrusive_ptr<Message> msg1 = createMessage("e", "A"); + intrusive_ptr<Message> msg2 = createMessage("e", "A"); string key; args.getLVQKey(key); @@ -645,8 +660,8 @@ QPID_AUTO_TEST_CASE(testLVQRecover){ intrusive_ptr<Message> received; queue1->create(args); - intrusive_ptr<Message> msg1 = create_message("e", "A"); - intrusive_ptr<Message> msg2 = create_message("e", "A"); + intrusive_ptr<Message> msg1 = createMessage("e", "A"); + intrusive_ptr<Message> msg2 = createMessage("e", "A"); // 2 string key; args.getLVQKey(key); @@ -673,7 +688,7 @@ QPID_AUTO_TEST_CASE(testLVQRecover){ void addMessagesToQueue(uint count, Queue& queue, uint oddTtl = 200, uint evenTtl = 0) { for (uint i = 0; i < count; i++) { - intrusive_ptr<Message> m = create_message("exchange", "key", i % 2 ? oddTtl : evenTtl); + intrusive_ptr<Message> m = createMessage("exchange", "key", i % 2 ? oddTtl : evenTtl); m->computeExpiration(new broker::ExpiryPolicy); queue.deliver(m); } @@ -736,7 +751,7 @@ QPID_AUTO_TEST_CASE(testGroupsMultiConsumer) { std::string("b"), std::string("b"), std::string("b"), std::string("c"), std::string("c"), std::string("c") }; for (int i = 0; i < 9; ++i) { - intrusive_ptr<Message> msg = create_message("e", "A"); + intrusive_ptr<Message> msg = createMessage("e", "A"); msg->insertCustomProperty("GROUP-ID", groups[i]); msg->insertCustomProperty("MY-ID", i); queue->deliver(msg); @@ -883,7 +898,7 @@ QPID_AUTO_TEST_CASE(testGroupsMultiConsumer) { // Queue = a-2, // Owners= ^C3, - intrusive_ptr<Message> msg = create_message("e", "A"); + intrusive_ptr<Message> msg = createMessage("e", "A"); msg->insertCustomProperty("GROUP-ID", "a"); msg->insertCustomProperty("MY-ID", 9); queue->deliver(msg); @@ -894,7 +909,7 @@ QPID_AUTO_TEST_CASE(testGroupsMultiConsumer) { gotOne = queue->dispatch(c2); BOOST_CHECK( !gotOne ); - msg = create_message("e", "A"); + msg = createMessage("e", "A"); msg->insertCustomProperty("GROUP-ID", "b"); msg->insertCustomProperty("MY-ID", 10); queue->deliver(msg); @@ -925,7 +940,7 @@ QPID_AUTO_TEST_CASE(testGroupsMultiConsumerDefaults) { queue->configure(args); for (int i = 0; i < 3; ++i) { - intrusive_ptr<Message> msg = create_message("e", "A"); + intrusive_ptr<Message> msg = createMessage("e", "A"); // no "GROUP-ID" header msg->insertCustomProperty("MY-ID", i); queue->deliver(msg); @@ -988,7 +1003,7 @@ QPID_AUTO_TEST_CASE(testMultiQueueLastNode){ Queue::shared_ptr queue2(new Queue("queue2", true, &testStore )); queue2->create(args); - intrusive_ptr<Message> msg1 = create_message("e", "A"); + intrusive_ptr<Message> msg1 = createMessage("e", "A"); queue1->deliver(msg1); queue2->deliver(msg1); @@ -1004,7 +1019,7 @@ QPID_AUTO_TEST_CASE(testMultiQueueLastNode){ queue2->setLastNodeFailure(); BOOST_CHECK_EQUAL(testStore.enqCnt, 2u); - intrusive_ptr<Message> msg2 = create_message("e", "B"); + intrusive_ptr<Message> msg2 = createMessage("e", "B"); queue1->deliver(msg2); queue2->deliver(msg2); @@ -1019,7 +1034,7 @@ QPID_AUTO_TEST_CASE(testMultiQueueLastNode){ queue1->clearLastNodeFailure(); queue2->clearLastNodeFailure(); - intrusive_ptr<Message> msg3 = create_message("e", "B"); + intrusive_ptr<Message> msg3 = createMessage("e", "B"); queue1->deliver(msg3); queue2->deliver(msg3); BOOST_CHECK_EQUAL(testStore.enqCnt, 4u); @@ -1033,8 +1048,8 @@ QPID_AUTO_TEST_CASE(testMultiQueueLastNode){ * internal details not part of the queue abstraction. // check requeue 1 - intrusive_ptr<Message> msg4 = create_message("e", "C"); - intrusive_ptr<Message> msg5 = create_message("e", "D"); + intrusive_ptr<Message> msg4 = createMessage("e", "C"); + intrusive_ptr<Message> msg5 = createMessage("e", "D"); framing::SequenceNumber sequence(1); QueuedMessage qmsg1(queue1.get(), msg4, sequence); @@ -1081,8 +1096,8 @@ not requeued to the store. queue1->create(args); // check requeue 1 - intrusive_ptr<Message> msg1 = create_message("e", "C"); - intrusive_ptr<Message> msg2 = create_message("e", "D"); + intrusive_ptr<Message> msg1 = createMessage("e", "C"); + intrusive_ptr<Message> msg2 = createMessage("e", "D"); queue1->recover(msg1); @@ -1114,7 +1129,7 @@ simulate store exception going into last node standing queue1->configure(args); // check requeue 1 - intrusive_ptr<Message> msg1 = create_message("e", "C"); + intrusive_ptr<Message> msg1 = createMessage("e", "C"); queue1->deliver(msg1); testStore.createError(); @@ -1401,6 +1416,133 @@ QPID_AUTO_TEST_CASE(testFlowToDiskBlocking){ BOOST_CHECK_EQUAL(5u, tq9->getMessageCount()); } +QPID_AUTO_TEST_CASE(testSetPositionFifo) { + Queue::shared_ptr q(new Queue("my-queue", true)); + BOOST_CHECK_EQUAL(q->getPosition(), SequenceNumber(0)); + for (int i = 0; i < 10; ++i) + q->deliver(contentMessage(boost::lexical_cast<string>(i+1))); + + // Verify the front of the queue + TestConsumer::shared_ptr c(new TestConsumer("test", false)); // Don't acquire + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(1u, c->last.position); // Numbered from 1 + BOOST_CHECK_EQUAL("1", getContent(c->last.payload)); + // Verify the back of the queue + QueuedMessage qm; + BOOST_CHECK_EQUAL(10u, q->getPosition()); + BOOST_CHECK(q->find(q->getPosition(), qm)); // Back of the queue + BOOST_CHECK_EQUAL("10", getContent(qm.payload)); + BOOST_CHECK_EQUAL(10u, q->getMessageCount()); + + // Using setPosition to introduce a gap in sequence numbers. + q->setPosition(15); + BOOST_CHECK_EQUAL(10u, q->getMessageCount()); + BOOST_CHECK_EQUAL(15u, q->getPosition()); + BOOST_CHECK(q->find(10, qm)); // Back of the queue + BOOST_CHECK_EQUAL("10", getContent(qm.payload)); + q->deliver(contentMessage("16")); + c->setPosition(9); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(10u, c->last.position); + BOOST_CHECK_EQUAL("10", getContent(c->last.payload)); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(16u, c->last.position); + BOOST_CHECK_EQUAL("16", getContent(c->last.payload)); + + // Using setPosition to trunkcate the queue + q->setPosition(5); + BOOST_CHECK_EQUAL(5u, q->getMessageCount()); + q->deliver(contentMessage("6a")); + c->setPosition(4); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(5u, c->last.position); + BOOST_CHECK_EQUAL("5", getContent(c->last.payload)); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(6u, c->last.position); + BOOST_CHECK_EQUAL("6a", getContent(c->last.payload)); + BOOST_CHECK(!q->dispatch(c)); // No more messages. +} + +QPID_AUTO_TEST_CASE(testSetPositionLvq) { + Queue::shared_ptr q(new Queue("my-queue", true)); + string key="key"; + framing::FieldTable args; + args.setString("qpid.last_value_queue_key", "key"); + q->configure(args); + + const char* values[] = { "a", "b", "c", "a", "b", "c" }; + for (size_t i = 0; i < sizeof(values)/sizeof(values[0]); ++i) { + intrusive_ptr<Message> m = contentMessage(boost::lexical_cast<string>(i+1)); + m->insertCustomProperty(key, values[i]); + q->deliver(m); + } + BOOST_CHECK_EQUAL(3u, q->getMessageCount()); + // Verify the front of the queue + TestConsumer::shared_ptr c(new TestConsumer("test", false)); // Don't acquire + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(4u, c->last.position); // Numbered from 1 + BOOST_CHECK_EQUAL("4", getContent(c->last.payload)); + // Verify the back of the queue + QueuedMessage qm; + BOOST_CHECK_EQUAL(6u, q->getPosition()); + BOOST_CHECK(q->find(q->getPosition(), qm)); // Back of the queue + BOOST_CHECK_EQUAL("6", getContent(qm.payload)); + + q->setPosition(5); + c->setPosition(4); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(5u, c->last.position); // Numbered from 1 + BOOST_CHECK(!q->dispatch(c)); +} + +QPID_AUTO_TEST_CASE(testSetPositionPriority) { + Queue::shared_ptr q(new Queue("my-queue", true)); + framing::FieldTable args; + args.setInt("qpid.priorities", 10); + q->configure(args); + + const int priorities[] = { 1, 2, 3, 2, 1, 3 }; + for (size_t i = 0; i < sizeof(priorities)/sizeof(priorities[0]); ++i) { + intrusive_ptr<Message> m = contentMessage(boost::lexical_cast<string>(i+1)); + m->getFrames().getHeaders()->get<DeliveryProperties>(true) + ->setPriority(priorities[i]); + q->deliver(m); + } + + // Truncation removes messages in fifo order, not priority order. + q->setPosition(3); + TestConsumer::shared_ptr c(new TestConsumer("test", false)); // Browse in FIFO order + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(1u, c->last.position); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(2u, c->last.position); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(3u, c->last.position); + BOOST_CHECK(!q->dispatch(c)); + + intrusive_ptr<Message> m = contentMessage("4a"); + m->getFrames().getHeaders()->get<DeliveryProperties>(true) + ->setPriority(4); + q->deliver(m); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(4u, c->last.position); + BOOST_CHECK_EQUAL("4a", getContent(c->last.payload)); + + // But consumers see priority order + c.reset(new TestConsumer("test", true)); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(4u, c->last.position); + BOOST_CHECK_EQUAL("4a", getContent(c->last.payload)); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(3u, c->last.position); + BOOST_CHECK_EQUAL("3", getContent(c->last.payload)); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(2u, c->last.position); + BOOST_CHECK_EQUAL("2", getContent(c->last.payload)); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(1u, c->last.position); + BOOST_CHECK_EQUAL("1", getContent(c->last.payload)); +} QPID_AUTO_TEST_SUITE_END() diff --git a/cpp/src/tests/RangeSet.cpp b/cpp/src/tests/RangeSet.cpp index db3a964086..285f432bf7 100644 --- a/cpp/src/tests/RangeSet.cpp +++ b/cpp/src/tests/RangeSet.cpp @@ -29,63 +29,71 @@ namespace tests { QPID_AUTO_TEST_SUITE(RangeSetTestSuite) -typedef qpid::Range<int> TestRange; -typedef qpid::RangeSet<int> TestRangeSet; +typedef qpid::Range<int> TR; // Test Range +typedef RangeSet<int> TRSet; QPID_AUTO_TEST_CASE(testEmptyRange) { - TestRange r; + TR r; + BOOST_CHECK_EQUAL(r, TR(0,0)); BOOST_CHECK(r.empty()); BOOST_CHECK(!r.contains(0)); - // BOOST_CHECK(r.contiguous(0)); } QPID_AUTO_TEST_CASE(testRangeSetAddPoint) { - TestRangeSet r; + TRSet 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_MESSAGE(r.contains(TR(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); + BOOST_CHECK_MESSAGE(r.contains(TR(5,6)), r); + BOOST_CHECK_MESSAGE(!r.contains(TR(3,6)), r); r += 4; - BOOST_CHECK_MESSAGE(r.contains(TestRange(3,6)), r); + BOOST_CHECK_MESSAGE(r.contains(TR(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); + TRSet r; + r += TR(0,3); + BOOST_CHECK(r.contains(TR(0,3))); + BOOST_CHECK(r.contiguous()); + r += TR(4,6); + BOOST_CHECK(!r.contiguous()); + BOOST_CHECK_MESSAGE(r.contains(TR(4,6)), r); r += 3; - BOOST_CHECK_MESSAGE(r.contains(TestRange(0,6)), r); + BOOST_CHECK_MESSAGE(r.contains(TR(0,6)), r); BOOST_CHECK(r.front() == 0); BOOST_CHECK(r.back() == 6); + + // Merging additions + r = TRSet(0,3)+TR(5,6); + TRSet e(0,6); + BOOST_CHECK_EQUAL(r + TR(3,5), e); + BOOST_CHECK(e.contiguous()); + r = TRSet(0,5)+TR(10,15)+TR(20,25)+TR(30,35)+TR(40,45); + BOOST_CHECK_EQUAL(r + TR(11,37), TRSet(0,5)+TR(11,37)+TR(40,45)); } QPID_AUTO_TEST_CASE(testRangeSetAddSet) { - TestRangeSet r; - TestRangeSet s = TestRangeSet(0,3)+TestRange(5,10); + TRSet r; + TRSet s = TRSet(0,3)+TR(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 += TRSet(3,5) + TR(7,12) + 15; + BOOST_CHECK_EQUAL(r, TRSet(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)); + r += TR::makeClosed(6,10); + BOOST_CHECK_EQUAL(r, TRSet(6,11)); + r += TRSet(2,6)+8; + BOOST_CHECK_EQUAL(r, TRSet(2,11)); } QPID_AUTO_TEST_CASE(testRangeSetIterate) { - TestRangeSet r; - (((r += 1) += 10) += TestRange(4,7)) += 2; - BOOST_MESSAGE(r); + TRSet r = TRSet(1,3)+TR(4,7)+TR(10,11); 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); @@ -94,51 +102,51 @@ QPID_AUTO_TEST_CASE(testRangeSetIterate) { 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)); + BOOST_CHECK_EQUAL(TRSet(0,5)-3, TRSet(0,3)+TR(4,5)); + BOOST_CHECK_EQUAL(TRSet(1,5)-5, TRSet(1,5)); + BOOST_CHECK_EQUAL(TRSet(1,5)-0, TRSet(1,5)); - TestRangeSet r(TestRangeSet(0,5)+TestRange(10,15)+TestRange(20,25)); + TRSet r(TRSet(0,5)+TR(10,15)+TR(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)); + // TRs + BOOST_CHECK_EQUAL(r-TR(0,5), TRSet(10,15)+TR(20,25)); + BOOST_CHECK_EQUAL(r-TR(10,15), TRSet(0,5)+TR(20,25)); + BOOST_CHECK_EQUAL(r-TR(20,25), TRSet(0,5)+TR(10,15)); - BOOST_CHECK_EQUAL(r-TestRange(-5, 30), TestRangeSet()); + BOOST_CHECK_EQUAL(r-TR(-5, 30), TRSet()); - 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-TR(-5, 7), TRSet(10,15)+TR(20,25)); + BOOST_CHECK_EQUAL(r-TR(8,19), TRSet(0,5)+TR(20,25)); + BOOST_CHECK_EQUAL(r-TR(17,30), TRSet(0,5)+TR(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-TR(-5, 5), TRSet(10,15)+TR(20,25)); + BOOST_CHECK_EQUAL(r-TR(10,19), TRSet(0,5)+TR(20,25)); + BOOST_CHECK_EQUAL(r-TR(18,25), TRSet(0,5)+TR(10,15)); + BOOST_CHECK_EQUAL(r-TR(23,25), TRSet(0,5)+TR(10,15)+TR(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)); + BOOST_CHECK_EQUAL(r-TR(-3, 3), TRSet(3,5)+TR(10,15)+TR(20,25)); + BOOST_CHECK_EQUAL(r-TR(3, 7), TRSet(0,2)+TR(10,15)+TR(20,25)); + BOOST_CHECK_EQUAL(r-TR(3, 12), TRSet(0,3)+TR(12,15)+TR(20,25)); + BOOST_CHECK_EQUAL(r-TR(3, 22), TRSet(12,15)+TR(22,25)); + BOOST_CHECK_EQUAL(r-TR(12, 22), TRSet(0,5)+TR(10,11)+TR(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)); + BOOST_CHECK_EQUAL(r-(TRSet(-1,6)+TR(11,14)+TR(23,25)), + TRSet(10,11)+TR(14,15)+TR(20,23)); + // Split the ranges + BOOST_CHECK_EQUAL(r-(TRSet(2,3)+TR(11,13)+TR(21,23)), + TRSet(0,2)+TR(4,5)+ + TR(10,11)+TR(14,15)+ + TR(20,21)+TR(23,25)); + // Truncate the ranges + BOOST_CHECK_EQUAL(r-(TRSet(0,3)+TR(13,15)+TR(19,23)), + TRSet(3,5)+TR(10,13)+TR(20,23)); + // Remove multiple ranges with truncation + BOOST_CHECK_EQUAL(r-(TRSet(3,23)), TRSet(0,3)+TR(23,25)); + // Remove multiple ranges in middle + TRSet r2 = TRSet(0,5)+TR(10,15)+TR(20,25)+TR(30,35); + BOOST_CHECK_EQUAL(r2-TRSet(11,24), + TRSet(0,5)+TR(10,11)+TR(24,25)+TR(30,35)); } QPID_AUTO_TEST_SUITE_END() diff --git a/cpp/src/tests/ReplicationTest.cpp b/cpp/src/tests/ReplicationTest.cpp index 1219a6b59e..055f06579f 100644 --- a/cpp/src/tests/ReplicationTest.cpp +++ b/cpp/src/tests/ReplicationTest.cpp @@ -62,7 +62,7 @@ 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)); + transform(args.begin(), args.end(), argv.begin(), boost::bind(&std::string::c_str, _1)); qpid::broker::Broker::Options opts; qpid::Plugin::addOptions(opts); @@ -72,7 +72,7 @@ qpid::broker::Broker::Options getBrokerOpts(const std::vector<std::string>& args QPID_AUTO_TEST_CASE(testReplicationExchange) { - qpid::broker::Broker::Options brokerOpts(getBrokerOpts(list_of<string>("qpidd") + qpid::broker::Broker::Options brokerOpts(getBrokerOpts(list_of<std::string>("qpidd") ("--replication-exchange-name=qpid.replication"))); SessionFixture f(brokerOpts); diff --git a/cpp/src/tests/SystemInfo.cpp b/cpp/src/tests/SystemInfo.cpp new file mode 100644 index 0000000000..12d8d3dba8 --- /dev/null +++ b/cpp/src/tests/SystemInfo.cpp @@ -0,0 +1,52 @@ +/* + * + * 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/SystemInfo.h" +#include <boost/assign.hpp> + +using namespace std; +using namespace qpid::sys; +using namespace boost::assign; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(SystemInfoTestSuite) + +QPID_AUTO_TEST_CASE(TestIsLocalHost) { + // Test that local hostname and addresses are considered local + Address a; + BOOST_ASSERT(SystemInfo::getLocalHostname(a)); + BOOST_ASSERT(SystemInfo::isLocalHost(a.host)); + std::vector<Address> addrs; + SystemInfo::getLocalIpAddresses(0, addrs); + for (std::vector<Address>::iterator i = addrs.begin(); i != addrs.end(); ++i) + BOOST_ASSERT(SystemInfo::isLocalHost(i->host)); + // Check some non-local addresses + BOOST_ASSERT(!SystemInfo::isLocalHost("123.4.5.6")); + BOOST_ASSERT(!SystemInfo::isLocalHost("nosuchhost")); + BOOST_ASSERT(SystemInfo::isLocalHost("127.0.0.1")); + BOOST_ASSERT(SystemInfo::isLocalHost("::1")); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/cpp/src/tests/TestMessageStore.h b/cpp/src/tests/TestMessageStore.h index 20e0b755b2..0b63bc9c15 100644 --- a/cpp/src/tests/TestMessageStore.h +++ b/cpp/src/tests/TestMessageStore.h @@ -31,7 +31,7 @@ using namespace qpid::framing; namespace qpid { namespace tests { -typedef std::pair<string, boost::intrusive_ptr<PersistableMessage> > msg_queue_pair; +typedef std::pair<std::string, boost::intrusive_ptr<PersistableMessage> > msg_queue_pair; class TestMessageStore : public NullMessageStore { diff --git a/cpp/src/tests/TimerTest.cpp b/cpp/src/tests/TimerTest.cpp index 6a0a196f4e..fc5004dcb0 100644 --- a/cpp/src/tests/TimerTest.cpp +++ b/cpp/src/tests/TimerTest.cpp @@ -81,6 +81,8 @@ class TestTask : public TimerTask uint64_t difference = _abs64(expected - actual); #elif defined(_WIN32) uint64_t difference = labs(expected - actual); +#elif defined(__SUNPRO_CC) + uint64_t difference = llabs(expected - actual); #else uint64_t difference = abs(expected - actual); #endif diff --git a/cpp/src/tests/TopicExchangeTest.cpp b/cpp/src/tests/TopicExchangeTest.cpp index ff8931f9c9..d57951ea3f 100644 --- a/cpp/src/tests/TopicExchangeTest.cpp +++ b/cpp/src/tests/TopicExchangeTest.cpp @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +#include "qpid/broker/TopicKeyNode.h" #include "qpid/broker/TopicExchange.h" #include "unit_test.h" #include "test_tools.h" @@ -32,14 +33,15 @@ class TopicExchange::TopicExchangeTester { public: typedef std::vector<std::string> BindingVec; + typedef TopicKeyNode<TopicExchange::BindingKey> TestBindingNode; private: // binding node iterator that collects all routes that are bound - class TestFinder : public TopicExchange::BindingNode::TreeIterator { + class TestFinder : public TestBindingNode::TreeIterator { public: TestFinder(BindingVec& m) : bv(m) {}; ~TestFinder() {}; - bool visit(BindingNode& node) { + bool visit(TestBindingNode& node) { if (!node.bindings.bindingVector.empty()) bv.push_back(node.routePattern); return true; @@ -53,7 +55,7 @@ public: ~TopicExchangeTester() {}; bool addBindingKey(const std::string& bKey) { string routingPattern = normalize(bKey); - BindingKey *bk = bindingTree.addBindingKey(routingPattern); + BindingKey *bk = bindingTree.add(routingPattern); if (bk) { // push a dummy binding to mark this node as "non-leaf" bk->bindingVector.push_back(Binding::shared_ptr()); @@ -64,12 +66,12 @@ public: bool removeBindingKey(const std::string& bKey){ string routingPattern = normalize(bKey); - BindingKey *bk = bindingTree.getBindingKey(routingPattern); + BindingKey *bk = bindingTree.get(routingPattern); if (bk) { bk->bindingVector.pop_back(); if (bk->bindingVector.empty()) { // no more bindings - remove this node - bindingTree.removeBindingKey(routingPattern); + bindingTree.remove(routingPattern); } return true; } @@ -87,7 +89,7 @@ public: } private: - TopicExchange::BindingNode bindingTree; + TestBindingNode bindingTree; }; } // namespace broker diff --git a/cpp/src/tests/TxPublishTest.cpp b/cpp/src/tests/TxPublishTest.cpp index 152581e4ba..a636646035 100644 --- a/cpp/src/tests/TxPublishTest.cpp +++ b/cpp/src/tests/TxPublishTest.cpp @@ -69,9 +69,9 @@ QPID_AUTO_TEST_CASE(testPrepare) //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(std::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(std::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()); } diff --git a/cpp/src/tests/Uuid.cpp b/cpp/src/tests/Uuid.cpp index f85a297adc..aa9580e25e 100644 --- a/cpp/src/tests/Uuid.cpp +++ b/cpp/src/tests/Uuid.cpp @@ -19,7 +19,6 @@ #include "qpid/framing/Uuid.h" #include "qpid/framing/Buffer.h" #include "qpid/types/Uuid.h" -#include "qpid/sys/alloca.h" #include "unit_test.h" @@ -52,6 +51,11 @@ boost::array<uint8_t, 16> sample = {{0x1b, 0x4e, 0x28, 0xba, 0x2f, 0xa1, 0x11, const string sampleStr("1b4e28ba-2fa1-11d2-883f-b9a761bde3fb"); const string zeroStr("00000000-0000-0000-0000-000000000000"); +QPID_AUTO_TEST_CASE(testUuidStr) { + Uuid uuid(sampleStr); + BOOST_CHECK(uuid == sample); +} + QPID_AUTO_TEST_CASE(testUuidIstream) { Uuid uuid; istringstream in(sampleStr); @@ -92,12 +96,12 @@ QPID_AUTO_TEST_CASE(testUuidIOstream) { } QPID_AUTO_TEST_CASE(testUuidEncodeDecode) { - char* buff = static_cast<char*>(::alloca(Uuid::size())); - Buffer wbuf(buff, Uuid::size()); + std::vector<char> buff(Uuid::size()); + Buffer wbuf(&buff[0], Uuid::size()); Uuid uuid(sample.c_array()); uuid.encode(wbuf); - Buffer rbuf(buff, Uuid::size()); + Buffer rbuf(&buff[0], Uuid::size()); Uuid decoded; decoded.decode(rbuf); BOOST_CHECK_EQUAL(string(sample.begin(), sample.end()), diff --git a/cpp/src/tests/acl.py b/cpp/src/tests/acl.py index 720b3b4216..0e096a6f5b 100755 --- a/cpp/src/tests/acl.py +++ b/cpp/src/tests/acl.py @@ -285,10 +285,38 @@ class ACLTests(TestBase010): if (result): self.fail(result) + def test_nested_groups(self): + """ + Test nested groups + """ + + aclf = self.get_acl_file() + aclf.write('group user-consume martin@QPID ted@QPID\n') + aclf.write('group group2 kim@QPID user-consume rob@QPID \n') + aclf.write('acl allow anonymous all all \n') + aclf.write('acl allow group2 create queue \n') + aclf.write('acl deny all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + session = self.get_session('rob','rob') + try: + session.queue_declare(queue="rob_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"); + + + def test_user_realm(self): """ Test a user defined without a realm Ex. group admin rajith + Note: a user name without a realm is interpreted as a group name """ aclf = self.get_acl_file() aclf.write('group admin bob\n') # shouldn't be allowed @@ -297,7 +325,7 @@ class ACLTests(TestBase010): aclf.close() result = self.reload_acl() - if (result.find("Username 'bob' must contain a realm",0,len(result)) == -1): + if (result.find("not defined yet.",0,len(result)) == -1): self.fail(result) def test_allowed_chars_for_username(self): @@ -1509,6 +1537,124 @@ class ACLTests(TestBase010): #===================================== + # QMF Topic Exchange tests + #===================================== + + def test_qmf_topic_exchange_tests(self): + """ + Test using QMF method hooks into ACL logic + """ + aclf = self.get_acl_file() + aclf.write('# begin hack alert: allow anonymous to access the lookup debug functions\n') + aclf.write('acl allow-log anonymous create queue\n') + aclf.write('acl allow-log anonymous all exchange name=qmf.*\n') + aclf.write('acl allow-log anonymous all exchange name=amq.direct\n') + aclf.write('acl allow-log anonymous all exchange name=qpid.management\n') + aclf.write('acl allow-log anonymous access method name=*\n') + aclf.write('# end hack alert\n') + aclf.write('acl allow-log uPlain1@COMPANY publish exchange name=X routingkey=ab.cd.e\n') + aclf.write('acl allow-log uPlain2@COMPANY publish exchange name=X routingkey=.\n') + aclf.write('acl allow-log uStar1@COMPANY publish exchange name=X routingkey=a.*.b\n') + aclf.write('acl allow-log uStar2@COMPANY publish exchange name=X routingkey=*.x\n') + aclf.write('acl allow-log uStar3@COMPANY publish exchange name=X routingkey=x.x.*\n') + aclf.write('acl allow-log uHash1@COMPANY publish exchange name=X routingkey=a.#.b\n') + aclf.write('acl allow-log uHash2@COMPANY publish exchange name=X routingkey=a.#\n') + aclf.write('acl allow-log uHash3@COMPANY publish exchange name=X routingkey=#.a\n') + aclf.write('acl allow-log uHash4@COMPANY publish exchange name=X routingkey=a.#.b.#.c\n') + aclf.write('acl allow-log uMixed1@COMPANY publish exchange name=X routingkey=*.x.#.y\n') + aclf.write('acl allow-log uMixed2@COMPANY publish exchange name=X routingkey=a.#.b.*\n') + aclf.write('acl allow-log uMixed3@COMPANY publish exchange name=X routingkey=*.*.*.#\n') + + aclf.write('acl allow-log all publish exchange name=X routingkey=MN.OP.Q\n') + aclf.write('acl allow-log all publish exchange name=X routingkey=M.*.N\n') + aclf.write('acl allow-log all publish exchange name=X routingkey=M.#.N\n') + aclf.write('acl allow-log all publish exchange name=X routingkey=*.M.#.N\n') + + aclf.write('acl deny-log all all\n') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + # aclKey: "ab.cd.e" + self.LookupPublish("uPlain1@COMPANY", "X", "ab.cd.e", "allow-log") + self.LookupPublish("uPlain1@COMPANY", "X", "abx.cd.e", "deny-log") + self.LookupPublish("uPlain1@COMPANY", "X", "ab.cd", "deny-log") + self.LookupPublish("uPlain1@COMPANY", "X", "ab.cd..e.", "deny-log") + self.LookupPublish("uPlain1@COMPANY", "X", "ab.cd.e.", "deny-log") + self.LookupPublish("uPlain1@COMPANY", "X", ".ab.cd.e", "deny-log") + # aclKey: "." + self.LookupPublish("uPlain2@COMPANY", "X", ".", "allow-log") + + # aclKey: "a.*.b" + self.LookupPublish("uStar1@COMPANY", "X", "a.xx.b", "allow-log") + self.LookupPublish("uStar1@COMPANY", "X", "a.b", "deny-log") + # aclKey: "*.x" + self.LookupPublish("uStar2@COMPANY", "X", "y.x", "allow-log") + self.LookupPublish("uStar2@COMPANY", "X", ".x", "allow-log") + self.LookupPublish("uStar2@COMPANY", "X", "x", "deny-log") + # aclKey: "x.x.*" + self.LookupPublish("uStar3@COMPANY", "X", "x.x.y", "allow-log") + self.LookupPublish("uStar3@COMPANY", "X", "x.x.", "allow-log") + self.LookupPublish("uStar3@COMPANY", "X", "x.x", "deny-log") + self.LookupPublish("uStar3@COMPANY", "X", "q.x.y", "deny-log") + + # aclKey: "a.#.b" + self.LookupPublish("uHash1@COMPANY", "X", "a.b", "allow-log") + self.LookupPublish("uHash1@COMPANY", "X", "a.x.b", "allow-log") + self.LookupPublish("uHash1@COMPANY", "X", "a..x.y.zz.b", "allow-log") + self.LookupPublish("uHash1@COMPANY", "X", "a.b.", "deny-log") + self.LookupPublish("uHash1@COMPANY", "X", "q.x.b", "deny-log") + + # aclKey: "a.#" + self.LookupPublish("uHash2@COMPANY", "X", "a", "allow-log") + self.LookupPublish("uHash2@COMPANY", "X", "a.b", "allow-log") + self.LookupPublish("uHash2@COMPANY", "X", "a.b.c", "allow-log") + + # aclKey: "#.a" + self.LookupPublish("uHash3@COMPANY", "X", "a", "allow-log") + self.LookupPublish("uHash3@COMPANY", "X", "x.y.a", "allow-log") + + # aclKey: "a.#.b.#.c" + self.LookupPublish("uHash4@COMPANY", "X", "a.b.c", "allow-log") + self.LookupPublish("uHash4@COMPANY", "X", "a.x.b.y.c", "allow-log") + self.LookupPublish("uHash4@COMPANY", "X", "a.x.x.b.y.y.c", "allow-log") + + # aclKey: "*.x.#.y" + self.LookupPublish("uMixed1@COMPANY", "X", "a.x.y", "allow-log") + self.LookupPublish("uMixed1@COMPANY", "X", "a.x.p.qq.y", "allow-log") + self.LookupPublish("uMixed1@COMPANY", "X", "a.a.x.y", "deny-log") + self.LookupPublish("uMixed1@COMPANY", "X", "aa.x.b.c", "deny-log") + + # aclKey: "a.#.b.*" + self.LookupPublish("uMixed2@COMPANY", "X", "a.b.x", "allow-log") + self.LookupPublish("uMixed2@COMPANY", "X", "a.x.x.x.b.x", "allow-log") + + # aclKey: "*.*.*.#" + self.LookupPublish("uMixed3@COMPANY", "X", "x.y.z", "allow-log") + self.LookupPublish("uMixed3@COMPANY", "X", "x.y.z.a.b.c", "allow-log") + self.LookupPublish("uMixed3@COMPANY", "X", "x.y", "deny-log") + self.LookupPublish("uMixed3@COMPANY", "X", "x", "deny-log") + + # Repeat the keys with wildcard user spec + self.LookupPublish("uPlain1@COMPANY", "X", "MN.OP.Q", "allow-log") + self.LookupPublish("uStar1@COMPANY" , "X", "M.xx.N", "allow-log") + self.LookupPublish("uHash1@COMPANY" , "X", "M.N", "allow-log") + self.LookupPublish("uHash1@COMPANY" , "X", "M.x.N", "allow-log") + self.LookupPublish("uHash1@COMPANY" , "X", "M..x.y.zz.N", "allow-log") + self.LookupPublish("uMixed1@COMPANY", "X", "a.M.N", "allow-log") + self.LookupPublish("uMixed1@COMPANY", "X", "a.M.p.qq.N", "allow-log") + + self.LookupPublish("dev@QPID", "X", "MN.OP.Q", "allow-log") + self.LookupPublish("dev@QPID", "X", "M.xx.N", "allow-log") + self.LookupPublish("dev@QPID", "X", "M.N", "allow-log") + self.LookupPublish("dev@QPID", "X", "M.x.N", "allow-log") + self.LookupPublish("dev@QPID", "X", "M..x.y.zz.N", "allow-log") + self.LookupPublish("dev@QPID", "X", "a.M.N", "allow-log") + self.LookupPublish("dev@QPID", "X", "a.M.p.qq.N", "allow-log") + + #===================================== # Connection limits #===================================== diff --git a/cpp/src/tests/asyncstore.cmake b/cpp/src/tests/asyncstore.cmake index 795ea55cf7..51efa88bd3 100644 --- a/cpp/src/tests/asyncstore.cmake +++ b/cpp/src/tests/asyncstore.cmake @@ -45,7 +45,7 @@ if (UNIX) qpidbroker rt ) - add_test (Store_Perftools_Smoke_Test ${CMAKE_CURRENT_SOURCE_DIR}/storePerftools/storePerftoolsSmokeTest.sh) + add_test (jrnl2Perf_smoke_test ${CMAKE_CURRENT_SOURCE_DIR}/storePerftools/jrnl2Perf_smoke_test.sh) endif (UNIX) # Async store perf test (asyncPerf) @@ -77,4 +77,5 @@ if (UNIX) qpidtypes rt ) + add_test (asyncStorePerf_smoke_test ${CMAKE_CURRENT_SOURCE_DIR}/storePerftools/asyncStorePerf_smoke_test.sh) endif (UNIX) diff --git a/cpp/src/tests/brokertest.py b/cpp/src/tests/brokertest.py index 8255fbe9ac..aea4460e5a 100644 --- a/cpp/src/tests/brokertest.py +++ b/cpp/src/tests/brokertest.py @@ -76,18 +76,20 @@ def error_line(filename, n=1): 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.""" +def retry(function, timeout=10, delay=.01, max_delay=1): + """Call function until it returns a true value or timeout expires. + Double the delay for each retry up to max_delay. + Returns what function returns if true, None if timeout expires.""" deadline = time.time() + timeout - while not function(): + ret = None + while True: + ret = function() + if ret: return ret remaining = deadline - time.time() if remaining <= 0: return False delay = min(delay, remaining) time.sleep(delay) - delay *= 2 - return True + delay = min(delay*2, max_delay) class AtomicCounter: def __init__(self): @@ -239,15 +241,13 @@ def find_in_file(str, filename): class Broker(Popen): "A broker process. Takes care of start, stop and logging." _broker_count = 0 + _log_count = 0 - def __str__(self): return "Broker<%s %s>"%(self.name, self.pname) + def __str__(self): return "Broker<%s %s :%d>"%(self.log, self.pname, self.port()) 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 + self.log = "%03d:%s.log" % (Broker._log_count, self.name) + Broker._log_count += 1 def get_log(self): return os.path.abspath(self.log) @@ -298,9 +298,9 @@ class Broker(Popen): # 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))) + except ValueError, e: + raise Exception("Can't get port for broker %s (%s)%s: %s" % + (self.name, self.pname, error_line(self.log,5), e)) return self._port def unexpected(self,msg): @@ -572,7 +572,7 @@ class NumberedSender(Thread): """ Thread.__init__(self) cmd = ["qpid-send", - "--broker", url or broker.host_port(), + "--broker", url or broker.host_port(), "--address", "%s;{create:always}"%queue, "--connection-options", "{%s}"%(connection_options), "--content-stdin" @@ -647,6 +647,7 @@ class NumberedReceiver(Thread): self.error = None self.sender = sender self.received = 0 + self.queue = queue def read_message(self): n = int(self.receiver.stdout.readline()) @@ -657,7 +658,7 @@ class NumberedReceiver(Thread): m = self.read_message() while m != -1: self.receiver.assert_running() - assert(m <= self.received) # Check for missing messages + assert m <= self.received, "%s missing message %s>%s"%(self.queue, m, self.received) if (m == self.received): # Ignore duplicates self.received += 1 if self.sender: diff --git a/cpp/src/tests/cluster_test_logs.py b/cpp/src/tests/cluster_test_logs.py index 003d82c619..22f2470590 100755 --- a/cpp/src/tests/cluster_test_logs.py +++ b/cpp/src/tests/cluster_test_logs.py @@ -66,7 +66,8 @@ def filter_log(log): '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 - "org.apache.qpid.broker:bridge:" # ignore bridge index + "org.apache.qpid.broker:bridge:", # ignore bridge index + "closed connection" ]) # 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' diff --git a/cpp/src/tests/cluster_tests.py b/cpp/src/tests/cluster_tests.py index 8952f5de7b..3c96b252df 100755 --- a/cpp/src/tests/cluster_tests.py +++ b/cpp/src/tests/cluster_tests.py @@ -227,6 +227,18 @@ acl deny all all self.assertEqual("x", cluster[0].get_message("q").content) self.assertEqual("y", cluster[1].get_message("q").content) + def test_other_mech(self): + """Test using a mechanism other than PLAIN/ANONYMOUS for cluster update authentication. + Regression test for https://issues.apache.org/jira/browse/QPID-3849""" + sasl_config=os.path.join(self.rootdir, "sasl_config") + cluster = self.cluster(2, args=["--auth", "yes", "--sasl-config", sasl_config, + "--cluster-username=zig", + "--cluster-password=zig", + "--cluster-mechanism=DIGEST-MD5"]) + cluster[0].connect() + cluster.start() # Before the fix this broker falied to join the cluster. + cluster[2].connect() + 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. @@ -768,6 +780,35 @@ acl deny all all fetch(cluster[2]) + def _verify_federation(self, src_broker, src, dst_broker, dst, timeout=30): + """ Prove that traffic can pass between two federated brokers. + """ + tot_time = 0 + active = False + send_session = src_broker.connect().session() + sender = send_session.sender(src) + receive_session = dst_broker.connect().session() + receiver = receive_session.receiver(dst) + while not active and tot_time < timeout: + sender.send(Message("Hello from Source!")) + try: + receiver.fetch(timeout = 1) + receive_session.acknowledge() + # Get this far without Empty exception, and the link is good! + active = True + while True: + # Keep receiving msgs, as several may have accumulated + receiver.fetch(timeout = 1) + receive_session.acknowledge() + except Empty: + if not active: + tot_time += 1 + receiver.close() + receive_session.close() + sender.close() + send_session.close() + return active + def test_federation_failover(self): """ Verify that federation operates across failures occuring in a cluster. @@ -778,38 +819,6 @@ acl deny all all cluster to newly-added members """ - TIMEOUT = 30 - def verify(src_broker, src, dst_broker, dst, timeout=TIMEOUT): - """ Prove that traffic can pass from source fed broker to - destination fed broker - """ - tot_time = 0 - active = False - send_session = src_broker.connect().session() - sender = send_session.sender(src) - receive_session = dst_broker.connect().session() - receiver = receive_session.receiver(dst) - while not active and tot_time < timeout: - sender.send(Message("Hello from Source!")) - try: - receiver.fetch(timeout = 1) - receive_session.acknowledge() - # Get this far without Empty exception, and the link is good! - active = True - while True: - # Keep receiving msgs, as several may have accumulated - receiver.fetch(timeout = 1) - receive_session.acknowledge() - except Empty: - if not active: - tot_time += 1 - receiver.close() - receive_session.close() - sender.close() - send_session.close() - self.assertTrue(active, "Bridge failed to become active") - - # 2 node cluster source, 2 node cluster destination src_cluster = self.cluster(2, expect=EXPECT_EXIT_FAIL) src_cluster.ready(); @@ -848,43 +857,145 @@ acl deny all all self.assertEqual(result.status, 0, result) # check that traffic passes - verify(src_cluster[0], "srcQ", dst_cluster[0], "destQ") + assert self._verify_federation(src_cluster[0], "srcQ", dst_cluster[0], "destQ") # add src[2] broker to source cluster src_cluster.start(expect=EXPECT_EXIT_FAIL); src_cluster.ready(); - verify(src_cluster[2], "srcQ", dst_cluster[0], "destQ") + assert self._verify_federation(src_cluster[2], "srcQ", dst_cluster[0], "destQ") # Kill src[0]. dst[0] should fail over to src[1] src_cluster[0].kill() for b in src_cluster[1:]: b.ready() - verify(src_cluster[1], "srcQ", dst_cluster[0], "destQ") + assert self._verify_federation(src_cluster[1], "srcQ", dst_cluster[0], "destQ") # Kill src[1], dst[0] should fail over to src[2] src_cluster[1].kill() for b in src_cluster[2:]: b.ready() - verify(src_cluster[2], "srcQ", dst_cluster[0], "destQ") + assert self._verify_federation(src_cluster[2], "srcQ", dst_cluster[0], "destQ") # Kill dest[0], force failover to dest[1] dst_cluster[0].kill() for b in dst_cluster[1:]: b.ready() - verify(src_cluster[2], "srcQ", dst_cluster[1], "destQ") + assert self._verify_federation(src_cluster[2], "srcQ", dst_cluster[1], "destQ") # Add dest[2] # dest[1] syncs dest[2] to current remote state dst_cluster.start(expect=EXPECT_EXIT_FAIL); for b in dst_cluster[1:]: b.ready() - verify(src_cluster[2], "srcQ", dst_cluster[1], "destQ") + assert self._verify_federation(src_cluster[2], "srcQ", dst_cluster[1], "destQ") # Kill dest[1], force failover to dest[2] dst_cluster[1].kill() for b in dst_cluster[2:]: b.ready() - verify(src_cluster[2], "srcQ", dst_cluster[2], "destQ") + assert self._verify_federation(src_cluster[2], "srcQ", dst_cluster[2], "destQ") for i in range(2, len(src_cluster)): src_cluster[i].kill() for i in range(2, len(dst_cluster)): dst_cluster[i].kill() + def test_federation_multilink_failover(self): + """ + Verify that multi-link federation operates across failures occuring in + a cluster. + """ + + # 1 node cluster source, 1 node cluster destination + src_cluster = self.cluster(1, expect=EXPECT_EXIT_FAIL) + src_cluster.ready(); + dst_cluster = self.cluster(1, expect=EXPECT_EXIT_FAIL) + dst_cluster.ready(); + + # federate a direct binding across two separate links + + # first, create a direct exchange bound to two queues using different + # bindings + cmd = self.popen(["qpid-config", + "--broker", src_cluster[0].host_port(), + "add", "exchange", "direct", "FedX"], + EXPECT_EXIT_OK) + cmd.wait() + + cmd = self.popen(["qpid-config", + "--broker", dst_cluster[0].host_port(), + "add", "exchange", "direct", "FedX"], + EXPECT_EXIT_OK) + cmd.wait() + + cmd = self.popen(["qpid-config", + "--broker", dst_cluster[0].host_port(), + "add", "queue", "destQ1"], + EXPECT_EXIT_OK) + cmd.wait() + + cmd = self.popen(["qpid-config", + "--broker", dst_cluster[0].host_port(), + "bind", "FedX", "destQ1", "one"], + EXPECT_EXIT_OK) + cmd.wait() + + cmd = self.popen(["qpid-config", + "--broker", dst_cluster[0].host_port(), + "add", "queue", "destQ2"], + EXPECT_EXIT_OK) + cmd.wait() + + cmd = self.popen(["qpid-config", + "--broker", dst_cluster[0].host_port(), + "bind", "FedX", "destQ2", "two"], + EXPECT_EXIT_OK) + cmd.wait() + + # Create two separate links between the dst and source brokers, bind + # each to different keys + dst_cluster[0].startQmf() + dst_broker = dst_cluster[0].qmf_session.getObjects(_class="broker")[0] + + for _l in [("link1", "bridge1", "one"), + ("link2", "bridge2", "two")]: + result = dst_broker.create("link", _l[0], + {"host":src_cluster[0].host(), + "port":src_cluster[0].port()}, + False) + self.assertEqual(result.status, 0, result); + result = dst_broker.create("bridge", _l[1], + {"link":_l[0], + "src":"FedX", + "dest":"FedX", + "key":_l[2]}, False) + self.assertEqual(result.status, 0); + + # check that traffic passes + assert self._verify_federation(src_cluster[0], "FedX/one", dst_cluster[0], "destQ1") + assert self._verify_federation(src_cluster[0], "FedX/two", dst_cluster[0], "destQ2") + + # add new member, verify traffic + src_cluster.start(expect=EXPECT_EXIT_FAIL); + src_cluster.ready(); + + dst_cluster.start(expect=EXPECT_EXIT_FAIL); + dst_cluster.ready(); + + assert self._verify_federation(src_cluster[0], "FedX/one", dst_cluster[0], "destQ1") + assert self._verify_federation(src_cluster[0], "FedX/two", dst_cluster[0], "destQ2") + + src_cluster[0].kill() + for b in src_cluster[1:]: b.ready() + + assert self._verify_federation(src_cluster[1], "FedX/one", dst_cluster[0], "destQ1") + assert self._verify_federation(src_cluster[1], "FedX/two", dst_cluster[0], "destQ2") + + dst_cluster[0].kill() + for b in dst_cluster[1:]: b.ready() + + assert self._verify_federation(src_cluster[1], "FedX/one", dst_cluster[1], "destQ1") + assert self._verify_federation(src_cluster[1], "FedX/two", dst_cluster[1], "destQ2") + + for i in range(1, len(src_cluster)): src_cluster[i].kill() + for i in range(1, len(dst_cluster)): dst_cluster[i].kill() + + + # Some utility code for transaction tests XA_RBROLLBACK = 1 XA_RBTIMEOUT = 2 diff --git a/cpp/src/tests/federation.py b/cpp/src/tests/federation.py index 7d613b98ce..dcd074eda9 100755 --- a/cpp/src/tests/federation.py +++ b/cpp/src/tests/federation.py @@ -23,6 +23,7 @@ from qpid.testlib import TestBase010 from qpid.datatypes import Message from qpid.queue import Empty from qpid.util import URL +import qpid.messaging from time import sleep, time @@ -94,6 +95,11 @@ class FederationTests(TestBase010): break self._brokers.append(_b) + # add a new-style messaging connection to each broker + for _b in self._brokers: + _b.connection = qpid.messaging.Connection(_b.url) + _b.connection.open() + def _teardown_brokers(self): """ Un-does _setup_brokers() """ @@ -103,7 +109,7 @@ class FederationTests(TestBase010): if not _b.client_session.error(): _b.client_session.close(timeout=10) _b.client_conn.close(timeout=10) - + _b.connection.close() def test_bridge_create_and_close(self): self.startQmf(); @@ -127,18 +133,28 @@ class FederationTests(TestBase010): self.verify_cleanup() def test_pull_from_exchange(self): + """ This test uses an alternative method to manage links and bridges + via the broker object. + """ 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, result) - link = qmf.getObjects(_class="link")[0] - result = link.bridge(False, "amq.direct", "amq.fanout", "my-key", "", "", False, False, False, 0) + # create link + link_args = {"host":self.remote_host(), "port":self.remote_port(), "durable":False, + "authMechanism":"PLAIN", "username":"guest", "password":"guest", + "transport":"tcp"} + result = broker.create("link", "test-link-1", link_args, False) self.assertEqual(result.status, 0, result) + link = qmf.getObjects(_class="link")[0] + # create bridge + bridge_args = {"link":"test-link-1", "src":"amq.direct", "dest":"amq.fanout", + "key":"my-key"} + result = broker.create("bridge", "test-bridge-1", bridge_args, False); + self.assertEqual(result.status, 0, result) bridge = qmf.getObjects(_class="bridge")[0] #setup queue to receive messages from local broker @@ -164,9 +180,11 @@ class FederationTests(TestBase010): self.fail("Got unexpected message in queue: " + extra.body) except Empty: None - result = bridge.close() + + result = broker.delete("bridge", "test-bridge-1", {}) self.assertEqual(result.status, 0, result) - result = link.close() + + result = broker.delete("link", "test-link-1", {}) self.assertEqual(result.status, 0, result) self.verify_cleanup() @@ -376,6 +394,9 @@ class FederationTests(TestBase010): for i in range(1, 11): try: msg = queue.get(timeout=5) + mp = msg.get("message_properties").application_headers + self.assertEqual(mp.__class__, dict) + self.assertEqual(mp['x-qpid.trace'], 'REMOTE') # check that the federation-tag override works self.assertEqual("Message %d" % i, msg.body) except Empty: self.fail("Failed to find expected message containing 'Message %d'" % i) @@ -2153,3 +2174,433 @@ class FederationTests(TestBase010): self.verify_cleanup() + def test_multilink_direct(self): + """ Verify that two distinct links can be created between federated + brokers. + """ + self.startQmf() + qmf = self.qmf + self._setup_brokers() + src_broker = self._brokers[0] + dst_broker = self._brokers[1] + + # create a direct exchange on each broker + for _b in [src_broker, dst_broker]: + _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!") + + # create destination queues + for _q in [("HiQ", "high"), ("MedQ", "medium"), ("LoQ", "low")]: + dst_broker.client_session.queue_declare(queue=_q[0], auto_delete=True) + dst_broker.client_session.exchange_bind(queue=_q[0], exchange="fedX.direct", binding_key=_q[1]) + + # create two connections, one for high priority traffic + for _q in ["HiPri", "Traffic"]: + result = dst_broker.qmf_object.create("link", _q, + {"host":src_broker.host, + "port":src_broker.port}, + False) + self.assertEqual(result.status, 0); + + links = qmf.getObjects(_broker=dst_broker.qmf_broker, _class="link") + for _l in links: + if _l.name == "HiPri": + hi_link = _l + elif _l.name == "Traffic": + data_link = _l + else: + self.fail("Unexpected Link found: " + _l.name) + + # now create a route for messages sent with key "high" to use the + # hi_link + result = dst_broker.qmf_object.create("bridge", "HiPriBridge", + {"link":hi_link.name, + "src":"fedX.direct", + "dest":"fedX.direct", + "key":"high"}, False) + self.assertEqual(result.status, 0); + + + # create routes for the "medium" and "low" links to use the normal + # data_link + for _b in [("MediumBridge", "medium"), ("LowBridge", "low")]: + result = dst_broker.qmf_object.create("bridge", _b[0], + {"link":data_link.name, + "src":"fedX.direct", + "dest":"fedX.direct", + "key":_b[1]}, False) + self.assertEqual(result.status, 0); + + # now wait for the links to become operational + for _l in [hi_link, data_link]: + expire_time = time() + 30 + while _l.state != "Operational" and time() < expire_time: + _l.update() + self.assertEqual(_l.state, "Operational", "Link failed to become operational") + + # verify each link uses a different connection + self.assertNotEqual(hi_link.connectionRef, data_link.connectionRef, + "Different links using the same connection") + + hi_conn = qmf.getObjects(_broker=dst_broker.qmf_broker, + _objectId=hi_link.connectionRef)[0] + data_conn = qmf.getObjects(_broker=dst_broker.qmf_broker, + _objectId=data_link.connectionRef)[0] + + + # send hi data, verify only goes over hi link + + r_ssn = dst_broker.connection.session() + hi_receiver = r_ssn.receiver("HiQ"); + med_receiver = r_ssn.receiver("MedQ"); + low_receiver = r_ssn.receiver("LoQ"); + + for _c in [hi_conn, data_conn]: + _c.update() + self.assertEqual(_c.msgsToClient, 0, "Unexpected messages received") + + s_ssn = src_broker.connection.session() + hi_sender = s_ssn.sender("fedX.direct/high") + med_sender = s_ssn.sender("fedX.direct/medium") + low_sender = s_ssn.sender("fedX.direct/low") + + try: + hi_sender.send(qpid.messaging.Message(content="hi priority")) + msg = hi_receiver.fetch(timeout=10) + r_ssn.acknowledge() + self.assertEqual(msg.content, "hi priority"); + except: + self.fail("Hi Pri message failure") + + hi_conn.update() + data_conn.update() + self.assertEqual(hi_conn.msgsToClient, 1, "Expected 1 hi pri message") + self.assertEqual(data_conn.msgsToClient, 0, "Expected 0 data messages") + + # send low and medium, verify it does not go over hi link + + try: + med_sender.send(qpid.messaging.Message(content="medium priority")) + msg = med_receiver.fetch(timeout=10) + r_ssn.acknowledge() + self.assertEqual(msg.content, "medium priority"); + except: + self.fail("Medium Pri message failure") + + hi_conn.update() + data_conn.update() + self.assertEqual(hi_conn.msgsToClient, 1, "Expected 1 hi pri message") + self.assertEqual(data_conn.msgsToClient, 1, "Expected 1 data message") + + try: + low_sender.send(qpid.messaging.Message(content="low priority")) + msg = low_receiver.fetch(timeout=10) + r_ssn.acknowledge() + self.assertEqual(msg.content, "low priority"); + except: + self.fail("Low Pri message failure") + + hi_conn.update() + data_conn.update() + self.assertEqual(hi_conn.msgsToClient, 1, "Expected 1 hi pri message") + self.assertEqual(data_conn.msgsToClient, 2, "Expected 2 data message") + + # cleanup + + for _b in qmf.getObjects(_broker=dst_broker.qmf_broker,_class="bridge"): + result = _b.close() + self.assertEqual(result.status, 0) + + for _l in qmf.getObjects(_broker=dst_broker.qmf_broker,_class="link"): + result = _l.close() + self.assertEqual(result.status, 0) + + for _q in [("HiQ", "high"), ("MedQ", "medium"), ("LoQ", "low")]: + dst_broker.client_session.exchange_unbind(queue=_q[0], exchange="fedX.direct", binding_key=_q[1]) + dst_broker.client_session.queue_delete(queue=_q[0]) + + for _b in [src_broker, dst_broker]: + _b.client_session.exchange_delete(exchange="fedX.direct") + + self._teardown_brokers() + + self.verify_cleanup() + + + def test_multilink_shared_queue(self): + """ Verify that two distinct links can be created between federated + brokers. + """ + self.startQmf() + qmf = self.qmf + self._setup_brokers() + src_broker = self._brokers[0] + dst_broker = self._brokers[1] + + # create a topic exchange on the destination broker + dst_broker.client_session.exchange_declare(exchange="fedX.topic", type="topic") + self.assertEqual(dst_broker.client_session.exchange_query(name="fedX.topic").type, + "topic", "exchange_declare failed!") + + # create a destination queue + dst_broker.client_session.queue_declare(queue="destQ", auto_delete=True) + dst_broker.client_session.exchange_bind(queue="destQ", exchange="fedX.topic", binding_key="srcQ") + + # create a single source queue + src_broker.client_session.queue_declare(queue="srcQ", auto_delete=True) + + # create two connections + for _q in ["Link1", "Link2"]: + result = dst_broker.qmf_object.create("link", _q, + {"host":src_broker.host, + "port":src_broker.port}, + False) + self.assertEqual(result.status, 0); + + links = qmf.getObjects(_broker=dst_broker.qmf_broker, _class="link") + self.assertEqual(len(links), 2) + + # now create two "parallel" queue routes from the source queue to the + # destination exchange. + result = dst_broker.qmf_object.create("bridge", "Bridge1", + {"link":"Link1", + "src":"srcQ", + "dest":"fedX.topic", + "srcIsQueue": True}, + False) + self.assertEqual(result.status, 0); + result = dst_broker.qmf_object.create("bridge", "Bridge2", + {"link":"Link2", + "src":"srcQ", + "dest":"fedX.topic", + "srcIsQueue": True}, + False) + self.assertEqual(result.status, 0); + + + # now wait for the links to become operational + for _l in links: + expire_time = time() + 30 + while _l.state != "Operational" and time() < expire_time: + _l.update() + self.assertEqual(_l.state, "Operational", "Link failed to become operational") + + # verify each link uses a different connection + self.assertNotEqual(links[0].connectionRef, links[1].connectionRef, + "Different links using the same connection") + + conn1 = qmf.getObjects(_broker=dst_broker.qmf_broker, + _objectId=links[0].connectionRef)[0] + conn2 = qmf.getObjects(_broker=dst_broker.qmf_broker, + _objectId=links[1].connectionRef)[0] + + # verify messages sent to the queue are pulled by each connection + + r_ssn = dst_broker.connection.session() + receiver = r_ssn.receiver("destQ"); + + for _c in [conn1, conn2]: + _c.update() + self.assertEqual(_c.msgsToClient, 0, "Unexpected messages received") + + s_ssn = src_broker.connection.session() + sender = s_ssn.sender("srcQ") + + try: + for x in range(5): + sender.send(qpid.messaging.Message(content="hello")) + for x in range(5): + msg = receiver.fetch(timeout=10) + self.assertEqual(msg.content, "hello"); + r_ssn.acknowledge() + except: + self.fail("Message failure") + + # expect messages to be split over each connection. + conn1.update() + conn2.update() + self.assertNotEqual(conn1.msgsToClient, 0, "No messages sent") + self.assertNotEqual(conn2.msgsToClient, 0, "No messages sent") + self.assertEqual(conn2.msgsToClient + conn1.msgsToClient, 5, + "Expected 5 messages total") + + for _b in qmf.getObjects(_broker=dst_broker.qmf_broker,_class="bridge"): + result = _b.close() + self.assertEqual(result.status, 0) + + for _l in qmf.getObjects(_broker=dst_broker.qmf_broker,_class="link"): + result = _l.close() + self.assertEqual(result.status, 0) + + dst_broker.client_session.exchange_unbind(queue="destQ", exchange="fedX.topic", binding_key="srcQ") + dst_broker.client_session.exchange_delete(exchange="fedX.topic") + + self._teardown_brokers() + + self.verify_cleanup() + + + def test_dynamic_direct_shared_queue(self): + """ + Route Topology: + + +<--- B1 + B0 <---+<--- B2 + +<--- B3 + """ + 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!") + + # Create 2 links per each source broker (1,2,3) to the downstream + # broker 0: + for _b in range(1,4): + for _l in ["dynamic", "queue"]: + result = self._brokers[0].qmf_object.create( "link", + "Link-%d-%s" % (_b, _l), + {"host":self._brokers[_b].host, + "port":self._brokers[_b].port}, False) + self.assertEqual(result.status, 0) + + # create queue on source brokers for use by the dynamic route + self._brokers[_b].client_session.queue_declare(queue="fedSrcQ", exclusive=False, auto_delete=True) + + for _l in range(1,4): + # for each dynamic link, create a dynamic bridge for the "fedX.direct" + # exchanges, using the fedSrcQ on each upstream source broker + result = self._brokers[0].qmf_object.create("bridge", + "Bridge-%d-dynamic" % _l, + {"link":"Link-%d-dynamic" % _l, + "src":"fedX.direct", + "dest":"fedX.direct", + "dynamic":True, + "queue":"fedSrcQ"}, False) + self.assertEqual(result.status, 0) + + # create a queue route that shares the queue used by the dynamic route + result = self._brokers[0].qmf_object.create("bridge", + "Bridge-%d-queue" % _l, + {"link":"Link-%d-queue" % _l, + "src":"fedSrcQ", + "dest":"fedX.direct", + "srcIsQueue":True}, False) + 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 B0, bound to "spudboy" + self._brokers[0].client_session.queue_declare(queue="DestQ", exclusive=True, auto_delete=True) + self._brokers[0].client_session.exchange_bind(queue="DestQ", exchange="fedX.direct", binding_key="spudboy") + + # subscribe to messages arriving on B2's queue + self.subscribe(self._brokers[0].client_session, queue="DestQ", destination="f1") + queue = self._brokers[0].client_session.incoming("f1") + + # wait until the binding key has propagated to each broker + + binding_counts = [1, 1, 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() + + for _b in range(1,4): + # send 3 msgs from each source broker + for i in range(3): + dp = self._brokers[_b].client_session.delivery_properties(routing_key="spudboy") + self._brokers[_b].client_session.message_transfer(destination="fedX.direct", message=Message(dp, "Message_drp %d" % i)) + + # get exactly 9 (3 per broker) on B0 + for i in range(9): + msg = queue.get(timeout=5) + + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected message in queue: " + extra.body) + except Empty: None + + # verify that messages went across every link + for _l in qmf.getObjects(_broker=self._brokers[0].qmf_broker, + _class="link"): + for _c in qmf.getObjects(_broker=self._brokers[0].qmf_broker, + _objectId=_l.connectionRef): + self.assertNotEqual(_c.msgsToClient, 0, "Messages did not pass over link as expected.") + + # cleanup + + self._brokers[0].client_session.exchange_unbind(queue="DestQ", exchange="fedX.direct", binding_key="spudboy") + self._brokers[0].client_session.message_cancel(destination="f1") + self._brokers[0].client_session.queue_delete(queue="DestQ") + + 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() + diff --git a/cpp/src/tests/ha_tests.py b/cpp/src/tests/ha_tests.py index 827cb7dca9..d25281eed5 100755 --- a/cpp/src/tests/ha_tests.py +++ b/cpp/src/tests/ha_tests.py @@ -18,59 +18,123 @@ # under the License. # -import os, signal, sys, time, imp, re, subprocess, glob, random, logging, shutil, math -from qpid.messaging import Message, NotFound, ConnectionError, ReceiverError, Connection +import os, signal, sys, time, imp, re, subprocess, glob, random, logging, shutil, math, unittest +import traceback +from qpid.messaging import Message, NotFound, ConnectionError, ReceiverError, Connection, Timeout, Disposition, REJECTED from qpid.datatypes import uuid4 from brokertest import * from threading import Thread, Lock, Condition -from logging import getLogger, WARN, ERROR, DEBUG +from logging import getLogger, WARN, ERROR, DEBUG, INFO from qpidtoollibs import BrokerAgent +from uuid import UUID log = getLogger(__name__) +class QmfAgent(object): + """Access to a QMF broker agent.""" + def __init__(self, address, **kwargs): + self._connection = Connection.establish( + address, client_properties={"qpid.ha-admin":1}, **kwargs) + self._agent = BrokerAgent(self._connection) + assert self._agent.getHaBroker(), "HA module not loaded in broker at: %s"%(address) + + def __getattr__(self, name): + a = getattr(self._agent, name) + return a + +class Credentials(object): + """SASL credentials: username, password, and mechanism""" + def __init__(self, username, password, mechanism): + (self.username, self.password, self.mechanism) = (username, password, mechanism) + + def __str__(self): return "Credentials%s"%(self.tuple(),) + + def tuple(self): return (self.username, self.password, self.mechanism) + + def add_user(self, url): return "%s/%s@%s"%(self.username, self.password, url) + class HaBroker(Broker): - def __init__(self, test, args=[], broker_url=None, ha_cluster=True, - ha_replicate="all", **kwargs): + """Start a broker with HA enabled + @param client_cred: (user, password, mechanism) for admin clients started by the HaBroker. + """ + def __init__(self, test, args=[], brokers_url=None, ha_cluster=True, ha_replicate="all", + client_credentials=None, **kwargs): assert BrokerTest.ha_lib, "Cannot locate HA plug-in" args = copy(args) args += ["--load-module", BrokerTest.ha_lib, - "--log-enable=info+", "--log-enable=debug+:ha::", + "--log-enable=debug+:ha::", # FIXME aconway 2012-02-13: workaround slow link failover. "--link-maintenace-interval=0.1", "--ha-cluster=%s"%ha_cluster] if ha_replicate is not None: args += [ "--ha-replicate=%s"%ha_replicate ] - if broker_url: args.extend([ "--ha-brokers", broker_url ]) + if brokers_url: args += [ "--ha-brokers-url", brokers_url ] Broker.__init__(self, test, args, **kwargs) - self.commands=os.getenv("PYTHON_COMMANDS") - assert os.path.isdir(self.commands) + self.qpid_ha_path=os.path.join(os.getenv("PYTHON_COMMANDS"), "qpid-ha") + assert os.path.exists(self.qpid_ha_path) + self.qpid_config_path=os.path.join(os.getenv("PYTHON_COMMANDS"), "qpid-config") + assert os.path.exists(self.qpid_config_path) getLogger().setLevel(ERROR) # Hide expected WARNING log messages from failover. - - def promote(self): - assert os.system("%s/qpid-ha promote -b %s"%(self.commands, self.host_port())) == 0 - - def set_client_url(self, url): - assert os.system( - "%s/qpid-ha set --public-brokers=%s -b %s"%(self.commands, url,self.host_port())) == 0 - - def set_broker_url(self, url): - assert os.system( - "%s/qpid-ha set --brokers=%s -b %s"%(self.commands, url, self.host_port())) == 0 - - def replicate(self, from_broker, queue): - assert os.system( - "%s/qpid-ha replicate -b %s %s %s"%(self.commands, self.host_port(), from_broker, queue)) == 0 + self.qpid_ha_script=import_script(self.qpid_ha_path) + self._agent = None + self.client_credentials = client_credentials + + def __str__(self): return Broker.__str__(self) + + def qpid_ha(self, args): + cred = self.client_credentials + url = self.host_port() + if cred: + url =cred.add_user(url) + args = args + ["--sasl-mechanism", cred.mechanism] + self.qpid_ha_script.main_except(["", "-b", url]+args) + + def promote(self): self.qpid_ha(["promote"]) + def set_client_url(self, url): self.qpid_ha(["set", "--public-url", url]) + def set_brokers_url(self, url): self.qpid_ha(["set", "--brokers-url", url]) + def replicate(self, from_broker, queue): self.qpid_ha(["replicate", from_broker, queue]) + + def agent(self): + if not self._agent: + cred = self.client_credentials + if cred: + self._agent = QmfAgent(cred.add_user(self.host_port()), sasl_mechanisms=cred.mechanism) + else: + self._agent = QmfAgent(self.host_port()) + return self._agent + + def ha_status(self): + hb = self.agent().getHaBroker() + hb.update() + return hb.status + + def wait_status(self, status): + def try_get_status(): + # Ignore ConnectionError, the broker may not be up yet. + try: return self.ha_status() == status; + except ConnectionError: return False + assert retry(try_get_status, timeout=20), "%s status != %r"%(self, status) + + # FIXME aconway 2012-05-01: do direct python call to qpid-config code. + def qpid_config(self, args): + assert subprocess.call( + [self.qpid_config_path, "--broker", self.host_port()]+args) == 0 def config_replicate(self, from_broker, queue): - assert os.system( - "%s/qpid-config --broker=%s add queue --start-replica %s %s"%(self.commands, self.host_port(), from_broker, queue)) == 0 + self.qpid_config(["add", "queue", "--start-replica", from_broker, queue]) def config_declare(self, queue, replication): - assert os.system( - "%s/qpid-config --broker=%s add queue %s --replicate %s"%(self.commands, self.host_port(), queue, replication)) == 0 + self.qpid_config(["add", "queue", queue, "--replicate", replication]) def connect_admin(self, **kwargs): - return Broker.connect(self, client_properties={"qpid.ha-admin":1}, **kwargs) + cred = self.client_credentials + if cred: + return Broker.connect( + self, client_properties={"qpid.ha-admin":1}, + username=cred.username, password=cred.password, sasl_mechanisms=cred.mechanism, + **kwargs) + else: + return Broker.connect(self, client_properties={"qpid.ha-admin":1}, **kwargs) def wait_backup(self, address): """Wait for address to become valid on a backup broker.""" @@ -78,6 +142,14 @@ class HaBroker(Broker): try: wait_address(bs, address) finally: bs.connection.close() + def assert_browse(self, queue, expected, **kwargs): + """Verify queue contents by browsing.""" + bs = self.connect().session() + try: + wait_address(bs, queue) + assert_browse_retry(bs, queue, expected, **kwargs) + finally: bs.connection.close() + def assert_browse_backup(self, queue, expected, **kwargs): """Combines wait_backup and assert_browse_retry.""" bs = self.connect_admin().session() @@ -86,33 +158,70 @@ class HaBroker(Broker): assert_browse_retry(bs, queue, expected, **kwargs) finally: bs.connection.close() + def assert_connect_fail(self): + try: + self.connect() + self.test.fail("Expected ConnectionError") + except ConnectionError: pass + + def try_connect(self): + try: return self.connect() + except ConnectionError: return None + class HaCluster(object): _cluster_count = 0 - def __init__(self, test, n, **kwargs): + def __init__(self, test, n, promote=True, **kwargs): """Start a cluster of n brokers""" self.test = test - self._brokers = [ HaBroker(test, name="broker%s-%s"%(HaCluster._cluster_count, i), **kwargs) for i in xrange(n)] + self.kwargs = kwargs + self._brokers = [] + self.id = HaCluster._cluster_count + self.broker_id = 0 HaCluster._cluster_count += 1 - self.url = ",".join([b.host_port() for b in self]) - for b in self: b.set_broker_url(self.url) + for i in xrange(n): self.start(False) + self.update_urls() self[0].promote() + def next_name(self): + name="cluster%s-%s"%(self.id, self.broker_id) + self.broker_id += 1 + return name + + def start(self, update_urls=True, args=[]): + """Start a new broker in the cluster""" + b = HaBroker(self.test, name=self.next_name(), **self.kwargs) + self._brokers.append(b) + if update_urls: self.update_urls() + return b + + def update_urls(self): + self.url = ",".join([b.host_port() for b in self]) + if len(self) > 1: # No failover addresses on a 1 cluster. + for b in self: b.set_brokers_url(self.url) + def connect(self, i): """Connect with reconnect_urls""" return self[i].connect(reconnect=True, reconnect_urls=self.url.split(",")) - def kill(self, i): + def kill(self, i, promote_next=True): """Kill broker i, promote broker i+1""" - self[i].kill() self[i].expect = EXPECT_EXIT_FAIL - self[(i+1) % len(self)].promote() + self[i].kill() + if promote_next: self[(i+1) % len(self)].promote() + + def restart(self, i): + """Start a broker with the same port, name and data directory. It will get + a separate log file: foo.n.log""" + b = self._brokers[i] + self._brokers[i] = HaBroker( + self.test, name=b.name, port=b.port(), brokers_url=self.url, + **self.kwargs) - def bounce(self, i): + def bounce(self, i, promote_next=True): """Stop and restart a broker in a cluster.""" - self.kill(i) - b = self[i] - self._brokers[i] = HaBroker(self.test, name=b.name, port=b.port(), broker_url=self.url) + self.kill(i, promote_next) + self.restart(i) # Behave like a list of brokers. def __len__(self): return len(self._brokers) @@ -128,12 +237,12 @@ def wait_address(session, address): except NotFound: return False assert retry(check), "Timed out waiting for address %s"%(address) -def assert_missing(session, address): - """Assert that the address is _not_ valid""" +def valid_address(session, address): + """Test if an address is valid""" try: session.receiver(address) - self.fail("Expected NotFound: %s"%(address)) - except NotFound: pass + return True + except NotFound: return False class ReplicationTests(BrokerTest): """Correctness tests for HA replication.""" @@ -180,7 +289,7 @@ class ReplicationTests(BrokerTest): self.assert_browse_retry(b, prefix+"q1", ["1", "4"]) self.assert_browse_retry(b, prefix+"q2", []) # configuration only - assert_missing(b, prefix+"q3") + assert not valid_address(b, prefix+"q3") b.sender(prefix+"e1").send(Message(prefix+"e1")) # Verify binds with replicate=all self.assert_browse_retry(b, prefix+"q1", ["1", "4", prefix+"e1"]) b.sender(prefix+"e2").send(Message(prefix+"e2")) # Verify binds with replicate=configuration @@ -195,7 +304,7 @@ class ReplicationTests(BrokerTest): # Create config, send messages before starting the backup, to test catch-up replication. setup(p, "1", primary) - backup = HaBroker(self, name="backup", broker_url=primary.host_port()) + backup = HaBroker(self, name="backup", brokers_url=primary.host_port()) # Create config, send messages after starting the backup, to test steady-state replication. setup(p, "2", primary) @@ -233,10 +342,10 @@ class ReplicationTests(BrokerTest): s = p.sender("q;{create:always}") for m in [str(i) for i in range(0,10)]: s.send(m) s.sync() - backup1 = HaBroker(self, name="backup1", broker_url=primary.host_port()) + backup1 = HaBroker(self, name="backup1", brokers_url=primary.host_port()) for m in [str(i) for i in range(10,20)]: s.send(m) s.sync() - backup2 = HaBroker(self, name="backup2", broker_url=primary.host_port()) + backup2 = HaBroker(self, name="backup2", brokers_url=primary.host_port()) for m in [str(i) for i in range(20,30)]: s.send(m) s.sync() @@ -276,7 +385,7 @@ class ReplicationTests(BrokerTest): """Verify that backups rejects connections and that fail-over works in python client""" primary = HaBroker(self, name="primary", expect=EXPECT_EXIT_FAIL) primary.promote() - backup = HaBroker(self, name="backup", broker_url=primary.host_port()) + backup = HaBroker(self, name="backup", brokers_url=primary.host_port()) # Check that backup rejects normal connections try: backup.connect().session() @@ -294,14 +403,15 @@ class ReplicationTests(BrokerTest): primary.kill() assert retry(lambda: not is_running(primary.pid)) backup.promote() - self.assert_browse_retry(s, "q", ["foo"]) + sender.send("bar") + self.assert_browse_retry(s, "q", ["foo", "bar"]) c.close() def test_failover_cpp(self): """Verify that failover works in the C++ client.""" primary = HaBroker(self, name="primary", expect=EXPECT_EXIT_FAIL) primary.promote() - backup = HaBroker(self, name="backup", broker_url=primary.host_port()) + backup = HaBroker(self, name="backup", brokers_url=primary.host_port()) url="%s,%s"%(primary.host_port(), backup.host_port()) primary.connect().session().sender("q;{create:always}") backup.wait_backup("q") @@ -344,6 +454,7 @@ class ReplicationTests(BrokerTest): def test_standalone_queue_replica(self): """Test replication of individual queues outside of cluster mode""" + getLogger().setLevel(ERROR) # Hide expected WARNING log messages from failover. primary = HaBroker(self, name="primary", ha_cluster=False) pc = primary.connect() ps = pc.session().sender("q;{create:always}") @@ -393,7 +504,7 @@ class ReplicationTests(BrokerTest): """Verify that we replicate to an LVQ correctly""" primary = HaBroker(self, name="primary") primary.promote() - backup = HaBroker(self, name="backup", broker_url=primary.host_port()) + backup = HaBroker(self, name="backup", brokers_url=primary.host_port()) s = primary.connect().session().sender("lvq; {create:always, node:{x-declare:{arguments:{'qpid.last_value_queue_key':lvq-key}}}}") def send(key,value): s.send(Message(content=value,properties={"lvq-key":key})) for kv in [("a","a-1"),("b","b-1"),("a","a-2"),("a","a-3"),("c","c-1"),("c","c-2")]: @@ -410,7 +521,7 @@ class ReplicationTests(BrokerTest): """Test replication with the ring queue policy""" primary = HaBroker(self, name="primary") primary.promote() - backup = HaBroker(self, name="backup", broker_url=primary.host_port()) + backup = HaBroker(self, name="backup", brokers_url=primary.host_port()) s = primary.connect().session().sender("q; {create:always, node:{x-declare:{arguments:{'qpid.policy_type':ring, 'qpid.max_count':5}}}}") for i in range(10): s.send(Message(str(i))) backup.assert_browse_backup("q", [str(i) for i in range(5,10)]) @@ -419,18 +530,20 @@ class ReplicationTests(BrokerTest): """Test replication with the reject queue policy""" primary = HaBroker(self, name="primary") primary.promote() - backup = HaBroker(self, name="backup", broker_url=primary.host_port()) + backup = HaBroker(self, name="backup", brokers_url=primary.host_port()) s = primary.connect().session().sender("q; {create:always, node:{x-declare:{arguments:{'qpid.policy_type':reject, 'qpid.max_count':5}}}}") try: for i in range(10): s.send(Message(str(i)), sync=False) except qpid.messaging.exceptions.TargetCapacityExceeded: pass backup.assert_browse_backup("q", [str(i) for i in range(0,5)]) + # Detach, don't close as there is a broken session + s.session.connection.detach() def test_priority(self): """Verify priority queues replicate correctly""" primary = HaBroker(self, name="primary") primary.promote() - backup = HaBroker(self, name="backup", broker_url=primary.host_port()) + backup = HaBroker(self, name="backup", brokers_url=primary.host_port()) session = primary.connect().session() s = session.sender("priority-queue; {create:always, node:{x-declare:{arguments:{'qpid.priorities':10}}}}") priorities = [8,9,5,1,2,2,3,4,9,7,8,9,9,2] @@ -445,7 +558,7 @@ class ReplicationTests(BrokerTest): """Verify priority queues replicate correctly""" primary = HaBroker(self, name="primary") primary.promote() - backup = HaBroker(self, name="backup", broker_url=primary.host_port()) + backup = HaBroker(self, name="backup", brokers_url=primary.host_port()) session = primary.connect().session() levels = 8 priorities = [4,5,3,7,8,8,2,8,2,8,8,16,6,6,6,6,6,6,8,3,5,8,3,5,5,3,3,8,8,3,7,3,7,7,7,8,8,8,2,3] @@ -464,7 +577,7 @@ class ReplicationTests(BrokerTest): def test_priority_ring(self): primary = HaBroker(self, name="primary") primary.promote() - backup = HaBroker(self, name="backup", broker_url=primary.host_port()) + backup = HaBroker(self, name="backup", brokers_url=primary.host_port()) s = primary.connect().session().sender("q; {create:always, node:{x-declare:{arguments:{'qpid.policy_type':ring, 'qpid.max_count':5, 'qpid.priorities':10}}}}") priorities = [8,9,5,1,2,2,3,4,9,7,8,9,9,2] for p in priorities: s.send(Message(priority=p)) @@ -475,8 +588,10 @@ class ReplicationTests(BrokerTest): # correct result, the uncommented one is for the actualy buggy # result. See https://issues.apache.org/jira/browse/QPID-3866 # - # backup.assert_browse_backup("q", sorted(priorities,reverse=True)[0:5], transform=lambda m: m.priority) - backup.assert_browse_backup("q", [9,9,9,9,2], transform=lambda m: m.priority) + # expect = sorted(priorities,reverse=True)[0:5] + expect = [9,9,9,9,2] + primary.assert_browse("q", expect, transform=lambda m: m.priority) + backup.assert_browse_backup("q", expect, transform=lambda m: m.priority) def test_backup_acquired(self): """Verify that acquired messages are backed up, for all queue types.""" @@ -509,11 +624,11 @@ class ReplicationTests(BrokerTest): primary = HaBroker(self, name="primary") primary.promote() - backup1 = HaBroker(self, name="backup1", broker_url=primary.host_port()) + backup1 = HaBroker(self, name="backup1", brokers_url=primary.host_port()) c = primary.connect() for t in tests: t.send(c) # Send messages, leave one unacknowledged. - backup2 = HaBroker(self, name="backup2", broker_url=primary.host_port()) + backup2 = HaBroker(self, name="backup2", brokers_url=primary.host_port()) # Wait for backups to catch up. for t in tests: t.wait(self, backup1) @@ -538,11 +653,13 @@ class ReplicationTests(BrokerTest): self.fail("Excpected no-such-queue exception") except NotFound: pass - def test_invalid_default(self): - """Verify that a queue with an invalid qpid.replicate gets default treatment""" - cluster = HaCluster(self, 2, ha_replicate="all") - c = cluster[0].connect().session().sender("q;{create:always, node:{x-declare:{arguments:{'qpid.replicate':XXinvalidXX}}}}") - cluster[1].wait_backup("q") + def test_invalid_replication(self): + """Verify that we reject an attempt to declare a queue with invalid replication value.""" + cluster = HaCluster(self, 1, ha_replicate="all") + try: + c = cluster[0].connect().session().sender("q;{create:always, node:{x-declare:{arguments:{'qpid.replicate':XXinvalidXX}}}}") + self.fail("Expected ConnectionError") + except ConnectionError: pass def test_exclusive_queue(self): """Ensure that we can back-up exclusive queues, i.e. the replicating @@ -559,6 +676,136 @@ class ReplicationTests(BrokerTest): test("excl_sub;{create:always, link:{x-subscribe:{exclusive:True}}}"); test("excl_queue;{create:always, node:{x-declare:{exclusive:True}}}") + def test_auto_delete_exclusive(self): + """Verify that we ignore auto-delete, exclusive, non-auto-delete-timeout queues""" + cluster = HaCluster(self,2) + s = cluster[0].connect().session() + s.receiver("exad;{create:always,node:{x-declare:{exclusive:True,auto-delete:True}}}") + s.receiver("ex;{create:always,node:{x-declare:{exclusive:True}}}") + s.receiver("ad;{create:always,node:{x-declare:{auto-delete:True}}}") + s.receiver("time;{create:always,node:{x-declare:{exclusive:True,auto-delete:True,arguments:{'qpid.auto_delete_timeout':1}}}}") + s.receiver("q;{create:always}") + + s = cluster[1].connect_admin().session() + cluster[1].wait_backup("q") + assert not valid_address(s, "exad") + assert valid_address(s, "ex") + assert valid_address(s, "ad") + assert valid_address(s, "time") + + def test_broker_info(self): + """Check that broker information is correctly published via management""" + cluster = HaCluster(self, 3) + + for broker in cluster: # Make sure HA system-id matches broker's + qmf = broker.agent().getHaBroker() + self.assertEqual(qmf.systemId, UUID(broker.agent().getBroker().systemRef)) + + cluster_ports = map(lambda b: b.port(), cluster) + cluster_ports.sort() + def ports(qmf): + qmf.update() + return sorted(map(lambda b: b["port"], qmf.members)) + # Check that all brokers have the same membership as the cluster + for broker in cluster: + qmf = broker.agent().getHaBroker() + assert retry(lambda: cluster_ports == ports(qmf), 1), "%s != %s on %s"%(cluster_ports, ports(qmf), broker) + # Add a new broker, check it is updated everywhere + b = cluster.start() + cluster_ports.append(b.port()) + cluster_ports.sort() + for broker in cluster: + qmf = broker.agent().getHaBroker() + assert retry(lambda: cluster_ports == ports(qmf), 1), "%s != %s"%(cluster_ports, ports(qmf)) + + def test_auth(self): + """Verify that authentication does not interfere with replication.""" + # FIXME aconway 2012-07-09: generate test sasl config portably for cmake + sasl_config=os.path.join(self.rootdir, "sasl_config") + if not os.path.exists(sasl_config): + print "WARNING: Skipping test, SASL test configuration %s not found."%sasl_config + return + acl=os.path.join(os.getcwd(), "policy.acl") + aclf=file(acl,"w") + # Verify that replication works with auth=yes and HA user has at least the following + # privileges: + aclf.write(""" +acl allow zag@QPID access queue +acl allow zag@QPID create queue +acl allow zag@QPID consume queue +acl allow zag@QPID delete queue +acl allow zag@QPID access exchange +acl allow zag@QPID create exchange +acl allow zag@QPID bind exchange +acl allow zag@QPID publish exchange +acl allow zag@QPID delete exchange +acl allow zag@QPID access method +acl allow zag@QPID create link +acl deny all all + """) + aclf.close() + cluster = HaCluster( + self, 2, + args=["--auth", "yes", "--sasl-config", sasl_config, + "--acl-file", acl, "--load-module", os.getenv("ACL_LIB"), + "--ha-username=zag", "--ha-password=zag", "--ha-mechanism=PLAIN" + ], + client_credentials=Credentials("zag", "zag", "PLAIN")) + s0 = cluster[0].connect(username="zag", password="zag").session(); + s0.receiver("q;{create:always}") + s0.receiver("ex;{create:always,node:{type:topic,x-declare:{type:'fanout'},x-bindings:[{exchange:'ex',queue:'q'}]}}") + cluster[1].wait_backup("q") + cluster[1].wait_backup("ex") + s1 = cluster[1].connect_admin().session(); # Uses Credentials above. + s1.sender("ex").send("foo"); + self.assertEqual(s1.receiver("q").fetch().content, "foo") + + def test_alternate_exchange(self): + """Verify that alternate-exchange on exchanges and queues is propagated + to new members of a cluster. """ + cluster = HaCluster(self, 2) + s = cluster[0].connect().session() + # altex exchange: acts as alternate exchange + s.sender("altex;{create:always,node:{type:topic,x-declare:{type:'fanout'}}}") + # altq queue bound to altex, collect re-routed messages. + s.sender("altq;{create:always,node:{x-bindings:[{exchange:'altex',queue:altq}]}}") + # 0ex exchange with alternate-exchange altex and no queues bound + s.sender("0ex;{create:always,node:{type:topic, x-declare:{type:'direct', alternate-exchange:'altex'}}}") + # create queue q with alternate-exchange altex + s.sender("q;{create:always,node:{type:queue, x-declare:{alternate-exchange:'altex'}}}") + # create a bunch of exchanges to ensure we don't clean up prematurely if the + # response comes in multiple fragments. + for i in xrange(200): s.sender("00ex%s;{create:always,node:{type:topic}}"%i) + + def verify(broker): + s = broker.connect().session() + # Verify unmatched message goes to ex's alternate. + s.sender("0ex").send("foo") + altq = s.receiver("altq") + self.assertEqual("foo", altq.fetch(timeout=0).content) + s.acknowledge() + # 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", altq.fetch(timeout=0).content) + s.acknowledge() + + # Sanity check: alternate exchanges on original broker + verify(cluster[0]) + # Check backup that was connected during setup. + cluster[1].wait_backup("0ex") + cluster[1].wait_backup("q") + cluster.bounce(0) + verify(cluster[1]) + # Check a newly started backup. + cluster.start() + cluster[2].wait_backup("0ex") + cluster[2].wait_backup("q") + cluster.bounce(1) + verify(cluster[2]) + def fairshare(msgs, limit, levels): """ Generator to return prioritised messages in expected order for a given fairshare limit @@ -601,49 +848,135 @@ class LongTests(BrokerTest): if d: return float(d)*60 else: return 3 # Default is to be quick - - def disable_test_failover(self): + def test_failover_send_receive(self): """Test failover with continuous send-receive""" - # FIXME aconway 2012-02-03: fails due to dropped messages, - # known issue: sending messages to new primary before - # backups are ready. Enable when fixed. - - # Start a cluster, all members will be killed during the test. - brokers = [ HaBroker(self, name=name, expect=EXPECT_EXIT_FAIL) - for name in ["ha0","ha1","ha2"] ] - url = ",".join([b.host_port() for b in brokers]) - for b in brokers: b.set_broker_url(url) - brokers[0].promote() + brokers = HaCluster(self, 3) # Start sender and receiver threads - sender = NumberedSender(brokers[0], max_depth=1000, failover_updates=False) - receiver = NumberedReceiver(brokers[0], sender=sender, failover_updates=False) - receiver.start() - sender.start() - # Wait for sender & receiver to get up and running - assert retry(lambda: receiver.received > 100) + n = 10 + senders = [NumberedSender(brokers[0], max_depth=1024, failover_updates=False, + queue="test%s"%(i)) for i in xrange(n)] + receivers = [NumberedReceiver(brokers[0], sender=senders[i], + failover_updates=False, + queue="test%s"%(i)) for i in xrange(n)] + for r in receivers: r.start() + for s in senders: s.start() + + def wait_passed(r, n): + """Wait for receiver r to pass n""" + def check(): + r.check() # Verify no exceptions + return r.received > n + assert retry(check), "Stalled %s at %s"%(r.queue, n) + + for r in receivers: wait_passed(r, 0) + # Kill and restart brokers in a cycle: endtime = time.time() + self.duration() i = 0 - while time.time() < endtime or i < 3: # At least 3 iterations - sender.sender.assert_running() - receiver.receiver.assert_running() - port = brokers[i].port() - brokers[i].kill() - brokers.append( - HaBroker(self, name="ha%d"%(i+3), broker_url=url, port=port, - expect=EXPECT_EXIT_FAIL)) - i += 1 - brokers[i].promote() - n = receiver.received # Verify we're still running - def enough(): - receiver.check() # Verify no exceptions - return receiver.received > n + 100 - assert retry(enough, timeout=5) - - sender.stop() - receiver.stop() - for b in brokers[i:]: b.kill() + try: + while time.time() < endtime or i < 3: # At least 3 iterations + for s in senders: s.sender.assert_running() + for r in receivers: r.receiver.assert_running() + checkpoint = [ r.received for r in receivers ] + # Don't kill primary till it is active and the next + # backup is ready, otherwise we can lose messages. + brokers[i%3].wait_status("active") + brokers[(i+1)%3].wait_status("ready") + brokers.bounce(i%3) + i += 1 + map(wait_passed, receivers, checkpoint) # Wait for all receivers + except: + traceback.print_exc() + raise + finally: + for s in senders: s.stop() + for r in receivers: r.stop() + dead = [] + for i in xrange(3): + if not brokers[i].is_running(): dead.append(i) + brokers.kill(i, False) + if dead: raise Exception("Brokers not running: %s"%dead) + +class RecoveryTests(BrokerTest): + """Tests for recovery after a failure.""" + + def test_queue_hold(self): + """Verify that the broker holds queues without sufficient backup, + i.e. does not complete messages sent to those queues.""" + + # We don't want backups to time out for this test, set long timeout. + cluster = HaCluster(self, 4, args=["--ha-backup-timeout=100000"]); + # Wait for the primary to be ready + cluster[0].wait_status("active") + # Create a queue before the failure. + s1 = cluster.connect(0).session().sender("q1;{create:always}") + for b in cluster: b.wait_backup("q1") + for i in xrange(100): s1.send(str(i)) + # Kill primary and 2 backups + for i in [0,1,2]: cluster.kill(i, False) + cluster[3].promote() # New primary, backups will be 1 and 2 + cluster[3].wait_status("recovering") + + def assertSyncTimeout(s): + try: + s.sync(timeout=.01) + self.fail("Expected Timeout exception") + except Timeout: pass + + # Create a queue after the failure + s2 = cluster.connect(3).session().sender("q2;{create:always}") + + # Verify that messages sent are not completed + for i in xrange(100,200): s1.send(str(i), sync=False); s2.send(str(i), sync=False) + assertSyncTimeout(s1) + self.assertEqual(s1.unsettled(), 100) + assertSyncTimeout(s2) + self.assertEqual(s2.unsettled(), 100) + + # Verify we can receive even if sending is on hold: + cluster[3].assert_browse("q1", [str(i) for i in range(100)+range(100,200)]) + + # Restart backups, verify queues are released only when both backups are up + cluster.restart(1) + assertSyncTimeout(s1) + self.assertEqual(s1.unsettled(), 100) + assertSyncTimeout(s2) + self.assertEqual(s2.unsettled(), 100) + self.assertEqual(cluster[3].ha_status(), "recovering") + cluster.restart(2) + + # Verify everything is up to date and active + def settled(sender): sender.sync(); return sender.unsettled() == 0; + assert retry(lambda: settled(s1)), "Unsetttled=%s"%(s1.unsettled()) + assert retry(lambda: settled(s2)), "Unsetttled=%s"%(s2.unsettled()) + cluster[1].assert_browse_backup("q1", [str(i) for i in range(100)+range(100,200)]) + cluster[1].assert_browse_backup("q2", [str(i) for i in range(100,200)]) + cluster[3].wait_status("active"), + s1.session.connection.close() + s2.session.connection.close() + + def test_expected_backup_timeout(self): + """Verify that we time-out expected backups and release held queues + after a configured interval. Verify backup is demoted to catch-up, + but can still rejoin. + """ + cluster = HaCluster(self, 3, args=["--ha-backup-timeout=0.5"]); + cluster[0].wait_status("active") # Primary ready + for b in cluster[1:4]: b.wait_status("ready") # Backups ready + for i in [0,1]: cluster.kill(i, False) + cluster[2].promote() # New primary, backups will be 1 and 2 + cluster[2].wait_status("recovering") + # Should not go active till the expected backup connects or times out. + self.assertEqual(cluster[2].ha_status(), "recovering") + # Messages should be held expected backup times out + s = cluster[2].connect().session().sender("q;{create:always}") + for i in xrange(100): s.send(str(i), sync=False) + # Verify message held initially. + try: s.sync(timeout=.01); self.fail("Expected Timeout exception") + except Timeout: pass + s.sync(timeout=1) # And released after the timeout. + self.assertEqual(cluster[2].ha_status(), "active") if __name__ == "__main__": shutil.rmtree("brokertest.tmp", True) diff --git a/cpp/src/tests/ipv6_test b/cpp/src/tests/ipv6_test index 6becfd8c96..9d1cb2acdd 100755 --- a/cpp/src/tests/ipv6_test +++ b/cpp/src/tests/ipv6_test @@ -19,6 +19,19 @@ # under the License. # +# Check whether we have any globally configured IPv6 addresses +# - if not then we can't run the tests because ipv6 lookups won't +# work within the qpid code. This is a deliberate feature to avoid +# getting addresses that can't be routed by the machine. + +if ip -f inet6 -o addr | cut -f 9 -s -d' ' | grep global > /dev/null ; then + echo "IPv6 addresses configured continuing" +else + echo "No global IPv6 addresses configured - skipping test" + exit 0 +fi + + # Run a simple test over IPv6 source ./test_env.sh diff --git a/cpp/src/tests/logging.cpp b/cpp/src/tests/logging.cpp index 5d5bb1feef..a29714c002 100644 --- a/cpp/src/tests/logging.cpp +++ b/cpp/src/tests/logging.cpp @@ -258,7 +258,7 @@ QPID_AUTO_TEST_CASE(testOverhead) { Statement statement( Level level, const char* file="", int line=0, const char* fn=0) { - Statement s={0, file, line, fn, level}; + Statement s={0, file, line, fn, level, ::qpid::log::unspecified}; return s; } @@ -347,11 +347,11 @@ QPID_AUTO_TEST_CASE(testLoggerStateure) { }; opts.parse(ARGC(argv), const_cast<char**>(argv)); l.configure(opts); - QPID_LOG(critical, "foo"); int srcline=__LINE__; + QPID_LOG_CAT(critical, test, "foo"); int srcline=__LINE__; ifstream log("logging.tmp"); string line; getline(log, line); - string expect=(format("critical %s:%d: foo")%__FILE__%srcline).str(); + string expect=(format("[Test] critical %s:%d: foo")%__FILE__%srcline).str(); BOOST_CHECK_EQUAL(expect, line); log.close(); unlink("logging.tmp"); @@ -375,11 +375,11 @@ QPID_AUTO_TEST_CASE(testQuoteNonPrintable) { char s[] = "null\0tab\tspace newline\nret\r\x80\x99\xff"; string str(s, sizeof(s)); - QPID_LOG(critical, str); + QPID_LOG_CAT(critical, test, 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"; + string expect="[Test] critical null\\x00tab\tspace newline\nret\r\\x80\\x99\\xFF\\x00\n"; BOOST_CHECK_EQUAL(expect, line); log.close(); unlink("logging.tmp"); diff --git a/cpp/src/tests/ping_broker b/cpp/src/tests/ping_broker new file mode 100755 index 0000000000..6c391027a3 --- /dev/null +++ b/cpp/src/tests/ping_broker @@ -0,0 +1,127 @@ +#!/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 +from optparse import OptionParser, OptionGroup +import sys +import locale +import socket +import re +from qpid.messaging import Connection + +home = os.environ.get("QPID_TOOLS_HOME", os.path.normpath("/usr/share/qpid-tools")) +sys.path.append(os.path.join(home, "python")) + +from qpidtoollibs import BrokerAgent +from qpidtoollibs import Display, Header, Sorter, YN, Commas, TimeLong + + +class Config: + def __init__(self): + self._host = "localhost" + self._connTimeout = 10 + +config = Config() +conn_options = {} + +def OptionsAndArguments(argv): + """ Set global variables for options, return arguments """ + + global config + global conn_options + + usage = "%prog [options]" + + parser = OptionParser(usage=usage) + + parser.add_option("-b", "--broker", action="store", type="string", default="localhost", metavar="<url>", + help="URL of the broker to query") + parser.add_option("-t", "--timeout", action="store", type="int", default=10, metavar="<secs>", + help="Maximum time to wait for broker connection (in seconds)") + parser.add_option("--sasl-mechanism", action="store", type="string", metavar="<mech>", + help="SASL mechanism for authentication (e.g. EXTERNAL, ANONYMOUS, PLAIN, CRAM-MD, DIGEST-MD5, GSSAPI). SASL automatically picks the most secure available mechanism - use this option to override.") + parser.add_option("--ssl-certificate", action="store", type="string", metavar="<cert>", help="Client SSL certificate (PEM Format)") + parser.add_option("--ssl-key", action="store", type="string", metavar="<key>", help="Client SSL private key (PEM Format)") + parser.add_option("--ha-admin", action="store_true", help="Allow connection to a HA backup broker.") + + opts, args = parser.parse_args(args=argv) + + config._host = opts.broker + config._connTimeout = opts.timeout + + if opts.sasl_mechanism: + conn_options['sasl_mechanisms'] = opts.sasl_mechanism + if opts.ssl_certificate: + conn_options['ssl_certfile'] = opts.ssl_certificate + if opts.ssl_key: + conn_options['ssl_key'] = opts.ssl_key + if opts.ha_admin: + conn_options['client_properties'] = {'qpid.ha-admin' : 1} + return args + +class BrokerManager: + def __init__(self): + self.brokerName = None + self.connection = None + self.broker = None + self.cluster = None + + def SetBroker(self, brokerUrl): + self.url = brokerUrl + self.connection = Connection.establish(self.url, **conn_options) + self.broker = BrokerAgent(self.connection) + + def Disconnect(self): + """ Release any allocated brokers. Ignore any failures as the tool is + shutting down. + """ + try: + connection.close() + except: + pass + + def Ping(self, args): + for sequence in range(10): + result = self.broker.echo(sequence, "ECHO BODY") + if result['sequence'] != sequence: + raise Exception("Invalid Sequence") + + +def main(argv=None): + + args = OptionsAndArguments(argv) + bm = BrokerManager() + + try: + bm.SetBroker(config._host) + bm.Ping(args) + bm.Disconnect() + return 0 + except KeyboardInterrupt: + print + except Exception,e: + print "Failed: %s - %s" % (e.__class__.__name__, e) + + bm.Disconnect() # try to deallocate brokers + return 1 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/cpp/src/tests/qpid-latency-test.cpp b/cpp/src/tests/qpid-latency-test.cpp index 20eb4568f3..2343cb1d77 100644 --- a/cpp/src/tests/qpid-latency-test.cpp +++ b/cpp/src/tests/qpid-latency-test.cpp @@ -359,19 +359,29 @@ void Sender::sendByRate() } uint64_t interval = TIME_SEC/opts.rate; int64_t timeLimit = opts.timeLimit * TIME_SEC; - uint64_t sent = 0, missedRate = 0; + uint64_t sent = 0; AbsTime start = now(); + AbsTime last = start; 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; + if (Duration(last, sentAt) > TIME_SEC*2) { + Duration t(start, now()); + //check rate actually achieved thus far + uint actualRate = sent / (t/TIME_SEC); + //report inability to stay within 1% of desired rate + if (actualRate < opts.rate && opts.rate - actualRate > opts.rate/100) { + std::cerr << "WARNING: Desired send rate: " << opts.rate << ", actual send rate: " << actualRate << std::endl; + } + last = sentAt; + } + AbsTime waitTill(start, sent*interval); Duration delay(sentAt, waitTill); - if (delay < 0) - ++missedRate; - else + if (delay > 0) sys::usleep(delay / TIME_USEC); if (timeLimit != 0 && Duration(start, now()) > timeLimit) { session.sync(); diff --git a/cpp/src/tests/qpid-receive.cpp b/cpp/src/tests/qpid-receive.cpp index 6deeb566dc..7a02b871db 100644 --- a/cpp/src/tests/qpid-receive.cpp +++ b/cpp/src/tests/qpid-receive.cpp @@ -68,6 +68,7 @@ struct Options : public qpid::Options bool reportHeader; string readyAddress; uint receiveRate; + std::string replyto; Options(const std::string& argv0=std::string()) : qpid::Options("Options"), @@ -114,6 +115,7 @@ struct Options : public qpid::Options ("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.") + ("reply-to", qpid::optValue(replyto, "REPLY-TO"), "specify reply-to address on response messages") ("help", qpid::optValue(help), "print this usage statement"); add(log); } @@ -246,6 +248,9 @@ int main(int argc, char ** argv) s = session.createSender(msg.getReplyTo()); s.setCapacity(opts.capacity); } + if (!opts.replyto.empty()) { + msg.setReplyTo(Address(opts.replyto)); + } s.send(msg); } if (opts.receiveRate) { diff --git a/cpp/src/tests/run_acl_tests b/cpp/src/tests/run_acl_tests index 3a8c03eda6..25241ad75e 100755 --- a/cpp/src/tests/run_acl_tests +++ b/cpp/src/tests/run_acl_tests @@ -30,9 +30,9 @@ 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 --log-to-file local.log > qpidd.port LOCAL_PORT=`cat qpidd.port` - ../qpidd --daemon --port 0 --no-module-dir --data-dir $DATA_DIRI --load-module $ACL_LIB --acl-file policy.acl --auth no --acl-max-connect-per-ip 2 --log-to-file locali.log > qpiddi.port + ../qpidd --daemon --port 0 --no-module-dir --data-dir $DATA_DIRI --load-module $ACL_LIB --acl-file policy.acl --auth no --max-connections-per-ip 2 --log-to-file locali.log > qpiddi.port LOCAL_PORTI=`cat qpiddi.port` - ../qpidd --daemon --port 0 --no-module-dir --data-dir $DATA_DIRU --load-module $ACL_LIB --acl-file policy.acl --auth no --acl-max-connect-per-user 2 --log-to-file localu.log > qpiddu.port + ../qpidd --daemon --port 0 --no-module-dir --data-dir $DATA_DIRU --load-module $ACL_LIB --acl-file policy.acl --auth no --max-connections-per-user 2 --log-to-file localu.log > qpiddu.port LOCAL_PORTU=`cat qpiddu.port` } diff --git a/cpp/src/tests/run_federation_tests b/cpp/src/tests/run_federation_tests index 7735b559cf..c2ee550226 100755 --- a/cpp/src/tests/run_federation_tests +++ b/cpp/src/tests/run_federation_tests @@ -36,10 +36,10 @@ fi QPIDD_CMD="../qpidd --daemon --port 0 --no-data-dir $MODULES --auth no --log-enable=info+ --log-enable=debug+:Bridge --log-to-file" start_brokers() { rm -f fed_local.log fed_remote.log fed_b1.log fed_b2.log - LOCAL_PORT=$($QPIDD_CMD fed_local.log) - REMOTE_PORT=$($QPIDD_CMD fed_remote.log) - REMOTE_B1=$($QPIDD_CMD fed_b1.log) - REMOTE_B2=$($QPIDD_CMD fed_b2.log) + LOCAL_PORT=$($QPIDD_CMD fed_local.log --federation-tag LOCAL) + REMOTE_PORT=$($QPIDD_CMD fed_remote.log --federation-tag REMOTE) + REMOTE_B1=$($QPIDD_CMD fed_b1.log --federation-tag B1) + REMOTE_B2=$($QPIDD_CMD fed_b2.log --federation-tag B2) } stop_brokers() { diff --git a/cpp/src/tests/run_ha_tests b/cpp/src/tests/run_ha_tests new file mode 100755 index 0000000000..1a469646c9 --- /dev/null +++ b/cpp/src/tests/run_ha_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. +# + + +# Make sure the python tools are available. They will be if we are building in +# a checkoug, they may not be in a distribution. +test -d $PYTHON_COMMANDS -a -x $PYTHON_COMMANDS/qpid-ha -a -x $PYTHON_COMMANDS/qpid-config || { echo "Skipping HA tests, qpid-ha or qpid-config not available."; exit 0; } + +srcdir=`dirname $0` +$srcdir/ha_tests.py + diff --git a/cpp/src/tests/sasl_test_setup.sh b/cpp/src/tests/sasl_test_setup.sh index 3e69c0f02b..3947986517 100755 --- a/cpp/src/tests/sasl_test_setup.sh +++ b/cpp/src/tests/sasl_test_setup.sh @@ -30,7 +30,7 @@ pwcheck_method: auxprop auxprop_plugin: sasldb sasldb_path: $PWD/sasl_config/qpidd.sasldb sql_select: dummy select -mech_list: ANONYMOUS PLAIN DIGEST-MD5 EXTERNAL +mech_list: ANONYMOUS PLAIN DIGEST-MD5 EXTERNAL CRAM-MD5 EOF # Populate temporary sasl db. diff --git a/cpp/src/tests/ssl_test b/cpp/src/tests/ssl_test index 91ff8eec1e..19a316a483 100755 --- a/cpp/src/tests/ssl_test +++ b/cpp/src/tests/ssl_test @@ -148,6 +148,11 @@ URL=$TEST_HOSTNAME:$PORT MSG=`./qpid-receive -b $URL --connection-options '{transport:ssl,heartbeat:2}' -a "foo;{create:always}" --messages 1` test "$MSG" = "hello again" || { echo "receive failed '$MSG' != 'hello again'"; exit 1; } +## Test using the Python client +echo "Testing Non-Authenticating with Python Client..." +URL=amqps://$TEST_HOSTNAME:$PORT +if `$top_srcdir/src/tests/ping_broker -b $URL`; then echo " Passed"; else { echo " Failed"; exit 1; }; fi + #### Client Authentication tests start_authenticating_broker diff --git a/cpp/src/tests/storePerftools/storePerftoolsSmokeTest.sh b/cpp/src/tests/storePerftools/asyncStorePerf_smoke_test.sh index 592e1e60a0..9e8880e128 100755 --- a/cpp/src/tests/storePerftools/storePerftoolsSmokeTest.sh +++ b/cpp/src/tests/storePerftools/asyncStorePerf_smoke_test.sh @@ -9,11 +9,16 @@ run_test() { fi } -NUM_MSGS=1000 +NUM_MSGS=10000 TEST_PROG="./asyncStorePerf" +# Default (no args) run_test "${TEST_PROG}" + +# Help run_test "${TEST_PROG} --help" + +# Limited combinations of major params for q in 1 2; do for p in 1 2; do for c in 1 2; do @@ -27,25 +32,3 @@ for q in 1 2; do done done done - - -NUM_MSGS=1000 -TEST_PROG="./jrnl2Perf" - - -run_test "${TEST_PROG}" - -# This test returns 1, don't use run_test until this is fixed. -cmd="${TEST_PROG} --help" -echo $cmd -$cmd - -for q in 1 2; do - for p in 1 2; do - for c in 1; do # BUG - this will fail for c > 1 - run_test "./jrnl2Perf --num_queues $q --num_msgs ${NUM_MSGS} --num_enq_threads_per_queue $p --num_deq_threads_per_queue $c" - done - done -done - -#exit 0 diff --git a/cpp/src/tests/storePerftools/jrnl2Perf_smoke_test.sh b/cpp/src/tests/storePerftools/jrnl2Perf_smoke_test.sh new file mode 100755 index 0000000000..23fe0fea9b --- /dev/null +++ b/cpp/src/tests/storePerftools/jrnl2Perf_smoke_test.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +run_test() { + local cmd=$1 + echo $cmd + $cmd + if (( $? != 0 )); then + exit 1 + fi +} + +NUM_MSGS=10000 +TEST_PROG="./jrnl2Perf" + +# Default (no args) +run_test "${TEST_PROG}" + +# Help +# This test returns 1, don't use run_test until this is fixed. +cmd="${TEST_PROG} --help" +echo $cmd +$cmd + +# Limited combinations of major params +for q in 1 2; do + for p in 1 2; do + for c in 1; do # BUG - this will fail for c > 1 + run_test "./jrnl2Perf --num_queues $q --num_msgs ${NUM_MSGS} --num_enq_threads_per_queue $p --num_deq_threads_per_queue $c" + done + done +done + +#exit 0 diff --git a/cpp/src/tests/txjob.cpp b/cpp/src/tests/txjob.cpp index a7a905c1b7..29394c3415 100644 --- a/cpp/src/tests/txjob.cpp +++ b/cpp/src/tests/txjob.cpp @@ -38,9 +38,9 @@ namespace tests { struct Args : public qpid::TestOptions { - string workQueue; - string source; - string dest; + std::string workQueue; + std::string source; + std::string dest; uint messages; uint jobs; bool quit; diff --git a/cpp/src/tests/txshift.cpp b/cpp/src/tests/txshift.cpp index 882d3716d8..bf85bee986 100644 --- a/cpp/src/tests/txshift.cpp +++ b/cpp/src/tests/txshift.cpp @@ -39,7 +39,7 @@ namespace tests { struct Args : public qpid::TestOptions { - string workQueue; + std::string workQueue; size_t workers; Args() : workQueue("txshift-control"), workers(1) diff --git a/cpp/xml/cluster.xml b/cpp/xml/cluster.xml index f9b8caf185..09434ea37b 100644 --- a/cpp/xml/cluster.xml +++ b/cpp/xml/cluster.xml @@ -179,6 +179,7 @@ <field name="position" type="sequence-no"/> <field name="used-msg-credit" type="uint32"/> <field name="used-byte-credit" type="uint32"/> + <field name="deliveryCount" type="uint32"/> </control> <!-- Delivery-record for outgoing messages sent but not yet accepted. --> |