Hi Michal
Epic TL;DR response follows - I'm afraid I got on a roll ..........
You are using QMF to talk to the c++ broker (qpidd) right?
Presumably when you say "the QMF library delivers this update with
ObjectId of the form..... " you are doing this via the getObjects()
call? E.g. something like:
List<QmfConsoleData> queues =
_console.getObjects("org.apache.qpid.broker", "queue");
Is that correct?
If so the code for that is in
qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/Console.java
(line 697 on trunk currently).
As far as I can recall that code simply reflects the information in the
QMF object (e.g. the queue objects in your case) that was sent by the
broker. It receives the message from JMS via:
Message response =
_responder.receive(timeout*1000);
then subsequently puts the information into a List<QmfConsoleData> via
if (AMQPMessage.isAMQPList(response))
{
List<Map> mapResults =
AMQPMessage.getList(response);
partials.ensureCapacity(partials.size() + mapResults.size());
for (Map content : mapResults)
{
partials.add(new
QmfConsoleData(content, agent));
}
}
That code caters for the fact that JMS doesn't support lists terribly well
qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/AMQPMessage.java
provides some code to make it a bit easier to deal with Map and List
messages in java.util Containers.
It basically first decodes the JMS message as a List<Map> because QMF2
query responses get sent by the Broker as an AMQP List of AMQP Maps. I
then go through and create a List<QmfConsoleData> where the
QmfConsoleData is basically a wrapper around a java.util.Map
I've looked at QmfConsoleData and when that gets constructed or
initialised the ObjectId instance _object_id (which is actually owned by
the parent QmfDescribed class) gets set via
new ObjectId((Map)m.get("_object_id"));
Which is explicitly getting the property named "_object_id" from the Map
used to construct the QmfConsoleData, which is as I say a property that
has been set in the QMF2 Message sent by the broker.
So in a long, roundabout, thinking out loud nutshell I'm pretty sure
that the "particular reason why the Java implementation of QMF doesn't
generate more unique ObjectId" is because it's not the Java
Implementation of QMF that is actually generating the ObjectId in the
first place, it's simply representing exactly what was sent to it by the
Broker.
You mention that you've also been using the Python QMF implementation,
can I ask which one? There are actually two Python QMF implementations
there's one that lives in:
qpid/extras/qmf/src/py/qmf/console.py
and is used by things like qpid-queue-stats and a few other things. This
is the asynchronous API and starts something like:
class BrokerManager(Console):
The other python API lives in
qpid/qpid-trunk/qpid/tools/src/py/qpidtoollibs/broker.py
And that's the one used by the python qpid-config etc.
Looking in broker.py getAllQueues calls getAllBrokerObjects which calls
_doClassQuery which does
items = []
done = False
while not done:
for item in response.content:
items.append(item)
Which again appears pretty much to be reflecting what is being sent by
the broker.
So when you say you've used Python's QMF implementation I'm suspecting
that you aren't using getAllQueues from qpidtoollibs/broker.py. but are
in fact using qmf/console.py?
(sorry this is now really TL;DR but I tend to think out loud and the
play back is I think useful - my Maths lecturer always drummed into me
"show your working" so it's all his fault :-))
Right....... I've now just proved to my satisfaction that it is indeed
the behaviour of the Broker. What I've just done is gently hacked the
qpid-config port that I've just written in JavaScript, that's a good
"control" because it's independent of either the Java QMF or
qpidtoollibs and also uses AMQP 1.0 and proton Messenger so should rule
out any accidental bias.
What I did was hack the queueListRecurse method to look like this:
for (var i = 0; i < queues.length; i++) {
var queue = queues[i];
var queueId = oid(queue._object_id);
console.log("queueId = " + queueId);
.......
So I'm basically printing out the ObjectId that I get directly from the
QMF response. In my console I see....
<start qpidd here>
./qpid-config.js -r queues
queueId =
1815:org.apache.qpid.broker:queue:4c44dfde-5ea6-4370-d102-549237ac4ac7_#
Queue '4c44dfde-5ea6-4370-d102-549237ac4ac7_#'
bind [4c44dfde-5ea6-4370-d102-549237ac4ac7_#] => ''
<that's the oid of the temporary QMF response queue BTW. In another
window I do qpid-config add queue test>
./qpid-config.js -r queues
queueId =
1815:org.apache.qpid.broker:queue:b5250fd9-80fc-4208-94e0-8e1bbc41106d_#
Queue 'b5250fd9-80fc-4208-94e0-8e1bbc41106d_#'
bind [b5250fd9-80fc-4208-94e0-8e1bbc41106d_#] => ''
queueId = 1815:org.apache.qpid.broker:queue:test
Queue 'test'
bind [test] => ''
<So now you can see the oid of the "test" queue. In the other window I
then do qpid-config del queue test>
./qpid-config.js -r queues
queueId =
1815:org.apache.qpid.broker:queue:5daeb2a4-11a3-4c4c-a9ff-eee19947a9db_#
Queue '5daeb2a4-11a3-4c4c-a9ff-eee19947a9db_#'
bind [5daeb2a4-11a3-4c4c-a9ff-eee19947a9db_#] => ''
<as you can see, it's gone again. Now I do qpid-config add queue test again>
./qpid-config.js -r queues
queueId =
1815:org.apache.qpid.broker:queue:f7e8307e-c8ce-441c-8279-f543ad91b076_#
Queue 'f7e8307e-c8ce-441c-8279-f543ad91b076_#'
bind [f7e8307e-c8ce-441c-8279-f543ad91b076_#] => ''
queueId = 1815:org.apache.qpid.broker:queue:test
Queue 'test'
bind [test] => ''
Clearly queueId = 1815:org.apache.qpid.broker:queue:test is back and has
essentially replicated your observation, but it's demonstrably yielding
what the Broker is telling it and is not a case of the QMF library
(Java, qpidtoollibs or the JavaScript) itself generating any ObjectID.
What I *think* you are observing in the qmf/console.py library is
possibly something to do with the old QMF1 messages (IIRC the Broker can
send both formats) the old QMF1 format was a binary form and the OIDs
were IIRC quite different in structure (I'm afraid I don't know too much
about QMF1).
Now that I think about it (sorry it's taken a while until the penny
finally dropped) the QMF2 specification
(https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal)
actually says something about ObjectIDs and allows them to be "predictable"
"
An object identifier uniquely addresses a data object within the domain
of its managing agent. QMF represents the object identifier as an opaque
string. An object identifier can be assigned to the object by the agent
when the object is instantiated.
Alternatively, a schema can define an object identifier by defining an
ordered list of names of data items. In this case, the object identifier
string is built by concatenating the string representations of the value
of each named data item. This approach is similar to defining index
fields within a database record.
"
It's the second paragraph that is in fact important for the purposes of
your observations "concatenating the string representations of the value
of each named data item" - does this ring any bells:
1815:org.apache.qpid.broker:queue:test
TBH I should have twigged sooner, I've actually been bitten by this in
the opposite direction. As it happens I implemented a QMF2 plugin for
the JavaBroker a while back and I actually originally implemented the
ObjectIDs by assigning UUIDs, but when I started testing it I noticed it
didn't work with the Python qpid-config, though it *did* work with my
Java qpid-config port...
I eventually figured out that my Java and JavaScript qpid-configs make
no assumptions about the ObjectID, so in order to invoke CRUD methods
(like in qpid-config add queue test) they query for the "Broker"
management object and invoke methods on that, but what qpidtoollibs does
is the following:
"
def _method(self, method, arguments,
addr="org.apache.qpid.broker:broker:amqp-broker", timeout=10):
props = {'method' : 'request',
'qmf.opcode' : '_method_request',
'x-amqp-0-10.app-id' : 'qmf2'}
correlator = str(self.next_correlator)
self.next_correlator += 1
content = {'_object_id' : {'_object_name' : addr},
'_method_name' : method,
'_arguments' : arguments}
message = self.message_class(
content, reply_to=self.reply_to, correlation_id=correlator,
properties=props, subject="broker")
self.tx.send(message)
response = self.reply_rx.fetch(timeout)
self.sess.acknowledge()
if response.properties['qmf.opcode'] == '_exception':
raise Exception("Exception from Agent: %r" %
response.content['_values'])
if response.properties['qmf.opcode'] != '_method_response':
raise Exception("bad response: %r" % response.properties)
return response.content['_arguments']
"
In other words it uses "org.apache.qpid.broker:broker:amqp-broker" which
is actually the ObjectID of the Broker Management Object, so
qpidtoollibs is choosing not to query for the Broker Management Object
to retrieve its ObjectID because it knows that the C++ Broker has
implemented ObjectIDs according to the second paragraph I quoted above.
So in precis:
1) The Java, Python qpidtoollibs and JavaScript QMF2 code are all
behaving as expected and are not in fact generating their own ObjectIDs
they are using what the Broker sent them.
2) The Broker is indeed behaving in the way that you observed, but is in
fact behaving correctly according to the QMF2 Specification.
Sorry it took so long to reach a conclusion that you probably didn't
want to hear, but hopefully the journey to the conclusion was
enlightening - and at least shows I'm not making it all up :-D
Sorry this probably doesn't help with your problem. I'm a bit torn - as
I say I originally implemented OIDs using UUIDs and changed the
behaviour to be consistent with the C++ Broker and qpidtoollibs, but
OTOH I suspect that for many people (if not the vast majority -
excepting you obviously) If you create a queue called "test" it's
probably actually quite useful to be able to refer to it by name
org.apache.qpid.broker:queue:test and if you delete it and re-add it
there's likely a reasonable argument either way as to whether you might
want it to be the *same* Object (e.g. the same object name in the same
logical namespace such as a queue and this approach gives your IDs
temporal persistence even surviving Broker restarts) the other side of
the coin is that having them unique is also not necessarily an
unreasonable assumption too - but that I'm afraid is not the way it's
interpreted by the Broker, and making them unique would itself break
qpid-config and most likely a bunch of other QMF tools, so it's probably
not going to gain much traction trying to push for a change in behaviour.
Re "It is quite important for us, since we want to keep track of also
deleted objects on our side. However, if the QMF library gives us object
with the same ObjectId (which we already keep as deleted) we have a
collision. "
I think TBH that you are simply going to have to change your algorithm.
In practice if you delete a queue named test_queue then re-add the queue
test_queue you've basically restored exactly the same QMF object as far
as the Broker is concerned, so technically you don't have a collision
you have an ObjectID that refers to the same Object and the deletion of
test_queue was merely transient behaviour.
Now if you actually *care* that the queue has been temporarily deleted
(perhaps for auditing purposes or whatever) then you have a few options
- but as far as the Broker is concerned they are the *same* Object.
If you want to identify that this has occurred you need to bear in mind
that although you say "we are using Java QMF library for receiving
asynchronously broker's objects updates " if you are using the
getObjects() stuff you are not actually working asynchronously you are
infact polling.
To deal more asynchronously you could either use QMF2 Events (which are
delivered asynchronously) and either use the info from the Event or use
that to trigger a call to getObjects() - look in
qpid/qpid-trunk/qpid/tools/src/java/qpid-qmf2-tools/src/main/java/org/apache/qpid/qmf2/tools
The ConnectionAudit.java and ConnectionLogger.java examples both
basically receive QMF Events
if (wi instanceof EventReceivedWorkItem)
.....
Then subsequently go on to do a bunch of getObjects() calls, so you
would see IIRC queueDeclare/queueDelete events that would relate to your
"test_queue"
Alternatively you could make use of the QMF2 API "Query Subscription"
mechanism. If you look in QpidQueueStats.java I've given an example of
how to use that, now I *personally* wouldn't go down that route.
Although my Java QMF2 API has implemented everything specified in
https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal (as
you may have gathered I'm just a bit OCD) as it happens the Java QMF
implementation is actually the only API that ever actually got round to
implementing all of that...... what basically happened was the
realisation that the QMF2 protocol is really when all is said and done
just a bunch of Map Messages, so there's a bit of an argument whether
one should use an API or simply just use the Map Messages. My view is
that there's more of an argument for an API in strongly typed languages
like Java and C++ (most of QmfConsoleData is actually defensive code
that I discovered the hard way 'cause qpidd sends strings as AMQP byte
and I got killed by ClassCastException when I tried to cast byte[] to
String :o)). It's also woth pointing out that there's likely to be a (I
suspect vaguely slow) migration from QMF to AMQP 1.0 Management
Specification and at some point when I get my act together I'll probably
try to provide a mechanism to get info from both protocols to ease the
transition, at which point I *might* deprecate some of the more
fancy/fiddly features of the QMF2 API 'cause I suspect that nobody has
actually used them since I wrote that demo...... If you stick to
getObjects and the Event stuff I'm more likely to try to make that
polyglot than the Query Subscription stuff, which was flipping fiddly to
implement if I'm honest.
I really hope that this has been useful even if not quite what you'd
hoped for, sorry it ended up as "War and Peace", but as a result you now
probably know about as much as anyone does on QMF :->
Finally, although I don't mind answering PM'd questions I'd be grateful
if you could post any other qpid related questions to the user list -
it's mainly because there's a fighting change that if you have a Qpid
related question you won't be the only one who might like to know the
answer.
Cheers,
Frase
On 17/09/14 12:07, michal.zer...@gmail.com wrote:
Hello Fraser,
we are using Java QMF library for receiving asynchronously broker's objects
updates. I have a question regarding the ObjectId - which is and object
identifying the broker's object change.
Imagine I create a queue test_queue on the broker, the QMF library delivers
this update with ObjectId of the form (toString()):
@2...@org.apache.qpid.broker:queue:test_queue
Now I delete the queue and I get the update identified again with the same
ObjectId. So far so good. But if I create the queue again (with the same name),
QMF library sends me the update again with the same ObjectId. This is different
comparing to e.g. Python's QMF implementation, where I would get the new ID.
It is quite important for us, since we want to keep track of also deleted
objects on our side. However, if the QMF library gives us object with the same
ObjectId (which we already keep as deleted) we have a collision.
So questions from my side:
- Is there a particular reason why the Java implementation of QMF doesn't
generate more unique ObjectId (like Python implementation does)?
- Would it be possible to change this behavior? We can implement it by
ourselves and provide the patch to the Qpid community if you can give us brief
instructions on it.
Thank you in advance!
Best regards,
Michal
_____________________________________
Sent from http://qpid.2158936.n2.nabble.com