qpid-users mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Fraser Adams <fraser.ad...@blueyonder.co.uk>
Subject Re: Java QMF library and ObjectId
Date Fri, 19 Sep 2014 11:50:29 GMT
On 18/09/14 10:36, Michal Zerola wrote:
> Hello Fraser and thanks for an exhaustive answer.
No probs, was that exhaustive or exhausting :-D
>
> There are still some foggy parts in this behavior for me, but let me clarify
> firstly some parts which were not explained clearly from my side:
>
> * we are connecting to the C++ Qpid broker
> * we are really using asynchronous mechanism from Java QMF API:
>
>  From what you have written we might be really the only users using
> query+subscribe mechanism :) but we are quite satisfied with it.
Firstly I'm pleased that you are quite satisfied with it, quite a bit of 
effort ended up going into the Query Subscription stuff for reasons I'll 
mention below.

Cards on the table, I actually wrote the Java QMF2 API > 3 years ago and 
it's been pretty stable, so I've not really had to do too much 
tinkering, so I'm a bit rusty on some of this myself (you are now the 
expert, enjoy ;->). As it happens I've never really needed to use the 
Query Subscription stuff in my own applications so writing the 
QpidQueueStats demo was pretty much the last time I actually used it.

There's a little bit of history, basically "back in the day" I came 
across the QMF2 API that I referenced the other day and it seemed like a 
good idea to implement it and as I'm slightly obsessive I ended up 
implementing it all - imagine my surprise when I realised that more or 
less everyone using QMF2 was doing their own thing, hence the C++ 
library API is totally different to my Java one and as I mentioned there 
are *two* python ones etc. etc. and indeed many people simply roll their 
own forsaking APIs altogether - take a look at Gordon's "Re: QPID C++ - 
Dynamically Managing Broker" post from yesterday to see what I mean. 
It's one of those things really, c'est la vie :-) So really there ended 
up being a lot of fairly sophisticated stuff in the Java implementation 
of the API that *nobody* else implemented nor is anyone ever likely too, 
hence me mentioning the possibility that I might look to 
simplify/rationalise/cull when I finally get round to trying to tie up 
QMF with the Management spec. I'm not saying I will, I just want to give 
you a little bit of fair warning just in case.

Another thing that you may not be aware of is that Query Subscriptions 
to the C++ broker "cheat". In my own Agent code I actually properly 
implemented the server side of the Query Subscription, but for the case 
of the C++ broker Management Agent this was never implemented (as I say 
nobody else ended up making use of some of this fancier stuff). However 
the C++ broker *does* send periodic "_data_indication" data pushes, so I 
intercept those on the client (Console) side and create "fake" 
SubscriptionIndication events from them. It's a bit cheeky, but it 
actually works pretty well as you've observed. If you weren't aware of 
this cheekiness, then clearly it's working pretty well.

So there you have it a bit of a potted history of the Java QMF API - 
take a look at the onMessage in Console.java if you are curious.

One thing that I should add though is that like the getObjects() calls 
on the "synchronous" API the SubscriptionIndication resultList contains 
QmfConsoleData objects that are constructed from the Map messages 
received from the broker "_data_indication", so I'm afraid that, as 
before, the ObjectIDs that you are seeing are exactly what has been sent 
by the broker and not some fabrication made up by the Java QMF 
implementation, and as I said in my mail the other day, the broker is 
perfectly at liberty to use "predictable" ObjectIDs 'cause the QMF2 spec 
says it can either use Unique Object IDs or the predictable ones made 
from name plus namespace info.


