diff options
author | Aidan Skinner <aidan@apache.org> | 2008-04-15 10:28:06 +0000 |
---|---|---|
committer | Aidan Skinner <aidan@apache.org> | 2008-04-15 10:28:06 +0000 |
commit | e8ce0cd33ede356877f2e4865e46d5e2c56e72db (patch) | |
tree | fa040bb7a6651f194972090b988dd64cd8c77e52 | |
parent | 4fa4e9070eb419cce07f7ea0aa83ffd7779cbb0d (diff) | |
download | qpid-python-e8ce0cd33ede356877f2e4865e46d5e2c56e72db.tar.gz |
Merged revisions 631979-647797 via svnmerge from
https://svn.apache.org/repos/asf/incubator/qpid/trunk
........
r631987 | aconway | 2008-02-28 14:47:59 +0000 (Thu, 28 Feb 2008) | 2 lines
Fixed merging of multiple XML files for the same version.
........
r632057 | rhs | 2008-02-28 17:04:28 +0000 (Thu, 28 Feb 2008) | 1 line
added an option to control junit forking of tests, and defaulted it to off
........
r632067 | arnaudsimon | 2008-02-28 17:28:43 +0000 (Thu, 28 Feb 2008) | 1 line
See Qpid-817
........
r632072 | arnaudsimon | 2008-02-28 17:44:37 +0000 (Thu, 28 Feb 2008) | 1 line
See Qpid-817
........
r632075 | aconway | 2008-02-28 17:53:47 +0000 (Thu, 28 Feb 2008) | 2 lines
amqp_0_10::ProxyTemplate - tested & functional.
........
r632087 | cctrieloff | 2008-02-28 18:55:21 +0000 (Thu, 28 Feb 2008) | 4 lines
QPID-820 from tross
........
r632214 | rhs | 2008-02-29 02:58:11 +0000 (Fri, 29 Feb 2008) | 1 line
turn off junit reloading
........
r632285 | arnaudsimon | 2008-02-29 10:30:37 +0000 (Fri, 29 Feb 2008) | 1 line
Rollback 632067 and 632072
........
r632297 | arnaudsimon | 2008-02-29 11:39:03 +0000 (Fri, 29 Feb 2008) | 1 line
This solved Qpid-819 as of Gordon suggested patch
........
r632331 | arnaudsimon | 2008-02-29 14:36:38 +0000 (Fri, 29 Feb 2008) | 1 line
Qpid-823: Dispatcher thread should be closed after consumers have been closed.
........
r632364 | ritchiem | 2008-02-29 16:00:36 +0000 (Fri, 29 Feb 2008) | 1 line
QPID-702 Removed cluster code as it has been scheduled for such since last year.
........
r632365 | ritchiem | 2008-02-29 16:01:35 +0000 (Fri, 29 Feb 2008) | 1 line
QPID-702 Removed cluster code as it has been scheduled for such since last year.
........
r632457 | aconway | 2008-02-29 22:07:40 +0000 (Fri, 29 Feb 2008) | 4 lines
Template visitors for amqp_0_10::Command, Control and Struct.
Serialization for all str/vbin types.
........
r632490 | aconway | 2008-02-29 23:18:51 +0000 (Fri, 29 Feb 2008) | 5 lines
- Added Buffer::Iterator so amqp_0_10::Codec can use a Buffer
- AMQBody wrappers for amqp_0_10::Command and Control
- Extended AMQBody for CommandBody and ControlBody.
........
r633085 | aconway | 2008-03-03 13:55:25 +0000 (Mon, 03 Mar 2008) | 1 line
Fix compile error.
........
r633108 | gsim | 2008-03-03 14:49:06 +0000 (Mon, 03 Mar 2008) | 5 lines
A further step to final 0-10 spec.
The extra.xml fragment adds class defs for connection in session that are in line with latest spec but use old schema.
The preview codepath (99-0) remains unaltered.
........
r633122 | gsim | 2008-03-03 15:18:23 +0000 (Mon, 03 Mar 2008) | 3 lines
Allow use of channel 0 for all controls and commands
........
r633164 | rhs | 2008-03-03 16:35:19 +0000 (Mon, 03 Mar 2008) | 1 line
added an assert to verify that no threads are leaked on connection open/close; this should detect problems with dispatcher threads not dieing as described inQPID-823
........
r633206 | gsim | 2008-03-03 17:55:21 +0000 (Mon, 03 Mar 2008) | 3 lines
Fixed consolidation of ranges and added further validation to tests.
........
r633241 | gsim | 2008-03-03 19:19:00 +0000 (Mon, 03 Mar 2008) | 3 lines
Updated tracking of outgoing command id and send command-point control on session attachment.
........
r633453 | arnaudsimon | 2008-03-04 12:37:25 +0000 (Tue, 04 Mar 2008) | 1 line
un-commented testNoLocal as it should be added to the exclude list until the merge is completed (see Qpid-721)
........
r633522 | arnaudsimon | 2008-03-04 15:53:13 +0000 (Tue, 04 Mar 2008) | 1 line
Stop the dispatcher thread before closing the session so it does not acquire a lock on _messageDeliveryLock (See QPID-833)
........
r633533 | aconway | 2008-03-04 16:34:01 +0000 (Tue, 04 Mar 2008) | 2 lines
Completed holders, visitors and serialization for 0-10 commands and controls.
........
r633610 | rhs | 2008-03-04 20:03:09 +0000 (Tue, 04 Mar 2008) | 1 line
import of in-process 0-10 final python client
........
r633623 | rhs | 2008-03-04 20:23:38 +0000 (Tue, 04 Mar 2008) | 1 line
check the mtime of the cached spec against the code that generates it
........
r633627 | gsim | 2008-03-04 20:42:19 +0000 (Tue, 04 Mar 2008) | 3 lines
Further updates to support final 0-10 spec
........
r633637 | gsim | 2008-03-04 21:00:00 +0000 (Tue, 04 Mar 2008) | 3 lines
Revert additions to valgrind suppressions checked in by mistake.
........
r633815 | rhs | 2008-03-05 11:12:39 +0000 (Wed, 05 Mar 2008) | 1 line
added logging; fixed deprecation warnings in old codec; filled in datatypes.Message
........
r633820 | rhs | 2008-03-05 11:26:52 +0000 (Wed, 05 Mar 2008) | 1 line
added frame-end back as a temporary workaround for C++; added a timeout to the hello-010-world session open
........
r633833 | gsim | 2008-03-05 12:09:26 +0000 (Wed, 05 Mar 2008) | 3 lines
Fixed calculation of size for frames with str16 fields.
........
r633861 | rhs | 2008-03-05 14:39:40 +0000 (Wed, 05 Mar 2008) | 1 line
added incoming queues for messages; altered session dispatch to send entire assembly to a single handler; added logging switch for hello-010-world
........
r634003 | gsim | 2008-03-05 19:56:43 +0000 (Wed, 05 Mar 2008) | 7 lines
forked python tests for 0-10 preview and 0-10 final
fixed result handling in c++ broker
modified testlib in python to allow new 0-10 client to be run as well
converted query tests for final 0-10
added python tests for 0-10 final to automated set for c++ broker (most unconverted still)
........
r634229 | gsim | 2008-03-06 11:44:36 +0000 (Thu, 06 Mar 2008) | 4 lines
Fix message delivery for 0-10 final codepath
Convert two more python tests to use 0-10 client
........
r634255 | rhs | 2008-03-06 13:00:58 +0000 (Thu, 06 Mar 2008) | 1 line
added codec for sequence_set; added id to Message; RangeSet -> RangedSet; added RangedSet.add(lower, upper)
........
r634273 | arnaudsimon | 2008-03-06 14:28:50 +0000 (Thu, 06 Mar 2008) | 1 line
Added resource cleaning (mainly connection close); see QPID-824
........
r634289 | rhs | 2008-03-06 15:05:04 +0000 (Thu, 06 Mar 2008) | 1 line
preliminary support for message headers
........
r634368 | gsim | 2008-03-06 17:52:59 +0000 (Thu, 06 Mar 2008) | 9 lines
Fixes to c++ broker:
- use final version of delivery properties where appropriate
- start sequence numbering of outgoing messages at 0
- explicit accept-mode is now 0 (no more confirm-mode)
- add default initialisers for numeric fields in methods
Converted some more python tests to use final 0-10 client.
........
r634661 | gsim | 2008-03-07 13:20:02 +0000 (Fri, 07 Mar 2008) | 4 lines
Altered management of delivery records to support separateion of completion (which drives flow control) and acceptance.
Converted flow control python tests.
........
r634664 | arnaudsimon | 2008-03-07 13:28:41 +0000 (Fri, 07 Mar 2008) | 1 line
This is a very simple fix for QPID-837 (this code will be changed once the merge is completed)
........
r634673 | arnaudsimon | 2008-03-07 13:46:07 +0000 (Fri, 07 Mar 2008) | 1 line
Changed host name to localhost so the test can run on Windows (see QPID-836)
........
r634678 | rhs | 2008-03-07 13:55:00 +0000 (Fri, 07 Mar 2008) | 1 line
added timeouts to hello-010-world; switched to conditions rather than events for handling connection/session state; handle session exceptions
........
r634696 | arnaudsimon | 2008-03-07 14:42:02 +0000 (Fri, 07 Mar 2008) | 1 line
Removed redundant code (see QPID-838). As this is a major 0.10 change tck has been run prior to committing it.
........
r634729 | gsim | 2008-03-07 16:19:30 +0000 (Fri, 07 Mar 2008) | 3 lines
Converted some more tests to use new client
........
r634744 | rhs | 2008-03-07 16:57:43 +0000 (Fri, 07 Mar 2008) | 1 line
added session.sync(); session.auto_sync; made transfers not auto-complete; fixed bug in RangedSet
........
r634763 | rhs | 2008-03-07 17:56:41 +0000 (Fri, 07 Mar 2008) | 1 line
send an empty frame for an empty segment
........
r634780 | gsim | 2008-03-07 19:07:32 +0000 (Fri, 07 Mar 2008) | 4 lines
Added acquire impl to final 0-10 codepath
Converted some more python tests
........
r634803 | rhs | 2008-03-07 20:22:18 +0000 (Fri, 07 Mar 2008) | 1 line
added support for maps
........
r635618 | gsim | 2008-03-10 17:47:06 +0000 (Mon, 10 Mar 2008) | 4 lines
Adjusted exception handling in c++ for final 0-10 path.
Converted some more tests.
........
r635660 | rhs | 2008-03-10 19:12:09 +0000 (Mon, 10 Mar 2008) | 1 line
renamed datatypes.Struct.type -> datatypes.Struct._type; this avoids naming conflicts with metadata-driven fields; moved argument validation -> datatypes.Struct and improved error checking; improved datatypes.Struct.__repr__
........
r635672 | rhs | 2008-03-10 19:50:16 +0000 (Mon, 10 Mar 2008) | 1 line
fixed datatypes.Struct.type -> datatypes.Struct._type rename omitted from server010
........
r635898 | gsim | 2008-03-11 12:24:46 +0000 (Tue, 11 Mar 2008) | 5 lines
Fixed broker to take application headers from final format message-properties struct
Fixed headers exchange to recognise x-match even if sent as a string other than 32 bit sized
Converted remaining python exchange tests
........
r635939 | rhs | 2008-03-11 14:29:17 +0000 (Tue, 11 Mar 2008) | 1 line
added convenience API for turning on logging; added logging for controls and commands; made logging prettier
........
r635941 | rhs | 2008-03-11 14:31:16 +0000 (Tue, 11 Mar 2008) | 1 line
added *.pyc to svn:ignore for tests and tests_0-10
........
r635976 | gsim | 2008-03-11 15:34:20 +0000 (Tue, 11 Mar 2008) | 4 lines
Fixed headers exchange to allow unbind using binding key and not args
Converted alternate exchange python tests
........
r635995 | gsim | 2008-03-11 16:21:44 +0000 (Tue, 11 Mar 2008) | 3 lines
Converted some more tests
........
r636121 | gsim | 2008-03-11 21:56:49 +0000 (Tue, 11 Mar 2008) | 4 lines
Enabled tx methods on final 0-10 path and converted tests accordingly
Added read/write- uuid to codec010
........
r636126 | aconway | 2008-03-11 22:13:10 +0000 (Tue, 11 Mar 2008) | 43 lines
rubygen/0-10/specification.rb:
- Simplified enum mapping/encoding.
- struct encoding
- ostream << operators
src/qpid/Serializer.h
- free funciton serialization
- separate Encoder/Decoder for const correctness
- split() to allow separate encode/decode for complex cases.
src/qpid/amqp_0_10/Assembly.cpp, Assembly.h: AMQP 0-10 final Assembly
src/qpid/amqp_0_10/Codec.h
- Replaced enable_if with overloads, simpler & more flexible.
src/qpid/amqp_0_10/Frame.cpp, .h: AMQP 0-10 final frame.
src/qpid/amqp_0_10/Holder.h:
- provide const and non-const apply
src/qpid/amqp_0_10/Segment.cpp, .h: AMQP 0-10 final Segment.
src/qpid/amqp_0_10/apply.h
- ConstApplyFunctor for const apply.
src/qpid/amqp_0_10/built_in_types.h
- SerializableString encoding
src/qpid/amqp_0_10/complex_types.cpp, .h
- const application
- Action base class for command/control.
src/qpid/framing/AMQBody.h
- removed 0-10 final changes, moving integration point down the stack.
src/qpid/sys/Shlib.h
- removed unused and uncompilable (on some compilers) function.
src/qpid/sys/Time.h, .cpp
- ostream << for AbsTime and Duration.
src/tests/Assembly.cpp, Segment.cpp, apply.cpp, serialize.cpp: testing new code.
........
r636211 | aconway | 2008-03-12 06:07:44 +0000 (Wed, 12 Mar 2008) | 1 line
Fix build failure.
........
r636737 | gsim | 2008-03-13 12:15:44 +0000 (Thu, 13 Mar 2008) | 3 lines
Added missing header files to distribution.
........
r636791 | rajith | 2008-03-13 16:04:46 +0000 (Thu, 13 Mar 2008) | 4 lines
Added constant to represent the AMQP versions, as previously it was hard-coded.
Modified the ConnectionDelegate to use the Constants for AMQP version.
Also the version cosntants were changed to 99-0 to work with the c++ broker until the 0-10 framing gets completed.
........
r637854 | gsim | 2008-03-17 12:17:55 +0000 (Mon, 17 Mar 2008) | 3 lines
Scope exclusive queues to sessions.
........
r637882 | gsim | 2008-03-17 13:33:02 +0000 (Mon, 17 Mar 2008) | 3 lines
Added file missed in last commit.
........
r638019 | rajith | 2008-03-17 18:42:34 +0000 (Mon, 17 Mar 2008) | 1 line
verify scripts for the JMS direct exchange examples. This is tracked in JIRA-859
........
r638344 | arnaudsimon | 2008-03-18 12:20:49 +0000 (Tue, 18 Mar 2008) | 1 line
Revision 636791 says: "Added constant to represent the AMQP versions, as previously it was hard-coded." Those constants must be used when the connection is established.
........
r638553 | rajith | 2008-03-18 20:23:46 +0000 (Tue, 18 Mar 2008) | 14 lines
This commit is for QPID-859
The verify_all script will run all verify scripts under each example
Following is a description of each script type
==============================================
verify - runs java producer and consumer.
verify_java_python - runs java producer and python consumer
verify_python_java - runs python consumer and java consumer
verify_cpp_java - runs cpp producer and java consumer
verify_java_cpp - runs java producer and cpp consumer
The xxx.in file contains the expected output.
It will be compared against the output of the test to determine any failures.
........
r638590 | aconway | 2008-03-18 21:31:08 +0000 (Tue, 18 Mar 2008) | 6 lines
Make AsyncIOAcceptor multi-protocol:
- ConnectionCodec interface replaces ConnectionInputHandle, moves encoding/decoding out of AsyncIOAcceptor.
- ConnectionCodec::Factory replaces ConnectionInputHandlerFactory
- Acceptor creates version-specific ConnectionCodec based on protocol header.
........
r638602 | aconway | 2008-03-18 21:55:32 +0000 (Tue, 18 Mar 2008) | 2 lines
Fix typo in man page: log.output -> log-output
........
r638604 | aconway | 2008-03-18 22:07:58 +0000 (Tue, 18 Mar 2008) | 2 lines
Qualify names to fix gcc 4.3 compile errors.
........
r639078 | rajith | 2008-03-19 23:46:10 +0000 (Wed, 19 Mar 2008) | 5 lines
This contains a trivial fix for QPID-863.
I also took the oportunity to organize the code a bit more.
Also removed the temp hack we used to bind a Topic to more than one routingkey to interop with the c++/python examples.
The topics can now be bound to more than one routingkey in the jndi properties file.
........
r639090 | rajith | 2008-03-20 00:03:48 +0000 (Thu, 20 Mar 2008) | 4 lines
This is a fix for QPID-864.
This allows the Listener or Consumer to start multiple instances by taking a JNDI name of a queue bound to the topic exchange as a program argument.
The JNDI name should be defined in the fanout.properties. Currently 3 such queues are defined.
........
r639095 | rajith | 2008-03-20 00:10:45 +0000 (Thu, 20 Mar 2008) | 4 lines
This commit is for JIRA QPID-859
This contains verify scripts and xxx.in files for pubsub, fanout and requestResponse.
These scripts will run the java examples against itself, c++ and python
........
r639098 | rajith | 2008-03-20 00:14:54 +0000 (Thu, 20 Mar 2008) | 3 lines
This commit is associated with JIRA QPID-859
The verify script is modfified to start and stop the c++ broker for each test.
........
r640117 | rhs | 2008-03-22 23:05:17 +0000 (Sat, 22 Mar 2008) | 1 line
Made dom query results addable per QPID-870.
........
r640479 | astitcher | 2008-03-24 17:43:55 +0000 (Mon, 24 Mar 2008) | 6 lines
- Refactored RefCounted class to avoid virtual inheritance
- Removed extraneous includes and definitions from RefCounted.h
- Fixed all the places that were relying on RefCounted.h to be including the
intrusive_ptr header file and were assuming that something had imported
intrusive_ptr into the qpid namespace
........
r640503 | nsantos | 2008-03-24 18:31:04 +0000 (Mon, 24 Mar 2008) | 1 line
make build.xml files backward-compatible with ant 1.6.5, by replacing ant 1.7 specific tasks/extensions
........
r640702 | astitcher | 2008-03-25 05:04:10 +0000 (Tue, 25 Mar 2008) | 2 lines
Fixed use of intrusive_ptr in code that was missed
........
r640806 | aconway | 2008-03-25 13:34:44 +0000 (Tue, 25 Mar 2008) | 10 lines
Fix compile errors/warnings with gcc 4.3
- added missing #includes that were implicitly included via old headers.
- add namespace-qualifiers to fix "changes meaning of name" warnings.
- ./qpid/ptr_map.h:51: fixed "qualified return value" warning.
- use const char* for "conversion from string constant to ?\226?\128?\152char*?\226?\128?\153" warnings
Applied patch from https://issues.apache.org/jira/browse/QPID-869
remove depenency on boost/date_time, causes warnings with gcc 4.3.
........
r640813 | aconway | 2008-03-25 13:48:30 +0000 (Tue, 25 Mar 2008) | 2 lines
Add missing header file to SOURCES.
........
r640970 | nsantos | 2008-03-25 20:30:01 +0000 (Tue, 25 Mar 2008) | 1 line
QPID-877: applied patch from Ted Ross
........
r641212 | arnaudsimon | 2008-03-26 09:05:57 +0000 (Wed, 26 Mar 2008) | 1 line
Qpid-861: Java RFC 1982 implementation + Junit tests
........
r641232 | arnaudsimon | 2008-03-26 10:03:56 +0000 (Wed, 26 Mar 2008) | 1 line
Qpid-860: provides support for specifying a list of test to be excluded
........
r641239 | arnaudsimon | 2008-03-26 10:34:06 +0000 (Wed, 26 Mar 2008) | 1 line
Qpid-860: changed running test condition
........
r641281 | gsim | 2008-03-26 12:14:18 +0000 (Wed, 26 Mar 2008) | 3 lines
Re-enable the establishment of inter-broker links.
........
r641304 | arnaudsimon | 2008-03-26 13:45:43 +0000 (Wed, 26 Mar 2008) | 1 line
Changed construtor serialbits type, was double should be long (ref qpid-861)
........
r641315 | aconway | 2008-03-26 14:01:38 +0000 (Wed, 26 Mar 2008) | 2 lines
Bounds-checking iterator wrapper, for use with Codec::encode/decode.
........
r641437 | aconway | 2008-03-26 17:55:01 +0000 (Wed, 26 Mar 2008) | 2 lines
Removed unused class RefCountedMap.
........
r641464 | gsim | 2008-03-26 18:38:35 +0000 (Wed, 26 Mar 2008) | 8 lines
Update to dtx inline with latest spec:
* Updated dtx handling in c++ broker to take account of separation of completion and acceptance.
* Added final dtx method defs to extra xml fragment and implemented appropriate handlers in c++ broker.
* Converted dtx python tests (recover test still requires some work on decoding arrays).
* Allow creation of structs without type codes through a python session method.
* Fixed exception handling in python client for commands with results.
........
r641929 | gsim | 2008-03-27 18:04:42 +0000 (Thu, 27 Mar 2008) | 4 lines
Send accept in response to message publications if required.
Hold up completion (and accept) until message from transfer is fully enqueued.
........
r641969 | aconway | 2008-03-27 20:32:01 +0000 (Thu, 27 Mar 2008) | 3 lines
Removed unused files.
........
r641976 | nsantos | 2008-03-27 20:51:42 +0000 (Thu, 27 Mar 2008) | 1 line
QPID-883: applying patch supplied by Ted Ross
........
r642346 | gsim | 2008-03-28 19:45:01 +0000 (Fri, 28 Mar 2008) | 3 lines
Prefer binding key for unbind if specified.
........
r642375 | nsantos | 2008-03-28 20:53:44 +0000 (Fri, 28 Mar 2008) | 1 line
QPID-885: patch from Ted Ross
........
r642959 | gsim | 2008-03-31 11:44:10 +0100 (Mon, 31 Mar 2008) | 3 lines
Prevent broker exit on receiving connection with invalid protocol version.
........
r642981 | gsim | 2008-03-31 13:51:36 +0100 (Mon, 31 Mar 2008) | 3 lines
Re-introduced old 'no-local' behaviour for exclusive queues via a proprietary arg to queue.declare.
........
r643067 | gsim | 2008-03-31 18:20:08 +0100 (Mon, 31 Mar 2008) | 6 lines
Updated xml fragment to reflect correct types for connection.start.mechanisms, connection.start.locales and connection.open.capabilities
Updated connection handler in line with above changes
Added Str16Value to FieldValues
Allow Array instances of different types to be created
........
r643086 | gsim | 2008-03-31 19:01:31 +0100 (Mon, 31 Mar 2008) | 3 lines
Allow zero sized arrays (with no typecode or count)
........
r643442 | nsantos | 2008-04-01 16:23:01 +0100 (Tue, 01 Apr 2008) | 1 line
QPID-892: Make qpidd daemon not run as root (rpm install)
........
r643472 | gsim | 2008-04-01 17:32:10 +0100 (Tue, 01 Apr 2008) | 3 lines
Fix some erroneous definitions in the transitional xml fragment for 0-10.
........
r643478 | gsim | 2008-04-01 18:13:43 +0100 (Tue, 01 Apr 2008) | 3 lines
Added a dump method to buffer for debugging io (patch from rafaels@redhat.com)
........
r643482 | gsim | 2008-04-01 18:37:34 +0100 (Tue, 01 Apr 2008) | 3 lines
Further correction to transitional xml def for final 0-10 (using old schema)
........
r643597 | nsantos | 2008-04-01 22:41:23 +0100 (Tue, 01 Apr 2008) | 1 line
QPID-892 - use daemon params instead of runuser; store pid of qpidd daemon to kill single instance
........
r643822 | arnaudsimon | 2008-04-02 10:55:27 +0100 (Wed, 02 Apr 2008) | 1 line
QPID-829 Remove 0.10 specific URL. The code path is now selected based on broker response. We first try the highest protocol version and update the handler if the broker replies with a different protocol version. NOTE that we need to update the current java broker and 0.8 client for handling protocol headers. This should happen with the M2.1 merge. For the moment we only support an in VM 0.8 broker. Moreover, we'll need to migrate to a 0.10 vs 99.0 protocol version.
........
r643891 | aconway | 2008-04-02 13:55:56 +0100 (Wed, 02 Apr 2008) | 2 lines
Fix gcc 4.3 warnings.
........
r643894 | arnaudsimon | 2008-04-02 14:03:39 +0100 (Wed, 02 Apr 2008) | 1 line
QPID-884 Updated ant for using a profile. I have created a default profile that runs the tests against an 0.8 in VM broker and cpp-async and cpp-sync that respectively runs the test against an 0.10 cpp broker with async store and with sync store.
........
r643900 | aconway | 2008-04-02 14:19:57 +0100 (Wed, 02 Apr 2008) | 2 lines
Fix doxygen warnings.
........
r643914 | aconway | 2008-04-02 15:10:08 +0100 (Wed, 02 Apr 2008) | 2 lines
Fix gcc 4.3 warnings.
........
r643924 | arnaudsimon | 2008-04-02 15:48:12 +0100 (Wed, 02 Apr 2008) | 1 line
QPID-884 made ant task test alton/error/failure configurable from profile file
........
r643957 | aconway | 2008-04-02 17:13:54 +0100 (Wed, 02 Apr 2008) | 2 lines
Fixed logger warning on F9.
........
r643995 | aconway | 2008-04-02 18:56:14 +0100 (Wed, 02 Apr 2008) | 3 lines
Encoding/decoding for new types: amqp_0_10::Map, amqp_0_10:UnknownType
........
r644005 | aconway | 2008-04-02 19:35:31 +0100 (Wed, 02 Apr 2008) | 1 line
Fix compile error on rhel5.
........
r644125 | aconway | 2008-04-03 01:57:44 +0100 (Thu, 03 Apr 2008) | 1 line
Fix serialize test failure on 64 bit architerctures.
........
r644245 | arnaudsimon | 2008-04-03 10:41:42 +0100 (Thu, 03 Apr 2008) | 1 line
QPID-897 this test was intermittently failing because of too short timeouts. This fix is a temporary measure until we agree about using a configurable receive timeout.
........
r644287 | kpvdr | 2008-04-03 13:41:40 +0100 (Thu, 03 Apr 2008) | 1 line
Patch from Ted Ross (see QPID-893): This patch enables management of plugged-in store modules.
........
r644308 | aconway | 2008-04-03 14:27:07 +0100 (Thu, 03 Apr 2008) | 11 lines
amqp_0_10/built_in_types.h
- generic Wrapper template for making distinct types.
- added Bit - bool wrapper with empty encode/decode.
amqp_0_10/complex_types.h:
- Added constants SIZE=0, PACK=2 to Action base class.
amqp_0_10/PackedCodec.h:
- Decode packed struct fields according to the packing bits.
........
r644413 | aconway | 2008-04-03 18:23:34 +0100 (Thu, 03 Apr 2008) | 6 lines
src/qpid/amqp_0_10/Map.h,.cpp: use preview encoding temporarily.
Misc cleanup for 0-10 encoding.
........
r644461 | aconway | 2008-04-03 20:57:14 +0100 (Thu, 03 Apr 2008) | 10 lines
rubygen/0-10/exceptions.rb:
- generate exception classes for each error code, e.g. InvalidArgumentException
rubygen/0-10/specification.rb
- extracted specification_fwd.h from specification.h, contains consts
enums, typedefs and forward declarations of classes.
src/qpid/amqp_0_10/Map.cpp, src/qpid/broker/SessionAdapter.cpp:
- updated to use exceptions.h
........
r644533 | aconway | 2008-04-03 23:19:47 +0100 (Thu, 03 Apr 2008) | 6 lines
qpid/Serializer.h, qpid/amqp_0_10/Codec.h:
- serializer enforces overrunning encode/decode limits.
tests/amqp_0_10: Moved unit tests for amqp_0_10 namespace into amqp_0_10 directory.
........
r644688 | arnaudsimon | 2008-04-04 13:02:52 +0100 (Fri, 04 Apr 2008) | 1 line
QPID-796: Added ability to enable/disable message prefetching. Prefetching is controlled through the property max_prefetch, it is turned off when max_prefetch =0. (this is 0.10 code path change)
........
r644689 | arnaudsimon | 2008-04-04 13:11:38 +0100 (Fri, 04 Apr 2008) | 1 line
QPID-798 Added boolean property fully_sync when true a sync is sent after a persistent message is transfered. .
........
r644727 | aconway | 2008-04-04 15:42:36 +0100 (Fri, 04 Apr 2008) | 5 lines
src/qpid/amqp_0_10/Assembly.cpp,.h:
- remove unused class.
src/qpid/Serializer.h, src/qpid/amqp_0_10/Map.h:
- Enforce map Size limits using serializer.
........
r644732 | nsantos | 2008-04-04 15:58:01 +0100 (Fri, 04 Apr 2008) | 1 line
QPID-898: move bz2 generation from the release target to a new release-all target
........
r644806 | kpvdr | 2008-04-04 19:14:42 +0100 (Fri, 04 Apr 2008) | 1 line
Patch from Ted Ross (see QPID-902): This patch contains the following improvements for management:\n1) Schema display cleaned up in the python mgmt-cli\n2) Locking added automatically to management object accessors (manual locking removed from broker/Queue.cpp)\n3) Schemas are now pre-registered with the management agent using a package initializer. This allows management consoles to get schema information for a class even if no instances of the class exist.
........
r644812 | kpvdr | 2008-04-04 19:25:08 +0100 (Fri, 04 Apr 2008) | 1 line
Additional files for Ted Ross's checkin
........
r644845 | aconway | 2008-04-04 20:35:14 +0100 (Fri, 04 Apr 2008) | 2 lines
Minor cleanup of base Exception and python_tests script.
........
r644917 | aconway | 2008-04-04 22:00:40 +0100 (Fri, 04 Apr 2008) | 5 lines
src/qpid/amqp_0_10/Exception.h
- base classes for 0-10 exceptions
rubygen/0-10/exceptions.rb
- generated 0-10 exceptions
........
r645470 | gsim | 2008-04-07 12:51:07 +0100 (Mon, 07 Apr 2008) | 4 lines
AsynchIoAcceptor.cpp: Limit output from codec to one buffer per 'idle' call.
PreviewConnectionCodec: Generate output frames for encoding while available and while they can fit in the buffer given
........
r645663 | aconway | 2008-04-07 21:12:31 +0100 (Mon, 07 Apr 2008) | 3 lines
Encoding/decoding for packed structs and optional members.
........
r645664 | aconway | 2008-04-07 21:13:59 +0100 (Mon, 07 Apr 2008) | 3 lines
Missing from last commit.
........
r645670 | aconway | 2008-04-07 21:42:28 +0100 (Mon, 07 Apr 2008) | 2 lines
Fix rhel5 build errors.
........
r645685 | astitcher | 2008-04-07 21:59:02 +0100 (Mon, 07 Apr 2008) | 3 lines
Fixed time classes for some changes that misunderstood how they are supposed
to be used (and documented them better to hopefully avoid this in the future)
........
r645699 | aconway | 2008-04-07 22:16:48 +0100 (Mon, 07 Apr 2008) | 8 lines
rubygen/0-10/specification.rb
- operator << for generated types.
- src/tests/amqp_0_10/serialize.cpp
src/qpid/BoundedIterator.h, .cpp
- removed unused file
........
r645733 | aconway | 2008-04-08 00:22:36 +0100 (Tue, 08 Apr 2008) | 7 lines
src/qpid/amqp_0_10/Body.h, Header.h: placeholders.
src/qpid/amqp_0_10/Unit.h,.cpp
- Decode "units" - command/control/header segments or body frames.
Equivalent to preview AMQFrame class.
........
r645804 | gsim | 2008-04-08 10:18:10 +0100 (Tue, 08 Apr 2008) | 3 lines
Fixed compilation error from ignored qualifier on function return type.
........
r645826 | gsim | 2008-04-08 11:16:32 +0100 (Tue, 08 Apr 2008) | 3 lines
Removed out-of-date and misleading comment.
........
r645951 | gsim | 2008-04-08 15:48:25 +0100 (Tue, 08 Apr 2008) | 3 lines
QPID-903: changed read-write lock to mutex (currently recursive) to avoid deadlocking when adding bridge.
........
r646045 | kpvdr | 2008-04-08 20:29:08 +0100 (Tue, 08 Apr 2008) | 1 line
Patch from Ted Ross: QPID-907: Management Improvements for C++ Broker and Store
........
r646054 | aconway | 2008-04-08 20:53:07 +0100 (Tue, 08 Apr 2008) | 21 lines
Summary: added 0-10 Array encoding and decoding.
rubygen/0-10/allsegmenttypes.rb: test header,body and each command and control type.
rubygen/0-10/specification.rb: enable packed encoding.
src/qpid/amqp_0_10/Array.h: Implemented array and array domains.
src/qpid/amqp_0_10/Codec.h: enable litte-endian encoding for pack bits
src/qpid/amqp_0_10/Packer.h: use litte-endian encoding for pack bits
src/qpid/amqp_0_10/Unit.cpp, .h: setting flags, fix op <<.
src/qpid/amqp_0_10/complex_types.cpp, .h: added op <<
src/qpid/framing/Blob.h: copy-object template constructor.
src/tests/amqp_0_10/serialize.cpp:
- test Array
Minor adjustments for new Array.h:
src/qpid/amqp_0_10/Map.cpp
src/qpid/amqp_0_10/Map.h
src/qpid/amqp_0_10/UnknownType.h
src/qpid/amqp_0_10/built_in_types.h
src/qpid/amqp_0_10/Body.h
........
r646071 | aconway | 2008-04-08 22:02:14 +0100 (Tue, 08 Apr 2008) | 2 lines
Touched existing template so new template allSegmentTypes.rb will be noticed.
........
r646093 | cctrieloff | 2008-04-08 22:51:17 +0100 (Tue, 08 Apr 2008) | 6 lines
QPID-908 from tross
+ corrected spec location -s.
........
r646099 | aconway | 2008-04-08 23:11:40 +0100 (Tue, 08 Apr 2008) | 2 lines
Fix packaing problem with generated file allSegmentTypes.h
........
r646376 | aconway | 2008-04-09 15:25:09 +0100 (Wed, 09 Apr 2008) | 2 lines
Improved diagnostics in allSegmentTypes test.
........
r646452 | gsim | 2008-04-09 18:59:38 +0100 (Wed, 09 Apr 2008) | 3 lines
Handle the set-redelivered flag on the final version of the message.release command.
........
r646505 | gsim | 2008-04-09 20:52:44 +0100 (Wed, 09 Apr 2008) | 3 lines
Fixes and automated tests for federation function.
........
r646519 | rajith | 2008-04-09 21:31:28 +0100 (Wed, 09 Apr 2008) | 3 lines
This is a fix for QPID-911.
When the message id is set, _hasBeenUpdated will be set to true.
........
r646778 | aconway | 2008-04-10 13:36:58 +0100 (Thu, 10 Apr 2008) | 2 lines
Invocation handlers, see src/tests/amqp_0_10/handlers.cpp for example.
........
r646783 | aconway | 2008-04-10 14:00:04 +0100 (Thu, 10 Apr 2008) | 3 lines
Use "Exception" instead of typeid.name() as default exception name.
Mangled type names are too confusing.
........
r646791 | kpvdr | 2008-04-10 14:17:44 +0100 (Thu, 10 Apr 2008) | 1 line
Minor change to format of log message when exception is thrown
........
r646943 | aconway | 2008-04-10 21:15:08 +0100 (Thu, 10 Apr 2008) | 3 lines
amqp_0_10: Encoding for packed structs.
........
r647099 | gsim | 2008-04-11 11:02:49 +0100 (Fri, 11 Apr 2008) | 3 lines
QPID-913: committed patch from tross@redhat.com
........
r647123 | gsim | 2008-04-11 12:44:12 +0100 (Fri, 11 Apr 2008) | 3 lines
Set executable property for commands
........
r647227 | rhs | 2008-04-11 18:12:29 +0100 (Fri, 11 Apr 2008) | 1 line
QPID-901: temporary workaround for AMQP-218
........
r647270 | kpvdr | 2008-04-11 20:10:05 +0100 (Fri, 11 Apr 2008) | 1 line
Patch from Ted Ross: added set methods to hilo types in generated management classes
........
r647704 | gsim | 2008-04-14 09:57:06 +0100 (Mon, 14 Apr 2008) | 3 lines
QPID-917: Use PLAIN (rather than the non-standard AMQPLAIN) as the SASL mechanism when authenticating python test clients.
........
r647716 | gsim | 2008-04-14 10:54:16 +0100 (Mon, 14 Apr 2008) | 8 lines
QPID-648: Initial support for sasl authentication for c++ broker. From patch submitted by mfarrellee@redhat.com.
Authentication is optional at compile time (based on user selection or availability of cyrus sasl libs) and at
runtime (through broker config option). Note: At present the runtime default is to not authenticate; this is a
temporary measure to give some time for any automation scripts etc to be updated and will revert shortly to be
on by default.
........
git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/branches/thegreatmerge@648207 13f79535-47bb-0310-9956-ffa450edef68
250 files changed, 17744 insertions, 9907 deletions
diff --git a/qpid/java/010ExcludeList b/qpid/java/010ExcludeList new file mode 100644 index 0000000000..709c846068 --- /dev/null +++ b/qpid/java/010ExcludeList @@ -0,0 +1,10 @@ +org.apache.qpid.test.unit.client.channelclose.ChannelCloseTest#* +org.apache.qpid.client.ResetMessageListenerTest#* +// those tests should be run with prefetch off +org.apache.qpid.client.MessageListenerMultiConsumerTest#testRecieveC2Only +org.apache.qpid.client.MessageListenerMultiConsumerTest#testRecieveBoth +org.apache.qpid.test.unit.xa.TopicTest#testMultiMessagesDurSubCrash +org.apache.qpid.test.unit.xa.TopicTest#testMigrateDurableSubscriber +// those tests need durable subscribe states to be persisted +org.apache.qpid.test.unit.topic.DurableSubscriptionTest#testDurSubRestoredAfterNonPersistentMessageSent +org.apache.qpid.test.unit.ct.DurableSubscriberTests#testDurSubRestoresMessageSelector
\ No newline at end of file diff --git a/qpid/java/08ExcludeList b/qpid/java/08ExcludeList new file mode 100644 index 0000000000..b101c08a85 --- /dev/null +++ b/qpid/java/08ExcludeList @@ -0,0 +1,4 @@ +org.apache.qpid.test.unit.xa.QueueTest#* +org.apache.qpid.test.unit.xa.TopicTest#* +org.apache.qpid.test.unit.ct.DurableSubscriberTests#* + diff --git a/qpid/java/build.xml b/qpid/java/build.xml index 5b00f630d1..1e91ce15c1 100644 --- a/qpid/java/build.xml +++ b/qpid/java/build.xml @@ -33,7 +33,7 @@ <basename property="qpid.jar.name" file="${qpid.jar}"/> <map property="release.excludes" value="${modules}"> - <regexpmapper from="(.*)" to="\1/**"/> + <globmapper from="*" to="*/\*\*"/> </map> <property name="release.zip" location="${release}/${project.namever}.zip"/> @@ -88,11 +88,16 @@ </target> <target name="manifest" depends="check-manifest" unless="manifest.done"> - <manifestclasspath property="qpid.jar.classpath" jarfile="${qpid.jar}"> - <classpath> - <fileset dir="${build.lib}" includes="**/*.jar" excludes="**/${qpid.jar.name}"/> - </classpath> - </manifestclasspath> + <path id="class.path"> + <fileset dir="${build.lib}" > + <include name="*.jar"/> + <exclude name="${qpid.jar.name}"/> + </fileset> + </path> + <pathconvert property="qpid.jar.classpath" pathsep=":" dirsep="/"> + <path refid="class.path"/> + <map from="${basedir}" to="lib/"/> + </pathconvert> <jar destfile="${qpid.jar}"> <manifest> @@ -132,7 +137,9 @@ <bzip2 src="${release.tar}" destfile="${release.bz2}"/> </target> - <target name="release" depends="zip,gzip,bzip2" description="build all release archives"/> + <target name="release" depends="zip,gzip" description="build all release archives except .bz2"/> + + <target name="release-all" depends="zip,gzip,bzip2" description="build all release archives"/> <target name="clean" description="remove build and release artifacts"> <iterate target="clean"/> diff --git a/qpid/java/client/build.xml b/qpid/java/client/build.xml index 847c38b3eb..1fb980b908 100644 --- a/qpid/java/client/build.xml +++ b/qpid/java/client/build.xml @@ -29,9 +29,9 @@ <target name="precompile"> <mkdir dir="${output.dir}"/> - <javacc target="src/main/grammar/SelectorParser.jj" - outputdirectory="${output.dir}" - javacchome="${project.root}/lib"/> + <exec executable="javacc"> + <arg line="-OUTPUT_DIRECTORY=${output.dir} src/main/grammar/SelectorParser.jj"/> + </exec> </target> </project> diff --git a/qpid/java/client/example/bin/verify_all b/qpid/java/client/example/bin/verify_all new file mode 100644 index 0000000000..5b90f708eb --- /dev/null +++ b/qpid/java/client/example/bin/verify_all @@ -0,0 +1,34 @@ +#!/bin/sh +# This script assumes QPID_SRC_HOME is set . + +if [[ "x$QPID_SRC_HOME" = "x" ]]; then + echo "WARNING >>> QPID_SRC_HOME needs to be set " + exit +fi + +export CPP=$QPID_SRC_HOME/cpp/examples/examples +export PYTHON=$QPID_SRC_HOME/python/examples +export JAVA=$QPID_SRC_HOME/java/client/example/src/main/java/org/apache/qpid/example/jmsexample + +run_broker(){ + $QPID_SRC_HOME/cpp/src/qpidd -d --no-data-dir +} + +stop_broker(){ + $QPID_SRC_HOME/cpp/src/qpidd -q +} + +QPID_LIBS=`find $QPID_SRC_HOME/java/build/lib -name '*.jar' | tr '\n' ":"` +export CLASSPATH=$QPID_LIBS:$CLASSPATH + +verify=$QPID_SRC_HOME/cpp/examples/verify + +for dir in $(find $JAVA/ -maxdepth 1 -type d -not -name '*.svn') +do + for script in $(find $dir -maxdepth 1 -type f -name 'verify*' -not -name '*.*') + do + run_broker + $verify $script + stop_broker + done +done diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/direct.properties b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/direct.properties index cc465e9251..4b86126cf6 100644 --- a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/direct.properties +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/direct.properties @@ -20,7 +20,7 @@ java.naming.factory.initial = org.apache.qpid.jndi.PropertiesFileInitialContextF # register some connection factories # connectionfactory.[jndiname] = [ConnectionURL] -connectionfactory.qpidConnectionfactory = qpid:password=pass;username=name@tcp:localhost:5672 +connectionfactory.qpidConnectionfactory = amqp://username:password@clientid/test?brokerlist='tcp://localhost:5672' # Register an AMQP destination in JNDI # destination.[jniName] = [BindingURL] diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify new file mode 100644 index 0000000000..afcd30af88 --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify @@ -0,0 +1,14 @@ +# See https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid/bin/verify +# The JMS producer doesn't create qeueues so utilising the c++ declare_queues +cpp=$CPP/direct + +direct_consumer_java(){ +java -cp "$CLASSPATH" org.apache.qpid.example.jmsexample.direct.Consumer +} + +direct_producer_java(){ +java -cp "$CLASSPATH" org.apache.qpid.example.jmsexample.direct.Producer +} + +clients $cpp/declare_queues direct_producer_java direct_consumer_java +outputs $cpp/declare_queues.out ./direct_producer_java.out ./direct_consumer_java.out diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify.in b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify.in new file mode 100644 index 0000000000..c87e5716c8 --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify.in @@ -0,0 +1,35 @@ +==== declare_queues.out +==== direct_producer_java.out +Producer: Creating a non-transacted, auto-acknowledged session +Producer: Creating a Message Producer +Producer: Creating a TestMessage to send to the destination +Producer: Sending message: 1 +Producer: Sending message: 2 +Producer: Sending message: 3 +Producer: Sending message: 4 +Producer: Sending message: 5 +Producer: Sending message: 6 +Producer: Sending message: 7 +Producer: Sending message: 8 +Producer: Sending message: 9 +Producer: Sending message: 10 +Producer: Closing connection +Producer: Closing JNDI context +==== direct_consumer_java.out +Consumer: Setting an ExceptionListener on the connection as sample uses a MessageConsumer +Consumer: Creating a non-transacted, auto-acknowledged session +Consumer: Creating a MessageConsumer +Consumer: Starting connection so MessageConsumer can receive messages +Consumer: Received message: Message 1 +Consumer: Received message: Message 2 +Consumer: Received message: Message 3 +Consumer: Received message: Message 4 +Consumer: Received message: Message 5 +Consumer: Received message: Message 6 +Consumer: Received message: Message 7 +Consumer: Received message: Message 8 +Consumer: Received message: Message 9 +Consumer: Received message: Message 10 +Consumer: Received final message That's all, folks! +Consumer: Closing connection +Consumer: Closing JNDI context diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify_cpp_java b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify_cpp_java new file mode 100644 index 0000000000..85c8a12903 --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify_cpp_java @@ -0,0 +1,10 @@ +# See https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid/bin/verify +cpp=$CPP/direct + +direct_consumer_java(){ +java -cp "$CLASSPATH" org.apache.qpid.example.jmsexample.direct.Consumer +} + +clients $cpp/declare_queues $cpp/direct_producer direct_consumer_java +outputs $cpp/declare_queues.out $cpp/direct_producer.out ./direct_consumer_java.out + diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify_cpp_java.in b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify_cpp_java.in new file mode 100644 index 0000000000..b50692da1f --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify_cpp_java.in @@ -0,0 +1,20 @@ +==== declare_queues.out +==== direct_producer.out +==== direct_consumer_java.out +Consumer: Setting an ExceptionListener on the connection as sample uses a MessageConsumer +Consumer: Creating a non-transacted, auto-acknowledged session +Consumer: Creating a MessageConsumer +Consumer: Starting connection so MessageConsumer can receive messages +Consumer: Received message: Message 0 +Consumer: Received message: Message 1 +Consumer: Received message: Message 2 +Consumer: Received message: Message 3 +Consumer: Received message: Message 4 +Consumer: Received message: Message 5 +Consumer: Received message: Message 6 +Consumer: Received message: Message 7 +Consumer: Received message: Message 8 +Consumer: Received message: Message 9 +Consumer: Received final message That's all, folks! +Consumer: Closing connection +Consumer: Closing JNDI context diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify_java_cpp b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify_java_cpp new file mode 100644 index 0000000000..8cea77025e --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify_java_cpp @@ -0,0 +1,10 @@ +# See https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid/bin/verify +cpp=$CPP/direct + +direct_producer_java(){ +java -cp "$CLASSPATH" org.apache.qpid.example.jmsexample.direct.Producer +} + +clients $cpp/declare_queues direct_producer_java $cpp/listener +outputs $cpp/declare_queues.out ./direct_producer_java.out $cpp/listener.out + diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify_java_cpp.in b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify_java_cpp.in new file mode 100644 index 0000000000..946c19953f --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify_java_cpp.in @@ -0,0 +1,30 @@ +==== declare_queues.out +==== direct_producer_java.out +Producer: Creating a non-transacted, auto-acknowledged session +Producer: Creating a Message Producer +Producer: Creating a TestMessage to send to the destination +Producer: Sending message: 1 +Producer: Sending message: 2 +Producer: Sending message: 3 +Producer: Sending message: 4 +Producer: Sending message: 5 +Producer: Sending message: 6 +Producer: Sending message: 7 +Producer: Sending message: 8 +Producer: Sending message: 9 +Producer: Sending message: 10 +Producer: Closing connection +Producer: Closing JNDI context +==== listener.out +Message: Message 1 +Message: Message 2 +Message: Message 3 +Message: Message 4 +Message: Message 5 +Message: Message 6 +Message: Message 7 +Message: Message 8 +Message: Message 9 +Message: Message 10 +Message: That's all, folks! +Shutting down listener for message_queue diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify_java_python b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify_java_python new file mode 100644 index 0000000000..5fedcb10e5 --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify_java_python @@ -0,0 +1,9 @@ +# See https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid/bin/verify +py=$PYTHON/direct + +direct_producer_java(){ +java -cp "$CLASSPATH" org.apache.qpid.example.jmsexample.direct.Producer +} + +clients $py/declare_queues.py direct_producer_java $py/direct_consumer.py +outputs $py/declare_queues.py.out ./direct_producer_java.out $py/direct_consumer.py.out diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify_java_python.in b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify_java_python.in new file mode 100644 index 0000000000..e012405352 --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify_java_python.in @@ -0,0 +1,29 @@ +==== declare_queues.py.out +==== direct_producer_java.out +Producer: Creating a non-transacted, auto-acknowledged session +Producer: Creating a Message Producer +Producer: Creating a TestMessage to send to the destination +Producer: Sending message: 1 +Producer: Sending message: 2 +Producer: Sending message: 3 +Producer: Sending message: 4 +Producer: Sending message: 5 +Producer: Sending message: 6 +Producer: Sending message: 7 +Producer: Sending message: 8 +Producer: Sending message: 9 +Producer: Sending message: 10 +Producer: Closing connection +Producer: Closing JNDI context +==== direct_consumer.py.out +Message 1 +Message 2 +Message 3 +Message 4 +Message 5 +Message 6 +Message 7 +Message 8 +Message 9 +Message 10 +That's all, folks! diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify_python_java b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify_python_java new file mode 100644 index 0000000000..7914665baf --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify_python_java @@ -0,0 +1,10 @@ +# See https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid/bin/verify +py=$PYTHON/direct + +direct_consumer_java(){ +java -cp "$CLASSPATH" org.apache.qpid.example.jmsexample.direct.Consumer +} + +clients $py/declare_queues.py $py/direct_producer.py direct_consumer_java +outputs $py/declare_queues.py.out $py/direct_producer.py.out ./direct_consumer_java.out + diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify_python_java.in b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify_python_java.in new file mode 100644 index 0000000000..6a9c9fdd10 --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/direct/verify_python_java.in @@ -0,0 +1,20 @@ +==== declare_queues.py.out +==== direct_producer.py.out +==== direct_consumer_java.out +Consumer: Setting an ExceptionListener on the connection as sample uses a MessageConsumer +Consumer: Creating a non-transacted, auto-acknowledged session +Consumer: Creating a MessageConsumer +Consumer: Starting connection so MessageConsumer can receive messages +Consumer: Received message: message 0 +Consumer: Received message: message 1 +Consumer: Received message: message 2 +Consumer: Received message: message 3 +Consumer: Received message: message 4 +Consumer: Received message: message 5 +Consumer: Received message: message 6 +Consumer: Received message: message 7 +Consumer: Received message: message 8 +Consumer: Received message: message 9 +Consumer: Received final message That's all, folks! +Consumer: Closing connection +Consumer: Closing JNDI context diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/Consumer.java b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/Consumer.java index e813c0ce60..daa1b10b6b 100755 --- a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/Consumer.java +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/Consumer.java @@ -59,16 +59,20 @@ public class Consumer * * @param args Command line arguments. */ - public static void main(String[] args) + public static void main(String[] args) throws Exception { + if (args.length == 0) + { + throw new Exception("You need to specify the JNDI name for the queue"); + } Consumer syncConsumer = new Consumer(); - syncConsumer.runTest(); + syncConsumer.runTest(args[0]); } /** * Start the example. */ - private void runTest() + private void runTest(String queueName) { try { @@ -80,7 +84,7 @@ public class Consumer Context ctx = new InitialContext(properties); // look up destination - Destination destination = (Destination)ctx.lookup("fanoutQueue"); + Destination destination = (Destination)ctx.lookup(queueName); // Lookup the connection factory ConnectionFactory conFac = (ConnectionFactory)ctx.lookup("qpidConnectionfactory"); diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/Listener.java b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/Listener.java index 4d645888a3..fb750693b2 100755 --- a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/Listener.java +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/Listener.java @@ -39,17 +39,17 @@ public class Listener implements MessageListener /** * An object to synchronize on. */ - private final static Object _lock = new Object(); + private final Object _lock = new Object(); /** * A boolean to indicate a clean finish. */ - private static boolean _finished = false; + private boolean _finished = false; /** * A boolean to indicate an unsuccesful finish. */ - private static boolean _failed = false; + private boolean _failed = false; @@ -58,16 +58,20 @@ public class Listener implements MessageListener * * @param args Command line arguments. */ - public static void main(String[] args) + public static void main(String[] args) throws Exception { + if (args.length == 0) + { + throw new Exception("You need to specify the JNDI name for the queue"); + } Listener listener = new Listener(); - listener.runTest(); + listener.runTest(args[0]); } /** * Start the example. */ - private void runTest() + private void runTest(String queueName) { try { @@ -77,7 +81,7 @@ public class Listener implements MessageListener //Create the initial context Context ctx = new InitialContext(properties); - Destination destination = (Destination)ctx.lookup("fanoutQueue"); + Destination destination = (Destination)ctx.lookup(queueName); // Declare the connection ConnectionFactory conFac = (ConnectionFactory)ctx.lookup("qpidConnectionfactory"); diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/fanout.properties b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/fanout.properties index bde9ae4dae..4b98477a5f 100644 --- a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/fanout.properties +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/fanout.properties @@ -21,8 +21,13 @@ java.naming.factory.initial = org.apache.qpid.jndi.PropertiesFileInitialContextF # register some connection factories # connectionfactory.[jndiname] = [ConnectionURL] -connectionfactory.qpidConnectionfactory = qpid:password=pass;username=name@tcp:localhost:5672 +connectionfactory.qpidConnectionfactory = amqp://username:password@clientid/test?brokerlist='tcp://localhost:5672' # Register an AMQP destination in JNDI # destination.[jniName] = [BindingURL] +destination.fanoutQueue1 = fanout://amq.fanout//message_queue1 +destination.fanoutQueue2 = fanout://amq.fanout//message_queue2 +destination.fanoutQueue3 = fanout://amq.fanout//message_queue3 + +# for producer destination.fanoutQueue = fanout://amq.fanout//message_queue
\ No newline at end of file diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify new file mode 100644 index 0000000000..69528a0cf9 --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify @@ -0,0 +1,17 @@ +# See https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid/bin/verify +# The JMS producer doesn't create qeueues so utilising the c++ declare_queues +cpp=$CPP/fanout + +fanout_listener_java(){ +java -cp "$CLASSPATH" org.apache.qpid.example.jmsexample.fanout.Listener $1 +} + +fanout_producer_java(){ +java -cp "$CLASSPATH" org.apache.qpid.example.jmsexample.fanout.Producer +} + +background "can receive messages" fanout_listener_java fanoutQueue1 +background "can receive messages" fanout_listener_java fanoutQueue2 +background "can receive messages" fanout_listener_java fanoutQueue3 +clients fanout_producer_java +outputs ./fanout_producer_java.out "./fanout_listener_java.out | remove_uuid" "./fanout_listener_javaX.out | remove_uuid" "./fanout_listener_javaXX.out | remove_uuid" diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify.in b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify.in new file mode 100644 index 0000000000..c36a515c2a --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify.in @@ -0,0 +1,70 @@ +==== fanout_producer_java.out +Producer: Creating a non-transacted, auto-acknowledged session +Producer: Creating a Message Producer +Producer: Creating a TestMessage to send to the destination +Producer: Sending message: 1 +Producer: Sending message: 2 +Producer: Sending message: 3 +Producer: Sending message: 4 +Producer: Sending message: 5 +Producer: Sending message: 6 +Producer: Sending message: 7 +Producer: Sending message: 8 +Producer: Sending message: 9 +Producer: Sending message: 10 +Producer: Closing connection +Producer: Closing JNDI context +==== fanout_listener_java.out | remove_uuid +Listener: Setting an ExceptionListener on the connection as sample uses a MessageConsumer +Listener: Creating a non-transacted, auto-acknowledged session +Listener: Creating a MessageConsumer +Listener: Starting connection so MessageConsumer can receive messages +Listener: Received message: Message 1 +Listener: Received message: Message 2 +Listener: Received message: Message 3 +Listener: Received message: Message 4 +Listener: Received message: Message 5 +Listener: Received message: Message 6 +Listener: Received message: Message 7 +Listener: Received message: Message 8 +Listener: Received message: Message 9 +Listener: Received message: Message 10 +Listener: Received final message That's all, folks! +Listener: Closing connection +Listener: Closing JNDI context +==== fanout_listener_javaX.out | remove_uuid +Listener: Setting an ExceptionListener on the connection as sample uses a MessageConsumer +Listener: Creating a non-transacted, auto-acknowledged session +Listener: Creating a MessageConsumer +Listener: Starting connection so MessageConsumer can receive messages +Listener: Received message: Message 1 +Listener: Received message: Message 2 +Listener: Received message: Message 3 +Listener: Received message: Message 4 +Listener: Received message: Message 5 +Listener: Received message: Message 6 +Listener: Received message: Message 7 +Listener: Received message: Message 8 +Listener: Received message: Message 9 +Listener: Received message: Message 10 +Listener: Received final message That's all, folks! +Listener: Closing connection +Listener: Closing JNDI context +==== fanout_listener_javaXX.out | remove_uuid +Listener: Setting an ExceptionListener on the connection as sample uses a MessageConsumer +Listener: Creating a non-transacted, auto-acknowledged session +Listener: Creating a MessageConsumer +Listener: Starting connection so MessageConsumer can receive messages +Listener: Received message: Message 1 +Listener: Received message: Message 2 +Listener: Received message: Message 3 +Listener: Received message: Message 4 +Listener: Received message: Message 5 +Listener: Received message: Message 6 +Listener: Received message: Message 7 +Listener: Received message: Message 8 +Listener: Received message: Message 9 +Listener: Received message: Message 10 +Listener: Received final message That's all, folks! +Listener: Closing connection +Listener: Closing JNDI context diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify_cpp_java b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify_cpp_java new file mode 100644 index 0000000000..3e0d7534bc --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify_cpp_java @@ -0,0 +1,13 @@ +# See https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid/bin/verify +# The JMS producer doesn't create qeueues so utilising the c++ declare_queues +cpp=$CPP/fanout + +fanout_listener_java(){ +java -cp "$CLASSPATH" org.apache.qpid.example.jmsexample.fanout.Listener $1 +} + +background "can receive messages" fanout_listener_java fanoutQueue1 +background "can receive messages" fanout_listener_java fanoutQueue2 +background "can receive messages" fanout_listener_java fanoutQueue3 +clients $cpp/fanout_producer +outputs $cpp/fanout_producer.out "./fanout_listener_java.out | remove_uuid" "./fanout_listener_javaX.out | remove_uuid" "./fanout_listener_javaXX.out | remove_uuid" diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify_cpp_java.in b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify_cpp_java.in new file mode 100644 index 0000000000..905fe3d0bc --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify_cpp_java.in @@ -0,0 +1,55 @@ +==== fanout_producer.out +==== fanout_listener_java.out | remove_uuid +Listener: Setting an ExceptionListener on the connection as sample uses a MessageConsumer +Listener: Creating a non-transacted, auto-acknowledged session +Listener: Creating a MessageConsumer +Listener: Starting connection so MessageConsumer can receive messages +Listener: Received message: Message 0 +Listener: Received message: Message 1 +Listener: Received message: Message 2 +Listener: Received message: Message 3 +Listener: Received message: Message 4 +Listener: Received message: Message 5 +Listener: Received message: Message 6 +Listener: Received message: Message 7 +Listener: Received message: Message 8 +Listener: Received message: Message 9 +Listener: Received final message That's all, folks! +Listener: Closing connection +Listener: Closing JNDI context +==== fanout_listener_javaX.out | remove_uuid +Listener: Setting an ExceptionListener on the connection as sample uses a MessageConsumer +Listener: Creating a non-transacted, auto-acknowledged session +Listener: Creating a MessageConsumer +Listener: Starting connection so MessageConsumer can receive messages +Listener: Received message: Message 0 +Listener: Received message: Message 1 +Listener: Received message: Message 2 +Listener: Received message: Message 3 +Listener: Received message: Message 4 +Listener: Received message: Message 5 +Listener: Received message: Message 6 +Listener: Received message: Message 7 +Listener: Received message: Message 8 +Listener: Received message: Message 9 +Listener: Received final message That's all, folks! +Listener: Closing connection +Listener: Closing JNDI context +==== fanout_listener_javaXX.out | remove_uuid +Listener: Setting an ExceptionListener on the connection as sample uses a MessageConsumer +Listener: Creating a non-transacted, auto-acknowledged session +Listener: Creating a MessageConsumer +Listener: Starting connection so MessageConsumer can receive messages +Listener: Received message: Message 0 +Listener: Received message: Message 1 +Listener: Received message: Message 2 +Listener: Received message: Message 3 +Listener: Received message: Message 4 +Listener: Received message: Message 5 +Listener: Received message: Message 6 +Listener: Received message: Message 7 +Listener: Received message: Message 8 +Listener: Received message: Message 9 +Listener: Received final message That's all, folks! +Listener: Closing connection +Listener: Closing JNDI context diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify_java_cpp b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify_java_cpp new file mode 100644 index 0000000000..c1e1585ac3 --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify_java_cpp @@ -0,0 +1,13 @@ +# See https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid/bin/verify +# The JMS producer doesn't create qeueues so utilising the c++ declare_queues +cpp=$CPP/fanout + +fanout_producer_java(){ +java -cp "$CLASSPATH" org.apache.qpid.example.jmsexample.fanout.Producer +} + +background "Listening" $cpp/listener +background "Listening" $cpp/listener +background "Listening" $cpp/listener +clients fanout_producer_java +outputs ./fanout_producer_java.out "$cpp/listener.out | remove_uuid" "$cpp/listenerX.out | remove_uuid" "$cpp/listenerXX.out | remove_uuid" diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify_java_cpp.in b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify_java_cpp.in new file mode 100644 index 0000000000..03e75e39c6 --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify_java_cpp.in @@ -0,0 +1,58 @@ +==== fanout_producer_java.out +Producer: Creating a non-transacted, auto-acknowledged session +Producer: Creating a Message Producer +Producer: Creating a TestMessage to send to the destination +Producer: Sending message: 1 +Producer: Sending message: 2 +Producer: Sending message: 3 +Producer: Sending message: 4 +Producer: Sending message: 5 +Producer: Sending message: 6 +Producer: Sending message: 7 +Producer: Sending message: 8 +Producer: Sending message: 9 +Producer: Sending message: 10 +Producer: Closing connection +Producer: Closing JNDI context +==== listener.out | remove_uuid +Listening +Message: Message 1 +Message: Message 2 +Message: Message 3 +Message: Message 4 +Message: Message 5 +Message: Message 6 +Message: Message 7 +Message: Message 8 +Message: Message 9 +Message: Message 10 +Message: That's all, folks! +Shutting down listener for +==== listenerX.out | remove_uuid +Listening +Message: Message 1 +Message: Message 2 +Message: Message 3 +Message: Message 4 +Message: Message 5 +Message: Message 6 +Message: Message 7 +Message: Message 8 +Message: Message 9 +Message: Message 10 +Message: That's all, folks! +Shutting down listener for +==== listenerXX.out | remove_uuid +Listening +Message: Message 1 +Message: Message 2 +Message: Message 3 +Message: Message 4 +Message: Message 5 +Message: Message 6 +Message: Message 7 +Message: Message 8 +Message: Message 9 +Message: Message 10 +Message: That's all, folks! +Shutting down listener for diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify_java_python b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify_java_python new file mode 100644 index 0000000000..6ac91f88c8 --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify_java_python @@ -0,0 +1,13 @@ +# See https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid/bin/verify +# The JMS producer doesn't create qeueues so utilising the c++ declare_queues +py=$PYTHON/fanout + +fanout_producer_java(){ +java -cp "$CLASSPATH" org.apache.qpid.example.jmsexample.fanout.Producer +} + +background "Subscribed" $py/fanout_consumer.py +background "Subscribed" $py/fanout_consumer.py +background "Subscribed" $py/fanout_consumer.py +clients fanout_producer_java +outputs ./fanout_producer_java.out "$py/fanout_consumer.py.out | remove_uuid64" "$py/fanout_consumer.pyX.out | remove_uuid64" "$py/fanout_consumer.pyXX.out | remove_uuid64" diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify_java_python.in b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify_java_python.in new file mode 100644 index 0000000000..4b5678105f --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify_java_python.in @@ -0,0 +1,61 @@ +==== fanout_producer_java.out +Producer: Creating a non-transacted, auto-acknowledged session +Producer: Creating a Message Producer +Producer: Creating a TestMessage to send to the destination +Producer: Sending message: 1 +Producer: Sending message: 2 +Producer: Sending message: 3 +Producer: Sending message: 4 +Producer: Sending message: 5 +Producer: Sending message: 6 +Producer: Sending message: 7 +Producer: Sending message: 8 +Producer: Sending message: 9 +Producer: Sending message: 10 +Producer: Closing connection +Producer: Closing JNDI context +==== fanout_consumer.py.out | remove_uuid64 +Messages queue: +Subscribed to queue +Response: Message 1 +Response: Message 2 +Response: Message 3 +Response: Message 4 +Response: Message 5 +Response: Message 6 +Response: Message 7 +Response: Message 8 +Response: Message 9 +Response: Message 10 +Response: That's all, folks! +No more messages! +==== fanout_consumer.pyX.out | remove_uuid64 +Messages queue: +Subscribed to queue +Response: Message 1 +Response: Message 2 +Response: Message 3 +Response: Message 4 +Response: Message 5 +Response: Message 6 +Response: Message 7 +Response: Message 8 +Response: Message 9 +Response: Message 10 +Response: That's all, folks! +No more messages! +==== fanout_consumer.pyXX.out | remove_uuid64 +Messages queue: +Subscribed to queue +Response: Message 1 +Response: Message 2 +Response: Message 3 +Response: Message 4 +Response: Message 5 +Response: Message 6 +Response: Message 7 +Response: Message 8 +Response: Message 9 +Response: Message 10 +Response: That's all, folks! +No more messages! diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify_python_java b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify_python_java new file mode 100644 index 0000000000..e10d077dfb --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify_python_java @@ -0,0 +1,13 @@ +# See https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid/bin/verify +# The JMS producer doesn't create qeueues so utilising the c++ declare_queues +py=$PYTHON/fanout + +fanout_listener_java(){ +java -cp "$CLASSPATH" org.apache.qpid.example.jmsexample.fanout.Listener $1 +} + +background "can receive messages" fanout_listener_java fanoutQueue1 +background "can receive messages" fanout_listener_java fanoutQueue2 +background "can receive messages" fanout_listener_java fanoutQueue3 +clients $py/fanout_producer.py +outputs $py/fanout_producer.py.out "./fanout_listener_java.out | remove_uuid" "./fanout_listener_javaX.out | remove_uuid" "./fanout_listener_javaXX.out | remove_uuid" diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify_python_java.in b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify_python_java.in new file mode 100644 index 0000000000..1d8e1c2790 --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/fanout/verify_python_java.in @@ -0,0 +1,55 @@ +==== fanout_producer.py.out +==== fanout_listener_java.out | remove_uuid +Listener: Setting an ExceptionListener on the connection as sample uses a MessageConsumer +Listener: Creating a non-transacted, auto-acknowledged session +Listener: Creating a MessageConsumer +Listener: Starting connection so MessageConsumer can receive messages +Listener: Received message: message 0 +Listener: Received message: message 1 +Listener: Received message: message 2 +Listener: Received message: message 3 +Listener: Received message: message 4 +Listener: Received message: message 5 +Listener: Received message: message 6 +Listener: Received message: message 7 +Listener: Received message: message 8 +Listener: Received message: message 9 +Listener: Received final message That's all, folks! +Listener: Closing connection +Listener: Closing JNDI context +==== fanout_listener_javaX.out | remove_uuid +Listener: Setting an ExceptionListener on the connection as sample uses a MessageConsumer +Listener: Creating a non-transacted, auto-acknowledged session +Listener: Creating a MessageConsumer +Listener: Starting connection so MessageConsumer can receive messages +Listener: Received message: message 0 +Listener: Received message: message 1 +Listener: Received message: message 2 +Listener: Received message: message 3 +Listener: Received message: message 4 +Listener: Received message: message 5 +Listener: Received message: message 6 +Listener: Received message: message 7 +Listener: Received message: message 8 +Listener: Received message: message 9 +Listener: Received final message That's all, folks! +Listener: Closing connection +Listener: Closing JNDI context +==== fanout_listener_javaXX.out | remove_uuid +Listener: Setting an ExceptionListener on the connection as sample uses a MessageConsumer +Listener: Creating a non-transacted, auto-acknowledged session +Listener: Creating a MessageConsumer +Listener: Starting connection so MessageConsumer can receive messages +Listener: Received message: message 0 +Listener: Received message: message 1 +Listener: Received message: message 2 +Listener: Received message: message 3 +Listener: Received message: message 4 +Listener: Received message: message 5 +Listener: Received message: message 6 +Listener: Received message: message 7 +Listener: Received message: message 8 +Listener: Received message: message 9 +Listener: Received final message That's all, folks! +Listener: Closing connection +Listener: Closing JNDI context diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/Listener.java b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/Listener.java index c8e5d89099..1a3d40041d 100644 --- a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/Listener.java +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/Listener.java @@ -5,9 +5,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 @@ -17,14 +17,21 @@ */ package org.apache.qpid.example.jmsexample.pubsub; -import org.apache.qpid.jms.TopicSubscriber; +import java.util.Properties; -import javax.jms.*; -import javax.jms.Session; +import javax.jms.BytesMessage; +import javax.jms.ConnectionFactory; +import javax.jms.ExceptionListener; +import javax.jms.JMSException; import javax.jms.Message; +import javax.jms.MessageListener; +import javax.jms.Session; +import javax.jms.TextMessage; +import javax.jms.Topic; +import javax.jms.TopicConnection; +import javax.jms.TopicSession; import javax.naming.Context; import javax.naming.InitialContext; -import java.util.Properties; /** * The example creates a TopicSubscriber on the specified @@ -56,6 +63,18 @@ public class Listener listener.runTest(); } + private void createListener(Context ctx,TopicSession session,String topicName) throws Exception{ + // lookup the topic usa + Topic topic=(Topic) ctx.lookup(topicName); + // Create a Message Subscriber + System.out.println(CLASS + ": Creating a Message Subscriber for topic " + topicName); + javax.jms.TopicSubscriber messageSubscriber=session.createSubscriber(topic); + + // Set a message listener on the messageConsumer + messageSubscriber.setMessageListener(new MyMessageListener(topicName)); + + } + /** * Start the example. */ @@ -94,77 +113,10 @@ public class Listener System.out.println(CLASS + ": Creating a non-transacted, auto-acknowledged session"); TopicSession session=connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); - // lookup the topic usa - Topic topic=(Topic) ctx.lookup("usa"); - // Create a Message Subscriber - System.out.println(CLASS + ": Creating a Message Subscriber for topic usa.#"); - javax.jms.TopicSubscriber messageSubscriber=session.createSubscriber(topic); - - // Bind each topic queue to the control queue so we know when to stop - /** - * The following line uses a temporary, experimental - * Qpid extension to add another binding to the topic's private queue. - * This extension is expected to be replaced by an alternative, - * less intrusive scheme in the very near future. - */ - ((TopicSubscriber) messageSubscriber).addBindingKey(topic, "control"); - - // Set a message listener on the messageConsumer - messageSubscriber.setMessageListener(new MyMessageListener("usa")); - - // lookup the topic europe - topic=(Topic) ctx.lookup("europe"); - // Create a Message Subscriber - System.out.println(CLASS + ": Creating a Message Subscriber for topic europe.#"); - messageSubscriber=session.createSubscriber(topic); - - // Bind each topic queue to the control queue so we know when to stop - /** - * The following line uses a temporary, experimental - * Qpid extension to add another binding to the topic's private queue. - * This extension is expected to be replaced by an alternative, - * less intrusive scheme in the very near future. - */ - ((org.apache.qpid.jms.TopicSubscriber) messageSubscriber).addBindingKey(topic, "control"); - - // Set a message listener on the messageConsumer - messageSubscriber.setMessageListener(new MyMessageListener("europe")); - - // lookup the topic news - topic=(Topic) ctx.lookup("news"); - // Create a Message Subscriber - System.out.println(CLASS + ": Creating a Message Subscriber for topic #.news"); - messageSubscriber=session.createSubscriber(topic); - - // Bind each topic queue to the control queue so we know when to stop - /** - * The following line uses a temporary, experimental - * Qpid extension to add another binding to the topic's private queue. - * This extension is expected to be replaced by an alternative, - * less intrusive scheme in the very near future. - */ - ((org.apache.qpid.jms.TopicSubscriber) messageSubscriber).addBindingKey(topic, "control"); - - // Set a message listener on the messageConsumer - messageSubscriber.setMessageListener(new MyMessageListener("news")); - - // lookup the topic weather - topic=(Topic) ctx.lookup("weather"); - // Create a Message Subscriber - System.out.println(CLASS + ": Creating a Message Subscriber for topic #.weather"); - messageSubscriber=session.createSubscriber(topic); - - // Bind each topic queue to the control queue so we know when to stop - /** - * The following line uses a temporary, experimental - * Qpid extension to add another binding to the topic's private queue. - * This extension is expected to be replaced by an alternative, - * less intrusive scheme in the very near future. - */ - ((org.apache.qpid.jms.TopicSubscriber) messageSubscriber).addBindingKey(topic, "control"); - - // Set a message listener on the messageConsumer - messageSubscriber.setMessageListener(new MyMessageListener("weather")); + createListener(ctx,session,"usa"); + createListener(ctx,session,"europe"); + createListener(ctx,session,"news"); + createListener(ctx,session,"weather"); // Now the messageConsumer is set up we can start the connection System.out.println(CLASS + ": Starting connection so TopicSubscriber can receive messages"); @@ -173,7 +125,7 @@ public class Listener // Wait for the messageConsumer to have received all the messages it needs synchronized (_lock) { - while (_finished < 3 && !_failed) + while (_finished < 4 && !_failed) { _lock.wait(); } diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/pubsub.properties b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/pubsub.properties index c72b0122df..675ac7fc0f 100644 --- a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/pubsub.properties +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/pubsub.properties @@ -21,16 +21,16 @@ java.naming.factory.initial = org.apache.qpid.jndi.PropertiesFileInitialContextF # register some connection factories # connectionfactory.[jndiname] = [ConnectionURL] -connectionfactory.qpidConnectionfactory = qpid:password=pass;username=name@tcp:localhost:5672 +connectionfactory.qpidConnectionfactory = amqp://username:password@clientid/test?brokerlist='tcp://localhost:5672' # register some topics in JNDI using the form # topic.[jndiName] = [physicalName] -topic.usa.weather = usa.weather -topic.usa.news = usa.news -topic.europe.weather = europe.weather -topic.europe.news = europe.news -topic.weather = #.weather -topic.news = #.news -topic.europe = europe.# -topic.usa = usa.# +topic.usa.weather = usa.weather,control +topic.usa.news = usa.news,control +topic.europe.weather = europe.weather,control +topic.europe.news = europe.news,control +topic.weather = #.weather,control +topic.news = #.news,control +topic.europe = europe.#,control +topic.usa = usa.#,control topic.control = control
\ No newline at end of file diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify new file mode 100644 index 0000000000..3e2a1b9b04 --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify @@ -0,0 +1,14 @@ +# See https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid/bin/verify +cpp=$CPP/pub-sub + +topic_listener_java(){ +java -cp "$CLASSPATH" org.apache.qpid.example.jmsexample.pubsub.Listener +} + +topic_publisher_java(){ +java -cp "$CLASSPATH" org.apache.qpid.example.jmsexample.pubsub.Publisher +} + +background "can receive messages" topic_listener_java +clients topic_publisher_java +outputs ./topic_publisher_java.out "topic_listener_java.out | remove_uuid | sort" diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify.in b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify.in new file mode 100644 index 0000000000..6e3074e611 --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify.in @@ -0,0 +1,95 @@ +==== topic_publisher_java.out +Publisher: Creating a non-transacted, auto-acknowledged session +Publisher: Creating a TestMessage to send to the topics +Publisher: Creating a Message Publisher for topic usa.weather +Publisher: Sending message 1 +Publisher: Sending message 2 +Publisher: Sending message 3 +Publisher: Sending message 4 +Publisher: Sending message 5 +Publisher: Sending message 6 +Publisher: Creating a Message Publisher for topic usa.news +Publisher: Sending message 1 +Publisher: Sending message 2 +Publisher: Sending message 3 +Publisher: Sending message 4 +Publisher: Sending message 5 +Publisher: Sending message 6 +Publisher: Creating a Message Publisher for topic europe.weather +Publisher: Sending message 1 +Publisher: Sending message 2 +Publisher: Sending message 3 +Publisher: Sending message 4 +Publisher: Sending message 5 +Publisher: Sending message 6 +Publisher: Creating a Message Publisher for topic europe.news +Publisher: Sending message 1 +Publisher: Sending message 2 +Publisher: Sending message 3 +Publisher: Sending message 4 +Publisher: Sending message 5 +Publisher: Sending message 6 +Publisher: Closing connection +Publisher: Closing JNDI context +==== topic_listener_java.out | remove_uuid | sort +Listener: Closing connection +Listener: Closing JNDI context +Listener: Creating a Message Subscriber for topic europe +Listener: Creating a Message Subscriber for topic news +Listener: Creating a Message Subscriber for topic usa +Listener: Creating a Message Subscriber for topic weather +Listener: Creating a non-transacted, auto-acknowledged session +Listener: Received message for topic: europe: message 1 +Listener: Received message for topic: europe: message 1 +Listener: Received message for topic: europe: message 2 +Listener: Received message for topic: europe: message 2 +Listener: Received message for topic: europe: message 3 +Listener: Received message for topic: europe: message 3 +Listener: Received message for topic: europe: message 4 +Listener: Received message for topic: europe: message 4 +Listener: Received message for topic: europe: message 5 +Listener: Received message for topic: europe: message 5 +Listener: Received message for topic: europe: message 6 +Listener: Received message for topic: europe: message 6 +Listener: Received message for topic: news: message 1 +Listener: Received message for topic: news: message 1 +Listener: Received message for topic: news: message 2 +Listener: Received message for topic: news: message 2 +Listener: Received message for topic: news: message 3 +Listener: Received message for topic: news: message 3 +Listener: Received message for topic: news: message 4 +Listener: Received message for topic: news: message 4 +Listener: Received message for topic: news: message 5 +Listener: Received message for topic: news: message 5 +Listener: Received message for topic: news: message 6 +Listener: Received message for topic: news: message 6 +Listener: Received message for topic: usa: message 1 +Listener: Received message for topic: usa: message 1 +Listener: Received message for topic: usa: message 2 +Listener: Received message for topic: usa: message 2 +Listener: Received message for topic: usa: message 3 +Listener: Received message for topic: usa: message 3 +Listener: Received message for topic: usa: message 4 +Listener: Received message for topic: usa: message 4 +Listener: Received message for topic: usa: message 5 +Listener: Received message for topic: usa: message 5 +Listener: Received message for topic: usa: message 6 +Listener: Received message for topic: usa: message 6 +Listener: Received message for topic: weather: message 1 +Listener: Received message for topic: weather: message 1 +Listener: Received message for topic: weather: message 2 +Listener: Received message for topic: weather: message 2 +Listener: Received message for topic: weather: message 3 +Listener: Received message for topic: weather: message 3 +Listener: Received message for topic: weather: message 4 +Listener: Received message for topic: weather: message 4 +Listener: Received message for topic: weather: message 5 +Listener: Received message for topic: weather: message 5 +Listener: Received message for topic: weather: message 6 +Listener: Received message for topic: weather: message 6 +Listener: Setting an ExceptionListener on the connection as sample uses a TopicSubscriber +Listener: Shutting down listener for europe +Listener: Shutting down listener for news +Listener: Shutting down listener for usa +Listener: Shutting down listener for weather +Listener: Starting connection so TopicSubscriber can receive messages diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify_cpp_java b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify_cpp_java new file mode 100644 index 0000000000..f189290fda --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify_cpp_java @@ -0,0 +1,10 @@ +# See https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid/bin/verify +cpp=$CPP/pub-sub + +topic_listener_java(){ +java -cp "$CLASSPATH" org.apache.qpid.example.jmsexample.pubsub.Listener +} + +background "can receive messages" topic_listener_java +clients $cpp/topic_publisher +outputs $cpp/topic_publisher.out "topic_listener_java.out | remove_uuid | sort" diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify_cpp_java.in b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify_cpp_java.in new file mode 100644 index 0000000000..62cc76baec --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify_cpp_java.in @@ -0,0 +1,55 @@ +==== topic_publisher.out +==== topic_listener_java.out | remove_uuid | sort +Listener: Closing connection +Listener: Closing JNDI context +Listener: Creating a Message Subscriber for topic europe +Listener: Creating a Message Subscriber for topic news +Listener: Creating a Message Subscriber for topic usa +Listener: Creating a Message Subscriber for topic weather +Listener: Creating a non-transacted, auto-acknowledged session +Listener: Received message for topic: europe: Message 0 +Listener: Received message for topic: europe: Message 0 +Listener: Received message for topic: europe: Message 1 +Listener: Received message for topic: europe: Message 1 +Listener: Received message for topic: europe: Message 2 +Listener: Received message for topic: europe: Message 2 +Listener: Received message for topic: europe: Message 3 +Listener: Received message for topic: europe: Message 3 +Listener: Received message for topic: europe: Message 4 +Listener: Received message for topic: europe: Message 4 +Listener: Received message for topic: news: Message 0 +Listener: Received message for topic: news: Message 0 +Listener: Received message for topic: news: Message 1 +Listener: Received message for topic: news: Message 1 +Listener: Received message for topic: news: Message 2 +Listener: Received message for topic: news: Message 2 +Listener: Received message for topic: news: Message 3 +Listener: Received message for topic: news: Message 3 +Listener: Received message for topic: news: Message 4 +Listener: Received message for topic: news: Message 4 +Listener: Received message for topic: usa: Message 0 +Listener: Received message for topic: usa: Message 0 +Listener: Received message for topic: usa: Message 1 +Listener: Received message for topic: usa: Message 1 +Listener: Received message for topic: usa: Message 2 +Listener: Received message for topic: usa: Message 2 +Listener: Received message for topic: usa: Message 3 +Listener: Received message for topic: usa: Message 3 +Listener: Received message for topic: usa: Message 4 +Listener: Received message for topic: usa: Message 4 +Listener: Received message for topic: weather: Message 0 +Listener: Received message for topic: weather: Message 0 +Listener: Received message for topic: weather: Message 1 +Listener: Received message for topic: weather: Message 1 +Listener: Received message for topic: weather: Message 2 +Listener: Received message for topic: weather: Message 2 +Listener: Received message for topic: weather: Message 3 +Listener: Received message for topic: weather: Message 3 +Listener: Received message for topic: weather: Message 4 +Listener: Received message for topic: weather: Message 4 +Listener: Setting an ExceptionListener on the connection as sample uses a TopicSubscriber +Listener: Shutting down listener for europe +Listener: Shutting down listener for news +Listener: Shutting down listener for usa +Listener: Shutting down listener for weather +Listener: Starting connection so TopicSubscriber can receive messages diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify_java_cpp b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify_java_cpp new file mode 100644 index 0000000000..87743681f4 --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify_java_cpp @@ -0,0 +1,10 @@ +# See https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid/bin/verify +cpp=$CPP/pub-sub + +topic_publisher_java(){ +java -cp "$CLASSPATH" org.apache.qpid.example.jmsexample.pubsub.Publisher +} + +background "Listening" $cpp/topic_listener +clients topic_publisher_java +outputs ./topic_publisher_java.out "$cpp/topic_listener.out | remove_uuid | sort" diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify_java_cpp.in b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify_java_cpp.in new file mode 100644 index 0000000000..8c5c26eaca --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify_java_cpp.in @@ -0,0 +1,99 @@ +==== topic_publisher_java.out +Publisher: Creating a non-transacted, auto-acknowledged session +Publisher: Creating a TestMessage to send to the topics +Publisher: Creating a Message Publisher for topic usa.weather +Publisher: Sending message 1 +Publisher: Sending message 2 +Publisher: Sending message 3 +Publisher: Sending message 4 +Publisher: Sending message 5 +Publisher: Sending message 6 +Publisher: Creating a Message Publisher for topic usa.news +Publisher: Sending message 1 +Publisher: Sending message 2 +Publisher: Sending message 3 +Publisher: Sending message 4 +Publisher: Sending message 5 +Publisher: Sending message 6 +Publisher: Creating a Message Publisher for topic europe.weather +Publisher: Sending message 1 +Publisher: Sending message 2 +Publisher: Sending message 3 +Publisher: Sending message 4 +Publisher: Sending message 5 +Publisher: Sending message 6 +Publisher: Creating a Message Publisher for topic europe.news +Publisher: Sending message 1 +Publisher: Sending message 2 +Publisher: Sending message 3 +Publisher: Sending message 4 +Publisher: Sending message 5 +Publisher: Sending message 6 +Publisher: Closing connection +Publisher: Closing JNDI context +==== topic_listener.out | remove_uuid | sort +Declaring queue: europe +Declaring queue: news +Declaring queue: usa +Declaring queue: weather +Listening for messages ... +Message: message 1 from europe +Message: message 1 from europe +Message: message 1 from news +Message: message 1 from news +Message: message 1 from usa +Message: message 1 from usa +Message: message 1 from weather +Message: message 1 from weather +Message: message 2 from europe +Message: message 2 from europe +Message: message 2 from news +Message: message 2 from news +Message: message 2 from usa +Message: message 2 from usa +Message: message 2 from weather +Message: message 2 from weather +Message: message 3 from europe +Message: message 3 from europe +Message: message 3 from news +Message: message 3 from news +Message: message 3 from usa +Message: message 3 from usa +Message: message 3 from weather +Message: message 3 from weather +Message: message 4 from europe +Message: message 4 from europe +Message: message 4 from news +Message: message 4 from news +Message: message 4 from usa +Message: message 4 from usa +Message: message 4 from weather +Message: message 4 from weather +Message: message 5 from europe +Message: message 5 from europe +Message: message 5 from news +Message: message 5 from news +Message: message 5 from usa +Message: message 5 from usa +Message: message 5 from weather +Message: message 5 from weather +Message: message 6 from europe +Message: message 6 from europe +Message: message 6 from news +Message: message 6 from news +Message: message 6 from usa +Message: message 6 from usa +Message: message 6 from weather +Message: message 6 from weather +Message: That's all, folks! from europe +Message: That's all, folks! from news +Message: That's all, folks! from usa +Message: That's all, folks! from weather +Shutting down listener for europe +Shutting down listener for news +Shutting down listener for usa +Shutting down listener for weather +Subscribing to queue europe +Subscribing to queue news +Subscribing to queue usa +Subscribing to queue weather diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify_java_python b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify_java_python new file mode 100644 index 0000000000..a6969c3951 --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify_java_python @@ -0,0 +1,10 @@ +# See https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid/bin/verify +py=$PYTHON/pubsub + +topic_publisher_java(){ +java -cp "$CLASSPATH" org.apache.qpid.example.jmsexample.pubsub.Publisher +} + +background "Queues created" $py/topic_subscriber.py +clients topic_publisher_java +outputs ./topic_publisher_java.out "$py/topic_subscriber.py.out | remove_uuid64 | sort" diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify_java_python.in b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify_java_python.in new file mode 100644 index 0000000000..eeb79a0a38 --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify_java_python.in @@ -0,0 +1,91 @@ +==== topic_publisher_java.out +Publisher: Creating a non-transacted, auto-acknowledged session +Publisher: Creating a TestMessage to send to the topics +Publisher: Creating a Message Publisher for topic usa.weather +Publisher: Sending message 1 +Publisher: Sending message 2 +Publisher: Sending message 3 +Publisher: Sending message 4 +Publisher: Sending message 5 +Publisher: Sending message 6 +Publisher: Creating a Message Publisher for topic usa.news +Publisher: Sending message 1 +Publisher: Sending message 2 +Publisher: Sending message 3 +Publisher: Sending message 4 +Publisher: Sending message 5 +Publisher: Sending message 6 +Publisher: Creating a Message Publisher for topic europe.weather +Publisher: Sending message 1 +Publisher: Sending message 2 +Publisher: Sending message 3 +Publisher: Sending message 4 +Publisher: Sending message 5 +Publisher: Sending message 6 +Publisher: Creating a Message Publisher for topic europe.news +Publisher: Sending message 1 +Publisher: Sending message 2 +Publisher: Sending message 3 +Publisher: Sending message 4 +Publisher: Sending message 5 +Publisher: Sending message 6 +Publisher: Closing connection +Publisher: Closing JNDI context +==== topic_subscriber.py.out | remove_uuid64 | sort +message 1 +message 1 +message 1 +message 1 +message 1 +message 1 +message 1 +message 1 +message 2 +message 2 +message 2 +message 2 +message 2 +message 2 +message 2 +message 2 +message 3 +message 3 +message 3 +message 3 +message 3 +message 3 +message 3 +message 3 +message 4 +message 4 +message 4 +message 4 +message 4 +message 4 +message 4 +message 4 +message 5 +message 5 +message 5 +message 5 +message 5 +message 5 +message 5 +message 5 +message 6 +message 6 +message 6 +message 6 +message 6 +message 6 +message 6 +message 6 +Messages queue: europe +Messages queue: news +Messages queue: usa +Messages queue: weather +Queues created - please start the topic producer +That's all, folks! +That's all, folks! +That's all, folks! +That's all, folks! diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify_python_java b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify_python_java new file mode 100644 index 0000000000..fc8f526145 --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify_python_java @@ -0,0 +1,10 @@ +# See https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid/bin/verify +py=$PYTHON/pubsub + +topic_listener_java(){ +java -cp "$CLASSPATH" org.apache.qpid.example.jmsexample.pubsub.Listener +} + +background "can receive messages" topic_listener_java +clients $py/topic_publisher.py +outputs $py/topic_publisher.py.out "topic_listener_java.out | remove_uuid | sort" diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify_python_java.in b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify_python_java.in new file mode 100644 index 0000000000..507a51f78c --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/pubsub/verify_python_java.in @@ -0,0 +1,55 @@ +==== topic_publisher.py.out +==== topic_listener_java.out | remove_uuid | sort +Listener: Closing connection +Listener: Closing JNDI context +Listener: Creating a Message Subscriber for topic europe +Listener: Creating a Message Subscriber for topic news +Listener: Creating a Message Subscriber for topic usa +Listener: Creating a Message Subscriber for topic weather +Listener: Creating a non-transacted, auto-acknowledged session +Listener: Received message for topic: europe: message 0 +Listener: Received message for topic: europe: message 0 +Listener: Received message for topic: europe: message 1 +Listener: Received message for topic: europe: message 1 +Listener: Received message for topic: europe: message 2 +Listener: Received message for topic: europe: message 2 +Listener: Received message for topic: europe: message 3 +Listener: Received message for topic: europe: message 3 +Listener: Received message for topic: europe: message 4 +Listener: Received message for topic: europe: message 4 +Listener: Received message for topic: news: message 0 +Listener: Received message for topic: news: message 0 +Listener: Received message for topic: news: message 1 +Listener: Received message for topic: news: message 1 +Listener: Received message for topic: news: message 2 +Listener: Received message for topic: news: message 2 +Listener: Received message for topic: news: message 3 +Listener: Received message for topic: news: message 3 +Listener: Received message for topic: news: message 4 +Listener: Received message for topic: news: message 4 +Listener: Received message for topic: usa: message 0 +Listener: Received message for topic: usa: message 0 +Listener: Received message for topic: usa: message 1 +Listener: Received message for topic: usa: message 1 +Listener: Received message for topic: usa: message 2 +Listener: Received message for topic: usa: message 2 +Listener: Received message for topic: usa: message 3 +Listener: Received message for topic: usa: message 3 +Listener: Received message for topic: usa: message 4 +Listener: Received message for topic: usa: message 4 +Listener: Received message for topic: weather: message 0 +Listener: Received message for topic: weather: message 0 +Listener: Received message for topic: weather: message 1 +Listener: Received message for topic: weather: message 1 +Listener: Received message for topic: weather: message 2 +Listener: Received message for topic: weather: message 2 +Listener: Received message for topic: weather: message 3 +Listener: Received message for topic: weather: message 3 +Listener: Received message for topic: weather: message 4 +Listener: Received message for topic: weather: message 4 +Listener: Setting an ExceptionListener on the connection as sample uses a TopicSubscriber +Listener: Shutting down listener for europe +Listener: Shutting down listener for news +Listener: Shutting down listener for usa +Listener: Shutting down listener for weather +Listener: Starting connection so TopicSubscriber can receive messages diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/requestResponse.properties b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/requestResponse.properties index e732ce560d..8d6706eeb8 100644 --- a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/requestResponse.properties +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/requestResponse.properties @@ -20,7 +20,7 @@ java.naming.factory.initial = org.apache.qpid.jndi.PropertiesFileInitialContextF # register some connection factories # connectionfactory.[jndiname] = [ConnectionURL] -connectionfactory.qpidConnectionfactory = qpid:password=pass;username=name@tcp:localhost:5672 +connectionfactory.qpidConnectionfactory = amqp://username:password@clientid/test?brokerlist='tcp://localhost:5672' # register some queues in JNDI using the form # queue.[jndiName] = [physicalName] diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify new file mode 100644 index 0000000000..576b871dc4 --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify @@ -0,0 +1,16 @@ +# See https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid/bin/verify +cpp=$CPP/pub-sub + +client_java(){ +java -cp "$CLASSPATH" org.apache.qpid.example.jmsexample.requestResponse.Client +} + +server_java(){ +java -cp "$CLASSPATH" org.apache.qpid.example.jmsexample.requestResponse.Server +} + +background "can receive messages" server_java +clients client_java +PID=`ps -aux | grep 'qpid-client-incubating-M3.jar'|grep -v 'grep'|awk '{ print $2 }'` +kill -9 $PID +outputs "client_java.out | remove_uuid" "server_java.out | remove_uuid" diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify.in b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify.in new file mode 100644 index 0000000000..f2c244dea6 --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify.in @@ -0,0 +1,38 @@ +==== client_java.out | remove_uuid +Client: Setting an ExceptionListener on the connection as sample uses a MessageConsumer +Client: Creating a non-transacted, auto-acknowledged session +Client: Creating a QueueRequestor +Client: Starting connection +Client: Request Content= Twas brillig, and the slithy toves +Client: Response Content= TWAS BRILLIG, AND THE SLITHY TOVES +Client: Request Content= Did gire and gymble in the wabe. +Client: Response Content= DID GIRE AND GYMBLE IN THE WABE. +Client: Request Content= All mimsy were the borogroves, +Client: Response Content= ALL MIMSY WERE THE BOROGROVES, +Client: Request Content= And the mome raths outgrabe. +Client: Response Content= AND THE MOME RATHS OUTGRABE. +Client: Closing connection +Client: Closing JNDI context +==== server_java.out | remove_uuid +Server: Setting an ExceptionListener on the connection as sample uses a MessageConsumer +Server: Creating a non-transacted, auto-acknowledged session +Server: Creating a MessageConsumer +Server: Creating a MessageProducer +Server: Starting connection so MessageConsumer can receive messages +Server: Receiving the message +Server: Activating response queue listener +Server: Response = TWAS BRILLIG, AND THE SLITHY TOVES + +Server: Receiving the message +Server: Activating response queue listener +Server: Response = DID GIRE AND GYMBLE IN THE WABE. + +Server: Receiving the message +Server: Activating response queue listener +Server: Response = ALL MIMSY WERE THE BOROGROVES, + +Server: Receiving the message +Server: Activating response queue listener +Server: Response = AND THE MOME RATHS OUTGRABE. + +Server: Receiving the message diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify_cpp_java b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify_cpp_java new file mode 100644 index 0000000000..4551b9ab0c --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify_cpp_java @@ -0,0 +1,11 @@ +# See https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid/bin/verify +cpp=$CPP/request-response + +client_java(){ +java -cp "$CLASSPATH" org.apache.qpid.example.jmsexample.requestResponse.Client +} + +background "Waiting" $cpp/server +clients client_java +kill %% +outputs "client_java.out | remove_uuid" "$cpp/server.out | remove_uuid" diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify_cpp_java.in b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify_cpp_java.in new file mode 100644 index 0000000000..4b7e7e0741 --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify_cpp_java.in @@ -0,0 +1,22 @@ +==== client_java.out | remove_uuid +Client: Setting an ExceptionListener on the connection as sample uses a MessageConsumer +Client: Creating a non-transacted, auto-acknowledged session +Client: Creating a QueueRequestor +Client: Starting connection +Client: Request Content= Twas brillig, and the slithy toves +Client: Response Content= TWAS BRILLIG, AND THE SLITHY TOVES +Client: Request Content= Did gire and gymble in the wabe. +Client: Response Content= DID GIRE AND GYMBLE IN THE WABE. +Client: Request Content= All mimsy were the borogroves, +Client: Response Content= ALL MIMSY WERE THE BOROGROVES, +Client: Request Content= And the mome raths outgrabe. +Client: Response Content= AND THE MOME RATHS OUTGRABE. +Client: Closing connection +Client: Closing JNDI context +==== server.out | remove_uuid +Activating request queue listener for: request +Waiting for requests +Request: Twas brillig, and the slithy toves (TempQueue) +Request: Did gire and gymble in the wabe. (TempQueue) +Request: All mimsy were the borogroves, (TempQueue) +Request: And the mome raths outgrabe. (TempQueue) diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify_java_cpp b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify_java_cpp new file mode 100644 index 0000000000..3e730fde5e --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify_java_cpp @@ -0,0 +1,12 @@ +# See https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid/bin/verify +cpp=$CPP/request-response + +server_java(){ +java -cp "$CLASSPATH" org.apache.qpid.example.jmsexample.requestResponse.Server +} + +background "can receive messages" server_java +clients $cpp/client +PID=`ps -aux | grep 'qpid-client-incubating-M3.jar'|grep -v 'grep'|awk '{ print $2 }'` +kill -9 $PID +outputs "$cpp/client.out | remove_uuid" "server_java.out | remove_uuid" diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify_java_cpp.in b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify_java_cpp.in new file mode 100644 index 0000000000..9cccf39393 --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify_java_cpp.in @@ -0,0 +1,35 @@ +==== client.out | remove_uuid +Activating response queue listener for: client +Request: Twas brillig, and the slithy toves +Request: Did gire and gymble in the wabe. +Request: All mimsy were the borogroves, +Request: And the mome raths outgrabe. +Waiting for all responses to arrive ... +Response: TWAS BRILLIG, AND THE SLITHY TOVES +Response: DID GIRE AND GYMBLE IN THE WABE. +Response: ALL MIMSY WERE THE BOROGROVES, +Response: AND THE MOME RATHS OUTGRABE. +Shutting down listener for client +==== server_java.out | remove_uuid +Server: Setting an ExceptionListener on the connection as sample uses a MessageConsumer +Server: Creating a non-transacted, auto-acknowledged session +Server: Creating a MessageConsumer +Server: Creating a MessageProducer +Server: Starting connection so MessageConsumer can receive messages +Server: Receiving the message +Server: Activating response queue listener +Server: Response = TWAS BRILLIG, AND THE SLITHY TOVES + +Server: Receiving the message +Server: Activating response queue listener +Server: Response = DID GIRE AND GYMBLE IN THE WABE. + +Server: Receiving the message +Server: Activating response queue listener +Server: Response = ALL MIMSY WERE THE BOROGROVES, + +Server: Receiving the message +Server: Activating response queue listener +Server: Response = AND THE MOME RATHS OUTGRABE. + +Server: Receiving the message diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify_java_python b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify_java_python new file mode 100644 index 0000000000..c773ca481d --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify_java_python @@ -0,0 +1,12 @@ +# See https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid/bin/verify +py=$PYTHON/request-response + +server_java(){ +java -cp "$CLASSPATH" org.apache.qpid.example.jmsexample.requestResponse.Server +} + +background "can receive messages" server_java +clients $py/client.py +PID=`ps -aux | grep 'qpid-client-incubating-M3.jar'|grep -v 'grep'|awk '{ print $2 }'` +kill -9 $PID +outputs "$py/client.py.out | remove_uuid64" "server_java.out | remove_uuid" diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify_java_python.in b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify_java_python.in new file mode 100644 index 0000000000..d7ff7df160 --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify_java_python.in @@ -0,0 +1,34 @@ +==== client.py.out | remove_uuid64 +Request: Twas brilling, and the slithy toves +Request: Did gyre and gimble in the wabe. +Request: All mimsy were the borogroves, +Request: And the mome raths outgrabe. +Messages queue: ReplyTo: +Response: TWAS BRILLING, AND THE SLITHY TOVES +Response: DID GYRE AND GIMBLE IN THE WABE. +Response: ALL MIMSY WERE THE BOROGROVES, +Response: AND THE MOME RATHS OUTGRABE. +No more messages! +==== server_java.out | remove_uuid +Server: Setting an ExceptionListener on the connection as sample uses a MessageConsumer +Server: Creating a non-transacted, auto-acknowledged session +Server: Creating a MessageConsumer +Server: Creating a MessageProducer +Server: Starting connection so MessageConsumer can receive messages +Server: Receiving the message +Server: Activating response queue listener +Server: Response = TWAS BRILLING, AND THE SLITHY TOVES + +Server: Receiving the message +Server: Activating response queue listener +Server: Response = DID GYRE AND GIMBLE IN THE WABE. + +Server: Receiving the message +Server: Activating response queue listener +Server: Response = ALL MIMSY WERE THE BOROGROVES, + +Server: Receiving the message +Server: Activating response queue listener +Server: Response = AND THE MOME RATHS OUTGRABE. + +Server: Receiving the message diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify_python_java b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify_python_java new file mode 100644 index 0000000000..3c0a3985ae --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify_python_java @@ -0,0 +1,11 @@ +# See https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid/bin/verify +py=$PYTHON/request-response + +client_java(){ +java -cp "$CLASSPATH" org.apache.qpid.example.jmsexample.requestResponse.Client +} + +background "Request server running" $py/server.py +clients client_java +kill %% +outputs "client_java.out | remove_uuid" "$py/server.py.out | remove_uuid64" diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify_python_java.in b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify_python_java.in new file mode 100644 index 0000000000..4da22b62cc --- /dev/null +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/requestResponse/verify_python_java.in @@ -0,0 +1,18 @@ +==== client_java.out | remove_uuid +Client: Setting an ExceptionListener on the connection as sample uses a MessageConsumer +Client: Creating a non-transacted, auto-acknowledged session +Client: Creating a QueueRequestor +Client: Starting connection +Client: Request Content= Twas brillig, and the slithy toves +Client: Response Content= TWAS BRILLIG, AND THE SLITHY TOVES +Client: Request Content= Did gire and gymble in the wabe. +Client: Response Content= DID GIRE AND GYMBLE IN THE WABE. +Client: Request Content= All mimsy were the borogroves, +Client: Response Content= ALL MIMSY WERE THE BOROGROVES, +Client: Request Content= And the mome raths outgrabe. +Client: Response Content= AND THE MOME RATHS OUTGRABE. +Client: Closing connection +Client: Closing JNDI context +==== server.py.out | remove_uuid64 +Request server running - run your client now. +(Times out after 100 seconds ...) diff --git a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/transacted/transacted.properties b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/transacted/transacted.properties index 394d5f9036..601c5a24e2 100644 --- a/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/transacted/transacted.properties +++ b/qpid/java/client/example/src/main/java/org/apache/qpid/example/jmsexample/transacted/transacted.properties @@ -20,7 +20,7 @@ java.naming.factory.initial = org.apache.qpid.jndi.PropertiesFileInitialContextF # register some connection factories # connectionfactory.[jndiname] = [ConnectionURL] -connectionfactory.qpidConnectionfactory = qpid:password=pass;username=name@tcp:localhost:5672 +connectionfactory.qpidConnectionfactory = amqp://username:password@clientid/test?brokerlist='tcp://localhost:5672' # register some queues in JNDI using the form # queue.[jndiName] = [physicalName] diff --git a/qpid/java/client/pom.xml b/qpid/java/client/pom.xml index 8a16ec1c44..bcce3e1d3b 100644 --- a/qpid/java/client/pom.xml +++ b/qpid/java/client/pom.xml @@ -173,6 +173,9 @@ <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> + <excludes> + <exclude>**/QpidTestCase.java</exclude> + </excludes> <systemProperties> <property> <name>amqj.logging.level</name> diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java index 80bed1a131..1d8a6df093 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java @@ -22,6 +22,7 @@ package org.apache.qpid.client; import org.apache.qpid.AMQConnectionFailureException; import org.apache.qpid.AMQException; +import org.apache.qpid.AMQProtocolException; import org.apache.qpid.AMQUndeliveredException; import org.apache.qpid.AMQUnresolvedAddressException; import org.apache.qpid.client.failover.FailoverException; @@ -41,6 +42,7 @@ import org.apache.qpid.jms.ConnectionURL; import org.apache.qpid.jms.FailoverPolicy; import org.apache.qpid.protocol.AMQConstant; import org.apache.qpid.url.URLSyntaxException; +import org.apache.qpidity.transport.TransportConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -339,30 +341,18 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect */ public AMQConnection(ConnectionURL connectionURL, SSLConfiguration sslConfig) throws AMQException { - /* This JVM arg is only used for test code - Unless u pass a url it is difficult to determine which version to use - Most of the test code use an AMQConnection constructor that doesn't use - the url. So you need this switch to say which code path to test. - - Another complication is that when a constructor is called with out a url - they would construct a 0-8 url and pass into the construtor that takes a url. - - In such an instance u need the jvm argument to force an 0-10 connection - Once the 0-10 code base stabilises, 0-10 will be the default. - */ - - if (Boolean.getBoolean("SwitchCon")) - { - connectionURL.setURLVersion((Boolean.getBoolean("0-10")? ConnectionURL.URL_0_10:ConnectionURL.URL_0_8)); - } - - if (connectionURL.getURLVersion() == ConnectionURL.URL_0_10) + _failoverPolicy = new FailoverPolicy(connectionURL); + if (_failoverPolicy.getCurrentBrokerDetails().getTransport().equals(BrokerDetails.VM)) { - _delegate = new AMQConnectionDelegate_0_10(this); + _delegate = new AMQConnectionDelegate_0_8(this); } else { - _delegate = new AMQConnectionDelegate_0_8(this); + // We always assume that the broker supports the lates AMQ protocol verions + // thie is currently 0.10 + // TODO: use this code once we have switch to 0.10 + // getDelegate(); + _delegate = new AMQConnectionDelegate_0_10(this); } final ArrayList<JMSException> exceptions = new ArrayList<JMSException>(); @@ -402,8 +392,6 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect _username = connectionURL.getUsername(); _password = connectionURL.getPassword(); - _protocolVersion = connectionURL.getProtocolVersion(); - setVirtualHost(connectionURL.getVirtualHost()); if (connectionURL.getDefaultQueueExchangeName() != null) @@ -426,7 +414,6 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect _temporaryTopicExchangeName = connectionURL.getTemporaryTopicExchangeName(); } - _failoverPolicy = new FailoverPolicy(connectionURL); _protocolHandler = new AMQProtocolHandler(this); @@ -442,6 +429,18 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect { makeBrokerConnection(_failoverPolicy.getNextBrokerDetails()); } + catch (AMQProtocolException pe) + { + if (_logger.isInfoEnabled()) + { + _logger.info(pe.getMessage()); + _logger.info("Trying broker supported protocol version: " + + TransportConstants.getVersionMajor() + "." + + TransportConstants.getVersionMinor()); + } + // we need to check whether we have a delegate for the supported protocol + getDelegate(); + } catch (Exception e) { lastException = e; @@ -542,6 +541,26 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect return ((cause instanceof ConnectException) || (cause instanceof UnresolvedAddressException)); } + private void getDelegate() throws AMQProtocolException + { + try + { + Class c = Class.forName("org.apache.qpid.client.AMQConnectionDelegate_" + + TransportConstants.getVersionMajor() + "_" + + TransportConstants.getVersionMinor()); + Class partypes[] = new Class[1]; + partypes[0] = AMQConnection.class; + _delegate = (AMQConnectionDelegate) c.getConstructor(partypes).newInstance(this); + } + catch (Exception e) + { + throw new AMQProtocolException(AMQConstant.UNSUPPORTED_CLIENT_PROTOCOL_ERROR, + "Protocol: " + TransportConstants.getVersionMajor() + "." + + TransportConstants.getVersionMinor() + " is rquired by the broker but is not " + + "currently supported by this client library implementation", e); + } + } + protected AMQConnection(String username, String password, String clientName, String virtualHost) { _clientName = clientName; diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_0_10.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_0_10.java index bf1ed49492..bde60c433f 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_0_10.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_0_10.java @@ -6,6 +6,7 @@ import javax.jms.JMSException; import javax.jms.XASession; import org.apache.qpid.AMQException; +import org.apache.qpid.AMQProtocolException; import org.apache.qpid.protocol.AMQConstant; import org.apache.qpid.client.failover.FailoverException; import org.apache.qpid.jms.BrokerDetails; @@ -14,6 +15,7 @@ import org.apache.qpidity.nclient.Client; import org.apache.qpidity.nclient.ClosedListener; import org.apache.qpidity.ErrorCode; import org.apache.qpidity.QpidException; +import org.apache.qpidity.ProtocolException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -113,6 +115,10 @@ public class AMQConnectionDelegate_0_10 implements AMQConnectionDelegate, Closed _conn.getUsername(), _conn.getPassword()); _qpidConnection.setClosedListener(this); } + catch(ProtocolException pe) + { + throw new AMQProtocolException(null, pe.getMessage(), pe); + } catch (QpidException e) { throw new AMQException(AMQConstant.CHANNEL_ERROR, "cannot connect to broker", e); diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionFactory.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionFactory.java index 35a2431b62..fd8063e99b 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionFactory.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionFactory.java @@ -38,6 +38,7 @@ import javax.naming.spi.ObjectFactory; import org.apache.qpid.jms.ConnectionURL; import org.apache.qpid.url.AMQBindingURL; import org.apache.qpid.url.URLSyntaxException; +import org.apache.qpidity.transport.TransportConstants; public class AMQConnectionFactory implements ConnectionFactory, QueueConnectionFactory, TopicConnectionFactory, @@ -433,9 +434,10 @@ public class AMQConnectionFactory implements ConnectionFactory, QueueConnectionF */ public XAConnection createXAConnection() throws JMSException { - if (_connectionDetails.getURLVersion() == ConnectionURL.URL_0_8) + if (TransportConstants.getVersionMajor() == 0 && + TransportConstants.getVersionMinor() == 8) { - throw new UnsupportedOperationException("This version does not support XA operations"); + throw new UnsupportedOperationException("This protocol version does not support XA operations"); } else { diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionURL.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionURL.java index 4c73882d7a..02959aff3b 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionURL.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionURL.java @@ -24,10 +24,8 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.net.MalformedURLException; -import org.apache.qpid.client.url.URLParser_0_8; -import org.apache.qpid.client.url.URLParser_0_10; +import org.apache.qpid.client.url.URLParser; import org.apache.qpid.framing.AMQShortString; import org.apache.qpid.framing.ProtocolVersion; import org.apache.qpid.jms.BrokerDetails; @@ -54,8 +52,6 @@ public class AMQConnectionURL implements ConnectionURL private AMQShortString _defaultTopicExchangeName; private AMQShortString _temporaryTopicExchangeName; private AMQShortString _temporaryQueueExchangeName; - private ProtocolVersion _protocolVersion = ProtocolVersion.defaultProtocolVersion(); - private byte _urlVersion; public AMQConnectionURL(String fullURL) throws URLSyntaxException { @@ -64,57 +60,7 @@ public class AMQConnectionURL implements ConnectionURL _options = new HashMap<String, String>(); _brokers = new LinkedList<BrokerDetails>(); _failoverOptions = new HashMap<String, String>(); - - if (!Boolean.getBoolean("SwitchCon")) - { - // We need to decided the version based on URL - if (fullURL.startsWith("qpid")) - { - //URLParser - URLParser_0_10 parser = null; - try - { - parser = new URLParser_0_10(fullURL); - } - catch (MalformedURLException e) - { - throw new URLSyntaxException(fullURL,e.getMessage(),0,0); - } - setBrokerDetails(parser.getAllBrokerDetails()); - // use the first instance username and password - // This is temporary as the URL must be changed for olding this information as part of the full URL - BrokerDetails firstBroker = getBrokerDetails(0); - setUsername(firstBroker.getProperty(BrokerDetails.USERNAME)); - setPassword(firstBroker.getProperty(BrokerDetails.PASSWORD)); - setClientName(firstBroker.getProperty(BrokerDetails.CLIENT_ID)); - setVirtualHost(firstBroker.getProperty(BrokerDetails.VIRTUAL_HOST)); - _urlVersion = URL_0_10; - } - else - { - new URLParser_0_8(this); - _urlVersion = URL_0_8; - } - } - } - - public byte getURLVersion() - { - return _urlVersion; - } - - public void setURLVersion(byte version) - { - _urlVersion = version; - if(_options.containsKey(OPTIONS_PROTOCOL_VERSION)) - { - ProtocolVersion pv = ProtocolVersion.parse(_options.get(OPTIONS_PROTOCOL_VERSION)); - if(pv != null) - { - _protocolVersion = pv; - } - } - + new URLParser(this); } public String getURL() @@ -277,11 +223,6 @@ public class AMQConnectionURL implements ConnectionURL _temporaryTopicExchangeName = temporaryTopicExchangeName; } - public ProtocolVersion getProtocolVersion() - { - return _protocolVersion; - } - public String toString() { return _url; diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession.java index 9baccdb9d9..f0078d0125 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession.java @@ -618,6 +618,10 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess // + Arrays.asList(stackTrace).subList(3, stackTrace.length - 1)); } + if( _dispatcher != null ) + { + _dispatcher.setConnectionStopped(true); + } synchronized (_connection.getFailoverMutex()) { // We must close down all producers and consumers in an orderly fashion. This is the only method @@ -2048,11 +2052,6 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess */ private void closeConsumers(Throwable error) throws JMSException { - if (_dispatcher != null) - { - _dispatcher.close(); - _dispatcher = null; - } // we need to clone the list of consumers since the close() method updates the _consumers collection // which would result in a concurrent modification exception final ArrayList<BasicMessageConsumer> clonedConsumers = new ArrayList<BasicMessageConsumer>(_consumers.values()); @@ -2071,6 +2070,11 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess } } // at this point the _consumers map will be empty + if (_dispatcher != null) + { + _dispatcher.close(); + _dispatcher = null; + } } /** @@ -2584,7 +2588,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess { UnprocessedMessage message = (UnprocessedMessage) messages.next(); - if ((consumerTag == null) || message.getConsumerTag().equals(consumerTag)) + if ((consumerTag == null) || message.getConsumerTag().equals(consumerTag.toString())) { if (_logger.isDebugEnabled()) { diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_10.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_10.java index dd129fdbfa..dbe95af1c3 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_10.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_10.java @@ -27,7 +27,6 @@ import org.apache.qpid.client.failover.FailoverProtectedOperation; import org.apache.qpid.client.protocol.AMQProtocolHandler; import org.apache.qpid.client.message.MessageFactoryRegistry; import org.apache.qpid.client.message.FiledTableSupport; -import org.apache.qpid.client.message.UnprocessedMessage; import org.apache.qpidity.nclient.Session; import org.apache.qpidity.nclient.util.MessagePartListenerAdapter; import org.apache.qpidity.ErrorCode; @@ -45,7 +44,6 @@ import javax.jms.IllegalStateException; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.UUID; import java.util.Map; -import java.util.Iterator; /** * This is a 0.10 Session @@ -58,10 +56,6 @@ public class AMQSession_0_10 extends AMQSession */ private static final Logger _logger = LoggerFactory.getLogger(AMQSession_0_10.class); - /** - * The maximum number of pre-fetched messages per destination - */ - public static long MAX_PREFETCH = 1000; /** * The underlying QpidSession @@ -101,8 +95,6 @@ public class AMQSession_0_10 extends AMQSession super(con, channelId, transacted, acknowledgeMode, messageFactoryRegistry, defaultPrefetchHighMark, defaultPrefetchLowMark); - MAX_PREFETCH = Integer.parseInt(System.getProperty("max_prefetch","1000")); - // create the qpid session with an expiry <= 0 so that the session does not expire _qpidSession = qpidConnection.createSession(0); // set the exception listnere for this session @@ -249,24 +241,6 @@ public class AMQSession_0_10 extends AMQSession getCurrentException(); } - /** - * We need to release message that may be pre-fetched in the local queue - * - * @throws JMSException - */ - public void close() throws JMSException - { - super.close(); - // We need to release pre-fetched messages - Iterator messages=_queue.iterator(); - while (messages.hasNext()) - { - UnprocessedMessage message=(UnprocessedMessage) messages.next(); - messages.remove(); - rejectMessage(message, true); - } - } - /** * Commit the receipt and the delivery of all messages exchanged by this session resources. @@ -422,17 +396,23 @@ public class AMQSession_0_10 extends AMQSession new MessagePartListenerAdapter((BasicMessageConsumer_0_10) consumer), null, consumer.isNoLocal() ? Option.NO_LOCAL : Option.NO_OPTION, consumer.isExclusive() ? Option.EXCLUSIVE : Option.NO_OPTION); - - getQpidSession().messageFlowMode(consumer.getConsumerTag().toString(), Session.MESSAGE_FLOW_MODE_WINDOW); + if (ClientProperties.MAX_PREFETCH == 0) + { + getQpidSession().messageFlowMode(consumer.getConsumerTag().toString(), Session.MESSAGE_FLOW_MODE_CREDIT); + } + else + { + getQpidSession().messageFlowMode(consumer.getConsumerTag().toString(), Session.MESSAGE_FLOW_MODE_WINDOW); + } getQpidSession().messageFlow(consumer.getConsumerTag().toString(), Session.MESSAGE_FLOW_UNIT_BYTE, 0xFFFFFFFF); // We need to sync so that we get notify of an error. - if(consumer.isStrated()) + // only if not immediat prefetch + if(ClientProperties.MAX_PREFETCH > 0 && (consumer.isStrated() || _immediatePrefetch)) { // set the flow getQpidSession().messageFlow(consumer.getConsumerTag().toString(), org.apache.qpidity.nclient.Session.MESSAGE_FLOW_UNIT_MESSAGE, - AMQSession_0_10.MAX_PREFETCH); - + ClientProperties.MAX_PREFETCH); } getQpidSession().sync(); getCurrentException(); @@ -534,17 +514,27 @@ public class AMQSession_0_10 extends AMQSession //only set if msg list is null try { - // if (consumer.getMessageListener() != null) - // { - getQpidSession().messageFlow(consumer.getConsumerTag().toString(), Session.MESSAGE_FLOW_UNIT_MESSAGE, - MAX_PREFETCH); - // } + if (ClientProperties.MAX_PREFETCH == 0) + { + if (consumer.getMessageListener() != null) + { + getQpidSession().messageFlow(consumer.getConsumerTag().toString(), + Session.MESSAGE_FLOW_UNIT_MESSAGE, 1); + } + } + else + { + getQpidSession() + .messageFlow(consumer.getConsumerTag().toString(), Session.MESSAGE_FLOW_UNIT_MESSAGE, + ClientProperties.MAX_PREFETCH); + } getQpidSession() - .messageFlow(consumer.getConsumerTag().toString(), Session.MESSAGE_FLOW_UNIT_BYTE, 0xFFFFFFFF); + .messageFlow(consumer.getConsumerTag().toString(), Session.MESSAGE_FLOW_UNIT_BYTE, + 0xFFFFFFFF); } - catch(Exception e) + catch (Exception e) { - throw new AMQException(AMQConstant.INTERNAL_ERROR,"Error while trying to get the listener",e); + throw new AMQException(AMQConstant.INTERNAL_ERROR, "Error while trying to get the listener", e); } } } @@ -654,21 +644,14 @@ public class AMQSession_0_10 extends AMQSession void start() throws AMQException { - suspendChannel(false); + super.start(); for(BasicMessageConsumer c: _consumers.values()) { c.start(); } - // If the event dispatcher is not running then start it too. - if (hasMessageListeners()) - { - startDistpatcherIfNecessary(); - } } - - void stop() throws AMQException { super.stop(); @@ -678,27 +661,7 @@ public class AMQSession_0_10 extends AMQSession } } - synchronized void startDistpatcherIfNecessary() - { - // If IMMEDIATE_PREFETCH is not set then we need to start fetching - if (!_immediatePrefetch) - { - // We do this now if this is the first call on a started connection - if (isSuspended() && _firstDispatcher.getAndSet(false)) - { - try - { - suspendChannel(false); - } - catch (AMQException e) - { - _logger.info("Unsuspending channel threw an exception:" + e); - } - } - } - - startDistpatcherIfNecessary(false); - } + public TopicSubscriber createDurableSubscriber(Topic topic, String name) throws JMSException diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_10.java b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_10.java index 74aac53cda..84d37ed0ef 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_10.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_10.java @@ -27,7 +27,6 @@ import org.apache.qpid.AMQException; import org.apache.qpid.exchange.ExchangeDefaults; import org.apache.qpid.protocol.AMQConstant; import org.apache.qpidity.api.Message; -import org.apache.qpidity.nclient.Session; import org.apache.qpidity.transport.*; import org.apache.qpidity.QpidException; import org.apache.qpidity.filter.MessageFilter; @@ -39,6 +38,7 @@ import javax.jms.MessageListener; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Iterator; +import java.util.concurrent.atomic.AtomicBoolean; /** * This is a 0.10 message consumer. @@ -72,6 +72,11 @@ public class BasicMessageConsumer_0_10 extends BasicMessageConsumer<Struct[], By */ private boolean _isStarted = false; + /** + * Specify whether this consumer is performing a sync receive + */ + private final AtomicBoolean _syncReceive = new AtomicBoolean(false); + //--- constructor protected BasicMessageConsumer_0_10(int channelId, AMQConnection connection, AMQDestination destination, String messageSelector, boolean noLocal, MessageFactoryRegistry messageFactory, @@ -136,6 +141,11 @@ public class BasicMessageConsumer_0_10 extends BasicMessageConsumer<Struct[], By } if (messageOk) { + if (isMessageListenerSet() && ClientProperties.MAX_PREFETCH == 0) + { + _0_10session.getQpidSession().messageFlow(getConsumerTag().toString(), + org.apache.qpidity.nclient.Session.MESSAGE_FLOW_UNIT_MESSAGE, 1); + } _logger.debug("messageOk, trying to notify"); super.notifyMessage(jmsMessage); } @@ -307,23 +317,33 @@ public class BasicMessageConsumer_0_10 extends BasicMessageConsumer<Struct[], By _logger.debug("messageOk " + messageOk); _logger.debug("_preAcquire " + _preAcquire); } - if (!messageOk && _preAcquire) + if (!messageOk) { - // this is the case for topics - // We need to ack this message - if (_logger.isDebugEnabled()) + if (_preAcquire) + { + // this is the case for topics + // We need to ack this message + if (_logger.isDebugEnabled()) + { + _logger.debug("filterMessage - trying to ack message"); + } + acknowledgeMessage(message); + } + else { - _logger.debug("filterMessage - trying to ack message"); + if (_logger.isDebugEnabled()) + { + _logger.debug("Message not OK, releasing"); + } + releaseMessage(message); } - acknowledgeMessage(message); - } - else if (!messageOk) - { - if (_logger.isDebugEnabled()) + // if we are syncrhonously waiting for a message + // and messages are not prefetched we then need to request another one + if(ClientProperties.MAX_PREFETCH == 0) { - _logger.debug("Message not OK, releasing"); + _0_10session.getQpidSession().messageFlow(getConsumerTag().toString(), + org.apache.qpidity.nclient.Session.MESSAGE_FLOW_UNIT_MESSAGE, 1); } - releaseMessage(message); } // now we need to acquire this message if needed // this is the case of queue with a message selector set @@ -429,40 +449,19 @@ public class BasicMessageConsumer_0_10 extends BasicMessageConsumer<Struct[], By public void setMessageListener(final MessageListener messageListener) throws JMSException { super.setMessageListener(messageListener); - if (messageListener == null) + if (messageListener != null && ClientProperties.MAX_PREFETCH == 0) { - /* _0_10session.getQpidSession().messageStop(getConsumerTag().toString()); - _0_10session.getQpidSession() - .messageFlowMode(getConsumerTag().toString(), Session.MESSAGE_FLOW_MODE_CREDIT); _0_10session.getQpidSession().messageFlow(getConsumerTag().toString(), - org.apache.qpidity.nclient.Session.MESSAGE_FLOW_UNIT_BYTE, - 0xFFFFFFFF); - _0_10session.getQpidSession().sync(); - */ + org.apache.qpidity.nclient.Session.MESSAGE_FLOW_UNIT_MESSAGE, 1); } - else + if (messageListener != null && !_synchronousQueue.isEmpty()) { - if(! _synchronousQueue.isEmpty()) + Iterator messages=_synchronousQueue.iterator(); + while (messages.hasNext()) { - Iterator messages=_synchronousQueue.iterator(); - while (messages.hasNext()) - { - AbstractJMSMessage message=(AbstractJMSMessage) messages.next(); - messages.remove(); - _session.rejectMessage(message, true); - } - } - if (_connection.started()) - { - _0_10session.getQpidSession() - .messageFlowMode(getConsumerTag().toString(), Session.MESSAGE_FLOW_MODE_WINDOW); - _0_10session.getQpidSession().messageFlow(getConsumerTag().toString(), - org.apache.qpidity.nclient.Session.MESSAGE_FLOW_UNIT_MESSAGE, - AMQSession_0_10.MAX_PREFETCH); - _0_10session.getQpidSession().messageFlow(getConsumerTag().toString(), - org.apache.qpidity.nclient.Session.MESSAGE_FLOW_UNIT_BYTE, - 0xFFFFFFFF); - _0_10session.getQpidSession().sync(); + AbstractJMSMessage message=(AbstractJMSMessage) messages.next(); + messages.remove(); + _session.rejectMessage(message, true); } } } @@ -475,6 +474,11 @@ public class BasicMessageConsumer_0_10 extends BasicMessageConsumer<Struct[], By public void start() { _isStarted = true; + if (_syncReceive.get()) + { + _0_10session.getQpidSession().messageFlow(getConsumerTag().toString(), + org.apache.qpidity.nclient.Session.MESSAGE_FLOW_UNIT_MESSAGE, 1); + } } public void stop() @@ -482,16 +486,31 @@ public class BasicMessageConsumer_0_10 extends BasicMessageConsumer<Struct[], By _isStarted = false; } - public void close() throws JMSException + /** + * When messages are not prefetched we need to request a message from the + * broker. + * Note that if the timeout is too short a message may be queued in _synchronousQueue until + * this consumer closes or request it. + * @param l + * @return + * @throws InterruptedException + */ + public Object getMessageFromQueue(long l) throws InterruptedException { - super.close(); - // release message that may be staged - Iterator messages=_synchronousQueue.iterator(); - while (messages.hasNext()) + if (isStrated() && ClientProperties.MAX_PREFETCH == 0 && _synchronousQueue.isEmpty()) + { + _0_10session.getQpidSession().messageFlow(getConsumerTag().toString(), + org.apache.qpidity.nclient.Session.MESSAGE_FLOW_UNIT_MESSAGE, 1); + } + if (ClientProperties.MAX_PREFETCH == 0) + { + _syncReceive.set(true); + } + Object o = super.getMessageFromQueue(l); + if (ClientProperties.MAX_PREFETCH == 0) { - AbstractJMSMessage message=(AbstractJMSMessage) messages.next(); - messages.remove(); - _session.rejectMessage(message, true); + _syncReceive.set(false); } + return o; } } diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_10.java b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_10.java index c6baf0b0fc..0e166ed4c8 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_10.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_10.java @@ -22,6 +22,7 @@ import java.net.URISyntaxException; import javax.jms.JMSException; import javax.jms.Message; +import javax.jms.DeliveryMode; import org.apache.qpid.client.message.AbstractJMSMessage; import org.apache.qpid.client.message.FiledTableSupport; @@ -186,9 +187,15 @@ public class BasicMessageProducer_0_10 extends BasicMessageProducer try { ((AMQSession_0_10) getSession()).getQpidSession().messageTransfer(destination.getExchangeName().toString(), - message.get010Message(), - org.apache.qpidity.nclient.Session.TRANSFER_CONFIRM_MODE_NOT_REQUIRED, - org.apache.qpidity.nclient.Session.TRANSFER_ACQUIRE_MODE_PRE_ACQUIRE); + message.get010Message(), + org.apache.qpidity.nclient.Session.TRANSFER_CONFIRM_MODE_NOT_REQUIRED, + org.apache.qpidity.nclient.Session.TRANSFER_ACQUIRE_MODE_PRE_ACQUIRE); + if(deliveryMode == DeliveryMode.PERSISTENT && ClientProperties.FULLY_SYNC ) + { + // we need to sync the delivery of this message + ((AMQSession_0_10) getSession()).getQpidSession().sync(); + } + } catch (IOException e) { diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/replay/MethodRecorder.java b/qpid/java/client/src/main/java/org/apache/qpid/client/ClientProperties.java index e45810438e..73c59dcf96 100644 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/replay/MethodRecorder.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/ClientProperties.java @@ -1,32 +1,36 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one +/* Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. - * */ -package org.apache.qpid.server.cluster.replay; -import org.apache.qpid.framing.AMQMethodBody; +package org.apache.qpid.client; /** - * Abstraction through which a method can be recorded for replay - * + * This class centralized the Qpid client properties. */ -interface MethodRecorder<T extends AMQMethodBody> +public class ClientProperties { - public void record(T method); + + /** + * The maximum number of pre-fetched messages per destination + */ + public static long MAX_PREFETCH = Long.valueOf(System.getProperties().getProperty("max_prefetch", "1000")); + + /** + * When true a sync command is sent after every persistent messages. + */ + public static boolean FULLY_SYNC = Boolean.getBoolean("fully_sync"); } diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/url/URLParser_0_8.java b/qpid/java/client/src/main/java/org/apache/qpid/client/url/URLParser.java index 5cde4d196a..b975713ad7 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/url/URLParser_0_8.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/url/URLParser.java @@ -11,11 +11,11 @@ import org.apache.qpid.framing.AMQShortString; import org.apache.qpid.url.URLHelper; import org.apache.qpid.url.URLSyntaxException; -public class URLParser_0_8 +public class URLParser { private AMQConnectionURL _url; - public URLParser_0_8(AMQConnectionURL url)throws URLSyntaxException + public URLParser(AMQConnectionURL url)throws URLSyntaxException { _url = url; parseURL(_url.getURL()); diff --git a/qpid/java/client/src/main/java/org/apache/qpid/jms/ConnectionURL.java b/qpid/java/client/src/main/java/org/apache/qpid/jms/ConnectionURL.java index 8fd24cc6d4..86e319d54f 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/jms/ConnectionURL.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/jms/ConnectionURL.java @@ -44,12 +44,7 @@ public interface ConnectionURL public static final String OPTIONS_TEMPORARY_QUEUE_EXCHANGE = "temporaryQueueExchange"; public static final byte URL_0_8 = 1; public static final byte URL_0_10 = 2; - public static final String OPTIONS_PROTOCOL_VERSION = "protocolVersion"; - - byte getURLVersion(); - - void setURLVersion(byte version); - + String getURL(); String getFailoverMethod(); @@ -94,5 +89,4 @@ public interface ConnectionURL AMQShortString getTemporaryTopicExchangeName(); - ProtocolVersion getProtocolVersion(); } diff --git a/qpid/java/client/src/main/java/org/apache/qpidity/nclient/Client.java b/qpid/java/client/src/main/java/org/apache/qpidity/nclient/Client.java index ad47e14fde..8b787615e4 100644 --- a/qpid/java/client/src/main/java/org/apache/qpidity/nclient/Client.java +++ b/qpid/java/client/src/main/java/org/apache/qpidity/nclient/Client.java @@ -11,6 +11,7 @@ import org.apache.qpid.jms.BrokerDetails; import org.apache.qpid.url.QpidURL; import org.apache.qpidity.ErrorCode; import org.apache.qpidity.QpidException; +import org.apache.qpidity.ProtocolException; import org.apache.qpidity.nclient.impl.ClientSession; import org.apache.qpidity.nclient.impl.ClientSessionDelegate; import org.apache.qpidity.transport.Channel; @@ -19,6 +20,7 @@ import org.apache.qpidity.transport.ConnectionClose; import org.apache.qpidity.transport.ConnectionCloseOk; import org.apache.qpidity.transport.ConnectionDelegate; import org.apache.qpidity.transport.ConnectionEvent; +import org.apache.qpidity.transport.TransportConstants; import org.apache.qpidity.transport.ProtocolHeader; import org.apache.qpidity.transport.SessionDelegate; import org.apache.qpidity.transport.network.mina.MinaHandler; @@ -48,14 +50,14 @@ public class Client implements org.apache.qpidity.nclient.Connection public void connect(String host, int port,String virtualHost,String username, String password) throws QpidException { - Condition negotiationComplete = _lock.newCondition(); + final Condition negotiationComplete = _lock.newCondition(); closeOk = _lock.newCondition(); _lock.lock(); ConnectionDelegate connectionDelegate = new ConnectionDelegate() { private boolean receivedClose = false; - + private String _unsupportedProtocol; public SessionDelegate getSessionDelegate() { return new ClientSessionDelegate(); @@ -114,6 +116,32 @@ public class Client implements org.apache.qpidity.nclient.Connection this.receivedClose = true; } + + @Override public void init(Channel ch, ProtocolHeader hdr) + { + // TODO: once the merge is done we'll need to update this code + // for handling 0.8 protocol version type i.e. major=8 and minor=0 :( + if (hdr.getMajor() != TransportConstants.getVersionMajor() + || hdr.getMinor() != TransportConstants.getVersionMinor()) + { + _unsupportedProtocol = TransportConstants.getVersionMajor() + "." + + TransportConstants.getVersionMinor(); + TransportConstants.setVersionMajor( hdr.getMajor() ); + TransportConstants.setVersionMinor( hdr.getMinor() ); + _lock.lock(); + negotiationComplete.signalAll(); + _lock.unlock(); + } + else + { + ch.connectionStart(hdr.getMajor(), hdr.getMinor(), null, "PLAIN", "utf8"); + } + } + + @Override public String getUnsupportedProtocol() + { + return _unsupportedProtocol; + } }; connectionDelegate.setCondition(_lock,negotiationComplete); @@ -122,8 +150,7 @@ public class Client implements org.apache.qpidity.nclient.Connection connectionDelegate.setVirtualHost(virtualHost); if (System.getProperty("transport","mina").equalsIgnoreCase("nio")) - { - System.out.println("Using NIO"); + { if( _logger.isDebugEnabled()) { _logger.debug("using NIO"); @@ -141,13 +168,21 @@ public class Client implements org.apache.qpidity.nclient.Connection } // XXX: hardcoded version numbers - _conn.send(new ConnectionEvent(0, new ProtocolHeader(1, 0, 10))); + _conn.send(new ConnectionEvent(0, new ProtocolHeader(1, TransportConstants.getVersionMajor(), + TransportConstants.getVersionMinor()))); try { negotiationComplete.await(); + if( connectionDelegate.getUnsupportedProtocol() != null ) + { + _conn.close(); + throw new ProtocolException("Unsupported protocol version: " + connectionDelegate.getUnsupportedProtocol() + , ErrorCode.UNSUPPORTED_PROTOCOL, null); + + } } - catch (Exception e) + catch (InterruptedException e) { // } diff --git a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/basic/InvalidDestinationTest.java b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/basic/InvalidDestinationTest.java index a3c7b2ea48..a5279a195b 100644 --- a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/basic/InvalidDestinationTest.java +++ b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/basic/InvalidDestinationTest.java @@ -44,6 +44,7 @@ public class InvalidDestinationTest extends QpidTestCase protected void tearDown() throws Exception
{
+ _connection.close();
super.tearDown();
}
diff --git a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/basic/LargeMessageTest.java b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/basic/LargeMessageTest.java index 6c23beffa7..81171fa330 100644 --- a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/basic/LargeMessageTest.java +++ b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/basic/LargeMessageTest.java @@ -42,13 +42,15 @@ public class LargeMessageTest extends QpidTestCase private Destination _destination; private AMQSession _session; - + private AMQConnection _connection; + protected void setUp() throws Exception { super.setUp(); try { - init((AMQConnection) getConnection("guest", "guest")); + _connection = (AMQConnection) getConnection("guest", "guest"); + init( _connection ); } catch (Exception e) { @@ -58,6 +60,7 @@ public class LargeMessageTest extends QpidTestCase protected void tearDown() throws Exception { + _connection.close(); super.tearDown(); } diff --git a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/basic/PropertyValueTest.java b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/basic/PropertyValueTest.java index dbc6283a3a..61ba3aad3a 100644 --- a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/basic/PropertyValueTest.java +++ b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/basic/PropertyValueTest.java @@ -60,6 +60,7 @@ public class PropertyValueTest extends QpidTestCase implements MessageListener private final List<String> messages = new ArrayList<String>(); private int _count = 1; public String _connectionString = "vm://:1"; + private static final String USERNAME = "guest"; protected void setUp() throws Exception { diff --git a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/basic/PubSubTwoConnectionTest.java b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/basic/PubSubTwoConnectionTest.java index 9e8f368985..60c84f451d 100644 --- a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/basic/PubSubTwoConnectionTest.java +++ b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/basic/PubSubTwoConnectionTest.java @@ -72,5 +72,7 @@ public class PubSubTwoConnectionTest extends QpidTestCase TextMessage tm1 = (TextMessage) consumer.receive(2000); assertNotNull(tm1); assertEquals("Hello", tm1.getText()); + con1.close(); + con2.close(); } } diff --git a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/basic/close/CloseTests.java b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/basic/close/CloseTests.java index 83fcbd7e65..10c054a863 100644 --- a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/basic/close/CloseTests.java +++ b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/basic/close/CloseTests.java @@ -69,6 +69,6 @@ public class CloseTests extends QpidTestCase consumer.close(); _logger.info("Closed Consumer"); - + connection.close(); } } diff --git a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/client/AMQConnectionTest.java b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/client/AMQConnectionTest.java index 158759bf1e..28782229a1 100644 --- a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/client/AMQConnectionTest.java +++ b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/client/AMQConnectionTest.java @@ -49,6 +49,7 @@ public class AMQConnectionTest extends QpidTestCase protected void tearDown() throws Exception { + _connection.close(); super.tearDown(); } diff --git a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/client/connection/ConnectionCloseTest.java b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/client/connection/ConnectionCloseTest.java index 20443944d2..7df8c87300 100644 --- a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/client/connection/ConnectionCloseTest.java +++ b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/client/connection/ConnectionCloseTest.java @@ -23,6 +23,9 @@ package org.apache.qpid.test.unit.client.connection; import org.apache.qpid.testutil.QpidTestCase; import org.apache.qpidity.transport.util.Logger; +import java.util.HashMap; +import java.util.Map; + import javax.jms.Connection; import javax.jms.MessageConsumer; import javax.jms.MessageProducer; @@ -42,6 +45,8 @@ public class ConnectionCloseTest extends QpidTestCase public void testSendReceiveClose() throws Exception { + Map<Thread,StackTraceElement[]> before = Thread.getAllStackTraces(); + for (int i = 0; i < 500; i++) { if ((i % 10) == 0) @@ -67,6 +72,30 @@ public class ConnectionCloseTest extends QpidTestCase assertEquals(m.getText(), "test"); receiver.close(); } + + Map<Thread,StackTraceElement[]> after = Thread.getAllStackTraces(); + + Map<Thread,StackTraceElement[]> delta = new HashMap<Thread,StackTraceElement[]>(after); + for (Thread t : before.keySet()) + { + delta.remove(t); + } + + dumpStacks(delta); + + assertTrue("Spurious thread creation exceeded threshold, " + + delta.size() + " threads created.", + delta.size() < 10); + } + + private void dumpStacks(Map<Thread,StackTraceElement[]> map) + { + for (Map.Entry<Thread,StackTraceElement[]> entry : map.entrySet()) + { + Throwable t = new Throwable(); + t.setStackTrace(entry.getValue()); + log.warn(t, entry.getKey().toString()); + } } } diff --git a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/client/connection/ConnectionTest.java b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/client/connection/ConnectionTest.java index c97e2c4cb1..dfd83d9f63 100644 --- a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/client/connection/ConnectionTest.java +++ b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/client/connection/ConnectionTest.java @@ -194,6 +194,10 @@ public class ConnectionTest extends TestCase { // PASS } + finally + { + connection.close(); + } } public void testClientIdIsPopulatedAutomatically() throws Exception @@ -201,6 +205,7 @@ public class ConnectionTest extends TestCase Connection connection = new AMQConnection(_broker, "guest", "guest", null, "test"); assertNotNull(connection.getClientID()); + connection.close(); } public static junit.framework.Test suite() diff --git a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/client/message/ObjectMessageTest.java b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/client/message/ObjectMessageTest.java index 347c0c43da..7b3077a1c1 100644 --- a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/client/message/ObjectMessageTest.java +++ b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/client/message/ObjectMessageTest.java @@ -83,6 +83,7 @@ public class ObjectMessageTest extends QpidTestCase implements MessageListener protected void tearDown() throws Exception { + close(); super.tearDown(); } @@ -108,10 +109,6 @@ public class ObjectMessageTest extends QpidTestCase implements MessageListener e.printStackTrace(); fail("This Test should succeed but failed due to: " + e); } - finally - { - close(); - } } public void testSetObjectPropertyForString() throws Exception diff --git a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/message/StreamMessageTest.java b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/message/StreamMessageTest.java index 94addd55dd..d9d078a01d 100644 --- a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/message/StreamMessageTest.java +++ b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/message/StreamMessageTest.java @@ -117,6 +117,8 @@ public class StreamMessageTest extends QpidTestCase { assertTrue("Expected MessageEOFException: " + e, e instanceof MessageEOFException); } + con.close(); + con2.close(); } public void testModifyReceivedMessageExpandsBuffer() throws Exception @@ -152,5 +154,7 @@ public class StreamMessageTest extends QpidTestCase sm.writeInt(42); mandatoryProducer.send(sm); Thread.sleep(2000); + con.close(); + con2.close(); } } diff --git a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/tests.properties b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/tests.properties index 893958949f..32ed16a392 100644 --- a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/tests.properties +++ b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/tests.properties @@ -23,7 +23,9 @@ java.naming.factory.initial = org.apache.qpid.jndi.PropertiesFileInitialContextF # register some connection factories # connectionfactory.[jndiname] = [ConnectionURL] -connectionfactory.local = qpid:password=guest;username=guest;client_id=clientid;virtualhost=test@tcp:127.0.0.1:5672 +connectionfactory.local = amqp://username:password@clientid/test?brokerlist='tcp://localhost:5672' +#qpid:password=guest;username=guest;client_id=clientid;virtualhost=test@tcp:127.0.0.1:5672 + # register some queues in JNDI using the form # queue.[jndiName] = [physicalName] diff --git a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/topic/DurableSubscriptionTest.java b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/topic/DurableSubscriptionTest.java index 0d74364cbc..bbb4ff4ac6 100644 --- a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/topic/DurableSubscriptionTest.java +++ b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/topic/DurableSubscriptionTest.java @@ -156,12 +156,12 @@ public class DurableSubscriptionTest extends QpidTestCase Message msg; msg = consumer1.receive(); assertEquals("A", ((TextMessage) msg).getText()); - msg = consumer1.receive(100); + msg = consumer1.receive(1000); assertEquals(null, msg); msg = consumer2.receive(); assertEquals("A", ((TextMessage) msg).getText()); - msg = consumer2.receive(100); + msg = consumer2.receive(1000); assertEquals(null, msg); consumer2.close(); @@ -245,10 +245,10 @@ public class DurableSubscriptionTest extends QpidTestCase producer.send(session0.createTextMessage("B")); _logger.info("Receive message on consumer 1 :expecting B"); - msg = consumer1.receive(100); + msg = consumer1.receive(1000); assertEquals("B", ((TextMessage) msg).getText()); _logger.info("Receive message on consumer 1 :expecting null"); - msg = consumer1.receive(100); + msg = consumer1.receive(1000); assertEquals(null, msg); // Re-attach a new consumer to the durable subscription, and check that it gets the message that it missed. @@ -259,12 +259,15 @@ public class DurableSubscriptionTest extends QpidTestCase TopicSubscriber consumer3 = session3.createDurableSubscriber(topic, "MySubscription"); _logger.info("Receive message on consumer 3 :expecting B"); - msg = consumer3.receive(100); + msg = consumer3.receive(1000); assertEquals("B", ((TextMessage) msg).getText()); _logger.info("Receive message on consumer 3 :expecting null"); - msg = consumer3.receive(500); - assertNull("There should be no more messages for consumption on consumer3.", msg); - + msg = consumer3.receive(1000); + assertEquals(null, msg); + // we need to unsubscribe as the session is NO_ACKNOWLEDGE + // messages for the durable subscriber are not deleted so the test cannot + // be run twice in a row + session2.unsubscribe("MySubscription"); consumer1.close(); consumer3.close(); diff --git a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/topic/TopicPublisherTest.java b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/topic/TopicPublisherTest.java index 4dfd4fdfea..4f0f0dbaa9 100644 --- a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/topic/TopicPublisherTest.java +++ b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/topic/TopicPublisherTest.java @@ -66,7 +66,7 @@ public class TopicPublisherTest extends QpidTestCase { // PASS } - + con.close(); } public static junit.framework.Test suite() diff --git a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/topic/TopicSessionTest.java b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/topic/TopicSessionTest.java index b905591f19..ad13c45575 100644 --- a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/topic/TopicSessionTest.java +++ b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/topic/TopicSessionTest.java @@ -286,7 +286,7 @@ public class TopicSessionTest extends QpidTestCase public void testNoLocal() throws Exception { -/* + AMQConnection con = (AMQConnection) getConnection("guest", "guest"); AMQTopic topic = new AMQTopic(con, "testNoLocal"); @@ -363,7 +363,7 @@ public class TopicSessionTest extends QpidTestCase con.close(); - con2.close();*/ + con2.close(); } public static junit.framework.Test suite() diff --git a/qpid/java/client/src/test/java/org/apache/qpid/testutil/QpidTestCase.java b/qpid/java/client/src/test/java/org/apache/qpid/testutil/QpidTestCase.java index 60b1b70d67..2f4a0e6b04 100644 --- a/qpid/java/client/src/test/java/org/apache/qpid/testutil/QpidTestCase.java +++ b/qpid/java/client/src/test/java/org/apache/qpid/testutil/QpidTestCase.java @@ -18,11 +18,14 @@ package org.apache.qpid.testutil; import junit.framework.TestCase; +import junit.framework.TestResult; import javax.jms.Connection; import javax.naming.InitialContext; -import java.io.InputStream; -import java.io.IOException; +import java.io.*; +import java.util.List; +import java.util.ArrayList; +import java.util.StringTokenizer; import org.apache.qpid.client.transport.TransportConnection; import org.apache.qpid.client.AMQConnection; @@ -41,6 +44,63 @@ public class QpidTestCase extends TestCase private static final Logger _logger = LoggerFactory.getLogger(QpidTestCase.class); + /** + * Some tests are excluded when the property test.excludes is set to true. + * An exclusion list is either a file (prop test.excludesfile) which contains one test name + * to be excluded per line or a String (prop test.excludeslist) where tests to be excluded are + * separated by " ". Excluded tests are specified following the format: + * className#testName where className is the class of the test to be + * excluded and testName is the name of the test to be excluded. + * className#* excludes all the tests of the specified class. + */ + static + { + if (Boolean.getBoolean("test.excludes")) + { + _logger.info("Some tests should be excluded, building the exclude list"); + String exclusionListURI = System.getProperties().getProperty("test.excludesfile", ""); + String exclusionListString = System.getProperties().getProperty("test.excludeslist", ""); + File file=new File(exclusionListURI); + List<String> exclusionList = new ArrayList<String>(); + if (file.exists()) + { + _logger.info("Using exclude file: " + exclusionListURI); + try + { + BufferedReader in = new BufferedReader(new FileReader(file)); + String excludedTest = in.readLine(); + do + { + exclusionList.add(excludedTest); + excludedTest = in.readLine(); + } + while (excludedTest != null); + } + catch (IOException e) + { + _logger.warn("Exception when reading exclusion list", e); + } + } + else if( ! exclusionListString.equals("")) + { + _logger.info("Using excludeslist: " + exclusionListString); + // the exclusion list may be specified as a string + StringTokenizer t = new StringTokenizer(exclusionListString, " "); + while (t.hasMoreTokens()) + { + exclusionList.add(t.nextToken()); + } + } + else + { + throw new RuntimeException("Aborting test: Cannot find excludes file nor excludes list"); + } + _exclusionList = exclusionList; + } + } + + private static List<String> _exclusionList; + // system properties private static final String BROKER = "broker"; private static final String BROKER_CLEAN = "broker.clean"; @@ -84,6 +144,21 @@ public class QpidTestCase extends TestCase } } + public void run(TestResult testResult) + { + if( _exclusionList != null && (_exclusionList.contains( getClass().getName() + "#*") || + _exclusionList.contains( getClass().getName() + "#" + getName()))) + { + _logger.info("Test: " + getName() + " is excluded"); + testResult.endTest(this); + } + else + { + super.run(testResult); + } + } + + private static final class Piper extends Thread { @@ -270,7 +345,7 @@ public class QpidTestCase extends TestCase return con; } - public Connection getConnection(String username, String password, String id) throws Exception + public Connection getConnection(String username, String password, String id) throws Exception { _logger.info("get Connection"); Connection con; diff --git a/qpid/java/cluster/doc/design.doc b/qpid/java/cluster/doc/design.doc Binary files differdeleted file mode 100644 index c5bbf0f8a4..0000000000 --- a/qpid/java/cluster/doc/design.doc +++ /dev/null diff --git a/qpid/java/cluster/pom.xml b/qpid/java/cluster/pom.xml deleted file mode 100644 index 0db6385bfc..0000000000 --- a/qpid/java/cluster/pom.xml +++ /dev/null @@ -1,69 +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. ---> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> - <modelVersion>4.0.0</modelVersion> - <groupId>org.apache.qpid</groupId> - <artifactId>qpid-cluster</artifactId> - <packaging>jar</packaging> - <version>1.0-incubating-M3-SNAPSHOT</version> - <name>Qpid Cluster</name> - <url>http://cwiki.apache.org/confluence/display/qpid</url> - - <parent> - <groupId>org.apache.qpid</groupId> - <artifactId>qpid</artifactId> - <version>1.0-incubating-M3-SNAPSHOT</version> - </parent> - - <properties> - <topDirectoryLocation>..</topDirectoryLocation> - </properties> - - <dependencies> - <dependency> - <groupId>org.apache.qpid</groupId> - <artifactId>qpid-common</artifactId> - </dependency> - <dependency> - <groupId>org.apache.qpid</groupId> - <artifactId>qpid-client</artifactId> - </dependency> - <dependency> - <groupId>org.apache.qpid</groupId> - <artifactId>qpid-broker</artifactId> - </dependency> - <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - </dependency> - </dependencies> - - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-surefire-plugin</artifactId> - <configuration> - <skip>true</skip> - </configuration> - </plugin> - </plugins> - </build> -</project> diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/AMQConnectionWaitException.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/AMQConnectionWaitException.java deleted file mode 100644 index a8a505294e..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/AMQConnectionWaitException.java +++ /dev/null @@ -1,41 +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.
- *
- */
-package org.apache.qpid.server.cluster;
-
-import org.apache.qpid.AMQException;
-
-/**
- * AMQConnectionWaitException represents a failure to connect to a cluster peer in a timely manner.
- *
- * <p/><table id="crc"><caption>CRC Card</caption>
- * <tr><th> Responsibilities <th> Collaborations
- * <tr><td> Represents failure to connect to a cluster peer in a timely manner.
- * </table>
- *
- * @todo Not an AMQP exception as no status code.
- */
-public class AMQConnectionWaitException extends AMQException
-{
- public AMQConnectionWaitException(String s, Throwable cause)
- {
- super(null, s, cause);
- }
-}
diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/AMQUnexpectedBodyTypeException.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/AMQUnexpectedBodyTypeException.java deleted file mode 100644 index 00f3ddc395..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/AMQUnexpectedBodyTypeException.java +++ /dev/null @@ -1,47 +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.
- *
- */
-package org.apache.qpid.server.cluster;
-
-import org.apache.qpid.AMQException;
-import org.apache.qpid.framing.AMQBody;
-
-/**
- * AMQUnexpectedBodyTypeException represents a failure where a message body does not match its expected type. For example,
- * and AMQP method should have a method body.
- *
- * <p/><table id="crc"><caption>CRC Card</caption>
- * <tr><th> Responsibilities <th> Collaborations
- * <tr><td> Represents a failure where a message body does not match its expected type.
- * </table>
- *
- * @todo Not an AMQP exception as no status code.
- *
- * @todo Seems like this exception was created to handle an unsafe type cast that will never happen in practice. Would
- * be better just to leave that as a ClassCastException. Check that the framing layer will pick up the error first.
- */
-public class AMQUnexpectedBodyTypeException extends AMQException
-{
- public AMQUnexpectedBodyTypeException(Class<? extends AMQBody> expectedClass, AMQBody body, Throwable cause)
- {
- super(null, "Unexpected body type. Expected: " + expectedClass.getName() + "; got: " + body.getClass().getName(),
- cause);
- }
-}
diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/AMQUnexpectedFrameTypeException.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/AMQUnexpectedFrameTypeException.java deleted file mode 100644 index 11096ccf7e..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/AMQUnexpectedFrameTypeException.java +++ /dev/null @@ -1,45 +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.
- *
- */
-package org.apache.qpid.server.cluster;
-
-import org.apache.qpid.AMQException;
-
-/**
- * AMQUnexpectedFrameTypeException represents a failure when Mina passes an unexpected frame type.
- *
- * <p/><table id="crc"><caption>CRC Card</caption>
- * <tr><th> Responsibilities <th> Collaborations
- * <tr><td> Represents failure to cast a frame to its expected type.
- * </table>
- *
- * @todo Not an AMQP exception as no status code.
- *
- * @todo Seems like this exception was created to handle an unsafe type cast that will never happen in practice. Would
- * be better just to leave that as a ClassCastException. However, check the framing layer catches this error
- * first.
- */
-public class AMQUnexpectedFrameTypeException extends AMQException
-{
- public AMQUnexpectedFrameTypeException(String s, Throwable cause)
- {
- super(null, s, cause);
- }
-}
diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/BlockingHandler.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/BlockingHandler.java deleted file mode 100644 index 39508df566..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/BlockingHandler.java +++ /dev/null @@ -1,91 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.qpid.framing.AMQMethodBody; - -public class BlockingHandler implements ResponseHandler -{ - private final Class _expected; - private boolean _completed; - private AMQMethodBody _response; - - - public BlockingHandler() - { - this(AMQMethodBody.class); - } - - public BlockingHandler(Class<? extends AMQMethodBody> expected) - { - _expected = expected; - } - - public void responded(AMQMethodBody response) - { - if (_expected.isInstance(response)) - { - _response = response; - completed(); - } - } - - public void removed() - { - completed(); - } - - private synchronized void completed() - { - _completed = true; - notifyAll(); - } - - synchronized void waitForCompletion() - { - while (!_completed) - { - try - { - wait(); - } - catch (InterruptedException ignore) - { - - } - } - } - - AMQMethodBody getResponse() - { - return _response; - } - - boolean failed() - { - return _response == null; - } - - boolean isCompleted() - { - return _completed; - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/BroadcastPolicy.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/BroadcastPolicy.java deleted file mode 100644 index 145aa58574..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/BroadcastPolicy.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -package org.apache.qpid.server.cluster; - -public interface BroadcastPolicy -{ - public boolean isComplete(int responded, int members); -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/Broker.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/Broker.java deleted file mode 100644 index 7e2cf6da83..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/Broker.java +++ /dev/null @@ -1,247 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.qpid.AMQException; -import org.apache.qpid.server.cluster.util.LogMessage; -import org.apache.qpid.framing.AMQMethodBody; -import org.apache.log4j.Logger; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -/** - * An implementation of the Member interface (through which data is sent to other - * peers in the cluster). This class provides a base from which subclasses can - * inherit some common behaviour for broadcasting GroupRequests and sending methods - * that may expect a response. It also extends the Member abstraction to support - * a richer set of operations that are useful within the package but should not be - * exposed outside of it. - * - */ -abstract class Broker extends SimpleMemberHandle implements Member -{ - private static final Logger _logger = Logger.getLogger(Broker.class); - private static final int DEFAULT_CHANNEL = 1; - private static final int START_CHANNEL = 2; - private static final int END_CHANNEL = 10000; - - - private MemberFailureListener _listener; - //a wrap-around counter to allocate _requests a unique channel: - private int _nextChannel = START_CHANNEL; - //outstanding _requests: - private final Map<Integer, ResponseHandler> _requests = new HashMap<Integer, ResponseHandler>(); - - Broker(String host, int port) - { - super(host, port); - } - - /** - * Allows a listener to be registered that will receive callbacks when communication - * to the peer this broker instance represents fails. - * @param listener the callback to be notified of failures - */ - public void addFailureListener(MemberFailureListener listener) - { - _listener = listener; - } - - /** - * Allows subclasses to signal comunication failures - */ - protected void failed() - { - if (_listener != null) - { - _listener.failed(this); - } - } - - /** - * Subclasses should call this on receiving message responses from the remote - * peer. They are matched to any outstanding request they might be response - * to, with the completion and callback of that request being managed if - * required. - * - * @param channel the channel on which the method was received - * @param response the response received - * @return true if the response matched an outstanding request - */ - protected synchronized boolean handleResponse(int channel, AMQMethodBody response) - { - ResponseHandler request = _requests.get(channel); - if (request == null) - { - if(!_requests.isEmpty()) - { - _logger.warn(new LogMessage("[next channel={3, integer}]: Response {0} on channel {1, integer} failed to match outstanding requests: {2}", response, channel, _requests, _nextChannel)); - } - return false; - } - else - { - request.responded(response); - return true; - } - } - - /** - * Called when this broker is excluded from the group. Any requests made on - * it are informed this member has left the group. - */ - synchronized void remove() - { - for (ResponseHandler r : _requests.values()) - { - r.removed(); - } - } - - /** - * Engages this broker in the specified group request - * - * @param request the request being made to a group of brokers - * @throws AMQException if there is any failure - */ - synchronized void invoke(GroupRequest request) throws AMQException - { - int channel = nextChannel(); - _requests.put(channel, new GroupRequestAdapter(request, channel)); - request.send(channel, this); - } - - /** - * Sends a message to the remote peer and undertakes to notify the specified - * handler of the response. - * - * @param msg the message to send - * @param handler the callback to notify of responses (or the removal of this broker - * from the group) - * @throws AMQException - */ - synchronized void send(Sendable msg, ResponseHandler handler) throws AMQException - { - int channel; - if (handler != null) - { - channel = nextChannel(); - _requests.put(channel, new RemovingWrapper(handler, channel)); - } - else - { - channel = DEFAULT_CHANNEL; - } - - msg.send(channel, this); - } - - private int nextChannel() - { - int channel = _nextChannel++; - if(_nextChannel >= END_CHANNEL) - { - _nextChannel = START_CHANNEL; - } - return channel; - } - - /** - * extablish connection without handling redirect - */ - abstract boolean connect() throws IOException, InterruptedException; - - /** - * Start connection process, including replay - */ - abstract void connectAsynch(Iterable<AMQMethodBody> msgs); - - /** - * Replay messages to the remote peer this instance represents. These messages - * must be sent before any others whose transmission is requested through send() etc. - * - * @param msgs - */ - abstract void replay(Iterable<AMQMethodBody> msgs); - - /** - * establish connection, handling redirect if required... - */ - abstract Broker connectToCluster() throws IOException, InterruptedException; - - private class GroupRequestAdapter implements ResponseHandler - { - private final GroupRequest request; - private final int channel; - - GroupRequestAdapter(GroupRequest request, int channel) - { - this.request = request; - this.channel = channel; - } - - public void responded(AMQMethodBody response) - { - request.responseReceived(Broker.this, response); - _requests.remove(channel); - } - - public void removed() - { - request.removed(Broker.this); - } - - public String toString() - { - return "GroupRequestAdapter{" + channel + ", " + request + "}"; - } - } - - private class RemovingWrapper implements ResponseHandler - { - private final ResponseHandler handler; - private final int channel; - - RemovingWrapper(ResponseHandler handler, int channel) - { - this.handler = handler; - this.channel = channel; - } - - public void responded(AMQMethodBody response) - { - handler.responded(response); - _requests.remove(channel); - } - - public void removed() - { - handler.removed(); - } - - public String toString() - { - return "RemovingWrapper{" + channel + ", " + handler + "}"; - } - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/BrokerFactory.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/BrokerFactory.java deleted file mode 100644 index 92c3c4e7bf..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/BrokerFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -package org.apache.qpid.server.cluster; - -interface BrokerFactory -{ - public Broker create(MemberHandle handle); -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/BrokerGroup.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/BrokerGroup.java deleted file mode 100644 index 755a341607..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/BrokerGroup.java +++ /dev/null @@ -1,368 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.log4j.Logger; -import org.apache.qpid.server.cluster.replay.ReplayManager; -import org.apache.qpid.server.cluster.util.LogMessage; -import org.apache.qpid.server.cluster.util.InvokeMultiple; -import org.apache.qpid.framing.AMQMethodBody; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * Manages the membership list of a group and the set of brokers representing the - * remote peers. The group should be initialised through a call to establish() - * or connectToLeader(). - * - */ -class BrokerGroup -{ - private static final Logger _logger = Logger.getLogger(BrokerGroup.class); - - private final InvokeMultiple<MembershipChangeListener> _changeListeners = new InvokeMultiple<MembershipChangeListener>(MembershipChangeListener.class); - private final ReplayManager _replayMgr; - private final MemberHandle _local; - private final BrokerFactory _factory; - private final Object _lock = new Object(); - private final Set<MemberHandle> _synch = new HashSet<MemberHandle>(); - private List<MemberHandle> _members; - private List<Broker> _peers = new ArrayList<Broker>(); - private JoinState _state = JoinState.UNINITIALISED; - - /** - * Creates an unitialised group. - * - * @param local a handle that represents the local broker - * @param replayMgr the replay manager to use when creating new brokers - * @param factory the factory through which broker instances are created - */ - BrokerGroup(MemberHandle local, ReplayManager replayMgr, BrokerFactory factory) - { - _replayMgr = replayMgr; - _local = local; - _factory = factory; - } - - /** - * Called to establish the local broker as the leader of a new group - */ - void establish() - { - synchronized (_lock) - { - setState(JoinState.JOINED); - _members = new ArrayList<MemberHandle>(); - _members.add(_local); - } - fireChange(); - } - - /** - * Called by prospect to connect to group - */ - Broker connectToLeader(MemberHandle handle) throws Exception - { - Broker leader = _factory.create(handle); - leader = leader.connectToCluster(); - synchronized (_lock) - { - setState(JoinState.JOINING); - _members = new ArrayList<MemberHandle>(); - _members.add(leader); - _peers.add(leader); - } - fireChange(); - return leader; - } - - /** - * Called by leader when handling a join request - */ - Broker connectToProspect(MemberHandle handle) throws IOException, InterruptedException - { - Broker prospect = _factory.create(handle); - prospect.connect(); - synchronized (_lock) - { - _members.add(prospect); - _peers.add(prospect); - } - fireChange(); - return prospect; - } - - /** - * Called in reponse to membership announcements. - * - * @param members the list of members now part of the group - */ - void setMembers(List<MemberHandle> members) - { - if (isJoined()) - { - List<Broker> old = _peers; - - synchronized (_lock) - { - _peers = getBrokers(members); - _members = new ArrayList<MemberHandle>(members); - } - - //remove those that are still members - old.removeAll(_peers); - - //handle failure of any brokers that haven't survived - for (Broker peer : old) - { - peer.remove(); - } - } - else - { - synchronized (_lock) - { - setState(JoinState.INITIATION); - _members = new ArrayList<MemberHandle>(members); - _synch.addAll(_members); - _synch.remove(_local); - } - } - fireChange(); - } - - List<MemberHandle> getMembers() - { - synchronized (_lock) - { - return Collections.unmodifiableList(_members); - } - } - - List<Broker> getPeers() - { - synchronized (_lock) - { - return _peers; - } - } - - /** - * Removes the member presented from the group - * @param peer the broker that should be removed - */ - void remove(Broker peer) - { - synchronized (_lock) - { - _peers.remove(peer); - _members.remove(peer); - } - fireChange(); - } - - MemberHandle getLocal() - { - return _local; - } - - Broker getLeader() - { - synchronized (_lock) - { - return _peers.size() > 0 ? _peers.get(0) : null; - } - } - - /** - * Allows a Broker instance to be retrieved for a given handle - * - * @param handle the handle for which a broker is sought - * @param create flag to indicate whther a broker should be created for the handle if - * one is not found within the list of known peers - * @return the broker corresponding to handle or null if a match cannot be found and - * create is false - */ - Broker findBroker(MemberHandle handle, boolean create) - { - if (handle instanceof Broker) - { - return (Broker) handle; - } - else - { - for (Broker b : getPeers()) - { - if (b.matches(handle)) - { - return b; - } - } - } - if (create) - { - Broker b = _factory.create(handle); - List<AMQMethodBody> msgs = _replayMgr.replay(isLeader(_local)); - _logger.info(new LogMessage("Replaying {0} from {1} to {2}", msgs, _local, b)); - b.connectAsynch(msgs); - - return b; - } - else - { - return null; - } - } - - /** - * @param member the member to test for leadership - * @return true if the passed in member is the group leader, false otherwise - */ - boolean isLeader(MemberHandle member) - { - synchronized (_lock) - { - return member.matches(_members.get(0)); - } - } - - /** - * @return true if the local broker is the group leader, false otherwise - */ - boolean isLeader() - { - return isLeader(_local); - } - - /** - * Used when the leader fails and the next broker in the list needs to - * assume leadership - * @return true if the action succeeds - */ - boolean assumeLeadership() - { - boolean valid; - synchronized (_lock) - { - valid = _members.size() > 1 && _local.matches(_members.get(1)); - if (valid) - { - _members.remove(0); - _peers.remove(0); - } - } - fireChange(); - return valid; - } - - /** - * Called in response to a Cluster.Synch message being received during the join - * process. This indicates that the member mentioned has replayed all necessary - * messages to the local broker. - * - * @param member the member from whom the synch messages was received - */ - void synched(MemberHandle member) - { - _logger.info(new LogMessage("Synchronised with {0}", member)); - synchronized (_lock) - { - if (isLeader(member)) - { - setState(JoinState.INDUCTION); - } - _synch.remove(member); - if (_synch.isEmpty()) - { - _peers = getBrokers(_members); - setState(JoinState.JOINED); - } - } - } - - - /** - * @return the state of the group - */ - JoinState getState() - { - synchronized (_lock) - { - return _state; - } - } - - void addMemberhipChangeListener(MembershipChangeListener l) - { - _changeListeners.addListener(l); - } - - void removeMemberhipChangeListener(MembershipChangeListener l) - { - _changeListeners.removeListener(l); - } - - - - private void setState(JoinState state) - { - _logger.info(new LogMessage("Changed state from {0} to {1}", _state, state)); - _state = state; - } - - private boolean isJoined() - { - return inState(JoinState.JOINED); - } - - private boolean inState(JoinState state) - { - return _state.equals(state); - } - - private List<Broker> getBrokers(List<MemberHandle> handles) - { - List<Broker> brokers = new ArrayList<Broker>(); - for (MemberHandle handle : handles) - { - if (!_local.matches(handle)) - { - brokers.add(findBroker(handle, true)); - } - } - return brokers; - } - - private void fireChange() - { - List<MemberHandle> members; - synchronized(this) - { - members = new ArrayList(_members); - } - _changeListeners.getProxy().changed(Collections.unmodifiableList(members)); - } -}
\ No newline at end of file diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ClientAdapter.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ClientAdapter.java deleted file mode 100644 index 1b4a3e8327..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ClientAdapter.java +++ /dev/null @@ -1,73 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.mina.common.IoSession; -import org.apache.qpid.AMQException; -import org.apache.qpid.client.AMQConnection; -import org.apache.qpid.protocol.AMQMethodEvent; -import org.apache.qpid.client.protocol.AMQProtocolSession; -import org.apache.qpid.client.state.AMQStateManager; -import org.apache.qpid.framing.AMQMethodBody; - -/** - * Hack to assist with reuse of the client handlers for connection setup in - * the inter-broker communication within the cluster. - * - */ -class ClientAdapter implements MethodHandler -{ - private final AMQProtocolSession _session; - private final AMQStateManager _stateMgr; - - ClientAdapter(IoSession session, AMQStateManager stateMgr) - { - this(session, stateMgr, "guest", "guest", session.toString(), "/cluster"); - } - - ClientAdapter(IoSession session, AMQStateManager stateMgr, String user, String password, String name, String path) - { - _session = new SessionAdapter(session, new ConnectionAdapter(user, password, name, path)); - _stateMgr = stateMgr; - } - - public void handle(int channel, AMQMethodBody method) throws AMQException - { - AMQMethodEvent evt = new AMQMethodEvent(channel, method); - _stateMgr.methodReceived(evt); - } - - private class SessionAdapter extends AMQProtocolSession - { - public SessionAdapter(IoSession session, AMQConnection connection) - { - super(null, session, connection); - } - } - - private static class ConnectionAdapter extends AMQConnection - { - ConnectionAdapter(String username, String password, String clientName, String virtualPath) - { - super(username, password, clientName, virtualPath); - } - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ClientHandlerRegistry.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ClientHandlerRegistry.java deleted file mode 100644 index f4a8e4c1e2..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ClientHandlerRegistry.java +++ /dev/null @@ -1,139 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.qpid.client.handler.ConnectionCloseMethodHandler; -import org.apache.qpid.client.handler.ConnectionOpenOkMethodHandler; -import org.apache.qpid.client.handler.ConnectionSecureMethodHandler; -import org.apache.qpid.client.handler.ConnectionStartMethodHandler; -import org.apache.qpid.client.handler.ConnectionTuneMethodHandler; -import org.apache.qpid.client.state.AMQState; -import org.apache.qpid.client.state.AMQStateManager; -// import org.apache.qpid.client.state.IllegalStateTransitionException; -import org.apache.qpid.client.state.StateAwareMethodListener; -import org.apache.qpid.client.protocol.AMQProtocolSession; -import org.apache.qpid.framing.*; - -import java.util.HashMap; -import java.util.Map; - -/** - * An extension of client.AMQStateManager that allows different handlers to be registered. - * - */ -public class ClientHandlerRegistry extends AMQStateManager -{ - private final Map<AMQState, ClientRegistry> _handlers = new HashMap<AMQState, ClientRegistry>(); - private final MemberHandle _identity; - - protected ClientHandlerRegistry(MemberHandle local, AMQProtocolSession protocolSession) - { - super(AMQState.CONNECTION_NOT_STARTED, false, protocolSession); - - _identity = local; - - addHandler(ConnectionStartBody.class, ConnectionStartMethodHandler.getInstance(), - AMQState.CONNECTION_NOT_STARTED); - - addHandler(ConnectionTuneBody.class, new ConnectionTuneHandler(), - AMQState.CONNECTION_NOT_TUNED); - addHandler(ConnectionSecureBody.class, ConnectionSecureMethodHandler.getInstance(), - AMQState.CONNECTION_NOT_TUNED); - addHandler(ConnectionOpenOkBody.class, ConnectionOpenOkMethodHandler.getInstance(), - AMQState.CONNECTION_NOT_OPENED); - - addHandlers(ConnectionCloseBody.class, ConnectionCloseMethodHandler.getInstance(), - AMQState.CONNECTION_NOT_STARTED, - AMQState.CONNECTION_NOT_TUNED, - AMQState.CONNECTION_NOT_OPENED); - - } - - private ClientRegistry state(AMQState state) - { - ClientRegistry registry = _handlers.get(state); - if (registry == null) - { - registry = new ClientRegistry(); - _handlers.put(state, registry); - } - return registry; - } - - protected StateAwareMethodListener findStateTransitionHandler(AMQState state, AMQMethodBody frame) //throws IllegalStateTransitionException - { - ClientRegistry registry = _handlers.get(state); - return registry == null ? null : registry.getHandler(frame); - } - - - <A extends Class<AMQMethodBody>> void addHandlers(Class type, StateAwareMethodListener handler, AMQState... states) - { - for (AMQState state : states) - { - addHandler(type, handler, state); - } - } - - <A extends Class<AMQMethodBody>> void addHandler(Class type, StateAwareMethodListener handler, AMQState state) - { - ClientRegistry registry = _handlers.get(state); - if (registry == null) - { - registry = new ClientRegistry(); - _handlers.put(state, registry); - } - registry.add(type, handler); - } - - static class ClientRegistry - { - private final Map<Class<? extends AMQMethodBody>, StateAwareMethodListener> registry - = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener>(); - - <A extends Class<AMQMethodBody>> void add(A type, StateAwareMethodListener handler) - { - registry.put(type, handler); - } - - StateAwareMethodListener getHandler(AMQMethodBody frame) - { - return registry.get(frame.getClass()); - } - } - - class ConnectionTuneHandler extends ConnectionTuneMethodHandler - { - protected AMQFrame createConnectionOpenFrame(int channel, AMQShortString path, AMQShortString capabilities, boolean insist, byte major, byte minor) - { - // Be aware of possible changes to parameter order as versions change. - return ConnectionOpenBody.createAMQFrame(channel, - major, - minor, - // AMQP version (major, minor) - new AMQShortString(ClusterCapability.add(capabilities, _identity)), - // capabilities - insist, - // insist - path); - } - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ClusterBuilder.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ClusterBuilder.java deleted file mode 100644 index 80f9ef62b1..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ClusterBuilder.java +++ /dev/null @@ -1,63 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.qpid.server.cluster.handler.ClusterMethodHandlerFactory; -import org.apache.qpid.server.cluster.replay.RecordingMethodHandlerFactory; -import org.apache.qpid.server.cluster.replay.ReplayStore; - -import java.net.InetSocketAddress; - -class ClusterBuilder -{ - private final LoadTable loadTable = new LoadTable(); - private final ReplayStore replayStore = new ReplayStore(); - private final MemberHandle handle; - private final GroupManager groupMgr; - - ClusterBuilder(InetSocketAddress address) - { - handle = new SimpleMemberHandle(address.getHostName(), address.getPort()).resolve(); - groupMgr = new DefaultGroupManager(handle, getBrokerFactory(), replayStore, loadTable); - } - - GroupManager getGroupManager() - { - return groupMgr; - } - - ServerHandlerRegistry getHandlerRegistry() - { - return new ServerHandlerRegistry(getHandlerFactory(), null, null); - } - - private MethodHandlerFactory getHandlerFactory() - { - MethodHandlerFactory factory = new ClusterMethodHandlerFactory(groupMgr, loadTable); - //need to wrap relevant handlers with recording handler for easy replay: - return new RecordingMethodHandlerFactory(factory, replayStore); - } - - private BrokerFactory getBrokerFactory() - { - return new MinaBrokerProxyFactory(handle); - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ClusterCapability.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ClusterCapability.java deleted file mode 100644 index 57c48f0611..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ClusterCapability.java +++ /dev/null @@ -1,60 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.qpid.framing.AMQShortString; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class ClusterCapability -{ - public static final String PATTERN = ".*\\bcluster_peer=(\\S*:\\d*)\b*.*"; - public static final String PEER = "cluster_peer"; - - public static AMQShortString add(AMQShortString original, MemberHandle identity) - { - return original == null ? peer(identity) : new AMQShortString(original + " " + peer(identity)); - } - - private static AMQShortString peer(MemberHandle identity) - { - return new AMQShortString(PEER + "=" + identity.getDetails()); - } - - public static boolean contains(AMQShortString in) - { - return in != null; // && in.contains(in); - } - - public static MemberHandle getPeer(AMQShortString in) - { - Matcher matcher = Pattern.compile(PATTERN).matcher(in); - if (matcher.matches()) - { - return new SimpleMemberHandle(matcher.group(1)); - } - else - { - throw new RuntimeException("Could not find peer in '" + in + "'"); - } - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ClusteredProtocolHandler.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ClusteredProtocolHandler.java deleted file mode 100644 index 480a6f3603..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ClusteredProtocolHandler.java +++ /dev/null @@ -1,190 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.log4j.Logger; -import org.apache.mina.common.IoSession; -import org.apache.qpid.AMQException; -import org.apache.qpid.codec.AMQCodecFactory; -import org.apache.qpid.framing.AMQBody; -import org.apache.qpid.framing.AMQFrame; -import org.apache.qpid.framing.ConnectionOpenBody; -import org.apache.qpid.framing.ConnectionSecureOkBody; -import org.apache.qpid.framing.ConnectionStartOkBody; -import org.apache.qpid.framing.ConnectionTuneOkBody; -import org.apache.qpid.framing.ClusterMembershipBody; -import org.apache.qpid.server.exchange.ExchangeRegistry; -import org.apache.qpid.server.protocol.AMQPFastProtocolHandler; -import org.apache.qpid.server.protocol.AMQProtocolSession; -import org.apache.qpid.server.queue.QueueRegistry; -import org.apache.qpid.server.registry.ApplicationRegistry; -import org.apache.qpid.server.registry.IApplicationRegistry; -import org.apache.qpid.server.cluster.util.LogMessage; -import org.apache.qpid.server.virtualhost.VirtualHostRegistry; - -import java.net.InetSocketAddress; - -public class ClusteredProtocolHandler extends AMQPFastProtocolHandler implements InductionBuffer.MessageHandler -{ - private static final Logger _logger = Logger.getLogger(ClusteredProtocolHandler.class); - private final InductionBuffer _peerBuffer = new InductionBuffer(this); - private final InductionBuffer _clientBuffer = new InductionBuffer(this); - private final GroupManager _groupMgr; - private final ServerHandlerRegistry _handlers; - - public ClusteredProtocolHandler(InetSocketAddress address) - { - this(ApplicationRegistry.getInstance(), address); - } - - public ClusteredProtocolHandler(IApplicationRegistry registry, InetSocketAddress address) - { - super(registry); - ClusterBuilder builder = new ClusterBuilder(address); - _groupMgr = builder.getGroupManager(); - _handlers = builder.getHandlerRegistry(); - } - - public ClusteredProtocolHandler(ClusteredProtocolHandler handler) - { - super(handler); - _groupMgr = handler._groupMgr; - _handlers = handler._handlers; - } - - protected void createSession(IoSession session, VirtualHostRegistry virtualHostRegistry, AMQProtocolSession protocolSession, AMQCodecFactory codec) throws AMQException - { - new ClusteredProtocolSession(session, virtualHostRegistry, codec, new ServerHandlerRegistry(_handlers, virtualHostRegistry, protocolSession)); - } - - void connect(String join) throws Exception - { - if (join == null) - { - _groupMgr.establish(); - } - else - { - _groupMgr.join(new SimpleMemberHandle(join)); - } - } - - private boolean inState(JoinState state) - { - return _groupMgr.getState().equals(state); - } - - public void messageReceived(IoSession session, Object msg) throws Exception - { - JoinState state = _groupMgr.getState(); - switch (state) - { - case JOINED: - _logger.debug(new LogMessage("Received {0}", msg)); - super.messageReceived(session, msg); - break; - case JOINING: - case INITIATION: - case INDUCTION: - buffer(session, msg); - break; - default: - throw new AMQException(null, "Received message while in state: " + state, null); - } - JoinState latest = _groupMgr.getState(); - if (!latest.equals(state)) - { - switch (latest) - { - case INDUCTION: - _logger.info("Reached induction, delivering buffered message from peers"); - _peerBuffer.deliver(); - break; - case JOINED: - _logger.info("Reached joined, delivering buffered message from clients"); - _clientBuffer.deliver(); - break; - } - } - } - - private void buffer(IoSession session, Object msg) throws Exception - { - if (isBufferable(msg)) - { - MemberHandle peer = ClusteredProtocolSession.getSessionPeer(session); - if (peer == null) - { - _logger.debug(new LogMessage("Buffering {0} for client", msg)); - _clientBuffer.receive(session, msg); - } - else if (inState(JoinState.JOINING) && isMembershipAnnouncement(msg)) - { - _logger.debug(new LogMessage("Initial membership [{0}] received from {1}", msg, peer)); - super.messageReceived(session, msg); - } - else if (inState(JoinState.INITIATION) && _groupMgr.isLeader(peer)) - { - _logger.debug(new LogMessage("Replaying {0} from leader ", msg)); - super.messageReceived(session, msg); - } - else if (inState(JoinState.INDUCTION)) - { - _logger.debug(new LogMessage("Replaying {0} from peer {1}", msg, peer)); - super.messageReceived(session, msg); - } - else - { - _logger.debug(new LogMessage("Buffering {0} for peer {1}", msg, peer)); - _peerBuffer.receive(session, msg); - } - } - else - { - _logger.debug(new LogMessage("Received {0}", msg)); - super.messageReceived(session, msg); - } - } - - public void deliver(IoSession session, Object msg) throws Exception - { - _logger.debug(new LogMessage("Delivering {0}", msg)); - super.messageReceived(session, msg); - } - - private boolean isMembershipAnnouncement(Object msg) - { - return msg instanceof AMQFrame && (((AMQFrame) msg).getBodyFrame() instanceof ClusterMembershipBody); - } - - private boolean isBufferable(Object msg) - { - return msg instanceof AMQFrame && isBuffereable(((AMQFrame) msg).getBodyFrame()); - } - - private boolean isBuffereable(AMQBody body) - { - return !(body instanceof ConnectionStartOkBody || - body instanceof ConnectionTuneOkBody || - body instanceof ConnectionSecureOkBody || - body instanceof ConnectionOpenBody); - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ClusteredProtocolSession.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ClusteredProtocolSession.java deleted file mode 100644 index 2406bd8f3f..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ClusteredProtocolSession.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.mina.common.IoSession; -import org.apache.qpid.AMQException; -import org.apache.qpid.codec.AMQCodecFactory; -import org.apache.qpid.server.AMQChannel; -import org.apache.qpid.server.virtualhost.VirtualHostRegistry; -import org.apache.qpid.server.virtualhost.VirtualHost; -import org.apache.qpid.server.exchange.ExchangeRegistry; -import org.apache.qpid.server.protocol.AMQMinaProtocolSession; -import org.apache.qpid.server.protocol.AMQProtocolSession; -import org.apache.qpid.server.queue.AMQMessage; -import org.apache.qpid.server.queue.QueueRegistry; -import org.apache.qpid.server.registry.ApplicationRegistry; -import org.apache.qpid.server.registry.IApplicationRegistry; -import org.apache.qpid.server.state.AMQStateManager; - -public class ClusteredProtocolSession extends AMQMinaProtocolSession -{ - private MemberHandle _peer; - - public ClusteredProtocolSession(IoSession session, VirtualHostRegistry virtualHostRegistry, AMQCodecFactory codecFactory, AMQStateManager stateManager) throws AMQException -// public ClusteredProtocolSession(IoSession session, QueueRegistry queueRegistry, -// ExchangeRegistry exchangeRegistry, AMQCodecFactory codecFactory) throws AMQException - { - super(session, virtualHostRegistry, codecFactory, stateManager); -// super(session, queueRegistry, exchangeRegistry, codecFactory); - } - - public boolean isPeerSession() - { - return _peer != null; - } - - public void setSessionPeer(MemberHandle peer) - { - _peer = peer; - } - - public MemberHandle getSessionPeer() - { - return _peer; - } - - public AMQChannel getChannel(int channelId) - throws AMQException - { - AMQChannel channel = super.getChannel(channelId); - if (isPeerSession() && channel == null) - { - channel = new OneUseChannel(channelId, getVirtualHost()); - addChannel(channel); - } - return channel; - } - - public static boolean isPeerSession(IoSession session) - { - return isPeerSession(getAMQProtocolSession(session)); - } - - public static boolean isPeerSession(AMQProtocolSession session) - { - return session instanceof ClusteredProtocolSession && ((ClusteredProtocolSession) session).isPeerSession(); - } - - public static void setSessionPeer(AMQProtocolSession session, MemberHandle peer) - { - ((ClusteredProtocolSession) session).setSessionPeer(peer); - } - - public static MemberHandle getSessionPeer(AMQProtocolSession session) - { - return ((ClusteredProtocolSession) session).getSessionPeer(); - } - - public static MemberHandle getSessionPeer(IoSession session) - { - return getSessionPeer(getAMQProtocolSession(session)); - } - - /** - * Cleans itself up after delivery of a message (publish frame, header and optional body frame(s)) - */ - private class OneUseChannel extends AMQChannel - { - public OneUseChannel(int channelId, VirtualHost virtualHost) - throws AMQException - { - super(ClusteredProtocolSession.this,channelId, - virtualHost.getTransactionManager(), - virtualHost.getMessageStore(), - virtualHost.getExchangeRegistry()); - } - - protected void routeCurrentMessage() throws AMQException - { - super.routeCurrentMessage(); - removeChannel(getChannelId()); - } - } - - public static boolean isPayloadFromPeer(AMQMessage payload) - { - return isPeerSession(payload.getPublisher()); - } - - public static boolean canRelay(AMQMessage payload, MemberHandle target) - { - //can only relay client messages that have not already been relayed to the given target - return !isPayloadFromPeer(payload) && !payload.checkToken(target); - } - -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ConnectionStatusMonitor.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ConnectionStatusMonitor.java deleted file mode 100644 index a1f01eff46..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ConnectionStatusMonitor.java +++ /dev/null @@ -1,80 +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. - * - */ -package org.apache.qpid.server.cluster; - -import java.io.IOException; - -class ConnectionStatusMonitor -{ - private boolean _complete; - private boolean _redirected; - private String _host; - private int _port; - private RuntimeException _error; - - synchronized void opened() - { - _complete = true; - notifyAll(); - } - - synchronized void redirect(String host, int port) - { - _complete = true; - _redirected = true; - this._host = host; - this._port = port; - } - - synchronized void failed(RuntimeException e) - { - _error = e; - _complete = true; - } - - synchronized boolean waitUntilOpen() throws InterruptedException - { - while (!_complete) - { - wait(); - } - if (_error != null) - { - throw _error; - } - return !_redirected; - } - - synchronized boolean isOpened() - { - return _complete; - } - - String getHost() - { - return _host; - } - - int getPort() - { - return _port; - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/DefaultGroupManager.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/DefaultGroupManager.java deleted file mode 100644 index a9a7a55128..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/DefaultGroupManager.java +++ /dev/null @@ -1,396 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.log4j.Logger; -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.*; -import org.apache.qpid.server.cluster.policy.StandardPolicies; -import org.apache.qpid.server.cluster.replay.ReplayManager; -import org.apache.qpid.server.cluster.util.LogMessage; - -import java.util.List; - -public class DefaultGroupManager implements GroupManager, MemberFailureListener, BrokerFactory, StandardPolicies -{ - private static final Logger _logger = Logger.getLogger(DefaultGroupManager.class); - private final LoadTable _loadTable; - private final BrokerFactory _factory; - private final ReplayManager _replayMgr; - private final BrokerGroup _group; - - DefaultGroupManager(MemberHandle handle, BrokerFactory factory, ReplayManager replayMgr) - { - this(handle, factory, replayMgr, new LoadTable()); - } - - DefaultGroupManager(MemberHandle handle, BrokerFactory factory, ReplayManager replayMgr, LoadTable loadTable) - { - handle = SimpleMemberHandle.resolve(handle); - _logger.info(handle); - _loadTable = loadTable; - _factory = factory; - _replayMgr = replayMgr; - _group = new BrokerGroup(handle, _replayMgr, this); - } - - public JoinState getState() - { - return _group.getState(); - } - - public void addMemberhipChangeListener(MembershipChangeListener l) - { - _group.addMemberhipChangeListener(l); - } - - public void removeMemberhipChangeListener(MembershipChangeListener l) - { - _group.removeMemberhipChangeListener(l); - } - - public void broadcast(Sendable message) throws AMQException - { - for (Broker b : _group.getPeers()) - { - b.send(message, null); - } - } - - public void broadcast(Sendable message, BroadcastPolicy policy, GroupResponseHandler callback) throws AMQException - { - GroupRequest request = new GroupRequest(message, policy, callback); - for (Broker b : _group.getPeers()) - { - b.invoke(request); - } - request.finishedSend(); - } - - public void send(MemberHandle broker, Sendable message) throws AMQException - { - Broker destination = findBroker(broker); - if(destination == null) - { - _logger.warn(new LogMessage("Invalid destination sending {0}. {1} not known", message, broker)); - } - else - { - destination.send(message, null); - _logger.debug(new LogMessage("Sent {0} to {1}", message, broker)); - } - } - - private void send(Broker broker, Sendable message, ResponseHandler handler) throws AMQException - { - broker.send(message, handler); - } - - private void ping(Broker b) throws AMQException - { - // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) - // TODO: Connect this to the session version obtained from ProtocolInitiation for this session. - ClusterPingBody ping = new ClusterPingBody((byte)8, - (byte)0, - ClusterPingBody.getClazz((byte)8, (byte)0), - ClusterPingBody.getMethod((byte)8, (byte)0), - _group.getLocal().getDetails(), - _loadTable.getLocalLoad(), - true); - BlockingHandler handler = new BlockingHandler(); - send(getLeader(), new SimpleBodySendable(ping), handler); - handler.waitForCompletion(); - if (handler.failed()) - { - if (isLeader()) - { - handleFailure(b); - } - else - { - suspect(b); - } - } - else - { - _loadTable.setLoad(b, ((ClusterPingBody) handler.getResponse()).load); - } - } - - public void handlePing(MemberHandle member, long load) - { - _loadTable.setLoad(findBroker(member), load); - } - - public Member redirect() - { - return _loadTable.redirect(); - } - - public void establish() - { - _group.establish(); - _logger.info("Established cluster"); - } - - public void join(MemberHandle member) throws AMQException - { - member = SimpleMemberHandle.resolve(member); - - Broker leader = connectToLeader(member); - _logger.info(new LogMessage("Connected to {0}. joining", leader)); - // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) - // TODO: Connect this to the session version obtained from ProtocolInitiation for this session. - ClusterJoinBody join = new ClusterJoinBody((byte)8, - (byte)0, - ClusterJoinBody.getClazz((byte)8, (byte)0), - ClusterJoinBody.getMethod((byte)8, (byte)0), - _group.getLocal().getDetails()); - - send(leader, new SimpleBodySendable(join)); - } - - private Broker connectToLeader(MemberHandle member) throws AMQException - { - try - { - return _group.connectToLeader(member); - } - catch (Exception e) - { - throw new AMQException(null, "Could not connect to leader: " + e, e); - } - } - - public void leave() throws AMQException - { - // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) - // TODO: Connect this to the session version obtained from ProtocolInitiation for this session. - ClusterLeaveBody leave = new ClusterLeaveBody((byte)8, - (byte)0, - ClusterLeaveBody.getClazz((byte)8, (byte)0), - ClusterLeaveBody.getMethod((byte)8, (byte)0), - _group.getLocal().getDetails()); - - send(getLeader(), new SimpleBodySendable(leave)); - } - - private void suspect(MemberHandle broker) throws AMQException - { - if (_group.isLeader(broker)) - { - //need new leader, if this broker is next in line it can assume leadership - if (_group.assumeLeadership()) - { - announceMembership(); - } - else - { - _logger.warn(new LogMessage("Leader failed. Expecting {0} to succeed.", _group.getMembers().get(1))); - } - } - else - { - // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) - // TODO: Connect this to the session version obtained from ProtocolInitiation for this session. - ClusterSuspectBody suspect = new ClusterSuspectBody((byte)8, - (byte)0, - ClusterSuspectBody.getClazz((byte)8, (byte)0), - ClusterSuspectBody.getMethod((byte)8, (byte)0), - broker.getDetails()); - - send(getLeader(), new SimpleBodySendable(suspect)); - } - } - - - public void handleJoin(MemberHandle member) throws AMQException - { - _logger.info(new LogMessage("Handling join request for {0}", member)); - if(isLeader()) - { - //connect to the host and port specified: - Broker prospect = connectToProspect(member); - announceMembership(); - List<AMQMethodBody> msgs = _replayMgr.replay(true); - _logger.info(new LogMessage("Replaying {0} from leader to {1}", msgs, prospect)); - prospect.replay(msgs); - } - else - { - //pass request on to leader: - // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) - // TODO: Connect this to the session version obtained from ProtocolInitiation for this session. - ClusterJoinBody request = new ClusterJoinBody((byte)8, (byte)0, - ClusterJoinBody.getClazz((byte)8, (byte)0), - ClusterJoinBody.getMethod((byte)8, (byte)0), - member.getDetails()); - - Broker leader = getLeader(); - send(leader, new SimpleBodySendable(request)); - _logger.info(new LogMessage("Passed join request for {0} to {1}", member, leader)); - } - } - - private Broker connectToProspect(MemberHandle member) throws AMQException - { - try - { - return _group.connectToProspect(member); - } - catch (Exception e) - { - e.printStackTrace(); - throw new AMQException(null, "Could not connect to prospect: " + e, e); - } - } - - public void handleLeave(MemberHandle member) throws AMQException - { - handleFailure(findBroker(member)); - announceMembership(); - } - - public void handleSuspect(MemberHandle member) throws AMQException - { - Broker b = findBroker(member); - if(b != null) - { - //ping it to check it has failed, ping will handle failure if it has - ping(b); - announceMembership(); - } - } - - public void handleSynch(MemberHandle member) - { - _group.synched(member); - } - - private ClusterMembershipBody createAnnouncement(String membership) - { - // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) - // TODO: Connect this to the session version obtained from ProtocolInitiation for this session. - ClusterMembershipBody announce = new ClusterMembershipBody((byte)8, (byte)0, - ClusterMembershipBody.getClazz((byte)8, (byte)0), - ClusterMembershipBody.getMethod((byte)8, (byte)0), - membership.getBytes()); - - - return announce; - } - - private void announceMembership() throws AMQException - { - String membership = SimpleMemberHandle.membersToString(_group.getMembers()); - ClusterMembershipBody announce = createAnnouncement(membership); - broadcast(new SimpleBodySendable(announce)); - _logger.info(new LogMessage("Membership announcement sent: {0}", membership)); - } - - private void handleFailure(Broker peer) - { - peer.remove(); - _group.remove(peer); - } - - public void handleMembershipAnnouncement(String membership) throws AMQException - { - _group.setMembers(SimpleMemberHandle.stringToMembers(membership)); - _logger.info(new LogMessage("Membership announcement received: {0}", membership)); - } - - public boolean isLeader() - { - return _group.isLeader(); - } - - public boolean isLeader(MemberHandle handle) - { - return _group.isLeader(handle); - } - - public Broker getLeader() - { - return _group.getLeader(); - } - - private Broker findBroker(MemberHandle handle) - { - return _group.findBroker(handle, false); - } - - public Member getMember(MemberHandle handle) - { - return findBroker(handle); - } - - public boolean isMember(MemberHandle member) - { - for (MemberHandle handle : _group.getMembers()) - { - if (handle.matches(member)) - { - return true; - } - } - return false; - } - - public MemberHandle getLocal() - { - return _group.getLocal(); - } - - public void failed(MemberHandle member) - { - if (isLeader()) - { - handleFailure(findBroker(member)); - try - { - announceMembership(); - } - catch (AMQException e) - { - _logger.error("Error announcing failure: " + e, e); - } - } - else - { - try - { - suspect(member); - } - catch (AMQException e) - { - _logger.error("Error sending suspect: " + e, e); - } - } - } - - public Broker create(MemberHandle handle) - { - Broker broker = _factory.create(handle); - broker.addFailureListener(this); - return broker; - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/GroupManager.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/GroupManager.java deleted file mode 100644 index 5599ae4b1f..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/GroupManager.java +++ /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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.qpid.AMQException; - -public interface GroupManager -{ - /** - * Establish a new cluster with the local member as the leader. - */ - public void establish(); - - /** - * Join the cluster to which member belongs - */ - public void join(MemberHandle member) throws AMQException; - - public void broadcast(Sendable message) throws AMQException; - - public void broadcast(Sendable message, BroadcastPolicy policy, GroupResponseHandler callback) throws AMQException; - - public void send(MemberHandle broker, Sendable message) throws AMQException; - - public void leave() throws AMQException; - - public void handleJoin(MemberHandle member) throws AMQException; - - public void handleLeave(MemberHandle member) throws AMQException; - - public void handleSuspect(MemberHandle member) throws AMQException; - - public void handlePing(MemberHandle member, long load); - - public void handleMembershipAnnouncement(String membership) throws AMQException; - - public void handleSynch(MemberHandle member); - - public boolean isLeader(); - - public boolean isLeader(MemberHandle handle); - - public boolean isMember(MemberHandle member); - - public MemberHandle redirect(); - - public MemberHandle getLocal(); - - public JoinState getState(); - - public void addMemberhipChangeListener(MembershipChangeListener l); - - public void removeMemberhipChangeListener(MembershipChangeListener l); -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/GroupRequest.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/GroupRequest.java deleted file mode 100644 index 8ab7856e87..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/GroupRequest.java +++ /dev/null @@ -1,107 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.AMQMethodBody; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Represents a method sent to a group of Member instances. Manages the responses, - * completion and callback. - * - */ -class GroupRequest -{ - private final Map<Member, AMQMethodBody> _responses = new HashMap<Member, AMQMethodBody>(); - private final List<Member> _brokers = new ArrayList<Member>(); - private boolean _sent; - - private final Sendable _request; - private final BroadcastPolicy _policy; - private final GroupResponseHandler _callback; - - GroupRequest(Sendable request, BroadcastPolicy policy, GroupResponseHandler callback) - { - _request = request; - _policy = policy; - _callback = callback; - } - - void send(int channel, Member session) throws AMQException - { - _brokers.add(session); - _request.send(channel, session); - } - - boolean finishedSend() - { - _sent = true; - return checkCompletion(); - } - - public boolean responseReceived(Member broker, AMQMethodBody response) - { - _responses.put(broker, response); - return checkCompletion(); - } - - public boolean removed(Member broker) - { - _brokers.remove(broker); - return checkCompletion(); - } - - private synchronized boolean checkCompletion() - { - return isComplete() && callback(); - } - - boolean isComplete() - { - return _sent && _policy != null && _policy.isComplete(_responses.size(), _brokers.size()); - } - - boolean callback() - { - _callback.response(getResults(), _brokers); - return true; - } - - List<AMQMethodBody> getResults() - { - List<AMQMethodBody> results = new ArrayList<AMQMethodBody>(_brokers.size()); - for (Member b : _brokers) - { - results.add(_responses.get(b)); - } - return results; - } - - public String toString() - { - return "GroupRequest{request=" + _request +", brokers=" + _brokers + ", responses=" + _responses + "}"; - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/GroupResponseHandler.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/GroupResponseHandler.java deleted file mode 100644 index d2e9de2f39..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/GroupResponseHandler.java +++ /dev/null @@ -1,31 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.qpid.framing.AMQMethodBody; - -import java.util.List; - -public interface GroupResponseHandler -{ - //Note: this implies that the response to a group request will always be a method body... - public void response(List<AMQMethodBody> responses, List<Member> members); -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/InductionBuffer.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/InductionBuffer.java deleted file mode 100644 index 586d7d4ae8..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/InductionBuffer.java +++ /dev/null @@ -1,90 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.mina.common.IoSession; - -import java.util.LinkedList; -import java.util.Queue; - -/** - * Buffers any received messages until join completes. - * - */ -class InductionBuffer -{ - private final Queue<Message> _buffer = new LinkedList<Message>(); - private final MessageHandler _handler; - private boolean _buffering = true; - - InductionBuffer(MessageHandler handler) - { - _handler = handler; - } - - private void process() throws Exception - { - for (Message o = _buffer.poll(); o != null; o = _buffer.poll()) - { - o.deliver(_handler); - } - _buffering = false; - } - - synchronized void deliver() throws Exception - { - process(); - } - - synchronized void receive(IoSession session, Object msg) throws Exception - { - if (_buffering) - { - _buffer.offer(new Message(session, msg)); - } - else - { - _handler.deliver(session, msg); - } - } - - private static class Message - { - private final IoSession _session; - private final Object _msg; - - Message(IoSession session, Object msg) - { - _session = session; - _msg = msg; - } - - void deliver(MessageHandler handler) throws Exception - { - handler.deliver(_session, _msg); - } - } - - static interface MessageHandler - { - public void deliver(IoSession session, Object msg) throws Exception; - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/JoinState.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/JoinState.java deleted file mode 100644 index 5f92aa2971..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/JoinState.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -package org.apache.qpid.server.cluster; - -public enum JoinState -{ - UNINITIALISED, JOINING, INITIATION, INDUCTION, JOINED -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/LoadTable.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/LoadTable.java deleted file mode 100644 index 13465a8615..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/LoadTable.java +++ /dev/null @@ -1,107 +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. - * - */ -package org.apache.qpid.server.cluster; - -import java.util.HashMap; -import java.util.Map; -import java.util.PriorityQueue; - -/** - * Maintains loading information about the local member and its cluster peers. - * - */ -public class LoadTable -{ - private final Map<MemberHandle, Loading> _peers = new HashMap<MemberHandle, Loading>(); - private final PriorityQueue<Loading> _loads = new PriorityQueue<Loading>(); - private final Loading _local = new Loading(null); - - public LoadTable() - { - _loads.add(_local); - } - - public void setLoad(Member member, long load) - { - synchronized (_peers) - { - Loading loading = _peers.get(member); - if (loading == null) - { - loading = new Loading(member); - synchronized (_loads) - { - _loads.add(loading); - } - _peers.put(member, loading); - } - loading.load = load; - } - } - - public void incrementLocalLoad() - { - synchronized (_local) - { - _local.load++; - } - } - - public void decrementLocalLoad() - { - synchronized (_local) - { - _local.load--; - } - } - - public long getLocalLoad() - { - synchronized (_local) - { - return _local.load; - } - } - - public Member redirect() - { - synchronized (_loads) - { - return _loads.peek().member; - } - } - - private static class Loading implements Comparable - { - private final Member member; - private long load; - - Loading(Member member) - { - this.member = member; - } - - public int compareTo(Object o) - { - return (int) (load - ((Loading) o).load); - } - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/Main.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/Main.java deleted file mode 100644 index 15752353d1..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/Main.java +++ /dev/null @@ -1,117 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.CommandLineParser; -import org.apache.commons.cli.HelpFormatter; -import org.apache.commons.cli.Option; -import org.apache.commons.cli.OptionBuilder; -import org.apache.commons.cli.Options; -import org.apache.commons.cli.ParseException; -import org.apache.commons.cli.PosixParser; -import org.apache.log4j.Logger; -import org.apache.mina.common.IoAcceptor; -import org.apache.mina.transport.socket.nio.SocketAcceptor; -import org.apache.mina.transport.socket.nio.SocketAcceptorConfig; -import org.apache.mina.transport.socket.nio.SocketSessionConfig; -import org.apache.qpid.pool.ReadWriteThreadModel; -import org.apache.qpid.server.registry.ApplicationRegistry; -import org.apache.qpid.server.registry.ConfigurationFileApplicationRegistry; -import org.apache.qpid.server.transport.ConnectorConfiguration; - -import java.io.File; -import java.io.IOException; -import java.net.InetAddress; -import java.net.InetSocketAddress; - -/** - * TODO: This is a cut-and-paste from the original broker Main class. Would be preferrable to make that class more - * reuseable to avoid all this duplication. - */ -public class Main extends org.apache.qpid.server.Main -{ - private static final Logger _logger = Logger.getLogger(Main.class); - - protected Main(String[] args) - { - super(args); - } - - protected void setOptions(Options otions) - { - super.setOptions(options); - - //extensions: - Option join = OptionBuilder.withArgName("join").hasArg().withDescription("Join the specified cluster member. Overrides any value in the config file"). - withLongOpt("join").create("j"); - options.addOption(join); - } - - protected void bind(int port, ConnectorConfiguration connectorConfig) - { - try - { - IoAcceptor acceptor = new SocketAcceptor(); - SocketAcceptorConfig sconfig = (SocketAcceptorConfig) acceptor.getDefaultConfig(); - SocketSessionConfig sc = (SocketSessionConfig) sconfig.getSessionConfig(); - - sc.setReceiveBufferSize(connectorConfig.socketReceiveBufferSize); - sc.setSendBufferSize(connectorConfig.socketWriteBuferSize); - sc.setTcpNoDelay(true); - - // if we do not use the executor pool threading model we get the default leader follower - // implementation provided by MINA - if (connectorConfig.enableExecutorPool) - { - sconfig.setThreadModel(ReadWriteThreadModel.getInstance()); - } - - String host = InetAddress.getLocalHost().getHostName(); - ClusteredProtocolHandler handler = new ClusteredProtocolHandler(new InetSocketAddress(host, port)); - if (!connectorConfig.enableSSL) - { - acceptor.bind(new InetSocketAddress(port), handler, sconfig); - _logger.info("Qpid.AMQP listening on non-SSL port " + port); - handler.connect(commandLine.getOptionValue("j")); - } - else - { - ClusteredProtocolHandler sslHandler = new ClusteredProtocolHandler(handler); - acceptor.bind(new InetSocketAddress(connectorConfig.sslPort), sslHandler, sconfig); - _logger.info("Qpid.AMQP listening on SSL port " + connectorConfig.sslPort); - } - } - catch (IOException e) - { - _logger.error("Unable to bind service to registry: " + e, e); - } - catch (Exception e) - { - _logger.error("Unable to connect to cluster: " + e, e); - } - } - - public static void main(String[] args) - { - new Main(args); - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/Member.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/Member.java deleted file mode 100644 index 3fbdfdde70..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/Member.java +++ /dev/null @@ -1,31 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.AMQDataBlock; - -public interface Member extends MemberHandle -{ - public void send(AMQDataBlock data) throws AMQException; - - public void addFailureListener(MemberFailureListener listener); -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/MemberFailureListener.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/MemberFailureListener.java deleted file mode 100644 index 7ce45dffaa..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/MemberFailureListener.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -package org.apache.qpid.server.cluster; - -interface MemberFailureListener -{ - public void failed(MemberHandle member); -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/MemberHandle.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/MemberHandle.java deleted file mode 100644 index b8099a12f7..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/MemberHandle.java +++ /dev/null @@ -1,36 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.qpid.framing.AMQShortString; - -public interface MemberHandle -{ - public String getHost(); - - public int getPort(); - - public boolean matches(MemberHandle m); - - public boolean matches(String host, int port); - - public AMQShortString getDetails(); -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/MembershipChangeListener.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/MembershipChangeListener.java deleted file mode 100644 index 591e652e32..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/MembershipChangeListener.java +++ /dev/null @@ -1,28 +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. - * - */ -package org.apache.qpid.server.cluster; - -import java.util.List; - -public interface MembershipChangeListener -{ - public void changed(List<MemberHandle> members); -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/MethodHandler.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/MethodHandler.java deleted file mode 100644 index a83f034021..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/MethodHandler.java +++ /dev/null @@ -1,29 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.AMQMethodBody; - -interface MethodHandler -{ - public void handle(int channel, AMQMethodBody method) throws AMQException; -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/MethodHandlerFactory.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/MethodHandlerFactory.java deleted file mode 100644 index 9bf04f5458..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/MethodHandlerFactory.java +++ /dev/null @@ -1,28 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.qpid.server.state.AMQState; - -public interface MethodHandlerFactory -{ - public MethodHandlerRegistry register(AMQState state, MethodHandlerRegistry registry); -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/MethodHandlerRegistry.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/MethodHandlerRegistry.java deleted file mode 100644 index 748a660bb8..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/MethodHandlerRegistry.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.qpid.framing.AMQMethodBody; -import org.apache.qpid.server.state.StateAwareMethodListener; - -import java.util.HashMap; -import java.util.Map; - -public class MethodHandlerRegistry -{ - private final Map<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>> registry = - new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>(); - - public <A extends AMQMethodBody, B extends Class<A>> MethodHandlerRegistry addHandler(B type, StateAwareMethodListener<A> handler) - { - registry.put(type, handler); - return this; - } - - public <B extends AMQMethodBody> StateAwareMethodListener<B> getHandler(B frame) - { - return (StateAwareMethodListener<B>) registry.get(frame.getClass()); - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/MinaBrokerProxy.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/MinaBrokerProxy.java deleted file mode 100644 index 6529c7f3e2..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/MinaBrokerProxy.java +++ /dev/null @@ -1,272 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.log4j.Logger; -import org.apache.mina.common.ConnectFuture; -import org.apache.mina.common.IoHandlerAdapter; -import org.apache.mina.common.IoSession; -import org.apache.mina.common.RuntimeIOException; -import org.apache.mina.filter.codec.ProtocolCodecFilter; -import org.apache.mina.transport.socket.nio.SocketConnector; -import org.apache.mina.transport.socket.nio.SocketConnectorConfig; -import org.apache.mina.transport.socket.nio.SocketSessionConfig; -import org.apache.qpid.AMQException; -import org.apache.qpid.server.cluster.util.LogMessage; -import org.apache.qpid.client.state.AMQState; -import org.apache.qpid.codec.AMQCodecFactory; -import org.apache.qpid.framing.AMQBody; -import org.apache.qpid.framing.AMQDataBlock; -import org.apache.qpid.framing.AMQFrame; -import org.apache.qpid.framing.AMQMethodBody; -import org.apache.qpid.framing.ConnectionRedirectBody; -import org.apache.qpid.framing.ProtocolInitiation; -import org.apache.qpid.framing.ProtocolVersion; - -import java.io.IOException; -import java.net.InetSocketAddress; - -/** - * A 'client stub' for a remote cluster peer, using MINA for IO Layer - * - */ -public class MinaBrokerProxy extends Broker implements MethodHandler -{ - private static final Logger _logger = Logger.getLogger(MinaBrokerProxy.class); - private final ConnectionStatusMonitor _connectionMonitor = new ConnectionStatusMonitor(); - private final ClientHandlerRegistry _legacyHandler; - private final MinaBinding _binding = new MinaBinding(); - private final MemberHandle _local; - private IoSession _session; - private MethodHandler _handler; - private Iterable<AMQMethodBody> _replay; - - MinaBrokerProxy(String host, int port, MemberHandle local) - { - super(host, port); - _local = local; - _legacyHandler = new ClientHandlerRegistry(local, null); - } - - private void init(IoSession session) - { - _session = session; - _handler = new ClientAdapter(session, _legacyHandler); - } - - private ConnectFuture connectImpl() - { - _logger.info("Connecting to cluster peer: " + getDetails()); - SocketConnector ioConnector = new SocketConnector(); - SocketConnectorConfig cfg = (SocketConnectorConfig) ioConnector.getDefaultConfig(); - - SocketSessionConfig scfg = (SocketSessionConfig) cfg.getSessionConfig(); - scfg.setTcpNoDelay(true); - scfg.setSendBufferSize(32768); - scfg.setReceiveBufferSize(32768); - InetSocketAddress address = new InetSocketAddress(getHost(), getPort()); - return ioConnector.connect(address, _binding); - } - - //extablish connection without handling redirect - boolean connect() throws IOException, InterruptedException - { - ConnectFuture future = connectImpl(); - // wait for connection to complete - future.join(); - // we call getSession which throws an IOException if there has been an error connecting - try - { - future.getSession(); - } - catch (RuntimeIOException e) - { - _connectionMonitor.failed(e); - _logger.error(new LogMessage("Could not connect to {0}: {1}", this, e), e); - throw e; - } - return _connectionMonitor.waitUntilOpen(); - } - - void connectAsynch(Iterable<AMQMethodBody> msgs) - { - _replay = msgs; - connectImpl(); - } - - void replay(Iterable<AMQMethodBody> msgs) - { - _replay = msgs; - if(_connectionMonitor.isOpened()) - { - replay(); - } - } - - //establish connection, handling redirect if required... - Broker connectToCluster() throws IOException, InterruptedException - { - connect(); - //wait until the connection is open or get a redirection - if (_connectionMonitor.waitUntilOpen()) - { - return this; - } - else - { - Broker broker = new MinaBrokerProxy(_connectionMonitor.getHost(), _connectionMonitor.getPort(), _local); - broker.connect(); - return broker; - } - } - - public void send(AMQDataBlock data) throws AMQConnectionWaitException - { - if (_session == null) - { - try - { - _connectionMonitor.waitUntilOpen(); - } - catch (InterruptedException e) - { - throw new AMQConnectionWaitException("Failed to send " + data + ": " + e, e); - } - } - _session.write(data); - } - - private void replay() - { - if(_replay != null) - { - for(AMQMethodBody b : _replay) - { - _session.write(new AMQFrame(0, b)); - } - } - } - - public void handle(int channel, AMQMethodBody method) throws AMQException - { - _logger.info(new LogMessage("Handling method: {0} for channel {1}", method, channel)); - if (!handleResponse(channel, method)) - { - _logger.warn(new LogMessage("Unhandled method: {0} for channel {1}", method, channel)); - } - } - - private void handleMethod(int channel, AMQMethodBody method) throws AMQException - { - if (method instanceof ConnectionRedirectBody) - { - //signal redirection to waiting thread - ConnectionRedirectBody redirect = (ConnectionRedirectBody) method; - String[] parts = redirect.host.toString().split(":"); - _connectionMonitor.redirect(parts[0], Integer.parseInt(parts[1])); - } - else - { - _handler.handle(channel, method); - if (AMQState.CONNECTION_OPEN.equals(_legacyHandler.getCurrentState()) && _handler != this) - { - _handler = this; - _logger.info(new LogMessage("Connection opened, handler switched")); - //replay any messages: - replay(); - //signal waiting thread: - _connectionMonitor.opened(); - } - } - } - - private void handleFrame(AMQFrame frame) throws AMQException - { - AMQBody body = frame.getBodyFrame(); - if (body instanceof AMQMethodBody) - { - handleMethod(frame.getChannel(), (AMQMethodBody) body); - } - else - { - throw new AMQUnexpectedBodyTypeException(AMQMethodBody.class, body, null); - } - } - - public String toString() - { - return "MinaBrokerProxy[" + (_session == null ? super.toString() : _session.getRemoteAddress()) + "]"; - } - - private class MinaBinding extends IoHandlerAdapter - { - public void sessionCreated(IoSession session) throws Exception - { - init(session); - _logger.info(new LogMessage("{0}: created", MinaBrokerProxy.this)); - ProtocolCodecFilter pcf = new ProtocolCodecFilter(new AMQCodecFactory(false)); - session.getFilterChain().addLast("protocolFilter", pcf); - - /* Find last protocol version in protocol version list. Make sure last protocol version - listed in the build file (build-module.xml) is the latest version which will be used - here. */ - - session.write(new ProtocolInitiation(ProtocolVersion.getLatestSupportedVersion())); - } - - public void sessionOpened(IoSession session) throws Exception - { - _logger.info(new LogMessage("{0}: opened", MinaBrokerProxy.this)); - } - - public void sessionClosed(IoSession session) throws Exception - { - _logger.info(new LogMessage("{0}: closed", MinaBrokerProxy.this)); - } - - public void exceptionCaught(IoSession session, Throwable throwable) throws Exception - { - _logger.error(new LogMessage("{0}: received {1}", MinaBrokerProxy.this, throwable), throwable); - if (! (throwable instanceof IOException)) - { - _session.close(); - } - failed(); - } - - public void messageReceived(IoSession session, Object object) throws Exception - { - if (object instanceof AMQFrame) - { - handleFrame((AMQFrame) object); - } - else - { - throw new AMQUnexpectedFrameTypeException("Received message of unrecognised type: " + object, null); - } - } - - public void messageSent(IoSession session, Object object) throws Exception - { - _logger.debug(new LogMessage("{0}: sent {1}", MinaBrokerProxy.this, object)); - } - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ResponseHandler.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ResponseHandler.java deleted file mode 100644 index fe76ca6505..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ResponseHandler.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.qpid.framing.AMQMethodBody; - -public interface ResponseHandler -{ - public void responded(AMQMethodBody response); - - public void removed(); -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/Sendable.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/Sendable.java deleted file mode 100644 index 159612331c..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/Sendable.java +++ /dev/null @@ -1,28 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.qpid.AMQException; - -public interface Sendable -{ - public void send(int channel, Member member) throws AMQException; -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ServerHandlerRegistry.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ServerHandlerRegistry.java deleted file mode 100644 index aadcfa4b4c..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/ServerHandlerRegistry.java +++ /dev/null @@ -1,98 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.log4j.Logger; -import org.apache.qpid.framing.AMQMethodBody; -import org.apache.qpid.server.state.AMQState; -import org.apache.qpid.server.state.AMQStateManager; -//import org.apache.qpid.server.state.IllegalStateTransitionException; -import org.apache.qpid.server.state.StateAwareMethodListener; -import org.apache.qpid.server.cluster.util.LogMessage; -import org.apache.qpid.server.queue.QueueRegistry; -import org.apache.qpid.server.exchange.ExchangeRegistry; -import org.apache.qpid.server.protocol.AMQProtocolSession; -import org.apache.qpid.server.virtualhost.VirtualHostRegistry; - -import java.util.HashMap; -import java.util.Map; - -/** - * An extension of server.AMQStateManager that allows different handlers to be registered. - * - */ -class ServerHandlerRegistry extends AMQStateManager -{ - private final Logger _logger = Logger.getLogger(ServerHandlerRegistry.class); - private final Map<AMQState, MethodHandlerRegistry> _handlers = new HashMap<AMQState, MethodHandlerRegistry>(); - - ServerHandlerRegistry(VirtualHostRegistry virtualHostRegistry, AMQProtocolSession protocolSession) - { - super(AMQState.CONNECTION_NOT_STARTED, false, virtualHostRegistry, protocolSession); - } - - ServerHandlerRegistry(ServerHandlerRegistry s, VirtualHostRegistry virtualHostRegistry, AMQProtocolSession protocolSession) - { - this(virtualHostRegistry, protocolSession); - _handlers.putAll(s._handlers); - } - - ServerHandlerRegistry(MethodHandlerFactory factory, VirtualHostRegistry virtualHostRegistry, AMQProtocolSession protocolSession) - { - this(virtualHostRegistry, protocolSession); - init(factory); - } - - void setHandlers(AMQState state, MethodHandlerRegistry handlers) - { - _handlers.put(state, handlers); - } - - void init(MethodHandlerFactory factory) - { - for (AMQState s : AMQState.values()) - { - setHandlers(s, factory.register(s, new MethodHandlerRegistry())); - } - } - - protected <B extends AMQMethodBody> StateAwareMethodListener<B> findStateTransitionHandler(AMQState state, B frame) //throws IllegalStateTransitionException - { - MethodHandlerRegistry registry = _handlers.get(state); - StateAwareMethodListener<B> handler = (registry == null) ? null : registry.getHandler(frame); - if (handler == null) - { - _logger.warn(new LogMessage("No handler for {0}, {1}", state, frame)); - } - return handler; - } - - <A extends AMQMethodBody, B extends Class<A>> void addHandler(AMQState state, B type, StateAwareMethodListener<A> handler) - { - MethodHandlerRegistry registry = _handlers.get(state); - if (registry == null) - { - registry = new MethodHandlerRegistry(); - _handlers.put(state, registry); - } - registry.addHandler(type, handler); - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/SimpleBodySendable.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/SimpleBodySendable.java deleted file mode 100644 index f7c40c60b3..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/SimpleBodySendable.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * - * 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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.AMQBody; -import org.apache.qpid.framing.AMQFrame; - -/** - */ -public class SimpleBodySendable implements Sendable -{ - private final AMQBody _body; - - public SimpleBodySendable(AMQBody body) - { - _body = body; - } - - public void send(int channel, Member member) throws AMQException - { - member.send(new AMQFrame(channel, _body)); - } - - public String toString() - { - return _body.toString(); - } - -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/SimpleMemberHandle.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/SimpleMemberHandle.java deleted file mode 100644 index 1255094b1d..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/SimpleMemberHandle.java +++ /dev/null @@ -1,166 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.qpid.framing.AMQShortString; - -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.List; - -public class SimpleMemberHandle implements MemberHandle -{ - private final String _host; - private final int _port; - - public SimpleMemberHandle(String host, int port) - { - _host = host; - _port = port; - } - - public SimpleMemberHandle(AMQShortString details) - { - this(details.toString()); - } - - public SimpleMemberHandle(String details) - { - String[] parts = details.split(":"); - _host = parts[0]; - _port = Integer.parseInt(parts[1]); - } - - public SimpleMemberHandle(InetSocketAddress address) throws UnknownHostException - { - this(address.getAddress(), address.getPort()); - } - - public SimpleMemberHandle(InetAddress address, int port) throws UnknownHostException - { - this(canonical(address).getHostAddress(), port); - } - - public String getHost() - { - return _host; - } - - public int getPort() - { - return _port; - } - - public int hashCode() - { - return getPort(); - } - - public boolean equals(Object o) - { - return o instanceof MemberHandle && matches((MemberHandle) o); - } - - public boolean matches(MemberHandle m) - { - return matches(m.getHost(), m.getPort()); - } - - public boolean matches(String host, int port) - { - return _host.equals(host) && _port == port; - } - - public AMQShortString getDetails() - { - return new AMQShortString(_host + ":" + _port); - } - - public String toString() - { - return getDetails().toString(); - } - - static List<MemberHandle> stringToMembers(String membership) - { - String[] names = membership.split("\\s"); - List<MemberHandle> members = new ArrayList<MemberHandle>(); - for (String name : names) - { - members.add(new SimpleMemberHandle(name)); - } - return members; - } - - static String membersToString(List<MemberHandle> members) - { - StringBuffer buffer = new StringBuffer(); - boolean first = true; - for (MemberHandle m : members) - { - if (first) - { - first = false; - } - else - { - buffer.append(" "); - } - buffer.append(m.getDetails()); - } - - return buffer.toString(); - } - - private static InetAddress canonical(InetAddress address) throws UnknownHostException - { - if (address.isLoopbackAddress()) - { - return InetAddress.getLocalHost(); - } - else - { - return address; - } - } - - public MemberHandle resolve() - { - return resolve(this); - } - - public static MemberHandle resolve(MemberHandle handle) - { - try - { - return new SimpleMemberHandle(new InetSocketAddress(handle.getHost(), handle.getPort())); - } - catch (UnknownHostException e) - { - e.printStackTrace(); - return handle; - } - } - - -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/SimpleSendable.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/SimpleSendable.java deleted file mode 100644 index 3699a9e128..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/SimpleSendable.java +++ /dev/null @@ -1,55 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.AMQFrame; -import org.apache.qpid.framing.amqp_8_0.MethodConverter_8_0; -import org.apache.qpid.framing.abstraction.ContentChunk; -import org.apache.qpid.framing.abstraction.ProtocolVersionMethodConverter; -import org.apache.qpid.server.queue.AMQMessage; - -import java.util.Iterator; - -public class SimpleSendable implements Sendable -{ - - //todo fixme - remove 0-8 hard coding - ProtocolVersionMethodConverter _methodConverter = new MethodConverter_8_0(); - - private final AMQMessage _message; - - public SimpleSendable(AMQMessage message) - { - _message = message; - } - - public void send(int channel, Member member) throws AMQException - { - member.send(new AMQFrame(channel, _methodConverter.convertToBody(_message.getMessagePublishInfo()))); - member.send(new AMQFrame(channel, _message.getContentHeaderBody())); - Iterator<ContentChunk> it = _message.getContentBodyIterator(); - while (it.hasNext()) - { - member.send(new AMQFrame(channel, _methodConverter.convertToBody(it.next()))); - } - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/ChainedClusterMethodHandler.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/ChainedClusterMethodHandler.java deleted file mode 100644 index 86710e8a31..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/ChainedClusterMethodHandler.java +++ /dev/null @@ -1,73 +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. - * - */ -package org.apache.qpid.server.cluster.handler; - -import org.apache.qpid.server.state.AMQStateManager; -import org.apache.qpid.server.queue.QueueRegistry; -import org.apache.qpid.server.exchange.ExchangeRegistry; -import org.apache.qpid.server.protocol.AMQProtocolSession; -import org.apache.qpid.server.virtualhost.VirtualHostRegistry; -import org.apache.qpid.protocol.AMQMethodEvent; -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.AMQMethodBody; - -import java.util.List; -import java.util.ArrayList; - -public class ChainedClusterMethodHandler <A extends AMQMethodBody> extends ClusterMethodHandler<A> -{ - private final List<ClusterMethodHandler<A>> _handlers; - - private ChainedClusterMethodHandler() - { - this(new ArrayList<ClusterMethodHandler<A>>()); - } - - public ChainedClusterMethodHandler(List<ClusterMethodHandler<A>> handlers) - { - _handlers = handlers; - } - - public ChainedClusterMethodHandler(ClusterMethodHandler<A>... handlers) - { - this(); - for(ClusterMethodHandler<A>handler: handlers) - { - _handlers.add(handler); - } - } - - protected final void peer(AMQStateManager stateMgr, AMQMethodEvent<A> evt) throws AMQException - { - for(ClusterMethodHandler<A> handler : _handlers) - { - handler.peer(stateMgr, evt); - } - } - - protected final void client(AMQStateManager stateMgr, AMQMethodEvent<A> evt) throws AMQException - { - for(ClusterMethodHandler<A> handler : _handlers) - { - handler.client(stateMgr, evt); - } - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/ChannelQueueManager.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/ChannelQueueManager.java deleted file mode 100644 index c9f6dbfb37..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/ChannelQueueManager.java +++ /dev/null @@ -1,136 +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. - * - */ -package org.apache.qpid.server.cluster.handler; - -import org.apache.qpid.server.state.StateAwareMethodListener; -import org.apache.qpid.server.state.AMQStateManager; -import org.apache.qpid.server.queue.QueueRegistry; -import org.apache.qpid.server.exchange.ExchangeRegistry; -import org.apache.qpid.server.protocol.AMQProtocolSession; -import org.apache.qpid.protocol.AMQMethodEvent; -import org.apache.qpid.server.cluster.util.LogMessage; -import org.apache.qpid.server.virtualhost.VirtualHostRegistry; -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.*; -import org.apache.log4j.Logger; - -import java.util.Map; -import java.util.HashMap; - -/** - * Maintains the default queue names for a channel, and alters subsequent frames where necessary - * to use this (i.e. when no queue is explictly specified). - * - */ -class ChannelQueueManager -{ - private static final Logger _logger = Logger.getLogger(ChannelQueueManager.class); - private final Map<Integer, AMQShortString> _channelQueues = new HashMap<Integer, AMQShortString>(); - - ClusterMethodHandler<QueueDeclareBody> createQueueDeclareHandler() - { - return new QueueDeclareHandler(); - } - - ClusterMethodHandler<QueueDeleteBody> createQueueDeleteHandler() - { - return new QueueDeleteHandler(); - } - - ClusterMethodHandler<QueueBindBody> createQueueBindHandler() - { - return new QueueBindHandler(); - } - - ClusterMethodHandler<BasicConsumeBody> createBasicConsumeHandler() - { - return new BasicConsumeHandler(); - } - - private void set(int channel, AMQShortString queue) - { - _channelQueues.put(channel, queue); - _logger.info(new LogMessage("Set default queue for {0} to {1}", channel, queue)); - } - - private AMQShortString get(int channel) - { - AMQShortString queue = _channelQueues.get(channel); - _logger.info(new LogMessage("Default queue for {0} is {1}", channel, queue)); - return queue; - } - - private class QueueDeclareHandler extends ClusterMethodHandler<QueueDeclareBody> - { - protected void peer(AMQStateManager stateMgr, AMQMethodEvent<QueueDeclareBody> evt) throws AMQException - { - } - - protected void client(AMQStateManager stateMgr, AMQMethodEvent<QueueDeclareBody> evt) throws AMQException - { - set(evt.getChannelId(), evt.getMethod().queue); - } - } - private class QueueBindHandler extends ClusterMethodHandler<QueueBindBody> - { - protected void peer(AMQStateManager stateMgr, AMQMethodEvent<QueueBindBody> evt) throws AMQException - { - } - - protected void client(AMQStateManager stateMgr, AMQMethodEvent<QueueBindBody> evt) throws AMQException - { - if(evt.getMethod().queue == null) - { - evt.getMethod().queue = get(evt.getChannelId()); - } - } - } - private class QueueDeleteHandler extends ClusterMethodHandler<QueueDeleteBody> - { - protected void peer(AMQStateManager stateMgr, AMQMethodEvent<QueueDeleteBody> evt) throws AMQException - { - } - - protected void client(AMQStateManager stateMgr, AMQMethodEvent<QueueDeleteBody> evt) throws AMQException - { - if(evt.getMethod().queue == null) - { - evt.getMethod().queue = get(evt.getChannelId()); - } - } - } - - private class BasicConsumeHandler extends ClusterMethodHandler<BasicConsumeBody> - { - protected void peer(AMQStateManager stateMgr, AMQMethodEvent<BasicConsumeBody> evt) throws AMQException - { - } - - protected void client(AMQStateManager stateMgr, AMQMethodEvent<BasicConsumeBody> evt) throws AMQException - { - if(evt.getMethod().queue == null) - { - evt.getMethod().queue = get(evt.getChannelId()); - } - } - } - -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/ClusterMethodHandler.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/ClusterMethodHandler.java deleted file mode 100644 index faab99b0f6..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/ClusterMethodHandler.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -package org.apache.qpid.server.cluster.handler; - -import org.apache.qpid.framing.AMQMethodBody; -import org.apache.qpid.server.state.StateAwareMethodListener; -import org.apache.qpid.server.state.AMQStateManager; -import org.apache.qpid.server.queue.QueueRegistry; -import org.apache.qpid.server.exchange.ExchangeRegistry; -import org.apache.qpid.server.protocol.AMQProtocolSession; -import org.apache.qpid.protocol.AMQMethodEvent; -import org.apache.qpid.server.cluster.ClusteredProtocolSession; -import org.apache.qpid.AMQException; - -public abstract class ClusterMethodHandler<A extends AMQMethodBody> implements StateAwareMethodListener<A> -{ - public final void methodReceived(AMQStateManager stateMgr, AMQMethodEvent<A> evt) throws AMQException - { - AMQProtocolSession session = stateMgr.getProtocolSession(); - - if (ClusteredProtocolSession.isPeerSession(session)) - { - peer(stateMgr, evt); - } - else - { - client(stateMgr, evt); - } - } - - protected abstract void peer(AMQStateManager stateMgr, AMQMethodEvent<A> evt) throws AMQException; - protected abstract void client(AMQStateManager stateMgr, AMQMethodEvent<A> evt) throws AMQException; -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/ClusterMethodHandlerFactory.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/ClusterMethodHandlerFactory.java deleted file mode 100644 index e7509da32a..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/ClusterMethodHandlerFactory.java +++ /dev/null @@ -1,239 +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. - * - */ -package org.apache.qpid.server.cluster.handler; - -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.*; -import org.apache.qpid.server.cluster.ClusterCapability; -import org.apache.qpid.server.cluster.ClusteredProtocolSession; -import org.apache.qpid.server.cluster.GroupManager; -import org.apache.qpid.server.cluster.LoadTable; -import org.apache.qpid.server.cluster.MemberHandle; -import org.apache.qpid.server.cluster.MethodHandlerFactory; -import org.apache.qpid.server.cluster.MethodHandlerRegistry; -import org.apache.qpid.server.cluster.SimpleMemberHandle; -import org.apache.qpid.server.handler.ChannelCloseHandler; -import org.apache.qpid.server.handler.ChannelFlowHandler; -import org.apache.qpid.server.handler.ChannelOpenHandler; -import org.apache.qpid.server.handler.ConnectionCloseMethodHandler; -import org.apache.qpid.server.handler.ConnectionOpenMethodHandler; -import org.apache.qpid.server.handler.ConnectionSecureOkMethodHandler; -import org.apache.qpid.server.handler.ConnectionStartOkMethodHandler; -import org.apache.qpid.server.handler.ConnectionTuneOkMethodHandler; -import org.apache.qpid.server.handler.ExchangeDeclareHandler; -import org.apache.qpid.server.handler.ExchangeDeleteHandler; -import org.apache.qpid.server.handler.BasicCancelMethodHandler; -import org.apache.qpid.server.handler.BasicPublishMethodHandler; -import org.apache.qpid.server.handler.QueueBindHandler; -import org.apache.qpid.server.handler.QueueDeleteHandler; -import org.apache.qpid.server.handler.BasicQosHandler; -import org.apache.qpid.server.handler.TxSelectHandler; -import org.apache.qpid.server.handler.TxCommitHandler; -import org.apache.qpid.server.handler.TxRollbackHandler; -import org.apache.qpid.protocol.AMQMethodEvent; -import org.apache.qpid.server.state.AMQState; -import org.apache.qpid.server.state.AMQStateManager; -import org.apache.qpid.server.state.StateAwareMethodListener; - -public class ClusterMethodHandlerFactory implements MethodHandlerFactory -{ - private final GroupManager _groupMgr; - private final LoadTable _loadTable; - - public ClusterMethodHandlerFactory(GroupManager groupMgr, LoadTable loadTable) - { - _groupMgr = groupMgr; - _loadTable = loadTable; - } - - public MethodHandlerRegistry register(AMQState state, MethodHandlerRegistry registry) - { - switch (state) - { - case CONNECTION_NOT_STARTED: - return registry.addHandler(ConnectionStartOkBody.class, ConnectionStartOkMethodHandler.getInstance()); - case CONNECTION_NOT_AUTH: - return registry.addHandler(ConnectionSecureOkBody.class, ConnectionSecureOkMethodHandler.getInstance()); - case CONNECTION_NOT_TUNED: - return registry.addHandler(ConnectionTuneOkBody.class, ConnectionTuneOkMethodHandler.getInstance()); - case CONNECTION_NOT_OPENED: - //connection.open override: - return registry.addHandler(ConnectionOpenBody.class, new ConnectionOpenHandler()); - case CONNECTION_OPEN: - return registerConnectionOpened(registry); - } - return registry; - } - - private MethodHandlerRegistry registerConnectionOpened(MethodHandlerRegistry registry) - { - //new cluster method handlers: - registry.addHandler(ClusterJoinBody.class, new JoinHandler()); - registry.addHandler(ClusterLeaveBody.class, new LeaveHandler()); - registry.addHandler(ClusterSuspectBody.class, new SuspectHandler()); - registry.addHandler(ClusterMembershipBody.class, new MembershipHandler()); - registry.addHandler(ClusterPingBody.class, new PingHandler()); - registry.addHandler(ClusterSynchBody.class, new SynchHandler()); - - //connection.close override: - registry.addHandler(ConnectionCloseBody.class, new ConnectionCloseHandler()); - - //replicated handlers: - registry.addHandler(ExchangeDeclareBody.class, replicated(ExchangeDeclareHandler.getInstance())); - registry.addHandler(ExchangeDeleteBody.class, replicated(ExchangeDeleteHandler.getInstance())); - - ChannelQueueManager channelQueueMgr = new ChannelQueueManager(); - - - LocalQueueDeclareHandler handler = new LocalQueueDeclareHandler(_groupMgr); - registry.addHandler(QueueDeclareBody.class, - chain(new QueueNameGenerator(handler), - channelQueueMgr.createQueueDeclareHandler(), - new ReplicatingHandler<QueueDeclareBody>(_groupMgr, handler))); - - registry.addHandler(QueueBindBody.class, chain(channelQueueMgr.createQueueBindHandler(), replicated(QueueBindHandler.getInstance()))); - registry.addHandler(QueueDeleteBody.class, chain(channelQueueMgr.createQueueDeleteHandler(), replicated(alternate(new QueueDeleteHandler(false), new QueueDeleteHandler(true))))); - registry.addHandler(BasicConsumeBody.class, chain(channelQueueMgr.createBasicConsumeHandler(), new ReplicatingConsumeHandler(_groupMgr))); - - //other modified handlers: - registry.addHandler(BasicCancelBody.class, alternate(new RemoteCancelHandler(), BasicCancelMethodHandler.getInstance())); - - //other unaffected handlers: - registry.addHandler(BasicPublishBody.class, BasicPublishMethodHandler.getInstance()); - registry.addHandler(BasicQosBody.class, BasicQosHandler.getInstance()); - registry.addHandler(ChannelOpenBody.class, ChannelOpenHandler.getInstance()); - registry.addHandler(ChannelCloseBody.class, ChannelCloseHandler.getInstance()); - registry.addHandler(ChannelFlowBody.class, ChannelFlowHandler.getInstance()); - registry.addHandler(TxSelectBody.class, TxSelectHandler.getInstance()); - registry.addHandler(TxCommitBody.class, TxCommitHandler.getInstance()); - registry.addHandler(TxRollbackBody.class, TxRollbackHandler.getInstance()); - - - return registry; - } - - private class SynchHandler implements StateAwareMethodListener<ClusterSynchBody> - { - public void methodReceived(AMQStateManager stateManager, AMQMethodEvent<ClusterSynchBody> evt) throws AMQException - { - _groupMgr.handleSynch(ClusteredProtocolSession.getSessionPeer(stateManager.getProtocolSession())); - } - } - - private class JoinHandler implements StateAwareMethodListener<ClusterJoinBody> - { - public void methodReceived(AMQStateManager stateManager, AMQMethodEvent<ClusterJoinBody> evt) throws AMQException - { - _groupMgr.handleJoin(new SimpleMemberHandle(evt.getMethod().broker)); - } - } - - private class LeaveHandler implements StateAwareMethodListener<ClusterLeaveBody> - { - public void methodReceived(AMQStateManager stateManager, AMQMethodEvent<ClusterLeaveBody> evt) throws AMQException - { - _groupMgr.handleLeave(new SimpleMemberHandle(evt.getMethod().broker)); - } - } - - private class SuspectHandler implements StateAwareMethodListener<ClusterSuspectBody> - { - public void methodReceived(AMQStateManager stateManager, AMQMethodEvent<ClusterSuspectBody> evt) throws AMQException - { - _groupMgr.handleSuspect(new SimpleMemberHandle(evt.getMethod().broker)); - } - } - - private class MembershipHandler implements StateAwareMethodListener<ClusterMembershipBody> - { - public void methodReceived(AMQStateManager stateManager, AMQMethodEvent<ClusterMembershipBody> evt) throws AMQException - { - ClusterMembershipBody body = evt.getMethod(); - _groupMgr.handleMembershipAnnouncement(new String(body.members)); - } - } - - private class PingHandler implements StateAwareMethodListener<ClusterPingBody> - { - public void methodReceived(AMQStateManager stateManager, AMQMethodEvent<ClusterPingBody> evt) throws AMQException - { - MemberHandle peer = new SimpleMemberHandle(evt.getMethod().broker); - _groupMgr.handlePing(peer, evt.getMethod().load); - if (evt.getMethod().responseRequired) - { - evt.getMethod().load = _loadTable.getLocalLoad(); - stateManager.getProtocolSession().writeFrame(new AMQFrame(evt.getChannelId(), evt.getMethod())); - } - } - } - - private class ConnectionOpenHandler extends ExtendedHandler<ConnectionOpenBody> - { - ConnectionOpenHandler() - { - super(ConnectionOpenMethodHandler.getInstance()); - } - - void postHandle(AMQStateManager stateMgr, AMQMethodEvent<ConnectionOpenBody> evt) - { - AMQShortString capabilities = evt.getMethod().capabilities; - if (ClusterCapability.contains(capabilities)) - { - ClusteredProtocolSession.setSessionPeer(stateMgr.getProtocolSession(), ClusterCapability.getPeer(capabilities)); - } - else - { - _loadTable.incrementLocalLoad(); - } - } - } - - private class ConnectionCloseHandler extends ExtendedHandler<ConnectionCloseBody> - { - ConnectionCloseHandler() - { - super(ConnectionCloseMethodHandler.getInstance()); - } - - void postHandle(AMQStateManager stateMgr, AMQMethodEvent<ConnectionCloseBody> evt) - { - if (!ClusteredProtocolSession.isPeerSession(stateMgr.getProtocolSession())) - { - _loadTable.decrementLocalLoad(); - } - } - } - - private <B extends AMQMethodBody> ReplicatingHandler<B> replicated(StateAwareMethodListener<B> handler) - { - return new ReplicatingHandler<B>(_groupMgr, handler); - } - - private <B extends AMQMethodBody> StateAwareMethodListener<B> alternate(StateAwareMethodListener<B> peer, StateAwareMethodListener<B> client) - { - return new PeerHandler<B>(peer, client); - } - - private <B extends AMQMethodBody> StateAwareMethodListener<B> chain(ClusterMethodHandler<B>... h) - { - return new ChainedClusterMethodHandler<B>(h); - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/ExtendedHandler.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/ExtendedHandler.java deleted file mode 100644 index a2f62f714b..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/ExtendedHandler.java +++ /dev/null @@ -1,55 +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. - * - */ -package org.apache.qpid.server.cluster.handler; - -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.AMQMethodBody; -import org.apache.qpid.server.exchange.ExchangeRegistry; -import org.apache.qpid.protocol.AMQMethodEvent; -import org.apache.qpid.server.protocol.AMQProtocolSession; -import org.apache.qpid.server.queue.QueueRegistry; -import org.apache.qpid.server.state.AMQStateManager; -import org.apache.qpid.server.state.StateAwareMethodListener; - -class ExtendedHandler<A extends AMQMethodBody> implements StateAwareMethodListener<A> -{ - private final StateAwareMethodListener<A> _base; - - ExtendedHandler(StateAwareMethodListener<A> base) - { - _base = base; - } - - public void methodReceived(AMQStateManager stateMgr, AMQMethodEvent<A> evt) throws AMQException - { - preHandle(stateMgr, evt); - _base.methodReceived(stateMgr, evt); - postHandle(stateMgr, evt); - } - - void preHandle(AMQStateManager stateMgr, AMQMethodEvent<A> evt) throws AMQException - { - } - - void postHandle(AMQStateManager stateMgr, AMQMethodEvent<A> evt) throws AMQException - { - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/HandlerUtils.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/HandlerUtils.java deleted file mode 100644 index 0dc7fe00d2..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/HandlerUtils.java +++ /dev/null @@ -1,25 +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. - * - */ -package org.apache.qpid.server.cluster.handler; - -public abstract class HandlerUtils -{ -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/LocalQueueDeclareHandler.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/LocalQueueDeclareHandler.java deleted file mode 100644 index f01a8349f2..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/LocalQueueDeclareHandler.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -package org.apache.qpid.server.cluster.handler; - -import org.apache.log4j.Logger; -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.QueueDeclareBody; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.server.cluster.ClusteredProtocolSession; -import org.apache.qpid.server.cluster.GroupManager; -import org.apache.qpid.server.cluster.util.LogMessage; -import org.apache.qpid.server.cluster.MemberHandle; -import org.apache.qpid.server.handler.QueueDeclareHandler; -import org.apache.qpid.server.protocol.AMQProtocolSession; -import org.apache.qpid.server.queue.AMQQueue; -import org.apache.qpid.server.queue.ClusteredQueue; -import org.apache.qpid.server.queue.PrivateQueue; -import org.apache.qpid.server.queue.QueueRegistry; -import org.apache.qpid.server.queue.RemoteQueueProxy; -import org.apache.qpid.server.virtualhost.VirtualHost; - -public class LocalQueueDeclareHandler extends QueueDeclareHandler -{ - private static final Logger _logger = Logger.getLogger(LocalQueueDeclareHandler.class); - private final GroupManager _groupMgr; - - LocalQueueDeclareHandler(GroupManager groupMgr) - { - _groupMgr = groupMgr; - } - - protected AMQShortString createName() - { - return new AMQShortString(super.createName().toString() + "@" + _groupMgr.getLocal().getDetails()); - } - - protected AMQQueue createQueue(QueueDeclareBody body, VirtualHost virtualHost, AMQProtocolSession session) throws AMQException - { - //is it private or shared: - if (body.exclusive) - { - if (ClusteredProtocolSession.isPeerSession(session)) - { - //need to get peer from the session... - MemberHandle peer = ClusteredProtocolSession.getSessionPeer(session); - _logger.debug(new LogMessage("Creating proxied queue {0} on behalf of {1}", body.queue, peer)); - return new RemoteQueueProxy(peer, _groupMgr, body.queue, body.durable, new AMQShortString(peer.getDetails()), body.autoDelete, virtualHost); - } - else - { - _logger.debug(new LogMessage("Creating local private queue {0}", body.queue)); - return new PrivateQueue(_groupMgr, body.queue, body.durable, session.getContextKey(), body.autoDelete, virtualHost); - } - } - else - { - _logger.debug(new LogMessage("Creating local shared queue {0}", body.queue)); - return new ClusteredQueue(_groupMgr, body.queue, body.durable, null, body.autoDelete, virtualHost); - } - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/NullListener.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/NullListener.java deleted file mode 100644 index 8b0bb4b127..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/NullListener.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -package org.apache.qpid.server.cluster.handler; - -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.AMQMethodBody; -import org.apache.qpid.server.exchange.ExchangeRegistry; -import org.apache.qpid.protocol.AMQMethodEvent; -import org.apache.qpid.server.protocol.AMQProtocolSession; -import org.apache.qpid.server.queue.QueueRegistry; -import org.apache.qpid.server.state.AMQStateManager; -import org.apache.qpid.server.state.StateAwareMethodListener; - -public class NullListener<T extends AMQMethodBody> implements StateAwareMethodListener<T> -{ - public void methodReceived(AMQStateManager stateManager, AMQMethodEvent<T> evt) throws AMQException - { - } -} - diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/PeerHandler.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/PeerHandler.java deleted file mode 100644 index 447e51ccd9..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/PeerHandler.java +++ /dev/null @@ -1,60 +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. - * - */ -package org.apache.qpid.server.cluster.handler; - -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.AMQMethodBody; -import org.apache.qpid.server.cluster.ClusteredProtocolSession; -import org.apache.qpid.server.exchange.ExchangeRegistry; -import org.apache.qpid.protocol.AMQMethodEvent; -import org.apache.qpid.server.protocol.AMQProtocolSession; -import org.apache.qpid.server.queue.QueueRegistry; -import org.apache.qpid.server.state.AMQStateManager; -import org.apache.qpid.server.state.StateAwareMethodListener; - -/** - * Base for implementing handlers that carry out different actions based on whether the method they - * are handling was sent by a peer (i.e. another broker in the cluster) or a client (i.e. an end-user - * application). - * - */ -public class PeerHandler<A extends AMQMethodBody> extends ClusterMethodHandler<A> -{ - private final StateAwareMethodListener<A> _peer; - private final StateAwareMethodListener<A> _client; - - PeerHandler(StateAwareMethodListener<A> peer, StateAwareMethodListener<A> client) - { - _peer = peer; - _client = client; - } - - protected void peer(AMQStateManager stateMgr, AMQMethodEvent<A> evt) throws AMQException - { - _peer.methodReceived(stateMgr, evt); - } - - protected void client(AMQStateManager stateMgr, AMQMethodEvent<A> evt) throws AMQException - { - _client.methodReceived(stateMgr, evt); - } - -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/QueueNameGenerator.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/QueueNameGenerator.java deleted file mode 100644 index a669171d3c..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/QueueNameGenerator.java +++ /dev/null @@ -1,62 +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. - * - */ -package org.apache.qpid.server.cluster.handler; - -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.QueueDeclareBody; -import org.apache.qpid.server.exchange.ExchangeRegistry; -import org.apache.qpid.protocol.AMQMethodEvent; -import org.apache.qpid.server.protocol.AMQProtocolSession; -import org.apache.qpid.server.queue.QueueRegistry; -import org.apache.qpid.server.state.AMQStateManager; - -/** - * Generates queue names for queues declared with no name. - * - */ -class QueueNameGenerator extends ClusterMethodHandler<QueueDeclareBody> -{ - private final LocalQueueDeclareHandler _handler; - - QueueNameGenerator(LocalQueueDeclareHandler handler) - { - _handler = handler; - } - - protected void peer(AMQStateManager stateMgr, AMQMethodEvent<QueueDeclareBody> evt) throws AMQException - { - } - - protected void client(AMQStateManager stateMgr, AMQMethodEvent<QueueDeclareBody> evt) - throws AMQException - { - setName(evt.getMethod());//need to set the name before propagating this method - } - - protected void setName(QueueDeclareBody body) - { - if (body.queue == null) - { - body.queue = _handler.createName(); - } - } -} - diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/RemoteCancelHandler.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/RemoteCancelHandler.java deleted file mode 100644 index f09763e1ad..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/RemoteCancelHandler.java +++ /dev/null @@ -1,59 +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. - * - */ -package org.apache.qpid.server.cluster.handler; - -import org.apache.log4j.Logger; -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.BasicCancelBody; -import org.apache.qpid.server.cluster.ClusteredProtocolSession; -import org.apache.qpid.server.exchange.ExchangeRegistry; -import org.apache.qpid.protocol.AMQMethodEvent; -import org.apache.qpid.server.protocol.AMQProtocolSession; -import org.apache.qpid.server.queue.AMQQueue; -import org.apache.qpid.server.queue.ClusteredQueue; -import org.apache.qpid.server.queue.QueueRegistry; -import org.apache.qpid.server.state.AMQStateManager; -import org.apache.qpid.server.state.StateAwareMethodListener; -import org.apache.qpid.server.virtualhost.VirtualHost; - -public class RemoteCancelHandler implements StateAwareMethodListener<BasicCancelBody> -{ - private final Logger _logger = Logger.getLogger(RemoteCancelHandler.class); - - public void methodReceived(AMQStateManager stateManager, AMQMethodEvent<BasicCancelBody> evt) throws AMQException - { - AMQProtocolSession session = stateManager.getProtocolSession(); - VirtualHost virtualHost = session.getVirtualHost(); - QueueRegistry queueRegistry = virtualHost.getQueueRegistry(); - - - //By convention, consumers setup between brokers use the queue name as the consumer tag: - AMQQueue queue = queueRegistry.getQueue(evt.getMethod().consumerTag); - if (queue instanceof ClusteredQueue) - { - ((ClusteredQueue) queue).removeRemoteSubscriber(ClusteredProtocolSession.getSessionPeer(session)); - } - else - { - _logger.warn("Got remote cancel request for non-clustered queue: " + queue); - } - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/RemoteConsumeHandler.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/RemoteConsumeHandler.java deleted file mode 100644 index 073b13688c..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/RemoteConsumeHandler.java +++ /dev/null @@ -1,69 +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. - * - */ -package org.apache.qpid.server.cluster.handler; - -import org.apache.log4j.Logger; -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.BasicConsumeBody; -import org.apache.qpid.framing.BasicConsumeOkBody; -import org.apache.qpid.server.cluster.ClusteredProtocolSession; -import org.apache.qpid.server.exchange.ExchangeRegistry; -import org.apache.qpid.protocol.AMQMethodEvent; -import org.apache.qpid.server.protocol.AMQProtocolSession; -import org.apache.qpid.server.queue.AMQQueue; -import org.apache.qpid.server.queue.ClusteredQueue; -import org.apache.qpid.server.queue.QueueRegistry; -import org.apache.qpid.server.state.AMQStateManager; -import org.apache.qpid.server.state.StateAwareMethodListener; -import org.apache.qpid.server.virtualhost.VirtualHost; - -/** - * Handles consume requests from other cluster members. - * - */ -public class RemoteConsumeHandler implements StateAwareMethodListener<BasicConsumeBody> -{ - private final Logger _logger = Logger.getLogger(RemoteConsumeHandler.class); - - public void methodReceived(AMQStateManager stateManager, AMQMethodEvent<BasicConsumeBody> evt) throws AMQException - { - AMQProtocolSession session = stateManager.getProtocolSession(); - VirtualHost virtualHost = session.getVirtualHost(); - QueueRegistry queueRegistry = virtualHost.getQueueRegistry(); - - AMQQueue queue = queueRegistry.getQueue(evt.getMethod().queue); - if (queue instanceof ClusteredQueue) - { - ((ClusteredQueue) queue).addRemoteSubcriber(ClusteredProtocolSession.getSessionPeer(session)); - // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) - // TODO: Connect this to the session version obtained from ProtocolInitiation for this session. - // Be aware of possible changes to parameter order as versions change. - session.writeFrame(BasicConsumeOkBody.createAMQFrame(evt.getChannelId(), - (byte)8, (byte)0, // AMQP version (major, minor) - evt.getMethod().queue // consumerTag - )); - } - else - { - _logger.warn("Got remote consume request for non-clustered queue: " + queue); - } - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/ReplicatingConsumeHandler.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/ReplicatingConsumeHandler.java deleted file mode 100644 index 897f8e4fb7..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/ReplicatingConsumeHandler.java +++ /dev/null @@ -1,90 +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. - * - */ -package org.apache.qpid.server.cluster.handler; - -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.BasicConsumeBody; -import org.apache.qpid.server.cluster.BroadcastPolicy; -import org.apache.qpid.server.cluster.GroupManager; -import org.apache.qpid.server.cluster.util.LogMessage; -import org.apache.qpid.server.exchange.ExchangeRegistry; -import org.apache.qpid.server.handler.BasicConsumeMethodHandler; -import org.apache.qpid.protocol.AMQMethodEvent; -import org.apache.qpid.server.protocol.AMQProtocolSession; -import org.apache.qpid.server.queue.AMQQueue; -import org.apache.qpid.server.queue.QueueRegistry; -import org.apache.qpid.server.state.AMQStateManager; -import org.apache.qpid.server.state.StateAwareMethodListener; -import org.apache.qpid.server.virtualhost.VirtualHost; - -public class ReplicatingConsumeHandler extends ReplicatingHandler<BasicConsumeBody> -{ - ReplicatingConsumeHandler(GroupManager groupMgr) - { - this(groupMgr, null); - } - - ReplicatingConsumeHandler(GroupManager groupMgr, BroadcastPolicy policy) - { - super(groupMgr, base(), policy); - } - - protected void replicate(AMQStateManager stateManager, AMQMethodEvent<BasicConsumeBody> evt) throws AMQException - { - AMQProtocolSession session = stateManager.getProtocolSession(); - VirtualHost virtualHost = session.getVirtualHost(); - ExchangeRegistry exchangeRegistry = virtualHost.getExchangeRegistry(); - QueueRegistry queueRegistry = virtualHost.getQueueRegistry(); - - //only replicate if the queue in question is a shared queue - if (isShared(queueRegistry.getQueue(evt.getMethod().queue))) - { - super.replicate(stateManager, evt); - } - else - { - _logger.info(new LogMessage("Handling consume for private queue ({0}) locally", evt.getMethod())); - local(stateManager, evt); - _logger.info(new LogMessage("Handled consume for private queue ({0}) locally", evt.getMethod())); - - } - } - - protected boolean isShared(AMQQueue queue) - { - return queue != null && queue.isShared(); - } - - static StateAwareMethodListener<BasicConsumeBody> base() - { - return new PeerHandler<BasicConsumeBody>(peer(), client()); - } - - static StateAwareMethodListener<BasicConsumeBody> peer() - { - return new RemoteConsumeHandler(); - } - - static StateAwareMethodListener<BasicConsumeBody> client() - { - return BasicConsumeMethodHandler.getInstance(); - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/ReplicatingHandler.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/ReplicatingHandler.java deleted file mode 100644 index 888fa4e426..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/ReplicatingHandler.java +++ /dev/null @@ -1,125 +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. - * - */ -package org.apache.qpid.server.cluster.handler; - -import org.apache.log4j.Logger; -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.AMQMethodBody; -import org.apache.qpid.server.cluster.util.LogMessage; -import org.apache.qpid.server.cluster.*; -import org.apache.qpid.server.cluster.policy.StandardPolicies; -import org.apache.qpid.server.exchange.ExchangeRegistry; -import org.apache.qpid.protocol.AMQMethodEvent; -import org.apache.qpid.server.protocol.AMQProtocolSession; -import org.apache.qpid.server.queue.QueueRegistry; -import org.apache.qpid.server.state.AMQStateManager; -import org.apache.qpid.server.state.StateAwareMethodListener; -import org.apache.qpid.server.virtualhost.VirtualHost; - -import java.util.List; - -/** - * Basic template for handling methods that should be broadcast to the group and - * processed locally after 'completion' of this broadcast. - * - */ -class ReplicatingHandler<A extends AMQMethodBody> extends ClusterMethodHandler<A> implements StandardPolicies -{ - protected static final Logger _logger = Logger.getLogger(ReplicatingHandler.class); - - private final StateAwareMethodListener<A> _base; - private final GroupManager _groupMgr; - private final BroadcastPolicy _policy; - - ReplicatingHandler(GroupManager groupMgr, StateAwareMethodListener<A> base) - { - this(groupMgr, base, null); - } - - ReplicatingHandler(GroupManager groupMgr, StateAwareMethodListener<A> base, BroadcastPolicy policy) - { - _groupMgr = groupMgr; - _base = base; - _policy = policy; - } - - protected void peer(AMQStateManager stateManager, AMQMethodEvent<A> evt) throws AMQException - { - AMQProtocolSession session = stateManager.getProtocolSession(); - VirtualHost virtualHost = session.getVirtualHost(); - ExchangeRegistry exchangeRegistry = virtualHost.getExchangeRegistry(); - QueueRegistry queueRegistry = virtualHost.getQueueRegistry(); - - local(stateManager, evt); - _logger.debug(new LogMessage("Handled {0} locally", evt.getMethod())); - } - - protected void client(AMQStateManager stateMgr, AMQMethodEvent<A> evt) throws AMQException - { - replicate(stateMgr, evt); - } - - protected void replicate(AMQStateManager stateMgr, AMQMethodEvent<A> evt) throws AMQException - { - if (_policy == null) - { - //asynch delivery - _groupMgr.broadcast(new SimpleBodySendable(evt.getMethod())); - local(stateMgr, evt); - } - else - { - Callback callback = new Callback(stateMgr, evt); - _groupMgr.broadcast(new SimpleBodySendable(evt.getMethod()), _policy, callback); - } - _logger.debug(new LogMessage("Replicated {0} to peers", evt.getMethod())); - } - - protected void local(AMQStateManager stateMgr, AMQMethodEvent<A> evt) throws AMQException - { - _base.methodReceived(stateMgr, evt); - } - - private class Callback implements GroupResponseHandler - { - private final AMQStateManager _stateMgr; - private final AMQMethodEvent<A> _evt; - - Callback(AMQStateManager stateMgr, AMQMethodEvent<A> evt) - { - _stateMgr = stateMgr; - _evt = evt; - } - - public void response(List<AMQMethodBody> responses, List<Member> members) - { - try - { - local(_stateMgr, _evt); - _logger.debug(new LogMessage("Handled {0} locally, in response to completion of replication", _evt.getMethod())); - } - catch (AMQException e) - { - _logger.error(new LogMessage("Error handling {0}:{1}", _evt.getMethod(), e), e); - } - } - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/WrappedListener.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/WrappedListener.java deleted file mode 100644 index 8b0c638d63..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/WrappedListener.java +++ /dev/null @@ -1,56 +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. - * - */ -package org.apache.qpid.server.cluster.handler; - -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.AMQMethodBody; -import org.apache.qpid.server.exchange.ExchangeRegistry; -import org.apache.qpid.protocol.AMQMethodEvent; -import org.apache.qpid.server.protocol.AMQProtocolSession; -import org.apache.qpid.server.queue.QueueRegistry; -import org.apache.qpid.server.state.AMQStateManager; -import org.apache.qpid.server.state.StateAwareMethodListener; - -public class WrappedListener<T extends AMQMethodBody> implements StateAwareMethodListener<T> -{ - private final StateAwareMethodListener<T> _primary; - private final StateAwareMethodListener _post; - private final StateAwareMethodListener _pre; - - WrappedListener(StateAwareMethodListener<T> primary, StateAwareMethodListener pre, StateAwareMethodListener post) - { - _pre = check(pre); - _post = check(post); - _primary = check(primary); - } - - public void methodReceived(AMQStateManager stateMgr, AMQMethodEvent<T> evt) throws AMQException - { - _pre.methodReceived(stateMgr, evt); - _primary.methodReceived(stateMgr, evt); - _post.methodReceived(stateMgr, evt); - } - - private static <T extends AMQMethodBody> StateAwareMethodListener<T> check(StateAwareMethodListener<T> in) - { - return in == null ? new NullListener<T>() : in; - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/WrappingMethodHandlerFactory.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/WrappingMethodHandlerFactory.java deleted file mode 100644 index 5ec3c9660a..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/handler/WrappingMethodHandlerFactory.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -package org.apache.qpid.server.cluster.handler; - -import org.apache.qpid.framing.AMQMethodBody; -import org.apache.qpid.server.cluster.MethodHandlerFactory; -import org.apache.qpid.server.cluster.MethodHandlerRegistry; -import org.apache.qpid.server.state.AMQState; -import org.apache.qpid.server.state.StateAwareMethodListener; - -public abstract class WrappingMethodHandlerFactory implements MethodHandlerFactory -{ - private final MethodHandlerFactory _delegate; - private final StateAwareMethodListener _pre; - private final StateAwareMethodListener _post; - - protected WrappingMethodHandlerFactory(MethodHandlerFactory delegate, - StateAwareMethodListener pre, - StateAwareMethodListener post) - { - _delegate = delegate; - _pre = pre; - _post = post; - } - - public MethodHandlerRegistry register(AMQState state, MethodHandlerRegistry registry) - { - if (isWrappableState(state)) - { - return wrap(_delegate.register(state, registry), state); - } - else - { - return _delegate.register(state, registry); - } - } - - protected abstract boolean isWrappableState(AMQState state); - - protected abstract Iterable<FrameDescriptor> getWrappableFrameTypes(AMQState state); - - private MethodHandlerRegistry wrap(MethodHandlerRegistry registry, AMQState state) - { - for (FrameDescriptor fd : getWrappableFrameTypes(state)) - { - wrap(registry, fd.type, fd.instance); - } - return registry; - } - - private <A extends AMQMethodBody, B extends Class<A>> void wrap(MethodHandlerRegistry r, B type, A frame) - { - r.addHandler(type, new WrappedListener<A>(r.getHandler(frame), _pre, _post)); - } - - protected static class FrameDescriptor<A extends AMQMethodBody, B extends Class<A>> - { - protected final A instance; - protected final B type; - - public FrameDescriptor(B type, A instance) - { - this.instance = instance; - this.type = type; - } - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/policy/AsynchBroadcastPolicy.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/policy/AsynchBroadcastPolicy.java deleted file mode 100644 index 79cb558ede..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/policy/AsynchBroadcastPolicy.java +++ /dev/null @@ -1,31 +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. - * - */ -package org.apache.qpid.server.cluster.policy; - -import org.apache.qpid.server.cluster.BroadcastPolicy; - -public class AsynchBroadcastPolicy implements BroadcastPolicy -{ - public boolean isComplete(int responded, int members) - { - return true; - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/policy/MajorityResponseBroadcastPolicy.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/policy/MajorityResponseBroadcastPolicy.java deleted file mode 100644 index 42382c6e7a..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/policy/MajorityResponseBroadcastPolicy.java +++ /dev/null @@ -1,31 +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. - * - */ -package org.apache.qpid.server.cluster.policy; - -import org.apache.qpid.server.cluster.BroadcastPolicy; - -public class MajorityResponseBroadcastPolicy implements BroadcastPolicy -{ - public boolean isComplete(int responded, int members) - { - return responded > members / 2; - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/policy/OneResponseBroadcastPolicy.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/policy/OneResponseBroadcastPolicy.java deleted file mode 100644 index e3072a6a40..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/policy/OneResponseBroadcastPolicy.java +++ /dev/null @@ -1,31 +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. - * - */ -package org.apache.qpid.server.cluster.policy; - -import org.apache.qpid.server.cluster.BroadcastPolicy; - -public class OneResponseBroadcastPolicy implements BroadcastPolicy -{ - public boolean isComplete(int responded, int members) - { - return responded > 0; - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/policy/StandardPolicies.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/policy/StandardPolicies.java deleted file mode 100644 index dbaf690d3a..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/policy/StandardPolicies.java +++ /dev/null @@ -1,29 +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. - * - */ -package org.apache.qpid.server.cluster.policy; - -import org.apache.qpid.server.cluster.BroadcastPolicy; - -public interface StandardPolicies -{ - public static final BroadcastPolicy ASYNCH_POLICY = new AsynchBroadcastPolicy(); - public static final BroadcastPolicy SYNCH_POLICY = new SynchBroadcastPolicy(); -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/policy/SynchBroadcastPolicy.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/policy/SynchBroadcastPolicy.java deleted file mode 100644 index 605b8dd51e..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/policy/SynchBroadcastPolicy.java +++ /dev/null @@ -1,31 +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. - * - */ -package org.apache.qpid.server.cluster.policy; - -import org.apache.qpid.server.cluster.BroadcastPolicy; - -public class SynchBroadcastPolicy implements BroadcastPolicy -{ - public boolean isComplete(int responded, int members) - { - return responded == members; - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/replay/ChainedMethodRecorder.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/replay/ChainedMethodRecorder.java deleted file mode 100644 index 3664be58bc..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/replay/ChainedMethodRecorder.java +++ /dev/null @@ -1,48 +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. - * - */ -package org.apache.qpid.server.cluster.replay; - -import org.apache.qpid.framing.AMQMethodBody; - -abstract class ChainedMethodRecorder <T extends AMQMethodBody> implements MethodRecorder<T> -{ - private final MethodRecorder<T> _recorder; - - ChainedMethodRecorder() - { - this(null); - } - - ChainedMethodRecorder(MethodRecorder<T> recorder) - { - _recorder = recorder; - } - - public final void record(T method) - { - if(!doRecord(method) && _recorder != null) - { - _recorder.record(method); - } - } - - protected abstract boolean doRecord(T method); -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/replay/ConsumerCounts.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/replay/ConsumerCounts.java deleted file mode 100644 index 5a433b869b..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/replay/ConsumerCounts.java +++ /dev/null @@ -1,83 +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. - * - */ -package org.apache.qpid.server.cluster.replay; - -import org.apache.qpid.framing.AMQMethodBody; -import org.apache.qpid.framing.BasicConsumeBody; -import org.apache.qpid.framing.AMQShortString; - -import java.util.Map; -import java.util.HashMap; -import java.util.List; - -class ConsumerCounts -{ - private final Map<AMQShortString, Integer> _counts = new HashMap<AMQShortString, Integer>(); - - synchronized void increment(AMQShortString queue) - { - _counts.put(queue, get(queue) + 1); - } - - synchronized void decrement(AMQShortString queue) - { - _counts.put(queue, get(queue) - 1); - } - - private int get(AMQShortString queue) - { - Integer count = _counts.get(queue); - return count == null ? 0 : count; - } - - synchronized void replay(List<AMQMethodBody> messages) - { - for(AMQShortString queue : _counts.keySet()) - { - // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) - // TODO: Connect this to the session version obtained from ProtocolInitiation for this session. - BasicConsumeBody m = new BasicConsumeBody((byte)8, - (byte)0, - BasicConsumeBody.getClazz((byte)8, (byte)0), - BasicConsumeBody.getMethod((byte)8, (byte)0), - null, - queue, - false, - false, - false, - false, - queue, - 0); - m.queue = queue; - m.consumerTag = queue; - replay(m, messages); - } - } - - private void replay(BasicConsumeBody msg, List<AMQMethodBody> messages) - { - int count = _counts.get(msg.queue); - for(int i = 0; i < count; i++) - { - messages.add(msg); - } - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/replay/RecordingMethodHandlerFactory.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/replay/RecordingMethodHandlerFactory.java deleted file mode 100644 index 4d3fe1dbed..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/replay/RecordingMethodHandlerFactory.java +++ /dev/null @@ -1,77 +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. - * - */ -package org.apache.qpid.server.cluster.replay; - -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.AMQMethodBody; -import org.apache.qpid.framing.BasicCancelBody; -import org.apache.qpid.framing.BasicConsumeBody; -import org.apache.qpid.framing.ExchangeDeclareBody; -import org.apache.qpid.framing.ExchangeDeleteBody; -import org.apache.qpid.framing.QueueBindBody; -import org.apache.qpid.framing.QueueDeclareBody; -import org.apache.qpid.framing.QueueDeleteBody; -import org.apache.qpid.server.cluster.MethodHandlerFactory; -import org.apache.qpid.server.cluster.MethodHandlerRegistry; -import org.apache.qpid.server.cluster.handler.WrappingMethodHandlerFactory; -import org.apache.qpid.server.exchange.ExchangeRegistry; -import org.apache.qpid.protocol.AMQMethodEvent; -import org.apache.qpid.server.protocol.AMQProtocolSession; -import org.apache.qpid.server.queue.QueueRegistry; -import org.apache.qpid.server.state.AMQState; -import org.apache.qpid.server.state.AMQStateManager; -import org.apache.qpid.server.state.StateAwareMethodListener; - -import java.util.Arrays; - -public class RecordingMethodHandlerFactory extends WrappingMethodHandlerFactory -{ - // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) - // TODO: Connect this to the session version obtained from ProtocolInitiation for this session. - private final byte major = (byte)8; - private final byte minor = (byte)0; - private final Iterable<FrameDescriptor> _frames = Arrays.asList(new FrameDescriptor[] - { - new FrameDescriptor(QueueDeclareBody.class, new QueueDeclareBody(major, minor, QueueDeclareBody.getClazz(major, minor), QueueDeclareBody.getMethod(major, minor),null,false,false,false,false,false,null,0)), - new FrameDescriptor(QueueDeleteBody.class, new QueueDeleteBody(major, minor, QueueDeleteBody.getClazz(major, minor), QueueDeleteBody.getMethod(major, minor),false,false,false,null,0)), - new FrameDescriptor(QueueBindBody.class, new QueueBindBody(major, minor, QueueBindBody.getClazz(major, minor), QueueBindBody.getMethod(major, minor),null,null,false,null,null,0)), - new FrameDescriptor(ExchangeDeclareBody.class, new ExchangeDeclareBody(major, minor, ExchangeDeclareBody.getClazz(major, minor), ExchangeDeclareBody.getMethod(major, minor),null,false,false,null,false,false,false,0,null)), - new FrameDescriptor(ExchangeDeleteBody.class, new ExchangeDeleteBody(major, minor, ExchangeDeleteBody.getClazz(major, minor), ExchangeDeleteBody.getMethod(major, minor),null,false,false,0)), - new FrameDescriptor(BasicConsumeBody.class, new BasicConsumeBody(major, minor, BasicConsumeBody.getClazz(major, minor), BasicConsumeBody.getMethod(major, minor),null,null,false,false,false,false,null,0)), - new FrameDescriptor(BasicCancelBody.class, new BasicCancelBody(major, minor, BasicCancelBody.getClazz(major, minor), BasicCancelBody.getMethod(major, minor),null,false)) - }); - - - public RecordingMethodHandlerFactory(MethodHandlerFactory factory, ReplayStore store) - { - super(factory, null, store); - } - - protected boolean isWrappableState(AMQState state) - { - return AMQState.CONNECTION_OPEN.equals(state); - } - - protected Iterable<FrameDescriptor> getWrappableFrameTypes(AMQState state) - { - return _frames; - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/replay/ReplayManager.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/replay/ReplayManager.java deleted file mode 100644 index 898cb80cb3..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/replay/ReplayManager.java +++ /dev/null @@ -1,37 +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. - * - */ -package org.apache.qpid.server.cluster.replay; - -import org.apache.qpid.server.cluster.Sendable; -import org.apache.qpid.framing.AMQDataBlock; -import org.apache.qpid.framing.AMQMethodBody; - -import java.util.List; - -/** - * Abstraction of a replay strategy for use in getting joining members up to - * date with respect to cluster state. - * - */ -public interface ReplayManager -{ - public List<AMQMethodBody> replay(boolean isLeader); -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/replay/ReplayStore.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/replay/ReplayStore.java deleted file mode 100644 index d7bbb1c36b..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/replay/ReplayStore.java +++ /dev/null @@ -1,311 +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. - * - */ -package org.apache.qpid.server.cluster.replay; - -import org.apache.log4j.Logger; -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.*; -import org.apache.qpid.server.cluster.ClusteredProtocolSession; -import org.apache.qpid.server.cluster.util.LogMessage; -import org.apache.qpid.server.cluster.util.Bindings; -import org.apache.qpid.server.exchange.ExchangeRegistry; -import org.apache.qpid.protocol.AMQMethodEvent; -import org.apache.qpid.server.protocol.AMQProtocolSession; -import org.apache.qpid.server.queue.QueueRegistry; -import org.apache.qpid.server.state.AMQStateManager; -import org.apache.qpid.server.state.StateAwareMethodListener; -import org.apache.qpid.server.virtualhost.VirtualHost; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Stores method invocations for replay to new members. - * - */ -public class ReplayStore implements ReplayManager, StateAwareMethodListener -{ - private static final Logger _logger = Logger.getLogger(ReplayStore.class); - - private final Map<Class<? extends AMQMethodBody>, MethodRecorder> _globalRecorders = new HashMap<Class<? extends AMQMethodBody>, MethodRecorder>(); - private final Map<Class<? extends AMQMethodBody>, MethodRecorder> _localRecorders = new HashMap<Class<? extends AMQMethodBody>, MethodRecorder>(); - private final Map<AMQShortString, QueueDeclareBody> _sharedQueues = new ConcurrentHashMap<AMQShortString, QueueDeclareBody>(); - private final Map<AMQShortString, QueueDeclareBody> _privateQueues = new ConcurrentHashMap<AMQShortString, QueueDeclareBody>(); - private final Bindings<AMQShortString, AMQShortString, QueueBindBody> _sharedBindings = new Bindings<AMQShortString, AMQShortString, QueueBindBody>(); - private final Bindings<AMQShortString, AMQShortString, QueueBindBody> _privateBindings = new Bindings<AMQShortString, AMQShortString, QueueBindBody>(); - private final Map<AMQShortString, ExchangeDeclareBody> _exchanges = new ConcurrentHashMap<AMQShortString, ExchangeDeclareBody>(); - private final ConsumerCounts _consumers = new ConsumerCounts(); - - public ReplayStore() - { - _globalRecorders.put(QueueDeclareBody.class, new SharedQueueDeclareRecorder()); - _globalRecorders.put(QueueDeleteBody.class, new SharedQueueDeleteRecorder()); - _globalRecorders.put(QueueBindBody.class, new SharedQueueBindRecorder()); - _globalRecorders.put(ExchangeDeclareBody.class, new ExchangeDeclareRecorder()); - _globalRecorders.put(ExchangeDeleteBody.class, new ExchangeDeleteRecorder()); - - _localRecorders.put(QueueDeclareBody.class, new PrivateQueueDeclareRecorder()); - _localRecorders.put(QueueDeleteBody.class, new PrivateQueueDeleteRecorder()); - _localRecorders.put(QueueBindBody.class, new PrivateQueueBindRecorder()); - _localRecorders.put(BasicConsumeBody.class, new BasicConsumeRecorder()); - _localRecorders.put(BasicCancelBody.class, new BasicCancelRecorder()); - _localRecorders.put(ExchangeDeclareBody.class, new ExchangeDeclareRecorder()); - _localRecorders.put(ExchangeDeleteBody.class, new ExchangeDeleteRecorder()); - } - - public void methodReceived(AMQStateManager stateManager, AMQMethodEvent evt) throws AMQException - { - AMQProtocolSession session = stateManager.getProtocolSession(); - VirtualHost virtualHost = session.getVirtualHost(); - - _logger.debug(new LogMessage("Replay store received {0}", evt.getMethod())); - AMQMethodBody request = evt.getMethod(); - - //allow any (relevant) recorder registered for this type of request to record it: - MethodRecorder recorder = getRecorders(session).get(request.getClass()); - if (recorder != null) - { - recorder.record(request); - } - } - - private Map<Class<? extends AMQMethodBody>, MethodRecorder> getRecorders(AMQProtocolSession session) - { - if (ClusteredProtocolSession.isPeerSession(session)) - { - return _globalRecorders; - } - else - { - return _localRecorders; - } - } - - public List<AMQMethodBody> replay(boolean isLeader) - { - List<AMQMethodBody> methods = new ArrayList<AMQMethodBody>(); - methods.addAll(_exchanges.values()); - methods.addAll(_privateQueues.values()); - synchronized(_privateBindings) - { - methods.addAll(_privateBindings.values()); - } - if (isLeader) - { - methods.addAll(_sharedQueues.values()); - synchronized(_sharedBindings) - { - methods.addAll(_sharedBindings.values()); - } - } - _consumers.replay(methods); - // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) - // TODO: Connect this to the session version obtained from ProtocolInitiation for this session. - methods.add(new ClusterSynchBody((byte)8, (byte)0, ClusterSynchBody.getClazz((byte)8, (byte)0), ClusterSynchBody.getMethod((byte)8, (byte)0))); - return methods; - } - - private class BasicConsumeRecorder implements MethodRecorder<BasicConsumeBody> - { - public void record(BasicConsumeBody method) - { - if(_sharedQueues.containsKey(method.queue)) - { - _consumers.increment(method.queue); - } - } - } - - private class BasicCancelRecorder implements MethodRecorder<BasicCancelBody> - { - public void record(BasicCancelBody method) - { - if(_sharedQueues.containsKey(method.consumerTag)) - { - _consumers.decrement(method.consumerTag); - } - } - } - - private class SharedQueueDeclareRecorder extends QueueDeclareRecorder - { - SharedQueueDeclareRecorder() - { - super(false, _sharedQueues); - } - } - - private class PrivateQueueDeclareRecorder extends QueueDeclareRecorder - { - PrivateQueueDeclareRecorder() - { - super(true, _privateQueues, new SharedQueueDeclareRecorder()); - } - } - - private class SharedQueueDeleteRecorder extends QueueDeleteRecorder - { - SharedQueueDeleteRecorder() - { - super(_sharedQueues, _sharedBindings); - } - } - - private class PrivateQueueDeleteRecorder extends QueueDeleteRecorder - { - PrivateQueueDeleteRecorder() - { - super(_privateQueues, _privateBindings, new SharedQueueDeleteRecorder()); - } - } - - private class SharedQueueBindRecorder extends QueueBindRecorder - { - SharedQueueBindRecorder() - { - super(_sharedQueues, _sharedBindings); - } - } - - private class PrivateQueueBindRecorder extends QueueBindRecorder - { - PrivateQueueBindRecorder() - { - super(_privateQueues, _privateBindings, new SharedQueueBindRecorder()); - } - } - - - private static class QueueDeclareRecorder extends ChainedMethodRecorder<QueueDeclareBody> - { - private final boolean _exclusive; - private final Map<AMQShortString, QueueDeclareBody> _queues; - - QueueDeclareRecorder(boolean exclusive, Map<AMQShortString, QueueDeclareBody> queues) - { - _queues = queues; - _exclusive = exclusive; - } - - QueueDeclareRecorder(boolean exclusive, Map<AMQShortString, QueueDeclareBody> queues, QueueDeclareRecorder recorder) - { - super(recorder); - _queues = queues; - _exclusive = exclusive; - } - - - protected boolean doRecord(QueueDeclareBody method) - { - if (_exclusive == method.exclusive) - { - _queues.put(method.queue, method); - return true; - } - else - { - return false; - } - } - } - - private class QueueDeleteRecorder extends ChainedMethodRecorder<QueueDeleteBody> - { - private final Map<AMQShortString, QueueDeclareBody> _queues; - private final Bindings<AMQShortString, AMQShortString, QueueBindBody> _bindings; - - QueueDeleteRecorder(Map<AMQShortString, QueueDeclareBody> queues, Bindings<AMQShortString, AMQShortString, QueueBindBody> bindings) - { - this(queues, bindings, null); - } - - QueueDeleteRecorder(Map<AMQShortString, QueueDeclareBody> queues, Bindings<AMQShortString, AMQShortString, QueueBindBody> bindings, QueueDeleteRecorder recorder) - { - super(recorder); - _queues = queues; - _bindings = bindings; - } - - protected boolean doRecord(QueueDeleteBody method) - { - if (_queues.remove(method.queue) != null) - { - _bindings.unbind1(method.queue); - return true; - } - else - { - return false; - } - } - } - - private class QueueBindRecorder extends ChainedMethodRecorder<QueueBindBody> - { - private final Map<AMQShortString, QueueDeclareBody> _queues; - private final Bindings<AMQShortString, AMQShortString, QueueBindBody> _bindings; - - QueueBindRecorder(Map<AMQShortString, QueueDeclareBody> queues, Bindings<AMQShortString, AMQShortString, QueueBindBody> bindings) - { - _queues = queues; - _bindings = bindings; - } - - QueueBindRecorder(Map<AMQShortString, QueueDeclareBody> queues, Bindings<AMQShortString, AMQShortString, QueueBindBody> bindings, QueueBindRecorder recorder) - { - super(recorder); - _queues = queues; - _bindings = bindings; - } - - protected boolean doRecord(QueueBindBody method) - { - if (_queues.containsKey(method.queue)) - { - _bindings.bind(method.queue, method.exchange, method); - return true; - } - else - { - return false; - } - } - } - - private class ExchangeDeclareRecorder implements MethodRecorder<ExchangeDeclareBody> - { - public void record(ExchangeDeclareBody method) - { - _exchanges.put(method.exchange, method); - } - } - - private class ExchangeDeleteRecorder implements MethodRecorder<ExchangeDeleteBody> - { - public void record(ExchangeDeleteBody method) - { - _exchanges.remove(method.exchange); - } - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/util/Bindings.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/util/Bindings.java deleted file mode 100644 index 49de0a7cbf..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/util/Bindings.java +++ /dev/null @@ -1,83 +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. - * - */ -package org.apache.qpid.server.cluster.util; - -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; - -/** - * Maps two separate keys to a list of values. - * - */ -public class Bindings<K1, K2, V> -{ - private final MultiValuedMap<K1, Binding<K2>> _a = new MultiValuedMap<K1, Binding<K2>>(); - private final MultiValuedMap<K2, Binding<K1>> _b = new MultiValuedMap<K2, Binding<K1>>(); - private final Collection<V> _values = new HashSet<V>(); - - public void bind(K1 key1, K2 key2, V value) - { - _a.add(key1, new Binding<K2>(key2, value)); - _b.add(key2, new Binding<K1>(key1, value)); - _values.add(value); - } - - public void unbind1(K1 key1) - { - Collection<Binding<K2>> values = _a.remove(key1); - for (Binding<K2> v : values) - { - _b.remove(v.key); - _values.remove(v.value); - } - } - - public void unbind2(K2 key2) - { - Collection<Binding<K1>> values = _b.remove(key2); - for (Binding<K1> v : values) - { - _a.remove(v.key); - _values.remove(v.value); - } - } - - public Collection<V> values() - { - return Collections.unmodifiableCollection(_values); - } - - /** - * Value needs to hold key to the other map - */ - private class Binding<T> - { - private final T key; - private final V value; - - Binding(T key, V value) - { - this.key = key; - this.value = value; - } - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/util/InvokeMultiple.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/util/InvokeMultiple.java deleted file mode 100644 index 406fe45701..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/util/InvokeMultiple.java +++ /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. - * - */ -package org.apache.qpid.server.cluster.util; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.Set; -import java.util.HashSet; - -/** - * Allows a method to be invoked on a list of listeners with one call - * - */ -public class InvokeMultiple <T> implements InvocationHandler -{ - private final Set<T> _targets = new HashSet<T>(); - private final T _proxy; - - public InvokeMultiple(Class<? extends T> type) - { - _proxy = (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, this); - } - - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable - { - Set<T> targets; - synchronized(this) - { - targets = new HashSet<T>(_targets); - } - - for(T target : targets) - { - method.invoke(target, args); - } - return null; - } - - public synchronized void addListener(T t) - { - _targets.add(t); - } - - public synchronized void removeListener(T t) - { - _targets.remove(t); - } - - public T getProxy() - { - return _proxy; - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/util/LogMessage.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/util/LogMessage.java deleted file mode 100644 index 9be90298ea..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/util/LogMessage.java +++ /dev/null @@ -1,53 +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. - * - */ -package org.apache.qpid.server.cluster.util; - -import java.text.MessageFormat; - -/** - * Convenience class to allow log messages to be specified in terms - * of MessageFormat patterns with a variable set of parameters. The - * production of the string is only done if toSTring is called so it - * works well with debug level messages, allowing complex messages - * to be specified that are only evaluated if actually printed. - * - */ -public class LogMessage -{ - private final String _message; - private final Object[] _args; - - public LogMessage(String message) - { - this(message, new Object[0]); - } - - public LogMessage(String message, Object... args) - { - _message = message; - _args = args; - } - - public String toString() - { - return MessageFormat.format(_message, _args); - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/util/MultiValuedMap.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/util/MultiValuedMap.java deleted file mode 100644 index ebe1fe47dd..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/util/MultiValuedMap.java +++ /dev/null @@ -1,61 +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. - * - */ -package org.apache.qpid.server.cluster.util; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -/** - * Maps a key to a collection of values - * - */ -public class MultiValuedMap<K, V> -{ - private Map<K, Collection<V>> _map = new HashMap<K, Collection<V>>(); - - public boolean add(K key, V value) - { - Collection<V> values = get(key); - if (values == null) - { - values = createList(); - _map.put(key, values); - } - return values.add(value); - } - - public Collection<V> get(K key) - { - return _map.get(key); - } - - public Collection<V> remove(K key) - { - return _map.remove(key); - } - - protected Collection<V> createList() - { - return new ArrayList<V>(); - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/queue/ClusteredQueue.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/queue/ClusteredQueue.java deleted file mode 100644 index 9fa96ece1e..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/queue/ClusteredQueue.java +++ /dev/null @@ -1,175 +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. - * - */ -package org.apache.qpid.server.queue; - -import org.apache.log4j.Logger; -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.BasicCancelBody; -import org.apache.qpid.framing.QueueDeleteBody; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.server.cluster.*; -import org.apache.qpid.server.cluster.util.LogMessage; -import org.apache.qpid.server.protocol.AMQProtocolSession; -import org.apache.qpid.server.store.StoreContext; -import org.apache.qpid.server.virtualhost.VirtualHost; - -import java.util.concurrent.ConcurrentHashMap; - -/** - * Represents a shared queue in a cluster. The key difference is that as well as any - * local consumers, there may be consumers for this queue on other members of the - * cluster. - * - */ -public class ClusteredQueue extends AMQQueue -{ - private static final Logger _logger = Logger.getLogger(ClusteredQueue.class); - private final ConcurrentHashMap<SimpleMemberHandle, RemoteSubscriptionImpl> _peers = new ConcurrentHashMap<SimpleMemberHandle, RemoteSubscriptionImpl>(); - private final GroupManager _groupMgr; - private final NestedSubscriptionManager _subscriptions; - - public ClusteredQueue(GroupManager groupMgr, AMQShortString name, boolean durable, AMQShortString owner, boolean autoDelete, VirtualHost virtualHost) - throws AMQException - { - super(name, durable, owner, autoDelete, virtualHost, new ClusteredSubscriptionManager()); - _groupMgr = groupMgr; - _subscriptions = ((ClusteredSubscriptionManager) getSubscribers()).getAllSubscribers(); - } - - - public void process(StoreContext storeContext, AMQMessage msg, boolean deliverFirst) throws AMQException - { - _logger.info(new LogMessage("{0} delivered to clustered queue {1}", msg, this)); - super.process(storeContext, msg, deliverFirst); - } - - protected void autodelete() throws AMQException - { - if(!_subscriptions.hasActiveSubscribers()) - { - //delete locally: - delete(); - - //send deletion request to all other members: - // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) - // TODO: Connect this to the session version obtained from ProtocolInitiation for this session. - QueueDeleteBody request = new QueueDeleteBody((byte)8, - (byte)0, - QueueDeleteBody.getClazz((byte)8,(byte)0), - QueueDeleteBody.getMethod((byte)8,(byte)0), - false, - false, - false, - getName(), - 0); - - _groupMgr.broadcast(new SimpleBodySendable(request)); - } - } - - public void unregisterProtocolSession(AMQProtocolSession ps, int channel, AMQShortString consumerTag) throws AMQException - { - //handle locally: - super.unregisterProtocolSession(ps, channel, consumerTag); - - //signal other members: - // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) - // TODO: Connect this to the session version obtained from ProtocolInitiation for this session. - BasicCancelBody request = new BasicCancelBody((byte)8, - (byte)0, - BasicCancelBody.getClazz((byte)8, (byte)0), - BasicCancelBody.getMethod((byte)8, (byte)0), - getName(), - false); - - _groupMgr.broadcast(new SimpleBodySendable(request)); - } - - public void addRemoteSubcriber(MemberHandle peer) - { - _logger.info(new LogMessage("Added remote subscriber for {0} to clustered queue {1}", peer, this)); - //find (or create) a matching subscriber for the peer then increment the count - getSubscriber(key(peer), true).increment(); - } - - public void removeRemoteSubscriber(MemberHandle peer) - { - //find a matching subscriber for the peer then decrement the count - //if count is now zero, remove the subscriber - SimpleMemberHandle key = key(peer); - RemoteSubscriptionImpl s = getSubscriber(key, true); - if (s == null) - { - throw new RuntimeException("No subscriber for " + peer); - } - if (s.decrement()) - { - _peers.remove(key); - _subscriptions.removeSubscription(s); - } - } - - public void removeAllRemoteSubscriber(MemberHandle peer) - { - SimpleMemberHandle key = key(peer); - RemoteSubscriptionImpl s = getSubscriber(key, true); - _peers.remove(key); - _subscriptions.removeSubscription(s); - } - - private RemoteSubscriptionImpl getSubscriber(SimpleMemberHandle key, boolean create) - { - RemoteSubscriptionImpl s = _peers.get(key); - if (s == null && create) - { - return addSubscriber(key, new RemoteSubscriptionImpl(_groupMgr, key)); - } - else - { - return s; - } - } - - private RemoteSubscriptionImpl addSubscriber(SimpleMemberHandle key, RemoteSubscriptionImpl s) - { - RemoteSubscriptionImpl other = _peers.putIfAbsent(key, s); - if (other == null) - { - _subscriptions.addSubscription(s); - new SubscriberCleanup(key, this, _groupMgr); - return s; - } - else - { - return other; - } - } - - private SimpleMemberHandle key(MemberHandle peer) - { - return peer instanceof SimpleMemberHandle ? (SimpleMemberHandle) peer : (SimpleMemberHandle) SimpleMemberHandle.resolve(peer); - } - - static boolean isFromBroker(AMQMessage msg) - { - return ClusteredProtocolSession.isPayloadFromPeer(msg); - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/queue/ClusteredSubscriptionManager.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/queue/ClusteredSubscriptionManager.java deleted file mode 100644 index 39ae7e3c3e..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/queue/ClusteredSubscriptionManager.java +++ /dev/null @@ -1,102 +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. - * - */ -package org.apache.qpid.server.queue; - -import org.apache.log4j.Logger; -import org.apache.qpid.server.cluster.util.LogMessage; - -import java.util.List; - -class ClusteredSubscriptionManager extends SubscriptionSet -{ - private static final Logger _logger = Logger.getLogger(ClusteredSubscriptionManager.class); - private final NestedSubscriptionManager _all; - - ClusteredSubscriptionManager() - { - this(new NestedSubscriptionManager()); - } - - private ClusteredSubscriptionManager(NestedSubscriptionManager all) - { - _all = all; - _all.addSubscription(new Parent()); - } - - NestedSubscriptionManager getAllSubscribers() - { - return _all; - } - - public boolean hasActiveSubscribers() - { - return _all.hasActiveSubscribers(); - } - - public Subscription nextSubscriber(AMQMessage msg) - { - if(ClusteredQueue.isFromBroker(msg)) - { - //if message is from another broker, it should only be delivered - //to another client to meet ordering constraints - Subscription s = super.nextSubscriber(msg); - _logger.info(new LogMessage("Returning next *client* subscriber {0}", s)); - if(s == null) - { - //TODO: deliver to another broker, but set the redelivered flag on the msg - //(this should be policy based) - - //for now just don't deliver it - return null; - } - else - { - return s; - } - } - Subscription s = _all.nextSubscriber(msg); - _logger.info(new LogMessage("Returning next subscriber {0}", s)); - return s; - } - - private class Parent implements WeightedSubscriptionManager - { - public int getWeight() - { - return ClusteredSubscriptionManager.this.getWeight(); - } - - public List<Subscription> getSubscriptions() - { - return ClusteredSubscriptionManager.super.getSubscriptions(); - } - - public boolean hasActiveSubscribers() - { - return ClusteredSubscriptionManager.super.hasActiveSubscribers(); - } - - public Subscription nextSubscriber(AMQMessage msg) - { - return ClusteredSubscriptionManager.super.nextSubscriber(msg); - } - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/queue/NestedSubscriptionManager.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/queue/NestedSubscriptionManager.java deleted file mode 100644 index 0566c5203b..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/queue/NestedSubscriptionManager.java +++ /dev/null @@ -1,116 +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. - * - */ -package org.apache.qpid.server.queue; - -import java.util.List; -import java.util.LinkedList; -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * Distributes messages among a list of subsscription managers, using their - * weighting. - */ -class NestedSubscriptionManager implements SubscriptionManager -{ - private final List<WeightedSubscriptionManager> _subscribers = new CopyOnWriteArrayList<WeightedSubscriptionManager>(); - private int _iterations; - private int _index; - - void addSubscription(WeightedSubscriptionManager s) - { - _subscribers.add(s); - } - - void removeSubscription(WeightedSubscriptionManager s) - { - _subscribers.remove(s); - } - - - public List<Subscription> getSubscriptions() - { - List<Subscription> allSubs = new LinkedList<Subscription>(); - - for (WeightedSubscriptionManager subMans : _subscribers) - { - allSubs.addAll(subMans.getSubscriptions()); - } - - return allSubs; - } - - public boolean hasActiveSubscribers() - { - for (WeightedSubscriptionManager s : _subscribers) - { - if (s.hasActiveSubscribers()) - { - return true; - } - } - return false; - } - - public Subscription nextSubscriber(AMQMessage msg) - { - WeightedSubscriptionManager start = current(); - for (WeightedSubscriptionManager s = start; s != null; s = next(start)) - { - if (hasMore(s)) - { - return nextSubscriber(s); - } - } - return null; - } - - private Subscription nextSubscriber(WeightedSubscriptionManager s) - { - _iterations++; - return s.nextSubscriber(null); - } - - private WeightedSubscriptionManager current() - { - return _subscribers.isEmpty() ? null : _subscribers.get(_index); - } - - private boolean hasMore(WeightedSubscriptionManager s) - { - return _iterations < s.getWeight(); - } - - private WeightedSubscriptionManager next(WeightedSubscriptionManager start) - { - WeightedSubscriptionManager s = next(); - return s == start && !hasMore(s) ? null : s; - } - - private WeightedSubscriptionManager next() - { - _iterations = 0; - if (++_index >= _subscribers.size()) - { - _index = 0; - } - return _subscribers.get(_index); - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/queue/PrivateQueue.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/queue/PrivateQueue.java deleted file mode 100644 index f8e4311a77..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/queue/PrivateQueue.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -package org.apache.qpid.server.queue; - -import org.apache.qpid.AMQException; -import org.apache.qpid.server.cluster.SimpleSendable; -import org.apache.qpid.server.cluster.GroupManager; -import org.apache.qpid.server.cluster.SimpleBodySendable; -import org.apache.qpid.server.virtualhost.VirtualHost; -import org.apache.qpid.framing.QueueDeleteBody; -import org.apache.qpid.framing.AMQShortString; - -import java.util.concurrent.Executor; - -/** - * Used to represent a private queue held locally. - * - */ -public class PrivateQueue extends AMQQueue -{ - private final GroupManager _groupMgr; - - public PrivateQueue(GroupManager groupMgr, AMQShortString name, boolean durable, AMQShortString owner, boolean autoDelete, VirtualHost virtualHost) - throws AMQException - { - super(name, durable, owner, autoDelete, virtualHost); - _groupMgr = groupMgr; - - } - - protected void autodelete() throws AMQException - { - //delete locally: - super.autodelete(); - - //send delete request to peers: - // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) - // TODO: Connect this to the session version obtained from ProtocolInitiation for this session. - QueueDeleteBody request = new QueueDeleteBody((byte)8, (byte)0, - QueueDeleteBody.getClazz((byte)8, (byte)0), - QueueDeleteBody.getMethod((byte)8, (byte)0), - false,false,false,null,0); - request.queue = getName(); - _groupMgr.broadcast(new SimpleBodySendable(request)); - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/queue/ProxiedQueueCleanup.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/queue/ProxiedQueueCleanup.java deleted file mode 100644 index efc0540c18..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/queue/ProxiedQueueCleanup.java +++ /dev/null @@ -1,60 +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. - * - */ -package org.apache.qpid.server.queue; - -import org.apache.qpid.server.cluster.MembershipChangeListener; -import org.apache.qpid.server.cluster.MemberHandle; -import org.apache.qpid.server.cluster.GroupManager; -import org.apache.qpid.server.cluster.util.LogMessage; -import org.apache.qpid.AMQException; -import org.apache.log4j.Logger; - -import java.util.List; - -class ProxiedQueueCleanup implements MembershipChangeListener -{ - private static final Logger _logger = Logger.getLogger(ProxiedQueueCleanup.class); - - private final MemberHandle _subject; - private final RemoteQueueProxy _queue; - - ProxiedQueueCleanup(MemberHandle subject, RemoteQueueProxy queue) - { - _subject = subject; - _queue = queue; - } - - public void changed(List<MemberHandle> members) - { - if(!members.contains(_subject)) - { - try - { - _queue.delete(); - _logger.info(new LogMessage("Deleted {0} in response to exclusion of {1}", _queue, _subject)); - } - catch (AMQException e) - { - _logger.info(new LogMessage("Failed to delete {0} in response to exclusion of {1}: {2}", _queue, _subject, e), e); - } - } - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/queue/RemoteQueueProxy.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/queue/RemoteQueueProxy.java deleted file mode 100644 index 2a83d65ae5..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/queue/RemoteQueueProxy.java +++ /dev/null @@ -1,91 +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. - * - */ -package org.apache.qpid.server.queue; - -import org.apache.log4j.Logger; -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.BasicPublishBody; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.server.cluster.ClusteredProtocolSession; -import org.apache.qpid.server.cluster.GroupManager; -import org.apache.qpid.server.cluster.MemberHandle; -import org.apache.qpid.server.cluster.SimpleSendable; -import org.apache.qpid.server.cluster.util.LogMessage; -import org.apache.qpid.server.virtualhost.VirtualHost; - -/** - * TODO: separate out an abstract base class from AMQQueue from which this inherits. It does - * not require all the functionality currently in AMQQueue. - * - */ -public class RemoteQueueProxy extends AMQQueue -{ - private static final Logger _logger = Logger.getLogger(RemoteQueueProxy.class); - private final MemberHandle _target; - private final GroupManager _groupMgr; - - public RemoteQueueProxy(MemberHandle target, GroupManager groupMgr, AMQShortString name, boolean durable, AMQShortString owner, boolean autoDelete, VirtualHost virtualHost) - throws AMQException - { - super(name, durable, owner, autoDelete, virtualHost); - _target = target; - _groupMgr = groupMgr; - _groupMgr.addMemberhipChangeListener(new ProxiedQueueCleanup(target, this)); - } - - - public void deliver(AMQMessage msg) throws NoConsumersException - { - if (ClusteredProtocolSession.canRelay(msg, _target)) - { - try - { - _logger.debug(new LogMessage("Relaying {0} to {1}", msg, _target)); - relay(msg); - } - catch (NoConsumersException e) - { - throw e; - } - catch (AMQException e) - { - //TODO: sort out exception handling... - e.printStackTrace(); - } - } - else - { - _logger.debug(new LogMessage("Cannot relay {0} to {1}", msg, _target)); - } - } - - void relay(AMQMessage msg) throws AMQException - { - // TODO FIXME - can no longer update the publish body as it is an opaque wrapper object - // if cluster can handle immediate then it should wrap the wrapper... - -// BasicPublishBody publish = msg.getMessagePublishInfo(); -// publish.immediate = false; //can't as yet handle the immediate flag in a cluster - - // send this on to the broker for which it is acting as proxy: - _groupMgr.send(_target, new SimpleSendable(msg)); - } -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/queue/RemoteSubscriptionImpl.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/queue/RemoteSubscriptionImpl.java deleted file mode 100644 index e396432cea..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/queue/RemoteSubscriptionImpl.java +++ /dev/null @@ -1,176 +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. - * - */ -package org.apache.qpid.server.queue; - -import org.apache.qpid.server.cluster.MemberHandle; -import org.apache.qpid.server.cluster.GroupManager; -import org.apache.qpid.server.cluster.SimpleSendable; -import org.apache.qpid.server.AMQChannel; -import org.apache.qpid.AMQException; - -import java.util.Queue; -import java.util.List; - -class RemoteSubscriptionImpl implements Subscription, WeightedSubscriptionManager -{ - private final GroupManager _groupMgr; - private final MemberHandle _peer; - private boolean _suspended; - private int _count; - - RemoteSubscriptionImpl(GroupManager groupMgr, MemberHandle peer) - { - _groupMgr = groupMgr; - _peer = peer; - } - - synchronized void increment() - { - _count++; - } - - synchronized boolean decrement() - { - return --_count <= 0; - } - - public void send(AMQMessage msg, AMQQueue queue) - { - try - { - _groupMgr.send(_peer, new SimpleSendable(msg)); - } - catch (AMQException e) - { - //TODO: handle exceptions properly... - e.printStackTrace(); - } - } - - public synchronized void setSuspended(boolean suspended) - { - _suspended = suspended; - } - - public synchronized boolean isSuspended() - { - return _suspended; - } - - public synchronized int getWeight() - { - return _count; - } - - public List<Subscription> getSubscriptions() - { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - public boolean hasActiveSubscribers() - { - return getWeight() == 0; - } - - public Subscription nextSubscriber(AMQMessage msg) - { - return this; - } - - public void queueDeleted(AMQQueue queue) - { - if (queue instanceof ClusteredQueue) - { - ((ClusteredQueue) queue).removeAllRemoteSubscriber(_peer); - } - } - - public boolean filtersMessages() - { - return false; - } - - public boolean hasInterest(AMQMessage msg) - { - return true; - } - - public Queue<AMQMessage> getPreDeliveryQueue() - { - return null; - } - - public Queue<AMQMessage> getResendQueue() - { - return null; - } - - public Queue<AMQMessage> getNextQueue(Queue<AMQMessage> messages) - { - return messages; - } - - public void enqueueForPreDelivery(AMQMessage msg, boolean deliverFirst) - { - //no-op -- if selectors are implemented on RemoteSubscriptions then look at SubscriptionImpl - } - - public boolean isAutoClose() - { - return false; - } - - public void close() - { - //no-op - } - - public boolean isClosed() - { - return false; - } - - public boolean isBrowser() - { - return false; - } - - public boolean wouldSuspend(AMQMessage msg) - { - return _suspended; - } - - public void addToResendQueue(AMQMessage msg) - { - //no-op - } - - public Object getSendLock() - { - return new Object(); - } - - public AMQChannel getChannel() - { - return null; - } - -} diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/queue/SubscriberCleanup.java b/qpid/java/cluster/src/main/java/org/apache/qpid/server/queue/SubscriberCleanup.java deleted file mode 100644 index cc951a4709..0000000000 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/queue/SubscriberCleanup.java +++ /dev/null @@ -1,56 +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. - * - */ -package org.apache.qpid.server.queue; - -import org.apache.qpid.server.cluster.MembershipChangeListener; -import org.apache.qpid.server.cluster.MemberHandle; -import org.apache.qpid.server.cluster.GroupManager; -import org.apache.qpid.server.cluster.util.LogMessage; -import org.apache.log4j.Logger; - -import java.util.List; - -class SubscriberCleanup implements MembershipChangeListener -{ - private static final Logger _logger = Logger.getLogger(SubscriberCleanup.class); - - private final MemberHandle _subject; - private final ClusteredQueue _queue; - private final GroupManager _manager; - - SubscriberCleanup(MemberHandle subject, ClusteredQueue queue, GroupManager manager) - { - _subject = subject; - _queue = queue; - _manager = manager; - _manager.addMemberhipChangeListener(this); - } - - public void changed(List<MemberHandle> members) - { - if(!members.contains(_subject)) - { - _queue.removeAllRemoteSubscriber(_subject); - _manager.removeMemberhipChangeListener(this); - _logger.info(new LogMessage("Removed {0} from {1}", _subject, _queue)); - } - } -} diff --git a/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/BrokerGroupTest.java b/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/BrokerGroupTest.java deleted file mode 100644 index b91d7140e0..0000000000 --- a/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/BrokerGroupTest.java +++ /dev/null @@ -1,270 +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. - * - */ -package org.apache.qpid.server.cluster; - -import junit.framework.TestCase; - -import java.io.IOException; -import java.util.Arrays; - -public class BrokerGroupTest extends TestCase -{ - private final MemberHandle a = new SimpleMemberHandle("A", 1); - private final MemberHandle b = new SimpleMemberHandle("B", 1); - private final MemberHandle c = new SimpleMemberHandle("C", 1); - private final MemberHandle d = new SimpleMemberHandle("D", 1); - - //join (new members perspective) - // (i) connectToLeader() - // ==> check state - // (ii) setMembers() - // ==> check state - // ==> check members - // (iii) synched(leader) - // ==> check state - // ==> check peers - // (iv) synched(other) - // ==> check state - // ==> check peers - // repeat for all others - public void testJoin_newMember() throws Exception - { - MemberHandle[] pre = new MemberHandle[]{a, b, c}; - MemberHandle[] post = new MemberHandle[]{a, b, c}; - - BrokerGroup group = new BrokerGroup(d, new TestReplayManager(), new TestBrokerFactory()); - assertEquals(JoinState.UNINITIALISED, group.getState()); - //(i) - group.connectToLeader(a); - assertEquals(JoinState.JOINING, group.getState()); - assertEquals("Wrong number of peers", 1, group.getPeers().size()); - //(ii) - group.setMembers(Arrays.asList(post)); - assertEquals(JoinState.INITIATION, group.getState()); - assertEquals(Arrays.asList(post), group.getMembers()); - //(iii) & (iv) - for (MemberHandle member : pre) - { - group.synched(member); - if (member == c) - { - assertEquals(JoinState.JOINED, group.getState()); - assertEquals("Wrong number of peers", pre.length, group.getPeers().size()); - } - else - { - assertEquals(JoinState.INDUCTION, group.getState()); - assertEquals("Wrong number of peers", 1, group.getPeers().size()); - } - } - } - - //join (leaders perspective) - // (i) extablish() - // ==> check state - // ==> check members - // ==> check peers - // (ii) connectToProspect() - // ==> check members - // ==> check peers - // repeat (ii) - public void testJoin_Leader() throws IOException, InterruptedException - { - MemberHandle[] prospects = new MemberHandle[]{b, c, d}; - - BrokerGroup group = new BrokerGroup(a, new TestReplayManager(), new TestBrokerFactory()); - assertEquals(JoinState.UNINITIALISED, group.getState()); - //(i) - group.establish(); - assertEquals(JoinState.JOINED, group.getState()); - assertEquals("Wrong number of peers", 0, group.getPeers().size()); - assertEquals("Wrong number of members", 1, group.getMembers().size()); - assertEquals(a, group.getMembers().get(0)); - //(ii) - for (int i = 0; i < prospects.length; i++) - { - group.connectToProspect(prospects[i]); - assertEquals("Wrong number of peers", i + 1, group.getPeers().size()); - for (int j = 0; j <= i; j++) - { - assertTrue(prospects[i].matches(group.getPeers().get(i))); - } - assertEquals("Wrong number of members", i + 2, group.getMembers().size()); - assertEquals(a, group.getMembers().get(0)); - for (int j = 0; j <= i; j++) - { - assertEquals(prospects[i], group.getMembers().get(i + 1)); - } - } - } - - //join (general perspective) - // (i) set up group - // (ii) setMembers() - // ==> check members - // ==> check peers - public void testJoin_general() throws Exception - { - MemberHandle[] view1 = new MemberHandle[]{a, b, c}; - MemberHandle[] view2 = new MemberHandle[]{a, b, c, d}; - MemberHandle[] peers = new MemberHandle[]{a, b, d}; - - BrokerGroup group = new BrokerGroup(c, new TestReplayManager(), new TestBrokerFactory()); - //(i) - group.connectToLeader(a); - group.setMembers(Arrays.asList(view1)); - for (MemberHandle h : view1) - { - group.synched(h); - } - //(ii) - group.setMembers(Arrays.asList(view2)); - assertEquals(Arrays.asList(view2), group.getMembers()); - assertEquals(peers.length, group.getPeers().size()); - for (int i = 0; i < peers.length; i++) - { - assertTrue(peers[i].matches(group.getPeers().get(i))); - } - } - - //leadership transfer (valid) - // (i) set up group - // (ii) assumeLeadership() - // ==> check return value - // ==> check members - // ==> check peers - // ==> check isLeader() - // ==> check isLeader(old_leader) - // ==> check isMember(old_leader) - public void testTransferLeadership_valid() throws Exception - { - MemberHandle[] view1 = new MemberHandle[]{a, b}; - MemberHandle[] view2 = new MemberHandle[]{a, b, c, d}; - MemberHandle[] view3 = new MemberHandle[]{b, c, d}; - - BrokerGroup group = new BrokerGroup(b, new TestReplayManager(), new TestBrokerFactory()); - //(i) - group.connectToLeader(a); - group.setMembers(Arrays.asList(view1)); - for (MemberHandle h : view1) - { - group.synched(h); - } - group.setMembers(Arrays.asList(view2)); - //(ii) - boolean result = group.assumeLeadership(); - assertTrue(result); - assertTrue(group.isLeader()); - assertFalse(group.isLeader(a)); - assertEquals(Arrays.asList(view3), group.getMembers()); - assertEquals(2, group.getPeers().size()); - assertTrue(c.matches(group.getPeers().get(0))); - assertTrue(d.matches(group.getPeers().get(1))); - } - - //leadership transfer (invalid) - // (i) set up group - // (ii) assumeLeadership() - // ==> check return value - // ==> check members - // ==> check peers - // ==> check isLeader() - // ==> check isLeader(old_leader) - // ==> check isMember(old_leader) - public void testTransferLeadership_invalid() throws Exception - { - MemberHandle[] view1 = new MemberHandle[]{a, b, c}; - MemberHandle[] view2 = new MemberHandle[]{a, b, c, d}; - - BrokerGroup group = new BrokerGroup(c, new TestReplayManager(), new TestBrokerFactory()); - //(i) - group.connectToLeader(a); - group.setMembers(Arrays.asList(view1)); - for (MemberHandle h : view1) - { - group.synched(h); - } - group.setMembers(Arrays.asList(view2)); - //(ii) - boolean result = group.assumeLeadership(); - assertFalse(result); - assertFalse(group.isLeader()); - assertTrue(group.isLeader(a)); - assertEquals(Arrays.asList(view2), group.getMembers()); - assertEquals(3, group.getPeers().size()); - assertTrue(a.matches(group.getPeers().get(0))); - assertTrue(b.matches(group.getPeers().get(1))); - assertTrue(d.matches(group.getPeers().get(2))); - - } - - //leave (leaders perspective) - // (i) set up group - // (ii) remove a member - // ==> check members - // ==> check peers - // ==> check isMember(removed_member) - // repeat (ii) - public void testLeave_leader() - { - MemberHandle[] view1 = new MemberHandle[]{a, b, c, d}; - MemberHandle[] view2 = new MemberHandle[]{a, b, d}; - MemberHandle[] view3 = new MemberHandle[]{a, d}; - MemberHandle[] view4 = new MemberHandle[]{a}; - //(i) - BrokerGroup group = new BrokerGroup(a, new TestReplayManager(), new TestBrokerFactory()); - group.establish(); - group.setMembers(Arrays.asList(view1)); - //(ii) - group.remove(group.findBroker(c, false)); - assertEquals(Arrays.asList(view2), group.getMembers()); - - group.remove(group.findBroker(b, false)); - assertEquals(Arrays.asList(view3), group.getMembers()); - - group.remove(group.findBroker(d, false)); - assertEquals(Arrays.asList(view4), group.getMembers()); - } - - - //leave (general perspective) - // (i) set up group - // (ii) setMember - // ==> check members - // ==> check peers - // ==> check isMember(removed_member) - // repeat (ii) - public void testLeave_general() - { - MemberHandle[] view1 = new MemberHandle[]{a, b, c, d}; - MemberHandle[] view2 = new MemberHandle[]{a, c, d}; - //(i) - BrokerGroup group = new BrokerGroup(c, new TestReplayManager(), new TestBrokerFactory()); - group.establish(); //not strictly the correct way to build up the group, but ok for here - group.setMembers(Arrays.asList(view1)); - //(ii) - group.setMembers(Arrays.asList(view2)); - assertEquals(Arrays.asList(view2), group.getMembers()); - assertEquals(2, group.getPeers().size()); - assertTrue(a.matches(group.getPeers().get(0))); - assertTrue(d.matches(group.getPeers().get(1))); - } -} diff --git a/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/BrokerTest.java b/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/BrokerTest.java deleted file mode 100644 index f1da312eea..0000000000 --- a/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/BrokerTest.java +++ /dev/null @@ -1,237 +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. - * - */ -package org.apache.qpid.server.cluster; - -import junit.framework.TestCase; -import org.apache.mina.common.ByteBuffer; -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.AMQBody; -import org.apache.qpid.framing.AMQDataBlock; -import org.apache.qpid.framing.AMQFrame; -import org.apache.qpid.framing.AMQFrameDecodingException; -import org.apache.qpid.framing.AMQMethodBody; -import org.apache.qpid.server.cluster.policy.StandardPolicies; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public class BrokerTest extends TestCase -{ - //group request (no failure) - public void testGroupRequest_noFailure() throws AMQException - { - RecordingBroker[] brokers = new RecordingBroker[]{ - new RecordingBroker("A", 1), - new RecordingBroker("B", 2), - new RecordingBroker("C", 3) - }; - GroupResponseValidator handler = new GroupResponseValidator(new TestMethod("response"), new ArrayList<Member>(Arrays.asList(brokers))); - GroupRequest grpRequest = new GroupRequest(new SimpleBodySendable(new TestMethod("request")), StandardPolicies.SYNCH_POLICY, handler); - for (Broker b : brokers) - { - b.invoke(grpRequest); - } - grpRequest.finishedSend(); - - for (RecordingBroker b : brokers) - { - b.handleResponse(((AMQFrame) b.getMessages().get(0)).getChannel(), new TestMethod("response")); - } - - assertTrue("Handler did not receive response", handler.isCompleted()); - } - - //group request (failure) - public void testGroupRequest_failure() throws AMQException - { - RecordingBroker a = new RecordingBroker("A", 1); - RecordingBroker b = new RecordingBroker("B", 2); - RecordingBroker c = new RecordingBroker("C", 3); - RecordingBroker[] all = new RecordingBroker[]{a, b, c}; - RecordingBroker[] succeeded = new RecordingBroker[]{a, c}; - - GroupResponseValidator handler = new GroupResponseValidator(new TestMethod("response"), new ArrayList<Member>(Arrays.asList(succeeded))); - GroupRequest grpRequest = new GroupRequest(new SimpleBodySendable(new TestMethod("request")), StandardPolicies.SYNCH_POLICY, handler); - - for (Broker broker : all) - { - broker.invoke(grpRequest); - } - grpRequest.finishedSend(); - - for (RecordingBroker broker : succeeded) - { - broker.handleResponse(((AMQFrame) broker.getMessages().get(0)).getChannel(), new TestMethod("response")); - } - b.remove(); - - assertTrue("Handler did not receive response", handler.isCompleted()); - } - - - //simple send (no response) - public void testSend_noResponse() throws AMQException - { - AMQBody[] msgs = new AMQBody[]{ - new TestMethod("A"), - new TestMethod("B"), - new TestMethod("C") - }; - RecordingBroker broker = new RecordingBroker("myhost", 1); - for (AMQBody msg : msgs) - { - broker.send(new SimpleBodySendable(msg), null); - } - List<AMQDataBlock> sent = broker.getMessages(); - assertEquals(msgs.length, sent.size()); - for (int i = 0; i < msgs.length; i++) - { - assertTrue(sent.get(i) instanceof AMQFrame); - assertEquals(msgs[i], ((AMQFrame) sent.get(i)).getBodyFrame()); - } - } - - //simple send (no failure) - public void testSend_noFailure() throws AMQException - { - RecordingBroker broker = new RecordingBroker("myhost", 1); - BlockingHandler handler = new BlockingHandler(); - broker.send(new SimpleBodySendable(new TestMethod("A")), handler); - List<AMQDataBlock> sent = broker.getMessages(); - assertEquals(1, sent.size()); - assertTrue(sent.get(0) instanceof AMQFrame); - assertEquals(new TestMethod("A"), ((AMQFrame) sent.get(0)).getBodyFrame()); - - broker.handleResponse(((AMQFrame) sent.get(0)).getChannel(), new TestMethod("B")); - - assertEquals(new TestMethod("B"), handler.getResponse()); - } - - //simple send (failure) - public void testSend_failure() throws AMQException - { - RecordingBroker broker = new RecordingBroker("myhost", 1); - BlockingHandler handler = new BlockingHandler(); - broker.send(new SimpleBodySendable(new TestMethod("A")), handler); - List<AMQDataBlock> sent = broker.getMessages(); - assertEquals(1, sent.size()); - assertTrue(sent.get(0) instanceof AMQFrame); - assertEquals(new TestMethod("A"), ((AMQFrame) sent.get(0)).getBodyFrame()); - broker.remove(); - assertEquals(null, handler.getResponse()); - assertTrue(handler.isCompleted()); - assertTrue(handler.failed()); - } - - private static class TestMethod extends AMQMethodBody - { - private final Object id; - - TestMethod(Object id) - { - // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) - // TODO: Connect this to the session version obtained from ProtocolInitiation for this session. - super((byte)8, (byte)0); - this.id = id; - } - - protected int getBodySize() - { - return 0; - } - - protected int getClazz() - { - return 1002; - } - - protected int getMethod() - { - return 1003; - } - - protected void writeMethodPayload(ByteBuffer buffer) - { - } - - protected byte getType() - { - return 0; - } - - protected int getSize() - { - return 0; - } - - protected void writePayload(ByteBuffer buffer) - { - } - - protected void populateMethodBodyFromBuffer(ByteBuffer buffer) throws AMQFrameDecodingException - { - } - - protected void populateFromBuffer(ByteBuffer buffer, long size) throws AMQFrameDecodingException - { - } - - public boolean equals(Object o) - { - return o instanceof TestMethod && id.equals(((TestMethod) o).id); - } - - public int hashCode() - { - return id.hashCode(); - } - - } - - private static class GroupResponseValidator implements GroupResponseHandler - { - private final AMQMethodBody _response; - private final List<Member> _members; - private boolean _completed = false; - - GroupResponseValidator(AMQMethodBody response, List<Member> members) - { - _response = response; - _members = members; - } - - public void response(List<AMQMethodBody> responses, List<Member> members) - { - for (AMQMethodBody r : responses) - { - assertEquals(_response, r); - } - assertEquals(_members, members); - _completed = true; - } - - boolean isCompleted() - { - return _completed; - } - } -} diff --git a/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/ClusterCapabilityTest.java b/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/ClusterCapabilityTest.java deleted file mode 100644 index 830a00f4c2..0000000000 --- a/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/ClusterCapabilityTest.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -package org.apache.qpid.server.cluster; - -import junit.framework.TestCase; -import org.apache.qpid.framing.AMQShortString; - -public class ClusterCapabilityTest extends TestCase -{ - public void testStartWithNull() - { - MemberHandle peer = new SimpleMemberHandle("myhost:9999"); - AMQShortString c = ClusterCapability.add(null, peer); - assertTrue(ClusterCapability.contains(c)); - assertTrue(peer.matches(ClusterCapability.getPeer(c))); - } - - public void testStartWithText() - { - MemberHandle peer = new SimpleMemberHandle("myhost:9999"); - AMQShortString c = ClusterCapability.add(new AMQShortString("existing text"), peer); - assertTrue(ClusterCapability.contains(c)); - assertTrue(peer.matches(ClusterCapability.getPeer(c))); - } -} diff --git a/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/InductionBufferTest.java b/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/InductionBufferTest.java deleted file mode 100644 index 7e58add91e..0000000000 --- a/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/InductionBufferTest.java +++ /dev/null @@ -1,106 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.mina.common.IoSession; - -import java.util.List; -import java.util.ArrayList; - -import junit.framework.TestCase; - -public class InductionBufferTest extends TestCase -{ - public void test() throws Exception - { - IoSession session1 = new TestSession(); - IoSession session2 = new TestSession(); - IoSession session3 = new TestSession(); - - TestMessageHandler handler = new TestMessageHandler(); - InductionBuffer buffer = new InductionBuffer(handler); - - buffer.receive(session1, "one"); - buffer.receive(session2, "two"); - buffer.receive(session3, "three"); - - buffer.receive(session1, "four"); - buffer.receive(session1, "five"); - buffer.receive(session1, "six"); - - buffer.receive(session3, "seven"); - buffer.receive(session3, "eight"); - - handler.checkEmpty(); - buffer.deliver(); - - handler.check(session1, "one"); - handler.check(session2, "two"); - handler.check(session3, "three"); - - handler.check(session1, "four"); - handler.check(session1, "five"); - handler.check(session1, "six"); - - handler.check(session3, "seven"); - handler.check(session3, "eight"); - handler.checkEmpty(); - - buffer.receive(session1, "nine"); - buffer.receive(session2, "ten"); - buffer.receive(session3, "eleven"); - - handler.check(session1, "nine"); - handler.check(session2, "ten"); - handler.check(session3, "eleven"); - - handler.checkEmpty(); - } - - private static class TestMessageHandler implements InductionBuffer.MessageHandler - { - private final List<IoSession> _sessions = new ArrayList<IoSession>(); - private final List<Object> _msgs = new ArrayList<Object>(); - - public synchronized void deliver(IoSession session, Object msg) throws Exception - { - _sessions.add(session); - _msgs.add(msg); - } - - void check(IoSession actualSession, Object actualMsg) - { - assertFalse(_sessions.isEmpty()); - assertFalse(_msgs.isEmpty()); - IoSession expectedSession = _sessions.remove(0); - Object expectedMsg = _msgs.remove(0); - assertEquals(expectedSession, actualSession); - assertEquals(expectedMsg, actualMsg); - } - - void checkEmpty() - { - assertTrue(_sessions.isEmpty()); - assertTrue(_msgs.isEmpty()); - } - } -} - diff --git a/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/RecordingBroker.java b/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/RecordingBroker.java deleted file mode 100644 index 1ec5154a98..0000000000 --- a/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/RecordingBroker.java +++ /dev/null @@ -1,53 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.AMQDataBlock; - -import java.util.ArrayList; -import java.util.List; - -class RecordingBroker extends TestBroker -{ - private final List<AMQDataBlock> _messages = new ArrayList<AMQDataBlock>(); - - RecordingBroker(String host, int port) - { - super(host, port); - } - - public void send(AMQDataBlock data) throws AMQException - { - _messages.add(data); - } - - List<AMQDataBlock> getMessages() - { - return _messages; - } - - void clear() - { - _messages.clear(); - } - -} diff --git a/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/RecordingBrokerFactory.java b/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/RecordingBrokerFactory.java deleted file mode 100644 index d3e972e273..0000000000 --- a/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/RecordingBrokerFactory.java +++ /dev/null @@ -1,29 +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. - * - */ -package org.apache.qpid.server.cluster; - -class RecordingBrokerFactory implements BrokerFactory -{ - public Broker create(MemberHandle handle) - { - return new RecordingBroker(handle.getHost(), handle.getPort()); - } -} diff --git a/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/SimpleClusterTest.java b/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/SimpleClusterTest.java deleted file mode 100644 index 86cde3cee7..0000000000 --- a/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/SimpleClusterTest.java +++ /dev/null @@ -1,45 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.url.URLSyntaxException; -import org.apache.qpid.client.AMQConnection; -import org.apache.qpid.client.AMQSession; - -import javax.jms.JMSException; - -import junit.framework.TestCase; - -public class SimpleClusterTest extends TestCase -{ - public void testDeclareExchange() throws AMQException, JMSException, URLSyntaxException - { - AMQConnection con = new AMQConnection("localhost:9000", "guest", "guest", "test", "/test"); - AMQSession session = (AMQSession) con.createSession(false, AMQSession.NO_ACKNOWLEDGE); - System.out.println("Session created"); - session.declareExchange(new AMQShortString("my_exchange"), new AMQShortString("direct"), true); - System.out.println("Exchange declared"); - con.close(); - System.out.println("Connection closed"); - } -} diff --git a/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/SimpleMemberHandleTest.java b/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/SimpleMemberHandleTest.java deleted file mode 100644 index 8ff8357377..0000000000 --- a/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/SimpleMemberHandleTest.java +++ /dev/null @@ -1,57 +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. - * - */ -package org.apache.qpid.server.cluster; - -import junit.framework.TestCase; - -public class SimpleMemberHandleTest extends TestCase -{ - public void testMatches() - { - assertMatch(new SimpleMemberHandle("localhost", 8888), new SimpleMemberHandle("localhost", 8888)); - assertNoMatch(new SimpleMemberHandle("localhost", 8889), new SimpleMemberHandle("localhost", 8888)); - assertNoMatch(new SimpleMemberHandle("localhost", 8888), new SimpleMemberHandle("localhost2", 8888)); - } - - public void testResolve() - { - assertEquivalent(new SimpleMemberHandle("WGLAIBD8XGR0J:9000"), new SimpleMemberHandle("localhost:9000")); - } - - private void assertEquivalent(MemberHandle a, MemberHandle b) - { - String msg = a + " is not equivalent to " + b; - a = SimpleMemberHandle.resolve(a); - b = SimpleMemberHandle.resolve(b); - msg += "(" + a + " does not match " + b + ")"; - assertTrue(msg, a.matches(b)); - } - - private void assertMatch(MemberHandle a, MemberHandle b) - { - assertTrue(a + " does not match " + b, a.matches(b)); - } - - private void assertNoMatch(MemberHandle a, MemberHandle b) - { - assertFalse(a + " matches " + b, a.matches(b)); - } -} diff --git a/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/TestBroker.java b/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/TestBroker.java deleted file mode 100644 index d3ccbf0ac6..0000000000 --- a/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/TestBroker.java +++ /dev/null @@ -1,70 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.AMQDataBlock; -import org.apache.qpid.framing.AMQFrame; -import org.apache.qpid.framing.AMQMethodBody; - -import java.io.IOException; - -class TestBroker extends Broker -{ - TestBroker(String host, int port) - { - super(host, port); - } - - boolean connect() throws IOException, InterruptedException - { - return true; - } - - void connectAsynch(Iterable<AMQMethodBody> msgs) - { - replay(msgs); - } - - void replay(Iterable<AMQMethodBody> msgs) - { - try - { - for (AMQMethodBody b : msgs) - { - send(new AMQFrame(0, b)); - } - } - catch (AMQException e) - { - throw new RuntimeException(e); - } - } - - Broker connectToCluster() throws IOException, InterruptedException - { - return this; - } - - public void send(AMQDataBlock data) throws AMQException - { - } -} diff --git a/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/TestBrokerFactory.java b/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/TestBrokerFactory.java deleted file mode 100644 index 92eaec876a..0000000000 --- a/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/TestBrokerFactory.java +++ /dev/null @@ -1,29 +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. - * - */ -package org.apache.qpid.server.cluster; - -class TestBrokerFactory implements BrokerFactory -{ - public Broker create(MemberHandle handle) - { - return new TestBroker(handle.getHost(), handle.getPort()); - } -} diff --git a/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/TestReplayManager.java b/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/TestReplayManager.java deleted file mode 100644 index c529c83cc0..0000000000 --- a/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/TestReplayManager.java +++ /dev/null @@ -1,47 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.qpid.framing.AMQMethodBody; -import org.apache.qpid.server.cluster.replay.ReplayManager; - -import java.util.ArrayList; -import java.util.List; - -class TestReplayManager implements ReplayManager -{ - private final List<AMQMethodBody> _msgs; - - TestReplayManager() - { - this(new ArrayList<AMQMethodBody>()); - } - - TestReplayManager(List<AMQMethodBody> msgs) - { - _msgs = msgs; - } - - public List<AMQMethodBody> replay(boolean isLeader) - { - return _msgs; - } -} diff --git a/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/TestSession.java b/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/TestSession.java deleted file mode 100644 index 86ec808924..0000000000 --- a/qpid/java/cluster/src/test/java/org/apache/qpid/server/cluster/TestSession.java +++ /dev/null @@ -1,269 +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. - * - */ -package org.apache.qpid.server.cluster; - -import org.apache.mina.common.*; - -import java.net.SocketAddress; -import java.util.Set; - -class TestSession implements IoSession -{ - public IoService getService() - { - return null; //TODO - } - - public IoServiceConfig getServiceConfig() - { - return null; //TODO - } - - public IoHandler getHandler() - { - return null; //TODO - } - - public IoSessionConfig getConfig() - { - return null; //TODO - } - - public IoFilterChain getFilterChain() - { - return null; //TODO - } - - public WriteFuture write(Object message) - { - return null; //TODO - } - - public CloseFuture close() - { - return null; //TODO - } - - public Object getAttachment() - { - return null; //TODO - } - - public Object setAttachment(Object attachment) - { - return null; //TODO - } - - public Object getAttribute(String key) - { - return null; //TODO - } - - public Object setAttribute(String key, Object value) - { - return null; //TODO - } - - public Object setAttribute(String key) - { - return null; //TODO - } - - public Object removeAttribute(String key) - { - return null; //TODO - } - - public boolean containsAttribute(String key) - { - return false; //TODO - } - - public Set getAttributeKeys() - { - return null; //TODO - } - - public TransportType getTransportType() - { - return null; //TODO - } - - public boolean isConnected() - { - return false; //TODO - } - - public boolean isClosing() - { - return false; //TODO - } - - public CloseFuture getCloseFuture() - { - return null; //TODO - } - - public SocketAddress getRemoteAddress() - { - return null; //TODO - } - - public SocketAddress getLocalAddress() - { - return null; //TODO - } - - public SocketAddress getServiceAddress() - { - return null; //TODO - } - - public int getIdleTime(IdleStatus status) - { - return 0; //TODO - } - - public long getIdleTimeInMillis(IdleStatus status) - { - return 0; //TODO - } - - public void setIdleTime(IdleStatus status, int idleTime) - { - //TODO - } - - public int getWriteTimeout() - { - return 0; //TODO - } - - public long getWriteTimeoutInMillis() - { - return 0; //TODO - } - - public void setWriteTimeout(int writeTimeout) - { - //TODO - } - - public TrafficMask getTrafficMask() - { - return null; //TODO - } - - public void setTrafficMask(TrafficMask trafficMask) - { - //TODO - } - - public void suspendRead() - { - //TODO - } - - public void suspendWrite() - { - //TODO - } - - public void resumeRead() - { - //TODO - } - - public void resumeWrite() - { - //TODO - } - - public long getReadBytes() - { - return 0; //TODO - } - - public long getWrittenBytes() - { - return 0; //TODO - } - - public long getReadMessages() - { - return 0; - } - - public long getWrittenMessages() - { - return 0; - } - - public long getWrittenWriteRequests() - { - return 0; //TODO - } - - public int getScheduledWriteRequests() - { - return 0; //TODO - } - - public int getScheduledWriteBytes() - { - return 0; //TODO - } - - public long getCreationTime() - { - return 0; //TODO - } - - public long getLastIoTime() - { - return 0; //TODO - } - - public long getLastReadTime() - { - return 0; //TODO - } - - public long getLastWriteTime() - { - return 0; //TODO - } - - public boolean isIdle(IdleStatus status) - { - return false; //TODO - } - - public int getIdleCount(IdleStatus status) - { - return 0; //TODO - } - - public long getLastIdleTime(IdleStatus status) - { - return 0; //TODO - } -} diff --git a/qpid/java/common/build.xml b/qpid/java/common/build.xml index e967520423..ea78243ed7 100644 --- a/qpid/java/common/build.xml +++ b/qpid/java/common/build.xml @@ -96,11 +96,11 @@ <target name="version" depends="check-version" if="version.stale"> <!-- Write the version.properties out. --> - <propertyfile file="${version.file}"> - <entry key="qpid.svnversion" value="${qpid.svnversion}"/> - <entry key="qpid.name" value="${project.name}"/> - <entry key="qpid.version" value="${project.version}"/> - </propertyfile> + <echo file="${version.file}" append="true"> + qpid.svnversion=${qpid.svnversion} + qpid.name=${project.name} + qpid.version=${project.version} + </echo> </target> <target name="precompile" depends="gentools,jython,version"/> diff --git a/qpid/java/common/src/main/java/org/apache/qpid/AMQProtocolException.java b/qpid/java/common/src/main/java/org/apache/qpid/AMQProtocolException.java new file mode 100644 index 0000000000..bbc569839a --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/AMQProtocolException.java @@ -0,0 +1,38 @@ +package org.apache.qpid; + +import org.apache.qpid.protocol.AMQConstant; + +/* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +public class AMQProtocolException extends AMQException +{ + /** + * Constructor for a Protocol Exception + * <p> This is the only provided constructor and the parameters have to be + * set to null when they are unknown. + * + * @param msg A description of the reason of this exception . + * @param errorCode A string specifyin the error code of this exception. + * @param cause The linked Execption. + */ + public AMQProtocolException(AMQConstant errorCode, String msg, Throwable cause) + { + super(errorCode, msg, cause); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/SerialException.java b/qpid/java/common/src/main/java/org/apache/qpid/SerialException.java new file mode 100644 index 0000000000..4fc8458e45 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/SerialException.java @@ -0,0 +1,19 @@ +package org.apache.qpid; + +/** + * This exception is used by the serial class (imp RFC 1982) + * + */ +public class SerialException extends ArithmeticException +{ + /** + * Constructs an <code>SerialException</code> with the specified + * detail message. + * + * @param message The exception message. + */ + public SerialException(String message) + { + super(message); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/BasicContentHeaderProperties.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/BasicContentHeaderProperties.java index 99b9f9e180..47b5c02beb 100644 --- a/qpid/java/common/src/main/java/org/apache/qpid/framing/BasicContentHeaderProperties.java +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/BasicContentHeaderProperties.java @@ -108,6 +108,7 @@ public class BasicContentHeaderProperties implements CommonContentHeaderProperti _hasBeenUpdated = false; return result; } + public void updated() { _hasBeenUpdated = true; @@ -683,6 +684,7 @@ public class BasicContentHeaderProperties implements CommonContentHeaderProperti public void setMessageId(String messageId) { + _hasBeenUpdated = true; clearEncodedForm(); _propertyFlags |= MESSAGE_ID_MASK; _messageId = (messageId == null) ? null : new AMQShortString(messageId); diff --git a/qpid/java/common/src/main/java/org/apache/qpid/protocol/AMQConstant.java b/qpid/java/common/src/main/java/org/apache/qpid/protocol/AMQConstant.java index 375df2a45d..8dee790a9e 100644 --- a/qpid/java/common/src/main/java/org/apache/qpid/protocol/AMQConstant.java +++ b/qpid/java/common/src/main/java/org/apache/qpid/protocol/AMQConstant.java @@ -153,6 +153,15 @@ public final class AMQConstant public static final AMQConstant FRAME_MIN_SIZE = new AMQConstant(4096, "frame min size", true); + /** + * The server does not support the protocol version + */ + public static final AMQConstant UNSUPPORTED_BROKER_PROTOCOL_ERROR = new AMQConstant(542, "broker unsupported protocol", true); + /** + * The client imp does not support the protocol version + */ + public static final AMQConstant UNSUPPORTED_CLIENT_PROTOCOL_ERROR = new AMQConstant(543, "client unsupported protocol", true); + /** The AMQP status code. */ private int _code; diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/Serial.java b/qpid/java/common/src/main/java/org/apache/qpid/util/Serial.java new file mode 100644 index 0000000000..c828290644 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/Serial.java @@ -0,0 +1,121 @@ +package org.apache.qpid.util; + +import org.apache.qpid.SerialException; + +/** + * This class provides basic + * serial number arithmetic as defined in RFC 1982. + */ + +public class Serial +{ + private long _maxIncrement; + private long _max; + private long _maxComparison; + + public Serial(long serialbits) + { + if( serialbits < 2) + { + throw new IllegalArgumentException("Meaningful serial number space has SERIAL_BITS >= 2, wrong value " + + serialbits); + } + _max = (long) Math.pow(2.0 , serialbits) - 1; + _maxIncrement = (long) Math.pow(2.0, serialbits - 1) - 1; + _maxComparison = (long) Math.pow(2.0, serialbits -1); + } + + /** + * Compares two numbers using serial arithmetic. + * + * @param serial1 The first serial number + * @param serial2 The second serial number + * @return 0 if the 2 serials numbers are equal, a positive number if serial1 is greater + * than serial2, and a negative number if serial2 is greater than serial1. + * @throws IllegalArgumentException serial1 or serial2 is out of range + * @throws SerialException serial1 and serial2 are not comparable. + */ + public int compare(long serial1, long serial2) throws IllegalArgumentException, SerialException + { + int result; + if (serial1 < 0 || serial1 > _max) + { + throw new IllegalArgumentException(serial1 + " out of range"); + } + if (serial2 < 0 || serial2 > _max) + { + throw new IllegalArgumentException(serial2 + " out of range"); + } + double diff; + if( serial1 < serial2 ) + { + diff = serial2 - serial1; + if( diff < _maxComparison ) + { + result = -1; + } + else if ( diff > _maxComparison ) + { + result = 1; + } + else + { + throw new SerialException("Cannot compare " + serial1 + " and " + serial2); + } + } + else if( serial1 > serial2 ) + { + diff = serial1 - serial2; + if( diff > _maxComparison ) + { + result = -1; + } + else if( diff < _maxComparison ) + { + result = 1; + } + else + { + throw new SerialException("Cannot compare " + serial1 + " and " + serial2); + } + } + else + { + result = 0; + } + return result; + } + + + /** + * Increments a serial numbers by the addition of a positive integer n, + * Serial numbers may be incremented by the addition of a positive + * integer n, where n is taken from the range of integers + * [0 .. (2^(SERIAL_BITS - 1) - 1)]. For a sequence number s, the + * result of such an addition, s', is defined as + * s' = (s + n) modulo (2 ^ SERIAL_BITS) + * @param serial The serila number to be incremented + * @param n The integer to be added to the serial number + * @return The incremented serial number + * @throws IllegalArgumentException serial number or n is out of range + */ + public long increment(long serial, long n) throws IllegalArgumentException + { + if (serial < 0 || serial > _max) + { + throw new IllegalArgumentException("Serial number: " + serial + " is out of range"); + } + if( n < 0 || n > _maxIncrement ) + { + throw new IllegalArgumentException("Increment: " + n + " is out of range"); + } + long result = serial + n; + // apply modulo (2 ^ SERIAL_BITS) + if(result > _max) + { + result = result - _max - 1; + } + return result; + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpidity/ErrorCode.java b/qpid/java/common/src/main/java/org/apache/qpidity/ErrorCode.java index 4ff6939139..4b18c46d16 100644 --- a/qpid/java/common/src/main/java/org/apache/qpidity/ErrorCode.java +++ b/qpid/java/common/src/main/java/org/apache/qpidity/ErrorCode.java @@ -6,6 +6,7 @@ public enum ErrorCode UNDEFINED(1,"undefined",true), MESSAGE_REJECTED(2,"message_rejected",true), CONNECTION_ERROR(3,"connection was closed",true), + UNSUPPORTED_PROTOCOL(4, "protocol version is unsupported", true), //This might change in the spec, the error class is not applicable NO_ERROR(200,"reply-success",true), diff --git a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/MinaBrokerProxyFactory.java b/qpid/java/common/src/main/java/org/apache/qpidity/ProtocolException.java index 5e70de7665..596143a1b9 100644 --- a/qpid/java/cluster/src/main/java/org/apache/qpid/server/cluster/MinaBrokerProxyFactory.java +++ b/qpid/java/common/src/main/java/org/apache/qpidity/ProtocolException.java @@ -1,36 +1,36 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one +/* Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. - * */ -package org.apache.qpid.server.cluster; -public class MinaBrokerProxyFactory implements BrokerFactory -{ - private final MemberHandle _local; +package org.apache.qpidity; - MinaBrokerProxyFactory(MemberHandle local) - { - _local = local; - } - - public Broker create(MemberHandle handle) +public class ProtocolException extends QpidException +{ + /** + * Constructor for a Ptotocol Exception. + * <p> This is the only provided constructor and the parameters have to be set to null when + * they are unknown. + * @param message A description of the reason of this exception. + * @param errorCode A string specifyin the error code of this exception. + * @param cause The linked Execption. + * + */ + public ProtocolException(String message, ErrorCode errorCode, Throwable cause) { - return new MinaBrokerProxy(handle.getHost(), handle.getPort(), _local); + super(message, errorCode, cause); } } diff --git a/qpid/java/common/src/main/java/org/apache/qpidity/ToyClient.java b/qpid/java/common/src/main/java/org/apache/qpidity/ToyClient.java index 977704fc0c..10b68bbb20 100644 --- a/qpid/java/common/src/main/java/org/apache/qpidity/ToyClient.java +++ b/qpid/java/common/src/main/java/org/apache/qpidity/ToyClient.java @@ -75,7 +75,9 @@ class ToyClient extends SessionDelegate } public void closed() {} }); - conn.send(new ConnectionEvent(0, new ProtocolHeader(1, 0, 10))); + conn.send(new ConnectionEvent(0, new ProtocolHeader(1, + TransportConstants.getVersionMajor(), + TransportConstants.getVersionMinor()))); Channel ch = conn.getChannel(0); Session ssn = new Session(); diff --git a/qpid/java/common/src/main/java/org/apache/qpidity/transport/ConnectionDelegate.java b/qpid/java/common/src/main/java/org/apache/qpidity/transport/ConnectionDelegate.java index 001ad7220c..4815f1025f 100644 --- a/qpid/java/common/src/main/java/org/apache/qpidity/transport/ConnectionDelegate.java +++ b/qpid/java/common/src/main/java/org/apache/qpidity/transport/ConnectionDelegate.java @@ -83,7 +83,8 @@ public abstract class ConnectionDelegate extends MethodDelegate<Channel> if (hdr.getMajor() != 0 && hdr.getMinor() != 10) { // XXX - ch.getConnection().send(new ConnectionEvent(0, new ProtocolHeader(1, 0, 10))); + ch.getConnection().send(new ConnectionEvent(0, new ProtocolHeader(1, TransportConstants.getVersionMajor(), + TransportConstants.getVersionMinor()))); ch.getConnection().close(); } else @@ -282,4 +283,9 @@ public abstract class ConnectionDelegate extends MethodDelegate<Channel> { _virtualHost = host; } + + public String getUnsupportedProtocol() + { + return null; + } } diff --git a/qpid/java/common/src/main/java/org/apache/qpidity/transport/TransportConstants.java b/qpid/java/common/src/main/java/org/apache/qpidity/transport/TransportConstants.java new file mode 100644 index 0000000000..54429a1a4f --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpidity/transport/TransportConstants.java @@ -0,0 +1,27 @@ +package org.apache.qpidity.transport; + +public class TransportConstants +{ + private static byte _protocol_version_minor = 0; + private static byte _protocol_version_major = 99; + + public static void setVersionMajor(byte value) + { + _protocol_version_major = value; + } + + public static void setVersionMinor(byte value) + { + _protocol_version_minor = value; + } + + public static byte getVersionMajor() + { + return _protocol_version_major; + } + + public static byte getVersionMinor() + { + return _protocol_version_minor; + } +} diff --git a/qpid/java/common/src/test/java/org/apache/qpid/util/SerialTest.java b/qpid/java/common/src/test/java/org/apache/qpid/util/SerialTest.java new file mode 100644 index 0000000000..312b1ab9ce --- /dev/null +++ b/qpid/java/common/src/test/java/org/apache/qpid/util/SerialTest.java @@ -0,0 +1,135 @@ +package org.apache.qpid.util; + +import junit.framework.TestCase; + +import java.util.Random; + +import org.apache.qpid.SerialException; + +/** + *Junit tests for the Serial class + */ +public class SerialTest extends TestCase +{ + + /** + * The simplest meaningful serial number space has SERIAL_BITS == 2. In + * this space, the integers that make up the serial number space are 0, + * 1, 2, and 3. That is, 3 == 2^SERIAL_BITS - 1. + * + * In this space, the largest integer that it is meaningful to add to a + * sequence number is 2^(SERIAL_BITS - 1) - 1, or 1. + * + * Then, as defined 0+1 == 1, 1+1 == 2, 2+1 == 3, and 3+1 == 0. + * Further, 1 > 0, 2 > 1, 3 > 2, and 0 > 3. It is undefined whether + * 2 > 0 or 0 > 2, and whether 1 > 3 or 3 > 1. + */ + public void testTrivialSample() + { + Serial serial = new Serial(2); + assertEquals( serial.increment(0, 1), 1); + assertEquals( serial.increment(1, 1), 2); + assertEquals( serial.increment(2, 1), 3); + assertEquals( serial.increment(3, 1), 0); + try + { + serial.increment(4, 1); + fail("IllegalArgumentException was not trhown"); + } + catch (IllegalArgumentException e) + { + // expected + } + try + { + assertTrue( serial.compare(1, 0) > 0); + assertTrue( serial.compare(2, 1) > 0); + assertTrue( serial.compare(3, 2) > 0); + assertTrue( serial.compare(0, 3) > 0); + assertTrue( serial.compare(0, 1) < 0); + assertTrue( serial.compare(1, 2) < 0); + assertTrue( serial.compare(2, 3) < 0); + assertTrue( serial.compare(3, 0) < 0); + } + catch (SerialException e) + { + fail("Unexpected exception " + e); + } + try + { + serial.compare(2, 0); + fail("AMQSerialException not thrown as expected"); + } + catch (SerialException e) + { + // expected + } + try + { + serial.compare(0, 2); + fail("AMQSerialException not thrown as expected"); + } + catch (SerialException e) + { + // expected + } + try + { + serial.compare(3, 1); + fail("AMQSerialException not thrown as expected"); + } + catch (SerialException e) + { + // expected + } + try + { + serial.compare(3, 1); + fail("AMQSerialException not thrown as expected"); + } + catch (SerialException e) + { + // expected + } + } + + /** + * Test the first Corollary of RFC 1982 + * For any sequence number s and any integer n such that addition of n + * to s is well defined, (s + n) >= s. Further (s + n) == s only when + * n == 0, in all other defined cases, (s + n) > s. + * strategy: + * Create a serial number with 32 bits and check in a loop that adding integers + * respect the corollary + */ + public void testCorollary1() + { + Serial serial = new Serial(32); + Random random = new Random(); + long number = random.nextInt((int) Math.pow(2.0 , 32.0) - 1); + for(int i = 1; i<= 10000; i++ ) + { + long nextInt = random.nextInt((int) Math.pow(2.0 , 32.0) - 1); + long inc = serial.increment(number, nextInt); + int res =0; + try + { + res=serial.compare(inc, number); + } + catch (SerialException e) + { + fail("un-expected exception " + e); + } + if( res < 1 ) + { + fail("Corollary 1 violated " + number + " + " + nextInt + " < " + number); + } + else if( res == 0 && nextInt > 0) + { + fail("Corollary 1 violated " + number + " + " + nextInt + " = " + number); + } + } + } + + +} diff --git a/qpid/java/common/src/test/java/org/apache/qpidity/transport/ConnectionTest.java b/qpid/java/common/src/test/java/org/apache/qpidity/transport/ConnectionTest.java index 0c5c69c1f1..d7ece9b745 100644 --- a/qpid/java/common/src/test/java/org/apache/qpidity/transport/ConnectionTest.java +++ b/qpid/java/common/src/test/java/org/apache/qpidity/transport/ConnectionTest.java @@ -62,12 +62,12 @@ public class ConnectionTest extends TestCase public void closed() {} }; - MinaHandler.accept("0.0.0.0", port, server); + MinaHandler.accept("localhost", port, server); } private Connection connect(final Condition closed) { - Connection conn = MinaHandler.connect("0.0.0.0", port, new ConnectionDelegate() + Connection conn = MinaHandler.connect("localhost", port, new ConnectionDelegate() { public SessionDelegate getSessionDelegate() { diff --git a/qpid/java/cpp.async.testprofile b/qpid/java/cpp.async.testprofile new file mode 100644 index 0000000000..d4daf47632 --- /dev/null +++ b/qpid/java/cpp.async.testprofile @@ -0,0 +1,17 @@ +broker.version=0-10 +broker=${project.root}/../cpp/src/qpidd --data-dir ${build.data} -t --log-output ${build.data}/broker.log --load-module ${project.root}/../../cppStore/cpp/lib/.libs/libbdbstore.so --store-async yes +broker.clean=${build.data} +java.naming.provider.url=${project.root}/test-provider.properties +max_prefetch=1000 +test.excludes=true +test.excludesfile=${project.root}/010ExcludeList +log=info +amqj.logging.level=$log +root.logging.level=$log +log4j.configuration=file://${project.root}/log4j-test.xml +test.fork=no +test.mem=512M +test=*Test +test1=*Tests +haltonfailure=no +haltonerror=no
\ No newline at end of file diff --git a/qpid/java/cpp.sync.testprofile b/qpid/java/cpp.sync.testprofile new file mode 100644 index 0000000000..44a2d71ea0 --- /dev/null +++ b/qpid/java/cpp.sync.testprofile @@ -0,0 +1,17 @@ +broker.version=0-10 +broker=${project.root}/../cpp/src/qpidd --data-dir ${build.data} -t --log-output ${build.data}/broker.log --load-module ${project.root}/../../cppStore/cpp/lib/.libs/libbdbstore.so --store-async no +broker.clean=${build.data} +java.naming.provider.url=${project.root}/test-provider.properties +test.excludes=true +max_prefetch=1000 +test.excludesfile=${project.root}/010ExcludeList +log=info +amqj.logging.level=$log +root.logging.level=$log +log4j.configuration=file://${project.root}/log4j-test.xml +test.fork=no +test.mem=512M +test=*Test +test1=*Tests +haltonfailure=no +haltonerror=no
\ No newline at end of file diff --git a/qpid/java/default.testprofile b/qpid/java/default.testprofile new file mode 100644 index 0000000000..db99ab93c9 --- /dev/null +++ b/qpid/java/default.testprofile @@ -0,0 +1,16 @@ +broker.version=0-8 +broker=vm +broker.clean=${project.root}/clean-dir +java.naming.provider.url=${project.root}/test-provider.properties +test.excludes=true +test.excludesfile=${project.root}/08ExcludeList +log=info +amqj.logging.level=$log +root.logging.level=$log +log4j.configuration=file://${project.root}/log4j-test.xml +test.fork=no +test.mem=512M +test=*Test +test1=*Tests +haltonfailure=no +haltonerror=no diff --git a/qpid/java/module.xml b/qpid/java/module.xml index b5f2efdf01..3dbab15962 100644 --- a/qpid/java/module.xml +++ b/qpid/java/module.xml @@ -157,42 +157,24 @@ </copy> </target> - <property name="test" value="*Test"/> - <property name="test.mem" value="512M"/> - - <property name="log" value="info"/> - <property name="amqj.logging.level" value="${log}"/> - <property name="root.logging.level" value="${log}"/> - <property name="log4j.configuration" value="file://${project.root}/log4j-test.xml"/> - <property name="java.naming.factory.initial" value="org.apache.qpid.jndi.PropertiesFileInitialContextFactory"/> - <property name="java.naming.provider.url" value="${project.root}/test-provider.properties"/> - - <condition property="brokerdefault" value="${project.root}/../cpp/src/qpidd --data-dir ${build.data} -t" else="vm"> - <isset property="cpp"/> + + <condition property="config" value="${profile}.testprofile" else="default.testprofile"> + <and> + <isset property="profile"/> + <available file="${project.root}/${profile}.testprofile" type="file"/> + </and> </condition> - <condition property="broker" value="${brokerdefault} --load-module ${store} --store-async yes" else="${brokerdefault}"> - <and> - <isset property="store"/> - <isset property="cpp"/> - </and> - </condition> - - <condition property="broker.clean" value="${project.root}/clean-dir ${build.data}"> - <isset property="cpp"/> - </condition> - - <condition property="broker.version" value="0-10" else="0-8"> - <isset property="cpp"/> - </condition> + <property file="${project.root}/${config}"/> <target name="test" depends="compile-tests" if="module.test.src.exists" description="execute unit tests"> - <junit fork="yes" haltonfailure="no" printsummary="on" timeout="600000"> - - <jvmarg value="-Xmx${test.mem}" /> + <junit fork="${test.fork}" maxmemory="${test.mem}" reloading="no" + haltonfailure="${haltonfailure}" haltonerror="${haltonerror}" + printsummary="on" timeout="600000" > + <sysproperty key="amqj.logging.level" value="${amqj.logging.level}"/> <sysproperty key="root.logging.level" value="${root.logging.level}"/> <sysproperty key="log4j.configuration" value="${log4j.configuration}"/> @@ -201,15 +183,19 @@ <sysproperty key="broker" value="${broker}"/> <sysproperty key="broker.clean" value="${broker.clean}"/> <sysproperty key="broker.version" value="${broker.version}"/> + <sysproperty key="test.excludes" value="${test.excludes}"/> + <sysproperty key="test.excludesfile" value="${test.excludesfile}"/> + <sysproperty key="max_prefetch" value ="${max_prefetch}"/> <formatter type="plain"/> <formatter type="xml"/> <classpath refid="module.test.path"/> - <batchtest fork="yes" todir="${build.results}"> + <batchtest fork="${test.fork}" todir="${build.results}"> <fileset dir="${module.test.src}"> - <include name="**/${test}.java"/> + <include name="**/${test1}.java"/> + <include name="**/${test}.java"/> </fileset> </batchtest> </junit> @@ -220,7 +206,7 @@ <fileset dir="${module.bin}"/> </copy> <chmod dir="${build.bin}" perm="ugo+rx" includes="**/*"/> - <copy todir="${build.etc}" failonerror="false"> + <copy todir="${build.etc}" failonerror="false"> <fileset dir="${module.etc}"/> </copy> </target> @@ -235,7 +221,7 @@ <target name="libs" description="copy dependencies into build tree"> <copy todir="${build.lib}" failonerror="false" flatten="true"> - <path refid="module.libs"/> + <fileset dir="${basedir}${file.separator}.." includes="${module.libs}"/> </copy> </target> diff --git a/qpid/java/pom.xml b/qpid/java/pom.xml index d850647fc3..b35d05a150 100644 --- a/qpid/java/pom.xml +++ b/qpid/java/pom.xml @@ -770,5 +770,13 @@ under the License. </plugins> </build> </profile> +<!--- Build profile to ignore test failures. --> +<profile> +<id>ignore</id> +<properties> +<maven.test.failure.ignore>true</maven.test.failure.ignore> +<maven.test.error.ignore>true</maven.test.error.ignore> +</properties> +</profile> </profiles> </project> diff --git a/qpid/java/test-provider.properties b/qpid/java/test-provider.properties index 8dcba7230f..38cc146ae6 100644 --- a/qpid/java/test-provider.properties +++ b/qpid/java/test-provider.properties @@ -1,4 +1,4 @@ -connectionfactory.local = qpid:password=guest;username=guest;client_id=clientid;virtualhost=test@tcp:127.0.0.1:5672 +connectionfactory.local = amqp://username:password@clientid/test?brokerlist='tcp://localhost:5672' queue.MyQueue = example.MyQueue queue.xaQueue = xaQueue diff --git a/qpid/python/commands/qpid-config b/qpid/python/commands/qpid-config new file mode 100755 index 0000000000..3fd8e93c63 --- /dev/null +++ b/qpid/python/commands/qpid-config @@ -0,0 +1,374 @@ +#!/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 +import getopt +import sys +import socket +import qpid +from threading import Condition +from qpid.management import managementClient +from qpid.peer import Closed +from qpid.client import Client +from time import sleep + +_defspecpath = "/usr/share/amqp/amqp.0-10-preview.xml" +_specpath = _defspecpath +_recursive = False +_host = "localhost" +_durable = False +_fileCount = 8 +_fileSize = 24 + +FILECOUNT = "qpid.file_count" +FILESIZE = "qpid.file_size" + +def Usage (): + print "Usage: qpid-config [OPTIONS]" + print " qpid-config [OPTIONS] exchanges [filter-string]" + print " qpid-config [OPTIONS] queues [filter-string]" + print " qpid-config [OPTIONS] add exchange <type> <name> [AddExchangeOptions]" + print " qpid-config [OPTIONS] del exchange <name>" + print " qpid-config [OPTIONS] add queue <name> [AddQueueOptions]" + print " qpid-config [OPTIONS] del queue <name>" + print " qpid-config [OPTIONS] bind <exchange-name> <queue-name> [binding-key]" + print " qpid-config [OPTIONS] unbind <exchange-name> <queue-name> [binding-key]" + print + print "Options:" + print " -b [ --bindings ] Show bindings in queue or exchange list" + print " -a [ --broker-addr ] Address (localhost) Address of qpidd broker" + print " broker-addr is in the form: hostname | ip-address [:<port>]" + print " ex: localhost, 10.1.1.7:10000, broker-host:10000" + print " -s [ --spec-file] Path (" + _defspecpath + ")" + print " AMQP specification file" + print + print "Add Queue Options:" + print " --durable Queue is durable" + print " --file-count N (8) Number of files in queue's persistence journal" + print " --file-size N (24) File size in pages (64Kib/page)" + print + print "Add Exchange Options:" + print " --durable Exchange is durable" + print + sys.exit (1) + +class Broker: + def __init__ (self, text): + colon = text.find (":") + if colon == -1: + host = text + self.port = 5672 + else: + host = text[:colon] + self.port = int (text[colon+1:]) + self.host = socket.gethostbyname (host) + + def name (self): + return self.host + ":" + str (self.port) + +class BrokerManager: + def __init__ (self): + self.dest = None + self.src = None + self.broker = None + + def SetBroker (self, broker): + self.broker = broker + + def ConnectToBroker (self): + try: + self.spec = qpid.spec.load (_specpath) + self.client = Client (self.broker.host, self.broker.port, self.spec) + self.client.start ({"LOGIN":"guest","PASSWORD":"guest"}) + self.channel = self.client.channel (1) + self.mclient = managementClient (self.spec) + self.mchannel = self.mclient.addChannel (self.channel) + except socket.error, e: + print "Connect Error:", e + exit (1) + + def Overview (self): + self.ConnectToBroker () + mc = self.mclient + mch = self.mchannel + mc.syncWaitForStable (mch) + exchanges = mc.syncGetObjects (mch, "exchange") + queues = mc.syncGetObjects (mch, "queue") + print "Total Exchanges: %d" % len (exchanges) + etype = {} + for ex in exchanges: + if ex.type not in etype: + etype[ex.type] = 1 + else: + etype[ex.type] = etype[ex.type] + 1 + for typ in etype: + print "%15s: %d" % (typ, etype[typ]) + + print + print " Total Queues: %d" % len (queues) + _durable = 0 + for queue in queues: + if queue.durable: + _durable = _durable + 1 + print " durable: %d" % _durable + print " non-durable: %d" % (len (queues) - _durable) + + def ExchangeList (self, filter): + self.ConnectToBroker () + mc = self.mclient + mch = self.mchannel + mc.syncWaitForStable (mch) + exchanges = mc.syncGetObjects (mch, "exchange") + print "Type Bindings Exchange Name" + print "=============================================" + for ex in exchanges: + if self.match (ex.name, filter): + print "%-10s%5d %s" % (ex.type, ex.bindings, ex.name) + + def ExchangeListRecurse (self, filter): + self.ConnectToBroker () + mc = self.mclient + mch = self.mchannel + mc.syncWaitForStable (mch) + exchanges = mc.syncGetObjects (mch, "exchange") + bindings = mc.syncGetObjects (mch, "binding") + queues = mc.syncGetObjects (mch, "queue") + for ex in exchanges: + if self.match (ex.name, filter): + print "Exchange '%s' (%s)" % (ex.name, ex.type) + for bind in bindings: + if bind.exchangeRef == ex.id: + qname = "<unknown>" + queue = self.findById (queues, bind.queueRef) + if queue != None: + qname = queue.name + print " bind [%s] => %s" % (bind.bindingKey, qname) + + + def QueueList (self, filter): + self.ConnectToBroker () + mc = self.mclient + mch = self.mchannel + mc.syncWaitForStable (mch) + queues = mc.syncGetObjects (mch, "queue") + journals = mc.syncGetObjects (mch, "journal") + print " Store Size" + print "Durable AutoDel Excl Bindings (files x file pages) Queue Name" + print "===========================================================================================" + for q in queues: + if self.match (q.name, filter): + args = q.arguments + if q.durable and FILESIZE in args and FILECOUNT in args: + fs = int (args[FILESIZE]) + fc = int (args[FILECOUNT]) + print "%4c%9c%7c%10d%11dx%-14d%s" % \ + (YN (q.durable), YN (q.autoDelete), + YN (q.exclusive), q.bindings, fc, fs, q.name) + else: + if not _durable: + print "%4c%9c%7c%10d %s" % \ + (YN (q.durable), YN (q.autoDelete), + YN (q.exclusive), q.bindings, q.name) + + def QueueListRecurse (self, filter): + self.ConnectToBroker () + mc = self.mclient + mch = self.mchannel + mc.syncWaitForStable (mch) + exchanges = mc.syncGetObjects (mch, "exchange") + bindings = mc.syncGetObjects (mch, "binding") + queues = mc.syncGetObjects (mch, "queue") + for queue in queues: + if self.match (queue.name, filter): + print "Queue '%s'" % queue.name + for bind in bindings: + if bind.queueRef == queue.id: + ename = "<unknown>" + ex = self.findById (exchanges, bind.exchangeRef) + if ex != None: + ename = ex.name + if ename == "": + ename = "''" + print " bind [%s] => %s" % (bind.bindingKey, ename) + + def AddExchange (self, args): + if len (args) < 2: + Usage () + self.ConnectToBroker () + etype = args[0] + ename = args[1] + + try: + self.channel.exchange_declare (exchange=ename, type=etype, durable=_durable) + except Closed, e: + print "Failed:", e + + def DelExchange (self, args): + if len (args) < 1: + Usage () + self.ConnectToBroker () + ename = args[0] + + try: + self.channel.exchange_delete (exchange=ename) + except Closed, e: + print "Failed:", e + + def AddQueue (self, args): + if len (args) < 1: + Usage () + self.ConnectToBroker () + qname = args[0] + declArgs = {} + if _durable: + declArgs[FILECOUNT] = _fileCount + declArgs[FILESIZE] = _fileSize + + try: + self.channel.queue_declare (queue=qname, durable=_durable, arguments=declArgs) + except Closed, e: + print "Failed:", e + + def DelQueue (self, args): + if len (args) < 1: + Usage () + self.ConnectToBroker () + qname = args[0] + + try: + self.channel.queue_delete (queue=qname) + except Closed, e: + print "Failed:", e + + def Bind (self, args): + if len (args) < 2: + Usage () + self.ConnectToBroker () + ename = args[0] + qname = args[1] + key = "" + if len (args) > 2: + key = args[2] + + try: + self.channel.queue_bind (queue=qname, exchange=ename, routing_key=key) + except Closed, e: + print "Failed:", e + + def Unbind (self, args): + if len (args) < 2: + Usage () + self.ConnectToBroker () + ename = args[0] + qname = args[1] + key = "" + if len (args) > 2: + key = args[2] + + try: + self.channel.queue_unbind (queue=qname, exchange=ename, routing_key=key) + except Closed, e: + print "Failed:", e + + def findById (self, items, id): + for item in items: + if item.id == id: + return item + return None + + def match (self, name, filter): + if filter == "": + return True + if name.find (filter) == -1: + return False + return True + +def YN (bool): + if bool: + return 'Y' + return 'N' + +## +## Main Program +## + +try: + longOpts = ("durable", "spec-file=", "bindings", "broker-addr=", "file-count=", "file-size=") + (optlist, cargs) = getopt.gnu_getopt (sys.argv[1:], "s:a:b", longOpts) +except: + Usage () + +for opt in optlist: + if opt[0] == "-s" or opt[0] == "--spec-file": + _specpath = opt[1] + if opt[0] == "-b" or opt[0] == "--bindings": + _recursive = True + if opt[0] == "-a" or opt[0] == "--broker-addr": + _host = opt[1] + if opt[0] == "--durable": + _durable = True + if opt[0] == "--file-count": + _fileCount = int (opt[1]) + if opt[0] == "--file-size": + _fileSize = int (opt[1]) + +nargs = len (cargs) +bm = BrokerManager () +bm.SetBroker (Broker (_host)) + +if nargs == 0: + bm.Overview () +else: + cmd = cargs[0] + modifier = "" + if nargs > 1: + modifier = cargs[1] + if cmd[0] == 'e': + if _recursive: + bm.ExchangeListRecurse (modifier) + else: + bm.ExchangeList (modifier) + elif cmd[0] == 'q': + if _recursive: + bm.QueueListRecurse (modifier) + else: + bm.QueueList (modifier) + elif cmd == "add": + if modifier == "exchange": + bm.AddExchange (cargs[2:]) + elif modifier == "queue": + bm.AddQueue (cargs[2:]) + else: + Usage () + elif cmd == "del": + if modifier == "exchange": + bm.DelExchange (cargs[2:]) + elif modifier == "queue": + bm.DelQueue (cargs[2:]) + else: + Usage () + elif cmd == "bind": + bm.Bind (cargs[1:]) + elif cmd == "unbind": + bm.Unbind (cargs[1:]) + else: + Usage () + diff --git a/qpid/python/commands/qpid-route b/qpid/python/commands/qpid-route new file mode 100755 index 0000000000..0db28c791b --- /dev/null +++ b/qpid/python/commands/qpid-route @@ -0,0 +1,270 @@ +#!/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 getopt +import sys +import socket +import qpid +from qpid.management import managementClient +from qpid.peer import Closed +from qpid.client import Client + +def Usage (): + print "Usage: qpid-route [OPTIONS] add <dest-broker> <src-broker> <exchange> <routing-key>" + print " qpid-route [OPTIONS] del <dest-broker> <src-broker> <exchange> <routing-key>" + print " qpid-route [OPTIONS] list <dest-broker>" + #print " qpid-route [OPTIONS] load <filename>" + print " qpid-route [OPTIONS] flush <dest-broker>" + print + print "Options:" + print " -s [ --spec-file ] PATH (/usr/share/amqp/amqp.0-10-preview.xml)" + print " -v [ --verbose ] Verbose output" + print " -q [ --quiet ] Quiet output, don't print duplicate warnings" + print + print " dest-broker and src-broker are in the form: hostname | ip-address [:<port>]" + print " ex: localhost, 10.1.1.7:10000, broker-host:10000" + print + #print " If loading the route configuration from a file, the input file has one line per route" + #print " in the form:" + #print + #print " <dest-broker> <src-broker> <exchange> <routing-key>" + #print + sys.exit (1) + +_specpath = "/usr/share/amqp/amqp.0-10-preview.xml" +_verbose = False +_quiet = False + +class Broker: + def __init__ (self, text): + colon = text.find (":") + if colon == -1: + host = text + self.port = 5672 + else: + host = text[:colon] + self.port = int (text[colon+1:]) + self.host = socket.gethostbyname (host) + + def name (self): + return self.host + ":" + str (self.port) + +class RouteManager: + def __init__ (self, destBroker): + self.dest = Broker (destBroker) + self.src = None + + def ConnectToBroker (self): + broker = self.dest + if _verbose: + print "Connecting to broker: %s:%d" % (broker.host, broker.port) + try: + self.spec = qpid.spec.load (_specpath) + self.client = Client (broker.host, broker.port, self.spec) + self.client.start ({"LOGIN":"guest","PASSWORD":"guest"}) + self.channel = self.client.channel (1) + self.mclient = managementClient (self.spec) + self.mch = self.mclient.addChannel (self.channel) + self.mclient.syncWaitForStable (self.mch) + except socket.error, e: + print "Connect Error:", e + sys.exit (1) + + def getLink (self): + links = self.mclient.syncGetObjects (self.mch, "link") + for link in links: + if link.address == self.src.name (): + return link + return None + + def AddRoute (self, srcBroker, exchange, routingKey): + self.src = Broker (srcBroker) + mc = self.mclient + + brokers = mc.syncGetObjects (self.mch, "broker") + broker = brokers[0] + + link = self.getLink () + if link == None: + if _verbose: + print "Inter-broker link not found, creating..." + + connectArgs = {} + connectArgs["host"] = self.src.host + connectArgs["port"] = self.src.port + res = mc.syncCallMethod (self.mch, broker.id, broker.classKey, "connect", connectArgs) + if _verbose: + print "Connect method returned:", res.status, res.statusText + link = self.getLink () + + if link == None: + print "Protocol Error - Missing link ID" + sys.exit (1) + + bridges = mc.syncGetObjects (self.mch, "bridge") + for bridge in bridges: + if bridge.linkRef == link.id and bridge.dest == exchange and bridge.key == routingKey: + if not _quiet: + print "Duplicate Route - ignoring: %s(%s)" % (exchange, routingKey) + sys.exit (1) + sys.exit (0) + + if _verbose: + print "Creating inter-broker binding..." + bridgeArgs = {} + bridgeArgs["src"] = exchange + bridgeArgs["dest"] = exchange + bridgeArgs["key"] = routingKey + bridgeArgs["src_is_queue"] = 0 + bridgeArgs["src_is_local"] = 0 + res = mc.syncCallMethod (self.mch, link.id, link.classKey, "bridge", bridgeArgs) + if _verbose: + print "Bridge method returned:", res.status, res.statusText + + def DelRoute (self, srcBroker, exchange, routingKey): + self.src = Broker (srcBroker) + mc = self.mclient + + link = self.getLink () + if link == None: + if not _quiet: + print "No link found from %s to %s" % (self.src.name(), self.dest.name()) + sys.exit (1) + sys.exit (0) + + bridges = mc.syncGetObjects (self.mch, "bridge") + for bridge in bridges: + if bridge.linkRef == link.id and bridge.dest == exchange and bridge.key == routingKey: + if _verbose: + print "Closing bridge..." + res = mc.syncCallMethod (self.mch, bridge.id, bridge.classKey, "close") + if res.status != 0: + print "Error closing bridge: %d - %s" % (res.status, res.statusText) + sys.exit (1) + if len (bridges) == 1: + link = self.getLink () + if link == None: + sys.exit (0) + if _verbose: + print "Last bridge on link, closing link..." + res = mc.syncCallMethod (self.mch, link.id, link.classKey, "close") + if res.status != 0: + print "Error closing link: %d - %s" % (res.status, res.statusText) + sys.exit (1) + sys.exit (0) + if not _quiet: + print "Route not found" + sys.exit (1) + + def ListRoutes (self): + mc = self.mclient + links = mc.syncGetObjects (self.mch, "link") + bridges = mc.syncGetObjects (self.mch, "bridge") + + for bridge in bridges: + myLink = None + for link in links: + if bridge.linkRef == link.id: + myLink = link + break + if myLink != None: + print "%s %s %s %s" % (self.dest.name(), myLink.address, bridge.dest, bridge.key) + + def LoadRoutes (self, inFile): + pass + + def ClearAllRoutes (self): + mc = self.mclient + links = mc.syncGetObjects (self.mch, "link") + bridges = mc.syncGetObjects (self.mch, "bridge") + + for bridge in bridges: + if _verbose: + myLink = None + for link in links: + if bridge.linkRef == link.id: + myLink = link + break + if myLink != None: + print "Deleting Bridge: %s %s %s... " % (myLink.address, bridge.dest, bridge.key), + res = mc.syncCallMethod (self.mch, bridge.id, bridge.classKey, "close") + if res.status != 0: + print "Error: %d - %s" % (res.status, res.statusText) + elif _verbose: + print "Ok" + + links = mc.syncGetObjects (self.mch, "link") + for link in links: + if _verbose: + print "Deleting Link: %s... " % link.address, + res = mc.syncCallMethod (self.mch, link.id, link.classKey, "close") + if res.status != 0: + print "Error: %d - %s" % (res.status, res.statusText) + elif _verbose: + print "Ok" + +## +## Main Program +## + +try: + longOpts = ("verbose", "quiet", "spec-file=") + (optlist, cargs) = getopt.gnu_getopt (sys.argv[1:], "s:vq", longOpts) +except: + Usage () + +for opt in optlist: + if opt[0] == "-s" or opt[0] == "--spec-file": + _specpath = opt[1] + if opt[0] == "-v" or opt[0] == "--verbose": + _verbose = True + if opt[0] == "-q" or opt[0] == "--quiet": + _quiet = True + +nargs = len (cargs) +if nargs < 2: + Usage () + +cmd = cargs[0] +if cmd != "load": + rm = RouteManager (cargs[1]) + rm.ConnectToBroker () + +if cmd == "add" or cmd == "del": + if nargs != 5: + Usage () + if cmd == "add": + rm.AddRoute (cargs[2], cargs[3], cargs[4]) + else: + rm.DelRoute (cargs[2], cargs[3], cargs[4]) +else: + if nargs != 2: + Usage () + + if cmd == "list": + rm.ListRoutes () + #elif cmd == "load": + # rm.LoadRoutes (cargs[1]) + elif cmd == "flush": + rm.ClearAllRoutes () + else: + Usage () + diff --git a/qpid/python/mgmt-cli/main.py b/qpid/python/commands/qpid-tool index 76e1f25c14..0983e1b8af 100755 --- a/qpid/python/mgmt-cli/main.py +++ b/qpid/python/commands/qpid-tool @@ -23,11 +23,11 @@ import os import getopt import sys import socket -from cmd import Cmd -from managementdata import ManagementData -from shlex import split -from disp import Display -from qpid.peer import Closed +from cmd import Cmd +from qpid.managementdata import ManagementData +from shlex import split +from qpid.disp import Display +from qpid.peer import Closed class Mcli (Cmd): """ Management Command Interpreter """ @@ -104,7 +104,10 @@ class Mcli (Cmd): self.dataObject.do_list (data) def do_call (self, data): - self.dataObject.do_call (data) + try: + self.dataObject.do_call (data) + except ValueError, e: + print "ValueError:", e def do_EOF (self, data): print "quit" @@ -121,7 +124,10 @@ class Mcli (Cmd): self.dataObject.close () def Usage (): - print sys.argv[0], "[<target-host> [<tcp-port>]]" + print "Usage:", sys.argv[0], "[OPTIONS] [<target-host[:<tcp-port>]]" + print + print "Options:" + print " -s [ --spec-file ] PATH (/usr/share/amqp/amqp.0-10-preview.xml)" print sys.exit (1) @@ -131,36 +137,43 @@ def Usage (): # Get host name and port if specified on the command line try: - (optlist, cargs) = getopt.getopt (sys.argv[1:], 's:') + longOpts = ("spec-file=") + (optlist, cargs) = getopt.gnu_getopt (sys.argv[1:], 's:', longOpts) except: Usage () + sys.exit (1) -specpath = "/usr/share/amqp/amqp.0-10-preview.xml" -host = "localhost" -port = 5672 +_specpath = "/usr/share/amqp/amqp.0-10-preview.xml" +_host = "localhost" -if "s" in optlist: - specpath = optlist["s"] +for opt in optlist: + if opt[0] == "-s" or opt[0] == "--spec-file": + _specpath = opt[1] if len (cargs) > 0: - host = cargs[0] + _host = cargs[0] -if len (cargs) > 1: - port = int (cargs[1]) - -print ("Management Tool for QPID") disp = Display () # Attempt to make a connection to the target broker try: - data = ManagementData (disp, host, port, spec=specpath) + data = ManagementData (disp, _host, specfile=_specpath) except socket.error, e: - sys.exit (0) + print "Socket Error (%s):" % _host, e[1] + sys.exit (1) except Closed, e: if str(e).find ("Exchange not found") != -1: print "Management not enabled on broker: Use '-m yes' option on broker startup." - sys.exit (0) + sys.exit (1) +except IOError, e: + print "IOError: %d - %s: %s" % (e.errno, e.strerror, e.filename) + sys.exit (1) # Instantiate the CLI interpreter and launch it. cli = Mcli (data, disp) -cli.cmdloop () +print ("Management Tool for QPID") +try: + cli.cmdloop () +except Closed, e: + print "Connection to Broker Lost:", e + sys.exit (1) diff --git a/qpid/python/cpp_failing_0-10.txt b/qpid/python/cpp_failing_0-10.txt index 77031cad08..cc1e57bca0 100644 --- a/qpid/python/cpp_failing_0-10.txt +++ b/qpid/python/cpp_failing_0-10.txt @@ -1,6 +1,13 @@ tests.codec.FieldTableTestCase.test_field_table_decode tests.codec.FieldTableTestCase.test_field_table_multiple_name_value_pair tests.codec.FieldTableTestCase.test_field_table_name_value_pair -tests_0-10.alternate_exchange.AlternateExchangeTests.test_immediate -tests_0-10.broker.BrokerTests.test_closed_channel - +tests_0-10.execution.ExecutionTests.test_flush +tests_0-10.dtx.DtxTests.test_recover +tests_0-10.message.MessageTests.test_no_size +tests_0-10.message.MessageTests.test_qos_prefetch_count +tests_0-10.message.MessageTests.test_qos_prefetch_size +tests_0-10.message.MessageTests.test_recover +tests_0-10.message.MessageTests.test_recover_requeue +tests_0-10.testlib.TestBaseTest.testAssertEmptyFail +tests_0-10.testlib.TestBaseTest.testAssertEmptyPass +tests_0-10.testlib.TestBaseTest.testMessageProperties diff --git a/qpid/python/cpp_failing_0-10_preview.txt b/qpid/python/cpp_failing_0-10_preview.txt new file mode 100644 index 0000000000..91c2a7fce4 --- /dev/null +++ b/qpid/python/cpp_failing_0-10_preview.txt @@ -0,0 +1,6 @@ +tests.codec.FieldTableTestCase.test_field_table_decode +tests.codec.FieldTableTestCase.test_field_table_multiple_name_value_pair +tests.codec.FieldTableTestCase.test_field_table_name_value_pair +tests_0-10_preview.alternate_exchange.AlternateExchangeTests.test_immediate +tests_0-10_preview.broker.BrokerTests.test_closed_channel + diff --git a/qpid/python/hello-010-world b/qpid/python/hello-010-world new file mode 100755 index 0000000000..a15817cd9d --- /dev/null +++ b/qpid/python/hello-010-world @@ -0,0 +1,53 @@ +#!/usr/bin/env python + +import sys +from qpid.connection010 import Connection +from qpid.spec010 import load +from qpid.util import connect +from qpid.datatypes import Message, RangedSet +from qpid.log import enable, DEBUG, WARN + +if "-v" in sys.argv: + level = DEBUG +else: + level = WARN + +enable("qpid.io.ctl", level) +enable("qpid.io.cmd", level) + +spec = load("../specs/amqp.0-10.xml") +conn = Connection(connect("0.0.0.0", spec.port), spec) +conn.start(timeout=10) + +ssn = conn.session("my-session", timeout=10) + +ssn.queue_declare("asdf") + + +ssn.message_transfer("this", None, None, Message("testing...")) +props = ssn.delivery_properties(routing_key="asdf") +ssn.message_transfer("is", None, None, Message(props, "more testing...")) +ssn.message_transfer("a") +ssn.message_transfer("test") + +m1 = ssn.incoming("this").get(timeout=10) +print m1 +m2 = ssn.incoming("is").get(timeout=10) +print m2 +m3 = ssn.incoming("a").get(timeout=10) +print m3 +m4 = ssn.incoming("test").get(timeout=10) +print m4 + +print ssn.sender._completed, ssn.sender.next_id +ssn.sync(10) +print ssn.sender.segments + +ssn.channel.session_flush(completed=True) + +ssn.message_accept(RangedSet(m1.id, m2.id, m3.id, m4.id)) + +print ssn.queue_query("testing") + +ssn.close(timeout=10) +conn.close(timeout=10) diff --git a/qpid/python/mllib/dom.py b/qpid/python/mllib/dom.py index 7c759dbdd5..df2b88322a 100644 --- a/qpid/python/mllib/dom.py +++ b/qpid/python/mllib/dom.py @@ -185,7 +185,24 @@ class Comment(Leaf): ## Query Classes ## ########################################################################### -class View: +class Adder: + + def __add__(self, other): + return Sum(self, other) + +class Sum(Adder): + + def __init__(self, left, right): + self.left = left + self.right = right + + def __iter__(self): + for x in self.left: + yield x + for x in self.right: + yield x + +class View(Adder): def __init__(self, source): self.source = source diff --git a/qpid/python/qpid/assembler.py b/qpid/python/qpid/assembler.py new file mode 100644 index 0000000000..92bb0aa0f8 --- /dev/null +++ b/qpid/python/qpid/assembler.py @@ -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. +# + +from codec010 import StringCodec +from framer import * +from logging import getLogger + +log = getLogger("qpid.io.seg") + +class Segment: + + def __init__(self, first, last, type, track, channel, payload): + self.id = None + self.offset = None + self.first = first + self.last = last + self.type = type + self.track = track + self.channel = channel + self.payload = payload + + def decode(self, spec): + segs = spec["segment_type"] + choice = segs.choices[self.type] + return getattr(self, "decode_%s" % choice.name)(spec) + + def decode_control(self, spec): + sc = StringCodec(spec, self.payload) + return sc.read_control() + + def decode_command(self, spec): + sc = StringCodec(spec, self.payload) + hdr, cmd = sc.read_command() + cmd.id = self.id + return hdr, cmd + + def decode_header(self, spec): + sc = StringCodec(spec, self.payload) + values = [] + while len(sc.encoded) > 0: + values.append(sc.read_struct32()) + return values + + def decode_body(self, spec): + return self.payload + + def __str__(self): + return "%s%s %s %s %s %r" % (int(self.first), int(self.last), self.type, + self.track, self.channel, self.payload) + + def __repr__(self): + return str(self) + +class Assembler(Framer): + + def __init__(self, sock, max_payload = Frame.MAX_PAYLOAD): + Framer.__init__(self, sock) + self.max_payload = max_payload + self.fragments = {} + + def read_segment(self): + while True: + frame = self.read_frame() + + key = (frame.channel, frame.track) + seg = self.fragments.get(key) + if seg == None: + seg = Segment(frame.isFirstSegment(), frame.isLastSegment(), + frame.type, frame.track, frame.channel, "") + self.fragments[key] = seg + + seg.payload += frame.payload + + if frame.isLastFrame(): + self.fragments.pop(key) + log.debug("RECV %s", seg) + return seg + + def write_segment(self, segment): + remaining = segment.payload + + first = True + while first or remaining: + payload = remaining[:self.max_payload] + remaining = remaining[self.max_payload:] + + flags = 0 + if first: + flags |= FIRST_FRM + first = False + if not remaining: + flags |= LAST_FRM + if segment.first: + flags |= FIRST_SEG + if segment.last: + flags |= LAST_SEG + + frame = Frame(flags, segment.type, segment.track, segment.channel, + payload) + self.write_frame(frame) + + log.debug("SENT %s", segment) diff --git a/qpid/python/qpid/codec.py b/qpid/python/qpid/codec.py index b25de11f11..dfa74b6a2f 100644 --- a/qpid/python/qpid/codec.py +++ b/qpid/python/qpid/codec.py @@ -198,7 +198,7 @@ class Codec: if (o < 0 or o > 255): raise ValueError('Valid range of octet is [0,255]') - self.pack("!B", o) + self.pack("!B", int(o)) def decode_octet(self): """ @@ -215,7 +215,7 @@ class Codec: if (o < 0 or o > 65535): raise ValueError('Valid range of short int is [0,65535]: %s' % o) - self.pack("!H", o) + self.pack("!H", int(o)) def decode_short(self): """ @@ -233,7 +233,7 @@ class Codec: if (o < 0 or o > 4294967295): raise ValueError('Valid range of long int is [0,4294967295]') - self.pack("!L", o) + self.pack("!L", int(o)) def decode_long(self): """ @@ -265,6 +265,38 @@ class Codec: """ return self.unpack("!Q") + def encode_float(self, o): + self.pack("!f", o) + + def decode_float(self): + return self.unpack("!f") + + def encode_double(self, o): + self.pack("!d", o) + + def decode_double(self): + return self.unpack("!d") + + def encode_bin128(self, b): + for idx in range (0,16): + self.pack("!B", ord (b[idx])) + + def decode_bin128(self): + result = "" + for idx in range (0,16): + result = result + chr (self.unpack("!B")) + return result + + def encode_raw(self, len, b): + for idx in range (0,len): + self.pack("!B", b[idx]) + + def decode_raw(self, len): + result = "" + for idx in range (0,len): + result = result + chr (self.unpack("!B")) + return result + def enc_str(self, fmt, s): """ encodes a string 's' in network byte order as per format 'fmt' diff --git a/qpid/python/qpid/codec010.py b/qpid/python/qpid/codec010.py new file mode 100644 index 0000000000..0e4244fb75 --- /dev/null +++ b/qpid/python/qpid/codec010.py @@ -0,0 +1,244 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from packer import Packer +from datatypes import RangedSet, Struct + +class CodecException(Exception): pass + +class Codec(Packer): + + def __init__(self, spec): + self.spec = spec + + def write_void(self, v): + assert v == None + def read_void(self): + return None + + def write_bit(self, b): + if not b: raise ValueError(b) + def read_bit(self): + return True + + def read_uint8(self): + return self.unpack("!B") + def write_uint8(self, n): + return self.pack("!B", n) + + def read_int8(self): + return self.unpack("!b") + def write_int8(self, n): + self.pack("!b", n) + + def read_char(self): + return self.unpack("!c") + def write_char(self, c): + self.pack("!c", c) + + def read_boolean(self): + return self.read_uint8() != 0 + def write_boolean(self, b): + if b: n = 1 + else: n = 0 + self.write_uint8(n) + + + def read_uint16(self): + return self.unpack("!H") + def write_uint16(self, n): + self.pack("!H", n) + + def read_int16(self): + return self.unpack("!h") + def write_int16(self, n): + return self.unpack("!h", n) + + + def read_uint32(self): + return self.unpack("!L") + def write_uint32(self, n): + self.pack("!L", n) + + def read_int32(self): + return self.unpack("!l") + def write_int32(self, n): + self.pack("!l", n) + + def read_float(self): + return self.unpack("!f") + def write_float(self, f): + self.pack("!f", f) + + def read_sequence_no(self): + return self.read_uint32() + def write_sequence_no(self, n): + self.write_uint32(n) + + + def read_uint64(self): + return self.unpack("!Q") + def write_uint64(self, n): + self.pack("!Q", n) + + def read_int64(self): + return self.unpack("!q") + def write_int64(self, n): + self.pack("!q", n) + + def read_double(self): + return self.unpack("!d") + def write_double(self, d): + self.pack("!d", d) + + + def read_vbin8(self): + return self.read(self.read_uint8()) + def write_vbin8(self, b): + self.write_uint8(len(b)) + self.write(b) + + def read_str8(self): + return self.read_vbin8().decode("utf8") + def write_str8(self, s): + self.write_vbin8(s.encode("utf8")) + + def read_str16(self): + return self.read_vbin16().decode("utf8") + def write_str16(self, s): + self.write_vbin16(s.encode("utf8")) + + + def read_vbin16(self): + return self.read(self.read_uint16()) + def write_vbin16(self, b): + self.write_uint16(len(b)) + self.write(b) + + def read_sequence_set(self): + result = RangedSet() + size = self.read_uint16() + nranges = size/8 + while nranges > 0: + lower = self.read_sequence_no() + upper = self.read_sequence_no() + result.add(lower, upper) + nranges -= 1 + return result + def write_sequence_set(self, ss): + size = 8*len(ss.ranges) + self.write_uint16(size) + for range in ss.ranges: + self.write_sequence_no(range.lower) + self.write_sequence_no(range.upper) + + def read_vbin32(self): + return self.read(self.read_uint32()) + def write_vbin32(self, b): + self.write_uint32(len(b)) + self.write(b) + + def write_map(self, m): + sc = StringCodec(self.spec) + for k, v in m.items(): + type = self.spec.encoding(v.__class__) + if type == None: + raise CodecException("no encoding for %s" % v.__class__) + sc.write_str8(k) + sc.write_uint8(type.code) + type.encode(sc, v) + # XXX: need to put in count when CPP supports it + self.write_vbin32(sc.encoded) + def read_map(self): + sc = StringCodec(self.spec, self.read_vbin32()) + result = {} + while sc.encoded: + k = sc.read_str8() + code = sc.read_uint8() + type = self.spec.types[code] + v = type.decode(sc) + result[k] = v + return result + + def write_array(self, a): + pass + def read_array(self): + pass + + def read_struct32(self): + size = self.read_uint32() + code = self.read_uint16() + type = self.spec.structs[code] + fields = type.decode_fields(self) + return Struct(type, **fields) + def write_struct32(self, value): + sc = StringCodec(self.spec) + sc.write_uint16(value._type.code) + value._type.encode_fields(sc, value) + self.write_vbin32(sc.encoded) + + def read_control(self): + cntrl = self.spec.controls[self.read_uint16()] + return cntrl.decode(self) + def write_control(self, ctrl): + type = ctrl._type + self.write_uint16(type.code) + type.encode(self, ctrl) + + def read_command(self): + type = self.spec.commands[self.read_uint16()] + hdr = self.spec["session.header"].decode(self) + cmd = type.decode(self) + return hdr, cmd + def write_command(self, hdr, cmd): + self.write_uint16(cmd._type.code) + hdr._type.encode(self, hdr) + cmd._type.encode(self, cmd) + + def read_size(self, width): + if width > 0: + attr = "read_uint%d" % (width*8) + return getattr(self, attr)() + + def write_size(self, width, n): + if width > 0: + attr = "write_uint%d" % (width*8) + getattr(self, attr)(n) + + def write_uuid(self, s): + self.pack("16s", s) + + def read_uuid(self): + return self.unpack("16s") + + + +class StringCodec(Codec): + + def __init__(self, spec, encoded = ""): + Codec.__init__(self, spec) + self.encoded = encoded + + def write(self, s): + self.encoded += s + + def read(self, n): + result = self.encoded[:n] + self.encoded = self.encoded[n:] + return result diff --git a/qpid/python/qpid/connection010.py b/qpid/python/qpid/connection010.py new file mode 100644 index 0000000000..59aa41c88d --- /dev/null +++ b/qpid/python/qpid/connection010.py @@ -0,0 +1,181 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import datatypes, session +from threading import Thread, Condition, RLock +from util import wait +from framer import Closed +from assembler import Assembler, Segment +from codec010 import StringCodec +from session import Session +from invoker import Invoker +from spec010 import Control, Command +from exceptions import * +from logging import getLogger +import delegates + +class ChannelBusy(Exception): pass + +class ChannelsBusy(Exception): pass + +class SessionBusy(Exception): pass + +def client(*args): + return delegates.Client(*args) + +def server(*args): + return delegates.Server(*args) + +class Connection(Assembler): + + def __init__(self, sock, spec, delegate=client): + Assembler.__init__(self, sock) + self.spec = spec + self.track = self.spec["track"] + + self.lock = RLock() + self.attached = {} + self.sessions = {} + + self.condition = Condition() + self.opened = False + + self.thread = Thread(target=self.run) + self.thread.setDaemon(True) + + self.channel_max = 65535 + + self.delegate = delegate(self) + + def attach(self, name, ch, delegate, force=False): + self.lock.acquire() + try: + ssn = self.attached.get(ch.id) + if ssn is not None: + if ssn.name != name: + raise ChannelBusy(ch, ssn) + else: + ssn = self.sessions.get(name) + if ssn is None: + ssn = Session(name, self.spec, delegate=delegate) + self.sessions[name] = ssn + elif ssn.channel is not None: + if force: + del self.attached[ssn.channel.id] + ssn.channel = None + else: + raise SessionBusy(ssn) + self.attached[ch.id] = ssn + ssn.channel = ch + ch.session = ssn + return ssn + finally: + self.lock.release() + + def detach(self, name, ch): + self.lock.acquire() + try: + self.attached.pop(ch.id, None) + ssn = self.sessions.pop(name, None) + if ssn is not None: + ssn.channel = None + return ssn + finally: + self.lock.release() + + def __channel(self): + # XXX: ch 0? + for i in xrange(self.channel_max): + if not self.attached.has_key(i): + return i + else: + raise ChannelsBusy() + + def session(self, name, timeout=None, delegate=session.client): + self.lock.acquire() + try: + ch = Channel(self, self.__channel()) + ssn = self.attach(name, ch, delegate) + ssn.channel.session_attach(name) + if wait(ssn.condition, lambda: ssn.channel is not None, timeout): + return ssn + else: + self.detach(name, ch) + raise Timeout() + finally: + self.lock.release() + + def start(self, timeout=None): + self.delegate.start() + self.thread.start() + if not wait(self.condition, lambda: self.opened, timeout): + raise Timeout() + + def run(self): + # XXX: we don't really have a good way to exit this loop without + # getting the other end to kill the socket + while True: + try: + seg = self.read_segment() + except Closed: + break + self.delegate.received(seg) + + def close(self, timeout=None): + if not self.opened: return + Channel(self, 0).connection_close() + if not wait(self.condition, lambda: not self.opened, timeout): + raise Timeout() + self.thread.join(timeout=timeout) + + def __str__(self): + return "%s:%s" % self.sock.getsockname() + + def __repr__(self): + return str(self) + +log = getLogger("qpid.io.ctl") + +class Channel(Invoker): + + def __init__(self, connection, id): + self.connection = connection + self.id = id + self.session = None + + def resolve_method(self, name): + inst = self.connection.spec.instructions.get(name) + if inst is not None and isinstance(inst, Control): + return inst + else: + return None + + def invoke(self, type, args, kwargs): + ctl = type.new(args, kwargs) + sc = StringCodec(self.connection.spec) + sc.write_control(ctl) + self.connection.write_segment(Segment(True, True, type.segment_type, + type.track, self.id, sc.encoded)) + log.debug("SENT %s", ctl) + + def __str__(self): + return "%s[%s]" % (self.connection, self.id) + + def __repr__(self): + return str(self) diff --git a/qpid/python/qpid/datatypes.py b/qpid/python/qpid/datatypes.py new file mode 100644 index 0000000000..0893269174 --- /dev/null +++ b/qpid/python/qpid/datatypes.py @@ -0,0 +1,174 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT 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 threading + +class Struct: + + def __init__(self, _type, *args, **kwargs): + if len(args) > len(_type.fields): + raise TypeError("%s() takes at most %s arguments (%s given)" % + (_type.name, len(_type.fields), len(args))) + + self._type = _type + + idx = 0 + for field in _type.fields: + if idx < len(args): + arg = args[idx] + if kwargs.has_key(field.name): + raise TypeError("%s() got multiple values for keyword argument '%s'" % + (_type.name, field.name)) + elif kwargs.has_key(field.name): + arg = kwargs.pop(field.name) + else: + arg = field.default() + setattr(self, field.name, arg) + idx += 1 + + if kwargs: + unexpected = kwargs.keys()[0] + raise TypeError("%s() got an unexpected keyword argument '%s'" % + (_type.name, unexpected)) + + def __getitem__(self, name): + return getattr(self, name) + + def __setitem__(self, name, value): + if not hasattr(self, name): + raise AttributeError("'%s' object has no attribute '%s'" % + (self._type.name, name)) + setattr(self, name, value) + + def __repr__(self): + fields = [] + for f in self._type.fields: + v = self[f.name] + if f.type.is_present(v): + fields.append("%s=%r" % (f.name, v)) + return "%s(%s)" % (self._type.name, ", ".join(fields)) + +class Message: + + def __init__(self, *args): + if args: + self.body = args[-1] + else: + self.body = None + if len(args) > 1: + self.headers = args[:-1] + else: + self.headers = None + self.id = None + + def __repr__(self): + args = [] + if self.headers: + args.extend(map(repr, self.headers)) + if self.body: + args.append(repr(self.body)) + if self.id is not None: + args.append("id=%s" % self.id) + return "Message(%s)" % ", ".join(args) + +class Range: + + def __init__(self, lower, upper = None): + self.lower = lower + if upper is None: + self.upper = lower + else: + self.upper = upper + + def __contains__(self, n): + return self.lower <= n and n <= self.upper + + def touches(self, r): + # XXX + return (self.lower - 1 in r or + self.upper + 1 in r or + r.lower - 1 in self or + r.upper + 1 in self or + self.lower in r or + self.upper in r or + r.lower in self or + r.upper in self) + + def span(self, r): + return Range(min(self.lower, r.lower), max(self.upper, r.upper)) + + def __repr__(self): + return "%s-%s" % (self.lower, self.upper) + +class RangedSet: + + def __init__(self, *args): + self.ranges = [] + for n in args: + self.add(n) + + def __contains__(self, n): + for r in self.ranges: + if n in r: + return True + return False + + def add_range(self, range): + idx = 0 + while idx < len(self.ranges): + r = self.ranges[idx] + if range.touches(r): + del self.ranges[idx] + range = range.span(r) + elif range.upper < r.lower: + self.ranges.insert(idx, range) + return + else: + idx += 1 + self.ranges.append(range) + + def add(self, lower, upper = None): + self.add_range(Range(lower, upper)) + + def __repr__(self): + return str(self.ranges) + +class Future: + def __init__(self, initial=None, exception=Exception): + self.value = initial + self._error = None + self._set = threading.Event() + self.exception = exception + + def error(self, error): + self._error = error + self._set.set() + + def set(self, value): + self.value = value + self._set.set() + + def get(self, timeout=None): + self._set.wait(timeout) + if self._error != None: + raise self.exception(self._error) + return self.value + + def is_set(self): + return self._set.isSet() diff --git a/qpid/python/qpid/delegates.py b/qpid/python/qpid/delegates.py new file mode 100644 index 0000000000..9df2cd04bd --- /dev/null +++ b/qpid/python/qpid/delegates.py @@ -0,0 +1,140 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import os, connection010, session +from util import notify +from datatypes import RangedSet +from logging import getLogger + +log = getLogger("qpid.io.ctl") + +class Delegate: + + def __init__(self, connection, delegate=session.client): + self.connection = connection + self.spec = connection.spec + self.delegate = delegate + self.control = self.spec["track.control"].value + + def received(self, seg): + ssn = self.connection.attached.get(seg.channel) + if ssn is None: + ch = connection010.Channel(self.connection, seg.channel) + else: + ch = ssn.channel + + if seg.track == self.control: + ctl = seg.decode(self.spec) + log.debug("RECV %s", ctl) + attr = ctl._type.qname.replace(".", "_") + getattr(self, attr)(ch, ctl) + elif ssn is None: + ch.session_detached() + else: + ssn.received(seg) + + def connection_close(self, ch, close): + ch.connection_close_ok() + self.connection.sock.close() + + def connection_close_ok(self, ch, close_ok): + self.connection.opened = False + notify(self.connection.condition) + + def session_attach(self, ch, a): + try: + self.connection.attach(a.name, ch, self.delegate, a.force) + ch.session_attached(a.name) + except connection010.ChannelBusy: + ch.session_detached(a.name) + except connection010.SessionBusy: + ch.session_detached(a.name) + + def session_attached(self, ch, a): + notify(ch.session.condition) + + def session_detach(self, ch, d): + self.connection.detach(d.name, ch) + ch.session_detached(d.name) + + def session_detached(self, ch, d): + ssn = self.connection.detach(d.name, ch) + if ssn is not None: + notify(ch.session.condition) + + def session_command_point(self, ch, cp): + ssn = ch.session + ssn.receiver.next_id = cp.command_id + ssn.receiver.next_offset = cp.command_offset + + def session_completed(self, ch, cmp): + ch.session.sender.completed(cmp.commands) + notify(ch.session.condition) + + def session_flush(self, ch, f): + rcv = ch.session.receiver + if f.expected: + if rcv.next_id == None: + exp = None + else: + exp = RangedSet(rcv.next_id) + ch.session_expected(exp) + if f.confirmed: + ch.session_confirmed(rcv._completed) + if f.completed: + ch.session_completed(rcv._completed) + +class Server(Delegate): + + def start(self): + self.connection.read_header() + self.connection.write_header(self.spec.major, self.spec.minor) + connection010.Channel(self.connection, 0).connection_start() + + def connection_start_ok(self, ch, start_ok): + ch.connection_tune() + + def connection_tune_ok(self, ch, tune_ok): + pass + + def connection_open(self, ch, open): + self.connection.opened = True + ch.connection_open_ok() + notify(self.connection.condition) + +class Client(Delegate): + + PROPERTIES = {"product": "qpid python client", + "version": "development", + "platform": os.name} + + def start(self): + self.connection.write_header(self.spec.major, self.spec.minor) + self.connection.read_header() + + def connection_start(self, ch, start): + ch.connection_start_ok(client_properties=Client.PROPERTIES) + + def connection_tune(self, ch, tune): + ch.connection_tune_ok() + ch.connection_open() + + def connection_open_ok(self, ch, open_ok): + self.connection.opened = True + notify(self.connection.condition) diff --git a/qpid/python/mgmt-cli/disp.py b/qpid/python/qpid/disp.py index 5746a26e51..5746a26e51 100644 --- a/qpid/python/mgmt-cli/disp.py +++ b/qpid/python/qpid/disp.py diff --git a/qpid/python/qpid/exceptions.py b/qpid/python/qpid/exceptions.py new file mode 100644 index 0000000000..2136793d3b --- /dev/null +++ b/qpid/python/qpid/exceptions.py @@ -0,0 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +class Timeout(Exception): pass diff --git a/qpid/python/qpid/framer.py b/qpid/python/qpid/framer.py new file mode 100644 index 0000000000..11fe385d46 --- /dev/null +++ b/qpid/python/qpid/framer.py @@ -0,0 +1,140 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import struct, socket +from packer import Packer +from logging import getLogger + +raw = getLogger("qpid.io.raw") +frm = getLogger("qpid.io.frm") + +FIRST_SEG = 0x08 +LAST_SEG = 0x04 +FIRST_FRM = 0x02 +LAST_FRM = 0x01 + +class Frame: + + HEADER = "!2BHxBH4x" + MAX_PAYLOAD = 65535 - struct.calcsize(HEADER) + + def __init__(self, flags, type, track, channel, payload): + if len(payload) > Frame.MAX_PAYLOAD: + raise ValueError("max payload size exceeded: %s" % len(payload)) + self.flags = flags + self.type = type + self.track = track + self.channel = channel + self.payload = payload + + def isFirstSegment(self): + return bool(FIRST_SEG & self.flags) + + def isLastSegment(self): + return bool(LAST_SEG & self.flags) + + def isFirstFrame(self): + return bool(FIRST_FRM & self.flags) + + def isLastFrame(self): + return bool(LAST_FRM & self.flags) + + def __str__(self): + return "%s%s%s%s %s %s %s %r" % (int(self.isFirstSegment()), + int(self.isLastSegment()), + int(self.isFirstFrame()), + int(self.isLastFrame()), + self.type, + self.track, + self.channel, + self.payload) + +class Closed(Exception): pass + +class FramingError(Exception): pass + +class Framer(Packer): + + HEADER="!4s4B" + + def __init__(self, sock): + self.sock = sock + + def aborted(self): + return False + + def write(self, buf): + while buf: + try: + n = self.sock.send(buf) + except socket.timeout: + if self.aborted(): + raise Closed() + else: + continue + raw.debug("SENT %r", buf[:n]) + buf = buf[n:] + + def read(self, n): + data = "" + while len(data) < n: + try: + s = self.sock.recv(n - len(data)) + except socket.timeout: + if self.aborted(): + raise Closed() + else: + continue + except socket.error, e: + if data != "": + raise e + else: + raise Closed() + if len(s) == 0: + raise Closed() + data += s + raw.debug("RECV %r", s) + return data + + def read_header(self): + return self.unpack(Framer.HEADER) + + def write_header(self, major, minor): + self.pack(Framer.HEADER, "AMQP", 1, 1, major, minor) + + def write_frame(self, frame): + size = len(frame.payload) + struct.calcsize(Frame.HEADER) + track = frame.track & 0x0F + self.pack(Frame.HEADER, frame.flags, frame.type, size, track, frame.channel) + self.write(frame.payload) + # XXX: NOT 0-10 FINAL, TEMPORARY WORKAROUND for C++ + self.write("\xCE") + frm.debug("SENT %s", frame) + + def read_frame(self): + flags, type, size, track, channel = self.unpack(Frame.HEADER) + if flags & 0xF0: raise FramingError() + payload = self.read(size - struct.calcsize(Frame.HEADER)) + # XXX: NOT 0-10 FINAL, TEMPORARY WORKAROUND for C++ + end = self.read(1) + if end != "\xCE": + raise FramingError() + frame = Frame(flags, type, track, channel, payload) + frm.debug("RECV %s", frame) + return frame diff --git a/qpid/python/qpid/invoker.py b/qpid/python/qpid/invoker.py new file mode 100644 index 0000000000..9e6f6943d8 --- /dev/null +++ b/qpid/python/qpid/invoker.py @@ -0,0 +1,32 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +class Invoker: + + def resolve_method(self, name): + pass + + def __getattr__(self, name): + resolved = self.resolve_method(name) + if resolved == None: + raise AttributeError("%s instance has no attribute '%s'" % + (self.__class__.__name__, name)) + method = lambda *args, **kwargs: self.invoke(resolved, args, kwargs) + self.__dict__[name] = method + return method diff --git a/qpid/python/qpid/log.py b/qpid/python/qpid/log.py new file mode 100644 index 0000000000..1fd7d74136 --- /dev/null +++ b/qpid/python/qpid/log.py @@ -0,0 +1,28 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from logging import getLogger, StreamHandler, Formatter +from logging import DEBUG, INFO, WARN, ERROR, CRITICAL + +def enable(name=None, level=WARN, file=None): + log = getLogger(name) + handler = StreamHandler(file) + handler.setFormatter(Formatter("%(asctime)s %(levelname)s %(message)s")) + log.addHandler(handler) + log.setLevel(level) diff --git a/qpid/python/qpid/management.py b/qpid/python/qpid/management.py index 40de2a5298..d2dcacd62a 100644 --- a/qpid/python/qpid/management.py +++ b/qpid/python/qpid/management.py @@ -22,25 +22,27 @@ Management API for Qpid """ import qpid -import base64 +import struct import socket from threading import Thread from message import Message -from time import sleep +from time import time from qpid.client import Client from qpid.content import Content from cStringIO import StringIO from codec import Codec, EOF -from threading import Lock +from threading import Lock, Condition class SequenceManager: + """ Manage sequence numbers for asynchronous method calls """ def __init__ (self): self.lock = Lock () self.sequence = 0 self.pending = {} def reserve (self, data): + """ Reserve a unique sequence number """ self.lock.acquire () result = self.sequence self.sequence = self.sequence + 1 @@ -49,6 +51,7 @@ class SequenceManager: return result def release (self, seq): + """ Release a reserved sequence number """ data = None self.lock.acquire () if seq in self.pending: @@ -57,12 +60,269 @@ class SequenceManager: self.lock.release () return data -class ManagementMetadata: - """One instance of this class is created for each ManagedBroker. It - is used to store metadata from the broker which is needed for the - proper interpretation of received management content.""" + +class mgmtObject (object): + """ Generic object that holds the contents of a management object with its + attributes set as object attributes. """ + + def __init__ (self, classKey, timestamps, row): + self.classKey = classKey + self.timestamps = timestamps + for cell in row: + setattr (self, cell[0], cell[1]) + +class methodResult: + """ Object that contains the result of a method call """ + + def __init__ (self, status, sText, args): + self.status = status + self.statusText = sText + for arg in args: + setattr (self, arg, args[arg]) + +class managementChannel: + """ This class represents a connection to an AMQP broker. """ + + def __init__ (self, ch, topicCb, replyCb, cbContext): + """ Given a channel on an established AMQP broker connection, this method + opens a session and performs all of the declarations and bindings needed + to participate in the management protocol. """ + response = ch.session_open (detached_lifetime=300) + self.sessionId = response.session_id + self.topicName = "mgmt-%08x-%04x-%04x-%04x-%04x%08x" % struct.unpack ("!LHHHHL", response.session_id) + self.replyName = "repl-%08x-%04x-%04x-%04x-%04x%08x" % struct.unpack ("!LHHHHL", response.session_id) + self.qpidChannel = ch + self.tcb = topicCb + self.rcb = replyCb + self.context = cbContext + self.reqsOutstanding = 0 + + ch.queue_declare (queue=self.topicName, exclusive=1, auto_delete=1) + ch.queue_declare (queue=self.replyName, exclusive=1, auto_delete=1) + + ch.queue_bind (exchange="qpid.management", + queue=self.topicName, routing_key="mgmt.#") + ch.queue_bind (exchange="amq.direct", + queue=self.replyName, routing_key=self.replyName) + ch.message_subscribe (queue=self.topicName, destination="tdest") + ch.message_subscribe (queue=self.replyName, destination="rdest") + + ch.client.queue ("tdest").listen (self.topicCb) + ch.client.queue ("rdest").listen (self.replyCb) + + ch.message_flow_mode (destination="tdest", mode=1) + ch.message_flow (destination="tdest", unit=0, value=0xFFFFFFFF) + ch.message_flow (destination="tdest", unit=1, value=0xFFFFFFFF) + + ch.message_flow_mode (destination="rdest", mode=1) + ch.message_flow (destination="rdest", unit=0, value=0xFFFFFFFF) + ch.message_flow (destination="rdest", unit=1, value=0xFFFFFFFF) + + def topicCb (self, msg): + """ Receive messages via the topic queue on this channel. """ + self.tcb (self, msg) + + def replyCb (self, msg): + """ Receive messages via the reply queue on this channel. """ + self.rcb (self, msg) + + def send (self, exchange, msg): + self.qpidChannel.message_transfer (destination=exchange, content=msg) + + +class managementClient: + """ This class provides an API for access to management data on the AMQP + network. It implements the management protocol and manages the management + schemas as advertised by the various management agents in the network. """ + + CTRL_BROKER_INFO = 1 + CTRL_SCHEMA_LOADED = 2 + CTRL_USER = 3 + + SYNC_TIME = 10.0 + + #======================================================== + # User API - interacts with the class's user + #======================================================== + def __init__ (self, amqpSpec, ctrlCb=None, configCb=None, instCb=None, methodCb=None): + self.spec = amqpSpec + self.ctrlCb = ctrlCb + self.configCb = configCb + self.instCb = instCb + self.methodCb = methodCb + self.schemaCb = None + self.eventCb = None + self.channels = [] + self.seqMgr = SequenceManager () + self.schema = {} + self.packages = {} + self.cv = Condition () + self.syncInFlight = False + self.syncSequence = 0 + self.syncResult = None + + def schemaListener (self, schemaCb): + """ Optionally register a callback to receive details of the schema of + managed objects in the network. """ + self.schemaCb = schemaCb + + def eventListener (self, eventCb): + """ Optionally register a callback to receive events from managed objects + in the network. """ + self.eventCb = eventCb + + def addChannel (self, channel, cbContext=None): + """ Register a new channel. """ + mch = managementChannel (channel, self.topicCb, self.replyCb, cbContext) + + self.channels.append (mch) + codec = Codec (StringIO (), self.spec) + self.setHeader (codec, ord ('B')) + msg = Content (codec.stream.getvalue ()) + msg["content_type"] = "application/octet-stream" + msg["routing_key"] = "agent" + msg["reply_to"] = self.spec.struct ("reply_to") + msg["reply_to"]["exchange_name"] = "amq.direct" + msg["reply_to"]["routing_key"] = mch.replyName + mch.send ("qpid.management", msg) + return mch + + def removeChannel (self, mch): + """ Remove a previously added channel from management. """ + self.channels.remove (mch) + + def callMethod (self, channel, userSequence, objId, className, methodName, args=None): + """ Invoke a method on a managed object. """ + self.method (channel, userSequence, objId, className, methodName, args) + + def getObjects (self, channel, userSequence, className): + """ Request immediate content from broker """ + codec = Codec (StringIO (), self.spec) + self.setHeader (codec, ord ('G'), userSequence) + ft = {} + ft["_class"] = className + codec.encode_table (ft) + msg = Content (codec.stream.getvalue ()) + msg["content_type"] = "application/octet-stream" + msg["routing_key"] = "agent" + msg["reply_to"] = self.spec.struct ("reply_to") + msg["reply_to"]["exchange_name"] = "amq.direct" + msg["reply_to"]["routing_key"] = channel.replyName + channel.send ("qpid.management", msg) + + def syncWaitForStable (self, channel): + """ Synchronous (blocking) call to wait for schema stability on a channel """ + self.cv.acquire () + self.syncInFlight = True + starttime = time () + while channel.reqsOutstanding != 0: + self.cv.wait (self.SYNC_TIME) + if time () - starttime > self.SYNC_TIME: + self.cv.release () + raise RuntimeError ("Timed out waiting for response on channel") + self.cv.release () + + def syncCallMethod (self, channel, objId, className, methodName, args=None): + """ Synchronous (blocking) method call """ + self.cv.acquire () + self.syncInFlight = True + self.syncResult = None + self.syncSequence = self.seqMgr.reserve ("sync") + self.cv.release () + self.callMethod (channel, self.syncSequence, objId, className, methodName, args) + self.cv.acquire () + starttime = time () + while self.syncInFlight: + self.cv.wait (self.SYNC_TIME) + if time () - starttime > self.SYNC_TIME: + self.cv.release () + raise RuntimeError ("Timed out waiting for response on channel") + result = self.syncResult + self.cv.release () + return result + + def syncGetObjects (self, channel, className): + """ Synchronous (blocking) get call """ + self.cv.acquire () + self.syncInFlight = True + self.syncResult = [] + self.syncSequence = self.seqMgr.reserve ("sync") + self.cv.release () + self.getObjects (channel, self.syncSequence, className) + self.cv.acquire () + starttime = time () + while self.syncInFlight: + self.cv.wait (self.SYNC_TIME) + if time () - starttime > self.SYNC_TIME: + self.cv.release () + raise RuntimeError ("Timed out waiting for response on channel") + result = self.syncResult + self.cv.release () + return result + + #======================================================== + # Channel API - interacts with registered channel objects + #======================================================== + def topicCb (self, ch, msg): + """ Receive messages via the topic queue of a particular channel. """ + codec = Codec (StringIO (msg.content.body), self.spec) + hdr = self.checkHeader (codec) + if hdr == None: + raise ValueError ("outer header invalid"); + self.parse (ch, codec, hdr[0], hdr[1]) + msg.complete () + + def replyCb (self, ch, msg): + """ Receive messages via the reply queue of a particular channel. """ + codec = Codec (StringIO (msg.content.body), self.spec) + hdr = self.checkHeader (codec) + if hdr == None: + msg.complete () + return + + if hdr[0] == 'm': + self.handleMethodReply (ch, codec, hdr[1]) + elif hdr[0] == 'z': + self.handleCommandComplete (ch, codec, hdr[1]) + elif hdr[0] == 'b': + self.handleBrokerResponse (ch, codec) + elif hdr[0] == 'p': + self.handlePackageInd (ch, codec) + elif hdr[0] == 'q': + self.handleClassInd (ch, codec) + else: + self.parse (ch, codec, hdr[0], hdr[1]) + msg.complete () + + #======================================================== + # Internal Functions + #======================================================== + def setHeader (self, codec, opcode, seq = 0): + """ Compose the header of a management message. """ + codec.encode_octet (ord ('A')) + codec.encode_octet (ord ('M')) + codec.encode_octet (ord ('1')) + codec.encode_octet (opcode) + codec.encode_long (seq) + + def checkHeader (self, codec): + """ Check the header of a management message and extract the opcode and + class. """ + octet = chr (codec.decode_octet ()) + if octet != 'A': + return None + octet = chr (codec.decode_octet ()) + if octet != 'M': + return None + octet = chr (codec.decode_octet ()) + if octet != '1': + return None + opcode = chr (codec.decode_octet ()) + seq = codec.decode_long () + return (opcode, seq) def encodeValue (self, codec, value, typecode): + """ Encode, into the codec, a value based on its typecode. """ if typecode == 1: codec.encode_octet (int (value)) elif typecode == 2: @@ -85,10 +345,19 @@ class ManagementMetadata: codec.encode_longlong (long (value)) elif typecode == 11: # BOOL codec.encode_octet (int (value)) + elif typecode == 12: # FLOAT + codec.encode_float (float (value)) + elif typecode == 13: # DOUBLE + codec.encode_double (double (value)) + elif typecode == 14: # UUID + codec.encode_uuid (value) + elif typecode == 15: # FTABLE + codec.encode_table (value) else: raise ValueError ("Invalid type code: %d" % typecode) def decodeValue (self, codec, typecode): + """ Decode, from the codec, a value based on its typecode. """ if typecode == 1: data = codec.decode_octet () elif typecode == 2: @@ -111,17 +380,167 @@ class ManagementMetadata: data = codec.decode_longlong () elif typecode == 11: # BOOL data = codec.decode_octet () + elif typecode == 12: # FLOAT + data = codec.decode_float () + elif typecode == 13: # DOUBLE + data = codec.decode_double () + elif typecode == 14: # UUID + data = codec.decode_uuid () + elif typecode == 15: # FTABLE + data = codec.decode_table () else: raise ValueError ("Invalid type code: %d" % typecode) return data + + def incOutstanding (self, ch): + self.cv.acquire () + ch.reqsOutstanding = ch.reqsOutstanding + 1 + self.cv.release () + + def decOutstanding (self, ch): + self.cv.acquire () + ch.reqsOutstanding = ch.reqsOutstanding - 1 + if ch.reqsOutstanding == 0 and self.syncInFlight: + self.syncInFlight = False + self.cv.notify () + self.cv.release () + + if ch.reqsOutstanding == 0: + if self.ctrlCb != None: + self.ctrlCb (ch.context, self.CTRL_SCHEMA_LOADED, None) + + def handleMethodReply (self, ch, codec, sequence): + status = codec.decode_long () + sText = codec.decode_shortstr () + + data = self.seqMgr.release (sequence) + if data == None: + return + + (userSequence, classId, methodName) = data + args = {} + context = self.seqMgr.release (userSequence) + + if status == 0: + schemaClass = self.schema[classId] + ms = schemaClass['M'] + arglist = None + for mname in ms: + (mdesc, margs) = ms[mname] + if mname == methodName: + arglist = margs + if arglist == None: + return + + for arg in arglist: + if arg[2].find("O") != -1: + args[arg[0]] = self.decodeValue (codec, arg[1]) + + if context == "sync" and userSequence == self.syncSequence: + self.cv.acquire () + self.syncInFlight = False + self.syncResult = methodResult (status, sText, args) + self.cv.notify () + self.cv.release () + elif self.methodCb != None: + self.methodCb (ch.context, userSequence, status, sText, args) + + def handleCommandComplete (self, ch, codec, seq): + code = codec.decode_long () + text = codec.decode_shortstr () + data = (seq, code, text) + context = self.seqMgr.release (seq) + if context == "outstanding": + self.decOutstanding (ch) + elif context == "sync" and seq == self.syncSequence: + self.cv.acquire () + self.syncInFlight = False + self.cv.notify () + self.cv.release () + elif self.ctrlCb != None: + self.ctrlCb (ch.context, self.CTRL_USER, data) + + def handleBrokerResponse (self, ch, codec): + if self.ctrlCb != None: + uuid = codec.decode_uuid () + data = (uuid, ch.sessionId) + self.ctrlCb (ch.context, self.CTRL_BROKER_INFO, data) + + # Send a package request + sendCodec = Codec (StringIO (), self.spec) + seq = self.seqMgr.reserve ("outstanding") + self.setHeader (sendCodec, ord ('P'), seq) + self.incOutstanding (ch) + smsg = Content (sendCodec.stream.getvalue ()) + smsg["content_type"] = "application/octet-stream" + smsg["routing_key"] = "agent" + smsg["reply_to"] = self.spec.struct ("reply_to") + smsg["reply_to"]["exchange_name"] = "amq.direct" + smsg["reply_to"]["routing_key"] = ch.replyName + ch.send ("qpid.management", smsg) - def parseSchema (self, cls, codec): + def handlePackageInd (self, ch, codec): + pname = codec.decode_shortstr () + if pname not in self.packages: + self.packages[pname] = {} + + # Send a class request + sendCodec = Codec (StringIO (), self.spec) + seq = self.seqMgr.reserve ("outstanding") + self.setHeader (sendCodec, ord ('Q'), seq) + self.incOutstanding (ch) + sendCodec.encode_shortstr (pname) + smsg = Content (sendCodec.stream.getvalue ()) + smsg["content_type"] = "application/octet-stream" + smsg["routing_key"] = "agent" + smsg["reply_to"] = self.spec.struct ("reply_to") + smsg["reply_to"]["exchange_name"] = "amq.direct" + smsg["reply_to"]["routing_key"] = ch.replyName + ch.send ("qpid.management", smsg) + + def handleClassInd (self, ch, codec): + pname = codec.decode_shortstr () + cname = codec.decode_shortstr () + hash = codec.decode_bin128 () + if pname not in self.packages: + return + + if (cname, hash) not in self.packages[pname]: + # Send a schema request + sendCodec = Codec (StringIO (), self.spec) + self.setHeader (sendCodec, ord ('S')) + self.incOutstanding (ch) + sendCodec.encode_shortstr (pname) + sendCodec.encode_shortstr (cname) + sendCodec.encode_bin128 (hash) + smsg = Content (sendCodec.stream.getvalue ()) + smsg["content_type"] = "application/octet-stream" + smsg["routing_key"] = "agent" + smsg["reply_to"] = self.spec.struct ("reply_to") + smsg["reply_to"]["exchange_name"] = "amq.direct" + smsg["reply_to"]["routing_key"] = ch.replyName + ch.send ("qpid.management", smsg) + + def parseSchema (self, ch, codec): + """ Parse a received schema-description message. """ + self.decOutstanding (ch) + packageName = codec.decode_shortstr () className = codec.decode_shortstr () + hash = codec.decode_bin128 () configCount = codec.decode_short () instCount = codec.decode_short () methodCount = codec.decode_short () eventCount = codec.decode_short () + if packageName not in self.packages: + return + if (className, hash) in self.packages[packageName]: + return + + classKey = (packageName, className, hash) + if classKey in self.schema: + return + configs = [] insts = [] methods = {} @@ -213,25 +632,29 @@ class ManagementMetadata: args.append (arg) methods[mname] = (mdesc, args) + schemaClass = {} + schemaClass['C'] = configs + schemaClass['I'] = insts + schemaClass['M'] = methods + schemaClass['E'] = events + self.schema[classKey] = schemaClass - self.schema[(className,'C')] = configs - self.schema[(className,'I')] = insts - self.schema[(className,'M')] = methods - self.schema[(className,'E')] = events - - if self.broker.schema_cb != None: - self.broker.schema_cb[1] (self.broker.schema_cb[0], className, - configs, insts, methods, events) + if self.schemaCb != None: + self.schemaCb (ch.context, classKey, configs, insts, methods, events) - def parseContent (self, cls, codec): - if cls == 'C' and self.broker.config_cb == None: + def parseContent (self, ch, cls, codec, seq=0): + """ Parse a received content message. """ + if (cls == 'C' or (cls == 'B' and seq == 0)) and self.configCb == None: return - if cls == 'I' and self.broker.inst_cb == None: + if cls == 'I' and self.instCb == None: return - className = codec.decode_shortstr () + packageName = codec.decode_shortstr () + className = codec.decode_shortstr () + hash = codec.decode_bin128 () + classKey = (packageName, className, hash) - if (className,cls) not in self.schema: + if classKey not in self.schema: return row = [] @@ -241,184 +664,68 @@ class ManagementMetadata: timestamps.append (codec.decode_longlong ()) # Create Time timestamps.append (codec.decode_longlong ()) # Delete Time - for element in self.schema[(className,cls)][:]: - tc = element[1] - name = element[0] - data = self.decodeValue (codec, tc) - row.append ((name, data)) - - if cls == 'C': - self.broker.config_cb[1] (self.broker.config_cb[0], className, row, timestamps) + schemaClass = self.schema[classKey] + if cls == 'C' or cls == 'B': + for element in schemaClass['C'][:]: + tc = element[1] + name = element[0] + data = self.decodeValue (codec, tc) + row.append ((name, data)) + + if cls == 'I' or cls == 'B': + if cls == 'B': + start = 1 + else: + start = 0 + for element in schemaClass['I'][start:]: + tc = element[1] + name = element[0] + data = self.decodeValue (codec, tc) + row.append ((name, data)) + + if cls == 'C' or (cls == 'B' and seq != self.syncSequence): + self.configCb (ch.context, classKey, row, timestamps) + elif cls == 'B' and seq == self.syncSequence: + if timestamps[2] == 0: + obj = mgmtObject (classKey, timestamps, row) + self.syncResult.append (obj) elif cls == 'I': - self.broker.inst_cb[1] (self.broker.inst_cb[0], className, row, timestamps) - - def parse (self, codec, opcode, cls): - if opcode == 'S': - self.parseSchema (cls, codec) - - elif opcode == 'C': - self.parseContent (cls, codec) - + self.instCb (ch.context, classKey, row, timestamps) + + def parse (self, ch, codec, opcode, seq): + """ Parse a message received from the topic queue. """ + if opcode == 's': + self.parseSchema (ch, codec) + elif opcode == 'c': + self.parseContent (ch, 'C', codec) + elif opcode == 'i': + self.parseContent (ch, 'I', codec) + elif opcode == 'g': + self.parseContent (ch, 'B', codec, seq) else: raise ValueError ("Unknown opcode: %c" % opcode); - def __init__ (self, broker): - self.broker = broker - self.schema = {} - - -class ManagedBroker: - """An object of this class represents a connection (over AMQP) to a - single managed broker.""" - - mExchange = "qpid.management" - dExchange = "amq.direct" - - def setHeader (self, codec, opcode, cls = 0): - codec.encode_octet (ord ('A')) - codec.encode_octet (ord ('M')) - codec.encode_octet (ord ('0')) - codec.encode_octet (ord ('1')) - codec.encode_octet (opcode) - codec.encode_octet (cls) - - def checkHeader (self, codec): - octet = chr (codec.decode_octet ()) - if octet != 'A': - return None - octet = chr (codec.decode_octet ()) - if octet != 'M': - return None - octet = chr (codec.decode_octet ()) - if octet != '0': - return None - octet = chr (codec.decode_octet ()) - if octet != '1': - return None - opcode = chr (codec.decode_octet ()) - cls = chr (codec.decode_octet ()) - return (opcode, cls) - - def publish_cb (self, msg): - codec = Codec (StringIO (msg.content.body), self.spec) - - hdr = self.checkHeader (codec) - if hdr == None: - raise ValueError ("outer header invalid"); - - self.metadata.parse (codec, hdr[0], hdr[1]) - msg.complete () - - def reply_cb (self, msg): - codec = Codec (StringIO (msg.content.body), self.spec) - hdr = self.checkHeader (codec) - if hdr == None: - msg.complete () - return - if hdr[0] != 'R': - msg.complete () - return - - sequence = codec.decode_long () - status = codec.decode_long () - sText = codec.decode_shortstr () - - data = self.sequenceManager.release (sequence) - if data == None: - msg.complete () - return - - (userSequence, className, methodName) = data - args = {} - - if status == 0: - ms = self.metadata.schema[(className,'M')] - arglist = None - for mname in ms: - (mdesc, margs) = ms[mname] - if mname == methodName: - arglist = margs - if arglist == None: - msg.complete () - return - - for arg in arglist: - if arg[2].find("O") != -1: - args[arg[0]] = self.metadata.decodeValue (codec, arg[1]) - - if self.method_cb != None: - self.method_cb[1] (self.method_cb[0], userSequence, status, sText, args) - - msg.complete () - - def __init__ (self, - host = "localhost", - port = 5672, - username = "guest", - password = "guest", - specfile = "/usr/share/amqp/amqp.0-10-preview.xml"): - - self.spec = qpid.spec.load (specfile) - self.client = None - self.channel = None - self.queue = None - self.rqueue = None - self.qname = None - self.rqname = None - self.metadata = ManagementMetadata (self) - self.sequenceManager = SequenceManager () - self.connected = 0 - self.lastConnectError = None - - # Initialize the callback records - self.status_cb = None - self.schema_cb = None - self.config_cb = None - self.inst_cb = None - self.method_cb = None - - self.host = host - self.port = port - self.username = username - self.password = password - - def statusListener (self, context, callback): - self.status_cb = (context, callback) - - def schemaListener (self, context, callback): - self.schema_cb = (context, callback) - - def configListener (self, context, callback): - self.config_cb = (context, callback) - - def methodListener (self, context, callback): - self.method_cb = (context, callback) - - def instrumentationListener (self, context, callback): - self.inst_cb = (context, callback) - - def method (self, userSequence, objId, className, - methodName, args=None, packageName="qpid"): - codec = Codec (StringIO (), self.spec); - sequence = self.sequenceManager.reserve ((userSequence, className, methodName)) - self.setHeader (codec, ord ('M')) - codec.encode_long (sequence) # Method sequence id + def method (self, channel, userSequence, objId, classId, methodName, args): + """ Invoke a method on an object """ + codec = Codec (StringIO (), self.spec) + sequence = self.seqMgr.reserve ((userSequence, classId, methodName)) + self.setHeader (codec, ord ('M'), sequence) codec.encode_longlong (objId) # ID of object - #codec.encode_shortstr (self.rqname) # name of reply queue # Encode args according to schema - if (className,'M') not in self.metadata.schema: - self.sequenceManager.release (sequence) - raise ValueError ("Unknown class name: %s" % className) + if classId not in self.schema: + self.seqMgr.release (sequence) + raise ValueError ("Unknown class name: %s" % classId) - ms = self.metadata.schema[(className,'M')] - arglist = None + schemaClass = self.schema[classId] + ms = schemaClass['M'] + arglist = None for mname in ms: (mdesc, margs) = ms[mname] if mname == methodName: arglist = margs if arglist == None: - self.sequenceManager.release (sequence) + self.seqMgr.release (sequence) raise ValueError ("Unknown method name: %s" % methodName) for arg in arglist: @@ -427,65 +734,17 @@ class ManagedBroker: if arg[0] in args: value = args[arg[0]] if value == None: - self.sequenceManager.release (sequence) + self.seqMgr.release (sequence) raise ValueError ("Missing non-defaulted argument: %s" % arg[0]) - self.metadata.encodeValue (codec, value, arg[1]) + self.encodeValue (codec, value, arg[1]) + packageName = classId[0] + className = classId[1] msg = Content (codec.stream.getvalue ()) msg["content_type"] = "application/octet-stream" - msg["routing_key"] = "method." + packageName + "." + className + "." + methodName + msg["routing_key"] = "agent.method." + packageName + "." + \ + className + "." + methodName msg["reply_to"] = self.spec.struct ("reply_to") msg["reply_to"]["exchange_name"] = "amq.direct" - msg["reply_to"]["routing_key"] = self.rqname - self.channel.message_transfer (destination="qpid.management", content=msg) - - def isConnected (self): - return connected - - def start (self): - print "Connecting to broker %s:%d" % (self.host, self.port) - - try: - self.client = Client (self.host, self.port, self.spec) - self.client.start ({"LOGIN": self.username, "PASSWORD": self.password}) - self.channel = self.client.channel (1) - response = self.channel.session_open (detached_lifetime=300) - self.qname = "mgmt-" + base64.urlsafe_b64encode (response.session_id) - self.rqname = "reply-" + base64.urlsafe_b64encode (response.session_id) - - self.channel.queue_declare (queue=self.qname, exclusive=1, auto_delete=1) - self.channel.queue_declare (queue=self.rqname, exclusive=1, auto_delete=1) - - self.channel.queue_bind (exchange=ManagedBroker.mExchange, queue=self.qname, - routing_key="mgmt.#") - self.channel.queue_bind (exchange=ManagedBroker.dExchange, queue=self.rqname, - routing_key=self.rqname) - - self.channel.message_subscribe (queue=self.qname, destination="mdest") - self.channel.message_subscribe (queue=self.rqname, destination="rdest") - - self.queue = self.client.queue ("mdest") - self.queue.listen (self.publish_cb) - - self.channel.message_flow_mode (destination="mdest", mode=1) - self.channel.message_flow (destination="mdest", unit=0, value=0xFFFFFFFF) - self.channel.message_flow (destination="mdest", unit=1, value=0xFFFFFFFF) - - self.rqueue = self.client.queue ("rdest") - self.rqueue.listen (self.reply_cb) - - self.channel.message_flow_mode (destination="rdest", mode=1) - self.channel.message_flow (destination="rdest", unit=0, value=0xFFFFFFFF) - self.channel.message_flow (destination="rdest", unit=1, value=0xFFFFFFFF) - - self.connected = 1 - - except socket.error, e: - print "Socket Error:", e[1] - self.lastConnectError = e - raise - except: - raise - - def stop (self): - pass + msg["reply_to"]["routing_key"] = channel.replyName + channel.send ("qpid.management", msg) diff --git a/qpid/python/mgmt-cli/managementdata.py b/qpid/python/qpid/managementdata.py index e7233c98ae..ff43352247 100644 --- a/qpid/python/mgmt-cli/managementdata.py +++ b/qpid/python/qpid/managementdata.py @@ -19,10 +19,27 @@ # under the License. # -from qpid.management import ManagedBroker +import qpid +import socket +from qpid.management import managementChannel, managementClient from threading import Lock from disp import Display from shlex import split +from qpid.client import Client + +class Broker: + def __init__ (self, text): + colon = text.find (":") + if colon == -1: + host = text + self.port = 5672 + else: + host = text[:colon] + self.port = int (text[colon+1:]) + self.host = socket.gethostbyname (host) + + def name (self): + return self.host + ":" + str (self.port) class ManagementData: @@ -35,9 +52,10 @@ class ManagementData: # The only historical data it keeps are the high and low watermarks # for hi-lo statistics. # - # tables :== {<class-name>} + # tables :== {class-key} # {<obj-id>} # (timestamp, config-record, inst-record) + # class-key :== (<package-name>, <class-name>, <class-hash>) # timestamp :== (<last-interval-time>, <create-time>, <delete-time>) # config-record :== [element] # inst-record :== [element] @@ -59,6 +77,10 @@ class ManagementData: return displayId + self.baseId return displayId - 5000 + 0x8000000000000000L + def displayClassName (self, cls): + (packageName, className, hash) = cls + return packageName + "." + className + def dataHandler (self, context, className, list, timestamps): """ Callback for configuration and instrumentation data updates """ self.lock.acquire () @@ -104,6 +126,16 @@ class ManagementData: finally: self.lock.release () + def ctrlHandler (self, context, op, data): + if op == self.mclient.CTRL_BROKER_INFO: + pass + + def configHandler (self, context, className, list, timestamps): + self.dataHandler (0, className, list, timestamps); + + def instHandler (self, context, className, list, timestamps): + self.dataHandler (1, className, list, timestamps); + def methodReply (self, broker, sequence, status, sText, args): """ Callback for method-reply messages """ self.lock.acquire () @@ -120,13 +152,9 @@ class ManagementData: if className not in self.schema: self.schema[className] = (configs, insts, methods, events) - def __init__ (self, disp, host, port=5672, username="guest", password="guest", - spec="../../specs/amqp.0-10-preview.xml"): - self.broker = ManagedBroker (host, port, username, password, spec) - self.broker.configListener (0, self.dataHandler) - self.broker.instrumentationListener (1, self.dataHandler) - self.broker.methodListener (None, self.methodReply) - self.broker.schemaListener (None, self.schemaHandler) + def __init__ (self, disp, host, username="guest", password="guest", + specfile="../../specs/amqp.0-10-preview.xml"): + self.spec = qpid.spec.load (specfile) self.lock = Lock () self.tables = {} self.schema = {} @@ -135,24 +163,33 @@ class ManagementData: self.lastUnit = None self.methodSeq = 1 self.methodsPending = {} - self.broker.start () + + self.broker = Broker (host) + self.client = Client (self.broker.host, self.broker.port, self.spec) + self.client.start ({"LOGIN": username, "PASSWORD": password}) + self.channel = self.client.channel (1) + + self.mclient = managementClient (self.spec, self.ctrlHandler, self.configHandler, + self.instHandler, self.methodReply) + self.mclient.schemaListener (self.schemaHandler) + self.mch = self.mclient.addChannel (self.channel) def close (self): - self.broker.stop () + self.mclient.removeChannel (self.mch) def refName (self, oid): if oid == 0: return "NULL" return str (self.displayObjId (oid)) - def valueDisplay (self, className, key, value): + def valueDisplay (self, classKey, key, value): for kind in range (2): - schema = self.schema[className][kind] + schema = self.schema[classKey][kind] for item in schema: if item[0] == key: typecode = item[1] unit = item[2] - if typecode >= 1 and typecode <= 5: # numerics + if (typecode >= 1 and typecode <= 5) or typecode >= 12: # numerics if unit == None or unit == self.lastUnit: return str (value) else: @@ -176,6 +213,10 @@ class ManagementData: return "False" else: return "True" + elif typecode == 14: + return str (UUID (bytes=value)) + elif typecode == 15: + return str (value) return "*type-error*" def getObjIndex (self, className, config): @@ -191,6 +232,20 @@ class ManagementData: result = result + self.valueDisplay (className, key, val) return result + def getClassKey (self, className): + dotPos = className.find(".") + if dotPos == -1: + for key in self.schema: + if key[1] == className: + return key + else: + package = className[0:dotPos] + name = className[dotPos + 1:] + for key in self.schema: + if key[0] == package and key[1] == name: + return key + return None + def classCompletions (self, prefix): """ Provide a list of candidate class names for command completion """ self.lock.acquire () @@ -227,6 +282,14 @@ class ManagementData: return "reference" elif typecode == 11: return "boolean" + elif typecode == 12: + return "float" + elif typecode == 13: + return "double" + elif typecode == 14: + return "uuid" + elif typecode == 15: + return "field-table" else: raise ValueError ("Invalid type code: %d" % typecode) @@ -253,16 +316,16 @@ class ManagementData: return False return True - def listOfIds (self, className, tokens): + def listOfIds (self, classKey, tokens): """ Generate a tuple of object ids for a classname based on command tokens. """ list = [] if tokens[0] == "all": - for id in self.tables[className]: + for id in self.tables[classKey]: list.append (self.displayObjId (id)) elif tokens[0] == "active": - for id in self.tables[className]: - if self.tables[className][id][0][2] == 0: + for id in self.tables[classKey]: + if self.tables[classKey][id][0][2] == 0: list.append (self.displayObjId (id)) else: @@ -271,7 +334,7 @@ class ManagementData: if token.find ("-") != -1: ids = token.split("-", 2) for id in range (int (ids[0]), int (ids[1]) + 1): - if self.getClassForId (self.rawObjId (long (id))) == className: + if self.getClassForId (self.rawObjId (long (id))) == classKey: list.append (id) else: list.append (token) @@ -301,9 +364,12 @@ class ManagementData: deleted = deleted + 1 else: active = active + 1 - rows.append ((name, active, deleted)) - self.disp.table ("Management Object Types:", - ("ObjectType", "Active", "Deleted"), rows) + rows.append ((self.displayClassName (name), active, deleted)) + if len (rows) != 0: + self.disp.table ("Management Object Types:", + ("ObjectType", "Active", "Deleted"), rows) + else: + print "Waiting for next periodic update" finally: self.lock.release () @@ -311,24 +377,26 @@ class ManagementData: """ Generate a display of a list of objects in a class """ self.lock.acquire () try: - if className not in self.tables: + classKey = self.getClassKey (className) + if classKey == None: print ("Object type %s not known" % className) else: rows = [] - sorted = self.tables[className].keys () - sorted.sort () - for objId in sorted: - (ts, config, inst) = self.tables[className][objId] - createTime = self.disp.timestamp (ts[1]) - destroyTime = "-" - if ts[2] > 0: - destroyTime = self.disp.timestamp (ts[2]) - objIndex = self.getObjIndex (className, config) - row = (self.refName (objId), createTime, destroyTime, objIndex) - rows.append (row) - self.disp.table ("Objects of type %s" % className, - ("ID", "Created", "Destroyed", "Index"), - rows) + if classKey in self.tables: + sorted = self.tables[classKey].keys () + sorted.sort () + for objId in sorted: + (ts, config, inst) = self.tables[classKey][objId] + createTime = self.disp.timestamp (ts[1]) + destroyTime = "-" + if ts[2] > 0: + destroyTime = self.disp.timestamp (ts[2]) + objIndex = self.getObjIndex (classKey, config) + row = (self.refName (objId), createTime, destroyTime, objIndex) + rows.append (row) + self.disp.table ("Objects of type %s.%s" % (classKey[0], classKey[1]), + ("ID", "Created", "Destroyed", "Index"), + rows) finally: self.lock.release () @@ -343,57 +411,57 @@ class ManagementData: else: rootId = int (tokens[0]) - className = self.getClassForId (self.rawObjId (rootId)) + classKey = self.getClassForId (self.rawObjId (rootId)) remaining = tokens - if className == None: + if classKey == None: print "Id not known: %d" % int (tokens[0]) raise ValueError () else: - className = tokens[0] + classKey = self.getClassKey (tokens[0]) remaining = tokens[1:] - if className not in self.tables: - print "Class not known: %s" % className + if classKey not in self.tables: + print "Class not known: %s" % tokens[0] raise ValueError () - userIds = self.listOfIds (className, remaining) + userIds = self.listOfIds (classKey, remaining) if len (userIds) == 0: print "No object IDs supplied" raise ValueError () ids = [] for id in userIds: - if self.getClassForId (self.rawObjId (long (id))) == className: + if self.getClassForId (self.rawObjId (long (id))) == classKey: ids.append (self.rawObjId (long (id))) rows = [] timestamp = None - config = self.tables[className][ids[0]][1] + config = self.tables[classKey][ids[0]][1] for eIdx in range (len (config)): key = config[eIdx][0] if key != "id": row = ("config", key) for id in ids: if timestamp == None or \ - timestamp < self.tables[className][id][0][0]: - timestamp = self.tables[className][id][0][0] - (key, value) = self.tables[className][id][1][eIdx] - row = row + (self.valueDisplay (className, key, value),) + timestamp < self.tables[classKey][id][0][0]: + timestamp = self.tables[classKey][id][0][0] + (key, value) = self.tables[classKey][id][1][eIdx] + row = row + (self.valueDisplay (classKey, key, value),) rows.append (row) - inst = self.tables[className][ids[0]][2] + inst = self.tables[classKey][ids[0]][2] for eIdx in range (len (inst)): key = inst[eIdx][0] if key != "id": row = ("inst", key) for id in ids: - (key, value) = self.tables[className][id][2][eIdx] - row = row + (self.valueDisplay (className, key, value),) + (key, value) = self.tables[classKey][id][2][eIdx] + row = row + (self.valueDisplay (classKey, key, value),) rows.append (row) titleRow = ("Type", "Element") for id in ids: titleRow = titleRow + (self.refName (id),) - caption = "Object of type %s:" % className + caption = "Object of type %s.%s:" % (classKey[0], classKey[1]) if timestamp != None: caption = caption + " (last sample time: " + self.disp.timestamp (timestamp) + ")" self.disp.table (caption, titleRow, rows) @@ -409,8 +477,9 @@ class ManagementData: rows = [] sorted = self.schema.keys () sorted.sort () - for className in sorted: - tuple = self.schema[className] + for classKey in sorted: + tuple = self.schema[classKey] + className = classKey[0] + "." + classKey[1] row = (className, len (tuple[0]), len (tuple[1]), len (tuple[2]), len (tuple[3])) rows.append (row) self.disp.table ("Classes in Schema:", @@ -423,12 +492,13 @@ class ManagementData: """ Generate a display of details of the schema of a particular class """ self.lock.acquire () try: - if className not in self.schema: + classKey = self.getClassKey (className) + if classKey == None: print ("Class name %s not known" % className) raise ValueError () rows = [] - for config in self.schema[className][0]: + for config in self.schema[classKey][0]: name = config[0] if name != "id": typename = self.typeName(config[1]) @@ -446,7 +516,7 @@ class ManagementData: extra = extra + "MaxLen: " + str (config[8]) rows.append ((name, typename, unit, access, extra, desc)) - for config in self.schema[className][1]: + for config in self.schema[classKey][1]: name = config[0] if name != "id": typename = self.typeName(config[1]) @@ -455,10 +525,10 @@ class ManagementData: rows.append ((name, typename, unit, "", "", desc)) titles = ("Element", "Type", "Unit", "Access", "Notes", "Description") - self.disp.table ("Schema for class '%s':" % className, titles, rows) + self.disp.table ("Schema for class '%s.%s':" % (classKey[0], classKey[1]), titles, rows) - for mname in self.schema[className][2]: - (mdesc, args) = self.schema[className][2][mname] + for mname in self.schema[classKey][2]: + (mdesc, args) = self.schema[classKey][2][mname] caption = "\nMethod '%s' %s" % (mname, self.notNone (mdesc)) rows = [] for arg in args: @@ -485,25 +555,25 @@ class ManagementData: self.lock.release () def getClassForId (self, objId): - """ Given an object ID, return the class name for the referenced object """ - for className in self.tables: - if objId in self.tables[className]: - return className + """ Given an object ID, return the class key for the referenced object """ + for classKey in self.tables: + if objId in self.tables[classKey]: + return classKey return None def callMethod (self, userOid, methodName, args): self.lock.acquire () methodOk = True try: - className = self.getClassForId (self.rawObjId (userOid)) - if className == None: + classKey = self.getClassForId (self.rawObjId (userOid)) + if classKey == None: raise ValueError () - if methodName not in self.schema[className][2]: - print "Method '%s' not valid for class '%s'" % (methodName, className) + if methodName not in self.schema[classKey][2]: + print "Method '%s' not valid for class '%s.%s'" % (methodName, classKey[0], classKey[1]) raise ValueError () - schemaMethod = self.schema[className][2][methodName] + schemaMethod = self.schema[classKey][2][methodName] if len (args) != len (schemaMethod[1]): print "Wrong number of method args: Need %d, Got %d" % (len (schemaMethod[1]), len (args)) raise ValueError () @@ -519,8 +589,8 @@ class ManagementData: self.lock.release () if methodOk: # try: - self.broker.method (self.methodSeq, self.rawObjId (userOid), className, - methodName, namedArgs) + self.mclient.callMethod (self.mch, self.methodSeq, self.rawObjId (userOid), classKey, + methodName, namedArgs) # except ValueError, e: # print "Error invoking method:", e diff --git a/qpid/python/qpid/packer.py b/qpid/python/qpid/packer.py new file mode 100644 index 0000000000..22c16918dc --- /dev/null +++ b/qpid/python/qpid/packer.py @@ -0,0 +1,36 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import struct + +class Packer: + + def read(self, n): abstract + + def write(self, s): abstract + + def unpack(self, fmt): + values = struct.unpack(fmt, self.read(struct.calcsize(fmt))) + if len(values) == 1: + return values[0] + else: + return values + + def pack(self, fmt, *args): + self.write(struct.pack(fmt, *args)) diff --git a/qpid/python/qpid/queue.py b/qpid/python/qpid/queue.py index 00946a9156..ea8f00d091 100644 --- a/qpid/python/qpid/queue.py +++ b/qpid/python/qpid/queue.py @@ -35,10 +35,12 @@ class Queue(BaseQueue): def __init__(self, *args, **kwargs): BaseQueue.__init__(self, *args, **kwargs) + self.error = None self.listener = None self.thread = None - def close(self): + def close(self, error = None): + self.error = error self.put(Queue.END) def get(self, block = True, timeout = None): @@ -47,7 +49,7 @@ class Queue(BaseQueue): # this guarantees that any other waiting threads or any future # calls to get will also result in a Closed exception self.put(Queue.END) - raise Closed() + raise Closed(self.error) else: return result diff --git a/qpid/python/qpid/session.py b/qpid/python/qpid/session.py new file mode 100644 index 0000000000..bbe2b326d6 --- /dev/null +++ b/qpid/python/qpid/session.py @@ -0,0 +1,315 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from threading import Condition, RLock, currentThread +from invoker import Invoker +from datatypes import RangedSet, Struct, Future +from codec010 import StringCodec +from assembler import Segment +from queue import Queue +from datatypes import Message +from util import wait, notify +from exceptions import * +from logging import getLogger + +log = getLogger("qpid.io.cmd") + +class SessionDetached(Exception): pass + +def client(*args): + return Client(*args) + +def server(*args): + return Server(*args) + +class SessionException(Exception): pass + +INCOMPLETE = object() + +class Session(Invoker): + + def __init__(self, name, spec, auto_sync=True, timeout=10, delegate=client): + self.name = name + self.spec = spec + self.auto_sync = auto_sync + self.timeout = timeout + self.channel = None + + self.condition = Condition() + + self.send_id = True + self.receiver = Receiver(self) + self.sender = Sender(self) + + self.lock = RLock() + self._incoming = {} + self.results = {} + self.exceptions = [] + + self.assembly = None + + self.delegate = delegate(self) + + def incoming(self, destination): + self.lock.acquire() + try: + queue = self._incoming.get(destination) + if queue == None: + queue = Queue() + self._incoming[destination] = queue + return queue + finally: + self.lock.release() + + def error(self): + exc = self.exceptions[:] + if len(exc) == 1: + return exc[0] + else: + return tuple(exc) + + def sync(self, timeout=None): + if currentThread() == self.channel.connection.thread: + raise SessionException("deadlock detected") + self.channel.session_flush(completed=True) + last = self.sender.next_id - 1 + if not wait(self.condition, lambda: + last in self.sender._completed or self.exceptions, + timeout): + raise Timeout() + if self.exceptions: + raise SessionException(self.error()) + + def close(self, timeout=None): + self.channel.session_detach(self.name) + if not wait(self.condition, lambda: self.channel is None, timeout): + raise Timeout() + + def resolve_method(self, name): + cmd = self.spec.instructions.get(name) + if cmd is not None and cmd.track == self.spec["track.command"].value: + return cmd + else: + # XXX + for st in self.spec.structs.values(): + if st.name == name: + return st + if self.spec.structs_by_name.has_key(name): + return self.spec.structs_by_name[name] + return None + + def invoke(self, type, args, kwargs): + # XXX + if not hasattr(type, "track"): + return type.new(args, kwargs) + + if self.channel == None: + raise SessionDetached() + + if type.segments: + if len(args) == len(type.fields) + 1: + message = args[-1] + args = args[:-1] + else: + message = kwargs.pop("message", None) + else: + message = None + + cmd = type.new(args, kwargs) + sc = StringCodec(self.spec) + hdr = Struct(self.spec["session.header"]) + sc.write_command(hdr, cmd) + + seg = Segment(True, (message == None or + (message.headers == None and message.body == None)), + type.segment_type, type.track, self.channel.id, sc.encoded) + + if type.result: + result = Future(exception=SessionException) + self.results[self.sender.next_id] = result + + self.send(seg) + + log.debug("SENT %s %s %s", seg.id, hdr, cmd) + + if message != None: + if message.headers != None: + sc = StringCodec(self.spec) + for st in message.headers: + sc.write_struct32(st) + seg = Segment(False, message.body == None, self.spec["segment_type.header"].value, + type.track, self.channel.id, sc.encoded) + self.send(seg) + if message.body != None: + seg = Segment(False, True, self.spec["segment_type.body"].value, + type.track, self.channel.id, message.body) + self.send(seg) + + if type.result: + if self.auto_sync: + return result.get(self.timeout) + else: + return result + elif self.auto_sync: + self.sync(self.timeout) + + def received(self, seg): + self.receiver.received(seg) + if seg.first: + assert self.assembly == None + self.assembly = [] + self.assembly.append(seg) + if seg.last: + self.dispatch(self.assembly) + self.assembly = None + + def dispatch(self, assembly): + segments = assembly[:] + + hdr, cmd = assembly.pop(0).decode(self.spec) + log.debug("RECV %s %s %s", cmd.id, hdr, cmd) + + args = [] + + for st in cmd._type.segments: + if assembly: + seg = assembly[0] + if seg.type == st.segment_type: + args.append(seg.decode(self.spec)) + assembly.pop(0) + continue + args.append(None) + + assert len(assembly) == 0 + + attr = cmd._type.qname.replace(".", "_") + result = getattr(self.delegate, attr)(cmd, *args) + + if cmd._type.result: + self.execution_result(cmd.id, result) + + if result is not INCOMPLETE: + for seg in segments: + self.receiver.completed(seg) + + def send(self, seg): + self.sender.send(seg) + + def __str__(self): + return '<Session: %s, %s>' % (self.name, self.channel) + + def __repr__(self): + return str(self) + +class Receiver: + + def __init__(self, session): + self.session = session + self.next_id = None + self.next_offset = None + self._completed = RangedSet() + + def received(self, seg): + if self.next_id == None or self.next_offset == None: + raise Exception("todo") + seg.id = self.next_id + seg.offset = self.next_offset + if seg.last: + self.next_id += 1 + self.next_offset = 0 + else: + self.next_offset += len(seg.payload) + + def completed(self, seg): + if seg.id == None: + raise ValueError("cannot complete unidentified segment") + if seg.last: + self._completed.add(seg.id) + +class Sender: + + def __init__(self, session): + self.session = session + self.next_id = 0 + self.next_offset = 0 + self.segments = [] + self._completed = RangedSet() + + def send(self, seg): + seg.id = self.next_id + seg.offset = self.next_offset + if seg.last: + self.next_id += 1 + self.next_offset = 0 + else: + self.next_offset += len(seg.payload) + self.segments.append(seg) + if self.session.send_id: + self.session.send_id = False + self.session.channel.session_command_point(seg.id, seg.offset) + self.session.channel.connection.write_segment(seg) + + def completed(self, commands): + idx = 0 + while idx < len(self.segments): + seg = self.segments[idx] + if seg.id in commands: + del self.segments[idx] + else: + idx += 1 + for range in commands.ranges: + self._completed.add(range.lower, range.upper) + +class Delegate: + + def __init__(self, session): + self.session = session + + def execution_result(self, er): + future = self.session.results.pop(er.command_id) + future.set(er.value) + + def execution_exception(self, ex): + self.session.lock.acquire() + try: + self.session.exceptions.append(ex) + error = self.session.error() + for id in self.session.results: + f = self.session.results[id] + f.error(error) + self.session.results.clear() + + for q in self.session._incoming.values(): + q.close(error) + notify(self.session.condition) + finally: + self.session.lock.release() + +msg = getLogger("qpid.io.msg") + +class Client(Delegate): + + def message_transfer(self, cmd, headers, body): + m = Message(body) + m.headers = headers + m.id = cmd.id + messages = self.session.incoming(cmd.destination) + messages.put(m) + msg.debug("RECV %s", m) + return INCOMPLETE diff --git a/qpid/python/qpid/spec010.py b/qpid/python/qpid/spec010.py new file mode 100644 index 0000000000..4eb03008d0 --- /dev/null +++ b/qpid/python/qpid/spec010.py @@ -0,0 +1,631 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT 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, cPickle, datatypes +from codec010 import StringCodec +from util import mtime + +class Node: + + def __init__(self, children): + self.children = children + self.named = {} + self.docs = [] + self.rules = [] + + def register(self): + for ch in self.children: + ch.register(self) + + def resolve(self): + for ch in self.children: + ch.resolve() + + def __getitem__(self, name): + path = name.split(".", 1) + nd = self.named + for step in path: + nd = nd[step] + return nd + + def __iter__(self): + return iter(self.children) + +class Anonymous: + + def __init__(self, children): + self.children = children + + def register(self, node): + for ch in self.children: + ch.register(node) + + def resolve(self): + for ch in self.children: + ch.resolve() + +class Named: + + def __init__(self, name): + self.name = name + self.qname = None + + def register(self, node): + self.spec = node.spec + self.klass = node.klass + node.named[self.name] = self + if node.qname: + self.qname = "%s.%s" % (node.qname, self.name) + else: + self.qname = self.name + + def __str__(self): + return self.qname + + def __repr__(self): + return str(self) + +class Lookup: + + def lookup(self, name): + value = None + if self.klass: + try: + value = self.klass[name] + except KeyError: + pass + if not value: + value = self.spec[name] + return value + +class Coded: + + def __init__(self, code): + self.code = code + +class Constant(Named, Node): + + def __init__(self, name, value, children): + Named.__init__(self, name) + Node.__init__(self, children) + self.value = value + + def register(self, node): + Named.register(self, node) + node.constants.append(self) + Node.register(self) + +class Type(Named, Node): + + def __init__(self, name, children): + Named.__init__(self, name) + Node.__init__(self, children) + + def is_present(self, value): + return value != None + + def register(self, node): + Named.register(self, node) + Node.register(self) + +class Primitive(Coded, Type): + + def __init__(self, name, code, fixed, variable, children): + Coded.__init__(self, code) + Type.__init__(self, name, children) + self.fixed = fixed + self.variable = variable + + def register(self, node): + Type.register(self, node) + if self.code is not None: + self.spec.types[self.code] = self + + def is_present(self, value): + if self.fixed == 0: + return value + else: + return Type.is_present(self, value) + + def encode(self, codec, value): + getattr(codec, "write_%s" % self.name)(value) + + def decode(self, codec): + return getattr(codec, "read_%s" % self.name)() + +class Domain(Type, Lookup): + + def __init__(self, name, type, children): + Type.__init__(self, name, children) + self.type = type + self.choices = {} + + def resolve(self): + self.type = self.lookup(self.type) + Node.resolve(self) + + def encode(self, codec, value): + self.type.encode(codec, value) + + def decode(self, codec): + return self.type.decode(codec) + +class Choice(Named, Node): + + def __init__(self, name, value, children): + Named.__init__(self, name) + Node.__init__(self, children) + self.value = value + + def register(self, node): + Named.register(self, node) + node.choices[self.value] = self + Node.register(self) + +class Composite(Type, Coded): + + def __init__(self, name, code, size, pack, children): + Coded.__init__(self, code) + Type.__init__(self, name, children) + self.fields = [] + self.size = size + self.pack = pack + + def new(self, args, kwargs): + return datatypes.Struct(self, *args, **kwargs) + + def decode(self, codec): + codec.read_size(self.size) + return datatypes.Struct(self, **self.decode_fields(codec)) + + def decode_fields(self, codec): + flags = 0 + for i in range(self.pack): + flags |= (codec.read_uint8() << 8*i) + + result = {} + + for i in range(len(self.fields)): + f = self.fields[i] + if flags & (0x1 << i): + result[f.name] = f.type.decode(codec) + else: + result[f.name] = None + return result + + def encode(self, codec, value): + sc = StringCodec(self.spec) + self.encode_fields(sc, value) + codec.write_size(self.size, len(sc.encoded)) + codec.write(sc.encoded) + + def encode_fields(self, codec, values): + flags = 0 + for i in range(len(self.fields)): + f = self.fields[i] + if f.type.is_present(values[f.name]): + flags |= (0x1 << i) + for i in range(self.pack): + codec.write_uint8((flags >> 8*i) & 0xFF) + for i in range(len(self.fields)): + f = self.fields[i] + if flags & (0x1 << i): + f.type.encode(codec, values[f.name]) + +class Field(Named, Node, Lookup): + + def __init__(self, name, type, children): + Named.__init__(self, name) + Node.__init__(self, children) + self.type = type + self.exceptions = [] + + def default(self): + return None + + def register(self, node): + Named.register(self, node) + node.fields.append(self) + Node.register(self) + + def resolve(self): + self.type = self.lookup(self.type) + Node.resolve(self) + + def __str__(self): + return "%s: %s" % (self.qname, self.type.qname) + +class Struct(Composite): + + def register(self, node): + Composite.register(self, node) + if self.code is not None: + self.spec.structs[self.code] = self + self.spec.structs_by_name[self.name] = self + + def __str__(self): + fields = ",\n ".join(["%s: %s" % (f.name, f.type.qname) + for f in self.fields]) + return "%s {\n %s\n}" % (self.qname, fields) + +class Segment: + + def __init__(self): + self.segment_type = None + + def register(self, node): + self.spec = node.spec + self.klass = node.klass + node.segments.append(self) + Node.register(self) + +class Instruction(Composite, Segment): + + def __init__(self, name, code, children): + Composite.__init__(self, name, code, 0, 2, children) + Segment.__init__(self) + self.track = None + self.handlers = [] + + def __str__(self): + return "%s(%s)" % (self.qname, ", ".join(["%s: %s" % (f.name, f.type.qname) + for f in self.fields])) + + def register(self, node): + Composite.register(self, node) + self.spec.instructions[self.qname.replace(".", "_")] = self + +class Control(Instruction): + + def __init__(self, name, code, children): + Instruction.__init__(self, name, code, children) + self.response = None + + def register(self, node): + Instruction.register(self, node) + node.controls.append(self) + self.spec.controls[self.code] = self + self.segment_type = self.spec["segment_type.control"].value + self.track = self.spec["track.control"].value + +class Command(Instruction): + + def __init__(self, name, code, children): + Instruction.__init__(self, name, code, children) + self.result = None + self.exceptions = [] + self.segments = [] + + def register(self, node): + Instruction.register(self, node) + node.commands.append(self) + self.spec.commands[self.code] = self + self.segment_type = self.spec["segment_type.command"].value + self.track = self.spec["track.command"].value + +class Header(Segment, Node): + + def __init__(self, children): + Segment.__init__(self) + Node.__init__(self, children) + self.entries = [] + + def register(self, node): + Segment.register(self, node) + self.segment_type = self.spec["segment_type.header"].value + Node.register(self) + +class Entry(Lookup): + + def __init__(self, type): + self.type = type + + def register(self, node): + self.spec = node.spec + self.klass = node.klass + node.entries.append(self) + + def resolve(self): + self.type = self.lookup(self.type) + +class Body(Segment, Node): + + def __init__(self, children): + Segment.__init__(self) + Node.__init__(self, children) + + def register(self, node): + Segment.register(self, node) + self.segment_type = self.spec["segment_type.body"].value + Node.register(self) + + def resolve(self): pass + +class Class(Named, Coded, Node): + + def __init__(self, name, code, children): + Named.__init__(self, name) + Coded.__init__(self, code) + Node.__init__(self, children) + self.controls = [] + self.commands = [] + + def register(self, node): + Named.register(self, node) + self.klass = self + node.classes.append(self) + Node.register(self) + +class Doc: + + def __init__(self, type, title, text): + self.type = type + self.title = title + self.text = text + + def register(self, node): + node.docs.append(self) + + def resolve(self): pass + +class Role(Named, Node): + + def __init__(self, name, children): + Named.__init__(self, name) + Node.__init__(self, children) + + def register(self, node): + Named.register(self, node) + Node.register(self) + +class Rule(Named, Node): + + def __init__(self, name, children): + Named.__init__(self, name) + Node.__init__(self, children) + + def register(self, node): + Named.register(self, node) + node.rules.append(self) + Node.register(self) + +class Exception(Named, Node): + + def __init__(self, name, error_code, children): + Named.__init__(self, name) + Node.__init__(self, children) + self.error_code = error_code + + def register(self, node): + Named.register(self, node) + node.exceptions.append(self) + Node.register(self) + +class Spec(Node): + + ENCODINGS = { + basestring: "vbin16", + int: "int32", + long: "int64", + None.__class__: "void", + list: "list", + tuple: "list", + dict: "map" + } + + def __init__(self, major, minor, port, children): + Node.__init__(self, children) + self.major = major + self.minor = minor + self.port = port + self.constants = [] + self.classes = [] + self.types = {} + self.qname = None + self.spec = self + self.klass = None + self.instructions = {} + self.controls = {} + self.commands = {} + self.structs = {} + self.structs_by_name = {} + + def encoding(self, klass): + if Spec.ENCODINGS.has_key(klass): + return self.named[Spec.ENCODINGS[klass]] + for base in klass.__bases__: + result = self.encoding(base) + if result != None: + return result + +class Implement: + + def __init__(self, handle): + self.handle = handle + + def register(self, node): + node.handlers.append(self.handle) + + def resolve(self): pass + +class Response(Node): + + def __init__(self, name, children): + Node.__init__(self, children) + self.name = name + + def register(self, node): + Node.register(self) + +class Result(Node, Lookup): + + def __init__(self, type, children): + self.type = type + Node.__init__(self, children) + + def register(self, node): + node.result = self + self.qname = node.qname + self.klass = node.klass + self.spec = node.spec + Node.register(self) + + def resolve(self): + self.type = self.lookup(self.type) + Node.resolve(self) + +import mllib + +def num(s): + if s: return int(s, 0) + +REPLACE = {" ": "_", "-": "_"} +KEYWORDS = {"global": "global_", + "return": "return_"} + +def id(name): + name = str(name) + for key, val in REPLACE.items(): + name = name.replace(key, val) + try: + name = KEYWORDS[name] + except KeyError: + pass + return name + +class Loader: + + def __init__(self): + self.class_code = 0 + + def code(self, nd): + c = num(nd["@code"]) + if c is None: + return None + else: + return c | (self.class_code << 8) + + def list(self, q): + result = [] + for nd in q: + result.append(nd.dispatch(self)) + return result + + def children(self, n): + return self.list(n.query["#tag"]) + + def data(self, d): + return d.data + + def do_amqp(self, a): + return Spec(num(a["@major"]), num(a["@minor"]), num(a["@port"]), + self.children(a)) + + def do_type(self, t): + return Primitive(id(t["@name"]), self.code(t), num(t["@fixed-width"]), + num(t["@variable-width"]), self.children(t)) + + def do_constant(self, c): + return Constant(id(c["@name"]), num(c["@value"]), self.children(c)) + + def do_domain(self, d): + return Domain(id(d["@name"]), id(d["@type"]), self.children(d)) + + def do_enum(self, e): + return Anonymous(self.children(e)) + + def do_choice(self, c): + return Choice(id(c["@name"]), num(c["@value"]), self.children(c)) + + def do_class(self, c): + code = num(c["@code"]) + self.class_code = code + children = self.children(c) + children += self.list(c.query["command/result/struct"]) + self.class_code = 0 + return Class(id(c["@name"]), code, children) + + def do_doc(self, doc): + text = reduce(lambda x, y: x + y, self.list(doc.children)) + return Doc(doc["@type"], doc["@title"], text) + + def do_xref(self, x): + return x["@ref"] + + def do_role(self, r): + return Role(id(r["@name"]), self.children(r)) + + def do_control(self, c): + return Control(id(c["@name"]), self.code(c), self.children(c)) + + def do_rule(self, r): + return Rule(id(r["@name"]), self.children(r)) + + def do_implement(self, i): + return Implement(id(i["@handle"])) + + def do_response(self, r): + return Response(id(r["@name"]), self.children(r)) + + def do_field(self, f): + return Field(id(f["@name"]), id(f["@type"]), self.children(f)) + + def do_struct(self, s): + return Struct(id(s["@name"]), self.code(s), num(s["@size"]), + num(s["@pack"]), self.children(s)) + + def do_command(self, c): + return Command(id(c["@name"]), self.code(c), self.children(c)) + + def do_segments(self, s): + return Anonymous(self.children(s)) + + def do_header(self, h): + return Header(self.children(h)) + + def do_entry(self, e): + return Entry(id(e["@type"])) + + def do_body(self, b): + return Body(self.children(b)) + + def do_result(self, r): + type = r["@type"] + if not type: + type = r["struct/@name"] + return Result(id(type), self.list(r.query["#tag", lambda x: x.name != "struct"])) + + def do_exception(self, e): + return Exception(id(e["@name"]), id(e["@error-code"]), self.children(e)) + +def load(xml): + fname = xml + ".pcl" + if os.path.exists(fname) and mtime(fname) > mtime(__file__): + file = open(fname, "r") + s = cPickle.load(file) + file.close() + else: + doc = mllib.xml_parse(xml) + s = doc["amqp"].dispatch(Loader()) + s.register() + s.resolve() + file = open(fname, "w") + cPickle.dump(s, file) + file.close() + return s diff --git a/qpid/python/qpid/testlib.py b/qpid/python/qpid/testlib.py index 5174fe10f4..931face3a4 100644 --- a/qpid/python/qpid/testlib.py +++ b/qpid/python/qpid/testlib.py @@ -29,6 +29,11 @@ from getopt import getopt, GetoptError from qpid.content import Content from qpid.message import Message +#0-10 support +from qpid.connection010 import Connection +from qpid.spec010 import load +from qpid.util import connect + def findmodules(root): """Find potential python modules under directory root""" found = [] @@ -125,23 +130,29 @@ Options: if opt in ("-S", "--skip-self-test"): self.skip_self_test = True if opt in ("-F", "--spec-folder"): TestRunner.SPEC_FOLDER = value # Abbreviations for default settings. - if (self.specfile == "0-8"): - self.specfile = self.get_spec_file("amqp.0-8.xml") - elif (self.specfile == "0-9"): - self.specfile = self.get_spec_file("amqp.0-9.xml") - self.errata.append(self.get_spec_file("amqp-errata.0-9.xml")) - - if (self.specfile == None): - self._die("No XML specification provided") - print "Using specification from:", self.specfile - self.spec = qpid.spec.load(self.specfile, *self.errata) + if (self.specfile == "0-10"): + self.spec = load(self.get_spec_file("amqp.0-10.xml")) + else: + if (self.specfile == "0-8"): + self.specfile = self.get_spec_file("amqp.0-8.xml") + elif (self.specfile == "0-9"): + self.specfile = self.get_spec_file("amqp.0-9.xml") + self.errata.append(self.get_spec_file("amqp-errata.0-9.xml")) + + if (self.specfile == None): + self._die("No XML specification provided") + print "Using specification from:", self.specfile + + self.spec = qpid.spec.load(self.specfile, *self.errata) if len(self.tests) == 0: if not self.skip_self_test: self.tests=findmodules("tests") if self.use08spec(): self.tests+=findmodules("tests_0-8") - elif (self.spec.major == 0 and self.spec.minor == 10) or (self.spec.major == 99 and self.spec.minor == 0): + elif (self.spec.major == 99 and self.spec.minor == 0): + self.tests+=findmodules("tests_0-10_preview") + elif (self.spec.major == 0 and self.spec.minor == 10): self.tests+=findmodules("tests_0-10") else: self.tests+=findmodules("tests_0-9") @@ -181,7 +192,10 @@ Options: user = user or self.user password = password or self.password client = qpid.client.Client(host, port, spec) - client.start({"LOGIN": user, "PASSWORD": password}, tune_params=tune_params) + if self.use08spec(): + client.start({"LOGIN": user, "PASSWORD": password}, tune_params=tune_params) + else: + client.start("\x00" + user + "\x00" + password, mechanism="PLAIN", tune_params=tune_params) return client def get_spec_file(self, fname): @@ -330,3 +344,30 @@ class TestBase(unittest.TestCase): self.assertEqual("close", message.method.name) self.assertEqual(expectedCode, message.reply_code) +class TestBase010(unittest.TestCase): + """ + Base class for Qpid test cases. using the final 0-10 spec + """ + + def setUp(self): + spec = testrunner.spec + self.conn = Connection(connect(testrunner.host, testrunner.port), spec) + self.conn.start(timeout=10) + self.session = self.conn.session("test-session", timeout=10) + + def connect(self): + spec = testrunner.spec + conn = Connection(connect(testrunner.host, testrunner.port), spec) + conn.start(timeout=10) + return conn + + def tearDown(self): + if not self.session.error(): self.session.close(timeout=10) + self.conn.close(timeout=10) + + def subscribe(self, session=None, **keys): + session = session or self.session + consumer_tag = keys["destination"] + session.message_subscribe(**keys) + session.message_flow(destination=consumer_tag, unit=0, value=0xFFFFFFFF) + session.message_flow(destination=consumer_tag, unit=1, value=0xFFFFFFFF) diff --git a/qpid/python/qpid/util.py b/qpid/python/qpid/util.py new file mode 100644 index 0000000000..d03a9bd7e9 --- /dev/null +++ b/qpid/python/qpid/util.py @@ -0,0 +1,67 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import os, socket, time + +def connect(host, port): + sock = socket.socket() + sock.connect((host, port)) + sock.setblocking(1) + # XXX: we could use this on read, but we'd have to put write in a + # loop as well + # sock.settimeout(1) + return sock + +def listen(host, port, predicate = lambda: True, bound = lambda: None): + sock = socket.socket() + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind((host, port)) + bound() + sock.listen(5) + while predicate(): + s, a = sock.accept() + yield s + +def mtime(filename): + return os.stat(filename).st_mtime + +def wait(condition, predicate, timeout=None): + condition.acquire() + try: + passed = 0 + start = time.time() + while not predicate(): + if timeout is None: + condition.wait() + elif passed < timeout: + condition.wait(timeout - passed) + else: + return False + passed = time.time() - start + return True + finally: + condition.release() + +def notify(condition, action=lambda: None): + condition.acquire() + try: + action() + condition.notifyAll() + finally: + condition.release() diff --git a/qpid/python/run-tests b/qpid/python/run-tests index 90c0200d01..84b76ebfc1 100755 --- a/qpid/python/run-tests +++ b/qpid/python/run-tests @@ -18,8 +18,16 @@ # under the License. # -import sys +import sys, logging from qpid.testlib import testrunner +from qpid.log import enable, WARN, DEBUG + +if "-vv" in sys.argv: + level = DEBUG +else: + level = WARN + +enable("qpid", level) if not testrunner.run(): sys.exit(1) diff --git a/qpid/python/server010 b/qpid/python/server010 new file mode 100755 index 0000000000..0a75e2534e --- /dev/null +++ b/qpid/python/server010 @@ -0,0 +1,54 @@ +#!/usr/bin/env python + +from qpid import delegates +from qpid.connection010 import Connection +from qpid.util import connect, listen +from qpid.spec010 import load +from qpid.session import Client +from qpid.datatypes import Message +from qpid.log import enable, DEBUG, WARN + +import sys + +if "-v" in sys.argv: + level = DEBUG +else: + level = WARN + +enable("qpid", level) + +spec = load("../specs/amqp.0-10.xml") + +class Server: + + def connection(self, connection): + return delegates.Server(connection, self.session) + + def session(self, session): + session.auto_sync = False + return SessionDelegate(session) + +class SessionDelegate(Client): + + def __init__(self, session): + self.session = session + + def queue_declare(self, qd): + print "Queue %s declared..." % qd.queue + + def queue_query(self, qq): + return qq._type.result.type.new((qq.queue,), {}) + + def message_transfer(self, cmd, headers, body): + m = Message(body) + m.headers = headers + self.session.message_transfer(cmd.destination, cmd.accept_mode, cmd.acquire_mode, m) + + def message_accept(self, messages): + print "ACCEPT %s" % messages + +server = Server() + +for s in listen("0.0.0.0", spec.port): + conn = Connection(s, spec, server.connection) + conn.start(5) diff --git a/qpid/python/tests/__init__.py b/qpid/python/tests/__init__.py index 41dcc705e6..521e2f15c9 100644 --- a/qpid/python/tests/__init__.py +++ b/qpid/python/tests/__init__.py @@ -22,3 +22,9 @@ from codec import * from queue import * from spec import * +from framer import * +from assembler import * +from datatypes import * +from connection010 import * +from spec010 import * +from codec010 import * diff --git a/qpid/python/tests/assembler.py b/qpid/python/tests/assembler.py new file mode 100644 index 0000000000..b76924e59d --- /dev/null +++ b/qpid/python/tests/assembler.py @@ -0,0 +1,77 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from threading import * +from unittest import TestCase +from qpid.util import connect, listen +from qpid.assembler import * + +PORT = 1234 + +class AssemblerTest(TestCase): + + def setUp(self): + started = Event() + self.running = True + + def run(): + running = True + for s in listen("0.0.0.0", PORT, lambda: self.running, lambda: started.set()): + asm = Assembler(s) + try: + asm.write_header(*asm.read_header()[-2:]) + while True: + seg = asm.read_segment() + asm.write_segment(seg) + except Closed: + pass + + self.server = Thread(target=run) + self.server.setDaemon(True) + self.server.start() + + started.wait(3) + + def tearDown(self): + self.running = False + self.server.join() + + def test(self): + asm = Assembler(connect("0.0.0.0", PORT), max_payload = 1) + asm.write_header(0, 10) + asm.write_segment(Segment(True, False, 1, 2, 3, "TEST")) + asm.write_segment(Segment(False, True, 1, 2, 3, "ING")) + + assert asm.read_header() == ("AMQP", 1, 1, 0, 10) + + seg = asm.read_segment() + assert seg.first == True + assert seg.last == False + assert seg.type == 1 + assert seg.track == 2 + assert seg.channel == 3 + assert seg.payload == "TEST" + + seg = asm.read_segment() + assert seg.first == False + assert seg.last == True + assert seg.type == 1 + assert seg.track == 2 + assert seg.channel == 3 + assert seg.payload == "ING" diff --git a/qpid/python/tests/codec010.py b/qpid/python/tests/codec010.py new file mode 100644 index 0000000000..65d4525758 --- /dev/null +++ b/qpid/python/tests/codec010.py @@ -0,0 +1,57 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from unittest import TestCase +from qpid.spec010 import load +from qpid.codec010 import StringCodec +from qpid.testlib import testrunner + +class CodecTest(TestCase): + + def setUp(self): + self.spec = load(testrunner.get_spec_file("amqp.0-10.xml")) + + def check(self, type, value): + t = self.spec[type] + sc = StringCodec(self.spec) + t.encode(sc, value) + decoded = t.decode(sc) + assert decoded == value + + def testMapString(self): + self.check("map", {"string": "this is a test"}) + + def testMapInt(self): + self.check("map", {"int": 3}) + + def testMapLong(self): + self.check("map", {"long": 2**32}) + + def testMapNone(self): + self.check("map", {"none": None}) + + def testMapNested(self): + self.check("map", {"map": {"string": "nested test"}}) + + def testMapAll(self): + self.check("map", {"string": "this is a test", + "int": 3, + "long": 2**32, + "none": None, + "map": {"string": "nested map"}}) diff --git a/qpid/python/tests/connection010.py b/qpid/python/tests/connection010.py new file mode 100644 index 0000000000..a953e034a2 --- /dev/null +++ b/qpid/python/tests/connection010.py @@ -0,0 +1,136 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from threading import * +from unittest import TestCase +from qpid.util import connect, listen +from qpid.connection010 import * +from qpid.datatypes import Message +from qpid.testlib import testrunner +from qpid.delegates import Server +from qpid.queue import Queue +from qpid.spec010 import load +from qpid.session import Delegate + +PORT = 1234 + +class TestServer: + + def __init__(self, queue): + self.queue = queue + + def connection(self, connection): + return Server(connection, delegate=self.session) + + def session(self, session): + session.auto_sync = False + return TestSession(session, self.queue) + +class TestSession(Delegate): + + def __init__(self, session, queue): + self.session = session + self.queue = queue + + def queue_query(self, qq): + return qq._type.result.type.new((qq.queue,), {}) + + def message_transfer(self, cmd, header, body): + self.queue.put((cmd, header, body)) + +class ConnectionTest(TestCase): + + def setUp(self): + self.spec = load(testrunner.get_spec_file("amqp.0-10.xml")) + self.queue = Queue() + self.running = True + started = Event() + + def run(): + ts = TestServer(self.queue) + for s in listen("0.0.0.0", PORT, lambda: self.running, lambda: started.set()): + conn = Connection(s, self.spec, ts.connection) + try: + conn.start(5) + except Closed: + pass + + self.server = Thread(target=run) + self.server.setDaemon(True) + self.server.start() + + started.wait(3) + + def tearDown(self): + self.running = False + connect("0.0.0.0", PORT).close() + self.server.join(3) + + def test(self): + c = Connection(connect("0.0.0.0", PORT), self.spec) + c.start(10) + + ssn1 = c.session("test1", timeout=10) + ssn2 = c.session("test2", timeout=10) + + assert ssn1 == c.sessions["test1"] + assert ssn2 == c.sessions["test2"] + assert ssn1.channel != None + assert ssn2.channel != None + assert ssn1 in c.attached.values() + assert ssn2 in c.attached.values() + + ssn1.close(5) + + assert ssn1.channel == None + assert ssn1 not in c.attached.values() + assert ssn2 in c.sessions.values() + + ssn2.close(5) + + assert ssn2.channel == None + assert ssn2 not in c.attached.values() + assert ssn2 not in c.sessions.values() + + ssn = c.session("session", timeout=10) + + assert ssn.channel != None + assert ssn in c.sessions.values() + + destinations = ("one", "two", "three") + + for d in destinations: + ssn.message_transfer(d) + + for d in destinations: + cmd, header, body = self.queue.get(10) + assert cmd.destination == d + assert header == None + assert body == None + + msg = Message("this is a test") + ssn.message_transfer("four", message=msg) + cmd, header, body = self.queue.get(10) + assert cmd.destination == "four" + assert header == None + assert body == msg.body + + qq = ssn.queue_query("asdf") + assert qq.queue == "asdf" + c.close(5) diff --git a/qpid/python/tests/datatypes.py b/qpid/python/tests/datatypes.py new file mode 100644 index 0000000000..7844cf4d10 --- /dev/null +++ b/qpid/python/tests/datatypes.py @@ -0,0 +1,102 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from unittest import TestCase +from qpid.datatypes import * + +class RangedSetTest(TestCase): + + def check(self, ranges): + posts = [] + for range in ranges: + posts.append(range.lower) + posts.append(range.upper) + + sorted = posts[:] + sorted.sort() + + assert posts == sorted + + idx = 1 + while idx + 1 < len(posts): + assert posts[idx] + 1 != posts[idx+1] + idx += 2 + + def test(self): + rs = RangedSet() + + self.check(rs.ranges) + + rs.add(1) + + assert 1 in rs + assert 2 not in rs + assert 0 not in rs + self.check(rs.ranges) + + rs.add(2) + + assert 0 not in rs + assert 1 in rs + assert 2 in rs + assert 3 not in rs + self.check(rs.ranges) + + rs.add(0) + + assert -1 not in rs + assert 0 in rs + assert 1 in rs + assert 2 in rs + assert 3 not in rs + self.check(rs.ranges) + + rs.add(37) + + assert -1 not in rs + assert 0 in rs + assert 1 in rs + assert 2 in rs + assert 3 not in rs + assert 36 not in rs + assert 37 in rs + assert 38 not in rs + self.check(rs.ranges) + + rs.add(-1) + self.check(rs.ranges) + + rs.add(-3) + self.check(rs.ranges) + + rs.add(1, 20) + assert 21 not in rs + assert 20 in rs + self.check(rs.ranges) + + def testAddSelf(self): + a = RangedSet() + a.add(0, 8) + self.check(a.ranges) + a.add(0, 8) + self.check(a.ranges) + assert len(a.ranges) == 1 + range = a.ranges[0] + assert range.lower == 0 + assert range.upper == 8 diff --git a/qpid/python/tests/framer.py b/qpid/python/tests/framer.py new file mode 100644 index 0000000000..ea2e04e954 --- /dev/null +++ b/qpid/python/tests/framer.py @@ -0,0 +1,92 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from threading import * +from unittest import TestCase +from qpid.util import connect, listen +from qpid.framer import * + +PORT = 1234 + +class FramerTest(TestCase): + + def setUp(self): + self.running = True + started = Event() + def run(): + for s in listen("0.0.0.0", PORT, lambda: self.running, lambda: started.set()): + conn = Framer(s) + try: + conn.write_header(*conn.read_header()[-2:]) + while True: + frame = conn.read_frame() + conn.write_frame(frame) + except Closed: + pass + + self.server = Thread(target=run) + self.server.setDaemon(True) + self.server.start() + + started.wait(3) + + def tearDown(self): + self.running = False + self.server.join(3) + + def test(self): + c = Framer(connect("0.0.0.0", PORT)) + + c.write_header(0, 10) + assert c.read_header() == ("AMQP", 1, 1, 0, 10) + + c.write_frame(Frame(FIRST_FRM, 1, 2, 3, "THIS")) + c.write_frame(Frame(0, 1, 2, 3, "IS")) + c.write_frame(Frame(0, 1, 2, 3, "A")) + c.write_frame(Frame(LAST_FRM, 1, 2, 3, "TEST")) + + f = c.read_frame() + assert f.flags & FIRST_FRM + assert not (f.flags & LAST_FRM) + assert f.type == 1 + assert f.track == 2 + assert f.channel == 3 + assert f.payload == "THIS" + + f = c.read_frame() + assert f.flags == 0 + assert f.type == 1 + assert f.track == 2 + assert f.channel == 3 + assert f.payload == "IS" + + f = c.read_frame() + assert f.flags == 0 + assert f.type == 1 + assert f.track == 2 + assert f.channel == 3 + assert f.payload == "A" + + f = c.read_frame() + assert f.flags & LAST_FRM + assert not (f.flags & FIRST_FRM) + assert f.type == 1 + assert f.track == 2 + assert f.channel == 3 + assert f.payload == "TEST" diff --git a/qpid/python/tests/spec010.py b/qpid/python/tests/spec010.py new file mode 100644 index 0000000000..4161dc060f --- /dev/null +++ b/qpid/python/tests/spec010.py @@ -0,0 +1,64 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from unittest import TestCase +from qpid.spec010 import load +from qpid.codec010 import Codec, StringCodec +from qpid.testlib import testrunner +from qpid.datatypes import Struct + +class SpecTest(TestCase): + + def setUp(self): + self.spec = load(testrunner.get_spec_file("amqp.0-10.xml")) + + def testSessionHeader(self): + hdr = self.spec["session.header"] + sc = StringCodec(self.spec) + hdr.encode(sc, Struct(hdr, sync=True)) + assert sc.encoded == "\x01\x01" + + sc = StringCodec(self.spec) + hdr.encode(sc, Struct(hdr, sync=False)) + assert sc.encoded == "\x01\x00" + + def encdec(self, type, value): + sc = StringCodec(self.spec) + type.encode(sc, value) + decoded = type.decode(sc) + return decoded + + def testMessageProperties(self): + mp = self.spec["message.message_properties"] + rt = self.spec["message.reply_to"] + + props = Struct(mp, content_length=0xDEADBEEF, + reply_to=Struct(rt, exchange="the exchange name", + routing_key="the routing key")) + dec = self.encdec(mp, props) + assert props.content_length == dec.content_length + assert props.reply_to.exchange == dec.reply_to.exchange + assert props.reply_to.routing_key == dec.reply_to.routing_key + + def testMessageSubscribe(self): + ms = self.spec["message.subscribe"] + cmd = Struct(ms, exclusive=True, destination="this is a test") + dec = self.encdec(self.spec["message.subscribe"], cmd) + assert cmd.exclusive == dec.exclusive + assert cmd.destination == dec.destination diff --git a/qpid/python/tests_0-10/alternate_exchange.py b/qpid/python/tests_0-10/alternate_exchange.py index 83f8d85811..c177c3deb7 100644 --- a/qpid/python/tests_0-10/alternate_exchange.py +++ b/qpid/python/tests_0-10/alternate_exchange.py @@ -16,12 +16,13 @@ # specific language governing permissions and limitations # under the License. # -from qpid.client import Client, Closed +import traceback from qpid.queue import Empty -from qpid.content import Content -from qpid.testlib import testrunner, TestBase +from qpid.datatypes import Message +from qpid.testlib import TestBase010 +from qpid.session import SessionException -class AlternateExchangeTests(TestBase): +class AlternateExchangeTests(TestBase010): """ Tests for the new mechanism for message returns introduced in 0-10 and available in 0-9 for preview @@ -31,36 +32,42 @@ class AlternateExchangeTests(TestBase): """ Test that unroutable messages are delivered to the alternate-exchange if specified """ - channel = self.channel + session = self.session #create an exchange with an alternate defined - channel.exchange_declare(exchange="secondary", type="fanout") - channel.exchange_declare(exchange="primary", type="direct", alternate_exchange="secondary") + session.exchange_declare(exchange="secondary", type="fanout") + session.exchange_declare(exchange="primary", type="direct", alternate_exchange="secondary") #declare, bind (to the alternate exchange) and consume from a queue for 'returned' messages - channel.queue_declare(queue="returns", exclusive=True, auto_delete=True) - channel.queue_bind(queue="returns", exchange="secondary") - self.subscribe(destination="a", queue="returns") - returned = self.client.queue("a") + session.queue_declare(queue="returns", exclusive=True, auto_delete=True) + session.exchange_bind(queue="returns", exchange="secondary") + session.message_subscribe(destination="a", queue="returns") + session.message_flow(destination="a", unit=0, value=0xFFFFFFFF) + session.message_flow(destination="a", unit=1, value=0xFFFFFFFF) + returned = session.incoming("a") #declare, bind (to the primary exchange) and consume from a queue for 'processed' messages - channel.queue_declare(queue="processed", exclusive=True, auto_delete=True) - channel.queue_bind(queue="processed", exchange="primary", routing_key="my-key") - self.subscribe(destination="b", queue="processed") - processed = self.client.queue("b") + session.queue_declare(queue="processed", exclusive=True, auto_delete=True) + session.exchange_bind(queue="processed", exchange="primary", binding_key="my-key") + session.message_subscribe(destination="b", queue="processed") + session.message_flow(destination="b", unit=0, value=0xFFFFFFFF) + session.message_flow(destination="b", unit=1, value=0xFFFFFFFF) + processed = session.incoming("b") #publish to the primary exchange #...one message that makes it to the 'processed' queue: - channel.message_transfer(destination="primary", content=Content("Good", properties={'routing_key':"my-key"})) + dp=self.session.delivery_properties(routing_key="my-key") + session.message_transfer(destination="primary", message=Message(dp, "Good")) #...and one that does not: - channel.message_transfer(destination="primary", content=Content("Bad", properties={'routing_key':"unused-key"})) + dp=self.session.delivery_properties(routing_key="unused-key") + session.message_transfer(destination="primary", message=Message(dp, "Bad")) #delete the exchanges - channel.exchange_delete(exchange="primary") - channel.exchange_delete(exchange="secondary") + session.exchange_delete(exchange="primary") + session.exchange_delete(exchange="secondary") #verify behaviour - self.assertEqual("Good", processed.get(timeout=1).content.body) - self.assertEqual("Bad", returned.get(timeout=1).content.body) + self.assertEqual("Good", processed.get(timeout=1).body) + self.assertEqual("Bad", returned.get(timeout=1).body) self.assertEmpty(processed) self.assertEmpty(returned) @@ -68,81 +75,51 @@ class AlternateExchangeTests(TestBase): """ Test that messages in a queue being deleted are delivered to the alternate-exchange if specified """ - channel = self.channel + session = self.session #set up a 'dead letter queue': - channel.exchange_declare(exchange="dlq", type="fanout") - channel.queue_declare(queue="deleted", exclusive=True, auto_delete=True) - channel.queue_bind(exchange="dlq", queue="deleted") - self.subscribe(destination="dlq", queue="deleted") - dlq = self.client.queue("dlq") + session.exchange_declare(exchange="dlq", type="fanout") + session.queue_declare(queue="deleted", exclusive=True, auto_delete=True) + session.exchange_bind(exchange="dlq", queue="deleted") + session.message_subscribe(destination="dlq", queue="deleted") + session.message_flow(destination="dlq", unit=0, value=0xFFFFFFFF) + session.message_flow(destination="dlq", unit=1, value=0xFFFFFFFF) + dlq = session.incoming("dlq") #create a queue using the dlq as its alternate exchange: - channel.queue_declare(queue="delete-me", alternate_exchange="dlq") + session.queue_declare(queue="delete-me", alternate_exchange="dlq") #send it some messages: - channel.message_transfer(content=Content("One", properties={'routing_key':"delete-me"})) - channel.message_transfer(content=Content("Two", properties={'routing_key':"delete-me"})) - channel.message_transfer(content=Content("Three", properties={'routing_key':"delete-me"})) + dp=self.session.delivery_properties(routing_key="delete-me") + session.message_transfer(message=Message(dp, "One")) + session.message_transfer(message=Message(dp, "Two")) + session.message_transfer(message=Message(dp, "Three")) #delete it: - channel.queue_delete(queue="delete-me") + session.queue_delete(queue="delete-me") #delete the dlq exchange: - channel.exchange_delete(exchange="dlq") + session.exchange_delete(exchange="dlq") #check the messages were delivered to the dlq: - self.assertEqual("One", dlq.get(timeout=1).content.body) - self.assertEqual("Two", dlq.get(timeout=1).content.body) - self.assertEqual("Three", dlq.get(timeout=1).content.body) + self.assertEqual("One", dlq.get(timeout=1).body) + self.assertEqual("Two", dlq.get(timeout=1).body) + self.assertEqual("Three", dlq.get(timeout=1).body) self.assertEmpty(dlq) - - def test_immediate(self): - """ - Test that messages in a queue being deleted are delivered to the alternate-exchange if specified - """ - channel = self.channel - #set up a 'dead letter queue': - channel.exchange_declare(exchange="dlq", type="fanout") - channel.queue_declare(queue="immediate", exclusive=True, auto_delete=True) - channel.queue_bind(exchange="dlq", queue="immediate") - self.subscribe(destination="dlq", queue="immediate") - dlq = self.client.queue("dlq") - - #create a queue using the dlq as its alternate exchange: - channel.queue_declare(queue="no-consumers", alternate_exchange="dlq", exclusive=True, auto_delete=True) - #send it some messages: - #TODO: WE HAVE LOST THE IMMEDIATE FLAG; FIX THIS ONCE ITS BACK - channel.message_transfer(content=Content("no one wants me", properties={'routing_key':"no-consumers"})) - - #check the messages were delivered to the dlq: - self.assertEqual("no one wants me", dlq.get(timeout=1).content.body) - self.assertEmpty(dlq) - - #cleanup: - channel.queue_delete(queue="no-consumers") - channel.exchange_delete(exchange="dlq") - - def test_delete_while_used_by_queue(self): """ Ensure an exchange still in use as an alternate-exchange for a queue can't be deleted """ - channel = self.channel - channel.exchange_declare(exchange="alternate", type="fanout") - channel.queue_declare(queue="q", exclusive=True, auto_delete=True, alternate_exchange="alternate") + session = self.session + session.exchange_declare(exchange="alternate", type="fanout") + + session2 = self.conn.session("alternate", 2) + session2.queue_declare(queue="q", exclusive=True, auto_delete=True, alternate_exchange="alternate") try: - channel.exchange_delete(exchange="alternate") + session2.exchange_delete(exchange="alternate") self.fail("Expected deletion of in-use alternate-exchange to fail") - except Closed, e: - #cleanup: - other = self.connect() - channel = other.channel(1) - channel.session_open() - channel.exchange_delete(exchange="alternate") - channel.session_close() - other.close() - - self.assertConnectionException(530, e.args[0]) - + except SessionException, e: + session = self.session + session.exchange_delete(exchange="alternate") + self.assertEquals(530, e.args[0].error_code) def test_delete_while_used_by_exchange(self): @@ -150,25 +127,19 @@ class AlternateExchangeTests(TestBase): Ensure an exchange still in use as an alternate-exchange for another exchange can't be deleted """ - channel = self.channel - channel.exchange_declare(exchange="alternate", type="fanout") - channel.exchange_declare(exchange="e", type="fanout", alternate_exchange="alternate") + session = self.session + session.exchange_declare(exchange="alternate", type="fanout") + + session = self.conn.session("alternate", 2) + session.exchange_declare(exchange="e", type="fanout", alternate_exchange="alternate") try: - channel.exchange_delete(exchange="alternate") - #cleanup: - channel.exchange_delete(exchange="e") + session.exchange_delete(exchange="alternate") self.fail("Expected deletion of in-use alternate-exchange to fail") - except Closed, e: - #cleanup: - other = self.connect() - channel = other.channel(1) - channel.session_open() - channel.exchange_delete(exchange="e") - channel.exchange_delete(exchange="alternate") - channel.session_close() - other.close() - - self.assertConnectionException(530, e.args[0]) + except SessionException, e: + session = self.session + session.exchange_delete(exchange="e") + session.exchange_delete(exchange="alternate") + self.assertEquals(530, e.args[0].error_code) def assertEmpty(self, queue): @@ -176,4 +147,3 @@ class AlternateExchangeTests(TestBase): msg = queue.get(timeout=1) self.fail("Queue not empty: " + msg) except Empty: None - diff --git a/qpid/python/tests_0-10/broker.py b/qpid/python/tests_0-10/broker.py index 99936ba742..25cf1241ec 100644 --- a/qpid/python/tests_0-10/broker.py +++ b/qpid/python/tests_0-10/broker.py @@ -18,10 +18,10 @@ # from qpid.client import Closed from qpid.queue import Empty -from qpid.content import Content -from qpid.testlib import testrunner, TestBase +from qpid.testlib import TestBase010 +from qpid.datatypes import Message, RangedSet -class BrokerTests(TestBase): +class BrokerTests(TestBase010): """Tests for basic Broker functionality""" def test_ack_and_no_ack(self): @@ -30,82 +30,64 @@ class BrokerTests(TestBase): consumer. Second, this test tries to explicitly receive and acknowledge a message with an acknowledging consumer. """ - ch = self.channel - self.queue_declare(ch, queue = "myqueue") + session = self.session + session.queue_declare(queue = "myqueue", exclusive=True, auto_delete=True) # No ack consumer ctag = "tag1" - self.subscribe(ch, queue = "myqueue", destination = ctag) + session.message_subscribe(queue = "myqueue", destination = ctag) + session.message_flow(destination=ctag, unit=0, value=0xFFFFFFFF) + session.message_flow(destination=ctag, unit=1, value=0xFFFFFFFF) body = "test no-ack" - ch.message_transfer(content = Content(body, properties = {"routing_key" : "myqueue"})) - msg = self.client.queue(ctag).get(timeout = 5) - self.assert_(msg.content.body == body) + session.message_transfer(message=Message(session.delivery_properties(routing_key="myqueue"), body)) + msg = session.incoming(ctag).get(timeout = 5) + self.assert_(msg.body == body) # Acknowledging consumer - self.queue_declare(ch, queue = "otherqueue") + session.queue_declare(queue = "otherqueue", exclusive=True, auto_delete=True) ctag = "tag2" - self.subscribe(ch, queue = "otherqueue", destination = ctag, confirm_mode = 1) - ch.message_flow(destination=ctag, unit=0, value=0xFFFFFFFF) - ch.message_flow(destination=ctag, unit=1, value=0xFFFFFFFF) + session.message_subscribe(queue = "otherqueue", destination = ctag, accept_mode = 1) + session.message_flow(destination=ctag, unit=0, value=0xFFFFFFFF) + session.message_flow(destination=ctag, unit=1, value=0xFFFFFFFF) body = "test ack" - ch.message_transfer(content = Content(body, properties = {"routing_key" : "otherqueue"})) - msg = self.client.queue(ctag).get(timeout = 5) - msg.complete() - self.assert_(msg.content.body == body) + session.message_transfer(message=Message(session.delivery_properties(routing_key="otherqueue"), body)) + msg = session.incoming(ctag).get(timeout = 5) + session.message_accept(RangedSet(msg.id)) + self.assert_(msg.body == body) def test_simple_delivery_immediate(self): """ Test simple message delivery where consume is issued before publish """ - channel = self.channel - self.exchange_declare(channel, exchange="test-exchange", type="direct") - self.queue_declare(channel, queue="test-queue") - channel.queue_bind(queue="test-queue", exchange="test-exchange", routing_key="key") + session = self.session + session.queue_declare(queue="test-queue", exclusive=True, auto_delete=True) + session.exchange_bind(queue="test-queue", exchange="amq.fanout") consumer_tag = "tag1" - self.subscribe(queue="test-queue", destination=consumer_tag) - queue = self.client.queue(consumer_tag) + session.message_subscribe(queue="test-queue", destination=consumer_tag) + session.message_flow(unit = 0, value = 0xFFFFFFFF, destination = consumer_tag) + session.message_flow(unit = 1, value = 0xFFFFFFFF, destination = consumer_tag) + queue = session.incoming(consumer_tag) body = "Immediate Delivery" - channel.message_transfer(destination="test-exchange", content = Content(body, properties = {"routing_key" : "key"})) + session.message_transfer("amq.fanout", None, None, Message(body)) msg = queue.get(timeout=5) - self.assert_(msg.content.body == body) - - # TODO: Ensure we fail if immediate=True and there's no consumer. - + self.assert_(msg.body == body) def test_simple_delivery_queued(self): """ Test basic message delivery where publish is issued before consume (i.e. requires queueing of the message) """ - channel = self.channel - self.exchange_declare(channel, exchange="test-exchange", type="direct") - self.queue_declare(channel, queue="test-queue") - channel.queue_bind(queue="test-queue", exchange="test-exchange", routing_key="key") + session = self.session + session.queue_declare(queue="test-queue", exclusive=True, auto_delete=True) + session.exchange_bind(queue="test-queue", exchange="amq.fanout") body = "Queued Delivery" - channel.message_transfer(destination="test-exchange", content = Content(body, properties = {"routing_key" : "key"})) + session.message_transfer("amq.fanout", None, None, Message(body)) consumer_tag = "tag1" - self.subscribe(queue="test-queue", destination=consumer_tag) - queue = self.client.queue(consumer_tag) + session.message_subscribe(queue="test-queue", destination=consumer_tag) + session.message_flow(unit = 0, value = 0xFFFFFFFF, destination = consumer_tag) + session.message_flow(unit = 1, value = 0xFFFFFFFF, destination = consumer_tag) + queue = session.incoming(consumer_tag) msg = queue.get(timeout=5) - self.assert_(msg.content.body == body) - - def test_invalid_channel(self): - channel = self.client.channel(200) - try: - channel.queue_declare(exclusive=True) - self.fail("Expected error on queue_declare for invalid channel") - except Closed, e: - self.assertConnectionException(504, e.args[0]) - - def test_closed_channel(self): - channel = self.client.channel(200) - channel.session_open() - channel.session_close() - try: - channel.queue_declare(exclusive=True) - self.fail("Expected error on queue_declare for closed channel") - except Closed, e: - if isinstance(e.args[0], str): self.fail(e) - self.assertConnectionException(504, e.args[0]) + self.assert_(msg.body == body) diff --git a/qpid/python/tests_0-10/dtx.py b/qpid/python/tests_0-10/dtx.py index f84f91c75a..042df521ae 100644 --- a/qpid/python/tests_0-10/dtx.py +++ b/qpid/python/tests_0-10/dtx.py @@ -18,12 +18,13 @@ # from qpid.client import Client, Closed from qpid.queue import Empty -from qpid.content import Content -from qpid.testlib import testrunner, TestBase +from qpid.datatypes import Message, RangedSet +from qpid.session import SessionException +from qpid.testlib import TestBase010 from struct import pack, unpack from time import sleep -class DtxTests(TestBase): +class DtxTests(TestBase010): """ Tests for the amqp dtx related classes. @@ -43,15 +44,15 @@ class DtxTests(TestBase): tx_counter = 0 def reset_channel(self): - self.channel.session_close() - self.channel = self.client.channel(self.channel.id + 1) - self.channel.session_open() + self.session.close() + self.session = self.conn.session("dtx-session", 1) def test_simple_commit(self): """ Test basic one-phase commit behaviour. """ - channel = self.channel + guard = self.keepQueuesAlive(["queue-a", "queue-b"]) + session = self.session tx = self.xid("my-xid") self.txswap(tx, "commit") @@ -60,9 +61,9 @@ class DtxTests(TestBase): self.assertMessageCount(0, "queue-b") #commit - self.assertEqual(self.XA_OK, channel.dtx_coordination_commit(xid=tx, one_phase=True).status) + self.assertEqual(self.XA_OK, session.dtx_commit(xid=tx, one_phase=True).status) - #should close and reopen channel to ensure no unacked messages are held + #should close and reopen session to ensure no unacked messages are held self.reset_channel() #check result @@ -74,19 +75,20 @@ class DtxTests(TestBase): """ Test basic two-phase commit behaviour. """ - channel = self.channel + guard = self.keepQueuesAlive(["queue-a", "queue-b"]) + session = self.session tx = self.xid("my-xid") self.txswap(tx, "prepare-commit") #prepare - self.assertEqual(self.XA_OK, channel.dtx_coordination_prepare(xid=tx).status) + self.assertEqual(self.XA_OK, session.dtx_prepare(xid=tx).status) #neither queue should have any messages accessible self.assertMessageCount(0, "queue-a") self.assertMessageCount(0, "queue-b") #commit - self.assertEqual(self.XA_OK, channel.dtx_coordination_commit(xid=tx, one_phase=False).status) + self.assertEqual(self.XA_OK, session.dtx_commit(xid=tx, one_phase=False).status) self.reset_channel() @@ -100,7 +102,8 @@ class DtxTests(TestBase): """ Test basic rollback behaviour. """ - channel = self.channel + guard = self.keepQueuesAlive(["queue-a", "queue-b"]) + session = self.session tx = self.xid("my-xid") self.txswap(tx, "rollback") @@ -109,7 +112,7 @@ class DtxTests(TestBase): self.assertMessageCount(0, "queue-b") #rollback - self.assertEqual(self.XA_OK, channel.dtx_coordination_rollback(xid=tx).status) + self.assertEqual(self.XA_OK, session.dtx_rollback(xid=tx).status) self.reset_channel() @@ -122,19 +125,20 @@ class DtxTests(TestBase): """ Test basic rollback behaviour after the transaction has been prepared. """ - channel = self.channel + guard = self.keepQueuesAlive(["queue-a", "queue-b"]) + session = self.session tx = self.xid("my-xid") self.txswap(tx, "prepare-rollback") #prepare - self.assertEqual(self.XA_OK, channel.dtx_coordination_prepare(xid=tx).status) + self.assertEqual(self.XA_OK, session.dtx_prepare(xid=tx).status) #neither queue should have any messages accessible self.assertMessageCount(0, "queue-a") self.assertMessageCount(0, "queue-b") #rollback - self.assertEqual(self.XA_OK, channel.dtx_coordination_rollback(xid=tx).status) + self.assertEqual(self.XA_OK, session.dtx_rollback(xid=tx).status) self.reset_channel() @@ -148,17 +152,17 @@ class DtxTests(TestBase): check that an error is flagged if select is not issued before start or end """ - channel = self.channel + session = self.session tx = self.xid("dummy") try: - channel.dtx_demarcation_start(xid=tx) + session.dtx_start(xid=tx) #if we get here we have failed, but need to do some cleanup: - channel.dtx_demarcation_end(xid=tx) - channel.dtx_coordination_rollback(xid=tx) - self.fail("Channel not selected for use with dtx, expected exception!") - except Closed, e: - self.assertConnectionException(503, e.args[0]) + session.dtx_end(xid=tx) + session.dtx_rollback(xid=tx) + self.fail("Session not selected for use with dtx, expected exception!") + except SessionException, e: + self.assertEquals(503, e.args[0].error_code) def test_start_already_known(self): """ @@ -166,36 +170,35 @@ class DtxTests(TestBase): transaction that is already known is not allowed (unless the join flag is set). """ - #create two channels on different connection & select them for use with dtx: - channel1 = self.channel - channel1.dtx_demarcation_select() + #create two sessions on different connection & select them for use with dtx: + session1 = self.session + session1.dtx_select() other = self.connect() - channel2 = other.channel(1) - channel2.session_open() - channel2.dtx_demarcation_select() + session2 = other.session("other", 0) + session2.dtx_select() #create a xid tx = self.xid("dummy") - #start work on one channel under that xid: - channel1.dtx_demarcation_start(xid=tx) + #start work on one session under that xid: + session1.dtx_start(xid=tx) #then start on the other without the join set failed = False try: - channel2.dtx_demarcation_start(xid=tx) - except Closed, e: + session2.dtx_start(xid=tx) + except SessionException, e: failed = True error = e #cleanup: if not failed: - channel2.dtx_demarcation_end(xid=tx) + session2.dtx_end(xid=tx) other.close() - channel1.dtx_demarcation_end(xid=tx) - channel1.dtx_coordination_rollback(xid=tx) + session1.dtx_end(xid=tx) + session1.dtx_rollback(xid=tx) #verification: - if failed: self.assertConnectionException(503, e.args[0]) + if failed: self.assertEquals(503, error.args[0].error_code) else: self.fail("Xid already known, expected exception!") def test_forget_xid_on_completion(self): @@ -205,69 +208,69 @@ class DtxTests(TestBase): """ #do some transactional work & complete the transaction self.test_simple_commit() - # channel has been reset, so reselect for use with dtx - self.channel.dtx_demarcation_select() + # session has been reset, so reselect for use with dtx + self.session.dtx_select() #start association for the same xid as the previously completed txn tx = self.xid("my-xid") - self.channel.dtx_demarcation_start(xid=tx) - self.channel.dtx_demarcation_end(xid=tx) - self.channel.dtx_coordination_rollback(xid=tx) + self.session.dtx_start(xid=tx) + self.session.dtx_end(xid=tx) + self.session.dtx_rollback(xid=tx) def test_start_join_and_resume(self): """ Ensure the correct error is signalled when both the join and resume flags are set on starting an association between a - channel and a transcation. + session and a transcation. """ - channel = self.channel - channel.dtx_demarcation_select() + session = self.session + session.dtx_select() tx = self.xid("dummy") try: - channel.dtx_demarcation_start(xid=tx, join=True, resume=True) + session.dtx_start(xid=tx, join=True, resume=True) #failed, but need some cleanup: - channel.dtx_demarcation_end(xid=tx) - channel.dtx_coordination_rollback(xid=tx) + session.dtx_end(xid=tx) + session.dtx_rollback(xid=tx) self.fail("Join and resume both set, expected exception!") - except Closed, e: - self.assertConnectionException(503, e.args[0]) + except SessionException, e: + self.assertEquals(503, e.args[0].error_code) def test_start_join(self): """ - Verify 'join' behaviour, where a channel is associated with a - transaction that is already associated with another channel. + Verify 'join' behaviour, where a session is associated with a + transaction that is already associated with another session. """ - #create two channels & select them for use with dtx: - channel1 = self.channel - channel1.dtx_demarcation_select() + guard = self.keepQueuesAlive(["one", "two"]) + #create two sessions & select them for use with dtx: + session1 = self.session + session1.dtx_select() - channel2 = self.client.channel(2) - channel2.session_open() - channel2.dtx_demarcation_select() + session2 = self.conn.session("second", 2) + session2.dtx_select() #setup - channel1.queue_declare(queue="one", exclusive=True, auto_delete=True) - channel1.queue_declare(queue="two", exclusive=True, auto_delete=True) - channel1.message_transfer(content=Content(properties={'routing_key':"one", 'message_id':"a"}, body="DtxMessage")) - channel1.message_transfer(content=Content(properties={'routing_key':"two", 'message_id':"b"}, body="DtxMessage")) + session1.queue_declare(queue="one", auto_delete=True) + session1.queue_declare(queue="two", auto_delete=True) + session1.message_transfer(self.createMessage(session1, "one", "a", "DtxMessage")) + session1.message_transfer(self.createMessage(session1, "two", "b", "DtxMessage")) #create a xid tx = self.xid("dummy") - #start work on one channel under that xid: - channel1.dtx_demarcation_start(xid=tx) + #start work on one session under that xid: + session1.dtx_start(xid=tx) #then start on the other with the join flag set - channel2.dtx_demarcation_start(xid=tx, join=True) + session2.dtx_start(xid=tx, join=True) - #do work through each channel - self.swap(channel1, "one", "two")#swap 'a' from 'one' to 'two' - self.swap(channel2, "two", "one")#swap 'b' from 'two' to 'one' + #do work through each session + self.swap(session1, "one", "two")#swap 'a' from 'one' to 'two' + self.swap(session2, "two", "one")#swap 'b' from 'two' to 'one' - #mark end on both channels - channel1.dtx_demarcation_end(xid=tx) - channel2.dtx_demarcation_end(xid=tx) + #mark end on both sessions + session1.dtx_end(xid=tx) + session2.dtx_end(xid=tx) #commit and check - channel1.dtx_coordination_commit(xid=tx, one_phase=True) + session1.dtx_commit(xid=tx, one_phase=True) self.assertMessageCount(1, "one") self.assertMessageCount(1, "two") self.assertMessageId("a", "two") @@ -278,27 +281,27 @@ class DtxTests(TestBase): """ Test suspension and resumption of an association """ - channel = self.channel - channel.dtx_demarcation_select() + session = self.session + session.dtx_select() #setup - channel.queue_declare(queue="one", exclusive=True, auto_delete=True) - channel.queue_declare(queue="two", exclusive=True, auto_delete=True) - channel.message_transfer(content=Content(properties={'routing_key':"one", 'message_id':"a"}, body="DtxMessage")) - channel.message_transfer(content=Content(properties={'routing_key':"two", 'message_id':"b"}, body="DtxMessage")) + session.queue_declare(queue="one", exclusive=True, auto_delete=True) + session.queue_declare(queue="two", exclusive=True, auto_delete=True) + session.message_transfer(self.createMessage(session, "one", "a", "DtxMessage")) + session.message_transfer(self.createMessage(session, "two", "b", "DtxMessage")) tx = self.xid("dummy") - channel.dtx_demarcation_start(xid=tx) - self.swap(channel, "one", "two")#swap 'a' from 'one' to 'two' - channel.dtx_demarcation_end(xid=tx, suspend=True) + session.dtx_start(xid=tx) + self.swap(session, "one", "two")#swap 'a' from 'one' to 'two' + session.dtx_end(xid=tx, suspend=True) - channel.dtx_demarcation_start(xid=tx, resume=True) - self.swap(channel, "two", "one")#swap 'b' from 'two' to 'one' - channel.dtx_demarcation_end(xid=tx) + session.dtx_start(xid=tx, resume=True) + self.swap(session, "two", "one")#swap 'b' from 'two' to 'one' + session.dtx_end(xid=tx) #commit and check - channel.dtx_coordination_commit(xid=tx, one_phase=True) + session.dtx_commit(xid=tx, one_phase=True) self.assertMessageCount(1, "one") self.assertMessageCount(1, "two") self.assertMessageId("a", "two") @@ -310,27 +313,27 @@ class DtxTests(TestBase): done on another transaction when the first transaction is suspended """ - channel = self.channel - channel.dtx_demarcation_select() + session = self.session + session.dtx_select() #setup - channel.queue_declare(queue="one", exclusive=True, auto_delete=True) - channel.queue_declare(queue="two", exclusive=True, auto_delete=True) - channel.message_transfer(content=Content(properties={'routing_key':"one", 'message_id':"a"}, body="DtxMessage")) - channel.message_transfer(content=Content(properties={'routing_key':"two", 'message_id':"b"}, body="DtxMessage")) + session.queue_declare(queue="one", exclusive=True, auto_delete=True) + session.queue_declare(queue="two", exclusive=True, auto_delete=True) + session.message_transfer(self.createMessage(session, "one", "a", "DtxMessage")) + session.message_transfer(self.createMessage(session, "two", "b", "DtxMessage")) tx = self.xid("dummy") - channel.dtx_demarcation_start(xid=tx) - self.swap(channel, "one", "two")#swap 'a' from 'one' to 'two' - channel.dtx_demarcation_end(xid=tx, suspend=True) + session.dtx_start(xid=tx) + self.swap(session, "one", "two")#swap 'a' from 'one' to 'two' + session.dtx_end(xid=tx, suspend=True) - channel.dtx_demarcation_start(xid=tx, resume=True) - self.swap(channel, "two", "one")#swap 'b' from 'two' to 'one' - channel.dtx_demarcation_end(xid=tx) + session.dtx_start(xid=tx, resume=True) + self.swap(session, "two", "one")#swap 'b' from 'two' to 'one' + session.dtx_end(xid=tx) #commit and check - channel.dtx_coordination_commit(xid=tx, one_phase=True) + session.dtx_commit(xid=tx, one_phase=True) self.assertMessageCount(1, "one") self.assertMessageCount(1, "two") self.assertMessageId("a", "two") @@ -340,24 +343,23 @@ class DtxTests(TestBase): """ Verify that the correct error is signalled if the suspend and fail flag are both set when disassociating a transaction from - the channel + the session """ - channel = self.channel - channel.dtx_demarcation_select() + session = self.session + session.dtx_select() tx = self.xid("suspend_and_fail") - channel.dtx_demarcation_start(xid=tx) + session.dtx_start(xid=tx) try: - channel.dtx_demarcation_end(xid=tx, suspend=True, fail=True) + session.dtx_end(xid=tx, suspend=True, fail=True) self.fail("Suspend and fail both set, expected exception!") - except Closed, e: - self.assertConnectionException(503, e.args[0]) + except SessionException, e: + self.assertEquals(503, e.args[0].error_code) #cleanup other = self.connect() - channel = other.channel(1) - channel.session_open() - channel.dtx_coordination_rollback(xid=tx) - channel.session_close() + session = other.session("cleanup", 1) + session.dtx_rollback(xid=tx) + session.close() other.close() @@ -365,51 +367,51 @@ class DtxTests(TestBase): """ Verifies that the correct exception is thrown when an attempt is made to end the association for a xid not previously - associated with the channel + associated with the session """ - channel = self.channel - channel.dtx_demarcation_select() + session = self.session + session.dtx_select() tx = self.xid("unknown-xid") try: - channel.dtx_demarcation_end(xid=tx) + session.dtx_end(xid=tx) self.fail("Attempted to end association with unknown xid, expected exception!") - except Closed, e: + except SessionException, e: #FYI: this is currently *not* the exception specified, but I think the spec is wrong! Confirming... - self.assertConnectionException(503, e.args[0]) + self.assertEquals(503, e.args[0].error_code) def test_end(self): """ Verify that the association is terminated by end and subsequent operations are non-transactional """ - channel = self.client.channel(2) - channel.session_open() - channel.queue_declare(queue="tx-queue", exclusive=True, auto_delete=True) + guard = self.keepQueuesAlive(["tx-queue"]) + session = self.conn.session("alternate", 1) + session.queue_declare(queue="tx-queue", exclusive=True, auto_delete=True) #publish a message under a transaction - channel.dtx_demarcation_select() + session.dtx_select() tx = self.xid("dummy") - channel.dtx_demarcation_start(xid=tx) - channel.message_transfer(content=Content(properties={'routing_key':"tx-queue", 'message_id':"one"}, body="DtxMessage")) - channel.dtx_demarcation_end(xid=tx) + session.dtx_start(xid=tx) + session.message_transfer(self.createMessage(session, "tx-queue", "one", "DtxMessage")) + session.dtx_end(xid=tx) #now that association with txn is ended, publish another message - channel.message_transfer(content=Content(properties={'routing_key':"tx-queue", 'message_id':"two"}, body="DtxMessage")) + session.message_transfer(self.createMessage(session, "tx-queue", "two", "DtxMessage")) #check the second message is available, but not the first self.assertMessageCount(1, "tx-queue") - self.subscribe(channel, queue="tx-queue", destination="results", confirm_mode=1) - msg = self.client.queue("results").get(timeout=1) - self.assertEqual("two", msg.content['message_id']) - channel.message_cancel(destination="results") - #ack the message then close the channel - msg.complete() - channel.session_close() - - channel = self.channel + self.subscribe(session, queue="tx-queue", destination="results") + msg = session.incoming("results").get(timeout=1) + self.assertEqual("two", self.getMessageProperty(msg, 'correlation_id')) + session.message_cancel(destination="results") + #ack the message then close the session + session.message_accept(RangedSet(msg.id)) + session.close() + + session = self.session #commit the transaction and check that the first message (and #only the first message) is then delivered - channel.dtx_coordination_commit(xid=tx, one_phase=True) + session.dtx_commit(xid=tx, one_phase=True) self.assertMessageCount(1, "tx-queue") self.assertMessageId("one", "tx-queue") @@ -419,27 +421,26 @@ class DtxTests(TestBase): transaction in question has already been prepared. """ other = self.connect() - tester = other.channel(1) - tester.session_open() + tester = other.session("tester", 1) tester.queue_declare(queue="dummy", exclusive=True, auto_delete=True) - tester.dtx_demarcation_select() + tester.dtx_select() tx = self.xid("dummy") - tester.dtx_demarcation_start(xid=tx) - tester.message_transfer(content=Content(properties={'routing_key':"dummy"}, body="whatever")) - tester.dtx_demarcation_end(xid=tx) - tester.dtx_coordination_prepare(xid=tx) + tester.dtx_start(xid=tx) + tester.message_transfer(self.createMessage(tester, "dummy", "dummy", "whatever")) + tester.dtx_end(xid=tx) + tester.dtx_prepare(xid=tx) failed = False try: - tester.dtx_coordination_commit(xid=tx, one_phase=True) - except Closed, e: + tester.dtx_commit(xid=tx, one_phase=True) + except SessionException, e: failed = True error = e if failed: - self.channel.dtx_coordination_rollback(xid=tx) - self.assertConnectionException(503, e.args[0]) + self.session.dtx_rollback(xid=tx) + self.assertEquals(503, error.args[0].error_code) else: - tester.session_close() + tester.close() other.close() self.fail("Invalid use of one_phase=True, expected exception!") @@ -453,99 +454,99 @@ class DtxTests(TestBase): transaction in question has already been prepared. """ other = self.connect() - tester = other.channel(1) - tester.session_open() + tester = other.session("tester", 1) tester.queue_declare(queue="dummy", exclusive=True, auto_delete=True) - tester.dtx_demarcation_select() + tester.dtx_select() tx = self.xid("dummy") - tester.dtx_demarcation_start(xid=tx) - tester.message_transfer(content=Content(properties={'routing_key':"dummy"}, body="whatever")) - tester.dtx_demarcation_end(xid=tx) + tester.dtx_start(xid=tx) + tester.message_transfer(self.createMessage(tester, "dummy", "dummy", "whatever")) + tester.dtx_end(xid=tx) failed = False try: - tester.dtx_coordination_commit(xid=tx, one_phase=False) - except Closed, e: + tester.dtx_commit(xid=tx, one_phase=False) + except SessionException, e: failed = True error = e if failed: - self.channel.dtx_coordination_rollback(xid=tx) - self.assertConnectionException(503, e.args[0]) + self.session.dtx_rollback(xid=tx) + self.assertEquals(503, error.args[0].error_code) else: - tester.session_close() + tester.close() other.close() self.fail("Invalid use of one_phase=False, expected exception!") def test_implicit_end(self): """ - Test that an association is implicitly ended when the channel + Test that an association is implicitly ended when the session is closed (whether by exception or explicit client request) and the transaction in question is marked as rollback only. """ - channel1 = self.channel - channel2 = self.client.channel(2) - channel2.session_open() + session1 = self.session + session2 = self.conn.session("other", 2) #setup: - channel2.queue_declare(queue="dummy", exclusive=True, auto_delete=True) - channel2.message_transfer(content=Content(properties={'routing_key':"dummy"}, body="whatever")) + session2.queue_declare(queue="dummy", exclusive=True, auto_delete=True) + session2.message_transfer(self.createMessage(session2, "dummy", "a", "whatever")) tx = self.xid("dummy") - channel2.dtx_demarcation_select() - channel2.dtx_demarcation_start(xid=tx) - channel2.message_subscribe(queue="dummy", destination="dummy", confirm_mode=1) - channel2.message_flow(destination="dummy", unit=0, value=1) - channel2.message_flow(destination="dummy", unit=1, value=0xFFFFFFFF) - self.client.queue("dummy").get(timeout=1).complete() - channel2.message_cancel(destination="dummy") - channel2.message_transfer(content=Content(properties={'routing_key':"dummy"}, body="whatever")) - channel2.session_close() + session2.dtx_select() + session2.dtx_start(xid=tx) + session2.message_subscribe(queue="dummy", destination="dummy") + session2.message_flow(destination="dummy", unit=0, value=1) + session2.message_flow(destination="dummy", unit=1, value=0xFFFFFFFF) + msg = session2.incoming("dummy").get(timeout=1) + session2.message_accept(RangedSet(msg.id)) + session2.message_cancel(destination="dummy") + session2.message_transfer(self.createMessage(session2, "dummy", "b", "whatever")) + session2.close() - self.assertEqual(self.XA_RBROLLBACK, channel1.dtx_coordination_prepare(xid=tx).status) - channel1.dtx_coordination_rollback(xid=tx) + self.assertEqual(self.XA_RBROLLBACK, session1.dtx_prepare(xid=tx).status) + session1.dtx_rollback(xid=tx) def test_get_timeout(self): """ Check that get-timeout returns the correct value, (and that a transaction with a timeout can complete normally) """ - channel = self.channel + session = self.session tx = self.xid("dummy") - channel.dtx_demarcation_select() - channel.dtx_demarcation_start(xid=tx) - self.assertEqual(0, channel.dtx_coordination_get_timeout(xid=tx).timeout) - channel.dtx_coordination_set_timeout(xid=tx, timeout=60) - self.assertEqual(60, channel.dtx_coordination_get_timeout(xid=tx).timeout) - self.assertEqual(self.XA_OK, channel.dtx_demarcation_end(xid=tx).status) - self.assertEqual(self.XA_OK, channel.dtx_coordination_rollback(xid=tx).status) + session.dtx_select() + session.dtx_start(xid=tx) + self.assertEqual(0, session.dtx_get_timeout(xid=tx).timeout) + session.dtx_set_timeout(xid=tx, timeout=60) + self.assertEqual(60, session.dtx_get_timeout(xid=tx).timeout) + self.assertEqual(self.XA_OK, session.dtx_end(xid=tx).status) + self.assertEqual(self.XA_OK, session.dtx_rollback(xid=tx).status) def test_set_timeout(self): """ Test the timeout of a transaction results in the expected behaviour """ - #open new channel to allow self.channel to be used in checking te queue - channel = self.client.channel(2) - channel.session_open() + + guard = self.keepQueuesAlive(["queue-a", "queue-b"]) + #open new session to allow self.session to be used in checking the queue + session = self.conn.session("worker", 1) #setup: tx = self.xid("dummy") - channel.queue_declare(queue="queue-a", exclusive=True, auto_delete=True) - channel.queue_declare(queue="queue-b", exclusive=True, auto_delete=True) - channel.message_transfer(content=Content(properties={'routing_key':"queue-a", 'message_id':"timeout"}, body="DtxMessage")) - - channel.dtx_demarcation_select() - channel.dtx_demarcation_start(xid=tx) - self.swap(channel, "queue-a", "queue-b") - channel.dtx_coordination_set_timeout(xid=tx, timeout=2) + session.queue_declare(queue="queue-a", auto_delete=True) + session.queue_declare(queue="queue-b", auto_delete=True) + session.message_transfer(self.createMessage(session, "queue-a", "timeout", "DtxMessage")) + + session.dtx_select() + session.dtx_start(xid=tx) + self.swap(session, "queue-a", "queue-b") + session.dtx_set_timeout(xid=tx, timeout=2) sleep(3) #check that the work has been rolled back already self.assertMessageCount(1, "queue-a") self.assertMessageCount(0, "queue-b") self.assertMessageId("timeout", "queue-a") #check the correct codes are returned when we try to complete the txn - self.assertEqual(self.XA_RBTIMEOUT, channel.dtx_demarcation_end(xid=tx).status) - self.assertEqual(self.XA_RBTIMEOUT, channel.dtx_coordination_rollback(xid=tx).status) + self.assertEqual(self.XA_RBTIMEOUT, session.dtx_end(xid=tx).status) + self.assertEqual(self.XA_RBTIMEOUT, session.dtx_rollback(xid=tx).status) @@ -553,28 +554,29 @@ class DtxTests(TestBase): """ Test basic recover behaviour """ - channel = self.channel + session = self.session - channel.dtx_demarcation_select() - channel.queue_declare(queue="dummy", exclusive=True, auto_delete=True) + session.dtx_select() + session.queue_declare(queue="dummy", exclusive=True, auto_delete=True) prepared = [] for i in range(1, 10): tx = self.xid("tx%s" % (i)) - channel.dtx_demarcation_start(xid=tx) - channel.message_transfer(content=Content(properties={'routing_key':"dummy"}, body="message%s" % (i))) - channel.dtx_demarcation_end(xid=tx) + session.dtx_start(xid=tx) + session.message_transfer(self.createMessage(session, "dummy", "message%s" % (i), "message%s" % (i))) + session.dtx_end(xid=tx) if i in [2, 5, 6, 8]: - channel.dtx_coordination_prepare(xid=tx) + session.dtx_prepare(xid=tx) prepared.append(tx) else: - channel.dtx_coordination_rollback(xid=tx) + session.dtx_rollback(xid=tx) - xids = channel.dtx_coordination_recover().in_doubt + xids = session.dtx_recover().in_doubt + print "xids=%s" % xids #rollback the prepared transactions returned by recover for x in xids: - channel.dtx_coordination_rollback(xid=x) + session.dtx_rollback(xid=x) #validate against the expected list of prepared transactions actual = set(xids) @@ -585,61 +587,83 @@ class DtxTests(TestBase): missing = expected.difference(actual) extra = actual.difference(expected) for x in missing: - channel.dtx_coordination_rollback(xid=x) + session.dtx_rollback(xid=x) self.fail("Recovered xids not as expected. missing: %s; extra: %s" % (missing, extra)) def test_bad_resume(self): """ Test that a resume on a session not selected for use with dtx fails """ - channel = self.channel + session = self.session try: - channel.dtx_demarcation_start(resume=True) - except Closed, e: - self.assertConnectionException(503, e.args[0]) + session.dtx_start(resume=True) + except SessionException, e: + self.assertEquals(503, e.args[0].error_code) def xid(self, txid): DtxTests.tx_counter += 1 branchqual = "v%s" % DtxTests.tx_counter - return pack('!LBB', 0, len(txid), len(branchqual)) + txid + branchqual - + return self.session.xid(format=0, global_id=txid, branch_id=branchqual) + def txswap(self, tx, id): - channel = self.channel + session = self.session #declare two queues: - channel.queue_declare(queue="queue-a", exclusive=True, auto_delete=True) - channel.queue_declare(queue="queue-b", exclusive=True, auto_delete=True) + session.queue_declare(queue="queue-a", auto_delete=True) + session.queue_declare(queue="queue-b", auto_delete=True) + #put message with specified id on one queue: - channel.message_transfer(content=Content(properties={'routing_key':"queue-a", 'message_id':id}, body="DtxMessage")) + dp=session.delivery_properties(routing_key="queue-a") + mp=session.message_properties(correlation_id=id) + session.message_transfer(message=Message(dp, mp, "DtxMessage")) #start the transaction: - channel.dtx_demarcation_select() - self.assertEqual(self.XA_OK, self.channel.dtx_demarcation_start(xid=tx).status) + session.dtx_select() + self.assertEqual(self.XA_OK, self.session.dtx_start(xid=tx).status) #'swap' the message from one queue to the other, under that transaction: - self.swap(self.channel, "queue-a", "queue-b") + self.swap(self.session, "queue-a", "queue-b") #mark the end of the transactional work: - self.assertEqual(self.XA_OK, self.channel.dtx_demarcation_end(xid=tx).status) + self.assertEqual(self.XA_OK, self.session.dtx_end(xid=tx).status) - def swap(self, channel, src, dest): + def swap(self, session, src, dest): #consume from src: - channel.message_subscribe(destination="temp-swap", queue=src, confirm_mode=1) - channel.message_flow(destination="temp-swap", unit=0, value=1) - channel.message_flow(destination="temp-swap", unit=1, value=0xFFFFFFFF) - msg = self.client.queue("temp-swap").get(timeout=1) - channel.message_cancel(destination="temp-swap") - msg.complete(); - - #re-publish to dest - channel.message_transfer(content=Content(properties={'routing_key':dest, 'message_id':msg.content['message_id']}, - body=msg.content.body)) + session.message_subscribe(destination="temp-swap", queue=src) + session.message_flow(destination="temp-swap", unit=0, value=1) + session.message_flow(destination="temp-swap", unit=1, value=0xFFFFFFFF) + msg = session.incoming("temp-swap").get(timeout=1) + session.message_cancel(destination="temp-swap") + session.message_accept(RangedSet(msg.id)) + #todo: also complete at this point? + + #re-publish to dest: + dp=session.delivery_properties(routing_key=dest) + mp=session.message_properties(correlation_id=self.getMessageProperty(msg, 'correlation_id')) + session.message_transfer(message=Message(dp, mp, msg.body)) def assertMessageCount(self, expected, queue): - self.assertEqual(expected, self.channel.queue_query(queue=queue).message_count) + self.assertEqual(expected, self.session.queue_query(queue=queue).message_count) def assertMessageId(self, expected, queue): - self.channel.message_subscribe(queue=queue, destination="results") - self.channel.message_flow(destination="results", unit=0, value=1) - self.channel.message_flow(destination="results", unit=1, value=0xFFFFFFFF) - self.assertEqual(expected, self.client.queue("results").get(timeout=1).content['message_id']) - self.channel.message_cancel(destination="results") + self.session.message_subscribe(queue=queue, destination="results") + self.session.message_flow(destination="results", unit=0, value=1) + self.session.message_flow(destination="results", unit=1, value=0xFFFFFFFF) + self.assertEqual(expected, self.getMessageProperty(self.session.incoming("results").get(timeout=1), 'correlation_id')) + self.session.message_cancel(destination="results") + + def getMessageProperty(self, msg, prop): + for h in msg.headers: + if hasattr(h, prop): return getattr(h, prop) + return None + + def keepQueuesAlive(self, names): + session = self.conn.session("nasty", 99) + for n in names: + session.queue_declare(queue=n, auto_delete=True) + session.message_subscribe(destination=n, queue=n) + return session + + def createMessage(self, session, key, id, body): + dp=session.delivery_properties(routing_key=key) + mp=session.message_properties(correlation_id=id) + session.message_transfer(message=Message(dp, mp, body)) diff --git a/qpid/python/tests_0-10/example.py b/qpid/python/tests_0-10/example.py index da5ee2441f..1e140a285d 100644 --- a/qpid/python/tests_0-10/example.py +++ b/qpid/python/tests_0-10/example.py @@ -17,10 +17,10 @@ # under the License. # -from qpid.content import Content -from qpid.testlib import testrunner, TestBase +from qpid.datatypes import Message, RangedSet +from qpid.testlib import TestBase010 -class ExampleTest (TestBase): +class ExampleTest (TestBase010): """ An example Qpid test, illustrating the unittest framework and the python Qpid client. The test class must inherit TestBase. The @@ -35,9 +35,9 @@ class ExampleTest (TestBase): """ # By inheriting TestBase, self.client is automatically connected - # and self.channel is automatically opened as channel(1) - # Other channel methods mimic the protocol. - channel = self.channel + # and self.session is automatically opened as session(1) + # Other session methods mimic the protocol. + session = self.session # Now we can send regular commands. If you want to see what the method # arguments mean or what other commands are available, you can use the @@ -57,30 +57,30 @@ class ExampleTest (TestBase): # interact with the server. # Here we use ordinal arguments. - self.exchange_declare(channel, 0, "test", "direct") + session.exchange_declare("test", "direct") # Here we use keyword arguments. - self.queue_declare(channel, queue="test-queue") - channel.queue_bind(queue="test-queue", exchange="test", routing_key="key") + session.queue_declare(queue="test-queue", exclusive=True, auto_delete=True) + session.exchange_bind(queue="test-queue", exchange="test", binding_key="key") - # Call Channel.basic_consume to register as a consumer. + # Call Session.subscribe to register as a consumer. # All the protocol methods return a message object. The message object # has fields corresponding to the reply method fields, plus a content # field that is filled if the reply includes content. In this case the # interesting field is the consumer_tag. - channel.message_subscribe(queue="test-queue", destination="consumer_tag") - channel.message_flow(destination="consumer_tag", unit=0, value=0xFFFFFFFF) - channel.message_flow(destination="consumer_tag", unit=1, value=0xFFFFFFFF) + session.message_subscribe(queue="test-queue", destination="consumer_tag") + session.message_flow(destination="consumer_tag", unit=0, value=0xFFFFFFFF) + session.message_flow(destination="consumer_tag", unit=1, value=0xFFFFFFFF) - # We can use the Client.queue(...) method to access the queue - # corresponding to our consumer_tag. - queue = self.client.queue("consumer_tag") + # We can use the session.incoming(...) method to access the messages + # delivered for our consumer_tag. + queue = session.incoming("consumer_tag") # Now lets publish a message and see if our consumer gets it. To do - # this we need to import the Content class. - sent = Content("Hello World!") - sent["routing_key"] = "key" - channel.message_transfer(destination="test", content=sent) + # this we need to import the Message class. + delivery_properties = session.delivery_properties(routing_key="key") + sent = Message(delivery_properties, "Hello World!") + session.message_transfer(destination="test", message=sent) # Now we'll wait for the message to arrive. We can use the timeout # argument in case the server hangs. By default queue.get() will wait @@ -88,8 +88,8 @@ class ExampleTest (TestBase): msg = queue.get(timeout=10) # And check that we got the right response with assertEqual - self.assertEqual(sent.body, msg.content.body) + self.assertEqual(sent.body, msg.body) # Now acknowledge the message. - msg.complete() + session.message_accept(RangedSet(msg.id)) diff --git a/qpid/python/tests_0-10/exchange.py b/qpid/python/tests_0-10/exchange.py index 86c39b7736..991da17ed4 100644 --- a/qpid/python/tests_0-10/exchange.py +++ b/qpid/python/tests_0-10/exchange.py @@ -23,10 +23,94 @@ Tests for exchange behaviour. Test classes ending in 'RuleTests' are derived from rules in amqp.xml. """ -import Queue, logging -from qpid.testlib import TestBase -from qpid.content import Content +import Queue, logging, traceback +from qpid.testlib import TestBase010 +from qpid.datatypes import Message from qpid.client import Closed +from qpid.session import SessionException + + +class TestHelper(TestBase010): + def setUp(self): + TestBase010.setUp(self) + self.queues = [] + self.exchanges = [] + + def tearDown(self): + try: + for ssn, q in self.queues: + ssn.queue_delete(queue=q) + for ssn, ex in self.exchanges: + ssn.exchange_delete(exchange=ex) + except: + print "Error on tearDown:" + print traceback.print_exc() + TestBase010.tearDown(self) + + def createMessage(self, key="", body=""): + return Message(self.session.delivery_properties(routing_key=key), body) + + def getApplicationHeaders(self, msg): + for h in msg.headers: + if hasattr(h, 'application_headers'): return getattr(h, 'application_headers') + return None + + def assertPublishGet(self, queue, exchange="", routing_key="", properties=None): + """ + Publish to exchange and assert queue.get() returns the same message. + """ + body = self.uniqueString() + dp=self.session.delivery_properties(routing_key=routing_key) + mp=self.session.message_properties(application_headers=properties) + self.session.message_transfer(destination=exchange, message=Message(dp, mp, body)) + msg = queue.get(timeout=1) + self.assertEqual(body, msg.body) + if (properties): + self.assertEqual(properties, self.getApplicationHeaders(msg)) + + def assertPublishConsume(self, queue="", exchange="", routing_key="", properties=None): + """ + Publish a message and consume it, assert it comes back intact. + Return the Queue object used to consume. + """ + self.assertPublishGet(self.consume(queue), exchange, routing_key, properties) + + def assertEmpty(self, queue): + """Assert that the queue is empty""" + try: + queue.get(timeout=1) + self.fail("Queue is not empty.") + except Queue.Empty: None # Ignore + + def queue_declare(self, session=None, *args, **keys): + session = session or self.session + reply = session.queue_declare(*args, **keys) + self.queues.append((session, keys["queue"])) + return reply + + def exchange_declare(self, session=None, ticket=0, exchange='', + type='', passive=False, durable=False, + auto_delete=False, + arguments={}): + session = session or self.session + reply = session.exchange_declare(exchange=exchange, type=type, passive=passive,durable=durable, auto_delete=auto_delete, arguments=arguments) + self.exchanges.append((session,exchange)) + return reply + + def uniqueString(self): + """Generate a unique string, unique for this TestBase instance""" + if not "uniqueCounter" in dir(self): self.uniqueCounter = 1; + return "Test Message " + str(self.uniqueCounter) + + def consume(self, queueName): + """Consume from named queue returns the Queue object.""" + if not "uniqueTag" in dir(self): self.uniqueTag = 1 + else: self.uniqueTag += 1 + consumer_tag = "tag" + str(self.uniqueTag) + self.session.message_subscribe(queue=queueName, destination=consumer_tag) + self.session.message_flow(destination=consumer_tag, unit=0, value=0xFFFFFFFF) + self.session.message_flow(destination=consumer_tag, unit=1, value=0xFFFFFFFF) + return self.session.incoming(consumer_tag) class StandardExchangeVerifier: @@ -37,7 +121,7 @@ class StandardExchangeVerifier: def verifyDirectExchange(self, ex): """Verify that ex behaves like a direct exchange.""" self.queue_declare(queue="q") - self.channel.queue_bind(queue="q", exchange=ex, routing_key="k") + self.session.exchange_bind(queue="q", exchange=ex, binding_key="k") self.assertPublishConsume(exchange=ex, queue="q", routing_key="k") try: self.assertPublishConsume(exchange=ex, queue="q", routing_key="kk") @@ -47,38 +131,38 @@ class StandardExchangeVerifier: def verifyFanOutExchange(self, ex): """Verify that ex behaves like a fanout exchange.""" self.queue_declare(queue="q") - self.channel.queue_bind(queue="q", exchange=ex) + self.session.exchange_bind(queue="q", exchange=ex) self.queue_declare(queue="p") - self.channel.queue_bind(queue="p", exchange=ex) + self.session.exchange_bind(queue="p", exchange=ex) for qname in ["q", "p"]: self.assertPublishGet(self.consume(qname), ex) def verifyTopicExchange(self, ex): """Verify that ex behaves like a topic exchange""" self.queue_declare(queue="a") - self.channel.queue_bind(queue="a", exchange=ex, routing_key="a.#.b.*") + self.session.exchange_bind(queue="a", exchange=ex, binding_key="a.#.b.*") q = self.consume("a") self.assertPublishGet(q, ex, "a.b.x") self.assertPublishGet(q, ex, "a.x.b.x") self.assertPublishGet(q, ex, "a.x.x.b.x") # Shouldn't match - self.channel.message_transfer(destination=ex, content=Content(properties={'routing_key':"a.b"})) - self.channel.message_transfer(destination=ex, content=Content(properties={'routing_key':"a.b.x.y"})) - self.channel.message_transfer(destination=ex, content=Content(properties={'routing_key':"x.a.b.x"})) - self.channel.message_transfer(destination=ex, content=Content(properties={'routing_key':"a.b"})) + self.session.message_transfer(destination=ex, message=self.createMessage("a.b")) + self.session.message_transfer(destination=ex, message=self.createMessage("a.b.x.y")) + self.session.message_transfer(destination=ex, message=self.createMessage("x.a.b.x")) + self.session.message_transfer(destination=ex, message=self.createMessage("a.b")) self.assert_(q.empty()) def verifyHeadersExchange(self, ex): """Verify that ex is a headers exchange""" self.queue_declare(queue="q") - self.channel.queue_bind(queue="q", exchange=ex, arguments={ "x-match":"all", "name":"fred" , "age":3} ) + self.session.exchange_bind(queue="q", exchange=ex, arguments={ "x-match":"all", "name":"fred" , "age":3} ) q = self.consume("q") headers = {"name":"fred", "age":3} self.assertPublishGet(q, exchange=ex, properties=headers) - self.channel.message_transfer(destination=ex) # No headers, won't deliver + self.session.message_transfer(destination=ex) # No headers, won't deliver self.assertEmpty(q); -class RecommendedTypesRuleTests(TestBase, StandardExchangeVerifier): +class RecommendedTypesRuleTests(TestHelper, StandardExchangeVerifier): """ The server SHOULD implement these standard exchange types: topic, headers. @@ -106,7 +190,7 @@ class RecommendedTypesRuleTests(TestBase, StandardExchangeVerifier): self.verifyHeadersExchange("h") -class RequiredInstancesRuleTests(TestBase, StandardExchangeVerifier): +class RequiredInstancesRuleTests(TestHelper, StandardExchangeVerifier): """ The server MUST, in each virtual host, pre-declare an exchange instance for each standard exchange type that it implements, where the name of the @@ -124,7 +208,7 @@ class RequiredInstancesRuleTests(TestBase, StandardExchangeVerifier): def testAmqMatch(self): self.verifyHeadersExchange("amq.match") -class DefaultExchangeRuleTests(TestBase, StandardExchangeVerifier): +class DefaultExchangeRuleTests(TestHelper, StandardExchangeVerifier): """ The server MUST predeclare a direct exchange to act as the default exchange for content Publish methods and for default queue bindings. @@ -144,20 +228,20 @@ class DefaultExchangeRuleTests(TestBase, StandardExchangeVerifier): # TODO aconway 2006-09-27: Fill in empty tests: -class DefaultAccessRuleTests(TestBase): +class DefaultAccessRuleTests(TestHelper): """ The server MUST NOT allow clients to access the default exchange except by specifying an empty exchange name in the Queue.Bind and content Publish methods. """ -class ExtensionsRuleTests(TestBase): +class ExtensionsRuleTests(TestHelper): """ The server MAY implement other exchange types as wanted. """ -class DeclareMethodMinimumRuleTests(TestBase): +class DeclareMethodMinimumRuleTests(TestHelper): """ The server SHOULD support a minimum of 16 exchanges per virtual host and ideally, impose no limit except as defined by available resources. @@ -168,7 +252,7 @@ class DeclareMethodMinimumRuleTests(TestBase): """ -class DeclareMethodTicketFieldValidityRuleTests(TestBase): +class DeclareMethodTicketFieldValidityRuleTests(TestHelper): """ The client MUST provide a valid access ticket giving "active" access to the realm in which the exchange exists or will be created, or "passive" @@ -179,7 +263,7 @@ class DeclareMethodTicketFieldValidityRuleTests(TestBase): """ -class DeclareMethodExchangeFieldReservedRuleTests(TestBase): +class DeclareMethodExchangeFieldReservedRuleTests(TestHelper): """ Exchange names starting with "amq." are reserved for predeclared and standardised exchanges. The client MUST NOT attempt to create an exchange @@ -189,7 +273,7 @@ class DeclareMethodExchangeFieldReservedRuleTests(TestBase): """ -class DeclareMethodTypeFieldTypedRuleTests(TestBase): +class DeclareMethodTypeFieldTypedRuleTests(TestHelper): """ Exchanges cannot be redeclared with different types. The client MUST not attempt to redeclare an existing exchange with a different type than used @@ -199,7 +283,7 @@ class DeclareMethodTypeFieldTypedRuleTests(TestBase): """ -class DeclareMethodTypeFieldSupportRuleTests(TestBase): +class DeclareMethodTypeFieldSupportRuleTests(TestHelper): """ The client MUST NOT attempt to create an exchange with a type that the server does not support. @@ -208,20 +292,20 @@ class DeclareMethodTypeFieldSupportRuleTests(TestBase): """ -class DeclareMethodPassiveFieldNotFoundRuleTests(TestBase): +class DeclareMethodPassiveFieldNotFoundRuleTests(TestHelper): """ If set, and the exchange does not already exist, the server MUST raise a channel exception with reply code 404 (not found). """ def test(self): try: - self.channel.exchange_declare(exchange="humpty_dumpty", passive=True) + self.session.exchange_declare(exchange="humpty_dumpty", passive=True) self.fail("Expected 404 for passive declaration of unknown exchange.") - except Closed, e: - self.assertChannelException(404, e.args[0]) + except SessionException, e: + self.assertEquals(404, e.args[0].error_code) -class DeclareMethodDurableFieldSupportRuleTests(TestBase): +class DeclareMethodDurableFieldSupportRuleTests(TestHelper): """ The server MUST support both durable and transient exchanges. @@ -229,7 +313,7 @@ class DeclareMethodDurableFieldSupportRuleTests(TestBase): """ -class DeclareMethodDurableFieldStickyRuleTests(TestBase): +class DeclareMethodDurableFieldStickyRuleTests(TestHelper): """ The server MUST ignore the durable field if the exchange already exists. @@ -237,7 +321,7 @@ class DeclareMethodDurableFieldStickyRuleTests(TestBase): """ -class DeclareMethodAutoDeleteFieldStickyRuleTests(TestBase): +class DeclareMethodAutoDeleteFieldStickyRuleTests(TestHelper): """ The server MUST ignore the auto-delete field if the exchange already exists. @@ -246,7 +330,7 @@ class DeclareMethodAutoDeleteFieldStickyRuleTests(TestBase): """ -class DeleteMethodTicketFieldValidityRuleTests(TestBase): +class DeleteMethodTicketFieldValidityRuleTests(TestHelper): """ The client MUST provide a valid access ticket giving "active" access rights to the exchange's access realm. @@ -256,18 +340,18 @@ class DeleteMethodTicketFieldValidityRuleTests(TestBase): """ -class DeleteMethodExchangeFieldExistsRuleTests(TestBase): +class DeleteMethodExchangeFieldExistsRuleTests(TestHelper): """ The client MUST NOT attempt to delete an exchange that does not exist. """ -class HeadersExchangeTests(TestBase): +class HeadersExchangeTests(TestHelper): """ Tests for headers exchange functionality. """ def setUp(self): - TestBase.setUp(self) + TestHelper.setUp(self) self.queue_declare(queue="q") self.q = self.consume("q") @@ -275,10 +359,11 @@ class HeadersExchangeTests(TestBase): self.assertPublishGet(self.q, exchange="amq.match", properties=headers) def myBasicPublish(self, headers): - self.channel.message_transfer(destination="amq.match", content=Content("foobar", properties={'application_headers':headers})) + mp=self.session.message_properties(application_headers=headers) + self.session.message_transfer(destination="amq.match", message=Message(mp, "foobar")) def testMatchAll(self): - self.channel.queue_bind(queue="q", exchange="amq.match", arguments={ 'x-match':'all', "name":"fred", "age":3}) + self.session.exchange_bind(queue="q", exchange="amq.match", arguments={ 'x-match':'all', "name":"fred", "age":3}) self.myAssertPublishGet({"name":"fred", "age":3}) self.myAssertPublishGet({"name":"fred", "age":3, "extra":"ignoreme"}) @@ -290,7 +375,7 @@ class HeadersExchangeTests(TestBase): self.assertEmpty(self.q) def testMatchAny(self): - self.channel.queue_bind(queue="q", exchange="amq.match", arguments={ 'x-match':'any', "name":"fred", "age":3}) + self.session.exchange_bind(queue="q", exchange="amq.match", arguments={ 'x-match':'any', "name":"fred", "age":3}) self.myAssertPublishGet({"name":"fred"}) self.myAssertPublishGet({"name":"fred", "ignoreme":10}) self.myAssertPublishGet({"ignoreme":10, "age":3}) @@ -301,35 +386,31 @@ class HeadersExchangeTests(TestBase): self.assertEmpty(self.q) -class MiscellaneousErrorsTests(TestBase): +class MiscellaneousErrorsTests(TestHelper): """ Test some miscellaneous error conditions """ def testTypeNotKnown(self): try: - self.channel.exchange_declare(exchange="test_type_not_known_exchange", type="invalid_type") + self.session.exchange_declare(exchange="test_type_not_known_exchange", type="invalid_type") self.fail("Expected 503 for declaration of unknown exchange type.") - except Closed, e: - self.assertConnectionException(503, e.args[0]) + except SessionException, e: + self.assertEquals(503, e.args[0].error_code) def testDifferentDeclaredType(self): - self.channel.exchange_declare(exchange="test_different_declared_type_exchange", type="direct") + self.exchange_declare(exchange="test_different_declared_type_exchange", type="direct") try: - self.channel.exchange_declare(exchange="test_different_declared_type_exchange", type="topic") + session = self.conn.session("alternate", 2) + session.exchange_declare(exchange="test_different_declared_type_exchange", type="topic") self.fail("Expected 530 for redeclaration of exchange with different type.") - except Closed, e: - self.assertConnectionException(530, e.args[0]) - #cleanup - other = self.connect() - c2 = other.channel(1) - c2.session_open() - c2.exchange_delete(exchange="test_different_declared_type_exchange") + except SessionException, e: + self.assertEquals(530, e.args[0].error_code) -class ExchangeTests(TestBase): +class ExchangeTests(TestHelper): def testHeadersBindNoMatchArg(self): - self.channel.queue_declare(queue="q", exclusive=True, auto_delete=True) + self.session.queue_declare(queue="q", exclusive=True, auto_delete=True) try: - self.channel.queue_bind(queue="q", exchange="amq.match", arguments={"name":"fred" , "age":3} ) + self.session.exchange_bind(queue="q", exchange="amq.match", arguments={"name":"fred" , "age":3} ) self.fail("Expected failure for missing x-match arg.") - except Closed, e: - self.assertConnectionException(541, e.args[0]) + except SessionException, e: + self.assertEquals(541, e.args[0].error_code) diff --git a/qpid/python/tests_0-10/message.py b/qpid/python/tests_0-10/message.py index a3d32bdb2d..2b1b446e7a 100644 --- a/qpid/python/tests_0-10/message.py +++ b/qpid/python/tests_0-10/message.py @@ -18,40 +18,43 @@ # from qpid.client import Client, Closed from qpid.queue import Empty +from qpid.testlib import TestBase010 +from qpid.datatypes import Message, RangedSet +from qpid.session import SessionException + from qpid.content import Content -from qpid.testlib import testrunner, TestBase -from qpid.reference import Reference, ReferenceId -class MessageTests(TestBase): + +class MessageTests(TestBase010): """Tests for 'methods' on the amqp message 'class'""" - def test_consume_no_local(self): + def test_no_local(self): """ Test that the no_local flag is honoured in the consume method """ - channel = self.channel - #setup, declare two queues: - channel.queue_declare(queue="test-queue-1a", exclusive=True, auto_delete=True) - channel.queue_declare(queue="test-queue-1b", exclusive=True, auto_delete=True) - #establish two consumers one of which excludes delivery of locally sent messages + session = self.session + #setup, declare two queues one of which excludes delivery of locally sent messages + session.queue_declare(queue="test-queue-1a", exclusive=True, auto_delete=True) + session.queue_declare(queue="test-queue-1b", exclusive=True, auto_delete=True, arguments={'no-local':'true'}) + #establish two consumers self.subscribe(destination="local_included", queue="test-queue-1a") - self.subscribe(destination="local_excluded", queue="test-queue-1b", no_local=True) + self.subscribe(destination="local_excluded", queue="test-queue-1b") #send a message - channel.message_transfer(content=Content(properties={'routing_key' : "test-queue-1a"}, body="consume_no_local")) - channel.message_transfer(content=Content(properties={'routing_key' : "test-queue-1b"}, body="consume_no_local")) + session.message_transfer(message=Message(session.delivery_properties(routing_key="test-queue-1a"), "deliver-me")) + session.message_transfer(message=Message(session.delivery_properties(routing_key="test-queue-1b"), "dont-deliver-me")) #check the queues of the two consumers - excluded = self.client.queue("local_excluded") - included = self.client.queue("local_included") + excluded = session.incoming("local_excluded") + included = session.incoming("local_included") msg = included.get(timeout=1) - self.assertEqual("consume_no_local", msg.content.body) + self.assertEqual("deliver-me", msg.body) try: excluded.get(timeout=1) self.fail("Received locally published message though no_local=true") except Empty: None - def test_consume_no_local_awkward(self): + def test_no_local_awkward(self): """ If an exclusive queue gets a no-local delivered to it, that @@ -62,141 +65,143 @@ class MessageTests(TestBase): deletes such messages. """ - channel = self.channel + session = self.session #setup: - channel.queue_declare(queue="test-queue", exclusive=True, auto_delete=True) + session.queue_declare(queue="test-queue", exclusive=True, auto_delete=True, arguments={'no-local':'true'}) #establish consumer which excludes delivery of locally sent messages - self.subscribe(destination="local_excluded", queue="test-queue", no_local=True) + self.subscribe(destination="local_excluded", queue="test-queue") #send a 'local' message - channel.message_transfer(content=Content(properties={'routing_key' : "test-queue"}, body="local")) + session.message_transfer(message=Message(session.delivery_properties(routing_key="test-queue"), "local")) #send a non local message other = self.connect() - channel2 = other.channel(1) - channel2.session_open() - channel2.message_transfer(content=Content(properties={'routing_key' : "test-queue"}, body="foreign")) - channel2.session_close() + session2 = other.session("my-session", 1) + session2.message_transfer(message=Message(session2.delivery_properties(routing_key="test-queue"), "foreign")) + session2.close() other.close() #check that the second message only is delivered - excluded = self.client.queue("local_excluded") + excluded = session.incoming("local_excluded") msg = excluded.get(timeout=1) - self.assertEqual("foreign", msg.content.body) + self.assertEqual("foreign", msg.body) try: excluded.get(timeout=1) self.fail("Received extra message") except Empty: None #check queue is empty - self.assertEqual(0, channel.queue_query(queue="test-queue").message_count) + self.assertEqual(0, session.queue_query(queue="test-queue").message_count) def test_consume_exclusive(self): """ - Test that the exclusive flag is honoured in the consume method + Test an exclusive consumer prevents other consumer being created """ - channel = self.channel - #setup, declare a queue: - channel.queue_declare(queue="test-queue-2", exclusive=True, auto_delete=True) - - #check that an exclusive consumer prevents other consumer being created: - self.subscribe(destination="first", queue="test-queue-2", exclusive=True) + session = self.session + session.queue_declare(queue="test-queue-2", exclusive=True, auto_delete=True) + session.message_subscribe(destination="first", queue="test-queue-2", exclusive=True) try: - self.subscribe(destination="second", queue="test-queue-2") + session.message_subscribe(destination="second", queue="test-queue-2") self.fail("Expected consume request to fail due to previous exclusive consumer") - except Closed, e: - self.assertChannelException(403, e.args[0]) - - #open new channel and cleanup last consumer: - channel = self.client.channel(2) - channel.session_open() + except SessionException, e: + self.assertEquals(403, e.args[0].error_code) - #check that an exclusive consumer cannot be created if a consumer already exists: - self.subscribe(channel, destination="first", queue="test-queue-2") + def test_consume_exclusive2(self): + """ + Check that an exclusive consumer cannot be created if a consumer already exists: + """ + session = self.session + session.queue_declare(queue="test-queue-2", exclusive=True, auto_delete=True) + session.message_subscribe(destination="first", queue="test-queue-2") try: - self.subscribe(destination="second", queue="test-queue-2", exclusive=True) + session.message_subscribe(destination="second", queue="test-queue-2", exclusive=True) self.fail("Expected exclusive consume request to fail due to previous consumer") - except Closed, e: - self.assertChannelException(403, e.args[0]) + except SessionException, e: + self.assertEquals(403, e.args[0].error_code) - def test_consume_queue_errors(self): + def test_consume_queue_not_found(self): """ Test error conditions associated with the queue field of the consume method: """ - channel = self.channel + session = self.session try: #queue specified but doesn't exist: - self.subscribe(queue="invalid-queue", destination="") + session.message_subscribe(queue="invalid-queue", destination="a") self.fail("Expected failure when consuming from non-existent queue") - except Closed, e: - self.assertChannelException(404, e.args[0]) + except SessionException, e: + self.assertEquals(404, e.args[0].error_code) - channel = self.client.channel(2) - channel.session_open() + def test_consume_queue_not_specified(self): + session = self.session try: #queue not specified and none previously declared for channel: - self.subscribe(channel, queue="", destination="") + session.message_subscribe(destination="a") self.fail("Expected failure when consuming from unspecified queue") - except Closed, e: - self.assertConnectionException(530, e.args[0]) + except SessionException, e: + self.assertEquals(531, e.args[0].error_code) def test_consume_unique_consumers(self): """ Ensure unique consumer tags are enforced """ - channel = self.channel + session = self.session #setup, declare a queue: - channel.queue_declare(queue="test-queue-3", exclusive=True, auto_delete=True) + session.queue_declare(queue="test-queue-3", exclusive=True, auto_delete=True) #check that attempts to use duplicate tags are detected and prevented: - self.subscribe(destination="first", queue="test-queue-3") + session.message_subscribe(destination="first", queue="test-queue-3") try: - self.subscribe(destination="first", queue="test-queue-3") + session.message_subscribe(destination="first", queue="test-queue-3") self.fail("Expected consume request to fail due to non-unique tag") - except Closed, e: - self.assertConnectionException(530, e.args[0]) + except SessionException, e: + self.assertEquals(530, e.args[0].error_code) def test_cancel(self): """ Test compliance of the basic.cancel method """ - channel = self.channel + session = self.session #setup, declare a queue: - channel.queue_declare(queue="test-queue-4", exclusive=True, auto_delete=True) - self.subscribe(destination="my-consumer", queue="test-queue-4") - channel.message_transfer(content=Content(properties={'routing_key' : "test-queue-4"}, body="One")) + session.queue_declare(queue="test-queue-4", exclusive=True, auto_delete=True) + session.message_transfer(message=Message(session.delivery_properties(routing_key="test-queue-4"), "One")) + + session.message_subscribe(destination="my-consumer", queue="test-queue-4") + session.message_flow(destination="my-consumer", unit=0, value=0xFFFFFFFF) + session.message_flow(destination="my-consumer", unit=1, value=0xFFFFFFFF) + + #should flush here #cancel should stop messages being delivered - channel.message_cancel(destination="my-consumer") - channel.message_transfer(content=Content(properties={'routing_key' : "test-queue-4"}, body="Two")) - myqueue = self.client.queue("my-consumer") + session.message_cancel(destination="my-consumer") + session.message_transfer(message=Message(session.delivery_properties(routing_key="test-queue-4"), "Two")) + myqueue = session.incoming("my-consumer") msg = myqueue.get(timeout=1) - self.assertEqual("One", msg.content.body) + self.assertEqual("One", msg.body) try: msg = myqueue.get(timeout=1) self.fail("Got message after cancellation: " + msg) except Empty: None #cancellation of non-existant consumers should be handled without error - channel.message_cancel(destination="my-consumer") - channel.message_cancel(destination="this-never-existed") + session.message_cancel(destination="my-consumer") + session.message_cancel(destination="this-never-existed") def test_ack(self): """ Test basic ack/recover behaviour """ - channel = self.channel - channel.queue_declare(queue="test-ack-queue", exclusive=True, auto_delete=True) + session = self.conn.session("alternate-session", timeout=10) + session.queue_declare(queue="test-ack-queue", auto_delete=True) - self.subscribe(queue="test-ack-queue", destination="consumer_tag", confirm_mode=1) - queue = self.client.queue("consumer_tag") + session.message_subscribe(queue = "test-ack-queue", destination = "consumer") + session.message_flow(destination="consumer", unit=0, value=0xFFFFFFFF) + session.message_flow(destination="consumer", unit=1, value=0xFFFFFFFF) + queue = session.incoming("consumer") - channel.message_transfer(content=Content(properties={'routing_key' : "test-ack-queue"}, body="One")) - channel.message_transfer(content=Content(properties={'routing_key' : "test-ack-queue"}, body="Two")) - channel.message_transfer(content=Content(properties={'routing_key' : "test-ack-queue"}, body="Three")) - channel.message_transfer(content=Content(properties={'routing_key' : "test-ack-queue"}, body="Four")) - channel.message_transfer(content=Content(properties={'routing_key' : "test-ack-queue"}, body="Five")) + delivery_properties = session.delivery_properties(routing_key="test-ack-queue") + for i in ["One", "Two", "Three", "Four", "Five"]: + session.message_transfer(message=Message(delivery_properties, i)) msg1 = queue.get(timeout=1) msg2 = queue.get(timeout=1) @@ -204,26 +209,36 @@ class MessageTests(TestBase): msg4 = queue.get(timeout=1) msg5 = queue.get(timeout=1) - self.assertEqual("One", msg1.content.body) - self.assertEqual("Two", msg2.content.body) - self.assertEqual("Three", msg3.content.body) - self.assertEqual("Four", msg4.content.body) - self.assertEqual("Five", msg5.content.body) + self.assertEqual("One", msg1.body) + self.assertEqual("Two", msg2.body) + self.assertEqual("Three", msg3.body) + self.assertEqual("Four", msg4.body) + self.assertEqual("Five", msg5.body) - msg2.complete(cumulative=True)#One and Two - msg4.complete(cumulative=False) + session.message_accept(RangedSet(msg1.id, msg2.id, msg4.id))#One, Two and Four - channel.message_recover(requeue=False) + #subscribe from second session here to ensure queue is not + #auto-deleted when alternate session closes (no need to ack on these): + self.session.message_subscribe(queue = "test-ack-queue", destination = "checker", accept_mode=1) + + #now close the session, and see that the unacked messages are + #then redelivered to another subscriber: + session.close(timeout=10) + + session = self.session + session.message_flow(destination="checker", unit=0, value=0xFFFFFFFF) + session.message_flow(destination="checker", unit=1, value=0xFFFFFFFF) + queue = session.incoming("checker") msg3b = queue.get(timeout=1) msg5b = queue.get(timeout=1) - self.assertEqual("Three", msg3b.content.body) - self.assertEqual("Five", msg5b.content.body) + self.assertEqual("Three", msg3b.body) + self.assertEqual("Five", msg5b.body) try: extra = queue.get(timeout=1) - self.fail("Got unexpected message: " + extra.content.body) + self.fail("Got unexpected message: " + extra.body) except Empty: None @@ -231,27 +246,27 @@ class MessageTests(TestBase): """ Test recover behaviour """ - channel = self.channel - channel.queue_declare(queue="queue-a", exclusive=True, auto_delete=True) - channel.queue_bind(exchange="amq.fanout", queue="queue-a") - channel.queue_declare(queue="queue-b", exclusive=True, auto_delete=True) - channel.queue_bind(exchange="amq.fanout", queue="queue-b") + session = self.session + session.queue_declare(queue="queue-a", exclusive=True, auto_delete=True) + session.queue_bind(exchange="amq.fanout", queue="queue-a") + session.queue_declare(queue="queue-b", exclusive=True, auto_delete=True) + session.queue_bind(exchange="amq.fanout", queue="queue-b") self.subscribe(queue="queue-a", destination="unconfirmed", confirm_mode=1) self.subscribe(queue="queue-b", destination="confirmed", confirm_mode=0) - confirmed = self.client.queue("confirmed") - unconfirmed = self.client.queue("unconfirmed") + confirmed = session.incoming("confirmed") + unconfirmed = session.incoming("unconfirmed") data = ["One", "Two", "Three", "Four", "Five"] for d in data: - channel.message_transfer(destination="amq.fanout", content=Content(body=d)) + session.message_transfer(destination="amq.fanout", content=Content(body=d)) for q in [confirmed, unconfirmed]: for d in data: self.assertEqual(d, q.get(timeout=1).content.body) self.assertEmpty(q) - channel.message_recover(requeue=False) + session.message_recover(requeue=False) self.assertEmpty(confirmed) @@ -259,29 +274,29 @@ class MessageTests(TestBase): msg = None for d in data: msg = unconfirmed.get(timeout=1) - self.assertEqual(d, msg.content.body) + self.assertEqual(d, msg.body) self.assertEqual(True, msg.content['redelivered']) self.assertEmpty(unconfirmed) - data.remove(msg.content.body) + data.remove(msg.body) msg.complete(cumulative=False) - channel.message_recover(requeue=False) + session.message_recover(requeue=False) def test_recover_requeue(self): """ Test requeing on recovery """ - channel = self.channel - channel.queue_declare(queue="test-requeue", exclusive=True, auto_delete=True) + session = self.session + session.queue_declare(queue="test-requeue", exclusive=True, auto_delete=True) self.subscribe(queue="test-requeue", destination="consumer_tag", confirm_mode=1) - queue = self.client.queue("consumer_tag") + queue = session.incoming("consumer_tag") - channel.message_transfer(content=Content(properties={'routing_key' : "test-requeue"}, body="One")) - channel.message_transfer(content=Content(properties={'routing_key' : "test-requeue"}, body="Two")) - channel.message_transfer(content=Content(properties={'routing_key' : "test-requeue"}, body="Three")) - channel.message_transfer(content=Content(properties={'routing_key' : "test-requeue"}, body="Four")) - channel.message_transfer(content=Content(properties={'routing_key' : "test-requeue"}, body="Five")) + session.message_transfer(content=Content(properties={'routing_key' : "test-requeue"}, body="One")) + session.message_transfer(content=Content(properties={'routing_key' : "test-requeue"}, body="Two")) + session.message_transfer(content=Content(properties={'routing_key' : "test-requeue"}, body="Three")) + session.message_transfer(content=Content(properties={'routing_key' : "test-requeue"}, body="Four")) + session.message_transfer(content=Content(properties={'routing_key' : "test-requeue"}, body="Five")) msg1 = queue.get(timeout=1) msg2 = queue.get(timeout=1) @@ -298,15 +313,15 @@ class MessageTests(TestBase): msg2.complete(cumulative=True) #One and Two msg4.complete(cumulative=False) #Four - channel.message_cancel(destination="consumer_tag") + session.message_cancel(destination="consumer_tag") #publish a new message - channel.message_transfer(content=Content(properties={'routing_key' : "test-requeue"}, body="Six")) + session.message_transfer(content=Content(properties={'routing_key' : "test-requeue"}, body="Six")) #requeue unacked messages (Three and Five) - channel.message_recover(requeue=True) + session.message_recover(requeue=True) self.subscribe(queue="test-requeue", destination="consumer_tag") - queue2 = self.client.queue("consumer_tag") + queue2 = session.incoming("consumer_tag") msg3b = queue2.get(timeout=1) msg5b = queue2.get(timeout=1) @@ -334,22 +349,22 @@ class MessageTests(TestBase): Test that the prefetch count specified is honoured """ #setup: declare queue and subscribe - channel = self.channel - channel.queue_declare(queue="test-prefetch-count", exclusive=True, auto_delete=True) + session = self.session + session.queue_declare(queue="test-prefetch-count", exclusive=True, auto_delete=True) subscription = self.subscribe(queue="test-prefetch-count", destination="consumer_tag", confirm_mode=1) - queue = self.client.queue("consumer_tag") + queue = session.incoming("consumer_tag") #set prefetch to 5: - channel.message_qos(prefetch_count=5) + session.message_qos(prefetch_count=5) #publish 10 messages: for i in range(1, 11): - channel.message_transfer(content=Content(properties={'routing_key' : "test-prefetch-count"}, body="Message %d" % i)) + session.message_transfer(content=Content(properties={'routing_key' : "test-prefetch-count"}, body="Message %d" % i)) #only 5 messages should have been delivered: for i in range(1, 6): msg = queue.get(timeout=1) - self.assertEqual("Message %d" % i, msg.content.body) + self.assertEqual("Message %d" % i, msg.body) try: extra = queue.get(timeout=1) self.fail("Got unexpected 6th message in original queue: " + extra.content.body) @@ -360,7 +375,7 @@ class MessageTests(TestBase): for i in range(6, 11): msg = queue.get(timeout=1) - self.assertEqual("Message %d" % i, msg.content.body) + self.assertEqual("Message %d" % i, msg.body) msg.complete() @@ -376,22 +391,22 @@ class MessageTests(TestBase): Test that the prefetch size specified is honoured """ #setup: declare queue and subscribe - channel = self.channel - channel.queue_declare(queue="test-prefetch-size", exclusive=True, auto_delete=True) + session = self.session + session.queue_declare(queue="test-prefetch-size", exclusive=True, auto_delete=True) subscription = self.subscribe(queue="test-prefetch-size", destination="consumer_tag", confirm_mode=1) - queue = self.client.queue("consumer_tag") + queue = session.incoming("consumer_tag") #set prefetch to 50 bytes (each message is 9 or 10 bytes): - channel.message_qos(prefetch_size=50) + session.message_qos(prefetch_size=50) #publish 10 messages: for i in range(1, 11): - channel.message_transfer(content=Content(properties={'routing_key' : "test-prefetch-size"}, body="Message %d" % i)) + session.message_transfer(content=Content(properties={'routing_key' : "test-prefetch-size"}, body="Message %d" % i)) #only 5 messages should have been delivered (i.e. 45 bytes worth): for i in range(1, 6): msg = queue.get(timeout=1) - self.assertEqual("Message %d" % i, msg.content.body) + self.assertEqual("Message %d" % i, msg.body) try: extra = queue.get(timeout=1) @@ -403,7 +418,7 @@ class MessageTests(TestBase): for i in range(6, 11): msg = queue.get(timeout=1) - self.assertEqual("Message %d" % i, msg.content.body) + self.assertEqual("Message %d" % i, msg.body) msg.complete() @@ -415,54 +430,58 @@ class MessageTests(TestBase): #make sure that a single oversized message still gets delivered large = "abcdefghijklmnopqrstuvwxyz" large = large + "-" + large; - channel.message_transfer(content=Content(properties={'routing_key' : "test-prefetch-size"}, body=large)) + session.message_transfer(content=Content(properties={'routing_key' : "test-prefetch-size"}, body=large)) msg = queue.get(timeout=1) - self.assertEqual(large, msg.content.body) + self.assertEqual(large, msg.body) def test_reject(self): - channel = self.channel - channel.queue_declare(queue = "q", exclusive=True, auto_delete=True, alternate_exchange="amq.fanout") - channel.queue_declare(queue = "r", exclusive=True, auto_delete=True) - channel.queue_bind(queue = "r", exchange = "amq.fanout") - - self.subscribe(queue = "q", destination = "consumer", confirm_mode = 1) - channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body="blah, blah")) - msg = self.client.queue("consumer").get(timeout = 1) - self.assertEquals(msg.content.body, "blah, blah") - channel.message_reject([msg.command_id, msg.command_id]) - - self.subscribe(queue = "r", destination = "checker") - msg = self.client.queue("checker").get(timeout = 1) - self.assertEquals(msg.content.body, "blah, blah") + session = self.session + session.queue_declare(queue = "q", exclusive=True, auto_delete=True, alternate_exchange="amq.fanout") + session.queue_declare(queue = "r", exclusive=True, auto_delete=True) + session.exchange_bind(queue = "r", exchange = "amq.fanout") + + session.message_subscribe(queue = "q", destination = "consumer") + session.message_flow(destination="consumer", unit=0, value=0xFFFFFFFF) + session.message_flow(destination="consumer", unit=1, value=0xFFFFFFFF) + session.message_transfer(message=Message(session.delivery_properties(routing_key="q"), "blah, blah")) + msg = session.incoming("consumer").get(timeout = 1) + self.assertEquals(msg.body, "blah, blah") + session.message_reject(RangedSet(msg.id)) + + session.message_subscribe(queue = "r", destination = "checker") + session.message_flow(destination="checker", unit=0, value=0xFFFFFFFF) + session.message_flow(destination="checker", unit=1, value=0xFFFFFFFF) + msg = session.incoming("checker").get(timeout = 1) + self.assertEquals(msg.body, "blah, blah") def test_credit_flow_messages(self): """ Test basic credit based flow control with unit = message """ #declare an exclusive queue - channel = self.channel - channel.queue_declare(queue = "q", exclusive=True, auto_delete=True) + session = self.session + session.queue_declare(queue = "q", exclusive=True, auto_delete=True) #create consumer (for now that defaults to infinite credit) - channel.message_subscribe(queue = "q", destination = "c") - channel.message_flow_mode(mode = 0, destination = "c") + session.message_subscribe(queue = "q", destination = "c") + session.message_set_flow_mode(flow_mode = 0, destination = "c") #send batch of messages to queue for i in range(1, 11): - channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body = "Message %d" % i)) + session.message_transfer(message=Message(session.delivery_properties(routing_key="q"), "Message %d" % i)) #set message credit to finite amount (less than enough for all messages) - channel.message_flow(unit = 0, value = 5, destination = "c") + session.message_flow(unit = 0, value = 5, destination = "c") #set infinite byte credit - channel.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "c") + session.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "c") #check that expected number were received - q = self.client.queue("c") + q = session.incoming("c") for i in range(1, 6): - self.assertDataEquals(channel, q.get(timeout = 1), "Message %d" % i) + self.assertDataEquals(session, q.get(timeout = 1), "Message %d" % i) self.assertEmpty(q) #increase credit again and check more are received for i in range(6, 11): - channel.message_flow(unit = 0, value = 1, destination = "c") - self.assertDataEquals(channel, q.get(timeout = 1), "Message %d" % i) + session.message_flow(unit = 0, value = 1, destination = "c") + self.assertDataEquals(session, q.get(timeout = 1), "Message %d" % i) self.assertEmpty(q) def test_credit_flow_bytes(self): @@ -470,32 +489,32 @@ class MessageTests(TestBase): Test basic credit based flow control with unit = bytes """ #declare an exclusive queue - channel = self.channel - channel.queue_declare(queue = "q", exclusive=True, auto_delete=True) + session = self.session + session.queue_declare(queue = "q", exclusive=True, auto_delete=True) #create consumer (for now that defaults to infinite credit) - channel.message_subscribe(queue = "q", destination = "c") - channel.message_flow_mode(mode = 0, destination = "c") + session.message_subscribe(queue = "q", destination = "c") + session.message_set_flow_mode(flow_mode = 0, destination = "c") #send batch of messages to queue for i in range(10): - channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body = "abcdefgh")) + session.message_transfer(message=Message(session.delivery_properties(routing_key="q"), "abcdefgh")) #each message is currently interpreted as requiring msg_size bytes of credit - msg_size = 35 + msg_size = 21 #set byte credit to finite amount (less than enough for all messages) - channel.message_flow(unit = 1, value = msg_size*5, destination = "c") + session.message_flow(unit = 1, value = msg_size*5, destination = "c") #set infinite message credit - channel.message_flow(unit = 0, value = 0xFFFFFFFF, destination = "c") + session.message_flow(unit = 0, value = 0xFFFFFFFF, destination = "c") #check that expected number were received - q = self.client.queue("c") + q = session.incoming("c") for i in range(5): - self.assertDataEquals(channel, q.get(timeout = 1), "abcdefgh") + self.assertDataEquals(session, q.get(timeout = 1), "abcdefgh") self.assertEmpty(q) #increase credit again and check more are received for i in range(5): - channel.message_flow(unit = 1, value = msg_size, destination = "c") - self.assertDataEquals(channel, q.get(timeout = 1), "abcdefgh") + session.message_flow(unit = 1, value = msg_size, destination = "c") + self.assertDataEquals(session, q.get(timeout = 1), "abcdefgh") self.assertEmpty(q) @@ -504,30 +523,33 @@ class MessageTests(TestBase): Test basic window based flow control with unit = message """ #declare an exclusive queue - channel = self.channel - channel.queue_declare(queue = "q", exclusive=True, auto_delete=True) + session = self.session + session.queue_declare(queue = "q", exclusive=True, auto_delete=True) #create consumer (for now that defaults to infinite credit) - channel.message_subscribe(queue = "q", destination = "c", confirm_mode = 1) - channel.message_flow_mode(mode = 1, destination = "c") + session.message_subscribe(queue = "q", destination = "c") + session.message_set_flow_mode(flow_mode = 1, destination = "c") #send batch of messages to queue for i in range(1, 11): - channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body = "Message %d" % i)) + session.message_transfer(message=Message(session.delivery_properties(routing_key="q"), "Message %d" % i)) #set message credit to finite amount (less than enough for all messages) - channel.message_flow(unit = 0, value = 5, destination = "c") + session.message_flow(unit = 0, value = 5, destination = "c") #set infinite byte credit - channel.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "c") + session.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "c") #check that expected number were received - q = self.client.queue("c") - for i in range(1, 6): + q = session.incoming("c") + for i in range(1, 6): msg = q.get(timeout = 1) - self.assertDataEquals(channel, msg, "Message %d" % i) + session.receiver._completed.add(msg.id)#TODO: this may be done automatically + self.assertDataEquals(session, msg, "Message %d" % i) self.assertEmpty(q) #acknowledge messages and check more are received - msg.complete(cumulative=True) + #TODO: there may be a nicer way of doing this + session.channel.session_completed(session.receiver._completed) + for i in range(6, 11): - self.assertDataEquals(channel, q.get(timeout = 1), "Message %d" % i) + self.assertDataEquals(session, q.get(timeout = 1), "Message %d" % i) self.assertEmpty(q) @@ -536,296 +558,368 @@ class MessageTests(TestBase): Test basic window based flow control with unit = bytes """ #declare an exclusive queue - channel = self.channel - channel.queue_declare(queue = "q", exclusive=True, auto_delete=True) + session = self.session + session.queue_declare(queue = "q", exclusive=True, auto_delete=True) #create consumer (for now that defaults to infinite credit) - channel.message_subscribe(queue = "q", destination = "c", confirm_mode = 1) - channel.message_flow_mode(mode = 1, destination = "c") + session.message_subscribe(queue = "q", destination = "c") + session.message_set_flow_mode(flow_mode = 1, destination = "c") #send batch of messages to queue for i in range(10): - channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body = "abcdefgh")) + session.message_transfer(message=Message(session.delivery_properties(routing_key="q"), "abcdefgh")) #each message is currently interpreted as requiring msg_size bytes of credit - msg_size = 40 + msg_size = 19 #set byte credit to finite amount (less than enough for all messages) - channel.message_flow(unit = 1, value = msg_size*5, destination = "c") + session.message_flow(unit = 1, value = msg_size*5, destination = "c") #set infinite message credit - channel.message_flow(unit = 0, value = 0xFFFFFFFF, destination = "c") + session.message_flow(unit = 0, value = 0xFFFFFFFF, destination = "c") #check that expected number were received - q = self.client.queue("c") + q = session.incoming("c") msgs = [] for i in range(5): msg = q.get(timeout = 1) msgs.append(msg) - self.assertDataEquals(channel, msg, "abcdefgh") + self.assertDataEquals(session, msg, "abcdefgh") self.assertEmpty(q) #ack each message individually and check more are received for i in range(5): msg = msgs.pop() - msg.complete(cumulative=False) - self.assertDataEquals(channel, q.get(timeout = 1), "abcdefgh") + #TODO: there may be a nicer way of doing this + session.receiver._completed.add(msg.id) + session.channel.session_completed(session.receiver._completed) + self.assertDataEquals(session, q.get(timeout = 1), "abcdefgh") self.assertEmpty(q) def test_subscribe_not_acquired(self): """ Test the not-acquired modes works as expected for a simple case """ - channel = self.channel - channel.queue_declare(queue = "q", exclusive=True, auto_delete=True) + session = self.session + session.queue_declare(queue = "q", exclusive=True, auto_delete=True) for i in range(1, 6): - channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body = "Message %s" % i)) + session.message_transfer(message=Message(session.delivery_properties(routing_key="q"), "Message %s" % i)) - self.subscribe(queue = "q", destination = "a", acquire_mode = 1) - self.subscribe(queue = "q", destination = "b", acquire_mode = 1) + session.message_subscribe(queue = "q", destination = "a", acquire_mode = 1) + session.message_flow(unit = 0, value = 0xFFFFFFFF, destination = "a") + session.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "a") + session.message_subscribe(queue = "q", destination = "b", acquire_mode = 1) + session.message_flow(unit = 0, value = 0xFFFFFFFF, destination = "b") + session.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "b") for i in range(6, 11): - channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body = "Message %s" % i)) + session.message_transfer(message=Message(session.delivery_properties(routing_key="q"), "Message %s" % i)) #both subscribers should see all messages - qA = self.client.queue("a") - qB = self.client.queue("b") + qA = session.incoming("a") + qB = session.incoming("b") for i in range(1, 11): for q in [qA, qB]: msg = q.get(timeout = 1) - self.assertEquals("Message %s" % i, msg.content.body) - msg.complete() + self.assertEquals("Message %s" % i, msg.body) + #TODO: tidy up completion + session.receiver._completed.add(msg.id) + #TODO: tidy up completion + session.channel.session_completed(session.receiver._completed) #messages should still be on the queue: - self.assertEquals(10, channel.queue_query(queue = "q").message_count) + self.assertEquals(10, session.queue_query(queue = "q").message_count) def test_acquire(self): """ Test explicit acquire function """ - channel = self.channel - channel.queue_declare(queue = "q", exclusive=True, auto_delete=True) - channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body = "acquire me")) + session = self.session + session.queue_declare(queue = "q", exclusive=True, auto_delete=True) + + #use fanout for now: + session.message_transfer(message=Message(session.delivery_properties(routing_key="q"), "acquire me")) - self.subscribe(queue = "q", destination = "a", acquire_mode = 1, confirm_mode = 1) - msg = self.client.queue("a").get(timeout = 1) + session.message_subscribe(queue = "q", destination = "a", acquire_mode = 1) + session.message_flow(destination="a", unit=0, value=0xFFFFFFFF) + session.message_flow(destination="a", unit=1, value=0xFFFFFFFF) + msg = session.incoming("a").get(timeout = 1) + self.assertEquals("acquire me", msg.body) #message should still be on the queue: - self.assertEquals(1, channel.queue_query(queue = "q").message_count) + self.assertEquals(1, session.queue_query(queue = "q").message_count) - channel.message_acquire([msg.command_id, msg.command_id]) + transfers = RangedSet(msg.id) + response = session.message_acquire(transfers) #check that we get notification (i.e. message_acquired) - response = channel.control_queue.get(timeout=1) - self.assertEquals(response.transfers, [msg.command_id, msg.command_id]) + self.assert_(msg.id in response.transfers) #message should have been removed from the queue: - self.assertEquals(0, channel.queue_query(queue = "q").message_count) - msg.complete() - - + self.assertEquals(0, session.queue_query(queue = "q").message_count) + session.message_accept(transfers) def test_release(self): """ Test explicit release function """ - channel = self.channel - channel.queue_declare(queue = "q", exclusive=True, auto_delete=True) - channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body = "release me")) + session = self.session + session.queue_declare(queue = "q", exclusive=True, auto_delete=True) - self.subscribe(queue = "q", destination = "a", acquire_mode = 0, confirm_mode = 1) - msg = self.client.queue("a").get(timeout = 1) - channel.message_cancel(destination = "a") - channel.message_release([msg.command_id, msg.command_id]) - msg.complete() + session.message_transfer(message=Message(session.delivery_properties(routing_key="q"), "release me")) + + session.message_subscribe(queue = "q", destination = "a") + session.message_flow(destination="a", unit=0, value=0xFFFFFFFF) + session.message_flow(destination="a", unit=1, value=0xFFFFFFFF) + msg = session.incoming("a").get(timeout = 1) + self.assertEquals("release me", msg.body) + session.message_cancel(destination = "a") + session.message_release(RangedSet(msg.id)) #message should not have been removed from the queue: - self.assertEquals(1, channel.queue_query(queue = "q").message_count) + self.assertEquals(1, session.queue_query(queue = "q").message_count) def test_release_ordering(self): """ Test order of released messages is as expected """ - channel = self.channel - channel.queue_declare(queue = "q", exclusive=True, auto_delete=True) + session = self.session + session.queue_declare(queue = "q", exclusive=True, auto_delete=True) for i in range (1, 11): - channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body = "released message %s" % (i))) + session.message_transfer(message=Message(session.delivery_properties(routing_key="q"), "released message %s" % (i))) - channel.message_subscribe(queue = "q", destination = "a", confirm_mode = 1) - channel.message_flow(unit = 0, value = 10, destination = "a") - channel.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "a") - queue = self.client.queue("a") + session.message_subscribe(queue = "q", destination = "a") + session.message_flow(unit = 0, value = 10, destination = "a") + session.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "a") + queue = session.incoming("a") first = queue.get(timeout = 1) - for i in range (2, 10): - self.assertEquals("released message %s" % (i), queue.get(timeout = 1).content.body) + for i in range(2, 10): + msg = queue.get(timeout = 1) + self.assertEquals("released message %s" % (i), msg.body) + last = queue.get(timeout = 1) self.assertEmpty(queue) - channel.message_release([first.command_id, last.command_id]) - last.complete()#will re-allocate credit, as in window mode - for i in range (1, 11): - self.assertEquals("released message %s" % (i), queue.get(timeout = 1).content.body) + released = RangedSet() + released.add(first.id, last.id) + session.message_release(released) + + #TODO: may want to clean this up... + session.receiver._completed.add(first.id, last.id) + session.channel.session_completed(session.receiver._completed) + + for i in range(1, 11): + self.assertEquals("released message %s" % (i), queue.get(timeout = 1).body) def test_ranged_ack(self): """ Test acking of messages ranges """ - channel = self.channel - channel.queue_declare(queue = "q", exclusive=True, auto_delete=True) + session = self.conn.session("alternate-session", timeout=10) + + session.queue_declare(queue = "q", auto_delete=True) + delivery_properties = session.delivery_properties(routing_key="q") for i in range (1, 11): - channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body = "message %s" % (i))) + session.message_transfer(message=Message(delivery_properties, "message %s" % (i))) - channel.message_subscribe(queue = "q", destination = "a", confirm_mode = 1) - channel.message_flow(unit = 0, value = 10, destination = "a") - channel.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "a") - queue = self.client.queue("a") + session.message_subscribe(queue = "q", destination = "a") + session.message_flow(unit = 0, value = 10, destination = "a") + session.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "a") + queue = session.incoming("a") + ids = [] for i in range (1, 11): - self.assertEquals("message %s" % (i), queue.get(timeout = 1).content.body) + msg = queue.get(timeout = 1) + self.assertEquals("message %s" % (i), msg.body) + ids.append(msg.id) + self.assertEmpty(queue) - #ack all but the third message (command id 2) - channel.execution_complete(cumulative_execution_mark=0xFFFFFFFF, ranged_execution_set=[0,1,3,6,7,7,8,9]) - channel.message_recover() - self.assertEquals("message 3", queue.get(timeout = 1).content.body) + #ack all but the fourth message (command id 2) + accepted = RangedSet() + accepted.add(ids[0], ids[2]) + accepted.add(ids[4], ids[9]) + session.message_accept(accepted) + + #subscribe from second session here to ensure queue is not + #auto-deleted when alternate session closes (no need to ack on these): + self.session.message_subscribe(queue = "q", destination = "checker") + + #now close the session, and see that the unacked messages are + #then redelivered to another subscriber: + session.close(timeout=10) + + session = self.session + session.message_flow(destination="checker", unit=0, value=0xFFFFFFFF) + session.message_flow(destination="checker", unit=1, value=0xFFFFFFFF) + queue = session.incoming("checker") + + self.assertEquals("message 4", queue.get(timeout = 1).body) self.assertEmpty(queue) def test_subscribe_not_acquired_2(self): - channel = self.channel + session = self.session #publish some messages - self.queue_declare(queue = "q", exclusive=True, auto_delete=True) + session.queue_declare(queue = "q", exclusive=True, auto_delete=True) for i in range(1, 11): - channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body = "message-%d" % (i))) + session.message_transfer(message=Message(session.delivery_properties(routing_key="q"), "message-%d" % (i))) #consume some of them - channel.message_subscribe(queue = "q", destination = "a", confirm_mode = 1) - channel.message_flow_mode(mode = 0, destination = "a") - channel.message_flow(unit = 0, value = 5, destination = "a") - channel.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "a") + session.message_subscribe(queue = "q", destination = "a") + session.message_set_flow_mode(flow_mode = 0, destination = "a") + session.message_flow(unit = 0, value = 5, destination = "a") + session.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "a") - queue = self.client.queue("a") + queue = session.incoming("a") for i in range(1, 6): msg = queue.get(timeout = 1) - self.assertEquals("message-%d" % (i), msg.content.body) - msg.complete() + self.assertEquals("message-%d" % (i), msg.body) + #complete and accept + session.message_accept(RangedSet(msg.id)) + #TODO: tidy up completion + session.receiver._completed.add(msg.id) + session.channel.session_completed(session.receiver._completed) self.assertEmpty(queue) #now create a not-acquired subscriber - channel.message_subscribe(queue = "q", destination = "b", confirm_mode = 1, acquire_mode=1) - channel.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "b") + session.message_subscribe(queue = "q", destination = "b", acquire_mode=1) + session.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "b") #check it gets those not consumed - queue = self.client.queue("b") - channel.message_flow(unit = 0, value = 1, destination = "b") + queue = session.incoming("b") + session.message_flow(unit = 0, value = 1, destination = "b") for i in range(6, 11): msg = queue.get(timeout = 1) - self.assertEquals("message-%d" % (i), msg.content.body) - msg.complete() - channel.message_flow(unit = 0, value = 1, destination = "b") + self.assertEquals("message-%d" % (i), msg.body) + session.message_release(RangedSet(msg.id)) + #TODO: tidy up completion + session.receiver._completed.add(msg.id) + session.channel.session_completed(session.receiver._completed) + session.message_flow(unit = 0, value = 1, destination = "b") self.assertEmpty(queue) #check all 'browsed' messages are still on the queue - self.assertEqual(5, channel.queue_query(queue="q").message_count) + self.assertEqual(5, session.queue_query(queue="q").message_count) def test_subscribe_not_acquired_3(self): - channel = self.channel + session = self.session #publish some messages - self.queue_declare(queue = "q", exclusive=True, auto_delete=True) + session.queue_declare(queue = "q", exclusive=True, auto_delete=True) for i in range(1, 11): - channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body = "message-%d" % (i))) + session.message_transfer(message=Message(session.delivery_properties(routing_key="q"), "message-%d" % (i))) #create a not-acquired subscriber - channel.message_subscribe(queue = "q", destination = "a", confirm_mode = 1, acquire_mode=1) - channel.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "a") - channel.message_flow(unit = 0, value = 10, destination = "a") + session.message_subscribe(queue = "q", destination = "a", acquire_mode=1) + session.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "a") + session.message_flow(unit = 0, value = 10, destination = "a") #browse through messages - queue = self.client.queue("a") + queue = session.incoming("a") for i in range(1, 11): msg = queue.get(timeout = 1) - self.assertEquals("message-%d" % (i), msg.content.body) + self.assertEquals("message-%d" % (i), msg.body) if (i % 2): #try to acquire every second message - channel.message_acquire([msg.command_id, msg.command_id]) + response = session.message_acquire(RangedSet(msg.id)) #check that acquire succeeds - response = channel.control_queue.get(timeout=1) - self.assertEquals(response.transfers, [msg.command_id, msg.command_id]) - msg.complete() + self.assert_(msg.id in response.transfers) + session.message_accept(RangedSet(msg.id)) + else: + session.message_release(RangedSet(msg.id)) + session.receiver._completed.add(msg.id) + session.channel.session_completed(session.receiver._completed) self.assertEmpty(queue) #create a second not-acquired subscriber - channel.message_subscribe(queue = "q", destination = "b", confirm_mode = 1, acquire_mode=1) - channel.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "b") - channel.message_flow(unit = 0, value = 1, destination = "b") + session.message_subscribe(queue = "q", destination = "b", acquire_mode=1) + session.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "b") + session.message_flow(unit = 0, value = 1, destination = "b") #check it gets those not consumed - queue = self.client.queue("b") + queue = session.incoming("b") for i in [2,4,6,8,10]: msg = queue.get(timeout = 1) - self.assertEquals("message-%d" % (i), msg.content.body) - msg.complete() - channel.message_flow(unit = 0, value = 1, destination = "b") + self.assertEquals("message-%d" % (i), msg.body) + session.message_release(RangedSet(msg.id)) + session.receiver._completed.add(msg.id) + session.channel.session_completed(session.receiver._completed) + session.message_flow(unit = 0, value = 1, destination = "b") self.assertEmpty(queue) #check all 'browsed' messages are still on the queue - self.assertEqual(5, channel.queue_query(queue="q").message_count) + self.assertEqual(5, session.queue_query(queue="q").message_count) def test_release_unacquired(self): - channel = self.channel + session = self.session #create queue - self.queue_declare(queue = "q", exclusive=True, auto_delete=True, durable=True) + session.queue_declare(queue = "q", exclusive=True, auto_delete=True) #send message - channel.message_transfer(content=Content(properties={'routing_key' : "q", 'delivery_mode':2}, body = "my-message")) + session.message_transfer(message=Message(session.delivery_properties(routing_key="q"), "my-message")) #create two 'browsers' - channel.message_subscribe(queue = "q", destination = "a", confirm_mode = 1, acquire_mode=1) - channel.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "a") - channel.message_flow(unit = 0, value = 10, destination = "a") - queueA = self.client.queue("a") - - channel.message_subscribe(queue = "q", destination = "b", confirm_mode = 1, acquire_mode=1) - channel.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "b") - channel.message_flow(unit = 0, value = 10, destination = "b") - queueB = self.client.queue("b") + session.message_subscribe(queue = "q", destination = "a", acquire_mode=1) + session.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "a") + session.message_flow(unit = 0, value = 10, destination = "a") + queueA = session.incoming("a") + + session.message_subscribe(queue = "q", destination = "b", acquire_mode=1) + session.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "b") + session.message_flow(unit = 0, value = 10, destination = "b") + queueB = session.incoming("b") #have each browser release the message msgA = queueA.get(timeout = 1) - channel.message_release([msgA.command_id, msgA.command_id]) + session.message_release(RangedSet(msgA.id)) msgB = queueB.get(timeout = 1) - channel.message_release([msgB.command_id, msgB.command_id]) + session.message_release(RangedSet(msgB.id)) #cancel browsers - channel.message_cancel(destination = "a") - channel.message_cancel(destination = "b") + session.message_cancel(destination = "a") + session.message_cancel(destination = "b") #create consumer - channel.message_subscribe(queue = "q", destination = "c", confirm_mode = 1, acquire_mode=0) - channel.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "c") - channel.message_flow(unit = 0, value = 10, destination = "c") - queueC = self.client.queue("c") + session.message_subscribe(queue = "q", destination = "c") + session.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "c") + session.message_flow(unit = 0, value = 10, destination = "c") + queueC = session.incoming("c") #consume the message then ack it msgC = queueC.get(timeout = 1) - msgC.complete() + session.message_accept(RangedSet(msgC.id)) #ensure there are no other messages self.assertEmpty(queueC) def test_no_size(self): self.queue_declare(queue = "q", exclusive=True, auto_delete=True) - ch = self.channel + ch = self.session ch.message_transfer(content=SizelessContent(properties={'routing_key' : "q"}, body="message-body")) ch.message_subscribe(queue = "q", destination="d", confirm_mode = 0) ch.message_flow(unit = 0, value = 0xFFFFFFFF, destination = "d") ch.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "d") - queue = self.client.queue("d") + queue = session.incoming("d") msg = queue.get(timeout = 3) - self.assertEquals("message-body", msg.content.body) + self.assertEquals("message-body", msg.body) + + def test_empty_body(self): + session = self.session + session.queue_declare(queue="xyz", exclusive=True, auto_delete=True) + props = session.delivery_properties(routing_key="xyz") + session.message_transfer(message=Message(props, "")) + + consumer_tag = "tag1" + session.message_subscribe(queue="xyz", destination=consumer_tag) + session.message_flow(unit = 0, value = 0xFFFFFFFF, destination = consumer_tag) + session.message_flow(unit = 1, value = 0xFFFFFFFF, destination = consumer_tag) + queue = session.incoming(consumer_tag) + msg = queue.get(timeout=1) + self.assertEquals("", msg.body) + session.message_accept(RangedSet(msg.id)) - def assertDataEquals(self, channel, msg, expected): - self.assertEquals(expected, msg.content.body) + def assertDataEquals(self, session, msg, expected): + self.assertEquals(expected, msg.body) def assertEmpty(self, queue): try: extra = queue.get(timeout=1) - self.fail("Queue not empty, contains: " + extra.content.body) + self.fail("Queue not empty, contains: " + extra.body) except Empty: None class SizelessContent(Content): diff --git a/qpid/python/tests_0-10/query.py b/qpid/python/tests_0-10/query.py index eba2ee6dd1..0bbfb079cc 100644 --- a/qpid/python/tests_0-10/query.py +++ b/qpid/python/tests_0-10/query.py @@ -19,209 +19,212 @@ from qpid.client import Client, Closed from qpid.queue import Empty from qpid.content import Content -from qpid.testlib import testrunner, TestBase +from qpid.testlib import TestBase010 -class QueryTests(TestBase): - """Tests for various query methods introduced in 0-10 and available in 0-9 for preview""" +class QueryTests(TestBase010): + """Tests for various query methods""" + + def test_queue_query(self): + session = self.session + session.queue_declare(queue="my-queue", exclusive=True) + result = session.queue_query(queue="my-queue") + self.assertEqual("my-queue", result.queue) def test_exchange_query(self): """ Test that the exchange_query method works as expected """ - channel = self.channel + session = self.session #check returned type for the standard exchanges - self.assert_type("direct", channel.exchange_query(name="amq.direct")) - self.assert_type("topic", channel.exchange_query(name="amq.topic")) - self.assert_type("fanout", channel.exchange_query(name="amq.fanout")) - self.assert_type("headers", channel.exchange_query(name="amq.match")) - self.assert_type("direct", channel.exchange_query(name="")) + self.assertEqual("direct", session.exchange_query(name="amq.direct").type) + self.assertEqual("topic", session.exchange_query(name="amq.topic").type) + self.assertEqual("fanout", session.exchange_query(name="amq.fanout").type) + self.assertEqual("headers", session.exchange_query(name="amq.match").type) + self.assertEqual("direct", session.exchange_query(name="").type) #declare an exchange - channel.exchange_declare(exchange="my-test-exchange", type= "direct", durable=False) + session.exchange_declare(exchange="my-test-exchange", type= "direct", durable=False) #check that the result of a query is as expected - response = channel.exchange_query(name="my-test-exchange") - self.assert_type("direct", response) - self.assertEqual(False, response.durable) - self.assertEqual(False, response.not_found) + response = session.exchange_query(name="my-test-exchange") + self.assertEqual("direct", response.type) + self.assert_(not response.durable) + self.assert_(not response.not_found) #delete the exchange - channel.exchange_delete(exchange="my-test-exchange") + session.exchange_delete(exchange="my-test-exchange") #check that the query now reports not-found - self.assertEqual(True, channel.exchange_query(name="my-test-exchange").not_found) - - def assert_type(self, expected_type, response): - self.assertEqual(expected_type, response.__getattr__("type")) + self.assert_(session.exchange_query(name="my-test-exchange").not_found) - def test_binding_query_direct(self): + def test_exchange_bound_direct(self): """ - Test that the binding_query method works as expected with the direct exchange + Test that the exchange_bound method works as expected with the direct exchange """ - self.binding_query_with_key("amq.direct") + self.exchange_bound_with_key("amq.direct") - def test_binding_query_topic(self): + def test_exchange_bound_topic(self): """ - Test that the binding_query method works as expected with the direct exchange + Test that the exchange_bound method works as expected with the direct exchange """ - self.binding_query_with_key("amq.topic") + self.exchange_bound_with_key("amq.topic") - def binding_query_with_key(self, exchange_name): - channel = self.channel + def exchange_bound_with_key(self, exchange_name): + session = self.session #setup: create two queues - channel.queue_declare(queue="used-queue", exclusive=True, auto_delete=True) - channel.queue_declare(queue="unused-queue", exclusive=True, auto_delete=True) + session.queue_declare(queue="used-queue", exclusive=True, auto_delete=True) + session.queue_declare(queue="unused-queue", exclusive=True, auto_delete=True) - channel.queue_bind(exchange=exchange_name, queue="used-queue", routing_key="used-key") + session.exchange_bind(exchange=exchange_name, queue="used-queue", binding_key="used-key") # test detection of any binding to specific queue - response = channel.binding_query(exchange=exchange_name, queue="used-queue") - self.assertEqual(False, response.exchange_not_found) - self.assertEqual(False, response.queue_not_found) - self.assertEqual(False, response.queue_not_matched) + response = session.exchange_bound(exchange=exchange_name, queue="used-queue") + self.assert_(not response.exchange_not_found) + self.assert_(not response.queue_not_found) + self.assert_(not response.queue_not_matched) # test detection of specific binding to any queue - response = channel.binding_query(exchange=exchange_name, routing_key="used-key") - self.assertEqual(False, response.exchange_not_found) - self.assertEqual(False, response.queue_not_found) - self.assertEqual(False, response.key_not_matched) + response = session.exchange_bound(exchange=exchange_name, binding_key="used-key") + self.assert_(not response.exchange_not_found) + self.assert_(not response.queue_not_found) + self.assert_(not response.key_not_matched) # test detection of specific binding to specific queue - response = channel.binding_query(exchange=exchange_name, queue="used-queue", routing_key="used-key") - self.assertEqual(False, response.exchange_not_found) - self.assertEqual(False, response.queue_not_found) - self.assertEqual(False, response.queue_not_matched) - self.assertEqual(False, response.key_not_matched) + response = session.exchange_bound(exchange=exchange_name, queue="used-queue", binding_key="used-key") + self.assert_(not response.exchange_not_found) + self.assert_(not response.queue_not_found) + self.assert_(not response.queue_not_matched) + self.assert_(not response.key_not_matched) # test unmatched queue, unspecified binding - response = channel.binding_query(exchange=exchange_name, queue="unused-queue") - self.assertEqual(False, response.exchange_not_found) - self.assertEqual(False, response.queue_not_found) + response = session.exchange_bound(exchange=exchange_name, queue="unused-queue") + self.assert_(not response.exchange_not_found) + self.assert_(not response.queue_not_found) self.assertEqual(True, response.queue_not_matched) # test unspecified queue, unmatched binding - response = channel.binding_query(exchange=exchange_name, routing_key="unused-key") - self.assertEqual(False, response.exchange_not_found) - self.assertEqual(False, response.queue_not_found) + response = session.exchange_bound(exchange=exchange_name, binding_key="unused-key") + self.assert_(not response.exchange_not_found) + self.assert_(not response.queue_not_found) self.assertEqual(True, response.key_not_matched) # test matched queue, unmatched binding - response = channel.binding_query(exchange=exchange_name, queue="used-queue", routing_key="unused-key") - self.assertEqual(False, response.exchange_not_found) - self.assertEqual(False, response.queue_not_found) - self.assertEqual(False, response.queue_not_matched) + response = session.exchange_bound(exchange=exchange_name, queue="used-queue", binding_key="unused-key") + self.assert_(not response.exchange_not_found) + self.assert_(not response.queue_not_found) + self.assert_(not response.queue_not_matched) self.assertEqual(True, response.key_not_matched) # test unmatched queue, matched binding - response = channel.binding_query(exchange=exchange_name, queue="unused-queue", routing_key="used-key") - self.assertEqual(False, response.exchange_not_found) - self.assertEqual(False, response.queue_not_found) + response = session.exchange_bound(exchange=exchange_name, queue="unused-queue", binding_key="used-key") + self.assert_(not response.exchange_not_found) + self.assert_(not response.queue_not_found) self.assertEqual(True, response.queue_not_matched) - self.assertEqual(False, response.key_not_matched) + self.assert_(not response.key_not_matched) # test unmatched queue, unmatched binding - response = channel.binding_query(exchange=exchange_name, queue="unused-queue", routing_key="unused-key") - self.assertEqual(False, response.exchange_not_found) - self.assertEqual(False, response.queue_not_found) + response = session.exchange_bound(exchange=exchange_name, queue="unused-queue", binding_key="unused-key") + self.assert_(not response.exchange_not_found) + self.assert_(not response.queue_not_found) self.assertEqual(True, response.queue_not_matched) self.assertEqual(True, response.key_not_matched) #test exchange not found - self.assertEqual(True, channel.binding_query(exchange="unknown-exchange").exchange_not_found) + self.assertEqual(True, session.exchange_bound(exchange="unknown-exchange").exchange_not_found) #test queue not found - self.assertEqual(True, channel.binding_query(exchange=exchange_name, queue="unknown-queue").queue_not_found) + self.assertEqual(True, session.exchange_bound(exchange=exchange_name, queue="unknown-queue").queue_not_found) - def test_binding_query_fanout(self): + def test_exchange_bound_fanout(self): """ - Test that the binding_query method works as expected with fanout exchange + Test that the exchange_bound method works as expected with fanout exchange """ - channel = self.channel + session = self.session #setup - channel.queue_declare(queue="used-queue", exclusive=True, auto_delete=True) - channel.queue_declare(queue="unused-queue", exclusive=True, auto_delete=True) - channel.queue_bind(exchange="amq.fanout", queue="used-queue") + session.queue_declare(queue="used-queue", exclusive=True, auto_delete=True) + session.queue_declare(queue="unused-queue", exclusive=True, auto_delete=True) + session.exchange_bind(exchange="amq.fanout", queue="used-queue") # test detection of any binding to specific queue - response = channel.binding_query(exchange="amq.fanout", queue="used-queue") - self.assertEqual(False, response.exchange_not_found) - self.assertEqual(False, response.queue_not_found) - self.assertEqual(False, response.queue_not_matched) + response = session.exchange_bound(exchange="amq.fanout", queue="used-queue") + self.assert_(not response.exchange_not_found) + self.assert_(not response.queue_not_found) + self.assert_(not response.queue_not_matched) # test unmatched queue, unspecified binding - response = channel.binding_query(exchange="amq.fanout", queue="unused-queue") - self.assertEqual(False, response.exchange_not_found) - self.assertEqual(False, response.queue_not_found) + response = session.exchange_bound(exchange="amq.fanout", queue="unused-queue") + self.assert_(not response.exchange_not_found) + self.assert_(not response.queue_not_found) self.assertEqual(True, response.queue_not_matched) #test exchange not found - self.assertEqual(True, channel.binding_query(exchange="unknown-exchange").exchange_not_found) + self.assertEqual(True, session.exchange_bound(exchange="unknown-exchange").exchange_not_found) #test queue not found - self.assertEqual(True, channel.binding_query(exchange="amq.fanout", queue="unknown-queue").queue_not_found) + self.assertEqual(True, session.exchange_bound(exchange="amq.fanout", queue="unknown-queue").queue_not_found) - def test_binding_query_header(self): + def test_exchange_bound_header(self): """ - Test that the binding_query method works as expected with headers exchanges + Test that the exchange_bound method works as expected with headers exchanges """ - channel = self.channel + session = self.session #setup - channel.queue_declare(queue="used-queue", exclusive=True, auto_delete=True) - channel.queue_declare(queue="unused-queue", exclusive=True, auto_delete=True) - channel.queue_bind(exchange="amq.match", queue="used-queue", arguments={"x-match":"all", "a":"A"} ) + session.queue_declare(queue="used-queue", exclusive=True, auto_delete=True) + session.queue_declare(queue="unused-queue", exclusive=True, auto_delete=True) + session.exchange_bind(exchange="amq.match", queue="used-queue", arguments={"x-match":"all", "a":"A"} ) # test detection of any binding to specific queue - response = channel.binding_query(exchange="amq.match", queue="used-queue") - self.assertEqual(False, response.exchange_not_found) - self.assertEqual(False, response.queue_not_found) - self.assertEqual(False, response.queue_not_matched) + response = session.exchange_bound(exchange="amq.match", queue="used-queue") + self.assert_(not response.exchange_not_found) + self.assert_(not response.queue_not_found) + self.assert_(not response.queue_not_matched) # test detection of specific binding to any queue - response = channel.binding_query(exchange="amq.match", arguments={"x-match":"all", "a":"A"}) - self.assertEqual(False, response.exchange_not_found) - self.assertEqual(False, response.queue_not_found) - self.assertEqual(False, response.args_not_matched) + response = session.exchange_bound(exchange="amq.match", arguments={"x-match":"all", "a":"A"}) + self.assert_(not response.exchange_not_found) + self.assert_(not response.queue_not_found) + self.assert_(not response.args_not_matched) # test detection of specific binding to specific queue - response = channel.binding_query(exchange="amq.match", queue="used-queue", arguments={"x-match":"all", "a":"A"}) - self.assertEqual(False, response.exchange_not_found) - self.assertEqual(False, response.queue_not_found) - self.assertEqual(False, response.queue_not_matched) - self.assertEqual(False, response.args_not_matched) + response = session.exchange_bound(exchange="amq.match", queue="used-queue", arguments={"x-match":"all", "a":"A"}) + self.assert_(not response.exchange_not_found) + self.assert_(not response.queue_not_found) + self.assert_(not response.queue_not_matched) + self.assert_(not response.args_not_matched) # test unmatched queue, unspecified binding - response = channel.binding_query(exchange="amq.match", queue="unused-queue") - self.assertEqual(False, response.exchange_not_found) - self.assertEqual(False, response.queue_not_found) + response = session.exchange_bound(exchange="amq.match", queue="unused-queue") + self.assert_(not response.exchange_not_found) + self.assert_(not response.queue_not_found) self.assertEqual(True, response.queue_not_matched) # test unspecified queue, unmatched binding - response = channel.binding_query(exchange="amq.match", arguments={"x-match":"all", "b":"B"}) - self.assertEqual(False, response.exchange_not_found) - self.assertEqual(False, response.queue_not_found) + response = session.exchange_bound(exchange="amq.match", arguments={"x-match":"all", "b":"B"}) + self.assert_(not response.exchange_not_found) + self.assert_(not response.queue_not_found) self.assertEqual(True, response.args_not_matched) # test matched queue, unmatched binding - response = channel.binding_query(exchange="amq.match", queue="used-queue", arguments={"x-match":"all", "b":"B"}) - self.assertEqual(False, response.exchange_not_found) - self.assertEqual(False, response.queue_not_found) - self.assertEqual(False, response.queue_not_matched) + response = session.exchange_bound(exchange="amq.match", queue="used-queue", arguments={"x-match":"all", "b":"B"}) + self.assert_(not response.exchange_not_found) + self.assert_(not response.queue_not_found) + self.assert_(not response.queue_not_matched) self.assertEqual(True, response.args_not_matched) # test unmatched queue, matched binding - response = channel.binding_query(exchange="amq.match", queue="unused-queue", arguments={"x-match":"all", "a":"A"}) - self.assertEqual(False, response.exchange_not_found) - self.assertEqual(False, response.queue_not_found) + response = session.exchange_bound(exchange="amq.match", queue="unused-queue", arguments={"x-match":"all", "a":"A"}) + self.assert_(not response.exchange_not_found) + self.assert_(not response.queue_not_found) self.assertEqual(True, response.queue_not_matched) - self.assertEqual(False, response.args_not_matched) + self.assert_(not response.args_not_matched) # test unmatched queue, unmatched binding - response = channel.binding_query(exchange="amq.match", queue="unused-queue", arguments={"x-match":"all", "b":"B"}) - self.assertEqual(False, response.exchange_not_found) - self.assertEqual(False, response.queue_not_found) + response = session.exchange_bound(exchange="amq.match", queue="unused-queue", arguments={"x-match":"all", "b":"B"}) + self.assert_(not response.exchange_not_found) + self.assert_(not response.queue_not_found) self.assertEqual(True, response.queue_not_matched) self.assertEqual(True, response.args_not_matched) #test exchange not found - self.assertEqual(True, channel.binding_query(exchange="unknown-exchange").exchange_not_found) + self.assertEqual(True, session.exchange_bound(exchange="unknown-exchange").exchange_not_found) #test queue not found - self.assertEqual(True, channel.binding_query(exchange="amq.match", queue="unknown-queue").queue_not_found) + self.assertEqual(True, session.exchange_bound(exchange="amq.match", queue="unknown-queue").queue_not_found) diff --git a/qpid/python/tests_0-10/queue.py b/qpid/python/tests_0-10/queue.py index 7b3590d11b..758794dd52 100644 --- a/qpid/python/tests_0-10/queue.py +++ b/qpid/python/tests_0-10/queue.py @@ -18,134 +18,133 @@ # from qpid.client import Client, Closed from qpid.queue import Empty -from qpid.content import Content -from qpid.testlib import testrunner, TestBase +from qpid.testlib import TestBase010 +from qpid.datatypes import Message +from qpid.session import SessionException -class QueueTests(TestBase): +class QueueTests(TestBase010): """Tests for 'methods' on the amqp queue 'class'""" def test_purge(self): """ Test that the purge method removes messages from the queue """ - channel = self.channel + session = self.session #setup, declare a queue and add some messages to it: - channel.exchange_declare(exchange="test-exchange", type="direct") - channel.queue_declare(queue="test-queue", exclusive=True, auto_delete=True) - channel.queue_bind(queue="test-queue", exchange="test-exchange", routing_key="key") - channel.message_transfer(destination="test-exchange", content=Content("one", properties={'routing_key':"key"})) - channel.message_transfer(destination="test-exchange", content=Content("two", properties={'routing_key':"key"})) - channel.message_transfer(destination="test-exchange", content=Content("three", properties={'routing_key':"key"})) + session.queue_declare(queue="test-queue", exclusive=True, auto_delete=True) + session.message_transfer(message=Message(session.delivery_properties(routing_key="test-queue"), "one")) + session.message_transfer(message=Message(session.delivery_properties(routing_key="test-queue"), "two")) + session.message_transfer(message=Message(session.delivery_properties(routing_key="test-queue"), "three")) #check that the queue now reports 3 messages: - channel.queue_declare(queue="test-queue") - reply = channel.queue_query(queue="test-queue") + session.queue_declare(queue="test-queue") + reply = session.queue_query(queue="test-queue") self.assertEqual(3, reply.message_count) #now do the purge, then test that three messages are purged and the count drops to 0 - channel.queue_purge(queue="test-queue"); - reply = channel.queue_query(queue="test-queue") + session.queue_purge(queue="test-queue"); + reply = session.queue_query(queue="test-queue") self.assertEqual(0, reply.message_count) #send a further message and consume it, ensuring that the other messages are really gone - channel.message_transfer(destination="test-exchange", content=Content("four", properties={'routing_key':"key"})) - self.subscribe(queue="test-queue", destination="tag") - queue = self.client.queue("tag") + session.message_transfer(message=Message(session.delivery_properties(routing_key="test-queue"), "four")) + session.message_subscribe(queue="test-queue", destination="tag") + session.message_flow(destination="tag", unit=0, value=0xFFFFFFFF) + session.message_flow(destination="tag", unit=1, value=0xFFFFFFFF) + queue = session.incoming("tag") msg = queue.get(timeout=1) - self.assertEqual("four", msg.content.body) - - #check error conditions (use new channels): - channel = self.client.channel(2) - channel.session_open() + self.assertEqual("four", msg.body) + + def test_purge_queue_exists(self): + """ + Test that the correct exception is thrown is no queue exists + for the name specified in purge + """ + session = self.session try: #queue specified but doesn't exist: - channel.queue_purge(queue="invalid-queue") + session.queue_purge(queue="invalid-queue") self.fail("Expected failure when purging non-existent queue") - except Closed, e: - self.assertChannelException(404, e.args[0]) + except SessionException, e: + self.assertEquals(404, e.args[0].error_code) #not-found - channel = self.client.channel(3) - channel.session_open() + def test_purge_empty_name(self): + """ + Test that the correct exception is thrown is no queue name + is specified for purge + """ + session = self.session try: #queue not specified and none previously declared for channel: - channel.queue_purge() + session.queue_purge() self.fail("Expected failure when purging unspecified queue") - except Closed, e: - self.assertConnectionException(530, e.args[0]) - - #cleanup - other = self.connect() - channel = other.channel(1) - channel.session_open() - channel.exchange_delete(exchange="test-exchange") + except SessionException, e: + self.assertEquals(531, e.args[0].error_code) #illegal-argument def test_declare_exclusive(self): """ Test that the exclusive field is honoured in queue.declare """ - # TestBase.setUp has already opened channel(1) - c1 = self.channel + # TestBase.setUp has already opened session(1) + s1 = self.session # Here we open a second separate connection: - other = self.connect() - c2 = other.channel(1) - c2.session_open() + s2 = self.conn.session("other", 2) #declare an exclusive queue: - c1.queue_declare(queue="exclusive-queue", exclusive=True, auto_delete=True) + s1.queue_declare(queue="exclusive-queue", exclusive=True, auto_delete=True) try: #other connection should not be allowed to declare this: - c2.queue_declare(queue="exclusive-queue", exclusive=True, auto_delete=True) + s2.queue_declare(queue="exclusive-queue", exclusive=True, auto_delete=True) self.fail("Expected second exclusive queue_declare to raise a channel exception") - except Closed, e: - self.assertChannelException(405, e.args[0]) + except SessionException, e: + self.assertEquals(405, e.args[0].error_code) def test_declare_passive(self): """ Test that the passive field is honoured in queue.declare """ - channel = self.channel + session = self.session #declare an exclusive queue: - channel.queue_declare(queue="passive-queue-1", exclusive=True, auto_delete=True) - channel.queue_declare(queue="passive-queue-1", passive=True) + session.queue_declare(queue="passive-queue-1", exclusive=True, auto_delete=True) + session.queue_declare(queue="passive-queue-1", passive=True) try: #other connection should not be allowed to declare this: - channel.queue_declare(queue="passive-queue-2", passive=True) + session.queue_declare(queue="passive-queue-2", passive=True) self.fail("Expected passive declaration of non-existant queue to raise a channel exception") - except Closed, e: - self.assertChannelException(404, e.args[0]) + except SessionException, e: + self.assertEquals(404, e.args[0].error_code) #not-found def test_bind(self): """ Test various permutations of the queue.bind method """ - channel = self.channel - channel.queue_declare(queue="queue-1", exclusive=True, auto_delete=True) + session = self.session + session.queue_declare(queue="queue-1", exclusive=True, auto_delete=True) #straightforward case, both exchange & queue exist so no errors expected: - channel.queue_bind(queue="queue-1", exchange="amq.direct", routing_key="key1") + session.exchange_bind(queue="queue-1", exchange="amq.direct", binding_key="key1") #use the queue name where the routing key is not specified: - channel.queue_bind(queue="queue-1", exchange="amq.direct") + session.exchange_bind(queue="queue-1", exchange="amq.direct") #try and bind to non-existant exchange try: - channel.queue_bind(queue="queue-1", exchange="an-invalid-exchange", routing_key="key1") + session.exchange_bind(queue="queue-1", exchange="an-invalid-exchange", binding_key="key1") self.fail("Expected bind to non-existant exchange to fail") - except Closed, e: - self.assertChannelException(404, e.args[0]) + except SessionException, e: + self.assertEquals(404, e.args[0].error_code) - #need to reopen a channel: - channel = self.client.channel(2) - channel.session_open() + def test_bind_queue_existence(self): + session = self.session #try and bind non-existant queue: try: - channel.queue_bind(queue="queue-2", exchange="amq.direct", routing_key="key1") + session.exchange_bind(queue="queue-2", exchange="amq.direct", binding_key="key1") self.fail("Expected bind of non-existant queue to fail") - except Closed, e: - self.assertChannelException(404, e.args[0]) + except SessionException, e: + self.assertEquals(404, e.args[0].error_code) def test_unbind_direct(self): self.unbind_test(exchange="amq.direct", routing_key="key") @@ -159,42 +158,53 @@ class QueueTests(TestBase): def test_unbind_headers(self): self.unbind_test(exchange="amq.match", args={ "x-match":"all", "a":"b"}, headers={"a":"b"}) - def unbind_test(self, exchange, routing_key="", args=None, headers={}): + def unbind_test(self, exchange, routing_key="", args=None, headers=None): #bind two queues and consume from them - channel = self.channel + session = self.session - channel.queue_declare(queue="queue-1", exclusive=True, auto_delete=True) - channel.queue_declare(queue="queue-2", exclusive=True, auto_delete=True) - - self.subscribe(queue="queue-1", destination="queue-1") - self.subscribe(queue="queue-2", destination="queue-2") - - queue1 = self.client.queue("queue-1") - queue2 = self.client.queue("queue-2") - - channel.queue_bind(exchange=exchange, queue="queue-1", routing_key=routing_key, arguments=args) - channel.queue_bind(exchange=exchange, queue="queue-2", routing_key=routing_key, arguments=args) - + session.queue_declare(queue="queue-1", exclusive=True, auto_delete=True) + session.queue_declare(queue="queue-2", exclusive=True, auto_delete=True) + + session.message_subscribe(queue="queue-1", destination="queue-1") + session.message_flow(destination="queue-1", unit=0, value=0xFFFFFFFF) + session.message_flow(destination="queue-1", unit=1, value=0xFFFFFFFF) + session.message_subscribe(queue="queue-2", destination="queue-2") + session.message_flow(destination="queue-2", unit=0, value=0xFFFFFFFF) + session.message_flow(destination="queue-2", unit=1, value=0xFFFFFFFF) + + queue1 = session.incoming("queue-1") + queue2 = session.incoming("queue-2") + + session.exchange_bind(exchange=exchange, queue="queue-1", binding_key=routing_key, arguments=args) + session.exchange_bind(exchange=exchange, queue="queue-2", binding_key=routing_key, arguments=args) + + dp = session.delivery_properties(routing_key=routing_key) + if (headers): + mp = session.message_properties(application_headers=headers) + msg1 = Message(dp, mp, "one") + msg2 = Message(dp, mp, "two") + else: + msg1 = Message(dp, "one") + msg2 = Message(dp, "two") + #send a message that will match both bindings - channel.message_transfer(destination=exchange, - content=Content("one", properties={'routing_key':routing_key, 'application_headers':headers})) + session.message_transfer(destination=exchange, message=msg1) #unbind first queue - channel.queue_unbind(exchange=exchange, queue="queue-1", routing_key=routing_key, arguments=args) + session.exchange_unbind(exchange=exchange, queue="queue-1", binding_key=routing_key) #send another message - channel.message_transfer(destination=exchange, - content=Content("two", properties={'routing_key':routing_key, 'application_headers':headers})) + session.message_transfer(destination=exchange, message=msg2) #check one queue has both messages and the other has only one - self.assertEquals("one", queue1.get(timeout=1).content.body) + self.assertEquals("one", queue1.get(timeout=1).body) try: msg = queue1.get(timeout=1) - self.fail("Got extra message: %s" % msg.content.body) + self.fail("Got extra message: %s" % msg.body) except Empty: pass - self.assertEquals("one", queue2.get(timeout=1).content.body) - self.assertEquals("two", queue2.get(timeout=1).content.body) + self.assertEquals("one", queue2.get(timeout=1).body) + self.assertEquals("two", queue2.get(timeout=1).body) try: msg = queue2.get(timeout=1) self.fail("Got extra message: " + msg) @@ -205,29 +215,32 @@ class QueueTests(TestBase): """ Test core queue deletion behaviour """ - channel = self.channel + session = self.session #straight-forward case: - channel.queue_declare(queue="delete-me") - channel.message_transfer(content=Content("a", properties={'routing_key':"delete-me"})) - channel.message_transfer(content=Content("b", properties={'routing_key':"delete-me"})) - channel.message_transfer(content=Content("c", properties={'routing_key':"delete-me"})) - channel.queue_delete(queue="delete-me") + session.queue_declare(queue="delete-me") + session.message_transfer(message=Message(session.delivery_properties(routing_key="delete-me"), "a")) + session.message_transfer(message=Message(session.delivery_properties(routing_key="delete-me"), "b")) + session.message_transfer(message=Message(session.delivery_properties(routing_key="delete-me"), "c")) + session.queue_delete(queue="delete-me") #check that it has gone be declaring passively try: - channel.queue_declare(queue="delete-me", passive=True) + session.queue_declare(queue="delete-me", passive=True) self.fail("Queue has not been deleted") - except Closed, e: - self.assertChannelException(404, e.args[0]) + except SessionException, e: + self.assertEquals(404, e.args[0].error_code) + def test_delete_queue_exists(self): + """ + Test core queue deletion behaviour + """ #check attempted deletion of non-existant queue is handled correctly: - channel = self.client.channel(2) - channel.session_open() + session = self.session try: - channel.queue_delete(queue="i-dont-exist", if_empty=True) + session.queue_delete(queue="i-dont-exist", if_empty=True) self.fail("Expected delete of non-existant queue to fail") - except Closed, e: - self.assertChannelException(404, e.args[0]) + except SessionException, e: + self.assertEquals(404, e.args[0].error_code) @@ -235,104 +248,103 @@ class QueueTests(TestBase): """ Test that if_empty field of queue_delete is honoured """ - channel = self.channel + session = self.session #create a queue and add a message to it (use default binding): - channel.queue_declare(queue="delete-me-2") - channel.queue_declare(queue="delete-me-2", passive=True) - channel.message_transfer(content=Content("message", properties={'routing_key':"delete-me-2"})) + session.queue_declare(queue="delete-me-2") + session.queue_declare(queue="delete-me-2", passive=True) + session.message_transfer(message=Message(session.delivery_properties(routing_key="delete-me-2"), "message")) #try to delete, but only if empty: try: - channel.queue_delete(queue="delete-me-2", if_empty=True) + session.queue_delete(queue="delete-me-2", if_empty=True) self.fail("Expected delete if_empty to fail for non-empty queue") - except Closed, e: - self.assertChannelException(406, e.args[0]) + except SessionException, e: + self.assertEquals(406, e.args[0].error_code) - #need new channel now: - channel = self.client.channel(2) - channel.session_open() + #need new session now: + session = self.conn.session("replacement", 2) #empty queue: - self.subscribe(channel, destination="consumer_tag", queue="delete-me-2") - queue = self.client.queue("consumer_tag") + session.message_subscribe(destination="consumer_tag", queue="delete-me-2") + session.message_flow(destination="consumer_tag", unit=0, value=0xFFFFFFFF) + session.message_flow(destination="consumer_tag", unit=1, value=0xFFFFFFFF) + queue = session.incoming("consumer_tag") msg = queue.get(timeout=1) - self.assertEqual("message", msg.content.body) - channel.message_cancel(destination="consumer_tag") + self.assertEqual("message", msg.body) + session.message_cancel(destination="consumer_tag") #retry deletion on empty queue: - channel.queue_delete(queue="delete-me-2", if_empty=True) + session.queue_delete(queue="delete-me-2", if_empty=True) #check that it has gone by declaring passively: try: - channel.queue_declare(queue="delete-me-2", passive=True) + session.queue_declare(queue="delete-me-2", passive=True) self.fail("Queue has not been deleted") - except Closed, e: - self.assertChannelException(404, e.args[0]) + except SessionException, e: + self.assertEquals(404, e.args[0].error_code) def test_delete_ifunused(self): """ Test that if_unused field of queue_delete is honoured """ - channel = self.channel + session = self.session #create a queue and register a consumer: - channel.queue_declare(queue="delete-me-3") - channel.queue_declare(queue="delete-me-3", passive=True) - self.subscribe(destination="consumer_tag", queue="delete-me-3") + session.queue_declare(queue="delete-me-3") + session.queue_declare(queue="delete-me-3", passive=True) + session.message_subscribe(destination="consumer_tag", queue="delete-me-3") + + #need new session now: + session2 = self.conn.session("replacement", 2) - #need new channel now: - channel2 = self.client.channel(2) - channel2.session_open() #try to delete, but only if empty: try: - channel2.queue_delete(queue="delete-me-3", if_unused=True) + session2.queue_delete(queue="delete-me-3", if_unused=True) self.fail("Expected delete if_unused to fail for queue with existing consumer") - except Closed, e: - self.assertChannelException(406, e.args[0]) - + except SessionException, e: + self.assertEquals(406, e.args[0].error_code) - channel.message_cancel(destination="consumer_tag") - channel.queue_delete(queue="delete-me-3", if_unused=True) + session.message_cancel(destination="consumer_tag") + session.queue_delete(queue="delete-me-3", if_unused=True) #check that it has gone by declaring passively: try: - channel.queue_declare(queue="delete-me-3", passive=True) + session.queue_declare(queue="delete-me-3", passive=True) self.fail("Queue has not been deleted") - except Closed, e: - self.assertChannelException(404, e.args[0]) + except SessionException, e: + self.assertEquals(404, e.args[0].error_code) def test_autodelete_shared(self): """ Test auto-deletion (of non-exclusive queues) """ - channel = self.channel - other = self.connect() - channel2 = other.channel(1) - channel2.session_open() + session = self.session + session2 =self.conn.session("other", 1) - channel.queue_declare(queue="auto-delete-me", auto_delete=True) + session.queue_declare(queue="auto-delete-me", auto_delete=True) - #consume from both channels - reply = channel.basic_consume(queue="auto-delete-me") - channel2.basic_consume(queue="auto-delete-me") + #consume from both sessions + tag = "my-tag" + session.message_subscribe(queue="auto-delete-me", destination=tag) + session2.message_subscribe(queue="auto-delete-me", destination=tag) #implicit cancel - channel2.session_close() + session2.close() #check it is still there - channel.queue_declare(queue="auto-delete-me", passive=True) + session.queue_declare(queue="auto-delete-me", passive=True) #explicit cancel => queue is now unused again: - channel.basic_cancel(consumer_tag=reply.consumer_tag) + session.message_cancel(destination=tag) #NOTE: this assumes there is no timeout in use - #check that it has gone be declaring passively + #check that it has gone by declaring it passively try: - channel.queue_declare(queue="auto-delete-me", passive=True) + session.queue_declare(queue="auto-delete-me", passive=True) self.fail("Expected queue to have been deleted") - except Closed, e: - self.assertChannelException(404, e.args[0]) + except SessionException, e: + self.assertEquals(404, e.args[0].error_code) diff --git a/qpid/python/tests_0-10/tx.py b/qpid/python/tests_0-10/tx.py index 3fd1065af3..5aef2b00e8 100644 --- a/qpid/python/tests_0-10/tx.py +++ b/qpid/python/tests_0-10/tx.py @@ -18,10 +18,10 @@ # from qpid.client import Client, Closed from qpid.queue import Empty -from qpid.content import Content -from qpid.testlib import testrunner, TestBase +from qpid.datatypes import Message, RangedSet +from qpid.testlib import testrunner, TestBase010 -class TxTests(TestBase): +class TxTests(TestBase010): """ Tests for 'methods' on the amqp tx 'class' """ @@ -30,202 +30,236 @@ class TxTests(TestBase): """ Test that commited publishes are delivered and commited acks are not re-delivered """ - channel2 = self.client.channel(2) - channel2.session_open() - self.perform_txn_work(channel2, "tx-commit-a", "tx-commit-b", "tx-commit-c") - channel2.tx_commit() - channel2.session_close() + session = self.session - #use a different channel with new subscriptions to ensure - #there is no redelivery of acked messages: - channel = self.channel - channel.tx_select() + #declare queues and create subscribers in the checking session + #to ensure that the queues are not auto-deleted too early: + self.declare_queues(["tx-commit-a", "tx-commit-b", "tx-commit-c"]) + session.message_subscribe(queue="tx-commit-a", destination="qa") + session.message_subscribe(queue="tx-commit-b", destination="qb") + session.message_subscribe(queue="tx-commit-c", destination="qc") - self.subscribe(channel, queue="tx-commit-a", destination="qa", confirm_mode=1) - queue_a = self.client.queue("qa") + #use a separate session for actual work + session2 = self.conn.session("worker", 2) + self.perform_txn_work(session2, "tx-commit-a", "tx-commit-b", "tx-commit-c") + session2.tx_commit() + session2.close() - self.subscribe(channel, queue="tx-commit-b", destination="qb", confirm_mode=1) - queue_b = self.client.queue("qb") + session.tx_select() - self.subscribe(channel, queue="tx-commit-c", destination="qc", confirm_mode=1) - queue_c = self.client.queue("qc") + self.enable_flow("qa") + queue_a = session.incoming("qa") + + self.enable_flow("qb") + queue_b = session.incoming("qb") + + self.enable_flow("qc") + queue_c = session.incoming("qc") #check results for i in range(1, 5): msg = queue_c.get(timeout=1) - self.assertEqual("TxMessage %d" % i, msg.content.body) - msg.complete() + self.assertEqual("TxMessage %d" % i, msg.body) + session.message_accept(RangedSet(msg.id)) msg = queue_b.get(timeout=1) - self.assertEqual("TxMessage 6", msg.content.body) - msg.complete() + self.assertEqual("TxMessage 6", msg.body) + session.message_accept(RangedSet(msg.id)) msg = queue_a.get(timeout=1) - self.assertEqual("TxMessage 7", msg.content.body) - msg.complete() + self.assertEqual("TxMessage 7", msg.body) + session.message_accept(RangedSet(msg.id)) for q in [queue_a, queue_b, queue_c]: try: extra = q.get(timeout=1) - self.fail("Got unexpected message: " + extra.content.body) + self.fail("Got unexpected message: " + extra.body) except Empty: None #cleanup - channel.tx_commit() + session.tx_commit() def test_auto_rollback(self): """ - Test that a channel closed with an open transaction is effectively rolled back + Test that a session closed with an open transaction is effectively rolled back """ - channel2 = self.client.channel(2) - channel2.session_open() - queue_a, queue_b, queue_c = self.perform_txn_work(channel2, "tx-autorollback-a", "tx-autorollback-b", "tx-autorollback-c") + session = self.session + self.declare_queues(["tx-autorollback-a", "tx-autorollback-b", "tx-autorollback-c"]) + session.message_subscribe(queue="tx-autorollback-a", destination="qa") + session.message_subscribe(queue="tx-autorollback-b", destination="qb") + session.message_subscribe(queue="tx-autorollback-c", destination="qc") + + session2 = self.conn.session("worker", 2) + queue_a, queue_b, queue_c, ignore = self.perform_txn_work(session2, "tx-autorollback-a", "tx-autorollback-b", "tx-autorollback-c") for q in [queue_a, queue_b, queue_c]: try: extra = q.get(timeout=1) - self.fail("Got unexpected message: " + extra.content.body) + self.fail("Got unexpected message: " + extra.body) except Empty: None - channel2.session_close() - channel = self.channel - channel.tx_select() + session2.close() - self.subscribe(channel, queue="tx-autorollback-a", destination="qa", confirm_mode=1) - queue_a = self.client.queue("qa") + session.tx_select() - self.subscribe(channel, queue="tx-autorollback-b", destination="qb", confirm_mode=1) - queue_b = self.client.queue("qb") + self.enable_flow("qa") + queue_a = session.incoming("qa") - self.subscribe(channel, queue="tx-autorollback-c", destination="qc", confirm_mode=1) - queue_c = self.client.queue("qc") + self.enable_flow("qb") + queue_b = session.incoming("qb") + + self.enable_flow("qc") + queue_c = session.incoming("qc") #check results for i in range(1, 5): msg = queue_a.get(timeout=1) - self.assertEqual("Message %d" % i, msg.content.body) - msg.complete() + self.assertEqual("Message %d" % i, msg.body) + session.message_accept(RangedSet(msg.id)) msg = queue_b.get(timeout=1) - self.assertEqual("Message 6", msg.content.body) - msg.complete() + self.assertEqual("Message 6", msg.body) + session.message_accept(RangedSet(msg.id)) msg = queue_c.get(timeout=1) - self.assertEqual("Message 7", msg.content.body) - msg.complete() + self.assertEqual("Message 7", msg.body) + session.message_accept(RangedSet(msg.id)) for q in [queue_a, queue_b, queue_c]: try: extra = q.get(timeout=1) - self.fail("Got unexpected message: " + extra.content.body) + self.fail("Got unexpected message: " + extra.body) except Empty: None #cleanup - channel.tx_commit() + session.tx_commit() def test_rollback(self): """ Test that rolled back publishes are not delivered and rolled back acks are re-delivered """ - channel = self.channel - queue_a, queue_b, queue_c = self.perform_txn_work(channel, "tx-rollback-a", "tx-rollback-b", "tx-rollback-c") + session = self.session + queue_a, queue_b, queue_c, consumed = self.perform_txn_work(session, "tx-rollback-a", "tx-rollback-b", "tx-rollback-c") for q in [queue_a, queue_b, queue_c]: try: extra = q.get(timeout=1) - self.fail("Got unexpected message: " + extra.content.body) + self.fail("Got unexpected message: " + extra.body) except Empty: None - #stop subscriptions (ensures no delivery occurs during rollback as messages are requeued) - for d in ["sub_a", "sub_b", "sub_c"]: - channel.message_stop(destination=d) - - channel.tx_rollback() + session.tx_rollback() - #restart susbcriptions - for d in ["sub_a", "sub_b", "sub_c"]: - channel.message_flow(destination=d, unit=0, value=0xFFFFFFFF) - channel.message_flow(destination=d, unit=1, value=0xFFFFFFFF) + #need to release messages to get them redelivered now: + session.message_release(consumed) #check results for i in range(1, 5): msg = queue_a.get(timeout=1) - self.assertEqual("Message %d" % i, msg.content.body) - msg.complete() + self.assertEqual("Message %d" % i, msg.body) + session.message_accept(RangedSet(msg.id)) msg = queue_b.get(timeout=1) - self.assertEqual("Message 6", msg.content.body) - msg.complete() + self.assertEqual("Message 6", msg.body) + session.message_accept(RangedSet(msg.id)) msg = queue_c.get(timeout=1) - self.assertEqual("Message 7", msg.content.body) - msg.complete() + self.assertEqual("Message 7", msg.body) + session.message_accept(RangedSet(msg.id)) for q in [queue_a, queue_b, queue_c]: try: extra = q.get(timeout=1) - self.fail("Got unexpected message: " + extra.content.body) + self.fail("Got unexpected message: " + extra.body) except Empty: None #cleanup - channel.tx_commit() + session.tx_commit() - def perform_txn_work(self, channel, name_a, name_b, name_c): + def perform_txn_work(self, session, name_a, name_b, name_c): """ Utility method that does some setup and some work under a transaction. Used for testing both commit and rollback """ #setup: - channel.queue_declare(queue=name_a, exclusive=True, auto_delete=True) - channel.queue_declare(queue=name_b, exclusive=True, auto_delete=True) - channel.queue_declare(queue=name_c, exclusive=True, auto_delete=True) + self.declare_queues([name_a, name_b, name_c]) key = "my_key_" + name_b topic = "my_topic_" + name_c - channel.queue_bind(queue=name_b, exchange="amq.direct", routing_key=key) - channel.queue_bind(queue=name_c, exchange="amq.topic", routing_key=topic) + session.exchange_bind(queue=name_b, exchange="amq.direct", binding_key=key) + session.exchange_bind(queue=name_c, exchange="amq.topic", binding_key=topic) + dp = session.delivery_properties(routing_key=name_a) for i in range(1, 5): - channel.message_transfer(content=Content(properties={'routing_key':name_a, 'message_id':"msg%d" % i}, body="Message %d" % i)) + mp = session.message_properties(message_id="msg%d" % i) + session.message_transfer(message=Message(dp, mp, "Message %d" % i)) - channel.message_transfer(destination="amq.direct", - content=Content(properties={'routing_key':key, 'message_id':"msg6"}, body="Message 6")) - channel.message_transfer(destination="amq.topic", - content=Content(properties={'routing_key':topic, 'message_id':"msg7"}, body="Message 7")) + dp = session.delivery_properties(routing_key=key) + mp = session.message_properties(message_id="msg6") + session.message_transfer(destination="amq.direct", message=Message(dp, mp, "Message 6")) - channel.tx_select() + dp = session.delivery_properties(routing_key=topic) + mp = session.message_properties(message_id="msg7") + session.message_transfer(destination="amq.topic", message=Message(dp, mp, "Message 7")) + + session.tx_select() #consume and ack messages - self.subscribe(channel, queue=name_a, destination="sub_a", confirm_mode=1) - queue_a = self.client.queue("sub_a") + acked = RangedSet() + self.subscribe(session, queue=name_a, destination="sub_a") + queue_a = session.incoming("sub_a") for i in range(1, 5): msg = queue_a.get(timeout=1) - self.assertEqual("Message %d" % i, msg.content.body) - - msg.complete() + acked.add(msg.id) + self.assertEqual("Message %d" % i, msg.body) - self.subscribe(channel, queue=name_b, destination="sub_b", confirm_mode=1) - queue_b = self.client.queue("sub_b") + self.subscribe(session, queue=name_b, destination="sub_b") + queue_b = session.incoming("sub_b") msg = queue_b.get(timeout=1) - self.assertEqual("Message 6", msg.content.body) - msg.complete() + self.assertEqual("Message 6", msg.body) + acked.add(msg.id) - sub_c = self.subscribe(channel, queue=name_c, destination="sub_c", confirm_mode=1) - queue_c = self.client.queue("sub_c") + sub_c = self.subscribe(session, queue=name_c, destination="sub_c") + queue_c = session.incoming("sub_c") msg = queue_c.get(timeout=1) - self.assertEqual("Message 7", msg.content.body) - msg.complete() + self.assertEqual("Message 7", msg.body) + acked.add(msg.id) + session.message_accept(acked) + + dp = session.delivery_properties(routing_key=topic) #publish messages for i in range(1, 5): - channel.message_transfer(destination="amq.topic", - content=Content(properties={'routing_key':topic, 'message_id':"tx-msg%d" % i}, - body="TxMessage %d" % i)) - - channel.message_transfer(destination="amq.direct", - content=Content(properties={'routing_key':key, 'message_id':"tx-msg6"}, - body="TxMessage 6")) - channel.message_transfer(content=Content(properties={'routing_key':name_a, 'message_id':"tx-msg7"}, - body="TxMessage 7")) - return queue_a, queue_b, queue_c + mp = session.message_properties(message_id="tx-msg%d" % i) + session.message_transfer(destination="amq.topic", message=Message(dp, mp, "TxMessage %d" % i)) + + dp = session.delivery_properties(routing_key=key) + mp = session.message_properties(message_id="tx-msg6") + session.message_transfer(destination="amq.direct", message=Message(dp, mp, "TxMessage 6")) + + dp = session.delivery_properties(routing_key=name_a) + mp = session.message_properties(message_id="tx-msg7") + session.message_transfer(message=Message(dp, mp, "TxMessage 7")) + return queue_a, queue_b, queue_c, acked + + def declare_queues(self, names, session=None): + session = session or self.session + for n in names: + session.queue_declare(queue=n, auto_delete=True) + + def subscribe(self, session=None, **keys): + session = session or self.session + consumer_tag = keys["destination"] + session.message_subscribe(**keys) + session.message_flow(destination=consumer_tag, unit=0, value=0xFFFFFFFF) + session.message_flow(destination=consumer_tag, unit=1, value=0xFFFFFFFF) + + def enable_flow(self, tag, session=None): + session = session or self.session + session.message_flow(destination=tag, unit=0, value=0xFFFFFFFF) + session.message_flow(destination=tag, unit=1, value=0xFFFFFFFF) + + def complete(self, session, msg): + session.receiver._completed.add(msg.id)#TODO: this may be done automatically + session.channel.session_completed(session.receiver._completed) + diff --git a/qpid/python/tests_0-10_preview/__init__.py b/qpid/python/tests_0-10_preview/__init__.py new file mode 100644 index 0000000000..f0acf9c632 --- /dev/null +++ b/qpid/python/tests_0-10_preview/__init__.py @@ -0,0 +1,33 @@ +# Do not delete - marks this directory as a python package. + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from alternate_exchange import * +from broker import * +from dtx import * +from example import * +from exchange import * +from execution import * +from management import * +from message import * +from query import * +from queue import * +from testlib import * +from tx import * diff --git a/qpid/python/tests_0-10_preview/alternate_exchange.py b/qpid/python/tests_0-10_preview/alternate_exchange.py new file mode 100644 index 0000000000..83f8d85811 --- /dev/null +++ b/qpid/python/tests_0-10_preview/alternate_exchange.py @@ -0,0 +1,179 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +from qpid.client import Client, Closed +from qpid.queue import Empty +from qpid.content import Content +from qpid.testlib import testrunner, TestBase + +class AlternateExchangeTests(TestBase): + """ + Tests for the new mechanism for message returns introduced in 0-10 + and available in 0-9 for preview + """ + + def test_unroutable(self): + """ + Test that unroutable messages are delivered to the alternate-exchange if specified + """ + channel = self.channel + #create an exchange with an alternate defined + channel.exchange_declare(exchange="secondary", type="fanout") + channel.exchange_declare(exchange="primary", type="direct", alternate_exchange="secondary") + + #declare, bind (to the alternate exchange) and consume from a queue for 'returned' messages + channel.queue_declare(queue="returns", exclusive=True, auto_delete=True) + channel.queue_bind(queue="returns", exchange="secondary") + self.subscribe(destination="a", queue="returns") + returned = self.client.queue("a") + + #declare, bind (to the primary exchange) and consume from a queue for 'processed' messages + channel.queue_declare(queue="processed", exclusive=True, auto_delete=True) + channel.queue_bind(queue="processed", exchange="primary", routing_key="my-key") + self.subscribe(destination="b", queue="processed") + processed = self.client.queue("b") + + #publish to the primary exchange + #...one message that makes it to the 'processed' queue: + channel.message_transfer(destination="primary", content=Content("Good", properties={'routing_key':"my-key"})) + #...and one that does not: + channel.message_transfer(destination="primary", content=Content("Bad", properties={'routing_key':"unused-key"})) + + #delete the exchanges + channel.exchange_delete(exchange="primary") + channel.exchange_delete(exchange="secondary") + + #verify behaviour + self.assertEqual("Good", processed.get(timeout=1).content.body) + self.assertEqual("Bad", returned.get(timeout=1).content.body) + self.assertEmpty(processed) + self.assertEmpty(returned) + + def test_queue_delete(self): + """ + Test that messages in a queue being deleted are delivered to the alternate-exchange if specified + """ + channel = self.channel + #set up a 'dead letter queue': + channel.exchange_declare(exchange="dlq", type="fanout") + channel.queue_declare(queue="deleted", exclusive=True, auto_delete=True) + channel.queue_bind(exchange="dlq", queue="deleted") + self.subscribe(destination="dlq", queue="deleted") + dlq = self.client.queue("dlq") + + #create a queue using the dlq as its alternate exchange: + channel.queue_declare(queue="delete-me", alternate_exchange="dlq") + #send it some messages: + channel.message_transfer(content=Content("One", properties={'routing_key':"delete-me"})) + channel.message_transfer(content=Content("Two", properties={'routing_key':"delete-me"})) + channel.message_transfer(content=Content("Three", properties={'routing_key':"delete-me"})) + #delete it: + channel.queue_delete(queue="delete-me") + #delete the dlq exchange: + channel.exchange_delete(exchange="dlq") + + #check the messages were delivered to the dlq: + self.assertEqual("One", dlq.get(timeout=1).content.body) + self.assertEqual("Two", dlq.get(timeout=1).content.body) + self.assertEqual("Three", dlq.get(timeout=1).content.body) + self.assertEmpty(dlq) + + + def test_immediate(self): + """ + Test that messages in a queue being deleted are delivered to the alternate-exchange if specified + """ + channel = self.channel + #set up a 'dead letter queue': + channel.exchange_declare(exchange="dlq", type="fanout") + channel.queue_declare(queue="immediate", exclusive=True, auto_delete=True) + channel.queue_bind(exchange="dlq", queue="immediate") + self.subscribe(destination="dlq", queue="immediate") + dlq = self.client.queue("dlq") + + #create a queue using the dlq as its alternate exchange: + channel.queue_declare(queue="no-consumers", alternate_exchange="dlq", exclusive=True, auto_delete=True) + #send it some messages: + #TODO: WE HAVE LOST THE IMMEDIATE FLAG; FIX THIS ONCE ITS BACK + channel.message_transfer(content=Content("no one wants me", properties={'routing_key':"no-consumers"})) + + #check the messages were delivered to the dlq: + self.assertEqual("no one wants me", dlq.get(timeout=1).content.body) + self.assertEmpty(dlq) + + #cleanup: + channel.queue_delete(queue="no-consumers") + channel.exchange_delete(exchange="dlq") + + + def test_delete_while_used_by_queue(self): + """ + Ensure an exchange still in use as an alternate-exchange for a + queue can't be deleted + """ + channel = self.channel + channel.exchange_declare(exchange="alternate", type="fanout") + channel.queue_declare(queue="q", exclusive=True, auto_delete=True, alternate_exchange="alternate") + try: + channel.exchange_delete(exchange="alternate") + self.fail("Expected deletion of in-use alternate-exchange to fail") + except Closed, e: + #cleanup: + other = self.connect() + channel = other.channel(1) + channel.session_open() + channel.exchange_delete(exchange="alternate") + channel.session_close() + other.close() + + self.assertConnectionException(530, e.args[0]) + + + + def test_delete_while_used_by_exchange(self): + """ + Ensure an exchange still in use as an alternate-exchange for + another exchange can't be deleted + """ + channel = self.channel + channel.exchange_declare(exchange="alternate", type="fanout") + channel.exchange_declare(exchange="e", type="fanout", alternate_exchange="alternate") + try: + channel.exchange_delete(exchange="alternate") + #cleanup: + channel.exchange_delete(exchange="e") + self.fail("Expected deletion of in-use alternate-exchange to fail") + except Closed, e: + #cleanup: + other = self.connect() + channel = other.channel(1) + channel.session_open() + channel.exchange_delete(exchange="e") + channel.exchange_delete(exchange="alternate") + channel.session_close() + other.close() + + self.assertConnectionException(530, e.args[0]) + + + def assertEmpty(self, queue): + try: + msg = queue.get(timeout=1) + self.fail("Queue not empty: " + msg) + except Empty: None + diff --git a/qpid/python/tests_0-10_preview/broker.py b/qpid/python/tests_0-10_preview/broker.py new file mode 100644 index 0000000000..99936ba742 --- /dev/null +++ b/qpid/python/tests_0-10_preview/broker.py @@ -0,0 +1,111 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +from qpid.client import Closed +from qpid.queue import Empty +from qpid.content import Content +from qpid.testlib import testrunner, TestBase + +class BrokerTests(TestBase): + """Tests for basic Broker functionality""" + + def test_ack_and_no_ack(self): + """ + First, this test tries to receive a message with a no-ack + consumer. Second, this test tries to explicitly receive and + acknowledge a message with an acknowledging consumer. + """ + ch = self.channel + self.queue_declare(ch, queue = "myqueue") + + # No ack consumer + ctag = "tag1" + self.subscribe(ch, queue = "myqueue", destination = ctag) + body = "test no-ack" + ch.message_transfer(content = Content(body, properties = {"routing_key" : "myqueue"})) + msg = self.client.queue(ctag).get(timeout = 5) + self.assert_(msg.content.body == body) + + # Acknowledging consumer + self.queue_declare(ch, queue = "otherqueue") + ctag = "tag2" + self.subscribe(ch, queue = "otherqueue", destination = ctag, confirm_mode = 1) + ch.message_flow(destination=ctag, unit=0, value=0xFFFFFFFF) + ch.message_flow(destination=ctag, unit=1, value=0xFFFFFFFF) + body = "test ack" + ch.message_transfer(content = Content(body, properties = {"routing_key" : "otherqueue"})) + msg = self.client.queue(ctag).get(timeout = 5) + msg.complete() + self.assert_(msg.content.body == body) + + def test_simple_delivery_immediate(self): + """ + Test simple message delivery where consume is issued before publish + """ + channel = self.channel + self.exchange_declare(channel, exchange="test-exchange", type="direct") + self.queue_declare(channel, queue="test-queue") + channel.queue_bind(queue="test-queue", exchange="test-exchange", routing_key="key") + consumer_tag = "tag1" + self.subscribe(queue="test-queue", destination=consumer_tag) + queue = self.client.queue(consumer_tag) + + body = "Immediate Delivery" + channel.message_transfer(destination="test-exchange", content = Content(body, properties = {"routing_key" : "key"})) + msg = queue.get(timeout=5) + self.assert_(msg.content.body == body) + + # TODO: Ensure we fail if immediate=True and there's no consumer. + + + def test_simple_delivery_queued(self): + """ + Test basic message delivery where publish is issued before consume + (i.e. requires queueing of the message) + """ + channel = self.channel + self.exchange_declare(channel, exchange="test-exchange", type="direct") + self.queue_declare(channel, queue="test-queue") + channel.queue_bind(queue="test-queue", exchange="test-exchange", routing_key="key") + body = "Queued Delivery" + channel.message_transfer(destination="test-exchange", content = Content(body, properties = {"routing_key" : "key"})) + + consumer_tag = "tag1" + self.subscribe(queue="test-queue", destination=consumer_tag) + queue = self.client.queue(consumer_tag) + msg = queue.get(timeout=5) + self.assert_(msg.content.body == body) + + def test_invalid_channel(self): + channel = self.client.channel(200) + try: + channel.queue_declare(exclusive=True) + self.fail("Expected error on queue_declare for invalid channel") + except Closed, e: + self.assertConnectionException(504, e.args[0]) + + def test_closed_channel(self): + channel = self.client.channel(200) + channel.session_open() + channel.session_close() + try: + channel.queue_declare(exclusive=True) + self.fail("Expected error on queue_declare for closed channel") + except Closed, e: + if isinstance(e.args[0], str): self.fail(e) + self.assertConnectionException(504, e.args[0]) diff --git a/qpid/python/tests_0-10_preview/dtx.py b/qpid/python/tests_0-10_preview/dtx.py new file mode 100644 index 0000000000..f84f91c75a --- /dev/null +++ b/qpid/python/tests_0-10_preview/dtx.py @@ -0,0 +1,645 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +from qpid.client import Client, Closed +from qpid.queue import Empty +from qpid.content import Content +from qpid.testlib import testrunner, TestBase +from struct import pack, unpack +from time import sleep + +class DtxTests(TestBase): + """ + Tests for the amqp dtx related classes. + + Tests of the form test_simple_xxx test the basic transactional + behaviour. The approach here is to 'swap' a message from one queue + to another by consuming and re-publishing in the same + transaction. That transaction is then completed in different ways + and the appropriate result verified. + + The other tests enforce more specific rules and behaviour on a + per-method or per-field basis. + """ + + XA_RBROLLBACK = 1 + XA_RBTIMEOUT = 2 + XA_OK = 0 + tx_counter = 0 + + def reset_channel(self): + self.channel.session_close() + self.channel = self.client.channel(self.channel.id + 1) + self.channel.session_open() + + def test_simple_commit(self): + """ + Test basic one-phase commit behaviour. + """ + channel = self.channel + tx = self.xid("my-xid") + self.txswap(tx, "commit") + + #neither queue should have any messages accessible + self.assertMessageCount(0, "queue-a") + self.assertMessageCount(0, "queue-b") + + #commit + self.assertEqual(self.XA_OK, channel.dtx_coordination_commit(xid=tx, one_phase=True).status) + + #should close and reopen channel to ensure no unacked messages are held + self.reset_channel() + + #check result + self.assertMessageCount(0, "queue-a") + self.assertMessageCount(1, "queue-b") + self.assertMessageId("commit", "queue-b") + + def test_simple_prepare_commit(self): + """ + Test basic two-phase commit behaviour. + """ + channel = self.channel + tx = self.xid("my-xid") + self.txswap(tx, "prepare-commit") + + #prepare + self.assertEqual(self.XA_OK, channel.dtx_coordination_prepare(xid=tx).status) + + #neither queue should have any messages accessible + self.assertMessageCount(0, "queue-a") + self.assertMessageCount(0, "queue-b") + + #commit + self.assertEqual(self.XA_OK, channel.dtx_coordination_commit(xid=tx, one_phase=False).status) + + self.reset_channel() + + #check result + self.assertMessageCount(0, "queue-a") + self.assertMessageCount(1, "queue-b") + self.assertMessageId("prepare-commit", "queue-b") + + + def test_simple_rollback(self): + """ + Test basic rollback behaviour. + """ + channel = self.channel + tx = self.xid("my-xid") + self.txswap(tx, "rollback") + + #neither queue should have any messages accessible + self.assertMessageCount(0, "queue-a") + self.assertMessageCount(0, "queue-b") + + #rollback + self.assertEqual(self.XA_OK, channel.dtx_coordination_rollback(xid=tx).status) + + self.reset_channel() + + #check result + self.assertMessageCount(1, "queue-a") + self.assertMessageCount(0, "queue-b") + self.assertMessageId("rollback", "queue-a") + + def test_simple_prepare_rollback(self): + """ + Test basic rollback behaviour after the transaction has been prepared. + """ + channel = self.channel + tx = self.xid("my-xid") + self.txswap(tx, "prepare-rollback") + + #prepare + self.assertEqual(self.XA_OK, channel.dtx_coordination_prepare(xid=tx).status) + + #neither queue should have any messages accessible + self.assertMessageCount(0, "queue-a") + self.assertMessageCount(0, "queue-b") + + #rollback + self.assertEqual(self.XA_OK, channel.dtx_coordination_rollback(xid=tx).status) + + self.reset_channel() + + #check result + self.assertMessageCount(1, "queue-a") + self.assertMessageCount(0, "queue-b") + self.assertMessageId("prepare-rollback", "queue-a") + + def test_select_required(self): + """ + check that an error is flagged if select is not issued before + start or end + """ + channel = self.channel + tx = self.xid("dummy") + try: + channel.dtx_demarcation_start(xid=tx) + + #if we get here we have failed, but need to do some cleanup: + channel.dtx_demarcation_end(xid=tx) + channel.dtx_coordination_rollback(xid=tx) + self.fail("Channel not selected for use with dtx, expected exception!") + except Closed, e: + self.assertConnectionException(503, e.args[0]) + + def test_start_already_known(self): + """ + Verify that an attempt to start an association with a + transaction that is already known is not allowed (unless the + join flag is set). + """ + #create two channels on different connection & select them for use with dtx: + channel1 = self.channel + channel1.dtx_demarcation_select() + + other = self.connect() + channel2 = other.channel(1) + channel2.session_open() + channel2.dtx_demarcation_select() + + #create a xid + tx = self.xid("dummy") + #start work on one channel under that xid: + channel1.dtx_demarcation_start(xid=tx) + #then start on the other without the join set + failed = False + try: + channel2.dtx_demarcation_start(xid=tx) + except Closed, e: + failed = True + error = e + + #cleanup: + if not failed: + channel2.dtx_demarcation_end(xid=tx) + other.close() + channel1.dtx_demarcation_end(xid=tx) + channel1.dtx_coordination_rollback(xid=tx) + + #verification: + if failed: self.assertConnectionException(503, e.args[0]) + else: self.fail("Xid already known, expected exception!") + + def test_forget_xid_on_completion(self): + """ + Verify that a xid is 'forgotten' - and can therefore be used + again - once it is completed. + """ + #do some transactional work & complete the transaction + self.test_simple_commit() + # channel has been reset, so reselect for use with dtx + self.channel.dtx_demarcation_select() + + #start association for the same xid as the previously completed txn + tx = self.xid("my-xid") + self.channel.dtx_demarcation_start(xid=tx) + self.channel.dtx_demarcation_end(xid=tx) + self.channel.dtx_coordination_rollback(xid=tx) + + def test_start_join_and_resume(self): + """ + Ensure the correct error is signalled when both the join and + resume flags are set on starting an association between a + channel and a transcation. + """ + channel = self.channel + channel.dtx_demarcation_select() + tx = self.xid("dummy") + try: + channel.dtx_demarcation_start(xid=tx, join=True, resume=True) + #failed, but need some cleanup: + channel.dtx_demarcation_end(xid=tx) + channel.dtx_coordination_rollback(xid=tx) + self.fail("Join and resume both set, expected exception!") + except Closed, e: + self.assertConnectionException(503, e.args[0]) + + def test_start_join(self): + """ + Verify 'join' behaviour, where a channel is associated with a + transaction that is already associated with another channel. + """ + #create two channels & select them for use with dtx: + channel1 = self.channel + channel1.dtx_demarcation_select() + + channel2 = self.client.channel(2) + channel2.session_open() + channel2.dtx_demarcation_select() + + #setup + channel1.queue_declare(queue="one", exclusive=True, auto_delete=True) + channel1.queue_declare(queue="two", exclusive=True, auto_delete=True) + channel1.message_transfer(content=Content(properties={'routing_key':"one", 'message_id':"a"}, body="DtxMessage")) + channel1.message_transfer(content=Content(properties={'routing_key':"two", 'message_id':"b"}, body="DtxMessage")) + + #create a xid + tx = self.xid("dummy") + #start work on one channel under that xid: + channel1.dtx_demarcation_start(xid=tx) + #then start on the other with the join flag set + channel2.dtx_demarcation_start(xid=tx, join=True) + + #do work through each channel + self.swap(channel1, "one", "two")#swap 'a' from 'one' to 'two' + self.swap(channel2, "two", "one")#swap 'b' from 'two' to 'one' + + #mark end on both channels + channel1.dtx_demarcation_end(xid=tx) + channel2.dtx_demarcation_end(xid=tx) + + #commit and check + channel1.dtx_coordination_commit(xid=tx, one_phase=True) + self.assertMessageCount(1, "one") + self.assertMessageCount(1, "two") + self.assertMessageId("a", "two") + self.assertMessageId("b", "one") + + + def test_suspend_resume(self): + """ + Test suspension and resumption of an association + """ + channel = self.channel + channel.dtx_demarcation_select() + + #setup + channel.queue_declare(queue="one", exclusive=True, auto_delete=True) + channel.queue_declare(queue="two", exclusive=True, auto_delete=True) + channel.message_transfer(content=Content(properties={'routing_key':"one", 'message_id':"a"}, body="DtxMessage")) + channel.message_transfer(content=Content(properties={'routing_key':"two", 'message_id':"b"}, body="DtxMessage")) + + tx = self.xid("dummy") + + channel.dtx_demarcation_start(xid=tx) + self.swap(channel, "one", "two")#swap 'a' from 'one' to 'two' + channel.dtx_demarcation_end(xid=tx, suspend=True) + + channel.dtx_demarcation_start(xid=tx, resume=True) + self.swap(channel, "two", "one")#swap 'b' from 'two' to 'one' + channel.dtx_demarcation_end(xid=tx) + + #commit and check + channel.dtx_coordination_commit(xid=tx, one_phase=True) + self.assertMessageCount(1, "one") + self.assertMessageCount(1, "two") + self.assertMessageId("a", "two") + self.assertMessageId("b", "one") + + def test_suspend_start_end_resume(self): + """ + Test suspension and resumption of an association with work + done on another transaction when the first transaction is + suspended + """ + channel = self.channel + channel.dtx_demarcation_select() + + #setup + channel.queue_declare(queue="one", exclusive=True, auto_delete=True) + channel.queue_declare(queue="two", exclusive=True, auto_delete=True) + channel.message_transfer(content=Content(properties={'routing_key':"one", 'message_id':"a"}, body="DtxMessage")) + channel.message_transfer(content=Content(properties={'routing_key':"two", 'message_id':"b"}, body="DtxMessage")) + + tx = self.xid("dummy") + + channel.dtx_demarcation_start(xid=tx) + self.swap(channel, "one", "two")#swap 'a' from 'one' to 'two' + channel.dtx_demarcation_end(xid=tx, suspend=True) + + channel.dtx_demarcation_start(xid=tx, resume=True) + self.swap(channel, "two", "one")#swap 'b' from 'two' to 'one' + channel.dtx_demarcation_end(xid=tx) + + #commit and check + channel.dtx_coordination_commit(xid=tx, one_phase=True) + self.assertMessageCount(1, "one") + self.assertMessageCount(1, "two") + self.assertMessageId("a", "two") + self.assertMessageId("b", "one") + + def test_end_suspend_and_fail(self): + """ + Verify that the correct error is signalled if the suspend and + fail flag are both set when disassociating a transaction from + the channel + """ + channel = self.channel + channel.dtx_demarcation_select() + tx = self.xid("suspend_and_fail") + channel.dtx_demarcation_start(xid=tx) + try: + channel.dtx_demarcation_end(xid=tx, suspend=True, fail=True) + self.fail("Suspend and fail both set, expected exception!") + except Closed, e: + self.assertConnectionException(503, e.args[0]) + + #cleanup + other = self.connect() + channel = other.channel(1) + channel.session_open() + channel.dtx_coordination_rollback(xid=tx) + channel.session_close() + other.close() + + + def test_end_unknown_xid(self): + """ + Verifies that the correct exception is thrown when an attempt + is made to end the association for a xid not previously + associated with the channel + """ + channel = self.channel + channel.dtx_demarcation_select() + tx = self.xid("unknown-xid") + try: + channel.dtx_demarcation_end(xid=tx) + self.fail("Attempted to end association with unknown xid, expected exception!") + except Closed, e: + #FYI: this is currently *not* the exception specified, but I think the spec is wrong! Confirming... + self.assertConnectionException(503, e.args[0]) + + def test_end(self): + """ + Verify that the association is terminated by end and subsequent + operations are non-transactional + """ + channel = self.client.channel(2) + channel.session_open() + channel.queue_declare(queue="tx-queue", exclusive=True, auto_delete=True) + + #publish a message under a transaction + channel.dtx_demarcation_select() + tx = self.xid("dummy") + channel.dtx_demarcation_start(xid=tx) + channel.message_transfer(content=Content(properties={'routing_key':"tx-queue", 'message_id':"one"}, body="DtxMessage")) + channel.dtx_demarcation_end(xid=tx) + + #now that association with txn is ended, publish another message + channel.message_transfer(content=Content(properties={'routing_key':"tx-queue", 'message_id':"two"}, body="DtxMessage")) + + #check the second message is available, but not the first + self.assertMessageCount(1, "tx-queue") + self.subscribe(channel, queue="tx-queue", destination="results", confirm_mode=1) + msg = self.client.queue("results").get(timeout=1) + self.assertEqual("two", msg.content['message_id']) + channel.message_cancel(destination="results") + #ack the message then close the channel + msg.complete() + channel.session_close() + + channel = self.channel + #commit the transaction and check that the first message (and + #only the first message) is then delivered + channel.dtx_coordination_commit(xid=tx, one_phase=True) + self.assertMessageCount(1, "tx-queue") + self.assertMessageId("one", "tx-queue") + + def test_invalid_commit_one_phase_true(self): + """ + Test that a commit with one_phase = True is rejected if the + transaction in question has already been prepared. + """ + other = self.connect() + tester = other.channel(1) + tester.session_open() + tester.queue_declare(queue="dummy", exclusive=True, auto_delete=True) + tester.dtx_demarcation_select() + tx = self.xid("dummy") + tester.dtx_demarcation_start(xid=tx) + tester.message_transfer(content=Content(properties={'routing_key':"dummy"}, body="whatever")) + tester.dtx_demarcation_end(xid=tx) + tester.dtx_coordination_prepare(xid=tx) + failed = False + try: + tester.dtx_coordination_commit(xid=tx, one_phase=True) + except Closed, e: + failed = True + error = e + + if failed: + self.channel.dtx_coordination_rollback(xid=tx) + self.assertConnectionException(503, e.args[0]) + else: + tester.session_close() + other.close() + self.fail("Invalid use of one_phase=True, expected exception!") + + def test_invalid_commit_one_phase_false(self): + """ + Test that a commit with one_phase = False is rejected if the + transaction in question has not yet been prepared. + """ + """ + Test that a commit with one_phase = True is rejected if the + transaction in question has already been prepared. + """ + other = self.connect() + tester = other.channel(1) + tester.session_open() + tester.queue_declare(queue="dummy", exclusive=True, auto_delete=True) + tester.dtx_demarcation_select() + tx = self.xid("dummy") + tester.dtx_demarcation_start(xid=tx) + tester.message_transfer(content=Content(properties={'routing_key':"dummy"}, body="whatever")) + tester.dtx_demarcation_end(xid=tx) + failed = False + try: + tester.dtx_coordination_commit(xid=tx, one_phase=False) + except Closed, e: + failed = True + error = e + + if failed: + self.channel.dtx_coordination_rollback(xid=tx) + self.assertConnectionException(503, e.args[0]) + else: + tester.session_close() + other.close() + self.fail("Invalid use of one_phase=False, expected exception!") + + def test_implicit_end(self): + """ + Test that an association is implicitly ended when the channel + is closed (whether by exception or explicit client request) + and the transaction in question is marked as rollback only. + """ + channel1 = self.channel + channel2 = self.client.channel(2) + channel2.session_open() + + #setup: + channel2.queue_declare(queue="dummy", exclusive=True, auto_delete=True) + channel2.message_transfer(content=Content(properties={'routing_key':"dummy"}, body="whatever")) + tx = self.xid("dummy") + + channel2.dtx_demarcation_select() + channel2.dtx_demarcation_start(xid=tx) + channel2.message_subscribe(queue="dummy", destination="dummy", confirm_mode=1) + channel2.message_flow(destination="dummy", unit=0, value=1) + channel2.message_flow(destination="dummy", unit=1, value=0xFFFFFFFF) + self.client.queue("dummy").get(timeout=1).complete() + channel2.message_cancel(destination="dummy") + channel2.message_transfer(content=Content(properties={'routing_key':"dummy"}, body="whatever")) + channel2.session_close() + + self.assertEqual(self.XA_RBROLLBACK, channel1.dtx_coordination_prepare(xid=tx).status) + channel1.dtx_coordination_rollback(xid=tx) + + def test_get_timeout(self): + """ + Check that get-timeout returns the correct value, (and that a + transaction with a timeout can complete normally) + """ + channel = self.channel + tx = self.xid("dummy") + + channel.dtx_demarcation_select() + channel.dtx_demarcation_start(xid=tx) + self.assertEqual(0, channel.dtx_coordination_get_timeout(xid=tx).timeout) + channel.dtx_coordination_set_timeout(xid=tx, timeout=60) + self.assertEqual(60, channel.dtx_coordination_get_timeout(xid=tx).timeout) + self.assertEqual(self.XA_OK, channel.dtx_demarcation_end(xid=tx).status) + self.assertEqual(self.XA_OK, channel.dtx_coordination_rollback(xid=tx).status) + + def test_set_timeout(self): + """ + Test the timeout of a transaction results in the expected + behaviour + """ + #open new channel to allow self.channel to be used in checking te queue + channel = self.client.channel(2) + channel.session_open() + #setup: + tx = self.xid("dummy") + channel.queue_declare(queue="queue-a", exclusive=True, auto_delete=True) + channel.queue_declare(queue="queue-b", exclusive=True, auto_delete=True) + channel.message_transfer(content=Content(properties={'routing_key':"queue-a", 'message_id':"timeout"}, body="DtxMessage")) + + channel.dtx_demarcation_select() + channel.dtx_demarcation_start(xid=tx) + self.swap(channel, "queue-a", "queue-b") + channel.dtx_coordination_set_timeout(xid=tx, timeout=2) + sleep(3) + #check that the work has been rolled back already + self.assertMessageCount(1, "queue-a") + self.assertMessageCount(0, "queue-b") + self.assertMessageId("timeout", "queue-a") + #check the correct codes are returned when we try to complete the txn + self.assertEqual(self.XA_RBTIMEOUT, channel.dtx_demarcation_end(xid=tx).status) + self.assertEqual(self.XA_RBTIMEOUT, channel.dtx_coordination_rollback(xid=tx).status) + + + + def test_recover(self): + """ + Test basic recover behaviour + """ + channel = self.channel + + channel.dtx_demarcation_select() + channel.queue_declare(queue="dummy", exclusive=True, auto_delete=True) + + prepared = [] + for i in range(1, 10): + tx = self.xid("tx%s" % (i)) + channel.dtx_demarcation_start(xid=tx) + channel.message_transfer(content=Content(properties={'routing_key':"dummy"}, body="message%s" % (i))) + channel.dtx_demarcation_end(xid=tx) + if i in [2, 5, 6, 8]: + channel.dtx_coordination_prepare(xid=tx) + prepared.append(tx) + else: + channel.dtx_coordination_rollback(xid=tx) + + xids = channel.dtx_coordination_recover().in_doubt + + #rollback the prepared transactions returned by recover + for x in xids: + channel.dtx_coordination_rollback(xid=x) + + #validate against the expected list of prepared transactions + actual = set(xids) + expected = set(prepared) + intersection = actual.intersection(expected) + + if intersection != expected: + missing = expected.difference(actual) + extra = actual.difference(expected) + for x in missing: + channel.dtx_coordination_rollback(xid=x) + self.fail("Recovered xids not as expected. missing: %s; extra: %s" % (missing, extra)) + + def test_bad_resume(self): + """ + Test that a resume on a session not selected for use with dtx fails + """ + channel = self.channel + try: + channel.dtx_demarcation_start(resume=True) + except Closed, e: + self.assertConnectionException(503, e.args[0]) + + def xid(self, txid): + DtxTests.tx_counter += 1 + branchqual = "v%s" % DtxTests.tx_counter + return pack('!LBB', 0, len(txid), len(branchqual)) + txid + branchqual + + def txswap(self, tx, id): + channel = self.channel + #declare two queues: + channel.queue_declare(queue="queue-a", exclusive=True, auto_delete=True) + channel.queue_declare(queue="queue-b", exclusive=True, auto_delete=True) + #put message with specified id on one queue: + channel.message_transfer(content=Content(properties={'routing_key':"queue-a", 'message_id':id}, body="DtxMessage")) + + #start the transaction: + channel.dtx_demarcation_select() + self.assertEqual(self.XA_OK, self.channel.dtx_demarcation_start(xid=tx).status) + + #'swap' the message from one queue to the other, under that transaction: + self.swap(self.channel, "queue-a", "queue-b") + + #mark the end of the transactional work: + self.assertEqual(self.XA_OK, self.channel.dtx_demarcation_end(xid=tx).status) + + def swap(self, channel, src, dest): + #consume from src: + channel.message_subscribe(destination="temp-swap", queue=src, confirm_mode=1) + channel.message_flow(destination="temp-swap", unit=0, value=1) + channel.message_flow(destination="temp-swap", unit=1, value=0xFFFFFFFF) + msg = self.client.queue("temp-swap").get(timeout=1) + channel.message_cancel(destination="temp-swap") + msg.complete(); + + #re-publish to dest + channel.message_transfer(content=Content(properties={'routing_key':dest, 'message_id':msg.content['message_id']}, + body=msg.content.body)) + + def assertMessageCount(self, expected, queue): + self.assertEqual(expected, self.channel.queue_query(queue=queue).message_count) + + def assertMessageId(self, expected, queue): + self.channel.message_subscribe(queue=queue, destination="results") + self.channel.message_flow(destination="results", unit=0, value=1) + self.channel.message_flow(destination="results", unit=1, value=0xFFFFFFFF) + self.assertEqual(expected, self.client.queue("results").get(timeout=1).content['message_id']) + self.channel.message_cancel(destination="results") diff --git a/qpid/python/tests_0-10_preview/example.py b/qpid/python/tests_0-10_preview/example.py new file mode 100644 index 0000000000..da5ee2441f --- /dev/null +++ b/qpid/python/tests_0-10_preview/example.py @@ -0,0 +1,95 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from qpid.content import Content +from qpid.testlib import testrunner, TestBase + +class ExampleTest (TestBase): + """ + An example Qpid test, illustrating the unittest framework and the + python Qpid client. The test class must inherit TestBase. The + test code uses the Qpid client to interact with a qpid broker and + verify it behaves as expected. + """ + + def test_example(self): + """ + An example test. Note that test functions must start with 'test_' + to be recognized by the test framework. + """ + + # By inheriting TestBase, self.client is automatically connected + # and self.channel is automatically opened as channel(1) + # Other channel methods mimic the protocol. + channel = self.channel + + # Now we can send regular commands. If you want to see what the method + # arguments mean or what other commands are available, you can use the + # python builtin help() method. For example: + #help(chan) + #help(chan.exchange_declare) + + # If you want browse the available protocol methods without being + # connected to a live server you can use the amqp-doc utility: + # + # Usage amqp-doc [<options>] <spec> [<pattern_1> ... <pattern_n>] + # + # Options: + # -e, --regexp use regex instead of glob when matching + + # Now that we know what commands are available we can use them to + # interact with the server. + + # Here we use ordinal arguments. + self.exchange_declare(channel, 0, "test", "direct") + + # Here we use keyword arguments. + self.queue_declare(channel, queue="test-queue") + channel.queue_bind(queue="test-queue", exchange="test", routing_key="key") + + # Call Channel.basic_consume to register as a consumer. + # All the protocol methods return a message object. The message object + # has fields corresponding to the reply method fields, plus a content + # field that is filled if the reply includes content. In this case the + # interesting field is the consumer_tag. + channel.message_subscribe(queue="test-queue", destination="consumer_tag") + channel.message_flow(destination="consumer_tag", unit=0, value=0xFFFFFFFF) + channel.message_flow(destination="consumer_tag", unit=1, value=0xFFFFFFFF) + + # We can use the Client.queue(...) method to access the queue + # corresponding to our consumer_tag. + queue = self.client.queue("consumer_tag") + + # Now lets publish a message and see if our consumer gets it. To do + # this we need to import the Content class. + sent = Content("Hello World!") + sent["routing_key"] = "key" + channel.message_transfer(destination="test", content=sent) + + # Now we'll wait for the message to arrive. We can use the timeout + # argument in case the server hangs. By default queue.get() will wait + # until a message arrives or the connection to the server dies. + msg = queue.get(timeout=10) + + # And check that we got the right response with assertEqual + self.assertEqual(sent.body, msg.content.body) + + # Now acknowledge the message. + msg.complete() + diff --git a/qpid/python/tests_0-10_preview/exchange.py b/qpid/python/tests_0-10_preview/exchange.py new file mode 100644 index 0000000000..86c39b7736 --- /dev/null +++ b/qpid/python/tests_0-10_preview/exchange.py @@ -0,0 +1,335 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +""" +Tests for exchange behaviour. + +Test classes ending in 'RuleTests' are derived from rules in amqp.xml. +""" + +import Queue, logging +from qpid.testlib import TestBase +from qpid.content import Content +from qpid.client import Closed + + +class StandardExchangeVerifier: + """Verifies standard exchange behavior. + + Used as base class for classes that test standard exchanges.""" + + def verifyDirectExchange(self, ex): + """Verify that ex behaves like a direct exchange.""" + self.queue_declare(queue="q") + self.channel.queue_bind(queue="q", exchange=ex, routing_key="k") + self.assertPublishConsume(exchange=ex, queue="q", routing_key="k") + try: + self.assertPublishConsume(exchange=ex, queue="q", routing_key="kk") + self.fail("Expected Empty exception") + except Queue.Empty: None # Expected + + def verifyFanOutExchange(self, ex): + """Verify that ex behaves like a fanout exchange.""" + self.queue_declare(queue="q") + self.channel.queue_bind(queue="q", exchange=ex) + self.queue_declare(queue="p") + self.channel.queue_bind(queue="p", exchange=ex) + for qname in ["q", "p"]: self.assertPublishGet(self.consume(qname), ex) + + def verifyTopicExchange(self, ex): + """Verify that ex behaves like a topic exchange""" + self.queue_declare(queue="a") + self.channel.queue_bind(queue="a", exchange=ex, routing_key="a.#.b.*") + q = self.consume("a") + self.assertPublishGet(q, ex, "a.b.x") + self.assertPublishGet(q, ex, "a.x.b.x") + self.assertPublishGet(q, ex, "a.x.x.b.x") + # Shouldn't match + self.channel.message_transfer(destination=ex, content=Content(properties={'routing_key':"a.b"})) + self.channel.message_transfer(destination=ex, content=Content(properties={'routing_key':"a.b.x.y"})) + self.channel.message_transfer(destination=ex, content=Content(properties={'routing_key':"x.a.b.x"})) + self.channel.message_transfer(destination=ex, content=Content(properties={'routing_key':"a.b"})) + self.assert_(q.empty()) + + def verifyHeadersExchange(self, ex): + """Verify that ex is a headers exchange""" + self.queue_declare(queue="q") + self.channel.queue_bind(queue="q", exchange=ex, arguments={ "x-match":"all", "name":"fred" , "age":3} ) + q = self.consume("q") + headers = {"name":"fred", "age":3} + self.assertPublishGet(q, exchange=ex, properties=headers) + self.channel.message_transfer(destination=ex) # No headers, won't deliver + self.assertEmpty(q); + + +class RecommendedTypesRuleTests(TestBase, StandardExchangeVerifier): + """ + The server SHOULD implement these standard exchange types: topic, headers. + + Client attempts to declare an exchange with each of these standard types. + """ + + def testDirect(self): + """Declare and test a direct exchange""" + self.exchange_declare(0, exchange="d", type="direct") + self.verifyDirectExchange("d") + + def testFanout(self): + """Declare and test a fanout exchange""" + self.exchange_declare(0, exchange="f", type="fanout") + self.verifyFanOutExchange("f") + + def testTopic(self): + """Declare and test a topic exchange""" + self.exchange_declare(0, exchange="t", type="topic") + self.verifyTopicExchange("t") + + def testHeaders(self): + """Declare and test a headers exchange""" + self.exchange_declare(0, exchange="h", type="headers") + self.verifyHeadersExchange("h") + + +class RequiredInstancesRuleTests(TestBase, StandardExchangeVerifier): + """ + The server MUST, in each virtual host, pre-declare an exchange instance + for each standard exchange type that it implements, where the name of the + exchange instance is amq. followed by the exchange type name. + + Client creates a temporary queue and attempts to bind to each required + exchange instance (amq.fanout, amq.direct, and amq.topic, amq.match if + those types are defined). + """ + def testAmqDirect(self): self.verifyDirectExchange("amq.direct") + + def testAmqFanOut(self): self.verifyFanOutExchange("amq.fanout") + + def testAmqTopic(self): self.verifyTopicExchange("amq.topic") + + def testAmqMatch(self): self.verifyHeadersExchange("amq.match") + +class DefaultExchangeRuleTests(TestBase, StandardExchangeVerifier): + """ + The server MUST predeclare a direct exchange to act as the default exchange + for content Publish methods and for default queue bindings. + + Client checks that the default exchange is active by specifying a queue + binding with no exchange name, and publishing a message with a suitable + routing key but without specifying the exchange name, then ensuring that + the message arrives in the queue correctly. + """ + def testDefaultExchange(self): + # Test automatic binding by queue name. + self.queue_declare(queue="d") + self.assertPublishConsume(queue="d", routing_key="d") + # Test explicit bind to default queue + self.verifyDirectExchange("") + + +# TODO aconway 2006-09-27: Fill in empty tests: + +class DefaultAccessRuleTests(TestBase): + """ + The server MUST NOT allow clients to access the default exchange except + by specifying an empty exchange name in the Queue.Bind and content Publish + methods. + """ + +class ExtensionsRuleTests(TestBase): + """ + The server MAY implement other exchange types as wanted. + """ + + +class DeclareMethodMinimumRuleTests(TestBase): + """ + The server SHOULD support a minimum of 16 exchanges per virtual host and + ideally, impose no limit except as defined by available resources. + + The client creates as many exchanges as it can until the server reports + an error; the number of exchanges successfuly created must be at least + sixteen. + """ + + +class DeclareMethodTicketFieldValidityRuleTests(TestBase): + """ + The client MUST provide a valid access ticket giving "active" access to + the realm in which the exchange exists or will be created, or "passive" + access if the if-exists flag is set. + + Client creates access ticket with wrong access rights and attempts to use + in this method. + """ + + +class DeclareMethodExchangeFieldReservedRuleTests(TestBase): + """ + Exchange names starting with "amq." are reserved for predeclared and + standardised exchanges. The client MUST NOT attempt to create an exchange + starting with "amq.". + + + """ + + +class DeclareMethodTypeFieldTypedRuleTests(TestBase): + """ + Exchanges cannot be redeclared with different types. The client MUST not + attempt to redeclare an existing exchange with a different type than used + in the original Exchange.Declare method. + + + """ + + +class DeclareMethodTypeFieldSupportRuleTests(TestBase): + """ + The client MUST NOT attempt to create an exchange with a type that the + server does not support. + + + """ + + +class DeclareMethodPassiveFieldNotFoundRuleTests(TestBase): + """ + If set, and the exchange does not already exist, the server MUST raise a + channel exception with reply code 404 (not found). + """ + def test(self): + try: + self.channel.exchange_declare(exchange="humpty_dumpty", passive=True) + self.fail("Expected 404 for passive declaration of unknown exchange.") + except Closed, e: + self.assertChannelException(404, e.args[0]) + + +class DeclareMethodDurableFieldSupportRuleTests(TestBase): + """ + The server MUST support both durable and transient exchanges. + + + """ + + +class DeclareMethodDurableFieldStickyRuleTests(TestBase): + """ + The server MUST ignore the durable field if the exchange already exists. + + + """ + + +class DeclareMethodAutoDeleteFieldStickyRuleTests(TestBase): + """ + The server MUST ignore the auto-delete field if the exchange already + exists. + + + """ + + +class DeleteMethodTicketFieldValidityRuleTests(TestBase): + """ + The client MUST provide a valid access ticket giving "active" access + rights to the exchange's access realm. + + Client creates access ticket with wrong access rights and attempts to use + in this method. + """ + + +class DeleteMethodExchangeFieldExistsRuleTests(TestBase): + """ + The client MUST NOT attempt to delete an exchange that does not exist. + """ + + +class HeadersExchangeTests(TestBase): + """ + Tests for headers exchange functionality. + """ + def setUp(self): + TestBase.setUp(self) + self.queue_declare(queue="q") + self.q = self.consume("q") + + def myAssertPublishGet(self, headers): + self.assertPublishGet(self.q, exchange="amq.match", properties=headers) + + def myBasicPublish(self, headers): + self.channel.message_transfer(destination="amq.match", content=Content("foobar", properties={'application_headers':headers})) + + def testMatchAll(self): + self.channel.queue_bind(queue="q", exchange="amq.match", arguments={ 'x-match':'all', "name":"fred", "age":3}) + self.myAssertPublishGet({"name":"fred", "age":3}) + self.myAssertPublishGet({"name":"fred", "age":3, "extra":"ignoreme"}) + + # None of these should match + self.myBasicPublish({}) + self.myBasicPublish({"name":"barney"}) + self.myBasicPublish({"name":10}) + self.myBasicPublish({"name":"fred", "age":2}) + self.assertEmpty(self.q) + + def testMatchAny(self): + self.channel.queue_bind(queue="q", exchange="amq.match", arguments={ 'x-match':'any', "name":"fred", "age":3}) + self.myAssertPublishGet({"name":"fred"}) + self.myAssertPublishGet({"name":"fred", "ignoreme":10}) + self.myAssertPublishGet({"ignoreme":10, "age":3}) + + # Wont match + self.myBasicPublish({}) + self.myBasicPublish({"irrelevant":0}) + self.assertEmpty(self.q) + + +class MiscellaneousErrorsTests(TestBase): + """ + Test some miscellaneous error conditions + """ + def testTypeNotKnown(self): + try: + self.channel.exchange_declare(exchange="test_type_not_known_exchange", type="invalid_type") + self.fail("Expected 503 for declaration of unknown exchange type.") + except Closed, e: + self.assertConnectionException(503, e.args[0]) + + def testDifferentDeclaredType(self): + self.channel.exchange_declare(exchange="test_different_declared_type_exchange", type="direct") + try: + self.channel.exchange_declare(exchange="test_different_declared_type_exchange", type="topic") + self.fail("Expected 530 for redeclaration of exchange with different type.") + except Closed, e: + self.assertConnectionException(530, e.args[0]) + #cleanup + other = self.connect() + c2 = other.channel(1) + c2.session_open() + c2.exchange_delete(exchange="test_different_declared_type_exchange") + +class ExchangeTests(TestBase): + def testHeadersBindNoMatchArg(self): + self.channel.queue_declare(queue="q", exclusive=True, auto_delete=True) + try: + self.channel.queue_bind(queue="q", exchange="amq.match", arguments={"name":"fred" , "age":3} ) + self.fail("Expected failure for missing x-match arg.") + except Closed, e: + self.assertConnectionException(541, e.args[0]) diff --git a/qpid/python/tests_0-10_preview/execution.py b/qpid/python/tests_0-10_preview/execution.py new file mode 100644 index 0000000000..3ff6d8ea65 --- /dev/null +++ b/qpid/python/tests_0-10_preview/execution.py @@ -0,0 +1,29 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from qpid.content import Content +from qpid.testlib import testrunner, TestBase + +class ExecutionTests (TestBase): + def test_flush(self): + channel = self.channel + for i in [1, 2, 3]: + channel.message_transfer( + content=Content(properties={'routing_key':str(i)})) + assert(channel.completion.wait(channel.completion.command_id, timeout=1)) diff --git a/qpid/python/tests_0-10_preview/management.py b/qpid/python/tests_0-10_preview/management.py new file mode 100644 index 0000000000..de6161ae96 --- /dev/null +++ b/qpid/python/tests_0-10_preview/management.py @@ -0,0 +1,89 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from qpid.datatypes import Message, RangedSet +from qpid.testlib import TestBase +from qpid.management import managementChannel, managementClient + +class ManagementTest (TestBase): + """ + Tests for the management hooks + """ + + def test_broker_connectivity (self): + """ + Call the "echo" method on the broker to verify it is alive and talking. + """ + channel = self.client.channel(2) + + mc = managementClient (channel.spec) + mch = mc.addChannel (channel) + + mc.syncWaitForStable (mch) + brokers = mc.syncGetObjects (mch, "broker") + self.assertEqual (len (brokers), 1) + broker = brokers[0] + args = {} + body = "Echo Message Body" + args["body"] = body + + for seq in range (1, 5): + args["sequence"] = seq + res = mc.syncCallMethod (mch, broker.id, broker.classKey, "echo", args) + self.assertEqual (res.status, 0) + self.assertEqual (res.statusText, "OK") + self.assertEqual (res.sequence, seq) + self.assertEqual (res.body, body) + + def test_system_object (self): + channel = self.client.channel(2) + + mc = managementClient (channel.spec) + mch = mc.addChannel (channel) + + mc.syncWaitForStable (mch) + systems = mc.syncGetObjects (mch, "system") + self.assertEqual (len (systems), 1) + + def test_standard_exchanges (self): + channel = self.client.channel(2) + + mc = managementClient (channel.spec) + mch = mc.addChannel (channel) + + mc.syncWaitForStable (mch) + exchanges = mc.syncGetObjects (mch, "exchange") + exchange = self.findExchange (exchanges, "") + self.assertEqual (exchange.type, "direct") + exchange = self.findExchange (exchanges, "amq.direct") + self.assertEqual (exchange.type, "direct") + exchange = self.findExchange (exchanges, "amq.topic") + self.assertEqual (exchange.type, "topic") + exchange = self.findExchange (exchanges, "amq.fanout") + self.assertEqual (exchange.type, "fanout") + exchange = self.findExchange (exchanges, "amq.match") + self.assertEqual (exchange.type, "headers") + exchange = self.findExchange (exchanges, "qpid.management") + self.assertEqual (exchange.type, "topic") + + def findExchange (self, exchanges, name): + for exchange in exchanges: + if exchange.name == name: + return exchange + return None diff --git a/qpid/python/tests_0-10_preview/message.py b/qpid/python/tests_0-10_preview/message.py new file mode 100644 index 0000000000..a3d32bdb2d --- /dev/null +++ b/qpid/python/tests_0-10_preview/message.py @@ -0,0 +1,834 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +from qpid.client import Client, Closed +from qpid.queue import Empty +from qpid.content import Content +from qpid.testlib import testrunner, TestBase +from qpid.reference import Reference, ReferenceId + +class MessageTests(TestBase): + """Tests for 'methods' on the amqp message 'class'""" + + def test_consume_no_local(self): + """ + Test that the no_local flag is honoured in the consume method + """ + channel = self.channel + #setup, declare two queues: + channel.queue_declare(queue="test-queue-1a", exclusive=True, auto_delete=True) + channel.queue_declare(queue="test-queue-1b", exclusive=True, auto_delete=True) + #establish two consumers one of which excludes delivery of locally sent messages + self.subscribe(destination="local_included", queue="test-queue-1a") + self.subscribe(destination="local_excluded", queue="test-queue-1b", no_local=True) + + #send a message + channel.message_transfer(content=Content(properties={'routing_key' : "test-queue-1a"}, body="consume_no_local")) + channel.message_transfer(content=Content(properties={'routing_key' : "test-queue-1b"}, body="consume_no_local")) + + #check the queues of the two consumers + excluded = self.client.queue("local_excluded") + included = self.client.queue("local_included") + msg = included.get(timeout=1) + self.assertEqual("consume_no_local", msg.content.body) + try: + excluded.get(timeout=1) + self.fail("Received locally published message though no_local=true") + except Empty: None + + def test_consume_no_local_awkward(self): + + """ + If an exclusive queue gets a no-local delivered to it, that + message could 'block' delivery of subsequent messages or it + could be left on the queue, possibly never being consumed + (this is the case for example in the qpid JMS mapping of + topics). This test excercises a Qpid C++ broker hack that + deletes such messages. + """ + + channel = self.channel + #setup: + channel.queue_declare(queue="test-queue", exclusive=True, auto_delete=True) + #establish consumer which excludes delivery of locally sent messages + self.subscribe(destination="local_excluded", queue="test-queue", no_local=True) + + #send a 'local' message + channel.message_transfer(content=Content(properties={'routing_key' : "test-queue"}, body="local")) + + #send a non local message + other = self.connect() + channel2 = other.channel(1) + channel2.session_open() + channel2.message_transfer(content=Content(properties={'routing_key' : "test-queue"}, body="foreign")) + channel2.session_close() + other.close() + + #check that the second message only is delivered + excluded = self.client.queue("local_excluded") + msg = excluded.get(timeout=1) + self.assertEqual("foreign", msg.content.body) + try: + excluded.get(timeout=1) + self.fail("Received extra message") + except Empty: None + #check queue is empty + self.assertEqual(0, channel.queue_query(queue="test-queue").message_count) + + + def test_consume_exclusive(self): + """ + Test that the exclusive flag is honoured in the consume method + """ + channel = self.channel + #setup, declare a queue: + channel.queue_declare(queue="test-queue-2", exclusive=True, auto_delete=True) + + #check that an exclusive consumer prevents other consumer being created: + self.subscribe(destination="first", queue="test-queue-2", exclusive=True) + try: + self.subscribe(destination="second", queue="test-queue-2") + self.fail("Expected consume request to fail due to previous exclusive consumer") + except Closed, e: + self.assertChannelException(403, e.args[0]) + + #open new channel and cleanup last consumer: + channel = self.client.channel(2) + channel.session_open() + + #check that an exclusive consumer cannot be created if a consumer already exists: + self.subscribe(channel, destination="first", queue="test-queue-2") + try: + self.subscribe(destination="second", queue="test-queue-2", exclusive=True) + self.fail("Expected exclusive consume request to fail due to previous consumer") + except Closed, e: + self.assertChannelException(403, e.args[0]) + + def test_consume_queue_errors(self): + """ + Test error conditions associated with the queue field of the consume method: + """ + channel = self.channel + try: + #queue specified but doesn't exist: + self.subscribe(queue="invalid-queue", destination="") + self.fail("Expected failure when consuming from non-existent queue") + except Closed, e: + self.assertChannelException(404, e.args[0]) + + channel = self.client.channel(2) + channel.session_open() + try: + #queue not specified and none previously declared for channel: + self.subscribe(channel, queue="", destination="") + self.fail("Expected failure when consuming from unspecified queue") + except Closed, e: + self.assertConnectionException(530, e.args[0]) + + def test_consume_unique_consumers(self): + """ + Ensure unique consumer tags are enforced + """ + channel = self.channel + #setup, declare a queue: + channel.queue_declare(queue="test-queue-3", exclusive=True, auto_delete=True) + + #check that attempts to use duplicate tags are detected and prevented: + self.subscribe(destination="first", queue="test-queue-3") + try: + self.subscribe(destination="first", queue="test-queue-3") + self.fail("Expected consume request to fail due to non-unique tag") + except Closed, e: + self.assertConnectionException(530, e.args[0]) + + def test_cancel(self): + """ + Test compliance of the basic.cancel method + """ + channel = self.channel + #setup, declare a queue: + channel.queue_declare(queue="test-queue-4", exclusive=True, auto_delete=True) + self.subscribe(destination="my-consumer", queue="test-queue-4") + channel.message_transfer(content=Content(properties={'routing_key' : "test-queue-4"}, body="One")) + + #cancel should stop messages being delivered + channel.message_cancel(destination="my-consumer") + channel.message_transfer(content=Content(properties={'routing_key' : "test-queue-4"}, body="Two")) + myqueue = self.client.queue("my-consumer") + msg = myqueue.get(timeout=1) + self.assertEqual("One", msg.content.body) + try: + msg = myqueue.get(timeout=1) + self.fail("Got message after cancellation: " + msg) + except Empty: None + + #cancellation of non-existant consumers should be handled without error + channel.message_cancel(destination="my-consumer") + channel.message_cancel(destination="this-never-existed") + + + def test_ack(self): + """ + Test basic ack/recover behaviour + """ + channel = self.channel + channel.queue_declare(queue="test-ack-queue", exclusive=True, auto_delete=True) + + self.subscribe(queue="test-ack-queue", destination="consumer_tag", confirm_mode=1) + queue = self.client.queue("consumer_tag") + + channel.message_transfer(content=Content(properties={'routing_key' : "test-ack-queue"}, body="One")) + channel.message_transfer(content=Content(properties={'routing_key' : "test-ack-queue"}, body="Two")) + channel.message_transfer(content=Content(properties={'routing_key' : "test-ack-queue"}, body="Three")) + channel.message_transfer(content=Content(properties={'routing_key' : "test-ack-queue"}, body="Four")) + channel.message_transfer(content=Content(properties={'routing_key' : "test-ack-queue"}, body="Five")) + + msg1 = queue.get(timeout=1) + msg2 = queue.get(timeout=1) + msg3 = queue.get(timeout=1) + msg4 = queue.get(timeout=1) + msg5 = queue.get(timeout=1) + + self.assertEqual("One", msg1.content.body) + self.assertEqual("Two", msg2.content.body) + self.assertEqual("Three", msg3.content.body) + self.assertEqual("Four", msg4.content.body) + self.assertEqual("Five", msg5.content.body) + + msg2.complete(cumulative=True)#One and Two + msg4.complete(cumulative=False) + + channel.message_recover(requeue=False) + + msg3b = queue.get(timeout=1) + msg5b = queue.get(timeout=1) + + self.assertEqual("Three", msg3b.content.body) + self.assertEqual("Five", msg5b.content.body) + + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected message: " + extra.content.body) + except Empty: None + + + def test_recover(self): + """ + Test recover behaviour + """ + channel = self.channel + channel.queue_declare(queue="queue-a", exclusive=True, auto_delete=True) + channel.queue_bind(exchange="amq.fanout", queue="queue-a") + channel.queue_declare(queue="queue-b", exclusive=True, auto_delete=True) + channel.queue_bind(exchange="amq.fanout", queue="queue-b") + + self.subscribe(queue="queue-a", destination="unconfirmed", confirm_mode=1) + self.subscribe(queue="queue-b", destination="confirmed", confirm_mode=0) + confirmed = self.client.queue("confirmed") + unconfirmed = self.client.queue("unconfirmed") + + data = ["One", "Two", "Three", "Four", "Five"] + for d in data: + channel.message_transfer(destination="amq.fanout", content=Content(body=d)) + + for q in [confirmed, unconfirmed]: + for d in data: + self.assertEqual(d, q.get(timeout=1).content.body) + self.assertEmpty(q) + + channel.message_recover(requeue=False) + + self.assertEmpty(confirmed) + + while len(data): + msg = None + for d in data: + msg = unconfirmed.get(timeout=1) + self.assertEqual(d, msg.content.body) + self.assertEqual(True, msg.content['redelivered']) + self.assertEmpty(unconfirmed) + data.remove(msg.content.body) + msg.complete(cumulative=False) + channel.message_recover(requeue=False) + + + def test_recover_requeue(self): + """ + Test requeing on recovery + """ + channel = self.channel + channel.queue_declare(queue="test-requeue", exclusive=True, auto_delete=True) + + self.subscribe(queue="test-requeue", destination="consumer_tag", confirm_mode=1) + queue = self.client.queue("consumer_tag") + + channel.message_transfer(content=Content(properties={'routing_key' : "test-requeue"}, body="One")) + channel.message_transfer(content=Content(properties={'routing_key' : "test-requeue"}, body="Two")) + channel.message_transfer(content=Content(properties={'routing_key' : "test-requeue"}, body="Three")) + channel.message_transfer(content=Content(properties={'routing_key' : "test-requeue"}, body="Four")) + channel.message_transfer(content=Content(properties={'routing_key' : "test-requeue"}, body="Five")) + + msg1 = queue.get(timeout=1) + msg2 = queue.get(timeout=1) + msg3 = queue.get(timeout=1) + msg4 = queue.get(timeout=1) + msg5 = queue.get(timeout=1) + + self.assertEqual("One", msg1.content.body) + self.assertEqual("Two", msg2.content.body) + self.assertEqual("Three", msg3.content.body) + self.assertEqual("Four", msg4.content.body) + self.assertEqual("Five", msg5.content.body) + + msg2.complete(cumulative=True) #One and Two + msg4.complete(cumulative=False) #Four + + channel.message_cancel(destination="consumer_tag") + + #publish a new message + channel.message_transfer(content=Content(properties={'routing_key' : "test-requeue"}, body="Six")) + #requeue unacked messages (Three and Five) + channel.message_recover(requeue=True) + + self.subscribe(queue="test-requeue", destination="consumer_tag") + queue2 = self.client.queue("consumer_tag") + + msg3b = queue2.get(timeout=1) + msg5b = queue2.get(timeout=1) + + self.assertEqual("Three", msg3b.content.body) + self.assertEqual("Five", msg5b.content.body) + + self.assertEqual(True, msg3b.content['redelivered']) + self.assertEqual(True, msg5b.content['redelivered']) + + self.assertEqual("Six", queue2.get(timeout=1).content.body) + + try: + extra = queue2.get(timeout=1) + self.fail("Got unexpected message in second queue: " + extra.content.body) + except Empty: None + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected message in original queue: " + extra.content.body) + except Empty: None + + + def test_qos_prefetch_count(self): + """ + Test that the prefetch count specified is honoured + """ + #setup: declare queue and subscribe + channel = self.channel + channel.queue_declare(queue="test-prefetch-count", exclusive=True, auto_delete=True) + subscription = self.subscribe(queue="test-prefetch-count", destination="consumer_tag", confirm_mode=1) + queue = self.client.queue("consumer_tag") + + #set prefetch to 5: + channel.message_qos(prefetch_count=5) + + #publish 10 messages: + for i in range(1, 11): + channel.message_transfer(content=Content(properties={'routing_key' : "test-prefetch-count"}, body="Message %d" % i)) + + #only 5 messages should have been delivered: + for i in range(1, 6): + msg = queue.get(timeout=1) + self.assertEqual("Message %d" % i, msg.content.body) + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected 6th message in original queue: " + extra.content.body) + except Empty: None + + #ack messages and check that the next set arrive ok: + msg.complete() + + for i in range(6, 11): + msg = queue.get(timeout=1) + self.assertEqual("Message %d" % i, msg.content.body) + + msg.complete() + + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected 11th message in original queue: " + extra.content.body) + except Empty: None + + + + def test_qos_prefetch_size(self): + """ + Test that the prefetch size specified is honoured + """ + #setup: declare queue and subscribe + channel = self.channel + channel.queue_declare(queue="test-prefetch-size", exclusive=True, auto_delete=True) + subscription = self.subscribe(queue="test-prefetch-size", destination="consumer_tag", confirm_mode=1) + queue = self.client.queue("consumer_tag") + + #set prefetch to 50 bytes (each message is 9 or 10 bytes): + channel.message_qos(prefetch_size=50) + + #publish 10 messages: + for i in range(1, 11): + channel.message_transfer(content=Content(properties={'routing_key' : "test-prefetch-size"}, body="Message %d" % i)) + + #only 5 messages should have been delivered (i.e. 45 bytes worth): + for i in range(1, 6): + msg = queue.get(timeout=1) + self.assertEqual("Message %d" % i, msg.content.body) + + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected 6th message in original queue: " + extra.content.body) + except Empty: None + + #ack messages and check that the next set arrive ok: + msg.complete() + + for i in range(6, 11): + msg = queue.get(timeout=1) + self.assertEqual("Message %d" % i, msg.content.body) + + msg.complete() + + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected 11th message in original queue: " + extra.content.body) + except Empty: None + + #make sure that a single oversized message still gets delivered + large = "abcdefghijklmnopqrstuvwxyz" + large = large + "-" + large; + channel.message_transfer(content=Content(properties={'routing_key' : "test-prefetch-size"}, body=large)) + msg = queue.get(timeout=1) + self.assertEqual(large, msg.content.body) + + def test_reject(self): + channel = self.channel + channel.queue_declare(queue = "q", exclusive=True, auto_delete=True, alternate_exchange="amq.fanout") + channel.queue_declare(queue = "r", exclusive=True, auto_delete=True) + channel.queue_bind(queue = "r", exchange = "amq.fanout") + + self.subscribe(queue = "q", destination = "consumer", confirm_mode = 1) + channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body="blah, blah")) + msg = self.client.queue("consumer").get(timeout = 1) + self.assertEquals(msg.content.body, "blah, blah") + channel.message_reject([msg.command_id, msg.command_id]) + + self.subscribe(queue = "r", destination = "checker") + msg = self.client.queue("checker").get(timeout = 1) + self.assertEquals(msg.content.body, "blah, blah") + + def test_credit_flow_messages(self): + """ + Test basic credit based flow control with unit = message + """ + #declare an exclusive queue + channel = self.channel + channel.queue_declare(queue = "q", exclusive=True, auto_delete=True) + #create consumer (for now that defaults to infinite credit) + channel.message_subscribe(queue = "q", destination = "c") + channel.message_flow_mode(mode = 0, destination = "c") + #send batch of messages to queue + for i in range(1, 11): + channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body = "Message %d" % i)) + + #set message credit to finite amount (less than enough for all messages) + channel.message_flow(unit = 0, value = 5, destination = "c") + #set infinite byte credit + channel.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "c") + #check that expected number were received + q = self.client.queue("c") + for i in range(1, 6): + self.assertDataEquals(channel, q.get(timeout = 1), "Message %d" % i) + self.assertEmpty(q) + + #increase credit again and check more are received + for i in range(6, 11): + channel.message_flow(unit = 0, value = 1, destination = "c") + self.assertDataEquals(channel, q.get(timeout = 1), "Message %d" % i) + self.assertEmpty(q) + + def test_credit_flow_bytes(self): + """ + Test basic credit based flow control with unit = bytes + """ + #declare an exclusive queue + channel = self.channel + channel.queue_declare(queue = "q", exclusive=True, auto_delete=True) + #create consumer (for now that defaults to infinite credit) + channel.message_subscribe(queue = "q", destination = "c") + channel.message_flow_mode(mode = 0, destination = "c") + #send batch of messages to queue + for i in range(10): + channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body = "abcdefgh")) + + #each message is currently interpreted as requiring msg_size bytes of credit + msg_size = 35 + + #set byte credit to finite amount (less than enough for all messages) + channel.message_flow(unit = 1, value = msg_size*5, destination = "c") + #set infinite message credit + channel.message_flow(unit = 0, value = 0xFFFFFFFF, destination = "c") + #check that expected number were received + q = self.client.queue("c") + for i in range(5): + self.assertDataEquals(channel, q.get(timeout = 1), "abcdefgh") + self.assertEmpty(q) + + #increase credit again and check more are received + for i in range(5): + channel.message_flow(unit = 1, value = msg_size, destination = "c") + self.assertDataEquals(channel, q.get(timeout = 1), "abcdefgh") + self.assertEmpty(q) + + + def test_window_flow_messages(self): + """ + Test basic window based flow control with unit = message + """ + #declare an exclusive queue + channel = self.channel + channel.queue_declare(queue = "q", exclusive=True, auto_delete=True) + #create consumer (for now that defaults to infinite credit) + channel.message_subscribe(queue = "q", destination = "c", confirm_mode = 1) + channel.message_flow_mode(mode = 1, destination = "c") + #send batch of messages to queue + for i in range(1, 11): + channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body = "Message %d" % i)) + + #set message credit to finite amount (less than enough for all messages) + channel.message_flow(unit = 0, value = 5, destination = "c") + #set infinite byte credit + channel.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "c") + #check that expected number were received + q = self.client.queue("c") + for i in range(1, 6): + msg = q.get(timeout = 1) + self.assertDataEquals(channel, msg, "Message %d" % i) + self.assertEmpty(q) + + #acknowledge messages and check more are received + msg.complete(cumulative=True) + for i in range(6, 11): + self.assertDataEquals(channel, q.get(timeout = 1), "Message %d" % i) + self.assertEmpty(q) + + + def test_window_flow_bytes(self): + """ + Test basic window based flow control with unit = bytes + """ + #declare an exclusive queue + channel = self.channel + channel.queue_declare(queue = "q", exclusive=True, auto_delete=True) + #create consumer (for now that defaults to infinite credit) + channel.message_subscribe(queue = "q", destination = "c", confirm_mode = 1) + channel.message_flow_mode(mode = 1, destination = "c") + #send batch of messages to queue + for i in range(10): + channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body = "abcdefgh")) + + #each message is currently interpreted as requiring msg_size bytes of credit + msg_size = 40 + + #set byte credit to finite amount (less than enough for all messages) + channel.message_flow(unit = 1, value = msg_size*5, destination = "c") + #set infinite message credit + channel.message_flow(unit = 0, value = 0xFFFFFFFF, destination = "c") + #check that expected number were received + q = self.client.queue("c") + msgs = [] + for i in range(5): + msg = q.get(timeout = 1) + msgs.append(msg) + self.assertDataEquals(channel, msg, "abcdefgh") + self.assertEmpty(q) + + #ack each message individually and check more are received + for i in range(5): + msg = msgs.pop() + msg.complete(cumulative=False) + self.assertDataEquals(channel, q.get(timeout = 1), "abcdefgh") + self.assertEmpty(q) + + def test_subscribe_not_acquired(self): + """ + Test the not-acquired modes works as expected for a simple case + """ + channel = self.channel + channel.queue_declare(queue = "q", exclusive=True, auto_delete=True) + for i in range(1, 6): + channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body = "Message %s" % i)) + + self.subscribe(queue = "q", destination = "a", acquire_mode = 1) + self.subscribe(queue = "q", destination = "b", acquire_mode = 1) + + for i in range(6, 11): + channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body = "Message %s" % i)) + + #both subscribers should see all messages + qA = self.client.queue("a") + qB = self.client.queue("b") + for i in range(1, 11): + for q in [qA, qB]: + msg = q.get(timeout = 1) + self.assertEquals("Message %s" % i, msg.content.body) + msg.complete() + + #messages should still be on the queue: + self.assertEquals(10, channel.queue_query(queue = "q").message_count) + + def test_acquire(self): + """ + Test explicit acquire function + """ + channel = self.channel + channel.queue_declare(queue = "q", exclusive=True, auto_delete=True) + channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body = "acquire me")) + + self.subscribe(queue = "q", destination = "a", acquire_mode = 1, confirm_mode = 1) + msg = self.client.queue("a").get(timeout = 1) + #message should still be on the queue: + self.assertEquals(1, channel.queue_query(queue = "q").message_count) + + channel.message_acquire([msg.command_id, msg.command_id]) + #check that we get notification (i.e. message_acquired) + response = channel.control_queue.get(timeout=1) + self.assertEquals(response.transfers, [msg.command_id, msg.command_id]) + #message should have been removed from the queue: + self.assertEquals(0, channel.queue_query(queue = "q").message_count) + msg.complete() + + + + + def test_release(self): + """ + Test explicit release function + """ + channel = self.channel + channel.queue_declare(queue = "q", exclusive=True, auto_delete=True) + channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body = "release me")) + + self.subscribe(queue = "q", destination = "a", acquire_mode = 0, confirm_mode = 1) + msg = self.client.queue("a").get(timeout = 1) + channel.message_cancel(destination = "a") + channel.message_release([msg.command_id, msg.command_id]) + msg.complete() + + #message should not have been removed from the queue: + self.assertEquals(1, channel.queue_query(queue = "q").message_count) + + def test_release_ordering(self): + """ + Test order of released messages is as expected + """ + channel = self.channel + channel.queue_declare(queue = "q", exclusive=True, auto_delete=True) + for i in range (1, 11): + channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body = "released message %s" % (i))) + + channel.message_subscribe(queue = "q", destination = "a", confirm_mode = 1) + channel.message_flow(unit = 0, value = 10, destination = "a") + channel.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "a") + queue = self.client.queue("a") + first = queue.get(timeout = 1) + for i in range (2, 10): + self.assertEquals("released message %s" % (i), queue.get(timeout = 1).content.body) + last = queue.get(timeout = 1) + self.assertEmpty(queue) + channel.message_release([first.command_id, last.command_id]) + last.complete()#will re-allocate credit, as in window mode + for i in range (1, 11): + self.assertEquals("released message %s" % (i), queue.get(timeout = 1).content.body) + + def test_ranged_ack(self): + """ + Test acking of messages ranges + """ + channel = self.channel + channel.queue_declare(queue = "q", exclusive=True, auto_delete=True) + for i in range (1, 11): + channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body = "message %s" % (i))) + + channel.message_subscribe(queue = "q", destination = "a", confirm_mode = 1) + channel.message_flow(unit = 0, value = 10, destination = "a") + channel.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "a") + queue = self.client.queue("a") + for i in range (1, 11): + self.assertEquals("message %s" % (i), queue.get(timeout = 1).content.body) + self.assertEmpty(queue) + + #ack all but the third message (command id 2) + channel.execution_complete(cumulative_execution_mark=0xFFFFFFFF, ranged_execution_set=[0,1,3,6,7,7,8,9]) + channel.message_recover() + self.assertEquals("message 3", queue.get(timeout = 1).content.body) + self.assertEmpty(queue) + + def test_subscribe_not_acquired_2(self): + channel = self.channel + + #publish some messages + self.queue_declare(queue = "q", exclusive=True, auto_delete=True) + for i in range(1, 11): + channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body = "message-%d" % (i))) + + #consume some of them + channel.message_subscribe(queue = "q", destination = "a", confirm_mode = 1) + channel.message_flow_mode(mode = 0, destination = "a") + channel.message_flow(unit = 0, value = 5, destination = "a") + channel.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "a") + + queue = self.client.queue("a") + for i in range(1, 6): + msg = queue.get(timeout = 1) + self.assertEquals("message-%d" % (i), msg.content.body) + msg.complete() + self.assertEmpty(queue) + + #now create a not-acquired subscriber + channel.message_subscribe(queue = "q", destination = "b", confirm_mode = 1, acquire_mode=1) + channel.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "b") + + #check it gets those not consumed + queue = self.client.queue("b") + channel.message_flow(unit = 0, value = 1, destination = "b") + for i in range(6, 11): + msg = queue.get(timeout = 1) + self.assertEquals("message-%d" % (i), msg.content.body) + msg.complete() + channel.message_flow(unit = 0, value = 1, destination = "b") + self.assertEmpty(queue) + + #check all 'browsed' messages are still on the queue + self.assertEqual(5, channel.queue_query(queue="q").message_count) + + def test_subscribe_not_acquired_3(self): + channel = self.channel + + #publish some messages + self.queue_declare(queue = "q", exclusive=True, auto_delete=True) + for i in range(1, 11): + channel.message_transfer(content=Content(properties={'routing_key' : "q"}, body = "message-%d" % (i))) + + #create a not-acquired subscriber + channel.message_subscribe(queue = "q", destination = "a", confirm_mode = 1, acquire_mode=1) + channel.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "a") + channel.message_flow(unit = 0, value = 10, destination = "a") + + #browse through messages + queue = self.client.queue("a") + for i in range(1, 11): + msg = queue.get(timeout = 1) + self.assertEquals("message-%d" % (i), msg.content.body) + if (i % 2): + #try to acquire every second message + channel.message_acquire([msg.command_id, msg.command_id]) + #check that acquire succeeds + response = channel.control_queue.get(timeout=1) + self.assertEquals(response.transfers, [msg.command_id, msg.command_id]) + msg.complete() + self.assertEmpty(queue) + + #create a second not-acquired subscriber + channel.message_subscribe(queue = "q", destination = "b", confirm_mode = 1, acquire_mode=1) + channel.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "b") + channel.message_flow(unit = 0, value = 1, destination = "b") + #check it gets those not consumed + queue = self.client.queue("b") + for i in [2,4,6,8,10]: + msg = queue.get(timeout = 1) + self.assertEquals("message-%d" % (i), msg.content.body) + msg.complete() + channel.message_flow(unit = 0, value = 1, destination = "b") + self.assertEmpty(queue) + + #check all 'browsed' messages are still on the queue + self.assertEqual(5, channel.queue_query(queue="q").message_count) + + def test_release_unacquired(self): + channel = self.channel + + #create queue + self.queue_declare(queue = "q", exclusive=True, auto_delete=True, durable=True) + + #send message + channel.message_transfer(content=Content(properties={'routing_key' : "q", 'delivery_mode':2}, body = "my-message")) + + #create two 'browsers' + channel.message_subscribe(queue = "q", destination = "a", confirm_mode = 1, acquire_mode=1) + channel.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "a") + channel.message_flow(unit = 0, value = 10, destination = "a") + queueA = self.client.queue("a") + + channel.message_subscribe(queue = "q", destination = "b", confirm_mode = 1, acquire_mode=1) + channel.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "b") + channel.message_flow(unit = 0, value = 10, destination = "b") + queueB = self.client.queue("b") + + #have each browser release the message + msgA = queueA.get(timeout = 1) + channel.message_release([msgA.command_id, msgA.command_id]) + + msgB = queueB.get(timeout = 1) + channel.message_release([msgB.command_id, msgB.command_id]) + + #cancel browsers + channel.message_cancel(destination = "a") + channel.message_cancel(destination = "b") + + #create consumer + channel.message_subscribe(queue = "q", destination = "c", confirm_mode = 1, acquire_mode=0) + channel.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "c") + channel.message_flow(unit = 0, value = 10, destination = "c") + queueC = self.client.queue("c") + #consume the message then ack it + msgC = queueC.get(timeout = 1) + msgC.complete() + #ensure there are no other messages + self.assertEmpty(queueC) + + def test_no_size(self): + self.queue_declare(queue = "q", exclusive=True, auto_delete=True) + + ch = self.channel + ch.message_transfer(content=SizelessContent(properties={'routing_key' : "q"}, body="message-body")) + + ch.message_subscribe(queue = "q", destination="d", confirm_mode = 0) + ch.message_flow(unit = 0, value = 0xFFFFFFFF, destination = "d") + ch.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "d") + + queue = self.client.queue("d") + msg = queue.get(timeout = 3) + self.assertEquals("message-body", msg.content.body) + + def assertDataEquals(self, channel, msg, expected): + self.assertEquals(expected, msg.content.body) + + def assertEmpty(self, queue): + try: + extra = queue.get(timeout=1) + self.fail("Queue not empty, contains: " + extra.content.body) + except Empty: None + +class SizelessContent(Content): + + def size(self): + return None diff --git a/qpid/python/tests_0-10_preview/persistence.py b/qpid/python/tests_0-10_preview/persistence.py new file mode 100644 index 0000000000..ad578474eb --- /dev/null +++ b/qpid/python/tests_0-10_preview/persistence.py @@ -0,0 +1,67 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +from qpid.client import Client, Closed +from qpid.queue import Empty +from qpid.content import Content +from qpid.testlib import testrunner, TestBase + +class PersistenceTests(TestBase): + def test_delete_queue_after_publish(self): + channel = self.channel + channel.synchronous = False + + #create queue + channel.queue_declare(queue = "q", auto_delete=True, durable=True) + + #send message + for i in range(1, 10): + channel.message_transfer(content=Content(properties={'routing_key' : "q", 'delivery_mode':2}, body = "my-message")) + + channel.synchronous = True + #explicitly delete queue + channel.queue_delete(queue = "q") + + def test_ack_message_from_deleted_queue(self): + channel = self.channel + channel.synchronous = False + + #create queue + channel.queue_declare(queue = "q", auto_delete=True, durable=True) + + #send message + channel.message_transfer(content=Content(properties={'routing_key' : "q", 'delivery_mode':2}, body = "my-message")) + + #create consumer + channel.message_subscribe(queue = "q", destination = "a", confirm_mode = 1, acquire_mode=0) + channel.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "a") + channel.message_flow(unit = 0, value = 10, destination = "a") + queue = self.client.queue("a") + + #consume the message, cancel subscription (triggering auto-delete), then ack it + msg = queue.get(timeout = 5) + channel.message_cancel(destination = "a") + msg.complete() + + def test_queue_deletion(self): + channel = self.channel + channel.queue_declare(queue = "durable-subscriber-queue", exclusive=True, durable=True) + channel.queue_bind(exchange="amq.topic", queue="durable-subscriber-queue", routing_key="xyz") + channel.message_transfer(destination= "amq.topic", content=Content(properties={'routing_key' : "xyz", 'delivery_mode':2}, body = "my-message")) + channel.queue_delete(queue = "durable-subscriber-queue") + diff --git a/qpid/python/tests_0-10_preview/query.py b/qpid/python/tests_0-10_preview/query.py new file mode 100644 index 0000000000..eba2ee6dd1 --- /dev/null +++ b/qpid/python/tests_0-10_preview/query.py @@ -0,0 +1,227 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +from qpid.client import Client, Closed +from qpid.queue import Empty +from qpid.content import Content +from qpid.testlib import testrunner, TestBase + +class QueryTests(TestBase): + """Tests for various query methods introduced in 0-10 and available in 0-9 for preview""" + + def test_exchange_query(self): + """ + Test that the exchange_query method works as expected + """ + channel = self.channel + #check returned type for the standard exchanges + self.assert_type("direct", channel.exchange_query(name="amq.direct")) + self.assert_type("topic", channel.exchange_query(name="amq.topic")) + self.assert_type("fanout", channel.exchange_query(name="amq.fanout")) + self.assert_type("headers", channel.exchange_query(name="amq.match")) + self.assert_type("direct", channel.exchange_query(name="")) + #declare an exchange + channel.exchange_declare(exchange="my-test-exchange", type= "direct", durable=False) + #check that the result of a query is as expected + response = channel.exchange_query(name="my-test-exchange") + self.assert_type("direct", response) + self.assertEqual(False, response.durable) + self.assertEqual(False, response.not_found) + #delete the exchange + channel.exchange_delete(exchange="my-test-exchange") + #check that the query now reports not-found + self.assertEqual(True, channel.exchange_query(name="my-test-exchange").not_found) + + def assert_type(self, expected_type, response): + self.assertEqual(expected_type, response.__getattr__("type")) + + def test_binding_query_direct(self): + """ + Test that the binding_query method works as expected with the direct exchange + """ + self.binding_query_with_key("amq.direct") + + def test_binding_query_topic(self): + """ + Test that the binding_query method works as expected with the direct exchange + """ + self.binding_query_with_key("amq.topic") + + def binding_query_with_key(self, exchange_name): + channel = self.channel + #setup: create two queues + channel.queue_declare(queue="used-queue", exclusive=True, auto_delete=True) + channel.queue_declare(queue="unused-queue", exclusive=True, auto_delete=True) + + channel.queue_bind(exchange=exchange_name, queue="used-queue", routing_key="used-key") + + # test detection of any binding to specific queue + response = channel.binding_query(exchange=exchange_name, queue="used-queue") + self.assertEqual(False, response.exchange_not_found) + self.assertEqual(False, response.queue_not_found) + self.assertEqual(False, response.queue_not_matched) + + # test detection of specific binding to any queue + response = channel.binding_query(exchange=exchange_name, routing_key="used-key") + self.assertEqual(False, response.exchange_not_found) + self.assertEqual(False, response.queue_not_found) + self.assertEqual(False, response.key_not_matched) + + # test detection of specific binding to specific queue + response = channel.binding_query(exchange=exchange_name, queue="used-queue", routing_key="used-key") + self.assertEqual(False, response.exchange_not_found) + self.assertEqual(False, response.queue_not_found) + self.assertEqual(False, response.queue_not_matched) + self.assertEqual(False, response.key_not_matched) + + # test unmatched queue, unspecified binding + response = channel.binding_query(exchange=exchange_name, queue="unused-queue") + self.assertEqual(False, response.exchange_not_found) + self.assertEqual(False, response.queue_not_found) + self.assertEqual(True, response.queue_not_matched) + + # test unspecified queue, unmatched binding + response = channel.binding_query(exchange=exchange_name, routing_key="unused-key") + self.assertEqual(False, response.exchange_not_found) + self.assertEqual(False, response.queue_not_found) + self.assertEqual(True, response.key_not_matched) + + # test matched queue, unmatched binding + response = channel.binding_query(exchange=exchange_name, queue="used-queue", routing_key="unused-key") + self.assertEqual(False, response.exchange_not_found) + self.assertEqual(False, response.queue_not_found) + self.assertEqual(False, response.queue_not_matched) + self.assertEqual(True, response.key_not_matched) + + # test unmatched queue, matched binding + response = channel.binding_query(exchange=exchange_name, queue="unused-queue", routing_key="used-key") + self.assertEqual(False, response.exchange_not_found) + self.assertEqual(False, response.queue_not_found) + self.assertEqual(True, response.queue_not_matched) + self.assertEqual(False, response.key_not_matched) + + # test unmatched queue, unmatched binding + response = channel.binding_query(exchange=exchange_name, queue="unused-queue", routing_key="unused-key") + self.assertEqual(False, response.exchange_not_found) + self.assertEqual(False, response.queue_not_found) + self.assertEqual(True, response.queue_not_matched) + self.assertEqual(True, response.key_not_matched) + + #test exchange not found + self.assertEqual(True, channel.binding_query(exchange="unknown-exchange").exchange_not_found) + + #test queue not found + self.assertEqual(True, channel.binding_query(exchange=exchange_name, queue="unknown-queue").queue_not_found) + + + def test_binding_query_fanout(self): + """ + Test that the binding_query method works as expected with fanout exchange + """ + channel = self.channel + #setup + channel.queue_declare(queue="used-queue", exclusive=True, auto_delete=True) + channel.queue_declare(queue="unused-queue", exclusive=True, auto_delete=True) + channel.queue_bind(exchange="amq.fanout", queue="used-queue") + + # test detection of any binding to specific queue + response = channel.binding_query(exchange="amq.fanout", queue="used-queue") + self.assertEqual(False, response.exchange_not_found) + self.assertEqual(False, response.queue_not_found) + self.assertEqual(False, response.queue_not_matched) + + # test unmatched queue, unspecified binding + response = channel.binding_query(exchange="amq.fanout", queue="unused-queue") + self.assertEqual(False, response.exchange_not_found) + self.assertEqual(False, response.queue_not_found) + self.assertEqual(True, response.queue_not_matched) + + #test exchange not found + self.assertEqual(True, channel.binding_query(exchange="unknown-exchange").exchange_not_found) + + #test queue not found + self.assertEqual(True, channel.binding_query(exchange="amq.fanout", queue="unknown-queue").queue_not_found) + + def test_binding_query_header(self): + """ + Test that the binding_query method works as expected with headers exchanges + """ + channel = self.channel + #setup + channel.queue_declare(queue="used-queue", exclusive=True, auto_delete=True) + channel.queue_declare(queue="unused-queue", exclusive=True, auto_delete=True) + channel.queue_bind(exchange="amq.match", queue="used-queue", arguments={"x-match":"all", "a":"A"} ) + + # test detection of any binding to specific queue + response = channel.binding_query(exchange="amq.match", queue="used-queue") + self.assertEqual(False, response.exchange_not_found) + self.assertEqual(False, response.queue_not_found) + self.assertEqual(False, response.queue_not_matched) + + # test detection of specific binding to any queue + response = channel.binding_query(exchange="amq.match", arguments={"x-match":"all", "a":"A"}) + self.assertEqual(False, response.exchange_not_found) + self.assertEqual(False, response.queue_not_found) + self.assertEqual(False, response.args_not_matched) + + # test detection of specific binding to specific queue + response = channel.binding_query(exchange="amq.match", queue="used-queue", arguments={"x-match":"all", "a":"A"}) + self.assertEqual(False, response.exchange_not_found) + self.assertEqual(False, response.queue_not_found) + self.assertEqual(False, response.queue_not_matched) + self.assertEqual(False, response.args_not_matched) + + # test unmatched queue, unspecified binding + response = channel.binding_query(exchange="amq.match", queue="unused-queue") + self.assertEqual(False, response.exchange_not_found) + self.assertEqual(False, response.queue_not_found) + self.assertEqual(True, response.queue_not_matched) + + # test unspecified queue, unmatched binding + response = channel.binding_query(exchange="amq.match", arguments={"x-match":"all", "b":"B"}) + self.assertEqual(False, response.exchange_not_found) + self.assertEqual(False, response.queue_not_found) + self.assertEqual(True, response.args_not_matched) + + # test matched queue, unmatched binding + response = channel.binding_query(exchange="amq.match", queue="used-queue", arguments={"x-match":"all", "b":"B"}) + self.assertEqual(False, response.exchange_not_found) + self.assertEqual(False, response.queue_not_found) + self.assertEqual(False, response.queue_not_matched) + self.assertEqual(True, response.args_not_matched) + + # test unmatched queue, matched binding + response = channel.binding_query(exchange="amq.match", queue="unused-queue", arguments={"x-match":"all", "a":"A"}) + self.assertEqual(False, response.exchange_not_found) + self.assertEqual(False, response.queue_not_found) + self.assertEqual(True, response.queue_not_matched) + self.assertEqual(False, response.args_not_matched) + + # test unmatched queue, unmatched binding + response = channel.binding_query(exchange="amq.match", queue="unused-queue", arguments={"x-match":"all", "b":"B"}) + self.assertEqual(False, response.exchange_not_found) + self.assertEqual(False, response.queue_not_found) + self.assertEqual(True, response.queue_not_matched) + self.assertEqual(True, response.args_not_matched) + + #test exchange not found + self.assertEqual(True, channel.binding_query(exchange="unknown-exchange").exchange_not_found) + + #test queue not found + self.assertEqual(True, channel.binding_query(exchange="amq.match", queue="unknown-queue").queue_not_found) + diff --git a/qpid/python/tests_0-10_preview/queue.py b/qpid/python/tests_0-10_preview/queue.py new file mode 100644 index 0000000000..7b3590d11b --- /dev/null +++ b/qpid/python/tests_0-10_preview/queue.py @@ -0,0 +1,338 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +from qpid.client import Client, Closed +from qpid.queue import Empty +from qpid.content import Content +from qpid.testlib import testrunner, TestBase + +class QueueTests(TestBase): + """Tests for 'methods' on the amqp queue 'class'""" + + def test_purge(self): + """ + Test that the purge method removes messages from the queue + """ + channel = self.channel + #setup, declare a queue and add some messages to it: + channel.exchange_declare(exchange="test-exchange", type="direct") + channel.queue_declare(queue="test-queue", exclusive=True, auto_delete=True) + channel.queue_bind(queue="test-queue", exchange="test-exchange", routing_key="key") + channel.message_transfer(destination="test-exchange", content=Content("one", properties={'routing_key':"key"})) + channel.message_transfer(destination="test-exchange", content=Content("two", properties={'routing_key':"key"})) + channel.message_transfer(destination="test-exchange", content=Content("three", properties={'routing_key':"key"})) + + #check that the queue now reports 3 messages: + channel.queue_declare(queue="test-queue") + reply = channel.queue_query(queue="test-queue") + self.assertEqual(3, reply.message_count) + + #now do the purge, then test that three messages are purged and the count drops to 0 + channel.queue_purge(queue="test-queue"); + reply = channel.queue_query(queue="test-queue") + self.assertEqual(0, reply.message_count) + + #send a further message and consume it, ensuring that the other messages are really gone + channel.message_transfer(destination="test-exchange", content=Content("four", properties={'routing_key':"key"})) + self.subscribe(queue="test-queue", destination="tag") + queue = self.client.queue("tag") + msg = queue.get(timeout=1) + self.assertEqual("four", msg.content.body) + + #check error conditions (use new channels): + channel = self.client.channel(2) + channel.session_open() + try: + #queue specified but doesn't exist: + channel.queue_purge(queue="invalid-queue") + self.fail("Expected failure when purging non-existent queue") + except Closed, e: + self.assertChannelException(404, e.args[0]) + + channel = self.client.channel(3) + channel.session_open() + try: + #queue not specified and none previously declared for channel: + channel.queue_purge() + self.fail("Expected failure when purging unspecified queue") + except Closed, e: + self.assertConnectionException(530, e.args[0]) + + #cleanup + other = self.connect() + channel = other.channel(1) + channel.session_open() + channel.exchange_delete(exchange="test-exchange") + + def test_declare_exclusive(self): + """ + Test that the exclusive field is honoured in queue.declare + """ + # TestBase.setUp has already opened channel(1) + c1 = self.channel + # Here we open a second separate connection: + other = self.connect() + c2 = other.channel(1) + c2.session_open() + + #declare an exclusive queue: + c1.queue_declare(queue="exclusive-queue", exclusive=True, auto_delete=True) + try: + #other connection should not be allowed to declare this: + c2.queue_declare(queue="exclusive-queue", exclusive=True, auto_delete=True) + self.fail("Expected second exclusive queue_declare to raise a channel exception") + except Closed, e: + self.assertChannelException(405, e.args[0]) + + + def test_declare_passive(self): + """ + Test that the passive field is honoured in queue.declare + """ + channel = self.channel + #declare an exclusive queue: + channel.queue_declare(queue="passive-queue-1", exclusive=True, auto_delete=True) + channel.queue_declare(queue="passive-queue-1", passive=True) + try: + #other connection should not be allowed to declare this: + channel.queue_declare(queue="passive-queue-2", passive=True) + self.fail("Expected passive declaration of non-existant queue to raise a channel exception") + except Closed, e: + self.assertChannelException(404, e.args[0]) + + + def test_bind(self): + """ + Test various permutations of the queue.bind method + """ + channel = self.channel + channel.queue_declare(queue="queue-1", exclusive=True, auto_delete=True) + + #straightforward case, both exchange & queue exist so no errors expected: + channel.queue_bind(queue="queue-1", exchange="amq.direct", routing_key="key1") + + #use the queue name where the routing key is not specified: + channel.queue_bind(queue="queue-1", exchange="amq.direct") + + #try and bind to non-existant exchange + try: + channel.queue_bind(queue="queue-1", exchange="an-invalid-exchange", routing_key="key1") + self.fail("Expected bind to non-existant exchange to fail") + except Closed, e: + self.assertChannelException(404, e.args[0]) + + #need to reopen a channel: + channel = self.client.channel(2) + channel.session_open() + + #try and bind non-existant queue: + try: + channel.queue_bind(queue="queue-2", exchange="amq.direct", routing_key="key1") + self.fail("Expected bind of non-existant queue to fail") + except Closed, e: + self.assertChannelException(404, e.args[0]) + + def test_unbind_direct(self): + self.unbind_test(exchange="amq.direct", routing_key="key") + + def test_unbind_topic(self): + self.unbind_test(exchange="amq.topic", routing_key="key") + + def test_unbind_fanout(self): + self.unbind_test(exchange="amq.fanout") + + def test_unbind_headers(self): + self.unbind_test(exchange="amq.match", args={ "x-match":"all", "a":"b"}, headers={"a":"b"}) + + def unbind_test(self, exchange, routing_key="", args=None, headers={}): + #bind two queues and consume from them + channel = self.channel + + channel.queue_declare(queue="queue-1", exclusive=True, auto_delete=True) + channel.queue_declare(queue="queue-2", exclusive=True, auto_delete=True) + + self.subscribe(queue="queue-1", destination="queue-1") + self.subscribe(queue="queue-2", destination="queue-2") + + queue1 = self.client.queue("queue-1") + queue2 = self.client.queue("queue-2") + + channel.queue_bind(exchange=exchange, queue="queue-1", routing_key=routing_key, arguments=args) + channel.queue_bind(exchange=exchange, queue="queue-2", routing_key=routing_key, arguments=args) + + #send a message that will match both bindings + channel.message_transfer(destination=exchange, + content=Content("one", properties={'routing_key':routing_key, 'application_headers':headers})) + + #unbind first queue + channel.queue_unbind(exchange=exchange, queue="queue-1", routing_key=routing_key, arguments=args) + + #send another message + channel.message_transfer(destination=exchange, + content=Content("two", properties={'routing_key':routing_key, 'application_headers':headers})) + + #check one queue has both messages and the other has only one + self.assertEquals("one", queue1.get(timeout=1).content.body) + try: + msg = queue1.get(timeout=1) + self.fail("Got extra message: %s" % msg.content.body) + except Empty: pass + + self.assertEquals("one", queue2.get(timeout=1).content.body) + self.assertEquals("two", queue2.get(timeout=1).content.body) + try: + msg = queue2.get(timeout=1) + self.fail("Got extra message: " + msg) + except Empty: pass + + + def test_delete_simple(self): + """ + Test core queue deletion behaviour + """ + channel = self.channel + + #straight-forward case: + channel.queue_declare(queue="delete-me") + channel.message_transfer(content=Content("a", properties={'routing_key':"delete-me"})) + channel.message_transfer(content=Content("b", properties={'routing_key':"delete-me"})) + channel.message_transfer(content=Content("c", properties={'routing_key':"delete-me"})) + channel.queue_delete(queue="delete-me") + #check that it has gone be declaring passively + try: + channel.queue_declare(queue="delete-me", passive=True) + self.fail("Queue has not been deleted") + except Closed, e: + self.assertChannelException(404, e.args[0]) + + #check attempted deletion of non-existant queue is handled correctly: + channel = self.client.channel(2) + channel.session_open() + try: + channel.queue_delete(queue="i-dont-exist", if_empty=True) + self.fail("Expected delete of non-existant queue to fail") + except Closed, e: + self.assertChannelException(404, e.args[0]) + + + + def test_delete_ifempty(self): + """ + Test that if_empty field of queue_delete is honoured + """ + channel = self.channel + + #create a queue and add a message to it (use default binding): + channel.queue_declare(queue="delete-me-2") + channel.queue_declare(queue="delete-me-2", passive=True) + channel.message_transfer(content=Content("message", properties={'routing_key':"delete-me-2"})) + + #try to delete, but only if empty: + try: + channel.queue_delete(queue="delete-me-2", if_empty=True) + self.fail("Expected delete if_empty to fail for non-empty queue") + except Closed, e: + self.assertChannelException(406, e.args[0]) + + #need new channel now: + channel = self.client.channel(2) + channel.session_open() + + #empty queue: + self.subscribe(channel, destination="consumer_tag", queue="delete-me-2") + queue = self.client.queue("consumer_tag") + msg = queue.get(timeout=1) + self.assertEqual("message", msg.content.body) + channel.message_cancel(destination="consumer_tag") + + #retry deletion on empty queue: + channel.queue_delete(queue="delete-me-2", if_empty=True) + + #check that it has gone by declaring passively: + try: + channel.queue_declare(queue="delete-me-2", passive=True) + self.fail("Queue has not been deleted") + except Closed, e: + self.assertChannelException(404, e.args[0]) + + def test_delete_ifunused(self): + """ + Test that if_unused field of queue_delete is honoured + """ + channel = self.channel + + #create a queue and register a consumer: + channel.queue_declare(queue="delete-me-3") + channel.queue_declare(queue="delete-me-3", passive=True) + self.subscribe(destination="consumer_tag", queue="delete-me-3") + + #need new channel now: + channel2 = self.client.channel(2) + channel2.session_open() + #try to delete, but only if empty: + try: + channel2.queue_delete(queue="delete-me-3", if_unused=True) + self.fail("Expected delete if_unused to fail for queue with existing consumer") + except Closed, e: + self.assertChannelException(406, e.args[0]) + + + channel.message_cancel(destination="consumer_tag") + channel.queue_delete(queue="delete-me-3", if_unused=True) + #check that it has gone by declaring passively: + try: + channel.queue_declare(queue="delete-me-3", passive=True) + self.fail("Queue has not been deleted") + except Closed, e: + self.assertChannelException(404, e.args[0]) + + + def test_autodelete_shared(self): + """ + Test auto-deletion (of non-exclusive queues) + """ + channel = self.channel + other = self.connect() + channel2 = other.channel(1) + channel2.session_open() + + channel.queue_declare(queue="auto-delete-me", auto_delete=True) + + #consume from both channels + reply = channel.basic_consume(queue="auto-delete-me") + channel2.basic_consume(queue="auto-delete-me") + + #implicit cancel + channel2.session_close() + + #check it is still there + channel.queue_declare(queue="auto-delete-me", passive=True) + + #explicit cancel => queue is now unused again: + channel.basic_cancel(consumer_tag=reply.consumer_tag) + + #NOTE: this assumes there is no timeout in use + + #check that it has gone be declaring passively + try: + channel.queue_declare(queue="auto-delete-me", passive=True) + self.fail("Expected queue to have been deleted") + except Closed, e: + self.assertChannelException(404, e.args[0]) + + diff --git a/qpid/python/tests_0-10_preview/testlib.py b/qpid/python/tests_0-10_preview/testlib.py new file mode 100644 index 0000000000..a0355c4ce0 --- /dev/null +++ b/qpid/python/tests_0-10_preview/testlib.py @@ -0,0 +1,66 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# +# Tests for the testlib itself. +# + +from qpid.content import Content +from qpid.testlib import testrunner, TestBase +from Queue import Empty + +import sys +from traceback import * + +def mytrace(frame, event, arg): + print_stack(frame); + print "====" + return mytrace + +class TestBaseTest(TestBase): + """Verify TestBase functions work as expected""" + + def testAssertEmptyPass(self): + """Test assert empty works""" + self.queue_declare(queue="empty") + q = self.consume("empty") + self.assertEmpty(q) + try: + q.get(timeout=1) + self.fail("Queue is not empty.") + except Empty: None # Ignore + + def testAssertEmptyFail(self): + self.queue_declare(queue="full") + q = self.consume("full") + self.channel.message_transfer(content=Content("", properties={'routing_key':"full"})) + try: + self.assertEmpty(q); + self.fail("assertEmpty did not assert on non-empty queue") + except AssertionError: None # Ignore + + def testMessageProperties(self): + """Verify properties are passed with message""" + props={"x":1, "y":2} + self.queue_declare(queue="q") + q = self.consume("q") + self.assertPublishGet(q, routing_key="q", properties=props) + + + diff --git a/qpid/python/tests_0-10_preview/tx.py b/qpid/python/tests_0-10_preview/tx.py new file mode 100644 index 0000000000..3fd1065af3 --- /dev/null +++ b/qpid/python/tests_0-10_preview/tx.py @@ -0,0 +1,231 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +from qpid.client import Client, Closed +from qpid.queue import Empty +from qpid.content import Content +from qpid.testlib import testrunner, TestBase + +class TxTests(TestBase): + """ + Tests for 'methods' on the amqp tx 'class' + """ + + def test_commit(self): + """ + Test that commited publishes are delivered and commited acks are not re-delivered + """ + channel2 = self.client.channel(2) + channel2.session_open() + self.perform_txn_work(channel2, "tx-commit-a", "tx-commit-b", "tx-commit-c") + channel2.tx_commit() + channel2.session_close() + + #use a different channel with new subscriptions to ensure + #there is no redelivery of acked messages: + channel = self.channel + channel.tx_select() + + self.subscribe(channel, queue="tx-commit-a", destination="qa", confirm_mode=1) + queue_a = self.client.queue("qa") + + self.subscribe(channel, queue="tx-commit-b", destination="qb", confirm_mode=1) + queue_b = self.client.queue("qb") + + self.subscribe(channel, queue="tx-commit-c", destination="qc", confirm_mode=1) + queue_c = self.client.queue("qc") + + #check results + for i in range(1, 5): + msg = queue_c.get(timeout=1) + self.assertEqual("TxMessage %d" % i, msg.content.body) + msg.complete() + + msg = queue_b.get(timeout=1) + self.assertEqual("TxMessage 6", msg.content.body) + msg.complete() + + msg = queue_a.get(timeout=1) + self.assertEqual("TxMessage 7", msg.content.body) + msg.complete() + + for q in [queue_a, queue_b, queue_c]: + try: + extra = q.get(timeout=1) + self.fail("Got unexpected message: " + extra.content.body) + except Empty: None + + #cleanup + channel.tx_commit() + + def test_auto_rollback(self): + """ + Test that a channel closed with an open transaction is effectively rolled back + """ + channel2 = self.client.channel(2) + channel2.session_open() + queue_a, queue_b, queue_c = self.perform_txn_work(channel2, "tx-autorollback-a", "tx-autorollback-b", "tx-autorollback-c") + + for q in [queue_a, queue_b, queue_c]: + try: + extra = q.get(timeout=1) + self.fail("Got unexpected message: " + extra.content.body) + except Empty: None + + channel2.session_close() + channel = self.channel + channel.tx_select() + + self.subscribe(channel, queue="tx-autorollback-a", destination="qa", confirm_mode=1) + queue_a = self.client.queue("qa") + + self.subscribe(channel, queue="tx-autorollback-b", destination="qb", confirm_mode=1) + queue_b = self.client.queue("qb") + + self.subscribe(channel, queue="tx-autorollback-c", destination="qc", confirm_mode=1) + queue_c = self.client.queue("qc") + + #check results + for i in range(1, 5): + msg = queue_a.get(timeout=1) + self.assertEqual("Message %d" % i, msg.content.body) + msg.complete() + + msg = queue_b.get(timeout=1) + self.assertEqual("Message 6", msg.content.body) + msg.complete() + + msg = queue_c.get(timeout=1) + self.assertEqual("Message 7", msg.content.body) + msg.complete() + + for q in [queue_a, queue_b, queue_c]: + try: + extra = q.get(timeout=1) + self.fail("Got unexpected message: " + extra.content.body) + except Empty: None + + #cleanup + channel.tx_commit() + + def test_rollback(self): + """ + Test that rolled back publishes are not delivered and rolled back acks are re-delivered + """ + channel = self.channel + queue_a, queue_b, queue_c = self.perform_txn_work(channel, "tx-rollback-a", "tx-rollback-b", "tx-rollback-c") + + for q in [queue_a, queue_b, queue_c]: + try: + extra = q.get(timeout=1) + self.fail("Got unexpected message: " + extra.content.body) + except Empty: None + + #stop subscriptions (ensures no delivery occurs during rollback as messages are requeued) + for d in ["sub_a", "sub_b", "sub_c"]: + channel.message_stop(destination=d) + + channel.tx_rollback() + + #restart susbcriptions + for d in ["sub_a", "sub_b", "sub_c"]: + channel.message_flow(destination=d, unit=0, value=0xFFFFFFFF) + channel.message_flow(destination=d, unit=1, value=0xFFFFFFFF) + + #check results + for i in range(1, 5): + msg = queue_a.get(timeout=1) + self.assertEqual("Message %d" % i, msg.content.body) + msg.complete() + + msg = queue_b.get(timeout=1) + self.assertEqual("Message 6", msg.content.body) + msg.complete() + + msg = queue_c.get(timeout=1) + self.assertEqual("Message 7", msg.content.body) + msg.complete() + + for q in [queue_a, queue_b, queue_c]: + try: + extra = q.get(timeout=1) + self.fail("Got unexpected message: " + extra.content.body) + except Empty: None + + #cleanup + channel.tx_commit() + + def perform_txn_work(self, channel, name_a, name_b, name_c): + """ + Utility method that does some setup and some work under a transaction. Used for testing both + commit and rollback + """ + #setup: + channel.queue_declare(queue=name_a, exclusive=True, auto_delete=True) + channel.queue_declare(queue=name_b, exclusive=True, auto_delete=True) + channel.queue_declare(queue=name_c, exclusive=True, auto_delete=True) + + key = "my_key_" + name_b + topic = "my_topic_" + name_c + + channel.queue_bind(queue=name_b, exchange="amq.direct", routing_key=key) + channel.queue_bind(queue=name_c, exchange="amq.topic", routing_key=topic) + + for i in range(1, 5): + channel.message_transfer(content=Content(properties={'routing_key':name_a, 'message_id':"msg%d" % i}, body="Message %d" % i)) + + channel.message_transfer(destination="amq.direct", + content=Content(properties={'routing_key':key, 'message_id':"msg6"}, body="Message 6")) + channel.message_transfer(destination="amq.topic", + content=Content(properties={'routing_key':topic, 'message_id':"msg7"}, body="Message 7")) + + channel.tx_select() + + #consume and ack messages + self.subscribe(channel, queue=name_a, destination="sub_a", confirm_mode=1) + queue_a = self.client.queue("sub_a") + for i in range(1, 5): + msg = queue_a.get(timeout=1) + self.assertEqual("Message %d" % i, msg.content.body) + + msg.complete() + + self.subscribe(channel, queue=name_b, destination="sub_b", confirm_mode=1) + queue_b = self.client.queue("sub_b") + msg = queue_b.get(timeout=1) + self.assertEqual("Message 6", msg.content.body) + msg.complete() + + sub_c = self.subscribe(channel, queue=name_c, destination="sub_c", confirm_mode=1) + queue_c = self.client.queue("sub_c") + msg = queue_c.get(timeout=1) + self.assertEqual("Message 7", msg.content.body) + msg.complete() + + #publish messages + for i in range(1, 5): + channel.message_transfer(destination="amq.topic", + content=Content(properties={'routing_key':topic, 'message_id':"tx-msg%d" % i}, + body="TxMessage %d" % i)) + + channel.message_transfer(destination="amq.direct", + content=Content(properties={'routing_key':key, 'message_id':"tx-msg6"}, + body="TxMessage 6")) + channel.message_transfer(content=Content(properties={'routing_key':name_a, 'message_id':"tx-msg7"}, + body="TxMessage 7")) + return queue_a, queue_b, queue_c diff --git a/qpid/specs/amqp.0-10-qpid-errata.xml b/qpid/specs/amqp.0-10-qpid-errata.xml new file mode 100644 index 0000000000..1b15588a5e --- /dev/null +++ b/qpid/specs/amqp.0-10-qpid-errata.xml @@ -0,0 +1,6652 @@ +<?xml version="1.0"?> + +<!-- + Copyright Notice + ================ + (c) Copyright Cisco Systems, Credit Suisse, Deutsche Borse Systems, Envoy Technologies, Inc., + Goldman Sachs, IONA Technologies PLC, iMatix Corporation sprl.,JPMorgan Chase Bank Inc. N.A, + Novell, Rabbit Technologies Ltd., Red Hat, Inc., TWIST Process Innovations ltd, and 29West Inc. + 2006, 2007. All rights reserved. + + License + ======= + + Cisco Systems, Credit Suisse, Deutsche Borse Systems, Envoy Technologies, Inc.,Goldman Sachs, + IONA Technologies PLC, iMatix Corporation sprl.,JPMorgan Chase Bank Inc. N.A, Novell, Rabbit + Technologies Ltd., Red Hat, Inc., TWIST Process Innovations ltd, and 29West Inc. (collectively, + the "Authors") each hereby grants to you a worldwide, perpetual, royalty-free, nontransferable, + nonexclusive license to (i) copy, display, distribute and implement the Advanced Messaging Queue + Protocol ("AMQP") Specification and (ii) the Licensed Claims that are held by the Authors, all for + the purpose of implementing the Advanced Messaging Queue Protocol Specification. Your license and + any rights under this Agreement will terminate immediately without notice from any Author if you + bring any claim, suit, demand, or action related to the Advanced Messaging Queue Protocol + Specification against any Author. Upon termination, you shall destroy all copies of the Advanced + Messaging Queue Protocol Specification in your possession or control. + + As used hereunder, "Licensed Claims" means those claims of a patent or patent application, + throughout the world, excluding design patents and design registrations, owned or controlled, or + that can be sublicensed without fee and in compliance with the requirements of this Agreement, by + an Author or its affiliates now or at any future time and which would necessarily be infringed by + implementation of the Advanced Messaging Queue Protocol Specification. A claim is necessarily + infringed hereunder only when it is not possible to avoid infringing it because there is no + plausible non-infringing alternative for implementing the required portions of the Advanced + Messaging Queue Protocol Specification. Notwithstanding the foregoing, Licensed Claims shall not + include any claims other than as set forth above even if contained in the same patent as Licensed + Claims; or that read solely on any implementations of any portion of the Advanced Messaging Queue + Protocol Specification that are not required by the Advanced Messaging Queue Protocol + Specification, or that, if licensed, would require a payment of royalties by the licensor to + unaffiliated third parties. Moreover, Licensed Claims shall not include (i) any enabling + technologies that may be necessary to make or use any Licensed Product but are not themselves + expressly set forth in the Advanced Messaging Queue Protocol Specification (e.g., semiconductor + manufacturing technology, compiler technology, object oriented technology, networking technology, + operating system technology, and the like); or (ii) the implementation of other published + standards developed elsewhere and merely referred to in the body of the Advanced Messaging Queue + Protocol Specification, or (iii) any Licensed Product and any combinations thereof the purpose or + function of which is not required for compliance with the Advanced Messaging Queue Protocol + Specification. For purposes of this definition, the Advanced Messaging Queue Protocol + Specification shall be deemed to include both architectural and interconnection requirements + essential for interoperability and may also include supporting source code artifacts where such + architectural, interconnection requirements and source code artifacts are expressly identified as + being required or documentation to achieve compliance with the Advanced Messaging Queue Protocol + Specification. + + As used hereunder, "Licensed Products" means only those specific portions of products (hardware, + software or combinations thereof) that implement and are compliant with all relevant portions of + the Advanced Messaging Queue Protocol Specification. + + The following disclaimers, which you hereby also acknowledge as to any use you may make of the + Advanced Messaging Queue Protocol Specification: + + THE ADVANCED MESSAGING QUEUE PROTOCOL SPECIFICATION IS PROVIDED "AS IS," AND THE AUTHORS MAKE NO + REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, OR TITLE; THAT THE CONTENTS + OF THE ADVANCED MESSAGING QUEUE PROTOCOL SPECIFICATION ARE SUITABLE FOR ANY PURPOSE; NOR THAT THE + IMPLEMENTATION OF THE ADVANCED MESSAGING QUEUE PROTOCOL SPECIFICATION WILL NOT INFRINGE ANY THIRD + PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. + + THE AUTHORS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL + DAMAGES ARISING OUT OF OR RELATING TO ANY USE, IMPLEMENTATION OR DISTRIBUTION OF THE ADVANCED + MESSAGING QUEUE PROTOCOL SPECIFICATION. + + The name and trademarks of the Authors may NOT be used in any manner, including advertising or + publicity pertaining to the Advanced Messaging Queue Protocol Specification or its contents + without specific, written prior permission. Title to copyright in the Advanced Messaging Queue + Protocol Specification will at all times remain with the Authors. + + No other rights are granted by implication, estoppel or otherwise. + + Upon termination of your license or rights under this Agreement, you shall destroy all copies of + the Advanced Messaging Queue Protocol Specification in your possession or control. + + Trademarks + ========== + "JPMorgan", "JPMorgan Chase", "Chase", the JPMorgan Chase logo and the Octagon Symbol are + trademarks of JPMorgan Chase & Co. + + IMATIX and the iMatix logo are trademarks of iMatix Corporation sprl. + + IONA, IONA Technologies, and the IONA logos are trademarks of IONA Technologies PLC and/or its + subsidiaries. + + LINUX is a trademark of Linus Torvalds. RED HAT and JBOSS are registered trademarks of Red Hat, + Inc. in the US and other countries. + + Java, all Java-based trademarks and OpenOffice.org are trademarks of Sun Microsystems, Inc. in the + United States, other countries, or both. + + Other company, product, or service names may be trademarks or service marks of others. + + Links to full AMQP specification: + ================================= + http://www.envoytech.org/spec/amq/ + http://www.iona.com/opensource/amqp/ + http://www.redhat.com/solutions/specifications/amqp/ + http://www.twiststandards.org/tiki-index.php?page=AMQ + http://www.imatix.com/amqp +--> + +<!-- + XML Notes + ========= + + We use entities to indicate repetition; attributes to indicate properties. + + We use the "name" attribute as an identifier, usually within the context of the surrounding + entities. + + We use hyphens (minus char '-') to seperate words in names. + + We do not enforce any particular validation mechanism but we support all mechanisms. The protocol + definition conforms to a formal grammar that is published seperately in several technologies. + +--> + +<!DOCTYPE amqp SYSTEM "amqp.0-10.dtd"> + +<amqp xmlns="http://www.amqp.org/schema/amqp.xsd" + major="0" minor="10" port="5672"> + + <!-- + ====================== == type definitions == ====================== + --> + + <!-- + 0x00 - 0x0f: Fixed width, 1 octet + --> + + <type name="bin8" code="0x00" fixed-width="1" label="octet of unspecified encoding"> + <doc> + The bin8 type consists of exactly one octet of opaque binary data. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET + +----------+ + | bin8 | + +----------+ + </doc> + + <doc type="bnf"> + bin8 = OCTET + </doc> + </type> + + <type name="int8" code="0x01" fixed-width="1" label="8-bit signed integral value (-128 - 127)"> + <doc> + The int8 type is a signed integral value encoded using an 8-bit two's complement + representation. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET + +----------+ + | int8 | + +----------+ + </doc> + + <doc type="bnf"> + int8 = OCTET + </doc> + </type> + + <type name="uint8" code="0x02" fixed-width="1" label="8-bit unsigned integral value (0 - 255)"> + <doc> + The uint8 type is an 8-bit unsigned integral value. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET + +---------+ + | uint8 | + +---------+ + </doc> + + <doc type="bnf"> + uint8 = OCTET + </doc> + </type> + + <type name="char" code="0x04" fixed-width="1" label="an iso-8859-15 character"> + <doc> + The char type encodes a single character from the iso-8859-15 character set. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET + +----------+ + | char | + +----------+ + </doc> + + <doc type="bnf"> + char = OCTET + </doc> + </type> + + <type name="boolean" code="0x08" fixed-width="1" + label="boolean value (zero represents false, nonzero represents true)"> + <doc> + The boolean type is a single octet that encodes a true or false value. If the octet is zero, + then the boolean is false. Any other value represents true. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET + +---------+ + | boolean | + +---------+ + </doc> + + <doc type="bnf"> + boolean = OCTET + </doc> + </type> + + <!-- + 0x10 - 0x1f: Fixed width, 2 octets + --> + + <type name="bin16" code="0x10" fixed-width="2" label="two octets of unspecified binary encoding"> + <doc> + The bin16 type consists of two consecutive octets of opaque binary data. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET 1 OCTET + +-----------+-----------+ + | octet-one | octet-two | + +-----------+-----------+ + </doc> + + <doc type="bnf"> + bin16 = 2 OCTET + </doc> + </type> + + <type name="int16" code="0x11" fixed-width="2" label="16-bit signed integral value"> + <doc> + The int16 type is a signed integral value encoded using a 16-bit two's complement + representation in network byte order. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET 1 OCTET + +-----------+----------+ + | high-byte | low-byte | + +-----------+----------+ + </doc> + + <doc type="bnf"> + int16 = high-byte low-byte + high-byte = OCTET + low-byte = OCTET + </doc> + </type> + + <type name="uint16" code="0x12" fixed-width="2" label="16-bit unsigned integer"> + <doc> + The uint16 type is a 16-bit unsigned integral value encoded in network byte order. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET 1 OCTET + +-----------+----------+ + | high-byte | low-byte | + +-----------+----------+ + </doc> + + <doc type="bnf"> + uint16 = high-byte low-byte + high-byte = OCTET + low-byte = OCTET + </doc> + </type> + + <!-- + 0x20 - 0x2f: Fixed width, 4 octets + --> + + <type name="bin32" code="0x20" fixed-width="4" label="four octets of unspecified binary encoding"> + <doc> + The bin32 type consists of 4 consecutive octets of opaque binary data. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET 1 OCTET 1 OCTET 1 OCTET + +-----------+-----------+-------------+------------+ + | octet-one | octet-two | octet-three | octet-four | + +-----------+-----------+-------------+------------+ + </doc> + + <doc type="bnf"> + bin32 = 4 OCTET + </doc> + </type> + + <type name="int32" code="0x21" fixed-width="4" label="32-bit signed integral value"> + <doc> + The int32 type is a signed integral value encoded using a 32-bit two's complement + representation in network byte order. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET 1 OCTET 1 OCTET 1 OCTET + +-----------+------------+----------+----------+ + | byte-four | byte-three | byte-two | byte-one | + +-----------+------------+----------+----------+ + MSB LSB + </doc> + + <doc type="bnf"> + int32 = byte-four byte-three byte-two byte-one + byte-four = OCTET ; most significant byte (MSB) + byte-three = OCTET + byte-two = OCTET + byte-one = OCTET ; least significant byte (LSB) + </doc> + </type> + + <type name="uint32" code="0x22" fixed-width="4" label="32-bit unsigned integral value"> + <doc> + The uint32 type is a 32-bit unsigned integral value encoded in network byte order. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET 1 OCTET 1 OCTET 1 OCTET + +-----------+------------+----------+----------+ + | byte-four | byte-three | byte-two | byte-one | + +-----------+------------+----------+----------+ + MSB LSB + </doc> + + <doc type="bnf"> + uint32 = byte-four byte-three byte-two byte-one + byte-four = OCTET ; most significant byte (MSB) + byte-three = OCTET + byte-two = OCTET + byte-one = OCTET ; least significant byte (LSB) + </doc> + </type> + + <type name="float" code="0x23" fixed-width="4" + label="single precision IEEE 754 32-bit floating point"> + <doc> + The float type encodes a single precision 32-bit floating point number. The format and + operations are defined by the IEEE 754 standard for 32-bit floating point numbers. + </doc> + + <doc type="picture" title="Wire Format"> + 4 OCTETs + +-----------------------+ + | float | + +-----------------------+ + IEEE 754 32-bit float + </doc> + + <doc type="bnf"> + float = 4 OCTET ; IEEE 754 32-bit floating point number + </doc> + </type> + + <type name="char-utf32" code="0x27" fixed-width="4" + label="single unicode character in UTF-32 encoding"> + <doc> + The char-utf32 type consists of a single unicode character in the UTF-32 encoding. + </doc> + + <doc type="picture" title="Wire Format"> + 4 OCTETs + +------------------+ + | char-utf32 | + +------------------+ + UTF-32 character + </doc> + + <doc type="bnf"> + char-utf32 = 4 OCTET ; single UTF-32 character + </doc> + </type> + + <type name="sequence-no" fixed-width="4" label="serial number defined in RFC-1982"> + <doc> + The sequence-no type encodes, in network byte order, a serial number as defined in RFC-1982. + The arithmetic, operators, and ranges for numbers of this type are defined by RFC-1982. + </doc> + + <doc type="picture" title="Wire Format"> + 4 OCTETs + +------------------------+ + | sequence-no | + +------------------------+ + RFC-1982 serial number + </doc> + + <doc type="bnf"> + sequence-no = 4 OCTET ; RFC-1982 serial number + </doc> + </type> + + <!-- + 0x30 - 0x3f: Fixed width types - 8 octets + --> + + <type name="bin64" code="0x30" fixed-width="8" + label="eight octets of unspecified binary encoding"> + <doc> + The bin64 type consists of eight consecutive octets of opaque binary data. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET 1 OCTET 1 OCTET 1 OCTET + +-----------+-----------+-----+-------------+-------------+ + | octet-one | octet-two | ... | octet-seven | octet-eight | + +-----------+-----------+-----+-------------+-------------+ + </doc> + + <doc type="bnf"> + bin64 = 8 OCTET + </doc> + </type> + + <type name="int64" code="0x31" fixed-width="8" label="64-bit signed integral value"> + <doc> + The int64 type is a signed integral value encoded using a 64-bit two's complement + representation in network byte order. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET 1 OCTET 1 OCTET 1 OCTET + +------------+------------+-----+----------+----------+ + | byte-eight | byte-seven | ... | byte-two | byte-one | + +------------+------------+-----+----------+----------+ + MSB LSB + </doc> + + <doc type="bnf"> + int64 = byte-eight byte-seven byte-six byte-five + byte-four byte-three byte-two byte-one + byte-eight = 1 OCTET ; most significant byte (MSB) + byte-seven = 1 OCTET + byte-six = 1 OCTET + byte-five = 1 OCTET + byte-four = 1 OCTET + byte-three = 1 OCTET + byte-two = 1 OCTET + byte-one = 1 OCTET ; least significant byte (LSB) + </doc> + </type> + + <type name="uint64" code="0x32" fixed-width="8" label="64-bit unsigned integral value"> + <doc> + The uint64 type is a 64-bit unsigned integral value encoded in network byte order. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET 1 OCTET 1 OCTET 1 OCTET + +------------+------------+-----+----------+----------+ + | byte-eight | byte-seven | ... | byte-two | byte-one | + +------------+------------+-----+----------+----------+ + MSB LSB + </doc> + + <doc type="bnf"> + uint64 = byte-eight byte-seven byte-six byte-five + byte-four byte-three byte-two byte-one + byte-eight = 1 OCTET ; most significant byte (MSB) + byte-seven = 1 OCTET + byte-six = 1 OCTET + byte-five = 1 OCTET + byte-four = 1 OCTET + byte-three = 1 OCTET + byte-two = 1 OCTET + byte-one = 1 OCTET ; least significant byte (LSB) + </doc> + </type> + + <type name="double" code="0x33" fixed-width="8" label="double precision IEEE 754 floating point"> + <doc> + The double type encodes a double precision 64-bit floating point number. The format and + operations are defined by the IEEE 754 standard for 64-bit double precision floating point + numbers. + </doc> + + <doc type="picture" title="Wire Format"> + 8 OCTETs + +-----------------------+ + | double | + +-----------------------+ + IEEE 754 64-bit float + </doc> + + <doc type="bnf"> + double = 8 OCTET ; double precision IEEE 754 floating point number + </doc> + </type> + + <type name="datetime" code="0x38" fixed-width="8" label="datetime in 64 bit POSIX time_t format"> + <doc> + The datetime type encodes a date and time using the 64 bit POSIX time_t format. + </doc> + + <doc type="picture" title="Wire Format"> + 8 OCTETs + +---------------------+ + | datetime | + +---------------------+ + posix time_t format + </doc> + + <doc type="bnf"> + datetime = 8 OCTET ; 64 bit posix time_t format + </doc> + </type> + + <!-- + 0x40 - 0x4f: Fixed width types - 16 octets + --> + + <type name="bin128" code="0x40" fixed-width="16" + label="sixteen octets of unspecified binary encoding"> + <doc> + The bin128 type consists of 16 consecutive octets of opaque binary data. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET 1 OCTET 1 OCTET 1 OCTET + +-----------+-----------+-----+---------------+---------------+ + | octet-one | octet-two | ... | octet-fifteen | octet-sixteen | + +-----------+-----------+-----+---------------+---------------+ + </doc> + + <doc type="bnf"> + bin128 = 16 OCTET + </doc> + </type> + + <type name="uuid" code="0x48" fixed-width="16" label="UUID (RFC-4122 section 4.1.2) - 16 octets"> + <doc> + The uuid type encodes a universally unique id as defined by RFC-4122. The format and + operations for this type can be found in section 4.1.2 of RFC-4122. + </doc> + + <doc type="picture" title="Wire Format"> + 16 OCTETs + +---------------+ + | uuid | + +---------------+ + RFC-4122 UUID + </doc> + + <doc type="bnf"> + uuid = 16 OCTET ; RFC-4122 section 4.1.2 + </doc> + </type> + + <!-- + 0x50 - 0x5f: Fixed width types - 32 octets + --> + + <type name="bin256" code="0x50" fixed-width="32" + label="thirty two octets of unspecified binary encoding"> + <doc> + The bin256 type consists of thirty two consecutive octets of opaque binary data. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET 1 OCTET 1 OCTET 1 OCTET + +-----------+-----------+-----+------------------+------------------+ + | octet-one | octet-two | ... | octet-thirty-one | octet-thirty-two | + +-----------+-----------+-----+------------------+------------------+ + </doc> + + <doc type="bnf"> + bin256 = 32 OCTET + </doc> + </type> + + <!-- + 0x60 - 0x6f: Fixed width types - 64 octets + --> + + <type name="bin512" code="0x60" fixed-width="64" + label="sixty four octets of unspecified binary encoding"> + <doc> + The bin512 type consists of sixty four consecutive octets of opaque binary data. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET 1 OCTET 1 OCTET 1 OCTET + +-----------+-----------+-----+-------------------+------------------+ + | octet-one | octet-two | ... | octet-sixty-three | octet-sixty-four | + +-----------+-----------+-----+-------------------+------------------+ + </doc> + + <doc type="bnf"> + bin512 = 64 OCTET + </doc> + </type> + + <!-- + 0x70 - 0x7f: Fixed width types - 128 octets + --> + + <type name="bin1024" code="0x70" fixed-width="128" + label="one hundred and twenty eight octets of unspecified binary encoding"> + <doc> + The bin1024 type consists of one hundred and twenty eight octets of opaque binary data. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET 1 OCTET 1 OCTET 1 OCTET + +-----------+-----------+-----+------------------------+------------------------+ + | octet-one | octet-two | ... | octet-one-twenty-seven | octet-one-twenty-eight | + +-----------+-----------+-----+------------------------+------------------------+ + </doc> + + <doc type="bnf"> + bin1024 = 128 OCTET + </doc> + </type> + + <!-- + 0x80 - 0x8f: Variable length - one byte length field (up to 255 octets) + --> + + <type name="vbin8" code="0x80" variable-width="1" label="up to 255 octets of opaque binary data"> + <doc> + The vbin8 type encodes up to 255 octets of opaque binary data. The number of octets is first + encoded as an 8-bit unsigned integral value. This is followed by the actual data. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET size OCTETs + +---------+-------------+ + | size | octets | + +---------+-------------+ + uint8 + </doc> + + <doc type="bnf"> + vbin8 = size octets + size = uint8 + octets = 0*255 OCTET ; size OCTETs + </doc> + </type> + + <type name="str8-latin" code="0x84" variable-width="1" label="up to 255 iso-8859-15 characters"> + <doc> + The str8-latin type encodes up to 255 octets of iso-8859-15 characters. The number of octets + is first encoded as an 8-bit unsigned integral value. This is followed by the actual + characters. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET size OCTETs + +---------+------------------------+ + | size | characters | + +---------+------------------------+ + uint16 iso-8859-15 characters + </doc> + + <doc type="bnf"> + str8-latin = size characters + size = uint8 + characters = 0*255 OCTET ; size OCTETs + </doc> + </type> + + <type name="str8" code="0x85" variable-width="1" label="up to 255 octets worth of UTF-8 unicode"> + <doc> + The str8 type encodes up to 255 octets worth of UTF-8 unicode. The number of octets of unicode + is first encoded as an 8-bit unsigned integral value. This is followed by the actual UTF-8 + unicode. Note that the encoded size refers to the number of octets of unicode, not necessarily + the number of characters since the UTF-8 unicode may include multi-byte character sequences. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET size OCTETs + +---------+--------------+ + | size | utf8-unicode | + +---------+--------------+ + uint8 + </doc> + + <doc type="bnf"> + str8 = size utf8-unicode + size = uint8 + utf8-unicode = 0*255 OCTET ; size OCTETs + </doc> + </type> + + <type name="str8-utf16" code="0x86" variable-width="1" + label="up to 255 octets worth of UTF-16 unicode"> + <doc> + The str8-utf16 type encodes up to 255 octets worth of UTF-16 unicode. The number of octets of + unicode is first encoded as an 8-bit unsigned integral value. This is followed by the actual + UTF-16 unicode. Note that the encoded size refers to the number of octets of unicode, not the + number of characters since the UTF-16 unicode will include at least two octets per unicode + character. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET size OCTETs + +---------+---------------+ + | size | utf16-unicode | + +---------+---------------+ + uint8 + </doc> + + <doc type="bnf"> + str8-utf16 = size utf16-unicode + size = uint8 + utf16-unicode = 0*255 OCTET ; size OCTETs + </doc> + </type> + + <!-- + 0x90 - 0x9f: Variable length types - two byte length field (up to 65535 octets) + --> + + <type name="vbin16" code="0x90" variable-width="2" + label="up to 65535 octets of opaque binary data"> + <doc> + The vbin16 type encodes up to 65535 octets of opaque binary data. The number of octets is + first encoded as a 16-bit unsigned integral value in network byte order. This is followed by + the actual data. + </doc> + + <doc type="picture" title="Wire Format"> + 2 OCTETs size OCTETs + +----------+-------------+ + | size | octets | + +----------+-------------+ + uint16 + </doc> + + <doc type="bnf"> + vbin16 = size octets + size = uint16 + octets = 0*65535 OCTET ; size OCTETs + </doc> + </type> + + <type name="str16-latin" code="0x94" variable-width="2" + label="up to 65535 iso-8859-15 characters"> + <doc> + The str16-latin type encodes up to 65535 octets of is-8859-15 characters. The number of octets + is first encoded as a 16-bit unsigned integral value in network byte order. This is followed + by the actual characters. + </doc> + + <doc type="picture" title="Wire Format"> + 2 OCTETs size OCTETs + +----------+------------------------+ + | size | characters | + +----------+------------------------+ + uint16 iso-8859-15 characters + </doc> + + <doc type="bnf"> + str16-latin = size characters + size = uint16 + characters = 0*65535 OCTET ; size OCTETs + </doc> + </type> + + <type name="str16" code="0x95" variable-width="2" + label="up to 65535 octets worth of UTF-8 unicode"> + <doc> + The str16 type encodes up to 65535 octets worth of UTF-8 unicode. The number of octets is + first encoded as a 16-bit unsigned integral value in network byte order. This is followed by + the actual UTF-8 unicode. Note that the encoded size refers to the number of octets of + unicode, not necessarily the number of unicode characters since the UTF-8 unicode may include + multi-byte character sequences. + </doc> + + <doc type="picture" title="Wire Format"> + 2 OCTETs size OCTETs + +----------+--------------+ + | size | utf8-unicode | + +----------+--------------+ + uint16 + </doc> + + <doc type="bnf"> + str16 = size utf8-unicode + size = uint16 + utf8-unicode = 0*65535 OCTET ; size OCTETs + </doc> + </type> + + <type name="str16-utf16" code="0x96" variable-width="2" + label="up to 65535 octets worth of UTF-16 unicode"> + <doc> + The str16-utf16 type encodes up to 65535 octets worth of UTF-16 unicode. The number of octets + is first encoded as a 16-bit unsigned integral value in network byte order. This is followed + by the actual UTF-16 unicode. Note that the encoded size refers to the number of octets of + unicode, not the number of unicode characters since the UTF-16 unicode will include at least + two octets per unicode character. + </doc> + + <doc type="picture" title="Wire Format"> + 2 OCTETs size OCTETs + +----------+---------------+ + | size | utf16-unicode | + +----------+---------------+ + uint16 + </doc> + + <doc type="bnf"> + str16-utf16 = size utf16-unicode + size = uint16 + utf16-unicode = 0*65535 OCTET ; size OCTETs + </doc> + </type> + + <type name="byte-ranges" variable-width="2" label="byte ranges within a 64-bit payload"> + <doc> + The byte-ranges type encodes up to 65535 octets worth of non-overlapping, non-touching, + ascending byte ranges within a 64-bit sequence of bytes. Each range is represented as an + inclusive lower and upper bound that identifies all the byte offsets included within a given + range. + </doc> + + <doc> + The number of octets of data is first encoded as a 16-bit unsigned integral value in network + byte order. This is then followed by the encoded representation of the ranges included in the + set. These MUST be encoded in ascending order, and any two ranges included in a given set MUST + NOT include overlapping or touching byte offsets. + </doc> + + <doc> + Each range is encoded as a pair of 64-bit unsigned integral values in network byte order + respectively representing the lower and upper bounds for that range. Note that because each + range is exactly 16 octets, the size in octets of the encoded ranges will always be 16 times + the number of ranges in the set. + </doc> + + <doc type="picture" title="Wire Format"> + +----= size OCTETs =----+ + | | + 2 OCTETs | 16 OCTETs | + +----------+-----+-----------+-----+ + | size | .../| range |\... | + +----------+---/ +-----------+ \---+ + uint16 / / \ \ + / / \ \ + / 8 OCTETs 8 OCTETs \ + +-----------+-----------+ + | lower | upper | + +-----------+-----------+ + uint64 uint64 + </doc> + + <doc type="bnf"> + byte-ranges = size *range + size = uint16 + range = lower upper + lower = uint64 + upper = uint64 + </doc> + </type> + + <type name="sequence-set" variable-width="2" label="ranged set representation"> + <doc> + The sequence-set type is a set of pairs of RFC-1982 numbers representing a discontinuous range + within an RFC-1982 sequence. Each pair represents a closed interval within the list. + </doc> + + <doc> + Sequence-sets can be represented as lists of pairs of positive 32-bit numbers, each pair + representing a closed interval that does not overlap or touch with any other interval in the + list. For example, a set containing words 0, 1, 2, 5, 6, and 15 can be represented: + </doc> + + <doc type="picture"> + [(0, 2), (5, 6), (15, 15)] + </doc> + + <doc> + 1) The list-of-pairs representation is sorted ascending (as defined by RFC 1982 + (http://www.ietf.org/rfc/rfc1982.txt) ) by the first elements of each pair. + </doc> + + <doc> + 2) The list-of-pairs is flattened into a list-of-words. + </doc> + + <doc> + 3) Each word in the list is packed into ascending locations in memory with network byte + ordering. + </doc> + + <doc> + 4) The size in bytes, represented as a 16-bit network-byte-order unsigned value, is prepended. + </doc> + + <doc> + For instance, the example from above would be encoded: + </doc> + + <doc type="picture"> + [(0, 2), (5, 6), (15, 15)] -- already sorted. + [0, 2, 5, 6, 15, 15] -- flattened. + 000000000000000200000005000000060000000F0000000F -- bytes in hex + 0018000000000000000200000005000000060000000F0000000F -- bytes in hex, + length (24) prepended + </doc> + + <doc type="picture" title="Wire Format"> + +----= size OCTETs =----+ + | | + 2 OCTETs | 8 OCTETs | + +----------+-----+-----------+-----+ + | size | .../| range |\... | + +----------+---/ +-----------+ \---+ + uint16 / / \ \ + / / \ \ + / / \ \ + / / \ \ + / 4 OCTETs 4 OCTETs \ + +-------------+-------------+ + | lower | upper | + +-------------+-------------+ + sequence-no sequence-no + </doc> + + <doc type="bnf"> + sequence-set = size *range + size = uint16 ; length of variable portion in bytes + + range = lower upper ; inclusive + lower = sequence-no + upper = sequence-no + </doc> + </type> + + <!-- + 0xa0 - 0xaf: Variable length types - four byte length field (up to 4294967295 octets) + --> + + <type name="vbin32" code="0xa0" variable-width="4" + label="up to 4294967295 octets of opaque binary data"> + <doc> + The vbin32 type encodes up to 4294967295 octets of opaque binary data. The number of octets is + first encoded as a 32-bit unsigned integral value in network byte order. This is followed by + the actual data. + </doc> + + <doc type="picture" title="Wire Format"> + 4 OCTETs size OCTETs + +----------+-------------+ + | size | octets | + +----------+-------------+ + uint32 + </doc> + + <doc type="bnf"> + vbin32 = size octets + size = uint32 + octets = 0*4294967295 OCTET ; size OCTETs + </doc> + </type> + + <type name="map" code="0xa8" variable-width="4" label="a mapping of keys to typed values"> + <doc> + A map is a set of distinct keys where each key has an associated (type,value) pair. The triple + of the key, type, and value, form an entry within a map. Each entry within a given map MUST + have a distinct key. A map is encoded as a size in octets, a count of the number of entries, + followed by the encoded entries themselves. + </doc> + + <doc> + An encoded map may contain up to (4294967295 - 4) octets worth of encoded entries. The size is + encoded as a 32-bit unsigned integral value in network byte order equal to the number of + octets worth of encoded entries plus 4. (The extra 4 octets is added for the entry count.) The + size is then followed by the number of entries encoded as a 32-bit unsigned integral value in + network byte order. Finally the entries are encoded sequentially. + </doc> + + <doc> + An entry is encoded as the key, followed by the type, and then the value. The key is always a + string encoded as a str8. The type is a single octet that may contain any valid AMQP type + code. The value is encoded according to the rules defined by the type code for that entry. + </doc> + + <doc type="picture" title="Wire Format"> + +------------= size OCTETs =-----------+ + | | + 4 OCTETs | 4 OCTETs | + +----------+----------+-----+---------------+-----+ + | size | count | .../| entry |\... | + +----------+----------+---/ +---------------+ \---+ + uint32 uint32 / / \ \ + / / \ \ + / / \ \ + / / \ \ + / / \ \ + / k OCTETs 1 OCTET n OCTETs \ + +-----------+---------+-----------+ + | key | type | value | + +-----------+---------+-----------+ + str8 *type* + </doc> + + <doc type="bnf"> + map = size count *entry + + size = uint32 ; size of count and entries in octets + count = uint32 ; number of entries in the map + + entry = key type value + key = str8 + type = OCTET ; type code of the value + value = *OCTET ; the encoded value + </doc> + </type> + + <type name="list" code="0xa9" variable-width="4" label="a series of consecutive type-value pairs"> + <doc> + A list is an ordered sequence of (type, value) pairs. The (type, value) pair forms an item + within the list. The list may contain items of many distinct types. A list is encoded as a + size in octets, followed by a count of the number of items, followed by the items themselves + encoded in their defined order. + </doc> + + <doc> + An encoded list may contain up to (4294967295 - 4) octets worth of encoded items. The size is + encoded as a 32-bit unsigned integral value in network byte order equal to the number of + octets worth of encoded items plus 4. (The extra 4 octets is added for the item count.) The + size is then followed by the number of items encoded as a 32-bit unsigned integral value in + network byte order. Finally the items are encoded sequentially in their defined order. + </doc> + + <doc> + An item is encoded as the type followed by the value. The type is a single octet that may + contain any valid AMQP type code. The value is encoded according to the rules defined by the + type code for that item. + </doc> + + <doc type="picture" title="Wire Format"> + +---------= size OCTETs =---------+ + | | + 4 OCTETs | 4 OCTETs | + +----------+----------+-----+----------+-----+ + | size | count | .../| item |\... | + +----------+----------+---/ +----------+ \---+ + uint32 uint32 / / \ \ + / / \ \ + / 1 OCTET n OCTETs \ + +----------+-----------+ + | type | value | + +----------+-----------+ + *type* + </doc> + + <doc type="bnf"> + list = size count *item + + size = uint32 ; size of count and items in octets + count = uint32 ; number of items in the list + + item = type value + type = OCTET ; type code of the value + value = *OCTET ; the encoded value + </doc> + </type> + + <type name="array" code="0xaa" variable-width="4" + label="a defined length collection of values of a single type"> + <doc> + An array is an ordered sequence of values of the same type. The array is encoded in as a size + in octets, followed by a type code, then a count of the number values in the array, and + finally the values encoded in their defined order. + </doc> + + <doc> + An encoded array may contain up to (4294967295 - 5) octets worth of encoded values. The size + is encoded as a 32-bit unsigned integral value in network byte order equal to the number of + octets worth of encoded values plus 5. (The extra 5 octets consist of 4 octets for the count + of the number of values, and one octet to hold the type code for the items in the array.) The + size is then followed by a single octet that may contain any valid AMQP type code. The type + code is then followed by the number of values encoded as a 32-bit unsigned integral value in + network byte order. Finally the values are encoded sequentially in their defined order + according to the rules defined by the type code for the array. + </doc> + + <doc type="picture" title="Wire Format"> + 4 OCTETs 1 OCTET 4 OCTETs (size - 5) OCTETs + +----------+---------+----------+-------------------------+ + | size | type | count | values | + +----------+---------+----------+-------------------------+ + uint32 uint32 *count* encoded *types* + </doc> + + <doc type="bnf"> + array = size type count values + + size = uint32 ; size of type, count, and values in octets + type = OCTET ; the type of the encoded values + count = uint32 ; number of items in the array + + values = 0*4294967290 OCTET ; (size - 5) OCTETs + </doc> + </type> + + <type name="struct32" code="0xab" variable-width="4" label="a coded struct with a 32-bit size"> + <doc> + The struct32 type describes any coded struct with a 32-bit (4 octet) size. The type is + restricted to be only coded structs with a 32-bit size, consequently the first six octets of + any encoded value for this type MUST always contain the size, class-code, and struct-code in + that order. + </doc> + + <doc> + The size is encoded as a 32-bit unsigned integral value in network byte order that is equal to + the size of the encoded field-data, packing-flags, class-code, and struct-code. The class-code + is a single octet that may be set to any valid class code. The struct-code is a single octet + that may be set to any valid struct code within the given class-code. + </doc> + + <doc> + The first six octets are then followed by the packing flags and encoded field data. The + presence and quantity of packing-flags, as well as the specific fields are determined by the + struct definition identified with the encoded class-code and struct-code. + </doc> + + <doc type="picture" title="Wire Format"> + 4 OCTETs 1 OCTET 1 OCTET pack-width OCTETs n OCTETs + +----------+------------+-------------+-------------------+------------+ + | size | class-code | struct-code | packing-flags | field-data | + +----------+------------+-------------+-------------------+------------+ + uint32 + + n = (size - 2 - pack-width) + </doc> + + <doc type="bnf"> + struct32 = size class-code struct-code packing-flags field-data + + size = uint32 + + class-code = OCTET ; zero for top-level structs + struct-code = OCTET ; together with class-code identifies the struct + ; definition which determines the pack-width and + ; fields + + packing-flags = 0*4 OCTET ; pack-width OCTETs + + field-data = *OCTET ; (size - 2 - pack-width) OCTETs + </doc> + </type> + + <!-- + 0xb0 - 0xbf: Reserved + --> + + <!-- + 0xc0 - 0xcf:Fixed width types - 5 octets + --> + + <type name="bin40" code="0xc0" fixed-width="5" label="five octets of unspecified binary encoding"> + <doc> + The bin40 type consists of five consecutive octets of opaque binary data. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET 1 OCTET 1 OCTET 1 OCTET 1 OCTET + +-----------+-----------+-------------+------------+------------+ + | octet-one | octet-two | octet-three | octet-four | octet-five | + +-----------+-----------+-------------+------------+------------+ + </doc> + + <doc type="bnf"> + bin40 = 5 OCTET + </doc> + </type> + + <type name="dec32" code="0xc8" fixed-width="5" + label="32-bit decimal value (e.g. for use in financial values)"> + <doc> + The dec32 type is decimal value with a variable number of digits following the decimal point. + It is encoded as an 8-bit unsigned integral value representing the number of decimal places. + This is followed by the signed integral value encoded using a 32-bit two's complement + representation in network byte order. + </doc> + + <doc> + The former value is referred to as the exponent of the divisor. The latter value is the + mantissa. The decimal value is given by: mantissa / 10^exponent. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET 4 OCTETs + +----------+----------+ + | exponent | mantissa | + +----------+----------+ + uint8 int32 + </doc> + + <doc type="bnf"> + dec32 = exponent mantissa + exponent = uint8 + mantissa = int32 + </doc> + </type> + + <!-- + 0xd0 - 0xdf: Fixed width types - 9 octets + --> + + <type name="bin72" code="0xd0" fixed-width="9" + label="nine octets of unspecified binary encoding"> + <doc> + The bin72 type consists of nine consecutive octets of opaque binary data. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET 1 OCTET 1 OCTET 1 OCTET + +-----------+-----------+-----+-------------+------------+ + | octet-one | octet-two | ... | octet-eight | octet-nine | + +-----------+-----------+-----+-------------+------------+ + </doc> + + <doc type="bnf"> + bin64 = 9 OCTET + </doc> + </type> + + <type name="dec64" code="0xd8" fixed-width="9" + label="64-bit decimal value (e.g. for use in financial values)"> + <doc> + The dec64 type is decimal value with a variable number of digits following the decimal point. + It is encoded as an 8-bit unsigned integral value representing the number of decimal places. + This is followed by the signed integral value encoded using a 64-bit two's complement + representation in network byte order. + </doc> + + <doc> + The former value is referred to as the exponent of the divisor. The latter value is the + mantissa. The decimal value is given by: mantissa / 10^exponent. + </doc> + + <doc type="picture" title="Wire Format"> + 1 OCTET 8 OCTETs + +----------+----------+ + | exponent | mantissa | + +----------+----------+ + uint8 int64 + </doc> + + <doc type="bnf"> + dec64 = exponent mantissa + exponent = uint8 + mantissa = int64 + </doc> + </type> + + <!-- + 0xe0 - 0xef: Reserved + --> + + <!-- + 0xf0 - 0xff: Zero-length types + --> + + <type name="void" code="0xf0" fixed-width="0" label="the void type"> + <doc> + The void type is used within tagged data structures such as maps and lists to indicate an + empty value. The void type has no value and is encoded as an empty sequence of octets. + </doc> + </type> + + <type name="bit" code="0xf1" fixed-width="0" label="presence indicator"> + <doc> + The bit type is used to indicate that a packing flag within a packed struct is being used to + represent a boolean value based on the presence of an empty value. The bit type has no value + and is encoded as an empty sequence of octets. + </doc> + </type> + + <!-- + ====================================================== + == CONSTANTS + ====================================================== + --> + + <!-- Protocol constants --> + + <constant name="MIN-MAX-FRAME-SIZE" value="4096" label="The minimum size (in bytes) which can be + agreed upon as the maximum frame size."> + <doc> + During the initial connection negotiation, the two peers must agree upon a maximum frame size. + This constant defines the minimum value to which the maximum frame size can be set. By + defining this value, the peers can guarantee that they can send frames of up to this size + until they have agreed a definitive maximum frame size for that connection. + </doc> + </constant> + + <!-- + ====================================================== + == DOMAIN TYPES + ====================================================== + --> + + <!-- Segment types --> + + <domain name="segment-type" type="uint8" label="valid values for the frame type indicator."> + <doc> + Segments are defined in <xref ref="specification.transport.assemblies_segments_and_frames"/>. + The segment domain defines the valid values that may be used for the segment indicator within + the frame header. + </doc> + + <enum> + <choice name="control" value="0"> + <doc> + The frame type indicator for Control segments (see <xref + ref="specification.formal_notation.controls"/>). + </doc> + </choice> + <choice name="command" value="1"> + <doc> + The frame type indicator for Command segments (see <xref + ref="specification.formal_notation.commands"/>). + </doc> + </choice> + <choice name="header" value="2" > + <doc> + The frame type indicator for Header segments (see <xref + ref="specification.formal_notation.segments.header"/>). + </doc> + </choice> + <choice name="body" value="3" > + <doc> + The frame type indicator for Body segments (see <xref + ref="specification.formal_notation.segments.body"/>). + </doc> + </choice> + </enum> + </domain> + + <!-- Tracks --> + + <domain name="track" type="uint8" label="Valid values for transport level tracks"> + <doc> Tracks are defined in <xref ref="specification.transport.channels_and_tracks"/>. The + track domain defines the valid values that may used for the track indicator within the frame + header</doc> + <enum> + <choice name="control" value="0"> + <doc> + The track used for all controls. All controls defined in this specification MUST be sent + on track 0. + </doc> + </choice> + <choice name="command" value="1"> + <doc> + The track used for all commands. All commands defined in this specification MUST be sent + on track 1. + </doc> + </choice> + </enum> + </domain> + + + <domain name="str16-array" type="array" label="An array of values of type str16."> + <doc> + An array of values of type str16. + </doc> + </domain> + + + + <!-- == Class: connection ==================================================================== --> + + <class name="connection" code="0x1" label="work with connections"> + <doc> + The connection class provides controls for a client to establish a network connection to a + server, and for both peers to operate the connection thereafter. + </doc> + + <doc type="grammar"> + connection = open-connection + *use-connection + close-connection + open-connection = C:protocol-header + S:START C:START-OK + *challenge + S:TUNE C:TUNE-OK + C:OPEN S:OPEN-OK | S:REDIRECT + challenge = S:SECURE C:SECURE-OK + use-connection = *channel + close-connection = C:CLOSE S:CLOSE-OK + / S:CLOSE C:CLOSE-OK + </doc> + + <role name="server" implement="MUST" /> + <role name="client" implement="MUST" /> + + <domain name="close-code" type="uint16" label="code used in the connection.close control to + indicate reason for closure"> + <enum> + <choice name="normal" value="200"> + <doc> + The connection closed normally. + </doc> + </choice> + + <choice name="connection-forced" value="320"> + <doc> + An operator intervened to close the connection for some reason. The client may retry at + some later date. + </doc> + </choice> + + <choice name="invalid-path" value="402"> + <doc> + The client tried to work with an unknown virtual host. + </doc> + </choice> + + <choice name="framing-error" value="501"> + <doc> + A valid frame header cannot be formed from the incoming byte stream. + </doc> + </choice> + </enum> + </domain> + + <domain name="amqp-host-url" type="str16" label="URL for identifying an AMQP Server"> + <doc> + The amqp-url domain defines a format for identifying an AMQP Server. It is used to provide + alternate hosts in the case where a client has to reconnect because of failure, or because + the server requests the client to do so upon initial connection. + </doc> + <doc type="bnf"><![CDATA[ + amqp_url = "amqp:" prot_addr_list + prot_addr_list = [prot_addr ","]* prot_addr + prot_addr = tcp_prot_addr | tls_prot_addr + + tcp_prot_addr = tcp_id tcp_addr + tcp_id = "tcp:" | "" + tcp_addr = [host [":" port] ] + host = <as per http://www.ietf.org/rfc/rfc3986.txt> + port = number]]> + </doc> + </domain> + + <domain name="amqp-host-array" type="array" label="An array of values of type amqp-host-url"> + <doc> + Used to provide a list of alternate hosts. + </doc> + </domain> + + <!-- - Control: connection.start - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <control name="start" code="0x1" label="start connection negotiation"> + <doc> + This control starts the connection negotiation process by telling the client the supported + security mechanisms and locales from which the client can choose. + </doc> + + <rule name="protocol-name"> + <doc> + If the server cannot support the protocol specified in the protocol header, it MUST close + the socket connection without sending any response control. + </doc> + <doc type="scenario"> + The client sends a protocol header containing an invalid protocol name. The server must + respond by closing the connection. + </doc> + </rule> + + <rule name="client-support"> + <doc> + If the client cannot handle the protocol version suggested by the server it MUST close the + socket connection. + </doc> + <doc type="scenario"> + The server sends a protocol version that is lower than any valid implementation, e.g. 0.1. + The client must respond by closing the connection. + </doc> + </rule> + + <implement role="client" handle="MUST" /> + + <response name="start-ok" /> + + <field name="server-properties" type="map" label="server properties"> + <rule name="required-fields"> + <doc> + The properties SHOULD contain at least these fields: "host", specifying the server host + name or address, "product", giving the name of the server product, "version", giving the + name of the server version, "platform", giving the name of the operating system, + "copyright", if appropriate, and "information", giving other general information. + </doc> + <doc type="scenario"> + Client connects to server and inspects the server properties. It checks for the presence + of the required fields. + </doc> + </rule> + </field> + + <field name="mechanisms" type="str16-array" label="available security mechanisms" + required="true"> + <doc> + A list of the security mechanisms that the server supports. + </doc> + </field> + + <field name="locales" type="str16-array" label="available message locales" required="true"> + <doc> + A list of the message locales that the server supports. The locale defines the language in + which the server will send reply texts. + </doc> + + <rule name="required-support"> + <doc> + The server MUST support at least the en_US locale. + </doc> + <doc type="scenario"> + Client connects to server and inspects the locales field. It checks for the presence of + the required locale(s). + </doc> + </rule> + </field> + </control> + + <!-- - Control: connection.start-ok - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <control name="start-ok" code="0x2" label="select security mechanism and locale"> + <doc> + This control selects a SASL security mechanism. + </doc> + + <implement role="server" handle="MUST" /> + + <field name="client-properties" type="map" label="client properties"> + <rule name="required-fields"> + <!-- This rule is not testable from the client side --> + <doc> + The properties SHOULD contain at least these fields: "product", giving the name of the + client product, "version", giving the name of the client version, "platform", giving the + name of the operating system, "copyright", if appropriate, and "information", giving + other general information. + </doc> + </rule> + </field> + + <field name="mechanism" type="str8" label="selected security mechanism" required="true"> + <doc> + A single security mechanisms selected by the client, which must be one of those specified + by the server. + </doc> + + <rule name="security"> + <doc> + The client SHOULD authenticate using the highest-level security profile it can handle + from the list provided by the server. + </doc> + </rule> + + <rule name="validity"> + <doc> + If the mechanism field does not contain one of the security mechanisms proposed by the + server in the Start control, the server MUST close the connection without sending any + further data. + </doc> + <doc type="scenario"> + Client connects to server and sends an invalid security mechanism. The server must + respond by closing the connection (a socket close, with no connection close + negotiation). + </doc> + </rule> + </field> + + <field name="response" type="vbin32" label="security response data" required="true"> + <doc> + A block of opaque data passed to the security mechanism. The contents of this data are + defined by the SASL security mechanism. + </doc> + </field> + + <field name="locale" type="str8" label="selected message locale" required="true"> + <doc> + A single message locale selected by the client, which must be one of those specified by + the server. + </doc> + </field> + </control> + + <!-- - Control: connection.secure - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <control name="secure" code="0x3" label="security mechanism challenge"> + <doc> + The SASL protocol works by exchanging challenges and responses until both peers have + received sufficient information to authenticate each other. This control challenges the + client to provide more information. + </doc> + + <implement role="client" handle="MUST" /> + + <response name="secure-ok" /> + + <field name="challenge" type="vbin32" label="security challenge data" required="true"> + <doc> + Challenge information, a block of opaque binary data passed to the security mechanism. + </doc> + </field> + </control> + + <!-- - Control: connection.secure-ok - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <control name="secure-ok" code="0x4" label="security mechanism response"> + <doc> + This control attempts to authenticate, passing a block of SASL data for the security + mechanism at the server side. + </doc> + + <implement role="server" handle="MUST" /> + + <field name="response" type="vbin32" label="security response data" required="true"> + <doc> + A block of opaque data passed to the security mechanism. The contents of this data are + defined by the SASL security mechanism. + </doc> + </field> + </control> + + <!-- - Control: connection.tune - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <control name="tune" code="0x5" label="propose connection tuning parameters"> + <doc> + This control proposes a set of connection configuration values to the client. The client can + accept and/or adjust these. + </doc> + + <implement role="client" handle="MUST" /> + + <response name="tune-ok" /> + + <field name="channel-max" type="uint16" label="proposed maximum channels"> + <doc> + The maximum total number of channels that the server allows per connection. If this is not + set it means that the server does not impose a fixed limit, but the number of allowed + channels may be limited by available server resources. + </doc> + </field> + + <field name="max-frame-size" type="uint16" label="proposed maximum frame size"> + <doc> + The largest frame size that the server proposes for the connection. The client can + negotiate a lower value. If this is not set means that the server does not impose any + specific limit but may reject very large frames if it cannot allocate resources for them. + </doc> + + <rule name="minimum"> + <doc> + Until the max-frame-size has been negotiated, both peers MUST accept frames of up to + MIN-MAX-FRAME-SIZE octets large, and the minimum negotiated value for max-frame-size is + also MIN-MAX-FRAME-SIZE. + </doc> + <doc type="scenario"> + Client connects to server and sends a large properties field, creating a frame of + MIN-MAX-FRAME-SIZE octets. The server must accept this frame. + </doc> + </rule> + </field> + + <field name="heartbeat-min" type="uint16" label="the minimum supported heartbeat delay"> + <doc> + The minimum delay, in seconds, of the connection heartbeat supported by the server. If + this is not set it means the server does not support sending heartbeats. + </doc> + </field> + + <field name="heartbeat-max" type="uint16" label="the maximum supported heartbeat delay"> + <doc> + The maximum delay, in seconds, of the connection heartbeat supported by the server. If + this is not set it means the server has no maximum. + </doc> + + <rule name="permitted-range"> + <doc> + The heartbeat-max value must be greater than or equal to the value supplied in the + heartbeat-min field. + </doc> + </rule> + + <rule name="no-heartbeat-min"> + <doc> + If no heartbeat-min is supplied, then the heartbeat-max field MUST remain empty. + </doc> + </rule> + </field> + </control> + + <!-- - Control: connection.tune-ok - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <control name="tune-ok" code="0x6" label="negotiate connection tuning parameters"> + <doc> + This control sends the client's connection tuning parameters to the server. Certain fields + are negotiated, others provide capability information. + </doc> + + <implement role="server" handle="MUST" /> + + <field name="channel-max" type="uint16" label="negotiated maximum channels" required="true"> + <doc> + The maximum total number of channels that the client will use per connection. + </doc> + + <rule name="upper-limit"> + <doc> + If the client specifies a channel max that is higher than the value provided by the + server, the server MUST close the connection without attempting a negotiated close. The + server may report the error in some fashion to assist implementers. + </doc> + + </rule> + + <rule name="available-channels"> + <doc> + If the client agrees to a channel-max of N channels, then the channels available for + communication between client and server are precisely the channels numbered 0 to (N-1). + </doc> + </rule> + </field> + + <field name="max-frame-size" type="uint16" label="negotiated maximum frame size"> + <doc> + The largest frame size that the client and server will use for the connection. If it is + not set means that the client does not impose any specific limit but may reject very large + frames if it cannot allocate resources for them. Note that the max-frame-size limit + applies principally to content frames, where large contents can be broken into frames of + arbitrary size. + </doc> + + <rule name="minimum"> + <doc> + Until the max-frame-size has been negotiated, both peers MUST accept frames of up to + MIN-MAX-FRAME-SIZE octets large, and the minimum negotiated value for max-frame-size is + also MIN-MAX-FRAME-SIZE. + </doc> + </rule> + + <rule name="upper-limit"> + <doc> + If the client specifies a max-frame-size that is higher than the value provided by the + server, the server MUST close the connection without attempting a negotiated close. The + server may report the error in some fashion to assist implementers. + </doc> + </rule> + + <rule name="max-frame-size"> + <doc> + A peer MUST NOT send frames larger than the agreed-upon size. A peer that receives an + oversized frame MUST close the connection with the framing-error close-code. + </doc> + </rule> + </field> + + <field name="heartbeat" type="uint16" label="negotiated heartbeat delay"> + <doc> + The delay, in seconds, of the connection heartbeat chosen by the client. If it is not set + it means the client does not want a heartbeat. + </doc> + + <rule name="permitted-range"> + <doc> + The chosen heartbeat MUST be in the range supplied by the heartbeat-min and + heartbeat-max fields of connection.tune. + </doc> + </rule> + + <rule name="no-heartbeat-min"> + <doc> + The heartbeat field MUST NOT be set if the heartbeat-min field of connection.tune was + not set by the server. + </doc> + </rule> + </field> + </control> + + <!-- - Control: connection.open - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <control name="open" code="0x7" label="open connection to virtual host"> + <doc> + This control opens a connection to a virtual host, which is a collection of resources, and + acts to separate multiple application domains within a server. The server may apply + arbitrary limits per virtual host, such as the number of each type of entity that may be + used, per connection and/or in total. + </doc> + + <implement role="server" handle="MUST" /> + + <response name="open-ok" /> + <response name="redirect" /> + + <field name="virtual-host" type="str8" label="virtual host name" required="true"> + <doc> + The name of the virtual host to work with. + </doc> + + <rule name="separation"> + <doc> + If the server supports multiple virtual hosts, it MUST enforce a full separation of + exchanges, queues, and all associated entities per virtual host. An application, + connected to a specific virtual host, MUST NOT be able to access resources of another + virtual host. + </doc> + </rule> + + <rule name="security"> + <doc> + The server SHOULD verify that the client has permission to access the specified virtual + host. + </doc> + </rule> + </field> + + <field name="capabilities" type="str16-array" label="required capabilities"> + <doc> + The client can specify zero or more capability names. The server can use this to determine + how to process the client's connection request. + </doc> + </field> + + <field name="insist" type="bit" label="insist on connecting to server"> + <doc> + In a configuration with multiple collaborating servers, the server may respond to a + connection.open control with a Connection.Redirect. The insist option tells the server + that the client is insisting on a connection to the specified server. + </doc> + <rule name="behavior"> + <doc> + When the client uses the insist option, the server MUST NOT respond with a + Connection.Redirect control. If it cannot accept the client's connection request it + should respond by closing the connection with a suitable reply code. + </doc> + </rule> + </field> + </control> + + <!-- - Control: connection.open-ok - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <control name="open-ok" code="0x8" label="signal that connection is ready"> + <doc> + This control signals to the client that the connection is ready for use. + </doc> + + <implement role="client" handle="MUST" /> + + <field name="known-hosts" type="amqp-host-array" label="alternate hosts which may be used in + the case of failure"> + <doc> + Specifies an array of equivalent or alternative hosts that the server knows about, which + will normally include the current server itself. Each entry in the array will be in the + form of an IP address or DNS name, optionally followed by a colon and a port number. + Clients can cache this information and use it when reconnecting to a server after a + failure. This field may be empty. + </doc> + </field> + </control> + + <!-- - Control: connection.redirect - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <control name="redirect" code="0x9" label="redirects client to other server"> + <doc> + This control redirects the client to another server, based on the requested virtual host + and/or capabilities. + </doc> + + <rule name="usage"> + <doc> + When getting the connection.redirect control, the client SHOULD reconnect to the host + specified, and if that host is not present, to any of the hosts specified in the + known-hosts list. + </doc> + </rule> + + <implement role="client" handle="MUST" /> + + <field name="host" type="amqp-host-url" label="server to connect to" required="true"> + <doc> + Specifies the server to connect to. + </doc> + </field> + + <field name="known-hosts" type="amqp-host-array" label="alternate hosts to try in case of + failure"> + <doc> + An array of equivalent or alternative hosts that the server knows about. + </doc> + </field> + </control> + + <!-- - Control: connection.heartbeat - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <control name="heartbeat" code="0xa" label="indicates connection is still alive"> + <doc> + The heartbeat control may be used to generate artificial network traffic when a connection + is idle. If a connection is idle for more than twice the negotiated heartbeat delay, the + peers MAY be considered disconnected. + </doc> + </control> + + <!-- - Control: connection.close - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <control name="close" code="0xb" label="request a connection close"> + <doc> + This control indicates that the sender wants to close the connection. The reason for close + is indicated with the reply-code and reply-text. The channel this control is sent on MAY be + used to indicate which channel caused the connection to close. + </doc> + + <implement role="client" handle="MUST" /> + <implement role="server" handle="MUST" /> + + <response name="close-ok" /> + + <field name="reply-code" type="close-code" label="the numeric reply code" + required="true"> + <doc> + Indicates the reason for connection closure. + </doc> + </field> + <field name="reply-text" type="str8" label="the localized reply text"> + <doc> + This text can be logged as an aid to resolving issues. + </doc> + </field> + </control> + + <!-- - Control: connection.close-ok - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <control name="close-ok" code="0xc" label="confirm a connection close"> + <doc> + This control confirms a connection.close control and tells the recipient that it is safe to + release resources for the connection and close the socket. + </doc> + + <rule name="reporting"> + <doc> + A peer that detects a socket closure without having received a Close-Ok handshake control + SHOULD log the error. + </doc> + </rule> + + <implement role="client" handle="MUST" /> + <implement role="server" handle="MUST" /> + </control> + + </class> + + <!-- == Class: session ======================================================================= --> + + <class name="session" code="0x2" label="session controls"> + <doc> + A session is a named interaction between two peers. Session names are chosen by the upper + layers and may be used indefinitely. The model layer may associate long-lived or durable state + with a given session name. The session layer provides transport of commands associated with + this interaction. + </doc> + + <doc> + The controls defined within this class are specified in terms of the "sender" of commands and + the "receiver" of commands. Since both client and server send and receive commands, the + overall session dialog is symmetric, however the semantics of the session controls are defined + in terms of a single sender/receiver pair, and it is assumed that the client and server will + each contain both a sender and receiver implementation. + </doc> + + <rule name="attachment"> + <doc> + The transport MUST be attached in order to use any control other than "attach", "attached", + "detach", or "detached". A peer receiving any other control on a detached transport MUST + discard it and send a session.detached with the "not-attached" reason code. + </doc> + </rule> + + <role name="server" implement="MUST" /> + <role name="client" implement="MUST" /> + + <role name="sender" implement="MUST"> + <doc> + The sender of commands. + </doc> + </role> + <role name="receiver" implement="MUST"> + <doc> + The receiver of commands. + </doc> + </role> + + <domain name="name" type="vbin16" label="opaque session name"> + <doc> + The session name uniquely identifies an interaction between two peers. It is scoped to a + given authentication principal. + </doc> + </domain> + + <domain name="detach-code" type="uint8" label="reason for detach"> + <enum> + <choice name="normal" value="0"> + <doc> + The session was detached by request. + </doc> + </choice> + <choice name="session-busy" value="1"> + <doc> + The session is currently attached to another transport. + </doc> + </choice> + <choice name="transport-busy" value="2"> + <doc> + The transport is currently attached to another session. + </doc> + </choice> + <choice name="not-attached" value="3"> + <doc> + The transport is not currently attached to any session. + </doc> + </choice> + <choice name="unknown-ids" value="4"> + <doc> + Command data was received prior to any use of the command-point control. + </doc> + </choice> + </enum> + </domain> + + <domain name="commands" type="sequence-set" label="identifies a set of commands"> + </domain> + + <struct name="header" size="1" pack="1"> + <doc> + The session header appears on commands after the class and command id, but prior to command + arguments. + </doc> + + <field name="sync" type="bit" label="request notification of completion"> + <doc> + Request notification of completion for this command. + </doc> + </field> + </struct> + + <struct name="command-fragment" size="0" pack="0" label="byte-ranges within a set of commands"> + + <field name="command-id" type="sequence-no" required="true"> + + </field> + <field name="byte-ranges" type="byte-ranges" required="true"> + + </field> + </struct> + + <domain name="command-fragments" type="array" label="an array of values of type + command-fragment"/> + + <control name="attach" code="0x1" label="attach to the named session"> + <doc> + Requests that the current transport be attached to the named session. Success or failure + will be indicated with an attached or detached response. This control is idempotent. + </doc> + + <rule name="one-transport-per-session"> + <doc> + A session MUST NOT be attached to more than one transport at a time. + </doc> + </rule> + + <rule name="one-session-per-transport"> + <doc> + A transport MUST NOT be attached to more than one session at a time. + </doc> + </rule> + + <rule name="idempotence"> + <doc> + Attaching a session to its current transport MUST succeed and result in an attached + response. + </doc> + </rule> + + <rule name="scoping"> + <doc> + Attachment to the same session name from distinct authentication principals MUST succeed. + </doc> + </rule> + + <implement role="server" handle="MUST" /> + <implement role="client" handle="MAY" /> + + <response name="attached"/> + <response name="detached"/> + + <field name="name" type="name" label="the session name" required="true"> + <doc> + Identifies the session to be attached to the current transport. + </doc> + </field> + + <field name="force" type="bit" label="force attachment to a busy session"> + <doc> + If set then a busy session will be forcibly detached from its other transport and + reattached to the current transport. + </doc> + </field> + </control> + + <control name="attached" code="0x2" label="confirm attachment to the named session"> + <doc> + Confirms successful attachment of the transport to the named session. + </doc> + + <implement role="server" handle="MUST" /> + <implement role="client" handle="MUST" /> + + <field name="name" type="name" label="the session name" required="true"> + <doc> + Identifies the session now attached to the current transport. + </doc> + </field> + </control> + + <control name="detach" code="0x3" label="detach from the named session"> + <doc> + Detaches the current transport from the named session. + </doc> + + <implement role="server" handle="MUST" /> + <implement role="client" handle="MUST" /> + + <response name="detached"/> + + <field name="name" type="name" label="the session name" required="true"> + <doc> + Identifies the session to detach. + </doc> + </field> + </control> + + <control name="detached" code="0x4" label="confirm detachment from the named session"> + <doc> + Confirms detachment of the current transport from the named session. + </doc> + + <implement role="server" handle="MUST" /> + <implement role="client" handle="MUST" /> + + <field name="name" type="name" label="the session name" required="true"> + <doc> + Identifies the detached session. + </doc> + </field> + <field name="code" type="detach-code" label="the reason for detach" required="true"> + <doc> + Identifies the reason for detaching from the named session. + </doc> + </field> + </control> + + <!-- + Execution state is the set of confirmed, and completed incoming commands, as well as the set + of outgoing in-doubt commands held for replay. + --> + + <control name="request-timeout" code="0x5" label="requests the execution timeout be changed"> + <doc> + This control may be sent by either the sender or receiver of commands. It requests that the + execution timeout be changed. This is the minimum amount of time that a peer must preserve + execution state for a detached session. + </doc> + + <rule name="maximum-granted-timeout"> + <doc> + The handler of this request MUST set his timeout to the maximum allowed value less than or + equal to the requested timeout, and MUST convey the chosen timeout in the response. + </doc> + </rule> + + <implement role="sender" handle="MUST" /> + <implement role="receiver" handle="MUST" /> + + <response name="timeout"/> + + <field name="timeout" type="uint32" label="the requested timeout"> + <doc> + The requested timeout for execution state in seconds. If not set, this control requests + that execution state is preserved indefinitely. + </doc> + </field> + </control> + + <control name="timeout" code="0x6" label="the granted timeout"> + <doc> + This control may be sent by the either the sender or receiver of commands. It is a + one-to-one reply to the request-timeout control that indicates the granted timeout for + execution state. + </doc> + + <implement role="sender" handle="MUST" /> + <implement role="receiver" handle="MUST" /> + + <field name="timeout" type="uint32" label="the execution timeout"> + <doc> + The timeout for execution state. If not set, then execution state is preserved + indefinitely. + </doc> + </field> + </control> + + <control name="command-point" code="0x7" + label="the command id and byte offset of subsequent data"> + <doc> + This control is sent by the sender of commands and handled by the receiver of commands. This + establishes the sequence numbers associated with all subsequent command data sent from the + sender to the receiver. The subsequent command data will be numbered starting with the + values supplied in this control and proceeding sequentially. This must be used at least once + prior to sending any command data on newly attached transports. + </doc> + + <rule name="newly-attached-transports"> + <doc> + If command data is sent on a newly attached transport the session MUST be detached with an + "unknown-id" reason-code. + </doc> + </rule> + + <rule name="zero-offset"> + <doc> + If the offset is zero, the next data frame MUST have the first-frame and first-segment + flags set. Violation of this is a framing error. + </doc> + </rule> + + <rule name="nonzero-offset"> + <doc> + If the offset is nonzero, the next data frame MUST NOT have both the first-frame and + first-segment flag set. Violation of this is a framing error. + </doc> + </rule> + + <implement role="receiver" handle="MUST" /> + + <field name="command-id" type="sequence-no" label="the command-id of the next command" + required="true"/> + <field name="command-offset" type="uint64" label="the byte offset within the command" + required="true"/> + </control> + + <control name="expected" code="0x8" label="informs the peer of expected commands"> + <doc> + This control is sent by the receiver of commands and handled by the sender of commands. It + informs the sender of what commands and command fragments are expected at the receiver. + This control is only sent in response to a flush control with the expected flag set. The + expected control is never sent spontaneously. + </doc> + + <rule name="include-next-command"> + <doc> + The set of expected commands MUST include the next command after the highest seen command. + </doc> + </rule> + + <rule name="commands-empty-means-new-session"> + <doc> + The set of expected commands MUST have zero elements if and only if the sender holds no + execution state for the session (i.e. it is a new session). + </doc> + </rule> + + <rule name="no-overlaps"> + <doc> + If a command-id appears in the commands field, it MUST NOT appear in the fragments field. + </doc> + </rule> + + <rule name="minimal-fragments"> + <doc> + When choice is permitted, a command MUST appear in the commands field rather than the + fragments field. + </doc> + </rule> + + <implement role="sender" handle="MUST" /> + + <field name="commands" type="commands" label="expected commands" required="true"/> + <field name="fragments" type="command-fragments" label="expected fragments" /> + </control> + + <control name="confirmed" code="0x9" label="notifies of confirmed commands"> + <doc> + This control is sent by the receiver of commands and handled by the sender of commands. This + sends the set of commands that will definitely be completed by this peer to the sender. This + excludes commands known by the receiver to be considered confirmed or complete at the + sender. + </doc> + <doc> + This control must be sent if the partner requests the set of confirmed commands using the + session.flush control with the confirmed flag set. + </doc> + <doc> + This control may be sent spontaneously. One reason for separating confirmation from + completion is for large persistent messages, where the receipt (and storage to a durable + store) of part of the message will result in less data needing to be replayed in the case of + transport failure during transmission. + </doc> + <doc> + A simple implementation of an AMQP client or server may be implemented to take no action on + receipt of session.confirmed controls, and take action only when receiving + session.completed controls. + </doc> + <doc> + A simple implementation of an AMQP client or server may be implemented such that it never + spontaneously sends session.confirmed and that when requested for the set of confirmed + commands (via the session.flush control) it responds with the same set of commands as it + would to when the set of completed commands was requested (trivially all completed commands + are confirmed). + </doc> + + <rule name="durability"> + <doc> + If a command has durable implications, it MUST NOT be confirmed until the fact of the + command has been recorded on durable media. + </doc> + </rule> + + <rule name="no-overlaps"> + <doc> + If a command-id appears in the commands field, it MUST NOT appear in the fragments field. + </doc> + </rule> + + <rule name="minimal-fragments"> + <doc> + When choice is permitted, a command MUST appear in the commands field rather than the + fragments field. + </doc> + </rule> + + <implement role="sender" handle="MUST" /> + + <field name="commands" type="commands" label="entirely confirmed commands"> + <rule name="exclude-known-complete"> + <doc> + Command-ids included in prior known-complete replies MUST be excluded from the set of + all confirmed commands. + </doc> + </rule> + </field> + <field name="fragments" type="command-fragments" label="partially confirmed commands"/> + </control> + + <control name="completed" code="0xa" label="notifies of command completion"> + <doc> + This control is sent by the receiver of commands, and handled by the sender of commands. It + informs the sender of all commands completed by the receiver. This excludes commands known + by the receiver to be considered complete at the sender. + </doc> + + <rule name="known-completed-reply"> + <doc> + The sender MUST eventually reply with a known-completed set that covers the completed ids. + </doc> + </rule> + + <rule name="delayed-reply"> + <doc> + The known-complete reply MAY be delayed at the senders discretion if the timely-reply + field is not set. + </doc> + </rule> + + <rule name="merged-reply"> + <doc> + Multiple replies may be merged by sending a single known-completed that includes the union + of the merged command-id sets. + </doc> + </rule> + + <implement role="sender" handle="MUST" /> + + <field name="commands" type="commands" label="completed commands"> + <doc> + The ids of all completed commands. This excludes commands known by the receiver to be + considered complete at the sender. + </doc> + + <rule name="completed-implies-confirmed"> + <doc> + The sender MUST consider any completed commands to also be confirmed. + </doc> + </rule> + + <rule name="exclude-known-complete"> + <doc> + Command-ids included in prior known-complete replies MUST be excluded from the set of + all completed commands. + </doc> + </rule> + </field> + <field name="timely-reply" type="bit"> + <doc> + If set, the sender is no longer free to delay the known-completed reply. + </doc> + </field> + </control> + + <control name="known-completed" code="0xb" label="Inform peer of which commands are known to be + completed"> + <doc> + This control is sent by the sender of commands, and handled by the receiver of commands. It + is sent in reply to one or more completed controls from the receiver. It informs the + receiver that commands are known to be completed by the sender. + </doc> + + <rule name="stateless"> + <doc> + The sender need not keep state to generate this reply. It is sufficient to reply to any + completed control with an exact echo of the completed ids. + </doc> + </rule> + + <implement role="receiver" handle="MUST" /> + + <field name="commands" type="commands" label="commands known to be complete"> + <doc> + The set of completed commands for one or more session.completed controls. + </doc> + + <rule name="known-completed-implies-known-confirmed"> + <doc> + The receiver MUST treat any of the specified commands to be considered by the sender as + confirmed as well as completed. + </doc> + </rule> + </field> + </control> + + <control name="flush" code="0xc" label="requests a session.completed"> + <doc> + This control is sent by the sender of commands and handled by the receiver of commands. It + requests that the receiver produce the indicated command sets. The receiver should issue the + indicated sets at the earliest possible opportunity. + </doc> + + <implement role="receiver" handle="MUST" /> + + <field name="expected" type="bit" label="request notification of expected commands"/> + <field name="confirmed" type="bit" label="request notification of confirmed commands"/> + <field name="completed" type="bit" label="request notification of completed commands"/> + </control> + + <control name="gap" code="0xd" label="indicates missing segments in the stream"> + <doc> + This control is sent by the sender of commands and handled by the receiver of commands. It + sends command ranges for which there will be no further data forthcoming. The receiver + should proceed with the next available commands that arrive after the gap. + </doc> + + <rule name="gap-confirmation-and-completion"> + <doc> + The command-ids covered by a session.gap MUST be added to the completed and confirmed sets + by the receiver. + </doc> + </rule> + + <rule name="aborted-commands"> + <doc> + If a session.gap covers a partially received command, the receiving peer MUST treat the + command as aborted. + </doc> + </rule> + + <rule name="completed-or-confirmed-commands"> + <doc> + If a session.gap covers a completed or confirmed command, the receiving peer MUST continue + to treat the command as completed or confirmed. + </doc> + </rule> + + <implement role="receiver" handle="MUST" /> + + <field name="commands" type="commands"> + <doc> + The set of command-ids that are contained in this gap. + </doc> + </field> + </control> + + </class> + + <!-- == Class: execution ===================================================================== --> + + <class name="execution" code="0x3" label="execution commands"> + <doc> + The execution class provides commands that carry execution information about other model level + commands. + </doc> + + <role name="server" implement="MUST"/> + <role name="client" implement="MUST"/> + + <domain name="error-code" type="uint16"> + <enum> + <choice name="unauthorized-access" value="403"> + <doc> + The client attempted to work with a server entity to which it has no access due to + security settings. + </doc> + </choice> + + <choice name="not-found" value="404"> + <doc> + The client attempted to work with a server entity that does not exist. + </doc> + </choice> + + <choice name="resource-locked" value="405"> + <doc> + The client attempted to work with a server entity to which it has no access because + another client is working with it. + </doc> + </choice> + + <choice name="precondition-failed" value="406"> + <doc> + The client requested a command that was not allowed because some precondition failed. + </doc> + </choice> + + <choice name="resource-deleted" value="408"> + <doc> + A server entity the client is working with has been deleted. + </doc> + </choice> + + <choice name="illegal-state" value="409"> + <doc> + The peer sent a command that is not permitted in the current state of the session. + </doc> + </choice> + + <choice name="command-invalid" value="503"> + <doc> + The command segments could not be decoded. + </doc> + </choice> + + <choice name="resource-limit-exceeded" value="506"> + <doc> + The client exceeded its resource allocation. + </doc> + </choice> + + <choice name="not-allowed" value="530"> + <doc> + The peer tried to use a command a manner that is inconsistent with the rules described + in the specification. + </doc> + </choice> + + <choice name="illegal-argument" value="531"> + <doc> + The command argument is malformed, i.e. it does not fall within the specified domain. + The illegal-argument exception can be raised on execution of any command which has + domain valued fields. + </doc> + </choice> + + <choice name="not-implemented" value="540"> + <doc> + The peer tried to use functionality that is not implemented in its partner. + </doc> + </choice> + + <choice name="internal-error" value="541"> + <doc> + The peer could not complete the command because of an internal error. The peer may + require intervention by an operator in order to resume normal operations. + </doc> + </choice> + + <choice name="invalid-argument" value="542"> + <doc> + An invalid argument was passed to a command, and the operation could not + proceed. An invalid argument is not illegal (see illegal-argument), i.e. it matches + the domain definition; however the particular value is invalid in this context. + </doc> + </choice> + </enum> + </domain> + + <!-- - Command: execution.sync - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="sync" code="0x1" label="request notification of completion for issued commands"> + <doc> + This command is complete when all prior commands are completed. + </doc> + + <implement role="server" handle="MUST"/> + <implement role="client" handle="MUST"/> + </command> + + <!-- - Command: execution.result - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="result" code="0x2" label="carries execution results"> + <doc> + This command carries data resulting from the execution of a command. + </doc> + + <implement role="server" handle="MUST"/> + <implement role="client" handle="MUST"/> + + <field name="command-id" type="sequence-no" required="true"/> + <field name="value" type="struct32"/> + </command> + + <!-- - Command: execution.exception - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="exception" code="0x3" label="notifies a peer of an execution error"> + <doc> + This command informs a peer of an execution exception. The command-id, when given, + correlates the error to a specific command. + </doc> + + <implement role="client" handle="MUST"/> + <implement role="server" handle="MUST"/> + + <field name="error-code" type="error-code" required="true" label="error code indicating the + type of error"/> + <field name="command-id" type="sequence-no" label="exceptional command"> + <doc> + The command-id of the command which caused the exception. If the exception was not caused + by a specific command, this value is not set. + </doc> + </field> + <field name="class-code" type="uint8" label="the class code of the command whose execution + gave rise to the error (if appropriate)"/> + <field name="command-code" type="uint8" label="the class code of the command whose execution + gave rise to the error (if appropriate)"/> + <field name="field-index" type="uint8" label="index of the exceptional field"> + <doc> + The zero based index of the exceptional field within the arguments to the exceptional + command. If the exception was not caused by a specific field, this value is not set. + </doc> + </field> + <field name="description" type="str16" label="descriptive text on the exception"> + <doc> + The description provided is implementation defined, but MUST be in the language + appropriate for the selected locale. The intention is that this description is suitable + for logging or alerting output. + </doc> + </field> + <field name="error-info" type="map" label="map to carry additional information about the + error"/> + + </command> + + </class> + + <!-- == Class: message ======================================================================= --> + + <class name="message" code="0x4" label="message transfer"> + <doc> + The message class provides commands that support an industry-standard messaging model. + </doc> + + <doc type="picture" title="Transfer States"> + START: + + The message has yet to be sent to the recipient. + + NOT-ACQUIRED: + + The message has been sent to the recipient, but is not + acquired by the recipient. + + ACQUIRED: + + The message has been sent to and acquired by the recipient. + + END: + + The transfer is complete. + </doc> + + <doc type="picture" title="State Transitions"><![CDATA[ + *:TRANSFER (accept-mode=none) *:TRANSFER (acquire-mode=pre-acquired) + +---------------------------------START------------------------------------------+ + | | | + | | *:TRANSFER (acquire-mode=not-acquired) | + | | | + | R:RELEASE \|/ | + | +-------------NOT-ACQUIRED<--+ | + | | | | | R:ACQUIRE (if unavailable) | + | | | +-----+ | + | | | | + | | | R:ACQUIRE (if available) | + | | | | + | | \|/ | + | | ACQUIRED<-------------------------------------------+ + | | | + | | | R:ACCEPT / R:REJECT / R:RELEASE + | | | + | | \|/ + | +------------->END]]> + | /|\ + | | + +-------------------------------+ + </doc> + + <doc type="grammar"> + message = *:TRANSFER [ R:ACQUIRE ] [ R:ACCEPT / R:REJECT / R:RELEASE ] + / *:RESUME + / *:SET-FLOW-MODE + / *:FLOW + / *:STOP + / C:SUBSCRIBE + / C:CANCEL + / C:FLUSH + </doc> + + <rule name="persistent-message"> + <doc> + The server SHOULD respect the delivery-mode property of messages and SHOULD make a + best-effort to hold persistent messages on a reliable storage mechanism. + </doc> + <doc type="scenario"> + Send a persistent message to queue, stop server, restart server and then verify whether + message is still present. Assumes that queues are durable. Persistence without durable + queues makes no sense. + </doc> + </rule> + + <rule name="no-persistent-message-discard"> + <doc> + The server MUST NOT discard a persistent message in case of a queue overflow. + </doc> + <doc type="scenario"> + Create a queue overflow situation with persistent messages and verify that messages do not + get lost (presumably the server will write them to disk). + </doc> + </rule> + + <rule name="throttling"> + <doc> + The server MAY use the message.flow command to slow or stop a message publisher when + necessary. + </doc> + </rule> + + <rule name="non-persistent-message-overflow"> + <doc> + The server MAY overflow non-persistent messages to persistent storage. + </doc> + </rule> + + <rule name="non-persistent-message-discard"> + <doc> + The server MAY discard or dead-letter non-persistent messages on a priority basis if the + queue size exceeds some configured limit. + </doc> + </rule> + + <rule name="min-priority-levels"> + <doc> + The server MUST implement at least 2 priority levels for messages, where priorities 0 and + 9 are treated as two distinct levels. + </doc> + </rule> + + <rule name="priority-level-implementation"> + <doc> + The server SHOULD implement distinct priority levels in the following manner: + </doc> + <doc> + If the server implements n distinct priorities then priorities 0 to 5 - ceiling(n/2) should + be treated equivalently and should be the lowest effective priority. The priorities 4 + + floor(n/2) should be treated equivalently and should be the highest effective priority. The + priorities (5 - ceiling(n/2)) to (4 + floor(n/2)) inclusive must be treated as distinct + priorities. + </doc> + <doc> + Thus, for example, if 2 distinct priorities are implemented, then levels 0 to 4 are + equivalent, and levels 5 to 9 are equivalent and levels 4 and 5 are distinct. If 3 distinct + priorities are implements the 0 to 3 are equivalent, 5 to 9 are equivalent and 3, 4 and 5 + are distinct. + </doc> + <doc> + This scheme ensures that if two priorities are distinct for a server which implements m + separate priority levels they are also distinct for a server which implements n different + priority levels where n > m. + </doc> + </rule> + + <rule name="priority-delivery"> + <doc> + The server MUST deliver messages of the same priority in order irrespective of their + individual persistence. + </doc> + <doc type="scenario"> + Send a set of messages with the same priority but different persistence settings to a queue. + Subscribe and verify that messages arrive in same order as originally published. + </doc> + </rule> + + <role name="server" implement="MUST" /> + <role name="client" implement="MUST" /> + + <domain name="destination" type="str8" label="destination for a message"> + <doc> + Specifies the destination to which the message is to be transferred. + </doc> + </domain> + + <domain name="accept-mode" type="uint8" label="indicates a confirmation mode"> + <doc> + Controls how the sender of messages is notified of successful transfer. + </doc> + + <enum> + <choice name="explicit" value="0"> + <doc> + Successful transfer is signaled by message.accept. An acquired message (whether + acquisition was implicit as in pre-acquired mode or explicit as in not-acquired mode) is + not considered transferred until a message.accept that includes the transfer command is + received. + </doc> + </choice> + + <choice name="none" value="1"> + <doc> + Successful transfer is assumed when accept-mode is "pre-acquired". Messages transferred + with an accept-mode of "not-acquired" cannot be acquired when accept-mode is "none". + </doc> + </choice> + </enum> + </domain> + + <domain name="acquire-mode" type="uint8" label="indicates the transfer mode"> + <doc> + Indicates whether a transferred message can be considered as automatically acquired or + whether an explicit request is necessary in order to acquire it. + </doc> + + <enum> + <choice name="pre-acquired" value="0"> + <doc> + the message is acquired when the transfer starts + </doc> + </choice> + + <choice name="not-acquired" value="1"> + <doc> + the message is not acquired when it arrives, and must be explicitly acquired by the + recipient + </doc> + </choice> + </enum> + </domain> + + <domain name="reject-code" type="uint16" label="reject code for transfer"> + <doc> + Code specifying the reason for a message reject. + </doc> + <enum> + <choice name="unspecified" value="0"> + <doc> + Rejected for an unspecified reason. + </doc> + </choice> + <choice name="unroutable" value="1"> + <doc> + Delivery was attempted but there were no queues which the message could be routed to. + </doc> + </choice> + <choice name="immediate" value="2"> + <doc> + The rejected message had the immediate flag set to true, but at the time of the transfer + at least one of the queues to which it was to be routed did not have any subscriber able + to take the message. + </doc> + </choice> + </enum> + </domain> + + <domain name="resume-id" type="str16"> + <doc> + A resume-id serves to identify partially transferred message content. The id is chosen by + the sender, and must be unique to a given user. A resume-id is not expected to be unique + across users. + </doc> + </domain> + + <domain name="delivery-mode" type="uint8" + label="indicates whether a message should be treated as transient or durable"> + <doc> + + Used to set the reliability requirements for a message which is transferred to the server. + </doc> + <enum> + <choice name="non-persistent" value="1"> + <doc> + A non-persistent message may be lost in event of a failure, but the nature of the + communication is such that an occasional message loss is tolerable. This is the lowest + overhead mode. Non-persistent messages are delivered at most once only. + </doc> + </choice> + + <choice name="persistent" value="2"> + <doc> + A persistent message is one which must be stored on a persistent medium (usually hard + drive) at every stage of delivery so that it will not be lost in event of failure (other + than of the medium itself). This is normally accomplished with some additional overhead. + A persistent message may be delivered more than once if there is uncertainty about the + state of its delivery after a failure and recovery. + </doc> + </choice> + </enum> + </domain> + + <domain name="delivery-priority" type="uint8" + label="indicates the desired priority to assign to a message transfer"> + <doc> + Used to assign a priority to a message transfer. Priorities range from 0 (lowest) to 9 + (highest). + </doc> + <enum> + <choice name="lowest" value="0"> + <doc> + Lowest possible priority message. + </doc> + </choice> + + <choice name="lower" value="1"> + <doc> + Very low priority message + </doc> + </choice> + + <choice name="low" value="2"> + <doc> + Low priority message. + </doc> + </choice> + + <choice name="below-average" value="3"> + <doc> + Below average priority message. + </doc> + </choice> + + <choice name="medium" value="4"> + <doc> + Medium priority message. + </doc> + </choice> + + + <choice name="above-average" value="5"> + <doc> + Above average priority message + </doc> + </choice> + + + <choice name="high" value="6"> + <doc> + High priority message + </doc> + </choice> + + <choice name="higher" value="7"> + <doc> + Higher priority message + </doc> + </choice> + + <choice name="very-high" value="8"> + <doc> + Very high priority message. + </doc> + </choice> + + <choice name="highest" value="9"> + <doc> + Highest possible priority message. + </doc> + </choice> + </enum> + </domain> + + <struct name="delivery-properties" size="4" code="0x1" pack="2"> + <field name="discard-unroutable" type="bit" label="controls discard of unroutable messages"> + <doc> + If set on a message that is not routable the broker can discard it. If not set, an + unroutable message should be handled by reject when accept-mode is explicit; or by routing + to the alternate-exchange if defined when accept-mode is none. + </doc> + </field> + + <field name="immediate" type="bit" label="Consider message unroutable if it cannot be + processed immediately"> + <doc> + If the immediate flag is set to true on a message transferred to a Server, then the + message should be considered unroutable (and not delivered to any queues) if, for any + queue that it is to be routed to according to the standard routing behavior, there is not + a subscription on that queue able to receive the message. The treatment of unroutable + messages is dependent on the value of the discard-unroutable flag. + </doc> + <doc> + The immediate flag is ignored on transferred to a Client. + </doc> + </field> + + <field name="redelivered" type="bit" label="redelivery flag"> + <doc> + This boolean flag indicates that the message may have been previously delivered to this + or another client. + </doc> + <doc> + If the redelivered flag is set on transfer to a Server, then any delivery of the message + from that Server to a Client must also have the redelivered flag set to true. + </doc> + <rule name="implementation"> + <doc> + The server MUST try to signal redelivered messages when it can. When redelivering a + message that was not successfully accepted, the server SHOULD deliver it to the original + client if possible. + </doc> + <doc type="scenario"> + Create a shared queue and publish a message to the queue. Subscribe using explicit + accept-mode, but do not accept the message. Close the session, reconnect, and subscribe + to the queue again. The message MUST arrive with the redelivered flag set. + </doc> + </rule> + <rule name="hinting"> + <doc> + The client should not rely on the redelivered field to detect duplicate messages where + publishers may themselves produce duplicates. A fully robust client should be able to + track duplicate received messages on non-transacted, and locally-transacted sessions. + </doc> + </rule> + </field> + + <field name="priority" type="delivery-priority" label="message priority, 0 to 9" + required="true"> + <doc> Message priority, which can be between 0 and 9. Messages with higher priorities may be + delivered before those with lower priorities. </doc> + </field> + + <field name="delivery-mode" type="delivery-mode" label="message persistence requirement" + required="true"> + <doc> The delivery mode may be non-persistent or persistent. </doc> + </field> + + <field name="ttl" type="uint64" label="time to live in ms"> + <doc> Duration in milliseconds for which the message should be considered "live". If this is + set then a message expiration time will be computed based on the current time plus this + value. Messages that live longer than their expiration time will be discarded (or dead + lettered).</doc> + <rule name="ttl-decrement"> + <doc> + If a message is transferred between brokers before delivery to a final subscriber the + ttl should be decremented before peer to peer transfer and both timestamp and expiration + should be cleared. + </doc> + </rule> + </field> + + <field name="timestamp" type="datetime" label="message timestamp"> + <doc> + The timestamp is set by the broker on arrival of the message. + </doc> + </field> + + <field name="expiration" type="datetime" label="message expiration time"> + <doc> + The expiration header assigned by the broker. After receiving the message the broker sets + expiration to the sum of the ttl specified in the publish command and the current time. + (ttl=expiration - timestamp) + </doc> + </field> + + <field name="exchange" type="exchange.name" label="originating exchange"> + <doc> + Identifies the exchange specified in the destination field of the message.transfer used to + publish the message. This MUST be set by the broker upon receipt of a message. + </doc> + </field> + + <field name="routing-key" type="str8" label="message routing key"> + <doc> + The value of the key determines to which queue the exchange will send the message. The way + in which keys are used to make this routing decision depends on the type of exchange to + which the message is sent. For example, a direct exchange will route a message to a queue + if that queue is bound to the exchange with a binding-key identical to the routing-key of + the message. + </doc> + </field> + + <field name="resume-id" type="resume-id" label="global id for message transfer"> + <doc> + When a resume-id is provided the recipient MAY use it to retain message data should the + session expire while the message transfer is still incomplete. + </doc> + </field> + + <field name="resume-ttl" type="uint64" label="ttl in ms for interrupted message data"> + <doc> + When a resume-ttl is provided the recipient MAY use it has a guideline for how long to + retain the partially complete data when a resume-id is specified. If no resume-id is + specified then this value should be ignored. + </doc> + </field> + </struct> + + <struct name="fragment-properties" size="4" code="0x2" pack="2"> + <doc> + These properties permit the transfer of message fragments. These may be used in conjunction + with byte level flow control to limit the rate at which large messages are received. Only + the first fragment carries the delivery-properties and message-properties. + + Syntactically each fragment appears as a complete message to the lower layers of the + protocol, however the model layer is required to treat all the fragments as a single + message. For example all fragments must be delivered to the same client. In pre-acquired + mode, no message fragments can be delivered by the broker until the entire message has been + received. + </doc> + + <field name="first" type="bit" default="1"> + <doc>True if this fragment contains the start of the message, false otherwise.</doc> + </field> + + <field name="last" type="bit" default="1"> + <doc>True if this fragment contains the end of the message, false otherwise.</doc> + </field> + + <field name="fragment-size" type="uint64"> + <doc>This field may optionally contain the size of the fragment.</doc> + </field> + </struct> + + <struct name="reply-to" size="2" pack="2"> + <doc>The reply-to domain provides a simple address structure for replying to to a message to a + destination within the same virtual-host.</doc> + <field name="exchange" type="exchange.name" label="the name of the exchange to reply to"/> + <field name="routing-key" type="str8" label="the routing-key to use when replying"/> + </struct> + + <struct name="message-properties" size="4" code="0x3" pack="2"> + <field name="content-length" type="uint64" label="length of the body segment in bytes"> + <doc> + The length of the body segment in bytes. + </doc> + </field> + + <field name="message-id" type="uuid" label="application message identifier"> + <doc> + Message-id is an optional property of UUID type which uniquely identifies a message within + the message system. The message producer is usually responsible for setting the + message-id. The server MAY discard a message as a duplicate if the value of the message-id + matches that of a previously received message. Duplicate messages MUST still be accepted + if transferred with an accept-mode of "explicit". + </doc> + + <rule name="unique"> + <doc> + A message-id MUST be unique within a given server instance. A message-id SHOULD be + globally unique (i.e. across different systems). + </doc> + </rule> + + <rule name="immutable"> + <doc> + A message ID is immutable. Once set, a message-id MUST NOT be changed or reassigned, + even if the message is replicated, resent or sent to multiple queues. + </doc> + </rule> + </field> + + <field name="correlation-id" type="vbin16" label="application correlation identifier"> + <doc> + This is a client-specific id that may be used to mark or identify messages between + clients. The server ignores this field. + </doc> + </field> + + <field name="reply-to" type="reply-to" label="destination to reply to"> + <doc> + The destination of any message that is sent in reply to this message. + </doc> + </field> + + <field name="content-type" type="str8" label="MIME content type"> + <doc> + The RFC-2046 MIME type for the message content (such as "text/plain"). This is set by the + originating client. + </doc> + </field> + + <field name="content-encoding" type="str8" label="MIME content encoding"> + <doc> + The encoding for character-based message content. This is set by the originating client. + Examples include UTF-8 and ISO-8859-15. + </doc> + </field> + + <field name="user-id" type="vbin16" label="creating user id"> + <doc> + The identity of the user responsible for producing the message. The client sets this + value, and it is authenticated by the broker. + </doc> + + <rule name="authentication"> + <doc> + The server MUST produce an unauthorized-access exception if the user-id field is set to + a principle for which the client is not authenticated. + </doc> + </rule> + </field> + + <field name="app-id" type="vbin16" label="creating application id"> + <doc> + The identity of the client application responsible for producing the message. + </doc> + </field> + + <field name="application-headers" type="map" label="application specific headers table"> + <doc> + This is a collection of user-defined headers or properties which may be set by the + producing client and retrieved by the consuming client. + </doc> + </field> + </struct> + + <domain name="flow-mode" type="uint8" label="the flow-mode for allocating flow credit"> + <enum> + <choice name="credit" value="0"> + <doc> + Credit based flow control. + </doc> + </choice> + + <choice name="window" value="1"> + <doc> + Window based flow control. + </doc> + </choice> + </enum> + </domain> + + <domain name="credit-unit" type="uint8" label="specifies the unit of credit balance"> + <enum> + <choice name="message" value="0"> + <doc>Indicates a value specified in messages.</doc> + </choice> + <choice name="byte" value="1"> + <doc>Indicates a value specified in bytes.</doc> + </choice> + </enum> + </domain> + + <!-- - Command: message.transfer - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="transfer" code="0x1" label="transfer a message"> + <doc> + This command transfers a message between two peers. When a client uses this command to + publish a message to a broker, the destination identifies a specific exchange. The message + will then be routed to queues as defined by the exchange configuration. + + The client may request a broker to transfer messages to it, from a particular queue, by + issuing a subscribe command. The subscribe command specifies the destination that the broker + should use for any resulting transfers. + </doc> + + <rule name="transactional-publish"> + <doc> + If a transfer to an exchange occurs within a transaction, then it is not available from + the queue until the transaction commits. It is not specified whether routing takes place + when the transfer is received or when the transaction commits. + </doc> + </rule> + + <implement role="server" handle="MUST" /> + <implement role="client" handle="MUST" /> + + + <field name="destination" type="destination" label="message destination"> + <doc> + Specifies the destination to which the message is to be transferred. + </doc> + + <rule name="blank-destination"> + <doc> + The server MUST accept a blank destination to mean the default exchange. + </doc> + </rule> + + <exception name="nonexistent-exchange" error-code="not-found"> + <doc> + If the destination refers to an exchange that does not exist, the peer MUST raise a + session exception. + </doc> + </exception> + </field> + + <field name="accept-mode" type="accept-mode" required="true"> + <doc> + Indicates whether message.accept, session.complete, or nothing at all is required to + indicate successful transfer of the message. + </doc> + </field> + + <field name="acquire-mode" type="acquire-mode" required="true"> + <doc> + Indicates whether or not the transferred message has been acquired. + </doc> + </field> + + <segments> + <header> + <entry type="delivery-properties"/> + <entry type="fragment-properties"/> + <entry type="message-properties"/> + </header> + <body/> + </segments> + </command> + + <!-- - Command: message.accept - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="accept" code="0x2" label="reject a message"> + <doc> + Accepts the message. Once a transfer is accepted, the command-id may no longer be referenced + from other commands. + </doc> + + <rule name="acquisition"> + <doc> + The recipient MUST have acquired a message in order to accept it. + </doc> + </rule> + + <implement role="server" handle="MUST" /> + <implement role="client" handle="MUST" /> + + <field name="transfers" type="session.commands" required="true"> + <doc> + Identifies the messages previously transferred that should be accepted. + </doc> + </field> + </command> + + <!-- - Command: message.reject - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="reject" code="0x3" label="reject a message"> + <doc> + Indicates that the message transfers are unprocessable in some way. A server may reject a + message if it is unroutable. A client may reject a message if it is invalid. A message may + be rejected for other reasons as well. Once a transfer is rejected, the command-id may no + longer be referenced from other commands. + </doc> + + <rule name="alternate-exchange"> + <doc> + When a client rejects a message, the server MUST deliver that message to the + alternate-exchange on the queue from which it was delivered. If no alternate-exchange is + defined for that queue the broker MAY discard the message. + </doc> + </rule> + + <rule name="acquisition"> + <doc> + The recipient MUST have acquired a message in order to reject it. If the message is not + acquired any reject MUST be ignored. + </doc> + </rule> + + <implement role="server" handle="MUST" /> + <implement role="client" handle="MUST" /> + + <field name="transfers" type="session.commands" required="true"> + <doc> + Identifies the messages previously transferred that should be rejected. + </doc> + </field> + <field name="code" type="reject-code" required="true"> + <doc> + Code describing the reason for rejection. + </doc> + </field> + <field name="text" type="str8" label="informational text for message reject"> + <doc> + Text describing the reason for rejection. + </doc> + </field> + </command> + + <!-- - Command: message.release - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="release" code="0x4" label="release a message"> + <doc> + Release previously transferred messages. When acquired messages are released, they become + available for acquisition by any subscriber. Once a transfer is released, the command-id may + no longer be referenced from other commands. + </doc> + + <rule name="ordering"> + <doc> + Acquired messages that have been released MAY subsequently be delivered out of order. + Implementations SHOULD ensure that released messages keep their position with respect to + undelivered messages of the same priority. + </doc> + </rule> + + <implement role="server" handle="MUST" /> + <implement role="client" handle="MAY" /> + + <field name="transfers" type="session.commands" required="true"> + <doc> + Indicates the messages to be released. + </doc> + </field> + <field name="set-redelivered" type="bit" label="mark the released messages as redelivered"> + <doc> + By setting set-redelivered to true, any acquired messages released to a queue with this + command will be marked as redelivered on their next transfer from that queue. If this flag + is not set, then an acquired message will retain its original redelivered status on the + queue. Messages that are not acquired are unaffected by the value of this flag. + </doc> + </field> + </command> + + <!-- - Command: message.acquire - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="acquire" code="0x5" label="acquire messages for consumption"> + <doc> + Acquires previously transferred messages for consumption. The acquired ids (if any) are + sent via message.acquired. + </doc> + + <rule name="one-to-one"> + <doc> + Each acquire MUST produce exactly one message.acquired even if it is empty. + </doc> + </rule> + + <implement role="server" handle="MUST" /> + + <field name="transfers" type="session.commands" required="true"> + <doc> + Indicates the messages to be acquired. + </doc> + </field> + + <result> + <struct name="acquired" size="4" code="0x4" pack="2" label="indicates acquired messages"> + <doc> + Identifies a set of previously transferred messages that have now been acquired. + </doc> + + <field name="transfers" type="session.commands" required="true"> + <doc> + Indicates the acquired messages. + </doc> + </field> + </struct> + </result> + </command> + + <!-- - Command: message.resume - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="resume" code="0x6" label="resume an interrupted message transfer"> + <doc> + This command resumes an interrupted transfer. The recipient should return the amount of + partially transferred data associated with the given resume-id, or zero if there is no data + at all. If a non-zero result is returned, the recipient should expect to receive message + fragment(s) containing the remainder of the interrupted message. + </doc> + + <implement role="server" handle="MUST" /> + <implement role="client" handle="MUST" /> + + <field name="destination" type="destination"> + <doc> + The destination to which the remaining message fragments are transferred. + </doc> + + <exception name="destination-not-found" error-code="not-found"> + <doc>If the destination does not exist, the recipient MUST close the session.</doc> + </exception> + </field> + + <field name="resume-id" type="resume-id" required="true"> + <doc> + The name of the transfer being resumed. + </doc> + + <rule name="unknown-resume-id"> + <doc>If the resume-id is not known, the recipient MUST return an offset of zero.</doc> + </rule> + </field> + + <result> + <struct name="message-resume-result" size="4" code="0x5" pack="2"> + <field name="offset" type="uint64"> + <doc> + Indicates the amount of data already transferred. + </doc> + </field> + </struct> + </result> + </command> + + <!-- - Command: message.subscribe - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="subscribe" code="0x7" label="start a queue subscription"> + <doc> This command asks the server to start a "subscription", which is a request for messages + from a specific queue. Subscriptions last as long as the session they were created on, or + until the client cancels them. </doc> + + <rule name="simultaneous-subscriptions"> + <doc> The server SHOULD support at least 16 subscriptions per queue, and ideally, impose no + limit except as defined by available resources. </doc> + <doc type="scenario"> Create a queue and create subscriptions on that queue until the server + closes the connection. Verify that the number of subscriptions created was at least + sixteen and report the total number. </doc> + </rule> + + <rule name="default-flow-mode"> + <doc> The default flow mode for new subscriptions is window-mode. </doc> + </rule> + + <exception name="queue-deletion" error-code="resource-deleted"> + <doc> + If the queue for this subscription is deleted, any subscribing sessions MUST be closed. + This exception may occur at any time after the subscription has been completed. + </doc> + </exception> + + <exception name="queue-not-found" error-code="not-found"> + <doc> If the queue for this subscription does not exist, then the subscribing session MUST + be closed. </doc> + </exception> + + <rule name="initial-credit"> + <doc> + Immediately after a subscription is created, the initial byte and message credit for that + destination is zero. + </doc> + </rule> + + <implement role="server" handle="MUST"/> + + <field name="queue" type="queue.name" required="true"> + <doc> Specifies the name of the subscribed queue. </doc> + </field> + + <field name="destination" type="destination" label="incoming message destination"> + <doc> The client specified name for the subscription. This is used as the destination for + all messages transferred from this subscription. The destination is scoped to the session. + </doc> + + <exception name="unique-subscriber-destination" error-code="not-allowed"> + <doc> The client MUST NOT specify a destination that refers to an existing subscription on + the same session. </doc> + <doc type="scenario"> Attempt to create two subscriptions on the same session with the + same non-empty destination. </doc> + </exception> + </field> + + <field name="accept-mode" type="accept-mode" required="true"> + <doc> The accept-mode to use for messages transferred from this subscription. </doc> + </field> + + <field name="acquire-mode" type="acquire-mode" required="true"> + <doc> The acquire-mode to use for messages transferred from this subscription. </doc> + </field> + + <field name="exclusive" type="bit" label="request exclusive access"> + <doc> Request an exclusive subscription. This prevents other subscribers from subscribing to + the queue. </doc> + + <exception name="in-use" error-code="resource-locked"> + <doc> The server MUST NOT grant an exclusive subscription to a queue that already has + subscribers. </doc> + <doc type="scenario"> Open two connections to a server, and in one connection create a + shared (non-exclusive) queue and then subscribe to the queue. In the second connection + attempt to subscribe to the same queue using the exclusive option. </doc> + </exception> + </field> + + <field name="resume-id" type="resume-id"> + <doc> Requests that the broker use the supplied resume-id when transferring messages for + this subscription. </doc> + </field> + + <field name="resume-ttl" type="uint64"> + <doc> Requested duration in milliseconds for the broker use as resume-ttl when transferring + messages for this subscription. </doc> + </field> + + <field name="arguments" type="map" label="arguments for vendor extensions"> + <doc> The syntax and semantics of these arguments depends on the providers implementation. + </doc> + </field> + </command> + + <!-- - Command: message.cancel - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="cancel" code="0x8" label="end a queue subscription"> + <doc> + This command cancels a subscription. This does not affect already delivered messages, but it + does mean the server will not send any more messages for that subscription. The client may + receive an arbitrary number of messages in between sending the cancel command and receiving + notification that the cancel command is complete. + </doc> + + <rule name="post-cancel-transfer-resolution"> + <doc> + Canceling a subscription MUST NOT affect pending transfers. A transfer made prior to + canceling transfers to the destination MUST be able to be accepted, released, acquired, or + rejected after the subscription is canceled. + </doc> + </rule> + + <implement role="server" handle="MUST" /> + + <field name="destination" type="destination" required="true"> + <exception name="subscription-not-found" error-code="not-found"> + <doc> + If the subscription specified by the destination is not found, the server MUST close the + session. + </doc> + </exception> + </field> + </command> + + <!-- - Command: message.set-flow-mode - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="set-flow-mode" code="0x9" label="set the flow control mode"> + <doc> + Sets the mode of flow control used for a given destination to either window or credit based + flow control. + + With credit based flow control, the sender of messages continually maintains its current + credit balance with the recipient. The credit balance consists of two values, a message + count, and a byte count. Whenever message data is sent, both counts must be decremented. + If either value reaches zero, the flow of message data must stop. Additional credit is + received via the message.flow command. + + The sender MUST NOT send partial assemblies. This means that if there is not enough byte + credit available to send a complete message, the sender must either wait or use message + fragmentation (see the fragment-properties header struct) to send the first part of the + message data in a complete assembly. + + Window based flow control is identical to credit based flow control, however message + transfer completion implicitly grants a single unit of message credit, and the size of the + message in byte credits for each completed message transfer. Completion of the transfer + command with session.completed is the only way credit is implicitly updated; message.accept, + message.release, message.reject, tx.commit and tx.rollback have no effect on the outstanding + credit balances. + </doc> + + <rule name="byte-accounting"> + <doc> + The byte count is decremented by the payload size of each transmitted frame with segment + type header or body appearing within a message.transfer command. Note that the payload + size is the frame size less the frame header size. + </doc> + </rule> + + <rule name="mode-switching"> + <doc> + Mode switching may only occur if both the byte and message credit balance are zero. There + are three ways for a recipient of messages to be sure that the sender's credit balances + are zero: + + 1) The recipient may send a message.stop command to the sender. When the recipient + receives notification of completion for the message.stop command, it knows that the + sender's credit is zero. + + 2) The recipient may perform the same steps described in (1) with the message.flush + command substituted for the message.stop command. + + 3) Immediately after a subscription is created with message.subscribe, the credit for + that destination is zero. + </doc> + </rule> + + <rule name="default-flow-mode"> + <doc> + Prior to receiving an explicit set-flow-mode command, a peer MUST consider the flow-mode + to be window. + </doc> + </rule> + + <implement role="server" handle="MUST" /> + <implement role="client" handle="MUST" /> + + <field name="destination" type="destination"/> + <field name="flow-mode" type="flow-mode" required="true"> + <doc> + The new flow control mode. + </doc> + </field> + </command> + + <!-- - Command: message.flow - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="flow" code="0xa" label="control message flow"> + <doc> + This command controls the flow of message data to a given destination. It is used by the + recipient of messages to dynamically match the incoming rate of message flow to its + processing or forwarding capacity. Upon receipt of this command, the sender must add "value" + number of the specified unit to the available credit balance for the specified destination. + A value of (0xFFFFFFFF) indicates an infinite amount of credit. This disables any limit for + the given unit until the credit balance is zeroed with message.stop or message.flush. + </doc> + + <!-- throws no-such-destination --> + + <implement role="server" handle="MUST" /> + <implement role="client" handle="MUST" /> + + <field name="destination" type="destination"/> + <field name="unit" type="credit-unit" required="true"> + <doc> + The unit of value. + </doc> + </field> + <field name="value" type="uint32"> + <doc> + If the value is not set then this indicates an infinite amount of credit. + </doc> + </field> + </command> + + <!-- - Command: message.flush - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="flush" code="0xb" label="force the sending of available messages"> + <doc> + Forces the sender to exhaust his credit supply. The sender's credit will always be zero when + this command completes. The command completes when immediately available message data has + been transferred, or when the credit supply is exhausted. + </doc> + + <implement role="server" handle="MUST" /> + + <field name="destination" type="destination"/> + </command> + + <!-- - Command: message.stop - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="stop" code="0xc" label="stop the sending of messages"> + <doc> + On receipt of this command, a producer of messages MUST set his credit to zero for the given + destination. When notifying of completion, credit MUST be zero and no further messages will + be sent until such a time as further credit is received. + </doc> + + <implement role="server" handle="MUST" /> + <implement role="client" handle="MUST" /> + + <field name="destination" type="destination"/> + </command> + + </class> + + <!-- == Class: tx ============================================================================ --> + + <class name="tx" code="0x5" label="work with standard transactions"> + <doc> + Standard transactions provide so-called "1.5 phase commit". We can ensure that work is never + lost, but there is a chance of confirmations being lost, so that messages may be resent. + Applications that use standard transactions must be able to detect and ignore duplicate + messages. + </doc> + + <doc type="grammar"> + tx = C:SELECT + / C:COMMIT + / C:ROLLBACK + </doc> + + <!-- XXX: this isn't really a rule, as stated there is no way for + a client library to implement this --> + <rule name="duplicate-tracking"> + <doc> + An client using standard transactions SHOULD be able to track all messages received within a + reasonable period, and thus detect and reject duplicates of the same message. It SHOULD NOT + pass these to the application layer. + </doc> + </rule> + + <role name="server" implement="SHOULD" /> + + <!-- - Command: tx.select - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="select" code="0x1" label="select standard transaction mode"> + <doc> + This command sets the session to use standard transactions. The client must use this command + exactly once on a session before using the Commit or Rollback commands. + </doc> + + <exception name="exactly-once" error-code="illegal-state"> + <doc> + A client MUST NOT select standard transactions on a session that is already transactional. + </doc> + </exception> + + <exception name="no-dtx" error-code="illegal-state"> + <doc> + A client MUST NOT select standard transactions on a session that is already enlisted in a + distributed transaction. + </doc> + </exception> + + <exception name="explicit-accepts" error-code="not-allowed"> + <doc> + On a session on which tx.select has been issued, a client MUST NOT issue a + message.subscribe command with the accept-mode property set to any value other than + explicit. Similarly a tx.select MUST NOT be issued on a session on which a there is a non + cancelled subscriber with accept-mode of none. + </doc> + </exception> + + <implement role="server" handle="MUST" /> + </command> + + <!-- - Command: tx.commit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="commit" code="0x2" label="commit the current transaction"> + <doc> + This command commits all messages published and accepted in the current transaction. A + new transaction starts immediately after a commit. + </doc> + <doc> + In more detail, the commit acts on all messages which have been transferred from the Client + to the Server, and on all acceptances of messages sent from Server to Client. Since the + commit acts on commands sent in the same direction as the commit command itself, there is no + ambiguity on the scope of the commands being committed. Further, the commit will not be + completed until all preceding commands which it affects have been completed. + </doc> + <doc> + Since transactions act on explicit accept commands, the only valid accept-mode for message + subscribers is explicit. For transferring messages from Client to Server (publishing) all + accept-modes are permitted. + </doc> + + <exception name="select-required" error-code="illegal-state"> + <doc> + A client MUST NOT issue tx.commit on a session that has not been selected for standard + transactions with tx.select. + </doc> + </exception> + + + + <implement role="server" handle="MUST" /> + </command> + + <!-- - Command: tx.rollback - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="rollback" code="0x3" label="abandon the current transaction"> + <doc> + This command abandons the current transaction. In particular the transfers from Client to + Server (publishes) and accepts of transfers from Server to Client which occurred in the + current transaction are discarded. A new transaction starts immediately after a rollback. + </doc> + <doc> + In more detail, when a rollback is issued, any the effects of transfers which occurred from + Client to Server are discarded. The Server will issue completion notification for all such + transfers prior to the completion of the rollback. Similarly the effects of any + message.accept issued from Client to Server prior to the issuance of the tx.rollback will be + discarded; and notification of completion for all such commands will be issued before the + issuance of the completion for the rollback. + </doc> + <doc> + After the completion of the rollback, the client will still hold the messages which it has + not yet accepted (including those for which accepts were previously issued within the + transaction); i.e. the messages remain "acquired". If the Client wishes to release those + messages back to the Server, then appropriate message.release commands must be issued. + </doc> + + <exception name="select-required" error-code="illegal-state"> + <doc> + A client MUST NOT issue tx.rollback on a session that has not been selected for standard + transactions with tx.select. + </doc> + </exception> + + <implement role="server" handle="MUST" /> + </command> + + </class> + + <!-- == Class: dtx =========================================================================== --> + + <class name="dtx" code="0x6" label="Demarcates dtx branches"> + <doc> + This provides the X-Open XA distributed transaction protocol support. It allows a session + to be selected for use with distributed transactions, the transactional boundaries for work on + that session to be demarcated and allows the transaction manager to coordinate transaction + outcomes. + </doc> + + <doc type="grammar"> + dtx-demarcation = C:SELECT *demarcation + demarcation = C:START C:END + </doc> + + <doc type="grammar"> + dtx-coordination = *coordination + coordination = command + / outcome + / recovery + command = C:SET-TIMEOUT + / C:GET-TIMEOUT + outcome = one-phase-commit + / one-phase-rollback + / two-phase-commit + / two-phase-rollback + one-phase-commit = C:COMMIT + one-phase-rollback = C:ROLLBACK + two-phase-commit = C:PREPARE C:COMMIT + two-phase-rollback = C:PREPARE C:ROLLBACK + recovery = C:RECOVER *recovery-outcome + recovery-outcome = one-phase-commit + / one-phase-rollback + / C:FORGET + + </doc> + + <rule name="transactionality"> + <doc> + Enabling XA transaction support on a session requires that the server MUST manage + transactions demarcated by start-end blocks. That is to say that on this XA-enabled session, + work undergone within transactional blocks is performed on behalf a transaction branch + whereas work performed outside of transactional blocks is NOT transactional. + </doc> + </rule> + + <role name="server" implement="MAY" /> + <role name="client" implement="MAY" /> + + <!-- XA domains --> + + <domain name="xa-status" type="uint16" label="XA return codes"> + <enum> + <choice name="xa-ok" value="0"> + <doc> + Normal execution completion (no error). + </doc> + </choice> + + <choice name="xa-rbrollback" value="1"> + <doc> + The rollback was caused for an unspecified reason. + </doc> + </choice> + + <choice name="xa-rbtimeout" value="2"> + <doc> + A transaction branch took too long. + </doc> + </choice> + + <choice name="xa-heurhaz" value="3"> + <doc> + The transaction branch may have been heuristically completed. + </doc> + </choice> + + <choice name="xa-heurcom" value="4"> + <doc> + The transaction branch has been heuristically committed. + </doc> + </choice> + + <choice name="xa-heurrb" value="5"> + <doc> + The transaction branch has been heuristically rolled back. + </doc> + </choice> + + <choice name="xa-heurmix" value="6"> + <doc> + The transaction branch has been heuristically committed and rolled back. + </doc> + </choice> + + <choice name="xa-rdonly" value="7"> + <doc> + The transaction branch was read-only and has been committed. + </doc> + </choice> + </enum> + </domain> + + <struct name="xa-result" size="4" code="0x1" pack="2"> + <field name="status" type="xa-status" required="true"/> + </struct> + + <!-- Struct for xid --> + + <struct name="xid" size="4" code="0x4" pack="2" label="dtx branch identifier"> + <doc> + An xid uniquely identifies a transaction branch. + </doc> + + <field name="format" type="uint32" label="implementation specific format code" + required="true"/> + <field name="global-id" type="vbin8" label="global transaction id" required="true"/> + <field name="branch-id" type="vbin8" label="branch qualifier" required="true"/> + </struct> + + <!-- - Command: dtx.select - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="select" code="0x1" label="Select dtx mode"> + <doc> + This command sets the session to use distributed transactions. The client must use this + command at least once on a session before using XA demarcation operations. + </doc> + + <implement role="server" handle="MAY" /> + </command> + + <!-- - Command: dtx.start - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="start" code="0x2" label="Start a dtx branch"> + <doc> + This command is called when messages should be produced and consumed on behalf a transaction + branch identified by xid. + </doc> + + <exception name="illegal-state" error-code="illegal-state"> + <doc> + If the command is invoked in an improper context (see class grammar) then the server MUST + send a session exception. + </doc> + </exception> + + <exception name="already-known" error-code="not-allowed"> + <doc> + If neither join nor resume is specified is specified and the transaction branch specified + by xid has previously been seen then the server MUST raise an exception. + </doc> + </exception> + + <exception name="join-and-resume" error-code="not-allowed"> + <doc> + If join and resume are specified then the server MUST raise an exception. + </doc> + </exception> + + <implement role="server" handle="MAY" /> + + <field name="xid" type="xid" label="Transaction xid" required="true"> + <doc> + Specifies the xid of the transaction branch to be started. + </doc> + + <exception name="unknown-xid" error-code="not-allowed"> + <doc> + If xid is already known by the broker then the server MUST raise an exception. + </doc> + </exception> + </field> + + <field name="join" type="bit" label="Join with existing xid flag"> + <doc> + Indicate whether this is joining an already associated xid. Indicate that the start + applies to joining a transaction previously seen. + </doc> + + <exception name="unsupported" error-code="not-implemented"> + <doc> + If the broker does not support join the server MUST raise an exception. + </doc> + </exception> + </field> + + <field name="resume" type="bit" label="Resume flag"> + <doc> + Indicate that the start applies to resuming a suspended transaction branch specified. + </doc> + </field> + + <result type="xa-result"> + <doc> + This confirms to the client that the transaction branch is started or specify the error + condition. + + The value of this field may be one of the following constants: + + xa-ok: Normal execution. + + xa-rbrollback: The broker marked the transaction branch rollback-only for an unspecified + reason. + + xa-rbtimeout: The work represented by this transaction branch took too long. + </doc> + </result> + </command> + + <!-- - Command: dtx.end - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="end" code="0x3" label="End a dtx branch"> + <doc> + This command is called when the work done on behalf a transaction branch finishes or needs + to be suspended. + </doc> + + <exception name="illegal-state" error-code="illegal-state"> + <doc> + If the command is invoked in an improper context (see class grammar) then the server MUST + raise an exception. + </doc> + </exception> + + <exception name="suspend-and-fail" error-code="not-allowed"> + <doc> + If suspend and fail are specified then the server MUST raise an exception. + </doc> + </exception> + + <rule name="success"> + <doc> + If neither fail nor suspend are specified then the portion of work has completed + successfully. + </doc> + </rule> + + <rule name="session-closed"> + <doc> + When a session is closed then the currently associated transaction branches MUST be marked + rollback-only. + </doc> + </rule> + + <implement role="server" handle="MAY" /> + + <field name="xid" type="xid" label="Transaction xid" required="true"> + <doc> + Specifies the xid of the transaction branch to be ended. + </doc> + + <exception name="not-associated" error-code="illegal-state"> + <doc> + The session MUST be currently associated with the given xid (through an earlier start + call with the same xid). + </doc> + </exception> + </field> + + <field name="fail" type="bit" label="Failure flag"> + <doc> + If set, indicates that this portion of work has failed; otherwise this portion of work has + completed successfully. + </doc> + + <rule name="failure"> + <doc> + An implementation MAY elect to roll a transaction back if this failure notification is + received. Should an implementation elect to implement this behavior, and this bit is + set, then then the transaction branch SHOULD be marked as rollback-only and the end + result SHOULD have the xa-rbrollback status set. + </doc> + </rule> + </field> + + <field name="suspend" type="bit" label="Temporary suspension flag"> + <doc> + Indicates that the transaction branch is temporarily suspended in an incomplete state. + </doc> + + <rule name="resume"> + <doc> + The transaction context is in a suspended state and must be resumed via the start + command with resume specified. + </doc> + </rule> + + </field> + + <result type="xa-result"> + <doc> + This command confirms to the client that the transaction branch is ended or specify the + error condition. + + The value of this field may be one of the following constants: + + xa-ok: Normal execution. + + xa-rbrollback: The broker marked the transaction branch rollback-only for an unspecified + reason. If an implementation chooses to implement rollback-on-failure behavior, then + this value should be selected if the dtx.end.fail bit was set. + + xa-rbtimeout: The work represented by this transaction branch took too long. + </doc> + </result> + </command> + + <!-- - Command: dtx.commit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="commit" code="0x4" label="Commit work on dtx branch"> + <doc> + Commit the work done on behalf a transaction branch. This command commits the work + associated with xid. Any produced messages are made available and any consumed messages are + discarded. + </doc> + + <exception name="illegal-state" error-code="illegal-state"> + <doc> + If the command is invoked in an improper context (see class grammar) then the server MUST + raise an exception. + </doc> + </exception> + + <implement role="server" handle="MAY" /> + + <field name="xid" type="xid" label="Transaction xid" required="true"> + <doc> + Specifies the xid of the transaction branch to be committed. + </doc> + + <exception name="unknown-xid" error-code="not-found"> + <doc> + If xid is unknown (the transaction branch has not been started or has already been + ended) then the server MUST raise an exception. + </doc> + </exception> + + <exception name="not-disassociated" error-code="illegal-state"> + <doc> + If this command is called when xid is still associated with a session then the server + MUST raise an exception. + </doc> + </exception> + </field> + + <field name="one-phase" type="bit" label="One-phase optimization flag"> + <doc> + Used to indicate whether one-phase or two-phase commit is used. + </doc> + + <exception name="one-phase" error-code="illegal-state"> + <doc> + The one-phase bit MUST be set if a commit is sent without a preceding prepare. + </doc> + </exception> + + <exception name="two-phase" error-code="illegal-state"> + <doc> + The one-phase bit MUST NOT be set if the commit has been preceded by prepare. + </doc> + </exception> + </field> + + <result type="xa-result"> + <doc> + This confirms to the client that the transaction branch is committed or specify the + error condition. + + The value of this field may be one of the following constants: + + xa-ok: Normal execution + + xa-heurhaz: Due to some failure, the work done on behalf of the specified transaction + branch may have been heuristically completed. + + xa-heurcom: Due to a heuristic decision, the work done on behalf of the specified + transaction branch was committed. + + xa-heurrb: Due to a heuristic decision, the work done on behalf of the specified + transaction branch was rolled back. + + xa-heurmix: Due to a heuristic decision, the work done on behalf of the specified + transaction branch was partially committed and partially rolled back. + + xa-rbrollback: The broker marked the transaction branch rollback-only for an unspecified + reason. + + xa-rbtimeout: The work represented by this transaction branch took too long. + </doc> + </result> + </command> + + <!-- - Command: dtx.forget - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="forget" code="0x5" label="Discard dtx branch"> + <doc> + This command is called to forget about a heuristically completed transaction branch. + </doc> + + <exception name="illegal-state" error-code="illegal-state"> + <doc> + If the command is invoked in an improper context (see class grammar) then the server MUST + raise an exception. + </doc> + </exception> + + <implement role="server" handle="MAY" /> + + <field name="xid" type="xid" label="Transaction xid" required="true"> + <doc> + Specifies the xid of the transaction branch to be forgotten. + </doc> + + <exception name="unknown-xid" error-code="not-found"> + <doc> + If xid is unknown (the transaction branch has not been started or has already been + ended) then the server MUST raise an exception. + </doc> + </exception> + + <exception name="not-disassociated" error-code="illegal-state"> + <doc> + If this command is called when xid is still associated with a session then the server + MUST raise an exception. + </doc> + </exception> + </field> + </command> + + <!-- - Command: dtx.get-timeout - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="get-timeout" code="0x6" label="Obtain dtx timeout in seconds"> + <doc> + This command obtains the current transaction timeout value in seconds. If set-timeout was + not used prior to invoking this command, the return value is the default timeout; otherwise, + the value used in the previous set-timeout call is returned. + </doc> + + <implement role="server" handle="MAY" /> + + <field name="xid" type="xid" label="Transaction xid" required="true"> + <doc> + Specifies the xid of the transaction branch for getting the timeout. + </doc> + + <exception name="unknown-xid" error-code="not-found"> + <doc> + If xid is unknown (the transaction branch has not been started or has already been + ended) then the server MUST raise an exception. + </doc> + </exception> + </field> + + <result> + <struct name="get-timeout-result" size="4" code="0x2" pack="2"> + <doc> Returns the value of the timeout last specified through set-timeout. </doc> + + <field name="timeout" type="uint32" label="The current transaction timeout value" + required="true"> + <doc> The current transaction timeout value in seconds. </doc> + </field> + </struct> + </result> + </command> + + <!-- - Command: dtx.prepare - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="prepare" code="0x7" label="Prepare a dtx branch"> + <doc> + This command prepares for commitment any message produced or consumed on behalf of xid. + </doc> + + <exception name="illegal-state" error-code="illegal-state"> + <doc> + If the command is invoked in an improper context (see class grammar) then the server MUST + raise an exception. + </doc> + </exception> + + <rule name="obligation-1"> + <doc> + Once this command successfully returns it is guaranteed that the transaction branch may be + either committed or rolled back regardless of failures. + </doc> + </rule> + + <rule name="obligation-2"> + <doc> + The knowledge of xid cannot be erased before commit or rollback complete the branch. + </doc> + </rule> + + <implement role="server" handle="MAY" /> + + <field name="xid" type="xid" label="Transaction xid" required="true"> + <doc> + Specifies the xid of the transaction branch that can be prepared. + </doc> + + <exception name="unknown-xid" error-code="not-found"> + <doc> + If xid is unknown (the transaction branch has not been started or has already been + ended) then the server MUST raise an exception. + </doc> + </exception> + + <exception name="not-disassociated" error-code="illegal-state"> + <doc> + If this command is called when xid is still associated with a session then the server + MUST raise an exception. + </doc> + </exception> + </field> + + <result type="xa-result"> + <doc> + This command confirms to the client that the transaction branch is prepared or specify the + error condition. + + The value of this field may be one of the following constants: + + xa-ok: Normal execution. + + xa-rdonly: The transaction branch was read-only and has been committed. + + xa-rbrollback: The broker marked the transaction branch rollback-only for an unspecified + reason. + + xa-rbtimeout: The work represented by this transaction branch took too long. + </doc> + </result> + </command> + + <!-- - Command: dtx.recover - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="recover" code="0x8" label="Get prepared or completed xids"> + <doc> + This command is called to obtain a list of transaction branches that are in a prepared or + heuristically completed state. + </doc> + + <implement role="server" handle="MAY" /> + + <result> + <struct name="recover-result" size="4" code="0x3" pack="2"> + <doc> + Returns to the client a table with single item that is a sequence of transaction xids + that are in a prepared or heuristically completed state. + </doc> + + <field name="in-doubt" type="array" label="array of xids to be recovered" required="true"> + <doc> Array containing the xids to be recovered (xids that are in a prepared or + heuristically completed state). </doc> + + </field> + </struct> + </result> + </command> + + <!-- - Command: dtx.rollback - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="rollback" code="0x9" label="Rollback a dtx branch"> + <doc> + This command rolls back the work associated with xid. Any produced messages are discarded + and any consumed messages are re-enqueued. + </doc> + + <exception name="illegal-state" error-code="illegal-state"> + <doc> + If the command is invoked in an improper context (see class grammar) then the server MUST + raise an exception. + </doc> + </exception> + + <implement role="server" handle="MAY" /> + + <field name="xid" type="xid" label="Transaction xid" required="true"> + <doc> + Specifies the xid of the transaction branch that can be rolled back. + </doc> + + <exception name="unknown-xid" error-code="not-found"> + <doc> + If xid is unknown (the transaction branch has not been started or has already been + ended) then the server MUST raise an exception. + </doc> + </exception> + + <exception name="not-disassociated" error-code="illegal-state"> + <doc> + If this command is called when xid is still associated with a session then the server + MUST raise an exception. + </doc> + </exception> + </field> + + <result type="xa-result"> + <doc> + This command confirms to the client that the transaction branch is rolled back or specify + the error condition. + + The value of this field may be one of the following constants: + + xa-ok: Normal execution + + xa-heurhaz: Due to some failure, the work done on behalf of the specified transaction + branch may have been heuristically completed. + + xa-heurcom: Due to a heuristic decision, the work done on behalf of the specified + transaction branch was committed. + + xa-heurrb: Due to a heuristic decision, the work done on behalf of the specified + transaction branch was rolled back. + + xa-heurmix: Due to a heuristic decision, the work done on behalf of the specified + transaction branch was partially committed and partially rolled back. + + xa-rbrollback: The broker marked the transaction branch rollback-only for an unspecified + reason. + + xa-rbtimeout: The work represented by this transaction branch took too long. + </doc> + </result> + </command> + + <!-- - Command: dtx.set-timeout - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="set-timeout" code="0xa" label="Set dtx timeout value"> + <doc> + Sets the specified transaction branch timeout value in seconds. + </doc> + + <rule name="effective"> + <doc> + Once set, this timeout value is effective until this command is reinvoked with a different + value. + </doc> + </rule> + + <rule name="reset"> + <doc> + A value of zero resets the timeout value to the default value. + </doc> + </rule> + + <implement role="server" handle="MAY" /> + + <field name="xid" type="xid" label="Transaction xid" required="true"> + <doc> + Specifies the xid of the transaction branch for setting the timeout. + </doc> + + <exception name="unknown-xid" error-code="not-found"> + <doc> + If xid is unknown (the transaction branch has not been started or has already been + ended) then the server MUST raise an exception. + </doc> + </exception> + + </field> + + <field name="timeout" type="uint32" label="Dtx timeout in seconds" required="true"> + <doc> + The transaction timeout value in seconds. + </doc> + </field> + </command> + + </class> + + <!-- == Class: exchange ====================================================================== --> + + <class name="exchange" code="0x7" label="work with exchanges"> + <doc> + Exchanges match and distribute messages across queues. Exchanges can be configured in the + server or created at runtime. + </doc> + + <doc type="grammar"> + exchange = C:DECLARE + / C:DELETE + / C:QUERY + </doc> + + <rule name="required-types"> + <doc> + The server MUST implement these standard exchange types: fanout, direct. + </doc> + <doc type="scenario"> + Client attempts to declare an exchange with each of these standard types. + </doc> + </rule> + + <rule name="recommended-types"> + <doc> + The server SHOULD implement these standard exchange types: topic, headers. + </doc> + <doc type="scenario"> + Client attempts to declare an exchange with each of these standard types. + </doc> + </rule> + + <rule name="required-instances"> + <doc> + The server MUST, in each virtual host, pre-declare an exchange instance for each standard + exchange type that it implements, where the name of the exchange instance, if defined, is + "amq." followed by the exchange type name. + + The server MUST, in each virtual host, pre-declare at least two direct exchange instances: + one named "amq.direct", the other with no public name that serves as a default exchange for + publish commands (such as message.transfer). + </doc> + <doc type="scenario"> + Client creates a temporary queue and attempts to bind to each required exchange instance + ("amq.fanout", "amq.direct", "amq.topic", and "amq.headers" if those types are defined). + </doc> + </rule> + + <rule name="default-exchange"> + <doc> + The server MUST pre-declare a direct exchange with no public name to act as the default + exchange for content publish commands (such as message.transfer) and for default queue + bindings. + </doc> + <doc type="scenario"> + Client checks that the default exchange is active by publishing a message with a suitable + routing key but without specifying the exchange name, then ensuring that the message arrives + in the queue correctly. + </doc> + </rule> + + <rule name="default-access"> + <doc> + The default exchange MUST NOT be accessible to the client except by specifying an empty + exchange name in a content publish command (such as message.transfer). That is, the server + must not let clients explicitly bind, unbind, delete, or make any other reference to this + exchange. + </doc> + </rule> + + <rule name="extensions"> + <doc> + The server MAY implement other exchange types as wanted. + </doc> + </rule> + + <role name="server" implement="MUST" /> + <role name="client" implement="MUST" /> + + <domain name="name" type="str8" label="exchange name"> + <doc> + The exchange name is a client-selected string that identifies the exchange for publish + commands. Exchange names may consist of any mixture of digits, letters, and underscores. + Exchange names are scoped by the virtual host. + </doc> + </domain> + + <!-- - Command: exchange.declare - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="declare" code="0x1" label="verify exchange exists, create if needed"> + <doc> + This command creates an exchange if it does not already exist, and if the exchange exists, + verifies that it is of the correct and expected class. + </doc> + + <rule name="minimum"> + <doc> + The server SHOULD support a minimum of 16 exchanges per virtual host and ideally, impose + no limit except as defined by available resources. + </doc> + <doc type="scenario"> + The client creates as many exchanges as it can until the server reports an error; the + number of exchanges successfully created must be at least sixteen. + </doc> + </rule> + + <implement role="server" handle="MUST" /> + + <field name="exchange" type="name" required="true"> + <exception name="reserved-names" error-code="not-allowed"> + <doc> + Exchange names starting with "amq." are reserved for pre-declared and standardized + exchanges. The client MUST NOT attempt to create an exchange starting with "amq.". + </doc> + </exception> + + <exception name="exchange-name-required" error-code="invalid-argument"> + <doc> + The name of the exchange MUST NOT be a blank or empty string. + </doc> + </exception> + </field> + + <field name="type" type="str8" label="exchange type" required="true"> + <doc> + Each exchange belongs to one of a set of exchange types implemented by the server. The + exchange types define the functionality of the exchange - i.e. how messages are routed + through it. It is not valid or meaningful to attempt to change the type of an existing + exchange. + </doc> + + <exception name="typed" error-code="not-allowed"> + <doc> + Exchanges cannot be redeclared with different types. The client MUST NOT attempt to + redeclare an existing exchange with a different type than used in the original + exchange.declare command. + </doc> + </exception> + + <exception name="exchange-type-not-found" error-code="not-found"> + <doc> + If the client attempts to create an exchange which the server does not recognize, an + exception MUST be sent. + </doc> + </exception> + </field> + + <field name="alternate-exchange" type="name" label= "exchange name for unroutable messages"> + <doc> + In the event that a message cannot be routed, this is the name of the exchange to which + the message will be sent. Messages transferred using message.transfer will be routed to + the alternate-exchange only if they are sent with the "none" accept-mode, and the + discard-unroutable delivery property is set to false, and there is no queue to route to + for the given message according to the bindings on this exchange. + </doc> + + <rule name="empty-name"> + <doc> + If alternate-exchange is not set (its name is an empty string), unroutable messages + that would be sent to the alternate-exchange MUST be dropped silently. + </doc> + </rule> + + <exception name="pre-existing-exchange" error-code="not-allowed"> + <doc> + If the alternate-exchange is not empty and if the exchange already exists with a + different alternate-exchange, then the declaration MUST result in an exception. + </doc> + </exception> + + <rule name="double-failure"> + <doc> + A message which is being routed to a alternate exchange, MUST NOT be re-routed to a + secondary alternate exchange if it fails to route in the primary alternate exchange. + After such a failure, the message MUST be dropped. This prevents looping. + </doc> + </rule> + </field> + + <field name="passive" type="bit" label="do not create exchange"> + <doc> + If set, the server will not create the exchange. The client can use this to check whether + an exchange exists without modifying the server state. + </doc> + <exception name="not-found" error-code="not-found"> + <doc> + If set, and the exchange does not already exist, the server MUST raise an exception. + </doc> + </exception> + </field> + + <field name="durable" type="bit" label="request a durable exchange"> + <doc> + If set when creating a new exchange, the exchange will be marked as durable. Durable + exchanges remain active when a server restarts. Non-durable exchanges (transient + exchanges) are purged if/when a server restarts. + </doc> + + <rule name="support"> + <doc> + The server MUST support both durable and transient exchanges. + </doc> + </rule> + + <rule name="sticky"> + <doc> + The server MUST ignore the durable field if the exchange already exists. + </doc> + </rule> + </field> + + <field name="auto-delete" type="bit" label="auto-delete when unused"> + <doc> + If set, the exchange is deleted automatically when there remain no bindings between the + exchange and any queue. Such an exchange will not be automatically deleted until at least + one binding has been made to prevent the immediate deletion of the exchange upon creation. + </doc> + <rule name="sticky"> + <doc> + The server MUST ignore the auto-delete field if the exchange already exists. + </doc> + </rule> + </field> + + <field name="arguments" type="map" label="arguments for declaration"> + <doc> + A set of arguments for the declaration. The syntax and semantics of these arguments + depends on the server implementation. This field is ignored if passive is 1. + </doc> + + <exception name="unknown-argument" error-code="not-implemented"> + <doc> + If the arguments field contains arguments which are not understood by the server, + it MUST raise an exception. + </doc> + </exception> + </field> + </command> + + <!-- - Command: exchange.delete - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="delete" code="0x2" label="delete an exchange"> + <doc> + This command deletes an exchange. When an exchange is deleted all queue bindings on the + exchange are cancelled. + </doc> + + <implement role="server" handle="MUST" /> + + <field name="exchange" type="name" required="true"> + <exception name="exists" error-code="not-found"> + <doc> + The client MUST NOT attempt to delete an exchange that does not exist. + </doc> + </exception> + + <exception name="exchange-name-required" error-code="invalid-argument"> + <doc> + The name of the exchange MUST NOT be a missing or empty string. + </doc> + </exception> + + <exception name="used-as-alternate" error-code="not-allowed"> + <doc> + An exchange MUST NOT be deleted if it is in use as an alternate-exchange by a queue or + by another exchange. + </doc> + </exception> + + </field> + + <field name="if-unused" type="bit" label="delete only if unused"> + <doc> + If set, the server will only delete the exchange if it has no queue bindings. If the + exchange has queue bindings the server does not delete it but raises an exception + instead. + </doc> + <exception name="exchange-in-use" error-code="precondition-failed"> + <doc> + If the exchange has queue bindings, and the if-unused flag is set, the server MUST NOT + delete the exchange, but MUST raise and exception. + </doc> + </exception> + </field> + </command> + + <!-- - Command: exchange.query - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="query" code="0x3" label="request information about an exchange"> + <doc> + This command is used to request information on a particular exchange. + </doc> + + <implement role="server" handle="MUST" /> + + <field name="name" type="str8" label="the exchange name"> + <doc> + The name of the exchange for which information is requested. If not specified explicitly + the default exchange is implied. + </doc> + </field> + + <result> + <struct name="exchange-query-result" size="4" code="0x1" pack="2"> + <doc> + This is sent in response to a query request and conveys information on a particular + exchange. + </doc> + + <field name="type" type="str8" label="indicate the exchange type"> + <doc> + The type of the exchange. Will be empty if the exchange is not found. + </doc> + </field> + + <field name="durable" type="bit" label="indicate the durability"> + <doc> + The durability of the exchange, i.e. if set the exchange is durable. Will not be set + if the exchange is not found. + </doc> + </field> + + <field name="not-found" type="bit" label="indicate an unknown exchange"> + <doc> + If set, the exchange for which information was requested is not known. + </doc> + </field> + + <field name="arguments" type="map" label="other unspecified exchange properties"> + <doc> + A set of properties of the exchange whose syntax and semantics depends on the server + implementation. Will be empty if the exchange is not found. + </doc> + </field> + </struct> + </result> + </command> + + <!-- - Command: exchange.bind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="bind" code="0x4" label="bind queue to an exchange"> + <doc> This command binds a queue to an exchange. Until a queue is bound it will not receive + any messages. In a classic messaging model, store-and-forward queues are bound to a direct + exchange and subscription queues are bound to a topic exchange. </doc> + + <rule name="duplicates"> + <doc> + A server MUST ignore duplicate bindings - that is, two or more bind commands with the + same exchange, queue, and binding-key - without treating these as an error. The value of + the arguments used for the binding MUST NOT be altered by subsequent binding requests. + </doc> + <doc type="scenario"> + A client binds a named queue to an exchange. The client then repeats the bind (with + identical exchange, queue, and binding-key). The second binding should use a different + value for the arguments field. + </doc> + </rule> + + <rule name="durable-exchange"> + <doc> Bindings between durable queues and durable exchanges are automatically durable and + the server MUST restore such bindings after a server restart. </doc> + <doc type="scenario"> A server creates a named durable queue and binds it to a durable + exchange. The server is restarted. The client then attempts to use the queue/exchange + combination. </doc> + </rule> + + <rule name="binding-count"> + <doc> The server SHOULD support at least 4 bindings per queue, and ideally, impose no limit + except as defined by available resources. </doc> + <doc type="scenario"> A client creates a named queue and attempts to bind it to 4 different + exchanges. </doc> + </rule> + + <rule name="multiple-bindings"> + <doc> Where more than one binding exists between a particular exchange instance and a + particular queue instance any given message published to that exchange should be delivered + to that queue at most once, regardless of how many distinct bindings match. </doc> + <doc type="scenario"> A client creates a named queue and binds it to the same topic exchange + at least three times using intersecting binding-keys (for example, "animals.*", + "animals.dogs.*", "animal.dogs.chihuahua"). Verify that a message matching all the + bindings (using previous example, routing key = "animal.dogs.chihuahua") is delivered once + only. </doc> + </rule> + + <implement role="server" handle="MUST"/> + + <field name="queue" type="queue.name" required="true"> + <doc> Specifies the name of the queue to bind. </doc> + + <exception name="empty-queue" error-code="invalid-argument"> + <doc> A client MUST NOT be allowed to bind a non-existent and unnamed queue (i.e. empty + queue name) to an exchange. </doc> + <doc type="scenario"> A client attempts to bind with an unnamed (empty) queue name to an + exchange. </doc> + </exception> + + <exception name="queue-existence" error-code="not-found"> + <doc> A client MUST NOT be allowed to bind a non-existent queue (i.e. not previously + declared) to an exchange. </doc> + <doc type="scenario"> A client attempts to bind an undeclared queue name to an exchange. + </doc> + </exception> + </field> + + <field name="exchange" type="name" label="name of the exchange to bind to" required="true"> + <exception name="exchange-existence" error-code="not-found"> + <doc> A client MUST NOT be allowed to bind a queue to a non-existent exchange. </doc> + <doc type="scenario"> A client attempts to bind a named queue to a undeclared exchange. + </doc> + </exception> + + <exception name="exchange-name-required" error-code="invalid-argument"> + <doc> The name of the exchange MUST NOT be a blank or empty string. </doc> + </exception> + </field> + + <field name="binding-key" type="str8" + label="identifies a binding between a given exchange and queue" required="true"> + <doc> The binding-key uniquely identifies a binding between a given (exchange, queue) pair. + Depending on the exchange configuration, the binding key may be matched against the + message routing key in order to make routing decisions. The match algorithm depends on the + exchange type. Some exchange types may ignore the binding key when making routing + decisions. Refer to the specific exchange type documentation. The meaning of an empty + binding key depends on the exchange implementation. </doc> + </field> + + <field name="arguments" type="map" label="arguments for binding"> + <doc> A set of arguments for the binding. The syntax and semantics of these arguments + depends on the exchange class. </doc> + + <exception name="unknown-argument" error-code="not-implemented"> + <doc> If the arguments field contains arguments which are not understood by the server, it + MUST raise an exception. </doc> + </exception> + </field> + </command> + + <!-- - Command: exchange.unbind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="unbind" code="0x5" label="unbind a queue from an exchange"> + <doc> + This command unbinds a queue from an exchange. + </doc> + + <implement role="server" handle="MUST" /> + + <field name="queue" type="queue.name" required="true"> + <doc> + Specifies the name of the queue to unbind. + </doc> + <exception name="non-existent-queue" error-code="not-found"> + <doc> + If the queue does not exist the server MUST raise an exception. + </doc> + </exception> + </field> + + <field name="exchange" type="name" required="true"> + <doc> + The name of the exchange to unbind from. + </doc> + + <exception name="non-existent-exchange" error-code="not-found"> + <doc> + If the exchange does not exist the server MUST raise an exception. + </doc> + </exception> + + <exception name="exchange-name-required" error-code="invalid-argument"> + <doc> + The name of the exchange MUST NOT be a blank or empty string. + </doc> + </exception> + </field> + + <field name="binding-key" type="str8" label="the key of the binding" required="true"> + <doc> + Specifies the binding-key of the binding to unbind. + </doc> + + <exception name="non-existent-binding-key" error-code="not-found"> + <doc> + If there is no matching binding-key the server MUST raise an exception. + </doc> + </exception> + </field> + </command> + + <!-- - Command: exchange.bound - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="bound" code="0x6" label="request information about bindings to an exchange"> + <doc> + This command is used to request information on the bindings to a particular exchange. + </doc> + + <implement role="server" handle="MUST" /> + + <field name="exchange" type="str8" label="the exchange name"> + <doc> + The name of the exchange for which binding information is being requested. If not + specified explicitly the default exchange is implied. + </doc> + </field> + + <field name="queue" type="str8" label="a queue name" required="true"> + <doc> + If populated then determine whether the given queue is bound to the exchange. + </doc> + </field> + + <field name="binding-key" type="str8" label="a binding-key"> + <doc> + If populated defines the binding-key of the binding of interest, if not populated the + request will ignore the binding-key on bindings when searching for a match. + </doc> + </field> + + <field name="arguments" type="map" label="a set of binding arguments"> + <doc> + If populated defines the arguments of the binding of interest if not populated the request + will ignore the arguments on bindings when searching for a match + </doc> + </field> + + <result> + <struct name="exchange-bound-result" size="4" code="0x2" pack="2"> + <field name="exchange-not-found" type="bit" label="indicate an unknown exchange"> + <doc> + If set, the exchange for which information was requested is not known. + </doc> + </field> + + <field name="queue-not-found" type="bit" label="indicate an unknown queue"> + <doc> + If set, the queue specified is not known. + </doc> + </field> + + <field name="queue-not-matched" type="bit" label="indicate no matching queue"> + <doc> + A bit which if set indicates that no binding was found from the specified exchange to + the specified queue. + </doc> + </field> + + <field name="key-not-matched" type="bit" label="indicate no matching binding-key"> + <doc> + A bit which if set indicates that no binding was found from the specified exchange + with the specified binding-key. + </doc> + </field> + + <field name="args-not-matched" type="bit" label="indicate no matching arguments"> + <doc> + A bit which if set indicates that no binding was found from the specified exchange + with the specified arguments. + </doc> + </field> + </struct> + </result> + </command> + + </class> + + <!-- == Class: queue ========================================================================= --> + + <class name="queue" code="0x8" label="work with queues"> + <doc> + Queues store and forward messages. Queues can be configured in the server or created at + runtime. Queues must be attached to at least one exchange in order to receive messages from + publishers. + </doc> + + <doc type="grammar"> + queue = C:DECLARE + / C:BIND + / C:PURGE + / C:DELETE + / C:QUERY + / C:UNBIND + </doc> + + <rule name="any-content"> + <doc> + A server MUST allow any content class to be sent to any queue, in any mix, and queue and + deliver these content classes independently. Note that all commands that fetch content off + queues are specific to a given content class. + </doc> + <doc type="scenario"> + Client creates an exchange of each standard type and several queues that it binds to each + exchange. It must then successfully send each of the standard content types to each of the + available queues. + </doc> + </rule> + + <role name="server" implement="MUST" /> + <role name="client" implement="MUST" /> + + <domain name="name" type="str8" label="queue name"> + <doc> + The queue name identifies the queue within the virtual host. Queue names must have a length + of between 1 and 255 characters inclusive, must start with a digit, letter or underscores + ('_') character, and must be otherwise encoded in UTF-8. + </doc> + </domain> + + <!-- - Command: queue.declare - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="declare" code="0x1" label="declare queue"> + <doc> + This command creates or checks a queue. When creating a new queue the client can specify + various properties that control the durability of the queue and its contents, and the level + of sharing for the queue. + </doc> + + <rule name="default-binding"> + <doc> + The server MUST create a default binding for a newly-created queue to the default + exchange, which is an exchange of type 'direct' and use the queue name as the binding-key. + </doc> + <doc type="scenario"> + Client creates a new queue, and then without explicitly binding it to an exchange, + attempts to send a message through the default exchange binding, i.e. publish a message to + the empty exchange, with the queue name as binding-key. + </doc> + </rule> + + <rule name="minimum-queues"> + <doc> + The server SHOULD support a minimum of 256 queues per virtual host and ideally, impose no + limit except as defined by available resources. + </doc> + <doc type="scenario"> + Client attempts to create as many queues as it can until the server reports an error. The + resulting count must at least be 256. + </doc> + </rule> + + <implement role="server" handle="MUST" /> + + <field name="queue" type="name" required="true"> + <exception name="reserved-prefix" error-code="not-allowed"> + <doc> + Queue names starting with "amq." are reserved for pre-declared and standardized server + queues. A client MUST NOT attempt to declare a queue with a name that starts with "amq." + and the passive option set to zero. + </doc> + <doc type="scenario"> + A client attempts to create a queue with a name starting with "amq." and with the + passive option set to zero. + </doc> + </exception> + </field> + + <field name="alternate-exchange" type="exchange.name" + label= "exchange name for messages with exceptions"> + <doc> + The alternate-exchange field specifies how messages on this queue should be treated when + they are rejected by a subscriber, or when they are orphaned by queue deletion. When + present, rejected or orphaned messages MUST be routed to the alternate-exchange. In all + cases the messages MUST be removed from the queue. + </doc> + + <exception name="pre-existing-exchange" error-code="not-allowed"> + <doc> + If the alternate-exchange is not empty and if the queue already exists with a different + alternate-exchange, then the declaration MUST result in an exception. + </doc> + </exception> + + <exception name="unknown-exchange" error-code="not-found"> + <doc> + if the alternate-exchange does not match the name of any existing exchange on the + server, then an exception must be raised. + </doc> + </exception> + </field> + + <field name="passive" type="bit" label="do not create queue"> + <doc> + If set, the server will not create the queue. This field allows the client to assert the + presence of a queue without modifying the server state. + </doc> + + <exception name="passive" error-code="not-found"> + <doc> + The client MAY ask the server to assert that a queue exists without creating the queue + if not. If the queue does not exist, the server treats this as a failure. + </doc> + <doc type="scenario"> + Client declares an existing queue with the passive option and expects the command to + succeed. Client then attempts to declare a non-existent queue with the passive option, + and the server must close the session with the correct exception. + </doc> + </exception> + </field> + + <field name="durable" type="bit" label="request a durable queue"> + <doc> + If set when creating a new queue, the queue will be marked as durable. Durable queues + remain active when a server restarts. Non-durable queues (transient queues) are purged + if/when a server restarts. Note that durable queues do not necessarily hold persistent + messages, although it does not make sense to send persistent messages to a transient + queue. + </doc> + + <rule name="persistence"> + <doc> + The queue definition MUST survive the server losing all transient memory, e.g. a + machine restart. + </doc> + <doc type="scenario"> + Client creates a durable queue; server is then restarted. Client then attempts to send + message to the queue. The message should be successfully delivered. + </doc> + </rule> + + <rule name="types"> + <doc> + The server MUST support both durable and transient queues. + </doc> + <doc type="scenario"> + A client creates two named queues, one durable and one transient. + </doc> + </rule> + + <rule name="pre-existence"> + <doc> + The server MUST ignore the durable field if the queue already exists. + </doc> + <doc type="scenario"> + A client creates two named queues, one durable and one transient. The client then + attempts to declare the two queues using the same names again, but reversing the value + of the durable flag in each case. Verify that the queues still exist with the original + durable flag values. + </doc> + </rule> + </field> + + <field name="exclusive" type="bit" label="request an exclusive queue"> + <doc> + Exclusive queues can only be used from one session at a time. Once a session + declares an exclusive queue, that queue cannot be used by any other session until the + declaring session closes. + </doc> + + <rule name="types"> + <doc> + The server MUST support both exclusive (private) and non-exclusive (shared) queues. + </doc> + <doc type="scenario"> + A client creates two named queues, one exclusive and one non-exclusive. + </doc> + </rule> + + <exception name="in-use" error-code="resource-locked"> + <doc> + If the server receives a declare, bind, consume or get request for a queue that has been + declared as exclusive by an existing client session, it MUST raise an exception. + </doc> + <doc type="scenario"> + A client declares an exclusive named queue. A second client on a different session + attempts to declare a queue of the same name. + </doc> + </exception> + </field> + + <field name="auto-delete" type="bit" label="auto-delete queue when unused"> + <doc> + If this field is set and the exclusive field is also set, then the queue MUST be deleted + when the session closes. + + If this field is set and the exclusive field is not set the queue is deleted when all + the consumers have finished using it. Last consumer can be cancelled either explicitly + or because its session is closed. If there was no consumer ever on the queue, it won't + be deleted. + </doc> + + <rule name="pre-existence"> + <doc> + The server MUST ignore the auto-delete field if the queue already exists. + </doc> + <doc type="scenario"> + A client creates two named queues, one as auto-delete and one explicit-delete. The + client then attempts to declare the two queues using the same names again, but reversing + the value of the auto-delete field in each case. Verify that the queues still exist with + the original auto-delete flag values. + </doc> + </rule> + </field> + + <field name="arguments" type="map" label="arguments for declaration"> + <doc> + A set of arguments for the declaration. The syntax and semantics of these arguments + depends on the server implementation. This field is ignored if passive is 1. + </doc> + + <exception name="unknown-argument" error-code="not-implemented"> + <doc> + If the arguments field contains arguments which are not understood by the server, + it MUST raise an exception. + </doc> + </exception> + </field> + </command> + + <!-- - Command: queue.delete - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="delete" code="0x2" label="delete a queue"> + <doc> + This command deletes a queue. When a queue is deleted any pending messages are sent to the + alternate-exchange if defined, or discarded if it is not. + </doc> + + + <implement role="server" handle="MUST" /> + + <field name="queue" type="name" required="true"> + <doc> + Specifies the name of the queue to delete. + </doc> + + <exception name="empty-name" error-code="invalid-argument"> + <doc> + If the queue name in this command is empty, the server MUST raise an exception. + </doc> + </exception> + + <exception name="queue-exists" error-code="not-found"> + <doc> + The queue must exist. If the client attempts to delete a non-existing queue the server + MUST raise an exception. + </doc> + </exception> + </field> + + <field name="if-unused" type="bit" label="delete only if unused"> + <doc> + If set, the server will only delete the queue if it has no consumers. If the queue has + consumers the server does does not delete it but raises an exception instead. + </doc> + + <exception name="if-unused-flag" error-code="precondition-failed"> + <doc> + The server MUST respect the if-unused flag when deleting a queue. + </doc> + </exception> + </field> + + <field name="if-empty" type="bit" label="delete only if empty"> + <doc> + If set, the server will only delete the queue if it has no messages. + </doc> + <exception name="not-empty" error-code="precondition-failed"> + <doc> + If the queue is not empty the server MUST raise an exception. + </doc> + </exception> + </field> + </command> + + <!-- - Command: queue.purge - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="purge" code="0x3" label="purge a queue"> + <doc> + This command removes all messages from a queue. It does not cancel subscribers. Purged + messages are deleted without any formal "undo" mechanism. + </doc> + + <rule name="empty"> + <doc> + A call to purge MUST result in an empty queue. + </doc> + </rule> + + <rule name="pending-messages"> + <doc> + The server MUST NOT purge messages that have already been sent to a client but not yet + accepted. + </doc> + </rule> + + <rule name="purge-recovery"> + <doc> + The server MAY implement a purge queue or log that allows system administrators to recover + accidentally-purged messages. The server SHOULD NOT keep purged messages in the same + storage spaces as the live messages since the volumes of purged messages may get very + large. + </doc> + </rule> + + <implement role="server" handle="MUST" /> + + <field name="queue" type="name" required="true"> + <doc> + Specifies the name of the queue to purge. + </doc> + + <exception name="empty-name" error-code="invalid-argument"> + <doc> + If the the queue name in this command is empty, the server MUST raise an exception. + </doc> + </exception> + + <exception name="queue-exists" error-code="not-found"> + <doc> + The queue MUST exist. Attempting to purge a non-existing queue MUST cause an exception. + </doc> + </exception> + </field> + </command> + + <!-- - Command: queue.query - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="query" code="0x4" label="request information about a queue"> + <doc> + This command requests information about a queue. + </doc> + + <implement role="server" handle="MUST" /> + + <field name="queue" type="name" label="the queried queue" required="true"/> + + <result> + <struct name="queue-query-result" size="4" code="0x1" pack="2"> + <doc> + This is sent in response to queue.query, and conveys the requested information about a + queue. If no queue with the specified name exists then none of the fields within the + returned result struct will be populated. + </doc> + + <field name="queue" type="name" required="true"> + <doc> + Reports the name of the queue. + </doc> + </field> + + <field name="alternate-exchange" type="exchange.name" /> + + <field name="durable" type="bit" /> + + <field name="exclusive" type="bit" /> + + <field name="auto-delete" type="bit" /> + + <field name="arguments" type="map" /> + + <field name="message-count" type="uint32" label="number of messages in queue" + required="true"> + <doc> Reports the number of messages in the queue. </doc> + </field> + + <field name="subscriber-count" type="uint32" label="number of subscribers" + required="true"> + <doc> + Reports the number of subscribers for the queue. + </doc> + </field> + </struct> + </result> + </command> + + </class> + + <!-- == Class: file ========================================================================== --> + + <class name="file" code="0x9" label="work with file content"> + <doc> + The file class provides commands that support reliable file transfer. File messages have a + specific set of properties that are required for interoperability with file transfer + applications. File messages and acknowledgements are subject to session transactions. Note + that the file class does not provide message browsing commands; these are not compatible with + the staging model. Applications that need browsable file transfer should use Message content + and the Message class. + </doc> + + <doc type="grammar"> + file = C:QOS S:QOS-OK + / C:CONSUME S:CONSUME-OK + / C:CANCEL + / C:OPEN S:OPEN-OK C:STAGE content + / S:OPEN C:OPEN-OK S:STAGE content + / C:PUBLISH + / S:DELIVER + / S:RETURN + / C:ACK + / C:REJECT + </doc> + + <rule name="reliable-storage"> + <doc> + The server MUST make a best-effort to hold file messages on a reliable storage mechanism. + </doc> + </rule> + + <rule name="no-discard"> + <doc> + The server MUST NOT discard a file message in case of a queue overflow. The server MUST use + the Session.Flow command to slow or stop a file message publisher when necessary. + </doc> + </rule> + + <rule name="priority-levels"> + <doc> + The server MUST implement at least 2 priority levels for file messages, where priorities 0-4 + and 5-9 are treated as two distinct levels. The server MAY implement up to 10 priority + levels. + </doc> + </rule> + + <rule name="acknowledgement-support"> + <doc> + The server MUST support both automatic and explicit acknowledgements on file content. + </doc> + </rule> + + <role name="server" implement="MAY" /> + <role name="client" implement="MAY" /> + + <!-- These are the properties for a File content --> + <struct name="file-properties" size="4" code="0x1" pack="2"> + <field name="content-type" type="str8" label="MIME content type" /> + <field name="content-encoding" type="str8" label="MIME content encoding" /> + <field name="headers" type="map" label="message header field table" /> + <field name="priority" type="uint8" label="message priority, 0 to 9" /> + <field name="reply-to" type="str8" label="destination to reply to" /> + <field name="message-id" type="str8" label="application message identifier" /> + <field name="filename" type="str8" label="message filename" /> + <field name="timestamp" type="datetime" label="message timestamp" /> + <!-- This field is deprecated pending review --> + <field name="cluster-id" type="str8" label="intra-cluster routing identifier" /> + </struct> + + <domain name="return-code" type="uint16" label="return code from server"> + <doc> + The return code. The AMQP return codes are defined by this enum. + </doc> + <enum> + <choice name="content-too-large" value="311"> + <doc> + The client attempted to transfer content larger than the server could accept. + </doc> + </choice> + + <choice name="no-route" value="312"> + <doc> + The exchange cannot route a message, most likely due to an invalid routing key. Only + when the mandatory flag is set. + </doc> + </choice> + + <choice name="no-consumers" value="313"> + <doc> + The exchange cannot deliver to a consumer when the immediate flag is set. As a result of + pending data on the queue or the absence of any consumers of the queue. + </doc> + </choice> + </enum> + </domain> + + <!-- - Command: file.qos - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="qos" code="0x1" label="specify quality of service"> + <doc> + This command requests a specific quality of service. The QoS can be specified for the + current session or for all sessions on the connection. The particular properties and + semantics of a qos command always depend on the content class semantics. Though the qos + command could in principle apply to both peers, it is currently meaningful only for the + server. + </doc> + + <implement role="server" handle="MUST" /> + + <response name="qos-ok" /> + + <field name="prefetch-size" type="uint32" label="pre-fetch window in octets"> + <doc> + The client can request that messages be sent in advance so that when the client finishes + processing a message, the following message is already held locally, rather than needing + to be sent within the session. Pre-fetching gives a performance improvement. This field + specifies the pre-fetch window size in octets. May be set to zero, meaning "no specific + limit". Note that other pre-fetch limits may still apply. The prefetch-size is ignored if + the no-ack option is set. + </doc> + </field> + + <field name="prefetch-count" type="uint16" label="pre-fetch window in messages"> + <doc> + Specifies a pre-fetch window in terms of whole messages. This is compatible with some file + API implementations. This field may be used in combination with the prefetch-size field; a + message will only be sent in advance if both pre-fetch windows (and those at the session + and connection level) allow it. The prefetch-count is ignored if the no-ack option is set. + </doc> + + <rule name="prefetch-discretion"> + <doc> + The server MAY send less data in advance than allowed by the client's specified + pre-fetch windows but it MUST NOT send more. + </doc> + </rule> + </field> + + <field name="global" type="bit" label="apply to entire connection"> + <doc> + By default the QoS settings apply to the current session only. If this field is set, they + are applied to the entire connection. + </doc> + </field> + </command> + + <!-- - Command: file.qos-ok - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="qos-ok" code="0x2" label="confirm the requested qos"> + <doc> + This command tells the client that the requested QoS levels could be handled by the server. + The requested QoS applies to all active consumers until a new QoS is defined. + </doc> + + <implement role="client" handle="MUST" /> + </command> + + <!-- - Command: file.consume - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="consume" code="0x3" label="start a queue consumer"> + <doc> + This command asks the server to start a "consumer", which is a transient request for + messages from a specific queue. Consumers last as long as the session they were created on, + or until the client cancels them. + </doc> + + <rule name="min-consumers"> + <doc> + The server SHOULD support at least 16 consumers per queue, unless the queue was declared + as private, and ideally, impose no limit except as defined by available resources. + </doc> + </rule> + + <implement role="server" handle="MUST" /> + + <response name="consume-ok" /> + + <field name="queue" type="queue.name"> + <doc> + Specifies the name of the queue to consume from. + </doc> + + <exception name="queue-exists-if-empty" error-code="not-allowed"> + <doc> + If the queue name in this command is empty, the server MUST raise an exception. + </doc> + </exception> + </field> + + <field name="consumer-tag" type="str8"> + <doc> + Specifies the identifier for the consumer. The consumer tag is local to a connection, so + two clients can use the same consumer tags. + </doc> + + <exception name="not-existing-consumer" error-code="not-allowed"> + <doc> + The tag MUST NOT refer to an existing consumer. If the client attempts to create two + consumers with the same non-empty tag the server MUST raise an exception. + </doc> + </exception> + + <exception name="not-empty-consumer-tag" error-code="not-allowed"> + <doc> + The client MUST NOT specify a tag that is empty or blank. + </doc> + <doc type="scenario"> + Attempt to create a consumers with an empty tag. + </doc> + </exception> + </field> + + <field name="no-local" type="bit"> + <doc>If the no-local field is set the server will not send messages to the connection that + published them.</doc> + </field> + + <field name="no-ack" type="bit" label="no acknowledgement needed"> + <doc> + If this field is set the server does not expect acknowledgements for messages. That is, + when a message is delivered to the client the server automatically and silently + acknowledges it on behalf of the client. This functionality increases performance but at + the cost of reliability. Messages can get lost if a client dies before it can deliver them + to the application. + </doc> + </field> + + <field name="exclusive" type="bit" label="request exclusive access"> + <doc> + Request exclusive consumer access, meaning only this consumer can access the queue. + </doc> + + <exception name="in-use" error-code="resource-locked"> + <doc> + If the server cannot grant exclusive access to the queue when asked, - because there are + other consumers active - it MUST raise an exception. + </doc> + </exception> + </field> + + <field name="nowait" type="bit" label="do not send a reply command"> + <doc> + If set, the server will not respond to the command. The client should not wait for a reply + command. If the server could not complete the command it will raise an exception. + </doc> + </field> + + <field name="arguments" type="map" label="arguments for consuming"> + <doc> + A set of arguments for the consume. The syntax and semantics of these arguments depends on + the providers implementation. + </doc> + </field> + </command> + + <command name="consume-ok" code="0x4" label="confirm a new consumer"> + <doc> + This command provides the client with a consumer tag which it MUST use in commands that work + with the consumer. + </doc> + + <implement role="client" handle="MUST" /> + + <field name="consumer-tag" type="str8"> + <doc> + Holds the consumer tag specified by the client or provided by the server. + </doc> + </field> + </command> + + <!-- - Command: file.cancel - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="cancel" code="0x5" label="end a queue consumer"> + <doc> + This command cancels a consumer. This does not affect already delivered messages, but it + does mean the server will not send any more messages for that consumer. + </doc> + + <implement role="server" handle="MUST" /> + + <field name="consumer-tag" type="str8"> + <doc> + the identifier of the consumer to be cancelled. + </doc> + </field> + </command> + + <!-- - Command: file.open - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="open" code="0x6" label="request to start staging"> + <doc> + This command requests permission to start staging a message. Staging means sending the + message into a temporary area at the recipient end and then delivering the message by + referring to this temporary area. Staging is how the protocol handles partial file transfers + - if a message is partially staged and the connection breaks, the next time the sender + starts to stage it, it can restart from where it left off. + </doc> + + <implement role="server" handle="MUST" /> + <implement role="client" handle="MUST" /> + + <response name="open-ok" /> + + <field name="identifier" type="str8" label="staging identifier"> + <doc> + This is the staging identifier. This is an arbitrary string chosen by the sender. For + staging to work correctly the sender must use the same staging identifier when staging the + same message a second time after recovery from a failure. A good choice for the staging + identifier would be the SHA1 hash of the message properties data (including the original + filename, revised time, etc.). + </doc> + </field> + + <field name="content-size" type="uint64" label="message content size"> + <doc> + The size of the content in octets. The recipient may use this information to allocate or + check available space in advance, to avoid "disk full" errors during staging of very large + messages. + </doc> + + <rule name="content-size"> + <doc> + The sender MUST accurately fill the content-size field. Zero-length content is + permitted. + </doc> + </rule> + </field> + </command> + + <!-- - Command: file.open-ok - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="open-ok" code="0x7" label="confirm staging ready"> + <doc> + This command confirms that the recipient is ready to accept staged data. If the message was + already partially-staged at a previous time the recipient will report the number of octets + already staged. + </doc> + + <implement role="server" handle="MUST" /> + <implement role="client" handle="MUST" /> + + <response name="stage" /> + + <field name="staged-size" type="uint64" label="already staged amount"> + <doc> + The amount of previously-staged content in octets. For a new message this will be zero. + </doc> + + <rule name="behavior"> + <doc> + The sender MUST start sending data from this octet offset in the message, counting from + zero. + </doc> + </rule> + + <rule name="staging"> + <doc> + The recipient MAY decide how long to hold partially-staged content and MAY implement + staging by always discarding partially-staged content. However if it uses the file + content type it MUST support the staging commands. + </doc> + </rule> + </field> + </command> + + <!-- - Command: file.stage - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="stage" code="0x8" label="stage message content"> + <doc> + This command stages the message, sending the message content to the recipient from the octet + offset specified in the Open-Ok command. + </doc> + + <implement role="server" handle="MUST" /> + <implement role="client" handle="MUST" /> + + <segments> + <header required="true"> + <entry type="file-properties"/> + </header> + <body/> + </segments> + </command> + + <!-- - Command: file.publish - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="publish" code="0x9" label="publish a message"> + <doc> + This command publishes a staged file message to a specific exchange. The file message will + be routed to queues as defined by the exchange configuration and distributed to any active + consumers when the transaction, if any, is committed. + </doc> + + <implement role="server" handle="MUST" /> + + <field name="exchange" type="exchange.name"> + <doc> + Specifies the name of the exchange to publish to. The exchange name can be empty, meaning + the default exchange. If the exchange name is specified, and that exchange does not exist, + the server will raise an exception. + </doc> + + <rule name="default"> + <doc> + The server MUST accept a blank exchange name to mean the default exchange. + </doc> + </rule> + + <exception name="refusal" error-code="not-implemented"> + <doc> + The exchange MAY refuse file content in which case it MUST send an exception. + </doc> + </exception> + </field> + + <field name="routing-key" type="str8" label="Message routing key"> + <doc> + Specifies the routing key for the message. The routing key is used for routing messages + depending on the exchange configuration. + </doc> + </field> + + <field name="mandatory" type="bit" label="indicate mandatory routing"> + <doc> + This flag tells the server how to react if the message cannot be routed to a queue. If + this flag is set, the server will return an unroutable message with a Return command. If + this flag is zero, the server silently drops the message. + </doc> + + <rule name="implementation"> + <doc> + The server SHOULD implement the mandatory flag. + </doc> + </rule> + </field> + + <field name="immediate" type="bit" label="request immediate delivery"> + <doc> + This flag tells the server how to react if the message cannot be routed to a queue + consumer immediately. If this flag is set, the server will return an undeliverable message + with a Return command. If this flag is zero, the server will queue the message, but with + no guarantee that it will ever be consumed. + </doc> + + <rule name="implementation"> + <doc> + The server SHOULD implement the immediate flag. + </doc> + </rule> + </field> + + <field name="identifier" type="str8" label="staging identifier"> + <doc> + This is the staging identifier of the message to publish. The message must have been + staged. Note that a client can send the Publish command asynchronously without waiting for + staging to finish. + </doc> + </field> + </command> + + <!-- - Command: file.return - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="return" code="0xa" label="return a failed message"> + <doc> + This command returns an undeliverable message that was published with the "immediate" flag + set, or an unroutable message published with the "mandatory" flag set. The reply code and + text provide information about the reason that the message was undeliverable. + </doc> + + <implement role="client" handle="MUST" /> + + <field name="reply-code" type="return-code" /> + + <field name="reply-text" type="str8" label="The localized reply text."> + <doc> + This text can be logged as an aid to resolving issues. + </doc> + </field> + + <field name="exchange" type="exchange.name"> + <doc> + Specifies the name of the exchange that the message was originally published to. + </doc> + </field> + + <field name="routing-key" type="str8" label="Message routing key"> + <doc> + Specifies the routing key name specified when the message was published. + </doc> + </field> + + <segments> + <header required="true"> + <entry type="file-properties"/> + </header> + <body/> + </segments> + </command> + + <!-- - Command: file.deliver - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="deliver" code="0xb" label="notify the client of a consumer message"> + <doc> + This command delivers a staged file message to the client, via a consumer. In the + asynchronous message delivery model, the client starts a consumer using the consume command, + then the server responds with Deliver commands as and when messages arrive for that + consumer. + </doc> + + <rule name="redelivery-tracking"> + <doc> + The server SHOULD track the number of times a message has been delivered to clients and + when a message is redelivered a certain number of times - e.g. 5 times - without being + acknowledged, the server SHOULD consider the message to be non-processable (possibly + causing client applications to abort), and move the message to a dead letter queue. + </doc> + </rule> + + <implement role="client" handle="MUST" /> + + <field name="consumer-tag" type="str8" /> + + <field name="delivery-tag" type="uint64" > + <doc> + The server-assigned and session-specific delivery tag + </doc> + + <rule name="non-zero"> + <doc> + The server MUST NOT use a zero value for delivery tags. Zero is reserved for client use, + meaning "all messages so far received". + </doc> + </rule> + </field> + + <field name="redelivered" type="bit" label="Indicate possible duplicate delivery"> + <doc> + This boolean flag indicates that the message may have been previously delivered to this + or another client. + </doc> + </field> + + <field name="exchange" type="exchange.name"> + <doc> + Specifies the name of the exchange that the message was originally published to. + </doc> + </field> + + <field name="routing-key" type="str8" label="Message routing key"> + <doc> + Specifies the routing key name specified when the message was published. + </doc> + </field> + + <field name="identifier" type="str8" label="staging identifier"> + <doc> + This is the staging identifier of the message to deliver. The message must have been + staged. Note that a server can send the Deliver command asynchronously without waiting for + staging to finish. + </doc> + </field> + </command> + + <!-- - Command: file.ack - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="ack" code="0xc" label="acknowledge one or more messages"> + <doc> + This command acknowledges one or more messages delivered via the Deliver command. The client + can ask to confirm a single message or a set of messages up to and including a specific + message. + </doc> + + <implement role="server" handle="MUST" /> + + <field name="delivery-tag" type="uint64" > + <doc> + The identifier of the message being acknowledged + </doc> + <rule name="session-local"> + <doc> + The delivery tag is valid only within the session from which the message was received. + i.e. A client MUST NOT receive a message on one session and then acknowledge it on + another. + </doc> + </rule> + </field> + + <field name="multiple" type="bit" label="acknowledge multiple messages"> + <doc> + If set to 1, the delivery tag is treated as "up to and including", so that the client can + acknowledge multiple messages with a single command. If set to zero, the delivery tag + refers to a single message. If the multiple field is 1, and the delivery tag is zero, + tells the server to acknowledge all outstanding messages. + </doc> + + <rule name="validation"> + <doc> + The server MUST validate that a non-zero delivery-tag refers to an delivered message, + and raise an exception if this is not the case. + </doc> + </rule> + </field> + </command> + + <!-- - Command: file.reject - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="reject" code="0xd" label="reject an incoming message"> + <doc> + This command allows a client to reject a message. It can be used to return untreatable + messages to their original queue. Note that file content is staged before delivery, so the + client will not use this command to interrupt delivery of a large message. + </doc> + + <rule name="server-interpretation"> + <doc> + The server SHOULD interpret this command as meaning that the client is unable to process + the message at this time. + </doc> + </rule> + + <rule name="not-selection"> + <doc> + A client MUST NOT use this command as a means of selecting messages to process. A rejected + message MAY be discarded or dead-lettered, not necessarily passed to another client. + </doc> + </rule> + + <implement role="server" handle="MUST" /> + + <field name="delivery-tag" type="uint64"> + <doc> + the identifier of the message to be rejected + </doc> + <rule name="session-local"> + <doc> + The delivery tag is valid only within the session from which the message was received. + i.e. A client MUST NOT receive a message on one session and then reject it on another. + </doc> + </rule> + </field> + + <field name="requeue" type="bit" label="requeue the message"> + <doc> + If this field is zero, the message will be discarded. If this bit is 1, the server will + attempt to requeue the message. + </doc> + + <rule name="requeue-strategy"> + <doc> + The server MUST NOT deliver the message to the same client within the context of the + current session. The recommended strategy is to attempt to deliver the message to an + alternative consumer, and if that is not possible, to move the message to a dead-letter + queue. The server MAY use more sophisticated tracking to hold the message on the queue + and redeliver it to the same client at a later stage. + </doc> + </rule> + </field> + </command> + + </class> + + <!-- == Class: stream ======================================================================== --> + + <class name="stream" code="0xa" label="work with streaming content"> + <doc> + The stream class provides commands that support multimedia streaming. The stream class uses + the following semantics: one message is one packet of data; delivery is unacknowledged and + unreliable; the consumer can specify quality of service parameters that the server can try to + adhere to; lower-priority messages may be discarded in favor of high priority messages. + </doc> + + <doc type="grammar"> + stream = C:QOS S:QOS-OK + / C:CONSUME S:CONSUME-OK + / C:CANCEL + / C:PUBLISH content + / S:RETURN + / S:DELIVER content + </doc> + + <rule name="overflow-discard"> + <doc> + The server SHOULD discard stream messages on a priority basis if the queue size exceeds some + configured limit. + </doc> + </rule> + + <rule name="priority-levels"> + <doc> + The server MUST implement at least 2 priority levels for stream messages, where priorities + 0-4 and 5-9 are treated as two distinct levels. The server MAY implement up to 10 priority + levels. + </doc> + </rule> + + <rule name="acknowledgement-support"> + <doc> + The server MUST implement automatic acknowledgements on stream content. That is, as soon as + a message is delivered to a client via a Deliver command, the server must remove it from the + queue. + </doc> + </rule> + + <role name="server" implement="MAY" /> + <role name="client" implement="MAY" /> + + <!-- These are the properties for a Stream content --> + <struct name="stream-properties" size="4" code="0x1" pack="2"> + <field name="content-type" type="str8" label="MIME content type" /> + <field name="content-encoding" type="str8" label="MIME content encoding" /> + <field name="headers" type="map" label="message header field table" /> + <field name="priority" type="uint8" label="message priority, 0 to 9" /> + <field name="timestamp" type="datetime" label="message timestamp" /> + </struct> + + <domain name="return-code" type="uint16" label="return code from server"> + <doc> + The return code. The AMQP return codes are defined by this enum. + </doc> + <enum> + <choice name="content-too-large" value="311"> + <doc> + The client attempted to transfer content larger than the server could accept. + </doc> + </choice> + + <choice name="no-route" value="312"> + <doc> + The exchange cannot route a message, most likely due to an invalid routing key. Only + when the mandatory flag is set. + </doc> + </choice> + + <choice name="no-consumers" value="313"> + <doc> + The exchange cannot deliver to a consumer when the immediate flag is set. As a result of + pending data on the queue or the absence of any consumers of the queue. + </doc> + </choice> + </enum> + </domain> + + <!-- - Command: stream.qos - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="qos" code="0x1" label="specify quality of service"> + <doc> + This command requests a specific quality of service. The QoS can be specified for the + current session or for all sessions on the connection. The particular properties and + semantics of a qos command always depend on the content class semantics. Though the qos + command could in principle apply to both peers, it is currently meaningful only for the + server. + </doc> + + <implement role="server" handle="MUST" /> + + <response name="qos-ok" /> + + <field name="prefetch-size" type="uint32" label="pre-fetch window in octets"> + <doc> + The client can request that messages be sent in advance so that when the client finishes + processing a message, the following message is already held locally, rather than needing + to be sent within the session. Pre-fetching gives a performance improvement. This field + specifies the pre-fetch window size in octets. May be set to zero, meaning "no specific + limit". Note that other pre-fetch limits may still apply. + </doc> + </field> + + <field name="prefetch-count" type="uint16" label="pre-fetch window in messages"> + <doc> + Specifies a pre-fetch window in terms of whole messages. This field may be used in + combination with the prefetch-size field; a message will only be sent in advance if both + pre-fetch windows (and those at the session and connection level) allow it. + </doc> + </field> + + <field name="consume-rate" type="uint32" label="transfer rate in octets/second"> + <doc> + Specifies a desired transfer rate in octets per second. This is usually determined by the + application that uses the streaming data. A value of zero means "no limit", i.e. as + rapidly as possible. + </doc> + + <rule name="ignore-prefetch"> + <doc> + The server MAY ignore the pre-fetch values and consume rates, depending on the type of + stream and the ability of the server to queue and/or reply it. + </doc> + </rule> + + <rule name="drop-by-priority"> + <doc> + The server MAY drop low-priority messages in favor of high-priority messages. + </doc> + </rule> + </field> + + <field name="global" type="bit" label="apply to entire connection"> + <doc> + By default the QoS settings apply to the current session only. If this field is set, they + are applied to the entire connection. + </doc> + </field> + </command> + + <!-- - Command: stream.qos-ok - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="qos-ok" code="0x2" label="confirm the requested qos"> + <doc> + This command tells the client that the requested QoS levels could be handled by the server. + The requested QoS applies to all active consumers until a new QoS is defined. + </doc> + + <implement role="client" handle="MUST" /> + </command> + + <!-- - Command: stream.consume - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="consume" code="0x3" label="start a queue consumer"> + <doc> + This command asks the server to start a "consumer", which is a transient request for + messages from a specific queue. Consumers last as long as the session they were created on, + or until the client cancels them. + </doc> + + <rule name="min-consumers"> + <doc> + The server SHOULD support at least 16 consumers per queue, unless the queue was declared + as private, and ideally, impose no limit except as defined by available resources. + </doc> + </rule> + + <rule name="priority-based-delivery"> + <doc> + Streaming applications SHOULD use different sessions to select different streaming + resolutions. AMQP makes no provision for filtering and/or transforming streams except on + the basis of priority-based selective delivery of individual messages. + </doc> + </rule> + + <implement role="server" handle="MUST" /> + + <response name="consume-ok" /> + + <field name="queue" type="queue.name"> + <doc> + Specifies the name of the queue to consume from. + </doc> + + <exception name="queue-exists-if-empty" error-code="not-allowed"> + <doc> + If the queue name in this command is empty, the server MUST raise an exception. + </doc> + </exception> + </field> + + <field name="consumer-tag" type="str8"> + <doc> + Specifies the identifier for the consumer. The consumer tag is local to a connection, so + two clients can use the same consumer tags. + </doc> + + <exception name="not-existing-consumer" error-code="not-allowed"> + <doc> + The tag MUST NOT refer to an existing consumer. If the client attempts to create two + consumers with the same non-empty tag the server MUST raise an exception. + </doc> + </exception> + + <exception name="not-empty-consumer-tag" error-code="not-allowed"> + <doc> + The client MUST NOT specify a tag that is empty or blank. + </doc> + <doc type="scenario"> + Attempt to create a consumers with an empty tag. + </doc> + </exception> + </field> + + <field name="no-local" type="bit"> + <doc>If the no-local field is set the server will not send messages to the connection that + published them.</doc> + </field> + + <field name="exclusive" type="bit" label="request exclusive access"> + <doc> + Request exclusive consumer access, meaning only this consumer can access the queue. + </doc> + + <exception name="in-use" error-code="resource-locked"> + <doc> + If the server cannot grant exclusive access to the queue when asked, - because there are + other consumers active - it MUST raise an exception with return code 405 + (resource locked). + </doc> + </exception> + </field> + + <field name="nowait" type="bit" label="do not send a reply command"> + <doc> + If set, the server will not respond to the command. The client should not wait for a reply + command. If the server could not complete the command it will raise an exception. + </doc> + </field> + + <field name="arguments" type="map" label="arguments for consuming"> + <doc> + A set of arguments for the consume. The syntax and semantics of these arguments depends on + the providers implementation. + </doc> + </field> + </command> + + <!-- - Command: stream.consume-ok - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="consume-ok" code="0x4" label="confirm a new consumer"> + <doc> + This command provides the client with a consumer tag which it may use in commands that work + with the consumer. + </doc> + + <implement role="client" handle="MUST" /> + + <field name="consumer-tag" type="str8"> + <doc> + Holds the consumer tag specified by the client or provided by the server. + </doc> + </field> + </command> + + <!-- - Command: stream.cancel - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="cancel" code="0x5" label="end a queue consumer"> + <doc> + This command cancels a consumer. Since message delivery is asynchronous the client may + continue to receive messages for a short while after cancelling a consumer. It may process + or discard these as appropriate. + </doc> + + <implement role="server" handle="MUST" /> + + <field name="consumer-tag" type="str8" /> + </command> + + <!-- - Command: stream.publish - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="publish" code="0x6" label="publish a message"> + <doc> + This command publishes a message to a specific exchange. The message will be routed to + queues as defined by the exchange configuration and distributed to any active consumers as + appropriate. + </doc> + + <implement role="server" handle="MUST" /> + + <field name="exchange" type="exchange.name"> + <doc> + Specifies the name of the exchange to publish to. The exchange name can be empty, meaning + the default exchange. If the exchange name is specified, and that exchange does not exist, + the server will raise an exception. + </doc> + + <rule name="default"> + <doc> + The server MUST accept a blank exchange name to mean the default exchange. + </doc> + </rule> + + <exception name="refusal" error-code="not-implemented"> + <doc> + The exchange MAY refuse stream content in which case it MUST respond with an exception. + </doc> + </exception> + </field> + + <field name="routing-key" type="str8" label="Message routing key"> + <doc> + Specifies the routing key for the message. The routing key is used for routing messages + depending on the exchange configuration. + </doc> + </field> + + <field name="mandatory" type="bit" label="indicate mandatory routing"> + <doc> + This flag tells the server how to react if the message cannot be routed to a queue. If + this flag is set, the server will return an unroutable message with a Return command. If + this flag is zero, the server silently drops the message. + </doc> + + <rule name="implementation"> + <doc> + The server SHOULD implement the mandatory flag. + </doc> + </rule> + </field> + + <field name="immediate" type="bit" label="request immediate delivery"> + <doc> + This flag tells the server how to react if the message cannot be routed to a queue + consumer immediately. If this flag is set, the server will return an undeliverable message + with a Return command. If this flag is zero, the server will queue the message, but with + no guarantee that it will ever be consumed. + </doc> + + <rule name="implementation"> + <doc> + The server SHOULD implement the immediate flag. + </doc> + </rule> + </field> + + <segments> + <header required="true"> + <entry type="stream-properties"/> + </header> + <body/> + </segments> + </command> + + <!-- - Command: stream.return - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="return" code="0x7" label="return a failed message"> + <doc> + This command returns an undeliverable message that was published with the "immediate" flag + set, or an unroutable message published with the "mandatory" flag set. The reply code and + text provide information about the reason that the message was undeliverable. + </doc> + + <implement role="client" handle="MUST" /> + + <field name="reply-code" type="return-code" /> + + <field name="reply-text" type="str8" label="The localized reply text."> + <doc> + The localized reply text. This text can be logged as an aid to resolving issues. + </doc> + </field> + + <field name="exchange" type="exchange.name"> + <doc> + Specifies the name of the exchange that the message was originally published to. + </doc> + </field> + + <field name="routing-key" type="str8" label="Message routing key"> + <doc> + Specifies the routing key name specified when the message was published. + </doc> + </field> + + <segments> + <header required="true"> + <entry type="stream-properties"/> + </header> + <body/> + </segments> + </command> + + <!-- - Command: stream.deliver - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + + <command name="deliver" code="0x8" label="notify the client of a consumer message"> + <doc> + This command delivers a message to the client, via a consumer. In the asynchronous message + delivery model, the client starts a consumer using the Consume command, then the server + responds with Deliver commands as and when messages arrive for that consumer. + </doc> + + <implement role="client" handle="MUST" /> + + <field name="consumer-tag" type="str8" /> + + <field name="delivery-tag" type="uint64"> + <doc> + The server-assigned and session-specific delivery tag + </doc> + <rule name="session-local"> + <doc> + The delivery tag is valid only within the session from which the message was received. + i.e. A client MUST NOT receive a message on one session and then acknowledge it on + another. + </doc> + </rule> + </field> + + <field name="exchange" type="exchange.name"> + <doc> + Specifies the name of the exchange that the message was originally published to. + </doc> + </field> + + <field name="queue" type="queue.name" required="true"> + <doc> + Specifies the name of the queue that the message came from. Note that a single session can + start many consumers on different queues. + </doc> + </field> + + <segments> + <header required="true"> + <entry type="stream-properties"/> + </header> + <body/> + </segments> + </command> + + </class> + +</amqp> diff --git a/qpid/specs/management-schema.xml b/qpid/specs/management-schema.xml index eab1033805..e37921a8f5 100644 --- a/qpid/specs/management-schema.xml +++ b/qpid/specs/management-schema.xml @@ -47,7 +47,11 @@ <class name="system"> <configElement name="sysId" index="y" type="sstr" access="RC"/> - <!-- RT config/instrumentation TBD --> + <configElement name="osName" type="sstr" access="RO" desc="Operating System Name"/> + <configElement name="nodeName" type="sstr" access="RO" desc="Node Name"/> + <configElement name="release" type="sstr" access="RO"/> + <configElement name="version" type="sstr" access="RO"/> + <configElement name="machine" type="sstr" access="RO"/> </class> @@ -57,20 +61,18 @@ =============================================================== --> <class name="broker"> - <configElement name="systemRef" type="objId" access="RC" index="y" desc="System ID"/> + <configElement name="systemRef" type="objId" access="RC" index="y" desc="System ID" parentRef="y"/> <configElement name="port" type="uint16" access="RC" index="y" desc="TCP Port for AMQP Service"/> <configElement name="workerThreads" type="uint16" access="RO" desc="Thread pool size"/> <configElement name="maxConns" type="uint16" access="RO" desc="Maximum allowed connections"/> <configElement name="connBacklog" type="uint16" access="RO" desc="Connection backlog limit for listening socket"/> <configElement name="stagingThreshold" type="uint32" access="RO" desc="Broker stages messages over this size to disk"/> - <configElement name="storeLib" type="sstr" access="RO" desc="Name of persistent storage library"/> - <configElement name="asyncStore" type="bool" access="RO" desc="Use async persistent store"/> <configElement name="mgmtPubInterval" type="uint16" access="RW" unit="second" min="1" desc="Interval for management broadcasts"/> - <configElement name="initialDiskPageSize" type="uint32" access="RO" desc="Number of disk pages allocated for storage"/> - <configElement name="initialPagesPerQueue" type="uint32" access="RO" desc="Number of disk pages allocated per queue"/> <configElement name="clusterName" type="sstr" access="RO" - desc="Name of cluster this server is a member of, zero-length for standalone server"/> + desc="Name of cluster this server is a member of"/> <configElement name="version" type="sstr" access="RO" desc="Running software version"/> + <configElement name="dataDirEnabled" type="bool" access="RO" desc="Persistent configuration storage enabled"/> + <configElement name="dataDir" type="sstr" access="RO" desc="Persistent configuration storage location"/> <method name="joinCluster"> <arg name="clusterName" dir="I" type="sstr"/> @@ -91,6 +93,15 @@ <!-- =============================================================== + Management Agent + =============================================================== + --> + <class name="agent"> + <configElement name="id" type="uuid" access="RO" index="y" desc="Agent ID"/> + </class> + + <!-- + =============================================================== Virtual Host =============================================================== --> @@ -111,6 +122,8 @@ <configElement name="durable" type="bool" access="RC"/> <configElement name="autoDelete" type="bool" access="RC"/> <configElement name="exclusive" type="bool" access="RC"/> + <configElement name="arguments" type="ftable" access="RO" desc="Arguments supplied in queue.declare"/> + <configElement name="storeRef" type="objId" access="RO" desc="Reference to persistent queue (if durable)"/> <instElement name="msgTotalEnqueues" type="count64" unit="message" desc="Total messages enqueued"/> <instElement name="msgTotalDequeues" type="count64" unit="message" desc="Total messages dequeued"/> @@ -137,9 +150,7 @@ <instElement name="consumers" type="hilo32" unit="consumer" desc="Current consumers on queue"/> <instElement name="bindings" type="hilo32" unit="binding" desc="Current bindings"/> <instElement name="unackedMessages" type="hilo32" unit="message" desc="Messages consumed but not yet acked"/> - <instElement name="messageLatencyMin" type="uint64" unit="nanosecond" desc="Minimum broker latency through this queue"/> - <instElement name="messageLatencyMax" type="uint64" unit="nanosecond" desc="Maximum broker latency through this queue"/> - <instElement name="messageLatencyAvg" type="uint64" unit="nanosecond" desc="Average broker latency through this queue"/> + <instElement name="messageLatency" type="mmaTime" unit="nanosecond" desc="Broker latency through this queue"/> <method name="purge" desc="Discard all messages on queue"/> </class> @@ -170,10 +181,10 @@ =============================================================== --> <class name="binding"> - <configElement name="exchangeRef" type="objId" access="RC" index="y" parentRef="y"/> - <configElement name="queueRef" type="objId" access="RC" index="y"/> - <configElement name="bindingKey" type="sstr" access="RC" index="y"/> -<!--<configElement name="arguments" type="fieldTable" access="RC"/> --> + <configElement name="exchangeRef" type="objId" access="RC" index="y" parentRef="y"/> + <configElement name="queueRef" type="objId" access="RC" index="y"/> + <configElement name="bindingKey" type="sstr" access="RC" index="y"/> + <configElement name="arguments" type="ftable" access="RC"/> <instElement name="msgMatched" type="count64"/> </class> @@ -203,11 +214,14 @@ =============================================================== --> <class name="link"> - <configElement name="vhostRef" type="objId" access="RC" index="y" parentRef="y"/> - <configElement name="address" type="sstr" access="RC" index="y"/> + + This class represents an inter-broker connection. + + <configElement name="vhostRef" type="objId" access="RC" index="y" parentRef="y"/> + <configElement name="address" type="sstr" access="RC" index="y"/> + <configElement name="authIdentity" type="sstr" access="RO"/> <instElement name="closing" type="bool" desc="This link is closing by management request"/> - <instElement name="authIdentity" type="sstr"/> <instElement name="framesFromPeer" type="count64"/> <instElement name="framesToPeer" type="count64"/> <instElement name="bytesFromPeer" type="count64"/> diff --git a/qpid/specs/management-types.xml b/qpid/specs/management-types.xml index 842a18cb30..b3e08a612f 100644 --- a/qpid/specs/management-types.xml +++ b/qpid/specs/management-types.xml @@ -19,16 +19,20 @@ under the License. --> -<type name="objId" base="REF" cpp="uint64_t" encode="@.putLongLong (#)" decode="# = @.getLongLong ()" accessor="direct" init="0"/> -<type name="uint8" base="U8" cpp="uint8_t" encode="@.putOctet (#)" decode="# = @.getOctet ()" accessor="direct" init="0"/> -<type name="uint16" base="U16" cpp="uint16_t" encode="@.putShort (#)" decode="# = @.getShort ()" accessor="direct" init="0"/> -<type name="uint32" base="U32" cpp="uint32_t" encode="@.putLong (#)" decode="# = @.getLong ()" accessor="direct" init="0"/> -<type name="uint64" base="U64" cpp="uint64_t" encode="@.putLongLong (#)" decode="# = @.getLongLong ()" accessor="direct" init="0"/> -<type name="bool" base="BOOL" cpp="uint8_t" encode="@.putOctet (#?1:0)" decode="# = @.getOctet ()==1" accessor="direct" init="0"/> -<type name="sstr" base="SSTR" cpp="std::string" encode="@.putShortString (#)" decode="@.getShortString (#)" accessor="direct" init='""'/> -<type name="lstr" base="LSTR" cpp="std::string" encode="@.putLongString (#)" decode="@.getLongString (#)" accessor="direct" init='""'/> -<type name="absTime" base="ABSTIME" cpp="uint64_t" encode="@.putLongLong (#)" decode="# = @.getLongLong ()" accessor="direct" init="0"/> -<type name="deltaTime" base="DELTATIME" cpp="uint64_t" encode="@.putLongLong (#)" decode="# = @.getLongLong ()" accessor="direct" init="0"/> +<type name="objId" base="REF" cpp="uint64_t" encode="@.putLongLong (#)" decode="# = @.getLongLong ()" accessor="direct" init="0"/> +<type name="uint8" base="U8" cpp="uint8_t" encode="@.putOctet (#)" decode="# = @.getOctet ()" accessor="direct" init="0"/> +<type name="uint16" base="U16" cpp="uint16_t" encode="@.putShort (#)" decode="# = @.getShort ()" accessor="direct" init="0"/> +<type name="uint32" base="U32" cpp="uint32_t" encode="@.putLong (#)" decode="# = @.getLong ()" accessor="direct" init="0"/> +<type name="uint64" base="U64" cpp="uint64_t" encode="@.putLongLong (#)" decode="# = @.getLongLong ()" accessor="direct" init="0"/> +<type name="bool" base="BOOL" cpp="uint8_t" encode="@.putOctet (#?1:0)" decode="# = @.getOctet ()==1" accessor="direct" init="0"/> +<type name="sstr" base="SSTR" cpp="std::string" encode="@.putShortString (#)" decode="@.getShortString (#)" accessor="direct" init='""'/> +<type name="lstr" base="LSTR" cpp="std::string" encode="@.putLongString (#)" decode="@.getLongString (#)" accessor="direct" init='""'/> +<type name="absTime" base="ABSTIME" cpp="uint64_t" encode="@.putLongLong (#)" decode="# = @.getLongLong ()" accessor="direct" init="0"/> +<type name="deltaTime" base="DELTATIME" cpp="uint64_t" encode="@.putLongLong (#)" decode="# = @.getLongLong ()" accessor="direct" init="0"/> +<type name="float" base="FLOAT" cpp="float" encode="@.putFloat (#)" decode="# = @.getFloat ()" accessor="direct" init="0."/> +<type name="double" base="DOUBLE" cpp="double" encode="@.putDouble (#)" decode="# = @.getDouble ()" accessor="direct" init="0."/> +<type name="uuid" base="UUID" cpp="framing::Uuid" encode="#.encode (@)" decode="#.decode (@)" accessor="direct"/> +<type name="ftable" base="FTABLE" cpp="framing::FieldTable" encode="#.encode (@)" decode="#.decode (@)" accessor="direct"/> <type name="hilo8" base="U8" cpp="uint8_t" encode="@.putOctet (#)" decode="# = @.getOctet ()" style="wm" accessor="counter" init="0"/> <type name="hilo16" base="U16" cpp="uint16_t" encode="@.putShort (#)" decode="# = @.getShort ()" style="wm" accessor="counter" init="0"/> @@ -41,8 +45,9 @@ <type name="count64" base="U64" cpp="uint64_t" encode="@.putLongLong (#)" decode="# = @.getLongLong ()" accessor="counter" init="0"/> <!-- Min/Max/Average statistics --> -<type name="mma32" base="U32" cpp="uint32_t" encode="@.putLong (#)" decode="# = @.getLong ()" style="mma" accessor="direct" init="0"/> -<type name="mma64" base="U64" cpp="uint64_t" encode="@.putLongLong (#)" decode="# = @.getLongLong ()" style="mma" accessor="direct" init="0"/> +<type name="mma32" base="U32" cpp="uint32_t" encode="@.putLong (#)" decode="# = @.getLong ()" style="mma" accessor="direct" init="0"/> +<type name="mma64" base="U64" cpp="uint64_t" encode="@.putLongLong (#)" decode="# = @.getLongLong ()" style="mma" accessor="direct" init="0"/> +<type name="mmaTime" base="DELTATIME" cpp="uint64_t" encode="@.putLongLong (#)" decode="# = @.getLongLong ()" style="mma" accessor="direct" init="0"/> <!-- Some Proposed Syntax for User-Defined Types: <enum name="enumeratedType" base="U8"> |