On 25/02/14 10:28, Gordon Sim wrote:
[...]
Similarly neither QMF nor AMQP 1.0 Management
provide a way to introspect Method formal parameters. So you can get
hold of the available Method names for sure, but arguments/parameters??
I think you can get this over QMF. For example, using qpid-tool you
can type: schema queue, and that gives you all the methods as well as
their parameters including types and a basic description.
I *thought* that schema information was only available from QMFv1 (where
it was necessary because QMF1 wasn't "self describing"). TBH it has been
a while since I looked, but back in the day when I was testing my Java
QMF2 stuff the C++ broker didn't return useful things for most of the
schema query methods. It *might* have been updated since then, but I
sort of doubt it (because schema stuff is generally not needed in QMF2)
I actually put together a patch along with a Jira *ages* ago (I think it
was ~0.12 timeframe) that took the QMF1 schema stuff and mapped it into
QMF2 Map Messages, but nobody seemed especially interested (again I
guess because the schema stuff isn't essential for QMF2) so I didn't
particularly pursue it. I've attached the patch for interest though
clearly things have moved on a bit since then.
It's certainly not available from AMQP 1.0 Management though :-/
Schema stuff is one of those interesting things that you kind of don't
miss until you miss it :-) I think that QMF1 was a bit of a pain because
you needed to have it, so I quite like that you can live without it with
QMF2 and AMQP 1.0 Management, but OTOH if you want to put together any
platform agnostic tooling it turns out to be kind of handy :-)
That said, I've got no great love of the mechanism QMF2 adopted for
schema retrieval, I guess it allows one to retrieve a bit at a time, but
I'm not clear why you'd want to do that, if you actually care about
schema you'd *probably* want the whole thing in which case you wind up
with a whole bunch of network calls. In this day and age I'd personally
opt for a simple call that can return a Container's Management Schema as
a JSON object (or perhaps an AMQP Map might be more appropriate).
Rob I'd be interested in your thoughts, the AMQP 1.0 Management
Specification certainly allows partial schema information given
GET-TYPES, GET-ATTRIBUTES and GET-OPERATIONS and I think if the latter
two are specified without an entityType they will return all attributes
and methods (the returned map has entityType as a key) but
GET-OPERATIONS only returns the method names, there's nothing that
describes arguments, which is I think a limitation.
As I say my gut feeling is that returning the entire schema in something
JSON like is:
a) Likely to be the most useful thing to do from the perspective of
users of the Management system
b) Is likely to be the easiest thing to implement - as it stands you end
up with some slightly fiddly code and you almost certainly have to
encode your schema information in some slightly unnatural way, whereas
it's likely to be pretty simple to create say a JSON document (or even
string in the code) to describe the Management Schema and either simply
return the JSON or encode it as AMQP Map/List etc. (and both the C++ and
Java brokers already have code to do that 'cause that's pretty much what
the Address Parser does).
I'm interested to understand the logic behind the current thinking
'cause the more I think about it the more I think it's not as useful as
it could be.
Frase
--- a/src/qpid/management/ManagementAgent.cpp 2011-07-14 09:53:37.000000000 +0100
+++ b/src/qpid/management/ManagementAgent.cpp 2011-12-19 12:56:50.000000000 +0000
@@ -1883,7 +1883,9 @@
sendCommandCompleteLH(replyToKey, sequence);
}
-
+/*
+ * Handles QMF2 Query request/response.
+ */
void ManagementAgent::handleGetQueryLH(const string& body, const string& rte, const string& rtk, const string& cid, bool viaLocal)
{
moveNewObjectsLH();
@@ -1901,7 +1903,7 @@
headers["qmf.agent"] = viaLocal ? "broker" : name_address;
/*
- * Unpack the _what element of the query. Currently we only support OBJECT queries.
+ * Unpack the _what element of the query and validate it.
*/
i = inMap.find("_what");
if (i == inMap.end()) {
@@ -1914,31 +1916,204 @@
return;
}
- if (i->second.asString() != "OBJECT") {
- sendExceptionLH(rte, rtk, cid, "Query for _what => '" + i->second.asString() + "' not supported");
+ /*
+ * Handle query for SCHEMA_ID. Return the SchemaId of all the Schema that the ManagementAgent supports.
+ */
+ if (i->second.asString() == "SCHEMA_ID") {
+ headers["qmf.content"] = "_schema_id";
+ Variant::List list;
+ for (PackageMap::iterator pIter = packages.begin(); pIter != packages.end(); pIter++) { // For each package.
+ const string& packageName = pIter->first;
+
+ ClassMap& cMap = pIter->second;
+ for (ClassMap::iterator cIter = cMap.begin(); cIter != cMap.end(); cIter++) { // For each class in a package.
+ const string& className = cIter->first.name;
+ const uint8_t* hash = cIter->first.hash;
+ const string& type = (cIter->second.kind == ManagementItem::CLASS_KIND_EVENT) ? "_event" : "_data";
+ list.push_back(mapEncodeSchemaId(packageName, className, type, hash));
+ }
+ }
+
+ string content;
+ ListCodec::encode(list, content);
+ sendBufferLH(content, cid, headers, "amqp/list", rte, rtk);
+ QPID_LOG(debug, "SENT QueryResponse (SCHEMA_ID query) to=" << rte << "/" << rtk);
+ return;
+ }
+
+ /*
+ * Handle query for OBJECT_ID. Return the ObjectId of all the objects registered with the ManagementAgent.
+ */
+ if (i->second.asString() == "OBJECT_ID") {
+ headers["qmf.content"] = "_object_id";
+ Variant::List list;
+ for (ManagementObjectMap::iterator iter = managementObjects.begin();
+ iter != managementObjects.end();
+ iter++) {
+ Variant::Map oidMap;
+ iter->first.mapEncode(oidMap);
+ list.push_back(oidMap);
+ }
+
+ string content;
+ ListCodec::encode(list, content);
+ sendBufferLH(content, cid, headers, "amqp/list", rte, rtk);
+ QPID_LOG(debug, "SENT QueryResponse (OBJECT_ID query) to=" << rte << "/" << rtk);
return;
}
string className;
string packageName;
+ SchemaClassKey key;
/*
- * Handle the _schema_id element, if supplied.
+ * Handle the _schema_id element, if supplied. We process this here as it's used by both SCHEMA and OBJECT queries
*/
- i = inMap.find("_schema_id");
- if (i != inMap.end() && i->second.getType() == qpid::types::VAR_MAP) {
- const Variant::Map& schemaIdMap(i->second.asMap());
+ Variant::Map::iterator j = inMap.find("_schema_id");
+ if (j != inMap.end() && j->second.getType() == qpid::types::VAR_MAP) {
+ Variant::Map& schemaIdMap(j->second.asMap());
Variant::Map::const_iterator s_iter = schemaIdMap.find("_class_name");
- if (s_iter != schemaIdMap.end() && s_iter->second.getType() == qpid::types::VAR_STRING)
+ if (s_iter != schemaIdMap.end() && s_iter->second.getType() == qpid::types::VAR_STRING) {
className = s_iter->second.asString();
+ schemaIdMap["_cname"] = className; // SchemaClassKey seems to use _cname not _class_name, why? is this legacy?
+ }
s_iter = schemaIdMap.find("_package_name");
if (s_iter != schemaIdMap.end() && s_iter->second.getType() == qpid::types::VAR_STRING)
packageName = s_iter->second.asString();
+
+ key.mapDecode(schemaIdMap); // Setting "_cname" and using key.mapDecode() is a handy way to populate the key.
+ schemaIdMap.erase("_cname");// Tidy up _schema_id by removing _cname again after using it to create the key.
+ }
+
+ /*
+ * Handle query for SCHEMA. Look up the SchemaClass object from the _schema_id info obtained from the query.
+ *
+ * This code is a bit untidy as we "transcode" from QMF1 to QMF2 format as I'm not familiar enough with the
+ * ManagementObject code generation to generate a mapEncodeSchema(). That approach would also need changes to
+ * registerClass(), registerEvent(), SchemaClass and a few other places because things are a bit tied to the
+ * QMF1 writeSchemaCall signature. So although this block is a bit untidy it's probably the least disruptive way
+ * to add support for QMF2 schema serialisation.
+ */
+ if (i->second.asString() == "SCHEMA") {
+ string QMF_TYPE[] = {"TYPE_VOID", "TYPE_INT", "TYPE_INT", "TYPE_INT", "TYPE_INT", "TYPE_VOID",
+ "TYPE_STRING", "TYPE_STRING", "TYPE_INT", "TYPE_INT", "TYPE_MAP", "TYPE_BOOL",
+ "TYPE_FLOAT", "TYPE_FLOAT", "TYPE_UUID", "TYPE_MAP", "TYPE_INT", "TYPE_INT",
+ "TYPE_INT", "TYPE_INT", "TYPE_VOID", "TYPE_LIST"};
+ string ACCESS[] = {"INVALID", "RC", "RW", "RO"};
+
+ headers["qmf.content"] = "_schema";
+ Variant::List list;
+
+ PackageMap::iterator pIter = packages.find(packageName);
+ if (pIter != packages.end()) { // If a package called packageName exists we then look up the class.
+ ClassMap& cMap = pIter->second; // A Map of classes associated to each package.
+ ClassMap::iterator cIter = cMap.find(key);
+ if (cIter != cMap.end()) { // If a class in the package matches the key (className + hash) we return schema.
+ SchemaClass& classInfo = cIter->second;
+ if (classInfo.hasSchema()) {
+ Buffer buf(outputBuffer, MA_BUFFER_SIZE); // Initialise the QMF1 buffer
+ classInfo.appendSchema(buf); // Serialise the schema into QMF1 buffer
+ buf.reset();
+
+ // We already know the SchemaClassId so we can mostly ignore ignore those QMF1 packed values.
+ uint8_t type = buf.getOctet(); // ManagementItem::CLASS_KIND_TABLE or ManagementItem::CLASS_KIND_EVENT
+ buf.getShortString(packageName); // Ignore Package Name
+ buf.getShortString(className); // Ignore Class Name
+ uint8_t hash[16];
+ buf.getBin128(hash); // Ignore Schema Hash
+
+ uint16_t methodCount = 0;
+ uint16_t propertyCount = buf.getShort(); // Initialise with Property Count
+
+ if (type == ManagementItem::CLASS_KIND_TABLE) { // For SchemaObjectClass
+ propertyCount += buf.getShort(); // Add Statistics Count
+ methodCount = buf.getShort(); // Method Count
+ }
+
+ Variant::List propertylist; // The List each property Map will be encoded into.
+ Variant::List methodlist; // The List each method will be encoded into.
+ Variant::Map propertyMap; // The Map each property will be encoded into.
+ FieldTable ft; // We decode the QMF1 Buffer into this FieldTable.
+ for (int p = 0; p < propertyCount; p++) { // Transcode Schema Properties in this block.
+ ft.decode(buf);
+ propertyMap.clear(); // We reuse this Map each iteration so clear it out.
+ for (FieldTable::ValueMap::const_iterator ci = ft.begin(); ci != ft.end(); ci++) {
+ const string& key = "_" + ci->first;
+ Variant variant = toVariant(ci->second);
+ if (key == "_type") { // Render type as QMF_TYPE string
+ propertyMap[key] = QMF_TYPE[variant.asUint8()];
+ } else if (key == "_access") { // Render access as either "RC", "RW", "RO
+ propertyMap[key] = ACCESS[variant.asUint8()];
+ } else if (key == "_index" || key == "_optional") { // These properties are explicitly
+ propertyMap[key] = variant.asBool(); // boolean according to QMF2 protocol.
+ } else {
+ propertyMap[key] = variant;
+ }
+ }
+ propertylist.push_back(propertyMap);
+ }
+
+ Variant::Map methodMap; // The Map each method will be encoded into.
+ Variant::Map argumentMap; // The Map each method argument will be encoded into.
+ Variant::List argumentList; // The List of arguments for each method.
+ for (int m = 0; m < methodCount; m++) { // Transcode Schema Methods in this block.
+ ft.decode(buf);
+ methodMap.clear(); // We reuse this Map each iteration so clear it out.
+ uint16_t argCount = 0;
+ for (FieldTable::ValueMap::const_iterator ci = ft.begin(); ci != ft.end(); ci++) {
+ const string& key = "_" + ci->first;
+ Variant variant = toVariant(ci->second);
+ if (key == "_argCount") {
+ argCount = variant.asUint16();
+ } else {
+ methodMap[key] = variant;
+ }
+ }
+
+ argumentList.clear(); // We reuse this List each iteration so clear it out.
+ for (int a = 0; a < argCount; a++) { // Transcode Schema Method Arguments in this block.
+ ft.decode(buf);
+ argumentMap.clear(); // We reuse this Map each iteration so clear it out.
+ for (FieldTable::ValueMap::const_iterator ci = ft.begin(); ci != ft.end(); ci++) {
+ const string& key = "_" + ci->first;
+ Variant variant = toVariant(ci->second);
+ if (key == "_type") { // Render type as QMF_TYPE string
+ argumentMap[key] = QMF_TYPE[variant.asUint8()];
+ } else {
+ argumentMap[key] = variant;
+ }
+ }
+ argumentList.push_back(argumentMap);
+ }
+
+ methodMap["_arguments"] = argumentList;
+ methodlist.push_back(methodMap);
+ }
+
+ Variant::Map schemaMap; // The Map the schema will be encoded into.
+ schemaMap["_schema_id"] = j->second.asMap(); // Found previously via j = inMap.find("_schema_id");
+ schemaMap["_properties"] = propertylist;
+ schemaMap["_methods"] = methodlist;
+ list.push_back(schemaMap);
+ }
+ }
+ }
+
+ string content;
+ ListCodec::encode(list, content);
+ sendBufferLH(content, cid, headers, "amqp/list", rte, rtk);
+ QPID_LOG(debug, "SENT QueryResponse (SCHEMA query) to=" << rte << "/" << rtk);
+ return;
}
+ if (i->second.asString() != "OBJECT") {
+ sendExceptionLH(rte, rtk, cid, "Query for _what => '" + i->second.asString() + "' not supported");
+ return;
+ }
+
/*
* Unpack the _object_id element of the query if it is present. If it is present, find that one
* object and return it. If it is not present, send a class-based result.
@@ -1975,7 +2150,7 @@
ListCodec::encode(list_, content);
sendBufferLH(content, cid, headers, "amqp/list", rte, rtk);
- QPID_LOG(debug, "SENT QueryResponse (query by object_id) to=" << rte << "/" << rtk);
+ QPID_LOG(debug, "SENT QueryResponse (OBJECT query by object_id) to=" << rte << "/" << rtk);
return;
}
} else {
@@ -2029,12 +2204,12 @@
ListCodec::encode(_list.front().asList(), content);
sendBufferLH(content, cid, headers, "amqp/list", rte, rtk);
_list.pop_front();
- QPID_LOG(debug, "SENT QueryResponse (partial, query by schema_id) to=" << rte << "/" << rtk << " len=" << content.length());
+ QPID_LOG(debug, "SENT QueryResponse (partial, OBJECT query by schema_id) to=" << rte << "/" << rtk << " len=" << content.length());
}
headers.erase("partial");
ListCodec::encode(_list.size() ? _list.front().asList() : Variant::List(), content);
sendBufferLH(content, cid, headers, "amqp/list", rte, rtk);
- QPID_LOG(debug, "SENT QueryResponse (query by schema_id) to=" << rte << "/" << rtk << " len=" << content.length());
+ QPID_LOG(debug, "SENT QueryResponse (OBJECT query by schema_id) to=" << rte << "/" << rtk << " len=" << content.length());
return;
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]