>   In
> particular, what we do is following:
>
> - create qmfPredicate describing what kind of objects are we interested in
> - create subscription based on the query using the predicate
> - register subscription to the console
> - in the onEvent() callback we receive updates and from the WorkItem we
> finally mine the List<QmfConsoleData> with that ObjectId(s) using which we
> identify what was changed/deleted/added and store it in our internal
> structures
Hopefully you've already noticed this, but the QmfConsoleData allows you 
to get create/update/delete timestamps and there's also an "isDeleted" 
method that lets you check if the object that has been pushed has been 
deleted on the broker. If you look in QpidQueueStats I make use of that 
in the block of code here....

                 List<QmfConsoleData> data = indication.getData();
                 for (QmfConsoleData record : data)
                 {
                     ObjectId id = record.getObjectId();
                     if (record.isDeleted())
                     { // If the object was deleted by the Agent we 
remove it from out Map
                         _objects.remove(id);
                     }
                     else
                     {
.....

>
> Now why do we have a problem with the 'recycled' ObjectIds. Because of
> auditing/monitoring reasons we need to keep track of also deleted objects on
> the broker (for some time).
As I said yesterday I'm afraid that you are going to have to think about 
your algorithm, 'cause the broker does what it does wrt. ObjectIDs.

As mentioned above though you should be able to identify that any given 
object has been deleted on the broker by using the isDeleted() call and 
remove it from your local object store.

> Things are more complicated since objects can
> reference each other - e.g. binding references queue and exchange it binds
> together. However, if the references are the same for deleted and newly
> created objects (with the same name) it is quite a challenge to keep track
> of what is referencing what.
yeah I definitely know about the binding queueRef and exchangeRef 
(QpidConfig uses that all over the place). "However, if the references 
are the same for deleted and newly created objects " the thing is though 
that when you delete a queue the bindings for that queue get deleted 
too!!! So you should be able to check for the isDeleted() on the binding 
objects that get pushed from the broker and remove those from your local 
store too.


>
> Of course, if it is given by the broker, there is not too much we can do
> about it.
I'm afraid that's exactly the position. FWIW I don't know if you are 
aware of this, but there is a pretty complete QMF GUI in the
qpid/qpid-trunk/qpid/tools/src/java/qpid-qmf2-tools/src/main/java/org/apache/qpid/qmf2/tools
area (well actually the RestAPI back end is there, the UI is in 
qpid/qpid-trunk/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web).

As it happens that UI (it's all JavaScript based) maintains a set of 
Maps on the browser that represent a client side representation of the 
broker state (there are a lot of Maps keyed by ObjectID) and that quite 
happily tracks state when I add/remove queues/exchanges/bindings etc. 
certainly if I add a queue then add bindings between that queue and an 
exchange, then if I delete the queue and re-add it I just see the queue 
object, without the bindings, 'cause they've been blatted as a result of 
deleting the queue.

Basically you just have to be a little more explicit with your state 
management than making an assumption that if you add an object with a 
specific name multiple times the broker will treat them as unique 
separate objects, as I say as far as the broker is actually concerned an 
object with a given name added then deleted then added is treated as the 
*same* object.

>
> Now, what still puzzles me:
>
> * from the Python I have been using qmf.console API and again it's
> asynchronous mechanism to receive object's updates. Briefly:
>
> ...Session(self.eventListener, rcvObjects=True, manageConnections=True,
> userBindings=True, rcvEvents=True)
> ...bindClass("org.apache.qpid.broker", "queue")
>
> where eventListener implements objectProps(self, broker, record) and
> objectStats(self, broker, record) callbacks. From here I am getting Object
> ID:
>
> oid = record.getObjectId()
>
> and here I can see that the ID has a different ('more unique') format: e.g.
> the same queue recreated had 0-2-1-0-15440 and later after deletion
> 0-2-1-0-1592.
That's definitely a QMF1 style ObjectID, I'm afraid that I know very 
little about the python asynchronous API nor really about QMF1 - I 
pretty much started out with QMF2.

>
> I can buy the argument that it uses older QMF1 protocol, but looking into
> the code of console.py I can see that it supports both QMF1 + QMF2 and if
> the QMF2 is available, the temporary request/response queues created should
> have form qmfc-v2-... - which is exactly what I can see on the broker.
I'm afraid that I can't help there, though I can recall a thread from 
the Easter before last on this API, if you can find that on nabble it 
might give you a few pointers.
>
> This makes me think: is my Python client communicating with QMF1 or QMF2? If
> QMF2 why do I see different ObjectId's than in Java QMF if they should be
> generated by the broker?
>
> Is there something I am missing?
What broker version are you using BTW? If you do qpidd -h you'll see 
something like:
--mgmt-qmf2 yes|no (1)                Enable broadcast of management
                                         information over QMF v2
   --mgmt-qmf1 yes|no (0)                Enable broadcast of management
                                         information over QMF v1


In more recent Qpid versions as you can see from the above the qmf1 
broadcast is disabled by default, but in earlier versions IIRC both were 
enabled. If you see something different to what I've copied above you 
could have a play with these flags to see if you get results that make 
some vague sense.

My guess is simply that the broker maintains both QMF1 and QMF2 style 
ObjectIDs and whilst the QMF1 ones are more random the QMF2 ones are as 
I've described previously.

I'm not an expert on the broker side of things, but if you look in
qpid/qpid-trunk/qpid/cpp/src/qpid/management/ManagementAgent.cpp you 
will see a call to (line 358)

// Deprecated:  V1 objects
ObjectId ManagementAgent::addObject(ManagementObject::shared_ptr object, 
uint64_t persistId, bool persistent)
{
     uint16_t sequence;
     uint64_t objectNum;

     sys::Mutex::ScopedLock lock(addLock);
     sequence = persistent ? 0 : bootSequence;
     objectNum = persistId ? persistId : nextObjectId++;

     ObjectId objId(0 /*flags*/, sequence, brokerBank, objectNum);
     objId.setV2Key(*object);   // let object generate the v2 key

     object->setObjectId(objId);

     newManagementObjects.push_back(object);
     QPID_LOG(debug, "Management object (V1) added: " << objId.getV2Key());
     return objId;
}

Which looks to me like every time addObject() is called for the QMF1 
form of objects objectNum is incremented.

whereas for V2 objects there is code:

ObjectId ManagementAgent::addObject(ManagementObject::shared_ptr object,
                                     const string& key,
                                     bool persistent)
{
     uint16_t sequence;

     sequence = persistent ? 0 : bootSequence;

     ObjectId objId(0 /*flags*/, sequence, brokerBank);
     if (key.empty()) {
         objId.setV2Key(*object);   // let object generate the key
     } else {
         objId.setV2Key(key);
     }

     object->setObjectId(objId);
     {
         sys::Mutex::ScopedLock lock(addLock);
         newManagementObjects.push_back(object);
     }
     QPID_LOG(debug, "Management object added: " << objId.getV2Key());
     return objId;
}

The setV2Key is in ManagementObject.cpp and looks like:

// generate the V2 key from the index fields defined
// in the schema.
void ObjectId::setV2Key(const ManagementObject& object)
{
     stringstream oname;
     oname << object.getPackageName() << ":" << object.getClassName() <<

":" << object.getKey();
     v2Key = oname.str();
}


Which I think backs up pretty much everything I've been saying, so 
basically the V1 Object IDs use an incrementing objectNum in their 
construction (which will result in multiple instances of any object that 
happens to have the same name be given different IDs) whereas for V2 
ObjectIDs are derived from package name (e.g. org.apache.qpid.broker), 
class name (e.g. queue) and "key" - which is from the "name" attribute 
of the managed object, e.g. "test_queue".


>
> Thank you.
Again no problem at all, though I'm afraid that again I've clearly not 
given you the answers that you might like, but I believe what I've given 
you is at least comprehensive and factually correct, which will 
hopefully help you to understand why you are seeing what you are seeing 
a little better.

Best regards and HTH,
Frase



---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@qpid.apache.org
For additional commands, e-mail: users-help@qpid.apache.org


Mime
View raw message