[mingw-qpid-cpp: 10/28] Added QMF2 patch from the native qpid-cpp package.
Kalev Lember
kalev at fedoraproject.org
Wed Mar 7 17:17:39 UTC 2012
commit 2a752bb558ed7a49974188632e53df61d5142656
Author: Ted Ross <tross at redhat.com>
Date: Mon Feb 21 22:22:20 2011 -0500
Added QMF2 patch from the native qpid-cpp package.
mingw32-qpid-cpp.spec | 7 +-
qpid-fedora-patch.patch |16808 +++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 16813 insertions(+), 2 deletions(-)
---
diff --git a/mingw32-qpid-cpp.spec b/mingw32-qpid-cpp.spec
index 7422606..0148111 100644
--- a/mingw32-qpid-cpp.spec
+++ b/mingw32-qpid-cpp.spec
@@ -7,7 +7,7 @@
Name: mingw32-qpid-cpp
Version: 0.8
-Release: 0.5%{?dist}
+Release: 0.6%{?dist}
Summary: MinGW Windows port of AMQP C++ Daemons and Libraries
Group: Development/Libraries
@@ -17,7 +17,7 @@ URL: http://qpid.apache.org
Source0: http://people.apache.org/~robbie/qpid/%{version}/RC3/qpid-%{version}.tar.gz
Patch6: qpid-mingw.patch
-Patch7: boost_filesystem_v2.patch
+Patch7: qpid-fedora-patch.patch
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
@@ -85,6 +85,9 @@ rm -rf $RPM_BUILD_ROOT
%doc cpp/RELEASE_NOTES
%changelog
+* Mon Feb 21 2011 Ted Ross <tross at apache.org> - 0.8-0.6
+- Added QMF2 patch from the native qpid-cpp package.
+
* Tue Feb 15 2011 Ted Ross <tross at apache.org> - 0.8-0.5
- Added definition to force use of boost_filesystem API version 2.
diff --git a/qpid-fedora-patch.patch b/qpid-fedora-patch.patch
new file mode 100644
index 0000000..4c9719d
--- /dev/null
+++ b/qpid-fedora-patch.patch
@@ -0,0 +1,16808 @@
+Index: cpp/src/qmf/Agent.cpp
+===================================================================
+--- cpp/src/qmf/Agent.cpp (revision 1056407)
++++ cpp/src/qmf/Agent.cpp (working copy)
+@@ -27,6 +27,7 @@
+ #include "qmf/Query.h"
+ #include "qmf/SchemaImpl.h"
+ #include "qmf/agentCapability.h"
++#include "qmf/constants.h"
+ #include "qpid/messaging/Sender.h"
+ #include "qpid/messaging/AddressParser.h"
+ #include "qpid/management/Buffer.h"
+@@ -70,8 +71,8 @@
+
+
+ AgentImpl::AgentImpl(const std::string& n, uint32_t e, ConsoleSessionImpl& s) :
+- name(n), epoch(e), session(s), touched(true), untouchedCount(0), capability(0),
+- nextCorrelator(1), schemaCache(s.schemaCache)
++ name(n), directSubject(n), epoch(e), session(s), touched(true), untouchedCount(0), capability(0),
++ sender(session.directSender), nextCorrelator(1), schemaCache(s.schemaCache)
+ {
+ }
+
+@@ -82,6 +83,11 @@
+ try {
+ capability = v.asUint32();
+ } catch (std::exception&) {}
++ if (k == "_direct_subject")
++ try {
++ directSubject = v.asString();
++ sender = session.topicSender;
++ } catch (std::exception&) {}
+ }
+
+ const Variant& AgentImpl::getAttribute(const string& k) const
+@@ -113,7 +119,9 @@
+ context->cond.wait(context->lock,
+ qpid::sys::AbsTime(qpid::sys::now(),
+ qpid::sys::Duration(milliseconds * qpid::sys::TIME_MSEC)));
+- if (context->response.isValid() && context->response.isFinal())
++ if (context->response.isValid() &&
++ ((context->response.getType() == CONSOLE_QUERY_RESPONSE && context->response.isFinal()) ||
++ (context->response.getType() == CONSOLE_EXCEPTION)))
+ result = context->response;
+ else {
+ auto_ptr<ConsoleEventImpl> impl(new ConsoleEventImpl(CONSOLE_EXCEPTION));
+@@ -331,7 +339,7 @@
+ uint32_t correlator;
+ boost::shared_ptr<SyncContext> context;
+
+- QPID_LOG(trace, "RCVD MethodResponse map=" << response);
++ QPID_LOG(trace, "RCVD MethodResponse cid=" << cid << " map=" << response);
+
+ aIter = response.find("_arguments");
+ if (aIter != response.end())
+@@ -369,9 +377,43 @@
+ }
+
+
+-void AgentImpl::handleDataIndication(const Variant::List&, const Message&)
++void AgentImpl::handleDataIndication(const Variant::List& list, const Message& msg)
+ {
+- // TODO
++ Variant::Map::const_iterator aIter;
++ const Variant::Map& props(msg.getProperties());
++ boost::shared_ptr<SyncContext> context;
++
++ aIter = props.find("qmf.content");
++ if (aIter == props.end())
++ return;
++
++ string content_type(aIter->second.asString());
++ if (content_type != "_event")
++ return;
++
++ for (Variant::List::const_iterator lIter = list.begin(); lIter != list.end(); lIter++) {
++ const Variant::Map& eventMap(lIter->asMap());
++ Data data(new DataImpl(eventMap, this));
++ int severity(SEV_NOTICE);
++ uint64_t timestamp(0);
++
++ aIter = eventMap.find("_severity");
++ if (aIter != eventMap.end())
++ severity = int(aIter->second.asInt8());
++
++ aIter = eventMap.find("_timestamp");
++ if (aIter != eventMap.end())
++ timestamp = aIter->second.asUint64();
++
++ auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_EVENT));
++ eventImpl->setAgent(this);
++ eventImpl->addData(data);
++ eventImpl->setSeverity(severity);
++ eventImpl->setTimestamp(timestamp);
++ if (data.hasSchema())
++ learnSchemaId(data.getSchemaId());
++ session.enqueueEvent(eventImpl.release());
++ }
+ }
+
+
+@@ -507,17 +549,21 @@
+ Variant::Map map;
+ Variant::Map& headers(msg.getProperties());
+
+- headers["method"] = "request";
+- headers["qmf.opcode"] = "_query_request";
+- headers["x-amqp-0-10.app-id"] = "qmf2";
++ headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_REQUEST;
++ headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_QUERY_REQUEST;
++ headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF;
+
+ msg.setReplyTo(session.replyAddress);
+ msg.setCorrelationId(boost::lexical_cast<string>(correlator));
+- msg.setSubject(name);
++ msg.setSubject(directSubject);
++ string userId(session.connection.getAuthenticatedUsername());
++ if (!userId.empty())
++ msg.setUserId(userId);
+ encode(QueryImplAccess::get(query).asMap(), msg);
+- session.directSender.send(msg);
+-
+- QPID_LOG(trace, "SENT QueryRequest to=" << name);
++ if (sender.isValid()) {
++ sender.send(msg);
++ QPID_LOG(trace, "SENT QueryRequest to=" << sender.getName() << "/" << directSubject << " cid=" << correlator);
++ }
+ }
+
+
+@@ -527,9 +573,9 @@
+ Variant::Map map;
+ Variant::Map& headers(msg.getProperties());
+
+- headers["method"] = "request";
+- headers["qmf.opcode"] = "_method_request";
+- headers["x-amqp-0-10.app-id"] = "qmf2";
++ headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_REQUEST;
++ headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_METHOD_REQUEST;
++ headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF;
+
+ map["_method_name"] = method;
+ map["_object_id"] = addr.asMap();
+@@ -537,11 +583,15 @@
+
+ msg.setReplyTo(session.replyAddress);
+ msg.setCorrelationId(boost::lexical_cast<string>(correlator));
+- msg.setSubject(name);
++ msg.setSubject(directSubject);
++ string userId(session.connection.getAuthenticatedUsername());
++ if (!userId.empty())
++ msg.setUserId(userId);
+ encode(map, msg);
+- session.directSender.send(msg);
+-
+- QPID_LOG(trace, "SENT MethodRequest method=" << method << " to=" << name);
++ if (sender.isValid()) {
++ sender.send(msg);
++ QPID_LOG(trace, "SENT MethodRequest method=" << method << " to=" << sender.getName() << "/" << directSubject << " content=" << map << " cid=" << correlator);
++ }
+ }
+
+ void AgentImpl::sendSchemaRequest(const SchemaId& id)
+@@ -577,10 +627,14 @@
+ Message msg;
+ msg.setReplyTo(session.replyAddress);
+ msg.setContent(content);
+- msg.setSubject(name);
+- session.directSender.send(msg);
+-
+- QPID_LOG(trace, "SENT V1SchemaRequest to=" << name);
++ msg.setSubject(directSubject);
++ string userId(session.connection.getAuthenticatedUsername());
++ if (!userId.empty())
++ msg.setUserId(userId);
++ if (sender.isValid()) {
++ sender.send(msg);
++ QPID_LOG(trace, "SENT V1SchemaRequest to=" << sender.getName() << "/" << directSubject);
++ }
+ }
+
+
+Index: cpp/src/qmf/Schema.cpp
+===================================================================
+--- cpp/src/qmf/Schema.cpp (revision 1056407)
++++ cpp/src/qmf/Schema.cpp (working copy)
+@@ -192,6 +192,55 @@
+ }
+
+
++bool SchemaImpl::isValidProperty(const std::string& k, const Variant& v) const
++{
++ for (list<SchemaProperty>::const_iterator iter = properties.begin(); iter != properties.end(); iter++)
++ if (iter->getName() == k)
++ return (isCompatibleType(iter->getType(), v.getType()));
++ return false;
++}
++
++
++bool SchemaImpl::isValidMethodInArg(const std::string& m, const std::string& k, const Variant& v) const
++{
++ for (list<SchemaMethod>::const_iterator mIter = methods.begin(); mIter != methods.end(); mIter++) {
++ if (mIter->getName() == m) {
++ uint32_t count(mIter->getArgumentCount());
++ for (uint32_t i = 0; i < count; i++) {
++ const SchemaProperty prop(mIter->getArgument(i));
++ if (prop.getName() == k) {
++ if (prop.getDirection() == DIR_IN || prop.getDirection() == DIR_IN_OUT)
++ return (isCompatibleType(prop.getType(), v.getType()));
++ else
++ return false;
++ }
++ }
++ }
++ }
++ return false;
++}
++
++
++bool SchemaImpl::isValidMethodOutArg(const std::string& m, const std::string& k, const Variant& v) const
++{
++ for (list<SchemaMethod>::const_iterator mIter = methods.begin(); mIter != methods.end(); mIter++) {
++ if (mIter->getName() == m) {
++ uint32_t count(mIter->getArgumentCount());
++ for (uint32_t i = 0; i < count; i++) {
++ const SchemaProperty prop(mIter->getArgument(i));
++ if (prop.getName() == k) {
++ if (prop.getDirection() == DIR_OUT || prop.getDirection() == DIR_IN_OUT)
++ return (isCompatibleType(prop.getType(), v.getType()));
++ else
++ return false;
++ }
++ }
++ }
++ }
++ return false;
++}
++
++
+ void SchemaImpl::finalize()
+ {
+ Hash hash;
+@@ -246,6 +295,57 @@
+ }
+
+
++bool SchemaImpl::isCompatibleType(int qmfType, qpid::types::VariantType qpidType) const
++{
++ bool typeValid(false);
++
++ switch (qpidType) {
++ case qpid::types::VAR_VOID:
++ if (qmfType == SCHEMA_DATA_VOID)
++ typeValid = true;
++ break;
++ case qpid::types::VAR_BOOL:
++ if (qmfType == SCHEMA_DATA_BOOL)
++ typeValid = true;
++ break;
++ case qpid::types::VAR_UINT8:
++ case qpid::types::VAR_UINT16:
++ case qpid::types::VAR_UINT32:
++ case qpid::types::VAR_UINT64:
++ case qpid::types::VAR_INT8:
++ case qpid::types::VAR_INT16:
++ case qpid::types::VAR_INT32:
++ case qpid::types::VAR_INT64:
++ if (qmfType == SCHEMA_DATA_INT)
++ typeValid = true;
++ break;
++ case qpid::types::VAR_FLOAT:
++ case qpid::types::VAR_DOUBLE:
++ if (qmfType == SCHEMA_DATA_FLOAT)
++ typeValid = true;
++ break;
++ case qpid::types::VAR_STRING:
++ if (qmfType == SCHEMA_DATA_STRING)
++ typeValid = true;
++ break;
++ case qpid::types::VAR_MAP:
++ if (qmfType == SCHEMA_DATA_MAP)
++ typeValid = true;
++ break;
++ case qpid::types::VAR_LIST:
++ if (qmfType == SCHEMA_DATA_LIST)
++ typeValid = true;
++ break;
++ case qpid::types::VAR_UUID:
++ if (qmfType == SCHEMA_DATA_UUID)
++ typeValid = true;
++ break;
++ }
++
++ return typeValid;
++}
++
++
+ SchemaImpl& SchemaImplAccess::get(Schema& item)
+ {
+ return *item.impl;
+Index: cpp/src/qmf/DataImpl.h
+===================================================================
+--- cpp/src/qmf/DataImpl.h (revision 1056407)
++++ cpp/src/qmf/DataImpl.h (working copy)
+@@ -24,8 +24,10 @@
+ #include "qpid/RefCounted.h"
+ #include "qmf/Data.h"
+ #include "qmf/SchemaId.h"
++#include "qmf/Schema.h"
+ #include "qmf/DataAddr.h"
+ #include "qmf/Agent.h"
++#include "qmf/AgentSubscription.h"
+ #include "qpid/types/Variant.h"
+
+ namespace qmf {
+@@ -37,18 +39,21 @@
+ DataImpl(const qpid::types::Variant::Map&, const Agent&);
+ qpid::types::Variant::Map asMap() const;
+ DataImpl() {}
++ void addSubscription(boost::shared_ptr<AgentSubscription>);
++ void delSubscription(uint64_t);
++ qpid::types::Variant::Map publishSubscription(uint64_t);
++ const Schema& getSchema() const { return schema; }
+
+ //
+ // Methods from API handle
+ //
+- DataImpl(const SchemaId& s) : schemaId(s) {}
+- void setSchema(const SchemaId& s) { schemaId = s; }
++ DataImpl(const Schema& s) : schema(s) {}
+ void setAddr(const DataAddr& a) { dataAddr = a; }
+- void setProperty(const std::string& k, const qpid::types::Variant& v) { properties[k] = v; }
++ void setProperty(const std::string& k, const qpid::types::Variant& v);
+ void overwriteProperties(const qpid::types::Variant::Map& m);
+- bool hasSchema() const { return schemaId.isValid(); }
++ bool hasSchema() const { return schemaId.isValid() || schema.isValid(); }
+ bool hasAddr() const { return dataAddr.isValid(); }
+- const SchemaId& getSchemaId() const { return schemaId; }
++ const SchemaId& getSchemaId() const { if (schema.isValid()) return schema.getSchemaId(); else return schemaId; }
+ const DataAddr& getAddr() const { return dataAddr; }
+ const qpid::types::Variant& getProperty(const std::string& k) const;
+ const qpid::types::Variant::Map& getProperties() const { return properties; }
+@@ -56,7 +61,14 @@
+ const Agent& getAgent() const { return agent; }
+
+ private:
++ struct Subscr {
++ boost::shared_ptr<AgentSubscription> subscription;
++ qpid::types::Variant::Map deltas;
++ };
++ std::map<uint64_t, boost::shared_ptr<Subscr> > subscriptions;
++
+ SchemaId schemaId;
++ Schema schema;
+ DataAddr dataAddr;
+ qpid::types::Variant::Map properties;
+ Agent agent;
+Index: cpp/src/qmf/AgentSubscription.cpp
+===================================================================
+--- cpp/src/qmf/AgentSubscription.cpp (revision 0)
++++ cpp/src/qmf/AgentSubscription.cpp (revision 0)
+@@ -0,0 +1,51 @@
++/*
++ *
++ * Licensed to the Apache Software Foundation (ASF) under one
++ * or more contributor license agreements. See the NOTICE file
++ * distributed with this work for additional information
++ * regarding copyright ownership. The ASF licenses this file
++ * to you under the Apache License, Version 2.0 (the
++ * "License"); you may not use this file except in compliance
++ * with the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing,
++ * software distributed under the License is distributed on an
++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
++ * KIND, either express or implied. See the License for the
++ * specific language governing permissions and limitations
++ * under the License.
++ *
++ */
++
++#include "qmf/AgentSubscription.h"
++
++using namespace qmf;
++
++AgentSubscription::AgentSubscription(uint64_t _id, uint64_t _interval, uint64_t _life,
++ const std::string& _replyTo, const std::string& _cid, Query _query) :
++ id(_id), interval(_interval), lifetime(_life), timeSincePublish(0), timeSinceKeepalive(0),
++ replyTo(_replyTo), cid(_cid), query(_query)
++{
++}
++
++
++AgentSubscription::~AgentSubscription()
++{
++}
++
++
++bool AgentSubscription::tick(uint64_t seconds)
++{
++ timeSinceKeepalive += seconds;
++ if (timeSinceKeepalive >= lifetime)
++ return false;
++
++ timeSincePublish += seconds;
++ if (timeSincePublish >= interval) {
++ }
++
++ return true;
++}
++
+Index: cpp/src/qmf/DataAddr.cpp
+===================================================================
+--- cpp/src/qmf/DataAddr.cpp (revision 1056407)
++++ cpp/src/qmf/DataAddr.cpp (working copy)
+@@ -38,6 +38,7 @@
+ bool DataAddr::operator==(const DataAddr& o) { return *impl == *o.impl; }
+ bool DataAddr::operator<(const DataAddr& o) { return *impl < *o.impl; }
+
++DataAddr::DataAddr(const qpid::types::Variant::Map& m) { PI::ctor(*this, new DataAddrImpl(m)); }
+ DataAddr::DataAddr(const string& n, const string& a, uint32_t e) { PI::ctor(*this, new DataAddrImpl(n, a, e)); }
+ const string& DataAddr::getName() const { return impl->getName(); }
+ const string& DataAddr::getAgentName() const { return impl->getAgentName(); }
+Index: cpp/src/qmf/SubscriptionImpl.h
+===================================================================
+--- cpp/src/qmf/SubscriptionImpl.h (revision 0)
++++ cpp/src/qmf/SubscriptionImpl.h (revision 0)
+@@ -0,0 +1,57 @@
++#ifndef _QMF_SUBSCRIPTION_IMPL_H_
++#define _QMF_SUBSCRIPTION_IMPL_H_
++/*
++ *
++ * Licensed to the Apache Software Foundation (ASF) under one
++ * or more contributor license agreements. See the NOTICE file
++ * distributed with this work for additional information
++ * regarding copyright ownership. The ASF licenses this file
++ * to you under the Apache License, Version 2.0 (the
++ * "License"); you may not use this file except in compliance
++ * with the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing,
++ * software distributed under the License is distributed on an
++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
++ * KIND, either express or implied. See the License for the
++ * specific language governing permissions and limitations
++ * under the License.
++ *
++ */
++
++#include "qpid/RefCounted.h"
++#include "qmf/Subscription.h"
++
++namespace qmf {
++ class SubscriptionImpl : public virtual qpid::RefCounted {
++ public:
++ //
++ // Public impl-only methods
++ //
++ SubscriptionImpl(int p) : placeholder(p) {}
++ ~SubscriptionImpl();
++
++ //
++ // Methods from API handle
++ //
++ void cancel();
++ bool isActive() const;
++ void lock();
++ void unlock();
++ uint32_t getDataCount() const;
++ Data getData(uint32_t) const;
++
++ private:
++ int placeholder;
++ };
++
++ struct SubscriptionImplAccess
++ {
++ static SubscriptionImpl& get(Subscription&);
++ static const SubscriptionImpl& get(const Subscription&);
++ };
++}
++
++#endif
+Index: cpp/src/qmf/SchemaId.cpp
+===================================================================
+--- cpp/src/qmf/SchemaId.cpp (revision 1056407)
++++ cpp/src/qmf/SchemaId.cpp (working copy)
+@@ -78,7 +78,8 @@
+ result["_type"] = "_data";
+ else
+ result["_type"] = "_event";
+- result["_hash"] = hash;
++ if (!hash.isNull())
++ result["_hash"] = hash;
+ return result;
+ }
+
+Index: cpp/src/qmf/constants.h
+===================================================================
+--- cpp/src/qmf/constants.h (revision 0)
++++ cpp/src/qmf/constants.h (revision 0)
+@@ -0,0 +1,83 @@
++#ifndef QMF_CONSTANTS_H
++#define QMF_CONSTANTS_H
++/*
++ *
++ * Licensed to the Apache Software Foundation (ASF) under one
++ * or more contributor license agreements. See the NOTICE file
++ * distributed with this work for additional information
++ * regarding copyright ownership. The ASF licenses this file
++ * to you under the Apache License, Version 2.0 (the
++ * "License"); you may not use this file except in compliance
++ * with the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing,
++ * software distributed under the License is distributed on an
++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
++ * KIND, either express or implied. See the License for the
++ * specific language governing permissions and limitations
++ * under the License.
++ *
++ */
++
++#include <string>
++
++namespace qmf {
++
++ struct protocol {
++ /**
++ * Header key strings
++ */
++ static const std::string HEADER_KEY_APP_ID;
++ static const std::string HEADER_KEY_METHOD;
++ static const std::string HEADER_KEY_OPCODE;
++ static const std::string HEADER_KEY_AGENT;
++ static const std::string HEADER_KEY_CONTENT;
++ static const std::string HEADER_KEY_PARTIAL;
++
++ /**
++ * Header values per-key
++ */
++ static const std::string HEADER_APP_ID_QMF;
++
++ static const std::string HEADER_METHOD_REQUEST;
++ static const std::string HEADER_METHOD_RESPONSE;
++ static const std::string HEADER_METHOD_INDICATION;
++
++ static const std::string HEADER_OPCODE_EXCEPTION;
++ static const std::string HEADER_OPCODE_AGENT_LOCATE_REQUEST;
++ static const std::string HEADER_OPCODE_AGENT_LOCATE_RESPONSE;
++ static const std::string HEADER_OPCODE_AGENT_HEARTBEAT_INDICATION;
++ static const std::string HEADER_OPCODE_QUERY_REQUEST;
++ static const std::string HEADER_OPCODE_QUERY_RESPONSE;
++ static const std::string HEADER_OPCODE_SUBSCRIBE_REQUEST;
++ static const std::string HEADER_OPCODE_SUBSCRIBE_RESPONSE;
++ static const std::string HEADER_OPCODE_SUBSCRIBE_CANCEL_INDICATION;
++ static const std::string HEADER_OPCODE_SUBSCRIBE_REFRESH_INDICATION;
++ static const std::string HEADER_OPCODE_DATA_INDICATION;
++ static const std::string HEADER_OPCODE_METHOD_REQUEST;
++ static const std::string HEADER_OPCODE_METHOD_RESPONSE;
++
++ static const std::string HEADER_CONTENT_SCHEMA_ID;
++ static const std::string HEADER_CONTENT_SCHEMA_CLASS;
++ static const std::string HEADER_CONTENT_OBJECT_ID;
++ static const std::string HEADER_CONTENT_DATA;
++ static const std::string HEADER_CONTENT_EVENT;
++ static const std::string HEADER_CONTENT_QUERY;
++
++ /**
++ * Keywords for Agent attributes
++ */
++ static const std::string AGENT_ATTR_VENDOR;
++ static const std::string AGENT_ATTR_PRODUCT;
++ static const std::string AGENT_ATTR_INSTANCE;
++ static const std::string AGENT_ATTR_NAME;
++ static const std::string AGENT_ATTR_TIMESTAMP;
++ static const std::string AGENT_ATTR_HEARTBEAT_INTERVAL;
++ static const std::string AGENT_ATTR_EPOCH;
++ static const std::string AGENT_ATTR_SCHEMA_UPDATED_TIMESTAMP;
++ };
++}
++
++#endif
+Index: cpp/src/qmf/AgentImpl.h
+===================================================================
+--- cpp/src/qmf/AgentImpl.h (revision 1056407)
++++ cpp/src/qmf/AgentImpl.h (working copy)
+@@ -29,6 +29,7 @@
+ #include "qmf/SchemaCache.h"
+ #include "qpid/messaging/Session.h"
+ #include "qpid/messaging/Message.h"
++#include "qpid/messaging/Sender.h"
+ #include "qpid/sys/Mutex.h"
+ #include "qpid/sys/Condition.h"
+ #include <boost/shared_ptr.hpp>
+@@ -90,11 +91,13 @@
+
+ mutable qpid::sys::Mutex lock;
+ std::string name;
++ std::string directSubject;
+ uint32_t epoch;
+ ConsoleSessionImpl& session;
+ bool touched;
+ uint32_t untouchedCount;
+ uint32_t capability;
++ qpid::messaging::Sender sender;
+ qpid::types::Variant::Map attributes;
+ uint32_t nextCorrelator;
+ std::map<uint32_t, boost::shared_ptr<SyncContext> > contextMap;
+Index: cpp/src/qmf/SchemaImpl.h
+===================================================================
+--- cpp/src/qmf/SchemaImpl.h (revision 1056407)
++++ cpp/src/qmf/SchemaImpl.h (working copy)
+@@ -46,6 +46,9 @@
+ qpid::types::Variant::Map asMap() const;
+ SchemaImpl(qpid::management::Buffer& v1Buffer);
+ std::string asV1Content(uint32_t sequence) const;
++ bool isValidProperty(const std::string& k, const qpid::types::Variant& v) const;
++ bool isValidMethodInArg(const std::string& m, const std::string& k, const qpid::types::Variant& v) const;
++ bool isValidMethodOutArg(const std::string& m, const std::string& k, const qpid::types::Variant& v) const;
+
+ //
+ // Methods from API handle
+@@ -79,6 +82,7 @@
+
+ void checkFinal() const;
+ void checkNotFinal() const;
++ bool isCompatibleType(int qmfType, qpid::types::VariantType qpidType) const;
+ };
+
+ struct SchemaImplAccess
+Index: cpp/src/qmf/DataAddrImpl.h
+===================================================================
+--- cpp/src/qmf/DataAddrImpl.h (revision 1056407)
++++ cpp/src/qmf/DataAddrImpl.h (working copy)
+@@ -32,7 +32,6 @@
+ //
+ // Impl-only methods
+ //
+- DataAddrImpl(const qpid::types::Variant::Map&);
+ void setName(const std::string& n) { name = n; }
+ void setAgent(const std::string& n, uint32_t e=0) { agentName = n; agentEpoch = e; }
+
+@@ -41,6 +40,7 @@
+ //
+ bool operator==(const DataAddrImpl&);
+ bool operator<(const DataAddrImpl&);
++ DataAddrImpl(const qpid::types::Variant::Map&);
+ DataAddrImpl(const std::string& _name, const std::string& _agentName, uint32_t _agentEpoch=0) :
+ agentName(_agentName), name(_name), agentEpoch(_agentEpoch) {}
+ const std::string& getName() const { return name; }
+Index: cpp/src/qmf/AgentEvent.cpp
+===================================================================
+--- cpp/src/qmf/AgentEvent.cpp (revision 1056407)
++++ cpp/src/qmf/AgentEvent.cpp (working copy)
+@@ -21,6 +21,7 @@
+
+ #include "qmf/AgentEventImpl.h"
+ #include "qmf/PrivateImplRef.h"
++#include "qmf/SchemaImpl.h"
+
+ using namespace std;
+ using namespace qmf;
+@@ -64,6 +65,8 @@
+
+ void AgentEventImpl::addReturnArgument(const string& key, const Variant& val, const string& subtype)
+ {
++ if (schema.isValid() && !SchemaImplAccess::get(schema).isValidMethodOutArg(methodName, key, val))
++ throw QmfException("Output argument is unknown or the type is incompatible");
+ outArguments[key] = val;
+ if (!subtype.empty())
+ outArgumentSubtypes[key] = subtype;
+Index: cpp/src/qmf/SchemaMethod.cpp
+===================================================================
+--- cpp/src/qmf/SchemaMethod.cpp (revision 1056407)
++++ cpp/src/qmf/SchemaMethod.cpp (working copy)
+@@ -163,7 +163,7 @@
+ Variant::Map map;
+
+ map["name"] = name;
+- map["argCount"] = arguments.size();
++ map["argCount"] = (uint64_t) arguments.size();
+ if (!desc.empty())
+ map["desc"] = desc;
+
+Index: cpp/src/qmf/AgentSession.cpp
+===================================================================
+--- cpp/src/qmf/AgentSession.cpp (revision 1056407)
++++ cpp/src/qmf/AgentSession.cpp (working copy)
+@@ -30,6 +30,7 @@
+ #include "qmf/DataImpl.h"
+ #include "qmf/QueryImpl.h"
+ #include "qmf/agentCapability.h"
++#include "qmf/constants.h"
+ #include "qpid/sys/Mutex.h"
+ #include "qpid/sys/Condition.h"
+ #include "qpid/sys/Thread.h"
+@@ -71,6 +72,7 @@
+ void open();
+ void close();
+ bool nextEvent(AgentEvent& e, Duration t);
++ int pendingEvents() const;
+
+ void registerSchema(Schema& s);
+ DataAddr addData(Data& d, const string& n, bool persist);
+@@ -84,6 +86,7 @@
+ void complete(AgentEvent& e);
+ void methodSuccess(AgentEvent& e);
+ void raiseEvent(const Data& d);
++ void raiseEvent(const Data& d, int s);
+
+ private:
+ typedef map<DataAddr, Data, DataAddrCompare> DataIndex;
+@@ -111,6 +114,12 @@
+ bool externalStorage;
+ bool autoAllowQueries;
+ bool autoAllowMethods;
++ uint32_t maxSubscriptions;
++ uint32_t minSubInterval;
++ uint32_t subLifetime;
++ bool publicEvents;
++ bool listenOnDirect;
++ bool strictSecurity;
+ uint64_t schemaUpdateTime;
+ string directBase;
+ string topicBase;
+@@ -129,6 +138,7 @@
+ void handleV1SchemaRequest(qpid::management::Buffer&, uint32_t, const Message&);
+ void dispatch(Message);
+ void sendHeartbeat();
++ void send(Message, const Address&);
+ void flushResponses(AgentEvent&, bool);
+ void periodicProcessing(uint64_t);
+ void run();
+@@ -152,6 +162,7 @@
+ void AgentSession::open() { impl->open(); }
+ void AgentSession::close() { impl->close(); }
+ bool AgentSession::nextEvent(AgentEvent& e, Duration t) { return impl->nextEvent(e, t); }
++int AgentSession::pendingEvents() const { return impl->pendingEvents(); }
+ void AgentSession::registerSchema(Schema& s) { impl->registerSchema(s); }
+ DataAddr AgentSession::addData(Data& d, const string& n, bool p) { return impl->addData(d, n, p); }
+ void AgentSession::delData(const DataAddr& a) { impl->delData(a); }
+@@ -163,6 +174,7 @@
+ void AgentSession::complete(AgentEvent& e) { impl->complete(e); }
+ void AgentSession::methodSuccess(AgentEvent& e) { impl->methodSuccess(e); }
+ void AgentSession::raiseEvent(const Data& d) { impl->raiseEvent(d); }
++void AgentSession::raiseEvent(const Data& d, int s) { impl->raiseEvent(d, s); }
+
+ //========================================================================================
+ // Impl Method Bodies
+@@ -172,10 +184,12 @@
+ connection(c), domain("default"), opened(false), thread(0), threadCanceled(false),
+ bootSequence(1), interval(60), lastHeartbeat(0), lastVisit(0), forceHeartbeat(false),
+ externalStorage(false), autoAllowQueries(true), autoAllowMethods(true),
++ maxSubscriptions(64), minSubInterval(3000), subLifetime(300), publicEvents(true),
++ listenOnDirect(true), strictSecurity(false),
+ schemaUpdateTime(uint64_t(qpid::sys::Duration(qpid::sys::EPOCH, qpid::sys::now())))
+ {
+ //
+- // Set Capability Level to 1
++ // Set Agent Capability Level
+ //
+ attributes["qmf.agent_capability"] = AGENT_CAPABILITY_0_8;
+
+@@ -208,6 +222,30 @@
+ iter = optMap.find("allow-methods");
+ if (iter != optMap.end())
+ autoAllowMethods = iter->second.asBool();
++
++ iter = optMap.find("max-subscriptions");
++ if (iter != optMap.end())
++ maxSubscriptions = iter->second.asUint32();
++
++ iter = optMap.find("min-sub-interval");
++ if (iter != optMap.end())
++ minSubInterval = iter->second.asUint32();
++
++ iter = optMap.find("sub-lifetime");
++ if (iter != optMap.end())
++ subLifetime = iter->second.asUint32();
++
++ iter = optMap.find("public-events");
++ if (iter != optMap.end())
++ publicEvents = iter->second.asBool();
++
++ iter = optMap.find("listen-on-direct");
++ if (iter != optMap.end())
++ listenOnDirect = iter->second.asBool();
++
++ iter = optMap.find("strict-security");
++ if (iter != optMap.end())
++ strictSecurity = iter->second.asBool();
+ }
+ }
+
+@@ -225,6 +263,8 @@
+ throw QmfException("The session is already open");
+
+ const string addrArgs(";{create:never,node:{type:topic}}");
++ const string routableAddr("direct-agent.route." + qpid::types::Uuid(true).str());
++ attributes["_direct_subject"] = routableAddr;
+
+ // Establish messaging addresses
+ setAgentName();
+@@ -233,13 +273,20 @@
+
+ // Create AMQP session, receivers, and senders
+ session = connection.createSession();
+- Receiver directRx = session.createReceiver(directBase + "/" + agentName + addrArgs);
++ Receiver directRx;
++ Receiver routableDirectRx = session.createReceiver(topicBase + "/" + routableAddr + addrArgs);
+ Receiver topicRx = session.createReceiver(topicBase + "/console.#" + addrArgs);
+
+- directRx.setCapacity(64);
++ if (listenOnDirect && !strictSecurity) {
++ directRx = session.createReceiver(directBase + "/" + agentName + addrArgs);
++ directRx.setCapacity(64);
++ }
++
++ routableDirectRx.setCapacity(64);
+ topicRx.setCapacity(64);
+
+- directSender = session.createSender(directBase + addrArgs);
++ if (!strictSecurity)
++ directSender = session.createSender(directBase + addrArgs);
+ topicSender = session.createSender(topicBase + addrArgs);
+
+ // Start the receiver thread
+@@ -287,6 +334,13 @@
+ }
+
+
++int AgentSessionImpl::pendingEvents() const
++{
++ qpid::sys::Mutex::ScopedLock l(lock);
++ return eventQueue.size();
++}
++
++
+ void AgentSessionImpl::registerSchema(Schema& schema)
+ {
+ if (!schema.isFinalized())
+@@ -421,20 +475,18 @@
+ Variant::Map map;
+ Variant::Map& headers(msg.getProperties());
+
+- headers["method"] = "response";
+- headers["qmf.opcode"] = "_exception";
+- headers["qmf.content"] = "_data";
+- headers["qmf.agent"] = agentName;
+- headers["x-amqp-0-10.app-id"] = "qmf2";
++ headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_RESPONSE;
++ headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_EXCEPTION;
++ headers[protocol::HEADER_KEY_CONTENT] = protocol::HEADER_CONTENT_DATA;
++ headers[protocol::HEADER_KEY_AGENT] = agentName;
++ headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF;
+
+ AgentEventImpl& eventImpl(AgentEventImplAccess::get(event));
+ const DataImpl& dataImpl(DataImplAccess::get(data));
+
+ msg.setCorrelationId(eventImpl.getCorrelationId());
+ encode(dataImpl.asMap(), msg);
+- Sender sender(session.createSender(eventImpl.getReplyTo()));
+- sender.send(msg);
+- sender.close();
++ send(msg, eventImpl.getReplyTo());
+
+ QPID_LOG(trace, "SENT Exception to=" << eventImpl.getReplyTo());
+ }
+@@ -461,10 +513,10 @@
+ Variant::Map map;
+ Variant::Map& headers(msg.getProperties());
+
+- headers["method"] = "response";
+- headers["qmf.opcode"] = "_method_response";
+- headers["qmf.agent"] = agentName;
+- headers["x-amqp-0-10.app-id"] = "qmf2";
++ headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_RESPONSE;
++ headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_METHOD_RESPONSE;
++ headers[protocol::HEADER_KEY_AGENT] = agentName;
++ headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF;
+
+ AgentEventImpl& eventImpl(AgentEventImplAccess::get(event));
+
+@@ -477,9 +529,7 @@
+
+ msg.setCorrelationId(eventImpl.getCorrelationId());
+ encode(map, msg);
+- Sender sender(session.createSender(eventImpl.getReplyTo()));
+- sender.send(msg);
+- sender.close();
++ send(msg, eventImpl.getReplyTo());
+
+ QPID_LOG(trace, "SENT MethodResponse to=" << eventImpl.getReplyTo());
+ }
+@@ -487,24 +537,50 @@
+
+ void AgentSessionImpl::raiseEvent(const Data& data)
+ {
++ int severity(SEV_NOTICE);
++ if (data.hasSchema()) {
++ const Schema& schema(DataImplAccess::get(data).getSchema());
++ if (schema.isValid())
++ severity = schema.getDefaultSeverity();
++ }
++
++ raiseEvent(data, severity);
++}
++
++
++void AgentSessionImpl::raiseEvent(const Data& data, int severity)
++{
+ Message msg;
+ Variant::Map map;
+ Variant::Map& headers(msg.getProperties());
++ string subject("agent.ind.event");
+
+- // TODO: add severity.package.class to key
+- // or modify to send only to subscriptions with matching queries
++ if (data.hasSchema()) {
++ const SchemaId& schemaId(data.getSchemaId());
++ if (schemaId.getType() != SCHEMA_TYPE_EVENT)
++ throw QmfException("Cannot call raiseEvent on data that is not an Event");
++ subject = subject + "." + schemaId.getPackageName() + "." + schemaId.getName();
++ }
+
+- headers["method"] = "indication";
+- headers["qmf.opcode"] = "_data_indication";
+- headers["qmf.content"] = "_event";
+- headers["qmf.agent"] = agentName;
+- headers["x-amqp-0-10.app-id"] = "qmf2";
+- msg.setSubject("agent.ind.event");
++ if (severity < SEV_EMERG || severity > SEV_DEBUG)
++ throw QmfException("Invalid severity value");
+
+- encode(DataImplAccess::get(data).asMap(), msg);
++ headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_INDICATION;
++ headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_DATA_INDICATION;
++ headers[protocol::HEADER_KEY_CONTENT] = protocol::HEADER_CONTENT_EVENT;
++ headers[protocol::HEADER_KEY_AGENT] = agentName;
++ headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF;
++ msg.setSubject(subject);
++
++ Variant::List list;
++ Variant::Map dataAsMap(DataImplAccess::get(data).asMap());
++ dataAsMap["_severity"] = severity;
++ dataAsMap["_timestamp"] = uint64_t(qpid::sys::Duration(qpid::sys::EPOCH, qpid::sys::now()));
++ list.push_back(dataAsMap);
++ encode(list, msg);
+ topicSender.send(msg);
+
+- QPID_LOG(trace, "SENT EventIndication to=agent.ind.event");
++ QPID_LOG(trace, "SENT EventIndication to=" << topicSender.getName() << "/" << subject);
+ }
+
+
+@@ -558,7 +634,7 @@
+
+ void AgentSessionImpl::handleLocateRequest(const Variant::List& predicate, const Message& msg)
+ {
+- QPID_LOG(trace, "RCVD AgentLocateRequest");
++ QPID_LOG(trace, "RCVD AgentLocateRequest from=" << msg.getReplyTo());
+
+ if (!predicate.empty()) {
+ Query agentQuery(QUERY_OBJECT);
+@@ -573,28 +649,26 @@
+ Variant::Map map;
+ Variant::Map& headers(reply.getProperties());
+
+- headers["method"] = "indication";
+- headers["qmf.opcode"] = "_agent_locate_response";
+- headers["qmf.agent"] = agentName;
+- headers["x-amqp-0-10.app-id"] = "qmf2";
++ headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_INDICATION;
++ headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_AGENT_LOCATE_RESPONSE;
++ headers[protocol::HEADER_KEY_AGENT] = agentName;
++ headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF;
+
+ map["_values"] = attributes;
+- map["_values"].asMap()["timestamp"] = uint64_t(qpid::sys::Duration(qpid::sys::EPOCH, qpid::sys::now()));
+- map["_values"].asMap()["heartbeat_interval"] = interval;
+- map["_values"].asMap()["epoch"] = bootSequence;
+- map["_values"].asMap()["schemaUpdated"] = schemaUpdateTime;
++ map["_values"].asMap()[protocol::AGENT_ATTR_TIMESTAMP] = uint64_t(qpid::sys::Duration(qpid::sys::EPOCH, qpid::sys::now()));
++ map["_values"].asMap()[protocol::AGENT_ATTR_HEARTBEAT_INTERVAL] = interval;
++ map["_values"].asMap()[protocol::AGENT_ATTR_EPOCH] = bootSequence;
++ map["_values"].asMap()[protocol::AGENT_ATTR_SCHEMA_UPDATED_TIMESTAMP] = schemaUpdateTime;
+
+ encode(map, reply);
+- Sender sender = session.createSender(msg.getReplyTo());
+- sender.send(reply);
++ send(reply, msg.getReplyTo());
+ QPID_LOG(trace, "SENT AgentLocateResponse to=" << msg.getReplyTo());
+- sender.close();
+ }
+
+
+ void AgentSessionImpl::handleMethodRequest(const Variant::Map& content, const Message& msg)
+ {
+- QPID_LOG(trace, "RCVD MethodRequest map=" << content << " from=" << msg.getReplyTo());
++ QPID_LOG(trace, "RCVD MethodRequest map=" << content << " from=" << msg.getReplyTo() << " cid=" << msg.getCorrelationId());
+
+ //
+ // Construct an AgentEvent to be sent to the application.
+@@ -614,10 +688,6 @@
+ }
+ eventImpl->setMethodName(iter->second.asString());
+
+- iter = content.find("_object_id");
+- if (iter != content.end())
+- eventImpl->setDataAddr(DataAddr(new DataAddrImpl(iter->second.asMap())));
+-
+ iter = content.find("_arguments");
+ if (iter != content.end())
+ eventImpl->setArguments(iter->second.asMap());
+@@ -626,13 +696,39 @@
+ if (iter != content.end())
+ eventImpl->setArgumentSubtypes(iter->second.asMap());
+
++ iter = content.find("_object_id");
++ if (iter != content.end()) {
++ DataAddr addr(new DataAddrImpl(iter->second.asMap()));
++ eventImpl->setDataAddr(addr);
++ DataIndex::const_iterator iter(globalIndex.find(addr));
++ if (iter == globalIndex.end()) {
++ AgentEvent event(eventImpl.release());
++ raiseException(event, "No data object found with the specified address");
++ return;
++ }
++
++ const Schema& schema(DataImplAccess::get(iter->second).getSchema());
++ if (schema.isValid()) {
++ eventImpl->setSchema(schema);
++ for (Variant::Map::const_iterator aIter = eventImpl->getArguments().begin();
++ aIter != eventImpl->getArguments().end(); aIter++) {
++ const Schema& schema(DataImplAccess::get(iter->second).getSchema());
++ if (!SchemaImplAccess::get(schema).isValidMethodInArg(eventImpl->getMethodName(), aIter->first, aIter->second)) {
++ AgentEvent event(eventImpl.release());
++ raiseException(event, "Invalid argument: " + aIter->first);
++ return;
++ }
++ }
++ }
++ }
++
+ enqueueEvent(AgentEvent(eventImpl.release()));
+ }
+
+
+ void AgentSessionImpl::handleQueryRequest(const Variant::Map& content, const Message& msg)
+ {
+- QPID_LOG(trace, "RCVD QueryRequest query=" << content << " from=" << msg.getReplyTo());
++ QPID_LOG(trace, "RCVD QueryRequest query=" << content << " from=" << msg.getReplyTo() << " cid=" << msg.getCorrelationId());
+
+ //
+ // Construct an AgentEvent to be sent to the application or directly handled by the agent.
+@@ -668,19 +764,19 @@
+ Variant::Map map;
+ Variant::Map& headers(msg.getProperties());
+
+- headers["method"] = "response";
+- headers["qmf.opcode"] = "_query_response";
+- headers["qmf.agent"] = agentName;
+- headers["x-amqp-0-10.app-id"] = "qmf2";
++ headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_RESPONSE;
++ headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_QUERY_RESPONSE;
++ headers[protocol::HEADER_KEY_AGENT] = agentName;
++ headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF;
+
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ if (query.getTarget() == QUERY_SCHEMA_ID) {
+- headers["qmf.content"] = "_schema_id";
++ headers[protocol::HEADER_KEY_CONTENT] = "_schema_id";
+ for (iter = schemata.begin(); iter != schemata.end(); iter++)
+ content.push_back(SchemaIdImplAccess::get(iter->first).asMap());
+ } else if (query.getSchemaId().isValid()) {
+- headers["qmf.content"] = "_schema";
++ headers[protocol::HEADER_KEY_CONTENT] = "_schema";
+ iter = schemata.find(query.getSchemaId());
+ if (iter != schemata.end())
+ content.push_back(SchemaImplAccess::get(iter->second).asMap());
+@@ -698,9 +794,7 @@
+
+ msg.setCorrelationId(eventImpl.getCorrelationId());
+ encode(content, msg);
+- Sender sender(session.createSender(eventImpl.getReplyTo()));
+- sender.send(msg);
+- sender.close();
++ send(msg, eventImpl.getReplyTo());
+
+ QPID_LOG(trace, "SENT QueryResponse(Schema) to=" << eventImpl.getReplyTo());
+ }
+@@ -744,13 +838,11 @@
+ Message reply;
+ Variant::Map& headers(reply.getProperties());
+
+- headers["qmf.agent"] = agentName;
++ headers[protocol::HEADER_KEY_AGENT] = agentName;
+ reply.setContent(replyContent);
+
+- Sender sender = session.createSender(msg.getReplyTo());
+- sender.send(reply);
++ send(reply, msg.getReplyTo());
+ QPID_LOG(trace, "SENT QMFv1 SchemaResponse to=" << msg.getReplyTo());
+- sender.close();
+ }
+
+
+@@ -759,12 +851,23 @@
+ const Variant::Map& properties(msg.getProperties());
+ Variant::Map::const_iterator iter;
+
+- iter = properties.find("x-amqp-0-10.app-id");
+- if (iter != properties.end() && iter->second.asString() == "qmf2") {
++ //
++ // If strict-security is enabled, make sure that reply-to address complies with the
++ // strict-security addressing pattern (i.e. start with 'qmf.<domain>.topic/direct-console.').
++ //
++ if (strictSecurity && msg.getReplyTo()) {
++ if (msg.getReplyTo().getName() != topicBase || msg.getReplyTo().getSubject().find("direct-console.") != 0) {
++ QPID_LOG(warning, "Reply-to violates strict-security policy: " << msg.getReplyTo().str());
++ return;
++ }
++ }
++
++ iter = properties.find(protocol::HEADER_KEY_APP_ID);
++ if (iter != properties.end() && iter->second.asString() == protocol::HEADER_APP_ID_QMF) {
+ //
+ // Dispatch a QMFv2 formatted message
+ //
+- iter = properties.find("qmf.opcode");
++ iter = properties.find(protocol::HEADER_KEY_OPCODE);
+ if (iter == properties.end()) {
+ QPID_LOG(trace, "Message received with no 'qmf.opcode' header");
+ return;
+@@ -776,7 +879,7 @@
+ Variant::List content;
+ decode(msg, content);
+
+- if (opcode == "_agent_locate_request") handleLocateRequest(content, msg);
++ if (opcode == protocol::HEADER_OPCODE_AGENT_LOCATE_REQUEST) handleLocateRequest(content, msg);
+ else {
+ QPID_LOG(trace, "Unexpected QMFv2 opcode with 'amqp/list' content: " << opcode);
+ }
+@@ -785,8 +888,8 @@
+ Variant::Map content;
+ decode(msg, content);
+
+- if (opcode == "_method_request") handleMethodRequest(content, msg);
+- else if (opcode == "_query_request") handleQueryRequest(content, msg);
++ if (opcode == protocol::HEADER_OPCODE_METHOD_REQUEST) handleMethodRequest(content, msg);
++ else if (opcode == protocol::HEADER_OPCODE_QUERY_REQUEST) handleQueryRequest(content, msg);
+ else {
+ QPID_LOG(trace, "Unexpected QMFv2 opcode with 'amqp/map' content: " << opcode);
+ }
+@@ -835,17 +938,17 @@
+ }
+ }
+
+- headers["method"] = "indication";
+- headers["qmf.opcode"] = "_agent_heartbeat_indication";
+- headers["qmf.agent"] = agentName;
+- headers["x-amqp-0-10.app-id"] = "qmf2";
++ headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_INDICATION;
++ headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_AGENT_HEARTBEAT_INDICATION;
++ headers[protocol::HEADER_KEY_AGENT] = agentName;
++ headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF;
+ msg.setSubject(address.str());
+
+ map["_values"] = attributes;
+- map["_values"].asMap()["timestamp"] = uint64_t(qpid::sys::Duration(qpid::sys::EPOCH, qpid::sys::now()));
+- map["_values"].asMap()["heartbeat_interval"] = interval;
+- map["_values"].asMap()["epoch"] = bootSequence;
+- map["_values"].asMap()["schemaUpdated"] = schemaUpdateTime;
++ map["_values"].asMap()[protocol::AGENT_ATTR_TIMESTAMP] = uint64_t(qpid::sys::Duration(qpid::sys::EPOCH, qpid::sys::now()));
++ map["_values"].asMap()[protocol::AGENT_ATTR_HEARTBEAT_INTERVAL] = interval;
++ map["_values"].asMap()[protocol::AGENT_ATTR_EPOCH] = bootSequence;
++ map["_values"].asMap()[protocol::AGENT_ATTR_SCHEMA_UPDATED_TIMESTAMP] = schemaUpdateTime;
+
+ encode(map, msg);
+ topicSender.send(msg);
+@@ -853,19 +956,41 @@
+ }
+
+
++void AgentSessionImpl::send(Message msg, const Address& to)
++{
++ Sender sender;
++
++ if (strictSecurity && to.getName() != topicBase) {
++ QPID_LOG(warning, "Address violates strict-security policy: " << to);
++ return;
++ }
++
++ if (to.getName() == directBase) {
++ msg.setSubject(to.getSubject());
++ sender = directSender;
++ } else if (to.getName() == topicBase) {
++ msg.setSubject(to.getSubject());
++ sender = topicSender;
++ } else
++ sender = session.createSender(to);
++
++ sender.send(msg);
++}
++
++
+ void AgentSessionImpl::flushResponses(AgentEvent& event, bool final)
+ {
+ Message msg;
+ Variant::Map map;
+ Variant::Map& headers(msg.getProperties());
+
+- headers["method"] = "response";
+- headers["qmf.opcode"] = "_query_response";
+- headers["qmf.content"] = "_data";
+- headers["qmf.agent"] = agentName;
+- headers["x-amqp-0-10.app-id"] = "qmf2";
++ headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_RESPONSE;
++ headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_QUERY_RESPONSE;
++ headers[protocol::HEADER_KEY_CONTENT] = protocol::HEADER_CONTENT_DATA;
++ headers[protocol::HEADER_KEY_AGENT] = agentName;
++ headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF;
+ if (!final)
+- headers["partial"] = Variant();
++ headers[protocol::HEADER_KEY_PARTIAL] = Variant();
+
+ Variant::List body;
+ AgentEventImpl& eventImpl(AgentEventImplAccess::get(event));
+@@ -878,9 +1003,7 @@
+
+ msg.setCorrelationId(eventImpl.getCorrelationId());
+ encode(body, msg);
+- Sender sender(session.createSender(eventImpl.getReplyTo()));
+- sender.send(msg);
+- sender.close();
++ send(msg, eventImpl.getReplyTo());
+
+ QPID_LOG(trace, "SENT QueryResponse to=" << eventImpl.getReplyTo());
+ }
+@@ -894,6 +1017,7 @@
+ //
+ if (seconds == lastVisit)
+ return;
++ //uint64_t thisInterval(seconds - lastVisit);
+ lastVisit = seconds;
+
+ //
+Index: cpp/src/qmf/ConsoleEvent.cpp
+===================================================================
+--- cpp/src/qmf/ConsoleEvent.cpp (revision 1056407)
++++ cpp/src/qmf/ConsoleEvent.cpp (working copy)
+@@ -44,6 +44,8 @@
+ Data ConsoleEvent::getData(uint32_t i) const { return impl->getData(i); }
+ bool ConsoleEvent::isFinal() const { return impl->isFinal(); }
+ const Variant::Map& ConsoleEvent::getArguments() const { return impl->getArguments(); }
++int ConsoleEvent::getSeverity() const { return impl->getSeverity(); }
++uint64_t ConsoleEvent::getTimestamp() const { return impl->getTimestamp(); }
+
+
+ SchemaId ConsoleEventImpl::getSchemaId(uint32_t i) const
+Index: cpp/src/qmf/Query.cpp
+===================================================================
+--- cpp/src/qmf/Query.cpp (revision 1056407)
++++ cpp/src/qmf/Query.cpp (working copy)
+@@ -50,7 +50,7 @@
+ bool Query::matchesPredicate(const qpid::types::Variant::Map& map) const { return impl->matchesPredicate(map); }
+
+
+-QueryImpl::QueryImpl(const Variant::Map& map)
++QueryImpl::QueryImpl(const Variant::Map& map) : predicateCompiled(false)
+ {
+ Variant::Map::const_iterator iter;
+
+Index: cpp/src/qmf/ConsoleSession.cpp
+===================================================================
+--- cpp/src/qmf/ConsoleSession.cpp (revision 1056407)
++++ cpp/src/qmf/ConsoleSession.cpp (working copy)
+@@ -25,14 +25,20 @@
+ #include "qmf/SchemaId.h"
+ #include "qmf/SchemaImpl.h"
+ #include "qmf/ConsoleEventImpl.h"
++#include "qmf/constants.h"
+ #include "qpid/log/Statement.h"
+ #include "qpid/messaging/AddressParser.h"
+ #include "qpid/messaging/Sender.h"
+ #include "qpid/messaging/Receiver.h"
+
+ using namespace std;
+-using namespace qpid::messaging;
+ using namespace qmf;
++using qpid::messaging::Address;
++using qpid::messaging::Connection;
++using qpid::messaging::Receiver;
++using qpid::messaging::Sender;
++using qpid::messaging::Duration;
++using qpid::messaging::Message;
+ using qpid::types::Variant;
+
+ typedef qmf::PrivateImplRef<ConsoleSession> PI;
+@@ -51,15 +57,17 @@
+ uint32_t ConsoleSession::getAgentCount() const { return impl->getAgentCount(); }
+ Agent ConsoleSession::getAgent(uint32_t i) const { return impl->getAgent(i); }
+ Agent ConsoleSession::getConnectedBrokerAgent() const { return impl->getConnectedBrokerAgent(); }
++Subscription ConsoleSession::subscribe(const Query& q, const string& f, const string& o) { return impl->subscribe(q, f, o); }
++Subscription ConsoleSession::subscribe(const string& q, const string& f, const string& o) { return impl->subscribe(q, f, o); }
+
+ //========================================================================================
+ // Impl Method Bodies
+ //========================================================================================
+
+ ConsoleSessionImpl::ConsoleSessionImpl(Connection& c, const string& options) :
+- connection(c), domain("default"), maxAgentAgeMinutes(5), opened(false),
+- thread(0), threadCanceled(false),
+- lastVisit(0), lastAgePass(0), connectedBrokerInAgentList(false), schemaCache(new SchemaCache())
++ connection(c), domain("default"), maxAgentAgeMinutes(5),
++ opened(false), thread(0), threadCanceled(false), lastVisit(0), lastAgePass(0),
++ connectedBrokerInAgentList(false), schemaCache(new SchemaCache())
+ {
+ if (!options.empty()) {
+ qpid::messaging::AddressParser parser(options);
+@@ -75,6 +83,14 @@
+ iter = optMap.find("max-agent-age");
+ if (iter != optMap.end())
+ maxAgentAgeMinutes = iter->second.asUint32();
++
++ iter = optMap.find("listen-on-direct");
++ if (iter != optMap.end())
++ listenOnDirect = iter->second.asBool();
++
++ iter = optMap.find("strict-security");
++ if (iter != optMap.end())
++ strictSecurity = iter->second.asBool();
+ }
+ }
+
+@@ -110,9 +126,17 @@
+ enqueueEventLH(eventImpl.release());
+ }
+
+- if (!connectedBrokerInAgentList && agentQuery.matchesPredicate(connectedBrokerAgent.getAttributes())) {
++ if (!connectedBrokerInAgentList && connectedBrokerAgent.isValid() &&
++ agentQuery.matchesPredicate(connectedBrokerAgent.getAttributes())) {
+ agents[connectedBrokerAgent.getName()] = connectedBrokerAgent;
+ connectedBrokerInAgentList = true;
++
++ //
++ // Enqueue a notification of the new agent.
++ //
++ auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_AGENT_ADD));
++ eventImpl->setAgent(connectedBrokerAgent);
++ enqueueEventLH(ConsoleEvent(eventImpl.release()));
+ }
+ }
+
+@@ -133,24 +157,26 @@
+ directBase = "qmf." + domain + ".direct";
+ topicBase = "qmf." + domain + ".topic";
+
+- string myKey("qmf-console-" + qpid::types::Uuid(true).str());
++ string myKey("direct-console." + qpid::types::Uuid(true).str());
+
+- replyAddress = Address(directBase + "/" + myKey + ";{node:{type:topic}}");
++ replyAddress = Address(topicBase + "/" + myKey + ";{node:{type:topic}}");
+
+ // Create AMQP session, receivers, and senders
+ session = connection.createSession();
+ Receiver directRx = session.createReceiver(replyAddress);
+ Receiver topicRx = session.createReceiver(topicBase + "/agent.#"); // TODO: be more discriminating
+- Receiver legacyRx = session.createReceiver("amq.direct/" + myKey + ";{node:{type:topic}}");
++ if (!strictSecurity) {
++ Receiver legacyRx = session.createReceiver("amq.direct/" + myKey + ";{node:{type:topic}}");
++ legacyRx.setCapacity(64);
++ directSender = session.createSender(directBase + ";{create:never,node:{type:topic}}");
++ directSender.setCapacity(128);
++ }
+
+ directRx.setCapacity(64);
+ topicRx.setCapacity(128);
+- legacyRx.setCapacity(64);
+
+- directSender = session.createSender(directBase + ";{create:never,node:{type:topic}}");
+ topicSender = session.createSender(topicBase + ";{create:never,node:{type:topic}}");
+
+- directSender.setCapacity(64);
+ topicSender.setCapacity(128);
+
+ // Start the receiver thread
+@@ -219,6 +245,18 @@
+ }
+
+
++Subscription ConsoleSessionImpl::subscribe(const Query&, const string&, const string&)
++{
++ return Subscription();
++}
++
++
++Subscription ConsoleSessionImpl::subscribe(const string&, const string&, const string&)
++{
++ return Subscription();
++}
++
++
+ void ConsoleSessionImpl::enqueueEvent(const ConsoleEvent& event)
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+@@ -241,17 +279,17 @@
+ Variant::Map::const_iterator iter;
+ Variant::Map::const_iterator oiter;
+
+- oiter = properties.find("qmf.opcode");
+- iter = properties.find("x-amqp-0-10.app-id");
++ oiter = properties.find(protocol::HEADER_KEY_OPCODE);
++ iter = properties.find(protocol::HEADER_KEY_APP_ID);
+ if (iter == properties.end())
+ iter = properties.find("app_id");
+- if (iter != properties.end() && iter->second.asString() == "qmf2" && oiter != properties.end()) {
++ if (iter != properties.end() && iter->second.asString() == protocol::HEADER_APP_ID_QMF && oiter != properties.end()) {
+ //
+ // Dispatch a QMFv2 formatted message
+ //
+ const string& opcode = oiter->second.asString();
+
+- iter = properties.find("qmf.agent");
++ iter = properties.find(protocol::HEADER_KEY_AGENT);
+ if (iter == properties.end()) {
+ QPID_LOG(trace, "Message received with no 'qmf.agent' header");
+ return;
+@@ -269,7 +307,7 @@
+ }
+
+ if (msg.getContentType() == "amqp/map" &&
+- (opcode == "_agent_heartbeat_indication" || opcode == "_agent_locate_response")) {
++ (opcode == protocol::HEADER_OPCODE_AGENT_HEARTBEAT_INDICATION || opcode == protocol::HEADER_OPCODE_AGENT_LOCATE_RESPONSE)) {
+ //
+ // This is the one case where it's ok (necessary actually) to receive a QMFv2
+ // message from an unknown agent (how else are they going to get known?)
+@@ -289,8 +327,8 @@
+ Variant::Map content;
+ decode(msg, content);
+
+- if (opcode == "_exception") agentImpl.handleException(content, msg);
+- else if (opcode == "_method_response") agentImpl.handleMethodResponse(content, msg);
++ if (opcode == protocol::HEADER_OPCODE_EXCEPTION) agentImpl.handleException(content, msg);
++ else if (opcode == protocol::HEADER_OPCODE_METHOD_RESPONSE) agentImpl.handleMethodResponse(content, msg);
+ else
+ QPID_LOG(error, "Received a map-formatted QMFv2 message with opcode=" << opcode);
+
+@@ -301,8 +339,8 @@
+ Variant::List content;
+ decode(msg, content);
+
+- if (opcode == "_query_response") agentImpl.handleQueryResponse(content, msg);
+- else if (opcode == "_data_indication") agentImpl.handleDataIndication(content, msg);
++ if (opcode == protocol::HEADER_OPCODE_QUERY_RESPONSE) agentImpl.handleQueryResponse(content, msg);
++ else if (opcode == protocol::HEADER_OPCODE_DATA_INDICATION) agentImpl.handleDataIndication(content, msg);
+ else
+ QPID_LOG(error, "Received a list-formatted QMFv2 message with opcode=" << opcode);
+
+@@ -336,15 +374,17 @@
+ Message msg;
+ Variant::Map& headers(msg.getProperties());
+
+- headers["method"] = "request";
+- headers["qmf.opcode"] = "_agent_locate_request";
+- headers["x-amqp-0-10.app-id"] = "qmf2";
++ headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_REQUEST;
++ headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_AGENT_LOCATE_REQUEST;
++ headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF;
+
+ msg.setReplyTo(replyAddress);
+ msg.setCorrelationId("broker-locate");
+ msg.setSubject("broker");
+
+- directSender.send(msg);
++ Sender sender = session.createSender(directBase + ";{create:never,node:{type:topic}}");
++ sender.send(msg);
++ sender.close();
+
+ QPID_LOG(trace, "SENT AgentLocate to broker");
+ }
+@@ -354,19 +394,20 @@
+ {
+ Message msg;
+ Variant::Map& headers(msg.getProperties());
++ static const string subject("console.request.agent_locate");
+
+- headers["method"] = "request";
+- headers["qmf.opcode"] = "_agent_locate_request";
+- headers["x-amqp-0-10.app-id"] = "qmf2";
++ headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_REQUEST;
++ headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_AGENT_LOCATE_REQUEST;
++ headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF;
+
+ msg.setReplyTo(replyAddress);
+ msg.setCorrelationId("agent-locate");
+- msg.setSubject("console.request.agent_locate");
++ msg.setSubject(subject);
+ encode(agentQuery.getPredicate(), msg);
+
+ topicSender.send(msg);
+
+- QPID_LOG(trace, "SENT AgentLocate to topic");
++ QPID_LOG(trace, "SENT AgentLocate to=" << topicSender.getName() << "/" << subject);
+ }
+
+
+@@ -382,17 +423,28 @@
+ return;
+ Variant::Map attrs(iter->second.asMap());
+
+- iter = attrs.find("epoch");
++ iter = attrs.find(protocol::AGENT_ATTR_EPOCH);
+ if (iter != attrs.end())
+ epoch = iter->second.asUint32();
+
+ if (cid == "broker-locate") {
+ qpid::sys::Mutex::ScopedLock l(lock);
+- agent = Agent(new AgentImpl(agentName, epoch, *this));
++ auto_ptr<AgentImpl> impl(new AgentImpl(agentName, epoch, *this));
++ for (iter = attrs.begin(); iter != attrs.end(); iter++)
++ if (iter->first != protocol::AGENT_ATTR_EPOCH)
++ impl->setAttribute(iter->first, iter->second);
++ agent = Agent(impl.release());
+ connectedBrokerAgent = agent;
+ if (!agentQuery || agentQuery.matchesPredicate(attrs)) {
+ connectedBrokerInAgentList = true;
+ agents[agentName] = agent;
++
++ //
++ // Enqueue a notification of the new agent.
++ //
++ auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_AGENT_ADD));
++ eventImpl->setAgent(agent);
++ enqueueEventLH(ConsoleEvent(eventImpl.release()));
+ }
+ return;
+ }
+@@ -415,7 +467,7 @@
+ //
+ auto_ptr<AgentImpl> impl(new AgentImpl(agentName, epoch, *this));
+ for (iter = attrs.begin(); iter != attrs.end(); iter++)
+- if (iter->first != "epoch")
++ if (iter->first != protocol::AGENT_ATTR_EPOCH)
+ impl->setAttribute(iter->first, iter->second);
+ agent = Agent(impl.release());
+ agents[agentName] = agent;
+@@ -430,6 +482,7 @@
+ //
+ // This is a refresh of an agent we are already tracking.
+ //
++ bool detectedRestart(false);
+ agent = aIter->second;
+ AgentImpl& impl(AgentImplAccess::get(agent));
+ impl.touch();
+@@ -442,19 +495,23 @@
+ auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_AGENT_RESTART));
+ eventImpl->setAgent(agent);
+ enqueueEventLH(ConsoleEvent(eventImpl.release()));
++ detectedRestart = true;
+ }
+
+- iter = attrs.find("schemaUpdated");
++ iter = attrs.find(protocol::AGENT_ATTR_SCHEMA_UPDATED_TIMESTAMP);
+ if (iter != attrs.end()) {
+ uint64_t ts(iter->second.asUint64());
+- if (ts > impl.getAttribute("schemaUpdated").asUint64()) {
++ if (ts > impl.getAttribute(protocol::AGENT_ATTR_SCHEMA_UPDATED_TIMESTAMP).asUint64()) {
+ //
+ // The agent has added new schema entries since we last heard from it.
+- // Enqueue a notification.
++ // Update the attribute and, if this doesn't accompany a restart, enqueue a notification.
+ //
+- auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_AGENT_SCHEMA_UPDATE));
+- eventImpl->setAgent(agent);
+- enqueueEventLH(ConsoleEvent(eventImpl.release()));
++ if (!detectedRestart) {
++ auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_AGENT_SCHEMA_UPDATE));
++ eventImpl->setAgent(agent);
++ enqueueEventLH(ConsoleEvent(eventImpl.release()));
++ }
++ impl.setAttribute(protocol::AGENT_ATTR_SCHEMA_UPDATED_TIMESTAMP, iter->second);
+ }
+ }
+ }
+Index: cpp/src/qmf/AgentSubscription.h
+===================================================================
+--- cpp/src/qmf/AgentSubscription.h (revision 0)
++++ cpp/src/qmf/AgentSubscription.h (revision 0)
+@@ -0,0 +1,52 @@
++#ifndef _QMF_AGENT_SUBSCRIPTION_H_
++#define _QMF_AGENT_SUBSCRIPTION_H_
++/*
++ *
++ * Licensed to the Apache Software Foundation (ASF) under one
++ * or more contributor license agreements. See the NOTICE file
++ * distributed with this work for additional information
++ * regarding copyright ownership. The ASF licenses this file
++ * to you under the Apache License, Version 2.0 (the
++ * "License"); you may not use this file except in compliance
++ * with the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing,
++ * software distributed under the License is distributed on an
++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
++ * KIND, either express or implied. See the License for the
++ * specific language governing permissions and limitations
++ * under the License.
++ *
++ */
++
++#include "qpid/sys/IntegerTypes.h"
++#include "qpid/types/Variant.h"
++#include "qmf/Query.h"
++#include "qmf/Data.h"
++#include <boost/shared_ptr.hpp>
++
++namespace qmf {
++ class AgentSubscription {
++ public:
++ AgentSubscription(uint64_t _id, uint64_t _interval, uint64_t _life,
++ const std::string& _replyTo, const std::string& _cid, Query _query);
++ ~AgentSubscription();
++ bool tick(uint64_t seconds);
++ void keepalive() { timeSinceKeepalive = 0; }
++
++ private:
++ uint64_t id;
++ uint64_t interval;
++ uint64_t lifetime;
++ uint64_t timeSincePublish;
++ uint64_t timeSinceKeepalive;
++ const std::string replyTo;
++ const std::string cid;
++ Query query;
++ };
++
++}
++
++#endif
+Index: cpp/src/qmf/Data.cpp
+===================================================================
+--- cpp/src/qmf/Data.cpp (revision 1056407)
++++ cpp/src/qmf/Data.cpp (working copy)
+@@ -21,8 +21,10 @@
+
+ #include "qmf/DataImpl.h"
+ #include "qmf/DataAddrImpl.h"
++#include "qmf/SchemaImpl.h"
+ #include "qmf/SchemaIdImpl.h"
+ #include "qmf/PrivateImplRef.h"
++#include "qmf/SchemaProperty.h"
+
+ using namespace std;
+ using namespace qmf;
+@@ -35,8 +37,7 @@
+ Data::~Data() { PI::dtor(*this); }
+ Data& Data::operator=(const Data& s) { return PI::assign(*this, s); }
+
+-Data::Data(const SchemaId& s) { PI::ctor(*this, new DataImpl(s)); }
+-void Data::setSchema(const SchemaId& s) { impl->setSchema(s); }
++Data::Data(const Schema& s) { PI::ctor(*this, new DataImpl(s)); }
+ void Data::setAddr(const DataAddr& a) { impl->setAddr(a); }
+ void Data::setProperty(const string& k, const qpid::types::Variant& v) { impl->setProperty(k, v); }
+ void Data::overwriteProperties(const qpid::types::Variant::Map& m) { impl->overwriteProperties(m); }
+@@ -103,6 +104,20 @@
+ }
+
+
++void DataImpl::setProperty(const std::string& k, const qpid::types::Variant& v)
++{
++ if (schema.isValid()) {
++ //
++ // If we have a valid schema, make sure that the property is included in the
++ // schema and that the variant type is compatible with the schema type.
++ //
++ if (!SchemaImplAccess::get(schema).isValidProperty(k, v))
++ throw QmfException("Property '" + k + "' either not in the schema or value is of incompatible type");
++ }
++ properties[k] = v;
++}
++
++
+ DataImpl& DataImplAccess::get(Data& item)
+ {
+ return *item.impl;
+Index: cpp/src/qmf/constants.cpp
+===================================================================
+--- cpp/src/qmf/constants.cpp (revision 0)
++++ cpp/src/qmf/constants.cpp (revision 0)
+@@ -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.
++ *
++ */
++
++#include "constants.h"
++
++using namespace std;
++using namespace qmf;
++
++/**
++ * Header key strings
++ */
++const string protocol::HEADER_KEY_APP_ID = "x-amqp-0-10.app-id";
++const string protocol::HEADER_KEY_METHOD = "method";
++const string protocol::HEADER_KEY_OPCODE = "qmf.opcode";
++const string protocol::HEADER_KEY_AGENT = "qmf.agent";
++const string protocol::HEADER_KEY_CONTENT = "qmf.content";
++const string protocol::HEADER_KEY_PARTIAL = "partial";
++
++/**
++ * Header values per-key
++ */
++const string protocol::HEADER_APP_ID_QMF = "qmf2";
++
++const string protocol::HEADER_METHOD_REQUEST = "request";
++const string protocol::HEADER_METHOD_RESPONSE = "response";
++const string protocol::HEADER_METHOD_INDICATION = "indication";
++
++const string protocol::HEADER_OPCODE_EXCEPTION = "_exception";
++const string protocol::HEADER_OPCODE_AGENT_LOCATE_REQUEST = "_agent_locate_request";
++const string protocol::HEADER_OPCODE_AGENT_LOCATE_RESPONSE = "_agent_locate_response";
++const string protocol::HEADER_OPCODE_AGENT_HEARTBEAT_INDICATION = "_agent_heartbeat_indication";
++const string protocol::HEADER_OPCODE_QUERY_REQUEST = "_query_request";
++const string protocol::HEADER_OPCODE_QUERY_RESPONSE = "_query_response";
++const string protocol::HEADER_OPCODE_SUBSCRIBE_REQUEST = "_subscribe_request";
++const string protocol::HEADER_OPCODE_SUBSCRIBE_RESPONSE = "_subscribe_response";
++const string protocol::HEADER_OPCODE_SUBSCRIBE_CANCEL_INDICATION = "_subscribe_cancel_indication";
++const string protocol::HEADER_OPCODE_SUBSCRIBE_REFRESH_INDICATION = "_subscribe_refresh_indication";
++const string protocol::HEADER_OPCODE_DATA_INDICATION = "_data_indication";
++const string protocol::HEADER_OPCODE_METHOD_REQUEST = "_method_request";
++const string protocol::HEADER_OPCODE_METHOD_RESPONSE = "_method_response";
++
++const string protocol::HEADER_CONTENT_SCHEMA_ID = "_schema_id";
++const string protocol::HEADER_CONTENT_SCHEMA_CLASS = "_schema_class";
++const string protocol::HEADER_CONTENT_OBJECT_ID = "_object_id";
++const string protocol::HEADER_CONTENT_DATA = "_data";
++const string protocol::HEADER_CONTENT_EVENT = "_event";
++const string protocol::HEADER_CONTENT_QUERY = "_query";
++
++/**
++ * Keywords for Agent attributes
++ */
++const string protocol::AGENT_ATTR_VENDOR = "_vendor";
++const string protocol::AGENT_ATTR_PRODUCT = "_product";
++const string protocol::AGENT_ATTR_INSTANCE = "_instance";
++const string protocol::AGENT_ATTR_NAME = "_name";
++const string protocol::AGENT_ATTR_TIMESTAMP = "_timestamp";
++const string protocol::AGENT_ATTR_HEARTBEAT_INTERVAL = "_heartbeat_interval";
++const string protocol::AGENT_ATTR_EPOCH = "_epoch";
++const string protocol::AGENT_ATTR_SCHEMA_UPDATED_TIMESTAMP = "_schema_updated";
+Index: cpp/src/qmf/Subscription.cpp
+===================================================================
+--- cpp/src/qmf/Subscription.cpp (revision 0)
++++ cpp/src/qmf/Subscription.cpp (revision 0)
+@@ -0,0 +1,88 @@
++/*
++ *
++ * Licensed to the Apache Software Foundation (ASF) under one
++ * or more contributor license agreements. See the NOTICE file
++ * distributed with this work for additional information
++ * regarding copyright ownership. The ASF licenses this file
++ * to you under the Apache License, Version 2.0 (the
++ * "License"); you may not use this file except in compliance
++ * with the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing,
++ * software distributed under the License is distributed on an
++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
++ * KIND, either express or implied. See the License for the
++ * specific language governing permissions and limitations
++ * under the License.
++ *
++ */
++
++#include "qmf/PrivateImplRef.h"
++#include "qmf/exceptions.h"
++#include "qmf/SubscriptionImpl.h"
++#include "qmf/DataImpl.h"
++
++using namespace std;
++using namespace qmf;
++using qpid::types::Variant;
++
++typedef PrivateImplRef<Subscription> PI;
++
++Subscription::Subscription(SubscriptionImpl* impl) { PI::ctor(*this, impl); }
++Subscription::Subscription(const Subscription& s) : qmf::Handle<SubscriptionImpl>() { PI::copy(*this, s); }
++Subscription::~Subscription() { PI::dtor(*this); }
++Subscription& Subscription::operator=(const Subscription& s) { return PI::assign(*this, s); }
++
++void Subscription::cancel() { impl->cancel(); }
++bool Subscription::isActive() const { return impl->isActive(); }
++void Subscription::lock() { impl->lock(); }
++void Subscription::unlock() { impl->unlock(); }
++uint32_t Subscription::getDataCount() const { return impl->getDataCount(); }
++Data Subscription::getData(uint32_t i) const { return impl->getData(i); }
++
++
++void SubscriptionImpl::cancel()
++{
++}
++
++
++bool SubscriptionImpl::isActive() const
++{
++ return false;
++}
++
++
++void SubscriptionImpl::lock()
++{
++}
++
++
++void SubscriptionImpl::unlock()
++{
++}
++
++
++uint32_t SubscriptionImpl::getDataCount() const
++{
++ return 0;
++}
++
++
++Data SubscriptionImpl::getData(uint32_t) const
++{
++ return Data();
++}
++
++
++SubscriptionImpl& SubscriptionImplAccess::get(Subscription& item)
++{
++ return *item.impl;
++}
++
++
++const SubscriptionImpl& SubscriptionImplAccess::get(const Subscription& item)
++{
++ return *item.impl;
++}
+Index: cpp/src/qmf/AgentEventImpl.h
+===================================================================
+--- cpp/src/qmf/AgentEventImpl.h (revision 1056407)
++++ cpp/src/qmf/AgentEventImpl.h (working copy)
+@@ -29,6 +29,7 @@
+ #include "qmf/Query.h"
+ #include "qmf/DataAddr.h"
+ #include "qmf/Data.h"
++#include "qmf/Schema.h"
+ #include <queue>
+
+ namespace qmf {
+@@ -45,6 +46,7 @@
+ void setArguments(const qpid::types::Variant::Map& a) { arguments = a; }
+ void setArgumentSubtypes(const qpid::types::Variant::Map& a) { argumentSubtypes = a; }
+ void setReplyTo(const qpid::messaging::Address& r) { replyTo = r; }
++ void setSchema(const Schema& s) { schema = s; }
+ const qpid::messaging::Address& getReplyTo() { return replyTo; }
+ void setCorrelationId(const std::string& c) { correlationId = c; }
+ const std::string& getCorrelationId() { return correlationId; }
+@@ -73,6 +75,7 @@
+ std::string correlationId;
+ Query query;
+ DataAddr dataAddr;
++ Schema schema;
+ std::string methodName;
+ qpid::types::Variant::Map arguments;
+ qpid::types::Variant::Map argumentSubtypes;
+Index: cpp/src/qmf/ConsoleEventImpl.h
+===================================================================
+--- cpp/src/qmf/ConsoleEventImpl.h (revision 1056407)
++++ cpp/src/qmf/ConsoleEventImpl.h (working copy)
+@@ -42,6 +42,8 @@
+ void addSchemaId(const SchemaId& s) { newSchemaIds.push_back(SchemaId(s)); }
+ void setFinal() { final = true; }
+ void setArguments(const qpid::types::Variant::Map& a) { arguments = a; }
++ void setSeverity(int s) { severity = s; }
++ void setTimestamp(uint64_t t) { timestamp = t; }
+
+ //
+ // Methods from API handle
+@@ -56,6 +58,8 @@
+ Data getData(uint32_t i) const;
+ bool isFinal() const { return final; }
+ const qpid::types::Variant::Map& getArguments() const { return arguments; }
++ int getSeverity() const { return severity; }
++ uint64_t getTimestamp() const { return timestamp; }
+
+ private:
+ const ConsoleEventCode eventType;
+@@ -66,6 +70,8 @@
+ std::list<Data> dataList;
+ std::list<SchemaId> newSchemaIds;
+ qpid::types::Variant::Map arguments;
++ int severity;
++ uint64_t timestamp;
+ };
+
+ struct ConsoleEventImplAccess
+Index: cpp/src/qmf/ConsoleSessionImpl.h
+===================================================================
+--- cpp/src/qmf/ConsoleSessionImpl.h (revision 1056407)
++++ cpp/src/qmf/ConsoleSessionImpl.h (working copy)
+@@ -61,6 +61,8 @@
+ uint32_t getAgentCount() const;
+ Agent getAgent(uint32_t i) const;
+ Agent getConnectedBrokerAgent() const { return connectedBrokerAgent; }
++ Subscription subscribe(const Query&, const std::string& agentFilter, const std::string& options);
++ Subscription subscribe(const std::string&, const std::string& agentFilter, const std::string& options);
+
+ protected:
+ mutable qpid::sys::Mutex lock;
+@@ -71,6 +73,8 @@
+ qpid::messaging::Sender topicSender;
+ std::string domain;
+ uint32_t maxAgentAgeMinutes;
++ bool listenOnDirect;
++ bool strictSecurity;
+ Query agentQuery;
+ bool opened;
+ std::queue<ConsoleEvent> eventQueue;
+Index: cpp/src/qmf.mk
+===================================================================
+--- cpp/src/qmf.mk (revision 1056407)
++++ cpp/src/qmf.mk (working copy)
+@@ -51,7 +51,8 @@
+ ../include/qmf/SchemaId.h \
+ ../include/qmf/SchemaMethod.h \
+ ../include/qmf/SchemaProperty.h \
+- ../include/qmf/SchemaTypes.h
++ ../include/qmf/SchemaTypes.h \
++ ../include/qmf/Subscription.h
+
+
+ #
+@@ -91,10 +92,14 @@
+ qmf/AgentEventImpl.h \
+ qmf/AgentImpl.h \
+ qmf/AgentSession.cpp \
++ qmf/AgentSubscription.cpp \
++ qmf/AgentSubscription.h \
+ qmf/ConsoleEvent.cpp \
+ qmf/ConsoleEventImpl.h \
+ qmf/ConsoleSession.cpp \
+ qmf/ConsoleSessionImpl.h \
++ qmf/constants.cpp \
++ qmf/constants.h \
+ qmf/DataAddr.cpp \
+ qmf/DataAddrImpl.h \
+ qmf/Data.cpp \
+@@ -116,7 +121,9 @@
+ qmf/SchemaMethod.cpp \
+ qmf/SchemaMethodImpl.h \
+ qmf/SchemaProperty.cpp \
+- qmf/SchemaPropertyImpl.h
++ qmf/SchemaPropertyImpl.h \
++ qmf/Subscription.cpp \
++ qmf/SubscriptionImpl.h
+
+ libqmfengine_la_SOURCES = \
+ $(QMF_ENGINE_API) \
+Index: cpp/src/qpid/agent/ManagementAgentImpl.h
+===================================================================
+--- cpp/src/qpid/agent/ManagementAgentImpl.h (revision 1056407)
++++ cpp/src/qpid/agent/ManagementAgentImpl.h (working copy)
+@@ -128,12 +128,14 @@
+ };
+
+ struct QueuedMethod {
+- QueuedMethod(const std::string& _cid, const std::string& _reply, const std::string& _body) :
+- cid(_cid), replyTo(_reply), body(_body) {}
++ QueuedMethod(const std::string& _cid, const std::string& _rte, const std::string& _rtk, const std::string& _body, const std::string& _uid) :
++ cid(_cid), replyToExchange(_rte), replyToKey(_rtk), body(_body), userId(_uid) {}
+
+ std::string cid;
+- std::string replyTo;
++ std::string replyToExchange;
++ std::string replyToKey;
+ std::string body;
++ std::string userId;
+ };
+
+ typedef std::deque<QueuedMethod*> MethodQueue;
+@@ -277,16 +279,16 @@
+ uint8_t type=ManagementItem::CLASS_KIND_TABLE);
+ bool checkHeader (framing::Buffer& buf, uint8_t *opcode, uint32_t *seq);
+ void sendHeartbeat();
+- void sendException(const std::string& replyToKey, const std::string& cid,
++ void sendException(const std::string& replyToExchange, const std::string& replyToKey, const std::string& cid,
+ const std::string& text, uint32_t code=1);
+ void handlePackageRequest (qpid::framing::Buffer& inBuffer);
+ void handleClassQuery (qpid::framing::Buffer& inBuffer);
+- void handleSchemaRequest (qpid::framing::Buffer& inBuffer, uint32_t sequence, const std::string& replyTo);
+- void invokeMethodRequest (const std::string& body, const std::string& cid, const std::string& replyTo);
++ void handleSchemaRequest (qpid::framing::Buffer& inBuffer, uint32_t sequence, const std::string& rte, const std::string& rtk);
++ void invokeMethodRequest (const std::string& body, const std::string& cid, const std::string& rte, const std::string& rtk, const std::string& userId);
+
+- void handleGetQuery (const std::string& body, const std::string& cid, const std::string& replyTo);
+- void handleLocateRequest (const std::string& body, const std::string& sequence, const std::string& replyTo);
+- void handleMethodRequest (const std::string& body, const std::string& sequence, const std::string& replyTo);
++ void handleGetQuery (const std::string& body, const std::string& cid, const std::string& rte, const std::string& rtk);
++ void handleLocateRequest (const std::string& body, const std::string& sequence, const std::string& rte, const std::string& rtk);
++ void handleMethodRequest (const std::string& body, const std::string& sequence, const std::string& rte, const std::string& rtk, const std::string& userId);
+ void handleConsoleAddedIndication();
+ void getHeartbeatContent (qpid::types::Variant::Map& map);
+ };
+Index: cpp/src/qpid/agent/ManagementAgentImpl.cpp
+===================================================================
+--- cpp/src/qpid/agent/ManagementAgentImpl.cpp (revision 1056407)
++++ cpp/src/qpid/agent/ManagementAgentImpl.cpp (working copy)
+@@ -339,8 +339,10 @@
+ headers["qmf.content"] = "_event";
+ headers["qmf.agent"] = name_address;
+
+- MapCodec::encode(map_, content);
+- connThreadBody.sendBuffer(content, "", headers, topicExchange, key.str());
++ Variant::List list;
++ list.push_back(map_);
++ ListCodec::encode(list, content);
++ connThreadBody.sendBuffer(content, "", headers, topicExchange, key.str(), "amqp/list");
+ }
+
+ uint32_t ManagementAgentImpl::pollCallbacks(uint32_t callLimit)
+@@ -360,7 +362,7 @@
+ methodQueue.pop_front();
+ {
+ sys::Mutex::ScopedUnlock unlock(agentLock);
+- invokeMethodRequest(item->body, item->cid, item->replyTo);
++ invokeMethodRequest(item->body, item->cid, item->replyToExchange, item->replyToKey, item->userId);
+ delete item;
+ }
+ }
+@@ -495,7 +497,7 @@
+ QPID_LOG(trace, "SENT AgentHeartbeat name=" << name_address);
+ }
+
+-void ManagementAgentImpl::sendException(const string& replyToKey, const string& cid,
++void ManagementAgentImpl::sendException(const string& rte, const string& rtk, const string& cid,
+ const string& text, uint32_t code)
+ {
+ Variant::Map map;
+@@ -512,12 +514,12 @@
+ map["_values"] = values;
+
+ MapCodec::encode(map, content);
+- connThreadBody.sendBuffer(content, cid, headers, directExchange, replyToKey);
++ connThreadBody.sendBuffer(content, cid, headers, rte, rtk);
+
+ QPID_LOG(trace, "SENT Exception code=" << code <<" text=" << text);
+ }
+
+-void ManagementAgentImpl::handleSchemaRequest(Buffer& inBuffer, uint32_t sequence, const string& replyTo)
++void ManagementAgentImpl::handleSchemaRequest(Buffer& inBuffer, uint32_t sequence, const string& rte, const string& rtk)
+ {
+ sys::Mutex::ScopedLock lock(agentLock);
+ string packageName;
+@@ -544,7 +546,7 @@
+ outBuffer.putRawData(body);
+ outLen = MA_BUFFER_SIZE - outBuffer.available();
+ outBuffer.reset();
+- connThreadBody.sendBuffer(outBuffer, outLen, "amq.direct", replyTo);
++ connThreadBody.sendBuffer(outBuffer, outLen, rte, rtk);
+
+ QPID_LOG(trace, "SENT SchemaInd: package=" << packageName << " class=" << key.name);
+ }
+@@ -559,7 +561,7 @@
+ QPID_LOG(trace, "RCVD ConsoleAddedInd");
+ }
+
+-void ManagementAgentImpl::invokeMethodRequest(const string& body, const string& cid, const string& replyTo)
++void ManagementAgentImpl::invokeMethodRequest(const string& body, const string& cid, const string& rte, const string& rtk, const string& userId)
+ {
+ string methodName;
+ bool failed = false;
+@@ -570,11 +572,9 @@
+
+ MapCodec::decode(body, inMap);
+
+- outMap["_values"] = Variant::Map();
+-
+ if ((oid = inMap.find("_object_id")) == inMap.end() ||
+ (mid = inMap.find("_method_name")) == inMap.end()) {
+- sendException(replyTo, cid, Manageable::StatusText(Manageable::STATUS_PARAMETER_INVALID),
++ sendException(rte, rtk, cid, Manageable::StatusText(Manageable::STATUS_PARAMETER_INVALID),
+ Manageable::STATUS_PARAMETER_INVALID);
+ failed = true;
+ } else {
+@@ -593,6 +593,8 @@
+ inArgs = (mid->second).asMap();
+ }
+
++ QPID_LOG(trace, "Invoking Method: name=" << methodName << " args=" << inArgs);
++
+ boost::shared_ptr<ManagementObject> oPtr;
+ {
+ sys::Mutex::ScopedLock lock(agentLock);
+@@ -602,11 +604,11 @@
+ }
+
+ if (oPtr.get() == 0) {
+- sendException(replyTo, cid, Manageable::StatusText(Manageable::STATUS_UNKNOWN_OBJECT),
++ sendException(rte, rtk, cid, Manageable::StatusText(Manageable::STATUS_UNKNOWN_OBJECT),
+ Manageable::STATUS_UNKNOWN_OBJECT);
+ failed = true;
+ } else {
+- oPtr->doMethod(methodName, inArgs, callMap);
++ oPtr->doMethod(methodName, inArgs, callMap, userId);
+
+ if (callMap["_status_code"].asUint32() == 0) {
+ outMap["_arguments"] = Variant::Map();
+@@ -615,13 +617,13 @@
+ if (iter->first != "_status_code" && iter->first != "_status_text")
+ outMap["_arguments"].asMap()[iter->first] = iter->second;
+ } else {
+- sendException(replyTo, cid, callMap["_status_text"], callMap["_status_code"]);
++ sendException(rte, rtk, cid, callMap["_status_text"], callMap["_status_code"]);
+ failed = true;
+ }
+ }
+
+ } catch(types::InvalidConversion& e) {
+- sendException(replyTo, cid, e.what(), Manageable::STATUS_EXCEPTION);
++ sendException(rte, rtk, cid, e.what(), Manageable::STATUS_EXCEPTION);
+ failed = true;
+ }
+ }
+@@ -633,11 +635,11 @@
+ headers["qmf.opcode"] = "_method_response";
+ QPID_LOG(trace, "SENT MethodResponse map=" << outMap);
+ MapCodec::encode(outMap, content);
+- connThreadBody.sendBuffer(content, cid, headers, directExchange, replyTo);
++ connThreadBody.sendBuffer(content, cid, headers, rte, rtk);
+ }
+ }
+
+-void ManagementAgentImpl::handleGetQuery(const string& body, const string& cid, const string& replyTo)
++void ManagementAgentImpl::handleGetQuery(const string& body, const string& cid, const string& rte, const string& rtk)
+ {
+ moveNewObjectsLH();
+
+@@ -664,12 +666,12 @@
+ */
+ i = inMap.find("_what");
+ if (i == inMap.end()) {
+- sendException(replyTo, cid, "_what element missing in Query");
++ sendException(rte, rtk, cid, "_what element missing in Query");
+ return;
+ }
+
+ if (i->second.getType() != qpid::types::VAR_STRING) {
+- sendException(replyTo, cid, "_what element is not a string");
++ sendException(rte, rtk, cid, "_what element is not a string");
+ return;
+ }
+
+@@ -707,8 +709,8 @@
+ headers.erase("partial");
+
+ ListCodec::encode(list_, content);
+- connThreadBody.sendBuffer(content, cid, headers, directExchange, replyTo, "amqp/list");
+- QPID_LOG(trace, "SENT QueryResponse (query by object_id) to=" << replyTo);
++ connThreadBody.sendBuffer(content, cid, headers, rte, rtk, "amqp/list");
++ QPID_LOG(trace, "SENT QueryResponse (query by object_id) to=" << rte << "/" << rtk);
+ return;
+ }
+ } else { // match using schema_id, if supplied
+@@ -769,8 +771,8 @@
+ if (++objCount >= maxV2ReplyObjs) {
+ objCount = 0;
+ ListCodec::encode(list_, content);
+- connThreadBody.sendBuffer(content, cid, headers, directExchange, replyTo, "amqp/list");
+- QPID_LOG(trace, "SENT QueryResponse (query by schema_id) to=" << replyTo);
++ connThreadBody.sendBuffer(content, cid, headers, rte, rtk, "amqp/list");
++ QPID_LOG(trace, "SENT QueryResponse (query by schema_id) to=" << rte << "/" << rtk);
+ content.clear();
+ list_.clear();
+ }
+@@ -782,8 +784,8 @@
+ // Send last "non-partial" message to indicate CommandComplete
+ headers.erase("partial");
+ ListCodec::encode(list_, content);
+- connThreadBody.sendBuffer(content, cid, headers, directExchange, replyTo, "amqp/list");
+- QPID_LOG(trace, "SENT QueryResponse (empty with no 'partial' indicator) to=" << replyTo);
++ connThreadBody.sendBuffer(content, cid, headers, rte, rtk, "amqp/list");
++ QPID_LOG(trace, "SENT QueryResponse (last message, no 'partial' indicator) to=" << rte << "/" << rtk);
+
+ } else if (i->second.asString() == "SCHEMA_ID") {
+ headers["qmf.content"] = "_schema_id";
+@@ -804,16 +806,16 @@
+
+ headers.erase("partial");
+ ListCodec::encode(list_, content);
+- connThreadBody.sendBuffer(content, cid, headers, directExchange, replyTo, "amqp/list");
+- QPID_LOG(trace, "SENT QueryResponse (SchemaId) to=" << replyTo);
++ connThreadBody.sendBuffer(content, cid, headers, rte, rtk, "amqp/list");
++ QPID_LOG(trace, "SENT QueryResponse (SchemaId) to=" << rte << "/" << rtk);
+
+ } else {
+ // Unknown query target
+- sendException(replyTo, cid, "Query for _what => '" + i->second.asString() + "' not supported");
++ sendException(rte, rtk, cid, "Query for _what => '" + i->second.asString() + "' not supported");
+ }
+ }
+
+-void ManagementAgentImpl::handleLocateRequest(const string&, const string& cid, const string& replyTo)
++void ManagementAgentImpl::handleLocateRequest(const string&, const string& cid, const string& rte, const string& rtk)
+ {
+ QPID_LOG(trace, "RCVD AgentLocateRequest");
+
+@@ -827,9 +829,9 @@
+
+ getHeartbeatContent(map);
+ MapCodec::encode(map, content);
+- connThreadBody.sendBuffer(content, cid, headers, directExchange, replyTo);
++ connThreadBody.sendBuffer(content, cid, headers, rte, rtk);
+
+- QPID_LOG(trace, "SENT AgentLocateResponse replyTo=" << replyTo);
++ QPID_LOG(trace, "SENT AgentLocateResponse replyTo=" << rte << "/" << rtk);
+
+ {
+ sys::Mutex::ScopedLock lock(agentLock);
+@@ -837,12 +839,12 @@
+ }
+ }
+
+-void ManagementAgentImpl::handleMethodRequest(const string& body, const string& cid, const string& replyTo)
++void ManagementAgentImpl::handleMethodRequest(const string& body, const string& cid, const string& rte, const string& rtk, const string& userId)
+ {
+ if (extThread) {
+ sys::Mutex::ScopedLock lock(agentLock);
+
+- methodQueue.push_back(new QueuedMethod(cid, replyTo, body));
++ methodQueue.push_back(new QueuedMethod(cid, rte, rtk, body, userId));
+ if (pipeHandle != 0) {
+ pipeHandle->write("X", 1);
+ } else if (notifyable != 0) {
+@@ -861,7 +863,7 @@
+ inCallback = false;
+ }
+ } else {
+- invokeMethodRequest(body, cid, replyTo);
++ invokeMethodRequest(body, cid, rte, rtk, userId);
+ }
+
+ QPID_LOG(trace, "RCVD MethodRequest");
+@@ -869,21 +871,27 @@
+
+ void ManagementAgentImpl::received(Message& msg)
+ {
++ string replyToExchange;
+ string replyToKey;
+ framing::MessageProperties mp = msg.getMessageProperties();
+ if (mp.hasReplyTo()) {
+ const framing::ReplyTo& rt = mp.getReplyTo();
++ replyToExchange = rt.getExchange();
+ replyToKey = rt.getRoutingKey();
+ }
+
++ string userId;
++ if (mp.hasUserId())
++ userId = mp.getUserId();
++
+ if (mp.hasAppId() && mp.getAppId() == "qmf2")
+ {
+ string opcode = mp.getApplicationHeaders().getAsString("qmf.opcode");
+ string cid = msg.getMessageProperties().getCorrelationId();
+
+- if (opcode == "_agent_locate_request") handleLocateRequest(msg.getData(), cid, replyToKey);
+- else if (opcode == "_method_request") handleMethodRequest(msg.getData(), cid, replyToKey);
+- else if (opcode == "_query_request") handleGetQuery(msg.getData(), cid, replyToKey);
++ if (opcode == "_agent_locate_request") handleLocateRequest(msg.getData(), cid, replyToExchange, replyToKey);
++ else if (opcode == "_method_request") handleMethodRequest(msg.getData(), cid, replyToExchange, replyToKey, userId);
++ else if (opcode == "_query_request") handleGetQuery(msg.getData(), cid, replyToExchange, replyToKey);
+ else {
+ QPID_LOG(warning, "Support for QMF V2 Opcode [" << opcode << "] TBD!!!");
+ }
+@@ -900,7 +908,7 @@
+
+ if (checkHeader(inBuffer, &opcode, &sequence))
+ {
+- if (opcode == 'S') handleSchemaRequest(inBuffer, sequence, replyToKey);
++ if (opcode == 'S') handleSchemaRequest(inBuffer, sequence, replyToExchange, replyToKey);
+ else if (opcode == 'x') handleConsoleAddedIndication();
+ else
+ QPID_LOG(warning, "Ignoring old-format QMF Request! opcode=" << char(opcode));
+@@ -1161,10 +1169,10 @@
+ void ManagementAgentImpl::getHeartbeatContent(qpid::types::Variant::Map& map)
+ {
+ map["_values"] = attrMap;
+- map["_values"].asMap()["timestamp"] = uint64_t(Duration(EPOCH, now()));
+- map["_values"].asMap()["heartbeat_interval"] = interval;
+- map["_values"].asMap()["epoch"] = bootSequence;
+- map["_values"].asMap()["schema_timestamp"] = uint64_t(schemaTimestamp);
++ map["_values"].asMap()["_timestamp"] = uint64_t(Duration(EPOCH, now()));
++ map["_values"].asMap()["_heartbeat_interval"] = interval;
++ map["_values"].asMap()["_epoch"] = bootSequence;
++ map["_values"].asMap()["_schema_updated"] = uint64_t(schemaTimestamp);
+ }
+
+ void ManagementAgentImpl::ConnectionThread::run()
+Index: cpp/src/qpid/management/ManagementAgent.h
+===================================================================
+--- cpp/src/qpid/management/ManagementAgent.h (revision 1056407)
++++ cpp/src/qpid/management/ManagementAgent.h (working copy)
+@@ -306,7 +306,11 @@
+ void sendBufferLH(framing::Buffer& buf,
+ uint32_t length,
+ qpid::broker::Exchange::shared_ptr exchange,
+- std::string routingKey);
++ const std::string& routingKey);
++ void sendBufferLH(framing::Buffer& buf,
++ uint32_t length,
++ const std::string& exchange,
++ const std::string& routingKey);
+ void sendBufferLH(const std::string& data,
+ const std::string& cid,
+ const qpid::types::Variant::Map& headers,
+@@ -314,6 +318,13 @@
+ qpid::broker::Exchange::shared_ptr exchange,
+ const std::string& routingKey,
+ uint64_t ttl_msec = 0);
++ void sendBufferLH(const std::string& data,
++ const std::string& cid,
++ const qpid::types::Variant::Map& headers,
++ const std::string& content_type,
++ const std::string& exchange,
++ const std::string& routingKey,
++ uint64_t ttl_msec = 0);
+ void moveNewObjectsLH();
+
+ bool authorizeAgentMessageLH(qpid::broker::Message& msg);
+@@ -337,20 +348,20 @@
+ void deleteOrphanedAgentsLH();
+ void sendCommandCompleteLH(const std::string& replyToKey, uint32_t sequence,
+ uint32_t code = 0, const std::string& text = "OK");
+- void sendExceptionLH(const std::string& replyToKey, const std::string& cid, const std::string& text, uint32_t code=1, bool viaLocal=false);
++ void sendExceptionLH(const std::string& rte, const std::string& rtk, const std::string& cid, const std::string& text, uint32_t code=1, bool viaLocal=false);
+ void handleBrokerRequestLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence);
+ void handlePackageQueryLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence);
+ void handlePackageIndLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence);
+ void handleClassQueryLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence);
+ void handleClassIndLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence);
+- void handleSchemaRequestLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence);
++ void handleSchemaRequestLH (framing::Buffer& inBuffer, const std::string& replyToEx ,const std::string& replyToKey, uint32_t sequence);
+ void handleSchemaResponseLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence);
+ void handleAttachRequestLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence, const qpid::broker::ConnectionToken* connToken);
+ void handleGetQueryLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence);
+ void handleMethodRequestLH (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence, const qpid::broker::ConnectionToken* connToken);
+- void handleGetQueryLH (const std::string& body, const std::string& replyToKey, const std::string& cid, bool viaLocal);
+- void handleMethodRequestLH (const std::string& body, const std::string& replyToKey, const std::string& cid, const qpid::broker::ConnectionToken* connToken, bool viaLocal);
+- void handleLocateRequestLH (const std::string& body, const std::string &replyToKey, const std::string& cid);
++ void handleGetQueryLH (const std::string& body, const std::string& replyToEx, const std::string& replyToKey, const std::string& cid, bool viaLocal);
++ void handleMethodRequestLH (const std::string& body, const std::string& replyToEx, const std::string& replyToKey, const std::string& cid, const qpid::broker::ConnectionToken* connToken, bool viaLocal);
++ void handleLocateRequestLH (const std::string& body, const std::string& replyToEx, const std::string& replyToKey, const std::string& cid);
+
+
+ size_t validateSchema(framing::Buffer&, uint8_t kind);
+Index: cpp/src/qpid/management/ManagementAgent.cpp
+===================================================================
+--- cpp/src/qpid/management/ManagementAgent.cpp (revision 1056407)
++++ cpp/src/qpid/management/ManagementAgent.cpp (working copy)
+@@ -404,8 +404,10 @@
+
+
+ string content;
+- MapCodec::encode(map_, content);
+- sendBufferLH(content, "", headers, "amqp/map", v2Topic, key.str());
++ Variant::List list_;
++ list_.push_back(map_);
++ ListCodec::encode(list_, content);
++ sendBufferLH(content, "", headers, "amqp/list", v2Topic, key.str());
+ QPID_LOG(trace, "SEND raiseEvent (v2) class=" << event.getPackageName() << "." << event.getEventName());
+ }
+
+@@ -504,7 +506,7 @@
+ void ManagementAgent::sendBufferLH(Buffer& buf,
+ uint32_t length,
+ qpid::broker::Exchange::shared_ptr exchange,
+- string routingKey)
++ const string& routingKey)
+ {
+ if (suppressed) {
+ QPID_LOG(trace, "Suppressed management message to " << routingKey);
+@@ -549,6 +551,17 @@
+ }
+
+
++void ManagementAgent::sendBufferLH(Buffer& buf,
++ uint32_t length,
++ const string& exchange,
++ const string& routingKey)
++{
++ qpid::broker::Exchange::shared_ptr ex(broker->getExchanges().get(exchange));
++ if (ex.get() != 0)
++ sendBufferLH(buf, length, ex, routingKey);
++}
++
++
+ // NOTE WELL: assumes userLock is held by caller (LH)
+ // NOTE EVEN WELLER: drops this lock when delivering the message!!!
+ void ManagementAgent::sendBufferLH(const string& data,
+@@ -612,6 +625,20 @@
+ }
+
+
++void ManagementAgent::sendBufferLH(const string& data,
++ const string& cid,
++ const Variant::Map& headers,
++ const string& content_type,
++ const string& exchange,
++ const string& routingKey,
++ uint64_t ttl_msec)
++{
++ qpid::broker::Exchange::shared_ptr ex(broker->getExchanges().get(exchange));
++ if (ex.get() != 0)
++ sendBufferLH(data, cid, headers, content_type, ex, routingKey, ttl_msec);
++}
++
++
+ void ManagementAgent::moveNewObjectsLH()
+ {
+ sys::Mutex::ScopedLock lock (addLock);
+@@ -840,9 +867,9 @@
+ headers["qmf.agent"] = name_address;
+
+ map["_values"] = attrMap;
+- map["_values"].asMap()["timestamp"] = uint64_t(sys::Duration(sys::EPOCH, sys::now()));
+- map["_values"].asMap()["heartbeat_interval"] = interval;
+- map["_values"].asMap()["epoch"] = bootSequence;
++ map["_values"].asMap()["_timestamp"] = uint64_t(sys::Duration(sys::EPOCH, sys::now()));
++ map["_values"].asMap()["_heartbeat_interval"] = interval;
++ map["_values"].asMap()["_epoch"] = bootSequence;
+
+ string content;
+ MapCodec::encode(map, content);
+@@ -944,7 +971,7 @@
+ replyToKey << " seq=" << sequence);
+ }
+
+-void ManagementAgent::sendExceptionLH(const string& replyToKey, const string& cid,
++void ManagementAgent::sendExceptionLH(const string& rte, const string& rtk, const string& cid,
+ const string& text, uint32_t code, bool viaLocal)
+ {
+ static const string addr_exchange("qmf.default.direct");
+@@ -963,7 +990,7 @@
+ map["_values"] = values;
+
+ MapCodec::encode(map, content);
+- sendBufferLH(content, cid, headers, "amqp/map", v2Direct, replyToKey);
++ sendBufferLH(content, cid, headers, "amqp/map", rte, rtk);
+
+ QPID_LOG(trace, "SENT Exception code=" << code <<" text=" << text);
+ }
+@@ -1083,8 +1110,8 @@
+ return;
+ }
+
++ string userId = ((const qpid::broker::ConnectionState*) connToken)->getUserId();
+ if (acl != 0) {
+- string userId = ((const qpid::broker::ConnectionState*) connToken)->getUserId();
+ map<acl::Property, string> params;
+ params[acl::PROP_SCHEMAPACKAGE] = packageName;
+ params[acl::PROP_SCHEMACLASS] = className;
+@@ -1115,7 +1142,7 @@
+ outBuffer.record();
+ sys::Mutex::ScopedUnlock u(userLock);
+ string outBuf;
+- iter->second->doMethod(methodName, inArgs, outBuf);
++ iter->second->doMethod(methodName, inArgs, outBuf, userId);
+ outBuffer.putRawData(outBuf);
+ } catch(exception& e) {
+ outBuffer.restore();
+@@ -1131,7 +1158,7 @@
+ }
+
+
+-void ManagementAgent::handleMethodRequestLH (const string& body, const string& replyTo,
++void ManagementAgent::handleMethodRequestLH (const string& body, const string& rte, const string& rtk,
+ const string& cid, const ConnectionToken* connToken, bool viaLocal)
+ {
+ string methodName;
+@@ -1151,7 +1178,7 @@
+
+ if ((oid = inMap.find("_object_id")) == inMap.end() ||
+ (mid = inMap.find("_method_name")) == inMap.end()) {
+- sendExceptionLH(replyTo, cid, Manageable::StatusText(Manageable::STATUS_PARAMETER_INVALID),
++ sendExceptionLH(rte, rtk, cid, Manageable::StatusText(Manageable::STATUS_PARAMETER_INVALID),
+ Manageable::STATUS_PARAMETER_INVALID, viaLocal);
+ return;
+ }
+@@ -1170,7 +1197,7 @@
+ inArgs = (mid->second).asMap();
+ }
+ } catch(exception& e) {
+- sendExceptionLH(replyTo, cid, e.what(), Manageable::STATUS_EXCEPTION, viaLocal);
++ sendExceptionLH(rte, rtk, cid, e.what(), Manageable::STATUS_EXCEPTION, viaLocal);
+ return;
+ }
+
+@@ -1179,7 +1206,7 @@
+ if (iter == managementObjects.end() || iter->second->isDeleted()) {
+ stringstream estr;
+ estr << "No object found with ID=" << objId;
+- sendExceptionLH(replyTo, cid, estr.str(), 1, viaLocal);
++ sendExceptionLH(rte, rtk, cid, estr.str(), 1, viaLocal);
+ return;
+ }
+
+@@ -1189,18 +1216,18 @@
+
+ i = disallowed.find(make_pair(iter->second->getClassName(), methodName));
+ if (i != disallowed.end()) {
+- sendExceptionLH(replyTo, cid, i->second, Manageable::STATUS_FORBIDDEN, viaLocal);
++ sendExceptionLH(rte, rtk, cid, i->second, Manageable::STATUS_FORBIDDEN, viaLocal);
+ return;
+ }
+
++ string userId = ((const qpid::broker::ConnectionState*) connToken)->getUserId();
+ if (acl != 0) {
+- string userId = ((const qpid::broker::ConnectionState*) connToken)->getUserId();
+ map<acl::Property, string> params;
+ params[acl::PROP_SCHEMAPACKAGE] = iter->second->getPackageName();
+ params[acl::PROP_SCHEMACLASS] = iter->second->getClassName();
+
+ if (!acl->authorise(userId, acl::ACT_ACCESS, acl::OBJ_METHOD, methodName, ¶ms)) {
+- sendExceptionLH(replyTo, cid, Manageable::StatusText(Manageable::STATUS_FORBIDDEN),
++ sendExceptionLH(rte, rtk, cid, Manageable::StatusText(Manageable::STATUS_FORBIDDEN),
+ Manageable::STATUS_FORBIDDEN, viaLocal);
+ return;
+ }
+@@ -1210,11 +1237,11 @@
+
+ QPID_LOG(trace, "RECV MethodRequest (v2) class=" << iter->second->getPackageName()
+ << ":" << iter->second->getClassName() << " method=" <<
+- methodName << " replyTo=" << replyTo << " objId=" << objId << " inArgs=" << inArgs);
++ methodName << " replyTo=" << rte << "/" << rtk << " objId=" << objId << " inArgs=" << inArgs);
+
+ try {
+ sys::Mutex::ScopedUnlock u(userLock);
+- iter->second->doMethod(methodName, inArgs, callMap);
++ iter->second->doMethod(methodName, inArgs, callMap, userId);
+ errorCode = callMap["_status_code"].asUint32();
+ if (errorCode == 0) {
+ outMap["_arguments"] = Variant::Map();
+@@ -1225,18 +1252,18 @@
+ } else
+ error = callMap["_status_text"].asString();
+ } catch(exception& e) {
+- sendExceptionLH(replyTo, cid, e.what(), Manageable::STATUS_EXCEPTION, viaLocal);
++ sendExceptionLH(rte, rtk, cid, e.what(), Manageable::STATUS_EXCEPTION, viaLocal);
+ return;
+ }
+
+ if (errorCode != 0) {
+- sendExceptionLH(replyTo, cid, error, errorCode, viaLocal);
++ sendExceptionLH(rte, rtk, cid, error, errorCode, viaLocal);
+ return;
+ }
+
+ MapCodec::encode(outMap, content);
+- sendBufferLH(content, cid, headers, "amqp/map", v2Direct, replyTo);
+- QPID_LOG(trace, "SEND MethodResponse (v2) to=" << replyTo << " seq=" << cid << " map=" << outMap);
++ sendBufferLH(content, cid, headers, "amqp/map", rte, rtk);
++ QPID_LOG(trace, "SEND MethodResponse (v2) to=" << rte << "/" << rtk << " seq=" << cid << " map=" << outMap);
+ }
+
+
+@@ -1383,7 +1410,7 @@
+ buf.putRawData(reinterpret_cast<uint8_t*>(&data[0]), data.size());
+ }
+
+-void ManagementAgent::handleSchemaRequestLH(Buffer& inBuffer, const string& replyToKey, uint32_t sequence)
++void ManagementAgent::handleSchemaRequestLH(Buffer& inBuffer, const string& rte, const string& rtk, uint32_t sequence)
+ {
+ string packageName;
+ SchemaClassKey key;
+@@ -1392,7 +1419,7 @@
+ key.decode(inBuffer);
+
+ QPID_LOG(trace, "RECV SchemaRequest class=" << packageName << ":" << key.name << "(" << Uuid(key.hash) <<
+- "), replyTo=" << replyToKey << " seq=" << sequence);
++ "), replyTo=" << rte << "/" << rtk << " seq=" << sequence);
+
+ PackageMap::iterator pIter = packages.find(packageName);
+ if (pIter != packages.end()) {
+@@ -1408,17 +1435,17 @@
+ classInfo.appendSchema(outBuffer);
+ outLen = MA_BUFFER_SIZE - outBuffer.available();
+ outBuffer.reset();
+- sendBufferLH(outBuffer, outLen, dExchange, replyToKey);
+- QPID_LOG(trace, "SEND SchemaResponse to=" << replyToKey << " seq=" << sequence);
++ sendBufferLH(outBuffer, outLen, rte, rtk);
++ QPID_LOG(trace, "SEND SchemaResponse to=" << rte << "/" << rtk << " seq=" << sequence);
+ }
+ else
+- sendCommandCompleteLH(replyToKey, sequence, 1, "Schema not available");
++ sendCommandCompleteLH(rtk, sequence, 1, "Schema not available");
+ }
+ else
+- sendCommandCompleteLH(replyToKey, sequence, 1, "Class key not found");
++ sendCommandCompleteLH(rtk, sequence, 1, "Class key not found");
+ }
+ else
+- sendCommandCompleteLH(replyToKey, sequence, 1, "Package not found");
++ sendCommandCompleteLH(rtk, sequence, 1, "Package not found");
+ }
+
+ void ManagementAgent::handleSchemaResponseLH(Buffer& inBuffer, const string& /*replyToKey*/, uint32_t sequence)
+@@ -1678,7 +1705,7 @@
+ }
+
+
+-void ManagementAgent::handleGetQueryLH(const string& body, const string& replyTo, const string& cid, bool viaLocal)
++void ManagementAgent::handleGetQueryLH(const string& body, const string& rte, const string& rtk, const string& cid, bool viaLocal)
+ {
+ moveNewObjectsLH();
+
+@@ -1699,17 +1726,17 @@
+ */
+ i = inMap.find("_what");
+ if (i == inMap.end()) {
+- sendExceptionLH(replyTo, cid, "_what element missing in Query");
++ sendExceptionLH(rte, rtk, cid, "_what element missing in Query");
+ return;
+ }
+
+ if (i->second.getType() != qpid::types::VAR_STRING) {
+- sendExceptionLH(replyTo, cid, "_what element is not a string");
++ sendExceptionLH(rte, rtk, cid, "_what element is not a string");
+ return;
+ }
+
+ if (i->second.asString() != "OBJECT") {
+- sendExceptionLH(replyTo, cid, "Query for _what => '" + i->second.asString() + "' not supported");
++ sendExceptionLH(rte, rtk, cid, "Query for _what => '" + i->second.asString() + "' not supported");
+ return;
+ }
+
+@@ -1768,8 +1795,8 @@
+ string content;
+
+ ListCodec::encode(list_, content);
+- sendBufferLH(content, cid, headers, "amqp/list", v2Direct, replyTo);
+- QPID_LOG(trace, "SENT QueryResponse (query by object_id) to=" << replyTo);
++ sendBufferLH(content, cid, headers, "amqp/list", rte, rtk);
++ QPID_LOG(trace, "SENT QueryResponse (query by object_id) to=" << rte << "/" << rtk);
+ return;
+ }
+ } else {
+@@ -1821,27 +1848,26 @@
+ string content;
+ while (_list.size() > 1) {
+ ListCodec::encode(_list.front().asList(), content);
+- sendBufferLH(content, cid, headers, "amqp/list", v2Direct, replyTo);
++ sendBufferLH(content, cid, headers, "amqp/list", rte, rtk);
+ _list.pop_front();
+- QPID_LOG(trace, "SENT QueryResponse (partial, query by schema_id) to=" << replyTo << " size=" << content.length());
++ QPID_LOG(trace, "SENT QueryResponse (partial, query by schema_id) to=" << rte << "/" << rtk << " size=" << content.length());
+ }
+ headers.erase("partial");
+ ListCodec::encode(_list.size() ? _list.front().asList() : Variant::List(), content);
+- sendBufferLH(content, cid, headers, "amqp/list", v2Direct, replyTo);
+- QPID_LOG(trace, "SENT QueryResponse (query by schema_id) to=" << replyTo << " size=" << content.length());
++ sendBufferLH(content, cid, headers, "amqp/list", rte, rtk);
++ QPID_LOG(trace, "SENT QueryResponse (query by schema_id) to=" << rte << "/" << rtk << " size=" << content.length());
+ return;
+ }
+
+ // Unrecognized query - Send empty message to indicate CommandComplete
+ string content;
+ ListCodec::encode(Variant::List(), content);
+- sendBufferLH(content, cid, headers, "amqp/list", v2Direct, replyTo);
+- QPID_LOG(trace, "SENT QueryResponse (empty) to=" << replyTo);
++ sendBufferLH(content, cid, headers, "amqp/list", rte, rtk);
++ QPID_LOG(trace, "SENT QueryResponse (empty) to=" << rte << "/" << rtk);
+ }
+
+
+-void ManagementAgent::handleLocateRequestLH(const string&, const string& replyTo,
+- const string& cid)
++void ManagementAgent::handleLocateRequestLH(const string&, const string& rte, const string& rtk, const string& cid)
+ {
+ QPID_LOG(trace, "RCVD AgentLocateRequest");
+
+@@ -1853,16 +1879,16 @@
+ headers["qmf.agent"] = name_address;
+
+ map["_values"] = attrMap;
+- map["_values"].asMap()["timestamp"] = uint64_t(sys::Duration(sys::EPOCH, sys::now()));
+- map["_values"].asMap()["heartbeat_interval"] = interval;
+- map["_values"].asMap()["epoch"] = bootSequence;
++ map["_values"].asMap()["_timestamp"] = uint64_t(sys::Duration(sys::EPOCH, sys::now()));
++ map["_values"].asMap()["_heartbeat_interval"] = interval;
++ map["_values"].asMap()["_epoch"] = bootSequence;
+
+ string content;
+ MapCodec::encode(map, content);
+- sendBufferLH(content, cid, headers, "amqp/map", v2Direct, replyTo);
++ sendBufferLH(content, cid, headers, "amqp/map", rte, rtk);
+ clientWasAdded = true;
+
+- QPID_LOG(trace, "SENT AgentLocateResponse replyTo=" << replyTo);
++ QPID_LOG(trace, "SENT AgentLocateResponse replyTo=" << rte << "/" << rtk);
+ }
+
+
+@@ -1984,13 +2010,14 @@
+ msg.getFrames().getHeaders()->get<framing::MessageProperties>();
+ if (p && p->hasReplyTo()) {
+ const framing::ReplyTo& rt = p->getReplyTo();
+- string replyToKey = rt.getRoutingKey();
++ string rte = rt.getExchange();
++ string rtk = rt.getRoutingKey();
+ string cid;
+ if (p && p->hasCorrelationId())
+ cid = p->getCorrelationId();
+
+ if (mapMsg) {
+- sendExceptionLH(replyToKey, cid, Manageable::StatusText(Manageable::STATUS_FORBIDDEN),
++ sendExceptionLH(rte, rtk, cid, Manageable::StatusText(Manageable::STATUS_FORBIDDEN),
+ Manageable::STATUS_FORBIDDEN, false);
+ } else {
+
+@@ -2002,7 +2029,7 @@
+ outBuffer.putMediumString(Manageable::StatusText(Manageable::STATUS_FORBIDDEN));
+ outLen = MA_BUFFER_SIZE - outBuffer.available();
+ outBuffer.reset();
+- sendBufferLH(outBuffer, outLen, dExchange, replyToKey);
++ sendBufferLH(outBuffer, outLen, rte, rtk);
+ }
+
+ QPID_LOG(trace, "SEND MethodResponse status=FORBIDDEN" << " seq=" << sequence);
+@@ -2016,12 +2043,15 @@
+
+ void ManagementAgent::dispatchAgentCommandLH(Message& msg, bool viaLocal)
+ {
+- string replyToKey;
++ string rte;
++ string rtk;
++
+ const framing::MessageProperties* p =
+ msg.getFrames().getHeaders()->get<framing::MessageProperties>();
+ if (p && p->hasReplyTo()) {
+ const framing::ReplyTo& rt = p->getReplyTo();
+- replyToKey = rt.getRoutingKey();
++ rte = rt.getExchange();
++ rtk = rt.getRoutingKey();
+ }
+ else
+ return;
+@@ -2053,11 +2083,11 @@
+ }
+
+ if (opcode == "_method_request")
+- return handleMethodRequestLH(body, replyToKey, cid, msg.getPublisher(), viaLocal);
++ return handleMethodRequestLH(body, rte, rtk, cid, msg.getPublisher(), viaLocal);
+ else if (opcode == "_query_request")
+- return handleGetQueryLH(body, replyToKey, cid, viaLocal);
++ return handleGetQueryLH(body, rte, rtk, cid, viaLocal);
+ else if (opcode == "_agent_locate_request")
+- return handleLocateRequestLH(body, replyToKey, cid);
++ return handleLocateRequestLH(body, rte, rtk, cid);
+
+ QPID_LOG(warning, "Support for QMF Opcode [" << opcode << "] TBD!!!");
+ return;
+@@ -2070,16 +2100,16 @@
+ if (!checkHeader(inBuffer, &opcode, &sequence))
+ return;
+
+- if (opcode == 'B') handleBrokerRequestLH (inBuffer, replyToKey, sequence);
+- else if (opcode == 'P') handlePackageQueryLH (inBuffer, replyToKey, sequence);
+- else if (opcode == 'p') handlePackageIndLH (inBuffer, replyToKey, sequence);
+- else if (opcode == 'Q') handleClassQueryLH (inBuffer, replyToKey, sequence);
+- else if (opcode == 'q') handleClassIndLH (inBuffer, replyToKey, sequence);
+- else if (opcode == 'S') handleSchemaRequestLH (inBuffer, replyToKey, sequence);
+- else if (opcode == 's') handleSchemaResponseLH (inBuffer, replyToKey, sequence);
+- else if (opcode == 'A') handleAttachRequestLH (inBuffer, replyToKey, sequence, msg.getPublisher());
+- else if (opcode == 'G') handleGetQueryLH (inBuffer, replyToKey, sequence);
+- else if (opcode == 'M') handleMethodRequestLH (inBuffer, replyToKey, sequence, msg.getPublisher());
++ if (opcode == 'B') handleBrokerRequestLH (inBuffer, rtk, sequence);
++ else if (opcode == 'P') handlePackageQueryLH (inBuffer, rtk, sequence);
++ else if (opcode == 'p') handlePackageIndLH (inBuffer, rtk, sequence);
++ else if (opcode == 'Q') handleClassQueryLH (inBuffer, rtk, sequence);
++ else if (opcode == 'q') handleClassIndLH (inBuffer, rtk, sequence);
++ else if (opcode == 'S') handleSchemaRequestLH (inBuffer, rte, rtk, sequence);
++ else if (opcode == 's') handleSchemaResponseLH (inBuffer, rtk, sequence);
++ else if (opcode == 'A') handleAttachRequestLH (inBuffer, rtk, sequence, msg.getPublisher());
++ else if (opcode == 'G') handleGetQueryLH (inBuffer, rtk, sequence);
++ else if (opcode == 'M') handleMethodRequestLH (inBuffer, rtk, sequence, msg.getPublisher());
+ }
+ }
+
+Index: cpp/src/qpid/management/Manageable.cpp
+===================================================================
+--- cpp/src/qpid/management/Manageable.cpp (revision 1056407)
++++ cpp/src/qpid/management/Manageable.cpp (working copy)
+@@ -46,3 +46,8 @@
+ return STATUS_UNKNOWN_METHOD;
+ }
+
++bool Manageable::AuthorizeMethod(uint32_t, Args&, const std::string&)
++{
++ return true;
++}
++
+Index: cpp/src/CMakeLists.txt
+===================================================================
+--- cpp/src/CMakeLists.txt (revision 1056407)
++++ cpp/src/CMakeLists.txt (working copy)
+@@ -487,6 +487,7 @@
+ CHECK_LIBRARY_EXISTS (xerces-c _init "" HAVE_XERCES)
+ CHECK_INCLUDE_FILE_CXX (xercesc/framework/MemBufInputSource.hpp HAVE_XERCES_H)
+ CHECK_INCLUDE_FILE_CXX (xqilla/xqilla-simple.hpp HAVE_XQILLA_H)
++CHECK_INCLUDE_FILE_CXX (xqilla/ast/XQEffectiveBooleanValue.hpp HAVE_XQ_EBV)
+
+ set (xml_default ${xml_force})
+ if (CMAKE_SYSTEM_NAME STREQUAL Windows)
+@@ -510,6 +511,10 @@
+ message(FATAL_ERROR "XML Exchange support requested but XQilla headers not found")
+ endif (NOT HAVE_XQILLA_H)
+
++ if (HAVE_XQ_EBV)
++ add_definitions(-DXQ_EFFECTIVE_BOOLEAN_VALUE_HPP)
++ endif (HAVE_XQ_EBV)
++
+ add_library (xml MODULE
+ qpid/xml/XmlExchange.cpp
+ qpid/xml/XmlExchange.h
+@@ -1052,7 +1057,8 @@
+ # REVISION => Version of underlying implementation.
+ # Bump if implementation changes but API/ABI doesn't
+ # AGE => Number of API/ABI versions this is backward compatible with
+-set (qmf_version 1.0.0)
++set (qmf_version 2.0.0)
++set (qmf2_version 1.0.0)
+ set (qmfengine_version 1.0.0)
+
+ set (qmf_SOURCES
+@@ -1069,6 +1075,83 @@
+ COMPONENT ${QPID_COMPONENT_QMF})
+ install_pdb (qmf ${QPID_COMPONENT_QMF})
+
++if(NOT WIN32)
++ set (qmf2_HEADERS
++ ../include/qmf/AgentEvent.h
++ ../include/qmf/Agent.h
++ ../include/qmf/AgentSession.h
++ ../include/qmf/ConsoleEvent.h
++ ../include/qmf/ConsoleSession.h
++ ../include/qmf/DataAddr.h
++ ../include/qmf/Data.h
++ ../include/qmf/exceptions.h
++ ../include/qmf/Handle.h
++ ../include/qmf/ImportExport.h
++ ../include/qmf/Query.h
++ ../include/qmf/Schema.h
++ ../include/qmf/SchemaId.h
++ ../include/qmf/SchemaMethod.h
++ ../include/qmf/SchemaProperty.h
++ ../include/qmf/SchemaTypes.h
++ ../include/qmf/Subscription.h
++ )
++
++ set (qmf2_SOURCES
++ ${qmf2_HEADERS}
++ qmf/agentCapability.h
++ qmf/Agent.cpp
++ qmf/AgentEvent.cpp
++ qmf/AgentEventImpl.h
++ qmf/AgentImpl.h
++ qmf/AgentSession.cpp
++ qmf/AgentSubscription.cpp
++ qmf/AgentSubscription.h
++ qmf/ConsoleEvent.cpp
++ qmf/ConsoleEventImpl.h
++ qmf/ConsoleSession.cpp
++ qmf/ConsoleSessionImpl.h
++ qmf/constants.cpp
++ qmf/constants.h
++ qmf/DataAddr.cpp
++ qmf/DataAddrImpl.h
++ qmf/Data.cpp
++ qmf/DataImpl.h
++ qmf/exceptions.cpp
++ qmf/Expression.cpp
++ qmf/Expression.h
++ qmf/Hash.cpp
++ qmf/Hash.h
++ qmf/PrivateImplRef.h
++ qmf/Query.cpp
++ qmf/QueryImpl.h
++ qmf/Schema.cpp
++ qmf/SchemaCache.cpp
++ qmf/SchemaCache.h
++ qmf/SchemaId.cpp
++ qmf/SchemaIdImpl.h
++ qmf/SchemaImpl.h
++ qmf/SchemaMethod.cpp
++ qmf/SchemaMethodImpl.h
++ qmf/SchemaProperty.cpp
++ qmf/SchemaPropertyImpl.h
++ qmf/Subscription.cpp
++ qmf/SubscriptionImpl.h
++ )
++
++ add_msvc_version (qmf2 library dll)
++ add_library (qmf2 SHARED ${qmf2_SOURCES})
++ target_link_libraries (qmf2 qpidmessaging qpidtypes qpidclient qpidcommon)
++ set_target_properties (qmf2 PROPERTIES
++ VERSION ${qmf2_version})
++ install (TARGETS qmf2 OPTIONAL
++ DESTINATION ${QPID_INSTALL_LIBDIR}
++ COMPONENT ${QPID_COMPONENT_QMF})
++ install (FILES ${qmf2_HEADERS}
++ DESTINATION ${QPID_INSTALL_INCLUDEDIR}/qmf
++ COMPONENT ${QPID_COMPONENT_QMF})
++ install_pdb (qmf2 ${QPID_COMPONENT_QMF})
++endif (NOT WIN32)
++
+ set (qmfengine_SOURCES
+ qmf/engine/Agent.cpp
+ qmf/engine/BrokerProxyImpl.cpp
+@@ -1206,6 +1289,8 @@
+ # file whereas older builds only have config.h on autoconf-generated builds.
+ add_definitions(-DHAVE_CONFIG_H)
+
++add_definitions(-DBOOST_FILESYSTEM_VERSION=2)
++
+ # Now create the config file from all the info learned above.
+ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake
+ ${CMAKE_CURRENT_BINARY_DIR}/config.h)
+Index: cpp/bindings/swig_python_typemaps.i
+===================================================================
+--- cpp/bindings/swig_python_typemaps.i (revision 1056407)
++++ cpp/bindings/swig_python_typemaps.i (working copy)
+@@ -32,11 +32,11 @@
+ void PyToList(PyObject*, qpid::types::Variant::List*);
+
+ qpid::types::Variant PyToVariant(PyObject* value) {
++ if (PyBool_Check(value)) return qpid::types::Variant(bool(PyInt_AS_LONG(value) ? true : false));
+ if (PyFloat_Check(value)) return qpid::types::Variant(PyFloat_AS_DOUBLE(value));
+- if (PyString_Check(value)) return qpid::types::Variant(std::string(PyString_AS_STRING(value)));
+ if (PyInt_Check(value)) return qpid::types::Variant(int64_t(PyInt_AS_LONG(value)));
+ if (PyLong_Check(value)) return qpid::types::Variant(int64_t(PyLong_AsLongLong(value)));
+- if (PyBool_Check(value)) return qpid::types::Variant(bool(PyInt_AS_LONG(value) ? true : false));
++ if (PyString_Check(value)) return qpid::types::Variant(std::string(PyString_AS_STRING(value)));
+ if (PyDict_Check(value)) {
+ qpid::types::Variant::Map map;
+ PyToMap(value, &map);
+@@ -271,19 +271,27 @@
+ Py_INCREF($result);
+ }
+
+-%typemap(typecheck) qpid::types::Variant::Map& {
+- $1 = PyDict_Check($input) ? 1 : 0;
+-}
+-
+ /*
+ * Variant types: C++ --> Python
+ */
++%typemap(out) qpid::types::Variant::Map {
++ $result = MapToPy(&$1);
++ if ($result)
++ Py_INCREF($result);
++}
++
+ %typemap(out) qpid::types::Variant::Map& {
+ $result = MapToPy($1);
+ if ($result)
+ Py_INCREF($result);
+ }
+
++%typemap(out) qpid::types::Variant::List {
++ $result = ListToPy(&$1);
++ if ($result)
++ Py_INCREF($result);
++}
++
+ %typemap(out) qpid::types::Variant::List& {
+ $result = ListToPy($1);
+ if ($result)
+@@ -314,6 +322,16 @@
+ PyToList($input, $1);
+ }
+
++%typemap(in) const qpid::types::Variant::Map const & {
++ $1 = new qpid::types::Variant::Map();
++ PyToMap($input, $1);
++}
++
++%typemap(in) const qpid::types::Variant::List const & {
++ $1 = new qpid::types::Variant::List();
++ PyToList($input, $1);
++}
++
+ %typemap(freearg) qpid::types::Variant& {
+ delete $1;
+ }
+@@ -334,7 +352,7 @@
+ $1 = PyDict_Check($input) ? 1 : 0;
+ }
+
+-%typemap(typecheck) qpid::types::Variant::List& {
++%typemap(typecheck) qpid::types::Variant::List& {
+ $1 = PyList_Check($input) ? 1 : 0;
+ }
+
+@@ -348,6 +366,24 @@
+ PyBool_Check($input)) ? 1 : 0;
+ }
+
++%typemap(typecheck) const qpid::types::Variant::Map const & {
++ $1 = PyDict_Check($input) ? 1 : 0;
++}
++
++%typemap(typecheck) const qpid::types::Variant::List const & {
++ $1 = PyList_Check($input) ? 1 : 0;
++}
++
++%typemap(typecheck) const qpid::types::Variant const & {
++ $1 = (PyFloat_Check($input) ||
++ PyString_Check($input) ||
++ PyInt_Check($input) ||
++ PyLong_Check($input) ||
++ PyDict_Check($input) ||
++ PyList_Check($input) ||
++ PyBool_Check($input)) ? 1 : 0;
++}
++
+ %typemap(typecheck) bool {
+ $1 = PyBool_Check($input) ? 1 : 0;
+ }
+Index: cpp/bindings/swig_ruby_typemaps.i
+===================================================================
+--- cpp/bindings/swig_ruby_typemaps.i (revision 1056407)
++++ cpp/bindings/swig_ruby_typemaps.i (working copy)
+@@ -237,10 +237,18 @@
+ /*
+ * Variant types: C++ --> Ruby
+ */
++%typemap(out) qpid::types::Variant::Map {
++ $result = MapToRb(&$1);
++}
++
+ %typemap(out) qpid::types::Variant::Map& {
+ $result = MapToRb($1);
+ }
+
++%typemap(out) qpid::types::Variant::List {
++ $result = ListToRb(&$1);
++}
++
+ %typemap(out) qpid::types::Variant::List& {
+ $result = ListToRb($1);
+ }
+@@ -267,6 +275,16 @@
+ RbToList($input, $1);
+ }
+
++%typemap(in) const qpid::types::Variant::Map const & {
++ $1 = new qpid::types::Variant::Map();
++ RbToMap($input, $1);
++}
++
++%typemap(in) const qpid::types::Variant::List const & {
++ $1 = new qpid::types::Variant::List();
++ RbToList($input, $1);
++}
++
+ %typemap(freearg) qpid::types::Variant& {
+ delete $1;
+ }
+@@ -300,6 +318,23 @@
+ TYPE($input) == T_FALSE) ? 1 : 0;
+ }
+
++%typemap(typecheck) qpid::types::Variant::Map const & {
++ $1 = (TYPE($input) == T_HASH) ? 1 : 0;
++}
++
++%typemap(typecheck) qpid::types::Variant::List const & {
++ $1 = (TYPE($input) == T_ARRAY) ? 1 : 0;
++}
++
++%typemap(typecheck) const qpid::types::Variant const & {
++ $1 = (TYPE($input) == T_FLOAT ||
++ TYPE($input) == T_STRING ||
++ TYPE($input) == T_FIXNUM ||
++ TYPE($input) == T_BIGNUM ||
++ TYPE($input) == T_TRUE ||
++ TYPE($input) == T_FALSE) ? 1 : 0;
++}
++
+ %typemap(typecheck) bool {
+ $1 = (TYPE($input) == T_TRUE ||
+ TYPE($input) == T_FALSE) ? 1 : 0;
+Index: cpp/bindings/qmf2/python/Makefile.am
+===================================================================
+--- cpp/bindings/qmf2/python/Makefile.am (revision 1056407)
++++ cpp/bindings/qmf2/python/Makefile.am (working copy)
+@@ -27,13 +27,16 @@
+
+ EXTRA_DIST = python.i
+ BUILT_SOURCES = $(generated_file_list)
++SWIG_FLAGS = -w362,401
+
+ $(generated_file_list): $(srcdir)/python.i $(srcdir)/../qmf2.i $(srcdir)/../../swig_python_typemaps.i
+- swig -c++ -python -Wall $(INCLUDES) $(QPID_CXXFLAGS) -I/usr/include -o cqmf2.cpp $(srcdir)/python.i
++ swig -c++ -python $(SWIG_FLAGS) $(INCLUDES) $(QPID_CXXFLAGS) -I/usr/include -o cqmf2.cpp $(srcdir)/python.i
+
+ pylibdir = $(PYTHON_LIB)
+
+ lib_LTLIBRARIES = _cqmf2.la
++cqpiddir = $(pythondir)
++cqpid_PYTHON = qmf2.py cqmf2.py
+
+ _cqmf2_la_LDFLAGS = -avoid-version -module -shared
+ _cqmf2_la_LIBADD = $(PYTHON_LIBS) -L$(top_builddir)/src/.libs $(top_builddir)/src/libqmf2.la
+Index: cpp/bindings/qmf2/python/qmf2.py
+===================================================================
+--- cpp/bindings/qmf2/python/qmf2.py (revision 1056407)
++++ cpp/bindings/qmf2/python/qmf2.py (working copy)
+@@ -137,7 +137,91 @@
+ pass
+
+
++#===================================================================================================
++# CONSOLE HANDLER
++#===================================================================================================
++class ConsoleHandler(Thread):
+
++ def __init__(self, consoleSession):
++ Thread.__init__(self)
++ self.__session = consoleSession
++ self.__running = True
++
++ def cancel(self):
++ """
++ Stop the handler thread.
++ """
++ self.__running = None
++
++ def run(self):
++ event = cqmf2.ConsoleEvent()
++ while self.__running:
++ valid = self.__session._impl.nextEvent(event, cqpid.Duration.SECOND)
++ if valid and self.__running:
++ if event.getType() == cqmf2.CONSOLE_AGENT_ADD:
++ self.agentAdded(Agent(event.getAgent()))
++
++ elif event.getType() == cqmf2.CONSOLE_AGENT_DEL:
++ reason = 'filter'
++ if event.getAgentDelReason() == cqmf2.AGENT_DEL_AGED:
++ reason = 'aged'
++ self.agentDeleted(Agent(event.getAgent()), reason)
++
++ elif event.getType() == cqmf2.CONSOLE_AGENT_RESTART:
++ self.agentRestarted(Agent(event.getAgent()))
++
++ elif event.getType() == cqmf2.CONSOLE_AGENT_SCHEMA_UPDATE:
++ self.agentSchemaUpdated(Agent(event.getAgent()))
++
++ elif event.getType() == cqmf2.CONSOLE_EVENT:
++ self.eventRaised(Agent(event.getAgent()), Data(event.getData(0)), event.getTimestamp(), event.getSeverity())
++
++ ##
++ ## The following methods are intended to be overridden in a sub-class. They are
++ ## handlers for events that occur on QMF consoles.
++ ##
++
++ #
++ # A new agent, whose attributes match the console's agent filter, has been discovered.
++ #
++ def agentAdded(self, agent):
++ pass
++
++ #
++ # A known agent has been removed from the agent list. There are two possible reasons
++ # for agent deletion:
++ #
++ # 1) 'aged' - The agent hasn't been heard from for the maximum age interval and is
++ # presumed dead.
++ # 2) 'filter' - The agent no longer matches the console's agent-filter and has been
++ # effectively removed from the agent list. Such occurrences are likely
++ # to be seen immediately after setting the filter to a new value.
++ #
++ def agentDeleted(self, agent, reason):
++ pass
++
++ #
++ # An agent-restart was detected. This occurs when the epoch number advertised by the
++ # agent changes. It indicates that the agent in question was shut-down/crashed and
++ # restarted.
++ #
++ def agentRestarted(self, agent):
++ pass
++
++ #
++ # The agent has registered new schema information which can now be queried, if desired.
++ #
++ def agentSchemaUpdated(self, agent):
++ pass
++
++ #
++ # An agent raised an event. The 'data' argument is a Data object that contains the
++ # content of the event.
++ #
++ def eventRaised(self, agent, data, timestamp, severity):
++ pass
++
++
+ #===================================================================================================
+ # CONSOLE SESSION
+ #===================================================================================================
+@@ -147,6 +231,16 @@
+
+ def __init__(self, connection, options=""):
+ """
++ ## The options string is of the form "{key:value,key:value}". The following keys are supported:
++ ##
++ ## domain:NAME - QMF Domain to join [default: "default"]
++ ## max-agent-age:N - Maximum time, in minutes, that we will tolerate not hearing from
++ ## an agent before deleting it [default: 5]
++ ## listen-on-direct:{True,False} - If True: Listen on legacy direct-exchange address for backward compatibility [default]
++ ## If False: Listen only on the routable direct address
++ ## strict-security:{True,False} - If True: Cooperate with the broker to enforce strict access control to the network
++ ## - If False: Operate more flexibly with regard to use of messaging facilities [default]
++ ##
+ """
+ self._impl = cqmf2.ConsoleSession(connection, options)
+
+@@ -195,6 +289,24 @@
+
+ def __init__(self, connection, options=""):
+ """
++ ## The options string is of the form "{key:value,key:value}". The following keys are supported:
++ ##
++ ## interval:N - Heartbeat interval in seconds [default: 60]
++ ## external:{True,False} - Use external data storage (queries and subscriptions are pass-through) [default: False]
++ ## allow-queries:{True,False} - If True: automatically allow all queries [default]
++ ## If False: generate an AUTH_QUERY event to allow per-query authorization
++ ## allow-methods:{True,False} - If True: automatically allow all methods [default]
++ ## If False: generate an AUTH_METHOD event to allow per-method authorization
++ ## max-subscriptions:N - Maximum number of concurrent subscription queries permitted [default: 64]
++ ## min-sub-interval:N - Minimum publish interval (in milliseconds) permitted for a subscription [default: 3000]
++ ## sub-lifetime:N - Lifetime (in seconds with no keepalive) for a subscription [default: 300]
++ ## public-events:{True,False} - If True: QMF events are sent to the topic exchange [default]
++ ## If False: QMF events are only sent to authorized subscribers
++ ## listen-on-direct:{True,False} - If True: Listen on legacy direct-exchange address for backward compatibility [default]
++ ## If False: Listen only on the routable direct address
++ ## strict-security:{True,False} - If True: Cooperate with the broker to enforce strict access control to the network
++ ## - If False: Operate more flexibly with regard to use of messaging facilities [default]
++ ##
+ """
+ self._impl = cqmf2.AgentSession(connection, options)
+
+@@ -261,7 +373,15 @@
+ else:
+ self._impl.raiseException(handle, data)
+
+- ## TODO: async and external operations
++ def raiseEvent(self, data, severity=None):
++ """
++ """
++ if not severity:
++ self._impl.raiseEvent(data._impl)
++ else:
++ if (severity.__class__ != int and severity.__class__ != long) or severity < 0 or severity > 7:
++ raise Exception("Severity must be an int between 0..7")
++ self._impl.raiseEvent(data._impl, severity);
+
+
+ #===================================================================================================
+@@ -311,7 +431,7 @@
+ """
+ """
+ if q.__class__ == Query:
+- q_arg = Query._impl
++ q_arg = q._impl
+ else:
+ q_arg = q
+ dur = cqpid.Duration(cqpid.Duration.SECOND.getMilliseconds() * timeout)
+@@ -366,11 +486,35 @@
+ """
+ """
+
+- def __init__(self, *kwargs):
++ def __init__(self, arg1, arg2=None, arg3=None, *kwargs):
+ """
+ """
+- pass
++ if arg1.__class__ == DataAddr:
++ self._impl = cqmf2.Query(arg1._impl)
+
++ def getAddr(self):
++ """
++ """
++ return DataAddr(self._impl.getDataAddr())
++
++ def getSchemaId(self):
++ """
++ """
++ return SchemaId(self._impl.getSchemaId())
++
++ def getPredicate(self):
++ """
++ """
++ return self._impl.getPredicate()
++
++ def matches(self, data):
++ """
++ """
++ m = data
++ if data.__class__ == Data:
++ m = data.getProperties()
++ return self._impl.matchesPredicate(m)
++
+ #===================================================================================================
+ # DATA
+ #===================================================================================================
+@@ -385,10 +529,8 @@
+ self._impl = cqmf2.Data()
+ elif arg.__class__ == cqmf2.Data:
+ self._impl = arg
+- elif arg.__class__ == SchemaId:
++ elif arg.__class__ == Schema:
+ self._impl = cqmf2.Data(arg._impl)
+- elif arg.__class__ == Schema:
+- self._impl = cqmf2.Data(arg.getSchemaId()._impl)
+ else:
+ raise Exception("Unsupported initializer for Data")
+ self._schema = None
+@@ -412,6 +554,17 @@
+ """
+ return Agent(self._impl.getAgent())
+
++ def update(self, timeout=5):
++ dur = cqpid.Duration(cqpid.Duration.SECOND.getMilliseconds() * timeout)
++ agent = self._impl.getAgent()
++ query = cqmf2.Query(self._impl.getAddr())
++ result = agent.query(query, dur)
++ if result.getType() != cqmf2.CONSOLE_QUERY_RESPONSE:
++ raise "Update query failed"
++ if result.getDataCount == 0:
++ raise "Object no longer exists on agent"
++ self._impl = cqmf2.Data(result.getData(0))
++
+ def getProperties(self):
+ """
+ """
+@@ -444,6 +597,7 @@
+ ## validate that we have the right number of arguments supplied, and marshall them
+ ## into a map for transmission.
+ ##
++ arglist = []
+ methods = self._schema.getMethods()
+ for m in methods:
+ if m.getName() == name:
+@@ -519,8 +673,13 @@
+ """
+ """
+
+- def __init__(self, impl):
+- self._impl = impl
++ def __init__(self, arg, agentName=""):
++ if arg.__class__ == dict:
++ self._impl = cqmf2.DataAddr(arg)
++ elif arg.__class__ == cqmf2.DataAddr:
++ self._impl = arg
++ else:
++ self._impl = cqmf2.DataAddr(arg, agentName)
+
+ def __repr__(self):
+ return "%s:%s" % (self.getAgentName(), self.getName())
+@@ -530,6 +689,11 @@
+ self.getName() == other.getName() and \
+ self.getAgentEpoch() == other.getAgentEpoch()
+
++ def asMap(self):
++ """
++ """
++ return self._impl.asMap()
++
+ def getAgentName(self):
+ """
+ """
+@@ -683,6 +847,11 @@
+ """
+ return self._impl.getName()
+
++ def getType(self):
++ """
++ """
++ return self._impl.getType()
++
+ def getAccess(self):
+ """
+ """
+Index: cpp/bindings/qmf2/ruby/qmf2.rb
+===================================================================
+--- cpp/bindings/qmf2/ruby/qmf2.rb (revision 1056407)
++++ cpp/bindings/qmf2/ruby/qmf2.rb (working copy)
+@@ -78,21 +78,292 @@
+ end
+
+ ##==============================================================================
+- ## AGENT HANDLER TODO
++ ## AGENT HANDLER
+ ##==============================================================================
+
+ class AgentHandler
+- def get_query(context, query, userId); end
+- def method_call(context, name, object_id, args, userId); end
++
++ def initialize(session)
++ @_session = session
++ @_running = false
++ @_thread = nil
++ end
++
++ ##
++ ## Call the "start" method to run the handler on a new thread.
++ ##
++ def start
++ @_thread = Thread.new do
++ run
++ end
++ end
++
++ ##
++ ## Request that the running thread complete and exit.
++ ##
++ def cancel
++ @_running = false
++ @_thread.join if @_thread
++ @_thread = nil
++ end
++
++ ##
++ ## Call the "run" method only if you want the handler to run on your own thread.
++ ##
++ def run
++ @_running = true
++ event = Cqmf2::AgentEvent.new
++ while @_running do
++ valid = @_session.impl.nextEvent(event, Cqpid::Duration.SECOND)
++ if valid and @_running
++ case event.getType
++ when Cqmf2::AGENT_AUTH_QUERY
++ yes = authorize_query(Query.new(event.getQuery()), event.getUserId())
++ if yes == true
++ @_session.impl.authAccept(event)
++ else
++ @_session.impl.authReject(event)
++ end
++
++ when Cqmf2::AGENT_QUERY
++ context = QueryContext.new(@_session, event)
++ get_query(context, Query.new(event.getQuery()), event.getUserId())
++
++ when Cqmf2::AGENT_METHOD
++ context = MethodContext.new(@_session, event)
++ begin
++ method_call(context, event.getMethodName(), event.getDataAddr(), event.getArguments(), event.getUserId())
++ rescue Exception => ex
++ @_session.impl.raiseException(event, "#{ex}")
++ end
++
++ end
++ end
++ end
++ end
++
++
++ ##
++ ## The following methods are intended to be overridden in a sub-class. They are
++ ## handlers for events that occur on QMF consoles.
++ ##
++
++ #
++ # This method will only be invoked if the "allow-queries" option is enabled on the
++ # agent session. When invoked, it provides the query and the authenticated user-id
++ # of the querying client.
++ #
++ # This method must return true if the query is permitted, false otherwise.
++ #
++ def authorize_query(query, user_id); end
++
++ #
++ # This method will only be invoked if the "external" option is "True" on the agent
++ # session. When invoked, the method should begin the process of responding to a data
++ # query. The authenticated user-id of the requestor is provided for informational
++ # purposes. The 'context' variable is used to provide the results back to the requestor.
++ #
++ # For each matching Data object, call context.response(data). When the query is complete,
++ # call context.complete(). After completing the query, you should not use 'context' any
++ # longer.
++ #
++ # Note: It is not necessary to process the query synchronously. If desired, this method
++ # may store the context for asynchronous processing or pass it to another thread for
++ # processing. There is no restriction on the number of contexts that may be in-flight
++ # concurrently.
++ #
++ def get_query(context, query, user_id); end
++
++ #
++ # This method is invoked when a console calls a QMF method on the agent. Supplied are
++ # a context for the response, the method name, the data address of the data object being
++ # called, the input arguments (a dictionary), and the caller's authenticated user-id.
++ #
++ # A method call can end one of two ways: Successful completion, in which the output
++ # arguments (if any) are supplied; and Exceptional completion if there is an error.
++ #
++ # Successful Completion:
++ # For each output argument, assign the value directly to context (context.arg1 = "value")
++ # Once arguments are assigned, call context._success().
++ #
++ # Exceptional Completion:
++ # Method 1: Call context._exception(data) where 'data' is a string or a Data object.
++ # Method 2: Raise an exception (raise "Error Text") synchronously in the method body.
++ #
++ # Note: Like get_query, method_call may process methods synchronously or asynchronously.
++ # This method may store the context for later asynchronous processing. There is no
++ # restriction on the number of contexts that may be in-flight concurrently.
++ #
++ # However, "Method 2" for Exceptional Completion can only be done synchronously.
++ #
++ def method_call(context, method_name, data_addr, args, user_id); end
+ end
+
++ class QueryContext
++ def initialize(agent, context)
++ @agent = agent
++ @context = context
++ end
++
++ def response(data)
++ @agent.impl.response(@context, data.impl)
++ end
++
++ def complete
++ @agent.impl.complete(@context)
++ end
++ end
++
++ class MethodContext
++ def initialize(agent, context)
++ @agent = agent
++ @context = context
++ end
++
++ def _success
++ @agent.impl.methodSuccess(@context)
++ end
++
++ def _exception(ex)
++ if ex.class == Data
++ @agent.impl.raiseException(@context, ex.impl)
++ else
++ @agent.impl.raiseException(@context, ex)
++ end
++ end
++
++ def method_missing(name_in, *args)
++ name = name_in.to_s
++ if name[name.length - 1] == 61
++ name = name[0..name.length - 2]
++ @context.impl.addReturnArgument(name, args[0])
++ else
++ super.method_missing(name_in, args)
++ end
++ end
++ end
++
+ ##==============================================================================
++ ## CONSOLE HANDLER
++ ##==============================================================================
++
++ class ConsoleHandler
++
++ def initialize(session)
++ @_session = session
++ @_running = false
++ @_thread = nil
++ end
++
++ ##
++ ## Call the "start" method to run the handler on a new thread.
++ ##
++ def start
++ @_thread = Thread.new do
++ run
++ end
++ end
++
++ ##
++ ## Request that the running thread complete and exit.
++ ##
++ def cancel
++ @_running = false
++ @_thread.join if @_thread
++ @_thread = nil
++ end
++
++ ##
++ ## Call the "run" method only if you want the handler to run on your own thread.
++ ##
++ def run
++ @_running = true
++ event = Cqmf2::ConsoleEvent.new
++ while @_running do
++ valid = @_session.impl.nextEvent(event, Cqpid::Duration.SECOND)
++ if valid and @_running
++ case event.getType
++ when Cqmf2::CONSOLE_AGENT_ADD
++ agent_added(Agent.new(event.getAgent))
++
++ when Cqmf2::CONSOLE_AGENT_DEL
++ reason = :filter
++ reason = :aged if event.getAgentDelReason == Cqmf2::AGENT_DEL_AGED
++ agent_deleted(Agent.new(event.getAgent), reason)
++
++ when Cqmf2::CONSOLE_AGENT_RESTART
++ agent_restarted(Agent.new(event.getAgent))
++
++ when Cqmf2::CONSOLE_AGENT_SCHEMA_UPDATE
++ agent_schema_updated(Agent.new(event.getAgent))
++
++ when Cqmf2::CONSOLE_EVENT
++ event_raised(Agent.new(event.getAgent), Data.new(event.getData(0)), event.getTimestamp, event.getSeverity)
++
++ end
++ end
++ end
++ end
++
++
++ ##
++ ## The following methods are intended to be overridden in a sub-class. They are
++ ## handlers for events that occur on QMF consoles.
++ ##
++
++ #
++ # A new agent, whose attributes match the console's agent filter, has been discovered.
++ #
++ def agent_added(agent); end
++
++ #
++ # A known agent has been removed from the agent list. There are two possible reasons
++ # for agent deletion:
++ #
++ # 1) :aged - The agent hasn't been heard from for the maximum age interval and is
++ # presumed dead.
++ # 2) :filter - The agent no longer matches the console's agent-filter and has been
++ # effectively removed from the agent list. Such occurrences are likely
++ # to be seen immediately after setting the filter to a new value.
++ #
++ def agent_deleted(agent, reason); end
++
++ #
++ # An agent-restart was detected. This occurs when the epoch number advertised by the
++ # agent changes. It indicates that the agent in question was shut-down/crashed and
++ # restarted.
++ #
++ def agent_restarted(agent); end
++
++ #
++ # The agent has registered new schema information which can now be queried, if desired.
++ #
++ def agent_schema_updated(agent); end
++
++ #
++ # An agent raised an event. The 'data' argument is a Data object that contains the
++ # content of the event.
++ #
++ def event_raised(agent, data, timestamp, severity); end
++ end
++
++ ##==============================================================================
+ ## CONSOLE SESSION
+ ##==============================================================================
+
+ class ConsoleSession
+ attr_reader :impl
+
++ ## The options string is of the form "{key:value,key:value}". The following keys are supported:
++ ##
++ ## domain:NAME - QMF Domain to join [default: "default"]
++ ## max-agent-age:N - Maximum time, in minutes, that we will tolerate not hearing from
++ ## an agent before deleting it [default: 5]
++ ## listen-on-direct:{True,False} - If True: Listen on legacy direct-exchange address for backward compatibility [default]
++ ## If False: Listen only on the routable direct address
++ ## strict-security:{True,False} - If True: Cooperate with the broker to enforce strict access control to the network
++ ## - If False: Operate more flexibly with regard to use of messaging facilities [default]
++ ##
+ def initialize(connection, options="")
+ @impl = Cqmf2::ConsoleSession.new(connection, options)
+ end
+@@ -101,7 +372,7 @@
+ def set_agent_filter(filter) @impl.setAgentFilter(filter) end
+
+ def open() @impl.open end
+- def close() @imp.close end
++ def close() @impl.close end
+
+ def agents
+ result = []
+@@ -124,6 +395,24 @@
+ class AgentSession
+ attr_reader :impl
+
++ ## The options string is of the form "{key:value,key:value}". The following keys are supported:
++ ##
++ ## interval:N - Heartbeat interval in seconds [default: 60]
++ ## external:{True,False} - Use external data storage (queries and subscriptions are pass-through) [default: False]
++ ## allow-queries:{True,False} - If True: automatically allow all queries [default]
++ ## If False: generate an AUTH_QUERY event to allow per-query authorization
++ ## allow-methods:{True,False} - If True: automatically allow all methods [default]
++ ## If False: generate an AUTH_METHOD event to allow per-method authorization
++ ## max-subscriptions:N - Maximum number of concurrent subscription queries permitted [default: 64]
++ ## min-sub-interval:N - Minimum publish interval (in milliseconds) permitted for a subscription [default: 3000]
++ ## sub-lifetime:N - Lifetime (in seconds with no keepalive) for a subscription [default: 300]
++ ## public-events:{True,False} - If True: QMF events are sent to the topic exchange [default]
++ ## If False: QMF events are only sent to authorized subscribers
++ ## listen-on-direct:{True,False} - If True: Listen on legacy direct-exchange address for backward compatibility [default]
++ ## If False: Listen only on the routable direct address
++ ## strict-security:{True,False} - If True: Cooperate with the broker to enforce strict access control to the network
++ ## - If False: Operate more flexibly with regard to use of messaging facilities [default]
++ ##
+ def initialize(connection, options="")
+ @impl = Cqmf2::AgentSession.new(connection, options)
+ end
+@@ -134,10 +423,10 @@
+ def set_instance(val) @impl.setInstance(val) end
+ def set_attribute(key, val) @impl.setAttribute(key, val) end
+ def open() @impl.open end
+- def close() @imp.close end
++ def close() @impl.close end
+ def register_schema(cls) @impl.registerSchema(cls.impl) end
+
+- def add_data(data, name="", persistent=:false)
++ def add_data(data, name="", persistent=false)
+ DataAddr.new(@impl.addData(data.impl, name, persistent))
+ end
+
+@@ -145,15 +434,11 @@
+ @impl.del_data(addr.impl)
+ end
+
+- def method_success(handle)
+- @impl.methodSuccess(handle)
+- end
+-
+- def raise_exception(handle, data)
+- if data.class == Data
+- @impl.raiseException(handle, data.impl)
++ def raise_event(data, severity=nil)
++ if !severity
++ @impl.raiseEvent(data.impl)
+ else
+- @impl.raiseException(handle, data)
++ @impl.raiseEvent(data.impl, severity)
+ end
+ end
+ end
+@@ -228,14 +513,26 @@
+ end
+
+ ##==============================================================================
+- ## QUERY TODO
++ ## QUERY
+ ##==============================================================================
+
+ class Query
+ attr_reader :impl
+- def initialize
+- @impl = nil
++ def initialize(arg1, arg2=nil, arg3=nil)
++ if arg1.class == Qmf2::DataAddr
++ @impl = Cqmf2::Query.new(arg1.impl)
++ end
+ end
++
++ def addr() DataAddr.new(@impl.getDataAddr()) end
++ def schema_id() SchemaId.new(@impl.getSchemaId()) end
++ def predicate() @impl.getPredicate() end
++
++ def matches?(data)
++ map = data
++ map = data.properties if data.class == Data
++ @impl.matchesPredicate(map)
++ end
+ end
+
+ ##==============================================================================
+@@ -246,18 +543,17 @@
+ attr_reader :impl
+
+ def initialize(arg=nil)
++ @schema = nil
+ if arg == nil
+ @impl = Cqmf2::Data.new
+ elsif arg.class == Cqmf2::Data
+ @impl = arg
+- elsif arg.class == SchemaId
+- @impl = Cqmf2::Data(arg.impl)
+ elsif arg.class == Schema
+- @impl = Cqmf2::Data(arg.impl.getSchemaId)
++ @impl = Cqmf2::Data.new(arg.impl)
++ @schema = arg
+ else
+ raise "Unsupported initializer for Data"
+ end
+- @schema = nil
+ end
+
+ def to_s
+@@ -271,6 +567,10 @@
+ return nil
+ end
+
++ def set_addr(addr)
++ @impl.setAddr(addr.impl)
++ end
++
+ def addr
+ if @impl.hasAddr
+ return DataAddr.new(@impl.getAddr)
+@@ -282,6 +582,17 @@
+ return Agent.new(@impl.getAgent)
+ end
+
++ def update(timeout=5)
++ dur = Cqpid::Duration.new(Cqpid::Duration.SECOND.getMilliseconds * timeout)
++ agent = @impl.getAgent
++ query = Cqmf2::Query.new(@impl.getAddr)
++ result = agent.query(query, dur)
++ raise "Update query failed" if result.getType != Cqmf2::CONSOLE_QUERY_RESPONSE
++ raise "Object no longer exists on agent" if result.getDataCount == 0
++ @impl = Cqmf2::Data.new(result.getData(0))
++ return nil
++ end
++
+ def properties
+ return @impl.getProperties
+ end
+@@ -392,14 +703,21 @@
+ class DataAddr
+ attr_reader :impl
+
+- def initialize(impl)
+- @impl = impl
++ def initialize(arg, agentName="")
++ if arg.class == Hash
++ @impl = Cqmf2::DataAddr.new(arg)
++ elsif arg.class == Cqmf2::DataAddr
++ @impl = arg
++ else
++ @impl = Cqmf2::DataAddr.new(arg, agentName)
++ end
+ end
+
+ def ==(other)
+ return @impl == other.impl
+ end
+
++ def as_map() @impl.asMap end
+ def agent_name() @impl.getAgentName end
+ def name() @impl.getName end
+ def agent_epoch() @impl.getAgentEpoch end
+Index: cpp/bindings/qmf2/ruby/Makefile.am
+===================================================================
+--- cpp/bindings/qmf2/ruby/Makefile.am (revision 1056407)
++++ cpp/bindings/qmf2/ruby/Makefile.am (working copy)
+@@ -23,14 +23,16 @@
+
+ EXTRA_DIST = ruby.i
+ BUILT_SOURCES = cqmf2.cpp
++SWIG_FLAGS = -w362,401
+
+ rubylibdir = $(RUBY_LIB)
+
+ cqmf2.cpp: $(srcdir)/ruby.i $(srcdir)/../qmf2.i $(srcdir)/../../swig_ruby_typemaps.i
+- $(SWIG) -ruby -c++ -Wall $(INCLUDES) $(QPID_CXXFLAGS) -I/usr/include -o cqmf2.cpp $(srcdir)/ruby.i
++ $(SWIG) -ruby -c++ $(SWIG_FLAGS) $(INCLUDES) $(QPID_CXXFLAGS) -I/usr/include -o cqmf2.cpp $(srcdir)/ruby.i
+
+ rubylibarchdir = $(RUBY_LIB_ARCH)
+ rubylibarch_LTLIBRARIES = cqmf2.la
++dist_rubylib_DATA = qmf2.rb
+
+ cqmf2_la_LDFLAGS = -avoid-version -module -shrext ".$(RUBY_DLEXT)"
+ cqmf2_la_LIBADD = $(RUBY_LIBS) -L$(top_builddir)/src/.libs -lqmf2 $(top_builddir)/src/libqmf2.la
+Index: cpp/bindings/qmf2/qmf2.i
+===================================================================
+--- cpp/bindings/qmf2/qmf2.i (revision 1056407)
++++ cpp/bindings/qmf2/qmf2.i (working copy)
+@@ -33,6 +33,7 @@
+ #include <qmf/SchemaMethod.h>
+ #include <qmf/SchemaProperty.h>
+ #include <qmf/SchemaTypes.h>
++#include <qmf/Subscription.h>
+
+ %}
+
+@@ -54,6 +55,7 @@
+ %include <qmf/SchemaMethod.h>
+ %include <qmf/SchemaProperty.h>
+ %include <qmf/SchemaTypes.h>
++%include <qmf/Subscription.h>
+
+ %{
+
+Index: cpp/bindings/qmf2/examples/python/agent.py
+===================================================================
+--- cpp/bindings/qmf2/examples/python/agent.py (revision 1056407)
++++ cpp/bindings/qmf2/examples/python/agent.py (working copy)
+@@ -34,7 +34,8 @@
+ ##
+ ## Create and open a messaging connection to a broker.
+ ##
+- self.connection = cqpid.Connection(url)
++ self.connection = cqpid.Connection(url, "{reconnect:True}")
++ self.session = None
+ self.connection.open()
+
+ ##
+@@ -56,7 +57,8 @@
+ """
+ Clean up the session and connection.
+ """
+- self.session.close()
++ if self.session:
++ self.session.close()
+ self.connection.close()
+
+
+@@ -67,26 +69,43 @@
+ if addr == self.controlAddr:
+ self.control.methodCount += 1
+
+- if methodName == "stop":
+- self.session.methodSuccess(handle)
+- self.cancel()
++ try:
++ if methodName == "stop":
++ self.session.methodSuccess(handle)
++ self.cancel()
+
+- elif methodName == "echo":
+- handle.addReturnArgument("sequence", args["sequence"])
+- handle.addReturnArgument("map", args["map"])
+- self.session.methodSuccess(handle)
++ elif methodName == "echo":
++ handle.addReturnArgument("sequence", args["sequence"])
++ handle.addReturnArgument("map", args["map"])
++ self.session.methodSuccess(handle)
+
+- elif methodName == "fail":
+- if args['useString']:
+- self.session.raiseException(handle, args['stringVal'])
+- else:
+- ex = Data(self.sch_exception)
+- ex.whatHappened = "It Failed"
+- ex.howBad = 75
+- ex.details = args['details']
+- self.session.raiseException(handle, ex)
++ elif methodName == "event":
++ ev = Data(self.sch_event)
++ ev.text = args['text']
++ self.session.raiseEvent(ev, args['severity'])
++ self.session.methodSuccess(handle)
+
++ elif methodName == "fail":
++ if args['useString']:
++ self.session.raiseException(handle, args['stringVal'])
++ else:
++ ex = Data(self.sch_exception)
++ ex.whatHappened = "It Failed"
++ ex.howBad = 75
++ ex.details = args['details']
++ self.session.raiseException(handle, ex)
+
++ elif methodName == "create_child":
++ name = args['name']
++ child = Data(self.sch_child)
++ child.name = name
++ addr = self.session.addData(child, name)
++ handle.addReturnArgument("childAddr", addr.asMap())
++ self.session.methodSuccess(handle)
++ except BaseException, e:
++ self.session.raiseException(handle, "%r" % e)
++
++
+ def setupSchema(self):
+ """
+ Create and register the schema for this agent.
+@@ -118,17 +137,41 @@
+ echoMethod.addArgument(SchemaProperty("map", SCHEMA_DATA_MAP, direction=DIR_IN_OUT))
+ self.sch_control.addMethod(echoMethod)
+
++ eventMethod = SchemaMethod("event", desc="Raise an Event")
++ eventMethod.addArgument(SchemaProperty("text", SCHEMA_DATA_STRING, direction=DIR_IN))
++ eventMethod.addArgument(SchemaProperty("severity", SCHEMA_DATA_INT, direction=DIR_IN))
++ self.sch_control.addMethod(eventMethod)
++
+ failMethod = SchemaMethod("fail", desc="Expected to Fail")
+ failMethod.addArgument(SchemaProperty("useString", SCHEMA_DATA_BOOL, direction=DIR_IN))
+ failMethod.addArgument(SchemaProperty("stringVal", SCHEMA_DATA_STRING, direction=DIR_IN))
+ failMethod.addArgument(SchemaProperty("details", SCHEMA_DATA_MAP, direction=DIR_IN))
+ self.sch_control.addMethod(failMethod)
+
++ createMethod = SchemaMethod("create_child", desc="Create Child Object")
++ createMethod.addArgument(SchemaProperty("name", SCHEMA_DATA_STRING, direction=DIR_IN))
++ createMethod.addArgument(SchemaProperty("childAddr", SCHEMA_DATA_MAP, direction=DIR_OUT))
++ self.sch_control.addMethod(createMethod)
++
+ ##
++ ## Declare a child object
++ ##
++ self.sch_child = Schema(SCHEMA_TYPE_DATA, package, "child")
++ self.sch_child.addProperty(SchemaProperty("name", SCHEMA_DATA_STRING))
++
++ ##
++ ## Declare the event class
++ ##
++ self.sch_event = Schema(SCHEMA_TYPE_EVENT, package, "event")
++ self.sch_event.addProperty(SchemaProperty("text", SCHEMA_DATA_STRING))
++
++ ##
+ ## Register our schemata with the agent session.
+ ##
+ self.session.registerSchema(self.sch_exception)
+ self.session.registerSchema(self.sch_control)
++ self.session.registerSchema(self.sch_child)
++ self.session.registerSchema(self.sch_event)
+
+
+ def populateData(self):
+@@ -141,10 +184,13 @@
+ self.controlAddr = self.session.addData(self.control, "singleton")
+
+
++try:
++ agent = ExampleAgent("localhost")
++ agent.setupSchema()
++ agent.populateData()
++ agent.run() # Use agent.start() to launch the agent in a separate thread
++ agent.shutdown()
++except Exception, e:
++ print "Exception Caught:", e
+
+-agent = ExampleAgent("localhost")
+-agent.setupSchema()
+-agent.populateData()
+-agent.run() # Use agent.start() to launch the agent in a separate thread
+-agent.shutdown()
+
+Index: cpp/bindings/qmf2/examples/python/find_agents.py
+===================================================================
+--- cpp/bindings/qmf2/examples/python/find_agents.py (revision 0)
++++ cpp/bindings/qmf2/examples/python/find_agents.py (revision 0)
+@@ -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.
++#
++
++import cqpid
++import qmf2
++
++class FindAgents(qmf2.ConsoleHandler):
++
++ def __init__(self, session):
++ qmf2.ConsoleHandler.__init__(self, session)
++
++ def agentAdded(self, agent):
++ print "Agent Added: %r" % agent
++
++ def agentDeleted(self, agent, reason):
++ print "Agent Deleted: %r reason: %s" % (agent, reason)
++
++ def agentRestarted(self, agent):
++ print "Agent Restarted: %r" % agent
++
++ def agentSchemaUpdated(self, agent):
++ print "Agent Schema Updated: %r" % agent
++
++ def eventRaised(self, agent, data, timestamp, severity):
++ print "Event: data=%r time=%d sev=%d" % (data.getProperties(), timestamp, severity)
++
++
++
++url = "localhost"
++options = ""
++
++connection = cqpid.Connection(url, options)
++connection.open()
++
++session = qmf2.ConsoleSession(connection)
++session.open()
++session.setAgentFilter("[]")
++
++main = FindAgents(session)
++main.run()
++
+Index: cpp/bindings/qmf2/examples/ruby/agent_external.rb
+===================================================================
+--- cpp/bindings/qmf2/examples/ruby/agent_external.rb (revision 0)
++++ cpp/bindings/qmf2/examples/ruby/agent_external.rb (revision 0)
+@@ -0,0 +1,84 @@
++#
++# 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.
++#
++
++require 'cqpid'
++require 'qmf2'
++
++class MyAgent < Qmf2::AgentHandler
++
++ def initialize(session, data)
++ super(session)
++ @data = data
++ end
++
++ def authorize_query(query, user_id)
++ puts "Authorizing #{user_id}"
++ return true
++ end
++
++ def get_query(context, query, user_id)
++ puts "Get Query"
++ context.response(@data)
++ context.complete
++ end
++
++ def method_call(context, method_name, data_addr, args, user_id)
++ puts "Method: #{method_name}"
++ context._success
++ end
++
++end
++
++
++class Program
++
++ def initialize(url)
++ @url = url
++ @sess_options = "{allow-queries:False, external:True}"
++ end
++
++ def setup_schema(agent)
++ @cls_control = Qmf2::Schema.new(Qmf2::SCHEMA_TYPE_DATA, "org.package", "control")
++ @cls_control.add_property(Qmf2::SchemaProperty.new("state", Qmf2::SCHEMA_DATA_STRING))
++ agent.register_schema(@cls_control)
++ end
++
++ def run
++ connection = Cqpid::Connection.new(@url)
++ connection.open
++
++ session = Qmf2::AgentSession.new(connection, @sess_options)
++ session.set_vendor("package.org")
++ session.set_product("external_agent")
++ setup_schema(session)
++ session.open
++
++ @control = Qmf2::Data.new(@cls_control)
++ @control.state = "OPERATIONAL-EXTERNAL"
++ @control.set_addr(Qmf2::DataAddr.new("singleton"))
++
++ main = MyAgent.new(session, @control)
++ main.run
++ end
++end
++
++prog = Program.new("localhost")
++prog.run
++
++
+Index: cpp/bindings/qmf2/examples/ruby/find_agents.rb
+===================================================================
+--- cpp/bindings/qmf2/examples/ruby/find_agents.rb (revision 0)
++++ cpp/bindings/qmf2/examples/ruby/find_agents.rb (revision 0)
+@@ -0,0 +1,63 @@
++#
++# 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.
++#
++
++require 'cqpid'
++require 'qmf2'
++
++class FindAgents < Qmf2::ConsoleHandler
++
++ def initialize(session)
++ super(session)
++ end
++
++ def agent_added(agent)
++ puts "Agent Added: #{agent.name}"
++ end
++
++ def agent_deleted(agent, reason)
++ puts "Agent Deleted: #{agent.to_s} reason: #{reason}"
++ end
++
++ def agent_restarted(agent)
++ puts "Agent Restarted: #{agent.to_s} epoch: #{agent.epoch}"
++ end
++
++ def agent_schema_updated(agent)
++ puts "Agent with new Schemata: #{agent.to_s}"
++ end
++
++ def event_raised(agent, data, timestamp, severity)
++ puts "Event Raised time=#{timestamp} sev=#{severity} data=#{data.properties}"
++ end
++end
++
++
++url = "localhost"
++options = ""
++
++connection = Cqpid::Connection.new(url, options)
++connection.open
++
++session = Qmf2::ConsoleSession.new(connection)
++session.open
++session.set_agent_filter("[]")
++
++main = FindAgents.new(session)
++main.run
++
+Index: cpp/bindings/qmf2/examples/ruby/agent_internal.rb
+===================================================================
+--- cpp/bindings/qmf2/examples/ruby/agent_internal.rb (revision 0)
++++ cpp/bindings/qmf2/examples/ruby/agent_internal.rb (revision 0)
+@@ -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.
++#
++
++require 'cqpid'
++require 'qmf2'
++
++class MyAgent < Qmf2::AgentHandler
++
++ def initialize(session)
++ super(session)
++ end
++
++ def authorize_query(query, user_id)
++ puts "Authorizing #{user_id}"
++ return true
++ end
++
++ def method_call(context, method_name, data_addr, args, user_id)
++ puts "Method: #{method_name}"
++ context._success
++ end
++
++end
++
++
++class Program
++
++ def initialize(url)
++ @url = url
++ @sess_options = "{allow-queries:False}"
++ end
++
++ def setup_schema(agent)
++ @cls_control = Qmf2::Schema.new(Qmf2::SCHEMA_TYPE_DATA, "org.package", "control")
++ @cls_control.add_property(Qmf2::SchemaProperty.new("state", Qmf2::SCHEMA_DATA_STRING))
++ agent.register_schema(@cls_control)
++ end
++
++ def run
++ connection = Cqpid::Connection.new(@url)
++ connection.open
++
++ session = Qmf2::AgentSession.new(connection, @sess_options)
++ session.set_vendor("package.org")
++ session.set_product("internal_agent")
++ setup_schema(session)
++ session.open
++
++ control = Qmf2::Data.new(@cls_control)
++ control.state = "OPERATIONAL"
++ session.add_data(control)
++
++ main = MyAgent.new(session)
++ main.run
++ end
++end
++
++prog = Program.new("localhost")
++prog.run
++
++
+Index: cpp/bindings/qmf2/examples/cpp/agent.cpp
+===================================================================
+--- cpp/bindings/qmf2/examples/cpp/agent.cpp (revision 1056407)
++++ cpp/bindings/qmf2/examples/cpp/agent.cpp (working copy)
+@@ -48,6 +48,8 @@
+ AgentSession session;
+ Schema sch_exception;
+ Schema sch_control;
++ Schema sch_child;
++ Schema sch_event;
+ Data control;
+ DataAddr controlAddr;
+
+@@ -60,7 +62,7 @@
+ //
+ // Create and open a messaging connection to a broker.
+ //
+- connection = qpid::messaging::Connection(url);
++ connection = qpid::messaging::Connection(url, "{reconnect:True}");
+ connection.open();
+
+ //
+@@ -114,17 +116,41 @@
+ echoMethod.addArgument(SchemaProperty("map", SCHEMA_DATA_MAP, "{dir:INOUT}"));
+ sch_control.addMethod(echoMethod);
+
++ SchemaMethod eventMethod("event", "{desc:'Raise an Event'}");
++ eventMethod.addArgument(SchemaProperty("text", SCHEMA_DATA_STRING, "{dir:IN}"));
++ eventMethod.addArgument(SchemaProperty("severity", SCHEMA_DATA_INT, "{dir:IN}"));
++ sch_control.addMethod(eventMethod);
++
+ SchemaMethod failMethod("fail", "{desc:'Expected to Fail'}");
+ failMethod.addArgument(SchemaProperty("useString", SCHEMA_DATA_BOOL, "{dir:IN}"));
+ failMethod.addArgument(SchemaProperty("stringVal", SCHEMA_DATA_STRING, "{dir:IN}"));
+ failMethod.addArgument(SchemaProperty("details", SCHEMA_DATA_MAP, "{dir:IN}"));
+ sch_control.addMethod(failMethod);
+
++ SchemaMethod createMethod("create_child", "{desc:'Create Child Object'}");
++ createMethod.addArgument(SchemaProperty("name", SCHEMA_DATA_STRING, "{dir:IN}"));
++ createMethod.addArgument(SchemaProperty("childAddr", SCHEMA_DATA_MAP, "{dir:OUT}"));
++ sch_control.addMethod(createMethod);
++
+ //
++ // Declare the child class
++ //
++ sch_child = Schema(SCHEMA_TYPE_DATA, package, "child");
++ sch_child.addProperty(SchemaProperty("name", SCHEMA_DATA_STRING));
++
++ //
++ // Declare the event class
++ //
++ sch_event = Schema(SCHEMA_TYPE_EVENT, package, "event");
++ sch_event.addProperty(SchemaProperty("text", SCHEMA_DATA_STRING));
++
++ //
+ // Register our schemata with the agent session.
+ //
+ session.registerSchema(sch_exception);
+ session.registerSchema(sch_control);
++ session.registerSchema(sch_child);
++ session.registerSchema(sch_event);
+ }
+
+ void ExampleAgent::populateData()
+@@ -132,7 +158,7 @@
+ //
+ // Create a control object and give it to the agent session to manage.
+ //
+- control = Data(sch_control.getSchemaId());
++ control = Data(sch_control);
+ control.setProperty("state", "OPERATIONAL");
+ control.setProperty("methodCount", 0);
+ controlAddr = session.addData(control, "singleton");
+@@ -160,31 +186,55 @@
+ const string& name(event.getMethodName());
+ control.setProperty("methodCount", control.getProperty("methodCount").asUint32() + 1);
+
+- if (controlAddr == event.getDataAddr()) {
+- if (name == "stop") {
+- cout << "Stopping: message=" << event.getArguments()["message"] << endl;
+- session.methodSuccess(event);
+- return false;
+- }
++ try {
++ if (controlAddr == event.getDataAddr()) {
++ if (name == "stop") {
++ cout << "Stopping: message=" << event.getArguments()["message"] << endl;
++ session.methodSuccess(event);
++ return false;
++ }
+
+- if (name == "echo") {
+- event.addReturnArgument("sequence", event.getArguments()["sequence"]);
+- event.addReturnArgument("map", event.getArguments()["map"]);
+- session.methodSuccess(event);
+- return true;
+- }
++ if (name == "echo") {
++ event.addReturnArgument("sequence", event.getArguments()["sequence"]);
++ event.addReturnArgument("map", event.getArguments()["map"]);
++ session.methodSuccess(event);
++ return true;
++ }
+
+- if (name == "fail") {
+- if (event.getArguments()["useString"])
+- session.raiseException(event, event.getArguments()["stringVal"]);
+- else {
+- Data ex(sch_exception.getSchemaId());
+- ex.setProperty("whatHappened", "It Failed");
+- ex.setProperty("howBad", 75);
+- ex.setProperty("details", event.getArguments()["details"]);
+- session.raiseException(event, ex);
++ if (name == "event") {
++ Data ev(sch_event);
++ ev.setProperty("text", event.getArguments()["text"]);
++ session.raiseEvent(ev, event.getArguments()["severity"]);
++ session.methodSuccess(event);
++ return true;
+ }
++
++ if (name == "fail") {
++ if (event.getArguments()["useString"])
++ session.raiseException(event, event.getArguments()["stringVal"]);
++ else {
++ Data ex(sch_exception);
++ ex.setProperty("whatHappened", "It Failed");
++ ex.setProperty("howBad", 75);
++ ex.setProperty("details", event.getArguments()["details"]);
++ session.raiseException(event, ex);
++ }
++ }
++
++ if (name == "create_child") {
++ const string& name(event.getArguments()["name"]);
++ Data child(sch_child);
++ child.setProperty("name", name);
++ DataAddr addr(session.addData(child, name));
++ event.addReturnArgument("childAddr", addr.asMap());
++ session.methodSuccess(event);
++ }
+ }
++ } catch (const exception& e) {
++ //
++ // Pass the exception on to the caller.
++ //
++ session.raiseException(event, e.what());
+ }
+
+ return true;
+Index: cpp/bindings/qmf2/examples/cpp/print_events.cpp
+===================================================================
+--- cpp/bindings/qmf2/examples/cpp/print_events.cpp (revision 0)
++++ cpp/bindings/qmf2/examples/cpp/print_events.cpp (revision 0)
+@@ -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.
++ */
++
++#include <qpid/messaging/Connection.h>
++#include <qpid/messaging/Duration.h>
++#include <qmf/ConsoleSession.h>
++#include <qmf/ConsoleEvent.h>
++#include <qmf/Data.h>
++#include <qpid/types/Variant.h>
++#include <string>
++#include <iostream>
++
++using namespace std;
++using namespace qmf;
++using qpid::types::Variant;
++using qpid::messaging::Duration;
++
++int main(int argc, char** argv)
++{
++ string url("localhost");
++ string connectionOptions;
++ string sessionOptions;
++
++ if (argc > 1)
++ url = argv[1];
++ if (argc > 2)
++ connectionOptions = argv[2];
++ if (argc > 3)
++ sessionOptions = argv[3];
++
++ qpid::messaging::Connection connection(url, connectionOptions);
++ connection.open();
++
++ ConsoleSession session(connection, sessionOptions);
++ session.open();
++
++ while (true) {
++ ConsoleEvent event;
++ if (session.nextEvent(event)) {
++ if (event.getType() == CONSOLE_EVENT) {
++ const Data& data(event.getData(0));
++ cout << "Event: timestamp=" << event.getTimestamp() << " severity=" <<
++ event.getSeverity() << " content=" << data.getProperties() << endl;
++ }
++ }
++ }
++}
++
+Index: cpp/bindings/qmf2/examples/cpp/Makefile.am
+===================================================================
+--- cpp/bindings/qmf2/examples/cpp/Makefile.am (revision 1056407)
++++ cpp/bindings/qmf2/examples/cpp/Makefile.am (working copy)
+@@ -21,6 +21,13 @@
+
+ AM_CPPFLAGS = $(INCLUDE)
+
+-noinst_PROGRAMS=agent
++noinst_PROGRAMS=agent list_agents print_events
++
+ agent_SOURCES=agent.cpp
+ agent_LDADD=$(top_builddir)/src/libqmf2.la
++
++list_agents_SOURCES=list_agents.cpp
++list_agents_LDADD=$(top_builddir)/src/libqmf2.la
++
++print_events_SOURCES=print_events.cpp
++print_events_LDADD=$(top_builddir)/src/libqmf2.la
+Index: cpp/bindings/qmf2/examples/cpp/list_agents.cpp
+===================================================================
+--- cpp/bindings/qmf2/examples/cpp/list_agents.cpp (revision 0)
++++ cpp/bindings/qmf2/examples/cpp/list_agents.cpp (revision 0)
+@@ -0,0 +1,73 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one
++ * or more contributor license agreements. See the NOTICE file
++ * distributed with this work for additional information
++ * regarding copyright ownership. The ASF licenses this file
++ * to you under the Apache License, Version 2.0 (the
++ * "License"); you may not use this file except in compliance
++ * with the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing,
++ * software distributed under the License is distributed on an
++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
++ * KIND, either express or implied. See the License for the
++ * specific language governing permissions and limitations
++ * under the License.
++ */
++
++#include <qpid/messaging/Connection.h>
++#include <qpid/messaging/Duration.h>
++#include <qmf/ConsoleSession.h>
++#include <qmf/ConsoleEvent.h>
++#include <qmf/Agent.h>
++#include <qpid/types/Variant.h>
++#include <string>
++#include <iostream>
++
++using namespace std;
++using namespace qmf;
++using qpid::types::Variant;
++using qpid::messaging::Duration;
++
++int main(int argc, char** argv)
++{
++ string url("localhost");
++ string connectionOptions;
++ string sessionOptions;
++
++ if (argc > 1)
++ url = argv[1];
++ if (argc > 2)
++ connectionOptions = argv[2];
++ if (argc > 3)
++ sessionOptions = argv[3];
++
++ qpid::messaging::Connection connection(url, connectionOptions);
++ connection.open();
++
++ ConsoleSession session(connection, sessionOptions);
++ session.open();
++
++ session.setAgentFilter("");
++
++ while (true) {
++ ConsoleEvent event;
++ if (session.nextEvent(event)) {
++ if (event.getType() == CONSOLE_AGENT_ADD) {
++ string extra;
++ if (event.getAgent().getName() == session.getConnectedBrokerAgent().getName())
++ extra = " [Connected Broker]";
++ cout << "Agent Added: " << event.getAgent().getName() << extra << endl;
++ }
++ if (event.getType() == CONSOLE_AGENT_DEL) {
++ if (event.getAgentDelReason() == AGENT_DEL_AGED)
++ cout << "Agent Aged: " << event.getAgent().getName() << endl;
++ else
++ cout << "Agent Filtered: " << event.getAgent().getName() << endl;
++ }
++ }
++ }
++}
++
+Index: cpp/bootstrap
+===================================================================
+--- cpp/bootstrap (revision 1056407)
++++ cpp/bootstrap (working copy)
+@@ -36,7 +36,7 @@
+ \$(mgen_cmd)
+ EOF
+
+-automake
++automake --add-missing
+ autoconf
+
+ # Optionally do the build as well.
+Index: cpp/include/qmf/Agent.h
+===================================================================
+--- cpp/include/qmf/Agent.h (revision 1056407)
++++ cpp/include/qmf/Agent.h (working copy)
+@@ -23,6 +23,7 @@
+
+ #include <qmf/ImportExport.h>
+ #include "qmf/Handle.h"
++//#include "qmf/Subscription.h"
+ #include "qmf/exceptions.h"
+ #include "qpid/messaging/Duration.h"
+ #include "qpid/types/Variant.h"
+@@ -61,6 +62,12 @@
+ QMF_EXTERN uint32_t queryAsync(const Query&);
+ QMF_EXTERN uint32_t queryAsync(const std::string&);
+
++ /**
++ * Create a subscription to this agent
++ */
++ //QMF_EXTERN Subscription subscribe(const Query&, const std::string& options = "");
++ //QMF_EXTERN Subscription subscribe(const std::string&, const std::string& options = "");
++
+ QMF_EXTERN ConsoleEvent callMethod(const std::string&, const qpid::types::Variant::Map&, const DataAddr&,
+ qpid::messaging::Duration timeout=qpid::messaging::Duration::MINUTE);
+ QMF_EXTERN uint32_t callMethodAsync(const std::string&, const qpid::types::Variant::Map&, const DataAddr&);
+Index: cpp/include/qmf/AgentSession.h
+===================================================================
+--- cpp/include/qmf/AgentSession.h (revision 1056407)
++++ cpp/include/qmf/AgentSession.h (working copy)
+@@ -57,11 +57,20 @@
+ * The options string is of the form "{key:value,key:value}". The following keys are supported:
+ *
+ * interval:N - Heartbeat interval in seconds [default: 60]
+- * external:{True,False} - Use external data storage (queries are pass-through) [default: False]
++ * external:{True,False} - Use external data storage (queries and subscriptions are pass-through) [default: False]
+ * allow-queries:{True,False} - If True: automatically allow all queries [default]
+ * If False: generate an AUTH_QUERY event to allow per-query authorization
+ * allow-methods:{True,False} - If True: automatically allow all methods [default]
+ * If False: generate an AUTH_METHOD event to allow per-method authorization
++ * max-subscriptions:N - Maximum number of concurrent subscription queries permitted [default: 64]
++ * min-sub-interval:N - Minimum publish interval (in milliseconds) permitted for a subscription [default: 3000]
++ * sub-lifetime:N - Lifetime (in seconds with no keepalive) for a subscription [default: 300]
++ * public-events:{True,False} - If True: QMF events are sent to the topic exchange [default]
++ * If False: QMF events are only sent to authorized subscribers
++ * listen-on-direct:{True,False} - If True: Listen on legacy direct-exchange address for backward compatibility [default]
++ * If False: Listen only on the routable direct address
++ * strict-security:{True,False} - If True: Cooperate with the broker to enforce strict access control to the network
++ * - If False: Operate more flexibly with regard to use of messaging facilities [default]
+ */
+ QMF_EXTERN AgentSession(qpid::messaging::Connection&, const std::string& options="");
+
+@@ -114,6 +123,11 @@
+ QMF_EXTERN bool nextEvent(AgentEvent&, qpid::messaging::Duration timeout=qpid::messaging::Duration::FOREVER);
+
+ /**
++ * Return the number of events pending for nextEvent. This method will never block.
++ */
++ QMF_EXTERN int pendingEvents() const;
++
++ /**
+ * Register a schema to be exposed by this agent.
+ */
+ QMF_EXTERN void registerSchema(Schema&);
+@@ -143,7 +157,7 @@
+ * authReject - Reject/forbid an authorization request.
+ * raiseException - indicate failure of an operation (i.e. query or method call).
+ * response - Provide data in response to a query (only for option: external:True)
+- * complete - Indicate that the response to a query is complete (external:true only)
++ * complete - Indicate that the response to a query is complete (external:True only)
+ * methodSuccess - Indicate the successful completion of a method call.
+ */
+ QMF_EXTERN void authAccept(AgentEvent&);
+@@ -156,8 +170,14 @@
+
+ /**
+ * Raise an event to be sent into the QMF network.
++ *
++ * @param data - A data object that contains the event contents.
++ * @param severity - Explicit severity (from qmf/SchemaTypes.h). If omitted, the severity is set to
++ * the default severity for the data's schema. If the data has no schema, the severity defaults
++ * to SEV_NOTICE.
+ */
+- QMF_EXTERN void raiseEvent(const Data&);
++ QMF_EXTERN void raiseEvent(const Data& data);
++ QMF_EXTERN void raiseEvent(const Data& data, int severity);
+
+ #ifndef SWIG
+ private:
+Index: cpp/include/qmf/DataAddr.h
+===================================================================
+--- cpp/include/qmf/DataAddr.h (revision 1056407)
++++ cpp/include/qmf/DataAddr.h (working copy)
+@@ -44,6 +44,7 @@
+ QMF_EXTERN bool operator==(const DataAddr&);
+ QMF_EXTERN bool operator<(const DataAddr&);
+
++ QMF_EXTERN DataAddr(const qpid::types::Variant::Map&);
+ QMF_EXTERN DataAddr(const std::string& name, const std::string& agentName, uint32_t agentEpoch=0);
+ QMF_EXTERN const std::string& getName() const;
+ QMF_EXTERN const std::string& getAgentName() const;
+Index: cpp/include/qmf/ConsoleEvent.h
+===================================================================
+--- cpp/include/qmf/ConsoleEvent.h (revision 1056407)
++++ cpp/include/qmf/ConsoleEvent.h (working copy)
+@@ -46,8 +46,10 @@
+ CONSOLE_QUERY_RESPONSE = 7,
+ CONSOLE_METHOD_RESPONSE = 8,
+ CONSOLE_EXCEPTION = 9,
+- CONSOLE_SUBSCRIBE_UPDATE = 10,
+- CONSOLE_THREAD_FAILED = 11
++ CONSOLE_SUBSCRIBE_ADD = 10,
++ CONSOLE_SUBSCRIBE_UPDATE = 11,
++ CONSOLE_SUBSCRIBE_DEL = 12,
++ CONSOLE_THREAD_FAILED = 13
+ };
+
+ enum AgentDelReason {
+@@ -72,6 +74,8 @@
+ QMF_EXTERN Data getData(uint32_t) const;
+ QMF_EXTERN bool isFinal() const;
+ QMF_EXTERN const qpid::types::Variant::Map& getArguments() const;
++ QMF_EXTERN int getSeverity() const;
++ QMF_EXTERN uint64_t getTimestamp() const;
+
+ #ifndef SWIG
+ private:
+Index: cpp/include/qmf/ConsoleSession.h
+===================================================================
+--- cpp/include/qmf/ConsoleSession.h (revision 1056407)
++++ cpp/include/qmf/ConsoleSession.h (working copy)
+@@ -24,6 +24,7 @@
+ #include <qmf/ImportExport.h>
+ #include "qmf/Handle.h"
+ #include "qmf/Agent.h"
++#include "qmf/Subscription.h"
+ #include "qpid/messaging/Duration.h"
+ #include "qpid/messaging/Connection.h"
+ #include <string>
+@@ -56,6 +57,10 @@
+ * domain:NAME - QMF Domain to join [default: "default"]
+ * max-agent-age:N - Maximum time, in minutes, that we will tolerate not hearing from
+ * an agent before deleting it [default: 5]
++ * listen-on-direct:{True,False} - If True: Listen on legacy direct-exchange address for backward compatibility [default]
++ * If False: Listen only on the routable direct address
++ * strict-security:{True,False} - If True: Cooperate with the broker to enforce strict access control to the network
++ * - If False: Operate more flexibly with regard to use of messaging facilities [default]
+ */
+ QMF_EXTERN ConsoleSession(qpid::messaging::Connection&, const std::string& options="");
+ QMF_EXTERN void setDomain(const std::string&);
+@@ -67,6 +72,16 @@
+ QMF_EXTERN Agent getAgent(uint32_t) const;
+ QMF_EXTERN Agent getConnectedBrokerAgent() const;
+
++ /**
++ * Create a subscription that involves a subset of the known agents. The set of known agents is defined by
++ * the session's agent-filter (see setAgentFilter). The agentFilter argument to the subscribe method is used
++ * to further refine the set of agents. If agentFilter is the empty string (i.e. match-all) the subscription
++ * will involve all known agents. If agentFilter is non-empty, it will be applied only to the set of known
++ * agents. A subscription cannot be created that involves an agent not known by the session.
++ */
++ QMF_EXTERN Subscription subscribe(const Query&, const std::string& agentFilter = "", const std::string& options = "");
++ QMF_EXTERN Subscription subscribe(const std::string&, const std::string& agentFilter = "", const std::string& options = "");
++
+ #ifndef SWIG
+ private:
+ friend class qmf::PrivateImplRef<ConsoleSession>;
+Index: cpp/include/qmf/Data.h
+===================================================================
+--- cpp/include/qmf/Data.h (revision 1056407)
++++ cpp/include/qmf/Data.h (working copy)
+@@ -34,6 +34,7 @@
+ #endif
+
+ class DataImpl;
++ class Schema;
+ class SchemaId;
+ class DataAddr;
+ class Agent;
+@@ -45,8 +46,7 @@
+ QMF_EXTERN Data& operator=(const Data&);
+ QMF_EXTERN ~Data();
+
+- QMF_EXTERN Data(const SchemaId&);
+- QMF_EXTERN void setSchema(const SchemaId&);
++ QMF_EXTERN Data(const Schema&);
+ QMF_EXTERN void setAddr(const DataAddr&);
+ QMF_EXTERN void setProperty(const std::string&, const qpid::types::Variant&);
+ QMF_EXTERN void overwriteProperties(const qpid::types::Variant::Map&);
+Index: cpp/include/qmf/Subscription.h
+===================================================================
+--- cpp/include/qmf/Subscription.h (revision 0)
++++ cpp/include/qmf/Subscription.h (revision 0)
+@@ -0,0 +1,82 @@
++#ifndef QMF_SUBSCRIPTION_H
++#define QMF_SUBSCRIPTION_H
++/*
++ *
++ * Licensed to the Apache Software Foundation (ASF) under one
++ * or more contributor license agreements. See the NOTICE file
++ * distributed with this work for additional information
++ * regarding copyright ownership. The ASF licenses this file
++ * to you under the Apache License, Version 2.0 (the
++ * "License"); you may not use this file except in compliance
++ * with the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing,
++ * software distributed under the License is distributed on an
++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
++ * KIND, either express or implied. See the License for the
++ * specific language governing permissions and limitations
++ * under the License.
++ *
++ */
++
++#include <qmf/ImportExport.h>
++#include "qmf/Handle.h"
++#include "qpid/types/Variant.h"
++#include <string>
++
++namespace qmf {
++
++#ifndef SWIG
++ template <class> class PrivateImplRef;
++#endif
++
++ class SubscriptionImpl;
++ class Data;
++
++ class Subscription : public qmf::Handle<SubscriptionImpl> {
++ public:
++ QMF_EXTERN Subscription(SubscriptionImpl* impl = 0);
++ QMF_EXTERN Subscription(const Subscription&);
++ QMF_EXTERN Subscription& operator=(const Subscription&);
++ QMF_EXTERN ~Subscription();
++
++ /**
++ * Construction: A subscription is created by calling ConsoleSession::subscribe.
++ */
++
++ /**
++ * Cancel subscriptions to all subscribed agents. After this is called, the subscription
++ * shall be inactive.
++ */
++ QMF_EXTERN void cancel();
++
++ /**
++ * Check to see if this subscription is active. It is active if it has a live subscription
++ * on at least one agent. If it is not active, there is nothing that can be done to make it
++ * active, it can only be deleted.
++ */
++ QMF_EXTERN bool isActive() const;
++
++ /**
++ * lock and unlock should be used to bracket a traversal of the data set. After lock is called,
++ * the subscription will not change its set of available data objects. Between calls to getDataCount
++ * and getData, no data objects will be added or removed. After unlock is called, the set of data
++ * will catch up to any activity that occurred while the lock was in effect.
++ */
++ QMF_EXTERN void lock();
++ QMF_EXTERN void unlock();
++ QMF_EXTERN uint32_t getDataCount() const;
++ QMF_EXTERN Data getData(uint32_t) const;
++
++#ifndef SWIG
++ private:
++ friend class qmf::PrivateImplRef<Subscription>;
++ friend class SubscriptionImplAccess;
++#endif
++ };
++
++}
++
++#endif
+Index: cpp/include/qpid/management/Manageable.h
+===================================================================
+--- cpp/include/qpid/management/Manageable.h (revision 1056407)
++++ cpp/include/qpid/management/Manageable.h (working copy)
+@@ -63,6 +63,11 @@
+ // method being called and must be down-cast to the appropriate sub class
+ // before use.
+ virtual status_t ManagementMethod(uint32_t methodId, Args& args, std::string& text);
++
++ // This optional method can be overridden to allow the agent application to
++ // authorize method invocations. Return true iff the authenticated user identified
++ // in userId us authorized to execute the method.
++ virtual bool AuthorizeMethod(uint32_t methodId, Args& args, const std::string& userId);
+ };
+
+ inline Manageable::~Manageable(void) {}
+Index: cpp/include/qpid/management/ManagementObject.h
+===================================================================
+--- cpp/include/qpid/management/ManagementObject.h (revision 1056407)
++++ cpp/include/qpid/management/ManagementObject.h (working copy)
+@@ -175,7 +175,8 @@
+ virtual void mapDecodeValues(const types::Variant::Map& map) = 0;
+ virtual void doMethod(std::string& methodName,
+ const types::Variant::Map& inMap,
+- types::Variant::Map& outMap) = 0;
++ types::Variant::Map& outMap,
++ const std::string& userId) = 0;
+ QPID_COMMON_EXTERN void writeTimestamps(types::Variant::Map& map) const;
+ QPID_COMMON_EXTERN void readTimestamps(const types::Variant::Map& buf);
+
+@@ -187,7 +188,7 @@
+ virtual void readProperties(const std::string&) {}
+ virtual void writeProperties(std::string&) const {}
+ virtual void writeStatistics(std::string&, bool = false) {}
+- virtual void doMethod(std::string&, const std::string&, std::string&) {}
++ virtual void doMethod(std::string&, const std::string&, std::string&, const std::string&) {}
+
+ QPID_COMMON_EXTERN virtual void setReference(ObjectId objectId);
+
+Index: cpp/managementgen/Makefile.am
+===================================================================
+--- cpp/managementgen/Makefile.am (revision 1056407)
++++ cpp/managementgen/Makefile.am (working copy)
+@@ -31,6 +31,8 @@
+ qmfgen/templates/Makefile.mk \
+ qmfgen/templates/Package.cpp \
+ qmfgen/templates/Package.h \
++ qmfgen/templates/V2Package.cpp \
++ qmfgen/templates/V2Package.h \
+ qmfgen/management-types.xml
+
+ EXTRA_DIST = $(nobase_qmfpython_DATA) CMakeLists.txt
+Index: cpp/managementgen/qmf-gen
+===================================================================
+--- cpp/managementgen/qmf-gen (revision 1056407)
++++ cpp/managementgen/qmf-gen (working copy)
+@@ -47,12 +47,15 @@
+ help="Generate makefile for Qpid broker")
+ parser.add_option("-b", "--broker-plugin", dest="brokerplugin", default=False, action="store_true",
+ help="Generate code for use in a qpid broker plugin")
++parser.add_option("-2", "--v2-style", dest="v2_style", default=False, action="store_true",
++ help="Generate code for use with the QMFv2 Agent API")
+
+ (opts, args) = parser.parse_args()
+
+ typefile = opts.typefile
+ templatedir = opts.templatedir
+ outdir = opts.outputdir
++v2_style = opts.v2_style
+ gen = Generator(outdir, templatedir)
+
+ if len(args) == 0:
+@@ -70,15 +73,20 @@
+ for schemafile in args:
+ package = SchemaPackage(typefile, schemafile, opts)
+
+- gen.setPackage (package.packageName)
+- gen.makeClassFiles ("Class.h", package, vars=vargs)
+- gen.makeClassFiles ("Class.cpp", package, vars=vargs)
+- gen.makeMethodFiles ("Args.h", package, vars=vargs)
+- gen.makeEventFiles ("Event.h", package, vars=vargs)
+- gen.makeEventFiles ("Event.cpp", package, vars=vargs)
+- gen.makePackageFile ("Package.h", package, vars=vargs)
+- gen.makePackageFile ("Package.cpp", package, vars=vargs)
++ gen.setPackage(package.packageName)
+
++ if v2_style:
++ gen.makeV2PackageFile("V2Package.h", package, vars=vargs)
++ gen.makeV2PackageFile("V2Package.cpp", package, vars=vargs)
++ else:
++ gen.makeClassFiles ("Class.h", package, vars=vargs)
++ gen.makeClassFiles ("Class.cpp", package, vars=vargs)
++ gen.makeMethodFiles ("Args.h", package, vars=vargs)
++ gen.makeEventFiles ("Event.h", package, vars=vargs)
++ gen.makeEventFiles ("Event.cpp", package, vars=vargs)
++ gen.makePackageFile ("Package.h", package, vars=vargs)
++ gen.makePackageFile ("Package.cpp", package, vars=vargs)
++
+ if opts.makefile != None:
+ args = {}
+ args["qpidbroker"] = opts.qpidbroker
+Index: cpp/managementgen/qmfgen/generate.py
+===================================================================
+--- cpp/managementgen/qmfgen/generate.py (revision 1056407)
++++ cpp/managementgen/qmfgen/generate.py (working copy)
+@@ -346,6 +346,14 @@
+ path = self.packagePath + "Package" + extension
+ return path
+
++ def targetV2PackageFile (self, schema, templateFile):
++ dot = templateFile.find(".")
++ if dot == -1:
++ raise ValueError ("Invalid template file name %s" % templateFile)
++ extension = templateFile[dot:len (templateFile)]
++ path = self.packagePath + "QmfPackage" + extension
++ return path
++
+ def targetClassFile (self, _class, templateFile):
+ dot = templateFile.find(".")
+ if dot == -1:
+@@ -448,6 +456,17 @@
+ stream = template.expand (schema)
+ self.writeIfChanged (stream, target, force)
+
++ def makeV2PackageFile (self, templateFile, schema, force=False, vars=None):
++ """ Generate a QMFv2 package definition file """
++ template = Template (self.input + templateFile, self)
++ if vars:
++ for arg in vars:
++ self.setVariable(arg, vars[arg])
++ self.templateFiles.append (templateFile)
++ target = self.targetV2PackageFile (schema, templateFile)
++ stream = template.expand (schema)
++ self.writeIfChanged (stream, target, force)
++
+ def makeSingleFile (self, templateFile, target, force=False, vars=None):
+ """ Generate a single expanded template """
+ dot = templateFile.find(".")
+Index: cpp/managementgen/qmfgen/schema.py
+===================================================================
+--- cpp/managementgen/qmfgen/schema.py (revision 1056407)
++++ cpp/managementgen/qmfgen/schema.py (working copy)
+@@ -1228,12 +1228,12 @@
+ inArgCount = inArgCount + 1
+
+ if methodCount == 0:
+- stream.write ("string&, const string&, string& outStr")
++ stream.write ("string&, const string&, string& outStr, const string&")
+ else:
+ if inArgCount == 0:
+- stream.write ("string& methodName, const string&, string& outStr")
++ stream.write ("string& methodName, const string&, string& outStr, const string& userId")
+ else:
+- stream.write ("string& methodName, const string& inStr, string& outStr")
++ stream.write ("string& methodName, const string& inStr, string& outStr, const string& userId")
+
+
+ def genDoMapMethodArgs (self, stream, variables):
+@@ -1248,16 +1248,16 @@
+ if methodCount == 0:
+ stream.write ("string&," +
+ " const ::qpid::types::Variant::Map&," +
+- " ::qpid::types::Variant::Map& outMap")
++ " ::qpid::types::Variant::Map& outMap, const string&")
+ else:
+ if inArgCount == 0:
+ stream.write ("string& methodName," +
+ " const ::qpid::types::Variant::Map&," +
+- " ::qpid::types::Variant::Map& outMap")
++ " ::qpid::types::Variant::Map& outMap, const string& userId")
+ else:
+ stream.write ("string& methodName," +
+ " const ::qpid::types::Variant::Map& inMap," +
+- " ::qpid::types::Variant::Map& outMap")
++ " ::qpid::types::Variant::Map& outMap, const string& userId")
+
+ def genHiLoStatResets (self, stream, variables):
+ for inst in self.statistics:
+@@ -1367,8 +1367,13 @@
+ arg.dir.lower () + "_" +\
+ arg.name, "inBuf") + ";\n")
+
+- stream.write (" status = coreObject->ManagementMethod (METHOD_" +\
++ stream.write (" bool allow = coreObject->AuthorizeMethod(METHOD_" +\
++ method.getName().upper() + ", ioArgs, userId);\n")
++ stream.write (" if (allow)\n")
++ stream.write (" status = coreObject->ManagementMethod (METHOD_" +\
+ method.getName().upper() + ", ioArgs, text);\n")
++ stream.write (" else\n")
++ stream.write (" status = Manageable::STATUS_FORBIDDEN;\n")
+ stream.write (" outBuf.putLong (status);\n")
+ stream.write (" outBuf.putMediumString(::qpid::management::Manageable::StatusText (status, text));\n")
+ for arg in method.args:
+@@ -1402,8 +1407,13 @@
+ arg.name,
+ "inMap")
+
+- stream.write (" status = coreObject->ManagementMethod (METHOD_" +\
++ stream.write (" bool allow = coreObject->AuthorizeMethod(METHOD_" +\
++ method.getName().upper() + ", ioArgs, userId);\n")
++ stream.write (" if (allow)\n")
++ stream.write (" status = coreObject->ManagementMethod (METHOD_" +\
+ method.getName().upper() + ", ioArgs, text);\n")
++ stream.write (" else\n")
++ stream.write (" status = Manageable::STATUS_FORBIDDEN;\n")
+ stream.write (" outMap[\"_status_code\"] = (uint32_t) status;\n")
+ stream.write (" outMap[\"_status_text\"] = ::qpid::management::Manageable::StatusText(status, text);\n")
+ for arg in method.args:
+@@ -1473,6 +1483,9 @@
+ for method in self.methods:
+ method.genSchemaMap(stream, variables)
+
++ def genName (self, stream, variables):
++ stream.write (self.name)
++
+ def genNameCap (self, stream, variables):
+ stream.write (capitalize(self.name))
+
+@@ -1637,6 +1650,9 @@
+ up = "_".join(self.packageName.split("."))
+ stream.write (up.upper())
+
++ def genPackageName (self, stream, variables):
++ stream.write(self.packageName)
++
+ def genNamePackageLower (self, stream, variables):
+ stream.write (self.packageName.lower ())
+
+@@ -1660,7 +1676,140 @@
+ _event.genNameCap(stream, variables)
+ stream.write("::registerSelf(agent);\n")
+
++ def genV2ClassMembers(self, stream, variables):
++ for _class in self.classes:
++ stream.write(" ::qmf::Schema data_%s;\n" % _class.name)
++ for _event in self.events:
++ stream.write(" ::qmf::Schema event_%s;\n" % _event.name)
+
++ def genV2ClassDefines(self, stream, variables):
++ for _class in self.classes:
++ stream.write("\n //\n // Data: %s\n //\n" % _class.name)
++ stream.write(" data_%s = qmf::Schema(SCHEMA_TYPE_DATA, package, \"%s\");\n" % (_class.name, _class.name))
++
++ for prop in _class.properties:
++ typeName, subType = self.qmfv2Type(prop.type)
++ access = self.qmfv2Access(prop.access)
++ stream.write(" {\n")
++ stream.write(" qmf::SchemaProperty prop(\"%s\", %s);\n" % (prop.name, typeName))
++ if subType:
++ stream.write(" prop.setSubtype(\"%s\");\n" % subType)
++ stream.write(" prop.setAccess(%s);\n" % access)
++ if prop.isIndex == 1:
++ stream.write(" prop.setIndex(true);\n")
++ if prop.isOptional == 1:
++ stream.write(" prop.setOptional(true);\n")
++ if prop.unit:
++ stream.write(" prop.setUnit(\"%s\");\n" % prop.unit)
++ if prop.desc:
++ stream.write(" prop.setDesc(\"%s\");\n" % prop.desc)
++ stream.write(" data_%s.addProperty(prop);\n" % _class.name)
++ stream.write(" }\n\n")
++
++ for stat in _class.statistics:
++ typeName, subType = self.qmfv2Type(stat.type)
++ stream.write(" {\n")
++ stream.write(" qmf::SchemaProperty prop(\"%s\", %s);\n" % (stat.name, typeName))
++ if subType:
++ stream.write(" prop.setSubtype(\"%s\");\n" % subType)
++ if stat.unit:
++ stream.write(" prop.setUnit(\"%s\");\n" % stat.unit)
++ if stat.desc:
++ stream.write(" prop.setDesc(\"%s\");\n" % stat.desc)
++ stream.write(" data_%s.addProperty(prop);\n" % _class.name)
++ stream.write(" }\n\n")
++
++ for method in _class.methods:
++ stream.write(" {\n")
++ stream.write(" qmf::SchemaMethod method(\"%s\");\n" % method.name)
++ if method.desc:
++ stream.write(" method.setDesc(\"%s\");\n" % method.desc)
++
++ for arg in method.args:
++ typeName, subType = self.qmfv2Type(arg.type)
++ stream.write(" {\n")
++ stream.write(" qmf::SchemaProperty arg(\"%s\", %s);\n" % (arg.name, typeName))
++ if subType:
++ stream.write(" arg.setSubtype(\"%s\");\n" % subType)
++ if stat.unit:
++ stream.write(" arg.setUnit(\"%s\");\n" % arg.unit)
++ if stat.desc:
++ stream.write(" arg.setDesc(\"%s\");\n" % arg.desc)
++ stream.write(" arg.setDirection(%s);\n" % self.qmfv2Dir(arg.dir))
++ stream.write(" method.addArgument(arg);\n")
++ stream.write(" }\n\n")
++
++ stream.write(" data_%s.addMethod(method);\n" % _class.name)
++ stream.write(" }\n\n")
++
++ stream.write(" session.registerSchema(data_%s);\n" % _class.name)
++
++ for _event in self.events:
++ stream.write("\n //\n // Event: %s\n //\n" % _event.name)
++ stream.write(" event_%s = qmf::Schema(SCHEMA_TYPE_EVENT, package, \"%s\");\n" % (_event.name, _event.name))
++ stream.write(" event_%s.setDefaultSeverity(%s);\n" % (_event.name, self.qmfv2Severity(_event.sev)))
++ for prop in _event.args:
++ typeName, subType = self.qmfv2Type(prop.type)
++ stream.write(" {\n")
++ stream.write(" qmf::SchemaProperty prop(\"%s\", %s);\n" % (prop.name, typeName))
++ if subType:
++ stream.write(" prop.setSubtype(\"%s\");\n" % subType)
++ if prop.unit:
++ stream.write(" prop.setUnit(\"%s\");\n" % prop.unit)
++ if prop.desc:
++ stream.write(" prop.setDesc(\"%s\");\n" % prop.desc)
++ stream.write(" event_%s.addProperty(prop);\n" % _event.name)
++ stream.write(" }\n\n")
++
++ stream.write(" session.registerSchema(event_%s);\n" % _event.name)
++
++
++ def qmfv2Type(self, typecode):
++ base = typecode.type.base
++ if base == "REF" : return ("qmf::SCHEMA_DATA_MAP", "reference")
++ if base == "U8" : return ("qmf::SCHEMA_DATA_INT", None)
++ if base == "U16" : return ("qmf::SCHEMA_DATA_INT", None)
++ if base == "U32" : return ("qmf::SCHEMA_DATA_INT", None)
++ if base == "U64" : return ("qmf::SCHEMA_DATA_INT", None)
++ if base == "S8" : return ("qmf::SCHEMA_DATA_INT", None)
++ if base == "S16" : return ("qmf::SCHEMA_DATA_INT", None)
++ if base == "S32" : return ("qmf::SCHEMA_DATA_INT", None)
++ if base == "S64" : return ("qmf::SCHEMA_DATA_INT", None)
++ if base == "BOOL" : return ("qmf::SCHEMA_DATA_BOOL", None)
++ if base == "SSTR" : return ("qmf::SCHEMA_DATA_STRING", None)
++ if base == "LSTR" : return ("qmf::SCHEMA_DATA_STRING", None)
++ if base == "ABSTIME" : return ("qmf::SCHEMA_DATA_INT", "abstime")
++ if base == "DELTATIME" : return ("qmf::SCHEMA_DATA_INT", "deltatime")
++ if base == "FLOAT" : return ("qmf::SCHEMA_DATA_FLOAT", None)
++ if base == "DOUBLE" : return ("qmf::SCHEMA_DATA_FLOAT", None)
++ if base == "UUID" : return ("qmf::SCHEMA_DATA_UUID", None)
++ if base == "FTABLE" : return ("qmf::SCHEMA_DATA_MAP", None)
++ if base == "LIST" : return ("qmf::SCHEMA_DATA_LIST", None)
++ raise ValueError("Unknown base type %s" % base)
++
++ def qmfv2Access(self, code):
++ if code == "RC": return "qmf::ACCESS_READ_CREATE"
++ if code == "RO": return "qmf::ACCESS_READ_ONLY"
++ if code == "RW": return "qmf::ACCESS_READ_WRITE"
++ raise ValueError("Unknown access type %s" % code)
++
++ def qmfv2Dir(self, code):
++ if code == "I" : return "qmf::DIR_IN"
++ if code == "O" : return "qmf::DIR_OUT"
++ if code == "IO": return "qmf::DIR_IN_OUT"
++ raise ValueError("Unknown direction type %s" % code)
++
++ def qmfv2Severity(self, code):
++ if code == 0 : return "qmf::SEV_EMERG"
++ if code == 1 : return "qmf::SEV_ALERT"
++ if code == 2 : return "qmf::SEV_CRIT"
++ if code == 3 : return "qmf::SEV_ERROR"
++ if code == 4 : return "qmf::SEV_WARN"
++ if code == 5 : return "qmf::SEV_NOTICE"
++ if code == 6 : return "qmf::SEV_INFORM"
++ if code == 7 : return "qmf::SEV_DEBUG"
++ raise ValueError("Out of Range Severity %d" % code)
++
+ #=====================================================================================
+ # Utility Functions
+ #=====================================================================================
+Index: cpp/managementgen/qmfgen/templates/V2Package.cpp
+===================================================================
+--- cpp/managementgen/qmfgen/templates/V2Package.cpp (revision 0)
++++ cpp/managementgen/qmfgen/templates/V2Package.cpp (revision 0)
+@@ -0,0 +1,37 @@
++/*MGEN:commentPrefix=//*/
++//
++// 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.
++//
++
++/*MGEN:Root.Disclaimer*/
++
++#include "QmfPackage.h"
++#include <qmf/Schema.h>
++#include <qmf/SchemaProperty.h>
++#include <qmf/SchemaMethod.h>
++#include <string>
++
++using namespace std;
++using namespace qmf::/*MGEN:Schema.Namespace*/;
++
++void PackageDefinition::configure(::qmf::AgentSession& session)
++{
++ string package("/*MGEN:Schema.PackageName*/");
++/*MGEN:Schema.V2ClassDefines*/
++}
++
+Index: cpp/managementgen/qmfgen/templates/V2Package.h
+===================================================================
+--- cpp/managementgen/qmfgen/templates/V2Package.h (revision 0)
++++ cpp/managementgen/qmfgen/templates/V2Package.h (revision 0)
+@@ -0,0 +1,46 @@
++/*MGEN:commentPrefix=//*/
++#ifndef _QMF_PACKAGE_/*MGEN:Schema.PackageNameUpper*/_
++#define _QMF_PACKAGE_/*MGEN:Schema.PackageNameUpper*/_
++
++//
++// 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.
++//
++
++/*MGEN:Root.Disclaimer*/
++
++#include <qmf/AgentSession.h>
++#include <qmf/Schema.h>
++#include <qmf/Data.h>
++#include <qmf/DataAddr.h>
++
++namespace qmf {
++/*MGEN:Class.OpenNamespaces*/
++
++class PackageDefinition
++{
++ public:
++ ~PackageDefinition() {}
++ void configure(::qmf::AgentSession& session);
++
++/*MGEN:Schema.V2ClassMembers*/
++};
++
++}/*MGEN:Class.CloseNamespaces*/
++
++
++#endif /*!_QMF_PACKAGE_/*MGEN:Schema.PackageNameUpper*/_*/
+Index: cpp/managementgen/qmfgen/templates/Class.h
+===================================================================
+--- cpp/managementgen/qmfgen/templates/Class.h (revision 1056407)
++++ cpp/managementgen/qmfgen/templates/Class.h (working copy)
+@@ -79,7 +79,8 @@
+ void mapDecodeValues(const ::qpid::types::Variant::Map& map);
+ void doMethod(std::string& methodName,
+ const ::qpid::types::Variant::Map& inMap,
+- ::qpid::types::Variant::Map& outMap);
++ ::qpid::types::Variant::Map& outMap,
++ const std::string& userId);
+ std::string getKey() const;
+ /*MGEN:IF(Root.GenQMFv1)*/
+ uint32_t writePropertiesSize() const;
+@@ -88,7 +89,8 @@
+ void writeStatistics(std::string& buf, bool skipHeaders = false);
+ void doMethod(std::string& methodName,
+ const std::string& inBuf,
+- std::string& outBuf);
++ std::string& outBuf,
++ const std::string& userId);
+ /*MGEN:ENDIF*/
+
+ writeSchemaCall_t getWriteSchemaCall() { return writeSchema; }
+Index: tools/src/py/qmf-tool
+===================================================================
+--- tools/src/py/qmf-tool (revision 0)
++++ tools/src/py/qmf-tool (revision 0)
+@@ -0,0 +1,684 @@
++#!/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 optparse
++import sys
++import socket
++from cmd import Cmd
++from shlex import split
++from threading import Lock
++from time import strftime, gmtime
++from qpid.disp import Display
++import cqpid
++import qmf2
++
++class Mcli(Cmd):
++ """ Management Command Interpreter """
++
++ def __init__(self, dataObject, dispObject):
++ Cmd.__init__(self)
++ self.dataObject = dataObject
++ self.dispObject = dispObject
++ self.dataObject.setCli(self)
++ self.prompt = "qmf: "
++
++ def emptyline(self):
++ pass
++
++ def setPromptMessage(self, p=None):
++ if p == None:
++ self.prompt = "qmf: "
++ else:
++ self.prompt = "qmf[%s]: " % p
++
++ def do_help(self, data):
++ print "Management Tool for QMF"
++ print
++ print "Agent Commands:"
++ print " set filter <filter-string> - Filter the list of agents"
++ print " show filter - Show the agent filter currently in effect"
++ print " list agents - Print a list of the known Agents"
++ print " show agent <item-number> - Print detailed information about an Agent"
++ print " set default <item-number> - Set the default agent for operations"
++ print
++ print "Schema Commands:"
++ print " list packages - Print a list of packages supported by the default agent"
++ print " list classes [<package-name>] - Print all classes supported byt the default agent"
++ print " show class <class-name> [<package-name>] - Show details of a class"
++ print
++ print "Data Commands:"
++ print " query <class-name> [<package-name>] [<predicate>] - Query for data from the agent"
++ print " list - List accumulated query results"
++ print " clear - Clear accumulated query results"
++ print " show <id> - Show details from a data object"
++ print " call <id> <method> [<args>] - Call a method on a data object"
++ print
++ print "General Commands:"
++ print " set time-format short - Select short timestamp format (default)"
++ print " set time-format long - Select long timestamp format"
++ print " quit or ^D - Exit the program"
++ print
++
++ def complete_set(self, text, line, begidx, endidx):
++ """ Command completion for the 'set' command """
++ tokens = split(line[:begidx])
++ if len(tokens) == 1:
++ return [i for i in ('filter ', 'default ', 'time-format ') if i.startswith(text)]
++ if len(tokens) == 2 and tokens[1] == 'time-format':
++ return [i for i in ('long', 'short') if i.startswith(text)]
++ return []
++
++ def do_set(self, data):
++ tokens = split(data)
++ try:
++ if tokens[0] == "time-format":
++ self.dispObject.do_setTimeFormat(tokens[1])
++ else:
++ self.dataObject.do_set(data)
++ except Exception, e:
++ print "Exception in set command:", e
++
++ def complete_list(self, text, line, begidx, endidx):
++ tokens = split(line[:begidx])
++ if len(tokens) == 1:
++ return [i for i in ('agents', 'packages', 'classes ') if i.startswith(text)]
++ return []
++
++ def do_list(self, data):
++ try:
++ self.dataObject.do_list(data)
++ except Exception, e:
++ print "Exception in list command:", e
++
++ def complete_show(self, text, line, begidx, endidx):
++ tokens = split(line[:begidx])
++ if len(tokens) == 1:
++ return [i for i in ('filter', 'agent ', 'class ') if i.startswith(text)]
++ return []
++
++ def do_show(self, data):
++ try:
++ self.dataObject.do_show(data)
++ except Exception, e:
++ print "Exception in show command:", e
++
++ def complete_query(self, text, line, begidx, endidx):
++ return []
++
++ def do_query(self, data):
++ try:
++ self.dataObject.do_query(data)
++ except Exception, e:
++ if e.message.__class__ == qmf2.Data:
++ e = e.message.getProperties()
++ print "Exception in query command:", e
++
++ def do_call(self, data):
++ try:
++ self.dataObject.do_call(data)
++ except Exception, e:
++ if e.message.__class__ == qmf2.Data:
++ e = e.message.getProperties()
++ print "Exception in call command:", e
++
++ def do_clear(self, data):
++ try:
++ self.dataObject.do_clear(data)
++ except Exception, e:
++ print "Exception in clear command:", e
++
++ def do_EOF(self, data):
++ print "quit"
++ try:
++ self.dataObject.do_exit()
++ except:
++ pass
++ return True
++
++ def do_quit(self, data):
++ try:
++ self.dataObject.do_exit()
++ except:
++ pass
++ return True
++
++ def postcmd(self, stop, line):
++ return stop
++
++ def postloop(self):
++ print "Exiting..."
++ self.dataObject.close()
++
++
++#======================================================================================================
++# QmfData
++#======================================================================================================
++class QmfData:
++ """
++ """
++ def __init__(self, disp, url):
++ self.disp = disp
++ self.url = url
++ self.agent_filter = '[]'
++ self.connection = cqpid.Connection(self.url)
++ self.connection.open()
++ self.session = qmf2.ConsoleSession(self.connection)
++ self.session.setAgentFilter(self.agent_filter)
++ self.session.open()
++ self.lock = Lock()
++ self.cli = None
++ self.agents = {} # Map of number => agent object
++ self.deleted_agents = {} # Map of number => agent object
++ self.agent_numbers = {} # Map of agent name => number
++ self.next_number = 1
++ self.focus_agent = None
++ self.data_list = {}
++ self.next_data_index = 1
++
++ #=======================
++ # Methods to support CLI
++ #=======================
++ def setCli(self, cli):
++ self.cli = cli
++
++ def close(self):
++ try:
++ self.session.close()
++ self.connection.close()
++ except:
++ pass # we're shutting down - ignore any errors
++
++ def do_list(self, data):
++ tokens = data.split()
++ if len(tokens) == 0:
++ self.listData()
++ elif tokens[0] == 'agents' or tokens[0] == 'agent':
++ self.listAgents()
++ elif tokens[0] == 'packages' or tokens[0] == 'package':
++ self.listPackages()
++ elif tokens[0] == 'classes' or tokens[0] == 'class':
++ self.listClasses(tokens[1:])
++
++ def do_set(self, data):
++ tokens = split(data)
++ if len(tokens) == 0:
++ print "What do you want to set? type 'help' for more information."
++ return
++ if tokens[0] == 'filter':
++ if len(tokens) == 2:
++ self.setAgentFilter(tokens[1])
++ elif tokens[0] == 'default':
++ if len(tokens) == 2:
++ self.updateAgents()
++ number = int(tokens[1])
++ self.focus_agent = self.agents[number]
++ print "Default Agent: %s" % self.focus_agent.getName()
++
++ def do_show(self, data):
++ tokens = split(data)
++ if len(tokens) == 0:
++ print "What do you want to show? Type 'help' for more information."
++ return
++
++ if tokens[0] == 'agent':
++ self.showAgent(tokens[1:])
++ return
++
++ if tokens[0] == 'filter':
++ print self.agent_filter
++ return
++
++ if tokens[0] == "default":
++ if not self.focus_agent:
++ self.updateAgents()
++ if self.focus_agent:
++ print "Default Agent: %s" % self.focus_agent.getName()
++ else:
++ print "Default Agent not set"
++ return
++
++ if tokens[0] == "class":
++ self.showClass(tokens[1:])
++ return
++
++ if tokens[0].isdigit():
++ self.showData(tokens[0])
++ return
++
++ print "What do you want to show? Type 'help' for more information."
++ return
++
++ def do_query(self, data):
++ tokens = split(data)
++ if len(tokens) == 0:
++ print "Class name not specified."
++ return
++ cname = tokens[0]
++ pname = None
++ pred = None
++ if len(tokens) >= 2:
++ if tokens[1][0] == '[':
++ pred = tokens[1]
++ else:
++ pname = tokens[1]
++ if len(tokens) >= 3:
++ pred = tokens[2]
++ query = "{class:'%s'" % cname
++ if pname:
++ query += ",package:'%s'" % pname
++ if pred:
++ query += ",where:%s" % pred
++ query += "}"
++ if not self.focus_agent:
++ self.updateAgents()
++ d_list = self.focus_agent.query(query)
++ local_data_list = {}
++ for d in d_list:
++ local_data_list[self.next_data_index] = d
++ self.next_data_index += 1
++ rows = []
++ for index,val in local_data_list.items():
++ rows.append((index, val.getAddr().getName()))
++ self.data_list[index] = val
++ self.disp.table("Data Objects Returned: %d:" % len(d_list), ("Number", "Data Address"), rows)
++
++ def do_call(self, data):
++ tokens = split(data)
++ if len(tokens) < 2:
++ print "Data ID and method-name not specified."
++ return
++ idx = int(tokens[0])
++ methodName = tokens[1]
++ args = []
++ for arg in tokens[2:]:
++ ##
++ ## If the argument is a map, list, boolean, integer, or floating (one decimal point),
++ ## run it through the Python evaluator so it is converted to the correct type.
++ ##
++ ## TODO: use a regex for this instead of this convoluted logic
++ if arg[0] == '{' or arg[0] == '[' or arg == "True" or arg == "False" or \
++ ((arg.count('.') < 2 and (arg.count('-') == 0 or \
++ (arg.count('-') == 1 and arg[0] == '-')) and \
++ arg.replace('.','').replace('-','').isdigit())):
++ args.append(eval(arg))
++ else:
++ args.append(arg)
++
++ if not idx in self.data_list:
++ print "Unknown data index, run 'query' to get a list of data indices"
++ return
++
++ data = self.data_list[idx]
++ data._getSchema()
++ result = data._invoke(methodName, args, {})
++ rows = []
++ for k,v in result.items():
++ rows.append((k,v))
++ self.disp.table("Output Parameters:", ("Name", "Value"), rows)
++
++ def do_clear(self, data):
++ self.data_list = {}
++ self.next_data_index = 1
++ print "Accumulated query results cleared"
++
++ def do_exit(self):
++ pass
++
++ #====================
++ # Sub-Command Methods
++ #====================
++ def setAgentFilter(self, filt):
++ self.agent_filter = filt
++ self.session.setAgentFilter(filt)
++
++ def updateAgents(self):
++ agents = self.session.getAgents()
++ number_list = []
++ for agent in agents:
++ if agent.getName() not in self.agent_numbers:
++ number = self.next_number
++ number_list.append(number)
++ self.next_number += 1
++ self.agent_numbers[agent.getName()] = number
++ self.agents[number] = agent
++ else:
++ ## Track seen agents so we can clean out deleted ones
++ number = self.agent_numbers[agent.getName()]
++ number_list.append(number)
++ if number in self.deleted_agents:
++ self.agents[number] = self.deleted_agents.pop(number)
++ deleted = []
++ for number in self.agents:
++ if number not in number_list:
++ deleted.append(number)
++ for number in deleted:
++ self.deleted_agents[number] = self.agents.pop(number)
++ if not self.focus_agent:
++ self.focus_agent = self.session.getConnectedBrokerAgent()
++
++ def listAgents(self):
++ self.updateAgents()
++ rows = []
++ for number in self.agents:
++ agent = self.agents[number]
++ if self.focus_agent and agent.getName() == self.focus_agent.getName():
++ d = '*'
++ else:
++ d = ''
++ rows.append((d, number, agent.getVendor(), agent.getProduct(), agent.getInstance(), agent.getEpoch()))
++ self.disp.table("QMF Agents:", ("", "Id", "Vendor", "Product", "Instance", "Epoch"), rows)
++
++ def listPackages(self):
++ if not self.focus_agent:
++ raise "Default Agent not set - use 'set default'"
++ self.focus_agent.loadSchemaInfo()
++ packages = self.focus_agent.getPackages()
++ for p in packages:
++ print " %s" % p
++
++ def getClasses(self, tokens):
++ if not self.focus_agent:
++ raise "Default Agent not set - use 'set default'"
++ return
++ self.focus_agent.loadSchemaInfo()
++ if len(tokens) == 1:
++ classes = self.focus_agent.getSchemaIds(tokens[0]);
++ else:
++ packages = self.focus_agent.getPackages()
++ classes = []
++ for p in packages:
++ classes.extend(self.focus_agent.getSchemaIds(p))
++ return classes
++
++ def listClasses(self, tokens):
++ classes = self.getClasses(tokens)
++ rows = []
++ for c in classes:
++ rows.append((c.getPackageName(), c.getName(), self.classTypeName(c.getType())))
++ self.disp.table("Classes:", ("Package", "Class", "Type"), rows)
++
++ def showClass(self, tokens):
++ if len(tokens) < 1:
++ return
++ classes = self.getClasses([])
++ c = tokens[0]
++ p = None
++ if len(tokens) == 2:
++ p = tokens[1]
++ schema = None
++ sid = None
++ for cls in classes:
++ if c == cls.getName():
++ if not p or p == cls.getPackageName():
++ schema = self.focus_agent.getSchema(cls)
++ sid = cls
++ break
++ if not sid:
++ return
++ print "Class: %s:%s (%s) - %s" % \
++ (sid.getPackageName(), sid.getName(), self.classTypeName(sid.getType()), schema.getDesc())
++ print " hash: %r" % sid.getHash()
++ props = schema.getProperties()
++ methods = schema.getMethods()
++ rows = []
++ for prop in props:
++ name = prop.getName()
++ dtype = self.typeName(prop.getType())
++ if len(prop.getSubtype()) > 0:
++ dtype += "(%s)" % prop.getSubtype()
++ access = self.accessName(prop.getAccess())
++ idx = self.yes_blank(prop.isIndex())
++ opt = self.yes_blank(prop.isOptional())
++ unit = prop.getUnit()
++ desc = prop.getDesc()
++ rows.append((name, dtype, idx, access, opt, unit, desc))
++ self.disp.table("Properties:", ("Name", "Type", "Index", "Access", "Optional", "Unit", "Description"), rows)
++ if len(methods) > 0:
++ for meth in methods:
++ name = meth.getName()
++ desc = meth.getDesc()
++ if len(desc) > 0:
++ desc = " - " + desc
++ args = meth.getArguments()
++ rows = []
++ for prop in args:
++ aname = prop.getName()
++ dtype = self.typeName(prop.getType())
++ if len(prop.getSubtype()) > 0:
++ dtype += "(%s)" % prop.getSubtype()
++ unit = prop.getUnit()
++ adesc = prop.getDesc()
++ io = self.dirName(prop.getDirection())
++ rows.append((aname, dtype, io, unit, adesc))
++ print
++ print " Method: %s%s" % (name, desc)
++ self.disp.table("Arguments:", ("Name", "Type", "Dir", "Unit", "Description"), rows)
++
++ def showAgent(self, tokens):
++ self.updateAgents()
++ for token in tokens:
++ number = int(token)
++ agent = self.agents[number]
++ print
++ print " =================================================================================="
++ print " Agent Id: %d" % number
++ print " Agent Name: %s" % agent.getName()
++ print " Epoch: %d" % agent.getEpoch()
++ print " Attributes:"
++ attrs = agent.getAttributes()
++ keys = attrs.keys()
++ keys.sort()
++ pairs = []
++ for key in keys:
++ if key == '_timestamp' or key == '_schema_updated':
++ val = disp.timestamp(attrs[key])
++ else:
++ val = attrs[key]
++ pairs.append((key, val))
++ self.printAlignedPairs(pairs)
++ agent.loadSchemaInfo()
++ print " Packages:"
++ packages = agent.getPackages()
++ for package in packages:
++ print " %s" % package
++
++ def showData(self, idx):
++ num = int(idx)
++ if not num in self.data_list:
++ print "Data ID not known, run 'query' first to get data"
++ return
++ data = self.data_list[num]
++ props = data.getProperties()
++ rows = []
++ for k,v in props.items():
++ rows.append((k, v))
++ self.disp.table("Properties:", ("Name", "Value"), rows)
++
++ def listData(self):
++ if len(self.data_list) == 0:
++ print "No Query Results - Use the 'query' command"
++ return
++ rows = []
++ for index,val in self.data_list.items():
++ rows.append((index, val.getAgent().getName(), val.getAddr().getName()))
++ self.disp.table("Accumulated Query Results:", ('Number', 'Agent', 'Data Address'), rows)
++
++ def printAlignedPairs(self, rows, indent=8):
++ maxlen = 0
++ for first, second in rows:
++ if len(first) > maxlen:
++ maxlen = len(first)
++ maxlen += indent
++ for first, second in rows:
++ for i in range(maxlen - len(first)):
++ print "",
++ print "%s : %s" % (first, second)
++
++ def classTypeName(self, code):
++ if code == qmf2.SCHEMA_TYPE_DATA: return "Data"
++ if code == qmf2.SCHEMA_TYPE_EVENT: return "Event"
++ return "Unknown"
++
++ def typeName (self, typecode):
++ """ Convert type-codes to printable strings """
++ if typecode == qmf2.SCHEMA_DATA_VOID: return "void"
++ elif typecode == qmf2.SCHEMA_DATA_BOOL: return "bool"
++ elif typecode == qmf2.SCHEMA_DATA_INT: return "int"
++ elif typecode == qmf2.SCHEMA_DATA_FLOAT: return "float"
++ elif typecode == qmf2.SCHEMA_DATA_STRING: return "string"
++ elif typecode == qmf2.SCHEMA_DATA_MAP: return "map"
++ elif typecode == qmf2.SCHEMA_DATA_LIST: return "list"
++ elif typecode == qmf2.SCHEMA_DATA_UUID: return "uuid"
++ else:
++ raise ValueError ("Invalid type code: %s" % str(typecode))
++
++ def valueByType(self, typecode, val):
++ if typecode == 1: return "%d" % val
++ elif typecode == 2: return "%d" % val
++ elif typecode == 3: return "%d" % val
++ elif typecode == 4: return "%d" % val
++ elif typecode == 6: return val
++ elif typecode == 7: return val
++ elif typecode == 8: return strftime("%c", gmtime(val / 1000000000))
++ elif typecode == 9:
++ if val < 0: val = 0
++ sec = val / 1000000000
++ min = sec / 60
++ hour = min / 60
++ day = hour / 24
++ result = ""
++ if day > 0:
++ result = "%dd " % day
++ if hour > 0 or result != "":
++ result += "%dh " % (hour % 24)
++ if min > 0 or result != "":
++ result += "%dm " % (min % 60)
++ result += "%ds" % (sec % 60)
++ return result
++
++ elif typecode == 10: return str(self.idRegistry.displayId(val))
++ elif typecode == 11:
++ if val:
++ return "True"
++ else:
++ return "False"
++
++ elif typecode == 12: return "%f" % val
++ elif typecode == 13: return "%f" % val
++ elif typecode == 14: return "%r" % val
++ elif typecode == 15: return "%r" % val
++ elif typecode == 16: return "%d" % val
++ elif typecode == 17: return "%d" % val
++ elif typecode == 18: return "%d" % val
++ elif typecode == 19: return "%d" % val
++ elif typecode == 20: return "%r" % val
++ elif typecode == 21: return "%r" % val
++ elif typecode == 22: return "%r" % val
++ else:
++ raise ValueError ("Invalid type code: %s" % str(typecode))
++
++ def accessName (self, code):
++ """ Convert element access codes to printable strings """
++ if code == qmf2.ACCESS_READ_CREATE: return "ReadCreate"
++ elif code == qmf2.ACCESS_READ_WRITE: return "ReadWrite"
++ elif code == qmf2.ACCESS_READ_ONLY: return "ReadOnly"
++ else:
++ raise ValueError ("Invalid access code: %s" % str(code))
++
++ def dirName(self, io):
++ if io == qmf2.DIR_IN: return "in"
++ elif io == qmf2.DIR_OUT: return "out"
++ elif io == qmf2.DIR_IN_OUT: return "in_out"
++ else:
++ raise ValueError("Invalid direction code: %r" % io)
++
++ def notNone (self, text):
++ if text == None:
++ return ""
++ else:
++ return text
++
++ def yes_blank(self, val):
++ if val:
++ return "Y"
++ return ""
++
++ def objectIndex(self, obj):
++ if obj._objectId.isV2:
++ return obj._objectId.getObject()
++ result = ""
++ first = True
++ props = obj.getProperties()
++ for prop in props:
++ if prop[0].index:
++ if not first:
++ result += "."
++ result += self.valueByType(prop[0].type, prop[1])
++ first = None
++ return result
++
++def Usage():
++ print "Usage: qpid-tool [[<username>/<password>@]<target-host>[:<tcp-port>]]"
++ print
++
++#=========================================================
++# Main Program
++#=========================================================
++
++# Get host name and port if specified on the command line
++cargs = sys.argv[1:]
++_host = "localhost"
++
++if len(cargs) > 0:
++ _host = cargs[0]
++
++if _host[0] == '-':
++ Usage()
++ if _host != '-h' and _host != "--help":
++ print "qpid-tool: error: no such option:", _host
++ sys.exit(1)
++
++disp = Display()
++
++# Attempt to make a connection to the target broker
++try:
++ data = QmfData(disp, _host)
++except Exception, e:
++ if str(e).find("Exchange not found") != -1:
++ print "Management not enabled on broker: Use '-m yes' option on broker startup."
++ else:
++ print "Failed: %s - %s" % (e.__class__.__name__, e)
++ sys.exit(1)
++
++# Instantiate the CLI interpreter and launch it.
++cli = Mcli(data, disp)
++print("Management Tool for QMF")
++try:
++ cli.cmdloop()
++except KeyboardInterrupt:
++ print
++ print "Exiting..."
++except Exception, e:
++ print "Failed: %s - %s" % (e.__class__.__name__, e)
++
++# alway attempt to cleanup broker resources
++data.close()
+
+Property changes on: tools/src/py/qmf-tool
+___________________________________________________________________
+Added: svn:executable
+ + *
+
+Index: tools/setup.py
+===================================================================
+--- tools/setup.py (revision 1056407)
++++ tools/setup.py (working copy)
+@@ -23,7 +23,8 @@
+ version="0.8",
+ author="Apache Qpid",
+ author_email="dev at qpid.apache.org",
+- scripts=["src/py/qpid-cluster",
++ scripts=["src/py/qmf-tool",
++ "src/py/qpid-cluster",
+ "src/py/qpid-cluster-store",
+ "src/py/qpid-config",
+ "src/py/qpid-printevents",
+Index: extras/qmf/src/py/qmf/console.py
+===================================================================
+--- extras/qmf/src/py/qmf/console.py (revision 1056413)
++++ extras/qmf/src/py/qmf/console.py (working copy)
+@@ -53,6 +53,10 @@
+ """ Invoked when a connection is established to a broker """
+ pass
+
++ def brokerConnectionFailed(self, broker):
++ """ Invoked when a connection to a broker fails """
++ pass
++
+ def brokerDisconnected(self, broker):
+ """ Invoked when the connection to a broker is lost """
+ pass
+@@ -376,7 +380,8 @@
+ dp.routing_key = self.getV2RoutingKey()
+ mp = self._broker.amqpSession.message_properties()
+ mp.content_type = "amqp/map"
+- mp.user_id = self._broker.authUser
++ if self._broker.saslUser:
++ mp.user_id = self._broker.saslUser
+ mp.correlation_id = str(seq)
+ mp.app_id = "qmf2"
+ mp.reply_to = self._broker.amqpSession.reply_to("qmf.default.direct", self._broker.v2_direct_queue)
+@@ -1206,11 +1211,11 @@
+ try:
+ agentName = ah["qmf.agent"]
+ values = content["_values"]
+- timestamp = values["timestamp"]
+- interval = values["heartbeat_interval"]
++ timestamp = values["_timestamp"]
++ interval = values["_heartbeat_interval"]
+ epoch = 0
+- if 'epoch' in values:
+- epoch = values['epoch']
++ if '_epoch' in values:
++ epoch = values['_epoch']
+ except Exception,e:
+ return
+
+@@ -1239,7 +1244,7 @@
+ agent.touch()
+ if self.rcvHeartbeats and self.console and agent:
+ self._heartbeatCallback(agent, timestamp)
+- agent.update_schema_timestamp(values.get("schema_timestamp", 0))
++ agent.update_schema_timestamp(values.get("_schema_updated", 0))
+
+
+ def _v2HandleAgentLocateRsp(self, broker, mp, ah, content):
+@@ -1488,7 +1493,8 @@
+ dp.routing_key = objectId.getV2RoutingKey()
+ mp = broker.amqpSession.message_properties()
+ mp.content_type = "amqp/map"
+- mp.user_id = broker.authUser
++ if broker.saslUser:
++ mp.user_id = broker.saslUser
+ mp.correlation_id = str(seq)
+ mp.app_id = "qmf2"
+ mp.reply_to = broker.amqpSession.reply_to("qmf.default.direct", broker.v2_direct_queue)
+@@ -2227,9 +2233,12 @@
+ self.port = port
+ self.mechanisms = authMechs
+ self.ssl = ssl
++ if connTimeout is not None:
++ connTimeout = float(connTimeout)
+ self.connTimeout = connTimeout
+ self.authUser = authUser
+ self.authPass = authPass
++ self.saslUser = None
+ self.cv = Condition()
+ self.seqToAgentMap = {}
+ self.error = None
+@@ -2403,6 +2412,11 @@
+ self.conn.start()
+ sock.settimeout(oldTimeout)
+ self.conn.aborted = oldAborted
++ uid = self.conn.user_id
++ if uid.__class__ == tuple and len(uid) == 2:
++ self.saslUser = uid[1]
++ else:
++ self.saslUser = None
+
+ # prevent topic queues from filling up (and causing the agents to
+ # disconnect) by discarding the oldest queued messages when full.
+@@ -2508,6 +2522,8 @@
+ except Exception, e:
+ self.error = "Exception during connection setup: %s - %s" % (e.__class__.__name__, e)
+ self.conn_exc = e
++ if self.session.console:
++ self.session.console.brokerConnectionFailed(self)
+ return False # connection failed
+
+ def _updateAgent(self, obj):
+@@ -2571,7 +2587,7 @@
+ for agent in to_notify:
+ self.session._delAgentCallback(agent)
+
+- def _v2SendAgentLocate(self, predicate={}):
++ def _v2SendAgentLocate(self, predicate=[]):
+ """
+ Broadcast an agent-locate request to cause all agents in the domain to tell us who they are.
+ """
+@@ -2579,13 +2595,14 @@
+ dp = self.amqpSession.delivery_properties()
+ dp.routing_key = "console.request.agent_locate"
+ mp = self.amqpSession.message_properties()
+- mp.content_type = "amqp/map"
+- mp.user_id = self.authUser
++ mp.content_type = "amqp/list"
++ if self.saslUser:
++ mp.user_id = self.saslUser
+ mp.app_id = "qmf2"
+ mp.reply_to = self.amqpSession.reply_to("qmf.default.direct", self.v2_direct_queue)
+ mp.application_headers = {'qmf.opcode':'_agent_locate_request'}
+ sendCodec = Codec()
+- sendCodec.write_map(predicate)
++ sendCodec.write_list(predicate)
+ msg = Message(dp, mp, sendCodec.encoded)
+ self._send(msg, "qmf.default.topic")
+
+@@ -2622,7 +2639,8 @@
+ dp.ttl = ttl
+ mp = self.amqpSession.message_properties()
+ mp.content_type = "x-application/qmf"
+- mp.user_id = self.authUser
++ if self.saslUser:
++ mp.user_id = self.saslUser
+ mp.reply_to = self.amqpSession.reply_to("amq.direct", self.replyName)
+ return Message(dp, mp, body)
+
+@@ -2853,7 +2871,7 @@
+ content = None
+ else:
+ content = None
+-
++
+ if content != None:
+ ##
+ ## Directly handle agent heartbeats and agent locate responses as these are broker-scope (they are
+@@ -3366,6 +3384,9 @@
+ Handle a QMFv2 data indication from the agent. Note: called from context
+ of the Broker thread.
+ """
++ if content.__class__ != list:
++ return
++
+ if mp.correlation_id:
+ try:
+ self.lock.acquire()
+@@ -3382,8 +3403,6 @@
+ if "qmf.content" in ah:
+ kind = ah["qmf.content"]
+ if kind == "_data":
+- if content.__class__ != list:
+- return
+ for omap in content:
+ context.addV2QueryResult(omap)
+ context.processV2Data()
+@@ -3391,14 +3410,15 @@
+ context.signal()
+
+ elif kind == "_event":
+- event = Event(self, v2Map=content)
+- if event.classKey is None or event.schema:
+- # schema optional or present
+- context.doEvent(event)
+- else:
+- # schema not optional and not present
+- if context.addPendingEvent(event):
+- self._v2SendSchemaRequest(event.classKey)
++ for omap in content:
++ event = Event(self, v2Map=omap)
++ if event.classKey is None or event.schema:
++ # schema optional or present
++ context.doEvent(event)
++ else:
++ # schema not optional and not present
++ if context.addPendingEvent(event):
++ self._v2SendSchemaRequest(event.classKey)
+
+ elif kind == "_schema_id":
+ for sid in content:
+@@ -3533,7 +3553,8 @@
+ dp.routing_key = self.getV2RoutingKey()
+ mp = self.broker.amqpSession.message_properties()
+ mp.content_type = "amqp/map"
+- mp.user_id = self.broker.authUser
++ if self.broker.saslUser:
++ mp.user_id = self.broker.saslUser
+ mp.correlation_id = str(sequence)
+ mp.app_id = "qmf2"
+ mp.reply_to = self.broker.amqpSession.reply_to("qmf.default.direct", self.broker.v2_direct_queue)
+@@ -3976,6 +3997,9 @@
+ def brokerConnected(self, broker):
+ print "brokerConnected:", broker
+
++ def brokerConnectionFailed(self, broker):
++ print "brokerConnectionFailed:", broker
++
+ def brokerDisconnected(self, broker):
+ print "brokerDisconnected:", broker
+
+Index: extras/qmf/src/py/qmf2/common.py
+===================================================================
+--- extras/qmf/src/py/qmf2/common.py (revision 1056407)
++++ extras/qmf/src/py/qmf2/common.py (working copy)
+@@ -1,1738 +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.
+-#
+-import time
+-from logging import getLogger
+-from threading import Lock
+-from threading import Condition
+-try:
+- import hashlib
+- _md5Obj = hashlib.md5
+-except ImportError:
+- import md5
+- _md5Obj = md5.new
+-
+-log = getLogger("qmf")
+-log_query = getLogger("qmf.query")
+-
+-
+-##
+-## Constants
+-##
+-
+-QMF_APP_ID="qmf2"
+-
+-
+-class ContentType(object):
+- """ Values for the 'qmf.content' message header
+- """
+- schema_package = "_schema_package"
+- schema_id = "_schema_id"
+- schema_class = "_schema_class"
+- object_id = "_object_id"
+- data = "_data"
+- event = "_event"
+-
+-
+-class OpCode(object):
+- """ Values for the 'qmf.opcode' message header.
+- """
+- noop = "_noop"
+-
+- # codes sent by a console and processed by the agent
+- agent_locate_req = "_agent_locate_request"
+- subscribe_req = "_subscribe_request"
+- subscribe_cancel_ind = "_subscribe_cancel_indication"
+- subscribe_refresh_ind = "_subscribe_refresh_indication"
+- query_req = "_query_request"
+- method_req = "_method_request"
+-
+-
+- # codes sent by the agent to a console
+- agent_locate_rsp = "_agent_locate_response"
+- agent_heartbeat_ind = "_agent_heartbeat_indication"
+- query_rsp = "_query_response"
+- subscribe_rsp = "_subscribe_response"
+- data_ind = "_data_indication"
+- method_rsp = "_method_response"
+-
+-
+-
+-def timedelta_to_secs(td):
+- """
+- Convert a time delta to a time interval in seconds (float)
+- """
+- return td.days * 86400 + td.seconds + td.microseconds/1000000.0
+-
+-
+-##==============================================================================
+-## Async Event Model
+-##==============================================================================
+-
+-
+-class Notifier(object):
+- """
+- Virtual base class that defines a call back which alerts the application that
+- a QMF Console notification is pending.
+- """
+- def indication(self):
+- """
+- Called when one or more items are ready for the application to process.
+- This method may be called by an internal QMF library thread. Its purpose is to
+- indicate that the application should process pending work items.
+- """
+- raise Exception("The indication method must be overridden by the application!")
+-
+-
+-
+-class WorkItem(object):
+- """
+- Describes an event that has arrived for the application to process. The
+- Notifier is invoked when one or more of these WorkItems become available
+- for processing.
+- """
+- # Enumeration of the types of WorkItems produced on the Console
+- AGENT_ADDED=1
+- AGENT_DELETED=2
+- NEW_PACKAGE=3
+- NEW_CLASS=4
+- OBJECT_UPDATE=5
+- EVENT_RECEIVED=7
+- AGENT_HEARTBEAT=8
+- QUERY_COMPLETE=9
+- METHOD_RESPONSE=10
+- SUBSCRIBE_RESPONSE=11
+- SUBSCRIBE_INDICATION=12
+- RESUBSCRIBE_RESPONSE=13
+- # Enumeration of the types of WorkItems produced on the Agent
+- METHOD_CALL=1000
+- QUERY=1001
+- SUBSCRIBE_REQUEST=1002
+- RESUBSCRIBE_REQUEST=1003
+- UNSUBSCRIBE_REQUEST=1004
+-
+- def __init__(self, kind, handle, _params=None):
+- """
+- Used by the Console to create a work item.
+-
+- @type kind: int
+- @param kind: work item type
+- """
+- self._kind = kind
+- self._handle = handle
+- self._params = _params
+-
+- def get_type(self):
+- return self._kind
+-
+- def get_handle(self):
+- return self._handle
+-
+- def get_params(self):
+- return self._params
+-
+-
+-
+-##==============================================================================
+-## Addressing
+-##==============================================================================
+-
+-class QmfAddress(object):
+- """
+- Address format: "qmf.<domain>.[topic|direct]/<subject>"
+- TBD
+- """
+-
+- TYPE_DIRECT = "direct"
+- TYPE_TOPIC = "topic"
+-
+- ADDRESS_FMT = "qmf.%s.%s/%s"
+- DEFAULT_DOMAIN = "default"
+-
+- # Directly-addressed messages:
+- # agent's direct address: "qmf.<domain>.direct/<agent-name>
+- # console's direct address: "qmf.<domain>.direct/<console-name>
+-
+- # Well-known Topic Addresses:
+- # "qmf.<domain>.topic/<subject>
+- # Where <subject> has the following format:
+- # "console.ind#" - indications sent from consoles
+- # "agent.ind#" - indications sent from agents
+- #
+- # The following "well known" subjects are defined:
+- #
+- # console.ind.locate[.<agent-name>] - agent discovery request
+- # agent.ind.heartbeat[.<agent-name>"] - agent heartbeats
+- # agent.ind.event[.<severity>.<agent-name>] - events
+- # agent.ind.schema[TBD] - schema updates
+- #
+- SUBJECT_AGENT_IND="agent.ind"
+- SUBJECT_AGENT_HEARTBEAT = "agent.ind.heartbeat"
+- SUBJECT_AGENT_EVENT="agent.ind.event"
+- SUBJECT_AGENT_SCHEMA="agent.ind.schema"
+-
+- SUBJECT_CONSOLE_IND="console.ind"
+- SUBJECT_CONSOLE_LOCATE_AGENT="console.ind.locate"
+-
+-
+-
+- def __init__(self, subject, domain, type_):
+- if '/' in domain or '.' in domain:
+- raise Exception("domain string must not contain '/' or '.'"
+- " characters.")
+-
+- self._subject = subject
+- self._domain = domain
+- self._type = type_
+-
+- def _direct(cls, subject, _domain=None):
+- if _domain is None:
+- _domain = QmfAddress.DEFAULT_DOMAIN
+- return cls(subject, _domain, type_=QmfAddress.TYPE_DIRECT)
+- direct = classmethod(_direct)
+-
+- def _topic(cls, subject, _domain=None):
+- if _domain is None:
+- _domain = QmfAddress.DEFAULT_DOMAIN
+- return cls(subject, _domain, type_=QmfAddress.TYPE_TOPIC)
+- topic = classmethod(_topic)
+-
+- def __from_string(cls, address):
+- node,subject = address.split('/',1)
+- qmf,domain,type_ = node.split('.',2)
+-
+- if qmf != "qmf" or (type_ != QmfAddress.TYPE_DIRECT and
+- type_ != QmfAddress.TYPE_TOPIC):
+- raise ValueError("invalid QmfAddress format: %s" % address)
+-
+- return cls(subject, domain, type_)
+- from_string = classmethod(__from_string)
+-
+- def get_address(self):
+- """
+- Return the QMF address as a string, suitable for use with the AMQP
+- messaging API.
+- """
+- return str(self)
+-
+- def get_node(self):
+- """
+- Return the 'node' portion of the address.
+- """
+- return self.get_address().split('/',1)[0]
+-
+- def get_subject(self):
+- """
+- Return the 'subject' portion of the address.
+- """
+- return self.get_address().split('/',1)[1]
+-
+- def get_domain(self):
+- return self._domain
+-
+- def is_direct(self):
+- return self._type == self.TYPE_DIRECT
+-
+- def __repr__(self):
+- return QmfAddress.ADDRESS_FMT % (self._domain, self._type, self._subject)
+-
+-
+-
+-
+-class AgentName(object):
+- """
+- Uniquely identifies a management agent within the management domain.
+- """
+- _separator = ":"
+-
+- def __init__(self, vendor, product, name, _str=None):
+- """
+- Note: this object must be immutable, as it is used to index into a dictionary
+- """
+- if _str is not None:
+- # construct from string representation
+- if _str.count(AgentName._separator) < 2:
+- raise TypeError("AgentName string format must be 'vendor.product.name'")
+- self._vendor, self._product, self._name = _str.split(AgentName._separator)
+- else:
+- self._vendor = vendor
+- self._product = product
+- self._name = name
+-
+-
+- def _from_str(cls, str_):
+- return cls(None, None, None, str_=str_)
+- from_str = classmethod(_from_str)
+-
+- def vendor(self):
+- return self._vendor
+-
+- def product(self):
+- return self._product
+-
+- def name(self):
+- return self._name
+-
+- def __cmp__(self, other):
+- if not isinstance(other, AgentName) :
+- raise TypeError("Invalid types for compare")
+- # return 1
+- me = str(self)
+- them = str(other)
+-
+- if me < them:
+- return -1
+- if me > them:
+- return 1
+- return 0
+-
+- def __hash__(self):
+- return (self._vendor, self._product, self._name).__hash__()
+-
+- def __repr__(self):
+- return self._vendor + AgentName._separator + \
+- self._product + AgentName._separator + \
+- self._name
+-
+-
+-
+-##==============================================================================
+-## DATA MODEL
+-##==============================================================================
+-
+-
+-class _mapEncoder(object):
+- """
+- virtual base class for all objects that support being converted to a map
+- """
+-
+- def map_encode(self):
+- raise Exception("The map_encode method my be overridden.")
+-
+-
+-class QmfData(_mapEncoder):
+- """
+- Base class representing management data.
+-
+- Map format:
+- map["_values"] = map of unordered "name"=<value> pairs (optional)
+- map["_subtype"] = map of unordered "name"="subtype string" pairs (optional)
+- map["_tag"] = application-specific tag for this instance (optional)
+- """
+- KEY_VALUES = "_values"
+- KEY_SUBTYPES = "_subtypes"
+- KEY_TAG="_tag"
+- KEY_OBJECT_ID = "_object_id"
+- KEY_SCHEMA_ID = "_schema_id"
+- KEY_UPDATE_TS = "_update_ts"
+- KEY_CREATE_TS = "_create_ts"
+- KEY_DELETE_TS = "_delete_ts"
+-
+- def __init__(self,
+- _values={}, _subtypes={}, _tag=None,
+- _object_id=None, _schema_id=None,
+- _ctime = 0, _utime = 0, _dtime = 0,
+- _map=None, _const=False):
+- """
+- @type _values: dict
+- @param _values: dictionary of initial name=value pairs for object's
+- named data.
+- @type _subtypes: dict
+- @param _subtype: dictionary of subtype strings for each of the object's
+- named data.
+- @type _desc: string
+- @param _desc: Human-readable description of this data object.
+- @type _const: boolean
+- @param _const: if true, this object cannot be modified
+- """
+- if _map is not None:
+- # construct from map
+- _tag = _map.get(self.KEY_TAG, _tag)
+- _values = _map.get(self.KEY_VALUES, _values)
+- _subtypes = _map.get(self.KEY_SUBTYPES, _subtypes)
+- _object_id = _map.get(self.KEY_OBJECT_ID, _object_id)
+- sid = _map.get(self.KEY_SCHEMA_ID)
+- if sid:
+- _schema_id = SchemaClassId.from_map(sid)
+- _ctime = long(_map.get(self.KEY_CREATE_TS, _ctime))
+- _utime = long(_map.get(self.KEY_UPDATE_TS, _utime))
+- _dtime = long(_map.get(self.KEY_DELETE_TS, _dtime))
+-
+- self._values = _values.copy()
+- self._subtypes = _subtypes.copy()
+- self._tag = _tag
+- self._ctime = _ctime
+- self._utime = _utime
+- self._dtime = _dtime
+- self._const = _const
+- self._schema_id = _schema_id
+- self._object_id = str(_object_id)
+-
+-
+- def __create(cls, values, _subtypes={}, _tag=None, _object_id=None,
+- _schema_id=None, _const=False):
+- # timestamp in millisec since epoch UTC
+- ctime = long(time.time() * 1000)
+- return cls(_values=values, _subtypes=_subtypes, _tag=_tag,
+- _ctime=ctime, _utime=ctime,
+- _object_id=_object_id, _schema_id=_schema_id, _const=_const)
+- create = classmethod(__create)
+-
+- def __from_map(cls, map_, _const=False):
+- return cls(_map=map_, _const=_const)
+- from_map = classmethod(__from_map)
+-
+- def is_managed(self):
+- return self._object_id is not None
+-
+- def is_described(self):
+- return self._schema_id is not None
+-
+- def get_tag(self):
+- return self._tag
+-
+- def get_value(self, name):
+- """
+- Will throw an AttributeError exception if the named value does not exist.
+- """
+- # meta-properties first:
+- if name == SchemaClassId.KEY_PACKAGE:
+- if self._schema_id:
+- return self._schema_id.get_package_name()
+- return None
+- if name == SchemaClassId.KEY_CLASS:
+- if self._schema_id:
+- return self._schema_id.get_class_name()
+- return None
+- if name == SchemaClassId.KEY_TYPE:
+- if self._schema_id:
+- return self._schema_id.get_type()
+- return None
+- if name == SchemaClassId.KEY_HASH:
+- if self._schema_id:
+- return self._schema_id.get_hash_string()
+- return None
+- if name == self.KEY_SCHEMA_ID:
+- return self._schema_id
+- if name == self.KEY_OBJECT_ID:
+- return self._object_id
+- if name == self.KEY_TAG:
+- return self._tag
+- if name == self.KEY_UPDATE_TS:
+- return self._utime
+- if name == self.KEY_CREATE_TS:
+- return self._ctime
+- if name == self.KEY_DELETE_TS:
+- return self._dtime
+-
+- try:
+- return self._values[name]
+- except KeyError:
+- raise AttributeError("no value named '%s' in this object" % name)
+-
+- def has_value(self, name):
+-
+- if name in [SchemaClassId.KEY_PACKAGE, SchemaClassId.KEY_CLASS,
+- SchemaClassId.KEY_TYPE, SchemaClassId.KEY_HASH,
+- self.KEY_SCHEMA_ID]:
+- return self._schema_id is not None
+- if name in [self.KEY_UPDATE_TS, self.KEY_CREATE_TS,
+- self.KEY_DELETE_TS]:
+- return True
+- if name == self.KEY_OBJECT_ID:
+- return self._object_id is not None
+- if name == self.KEY_TAG:
+- return self._tag is not None
+-
+- return name in self._values
+-
+- def set_value(self, _name, _value, _subType=None):
+- if self._const:
+- raise Exception("cannot modify constant data object")
+- self._values[_name] = _value
+- if _subType:
+- self._subtypes[_name] = _subType
+- return _value
+-
+- def get_subtype(self, _name):
+- return self._subtypes.get(_name)
+-
+- def get_schema_class_id(self):
+- """
+- @rtype: class SchemaClassId
+- @returns: the identifier of the Schema that describes the structure of the data.
+- """
+- return self._schema_id
+-
+- def get_object_id(self):
+- """
+- Get the instance's identification string.
+- @rtype: str
+- @returns: the identification string, or None if not assigned and id.
+- """
+- return self._object_id
+-
+- def map_encode(self):
+- _map = {}
+- if self._tag:
+- _map[self.KEY_TAG] = self._tag
+-
+- # data in the _values map may require recursive map_encode()
+- vmap = {}
+- for name,val in self._values.iteritems():
+- if isinstance(val, _mapEncoder):
+- vmap[name] = val.map_encode()
+- else:
+- # otherwise, just toss in the native type...
+- vmap[name] = val
+-
+- _map[self.KEY_VALUES] = vmap
+- # subtypes are never complex, so safe to just copy
+- _map[self.KEY_SUBTYPES] = self._subtypes.copy()
+- if self._object_id:
+- _map[self.KEY_OBJECT_ID] = self._object_id
+- if self._schema_id:
+- _map[self.KEY_SCHEMA_ID] = self._schema_id.map_encode()
+- return _map
+-
+- def __repr__(self):
+- return "QmfData=<<" + str(self.map_encode()) + ">>"
+-
+-
+- def __setattr__(self, _name, _value):
+- # ignore private data members
+- if _name[0] == '_':
+- return super(QmfData, self).__setattr__(_name, _value)
+- if _name in self._values:
+- return self.set_value(_name, _value)
+- return super(QmfData, self).__setattr__(_name, _value)
+-
+- def __getattr__(self, _name):
+- if _name != "_values" and _name in self._values:
+- return self._values[_name]
+- raise AttributeError("no value named '%s' in this object" % _name)
+-
+- def __getitem__(self, _name):
+- return self.__getattr__(_name)
+-
+- def __setitem__(self, _name, _value):
+- return self.__setattr__(_name, _value)
+-
+-
+-
+-class QmfEvent(QmfData):
+- """
+- A QMF Event is a type of described data that is not managed. Events are
+- notifications that are sent by Agents. An event notifies a Console of a
+- change in some aspect of the system under managment.
+- """
+- KEY_TIMESTAMP = "_timestamp"
+- KEY_SEVERITY = "_severity"
+-
+- SEV_EMERG = "emerg"
+- SEV_ALERT = "alert"
+- SEV_CRIT = "crit"
+- SEV_ERR = "err"
+- SEV_WARNING = "warning"
+- SEV_NOTICE = "notice"
+- SEV_INFO = "info"
+- SEV_DEBUG = "debug"
+-
+- def __init__(self, _timestamp=None, _sev=SEV_NOTICE, _values={},
+- _subtypes={}, _tag=None,
+- _map=None,
+- _schema_id=None, _const=True):
+- """
+- @type _map: dict
+- @param _map: if not None, construct instance from map representation.
+- @type _timestamp: int
+- @param _timestamp: moment in time when event occurred, expressed
+- as milliseconds since Midnight, Jan 1, 1970 UTC.
+- @type _agentId: class AgentId
+- @param _agentId: Identifies agent issuing this event.
+- @type _schema: class Schema
+- @param _schema:
+- @type _schemaId: class SchemaClassId (event)
+- @param _schemaId: identi
+- """
+-
+- if _map is not None:
+- # construct from map
+- super(QmfEvent, self).__init__(_map=_map, _const=_const,
+- _object_id="_event")
+- _timestamp = _map.get(self.KEY_TIMESTAMP, _timestamp)
+- _sev = _map.get(self.KEY_SEVERITY, _sev)
+- else:
+- super(QmfEvent, self).__init__(_object_id="_event",
+- _values=_values,
+- _subtypes=_subtypes, _tag=_tag,
+- _schema_id=_schema_id,
+- _const=_const)
+- if _timestamp is None:
+- raise TypeError("QmfEvent: a valid timestamp is required.")
+-
+- try:
+- self._timestamp = long(_timestamp)
+- except:
+- raise TypeError("QmfEvent: a numeric timestamp is required.")
+-
+- self._severity = _sev
+-
+- def _create(cls, timestamp, severity, values,
+- _subtypes={}, _tag=None, _schema_id=None, _const=False):
+- return cls(_timestamp=timestamp, _sev=severity, _values=values,
+- _subtypes=_subtypes, _tag=_tag, _schema_id=_schema_id, _const=_const)
+- create = classmethod(_create)
+-
+- def _from_map(cls, map_, _const=False):
+- return cls(_map=map_, _const=_const)
+- from_map = classmethod(_from_map)
+-
+- def get_timestamp(self):
+- return self._timestamp
+-
+- def get_severity(self):
+- return self._severity
+-
+- def map_encode(self):
+- _map = super(QmfEvent, self).map_encode()
+- _map[self.KEY_TIMESTAMP] = self._timestamp
+- _map[self.KEY_SEVERITY] = self._severity
+- return _map
+-
+-
+-
+-##==============================================================================
+-## QUERY
+-##==============================================================================
+-
+-
+-
+-class QmfQuery(_mapEncoder):
+-
+- KEY_TARGET="what"
+- KEY_PREDICATE="where"
+- KEY_ID="id"
+-
+- ### Query Types
+- ID=1
+- PREDICATE=2
+-
+- #### Query Targets ####
+- TARGET_PACKAGES="schema_package"
+- # (returns just package names)
+- # allowed predicate key(s):
+- #
+- # SchemaClassId.KEY_PACKAGE
+-
+- TARGET_SCHEMA_ID="schema_id"
+- TARGET_SCHEMA="schema"
+- # allowed id: value:
+- # SchemaClassId
+- #
+- # allowed predicate key(s):
+- # SchemaClassId.KEY_PACKAGE
+- # SchemaClassId.KEY_CLASS
+- # SchemaClassId.KEY_TYPE
+- # SchemaClassId.KEY_HASH
+- # SchemaClass.KEY_SCHEMA_ID
+- # name of property (exist test only)
+- # name of method (exist test only)
+-
+- TARGET_AGENT="agent"
+- # allowed id: value:
+- # string name of agent
+- # allowed predicate keys(s):
+- #
+- KEY_AGENT_NAME="_name"
+-
+- TARGET_OBJECT_ID="object_id"
+- TARGET_OBJECT="object"
+- # If object is described by a schema, the value of the target map must
+- # include a "_schema_id": {map encoded schema id} value.
+- #
+- # allowed id: value:
+- # object_id string
+- #
+- # allowed predicate keys(s):
+- #
+- # QmfData.KEY_OBJECT_ID
+- # QmfData.KEY_UPDATE_TS
+- # QmfData.KEY_CREATE_TS
+- # QmfData.KEY_DELETE_TS
+- # <name of data value>
+-
+- # supported predicate operators
+-
+- # evaluation operators
+- QUOTE="quote"
+- UNQUOTE="unquote"
+- # boolean operators
+- EQ="eq"
+- NE="ne"
+- LT="lt"
+- LE="le"
+- GT="gt"
+- GE="ge"
+- RE_MATCH="re_match"
+- EXISTS="exists"
+- TRUE="true"
+- FALSE="false"
+- # logic operators
+- AND="and"
+- OR="or"
+- NOT="not"
+-
+- _valid_targets = [TARGET_PACKAGES, TARGET_OBJECT_ID, TARGET_SCHEMA, TARGET_SCHEMA_ID,
+- TARGET_OBJECT, TARGET_AGENT]
+- _valid_bool_ops = [EQ, NE, LT, GT, LE, GE, EXISTS, RE_MATCH, TRUE, FALSE]
+- _valid_logic_ops = [AND, OR, NOT]
+- _valid_eval_ops = [QUOTE, UNQUOTE]
+-
+- def __init__(self, _target=None, _target_params=None, _predicate=None,
+- _id=None, _map=None):
+- """
+- """
+- if _map is not None:
+- target_map = _map.get(self.KEY_TARGET)
+- if not target_map:
+- raise TypeError("QmfQuery requires a target map")
+-
+- _target = None
+- for key in target_map.iterkeys():
+- if key in self._valid_targets:
+- _target = key
+- break
+- if _target is None:
+- raise TypeError("Invalid QmfQuery target: '%s'" %
+- str(target_map))
+-
+- # convert target params from map format
+- _target_params = target_map.get(_target)
+- if _target_params:
+- if not isinstance(_target_params, type({})):
+- raise TypeError("target params must be a map: '%s'" %
+- str(_target_params))
+- t_params = {}
+- for name,value in _target_params.iteritems():
+- if name == QmfData.KEY_SCHEMA_ID:
+- t_params[name] = SchemaClassId.from_map(value)
+- else:
+- t_params[name] = value
+- _target_params = t_params
+-
+- _id = _map.get(self.KEY_ID)
+- if _id is not None:
+- # Convert identifier to native type if necessary
+- if _target == self.TARGET_SCHEMA:
+- _id = SchemaClassId.from_map(_id)
+- else:
+- _predicate = _map.get(self.KEY_PREDICATE, _predicate)
+-
+- self._target = _target
+- if not self._target:
+- raise TypeError("QmfQuery requires a target value")
+- self._target_params = _target_params
+- self._predicate = _predicate
+- self._id = _id
+-
+- # constructors
+- def _create_wildcard(cls, target, _target_params=None):
+- return cls(_target=target, _target_params=_target_params)
+- create_wildcard = classmethod(_create_wildcard)
+-
+- def _create_wildcard_object_id(cls, schema_id):
+- """
+- Create a wildcard to match all object_ids for a given schema.
+- """
+- if not isinstance(schema_id, SchemaClassId):
+- raise TypeError("class SchemaClassId expected")
+- params = {QmfData.KEY_SCHEMA_ID: schema_id}
+- return cls(_target=QmfQuery.TARGET_OBJECT_ID,
+- _target_params=params)
+- create_wildcard_object_id = classmethod(_create_wildcard_object_id)
+-
+- def _create_wildcard_object(cls, schema_id):
+- """
+- Create a wildcard to match all objects for a given schema.
+- """
+- if not isinstance(schema_id, SchemaClassId):
+- raise TypeError("class SchemaClassId expected")
+- params = {QmfData.KEY_SCHEMA_ID: schema_id}
+- return cls(_target=QmfQuery.TARGET_OBJECT,
+- _target_params=params)
+- create_wildcard_object = classmethod(_create_wildcard_object)
+-
+- def _create_predicate(cls, target, predicate, _target_params=None):
+- return cls(_target=target, _target_params=_target_params,
+- _predicate=predicate)
+- create_predicate = classmethod(_create_predicate)
+-
+- def _create_id(cls, target, ident, _target_params=None):
+- return cls(_target=target, _target_params=_target_params, _id=ident)
+- create_id = classmethod(_create_id)
+-
+- def _create_id_object(cls, object_id, _schema_id=None):
+- """
+- Create a ID Query for an object (schema optional).
+- """
+- if _schema_id is not None:
+- if not isinstance(_schema_id, SchemaClassId):
+- raise TypeError("class SchemaClassId expected")
+- params = {QmfData.KEY_SCHEMA_ID: _schema_id}
+- else:
+- params = None
+- return cls(_target=QmfQuery.TARGET_OBJECT,
+- _id=object_id,
+- _target_params=params)
+- create_id_object = classmethod(_create_id_object)
+-
+- def _create_id_object_id(cls, object_id, _schema_id=None):
+- """
+- Create a ID Query for object_ids (schema optional).
+- """
+- if _schema_id is not None:
+- if not isinstance(_schema_id, SchemaClassId):
+- raise TypeError("class SchemaClassId expected")
+- params = {QmfData.KEY_SCHEMA_ID: _schema_id}
+- else:
+- params = None
+- return cls(_target=QmfQuery.TARGET_OBJECT_ID,
+- _id=object_id,
+- _target_params=params)
+- create_id_object_id = classmethod(_create_id_object_id)
+-
+- def _from_map(cls, map_):
+- return cls(_map=map_)
+- from_map = classmethod(_from_map)
+- # end constructors
+-
+- def get_target(self):
+- return self._target
+-
+- def get_target_param(self):
+- return self._target_params
+-
+- def get_selector(self):
+- if self._id:
+- return QmfQuery.ID
+- else:
+- return QmfQuery.PREDICATE
+-
+- def get_id(self):
+- return self._id
+-
+- def get_predicate(self):
+- """
+- """
+- return self._predicate
+-
+- def evaluate(self, qmfData):
+- """
+- """
+- if self._id:
+- if self._target == self.TARGET_SCHEMA:
+- return (qmfData.has_value(qmfData.KEY_SCHEMA_ID) and
+- qmfData.get_value(qmfData.KEY_SCHEMA_ID) == self._id)
+- elif self._target == self.TARGET_OBJECT:
+- return (qmfData.has_value(qmfData.KEY_OBJECT_ID) and
+- qmfData.get_value(qmfData.KEY_OBJECT_ID) == self._id)
+- elif self._target == self.TARGET_AGENT:
+- return (qmfData.has_value(self.KEY_AGENT_NAME) and
+- qmfData.get_value(self.KEY_AGENT_NAME) == self._id)
+-
+- raise Exception("Unsupported query target '%s'" % str(self._target))
+-
+- if self._predicate:
+- return self._eval_pred(self._predicate, qmfData)
+- # no predicate and no id - always match
+- return True
+-
+- def map_encode(self):
+- t_params = {}
+- if self._target_params:
+- for name,value in self._target_params.iteritems():
+- if isinstance(value, _mapEncoder):
+- t_params[name] = value.map_encode()
+- else:
+- t_params[name] = value
+- if t_params:
+- _map = {self.KEY_TARGET: {self._target: t_params}}
+- else:
+- _map = {self.KEY_TARGET: {self._target: None}}
+-
+- if self._id is not None:
+- if isinstance(self._id, _mapEncoder):
+- _map[self.KEY_ID] = self._id.map_encode()
+- else:
+- _map[self.KEY_ID] = self._id
+- elif self._predicate is not None:
+- _map[self.KEY_PREDICATE] = self._predicate
+- return _map
+-
+- def _eval_pred(self, pred, qmfData):
+- """
+- Evaluate the predicate expression against a QmfData object.
+- """
+- if not isinstance(qmfData, QmfData):
+- raise TypeError("Query expects to evaluate QmfData types.")
+-
+- if not isinstance(pred, type([])):
+- log.warning("Invalid type for predicate expression: '%s'" % str(pred))
+- return False
+-
+- # empty predicate - match all???
+- if len(pred) == 0:
+- return True
+-
+- oper = pred[0]
+- if oper == QmfQuery.TRUE:
+- log_query.debug("query evaluate TRUE")
+- return True
+-
+- if oper == QmfQuery.FALSE:
+- log_query.debug("query evaluate FALSE")
+- return False
+-
+- if oper == QmfQuery.AND:
+- log_query.debug("query evaluate AND: '%s'" % str(pred))
+- for exp in pred[1:]:
+- if not self._eval_pred(exp, qmfData):
+- log_query.debug("---> False")
+- return False
+- log_query.debug("---> True")
+- return True
+-
+- if oper == QmfQuery.OR:
+- log_query.debug("query evaluate OR: [%s]" % str(pred))
+- for exp in pred[1:]:
+- if self._eval_pred(exp, qmfData):
+- log_query.debug("---> True")
+- return True
+- log_query.debug("---> False")
+- return False
+-
+- if oper == QmfQuery.NOT:
+- log_query.debug("query evaluate NOT: [%s]" % str(pred))
+- for exp in pred[1:]:
+- if self._eval_pred(exp, qmfData):
+- log_query.debug("---> False")
+- return False
+- log_query.debug("---> True")
+- return True
+-
+- if oper == QmfQuery.EXISTS:
+- if len(pred) != 2:
+- log.warning("Malformed query: 'exists' operator"
+- " - bad arguments '%s'" % str(pred))
+- return False
+- ### Q: Should we assume "quote", or should it be explicit?
+- ### "foo" or ["quote" "foo"]
+- ### my guess is "explicit"
+- log_query.debug("query evaluate EXISTS: [%s]" % str(pred))
+- try:
+- arg = self._fetch_pred_arg(pred[1], qmfData)
+- except AttributeError:
+- log.warning("query parameter not found: '%s'" % str(pred))
+- return False
+- v = qmfData.has_value(arg)
+- log_query.debug("---> %s" % str(v))
+- return v
+-
+- # binary operators
+- if oper in [QmfQuery.EQ, QmfQuery.NE, QmfQuery.LT,
+- QmfQuery.LE, QmfQuery.GT, QmfQuery.GE,
+- QmfQuery.RE_MATCH]:
+- if len(pred) != 3:
+- log.warning("Malformed query: '%s' operator"
+- " - requires 2 arguments '%s'" %
+- (oper, str(pred)))
+- return False
+- # @todo: support regular expression match
+- log_query.debug("query evaluate binary op: [%s]" % str(pred))
+- try:
+- arg1 = self._fetch_pred_arg(pred[1], qmfData)
+- arg2 = self._fetch_pred_arg(pred[2], qmfData)
+- except AttributeError:
+- log.warning("query parameter not found: '%s'" % str(pred))
+- return False
+- log_query.debug("query evaluate %s: %s, %s" % (oper, str(arg1), str(arg2)))
+- v = False
+- try:
+- if oper == QmfQuery.EQ: v = arg1 == arg2
+- elif oper == QmfQuery.NE: v = arg1 != arg2
+- elif oper == QmfQuery.LT: v = arg1 < arg2
+- elif oper == QmfQuery.LE: v = arg1 <= arg2
+- elif oper == QmfQuery.GT: v = arg1 > arg2
+- elif oper == QmfQuery.GE: v = arg1 >= arg2
+- except TypeError:
+- log.warning("query comparison failed: '%s'" % str(pred))
+- log_query.debug("---> %s" % str(v))
+- return v
+-
+- log.warning("Unrecognized query operator: [%s]" % str(pred[0]))
+- return False
+-
+- def _fetch_pred_arg(self, arg, qmfData):
+- """
+- Determine the value of a predicate argument by evaluating quoted
+- arguments.
+- """
+- if isinstance(arg, basestring):
+- return qmfData.get_value(arg)
+- if isinstance(arg, type([])) and len(arg) == 2:
+- if arg[0] == QmfQuery.QUOTE:
+- return arg[1]
+- if arg[0] == QmfQuery.UNQUOTE:
+- return qmfData.get_value(arg[1])
+- return arg
+-
+- def __repr__(self):
+- return "QmfQuery=<<" + str(self.map_encode()) + ">>"
+-
+-
+-
+-
+-
+-##==============================================================================
+-## SCHEMA
+-##==============================================================================
+-
+-
+-# Argument typecodes, access, and direction qualifiers
+-
+-class qmfTypes(object):
+- TYPE_UINT8 = 1
+- TYPE_UINT16 = 2
+- TYPE_UINT32 = 3
+- TYPE_UINT64 = 4
+-
+- TYPE_SSTR = 6
+- TYPE_LSTR = 7
+-
+- TYPE_ABSTIME = 8
+- TYPE_DELTATIME = 9
+-
+- TYPE_REF = 10
+-
+- TYPE_BOOL = 11
+-
+- TYPE_FLOAT = 12
+- TYPE_DOUBLE = 13
+-
+- TYPE_UUID = 14
+-
+- TYPE_MAP = 15
+-
+- TYPE_INT8 = 16
+- TYPE_INT16 = 17
+- TYPE_INT32 = 18
+- TYPE_INT64 = 19
+-
+- TYPE_OBJECT = 20
+-
+- TYPE_LIST = 21
+-
+- TYPE_ARRAY = 22
+-
+-# New subtypes:
+-# integer (for time, duration, signed/unsigned)
+-# double (float)
+-# bool
+-# string
+-# map (ref, qmfdata)
+-# list
+-# uuid
+-
+-
+-class qmfAccess(object):
+- READ_CREATE = 1
+- READ_WRITE = 2
+- READ_ONLY = 3
+-
+-
+-class qmfDirection(object):
+- DIR_IN = 1
+- DIR_OUT = 2
+- DIR_IN_OUT = 3
+-
+-
+-
+-def _to_bool( param ):
+- """
+- Helper routine to convert human-readable representations of
+- boolean values to python bool types.
+- """
+- _false_strings = ["off", "no", "false", "0", "none"]
+- _true_strings = ["on", "yes", "true", "1"]
+- if type(param) == str:
+- lparam = param.lower()
+- if lparam in _false_strings:
+- return False
+- if lparam in _true_strings:
+- return True
+- raise TypeError("unrecognized boolean string: '%s'" % param )
+- else:
+- return bool(param)
+-
+-
+-
+-class SchemaClassId(_mapEncoder):
+- """
+- Unique identifier for an instance of a SchemaClass.
+-
+- Map format:
+- map["package_name"] = str, name of associated package
+- map["class_name"] = str, name of associated class
+- map["type"] = str, "data"|"event", default: "data"
+- optional:
+- map["hash_str"] = str, hash value in standard format or None
+- if hash is unknown.
+- """
+- KEY_PACKAGE="_package_name"
+- KEY_CLASS="_class_name"
+- KEY_TYPE="_type"
+- KEY_HASH="_hash_str"
+-
+- TYPE_DATA = "_data"
+- TYPE_EVENT = "_event"
+-
+- _valid_types=[TYPE_DATA, TYPE_EVENT]
+- _schemaHashStrFormat = "%08x-%08x-%08x-%08x"
+- _schemaHashStrDefault = "00000000-00000000-00000000-00000000"
+-
+- def __init__(self, pname=None, cname=None, stype=TYPE_DATA, hstr=None,
+- _map=None):
+- """
+- @type pname: str
+- @param pname: the name of the class's package
+- @type cname: str
+- @param cname: name of the class
+- @type stype: str
+- @param stype: schema type [data | event]
+- @type hstr: str
+- @param hstr: the hash value in '%08x-%08x-%08x-%08x' format
+- """
+- if _map is not None:
+- # construct from map
+- pname = _map.get(self.KEY_PACKAGE, pname)
+- cname = _map.get(self.KEY_CLASS, cname)
+- stype = _map.get(self.KEY_TYPE, stype)
+- hstr = _map.get(self.KEY_HASH, hstr)
+-
+- self._pname = pname
+- self._cname = cname
+- if stype not in SchemaClassId._valid_types:
+- raise TypeError("Invalid SchemaClassId type: '%s'" % stype)
+- self._type = stype
+- self._hstr = hstr
+- if self._hstr:
+- try:
+- # sanity check the format of the hash string
+- hexValues = hstr.split("-")
+- h0 = int(hexValues[0], 16)
+- h1 = int(hexValues[1], 16)
+- h2 = int(hexValues[2], 16)
+- h3 = int(hexValues[3], 16)
+- except:
+- raise Exception("Invalid SchemaClassId format: bad hash string: '%s':"
+- % hstr)
+- # constructor
+- def _create(cls, pname, cname, stype=TYPE_DATA, hstr=None):
+- return cls(pname=pname, cname=cname, stype=stype, hstr=hstr)
+- create = classmethod(_create)
+-
+- # map constructor
+- def _from_map(cls, map_):
+- return cls(_map=map_)
+- from_map = classmethod(_from_map)
+-
+- def get_package_name(self):
+- """
+- Access the package name in the SchemaClassId.
+-
+- @rtype: str
+- """
+- return self._pname
+-
+-
+- def get_class_name(self):
+- """
+- Access the class name in the SchemaClassId
+-
+- @rtype: str
+- """
+- return self._cname
+-
+-
+- def get_hash_string(self):
+- """
+- Access the schema's hash as a string value
+-
+- @rtype: str
+- """
+- return self._hstr
+-
+-
+- def get_type(self):
+- """
+- Returns the type code associated with this Schema
+-
+- @rtype: str
+- """
+- return self._type
+-
+- def map_encode(self):
+- _map = {}
+- _map[self.KEY_PACKAGE] = self._pname
+- _map[self.KEY_CLASS] = self._cname
+- _map[self.KEY_TYPE] = self._type
+- if self._hstr: _map[self.KEY_HASH] = self._hstr
+- return _map
+-
+- def __repr__(self):
+- hstr = self.get_hash_string()
+- if not hstr:
+- hstr = SchemaClassId._schemaHashStrDefault
+- return self._pname + ":" + self._cname + ":" + self._type + "(" + hstr + ")"
+-
+-
+- def __cmp__(self, other):
+- if isinstance(other, dict):
+- other = SchemaClassId.from_map(other)
+- if not isinstance(other, SchemaClassId):
+- raise TypeError("Invalid types for compare")
+- # return 1
+- me = str(self)
+- them = str(other)
+- if me < them:
+- return -1
+- if me > them:
+- return 1
+- return 0
+-
+-
+- def __hash__(self):
+- return (self._pname, self._cname, self._hstr).__hash__()
+-
+-
+-
+-class SchemaProperty(_mapEncoder):
+- """
+- Describes the structure of a Property data object.
+- Map format:
+- map["amqp_type"] = int, AMQP type code indicating property's data type
+-
+- optional:
+- map["access"] = str, access allowed to this property, default "RO"
+- map["index"] = bool, True if this property is an index value, default False
+- map["optional"] = bool, True if this property is optional, default False
+- map["unit"] = str, describes units used
+- map["min"] = int, minimum allowed value
+- map["max"] = int, maximun allowed value
+- map["maxlen"] = int, if string type, this is the maximum length in bytes
+- required to represent the longest instance of this string.
+- map["desc"] = str, human-readable description of this argument
+- map["reference"] = str, ???
+- map["parent_ref"] = bool, true if this property references an object in
+- which this object is in a child-parent relationship. Default False
+- map["continuous"] = bool, true if the value potentially changes too fast to
+- be directly monitorable. Example: fast changing statistic or random
+- number. Subscriptions to objects containing continuous data will publish
+- only on an interval basis, rather than every time the data changes. Default
+- False.
+- """
+- __hash__ = None
+- _access_strings = ["RO","RW","RC"]
+- _dir_strings = ["I", "O", "IO"]
+- def __init__(self, _type_code=None, _map=None, kwargs={}):
+- if _map is not None:
+- # construct from map
+- _type_code = _map.get("amqp_type", _type_code)
+- kwargs = _map
+- if not _type_code:
+- raise TypeError("SchemaProperty: amqp_type is a mandatory"
+- " parameter")
+-
+- self._type = _type_code
+- self._access = "RO"
+- self._isIndex = False
+- self._isOptional = False
+- self._unit = None
+- self._min = None
+- self._max = None
+- self._maxlen = None
+- self._desc = None
+- self._reference = None
+- self._isParentRef = False
+- self._dir = None
+- self._default = None
+- self._is_continuous = False
+-
+- for key, value in kwargs.items():
+- if key == "access":
+- value = str(value).upper()
+- if value not in self._access_strings:
+- raise TypeError("invalid value for access parameter: '%s':" % value )
+- self._access = value
+- elif key == "index" : self._isIndex = _to_bool(value)
+- elif key == "optional": self._isOptional = _to_bool(value)
+- elif key == "unit" : self._unit = value
+- elif key == "min" : self._min = value
+- elif key == "max" : self._max = value
+- elif key == "maxlen" : self._maxlen = value
+- elif key == "desc" : self._desc = value
+- elif key == "reference" : self._reference = value
+- elif key == "parent_ref" : self._isParentRef = _to_bool(value)
+- elif key == "parent_ref" : self._isParentRef = _to_bool(value)
+- elif key == "continuous" : self._is_continuous = _to_bool(value)
+- elif key == "dir":
+- value = str(value).upper()
+- if value not in self._dir_strings:
+- raise TypeError("invalid value for direction parameter: '%s'" % value)
+- self._dir = value
+- elif key == "default" : self._default = value
+-
+- # constructor
+- def _create(cls, type_code, **kwargs):
+- return cls(_type_code=type_code, kwargs=kwargs)
+- create = classmethod(_create)
+-
+- # map constructor
+- def _from_map(cls, map_):
+- return cls(_map=map_)
+- from_map = classmethod(_from_map)
+-
+- def get_type(self): return self._type
+-
+- def get_access(self): return self._access
+-
+- def is_optional(self): return self._isOptional
+-
+- def is_index(self): return self._isIndex
+-
+- def get_unit(self): return self._unit
+-
+- def get_min(self): return self._min
+-
+- def get_max(self): return self._max
+-
+- def get_max_len(self): return self._maxlen
+-
+- def get_desc(self): return self._desc
+-
+- def get_reference(self): return self._reference
+-
+- def is_parent_ref(self): return self._isParentRef
+-
+- def get_direction(self): return self._dir
+-
+- def get_default(self): return self._default
+-
+- def is_continuous(self): return self._is_continuous
+-
+- def map_encode(self):
+- """
+- Return the map encoding of this schema.
+- """
+- _map = {}
+- _map["amqp_type"] = self._type
+- _map["access"] = self._access
+- _map["index"] = self._isIndex
+- _map["optional"] = self._isOptional
+- if self._unit: _map["unit"] = self._unit
+- if self._min: _map["min"] = self._min
+- if self._max: _map["max"] = self._max
+- if self._maxlen: _map["maxlen"] = self._maxlen
+- if self._desc: _map["desc"] = self._desc
+- if self._reference: _map["reference"] = self._reference
+- _map["parent_ref"] = self._isParentRef
+- if self._dir: _map["dir"] = self._dir
+- if self._default: _map["default"] = self._default
+- if self._is_continuous: _map["continuous"] = self._is_continuous
+- return _map
+-
+- def __repr__(self):
+- return "SchemaProperty=<<" + str(self.map_encode()) + ">>"
+-
+- def _update_hash(self, hasher):
+- """
+- Update the given hash object with a hash computed over this schema.
+- """
+- hasher.update(str(self._type))
+- hasher.update(str(self._isIndex))
+- hasher.update(str(self._isOptional))
+- hasher.update(str(self._is_continuous))
+- if self._access: hasher.update(self._access)
+- if self._unit: hasher.update(self._unit)
+- if self._desc: hasher.update(self._desc)
+- if self._dir: hasher.update(self._dir)
+- if self._default: hasher.update(self._default)
+-
+-
+-class SchemaMethod(_mapEncoder):
+- """
+- The SchemaMethod class describes the method's structure, and contains a
+- SchemaProperty class for each argument declared by the method.
+-
+- Map format:
+- map["arguments"] = map of "name"=<SchemaProperty> pairs.
+- map["desc"] = str, description of the method
+- """
+- KEY_NAME="_name"
+- KEY_ARGUMENTS="_arguments"
+- KEY_DESC="_desc"
+- KEY_ERROR="_error"
+- def __init__(self, _args={}, _desc=None, _map=None):
+- """
+- Construct a SchemaMethod.
+-
+- @type args: map of "name"=<SchemaProperty> objects
+- @param args: describes the arguments accepted by the method
+- @type _desc: str
+- @param _desc: Human-readable description of the schema
+- """
+- if _map is not None:
+- _desc = _map.get(self.KEY_DESC)
+- margs = _map.get(self.KEY_ARGUMENTS)
+- if margs:
+- # margs are in map format - covert to SchemaProperty
+- tmp_args = {}
+- for name,val in margs.iteritems():
+- tmp_args[name] = SchemaProperty.from_map(val)
+- _args=tmp_args
+-
+- self._arguments = _args.copy()
+- self._desc = _desc
+-
+- # map constructor
+- def _from_map(cls, map_):
+- return cls(_map=map_)
+- from_map = classmethod(_from_map)
+-
+- def get_desc(self): return self._desc
+-
+- def get_arg_count(self): return len(self._arguments)
+-
+- def get_arguments(self): return self._arguments.copy()
+-
+- def get_argument(self, name): return self._arguments.get(name)
+-
+- def add_argument(self, name, schema):
+- """
+- Add an argument to the list of arguments passed to this method.
+- Used by an agent for dynamically creating method schema.
+-
+- @type name: string
+- @param name: name of new argument
+- @type schema: SchemaProperty
+- @param schema: SchemaProperty to add to this method
+- """
+- if not isinstance(schema, SchemaProperty):
+- raise TypeError("argument must be a SchemaProperty class")
+- # "Input" argument, by default
+- if schema._dir is None:
+- schema._dir = "I"
+- self._arguments[name] = schema
+-
+- def map_encode(self):
+- """
+- Return the map encoding of this schema.
+- """
+- _map = {}
+- _args = {}
+- for name,val in self._arguments.iteritems():
+- _args[name] = val.map_encode()
+- _map[self.KEY_ARGUMENTS] = _args
+- if self._desc: _map[self.KEY_DESC] = self._desc
+- return _map
+-
+- def __repr__(self):
+- result = "SchemaMethod=<<args=("
+- first = True
+- for name,arg in self._arguments.iteritems():
+- if first:
+- first = False
+- else:
+- result += ", "
+- result += name
+- result += ")>>"
+- return result
+-
+- def _update_hash(self, hasher):
+- """
+- Update the given hash object with a hash computed over this schema.
+- """
+- for name,val in self._arguments.iteritems():
+- hasher.update(name)
+- val._update_hash(hasher)
+- if self._desc: hasher.update(self._desc)
+-
+-
+-
+-class SchemaClass(QmfData):
+- """
+- Base class for Data and Event Schema classes.
+-
+- Map format:
+- map(QmfData), plus:
+- map["_schema_id"] = map representation of a SchemaClassId instance
+- map["_primary_key_names"] = order list of primary key names
+- """
+- KEY_PRIMARY_KEY_NAMES="_primary_key_names"
+- KEY_DESC = "_desc"
+-
+- SUBTYPE_PROPERTY="qmfProperty"
+- SUBTYPE_METHOD="qmfMethod"
+-
+- def __init__(self, _classId=None, _desc=None, _map=None):
+- """
+- Schema Class constructor.
+-
+- @type classId: class SchemaClassId
+- @param classId: Identifier for this SchemaClass
+- @type _desc: str
+- @param _desc: Human-readable description of the schema
+- """
+- if _map is not None:
+- super(SchemaClass, self).__init__(_map=_map)
+-
+- # decode each value based on its type
+- for name,value in self._values.iteritems():
+- if self._subtypes.get(name) == self.SUBTYPE_METHOD:
+- self._values[name] = SchemaMethod.from_map(value)
+- else:
+- self._values[name] = SchemaProperty.from_map(value)
+- cid = _map.get(self.KEY_SCHEMA_ID)
+- if cid:
+- _classId = SchemaClassId.from_map(cid)
+- self._object_id_names = _map.get(self.KEY_PRIMARY_KEY_NAMES,[])
+- _desc = _map.get(self.KEY_DESC)
+- else:
+- if _classId is None:
+- raise Exception("A class identifier must be supplied.")
+- super(SchemaClass, self).__init__(_object_id=str(_classId))
+- self._object_id_names = []
+-
+- self._classId = _classId
+- self._desc = _desc
+-
+- def get_class_id(self):
+- if not self._classId.get_hash_string():
+- self.generate_hash()
+- return self._classId
+-
+- def get_desc(self): return self._desc
+-
+- def generate_hash(self):
+- """
+- generate an md5 hash over the body of the schema,
+- and return a string representation of the hash
+- in format "%08x-%08x-%08x-%08x"
+- """
+- md5Hash = _md5Obj()
+- md5Hash.update(self._classId.get_package_name())
+- md5Hash.update(self._classId.get_class_name())
+- md5Hash.update(self._classId.get_type())
+- for name,x in self._values.iteritems():
+- md5Hash.update(name)
+- x._update_hash( md5Hash )
+- for name,value in self._subtypes.iteritems():
+- md5Hash.update(name)
+- md5Hash.update(value)
+- idx = 0
+- for name in self._object_id_names:
+- md5Hash.update(str(idx) + name)
+- idx += 1
+- hstr = md5Hash.hexdigest()[0:8] + "-" +\
+- md5Hash.hexdigest()[8:16] + "-" +\
+- md5Hash.hexdigest()[16:24] + "-" +\
+- md5Hash.hexdigest()[24:32]
+- # update classId with new hash value
+- self._classId._hstr = hstr
+- return hstr
+-
+-
+- def get_property_count(self):
+- count = 0
+- for value in self._subtypes.itervalues():
+- if value == self.SUBTYPE_PROPERTY:
+- count += 1
+- return count
+-
+- def get_properties(self):
+- props = {}
+- for name,value in self._subtypes.iteritems():
+- if value == self.SUBTYPE_PROPERTY:
+- props[name] = self._values.get(name)
+- return props
+-
+- def get_property(self, name):
+- if self._subtypes.get(name) == self.SUBTYPE_PROPERTY:
+- return self._values.get(name)
+- return None
+-
+- def add_property(self, name, prop):
+- self.set_value(name, prop, self.SUBTYPE_PROPERTY)
+- # need to re-generate schema hash
+- self._classId._hstr = None
+-
+- def get_value(self, name):
+- # check for meta-properties first
+- if name == SchemaClassId.KEY_PACKAGE:
+- return self._classId.get_package_name()
+- if name == SchemaClassId.KEY_CLASS:
+- return self._classId.get_class_name()
+- if name == SchemaClassId.KEY_TYPE:
+- return self._classId.get_type()
+- if name == SchemaClassId.KEY_HASH:
+- return self.get_class_id().get_hash_string()
+- if name == self.KEY_SCHEMA_ID:
+- return self.get_class_id()
+- if name == self.KEY_PRIMARY_KEY_NAMES:
+- return self._object_id_names[:]
+- return super(SchemaClass, self).get_value(name)
+-
+- def has_value(self, name):
+- if name in [SchemaClassId.KEY_PACKAGE, SchemaClassId.KEY_CLASS, SchemaClassId.KEY_TYPE,
+- SchemaClassId.KEY_HASH, self.KEY_SCHEMA_ID, self.KEY_PRIMARY_KEY_NAMES]:
+- return True
+- super(SchemaClass, self).has_value(name)
+-
+- def map_encode(self):
+- """
+- Return the map encoding of this schema.
+- """
+- _map = super(SchemaClass,self).map_encode()
+- _map[self.KEY_SCHEMA_ID] = self.get_class_id().map_encode()
+- if self._object_id_names:
+- _map[self.KEY_PRIMARY_KEY_NAMES] = self._object_id_names[:]
+- if self._desc:
+- _map[self.KEY_DESC] = self._desc
+- return _map
+-
+- def __repr__(self):
+- return str(self.get_class_id())
+-
+-
+-
+-class SchemaObjectClass(SchemaClass):
+- """
+- A schema class that describes a data object. The data object is composed
+- of zero or more properties and methods. An instance of the SchemaObjectClass
+- can be identified using a key generated by concantenating the values of
+- all properties named in the primary key list.
+-
+- Map format:
+- map(SchemaClass)
+- """
+- def __init__(self, _classId=None, _desc=None,
+- _props={}, _methods={}, _object_id_names=[],
+- _map=None):
+- """
+- @type pname: str
+- @param pname: name of package this schema belongs to
+- @type cname: str
+- @param cname: class name for this schema
+- @type desc: str
+- @param desc: Human-readable description of the schema
+- @type _hash: str
+- @param _methods: hash computed on the body of this schema, if known
+- @type _props: map of 'name':<SchemaProperty> objects
+- @param _props: all properties provided by this schema
+- @type _pkey: list of strings
+- @param _pkey: names of each property to be used for constructing the primary key
+- @type _methods: map of 'name':<SchemaMethod> objects
+- @param _methods: all methods provided by this schema
+- """
+- if _map is not None:
+- super(SchemaObjectClass,self).__init__(_map=_map)
+- else:
+- super(SchemaObjectClass, self).__init__(_classId=_classId, _desc=_desc)
+- self._object_id_names = _object_id_names
+- for name,value in _props.iteritems():
+- self.set_value(name, value, self.SUBTYPE_PROPERTY)
+- for name,value in _methods.iteritems():
+- self.set_value(name, value, self.SUBTYPE_METHOD)
+-
+- if self._classId.get_type() != SchemaClassId.TYPE_DATA:
+- raise TypeError("Invalid ClassId type for data schema: %s" % self._classId)
+-
+- # map constructor
+- def __from_map(cls, map_):
+- return cls(_map=map_)
+- from_map = classmethod(__from_map)
+-
+- def get_id_names(self):
+- return self._object_id_names[:]
+-
+- def get_method_count(self):
+- count = 0
+- for value in self._subtypes.itervalues():
+- if value == self.SUBTYPE_METHOD:
+- count += 1
+- return count
+-
+- def get_methods(self):
+- meths = {}
+- for name,value in self._subtypes.iteritems():
+- if value == self.SUBTYPE_METHOD:
+- meths[name] = self._values.get(name)
+- return meths
+-
+- def get_method(self, name):
+- if self._subtypes.get(name) == self.SUBTYPE_METHOD:
+- return self._values.get(name)
+- return None
+-
+- def add_method(self, name, method):
+- self.set_value(name, method, self.SUBTYPE_METHOD)
+- # need to re-generate schema hash
+- self._classId._hstr = None
+-
+-
+-
+-
+-class SchemaEventClass(SchemaClass):
+- """
+- A schema class that describes an event. The event is composed
+- of zero or more properties.
+-
+- Map format:
+- map["schema_id"] = map, SchemaClassId map for this object.
+- map["desc"] = string description of this schema
+- map["properties"] = map of "name":SchemaProperty values.
+- """
+- def __init__(self, _classId=None, _desc=None, _props={},
+- _map=None):
+- if _map is not None:
+- super(SchemaEventClass,self).__init__(_map=_map)
+- else:
+- super(SchemaEventClass, self).__init__(_classId=_classId,
+- _desc=_desc)
+- for name,value in _props.iteritems():
+- self.set_value(name, value, self.SUBTYPE_PROPERTY)
+-
+- if self._classId.get_type() != SchemaClassId.TYPE_EVENT:
+- raise TypeError("Invalid ClassId type for event schema: %s" %
+- self._classId)
+-
+- # map constructor
+- def __from_map(cls, map_):
+- return cls(_map=map_)
+- from_map = classmethod(__from_map)
+-
+Index: extras/qmf/src/py/qmf2/tests/multi_response.py
+===================================================================
+--- extras/qmf/src/py/qmf2/tests/multi_response.py (revision 1056407)
++++ extras/qmf/src/py/qmf2/tests/multi_response.py (working copy)
+@@ -1,280 +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.
+-#
+-import unittest
+-import logging
+-from threading import Thread, Event
+-
+-import qpid.messaging
+-from qmf2.common import (Notifier, SchemaObjectClass, SchemaClassId,
+- SchemaProperty, qmfTypes, SchemaMethod, QmfQuery,
+- QmfData)
+-import qmf2.console
+-from qmf2.agent import(QmfAgentData, Agent)
+-
+-# note: objects, schema per agent must each be > max objs
+-_SCHEMAS_PER_AGENT=7
+-_OBJS_PER_AGENT=19
+-_MAX_OBJS_PER_MSG=3
+-
+-
+-class _testNotifier(Notifier):
+- def __init__(self):
+- self._event = Event()
+-
+- def indication(self):
+- # note: called by qmf daemon thread
+- self._event.set()
+-
+- def wait_for_work(self, timeout):
+- # note: called by application thread to wait
+- # for qmf to generate work
+- self._event.wait(timeout)
+- timed_out = self._event.isSet() == False
+- if not timed_out:
+- self._event.clear()
+- return True
+- return False
+-
+-
+-class _agentApp(Thread):
+- def __init__(self, name, broker_url, heartbeat):
+- Thread.__init__(self)
+- self.schema_count = _SCHEMAS_PER_AGENT
+- self.obj_count = _OBJS_PER_AGENT
+- self.notifier = _testNotifier()
+- self.broker_url = broker_url
+- self.agent = Agent(name,
+- _notifier=self.notifier,
+- heartbeat_interval=heartbeat,
+- max_msg_size=_MAX_OBJS_PER_MSG)
+-
+- # Dynamically construct a management database
+- for i in range(self.schema_count):
+- _schema = SchemaObjectClass( _classId=SchemaClassId("MyPackage",
+- "MyClass-" + str(i)),
+- _desc="A test data schema",
+- _object_id_names=["index1", "index2"] )
+- # add properties
+- _schema.add_property( "index1", SchemaProperty(qmfTypes.TYPE_UINT8))
+- _schema.add_property( "index2", SchemaProperty(qmfTypes.TYPE_LSTR))
+-
+- # these two properties are statistics
+- _schema.add_property( "query_count", SchemaProperty(qmfTypes.TYPE_UINT32))
+- _schema.add_property( "method_call_count", SchemaProperty(qmfTypes.TYPE_UINT32))
+-
+- # These two properties can be set via the method call
+- _schema.add_property( "set_string", SchemaProperty(qmfTypes.TYPE_LSTR))
+- _schema.add_property( "set_int", SchemaProperty(qmfTypes.TYPE_UINT32))
+-
+- # add method
+- _meth = SchemaMethod( _desc="Method to set string and int in object." )
+- _meth.add_argument( "arg_int", SchemaProperty(qmfTypes.TYPE_UINT32) )
+- _meth.add_argument( "arg_str", SchemaProperty(qmfTypes.TYPE_LSTR) )
+- _schema.add_method( "set_meth", _meth )
+-
+- # Add schema to Agent
+-
+- self.agent.register_object_class(_schema)
+-
+- # instantiate managed data objects matching the schema
+-
+- for j in range(self.obj_count):
+-
+- self.agent.add_object( QmfAgentData( self.agent, _schema=_schema,
+- _values={"index1":j,
+- "index2": "name-" + str(j),
+- "set_string": "UNSET",
+- "set_int": 0,
+- "query_count": 0,
+- "method_call_count": 0} ))
+-
+- self.running = False
+- self.ready = Event()
+-
+- def start_app(self):
+- self.running = True
+- self.start()
+- self.ready.wait(10)
+- if not self.ready.is_set():
+- raise Exception("Agent failed to connect to broker.")
+-
+- def stop_app(self):
+- self.running = False
+- # wake main thread
+- self.notifier.indication() # hmmm... collide with daemon???
+- self.join(10)
+- if self.isAlive():
+- raise Exception("AGENT DID NOT TERMINATE AS EXPECTED!!!")
+-
+- def run(self):
+- # broker_url = "user/passwd at hostname:port"
+- self.conn = qpid.messaging.Connection(self.broker_url)
+- self.conn.open()
+- self.agent.set_connection(self.conn)
+- self.ready.set()
+-
+- while self.running:
+- self.notifier.wait_for_work(None)
+- wi = self.agent.get_next_workitem(timeout=0)
+- while wi is not None:
+- logging.error("UNEXPECTED AGENT WORKITEM RECEIVED=%s" % wi.get_type())
+- self.agent.release_workitem(wi)
+- wi = self.agent.get_next_workitem(timeout=0)
+-
+- if self.conn:
+- self.agent.remove_connection(10)
+- self.agent.destroy(10)
+-
+-
+-
+-
+-class BaseTest(unittest.TestCase):
+- def configure(self, config):
+- self.agent_count = 2
+- self.config = config
+- self.broker = config.broker
+- self.defines = self.config.defines
+-
+- def setUp(self):
+- # one second agent indication interval
+- self.agent_heartbeat = 1
+- self.agents = []
+- for a in range(self.agent_count):
+- agent = _agentApp("agent-" + str(a),
+- self.broker,
+- self.agent_heartbeat)
+- agent.start_app()
+- self.agents.append(agent)
+-
+- def tearDown(self):
+- for agent in self.agents:
+- if agent is not None:
+- agent.stop_app()
+-
+- def test_all_schema_id(self):
+- # create console
+- # find agents
+- # synchronous query for all schemas_ids
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- for agent_app in self.agents:
+- agent = self.console.find_agent(agent_app.agent.get_name(), timeout=3)
+- self.assertTrue(agent and agent.get_name() == agent_app.agent.get_name())
+-
+- # get a list of all schema_ids
+- query = QmfQuery.create_wildcard(QmfQuery.TARGET_SCHEMA_ID)
+- sid_list = self.console.do_query(agent, query)
+- self.assertTrue(sid_list and len(sid_list) == _SCHEMAS_PER_AGENT)
+- for sid in sid_list:
+- self.assertTrue(isinstance(sid, SchemaClassId))
+- self.assertTrue(sid.get_package_name() == "MyPackage")
+- self.assertTrue(sid.get_class_name().split('-')[0] == "MyClass")
+-
+- self.console.destroy(10)
+-
+-
+- def test_all_schema(self):
+- # create console
+- # find agents
+- # synchronous query for all schemas
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- for agent_app in self.agents:
+- agent = self.console.find_agent(agent_app.agent.get_name(), timeout=3)
+- self.assertTrue(agent and agent.get_name() == agent_app.agent.get_name())
+-
+- # get a list of all schema_ids
+- query = QmfQuery.create_wildcard(QmfQuery.TARGET_SCHEMA)
+- schema_list = self.console.do_query(agent, query)
+- self.assertTrue(schema_list and
+- len(schema_list) == _SCHEMAS_PER_AGENT)
+- for schema in schema_list:
+- self.assertTrue(isinstance(schema, SchemaObjectClass))
+-
+- self.console.destroy(10)
+-
+-
+- def test_all_object_id(self):
+- # create console
+- # find agents
+- # synchronous query for all object_ids by schema_id
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- for agent_app in self.agents:
+- agent = self.console.find_agent(agent_app.agent.get_name(), timeout=3)
+- self.assertTrue(agent and agent.get_name() == agent_app.agent.get_name())
+-
+- # get a list of all schema_ids
+- query = QmfQuery.create_wildcard(QmfQuery.TARGET_SCHEMA_ID)
+- sid_list = self.console.do_query(agent, query)
+- self.assertTrue(sid_list and len(sid_list) == _SCHEMAS_PER_AGENT)
+- for sid in sid_list:
+- query = QmfQuery.create_wildcard_object_id(sid)
+- oid_list = self.console.do_query(agent, query)
+- self.assertTrue(oid_list and
+- len(oid_list) == _OBJS_PER_AGENT)
+- for oid in oid_list:
+- self.assertTrue(isinstance(oid, basestring))
+-
+- self.console.destroy(10)
+-
+-
+- def test_all_objects(self):
+- # create console
+- # find agents
+- # synchronous query for all objects by schema_id
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- for agent_app in self.agents:
+- agent = self.console.find_agent(agent_app.agent.get_name(), timeout=3)
+- self.assertTrue(agent and agent.get_name() == agent_app.agent.get_name())
+-
+- # get a list of all schema_ids
+- query = QmfQuery.create_wildcard(QmfQuery.TARGET_SCHEMA_ID)
+- sid_list = self.console.do_query(agent, query)
+- self.assertTrue(sid_list and len(sid_list) == _SCHEMAS_PER_AGENT)
+- for sid in sid_list:
+- query = QmfQuery.create_wildcard_object(sid)
+- obj_list = self.console.do_query(agent, query)
+- self.assertTrue(obj_list and
+- len(obj_list) == _OBJS_PER_AGENT)
+- for obj in obj_list:
+- self.assertTrue(isinstance(obj,
+- qmf2.console.QmfConsoleData))
+-
+- self.console.destroy(10)
+Index: extras/qmf/src/py/qmf2/tests/obj_gets.py
+===================================================================
+--- extras/qmf/src/py/qmf2/tests/obj_gets.py (revision 1056407)
++++ extras/qmf/src/py/qmf2/tests/obj_gets.py (working copy)
+@@ -1,581 +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.
+-#
+-import unittest
+-import logging
+-import datetime
+-from threading import Thread, Event
+-
+-import qpid.messaging
+-from qmf2.common import (Notifier, SchemaObjectClass, SchemaClassId,
+- SchemaProperty, qmfTypes, SchemaMethod, QmfQuery,
+- QmfData)
+-import qmf2.console
+-from qmf2.agent import(QmfAgentData, Agent)
+-
+-
+-class _testNotifier(Notifier):
+- def __init__(self):
+- self._event = Event()
+-
+- def indication(self):
+- # note: called by qmf daemon thread
+- self._event.set()
+-
+- def wait_for_work(self, timeout):
+- # note: called by application thread to wait
+- # for qmf to generate work
+- self._event.wait(timeout)
+- timed_out = self._event.isSet() == False
+- if not timed_out:
+- self._event.clear()
+- return True
+- return False
+-
+-
+-class _agentApp(Thread):
+- def __init__(self, name, broker_url, heartbeat):
+- Thread.__init__(self)
+- self.notifier = _testNotifier()
+- self.broker_url = broker_url
+- self.agent = Agent(name,
+- _notifier=self.notifier,
+- heartbeat_interval=heartbeat)
+-
+- # Management Database
+- # - two different schema packages,
+- # - two classes within one schema package
+- # - multiple objects per schema package+class
+- # - two "undescribed" objects
+-
+- # "package1/class1"
+-
+- _schema = SchemaObjectClass( _classId=SchemaClassId("package1", "class1"),
+- _desc="A test data schema - one",
+- _object_id_names=["key"] )
+-
+- _schema.add_property( "key", SchemaProperty(qmfTypes.TYPE_LSTR))
+- _schema.add_property( "count1", SchemaProperty(qmfTypes.TYPE_UINT32))
+- _schema.add_property( "count2", SchemaProperty(qmfTypes.TYPE_UINT32))
+-
+- self.agent.register_object_class(_schema)
+-
+- _obj = QmfAgentData( self.agent,
+- _values={"key":"p1c1_key1"},
+- _schema=_schema)
+- _obj.set_value("count1", 0)
+- _obj.set_value("count2", 0)
+- self.agent.add_object( _obj )
+-
+- _obj = QmfAgentData( self.agent,
+- _values={"key":"p1c1_key2"},
+- _schema=_schema )
+- _obj.set_value("count1", 9)
+- _obj.set_value("count2", 10)
+- self.agent.add_object( _obj )
+-
+- # "package1/class2"
+-
+- _schema = SchemaObjectClass( _classId=SchemaClassId("package1", "class2"),
+- _desc="A test data schema - two",
+- _object_id_names=["name"] )
+- # add properties
+- _schema.add_property( "name", SchemaProperty(qmfTypes.TYPE_LSTR))
+- _schema.add_property( "string1", SchemaProperty(qmfTypes.TYPE_LSTR))
+-
+- self.agent.register_object_class(_schema)
+-
+- _obj = QmfAgentData( self.agent,
+- _values={"name":"p1c2_name1"},
+- _schema=_schema )
+- _obj.set_value("string1", "a data string")
+- self.agent.add_object( _obj )
+-
+-
+- # "package2/class1"
+-
+- _schema = SchemaObjectClass( _classId=SchemaClassId("package2", "class1"),
+- _desc="A test data schema - second package",
+- _object_id_names=["key"] )
+-
+- _schema.add_property( "key", SchemaProperty(qmfTypes.TYPE_LSTR))
+- _schema.add_property( "counter", SchemaProperty(qmfTypes.TYPE_UINT32))
+-
+- self.agent.register_object_class(_schema)
+-
+- _obj = QmfAgentData( self.agent,
+- _values={"key":"p2c1_key1"},
+- _schema=_schema )
+- _obj.set_value("counter", 0)
+- self.agent.add_object( _obj )
+-
+- _obj = QmfAgentData( self.agent,
+- _values={"key":"p2c1_key2"},
+- _schema=_schema )
+- _obj.set_value("counter", 2112)
+- self.agent.add_object( _obj )
+-
+-
+- # add two "unstructured" objects to the Agent
+-
+- _obj = QmfAgentData(self.agent, _object_id="undesc-1")
+- _obj.set_value("field1", "a value")
+- _obj.set_value("field2", 2)
+- _obj.set_value("field3", {"a":1, "map":2, "value":3})
+- _obj.set_value("field4", ["a", "list", "value"])
+- self.agent.add_object(_obj)
+-
+-
+- _obj = QmfAgentData(self.agent, _object_id="undesc-2")
+- _obj.set_value("key-1", "a value")
+- _obj.set_value("key-2", 2)
+- self.agent.add_object(_obj)
+-
+- self.running = False
+- self.ready = Event()
+-
+- def start_app(self):
+- self.running = True
+- self.start()
+- self.ready.wait(10)
+- if not self.ready.is_set():
+- raise Exception("Agent failed to connect to broker.")
+-
+- def stop_app(self):
+- self.running = False
+- self.notifier.indication() # hmmm... collide with daemon???
+- self.join(10)
+- if self.isAlive():
+- raise Exception("AGENT DID NOT TERMINATE AS EXPECTED!!!")
+-
+- def run(self):
+- # broker_url = "user/passwd at hostname:port"
+- self.conn = qpid.messaging.Connection(self.broker_url)
+- self.conn.open()
+- self.agent.set_connection(self.conn)
+- self.ready.set()
+-
+- while self.running:
+- self.notifier.wait_for_work(None)
+- wi = self.agent.get_next_workitem(timeout=0)
+- while wi is not None:
+- logging.error("UNEXPECTED AGENT WORKITEM RECEIVED=%s" % wi.get_type())
+- self.agent.release_workitem(wi)
+- wi = self.agent.get_next_workitem(timeout=0)
+-
+- if self.conn:
+- self.agent.remove_connection(10)
+- self.agent.destroy(10)
+-
+-
+-class BaseTest(unittest.TestCase):
+- agent_count = 5
+-
+- def configure(self, config):
+- self.config = config
+- self.broker = config.broker
+- self.defines = self.config.defines
+-
+- def setUp(self):
+- self.agents = []
+- for i in range(self.agent_count):
+- agent = _agentApp("agent-" + str(i), self.broker, 1)
+- agent.start_app()
+- self.agents.append(agent)
+- #print("!!!! STARTING TEST: %s" % datetime.datetime.utcnow())
+-
+- def tearDown(self):
+- #print("!!!! STOPPING TEST: %s" % datetime.datetime.utcnow())
+- for agent in self.agents:
+- if agent is not None:
+- agent.stop_app()
+-
+-
+- def test_all_agents(self):
+- # create console
+- # find all agents
+- # synchronous query for all objects by id
+- # verify known object ids are returned
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- for agent_app in self.agents:
+- aname = agent_app.agent.get_name()
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- # console has discovered all agents, now query all undesc-2 objects
+- #print("!!!! STARTING GET: %s" % datetime.datetime.utcnow())
+- objs = self.console.get_objects(_object_id="undesc-2", _timeout=5)
+- #print("!!!! STOPPING GET: %s" % datetime.datetime.utcnow())
+- self.assertTrue(len(objs) == self.agent_count)
+- for obj in objs:
+- self.assertTrue(obj.get_object_id() == "undesc-2")
+-
+- # now query all objects from schema "package1"
+- #print("!!!! STARTING GET: %s" % datetime.datetime.utcnow())
+- objs = self.console.get_objects(_pname="package1", _timeout=5)
+- #print("!!!! STOPPING GET: %s" % datetime.datetime.utcnow())
+- self.assertTrue(len(objs) == (self.agent_count * 3))
+- for obj in objs:
+- self.assertTrue(obj.get_schema_class_id().get_package_name() == "package1")
+-
+- # now query all objects from schema "package2"
+- #print("!!!! STARTING GET: %s" % datetime.datetime.utcnow())
+- objs = self.console.get_objects(_pname="package2", _timeout=5)
+- #print("!!!! STOPPING GET: %s" % datetime.datetime.utcnow())
+- self.assertTrue(len(objs) == (self.agent_count * 2))
+- for obj in objs:
+- self.assertTrue(obj.get_schema_class_id().get_package_name() == "package2")
+-
+- # now query all objects from schema "package1/class2"
+- #print("!!!! STARTING GET: %s" % datetime.datetime.utcnow())
+- objs = self.console.get_objects(_pname="package1", _cname="class2",
+- _timeout=5)
+- #print("!!!! STOPPING GET: %s" % datetime.datetime.utcnow())
+- self.assertTrue(len(objs) == self.agent_count)
+- for obj in objs:
+- self.assertTrue(obj.get_schema_class_id().get_package_name() == "package1")
+- self.assertTrue(obj.get_schema_class_id().get_class_name() == "class2")
+-
+- # given the schema identifier from the last query, repeat using the
+- # specific schema id
+- #print("!!!! STARTING GET: %s" % datetime.datetime.utcnow())
+- schema_id = objs[0].get_schema_class_id()
+- #print("!!!! STOPPING GET: %s" % datetime.datetime.utcnow())
+- objs = self.console.get_objects(_schema_id=schema_id, _timeout=5)
+- self.assertTrue(len(objs) == self.agent_count)
+- for obj in objs:
+- self.assertTrue(obj.get_schema_class_id() == schema_id)
+-
+-
+- self.console.destroy(10)
+-
+-
+-
+- def test_agent_subset(self):
+- # create console
+- # find all agents
+- # synchronous query for all objects by id
+- # verify known object ids are returned
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- agent_list = []
+- for agent_app in self.agents:
+- aname = agent_app.agent.get_name()
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+- agent_list.append(agent)
+-
+- # Only use a subset of the agents:
+- agent_list = agent_list[:len(agent_list)/2]
+-
+- # console has discovered all agents, now query all undesc-2 objects
+- objs = self.console.get_objects(_object_id="undesc-2",
+- _agents=agent_list, _timeout=5)
+- self.assertTrue(len(objs) == len(agent_list))
+- for obj in objs:
+- self.assertTrue(obj.get_object_id() == "undesc-2")
+-
+- # now query all objects from schema "package1"
+- objs = self.console.get_objects(_pname="package1",
+- _agents=agent_list,
+- _timeout=5)
+- self.assertTrue(len(objs) == (len(agent_list) * 3))
+- for obj in objs:
+- self.assertTrue(obj.get_schema_class_id().get_package_name() == "package1")
+-
+- # now query all objects from schema "package2"
+- objs = self.console.get_objects(_pname="package2",
+- _agents=agent_list,
+- _timeout=5)
+- self.assertTrue(len(objs) == (len(agent_list) * 2))
+- for obj in objs:
+- self.assertTrue(obj.get_schema_class_id().get_package_name() == "package2")
+-
+- # now query all objects from schema "package1/class2"
+- objs = self.console.get_objects(_pname="package1", _cname="class2",
+- _agents=agent_list,
+- _timeout=5)
+- self.assertTrue(len(objs) == len(agent_list))
+- for obj in objs:
+- self.assertTrue(obj.get_schema_class_id().get_package_name() == "package1")
+- self.assertTrue(obj.get_schema_class_id().get_class_name() == "class2")
+-
+- # given the schema identifier from the last query, repeat using the
+- # specific schema id
+- schema_id = objs[0].get_schema_class_id()
+- objs = self.console.get_objects(_schema_id=schema_id,
+- _agents=agent_list,
+- _timeout=5)
+- self.assertTrue(len(objs) == len(agent_list))
+- for obj in objs:
+- self.assertTrue(obj.get_schema_class_id() == schema_id)
+-
+-
+- self.console.destroy(10)
+-
+-
+-
+- def test_single_agent(self):
+- # create console
+- # find all agents
+- # synchronous query for all objects by id
+- # verify known object ids are returned
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- agent_list = []
+- for agent_app in self.agents:
+- aname = agent_app.agent.get_name()
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+- agent_list.append(agent)
+-
+- # Only use one agent
+- agent = agent_list[0]
+-
+- # console has discovered all agents, now query all undesc-2 objects
+- objs = self.console.get_objects(_object_id="undesc-2",
+- _agents=agent, _timeout=5)
+- self.assertTrue(len(objs) == 1)
+- for obj in objs:
+- self.assertTrue(obj.get_object_id() == "undesc-2")
+-
+- # now query all objects from schema "package1"
+- objs = self.console.get_objects(_pname="package1",
+- _agents=agent,
+- _timeout=5)
+- self.assertTrue(len(objs) == 3)
+- for obj in objs:
+- self.assertTrue(obj.get_schema_class_id().get_package_name() == "package1")
+-
+- # now query all objects from schema "package2"
+- objs = self.console.get_objects(_pname="package2",
+- _agents=agent,
+- _timeout=5)
+- self.assertTrue(len(objs) == 2)
+- for obj in objs:
+- self.assertTrue(obj.get_schema_class_id().get_package_name() == "package2")
+-
+- # now query all objects from schema "package1/class2"
+- objs = self.console.get_objects(_pname="package1", _cname="class2",
+- _agents=agent,
+- _timeout=5)
+- self.assertTrue(len(objs) == 1)
+- for obj in objs:
+- self.assertTrue(obj.get_schema_class_id().get_package_name() == "package1")
+- self.assertTrue(obj.get_schema_class_id().get_class_name() == "class2")
+-
+- # given the schema identifier from the last query, repeat using the
+- # specific schema id
+- schema_id = objs[0].get_schema_class_id()
+- objs = self.console.get_objects(_schema_id=schema_id,
+- _agents=agent,
+- _timeout=5)
+- self.assertTrue(len(objs) == 1)
+- for obj in objs:
+- self.assertTrue(obj.get_schema_class_id() == schema_id)
+-
+-
+- self.console.destroy(10)
+-
+-
+-
+- def test_all_objs_by_oid(self):
+- # create console
+- # find all agents
+- # synchronous query for all described objects by:
+- # oid & schema_id
+- # oid & package name
+- # oid & package and class name
+- # verify known object ids are returned
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- for agent_app in self.agents:
+- aname = agent_app.agent.get_name()
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- # now query all objects from schema "package1"
+- #print("!!!! STARTING GET: %s" % datetime.datetime.utcnow())
+- objs = self.console.get_objects(_pname="package1",
+- _object_id="p1c1_key1", _timeout=5)
+- #print("!!!! STOPPING GET: %s" % datetime.datetime.utcnow())
+- self.assertTrue(len(objs) == self.agent_count)
+- for obj in objs:
+- self.assertTrue(obj.get_schema_class_id().get_package_name() == "package1")
+- self.assertTrue(obj.get_schema_class_id().get_class_name() == "class1")
+- self.assertTrue(obj.get_object_id() == "p1c1_key1")
+- # mooch the schema for a later test
+- schema_id_p1c1 = objs[0].get_schema_class_id()
+-
+- #print("!!!! STARTING GET: %s" % datetime.datetime.utcnow())
+- objs = self.console.get_objects(_pname="package1",
+- _object_id="p1c2_name1", _timeout=5)
+- #print("!!!! STOPPING GET: %s" % datetime.datetime.utcnow())
+- self.assertTrue(len(objs) == self.agent_count)
+- for obj in objs:
+- self.assertTrue(obj.get_schema_class_id().get_package_name() == "package1")
+- self.assertTrue(obj.get_schema_class_id().get_class_name() == "class2")
+- self.assertTrue(obj.get_object_id() == "p1c2_name1")
+-
+- #print("!!!! STARTING GET: %s" % datetime.datetime.utcnow())
+- objs = self.console.get_objects(_pname="package2", _cname="class1",
+- _object_id="p2c1_key1", _timeout=5)
+- #print("!!!! STOPPING GET: %s" % datetime.datetime.utcnow())
+- self.assertTrue(len(objs) == self.agent_count)
+- for obj in objs:
+- self.assertTrue(obj.get_schema_class_id().get_package_name() == "package2")
+- self.assertTrue(obj.get_schema_class_id().get_class_name() == "class1")
+- self.assertTrue(obj.get_object_id() == "p2c1_key1")
+-
+- #print("!!!! STARTING GET: %s" % datetime.datetime.utcnow())
+- objs = self.console.get_objects(_schema_id=schema_id_p1c1,
+- _object_id="p1c1_key2", _timeout=5)
+- #print("!!!! STOPPING GET: %s" % datetime.datetime.utcnow())
+- self.assertTrue(len(objs) == self.agent_count)
+- for obj in objs:
+- self.assertTrue(obj.get_schema_class_id().get_package_name() == "package1")
+- self.assertTrue(obj.get_schema_class_id().get_class_name() == "class1")
+- self.assertTrue(obj.get_object_id() == "p1c1_key2")
+-
+- # this should return all "undescribed" objects
+- #print("!!!! STARTING GET: %s" % datetime.datetime.utcnow())
+- objs = self.console.get_objects(_timeout=5)
+- #print("!!!! STOPPING GET: %s" % datetime.datetime.utcnow())
+- self.assertTrue(len(objs) == (self.agent_count * 2))
+- for obj in objs:
+- self.assertTrue(obj.get_object_id() == "undesc-1" or
+- obj.get_object_id() == "undesc-2")
+-
+- # these should fail
+- #print("!!!! STARTING GET: %s" % datetime.datetime.utcnow())
+- objs = self.console.get_objects(_schema_id=schema_id_p1c1,
+- _object_id="does not exist",
+- _timeout=5)
+- #print("!!!! STOPPING GET: %s" % datetime.datetime.utcnow())
+- self.assertTrue(objs == None)
+-
+- #print("!!!! STARTING GET: %s" % datetime.datetime.utcnow())
+- objs = self.console.get_objects(_pname="package2",
+- _object_id="does not exist",
+- _timeout=5)
+- #print("!!!! STOPPING GET: %s" % datetime.datetime.utcnow())
+- self.assertTrue(objs == None)
+-
+- #print("!!!! STARTING GET: %s" % datetime.datetime.utcnow())
+- objs = self.console.get_objects(_pname="package3",
+- _object_id="does not exist",
+- _timeout=5)
+- #print("!!!! STOPPING GET: %s" % datetime.datetime.utcnow())
+- self.assertTrue(objs == None)
+-
+- self.console.destroy(10)
+-
+-
+- def test_wildcard_schema_id(self):
+- # create console
+- # find all agents
+- # synchronous query for all described objects by:
+- # oid & wildcard schema_id
+- # wildcard schema_id
+- # verify known object ids are returned
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- for agent_app in self.agents:
+- aname = agent_app.agent.get_name()
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- wild_schema_id = SchemaClassId("package1", "class1")
+- objs = self.console.get_objects(_schema_id=wild_schema_id, _timeout=5)
+- self.assertTrue(len(objs) == (self.agent_count * 2))
+- for obj in objs:
+- self.assertTrue(obj.get_schema_class_id().get_package_name() == "package1")
+- self.assertTrue(obj.get_schema_class_id().get_class_name() == "class1")
+-
+- wild_schema_id = SchemaClassId("package1", "class2")
+- objs = self.console.get_objects(_schema_id=wild_schema_id, _timeout=5)
+- self.assertTrue(len(objs) == self.agent_count)
+- for obj in objs:
+- self.assertTrue(obj.get_schema_class_id().get_package_name() == "package1")
+- self.assertTrue(obj.get_schema_class_id().get_class_name() == "class2")
+- self.assertTrue(obj.get_object_id() == "p1c2_name1")
+-
+- wild_schema_id = SchemaClassId("package2", "class1")
+- objs = self.console.get_objects(_schema_id=wild_schema_id, _timeout=5)
+- self.assertTrue(len(objs) == (self.agent_count * 2))
+- for obj in objs:
+- self.assertTrue(obj.get_schema_class_id().get_package_name() == "package2")
+- self.assertTrue(obj.get_schema_class_id().get_class_name() == "class1")
+-
+- wild_schema_id = SchemaClassId("package1", "class1")
+- objs = self.console.get_objects(_schema_id=wild_schema_id,
+- _object_id="p1c1_key2", _timeout=5)
+- self.assertTrue(len(objs) == self.agent_count)
+- for obj in objs:
+- self.assertTrue(obj.get_schema_class_id().get_package_name() == "package1")
+- self.assertTrue(obj.get_schema_class_id().get_class_name() == "class1")
+- self.assertTrue(obj.get_object_id() == "p1c1_key2")
+-
+- # should fail
+- objs = self.console.get_objects(_schema_id=wild_schema_id,
+- _object_id="does not exist",
+- _timeout=5)
+- self.assertTrue(objs == None)
+-
+- wild_schema_id = SchemaClassId("package2", "class1")
+- objs = self.console.get_objects(_schema_id=wild_schema_id,
+- _object_id="p2c1_key2", _timeout=5)
+- self.assertTrue(len(objs) == self.agent_count)
+- for obj in objs:
+- self.assertTrue(obj.get_schema_class_id().get_package_name() == "package2")
+- self.assertTrue(obj.get_schema_class_id().get_class_name() == "class1")
+- self.assertTrue(obj.get_object_id() == "p2c1_key2")
+-
+- # should fail
+- wild_schema_id = SchemaClassId("package1", "bad-class")
+- objs = self.console.get_objects(_schema_id=wild_schema_id,
+- _object_id="p1c1_key2", _timeout=5)
+- self.assertTrue(objs == None)
+-
+- self.console.destroy(10)
+-
+Index: extras/qmf/src/py/qmf2/tests/agent_test.py
+===================================================================
+--- extras/qmf/src/py/qmf2/tests/agent_test.py (revision 1056407)
++++ extras/qmf/src/py/qmf2/tests/agent_test.py (working copy)
+@@ -1,167 +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.
+-#
+-import logging
+-import time
+-import unittest
+-from threading import Semaphore
+-
+-
+-from qpid.messaging import *
+-from qmf2.common import (qmfTypes, SchemaProperty, SchemaObjectClass, QmfData,
+- QmfEvent, SchemaMethod, Notifier, SchemaClassId,
+- WorkItem)
+-from qmf2.agent import (Agent, QmfAgentData)
+-
+-
+-
+-class ExampleNotifier(Notifier):
+- def __init__(self):
+- self._sema4 = Semaphore(0) # locked
+-
+- def indication(self):
+- self._sema4.release()
+-
+- def waitForWork(self):
+- print("Waiting for event...")
+- self._sema4.acquire()
+- print("...event present")
+-
+-
+-
+-
+-class QmfTest(unittest.TestCase):
+- def test_begin(self):
+- print("!!! being test")
+-
+- def test_end(self):
+- print("!!! end test")
+-
+-
+-#
+-# An example agent application
+-#
+-
+-
+-if __name__ == '__main__':
+- _notifier = ExampleNotifier()
+- _agent = Agent( "qmf.testAgent", _notifier=_notifier )
+-
+- # Dynamically construct a class schema
+-
+- _schema = SchemaObjectClass( _classId=SchemaClassId("MyPackage", "MyClass"),
+- _desc="A test data schema",
+- _object_id_names=["index1", "index2"] )
+- # add properties
+- _schema.add_property( "index1", SchemaProperty(qmfTypes.TYPE_UINT8))
+- _schema.add_property( "index2", SchemaProperty(qmfTypes.TYPE_LSTR))
+-
+- # these two properties are statistics
+- _schema.add_property( "query_count", SchemaProperty(qmfTypes.TYPE_UINT32))
+- _schema.add_property( "method_call_count", SchemaProperty(qmfTypes.TYPE_UINT32))
+-
+- # These two properties can be set via the method call
+- _schema.add_property( "set_string", SchemaProperty(qmfTypes.TYPE_LSTR))
+- _schema.add_property( "set_int", SchemaProperty(qmfTypes.TYPE_UINT32))
+-
+-
+- # add method
+- _meth = SchemaMethod( _desc="Method to set string and int in object." )
+- _meth.add_argument( "arg_int", SchemaProperty(qmfTypes.TYPE_UINT32) )
+- _meth.add_argument( "arg_str", SchemaProperty(qmfTypes.TYPE_LSTR) )
+- _schema.add_method( "set_meth", _meth )
+-
+- # Add schema to Agent
+-
+- _agent.register_object_class(_schema)
+-
+- # instantiate managed data objects matching the schema
+-
+- _obj1 = QmfAgentData( _agent, _schema=_schema )
+- _obj1.set_value("index1", 100)
+- _obj1.set_value("index2", "a name" )
+- _obj1.set_value("set_string", "UNSET")
+- _obj1.set_value("set_int", 0)
+- _obj1.set_value("query_count", 0)
+- _obj1.set_value("method_call_count", 0)
+- _agent.add_object( _obj1 )
+-
+- _agent.add_object( QmfAgentData( _agent, _schema=_schema,
+- _values={"index1":99,
+- "index2": "another name",
+- "set_string": "UNSET",
+- "set_int": 0,
+- "query_count": 0,
+- "method_call_count": 0} ))
+-
+- # add an "unstructured" object to the Agent
+- _obj2 = QmfAgentData(_agent, _object_id="01545")
+- _obj2.set_value("field1", "a value")
+- _obj2.set_value("field2", 2)
+- _obj2.set_value("field3", {"a":1, "map":2, "value":3})
+- _obj2.set_value("field4", ["a", "list", "value"])
+- _agent.add_object(_obj2)
+-
+-
+- ## Now connect to the broker
+-
+- _c = Connection("localhost")
+- _c.connect()
+- _agent.setConnection(_c)
+-
+- _error_data = QmfData.create({"code": -1, "description": "You made a boo-boo."})
+-
+- _done = False
+- while not _done:
+- # try:
+- _notifier.waitForWork()
+-
+- _wi = _agent.get_next_workitem(timeout=0)
+- while _wi:
+-
+- if _wi.get_type() == WorkItem.METHOD_CALL:
+- mc = _wi.get_params()
+-
+- if mc.get_name() == "set_meth":
+- print("!!! Calling 'set_meth' on Object_id = %s" % mc.get_object_id())
+- print("!!! args='%s'" % str(mc.get_args()))
+- print("!!! userid=%s" % str(mc.get_user_id()))
+- print("!!! handle=%s" % _wi.get_handle())
+- _agent.method_response(_wi.get_handle(),
+- {"rc1": 100, "rc2": "Success"})
+- else:
+- print("!!! Unknown Method name = %s" % mc.get_name())
+- _agent.method_response(_wi.get_handle(), _error=_error_data)
+- else:
+- print("TBD: work item %d:%s" % (_wi.get_type(), str(_wi.get_params())))
+-
+- _agent.release_workitem(_wi)
+- _wi = _agent.get_next_workitem(timeout=0)
+- # except:
+- # print( "shutting down...")
+- # _done = True
+-
+- print( "Removing connection... TBD!!!" )
+- #_myConsole.remove_connection( _c, 10 )
+-
+- print( "Destroying agent... TBD!!!" )
+- #_myConsole.destroy( 10 )
+-
+- print( "******** agent test done ********" )
+-
+-
+-
+Index: extras/qmf/src/py/qmf2/tests/async_method.py
+===================================================================
+--- extras/qmf/src/py/qmf2/tests/async_method.py (revision 1056407)
++++ extras/qmf/src/py/qmf2/tests/async_method.py (working copy)
+@@ -1,353 +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.
+-#
+-import unittest
+-import logging
+-from threading import Thread, Event
+-
+-import qpid.messaging
+-from qmf2.common import (Notifier, SchemaObjectClass, SchemaClassId,
+- SchemaProperty, qmfTypes, SchemaMethod, QmfQuery,
+- QmfData, WorkItem)
+-import qmf2.console
+-from qmf2.agent import(QmfAgentData, Agent, MethodCallParams)
+-
+-
+-class _testNotifier(Notifier):
+- def __init__(self):
+- self._event = Event()
+-
+- def indication(self):
+- # note: called by qmf daemon thread
+- self._event.set()
+-
+- def wait_for_work(self, timeout):
+- # note: called by application thread to wait
+- # for qmf to generate work
+- self._event.wait(timeout)
+- timed_out = self._event.isSet() == False
+- if not timed_out:
+- self._event.clear()
+- return True
+- return False
+-
+-
+-class _agentApp(Thread):
+- def __init__(self, name, broker_url, heartbeat):
+- Thread.__init__(self)
+- self.notifier = _testNotifier()
+- self.broker_url = broker_url
+- self.agent = Agent(name,
+- _notifier=self.notifier,
+- heartbeat_interval=heartbeat)
+-
+- # Dynamically construct a management database
+-
+- _schema = SchemaObjectClass( _classId=SchemaClassId("MyPackage", "MyClass"),
+- _desc="A test data schema",
+- _object_id_names=["index1", "index2"] )
+- # add properties
+- _schema.add_property( "index1", SchemaProperty(qmfTypes.TYPE_UINT8))
+- _schema.add_property( "index2", SchemaProperty(qmfTypes.TYPE_LSTR))
+-
+- # these two properties are statistics
+- _schema.add_property( "query_count", SchemaProperty(qmfTypes.TYPE_UINT32))
+- _schema.add_property( "method_call_count", SchemaProperty(qmfTypes.TYPE_UINT32))
+-
+- # These two properties can be set via the method call
+- _schema.add_property( "set_string", SchemaProperty(qmfTypes.TYPE_LSTR))
+- _schema.add_property( "set_int", SchemaProperty(qmfTypes.TYPE_UINT32))
+-
+- # add method
+- _meth = SchemaMethod( _desc="Method to set string and int in object." )
+- _meth.add_argument( "arg_int", SchemaProperty(qmfTypes.TYPE_UINT32) )
+- _meth.add_argument( "arg_str", SchemaProperty(qmfTypes.TYPE_LSTR) )
+- # the input value of cookie is returned in the response
+- _meth.add_argument( "cookie", SchemaProperty(qmfTypes.TYPE_LSTR,
+- kwargs={"dir":"IO"}))
+- _schema.add_method( "set_meth", _meth )
+-
+- # Add schema to Agent
+-
+- self.agent.register_object_class(_schema)
+-
+- # instantiate managed data objects matching the schema
+-
+- _obj1 = QmfAgentData( self.agent, _schema=_schema,
+- _values={"index1":100, "index2":"a name"})
+- _obj1.set_value("set_string", "UNSET")
+- _obj1.set_value("set_int", 0)
+- _obj1.set_value("query_count", 0)
+- _obj1.set_value("method_call_count", 0)
+- self.agent.add_object( _obj1 )
+-
+- self.agent.add_object( QmfAgentData( self.agent, _schema=_schema,
+- _values={"index1":99,
+- "index2": "another name",
+- "set_string": "UNSET",
+- "set_int": 0,
+- "query_count": 0,
+- "method_call_count": 0} ))
+-
+- # add an "unstructured" object to the Agent
+- _obj2 = QmfAgentData(self.agent, _object_id="01545")
+- _obj2.set_value("field1", "a value")
+- _obj2.set_value("field2", 2)
+- _obj2.set_value("field3", {"a":1, "map":2, "value":3})
+- _obj2.set_value("field4", ["a", "list", "value"])
+- self.agent.add_object(_obj2)
+-
+- self.running = False
+- self.ready = Event()
+-
+- def start_app(self):
+- self.running = True
+- self.start()
+- self.ready.wait(10)
+- if not self.ready.is_set():
+- raise Exception("Agent failed to connect to broker.")
+-
+- def stop_app(self):
+- self.running = False
+- # wake main thread
+- self.notifier.indication() # hmmm... collide with daemon???
+- self.join(10)
+- if self.isAlive():
+- raise Exception("AGENT DID NOT TERMINATE AS EXPECTED!!!")
+-
+- def run(self):
+- # broker_url = "user/passwd at hostname:port"
+- self.conn = qpid.messaging.Connection(self.broker_url)
+- self.conn.open()
+- self.agent.set_connection(self.conn)
+- self.ready.set()
+-
+- # Agent application main processing loop
+- while self.running:
+- self.notifier.wait_for_work(None)
+- wi = self.agent.get_next_workitem(timeout=0)
+- while wi is not None:
+- if wi.get_type() == WorkItem.METHOD_CALL:
+- mc = wi.get_params()
+- if not isinstance(mc, MethodCallParams):
+- raise Exception("Unexpected method call parameters")
+-
+- if mc.get_name() == "set_meth":
+- obj = self.agent.get_object(mc.get_object_id(),
+- mc.get_schema_id())
+- if obj is None:
+- error_info = QmfData.create({"code": -2,
+- "description":
+- "Bad Object Id."},
+- _object_id="_error")
+- self.agent.method_response(wi.get_handle(),
+- _error=error_info)
+- else:
+- obj.inc_value("method_call_count")
+- out_args = {"code" : 0}
+- if "cookie" in mc.get_args():
+- out_args["cookie"] = mc.get_args()["cookie"]
+- if "arg_int" in mc.get_args():
+- obj.set_value("set_int", mc.get_args()["arg_int"])
+- if "arg_str" in mc.get_args():
+- obj.set_value("set_string", mc.get_args()["arg_str"])
+- self.agent.method_response(wi.get_handle(),
+- out_args)
+- elif mc.get_name() == "a_method":
+- obj = self.agent.get_object(mc.get_object_id(),
+- mc.get_schema_id())
+- if obj is None:
+- error_info = QmfData.create({"code": -3,
+- "description":
+- "Unknown object id."},
+- _object_id="_error")
+- self.agent.method_response(wi.get_handle(),
+- _error=error_info)
+- elif obj.get_object_id() != "01545":
+- error_info = QmfData.create( {"code": -4,
+- "description":
+- "Unexpected id."},
+- _object_id="_error")
+- self.agent.method_response(wi.get_handle(),
+- _error=error_info)
+- else:
+- args = mc.get_args()
+- if ("arg1" in args and args["arg1"] == 1 and
+- "arg2" in args and args["arg2"] == "Now set!"
+- and "arg3" in args and args["arg3"] == 1966):
+- out_args = {"code" : 0}
+- if "cookie" in mc.get_args():
+- out_args["cookie"] = mc.get_args()["cookie"]
+- self.agent.method_response(wi.get_handle(),
+- out_args)
+- else:
+- error_info = QmfData.create(
+- {"code": -5,
+- "description":
+- "Bad Args."},
+- _object_id="_error")
+- self.agent.method_response(wi.get_handle(),
+- _error=error_info)
+- else:
+- error_info = QmfData.create( {"code": -1,
+- "description":
+- "Unknown method call."},
+- _object_id="_error")
+- self.agent.method_response(wi.get_handle(), _error=error_info)
+-
+- self.agent.release_workitem(wi)
+- wi = self.agent.get_next_workitem(timeout=0)
+-
+- if self.conn:
+- self.agent.remove_connection(10)
+- self.agent.destroy(10)
+-
+-
+-
+-class BaseTest(unittest.TestCase):
+- def configure(self, config):
+- self.config = config
+- self.broker = config.broker
+- self.defines = self.config.defines
+-
+- def setUp(self):
+- # one second agent heartbeat interval
+- self.agent_heartbeat = 1
+- self.agent1 = _agentApp("agent1", self.broker, self.agent_heartbeat)
+- self.agent1.start_app()
+- self.agent2 = _agentApp("agent2", self.broker, self.agent_heartbeat)
+- self.agent2.start_app()
+-
+- def tearDown(self):
+- if self.agent1:
+- self.agent1.stop_app()
+- self.agent1 = None
+- if self.agent2:
+- self.agent2.stop_app()
+- self.agent2 = None
+-
+- def test_described_obj(self):
+- # create console
+- # find agents
+- # synchronous query for all objects in schema
+- # method call on each object
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- i_count = 0
+- for aname in ["agent1", "agent2"]:
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- query = QmfQuery.create_wildcard(QmfQuery.TARGET_SCHEMA_ID)
+-
+- sid_list = self.console.do_query(agent, query)
+- self.assertTrue(sid_list and len(sid_list) == 1)
+- for sid in sid_list:
+- t_params = {QmfData.KEY_SCHEMA_ID: sid}
+- query = QmfQuery.create_wildcard(QmfQuery.TARGET_OBJECT,
+- _target_params=t_params)
+- obj_list = self.console.do_query(agent, query)
+- self.assertTrue(len(obj_list) == 2)
+- for obj in obj_list:
+- cookie = "cookie-" + str(i_count)
+- i_count += 1
+- mr = obj.invoke_method( "set_meth",
+- {"arg_int": -99,
+- "arg_str": "Now set!",
+- "cookie": cookie},
+- _reply_handle=cookie,
+- _timeout=3)
+- self.assertTrue(mr)
+-
+- # done, now wait for async responses
+-
+- r_count = 0
+- while self.notifier.wait_for_work(3):
+- wi = self.console.get_next_workitem(timeout=0)
+- while wi is not None:
+- r_count += 1
+- self.assertTrue(wi.get_type() == WorkItem.METHOD_RESPONSE)
+- reply = wi.get_params()
+- self.assertTrue(isinstance(reply, qmf2.console.MethodResult))
+- self.assertTrue(reply.succeeded())
+- self.assertTrue(reply.get_argument("cookie") == wi.get_handle())
+-
+- wi = self.console.get_next_workitem(timeout=0)
+-
+- self.assertTrue(r_count == i_count)
+-
+- self.console.destroy(10)
+-
+-
+- def test_managed_obj(self):
+- # create console
+- # find agents
+- # synchronous query for a managed object
+- # method call on each object
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- i_count = 0
+- for aname in ["agent1", "agent2"]:
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- query = QmfQuery.create_id(QmfQuery.TARGET_OBJECT, "01545")
+- obj_list = self.console.do_query(agent, query)
+-
+- self.assertTrue(isinstance(obj_list, type([])))
+- self.assertTrue(len(obj_list) == 1)
+- obj = obj_list[0]
+-
+- cookie = "cookie-" + str(i_count)
+- i_count += 1
+- mr = obj.invoke_method("a_method",
+- {"arg1": 1,
+- "arg2": "Now set!",
+- "arg3": 1966,
+- "cookie": cookie},
+- _reply_handle=cookie,
+- _timeout=3)
+- self.assertTrue(mr)
+-
+- # done, now wait for async responses
+-
+- r_count = 0
+- while self.notifier.wait_for_work(3):
+- wi = self.console.get_next_workitem(timeout=0)
+- while wi is not None:
+- r_count += 1
+- self.assertTrue(wi.get_type() == WorkItem.METHOD_RESPONSE)
+- reply = wi.get_params()
+- self.assertTrue(isinstance(reply, qmf2.console.MethodResult))
+- self.assertTrue(reply.succeeded())
+- self.assertTrue(reply.get_argument("cookie") == wi.get_handle())
+-
+- wi = self.console.get_next_workitem(timeout=0)
+-
+- self.assertTrue(r_count == i_count)
+-
+- self.console.destroy(10)
+Index: extras/qmf/src/py/qmf2/tests/__init__.py
+===================================================================
+--- extras/qmf/src/py/qmf2/tests/__init__.py (revision 1056407)
++++ extras/qmf/src/py/qmf2/tests/__init__.py (working copy)
+@@ -1,30 +0,0 @@
+-# 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.
+-#
+-
+-import agent_discovery
+-import basic_query
+-import basic_method
+-import obj_gets
+-import events
+-import multi_response
+-import async_query
+-import async_method
+-import subscriptions
+Index: extras/qmf/src/py/qmf2/tests/basic_method.py
+===================================================================
+--- extras/qmf/src/py/qmf2/tests/basic_method.py (revision 1056407)
++++ extras/qmf/src/py/qmf2/tests/basic_method.py (working copy)
+@@ -1,391 +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.
+-#
+-import unittest
+-import logging
+-from threading import Thread, Event
+-
+-import qpid.messaging
+-from qmf2.common import (Notifier, SchemaObjectClass, SchemaClassId,
+- SchemaProperty, qmfTypes, SchemaMethod, QmfQuery,
+- QmfData, WorkItem)
+-import qmf2.console
+-from qmf2.agent import(QmfAgentData, Agent, MethodCallParams)
+-
+-
+-class _testNotifier(Notifier):
+- def __init__(self):
+- self._event = Event()
+-
+- def indication(self):
+- # note: called by qmf daemon thread
+- self._event.set()
+-
+- def wait_for_work(self, timeout):
+- # note: called by application thread to wait
+- # for qmf to generate work
+- self._event.wait(timeout)
+- timed_out = self._event.isSet() == False
+- if not timed_out:
+- self._event.clear()
+- return True
+- return False
+-
+-
+-class _agentApp(Thread):
+- def __init__(self, name, broker_url, heartbeat):
+- Thread.__init__(self)
+- self.notifier = _testNotifier()
+- self.broker_url = broker_url
+- self.agent = Agent(name,
+- _notifier=self.notifier,
+- heartbeat_interval=heartbeat)
+-
+- # Dynamically construct a management database
+-
+- _schema = SchemaObjectClass( _classId=SchemaClassId("MyPackage", "MyClass"),
+- _desc="A test data schema",
+- _object_id_names=["index1", "index2"] )
+- # add properties
+- _schema.add_property( "index1", SchemaProperty(qmfTypes.TYPE_UINT8))
+- _schema.add_property( "index2", SchemaProperty(qmfTypes.TYPE_LSTR))
+-
+- # these two properties are statistics
+- _schema.add_property( "query_count", SchemaProperty(qmfTypes.TYPE_UINT32))
+- _schema.add_property( "method_call_count", SchemaProperty(qmfTypes.TYPE_UINT32))
+-
+- # These two properties can be set via the method call
+- _schema.add_property( "set_string", SchemaProperty(qmfTypes.TYPE_LSTR))
+- _schema.add_property( "set_int", SchemaProperty(qmfTypes.TYPE_UINT32))
+-
+- # add method
+- _meth = SchemaMethod( _desc="Method to set string and int in object." )
+- _meth.add_argument( "arg_int", SchemaProperty(qmfTypes.TYPE_UINT32) )
+- _meth.add_argument( "arg_str", SchemaProperty(qmfTypes.TYPE_LSTR) )
+- _schema.add_method( "set_meth", _meth )
+-
+- # Add schema to Agent
+-
+- self.agent.register_object_class(_schema)
+-
+- # instantiate managed data objects matching the schema
+-
+- _obj1 = QmfAgentData( self.agent, _schema=_schema,
+- _values={"index1":100, "index2":"a name"})
+- _obj1.set_value("set_string", "UNSET")
+- _obj1.set_value("set_int", 0)
+- _obj1.set_value("query_count", 0)
+- _obj1.set_value("method_call_count", 0)
+- self.agent.add_object( _obj1 )
+-
+- self.agent.add_object( QmfAgentData( self.agent, _schema=_schema,
+- _values={"index1":99,
+- "index2": "another name",
+- "set_string": "UNSET",
+- "set_int": 0,
+- "query_count": 0,
+- "method_call_count": 0} ))
+-
+- # add an "unstructured" object to the Agent
+- _obj2 = QmfAgentData(self.agent, _object_id="01545")
+- _obj2.set_value("field1", "a value")
+- _obj2.set_value("field2", 2)
+- _obj2.set_value("field3", {"a":1, "map":2, "value":3})
+- _obj2.set_value("field4", ["a", "list", "value"])
+- self.agent.add_object(_obj2)
+-
+- self.running = False
+- self.ready = Event()
+-
+- def start_app(self):
+- self.running = True
+- self.start()
+- self.ready.wait(10)
+- if not self.ready.is_set():
+- raise Exception("Agent failed to connect to broker.")
+-
+- def stop_app(self):
+- self.running = False
+- # wake main thread
+- self.notifier.indication() # hmmm... collide with daemon???
+- self.join(10)
+- if self.isAlive():
+- raise Exception("AGENT DID NOT TERMINATE AS EXPECTED!!!")
+-
+- def run(self):
+- # broker_url = "user/passwd at hostname:port"
+- self.conn = qpid.messaging.Connection(self.broker_url)
+- self.conn.open()
+- self.agent.set_connection(self.conn)
+- self.ready.set()
+-
+- # Agent application main processing loop
+- while self.running:
+- self.notifier.wait_for_work(None)
+- wi = self.agent.get_next_workitem(timeout=0)
+- while wi is not None:
+- if wi.get_type() == WorkItem.METHOD_CALL:
+- mc = wi.get_params()
+- if not isinstance(mc, MethodCallParams):
+- raise Exception("Unexpected method call parameters")
+-
+- if mc.get_name() == "set_meth":
+- obj = self.agent.get_object(mc.get_object_id(),
+- mc.get_schema_id())
+- if obj is None:
+- error_info = QmfData.create({"code": -2,
+- "description":
+- "Bad Object Id."},
+- _object_id="_error")
+- self.agent.method_response(wi.get_handle(),
+- _error=error_info)
+- else:
+- obj.inc_value("method_call_count")
+- if "arg_int" in mc.get_args():
+- obj.set_value("set_int", mc.get_args()["arg_int"])
+- if "arg_str" in mc.get_args():
+- obj.set_value("set_string", mc.get_args()["arg_str"])
+- self.agent.method_response(wi.get_handle(),
+- {"code" : 0})
+- elif mc.get_name() == "a_method":
+- obj = self.agent.get_object(mc.get_object_id(),
+- mc.get_schema_id())
+- if obj is None:
+- error_info = QmfData.create({"code": -3,
+- "description":
+- "Unknown object id."},
+- _object_id="_error")
+- self.agent.method_response(wi.get_handle(),
+- _error=error_info)
+- elif obj.get_object_id() != "01545":
+- error_info = QmfData.create( {"code": -4,
+- "description":
+- "Unexpected id."},
+- _object_id="_error")
+- self.agent.method_response(wi.get_handle(),
+- _error=error_info)
+- else:
+- args = mc.get_args()
+- if ("arg1" in args and args["arg1"] == 1 and
+- "arg2" in args and args["arg2"] == "Now set!"
+- and "arg3" in args and args["arg3"] == 1966):
+- self.agent.method_response(wi.get_handle(),
+- {"code" : 0})
+- else:
+- error_info = QmfData.create(
+- {"code": -5,
+- "description":
+- "Bad Args."},
+- _object_id="_error")
+- self.agent.method_response(wi.get_handle(),
+- _error=error_info)
+- else:
+- error_info = QmfData.create( {"code": -1,
+- "description":
+- "Unknown method call."},
+- _object_id="_error")
+- self.agent.method_response(wi.get_handle(), _error=error_info)
+-
+- self.agent.release_workitem(wi)
+- wi = self.agent.get_next_workitem(timeout=0)
+-
+- if self.conn:
+- self.agent.remove_connection(10)
+- self.agent.destroy(10)
+-
+-
+-
+-class BaseTest(unittest.TestCase):
+- def configure(self, config):
+- self.config = config
+- self.broker = config.broker
+- self.defines = self.config.defines
+-
+- def setUp(self):
+- # one second agent heartbeat interval
+- self.agent_heartbeat = 1
+- self.agent1 = _agentApp("agent1", self.broker, self.agent_heartbeat)
+- self.agent1.start_app()
+- self.agent2 = _agentApp("agent2", self.broker, self.agent_heartbeat)
+- self.agent2.start_app()
+-
+- def tearDown(self):
+- if self.agent1:
+- self.agent1.stop_app()
+- self.agent1 = None
+- if self.agent2:
+- self.agent2.stop_app()
+- self.agent2 = None
+-
+- def test_described_obj(self):
+- # create console
+- # find agents
+- # synchronous query for all objects in schema
+- # method call on each object
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- for aname in ["agent1", "agent2"]:
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- query = QmfQuery.create_wildcard(QmfQuery.TARGET_SCHEMA_ID)
+-
+- sid_list = self.console.do_query(agent, query)
+- self.assertTrue(sid_list and len(sid_list) == 1)
+- for sid in sid_list:
+- t_params = {QmfData.KEY_SCHEMA_ID: sid}
+- query = QmfQuery.create_wildcard(QmfQuery.TARGET_OBJECT,
+- _target_params=t_params)
+- obj_list = self.console.do_query(agent, query)
+- self.assertTrue(len(obj_list) == 2)
+- for obj in obj_list:
+- mr = obj.invoke_method( "set_meth", {"arg_int": -99,
+- "arg_str": "Now set!"},
+- _timeout=3)
+- self.assertTrue(isinstance(mr, qmf2.console.MethodResult))
+- self.assertTrue(mr.succeeded())
+- self.assertTrue(mr.get_argument("code") == 0)
+-
+- self.assertTrue(obj.get_value("method_call_count") == 0)
+- self.assertTrue(obj.get_value("set_string") == "UNSET")
+- self.assertTrue(obj.get_value("set_int") == 0)
+-
+- obj.refresh()
+-
+- self.assertTrue(obj.get_value("method_call_count") == 1)
+- self.assertTrue(obj.get_value("set_string") == "Now set!")
+- self.assertTrue(obj.get_value("set_int") == -99)
+-
+- self.console.destroy(10)
+-
+-
+- def test_bad_method_schema(self):
+- # create console
+- # find agents
+- # synchronous query for all objects with schema
+- # invalid method call on each object
+- # - should throw a ValueError - NOT YET.
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- for aname in ["agent1", "agent2"]:
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- query = QmfQuery.create_wildcard(QmfQuery.TARGET_SCHEMA_ID)
+-
+- sid_list = self.console.do_query(agent, query)
+- self.assertTrue(sid_list and len(sid_list) == 1)
+- for sid in sid_list:
+-
+- t_params = {QmfData.KEY_SCHEMA_ID: sid}
+- query = QmfQuery.create_predicate(QmfQuery.TARGET_OBJECT,
+- [QmfQuery.TRUE],
+- _target_params=t_params)
+-
+- obj_list = self.console.do_query(agent, query)
+- self.assertTrue(len(obj_list) == 2)
+- for obj in obj_list:
+- mr = obj.invoke_method("unknown_method",
+- {"arg1": -99, "arg2": "Now set!"},
+- _timeout=3)
+- # self.failUnlessRaises(ValueError,
+- # obj.invoke_method,
+- # "unknown_meth",
+- # {"arg1": -99, "arg2": "Now set!"},
+- # _timeout=3)
+- self.assertTrue(isinstance(mr, qmf2.console.MethodResult))
+- self.assertFalse(mr.succeeded())
+- self.assertTrue(isinstance(mr.get_exception(), QmfData))
+-
+- self.console.destroy(10)
+-
+- def test_bad_method_no_schema(self):
+- # create console
+- # find agents
+- # synchronous query for all objects with no schema
+- # invalid method call on each object
+- # - should throw a ValueError
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- for aname in ["agent1", "agent2"]:
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- query = QmfQuery.create_wildcard(QmfQuery.TARGET_OBJECT)
+-
+- obj_list = self.console.do_query(agent, query)
+- self.assertTrue(len(obj_list) == 1)
+- for obj in obj_list:
+- self.assertTrue(obj.get_schema_class_id() == None)
+- mr = obj.invoke_method("unknown_meth",
+- {"arg1": -99, "arg2": "Now set!"},
+- _timeout=3)
+- self.assertTrue(isinstance(mr, qmf2.console.MethodResult))
+- self.assertFalse(mr.succeeded())
+- self.assertTrue(isinstance(mr.get_exception(), QmfData))
+-
+- self.console.destroy(10)
+-
+- def test_managed_obj(self):
+- # create console
+- # find agents
+- # synchronous query for a managed object
+- # method call on each object
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- for aname in ["agent1", "agent2"]:
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- query = QmfQuery.create_id(QmfQuery.TARGET_OBJECT, "01545")
+- obj_list = self.console.do_query(agent, query)
+-
+- self.assertTrue(isinstance(obj_list, type([])))
+- self.assertTrue(len(obj_list) == 1)
+- obj = obj_list[0]
+-
+- mr = obj.invoke_method("a_method",
+- {"arg1": 1,
+- "arg2": "Now set!",
+- "arg3": 1966},
+- _timeout=3)
+- self.assertTrue(isinstance(mr, qmf2.console.MethodResult))
+- self.assertTrue(mr.succeeded())
+- self.assertTrue(mr.get_argument("code") == 0)
+- # @todo refresh and verify changes
+-
+- self.console.destroy(10)
+Index: extras/qmf/src/py/qmf2/tests/console_test.py
+===================================================================
+--- extras/qmf/src/py/qmf2/tests/console_test.py (revision 1056407)
++++ extras/qmf/src/py/qmf2/tests/console_test.py (working copy)
+@@ -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.
+-#
+-import logging
+-import time
+-from threading import Semaphore
+-
+-
+-from qpid.messaging import *
+-from qmf2.common import (Notifier, QmfQuery, QmfQueryPredicate, MsgKey,
+- SchemaClassId, SchemaClass, QmfData)
+-from qmf2.console import Console
+-
+-
+-class ExampleNotifier(Notifier):
+- def __init__(self):
+- self._sema4 = Semaphore(0) # locked
+-
+- def indication(self):
+- self._sema4.release()
+-
+- def waitForWork(self):
+- print("Waiting for event...")
+- self._sema4.acquire()
+- print("...event present")
+-
+-
+-logging.getLogger().setLevel(logging.INFO)
+-
+-print( "Starting Connection" )
+-_c = Connection("localhost")
+-_c.connect()
+-
+-print( "Starting Console" )
+-
+-_notifier = ExampleNotifier()
+-_myConsole = Console(notifier=_notifier)
+-_myConsole.addConnection( _c )
+-
+-# Allow discovery only for the agent named "qmf.testAgent"
+-# @todo: replace "manual" query construction with
+-# a formal class-based Query API
+-_query = QmfQuery.create_predicate(QmfQuery.TARGET_AGENT,
+- QmfQueryPredicate({QmfQuery.CMP_EQ:
+- [QmfQuery.KEY_AGENT_NAME,
+- "qmf.testAgent"]}))
+-_myConsole.enable_agent_discovery(_query)
+-
+-_done = False
+-while not _done:
+-# try:
+- _notifier.waitForWork()
+-
+- _wi = _myConsole.get_next_workitem(timeout=0)
+- while _wi:
+- print("!!! work item received %d:%s" % (_wi.get_type(),
+- str(_wi.get_params())))
+-
+-
+- if _wi.get_type() == _wi.AGENT_ADDED:
+- _agent = _wi.get_params().get("agent")
+- if not _agent:
+- print("!!!! AGENT IN REPLY IS NULL !!! ")
+-
+- _query = QmfQuery.create_wildcard(QmfQuery.TARGET_OBJECT_ID)
+- oid_list = _myConsole.doQuery(_agent, _query)
+-
+- print("!!!************************** REPLY=%s" % oid_list)
+-
+- for oid in oid_list:
+- _query = QmfQuery.create_id(QmfQuery.TARGET_OBJECT,
+- oid)
+- obj_list = _myConsole.doQuery(_agent, _query)
+-
+- print("!!!************************** REPLY=%s" % obj_list)
+-
+- if obj_list is None:
+- obj_list={}
+-
+- for obj in obj_list:
+- resp = obj.invoke_method( "set_meth",
+- {"arg_int": -11,
+- "arg_str": "are we not goons?"},
+- None,
+- 3)
+- if resp is None:
+- print("!!!*** NO RESPONSE FROM METHOD????")
+- else:
+- print("!!! method succeeded()=%s" % resp.succeeded())
+- print("!!! method exception()=%s" % resp.get_exception())
+- print("!!! method get args() = %s" % resp.get_arguments())
+-
+- if not obj.is_described():
+- resp = obj.invoke_method( "bad method",
+- {"arg_int": -11,
+- "arg_str": "are we not goons?"},
+- None,
+- 3)
+- if resp is None:
+- print("!!!*** NO RESPONSE FROM METHOD????")
+- else:
+- print("!!! method succeeded()=%s" % resp.succeeded())
+- print("!!! method exception()=%s" % resp.get_exception())
+- print("!!! method get args() = %s" % resp.get_arguments())
+-
+-
+- #---------------------------------
+- #_query = QmfQuery.create_id(QmfQuery.TARGET_OBJECT, "99another name")
+-
+- #obj_list = _myConsole.doQuery(_agent, _query)
+-
+- #---------------------------------
+-
+- # _query = QmfQuery.create_wildcard(QmfQuery.TARGET_PACKAGES)
+-
+- # package_list = _myConsole.doQuery(_agent, _query)
+-
+- # for pname in package_list:
+- # print("!!! Querying for schema from package: %s" % pname)
+- # _query = QmfQuery.create_predicate(QmfQuery.TARGET_SCHEMA_ID,
+- # QmfQueryPredicate(
+- # {QmfQuery.CMP_EQ: [SchemaClassId.KEY_PACKAGE, pname]}))
+-
+- # schema_id_list = _myConsole.doQuery(_agent, _query)
+- # for sid in schema_id_list:
+- # _query = QmfQuery.create_predicate(QmfQuery.TARGET_SCHEMA,
+- # QmfQueryPredicate(
+- # {QmfQuery.CMP_EQ: [SchemaClass.KEY_SCHEMA_ID,
+- # sid.map_encode()]}))
+-
+- # schema_list = _myConsole.doQuery(_agent, _query)
+- # for schema in schema_list:
+- # sid = schema.get_class_id()
+- # _query = QmfQuery.create_predicate(
+- # QmfQuery.TARGET_OBJECT_ID,
+- # QmfQueryPredicate({QmfQuery.CMP_EQ:
+- # [QmfData.KEY_SCHEMA_ID,
+- # sid.map_encode()]}))
+-
+- # oid_list = _myConsole.doQuery(_agent, _query)
+- # for oid in oid_list:
+- # _query = QmfQuery.create_id(
+- # QmfQuery.TARGET_OBJECT, oid)
+- # _reply = _myConsole.doQuery(_agent, _query)
+-
+- # print("!!!************************** REPLY=%s" % _reply)
+-
+-
+- _myConsole.release_workitem(_wi)
+- _wi = _myConsole.get_next_workitem(timeout=0)
+-# except:
+-# logging.info( "shutting down..." )
+-# _done = True
+-
+-print( "Removing connection" )
+-_myConsole.removeConnection( _c, 10 )
+-
+-print( "Destroying console:" )
+-_myConsole.destroy( 10 )
+-
+-print( "******** console test done ********" )
+Index: extras/qmf/src/py/qmf2/tests/async_query.py
+===================================================================
+--- extras/qmf/src/py/qmf2/tests/async_query.py (revision 1056407)
++++ extras/qmf/src/py/qmf2/tests/async_query.py (working copy)
+@@ -1,444 +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.
+-#
+-import unittest
+-import logging
+-from threading import Thread, Event
+-
+-import qpid.messaging
+-from qmf2.common import (Notifier, SchemaObjectClass, SchemaClassId,
+- SchemaProperty, qmfTypes, SchemaMethod, QmfQuery,
+- QmfData, WorkItem)
+-import qmf2.console
+-from qmf2.agent import(QmfAgentData, Agent)
+-
+-
+-class _testNotifier(Notifier):
+- def __init__(self):
+- self._event = Event()
+-
+- def indication(self):
+- # note: called by qmf daemon thread
+- self._event.set()
+-
+- def wait_for_work(self, timeout):
+- # note: called by application thread to wait
+- # for qmf to generate work
+- self._event.wait(timeout)
+- timed_out = self._event.isSet() == False
+- if not timed_out:
+- self._event.clear()
+- return True
+- return False
+-
+-
+-class _agentApp(Thread):
+- def __init__(self, name, broker_url, heartbeat):
+- Thread.__init__(self)
+- self.notifier = _testNotifier()
+- self.broker_url = broker_url
+- self.agent = Agent(name,
+- _notifier=self.notifier,
+- heartbeat_interval=heartbeat)
+-
+- # Dynamically construct a management database
+-
+- _schema = SchemaObjectClass( _classId=SchemaClassId("MyPackage", "MyClass"),
+- _desc="A test data schema",
+- _object_id_names=["index1", "index2"] )
+- # add properties
+- _schema.add_property( "index1", SchemaProperty(qmfTypes.TYPE_UINT8))
+- _schema.add_property( "index2", SchemaProperty(qmfTypes.TYPE_LSTR))
+-
+- # these two properties are statistics
+- _schema.add_property( "query_count", SchemaProperty(qmfTypes.TYPE_UINT32))
+- _schema.add_property( "method_call_count", SchemaProperty(qmfTypes.TYPE_UINT32))
+-
+- # These two properties can be set via the method call
+- _schema.add_property( "set_string", SchemaProperty(qmfTypes.TYPE_LSTR))
+- _schema.add_property( "set_int", SchemaProperty(qmfTypes.TYPE_UINT32))
+-
+- # add method
+- _meth = SchemaMethod( _desc="Method to set string and int in object." )
+- _meth.add_argument( "arg_int", SchemaProperty(qmfTypes.TYPE_UINT32) )
+- _meth.add_argument( "arg_str", SchemaProperty(qmfTypes.TYPE_LSTR) )
+- _schema.add_method( "set_meth", _meth )
+-
+- # Add schema to Agent
+-
+- self.agent.register_object_class(_schema)
+-
+- # instantiate managed data objects matching the schema
+-
+- _obj1 = QmfAgentData( self.agent, _schema=_schema,
+- _values={"index1":100, "index2":"a name"})
+- _obj1.set_value("set_string", "UNSET")
+- _obj1.set_value("set_int", 0)
+- _obj1.set_value("query_count", 0)
+- _obj1.set_value("method_call_count", 0)
+- self.agent.add_object( _obj1 )
+-
+- self.agent.add_object( QmfAgentData( self.agent, _schema=_schema,
+- _values={"index1":99,
+- "index2": "another name",
+- "set_string": "UNSET",
+- "set_int": 0,
+- "query_count": 0,
+- "method_call_count": 0} ))
+-
+- self.agent.add_object( QmfAgentData( self.agent, _schema=_schema,
+- _values={"index1":50,
+- "index2": "my name",
+- "set_string": "SET",
+- "set_int": 0,
+- "query_count": 0,
+- "method_call_count": 0} ))
+-
+-
+- # add an "unstructured" object to the Agent
+- _obj2 = QmfAgentData(self.agent, _object_id="01545")
+- _obj2.set_value("field1", "a value")
+- _obj2.set_value("field2", 2)
+- _obj2.set_value("field3", {"a":1, "map":2, "value":3})
+- _obj2.set_value("field4", ["a", "list", "value"])
+- _obj2.set_value("index1", 50)
+- self.agent.add_object(_obj2)
+-
+- _obj2 = QmfAgentData(self.agent, _object_id="01546")
+- _obj2.set_value("field1", "a value")
+- _obj2.set_value("field2", 3)
+- _obj2.set_value("field3", {"a":1, "map":2, "value":3})
+- _obj2.set_value("field4", ["a", "list", "value"])
+- _obj2.set_value("index1", 51)
+- self.agent.add_object(_obj2)
+-
+- _obj2 = QmfAgentData(self.agent, _object_id="01544")
+- _obj2.set_value("field1", "a value")
+- _obj2.set_value("field2", 4)
+- _obj2.set_value("field3", {"a":1, "map":2, "value":3})
+- _obj2.set_value("field4", ["a", "list", "value"])
+- _obj2.set_value("index1", 49)
+- self.agent.add_object(_obj2)
+-
+- _obj2 = QmfAgentData(self.agent, _object_id="01543")
+- _obj2.set_value("field1", "a value")
+- _obj2.set_value("field2", 4)
+- _obj2.set_value("field3", {"a":1, "map":2, "value":3})
+- _obj2.set_value("field4", ["a", "list", "value"])
+- _obj2.set_value("index1", 48)
+- self.agent.add_object(_obj2)
+-
+- self.running = False
+- self.ready = Event()
+-
+- def start_app(self):
+- self.running = True
+- self.start()
+- self.ready.wait(10)
+- if not self.ready.is_set():
+- raise Exception("Agent failed to connect to broker.")
+-
+- def stop_app(self):
+- self.running = False
+- # wake main thread
+- self.notifier.indication() # hmmm... collide with daemon???
+- self.join(10)
+- if self.isAlive():
+- raise Exception("AGENT DID NOT TERMINATE AS EXPECTED!!!")
+-
+- def run(self):
+- # broker_url = "user/passwd at hostname:port"
+- self.conn = qpid.messaging.Connection(self.broker_url)
+- self.conn.open()
+- self.agent.set_connection(self.conn)
+- self.ready.set()
+-
+- while self.running:
+- self.notifier.wait_for_work(None)
+- wi = self.agent.get_next_workitem(timeout=0)
+- while wi is not None:
+- logging.error("UNEXPECTED AGENT WORKITEM RECEIVED=%s" % wi.get_type())
+- self.agent.release_workitem(wi)
+- wi = self.agent.get_next_workitem(timeout=0)
+-
+- if self.conn:
+- self.agent.remove_connection(10)
+- self.agent.destroy(10)
+-
+-
+-
+-
+-class BaseTest(unittest.TestCase):
+- def configure(self, config):
+- self.config = config
+- self.broker = config.broker
+- self.defines = self.config.defines
+-
+- def setUp(self):
+- # one second agent indication interval
+- self.agent_heartbeat = 1
+- self.agent1 = _agentApp("agent1", self.broker, self.agent_heartbeat)
+- self.agent1.start_app()
+- self.agent2 = _agentApp("agent2", self.broker, self.agent_heartbeat)
+- self.agent2.start_app()
+-
+- def tearDown(self):
+- if self.agent1:
+- self.agent1.stop_app()
+- self.agent1 = None
+- if self.agent2:
+- self.agent2.stop_app()
+- self.agent2 = None
+-
+- def test_all_schema_ids(self):
+- # create console
+- # find agents
+- # asynchronous query for all schema ids
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- for aname in ["agent1", "agent2"]:
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- # send queries
+- query = QmfQuery.create_wildcard(QmfQuery.TARGET_SCHEMA_ID)
+- rc = self.console.do_query(agent, query,
+- _reply_handle=aname)
+- self.assertTrue(rc)
+-
+- # done. Now wait for async responses
+-
+- count = 0
+- while self.notifier.wait_for_work(3):
+- wi = self.console.get_next_workitem(timeout=0)
+- while wi is not None:
+- count += 1
+- self.assertTrue(wi.get_type() == WorkItem.QUERY_COMPLETE)
+- self.assertTrue(wi.get_handle() == "agent1" or
+- wi.get_handle() == "agent2")
+- reply = wi.get_params()
+- self.assertTrue(len(reply) == 1)
+- self.assertTrue(isinstance(reply[0], SchemaClassId))
+- self.assertTrue(reply[0].get_package_name() == "MyPackage")
+- self.assertTrue(reply[0].get_class_name() == "MyClass")
+- self.console.release_workitem(wi)
+- wi = self.console.get_next_workitem(timeout=0)
+-
+- self.assertTrue(count == 2)
+- self.console.destroy(10)
+-
+-
+-
+- def test_undescribed_objs(self):
+- # create console
+- # find agents
+- # asynchronous query for all non-schema objects
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- for aname in ["agent1", "agent2"]:
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- # send queries
+- query = QmfQuery.create_wildcard(QmfQuery.TARGET_OBJECT)
+- rc = self.console.do_query(agent, query, _reply_handle=aname)
+- self.assertTrue(rc)
+-
+- # done. Now wait for async responses
+-
+- count = 0
+- while self.notifier.wait_for_work(3):
+- wi = self.console.get_next_workitem(timeout=0)
+- while wi is not None:
+- count += 1
+- self.assertTrue(wi.get_type() == WorkItem.QUERY_COMPLETE)
+- self.assertTrue(wi.get_handle() == "agent1" or
+- wi.get_handle() == "agent2")
+- reply = wi.get_params()
+- self.assertTrue(len(reply) == 4)
+- self.assertTrue(isinstance(reply[0], qmf2.console.QmfConsoleData))
+- self.assertFalse(reply[0].is_described()) # no schema
+- self.console.release_workitem(wi)
+- wi = self.console.get_next_workitem(timeout=0)
+-
+- self.assertTrue(count == 2)
+- self.console.destroy(10)
+-
+-
+-
+- def test_described_objs(self):
+- # create console
+- # find agents
+- # asynchronous query for all schema-based objects
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- for aname in ["agent1", "agent2"]:
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- #
+- t_params = {QmfData.KEY_SCHEMA_ID: SchemaClassId("MyPackage", "MyClass")}
+- query = QmfQuery.create_wildcard(QmfQuery.TARGET_OBJECT, t_params)
+- #
+- rc = self.console.do_query(agent, query, _reply_handle=aname)
+- self.assertTrue(rc)
+-
+- # done. Now wait for async responses
+-
+- count = 0
+- while self.notifier.wait_for_work(3):
+- wi = self.console.get_next_workitem(timeout=0)
+- while wi is not None:
+- count += 1
+- self.assertTrue(wi.get_type() == WorkItem.QUERY_COMPLETE)
+- self.assertTrue(wi.get_handle() == "agent1" or
+- wi.get_handle() == "agent2")
+- reply = wi.get_params()
+- self.assertTrue(len(reply) == 3)
+- self.assertTrue(isinstance(reply[0], qmf2.console.QmfConsoleData))
+- self.assertTrue(reply[0].is_described()) # has schema
+- self.console.release_workitem(wi)
+- wi = self.console.get_next_workitem(timeout=0)
+-
+- self.assertTrue(count == 2)
+- # @todo test if the console has learned the corresponding schemas....
+- self.console.destroy(10)
+-
+-
+-
+- def test_all_schemas(self):
+- # create console
+- # find agents
+- # asynchronous query for all schemas
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- # test internal state using non-api calls:
+- # no schemas present yet
+- self.assertTrue(len(self.console._schema_cache) == 0)
+- # end test
+-
+- for aname in ["agent1", "agent2"]:
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- # send queries
+- query = QmfQuery.create_wildcard(QmfQuery.TARGET_SCHEMA)
+- rc = self.console.do_query(agent, query, _reply_handle=aname)
+- self.assertTrue(rc)
+-
+- # done. Now wait for async responses
+-
+- count = 0
+- while self.notifier.wait_for_work(3):
+- wi = self.console.get_next_workitem(timeout=0)
+- while wi is not None:
+- count += 1
+- self.assertTrue(wi.get_type() == WorkItem.QUERY_COMPLETE)
+- self.assertTrue(wi.get_handle() == "agent1" or
+- wi.get_handle() == "agent2")
+- reply = wi.get_params()
+- self.assertTrue(len(reply) == 1)
+- self.assertTrue(isinstance(reply[0], qmf2.common.SchemaObjectClass))
+- self.assertTrue(reply[0].get_class_id().get_package_name() == "MyPackage")
+- self.assertTrue(reply[0].get_class_id().get_class_name() == "MyClass")
+- self.console.release_workitem(wi)
+- wi = self.console.get_next_workitem(timeout=0)
+-
+- self.assertTrue(count == 2)
+-
+- # test internal state using non-api calls:
+- # schema has been learned
+- self.assertTrue(len(self.console._schema_cache) == 1)
+- # end test
+-
+- self.console.destroy(10)
+-
+-
+-
+- def test_query_expiration(self):
+- # create console
+- # find agents
+- # kill the agents
+- # send async query
+- # wait for & verify expiration
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=30)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- # find the agents
+- agents = []
+- for aname in ["agent1", "agent2"]:
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+- agents.append(agent)
+-
+- # now nuke the agents from orbit. It's the only way to be sure.
+-
+- self.agent1.stop_app()
+- self.agent1 = None
+- self.agent2.stop_app()
+- self.agent2 = None
+-
+- # now send queries to agents that no longer exist
+- for agent in agents:
+- query = QmfQuery.create_wildcard(QmfQuery.TARGET_SCHEMA)
+- rc = self.console.do_query(agent, query,
+- _reply_handle=agent.get_name(),
+- _timeout=2)
+- self.assertTrue(rc)
+-
+- # done. Now wait for async responses due to timeouts
+-
+- count = 0
+- while self.notifier.wait_for_work(3):
+- wi = self.console.get_next_workitem(timeout=0)
+- while wi is not None:
+- count += 1
+- self.assertTrue(wi.get_type() == WorkItem.QUERY_COMPLETE)
+- self.assertTrue(wi.get_handle() == "agent1" or
+- wi.get_handle() == "agent2")
+- reply = wi.get_params()
+- self.assertTrue(len(reply) == 0) # empty
+-
+- self.console.release_workitem(wi)
+- wi = self.console.get_next_workitem(timeout=0)
+-
+- self.assertTrue(count == 2)
+- self.console.destroy(10)
+Index: extras/qmf/src/py/qmf2/tests/events.py
+===================================================================
+--- extras/qmf/src/py/qmf2/tests/events.py (revision 1056407)
++++ extras/qmf/src/py/qmf2/tests/events.py (working copy)
+@@ -1,202 +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.
+-#
+-import unittest
+-import time
+-import datetime
+-import logging
+-from threading import Thread, Event
+-
+-import qpid.messaging
+-from qpid.harness import Skipped
+-from qmf2.common import (Notifier, SchemaObjectClass, SchemaClassId,
+- SchemaProperty, qmfTypes, SchemaMethod, QmfQuery,
+- QmfData, SchemaEventClass,
+- QmfEvent)
+-import qmf2.console
+-from qmf2.agent import(QmfAgentData, Agent)
+-
+-
+-class _testNotifier(Notifier):
+- def __init__(self):
+- self._event = Event()
+-
+- def indication(self):
+- # note: called by qmf daemon thread
+- self._event.set()
+-
+- def wait_for_work(self, timeout):
+- # note: called by application thread to wait
+- # for qmf to generate work
+- self._event.wait(timeout)
+- timed_out = self._event.isSet() == False
+- if not timed_out:
+- self._event.clear()
+- return True
+- return False
+-
+-
+-class _agentApp(Thread):
+- def __init__(self, name, broker_url, heartbeat):
+- Thread.__init__(self)
+- self.timeout = 3
+- self.broker_url = broker_url
+- self.notifier = _testNotifier()
+- self.agent = Agent(name,
+- _notifier=self.notifier,
+- heartbeat_interval=heartbeat)
+-
+- # Dynamically construct a management database
+-
+- _schema = SchemaEventClass(_classId=SchemaClassId("MyPackage",
+- "MyClass",
+- stype=SchemaClassId.TYPE_EVENT),
+- _desc="A test event schema")
+- # add properties
+- _schema.add_property( "prop-1", SchemaProperty(qmfTypes.TYPE_UINT8))
+- _schema.add_property( "prop-2", SchemaProperty(qmfTypes.TYPE_LSTR))
+-
+- # Add schema to Agent
+- self.schema = _schema
+- self.agent.register_object_class(_schema)
+-
+- self.running = False
+- self.ready = Event()
+-
+- def start_app(self):
+- self.running = True
+- self.start()
+- self.ready.wait(10)
+- if not self.ready.is_set():
+- raise Exception("Agent failed to connect to broker.")
+- # time.sleep(1)
+-
+- def stop_app(self):
+- self.running = False
+- # wake main thread
+- self.notifier.indication() # hmmm... collide with daemon???
+- self.join(self.timeout)
+- if self.isAlive():
+- raise Exception("AGENT DID NOT TERMINATE AS EXPECTED!!!")
+-
+- def run(self):
+- # broker_url = "user/passwd at hostname:port"
+- conn = qpid.messaging.Connection(self.broker_url)
+- try:
+- conn.open()
+- except qpid.messaging.ConnectError, e:
+- raise Skipped(e)
+-
+- self.agent.set_connection(conn)
+- self.ready.set()
+-
+- counter = 1
+- while self.running:
+- # post an event every second
+- event = QmfEvent.create(long(time.time() * 1000),
+- QmfEvent.SEV_WARNING,
+- {"prop-1": counter,
+- "prop-2": str(datetime.datetime.utcnow())},
+- _schema_id=self.schema.get_class_id())
+- counter += 1
+- self.agent.raise_event(event)
+- wi = self.agent.get_next_workitem(timeout=0)
+- while wi is not None:
+- logging.error("UNEXPECTED AGENT WORKITEM RECEIVED=%s" % wi.get_type())
+- self.agent.release_workitem(wi)
+- wi = self.agent.get_next_workitem(timeout=0)
+- self.notifier.wait_for_work(1)
+-
+- self.agent.remove_connection(self.timeout)
+- self.agent.destroy(self.timeout)
+-
+-
+-
+-class BaseTest(unittest.TestCase):
+- def configure(self, config):
+- self.config = config
+- self.broker = config.broker
+- self.defines = self.config.defines
+-
+- def setUp(self):
+- # one second agent indication interval
+- self.agent1 = _agentApp("agent1", self.broker, 1)
+- self.agent1.start_app()
+- self.agent2 = _agentApp("agent2", self.broker, 1)
+- self.agent2.start_app()
+-
+- def tearDown(self):
+- if self.agent1:
+- self.agent1.stop_app()
+- self.agent1 = None
+- if self.agent2:
+- self.agent2.stop_app()
+- self.agent2 = None
+-
+- def test_get_events(self):
+- # create console
+- # find agents
+-
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- try:
+- self.conn.open()
+- except qpid.messaging.ConnectError, e:
+- raise Skipped(e)
+-
+- self.console.add_connection(self.conn)
+-
+- # find the agents
+- for aname in ["agent1", "agent2"]:
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- # now wait for events
+- agent1_events = agent2_events = 0
+- wi = self.console.get_next_workitem(timeout=4)
+- while wi:
+- if wi.get_type() == wi.EVENT_RECEIVED:
+- event = wi.get_params().get("event")
+- self.assertTrue(isinstance(event, QmfEvent))
+- self.assertTrue(event.get_severity() == QmfEvent.SEV_WARNING)
+- self.assertTrue(event.get_value("prop-1") > 0)
+-
+- agent = wi.get_params().get("agent")
+- if not agent or not isinstance(agent, qmf2.console.Agent):
+- self.fail("Unexpected workitem from agent")
+- else:
+- if agent.get_name() == "agent1":
+- agent1_events += 1
+- elif agent.get_name() == "agent2":
+- agent2_events += 1
+- else:
+- self.fail("Unexpected agent name received: %s" %
+- agent.get_name())
+- if agent1_events and agent2_events:
+- break;
+-
+- wi = self.console.get_next_workitem(timeout=4)
+-
+- self.assertTrue(agent1_events > 0 and agent2_events > 0)
+-
+- self.console.destroy(10)
+-
+-
+-
+-
+Index: extras/qmf/src/py/qmf2/tests/agent_discovery.py
+===================================================================
+--- extras/qmf/src/py/qmf2/tests/agent_discovery.py (revision 1056407)
++++ extras/qmf/src/py/qmf2/tests/agent_discovery.py (working copy)
+@@ -1,464 +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.
+-#
+-import unittest
+-import logging
+-import time
+-from threading import Thread, Event
+-
+-import qpid.messaging
+-import qmf2.common
+-import qmf2.console
+-import qmf2.agent
+-
+-
+-class _testNotifier(qmf2.common.Notifier):
+- def __init__(self):
+- self._event = Event()
+-
+- def indication(self):
+- # note: called by qmf daemon thread
+- self._event.set()
+-
+- def wait_for_work(self, timeout):
+- # note: called by application thread to wait
+- # for qmf to generate work
+- self._event.wait(timeout)
+- timed_out = self._event.isSet() == False
+- if not timed_out:
+- self._event.clear()
+- return True
+- return False
+-
+-
+-class _agentApp(Thread):
+- def __init__(self, name, broker_url, heartbeat):
+- Thread.__init__(self)
+- self.timeout = 3
+- self.broker_url = broker_url
+- self.notifier = _testNotifier()
+- self.agent = qmf2.agent.Agent(name,
+- _notifier=self.notifier,
+- heartbeat_interval=heartbeat)
+- # No database needed for this test
+- self.running = False
+- self.ready = Event()
+-
+- def start_app(self):
+- self.running = True
+- self.start()
+- self.ready.wait(10)
+- if not self.ready.is_set():
+- raise Exception("Agent failed to connect to broker.")
+-
+- def stop_app(self):
+- self.running = False
+- # wake main thread
+- self.notifier.indication() # hmmm... collide with daemon???
+- self.join(10)
+- if self.isAlive():
+- raise Exception("AGENT DID NOT TERMINATE AS EXPECTED!!!")
+-
+- def run(self):
+- # Connect the agent to the broker,
+- # broker_url = "user/passwd at hostname:port"
+-
+- conn = qpid.messaging.Connection(self.broker_url)
+- conn.open()
+- self.agent.set_connection(conn)
+- self.ready.set()
+-
+- while self.running:
+- self.notifier.wait_for_work(None)
+- wi = self.agent.get_next_workitem(timeout=0)
+- while wi is not None:
+- logging.error("UNEXPECTED AGENT WORKITEM RECEIVED=%s" % wi.get_type())
+- self.agent.release_workitem(wi)
+- wi = self.agent.get_next_workitem(timeout=0)
+-
+- # done, cleanup agent
+- self.agent.remove_connection(self.timeout)
+- self.agent.destroy(self.timeout)
+-
+-
+-class BaseTest(unittest.TestCase):
+- def configure(self, config):
+- self.config = config
+- self.broker = config.broker
+- self.defines = self.config.defines
+-
+- def setUp(self):
+- # one second agent indication interval
+- self.agent_heartbeat = 1
+- self.agent1 = _agentApp("agent1", self.broker, self.agent_heartbeat)
+- self.agent1.start_app()
+- self.agent2 = _agentApp("agent2", self.broker, self.agent_heartbeat)
+- self.agent2.start_app()
+-
+- def tearDown(self):
+- if self.agent1:
+- self.agent1.stop_app()
+- self.agent1 = None
+- if self.agent2:
+- self.agent2.stop_app()
+- self.agent2 = None
+-
+- def test_discover_all(self):
+- """
+- create console
+- enable agent discovery
+- wait
+- expect agent add for agent1 and agent2
+- """
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+- self.console.enable_agent_discovery()
+-
+- agent1_found = agent2_found = False
+- wi = self.console.get_next_workitem(timeout=3)
+- while wi and not (agent1_found and agent2_found):
+- if wi.get_type() == wi.AGENT_ADDED:
+- agent = wi.get_params().get("agent")
+- if not agent or not isinstance(agent, qmf2.console.Agent):
+- self.fail("Unexpected workitem from agent")
+- else:
+- if agent.get_name() == "agent1":
+- agent1_found = True
+- elif agent.get_name() == "agent2":
+- agent2_found = True
+- else:
+- self.fail("Unexpected agent name received: %s" %
+- agent.get_name())
+- if agent1_found and agent2_found:
+- break;
+-
+- wi = self.console.get_next_workitem(timeout=3)
+-
+- self.assertTrue(agent1_found and agent2_found, "All agents not discovered")
+-
+- self.console.destroy(10)
+-
+-
+- def test_discover_one(self):
+- """
+- create console
+- enable agent discovery, filter for agent1 only
+- wait until timeout
+- expect agent add for agent1 only
+- """
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- query = qmf2.common.QmfQuery.create_predicate(
+- qmf2.common.QmfQuery.TARGET_AGENT,
+- [qmf2.common.QmfQuery.EQ, qmf2.common.QmfQuery.KEY_AGENT_NAME,
+- [qmf2.common.QmfQuery.QUOTE, "agent1"]])
+- self.console.enable_agent_discovery(query)
+-
+- agent1_found = agent2_found = False
+- wi = self.console.get_next_workitem(timeout=3)
+- while wi:
+- if wi.get_type() == wi.AGENT_ADDED:
+- agent = wi.get_params().get("agent")
+- if not agent or not isinstance(agent, qmf2.console.Agent):
+- self.fail("Unexpected workitem from agent")
+- else:
+- if agent.get_name() == "agent1":
+- agent1_found = True
+- elif agent.get_name() == "agent2":
+- agent2_found = True
+- else:
+- self.fail("Unexpected agent name received: %s" %
+- agent.get_name())
+-
+- wi = self.console.get_next_workitem(timeout=2)
+-
+- self.assertTrue(agent1_found and not agent2_found, "Unexpected agent discovered")
+-
+- self.console.destroy(10)
+-
+-
+- def test_heartbeat(self):
+- """
+- create console with 2 sec agent timeout
+- enable agent discovery, find all agents
+- stop agent1, expect timeout notification
+- stop agent2, expect timeout notification
+- """
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=2)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+- self.console.enable_agent_discovery()
+-
+- agent1_found = agent2_found = False
+- wi = self.console.get_next_workitem(timeout=4)
+- while wi and not (agent1_found and agent2_found):
+- if wi.get_type() == wi.AGENT_ADDED:
+- agent = wi.get_params().get("agent")
+- if not agent or not isinstance(agent, qmf2.console.Agent):
+- self.fail("Unexpected workitem from agent")
+- else:
+- if agent.get_name() == "agent1":
+- agent1_found = True
+- elif agent.get_name() == "agent2":
+- agent2_found = True
+- else:
+- self.fail("Unexpected agent name received: %s" %
+- agent.get_name())
+- if agent1_found and agent2_found:
+- break;
+-
+- wi = self.console.get_next_workitem(timeout=4)
+-
+- self.assertTrue(agent1_found and agent2_found, "All agents not discovered")
+-
+- # now kill agent1 and wait for expiration
+-
+- agent1 = self.agent1
+- self.agent1 = None
+- agent1.stop_app()
+-
+- wi = self.console.get_next_workitem(timeout=4)
+- while wi is not None:
+- if wi.get_type() == wi.AGENT_DELETED:
+- agent = wi.get_params().get("agent")
+- if not agent or not isinstance(agent, qmf2.console.Agent):
+- self.fail("Unexpected workitem from agent")
+- else:
+- if agent.get_name() == "agent1":
+- agent1_found = False
+- else:
+- self.fail("Unexpected agent_deleted received: %s" %
+- agent.get_name())
+- if not agent1_found:
+- break;
+-
+- wi = self.console.get_next_workitem(timeout=4)
+-
+- self.assertFalse(agent1_found, "agent1 did not delete!")
+-
+- # now kill agent2 and wait for expiration
+-
+- agent2 = self.agent2
+- self.agent2 = None
+- agent2.stop_app()
+-
+- wi = self.console.get_next_workitem(timeout=4)
+- while wi is not None:
+- if wi.get_type() == wi.AGENT_DELETED:
+- agent = wi.get_params().get("agent")
+- if not agent or not isinstance(agent, qmf2.console.Agent):
+- self.fail("Unexpected workitem from agent")
+- else:
+- if agent.get_name() == "agent2":
+- agent2_found = False
+- else:
+- self.fail("Unexpected agent_deleted received: %s" %
+- agent.get_name())
+- if not agent2_found:
+- break;
+-
+- wi = self.console.get_next_workitem(timeout=4)
+-
+- self.assertFalse(agent2_found, "agent2 did not delete!")
+-
+- self.console.destroy(10)
+-
+-
+- def test_find_agent(self):
+- """
+- create console
+- do not enable agent discovery
+- find agent1, expect success
+- find agent-none, expect failure
+- find agent2, expect success
+- """
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- agent1 = self.console.find_agent("agent1", timeout=3)
+- self.assertTrue(agent1 and agent1.get_name() == "agent1")
+-
+- no_agent = self.console.find_agent("agent-none", timeout=3)
+- self.assertTrue(no_agent == None)
+-
+- agent2 = self.console.find_agent("agent2", timeout=3)
+- self.assertTrue(agent2 and agent2.get_name() == "agent2")
+-
+- self.console.remove_connection(self.conn, 10)
+- self.console.destroy(10)
+-
+-
+- def test_heartbeat_x2(self):
+- """
+- create 2 consoles with 2 sec agent timeout
+- enable agent discovery, find all agents
+- stop agent1, expect timeout notification on both consoles
+- stop agent2, expect timeout notification on both consoles
+- """
+- console_count = 2
+- self.consoles = []
+- for i in range(console_count):
+- console = qmf2.console.Console("test-console-" + str(i),
+- notifier=_testNotifier(),
+- agent_timeout=2)
+- conn = qpid.messaging.Connection(self.broker)
+- conn.open()
+- console.add_connection(conn)
+- console.enable_agent_discovery()
+- self.consoles.append(console)
+-
+- # now wait for all consoles to discover all agents,
+- # agents send a heartbeat once a second
+- for console in self.consoles:
+- agent1_found = agent2_found = False
+- wi = console.get_next_workitem(timeout=2)
+- while wi and not (agent1_found and agent2_found):
+- if wi.get_type() == wi.AGENT_ADDED:
+- agent = wi.get_params().get("agent")
+- if not agent or not isinstance(agent, qmf2.console.Agent):
+- self.fail("Unexpected workitem from agent")
+- else:
+- if agent.get_name() == "agent1":
+- agent1_found = True
+- elif agent.get_name() == "agent2":
+- agent2_found = True
+- else:
+- self.fail("Unexpected agent name received: %s" %
+- agent.get_name())
+- if agent1_found and agent2_found:
+- break;
+- wi = console.get_next_workitem(timeout=2)
+-
+- self.assertTrue(agent1_found and agent2_found, "All agents not discovered")
+-
+- # now kill agent1 and wait for expiration
+-
+- agent1 = self.agent1
+- self.agent1 = None
+- agent1.stop_app()
+-
+- for console in self.consoles:
+- agent1_found = True
+- wi = console.get_next_workitem(timeout=4)
+- while wi is not None:
+- if wi.get_type() == wi.AGENT_DELETED:
+- agent = wi.get_params().get("agent")
+- if not agent or not isinstance(agent, qmf2.console.Agent):
+- self.fail("Unexpected workitem from agent")
+- else:
+- if agent.get_name() == "agent1":
+- agent1_found = False
+- break
+- else:
+- self.fail("Unexpected agent_deleted received: %s" %
+- agent.get_name())
+-
+- wi = console.get_next_workitem(timeout=4)
+-
+- self.assertFalse(agent1_found, "agent1 did not delete!")
+-
+- # now kill agent2 and wait for expiration
+-
+- agent2 = self.agent2
+- self.agent2 = None
+- agent2.stop_app()
+-
+- for console in self.consoles:
+- agent2_found = True
+- wi = console.get_next_workitem(timeout=4)
+- while wi is not None:
+- if wi.get_type() == wi.AGENT_DELETED:
+- agent = wi.get_params().get("agent")
+- if not agent or not isinstance(agent, qmf2.console.Agent):
+- self.fail("Unexpected workitem from agent")
+- else:
+- if agent.get_name() == "agent2":
+- agent2_found = False
+- break
+- else:
+- self.fail("Unexpected agent_deleted received: %s" %
+- agent.get_name())
+-
+- wi = console.get_next_workitem(timeout=4)
+-
+- self.assertFalse(agent2_found, "agent2 did not delete!")
+-
+-
+- for console in self.consoles:
+- console.destroy(10)
+-
+-
+- def test_find_agent_x2(self):
+- """
+- create 2 consoles, do not enable agent discovery
+- console-1: find agent1, expect success
+- console-2: find agent2, expect success
+- Verify console-1 does -not- know agent2
+- Verify console-2 does -not- know agent1
+- """
+- console_count = 2
+- self.consoles = []
+- for i in range(console_count):
+- console = qmf2.console.Console("test-console-" + str(i),
+- notifier=_testNotifier(),
+- agent_timeout=2)
+- conn = qpid.messaging.Connection(self.broker)
+- conn.open()
+- console.add_connection(conn)
+- self.consoles.append(console)
+-
+- agent1 = self.consoles[0].find_agent("agent1", timeout=3)
+- self.assertTrue(agent1 and agent1.get_name() == "agent1")
+-
+- agent2 = self.consoles[1].find_agent("agent2", timeout=3)
+- self.assertTrue(agent2 and agent2.get_name() == "agent2")
+-
+- # wait long enough for agent heartbeats to be sent...
+-
+- time.sleep(self.agent_heartbeat * 2)
+-
+- agents = self.consoles[0].get_agents()
+- self.assertTrue(len(agents) == 1 and agents[0].get_name() == "agent1")
+- agent1 = self.consoles[0].get_agent("agent1")
+- self.assertTrue(agent1 and agent1.get_name() == "agent1")
+-
+-
+- agents = self.consoles[1].get_agents()
+- self.assertTrue(len(agents) == 1 and agents[0].get_name() == "agent2")
+- agent2 = self.consoles[1].get_agent("agent2")
+- self.assertTrue(agent2 and agent2.get_name() == "agent2")
+-
+- # verify no new agents were learned
+-
+- for console in self.consoles:
+- console.destroy(10)
+-
+Index: extras/qmf/src/py/qmf2/tests/basic_query.py
+===================================================================
+--- extras/qmf/src/py/qmf2/tests/basic_query.py (revision 1056407)
++++ extras/qmf/src/py/qmf2/tests/basic_query.py (working copy)
+@@ -1,492 +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.
+-#
+-import unittest
+-import logging
+-from threading import Thread, Event
+-
+-import qpid.messaging
+-from qmf2.common import (Notifier, SchemaObjectClass, SchemaClassId,
+- SchemaProperty, qmfTypes, SchemaMethod, QmfQuery,
+- QmfData)
+-import qmf2.console
+-from qmf2.agent import(QmfAgentData, Agent)
+-
+-
+-class _testNotifier(Notifier):
+- def __init__(self):
+- self._event = Event()
+-
+- def indication(self):
+- # note: called by qmf daemon thread
+- self._event.set()
+-
+- def wait_for_work(self, timeout):
+- # note: called by application thread to wait
+- # for qmf to generate work
+- self._event.wait(timeout)
+- timed_out = self._event.isSet() == False
+- if not timed_out:
+- self._event.clear()
+- return True
+- return False
+-
+-
+-class _agentApp(Thread):
+- def __init__(self, name, broker_url, heartbeat):
+- Thread.__init__(self)
+- self.notifier = _testNotifier()
+- self.broker_url = broker_url
+- self.agent = Agent(name,
+- _notifier=self.notifier,
+- heartbeat_interval=heartbeat)
+-
+- # Dynamically construct a management database
+-
+- _schema = SchemaObjectClass( _classId=SchemaClassId("MyPackage", "MyClass"),
+- _desc="A test data schema",
+- _object_id_names=["index1", "index2"] )
+- # add properties
+- _schema.add_property( "index1", SchemaProperty(qmfTypes.TYPE_UINT8))
+- _schema.add_property( "index2", SchemaProperty(qmfTypes.TYPE_LSTR))
+-
+- # these two properties are statistics
+- _schema.add_property( "query_count", SchemaProperty(qmfTypes.TYPE_UINT32))
+- _schema.add_property( "method_call_count", SchemaProperty(qmfTypes.TYPE_UINT32))
+-
+- # These two properties can be set via the method call
+- _schema.add_property( "set_string", SchemaProperty(qmfTypes.TYPE_LSTR))
+- _schema.add_property( "set_int", SchemaProperty(qmfTypes.TYPE_UINT32))
+-
+- # add method
+- _meth = SchemaMethod( _desc="Method to set string and int in object." )
+- _meth.add_argument( "arg_int", SchemaProperty(qmfTypes.TYPE_UINT32) )
+- _meth.add_argument( "arg_str", SchemaProperty(qmfTypes.TYPE_LSTR) )
+- _schema.add_method( "set_meth", _meth )
+-
+- # Add schema to Agent
+-
+- self.agent.register_object_class(_schema)
+-
+- # instantiate managed data objects matching the schema
+-
+- _obj1 = QmfAgentData( self.agent, _schema=_schema,
+- _values={"index1":100, "index2":"a name"})
+- _obj1.set_value("set_string", "UNSET")
+- _obj1.set_value("set_int", 0)
+- _obj1.set_value("query_count", 0)
+- _obj1.set_value("method_call_count", 0)
+- self.agent.add_object( _obj1 )
+-
+- self.agent.add_object( QmfAgentData( self.agent, _schema=_schema,
+- _values={"index1":99,
+- "index2": "another name",
+- "set_string": "UNSET",
+- "set_int": 0,
+- "query_count": 0,
+- "method_call_count": 0} ))
+-
+- self.agent.add_object( QmfAgentData( self.agent, _schema=_schema,
+- _values={"index1":50,
+- "index2": "my name",
+- "set_string": "SET",
+- "set_int": 0,
+- "query_count": 0,
+- "method_call_count": 0} ))
+-
+-
+- # add an "unstructured" object to the Agent
+- _obj2 = QmfAgentData(self.agent, _object_id="01545")
+- _obj2.set_value("field1", "a value")
+- _obj2.set_value("field2", 2)
+- _obj2.set_value("field3", {"a":1, "map":2, "value":3})
+- _obj2.set_value("field4", ["a", "list", "value"])
+- _obj2.set_value("index1", 50)
+- self.agent.add_object(_obj2)
+-
+- _obj2 = QmfAgentData(self.agent, _object_id="01546")
+- _obj2.set_value("field1", "a value")
+- _obj2.set_value("field2", 3)
+- _obj2.set_value("field3", {"a":1, "map":2, "value":3})
+- _obj2.set_value("field4", ["a", "list", "value"])
+- _obj2.set_value("index1", 51)
+- self.agent.add_object(_obj2)
+-
+- _obj2 = QmfAgentData(self.agent, _object_id="01544")
+- _obj2.set_value("field1", "a value")
+- _obj2.set_value("field2", 4)
+- _obj2.set_value("field3", {"a":1, "map":2, "value":3})
+- _obj2.set_value("field4", ["a", "list", "value"])
+- _obj2.set_value("index1", 49)
+- self.agent.add_object(_obj2)
+-
+- _obj2 = QmfAgentData(self.agent, _object_id="01543")
+- _obj2.set_value("field1", "a value")
+- _obj2.set_value("field2", 4)
+- _obj2.set_value("field3", {"a":1, "map":2, "value":3})
+- _obj2.set_value("field4", ["a", "list", "value"])
+- _obj2.set_value("index1", 48)
+- self.agent.add_object(_obj2)
+-
+- self.running = False
+- self.ready = Event()
+-
+- def start_app(self):
+- self.running = True
+- self.start()
+- self.ready.wait(10)
+- if not self.ready.is_set():
+- raise Exception("Agent failed to connect to broker.")
+-
+- def stop_app(self):
+- self.running = False
+- # wake main thread
+- self.notifier.indication() # hmmm... collide with daemon???
+- self.join(10)
+- if self.isAlive():
+- raise Exception("AGENT DID NOT TERMINATE AS EXPECTED!!!")
+-
+- def run(self):
+- # broker_url = "user/passwd at hostname:port"
+- self.conn = qpid.messaging.Connection(self.broker_url)
+- self.conn.open()
+- self.agent.set_connection(self.conn)
+- self.ready.set()
+-
+- while self.running:
+- self.notifier.wait_for_work(None)
+- wi = self.agent.get_next_workitem(timeout=0)
+- while wi is not None:
+- logging.error("UNEXPECTED AGENT WORKITEM RECEIVED=%s" % wi.get_type())
+- self.agent.release_workitem(wi)
+- wi = self.agent.get_next_workitem(timeout=0)
+-
+- if self.conn:
+- self.agent.remove_connection(10)
+- self.agent.destroy(10)
+-
+-
+-
+-
+-class BaseTest(unittest.TestCase):
+- def configure(self, config):
+- self.config = config
+- self.broker = config.broker
+- self.defines = self.config.defines
+-
+- def setUp(self):
+- # one second agent indication interval
+- self.agent_heartbeat = 1
+- self.agent1 = _agentApp("agent1", self.broker, self.agent_heartbeat)
+- self.agent1.start_app()
+- self.agent2 = _agentApp("agent2", self.broker, self.agent_heartbeat)
+- self.agent2.start_app()
+-
+- def tearDown(self):
+- if self.agent1:
+- self.agent1.stop_app()
+- self.agent1 = None
+- if self.agent2:
+- self.agent2.stop_app()
+- self.agent2 = None
+-
+- def test_all_oids(self):
+- # create console
+- # find agents
+- # synchronous query for all schemas
+- # synchronous query for all objects per schema
+- # verify known object ids are returned
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- for aname in ["agent1", "agent2"]:
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- # first, find objects per schema
+- query = QmfQuery.create_wildcard(QmfQuery.TARGET_SCHEMA_ID)
+- sid_list = self.console.do_query(agent, query)
+- self.assertTrue(sid_list and len(sid_list) == 1)
+- for sid in sid_list:
+- t_params = {QmfData.KEY_SCHEMA_ID: sid}
+- query = QmfQuery.create_wildcard(QmfQuery.TARGET_OBJECT_ID,
+- _target_params=t_params)
+-
+- oid_list = self.console.do_query(agent, query)
+-
+- self.assertTrue(isinstance(oid_list, type([])),
+- "Unexpected return type")
+- self.assertTrue(len(oid_list) == 3, "Wrong count")
+- self.assertTrue('100a name' in oid_list)
+- self.assertTrue('99another name' in oid_list)
+- self.assertTrue('50my name' in oid_list)
+- self.assertTrue('01545' not in oid_list)
+-
+-
+- # now, find all unmanaged objects (no schema)
+- query = QmfQuery.create_wildcard(QmfQuery.TARGET_OBJECT_ID)
+- oid_list = self.console.do_query(agent, query)
+-
+- self.assertTrue(isinstance(oid_list, type([])),
+- "Unexpected return type")
+- self.assertTrue(len(oid_list) == 4, "Wrong count")
+- self.assertTrue('100a name' not in oid_list)
+- self.assertTrue('99another name' not in oid_list)
+- self.assertTrue('01545' in oid_list)
+- self.assertTrue('01544' in oid_list)
+- self.assertTrue('01543' in oid_list)
+- self.assertTrue('01546' in oid_list)
+-
+- self.console.destroy(10)
+-
+-
+- def test_direct_oids(self):
+- # create console
+- # find agents
+- # synchronous query for each objects
+- # verify objects and schemas are correct
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- for aname in ["agent1", "agent2"]:
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- # first, find objects per schema
+- query = QmfQuery.create_wildcard(QmfQuery.TARGET_SCHEMA_ID)
+- sid_list = self.console.do_query(agent, query)
+- self.assertTrue(sid_list and len(sid_list) == 1)
+-
+- for oid in ['100a name', '99another name']:
+- query = QmfQuery.create_id_object(oid, sid_list[0])
+- obj_list = self.console.do_query(agent, query)
+-
+- self.assertTrue(isinstance(obj_list, type([])),
+- "Unexpected return type")
+- self.assertTrue(len(obj_list) == 1)
+- obj = obj_list[0]
+- self.assertTrue(isinstance(obj, QmfData))
+- self.assertTrue(obj.get_object_id() == oid)
+- self.assertTrue(obj.get_schema_class_id() == sid_list[0])
+- schema_id = obj.get_schema_class_id()
+- self.assertTrue(isinstance(schema_id, SchemaClassId))
+- self.assertTrue(obj.is_described())
+-
+- # now find schema-less objects
+- for oid in ['01545']:
+- query = QmfQuery.create_id_object(oid)
+- obj_list = self.console.do_query(agent, query)
+-
+- self.assertTrue(isinstance(obj_list, type([])),
+- "Unexpected return type")
+- self.assertTrue(len(obj_list) == 1)
+- obj = obj_list[0]
+- self.assertTrue(isinstance(obj, QmfData))
+- self.assertTrue(obj.get_object_id() == oid)
+- self.assertFalse(obj.is_described())
+-
+- self.console.destroy(10)
+-
+-
+-
+- def test_packages(self):
+- # create console
+- # find agents
+- # synchronous query all package names
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- for aname in ["agent1", "agent2"]:
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- query = QmfQuery.create_wildcard(QmfQuery.TARGET_PACKAGES)
+- package_list = self.console.do_query(agent, query)
+- self.assertTrue(len(package_list) == 1)
+- self.assertTrue('MyPackage' in package_list)
+-
+-
+- self.console.destroy(10)
+-
+-
+-
+- def test_predicate_schema_id(self):
+- # create console
+- # find agents
+- # synchronous query for all schema by package name
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- for aname in ["agent1", "agent2"]:
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- query = QmfQuery.create_predicate(QmfQuery.TARGET_SCHEMA,
+- [QmfQuery.EQ,
+- SchemaClassId.KEY_PACKAGE,
+- [QmfQuery.QUOTE, "MyPackage"]])
+-
+- schema_list = self.console.do_query(agent, query)
+- self.assertTrue(len(schema_list))
+- for schema in schema_list:
+- self.assertTrue(schema.get_class_id().get_package_name() ==
+- "MyPackage")
+-
+-
+- self.console.destroy(10)
+-
+-
+-
+- def test_predicate_no_match(self):
+- # create console
+- # find agents
+- # synchronous query for all schema by package name
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- for aname in ["agent1", "agent2"]:
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- query = QmfQuery.create_predicate(QmfQuery.TARGET_SCHEMA,
+- [QmfQuery.EQ,
+- [QmfQuery.UNQUOTE, SchemaClassId.KEY_PACKAGE],
+- [QmfQuery.QUOTE, "No-Such-Package"]])
+-
+- schema_list = self.console.do_query(agent, query)
+- self.assertTrue(len(schema_list) == 0)
+-
+- self.console.destroy(10)
+-
+-
+- def test_predicate_match_string(self):
+- # create console
+- # find agents
+- # synchronous query for all objects with a value named
+- # set_string which is < or equal to "UNSET"
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- for aname in ["agent1", "agent2"]:
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- # get the schema id for MyPackage:MyClass schema
+- query = QmfQuery.create_predicate(QmfQuery.TARGET_SCHEMA_ID,
+- [QmfQuery.AND,
+- [QmfQuery.EQ, SchemaClassId.KEY_PACKAGE,
+- [QmfQuery.QUOTE, "MyPackage"]],
+- [QmfQuery.EQ, SchemaClassId.KEY_CLASS,
+- [QmfQuery.QUOTE, "MyClass"]]])
+- sid_list = self.console.do_query(agent, query)
+- self.assertTrue(len(sid_list) == 1)
+-
+- query = QmfQuery.create_predicate(QmfQuery.TARGET_OBJECT,
+- [QmfQuery.AND,
+- [QmfQuery.EXISTS, [QmfQuery.QUOTE, "set_string"]],
+- [QmfQuery.EQ, "set_string", [QmfQuery.QUOTE, "UNSET"]]],
+- _target_params={QmfData.KEY_SCHEMA_ID: sid_list[0]})
+- obj_list = self.console.do_query(agent, query)
+- self.assertTrue(len(obj_list) == 2)
+- for obj in obj_list:
+- self.assertTrue(obj.has_value("set_string"))
+- self.assertTrue(obj.get_value("set_string") == "UNSET")
+-
+- self.console.destroy(10)
+-
+-
+-
+- def test_predicate_match_integer(self):
+- # create console
+- # find agents
+- # synchronous query for all objects with a value named
+- # "index1" which is < or equal to various values
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- for aname in ["agent1", "agent2"]:
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- # Query the unmanaged (no schema) objects
+-
+- # == 50
+- query = QmfQuery.create_predicate(QmfQuery.TARGET_OBJECT,
+- [QmfQuery.AND,
+- [QmfQuery.EXISTS, [QmfQuery.QUOTE, "index1"]],
+- [QmfQuery.EQ, "index1", 50]])
+-
+- obj_list = self.console.do_query(agent, query)
+- self.assertTrue(len(obj_list) == 1)
+- self.assertTrue(obj_list[0].has_value("index1"))
+- self.assertTrue(obj_list[0].get_value("index1") == 50)
+-
+- # <= 50
+- query = QmfQuery.create_predicate(QmfQuery.TARGET_OBJECT,
+- [QmfQuery.AND,
+- [QmfQuery.EXISTS, [QmfQuery.QUOTE, "index1"]],
+- [QmfQuery.LE, "index1", 50]])
+-
+- obj_list = self.console.do_query(agent, query)
+- self.assertTrue(len(obj_list) == 3)
+- for obj in obj_list:
+- self.assertTrue(obj.has_value("index1"))
+- self.assertTrue(obj.get_value("index1") <= 50)
+-
+-
+- # > 50
+- query = QmfQuery.create_predicate(QmfQuery.TARGET_OBJECT,
+- [QmfQuery.AND,
+- [QmfQuery.EXISTS, [QmfQuery.QUOTE, "index1"]],
+- [QmfQuery.GT, "index1", 50]])
+-
+- obj_list = self.console.do_query(agent, query)
+- self.assertTrue(len(obj_list) == 1)
+- for obj in obj_list:
+- self.assertTrue(obj.has_value("index1"))
+- self.assertTrue(obj.get_value("index1") > 50)
+-
+- self.console.destroy(10)
+-
+Index: extras/qmf/src/py/qmf2/tests/subscriptions.py
+===================================================================
+--- extras/qmf/src/py/qmf2/tests/subscriptions.py (revision 1056407)
++++ extras/qmf/src/py/qmf2/tests/subscriptions.py (working copy)
+@@ -1,983 +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.
+-#
+-import unittest
+-import logging
+-import datetime
+-import time
+-from threading import Thread, Event
+-
+-import qpid.messaging
+-from qmf2.common import (Notifier, SchemaObjectClass, SchemaClassId,
+- SchemaProperty, qmfTypes, SchemaMethod, QmfQuery,
+- QmfData, WorkItem)
+-import qmf2.console
+-from qmf2.agent import(QmfAgentData, Agent)
+-
+-
+-class _testNotifier(Notifier):
+- def __init__(self):
+- self._event = Event()
+-
+- def indication(self):
+- # note: called by qmf daemon thread
+- self._event.set()
+-
+- def wait_for_work(self, timeout):
+- # note: called by application thread to wait
+- # for qmf to generate work
+- self._event.wait(timeout)
+- timed_out = self._event.isSet() == False
+- if not timed_out:
+- self._event.clear()
+- return True
+- return False
+-
+-
+-class _agentApp(Thread):
+- def __init__(self, name, broker_url, heartbeat):
+- Thread.__init__(self)
+- self.notifier = _testNotifier()
+- self.broker_url = broker_url
+- self.agent = Agent(name,
+- _notifier=self.notifier,
+- heartbeat_interval=heartbeat,
+- max_duration=10,
+- default_duration=7,
+- min_duration=5,
+- min_interval=1,
+- default_interval=2)
+-
+- # Management Database
+- # - two different schema packages,
+- # - two classes within one schema package
+- # - multiple objects per schema package+class
+- # - two "undescribed" objects
+-
+- # "package1/class1"
+-
+- _schema = SchemaObjectClass( _classId=SchemaClassId("package1", "class1"),
+- _desc="A test data schema - one",
+- _object_id_names=["key"] )
+-
+- _schema.add_property( "key", SchemaProperty(qmfTypes.TYPE_LSTR))
+-
+- # note: count1 is continuous, count2 is not
+- count1_prop = SchemaProperty.create(qmfTypes.TYPE_UINT32,
+- continuous=True)
+- _schema.add_property( "count1", count1_prop)
+- count2_prop = SchemaProperty.create(qmfTypes.TYPE_UINT32,
+- continuous=False)
+- _schema.add_property( "count2", SchemaProperty(qmfTypes.TYPE_UINT32))
+-
+- self.agent.register_object_class(_schema)
+-
+- _obj = QmfAgentData( self.agent,
+- _values={"key":"p1c1_key1"},
+- _schema=_schema)
+- _obj.set_value("count1", 0)
+- _obj.set_value("count2", 0)
+- self.agent.add_object( _obj )
+-
+- _obj = QmfAgentData( self.agent,
+- _values={"key":"p1c1_key2"},
+- _schema=_schema )
+- _obj.set_value("count1", 9)
+- _obj.set_value("count2", 10)
+- self.agent.add_object( _obj )
+-
+- # "package1/class2"
+-
+- _schema = SchemaObjectClass( _classId=SchemaClassId("package1", "class2"),
+- _desc="A test data schema - two",
+- _object_id_names=["name"] )
+- # add properties
+- _schema.add_property( "name", SchemaProperty(qmfTypes.TYPE_LSTR))
+- _schema.add_property( "string1", SchemaProperty(qmfTypes.TYPE_LSTR))
+-
+- self.agent.register_object_class(_schema)
+-
+- _obj = QmfAgentData( self.agent,
+- _values={"name":"p1c2_name1"},
+- _schema=_schema )
+- _obj.set_value("string1", "a data string")
+- self.agent.add_object( _obj )
+-
+- _obj = QmfAgentData( self.agent,
+- _values={"name":"p1c2_name2"},
+- _schema=_schema )
+- _obj.set_value("string1", "another data string")
+- self.agent.add_object( _obj )
+-
+-
+- # "package2/class1"
+-
+- _schema = SchemaObjectClass( _classId=SchemaClassId("package2", "class1"),
+- _desc="A test data schema - second package",
+- _object_id_names=["key"] )
+-
+- _schema.add_property( "key", SchemaProperty(qmfTypes.TYPE_LSTR))
+- _schema.add_property( "counter", SchemaProperty(qmfTypes.TYPE_UINT32))
+-
+- self.agent.register_object_class(_schema)
+-
+- _obj = QmfAgentData( self.agent,
+- _values={"key":"p2c1_key1"},
+- _schema=_schema )
+- _obj.set_value("counter", 0)
+- self.agent.add_object( _obj )
+-
+- _obj = QmfAgentData( self.agent,
+- _values={"key":"p2c1_key2"},
+- _schema=_schema )
+- _obj.set_value("counter", 2112)
+- self.agent.add_object( _obj )
+-
+-
+- # add two "unstructured" objects to the Agent
+-
+- _obj = QmfAgentData(self.agent, _object_id="undesc-1")
+- _obj.set_value("field1", "a value")
+- _obj.set_value("field2", 2)
+- _obj.set_value("field3", {"a":1, "map":2, "value":3})
+- _obj.set_value("field4", ["a", "list", "value"])
+- self.agent.add_object(_obj)
+-
+-
+- _obj = QmfAgentData(self.agent, _object_id="undesc-2")
+- _obj.set_value("key-1", "a value")
+- _obj.set_value("key-2", 2)
+- self.agent.add_object(_obj)
+-
+- self.running = False
+- self.ready = Event()
+-
+- def start_app(self):
+- self.running = True
+- self.start()
+- self.ready.wait(10)
+- if not self.ready.is_set():
+- raise Exception("Agent failed to connect to broker.")
+-
+- def stop_app(self):
+- self.running = False
+- self.notifier.indication() # hmmm... collide with daemon???
+- self.join(10)
+- if self.isAlive():
+- raise Exception("AGENT DID NOT TERMINATE AS EXPECTED!!!")
+-
+- def run(self):
+- # broker_url = "user/passwd at hostname:port"
+- self.conn = qpid.messaging.Connection(self.broker_url)
+- self.conn.open()
+- self.agent.set_connection(self.conn)
+- self.ready.set()
+-
+- while self.running:
+- self.notifier.wait_for_work(None)
+- wi = self.agent.get_next_workitem(timeout=0)
+- while wi is not None:
+- logging.error("UNEXPECTED AGENT WORKITEM RECEIVED=%s" % wi.get_type())
+- self.agent.release_workitem(wi)
+- wi = self.agent.get_next_workitem(timeout=0)
+-
+- if self.conn:
+- self.agent.remove_connection(10)
+- self.agent.destroy(10)
+-
+-
+-class BaseTest(unittest.TestCase):
+- agent_count = 5
+-
+- def configure(self, config):
+- self.config = config
+- self.broker = config.broker
+- self.defines = self.config.defines
+-
+- def setUp(self):
+- self.agents = []
+- for i in range(self.agent_count):
+- agent = _agentApp("agent-" + str(i), self.broker, 1)
+- agent.start_app()
+- self.agents.append(agent)
+- #print("!!!! STARTING TEST: %s" % datetime.datetime.utcnow())
+-
+- def tearDown(self):
+- #print("!!!! STOPPING TEST: %s" % datetime.datetime.utcnow())
+- for agent in self.agents:
+- if agent is not None:
+- agent.stop_app()
+-
+-
+- def test_sync_by_schema(self):
+- # create console
+- # find all agents
+- # subscribe to changes to any object in package1/class1
+- # should succeed - verify 1 publish
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- subscriptions = []
+- index = 0
+-
+- # query to match all objects in schema package1/class1
+- sid = SchemaClassId.create("package1", "class1")
+- t_params = {QmfData.KEY_SCHEMA_ID: sid}
+- query = QmfQuery.create_wildcard(QmfQuery.TARGET_OBJECT,
+- _target_params=t_params)
+- for agent_app in self.agents:
+- aname = agent_app.agent.get_name()
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- # now subscribe to agent
+-
+- sp = self.console.create_subscription(agent,
+- query,
+- index)
+- self.assertTrue(isinstance(sp, qmf2.console.SubscribeParams))
+- self.assertTrue(sp.succeeded())
+- self.assertTrue(sp.get_error() == None)
+- self.assertTrue(sp.get_duration() == 10)
+- self.assertTrue(sp.get_publish_interval() == 2)
+-
+- subscriptions.append([sp, 0])
+- index += 1
+-
+- # now wait for the (2 * interval) and count the updates
+- r_count = 0
+- while self.notifier.wait_for_work(4):
+- wi = self.console.get_next_workitem(timeout=0)
+- while wi is not None:
+- r_count += 1
+- self.assertTrue(wi.get_type() == WorkItem.SUBSCRIBE_INDICATION)
+- reply = wi.get_params()
+- self.assertTrue(isinstance(reply, type([])))
+- self.assertTrue(len(reply) == 2)
+- for obj in reply:
+- self.assertTrue(isinstance(obj, QmfData))
+- self.assertTrue(obj.get_object_id() == "p1c1_key2" or
+- obj.get_object_id() == "p1c1_key1")
+- sid = reply[0].get_schema_class_id()
+- self.assertTrue(isinstance(sid, SchemaClassId))
+- self.assertTrue(sid.get_package_name() == "package1")
+- self.assertTrue(sid.get_class_name() == "class1")
+-
+- self.assertTrue(wi.get_handle() < len(subscriptions))
+- subscriptions[wi.get_handle()][1] += 1
+-
+- self.console.release_workitem(wi)
+-
+- wi = self.console.get_next_workitem(timeout=0)
+-
+- # expect 1 publish per subscription
+- self.assertTrue(r_count == 5)
+- for ii in range(len(subscriptions)):
+- self.assertTrue(subscriptions[ii][1] == 1)
+-
+- self.console.destroy(10)
+-
+-
+- def test_sync_by_obj_id(self):
+- # create console
+- # find all agents
+- # subscribe to changes to any object in package1/class1
+- # should succeed
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- subscriptions = []
+- index = 0
+-
+- # query to match all objects in schema package1/class1
+- # sid = SchemaClassId.create("package1", "class1")
+- # t_params = {QmfData.KEY_SCHEMA_ID: sid}
+- query = QmfQuery.create_id_object("undesc-2")
+-
+- for agent_app in self.agents:
+- aname = agent_app.agent.get_name()
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- # now subscribe to agent
+-
+- sp = self.console.create_subscription(agent,
+- query,
+- index)
+- self.assertTrue(isinstance(sp, qmf2.console.SubscribeParams))
+- self.assertTrue(sp.succeeded())
+- self.assertTrue(sp.get_error() == None)
+-
+- subscriptions.append([sp, 0])
+- index += 1
+-
+- # now wait for all subscriptions to expire (2x interval w/o
+- # indications)
+- r_count = 0
+- while self.notifier.wait_for_work(4):
+- wi = self.console.get_next_workitem(timeout=0)
+- while wi is not None:
+- r_count += 1
+- self.assertTrue(wi.get_type() == WorkItem.SUBSCRIBE_INDICATION)
+- reply = wi.get_params()
+- self.assertTrue(isinstance(reply, type([])))
+- self.assertTrue(len(reply) == 1)
+- self.assertTrue(isinstance(reply[0], QmfData))
+- self.assertTrue(reply[0].get_object_id() == "undesc-2")
+- self.assertTrue(wi.get_handle() < len(subscriptions))
+- subscriptions[wi.get_handle()][1] += 1
+-
+- self.console.release_workitem(wi)
+-
+- wi = self.console.get_next_workitem(timeout=0)
+-
+- # expect 1 publish per subscription
+- self.assertTrue(r_count == 5)
+- for ii in range(len(subscriptions)):
+- self.assertTrue(subscriptions[ii][1] == 1)
+-
+- self.console.destroy(10)
+-
+-
+- def test_sync_by_obj_id_schema(self):
+- # create console
+- # find all agents
+- # subscribe to changes to any object in package1/class1
+- # should succeed
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- subscriptions = []
+- index = 0
+-
+- # query to match object "p2c1_key2" in schema package2/class1
+- sid = SchemaClassId.create("package2", "class1")
+- query = QmfQuery.create_id_object("p2c1_key2", sid)
+-
+- for agent_app in self.agents:
+- aname = agent_app.agent.get_name()
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- # now subscribe to agent
+-
+- sp = self.console.create_subscription(agent,
+- query,
+- index)
+- self.assertTrue(isinstance(sp, qmf2.console.SubscribeParams))
+- self.assertTrue(sp.succeeded())
+- self.assertTrue(sp.get_error() == None)
+-
+- subscriptions.append([sp, 0])
+- index += 1
+-
+- # now wait for all subscriptions to expire (2x interval w/o
+- # indications)
+- r_count = 0
+- while self.notifier.wait_for_work(4):
+- wi = self.console.get_next_workitem(timeout=0)
+- while wi is not None:
+- r_count += 1
+- self.assertTrue(wi.get_type() == WorkItem.SUBSCRIBE_INDICATION)
+- reply = wi.get_params()
+- self.assertTrue(isinstance(reply, type([])))
+- self.assertTrue(len(reply) == 1)
+- self.assertTrue(isinstance(reply[0], QmfData))
+- self.assertTrue(reply[0].get_object_id() == "p2c1_key2")
+- sid = reply[0].get_schema_class_id()
+- self.assertTrue(isinstance(sid, SchemaClassId))
+- self.assertTrue(sid.get_package_name() == "package2")
+- self.assertTrue(sid.get_class_name() == "class1")
+- self.assertTrue(wi.get_handle() < len(subscriptions))
+- subscriptions[wi.get_handle()][1] += 1
+-
+- self.console.release_workitem(wi)
+-
+- wi = self.console.get_next_workitem(timeout=0)
+-
+- # expect 1 publish per subscription
+- self.assertTrue(r_count == 5)
+- for ii in range(len(subscriptions)):
+- self.assertTrue(subscriptions[ii][1] == 1)
+-
+- self.console.destroy(10)
+-
+-
+-
+- def test_sync_refresh(self):
+- # create console
+- # find one agent
+- # subscribe to changes to any object in package1/class1
+- # after 3 data indications, refresh
+- # verify > 5 more data indications received
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- # query to match object "p1c1_key2" in schema package1/class1
+- sid = SchemaClassId.create("package1", "class1")
+- query = QmfQuery.create_id_object("p1c1_key2", sid)
+-
+- agent_app = self.agents[0]
+- aname = agent_app.agent.get_name()
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- # setup subscription on agent
+-
+- sp = self.console.create_subscription(agent,
+- query,
+- "my-handle")
+- self.assertTrue(isinstance(sp, qmf2.console.SubscribeParams))
+- self.assertTrue(sp.succeeded())
+- self.assertTrue(sp.get_error() == None)
+-
+- # refresh after three subscribe indications, count all
+- # indications to verify refresh worked
+- r_count = 0
+- while self.notifier.wait_for_work(4):
+- wi = self.console.get_next_workitem(timeout=0)
+- while wi is not None:
+- r_count += 1
+- self.assertTrue(wi.get_type() == WorkItem.SUBSCRIBE_INDICATION)
+- reply = wi.get_params()
+- self.assertTrue(isinstance(reply, type([])))
+- self.assertTrue(len(reply) == 1)
+- self.assertTrue(isinstance(reply[0], QmfData))
+- self.assertTrue(reply[0].get_object_id() == "p1c1_key2")
+- sid = reply[0].get_schema_class_id()
+- self.assertTrue(isinstance(sid, SchemaClassId))
+- self.assertTrue(sid.get_package_name() == "package1")
+- self.assertTrue(sid.get_class_name() == "class1")
+- self.assertTrue(wi.get_handle() == "my-handle")
+-
+- # count1 is continuous, touching it will force a
+- # publish on the interval
+- self.assertTrue(sid is not None)
+- test_obj = agent_app.agent.get_object("p1c1_key2", sid)
+- self.assertTrue(test_obj is not None)
+- test_obj.set_value("count1", r_count)
+-
+- self.console.release_workitem(wi)
+-
+- if r_count == 3:
+- rp = self.console.refresh_subscription(sp.get_subscription_id())
+- self.assertTrue(rp)
+-
+- wi = self.console.get_next_workitem(timeout=0)
+-
+- # expect 5 publish per subscription, more if refreshed
+- self.assertTrue(r_count > 5)
+-
+- self.console.destroy(10)
+-
+-
+-
+- def test_sync_cancel(self):
+- # create console
+- # find one agent
+- # subscribe to changes to any object in package1/class1
+- # after 2 data indications, cancel subscription
+- # verify < 5 data indications received
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- # query to match object "p1c1_key2" in schema package1/class1
+- sid = SchemaClassId.create("package1", "class1")
+- query = QmfQuery.create_id_object("p1c1_key2", sid)
+-
+- agent_app = self.agents[0]
+- aname = agent_app.agent.get_name()
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- # setup subscription on agent
+-
+- sp = self.console.create_subscription(agent,
+- query,
+- "my-handle")
+- self.assertTrue(isinstance(sp, qmf2.console.SubscribeParams))
+- self.assertTrue(sp.succeeded())
+- self.assertTrue(sp.get_error() == None)
+-
+- # refresh after three subscribe indications, count all
+- # indications to verify refresh worked
+- r_count = 0
+- while self.notifier.wait_for_work(4):
+- wi = self.console.get_next_workitem(timeout=0)
+- while wi is not None:
+- r_count += 1
+- self.assertTrue(wi.get_type() == WorkItem.SUBSCRIBE_INDICATION)
+- reply = wi.get_params()
+- self.assertTrue(isinstance(reply, type([])))
+- self.assertTrue(len(reply) == 1)
+- self.assertTrue(isinstance(reply[0], QmfData))
+- self.assertTrue(reply[0].get_object_id() == "p1c1_key2")
+- sid = reply[0].get_schema_class_id()
+- self.assertTrue(isinstance(sid, SchemaClassId))
+- self.assertTrue(sid.get_package_name() == "package1")
+- self.assertTrue(sid.get_class_name() == "class1")
+- self.assertTrue(wi.get_handle() == "my-handle")
+-
+- # count1 is continuous, touching it will force a
+- # publish on the interval
+- self.assertTrue(sid is not None)
+- test_obj = agent_app.agent.get_object("p1c1_key2", sid)
+- self.assertTrue(test_obj is not None)
+- test_obj.set_value("count1", r_count)
+-
+- self.console.release_workitem(wi)
+-
+- if r_count == 3:
+- self.console.cancel_subscription(sp.get_subscription_id())
+-
+- wi = self.console.get_next_workitem(timeout=0)
+-
+- # expect only 3 publish received before cancel
+- self.assertTrue(r_count == 3)
+-
+- self.console.destroy(10)
+-
+-
+- def test_async_by_obj_id_schema(self):
+- # create console
+- # find one agent
+- # async subscribe to changes to any object in package1/class1
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- # query to match object "p2c1_key2" in schema package2/class1
+- sid = SchemaClassId.create("package2", "class1")
+- query = QmfQuery.create_id_object("p2c1_key2", sid)
+-
+- agent_app = self.agents[0]
+- aname = agent_app.agent.get_name()
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- # setup subscription on agent
+-
+- rc = self.console.create_subscription(agent,
+- query,
+- "my-handle",
+- _blocking=False)
+- self.assertTrue(rc)
+-
+- r_count = 0
+- sp = None
+- while self.notifier.wait_for_work(4):
+- wi = self.console.get_next_workitem(timeout=0)
+- while wi is not None:
+- r_count += 1
+- if wi.get_type() == WorkItem.SUBSCRIBE_RESPONSE:
+- self.assertTrue(wi.get_handle() == "my-handle")
+- sp = wi.get_params()
+- self.assertTrue(isinstance(sp, qmf2.console.SubscribeParams))
+- self.assertTrue(sp.succeeded())
+- self.assertTrue(sp.get_error() == None)
+- else:
+- self.assertTrue(wi.get_type() ==
+- WorkItem.SUBSCRIBE_INDICATION)
+- # sp better be set up by now!
+- self.assertTrue(isinstance(sp, qmf2.console.SubscribeParams))
+- reply = wi.get_params()
+- self.assertTrue(isinstance(reply, type([])))
+- self.assertTrue(len(reply) == 1)
+- self.assertTrue(isinstance(reply[0], QmfData))
+- self.assertTrue(reply[0].get_object_id() == "p2c1_key2")
+- sid = reply[0].get_schema_class_id()
+- self.assertTrue(isinstance(sid, SchemaClassId))
+- self.assertTrue(sid.get_package_name() == "package2")
+- self.assertTrue(sid.get_class_name() == "class1")
+- self.assertTrue(wi.get_handle() == "my-handle")
+-
+- self.console.release_workitem(wi)
+-
+- wi = self.console.get_next_workitem(timeout=0)
+-
+- # one response + one publish = 2
+- self.assertTrue(r_count == 2)
+-
+- self.console.destroy(10)
+-
+- def test_async_refresh(self):
+- # create console
+- # find one agent
+- # async subscribe to changes to any object in package1/class1
+- # refresh after third data indication
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- # query to match object "p1c1_key2" in schema package1/class1
+- sid = SchemaClassId.create("package1", "class1")
+- query = QmfQuery.create_id_object("p1c1_key2", sid)
+-
+- agent_app = self.agents[0]
+- aname = agent_app.agent.get_name()
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- # setup subscription on agent
+-
+- rc = self.console.create_subscription(agent,
+- query,
+- "my-handle",
+- _blocking=False)
+- self.assertTrue(rc)
+-
+- # refresh after three subscribe indications, count all
+- # indications to verify refresh worked
+- r_count = 0
+- i_count = 0
+- sp = None
+- while self.notifier.wait_for_work(4):
+- wi = self.console.get_next_workitem(timeout=0)
+- while wi is not None:
+- r_count += 1
+- if wi.get_type() == WorkItem.SUBSCRIBE_RESPONSE:
+- self.assertTrue(wi.get_handle() == "my-handle")
+- sp = wi.get_params()
+- self.assertTrue(isinstance(sp, qmf2.console.SubscribeParams))
+- self.assertTrue(sp.succeeded())
+- self.assertTrue(sp.get_error() == None)
+- else:
+- self.assertTrue(wi.get_type() ==
+- WorkItem.SUBSCRIBE_INDICATION)
+- i_count += 1
+- # sp better be set up by now!
+- self.assertTrue(isinstance(sp, qmf2.console.SubscribeParams))
+- reply = wi.get_params()
+- self.assertTrue(isinstance(reply, type([])))
+- self.assertTrue(len(reply) == 1)
+- self.assertTrue(isinstance(reply[0], QmfData))
+- self.assertTrue(reply[0].get_object_id() == "p1c1_key2")
+- sid = reply[0].get_schema_class_id()
+- self.assertTrue(isinstance(sid, SchemaClassId))
+- self.assertTrue(sid.get_package_name() == "package1")
+- self.assertTrue(sid.get_class_name() == "class1")
+- self.assertTrue(wi.get_handle() == "my-handle")
+-
+- # count1 is continuous, touching it will force a
+- # publish on the interval
+- self.assertTrue(sid is not None)
+- test_obj = agent_app.agent.get_object("p1c1_key2", sid)
+- self.assertTrue(test_obj is not None)
+- test_obj.set_value("count1", r_count)
+-
+- if r_count == 4: # 3 data + 1 subscribe reply
+- rp = self.console.refresh_subscription(sp.get_subscription_id())
+- self.assertTrue(rp)
+-
+- self.console.release_workitem(wi)
+-
+- wi = self.console.get_next_workitem(timeout=0)
+-
+- # expect 5 publish per subscription, more if refreshed
+- self.assertTrue(sp is not None)
+- self.assertTrue(i_count > 5)
+-
+- self.console.destroy(10)
+-
+-
+- def test_async_cancel(self):
+- # create console
+- # find one agent
+- # async subscribe to changes to any object in package1/class1
+- # cancel after first data indication
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- # query to match object "p1c1_key2" in schema package1/class1
+- sid = SchemaClassId.create("package1", "class1")
+- query = QmfQuery.create_id_object("p1c1_key2", sid)
+-
+- agent_app = self.agents[0]
+- aname = agent_app.agent.get_name()
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- # setup subscription on agent
+-
+- rc = self.console.create_subscription(agent,
+- query,
+- "my-handle",
+- _blocking=False)
+- self.assertTrue(rc)
+-
+- r_count = 0
+- sp = None
+- while self.notifier.wait_for_work(4):
+- wi = self.console.get_next_workitem(timeout=0)
+- while wi is not None:
+- r_count += 1
+- if wi.get_type() == WorkItem.SUBSCRIBE_RESPONSE:
+- self.assertTrue(wi.get_handle() == "my-handle")
+- sp = wi.get_params()
+- self.assertTrue(isinstance(sp, qmf2.console.SubscribeParams))
+- self.assertTrue(sp.succeeded())
+- self.assertTrue(sp.get_error() == None)
+- else:
+- self.assertTrue(wi.get_type() ==
+- WorkItem.SUBSCRIBE_INDICATION)
+- # sp better be set up by now!
+- self.assertTrue(isinstance(sp, qmf2.console.SubscribeParams))
+- reply = wi.get_params()
+- self.assertTrue(isinstance(reply, type([])))
+- self.assertTrue(len(reply) == 1)
+- self.assertTrue(isinstance(reply[0], QmfData))
+- self.assertTrue(reply[0].get_object_id() == "p1c1_key2")
+- sid = reply[0].get_schema_class_id()
+- self.assertTrue(isinstance(sid, SchemaClassId))
+- self.assertTrue(sid.get_package_name() == "package1")
+- self.assertTrue(sid.get_class_name() == "class1")
+- self.assertTrue(wi.get_handle() == "my-handle")
+-
+- # count1 is continuous, touching it will force a
+- # publish on the interval
+- self.assertTrue(sid is not None)
+- test_obj = agent_app.agent.get_object("p1c1_key2", sid)
+- self.assertTrue(test_obj is not None)
+- test_obj.set_value("count1", r_count)
+-
+- if r_count == 3:
+- self.console.cancel_subscription(sp.get_subscription_id())
+-
+- self.console.release_workitem(wi)
+-
+- wi = self.console.get_next_workitem(timeout=0)
+-
+- # expect cancel after 3 replies
+- self.assertTrue(r_count == 3)
+-
+- self.console.destroy(10)
+-
+-
+-
+-
+- def test_sync_periodic_publish_continuous(self):
+- # create console
+- # find all agents
+- # subscribe to changes to any object in package1/class1
+- # should succeed - verify 1 publish
+- # Change continuous property on each publish,
+- # should only see 1 publish per interval
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- subscriptions = []
+- index = 0
+-
+- # query to match all objects in schema package1/class1
+- sid = SchemaClassId.create("package1", "class1")
+- t_params = {QmfData.KEY_SCHEMA_ID: sid}
+- query = QmfQuery.create_wildcard(QmfQuery.TARGET_OBJECT,
+- _target_params=t_params)
+- # find an agent
+- agent_app = self.agents[0]
+- aname = agent_app.agent.get_name()
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- # setup subscription on agent
+-
+- sp = self.console.create_subscription(agent,
+- query,
+- "some-handle")
+- self.assertTrue(isinstance(sp, qmf2.console.SubscribeParams))
+- self.assertTrue(sp.succeeded())
+- self.assertTrue(sp.get_error() == None)
+- self.assertTrue(sp.get_duration() == 10)
+- self.assertTrue(sp.get_publish_interval() == 2)
+-
+- # now wait for the (2 * interval) and count the updates
+- r_count = 0
+- sid = None
+- while self.notifier.wait_for_work(4):
+- wi = self.console.get_next_workitem(timeout=0)
+- while wi is not None:
+- r_count += 1
+- self.assertTrue(wi.get_type() == WorkItem.SUBSCRIBE_INDICATION)
+- self.assertTrue(wi.get_handle() == "some-handle")
+- if r_count == 1:
+- # first indication - returns all matching objects
+- reply = wi.get_params()
+- self.assertTrue(isinstance(reply, type([])))
+- self.assertTrue(len(reply) == 2)
+- for obj in reply:
+- self.assertTrue(isinstance(obj, QmfData))
+- self.assertTrue(obj.get_object_id() == "p1c1_key2" or
+- obj.get_object_id() == "p1c1_key1")
+- sid = obj.get_schema_class_id()
+- self.assertTrue(isinstance(sid, SchemaClassId))
+- self.assertTrue(sid.get_package_name() == "package1")
+- self.assertTrue(sid.get_class_name() == "class1")
+-
+- else:
+- # verify publish of modified object only!
+- reply = wi.get_params()
+- self.assertTrue(isinstance(reply, type([])))
+- self.assertTrue(len(reply) == 1)
+- obj = reply[0]
+- self.assertTrue(isinstance(obj, QmfData))
+- self.assertTrue(obj.get_object_id() == "p1c1_key2")
+- self.assertTrue(obj.get_value("count1") == r_count - 1)
+- # fail test if we receive more than expected
+- self.assertTrue(r_count < 10)
+-
+-
+- # now update one of the objects!
+- self.assertTrue(sid is not None)
+- test_obj = agent_app.agent.get_object("p1c1_key2", sid)
+- self.assertTrue(test_obj is not None)
+- test_obj.set_value("count1", r_count)
+-
+- self.console.release_workitem(wi)
+-
+- wi = self.console.get_next_workitem(timeout=0)
+-
+- # expect at most 1 publish per interval seen
+- self.assertTrue(r_count < 10)
+-
+- self.console.destroy(10)
+-
+-
+-
+-
+- def test_sync_periodic_publish_noncontinuous(self):
+- # create console, find agent
+- # subscribe to changes to any object in package1/class1
+- # should succeed - verify 1 publish
+- # Change noncontinuous property on each publish,
+- # should only see 1 publish per each update
+- self.notifier = _testNotifier()
+- self.console = qmf2.console.Console(notifier=self.notifier,
+- agent_timeout=3)
+- self.conn = qpid.messaging.Connection(self.broker)
+- self.conn.open()
+- self.console.add_connection(self.conn)
+-
+- subscriptions = []
+- index = 0
+-
+- # query to match all objects in schema package1/class1
+- sid = SchemaClassId.create("package1", "class1")
+- t_params = {QmfData.KEY_SCHEMA_ID: sid}
+- query = QmfQuery.create_wildcard(QmfQuery.TARGET_OBJECT,
+- _target_params=t_params)
+- # find an agent
+- agent_app = self.agents[0]
+- aname = agent_app.agent.get_name()
+- agent = self.console.find_agent(aname, timeout=3)
+- self.assertTrue(agent and agent.get_name() == aname)
+-
+- # setup subscription on agent
+-
+- sp = self.console.create_subscription(agent,
+- query,
+- "some-handle")
+- self.assertTrue(isinstance(sp, qmf2.console.SubscribeParams))
+- self.assertTrue(sp.succeeded())
+- self.assertTrue(sp.get_error() == None)
+- self.assertTrue(sp.get_duration() == 10)
+- self.assertTrue(sp.get_publish_interval() == 2)
+-
+- # now wait for the (2 * interval) and count the updates
+- r_count = 0
+- sid = None
+- while self.notifier.wait_for_work(4):
+- wi = self.console.get_next_workitem(timeout=0)
+- while wi is not None:
+- r_count += 1
+- self.assertTrue(wi.get_type() == WorkItem.SUBSCRIBE_INDICATION)
+- self.assertTrue(wi.get_handle() == "some-handle")
+- if r_count == 1:
+- # first indication - returns all matching objects
+- reply = wi.get_params()
+- self.assertTrue(isinstance(reply, type([])))
+- self.assertTrue(len(reply) == 2)
+- for obj in reply:
+- self.assertTrue(isinstance(obj, QmfData))
+- self.assertTrue(obj.get_object_id() == "p1c1_key2" or
+- obj.get_object_id() == "p1c1_key1")
+- sid = obj.get_schema_class_id()
+- self.assertTrue(isinstance(sid, SchemaClassId))
+- self.assertTrue(sid.get_package_name() == "package1")
+- self.assertTrue(sid.get_class_name() == "class1")
+-
+- else:
+- # verify publish of modified object only!
+- reply = wi.get_params()
+- self.assertTrue(isinstance(reply, type([])))
+- self.assertTrue(len(reply) == 1)
+- obj = reply[0]
+- self.assertTrue(isinstance(obj, QmfData))
+- self.assertTrue(obj.get_object_id() == "p1c1_key2")
+- self.assertTrue(obj.get_value("count2") == r_count - 1)
+- # fail test if we receive more than expected
+- self.assertTrue(r_count < 30)
+-
+-
+- # now update the noncontinuous field of one of the objects!
+- if r_count < 20:
+- self.assertTrue(sid is not None)
+- test_obj = agent_app.agent.get_object("p1c1_key2", sid)
+- self.assertTrue(test_obj is not None)
+- test_obj.set_value("count2", r_count)
+-
+- self.console.release_workitem(wi)
+-
+- wi = self.console.get_next_workitem(timeout=0)
+-
+- # expect at least 1 publish per update
+- self.assertTrue(r_count > 10)
+-
+- self.console.destroy(10)
+Index: extras/qmf/src/py/qmf2/agent.py
+===================================================================
+--- extras/qmf/src/py/qmf2/agent.py (revision 1056407)
++++ extras/qmf/src/py/qmf2/agent.py (working copy)
+@@ -1,1380 +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.
+-#
+-
+-import sys
+-import datetime
+-import time
+-import Queue
+-from logging import getLogger
+-from threading import Thread, RLock, currentThread, Event
+-from qpid.messaging import Connection, Message, Empty, SendError
+-from uuid import uuid4
+-from common import (OpCode, QmfQuery, ContentType, SchemaObjectClass,
+- QmfData, QmfAddress, SchemaClass, SchemaClassId, WorkItem,
+- SchemaMethod, timedelta_to_secs, QMF_APP_ID)
+-
+-# global flag that indicates which thread (if any) is
+-# running the agent notifier callback
+-_callback_thread=None
+-
+-log = getLogger("qmf")
+-trace = getLogger("qmf.agent")
+-
+-
+- ##==============================================================================
+- ## METHOD CALL
+- ##==============================================================================
+-
+-class _MethodCallHandle(object):
+- """
+- Private class used to hold context when handing off a method call to the
+- application. Given to the app in a WorkItem, provided to the agent when
+- method_response() is invoked.
+- """
+- def __init__(self, correlation_id, reply_to, meth_name, _oid=None,
+- _schema_id=None):
+- self.correlation_id = correlation_id
+- self.reply_to = reply_to
+- self.meth_name = meth_name
+- self.oid = _oid
+- self.schema_id = _schema_id
+-
+-class MethodCallParams(object):
+- """
+- """
+- def __init__(self, name, _oid=None, _schema_id=None, _in_args=None,
+- _user_id=None):
+- self._meth_name = name
+- self._oid = _oid
+- self._schema_id = _schema_id
+- self._in_args = _in_args
+- self._user_id = _user_id
+-
+- def get_name(self):
+- return self._meth_name
+-
+- def get_object_id(self):
+- return self._oid
+-
+- def get_schema_id(self):
+- return self._schema_id
+-
+- def get_args(self):
+- return self._in_args
+-
+- def get_user_id(self):
+- return self._user_id
+-
+-
+- ##==============================================================================
+- ## SUBSCRIPTIONS
+- ##==============================================================================
+-
+-
+-class _SubscriptionState(object):
+- """
+- An internally-managed subscription.
+- """
+- def __init__(self, reply_to, cid, query, interval, duration):
+- self.reply_to = reply_to
+- self.correlation_id = cid
+- self.query = query
+- self.interval = interval
+- self.duration = duration
+- now = datetime.datetime.utcnow()
+- self.next_update = now # do an immediate update
+- self.expiration = now + datetime.timedelta(seconds=duration)
+- self.last_update = None
+- self.id = 0
+-
+- def resubscribe(self, now, _duration=None):
+- if _duration is not None:
+- self.duration = _duration
+- self.expiration = now + datetime.timedelta(seconds=self.duration)
+-
+- def published(self, now):
+- self.next_update = now + datetime.timedelta(seconds=self.interval)
+- self.last_update = now
+-
+-
+- ##==============================================================================
+- ## AGENT
+- ##==============================================================================
+-
+-class Agent(Thread):
+- def __init__(self, name, _domain=None, _notifier=None, **options):
+- Thread.__init__(self)
+- self._running = False
+- self._ready = Event()
+-
+- self.name = str(name)
+- self._domain = _domain
+- self._address = QmfAddress.direct(self.name, self._domain)
+- self._notifier = _notifier
+-
+- # configurable parameters
+- #
+- self._heartbeat_interval = options.get("heartbeat_interval", 30)
+- self._capacity = options.get("capacity", 10)
+- self._default_duration = options.get("default_duration", 300)
+- self._max_duration = options.get("max_duration", 3600)
+- self._min_duration = options.get("min_duration", 10)
+- self._default_interval = options.get("default_interval", 30)
+- self._min_interval = options.get("min_interval", 5)
+-
+- # @todo: currently, max # of objects in a single reply message, would
+- # be better if it were max bytesize of per-msg content...
+- self._max_msg_size = options.get("max_msg_size", 0)
+-
+- self._conn = None
+- self._session = None
+- self._direct_receiver = None
+- self._topic_receiver = None
+- self._direct_sender = None
+- self._topic_sender = None
+-
+- self._lock = RLock()
+- self._packages = {}
+- self._schema_timestamp = long(0)
+- self._schema = {}
+- # _described_data holds QmfData objects that are associated with schema
+- # it is index by schema_id, object_id
+- self._described_data = {}
+- # _undescribed_data holds unstructured QmfData objects - these objects
+- # have no schema. it is indexed by object_id only.
+- self._undescribed_data = {}
+- self._work_q = Queue.Queue()
+- self._work_q_put = False
+- # subscriptions
+- self._subscription_id = long(time.time())
+- self._subscriptions = {}
+- self._next_subscribe_event = None
+-
+- # prevents multiple _wake_thread() calls
+- self._noop_pending = False
+-
+-
+- def destroy(self, timeout=None):
+- """
+- Must be called before the Agent is deleted.
+- Frees up all resources and shuts down all background threads.
+-
+- @type timeout: float
+- @param timeout: maximum time in seconds to wait for all background threads to terminate. Default: forever.
+- """
+- trace.debug("Destroying Agent %s" % self.name)
+- if self._conn:
+- self.remove_connection(timeout)
+- trace.debug("Agent Destroyed")
+-
+-
+- def get_name(self):
+- return self.name
+-
+- def set_connection(self, conn):
+- self._conn = conn
+- self._session = self._conn.session()
+-
+- # for messages directly addressed to me
+- self._direct_receiver = self._session.receiver(str(self._address) +
+- ";{create:always,"
+- " node:"
+- " {type:topic,"
+- " x-declare:"
+- " {type:direct}}}",
+- capacity=self._capacity)
+- trace.debug("my direct addr=%s" % self._direct_receiver.source)
+-
+- # for sending directly addressed messages.
+- self._direct_sender = self._session.sender(str(self._address.get_node()) +
+- ";{create:always,"
+- " node:"
+- " {type:topic,"
+- " x-declare:"
+- " {type:direct}}}")
+- trace.debug("my default direct send addr=%s" % self._direct_sender.target)
+-
+- # for receiving "broadcast" messages from consoles
+- default_addr = QmfAddress.topic(QmfAddress.SUBJECT_CONSOLE_IND + ".#",
+- self._domain)
+- self._topic_receiver = self._session.receiver(str(default_addr) +
+- ";{create:always,"
+- " node:"
+- " {type:topic}}",
+- capacity=self._capacity)
+- trace.debug("console.ind addr=%s" % self._topic_receiver.source)
+-
+- # for sending to topic subscribers
+- ind_addr = QmfAddress.topic(QmfAddress.SUBJECT_AGENT_IND,
+- self._domain)
+- self._topic_sender = self._session.sender(str(ind_addr) +
+- ";{create:always,"
+- " node:"
+- " {type:topic}}")
+- trace.debug("agent.ind addr=%s" % self._topic_sender.target)
+-
+- self._running = True
+- self.start()
+- self._ready.wait(10)
+- if not self._ready.isSet():
+- raise Exception("Agent managment thread failed to start.")
+-
+- def remove_connection(self, timeout=None):
+- # tell connection thread to shutdown
+- self._running = False
+- if self.isAlive():
+- # kick my thread to wake it up
+- self._wake_thread()
+- trace.debug("waiting for agent receiver thread to exit")
+- self.join(timeout)
+- if self.isAlive():
+- log.error( "Agent thread '%s' is hung..." % self.name)
+- self._direct_receiver.close()
+- self._direct_receiver = None
+- self._direct_sender.close()
+- self._direct_sender = None
+- self._topic_receiver.close()
+- self._topic_receiver = None
+- self._topic_sender.close()
+- self._topic_sender = None
+- self._session.close()
+- self._session = None
+- self._conn = None
+- trace.debug("agent connection removal complete")
+-
+- def register_object_class(self, schema):
+- """
+- Register an instance of a SchemaClass with this agent
+- """
+- # @todo: need to update subscriptions
+- # @todo: need to mark schema as "non-const"
+- if not isinstance(schema, SchemaClass):
+- raise TypeError("SchemaClass instance expected")
+-
+- classId = schema.get_class_id()
+- pname = classId.get_package_name()
+- cname = classId.get_class_name()
+- hstr = classId.get_hash_string()
+- if not hstr:
+- raise Exception("Schema hash is not set.")
+-
+- self._lock.acquire()
+- try:
+- if pname not in self._packages:
+- self._packages[pname] = [cname]
+- else:
+- if cname not in self._packages[pname]:
+- self._packages[pname].append(cname)
+- self._schema[classId] = schema
+- self._schema_timestamp = long(time.time() * 1000)
+- finally:
+- self._lock.release()
+-
+- def register_event_class(self, schema):
+- return self.register_object_class(schema)
+-
+- def raise_event(self, qmfEvent):
+- """
+- TBD
+- """
+- if not self._topic_sender:
+- raise Exception("No connection available")
+-
+- # @todo: should we validate against the schema?
+- msg = Message(id=QMF_APP_ID,
+- subject=QmfAddress.SUBJECT_AGENT_EVENT + "." +
+- qmfEvent.get_severity() + "." + self.name,
+- properties={"method":"indication",
+- "qmf.opcode":OpCode.data_ind,
+- "qmf.content": ContentType.event,
+- "qmf.agent":self.name},
+- content=[qmfEvent.map_encode()])
+- # TRACE
+- # log.error("!!! Agent %s sending Event (%s)" %
+- # (self.name, str(msg)))
+- self._topic_sender.send(msg)
+-
+- def add_object(self, data):
+- """
+- Register an instance of a QmfAgentData object.
+- """
+- # @todo: need to mark schema as "non-const"
+- if not isinstance(data, QmfAgentData):
+- raise TypeError("QmfAgentData instance expected")
+-
+- oid = data.get_object_id()
+- if not oid:
+- raise TypeError("No identifier assigned to QmfAgentData!")
+-
+- sid = data.get_schema_class_id()
+-
+- self._lock.acquire()
+- try:
+- if sid:
+- if sid not in self._described_data:
+- self._described_data[sid] = {oid: data}
+- else:
+- self._described_data[sid][oid] = data
+- else:
+- self._undescribed_data[oid] = data
+-
+- # does the new object match any subscriptions?
+- now = datetime.datetime.utcnow()
+- for sid,sub in self._subscriptions.iteritems():
+- if sub.query.evaluate(data):
+- # matched. Mark the subscription as needing to be
+- # serviced. The _publish() method will notice the new
+- # object and will publish it next time it runs.
+- sub.next_update = now
+- self._next_subscribe_event = None
+- # @todo: should we immediately publish?
+-
+- finally:
+- self._lock.release()
+-
+- def get_object(self, oid, schema_id):
+- data = None
+- self._lock.acquire()
+- try:
+- if schema_id:
+- data = self._described_data.get(schema_id)
+- if data:
+- data = data.get(oid)
+- else:
+- data = self._undescribed_data.get(oid)
+- finally:
+- self._lock.release()
+- return data
+-
+-
+- def method_response(self, handle, _out_args=None, _error=None):
+- """
+- """
+- if not isinstance(handle, _MethodCallHandle):
+- raise TypeError("Invalid handle passed to method_response!")
+-
+- _map = {SchemaMethod.KEY_NAME:handle.meth_name}
+- if handle.oid is not None:
+- _map[QmfData.KEY_OBJECT_ID] = handle.oid
+- if handle.schema_id is not None:
+- _map[QmfData.KEY_SCHEMA_ID] = handle.schema_id.map_encode()
+- if _out_args is not None:
+- _map[SchemaMethod.KEY_ARGUMENTS] = _out_args.copy()
+- if _error is not None:
+- if not isinstance(_error, QmfData):
+- raise TypeError("Invalid type for error - must be QmfData")
+- _map[SchemaMethod.KEY_ERROR] = _error.map_encode()
+-
+- msg = Message(id=QMF_APP_ID,
+- properties={"method":"response",
+- "qmf.opcode":OpCode.method_rsp},
+- content=_map)
+- msg.correlation_id = handle.correlation_id
+-
+- self._send_reply(msg, handle.reply_to)
+-
+- def get_workitem_count(self):
+- """
+- Returns the count of pending WorkItems that can be retrieved.
+- """
+- return self._work_q.qsize()
+-
+- def get_next_workitem(self, timeout=None):
+- """
+- Obtains the next pending work item, or None if none available.
+- """
+- try:
+- wi = self._work_q.get(True, timeout)
+- except Queue.Empty:
+- return None
+- return wi
+-
+- def release_workitem(self, wi):
+- """
+- Releases a WorkItem instance obtained by getNextWorkItem(). Called when
+- the application has finished processing the WorkItem.
+- """
+- pass
+-
+-
+- def run(self):
+- global _callback_thread
+- next_heartbeat = datetime.datetime.utcnow()
+- batch_limit = 10 # a guess
+-
+- self._ready.set()
+-
+- while self._running:
+-
+- #
+- # Process inbound messages
+- #
+- trace.debug("%s processing inbound messages..." % self.name)
+- for i in range(batch_limit):
+- try:
+- msg = self._topic_receiver.fetch(timeout=0)
+- except Empty:
+- break
+- # TRACE
+- # log.error("!!! Agent %s: msg on %s [%s]" %
+- # (self.name, self._topic_receiver.source, msg))
+- self._dispatch(msg, _direct=False)
+-
+- for i in range(batch_limit):
+- try:
+- msg = self._direct_receiver.fetch(timeout=0)
+- except Empty:
+- break
+- # TRACE
+- # log.error("!!! Agent %s: msg on %s [%s]" %
+- # (self.name, self._direct_receiver.source, msg))
+- self._dispatch(msg, _direct=True)
+-
+- #
+- # Send Heartbeat Notification
+- #
+- now = datetime.datetime.utcnow()
+- if now >= next_heartbeat:
+- trace.debug("%s sending heartbeat..." % self.name)
+- ind = Message(id=QMF_APP_ID,
+- subject=QmfAddress.SUBJECT_AGENT_HEARTBEAT,
+- properties={"method":"indication",
+- "qmf.opcode":OpCode.agent_heartbeat_ind,
+- "qmf.agent":self.name},
+- content=self._makeAgentInfoBody())
+- # TRACE
+- #log.error("!!! Agent %s sending Heartbeat (%s)" %
+- # (self.name, str(ind)))
+- self._topic_sender.send(ind)
+- trace.debug("Agent Indication Sent")
+- next_heartbeat = now + datetime.timedelta(seconds = self._heartbeat_interval)
+-
+- #
+- # Monitor Subscriptions
+- #
+- self._lock.acquire()
+- try:
+- now = datetime.datetime.utcnow()
+- if (self._next_subscribe_event is None or
+- now >= self._next_subscribe_event):
+- trace.debug("%s polling subscriptions..." % self.name)
+- self._next_subscribe_event = now + datetime.timedelta(seconds=
+- self._max_duration)
+- dead_ss = {}
+- for sid,ss in self._subscriptions.iteritems():
+- if now >= ss.expiration:
+- dead_ss[sid] = ss
+- continue
+- if now >= ss.next_update:
+- self._publish(ss)
+- next_timeout = min(ss.expiration, ss.next_update)
+- if next_timeout < self._next_subscribe_event:
+- self._next_subscribe_event = next_timeout
+-
+- for sid,ss in dead_ss.iteritems():
+- del self._subscriptions[sid]
+- self._unpublish(ss)
+- finally:
+- self._lock.release()
+-
+- #
+- # notify application of pending WorkItems
+- #
+- if self._work_q_put and self._notifier:
+- trace.debug("%s notifying application..." % self.name)
+- # new stuff on work queue, kick the the application...
+- self._work_q_put = False
+- _callback_thread = currentThread()
+- trace.debug("Calling agent notifier.indication")
+- self._notifier.indication()
+- _callback_thread = None
+-
+- #
+- # Sleep until messages arrive or something times out
+- #
+- now = datetime.datetime.utcnow()
+- next_timeout = next_heartbeat
+- self._lock.acquire()
+- try:
+- # the mailbox expire flag may be cleared by the
+- # app thread(s) in order to force an immediate publish
+- if self._next_subscribe_event is None:
+- next_timeout = now
+- elif self._next_subscribe_event < next_timeout:
+- next_timeout = self._next_subscribe_event
+- finally:
+- self._lock.release()
+-
+- timeout = timedelta_to_secs(next_timeout - now)
+-
+- if self._running and timeout > 0.0:
+- trace.debug("%s sleeping %s seconds..." % (self.name,
+- timeout))
+- try:
+- self._session.next_receiver(timeout=timeout)
+- except Empty:
+- pass
+-
+-
+- trace.debug("Shutting down Agent %s thread" % self.name)
+-
+- #
+- # Private:
+- #
+-
+- def _makeAgentInfoBody(self):
+- """
+- Create an agent indication message body identifying this agent
+- """
+- return QmfData.create({"_name": self.get_name(),
+- "_schema_timestamp": self._schema_timestamp}).map_encode()
+-
+- def _send_reply(self, msg, reply_to):
+- """
+- Send a reply message to the given reply_to address
+- """
+- if not isinstance(reply_to, QmfAddress):
+- try:
+- reply_to = QmfAddress.from_string(str(reply_to))
+- except ValueError:
+- log.error("Invalid reply-to address '%s'" % reply_to)
+-
+- msg.subject = reply_to.get_subject()
+-
+- try:
+- if reply_to.is_direct():
+- # TRACE
+- #log.error("!!! Agent %s direct REPLY-To:%s (%s)" %
+- # (self.name, str(reply_to), str(msg)))
+- self._direct_sender.send(msg)
+- else:
+- # TRACE
+- # log.error("!!! Agent %s topic REPLY-To:%s (%s)" %
+- # (self.name, str(reply_to), str(msg)))
+- self._topic_sender.send(msg)
+- trace.debug("reply msg sent to [%s]" % str(reply_to))
+- except SendError, e:
+- log.error("Failed to send reply msg '%s' (%s)" % (msg, str(e)))
+-
+- def _send_query_response(self, content_type, cid, reply_to, objects):
+- """
+- Send a response to a query, breaking the result into multiple
+- messages based on the agent's _max_msg_size config parameter
+- """
+-
+- total = len(objects)
+- if self._max_msg_size:
+- max_count = self._max_msg_size
+- else:
+- max_count = total
+-
+- start = 0
+- end = min(total, max_count)
+- # send partial response if too many objects present
+- while end < total:
+- m = Message(id=QMF_APP_ID,
+- properties={"method":"response",
+- "partial":None,
+- "qmf.opcode":OpCode.data_ind,
+- "qmf.content":content_type,
+- "qmf.agent":self.name},
+- correlation_id = cid,
+- content=objects[start:end])
+- self._send_reply(m, reply_to)
+- start = end
+- end = min(total, end + max_count)
+-
+- m = Message(id=QMF_APP_ID,
+- properties={"method":"response",
+- "qmf.opcode":OpCode.data_ind,
+- "qmf.content":content_type,
+- "qmf.agent":self.name},
+- correlation_id = cid,
+- content=objects[start:end])
+- self._send_reply(m, reply_to)
+-
+- def _dispatch(self, msg, _direct=False):
+- """
+- Process a message from a console.
+-
+- @param _direct: True if msg directly addressed to this agent.
+- """
+- trace.debug( "Message received from Console! [%s]" % msg )
+-
+- opcode = msg.properties.get("qmf.opcode")
+- if not opcode:
+- log.warning("Ignoring unrecognized message '%s'" % msg)
+- return
+- version = 2 # @todo: fix me
+- cmap = {}; props={}
+- if msg.content_type == "amqp/map":
+- cmap = msg.content
+- if msg.properties:
+- props = msg.properties
+-
+- if opcode == OpCode.agent_locate_req:
+- self._handleAgentLocateMsg( msg, cmap, props, version, _direct )
+- elif opcode == OpCode.query_req:
+- self._handleQueryMsg( msg, cmap, props, version, _direct )
+- elif opcode == OpCode.method_req:
+- self._handleMethodReqMsg(msg, cmap, props, version, _direct)
+- elif opcode == OpCode.subscribe_req:
+- self._handleSubscribeReqMsg(msg, cmap, props, version, _direct)
+- elif opcode == OpCode.subscribe_refresh_ind:
+- self._handleResubscribeReqMsg(msg, cmap, props, version, _direct)
+- elif opcode == OpCode.subscribe_cancel_ind:
+- self._handleUnsubscribeReqMsg(msg, cmap, props, version, _direct)
+- elif opcode == OpCode.noop:
+- self._noop_pending = False
+- trace.debug("No-op msg received.")
+- else:
+- log.warning("Ignoring message with unrecognized 'opcode' value: '%s'"
+- % opcode)
+-
+- def _handleAgentLocateMsg( self, msg, cmap, props, version, direct ):
+- """
+- Process a received agent-locate message
+- """
+- trace.debug("_handleAgentLocateMsg")
+-
+- reply = False
+- if props.get("method") == "request":
+- # if the message is addressed to me or wildcard, process it
+- if (msg.subject == "console.ind" or
+- msg.subject == "console.ind.locate" or
+- msg.subject == "console.ind.locate." + self.name):
+- pred = msg.content
+- if not pred:
+- reply = True
+- elif isinstance(pred, type([])):
+- # fake a QmfData containing my identifier for the query compare
+- query = QmfQuery.create_predicate(QmfQuery.TARGET_AGENT, pred)
+- tmpData = QmfData.create({QmfQuery.KEY_AGENT_NAME:
+- self.get_name()},
+- _object_id="my-name")
+- reply = query.evaluate(tmpData)
+-
+- if reply:
+- m = Message(id=QMF_APP_ID,
+- properties={"method":"response",
+- "qmf.opcode":OpCode.agent_locate_rsp},
+- content=self._makeAgentInfoBody())
+- m.correlation_id = msg.correlation_id
+- self._send_reply(m, msg.reply_to)
+- else:
+- trace.debug("agent-locate msg not mine - no reply sent")
+-
+-
+- def _handleQueryMsg(self, msg, cmap, props, version, _direct ):
+- """
+- Handle received query message
+- """
+- trace.debug("_handleQueryMsg")
+-
+- if "method" in props and props["method"] == "request":
+- if cmap:
+- try:
+- query = QmfQuery.from_map(cmap)
+- except TypeError:
+- log.error("Invalid Query format: '%s'" % str(cmap))
+- return
+- target = query.get_target()
+- if target == QmfQuery.TARGET_PACKAGES:
+- self._queryPackagesReply( msg, query )
+- elif target == QmfQuery.TARGET_SCHEMA_ID:
+- self._querySchemaReply( msg, query, _idOnly=True )
+- elif target == QmfQuery.TARGET_SCHEMA:
+- self._querySchemaReply( msg, query)
+- elif target == QmfQuery.TARGET_AGENT:
+- log.warning("!!! @todo: Query TARGET=AGENT TBD !!!")
+- elif target == QmfQuery.TARGET_OBJECT_ID:
+- self._queryDataReply(msg, query, _idOnly=True)
+- elif target == QmfQuery.TARGET_OBJECT:
+- self._queryDataReply(msg, query)
+- else:
+- log.warning("Unrecognized query target: '%s'" % str(target))
+-
+-
+-
+- def _handleMethodReqMsg(self, msg, cmap, props, version, _direct):
+- """
+- Process received Method Request
+- """
+- if "method" in props and props["method"] == "request":
+- mname = cmap.get(SchemaMethod.KEY_NAME)
+- if not mname:
+- log.warning("Invalid method call from '%s': no name"
+- % msg.reply_to)
+- return
+-
+- in_args = cmap.get(SchemaMethod.KEY_ARGUMENTS)
+- oid = cmap.get(QmfData.KEY_OBJECT_ID)
+- schema_id = cmap.get(QmfData.KEY_SCHEMA_ID)
+- if schema_id:
+- schema_id = SchemaClassId.from_map(schema_id)
+- handle = _MethodCallHandle(msg.correlation_id,
+- msg.reply_to,
+- mname,
+- oid, schema_id)
+- param = MethodCallParams( mname, oid, schema_id, in_args,
+- msg.user_id)
+-
+- # @todo: validate the method against the schema:
+- # if self._schema:
+- # # validate
+- # _in_args = _in_args.copy()
+- # ms = self._schema.get_method(name)
+- # if ms is None:
+- # raise ValueError("Method '%s' is undefined." % name)
+-
+- # for aname,prop in ms.get_arguments().iteritems():
+- # if aname not in _in_args:
+- # if prop.get_default():
+- # _in_args[aname] = prop.get_default()
+- # elif not prop.is_optional():
+- # raise ValueError("Method '%s' requires argument '%s'"
+- # % (name, aname))
+- # for aname in _in_args.iterkeys():
+- # prop = ms.get_argument(aname)
+- # if prop is None:
+- # raise ValueError("Method '%s' does not define argument"
+- # " '%s'" % (name, aname))
+- # if "I" not in prop.get_direction():
+- # raise ValueError("Method '%s' argument '%s' is not an"
+- # " input." % (name, aname))
+-
+- # # @todo check if value is correct (type, range, etc)
+-
+- self._work_q.put(WorkItem(WorkItem.METHOD_CALL, handle, param))
+- self._work_q_put = True
+-
+- def _handleSubscribeReqMsg(self, msg, cmap, props, version, _direct):
+- """
+- Process received Subscription Request
+- """
+- if "method" in props and props["method"] == "request":
+- query_map = cmap.get("_query")
+- interval = cmap.get("_interval")
+- duration = cmap.get("_duration")
+-
+- try:
+- query = QmfQuery.from_map(query_map)
+- except TypeError:
+- log.warning("Invalid query for subscription: %s" %
+- str(query_map))
+- return
+-
+- if isinstance(self, AgentExternal):
+- # param = SubscriptionParams(_ConsoleHandle(console_handle,
+- # msg.reply_to),
+- # query,
+- # interval,
+- # duration,
+- # msg.user_id)
+- # self._work_q.put(WorkItem(WorkItem.SUBSCRIBE_REQUEST,
+- # msg.correlation_id, param))
+- # self._work_q_put = True
+- log.error("External Subscription TBD")
+- return
+-
+- # validate the query - only specific objects, or
+- # objects wildcard, are currently supported.
+- if (query.get_target() != QmfQuery.TARGET_OBJECT or
+- (query.get_selector() == QmfQuery.PREDICATE and
+- query.get_predicate())):
+- log.error("Subscriptions only support (wildcard) Object"
+- " Queries.")
+- err = QmfData.create(
+- {"reason": "Unsupported Query type for subscription.",
+- "query": str(query.map_encode())})
+- m = Message(id=QMF_APP_ID,
+- properties={"method":"response",
+- "qmf.opcode":OpCode.subscribe_rsp},
+- correlation_id = msg.correlation_id,
+- content={"_error": err.map_encode()})
+- self._send_reply(m, msg.reply_to)
+- return
+-
+- if duration is None:
+- duration = self._default_duration
+- else:
+- try:
+- duration = float(duration)
+- if duration > self._max_duration:
+- duration = self._max_duration
+- elif duration < self._min_duration:
+- duration = self._min_duration
+- except:
+- log.warning("Bad duration value: %s" % str(msg))
+- duration = self._default_duration
+-
+- if interval is None:
+- interval = self._default_interval
+- else:
+- try:
+- interval = float(interval)
+- if interval < self._min_interval:
+- interval = self._min_interval
+- except:
+- log.warning("Bad interval value: %s" % str(msg))
+- interval = self._default_interval
+-
+- ss = _SubscriptionState(msg.reply_to,
+- msg.correlation_id,
+- query,
+- interval,
+- duration)
+- self._lock.acquire()
+- try:
+- sid = self._subscription_id
+- self._subscription_id += 1
+- ss.id = sid
+- self._subscriptions[sid] = ss
+- self._next_subscribe_event = None
+- finally:
+- self._lock.release()
+-
+- sr_map = {"_subscription_id": sid,
+- "_interval": interval,
+- "_duration": duration}
+- m = Message(id=QMF_APP_ID,
+- properties={"method":"response",
+- "qmf.opcode":OpCode.subscribe_rsp},
+- correlation_id = msg.correlation_id,
+- content=sr_map)
+- self._send_reply(m, msg.reply_to)
+-
+-
+-
+- def _handleResubscribeReqMsg(self, msg, cmap, props, version, _direct):
+- """
+- Process received Renew Subscription Request
+- """
+- if props.get("method") == "request":
+- sid = cmap.get("_subscription_id")
+- if not sid:
+- log.error("Invalid subscription refresh msg: %s" %
+- str(msg))
+- return
+-
+- self._lock.acquire()
+- try:
+- ss = self._subscriptions.get(sid)
+- if not ss:
+- log.error("Ignoring unknown subscription: %s" %
+- str(sid))
+- return
+- duration = cmap.get("_duration")
+- if duration is not None:
+- try:
+- duration = float(duration)
+- if duration > self._max_duration:
+- duration = self._max_duration
+- elif duration < self._min_duration:
+- duration = self._min_duration
+- except:
+- log.error("Bad duration value: %s" % str(msg))
+- duration = None # use existing duration
+-
+- ss.resubscribe(datetime.datetime.utcnow(), duration)
+-
+- new_duration = ss.duration
+- new_interval = ss.interval
+-
+- finally:
+- self._lock.release()
+-
+-
+- sr_map = {"_subscription_id": sid,
+- "_interval": new_interval,
+- "_duration": new_duration}
+- m = Message(id=QMF_APP_ID,
+- properties={"method":"response",
+- "qmf.opcode":OpCode.subscribe_rsp},
+- correlation_id = msg.correlation_id,
+- content=sr_map)
+- self._send_reply(m, msg.reply_to)
+-
+-
+- def _handleUnsubscribeReqMsg(self, msg, cmap, props, version, _direct):
+- """
+- Process received Cancel Subscription Request
+- """
+- if props.get("method") == "request":
+- sid = cmap.get("_subscription_id")
+- if not sid:
+- log.warning("No subscription id supplied: %s" % msg)
+- return
+-
+- self._lock.acquire()
+- try:
+- if sid in self._subscriptions:
+- dead_sub = self._subscriptions[sid]
+- del self._subscriptions[sid]
+- finally:
+- self._lock.release()
+-
+- self._unpublish(dead_sub)
+-
+-
+- def _queryPackagesReply(self, msg, query):
+- """
+- Run a query against the list of known packages
+- """
+- pnames = []
+- self._lock.acquire()
+- try:
+- for name in self._packages.iterkeys():
+- qmfData = QmfData.create({SchemaClassId.KEY_PACKAGE:name},
+- _object_id="_package")
+- if query.evaluate(qmfData):
+- pnames.append(name)
+-
+- self._send_query_response(ContentType.schema_package,
+- msg.correlation_id,
+- msg.reply_to,
+- pnames)
+- finally:
+- self._lock.release()
+-
+-
+- def _querySchemaReply( self, msg, query, _idOnly=False ):
+- """
+- """
+- schemas = []
+-
+- self._lock.acquire()
+- try:
+- # if querying for a specific schema, do a direct lookup
+- if query.get_selector() == QmfQuery.ID:
+- found = self._schema.get(query.get_id())
+- if found:
+- if _idOnly:
+- schemas.append(query.get_id().map_encode())
+- else:
+- schemas.append(found.map_encode())
+- else: # otherwise, evaluate all schema
+- for sid,val in self._schema.iteritems():
+- if query.evaluate(val):
+- if _idOnly:
+- schemas.append(sid.map_encode())
+- else:
+- schemas.append(val.map_encode())
+- if _idOnly:
+- msgkey = ContentType.schema_id
+- else:
+- msgkey = ContentType.schema_class
+-
+- self._send_query_response(msgkey,
+- msg.correlation_id,
+- msg.reply_to,
+- schemas)
+- finally:
+- self._lock.release()
+-
+-
+- def _queryDataReply( self, msg, query, _idOnly=False ):
+- """
+- """
+- # hold the (recursive) lock for the duration so the Agent
+- # won't send data that is currently being modified by the
+- # app.
+- self._lock.acquire()
+- try:
+- response = []
+- data_objs = self._queryData(query)
+- if _idOnly:
+- for obj in data_objs:
+- response.append(obj.get_object_id())
+- else:
+- for obj in data_objs:
+- response.append(obj.map_encode())
+-
+- if _idOnly:
+- msgkey = ContentType.object_id
+- else:
+- msgkey = ContentType.data
+-
+- self._send_query_response(msgkey,
+- msg.correlation_id,
+- msg.reply_to,
+- response)
+- finally:
+- self._lock.release()
+-
+-
+- def _queryData(self, query):
+- """
+- Return a list of QmfData objects that match a given query
+- """
+- data_objs = []
+- # extract optional schema_id from target params
+- sid = None
+- t_params = query.get_target_param()
+- if t_params:
+- sid = t_params.get(QmfData.KEY_SCHEMA_ID)
+-
+- self._lock.acquire()
+- try:
+- # if querying for a specific object, do a direct lookup
+- if query.get_selector() == QmfQuery.ID:
+- oid = query.get_id()
+- if sid and not sid.get_hash_string():
+- # wildcard schema_id match, check each schema
+- for name,db in self._described_data.iteritems():
+- if (name.get_class_name() == sid.get_class_name()
+- and name.get_package_name() == sid.get_package_name()):
+- found = db.get(oid)
+- if found:
+- data_objs.append(found)
+- else:
+- found = None
+- if sid:
+- db = self._described_data.get(sid)
+- if db:
+- found = db.get(oid)
+- else:
+- found = self._undescribed_data.get(oid)
+- if found:
+- data_objs.append(found)
+-
+- else: # otherwise, evaluate all data
+- if sid and not sid.get_hash_string():
+- # wildcard schema_id match, check each schema
+- for name,db in self._described_data.iteritems():
+- if (name.get_class_name() == sid.get_class_name()
+- and name.get_package_name() == sid.get_package_name()):
+- for oid,data in db.iteritems():
+- if query.evaluate(data):
+- data_objs.append(data)
+- else:
+- if sid:
+- db = self._described_data.get(sid)
+- else:
+- db = self._undescribed_data
+-
+- if db:
+- for oid,data in db.iteritems():
+- if query.evaluate(data):
+- data_objs.append(data)
+- finally:
+- self._lock.release()
+-
+- return data_objs
+-
+- def _publish(self, sub):
+- """ Publish a subscription.
+- """
+- response = []
+- now = datetime.datetime.utcnow()
+- objs = self._queryData(sub.query)
+- if objs:
+- for obj in objs:
+- if sub.id not in obj._subscriptions:
+- # new to subscription - publish it
+- obj._subscriptions[sub.id] = sub
+- response.append(obj.map_encode())
+- elif obj._dtime:
+- # obj._dtime is millisec since utc. Convert to datetime
+- utcdt = datetime.datetime.utcfromtimestamp(obj._dtime/1000.0)
+- if utcdt > sub.last_update:
+- response.append(obj.map_encode())
+- else:
+- # obj._utime is millisec since utc. Convert to datetime
+- utcdt = datetime.datetime.utcfromtimestamp(obj._utime/1000.0)
+- if utcdt > sub.last_update:
+- response.append(obj.map_encode())
+-
+- if response:
+- trace.debug("!!! %s publishing %s!!!" % (self.name, sub.correlation_id))
+- self._send_query_response( ContentType.data,
+- sub.correlation_id,
+- sub.reply_to,
+- response)
+- sub.published(now)
+-
+- def _unpublish(self, sub):
+- """ This subscription is about to be deleted, remove it from any
+- referencing objects.
+- """
+- objs = self._queryData(sub.query)
+- if objs:
+- for obj in objs:
+- if sub.id in obj._subscriptions:
+- del obj._subscriptions[sub.id]
+-
+-
+-
+- def _wake_thread(self):
+- """
+- Make the agent management thread loop wakeup from its next_receiver
+- sleep.
+- """
+- self._lock.acquire()
+- try:
+- if not self._noop_pending:
+- trace.debug("Sending noop to wake up [%s]" % self._address)
+- msg = Message(id=QMF_APP_ID,
+- subject=self.name,
+- properties={"method":"indication",
+- "qmf.opcode":OpCode.noop},
+- content={})
+- try:
+- self._direct_sender.send( msg, sync=True )
+- self._noop_pending = True
+- except SendError, e:
+- log.error(str(e))
+- finally:
+- self._lock.release()
+-
+-
+- ##==============================================================================
+- ## EXTERNAL DATABASE AGENT
+- ##==============================================================================
+-
+-class AgentExternal(Agent):
+- """
+- An Agent which uses an external management database.
+- """
+- def __init__(self, name, _domain=None, _notifier=None,
+- _heartbeat_interval=30, _max_msg_size=0, _capacity=10):
+- super(AgentExternal, self).__init__(name, _domain, _notifier,
+- _heartbeat_interval,
+- _max_msg_size, _capacity)
+- log.error("AgentExternal TBD")
+-
+-
+-
+- ##==============================================================================
+- ## DATA MODEL
+- ##==============================================================================
+-
+-
+-class QmfAgentData(QmfData):
+- """
+- A managed data object that is owned by an agent.
+- """
+-
+- def __init__(self, agent, _values={}, _subtypes={}, _tag=None,
+- _object_id=None, _schema=None):
+- schema_id = None
+- if _schema:
+- schema_id = _schema.get_class_id()
+-
+- if _object_id is None:
+- if not isinstance(_schema, SchemaObjectClass):
+- raise Exception("An object_id must be provided if the object"
+- "doesn't have an associated schema.")
+- ids = _schema.get_id_names()
+- if not ids:
+- raise Exception("Object must have an Id or a schema that"
+- " provides an Id")
+- _object_id = u""
+- for key in ids:
+- value = _values.get(key)
+- if value is None:
+- raise Exception("Object must have a value for key"
+- " attribute '%s'" % str(key))
+- try:
+- _object_id += unicode(value)
+- except:
+- raise Exception("Cannot create object_id from key"
+- " value '%s'" % str(value))
+-
+- # timestamp in millisec since epoch UTC
+- ctime = long(time.time() * 1000)
+- super(QmfAgentData, self).__init__(_values=_values, _subtypes=_subtypes,
+- _tag=_tag, _ctime=ctime,
+- _utime=ctime, _object_id=_object_id,
+- _schema_id=schema_id, _const=False)
+- self._agent = agent
+- self._validated = False
+- self._modified = True
+- self._subscriptions = {}
+-
+- def destroy(self):
+- self._dtime = long(time.time() * 1000)
+- self._touch()
+- # @todo: publish change
+-
+- def is_deleted(self):
+- return self._dtime == 0
+-
+- def set_value(self, _name, _value, _subType=None):
+- super(QmfAgentData, self).set_value(_name, _value, _subType)
+- self._utime = long(time.time() * 1000)
+- self._touch(_name)
+- # @todo: publish change
+-
+- def inc_value(self, name, delta=1):
+- """ add the delta to the property """
+- # @todo: need to take write-lock
+- val = self.get_value(name)
+- try:
+- val += delta
+- except:
+- raise
+- self.set_value(name, val)
+-
+- def dec_value(self, name, delta=1):
+- """ subtract the delta from the property """
+- # @todo: need to take write-lock
+- val = self.get_value(name)
+- try:
+- val -= delta
+- except:
+- raise
+- self.set_value(name, val)
+-
+- def validate(self):
+- """
+- Compares this object's data against the associated schema. Throws an
+- exception if the data does not conform to the schema.
+- """
+- props = self._schema.get_properties()
+- for name,val in props.iteritems():
+- # @todo validate: type compatible with amqp_type?
+- # @todo validate: primary keys have values
+- if name not in self._values:
+- if val._isOptional:
+- # ok not to be present, put in dummy value
+- # to simplify access
+- self._values[name] = None
+- else:
+- raise Exception("Required property '%s' not present." % name)
+- self._validated = True
+-
+- def _touch(self, field=None):
+- """
+- Mark this object as modified. Used to force a publish of this object
+- if on subscription.
+- """
+- now = datetime.datetime.utcnow()
+- publish = False
+- if field:
+- # if the named field is not continuous, mark any subscriptions as
+- # needing to be published.
+- sid = self.get_schema_class_id()
+- if sid:
+- self._agent._lock.acquire()
+- try:
+- schema = self._agent._schema.get(sid)
+- if schema:
+- prop = schema.get_property(field)
+- if prop and not prop.is_continuous():
+- for sid,sub in self._subscriptions.iteritems():
+- sub.next_update = now
+- publish = True
+- if publish:
+- self._agent._next_subscribe_event = None
+- self._agent._wake_thread()
+- finally:
+- self._agent._lock.release()
+-
+-
+-
+-################################################################################
+-################################################################################
+-################################################################################
+-################################################################################
+-
+-if __name__ == '__main__':
+- # static test cases - no message passing, just exercise API
+- import logging
+- from common import (AgentName, SchemaProperty, qmfTypes, SchemaEventClass)
+-
+- logging.getLogger().setLevel(logging.INFO)
+-
+- logging.info( "Create an Agent" )
+- _agent_name = AgentName("redhat.com", "agent", "tross")
+- _agent = Agent(str(_agent_name))
+-
+- logging.info( "Get agent name: '%s'" % _agent.get_name())
+-
+- logging.info( "Create SchemaObjectClass" )
+-
+- _schema = SchemaObjectClass(SchemaClassId("MyPackage", "MyClass"),
+- _desc="A test data schema",
+- _object_id_names=["index1", "index2"])
+- # add properties
+- _schema.add_property("index1", SchemaProperty(qmfTypes.TYPE_UINT8))
+- _schema.add_property("index2", SchemaProperty(qmfTypes.TYPE_LSTR))
+-
+- # these two properties are statistics
+- _schema.add_property("query_count", SchemaProperty(qmfTypes.TYPE_UINT32))
+- _schema.add_property("method_call_count", SchemaProperty(qmfTypes.TYPE_UINT32))
+- # These two properties can be set via the method call
+- _schema.add_property("set_string", SchemaProperty(qmfTypes.TYPE_LSTR))
+- _schema.add_property("set_int", SchemaProperty(qmfTypes.TYPE_UINT32))
+-
+- # add method
+- _meth = SchemaMethod(_desc="Method to set string and int in object." )
+- _meth.add_argument( "arg_int", SchemaProperty(qmfTypes.TYPE_UINT32) )
+- _meth.add_argument( "arg_str", SchemaProperty(qmfTypes.TYPE_LSTR) )
+- _schema.add_method( "set_meth", _meth )
+-
+- # Add schema to Agent
+-
+- print("Schema Map='%s'" % str(_schema.map_encode()))
+-
+- _agent.register_object_class(_schema)
+-
+- # instantiate managed data objects matching the schema
+-
+- logging.info( "Create QmfAgentData" )
+-
+- _obj = QmfAgentData( _agent, _schema=_schema )
+- _obj.set_value("index1", 100)
+- _obj.set_value("index2", "a name" )
+- _obj.set_value("set_string", "UNSET")
+- _obj.set_value("set_int", 0)
+- _obj.set_value("query_count", 0)
+- _obj.set_value("method_call_count", 0)
+-
+- print("Obj1 Map='%s'" % str(_obj.map_encode()))
+-
+- _agent.add_object( _obj )
+-
+- _obj = QmfAgentData( _agent,
+- _values={"index1":99,
+- "index2": "another name",
+- "set_string": "UNSET",
+- "set_int": 0,
+- "query_count": 0,
+- "method_call_count": 0},
+- _schema=_schema)
+-
+- print("Obj2 Map='%s'" % str(_obj.map_encode()))
+-
+- _agent.add_object(_obj)
+-
+- ##############
+-
+-
+-
+- logging.info( "Create SchemaEventClass" )
+-
+- _event = SchemaEventClass(SchemaClassId("MyPackage", "MyEvent",
+- stype=SchemaClassId.TYPE_EVENT),
+- _desc="A test data schema",
+- _props={"edata_1": SchemaProperty(qmfTypes.TYPE_UINT32)})
+- _event.add_property("edata_2", SchemaProperty(qmfTypes.TYPE_LSTR))
+-
+- print("Event Map='%s'" % str(_event.map_encode()))
+-
+- _agent.register_event_class(_event)
+Index: extras/qmf/src/py/qmf2/__init__.py
+===================================================================
+--- extras/qmf/src/py/qmf2/__init__.py (revision 1056407)
++++ extras/qmf/src/py/qmf2/__init__.py (working copy)
+@@ -1,18 +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.
+-#
+Index: extras/qmf/src/py/qmf2/console.py
+===================================================================
+--- extras/qmf/src/py/qmf2/console.py (revision 1056407)
++++ extras/qmf/src/py/qmf2/console.py (working copy)
+@@ -1,2626 +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.
+-#
+-import sys
+-import os
+-import platform
+-import time
+-import datetime
+-import Queue
+-from logging import getLogger
+-from threading import Thread, Event
+-from threading import RLock
+-from threading import currentThread
+-from threading import Condition
+-
+-from qpid.messaging import Connection, Message, Empty, SendError
+-
+-from common import (QMF_APP_ID, OpCode, QmfQuery, Notifier, ContentType,
+- QmfData, QmfAddress, SchemaClass, SchemaClassId,
+- SchemaEventClass, SchemaObjectClass, WorkItem,
+- SchemaMethod, QmfEvent, timedelta_to_secs)
+-
+-
+-# global flag that indicates which thread (if any) is
+-# running the console notifier callback
+-_callback_thread=None
+-
+-
+-log = getLogger("qmf")
+-trace = getLogger("qmf.console")
+-
+-
+-##==============================================================================
+-## Console Transaction Management
+-##
+-## At any given time, a console application may have multiple outstanding
+-## message transactions with agents. The following objects allow the console
+-## to track these outstanding transactions.
+-##==============================================================================
+-
+-
+-class _Mailbox(object):
+- """
+- Virtual base class for all Mailbox-like objects.
+- """
+- def __init__(self, console):
+- self.console = console
+- self.cid = 0
+- self.console._add_mailbox(self)
+-
+- def get_address(self):
+- return self.cid
+-
+- def deliver(self, data):
+- """
+- Invoked by Console Management thread when a message arrives for
+- this mailbox.
+- """
+- raise Exception("_Mailbox deliver() method must be provided")
+-
+- def destroy(self):
+- """
+- Release the mailbox. Once called, the mailbox should no longer be
+- referenced.
+- """
+- self.console._remove_mailbox(self.cid)
+-
+-
+-class _SyncMailbox(_Mailbox):
+- """
+- A simple mailbox that allows a consumer to wait for delivery of data.
+- """
+- def __init__(self, console):
+- """
+- Invoked by application thread.
+- """
+- super(_SyncMailbox, self).__init__(console)
+- self._cv = Condition()
+- self._data = []
+- self._waiting = False
+-
+- def deliver(self, data):
+- """
+- Drop data into the mailbox, waking any waiters if necessary.
+- Invoked by Console Management thread only.
+- """
+- self._cv.acquire()
+- try:
+- self._data.append(data)
+- # if was empty, notify waiters
+- if len(self._data) == 1:
+- self._cv.notify()
+- finally:
+- self._cv.release()
+-
+- def fetch(self, timeout=None):
+- """
+- Get one data item from a mailbox, with timeout.
+- Invoked by application thread.
+- """
+- self._cv.acquire()
+- try:
+- if len(self._data) == 0:
+- self._cv.wait(timeout)
+- if len(self._data):
+- return self._data.pop(0)
+- return None
+- finally:
+- self._cv.release()
+-
+-
+-class _AsyncMailbox(_Mailbox):
+- """
+- A Mailbox for asynchronous delivery, with a timeout value.
+- """
+- def __init__(self, console,
+- _timeout=None):
+- """
+- Invoked by application thread.
+- """
+- super(_AsyncMailbox, self).__init__(console)
+- self.console = console
+-
+- if _timeout is None:
+- _timeout = console._reply_timeout
+- self.expiration_date = (datetime.datetime.utcnow() +
+- datetime.timedelta(seconds=_timeout))
+- console._lock.acquire()
+- try:
+- console._async_mboxes[self.cid] = self
+- console._next_mbox_expire = None
+- finally:
+- console._lock.release()
+-
+- # now that an async mbox has been created, wake the
+- # console mgmt thread so it will know about the mbox expiration
+- # date (and adjust its idle sleep period correctly)
+-
+- console._wake_thread()
+-
+- def reset_timeout(self, _timeout=None):
+- """ Reset the expiration date for this mailbox.
+- """
+- if _timeout is None:
+- _timeout = self.console._reply_timeout
+- self.console._lock.acquire()
+- try:
+- self.expiration_date = (datetime.datetime.utcnow() +
+- datetime.timedelta(seconds=_timeout))
+- self.console._next_mbox_expire = None
+- finally:
+- self.console._lock.release()
+-
+- # wake the console mgmt thread so it will learn about the mbox
+- # expiration date (and adjust its idle sleep period correctly)
+-
+- self.console._wake_thread()
+-
+- def deliver(self, msg):
+- """
+- """
+- raise Exception("deliver() method must be provided")
+-
+- def expire(self):
+- raise Exception("expire() method must be provided")
+-
+-
+- def destroy(self):
+- self.console._lock.acquire()
+- try:
+- if self.cid in self.console._async_mboxes:
+- del self.console._async_mboxes[self.cid]
+- finally:
+- self.console._lock.release()
+- super(_AsyncMailbox, self).destroy()
+-
+-
+-
+-class _QueryMailbox(_AsyncMailbox):
+- """
+- A mailbox used for asynchronous query requests.
+- """
+- def __init__(self, console,
+- agent_name,
+- context,
+- target,
+- _timeout=None):
+- """
+- Invoked by application thread.
+- """
+- super(_QueryMailbox, self).__init__(console,
+- _timeout)
+- self.agent_name = agent_name
+- self.target = target
+- self.context = context
+- self.result = []
+-
+- def deliver(self, reply):
+- """
+- Process query response messages delivered to this mailbox.
+- Invoked by Console Management thread only.
+- """
+- trace.debug("Delivering to query mailbox (agent=%s)." % self.agent_name)
+- objects = reply.content
+- if isinstance(objects, type([])):
+- # convert from map to native types if needed
+- if self.target == QmfQuery.TARGET_SCHEMA_ID:
+- for sid_map in objects:
+- self.result.append(SchemaClassId.from_map(sid_map))
+-
+- elif self.target == QmfQuery.TARGET_SCHEMA:
+- for schema_map in objects:
+- # extract schema id, convert based on schema type
+- sid_map = schema_map.get(SchemaClass.KEY_SCHEMA_ID)
+- if sid_map:
+- sid = SchemaClassId.from_map(sid_map)
+- if sid:
+- if sid.get_type() == SchemaClassId.TYPE_DATA:
+- schema = SchemaObjectClass.from_map(schema_map)
+- else:
+- schema = SchemaEventClass.from_map(schema_map)
+- self.console._add_schema(schema) # add to schema cache
+- self.result.append(schema)
+-
+- elif self.target == QmfQuery.TARGET_OBJECT:
+- for obj_map in objects:
+- # @todo: need the agent name - ideally from the
+- # reply message iself.
+- agent = self.console.get_agent(self.agent_name)
+- if agent:
+- obj = QmfConsoleData(map_=obj_map, agent=agent)
+- # start fetch of schema if not known
+- sid = obj.get_schema_class_id()
+- if sid:
+- self.console._prefetch_schema(sid, agent)
+- self.result.append(obj)
+-
+-
+- else:
+- # no conversion needed.
+- self.result += objects
+-
+- if not "partial" in reply.properties:
+- # log.error("QUERY COMPLETE for %s" % str(self.context))
+- wi = WorkItem(WorkItem.QUERY_COMPLETE, self.context, self.result)
+- self.console._work_q.put(wi)
+- self.console._work_q_put = True
+-
+- self.destroy()
+-
+-
+- def expire(self):
+- trace.debug("Expiring query mailbox (agent=%s)." % self.agent_name)
+- # send along whatever (possibly none) has been received so far
+- wi = WorkItem(WorkItem.QUERY_COMPLETE, self.context, self.result)
+- self.console._work_q.put(wi)
+- self.console._work_q_put = True
+-
+- self.destroy()
+-
+-
+-
+-class _SchemaPrefetchMailbox(_AsyncMailbox):
+- """
+- Handles responses to schema fetches made by the console.
+- """
+- def __init__(self, console,
+- schema_id,
+- _timeout=None):
+- """
+- Invoked by application thread.
+- """
+- super(_SchemaPrefetchMailbox, self).__init__(console,
+- _timeout)
+- self.schema_id = schema_id
+-
+- def deliver(self, reply):
+- """
+- Process schema response messages.
+- """
+- trace.debug("Delivering schema mailbox (id=%s)." % self.schema_id)
+- done = False
+- schemas = reply.content
+- if schemas and isinstance(schemas, type([])):
+- for schema_map in schemas:
+- # extract schema id, convert based on schema type
+- sid_map = schema_map.get(SchemaClass.KEY_SCHEMA_ID)
+- if sid_map:
+- sid = SchemaClassId.from_map(sid_map)
+- if sid:
+- if sid.get_type() == SchemaClassId.TYPE_DATA:
+- schema = SchemaObjectClass.from_map(schema_map)
+- else:
+- schema = SchemaEventClass.from_map(schema_map)
+- self.console._add_schema(schema) # add to schema cache
+- self.destroy()
+-
+-
+- def expire(self):
+- trace.debug("Expiring schema mailbox (id=%s)." % self.schema_id)
+- self.destroy()
+-
+-
+-
+-class _MethodMailbox(_AsyncMailbox):
+- """
+- A mailbox used for asynchronous method requests.
+- """
+- def __init__(self, console,
+- context,
+- _timeout=None):
+- """
+- Invoked by application thread.
+- """
+- super(_MethodMailbox, self).__init__(console,
+- _timeout)
+- self.context = context
+-
+- def deliver(self, reply):
+- """
+- Process method response messages delivered to this mailbox.
+- Invoked by Console Management thread only.
+- """
+- trace.debug("Delivering to method mailbox.")
+- _map = reply.content
+- if not _map or not isinstance(_map, type({})):
+- log.error("Invalid method call reply message")
+- result = None
+- else:
+- error=_map.get(SchemaMethod.KEY_ERROR)
+- if error:
+- error = QmfData.from_map(error)
+- result = MethodResult(_error=error)
+- else:
+- result = MethodResult(_out_args=_map.get(SchemaMethod.KEY_ARGUMENTS))
+-
+- # create workitem
+- wi = WorkItem(WorkItem.METHOD_RESPONSE, self.context, result)
+- self.console._work_q.put(wi)
+- self.console._work_q_put = True
+-
+- self.destroy()
+-
+-
+- def expire(self):
+- """
+- The mailbox expired without receiving a reply.
+- Invoked by the Console Management thread only.
+- """
+- trace.debug("Expiring method mailbox.")
+- # send along an empty response
+- wi = WorkItem(WorkItem.METHOD_RESPONSE, self.context, None)
+- self.console._work_q.put(wi)
+- self.console._work_q_put = True
+-
+- self.destroy()
+-
+-
+-
+-class _SubscriptionMailbox(_AsyncMailbox):
+- """
+- A Mailbox for a single subscription. Allows only sychronous "subscribe"
+- and "refresh" requests.
+- """
+- def __init__(self, console, context, agent, duration, interval):
+- """
+- Invoked by application thread.
+- """
+- super(_SubscriptionMailbox, self).__init__(console, duration)
+- self.cv = Condition()
+- self.data = []
+- self.result = []
+- self.context = context
+- self.duration = duration
+- self.interval = interval
+- self.agent_name = agent.get_name()
+- self.agent_subscription_id = None # from agent
+-
+- def subscribe(self, query):
+- agent = self.console.get_agent(self.agent_name)
+- if not agent:
+- log.warning("subscribed failed - unknown agent '%s'" %
+- self.agent_name)
+- return False
+- try:
+- trace.debug("Sending Subscribe to Agent (%s)" % self.agent_name)
+- agent._send_subscribe_req(query, self.get_address(), self.interval,
+- self.duration)
+- except SendError, e:
+- log.error(str(e))
+- return False
+- return True
+-
+- def resubscribe(self):
+- agent = self.console.get_agent(self.agent_name)
+- if not agent:
+- log.warning("resubscribed failed - unknown agent '%s'",
+- self.agent_name)
+- return False
+- try:
+- trace.debug("Sending resubscribe to Agent %s", self.agent_name)
+- agent._send_resubscribe_req(self.get_address(),
+- self.agent_subscription_id)
+- except SendError, e:
+- log.error(str(e))
+- return False
+- return True
+-
+- def deliver(self, msg):
+- """
+- """
+- opcode = msg.properties.get("qmf.opcode")
+- if (opcode == OpCode.subscribe_rsp):
+-
+- error = msg.content.get("_error")
+- if error:
+- try:
+- e_map = QmfData.from_map(error)
+- except TypeError:
+- log.warning("Invalid QmfData map received: '%s'"
+- % str(error))
+- e_map = QmfData.create({"error":"Unknown error"})
+- sp = SubscribeParams(None, None, None, e_map)
+- else:
+- self.agent_subscription_id = msg.content.get("_subscription_id")
+- self.duration = msg.content.get("_duration", self.duration)
+- self.interval = msg.content.get("_interval", self.interval)
+- self.reset_timeout(self.duration)
+- sp = SubscribeParams(self.get_address(),
+- self.interval,
+- self.duration,
+- None)
+- self.cv.acquire()
+- try:
+- self.data.append(sp)
+- # if was empty, notify waiters
+- if len(self.data) == 1:
+- self.cv.notify()
+- finally:
+- self.cv.release()
+- return
+-
+- # else: data indication
+- agent_name = msg.properties.get("qmf.agent")
+- if not agent_name:
+- log.warning("Ignoring data_ind - no agent name given: %s" %
+- msg)
+- return
+- agent = self.console.get_agent(agent_name)
+- if not agent:
+- log.warning("Ignoring data_ind - unknown agent '%s'" %
+- agent_name)
+- return
+-
+- objects = msg.content
+- for obj_map in objects:
+- obj = QmfConsoleData(map_=obj_map, agent=agent)
+- # start fetch of schema if not known
+- sid = obj.get_schema_class_id()
+- if sid:
+- self.console._prefetch_schema(sid, agent)
+- self.result.append(obj)
+-
+- if not "partial" in msg.properties:
+- wi = WorkItem(WorkItem.SUBSCRIBE_INDICATION, self.context, self.result)
+- self.result = []
+- self.console._work_q.put(wi)
+- self.console._work_q_put = True
+-
+- def fetch(self, timeout=None):
+- """
+- Get one data item from a mailbox, with timeout.
+- Invoked by application thread.
+- """
+- self.cv.acquire()
+- try:
+- if len(self.data) == 0:
+- self.cv.wait(timeout)
+- if len(self.data):
+- return self.data.pop(0)
+- return None
+- finally:
+- self.cv.release()
+-
+- def expire(self):
+- """ The subscription expired.
+- """
+- self.destroy()
+-
+-
+-
+-
+-class _AsyncSubscriptionMailbox(_SubscriptionMailbox):
+- """
+- A Mailbox for a single subscription. Allows only asychronous "subscribe"
+- and "refresh" requests.
+- """
+- def __init__(self, console, context, agent, duration, interval):
+- """
+- Invoked by application thread.
+- """
+- super(_AsyncSubscriptionMailbox, self).__init__(console, context,
+- agent, duration,
+- interval)
+- self.subscribe_pending = False
+-
+- def subscribe(self, query, reply_timeout):
+- if super(_AsyncSubscriptionMailbox, self).subscribe(query):
+- self.subscribe_pending = True
+- self.reset_timeout(reply_timeout)
+- return True
+- return False
+-
+- def deliver(self, msg):
+- """
+- """
+- super(_AsyncSubscriptionMailbox, self).deliver(msg)
+- sp = self.fetch(0)
+- if sp and self.subscribe_pending:
+- wi = WorkItem(WorkItem.SUBSCRIBE_RESPONSE, self.context, sp)
+- self.console._work_q.put(wi)
+- self.console._work_q_put = True
+-
+- self.subscribe_pending = False
+-
+- if not sp.succeeded():
+- self.destroy()
+-
+-
+- def expire(self):
+- """ Either the subscription expired, or a request timedout.
+- """
+- if self.subscribe_pending:
+- wi = WorkItem(WorkItem.SUBSCRIBE_RESPONSE, self.context, None)
+- self.console._work_q.put(wi)
+- self.console._work_q_put = True
+- self.destroy()
+-
+-
+-##==============================================================================
+-## DATA MODEL
+-##==============================================================================
+-
+-
+-class QmfConsoleData(QmfData):
+- """
+- Console's representation of an managed QmfData instance.
+- """
+- def __init__(self, map_, agent):
+- super(QmfConsoleData, self).__init__(_map=map_,
+- _const=True)
+- self._agent = agent
+-
+- def get_timestamps(self):
+- """
+- Returns a list of timestamps describing the lifecycle of
+- the object. All timestamps are represented by the AMQP
+- timestamp type. [0] = time of last update from Agent,
+- [1] = creation timestamp
+- [2] = deletion timestamp, or zero if not
+- deleted.
+- """
+- return [self._utime, self._ctime, self._dtime]
+-
+- def get_create_time(self):
+- """
+- returns the creation timestamp
+- """
+- return self._ctime
+-
+- def get_update_time(self):
+- """
+- returns the update timestamp
+- """
+- return self._utime
+-
+- def get_delete_time(self):
+- """
+- returns the deletion timestamp, or zero if not yet deleted.
+- """
+- return self._dtime
+-
+- def is_deleted(self):
+- """
+- True if deletion timestamp not zero.
+- """
+- return self._dtime != long(0)
+-
+- def refresh(self, _reply_handle=None, _timeout=None):
+- """
+- request that the Agent update the value of this object's
+- contents.
+- """
+- if _reply_handle is not None:
+- log.error(" ASYNC REFRESH TBD!!!")
+- return None
+-
+- assert self._agent
+- assert self._agent._console
+-
+- if _timeout is None:
+- _timeout = self._agent._console._reply_timeout
+-
+- # create query to agent using this objects ID
+- query = QmfQuery.create_id_object(self.get_object_id(),
+- self.get_schema_class_id())
+- obj_list = self._agent._console.do_query(self._agent, query,
+- _timeout=_timeout)
+- if obj_list is None or len(obj_list) != 1:
+- return None
+-
+- self._update(obj_list[0])
+- return self
+-
+-
+- def invoke_method(self, name, _in_args={}, _reply_handle=None,
+- _timeout=None):
+- """
+- Invoke the named method on this object.
+- """
+- assert self._agent
+- assert self._agent._console
+-
+- oid = self.get_object_id()
+- if oid is None:
+- raise ValueError("Cannot invoke methods on unmanaged objects.")
+-
+- if _timeout is None:
+- _timeout = self._agent._console._reply_timeout
+-
+- if _reply_handle is not None:
+- mbox = _MethodMailbox(self._agent._console,
+- _reply_handle)
+- else:
+- mbox = _SyncMailbox(self._agent._console)
+- cid = mbox.get_address()
+-
+- _map = {self.KEY_OBJECT_ID:str(oid),
+- SchemaMethod.KEY_NAME:name}
+-
+- sid = self.get_schema_class_id()
+- if sid:
+- _map[self.KEY_SCHEMA_ID] = sid.map_encode()
+- if _in_args:
+- _map[SchemaMethod.KEY_ARGUMENTS] = _in_args
+-
+- trace.debug("Sending method req to Agent (%s)" % time.time())
+- try:
+- self._agent._send_method_req(_map, cid)
+- except SendError, e:
+- log.error(str(e))
+- mbox.destroy()
+- return None
+-
+- if _reply_handle is not None:
+- return True
+-
+- trace.debug("Waiting for response to method req (%s)" % _timeout)
+- replyMsg = mbox.fetch(_timeout)
+- mbox.destroy()
+-
+- if not replyMsg:
+- trace.debug("Agent method req wait timed-out.")
+- return None
+-
+- _map = replyMsg.content
+- if not _map or not isinstance(_map, type({})):
+- log.error("Invalid method call reply message")
+- return None
+-
+- error=_map.get(SchemaMethod.KEY_ERROR)
+- if error:
+- return MethodResult(_error=QmfData.from_map(error))
+- else:
+- return MethodResult(_out_args=_map.get(SchemaMethod.KEY_ARGUMENTS))
+-
+- def _update(self, newer):
+- super(QmfConsoleData,self).__init__(_values=newer._values, _subtypes=newer._subtypes,
+- _tag=newer._tag, _object_id=newer._object_id,
+- _ctime=newer._ctime, _utime=newer._utime,
+- _dtime=newer._dtime,
+- _schema_id=newer._schema_id, _const=True)
+-
+-class QmfLocalData(QmfData):
+- """
+- Console's representation of an unmanaged QmfData instance. There
+- is no remote agent associated with this instance. The Console has
+- full control over this instance.
+- """
+- def __init__(self, values, _subtypes={}, _tag=None, _object_id=None,
+- _schema=None):
+- # timestamp in millisec since epoch UTC
+- ctime = long(time.time() * 1000)
+- super(QmfLocalData, self).__init__(_values=values,
+- _subtypes=_subtypes, _tag=_tag,
+- _object_id=_object_id,
+- _schema=_schema, _ctime=ctime,
+- _utime=ctime, _const=False)
+-
+-
+-class Agent(object):
+- """
+- A local representation of a remote agent managed by this console.
+- """
+- def __init__(self, name, console):
+- """
+- @type name: string
+- @param name: uniquely identifies this agent in the AMQP domain.
+- """
+-
+- if not isinstance(console, Console):
+- raise TypeError("parameter must be an instance of class Console")
+-
+- self._name = name
+- self._address = QmfAddress.direct(name, console._domain)
+- self._console = console
+- self._sender = None
+- self._packages = {} # map of {package-name:[list of class-names], } for this agent
+- self._subscriptions = [] # list of active standing subscriptions for this agent
+- self._announce_timestamp = None # datetime when last announce received
+- trace.debug( "Created Agent with address: [%s]" % self._address )
+-
+-
+- def get_name(self):
+- return self._name
+-
+- def is_active(self):
+- return self._announce_timestamp != None
+-
+- def _send_msg(self, msg, correlation_id=None):
+- """
+- Low-level routine to asynchronously send a message to this agent.
+- """
+- msg.reply_to = str(self._console._address)
+- if correlation_id:
+- msg.correlation_id = str(correlation_id)
+- # TRACE
+- #log.error("!!! Console %s sending to agent %s (%s)" %
+- # (self._console._name, self._name, str(msg)))
+- self._sender.send(msg)
+- # return handle
+-
+- def get_packages(self):
+- """
+- Return a list of the names of all packages known to this agent.
+- """
+- return self._packages.keys()
+-
+- def get_classes(self):
+- """
+- Return a dictionary [key:class] of classes known to this agent.
+- """
+- return self._packages.copy()
+-
+- def get_objects(self, query, kwargs={}):
+- """
+- Return a list of objects that satisfy the given query.
+-
+- @type query: dict, or common.Query
+- @param query: filter for requested objects
+- @type kwargs: dict
+- @param kwargs: ??? used to build match selector and query ???
+- @rtype: list
+- @return: list of matching objects, or None.
+- """
+- pass
+-
+- def get_object(self, query, kwargs={}):
+- """
+- Get one object - query is expected to match only one object.
+- ??? Recommended: explicit timeout param, default None ???
+-
+- @type query: dict, or common.Query
+- @param query: filter for requested objects
+- @type kwargs: dict
+- @param kwargs: ??? used to build match selector and query ???
+- @rtype: qmfConsole.ObjectProxy
+- @return: one matching object, or none
+- """
+- pass
+-
+-
+- def create_subscription(self, query):
+- """
+- Factory for creating standing subscriptions based on a given query.
+-
+- @type query: common.Query object
+- @param query: determines the list of objects for which this subscription applies
+- @rtype: qmfConsole.Subscription
+- @returns: an object representing the standing subscription.
+- """
+- pass
+-
+-
+- def invoke_method(self, name, _in_args={}, _reply_handle=None,
+- _timeout=None):
+- """
+- Invoke the named method on this agent.
+- """
+- assert self._console
+-
+- if _timeout is None:
+- _timeout = self._console._reply_timeout
+-
+- if _reply_handle is not None:
+- mbox = _MethodMailbox(self._console,
+- _reply_handle)
+- else:
+- mbox = _SyncMailbox(self._console)
+- cid = mbox.get_address()
+-
+- _map = {SchemaMethod.KEY_NAME:name}
+- if _in_args:
+- _map[SchemaMethod.KEY_ARGUMENTS] = _in_args.copy()
+-
+- trace.debug("Sending method req to Agent (%s)" % time.time())
+- try:
+- self._send_method_req(_map, cid)
+- except SendError, e:
+- log.error(str(e))
+- mbox.destroy()
+- return None
+-
+- if _reply_handle is not None:
+- return True
+-
+- trace.debug("Waiting for response to method req (%s)" % _timeout)
+- replyMsg = mbox.fetch(_timeout)
+- mbox.destroy()
+-
+- if not replyMsg:
+- trace.debug("Agent method req wait timed-out.")
+- return None
+-
+- _map = replyMsg.content
+- if not _map or not isinstance(_map, type({})):
+- log.error("Invalid method call reply message")
+- return None
+-
+- return MethodResult(_out_args=_map.get(SchemaMethod.KEY_ARGUMENTS),
+- _error=_map.get(SchemaMethod.KEY_ERROR))
+-
+- def enable_events(self):
+- raise Exception("enable_events tbd")
+-
+- def disable_events(self):
+- raise Exception("disable_events tbd")
+-
+- def destroy(self):
+- raise Exception("destroy tbd")
+-
+- def __repr__(self):
+- return str(self._address)
+-
+- def __str__(self):
+- return self.__repr__()
+-
+- def _send_query(self, query, correlation_id=None):
+- """
+- """
+- msg = Message(id=QMF_APP_ID,
+- properties={"method":"request",
+- "qmf.opcode":OpCode.query_req},
+- content=query.map_encode())
+- self._send_msg( msg, correlation_id )
+-
+-
+- def _send_method_req(self, mr_map, correlation_id=None):
+- """
+- """
+- msg = Message(id=QMF_APP_ID,
+- properties={"method":"request",
+- "qmf.opcode":OpCode.method_req},
+- content=mr_map)
+- self._send_msg( msg, correlation_id )
+-
+- def _send_subscribe_req(self, query, correlation_id, _interval=None,
+- _lifetime=None):
+- """
+- """
+- sr_map = {"_query":query.map_encode()}
+- if _interval is not None:
+- sr_map["_interval"] = _interval
+- if _lifetime is not None:
+- sr_map["_duration"] = _lifetime
+-
+- msg = Message(id=QMF_APP_ID,
+- properties={"method":"request",
+- "qmf.opcode":OpCode.subscribe_req},
+- content=sr_map)
+- self._send_msg(msg, correlation_id)
+-
+-
+- def _send_resubscribe_req(self, correlation_id,
+- subscription_id):
+- """
+- """
+- sr_map = {"_subscription_id":subscription_id}
+-
+- msg = Message(id=QMF_APP_ID,
+- properties={"method":"request",
+- "qmf.opcode":OpCode.subscribe_refresh_ind},
+- content=sr_map)
+- self._send_msg(msg, correlation_id)
+-
+-
+- def _send_unsubscribe_ind(self, correlation_id, subscription_id):
+- """
+- """
+- sr_map = {"_subscription_id":subscription_id}
+-
+- msg = Message(id=QMF_APP_ID,
+- properties={"method":"request",
+- "qmf.opcode":OpCode.subscribe_cancel_ind},
+- content=sr_map)
+- self._send_msg(msg, correlation_id)
+-
+-
+- ##==============================================================================
+- ## METHOD CALL
+- ##==============================================================================
+-
+-class MethodResult(object):
+- def __init__(self, _out_args=None, _error=None):
+- self._error = _error
+- self._out_args = _out_args
+-
+- def succeeded(self):
+- return self._error is None
+-
+- def get_exception(self):
+- return self._error
+-
+- def get_arguments(self):
+- return self._out_args
+-
+- def get_argument(self, name):
+- arg = None
+- if self._out_args:
+- arg = self._out_args.get(name)
+- return arg
+-
+-
+-
+- ##==============================================================================
+- ## SUBSCRIPTION
+- ##==============================================================================
+-
+-class SubscribeParams(object):
+- """ Represents a standing subscription for this console.
+- """
+- def __init__(self, sid, interval, duration, _error=None):
+- self._sid = sid
+- self._interval = interval
+- self._duration = duration
+- self._error = _error
+-
+- def succeeded(self):
+- return self._error is None
+-
+- def get_error(self):
+- return self._error
+-
+- def get_subscription_id(self):
+- return self._sid
+-
+- def get_publish_interval(self):
+- return self._interval
+-
+- def get_duration(self):
+- return self._duration
+-
+-
+- ##==============================================================================
+- ## CONSOLE
+- ##==============================================================================
+-
+-
+-
+-
+-
+-
+-class Console(Thread):
+- """
+- A Console manages communications to a collection of agents on behalf of an application.
+- """
+- def __init__(self, name=None, _domain=None, notifier=None,
+- reply_timeout = 60,
+- # agent_timeout = 120,
+- agent_timeout = 60,
+- kwargs={}):
+- """
+- @type name: str
+- @param name: identifier for this console. Must be unique.
+- @type notifier: qmfConsole.Notifier
+- @param notifier: invoked when events arrive for processing.
+- @type kwargs: dict
+- @param kwargs: ??? Unused
+- """
+- Thread.__init__(self)
+- self._operational = False
+- self._ready = Event()
+-
+- if not name:
+- self._name = "qmfc-%s.%d" % (platform.node(), os.getpid())
+- else:
+- self._name = str(name)
+- self._domain = _domain
+- self._address = QmfAddress.direct(self._name, self._domain)
+- self._notifier = notifier
+- self._lock = RLock()
+- self._conn = None
+- self._session = None
+- # dict of "agent-direct-address":class Agent entries
+- self._agent_map = {}
+- self._direct_recvr = None
+- self._announce_recvr = None
+- self._locate_sender = None
+- self._schema_cache = {}
+- self._pending_schema_req = []
+- self._agent_discovery_filter = None
+- self._reply_timeout = reply_timeout
+- self._agent_timeout = agent_timeout
+- self._subscribe_timeout = 300 # @todo: parameterize
+- self._next_agent_expire = None
+- self._next_mbox_expire = None
+- # for passing WorkItems to the application
+- self._work_q = Queue.Queue()
+- self._work_q_put = False
+- # Correlation ID and mailbox storage
+- self._correlation_id = long(time.time()) # pseudo-randomize
+- self._post_office = {} # indexed by cid
+- self._async_mboxes = {} # indexed by cid, used to expire them
+-
+- def destroy(self, timeout=None):
+- """
+- Must be called before the Console is deleted.
+- Frees up all resources and shuts down all background threads.
+-
+- @type timeout: float
+- @param timeout: maximum time in seconds to wait for all background threads to terminate. Default: forever.
+- """
+- trace.debug("Destroying Console...")
+- if self._conn:
+- self.remove_connection(self._conn, timeout)
+- trace.debug("Console Destroyed")
+-
+- def add_connection(self, conn):
+- """
+- Add a AMQP connection to the console. The console will setup a session over the
+- connection. The console will then broadcast an Agent Locate Indication over
+- the session in order to discover present agents.
+-
+- @type conn: qpid.messaging.Connection
+- @param conn: the connection to the AMQP messaging infrastructure.
+- """
+- if self._conn:
+- raise Exception( "Multiple connections per Console not supported." );
+- self._conn = conn
+- self._session = conn.session(name=self._name)
+-
+- # for messages directly addressed to me
+- self._direct_recvr = self._session.receiver(str(self._address) +
+- ";{create:always,"
+- " node:"
+- " {type:topic,"
+- " x-declare:"
+- " {type:direct}}}",
+- capacity=1)
+- trace.debug("my direct addr=%s" % self._direct_recvr.source)
+-
+- self._direct_sender = self._session.sender(str(self._address.get_node()) +
+- ";{create:always,"
+- " node:"
+- " {type:topic,"
+- " x-declare:"
+- " {type:direct}}}")
+- trace.debug("my direct sender=%s" % self._direct_sender.target)
+-
+- # for receiving "broadcast" messages from agents
+- default_addr = QmfAddress.topic(QmfAddress.SUBJECT_AGENT_IND + ".#",
+- self._domain)
+- self._topic_recvr = self._session.receiver(str(default_addr) +
+- ";{create:always,"
+- " node:{type:topic}}",
+- capacity=1)
+- trace.debug("default topic recv addr=%s" % self._topic_recvr.source)
+-
+-
+- # for sending to topic subscribers
+- topic_addr = QmfAddress.topic(QmfAddress.SUBJECT_CONSOLE_IND, self._domain)
+- self._topic_sender = self._session.sender(str(topic_addr) +
+- ";{create:always,"
+- " node:{type:topic}}")
+- trace.debug("default topic send addr=%s" % self._topic_sender.target)
+-
+- #
+- # Now that receivers are created, fire off the receive thread...
+- #
+- self._operational = True
+- self.start()
+- self._ready.wait(10)
+- if not self._ready.isSet():
+- raise Exception("Console managment thread failed to start.")
+-
+-
+-
+- def remove_connection(self, conn, timeout=None):
+- """
+- Remove an AMQP connection from the console. Un-does the add_connection() operation,
+- and releases any agents and sessions associated with the connection.
+-
+- @type conn: qpid.messaging.Connection
+- @param conn: connection previously added by add_connection()
+- """
+- if self._conn and conn and conn != self._conn:
+- log.error( "Attempt to delete unknown connection: %s" % str(conn))
+-
+- # tell connection thread to shutdown
+- self._operational = False
+- if self.isAlive():
+- # kick my thread to wake it up
+- self._wake_thread()
+- trace.debug("waiting for console receiver thread to exit")
+- self.join(timeout)
+- if self.isAlive():
+- log.error( "Console thread '%s' is hung..." % self.getName() )
+- self._direct_recvr.close()
+- self._direct_sender.close()
+- self._topic_recvr.close()
+- self._topic_sender.close()
+- self._session.close()
+- self._session = None
+- self._conn = None
+- trace.debug("console connection removal complete")
+-
+-
+- def get_address(self):
+- """
+- The AMQP address this Console is listening to.
+- """
+- return self._address
+-
+-
+- def destroy_agent( self, agent ):
+- """
+- Undoes create.
+- """
+- if not isinstance(agent, Agent):
+- raise TypeError("agent must be an instance of class Agent")
+-
+- self._lock.acquire()
+- try:
+- if agent._name in self._agent_map:
+- del self._agent_map[agent._name]
+- finally:
+- self._lock.release()
+-
+- def find_agent(self, name, timeout=None ):
+- """
+- Given the name of a particular agent, return an instance of class Agent
+- representing that agent. Return None if the agent does not exist.
+- """
+-
+- self._lock.acquire()
+- try:
+- agent = self._agent_map.get(name)
+- if agent:
+- return agent
+- finally:
+- self._lock.release()
+-
+- # agent not present yet - ping it with an agent_locate
+-
+- mbox = _SyncMailbox(self)
+- cid = mbox.get_address()
+-
+- query = QmfQuery.create_id(QmfQuery.TARGET_AGENT, name)
+- msg = Message(id=QMF_APP_ID,
+- subject="console.ind.locate." + name,
+- properties={"method":"request",
+- "qmf.opcode":OpCode.agent_locate_req},
+- content=query._predicate)
+- msg.content_type="amqp/list"
+- msg.reply_to = str(self._address)
+- msg.correlation_id = str(cid)
+- trace.debug("%s Sending Agent Locate (%s)", self._name, str(msg))
+- try:
+- self._topic_sender.send(msg)
+- except SendError, e:
+- log.error(str(e))
+- mbox.destroy()
+- return None
+-
+- if timeout is None:
+- timeout = self._reply_timeout
+-
+- new_agent = None
+- trace.debug("Waiting for response to Agent Locate (%s)" % timeout)
+- mbox.fetch(timeout)
+- mbox.destroy()
+- trace.debug("Agent Locate wait ended (%s)" % time.time())
+- self._lock.acquire()
+- try:
+- new_agent = self._agent_map.get(name)
+- finally:
+- self._lock.release()
+-
+- return new_agent
+-
+-
+- def get_agents(self):
+- """
+- Return the list of known agents.
+- """
+- self._lock.acquire()
+- try:
+- agents = self._agent_map.values()
+- finally:
+- self._lock.release()
+- return agents
+-
+-
+- def get_agent(self, name):
+- """
+- Return the named agent, else None if not currently available.
+- """
+- self._lock.acquire()
+- try:
+- agent = self._agent_map.get(name)
+- finally:
+- self._lock.release()
+- return agent
+-
+-
+- def do_query(self, agent, query, _reply_handle=None, _timeout=None ):
+- """
+- """
+- target = query.get_target()
+-
+- if _reply_handle is not None:
+- mbox = _QueryMailbox(self,
+- agent.get_name(),
+- _reply_handle,
+- target,
+- _timeout)
+- else:
+- mbox = _SyncMailbox(self)
+-
+- cid = mbox.get_address()
+-
+- try:
+- trace.debug("Sending Query to Agent (%s)" % time.time())
+- agent._send_query(query, cid)
+- except SendError, e:
+- log.error(str(e))
+- mbox.destroy()
+- return None
+-
+- # return now if async reply expected
+- if _reply_handle is not None:
+- return True
+-
+- if not _timeout:
+- _timeout = self._reply_timeout
+-
+- trace.debug("Waiting for response to Query (%s)" % _timeout)
+- now = datetime.datetime.utcnow()
+- expire = now + datetime.timedelta(seconds=_timeout)
+-
+- response = []
+- while (expire > now):
+- _timeout = timedelta_to_secs(expire - now)
+- reply = mbox.fetch(_timeout)
+- if not reply:
+- trace.debug("Query wait timed-out.")
+- break
+-
+- objects = reply.content
+- if not objects or not isinstance(objects, type([])):
+- break
+-
+- # convert from map to native types if needed
+- if target == QmfQuery.TARGET_SCHEMA_ID:
+- for sid_map in objects:
+- response.append(SchemaClassId.from_map(sid_map))
+-
+- elif target == QmfQuery.TARGET_SCHEMA:
+- for schema_map in objects:
+- # extract schema id, convert based on schema type
+- sid_map = schema_map.get(SchemaClass.KEY_SCHEMA_ID)
+- if sid_map:
+- sid = SchemaClassId.from_map(sid_map)
+- if sid:
+- if sid.get_type() == SchemaClassId.TYPE_DATA:
+- schema = SchemaObjectClass.from_map(schema_map)
+- else:
+- schema = SchemaEventClass.from_map(schema_map)
+- self._add_schema(schema) # add to schema cache
+- response.append(schema)
+-
+- elif target == QmfQuery.TARGET_OBJECT:
+- for obj_map in objects:
+- obj = QmfConsoleData(map_=obj_map, agent=agent)
+- # start fetch of schema if not known
+- sid = obj.get_schema_class_id()
+- if sid:
+- self._prefetch_schema(sid, agent)
+- response.append(obj)
+- else:
+- # no conversion needed.
+- response += objects
+-
+- if not "partial" in reply.properties:
+- # reply not broken up over multiple msgs
+- break
+-
+- now = datetime.datetime.utcnow()
+-
+- mbox.destroy()
+- return response
+-
+-
+- def create_subscription(self, agent, query, console_handle,
+- _interval=None, _duration=None,
+- _blocking=True, _timeout=None):
+- if not _duration:
+- _duration = self._subscribe_timeout
+-
+- if _timeout is None:
+- _timeout = self._reply_timeout
+-
+- if not _blocking:
+- mbox = _AsyncSubscriptionMailbox(self, console_handle, agent,
+- _duration, _interval)
+- if not mbox.subscribe(query, _timeout):
+- mbox.destroy()
+- return False
+- return True
+- else:
+- mbox = _SubscriptionMailbox(self, console_handle, agent, _duration,
+- _interval)
+-
+- if not mbox.subscribe(query):
+- mbox.destroy()
+- return None
+-
+- trace.debug("Waiting for response to subscription (%s)" % _timeout)
+- # @todo: what if mbox expires here?
+- sp = mbox.fetch(_timeout)
+-
+- if not sp:
+- trace.debug("Subscription request wait timed-out.")
+- mbox.destroy()
+- return None
+-
+- if not sp.succeeded():
+- mbox.destroy()
+-
+- return sp
+-
+- def refresh_subscription(self, subscription_id,
+- _duration=None,
+- _timeout=None):
+- if _timeout is None:
+- _timeout = self._reply_timeout
+-
+- mbox = self._get_mailbox(subscription_id)
+- if not mbox:
+- log.warning("Subscription %s not found." % subscription_id)
+- return None
+-
+- if isinstance(mbox, _AsyncSubscriptionMailbox):
+- return mbox.resubscribe()
+- else:
+- # synchronous - wait for reply
+- if not mbox.resubscribe():
+- # @todo ???? mbox.destroy()
+- return None
+-
+- # wait for reply
+-
+- trace.debug("Waiting for response to subscription (%s)" % _timeout)
+- sp = mbox.fetch(_timeout)
+-
+- if not sp:
+- trace.debug("re-subscribe request wait timed-out.")
+- # @todo???? mbox.destroy()
+- return None
+-
+- return sp
+-
+-
+- def cancel_subscription(self, subscription_id):
+- """
+- """
+- mbox = self._get_mailbox(subscription_id)
+- if not mbox:
+- return
+-
+- agent = self.get_agent(mbox.agent_name)
+- if agent:
+- try:
+- trace.debug("Sending UnSubscribe to Agent (%s)" % time.time())
+- agent._send_unsubscribe_ind(subscription_id,
+- mbox.agent_subscription_id)
+- except SendError, e:
+- log.error(str(e))
+-
+- mbox.destroy()
+-
+-
+- def _wake_thread(self):
+- """
+- Make the console management thread loop wakeup from its next_receiver
+- sleep.
+- """
+- trace.debug("Sending noop to wake up [%s]" % self._address)
+- msg = Message(id=QMF_APP_ID,
+- subject=self._name,
+- properties={"method":"indication",
+- "qmf.opcode":OpCode.noop},
+- content={})
+- try:
+- self._direct_sender.send( msg, sync=True )
+- except SendError, e:
+- log.error(str(e))
+-
+-
+- def run(self):
+- """
+- Console Management Thread main loop.
+- Handles inbound messages, agent discovery, async mailbox timeouts.
+- """
+- global _callback_thread
+-
+- self._ready.set()
+-
+- while self._operational:
+-
+- # qLen = self._work_q.qsize()
+-
+- while True:
+- try:
+- msg = self._topic_recvr.fetch(timeout=0)
+- except Empty:
+- break
+- # TRACE:
+- # log.error("!!! Console %s: msg on %s [%s]" %
+- # (self._name, self._topic_recvr.source, msg))
+- self._dispatch(msg, _direct=False)
+-
+- while True:
+- try:
+- msg = self._direct_recvr.fetch(timeout = 0)
+- except Empty:
+- break
+- # TRACE
+- #log.error("!!! Console %s: msg on %s [%s]" %
+- # (self._name, self._direct_recvr.source, msg))
+- self._dispatch(msg, _direct=True)
+-
+- self._expire_agents() # check for expired agents
+- self._expire_mboxes() # check for expired async mailbox requests
+-
+- #if qLen == 0 and self._work_q.qsize() and self._notifier:
+- if self._work_q_put and self._notifier:
+- # new stuff on work queue, kick the the application...
+- self._work_q_put = False
+- _callback_thread = currentThread()
+- trace.debug("Calling console notifier.indication")
+- self._notifier.indication()
+- _callback_thread = None
+-
+-
+- # wait for a message to arrive, or an agent
+- # to expire, or a mailbox requrest to time out
+- now = datetime.datetime.utcnow()
+- next_expire = self._next_agent_expire
+-
+- self._lock.acquire()
+- try:
+- # the mailbox expire flag may be cleared by the
+- # app thread(s) to force an immedate mailbox scan
+- if self._next_mbox_expire is None:
+- next_expire = now
+- elif self._next_mbox_expire < next_expire:
+- next_expire = self._next_mbox_expire
+- finally:
+- self._lock.release()
+-
+- timeout = timedelta_to_secs(next_expire - now)
+-
+- if self._operational and timeout > 0.0:
+- try:
+- trace.debug("waiting for next rcvr (timeout=%s)..." % timeout)
+- self._session.next_receiver(timeout = timeout)
+- except Empty:
+- pass
+-
+- trace.debug("Shutting down Console thread")
+-
+- def get_objects(self,
+- _object_id=None,
+- _schema_id=None,
+- _pname=None, _cname=None,
+- _agents=None,
+- _timeout=None):
+- """
+- Retrieve objects by id or schema.
+-
+- By object_id: must specify schema_id or pname & cname if object defined
+- by a schema. Undescribed objects: only object_id needed.
+-
+- By schema: must specify schema_id or pname & cname - all instances of
+- objects defined by that schema are returned.
+- """
+- if _agents is None:
+- # use copy of current agent list
+- self._lock.acquire()
+- try:
+- agent_list = self._agent_map.values()
+- finally:
+- self._lock.release()
+- elif isinstance(_agents, Agent):
+- agent_list = [_agents]
+- else:
+- agent_list = _agents
+- # @todo validate this list!
+-
+- if _timeout is None:
+- _timeout = self._reply_timeout
+-
+- # @todo: fix when async do_query done - query all agents at once, then
+- # wait for replies, instead of per-agent querying....
+-
+- obj_list = []
+- expired = datetime.datetime.utcnow() + datetime.timedelta(seconds=_timeout)
+- for agent in agent_list:
+- if not agent.is_active():
+- continue
+- now = datetime.datetime.utcnow()
+- if now >= expired:
+- break
+-
+- if _pname is None:
+- if _object_id:
+- query = QmfQuery.create_id_object(_object_id,
+- _schema_id)
+- else:
+- if _schema_id is not None:
+- t_params = {QmfData.KEY_SCHEMA_ID: _schema_id}
+- else:
+- t_params = None
+- query = QmfQuery.create_wildcard(QmfQuery.TARGET_OBJECT,
+- t_params)
+- timeout = timedelta_to_secs(expired - now)
+- reply = self.do_query(agent, query, _timeout=timeout)
+- if reply:
+- obj_list = obj_list + reply
+- else:
+- # looking up by package name (and maybe class name), need to
+- # find all schema_ids in that package, then lookup object by
+- # schema_id
+- if _cname is not None:
+- pred = [QmfQuery.AND,
+- [QmfQuery.EQ,
+- SchemaClassId.KEY_PACKAGE,
+- [QmfQuery.QUOTE, _pname]],
+- [QmfQuery.EQ, SchemaClassId.KEY_CLASS,
+- [QmfQuery.QUOTE, _cname]]]
+- else:
+- pred = [QmfQuery.EQ,
+- SchemaClassId.KEY_PACKAGE,
+- [QmfQuery.QUOTE, _pname]]
+- query = QmfQuery.create_predicate(QmfQuery.TARGET_SCHEMA_ID, pred)
+- timeout = timedelta_to_secs(expired - now)
+- sid_list = self.do_query(agent, query, _timeout=timeout)
+- if sid_list:
+- for sid in sid_list:
+- now = datetime.datetime.utcnow()
+- if now >= expired:
+- break
+- if _object_id is not None:
+- query = QmfQuery.create_id_object(_object_id, sid)
+- else:
+- t_params = {QmfData.KEY_SCHEMA_ID: sid}
+- query = QmfQuery.create_wildcard(QmfQuery.TARGET_OBJECT, t_params)
+- timeout = timedelta_to_secs(expired - now)
+- reply = self.do_query(agent, query, _timeout=timeout)
+- if reply:
+- obj_list = obj_list + reply
+- if obj_list:
+- return obj_list
+- return None
+-
+-
+-
+- # called by run() thread ONLY
+- #
+- def _dispatch(self, msg, _direct=True):
+- """
+- PRIVATE: Process a message received from an Agent
+- """
+- trace.debug( "Message received from Agent! [%s]", msg )
+-
+- opcode = msg.properties.get("qmf.opcode")
+- if not opcode:
+- log.error("Ignoring unrecognized message '%s'", msg)
+- return
+- version = 2 # @todo: fix me
+-
+- cmap = {}; props = {}
+- if msg.content_type == "amqp/map":
+- cmap = msg.content
+- if msg.properties:
+- props = msg.properties
+-
+- if opcode == OpCode.agent_heartbeat_ind:
+- self._handle_agent_ind_msg( msg, cmap, version, _direct )
+- elif opcode == OpCode.agent_locate_rsp:
+- self._handle_agent_ind_msg( msg, cmap, version, _direct )
+- elif msg.correlation_id:
+- self._handle_response_msg(msg, cmap, version, _direct)
+- elif opcode == OpCode.data_ind:
+- self._handle_indication_msg(msg, cmap, version, _direct)
+- elif opcode == OpCode.noop:
+- trace.debug("No-op msg received.")
+- else:
+- log.warning("Ignoring message with unrecognized 'opcode' value: '%s'", opcode)
+-
+-
+- def _handle_agent_ind_msg(self, msg, cmap, version, direct):
+- """
+- Process a received agent-ind message. This message may be a response to a
+- agent-locate, or it can be an unsolicited agent announce.
+- """
+-
+- trace.debug("%s _handle_agent_ind_msg '%s'", self._name, str(msg))
+-
+- try:
+- tmp = QmfData.from_map(msg.content)
+- except:
+- log.warning("%s invalid Agent Indication msg format '%s'",
+- self._name, str(msg))
+- return
+-
+- try:
+- name = tmp.get_value("_name")
+- except:
+- log.warning("Bad Agent ind msg received: %s", str(msg))
+- return
+-
+- correlated = False
+- if msg.correlation_id:
+- mbox = self._get_mailbox(msg.correlation_id)
+- correlated = mbox is not None
+-
+- agent = None
+- self._lock.acquire()
+- try:
+- agent = self._agent_map.get(name)
+- if agent:
+- # agent already known, just update timestamp
+- agent._announce_timestamp = datetime.datetime.utcnow()
+- finally:
+- self._lock.release()
+-
+- if not agent:
+- # need to create and add a new agent?
+- matched = False
+- if self._agent_discovery_filter:
+- matched = self._agent_discovery_filter.evaluate(tmp)
+-
+- if (correlated or matched):
+- agent = self._create_agent(name)
+- if not agent:
+- return # failed to add agent
+- agent._announce_timestamp = datetime.datetime.utcnow()
+-
+- if matched:
+- # unsolicited, but newly discovered
+- trace.debug("AGENT_ADDED for %s (%s)" % (agent, time.time()))
+- wi = WorkItem(WorkItem.AGENT_ADDED, None, {"agent": agent})
+- self._work_q.put(wi)
+- self._work_q_put = True
+-
+- if correlated:
+- # wake up all waiters
+- trace.debug("waking waiters for correlation id %s" % msg.correlation_id)
+- mbox.deliver(msg)
+-
+- def _handle_response_msg(self, msg, cmap, version, direct):
+- """
+- Process a received data-ind message.
+- """
+- trace.debug("%s _handle_response_msg '%s'", self._name, str(msg))
+-
+- mbox = self._get_mailbox(msg.correlation_id)
+- if not mbox:
+- log.warning("%s Response msg received with unknown correlation_id"
+- " msg='%s'", self._name, str(msg))
+- return
+-
+- # wake up all waiters
+- trace.debug("waking waiters for correlation id %s" % msg.correlation_id)
+- mbox.deliver(msg)
+-
+- def _handle_indication_msg(self, msg, cmap, version, _direct):
+-
+- aname = msg.properties.get("qmf.agent")
+- if not aname:
+- trace.debug("No agent name field in indication message.")
+- return
+-
+- content_type = msg.properties.get("qmf.content")
+- if (content_type != ContentType.event or
+- not isinstance(msg.content, type([]))):
+- log.warning("Bad event indication message received: '%s'", msg)
+- return
+-
+- emap = msg.content[0]
+- if not isinstance(emap, type({})):
+- trace.debug("Invalid event body in indication message: '%s'", msg)
+- return
+-
+- agent = None
+- self._lock.acquire()
+- try:
+- agent = self._agent_map.get(aname)
+- finally:
+- self._lock.release()
+- if not agent:
+- trace.debug("Agent '%s' not known." % aname)
+- return
+- try:
+- # @todo: schema???
+- event = QmfEvent.from_map(emap)
+- except TypeError:
+- trace.debug("Invalid QmfEvent map received: %s" % str(emap))
+- return
+-
+- # @todo: schema? Need to fetch it, but not from this thread!
+- # This thread can not pend on a request.
+- trace.debug("Publishing event received from agent %s" % aname)
+- wi = WorkItem(WorkItem.EVENT_RECEIVED, None,
+- {"agent":agent,
+- "event":event})
+- self._work_q.put(wi)
+- self._work_q_put = True
+-
+-
+- def _expire_mboxes(self):
+- """
+- Check all async mailboxes for outstanding requests that have expired.
+- """
+- self._lock.acquire()
+- try:
+- now = datetime.datetime.utcnow()
+- if self._next_mbox_expire and now < self._next_mbox_expire:
+- return
+- expired_mboxes = []
+- self._next_mbox_expire = None
+- for mbox in self._async_mboxes.itervalues():
+- if now >= mbox.expiration_date:
+- expired_mboxes.append(mbox)
+- else:
+- if (self._next_mbox_expire is None or
+- mbox.expiration_date < self._next_mbox_expire):
+- self._next_mbox_expire = mbox.expiration_date
+-
+- for mbox in expired_mboxes:
+- del self._async_mboxes[mbox.cid]
+- finally:
+- self._lock.release()
+-
+- for mbox in expired_mboxes:
+- # note: expire() may deallocate the mbox, so don't touch
+- # it further.
+- mbox.expire()
+-
+-
+- def _expire_agents(self):
+- """
+- Check for expired agents and issue notifications when they expire.
+- """
+- now = datetime.datetime.utcnow()
+- if self._next_agent_expire and now < self._next_agent_expire:
+- return
+- lifetime_delta = datetime.timedelta(seconds = self._agent_timeout)
+- next_expire_delta = lifetime_delta
+- self._lock.acquire()
+- try:
+- trace.debug("!!! expiring agents '%s'" % now)
+- for agent in self._agent_map.itervalues():
+- if agent._announce_timestamp:
+- agent_deathtime = agent._announce_timestamp + lifetime_delta
+- if agent_deathtime <= now:
+- trace.debug("AGENT_DELETED for %s" % agent)
+- agent._announce_timestamp = None
+- wi = WorkItem(WorkItem.AGENT_DELETED, None,
+- {"agent":agent})
+- # @todo: remove agent from self._agent_map
+- self._work_q.put(wi)
+- self._work_q_put = True
+- else:
+- if (agent_deathtime - now) < next_expire_delta:
+- next_expire_delta = agent_deathtime - now
+-
+- self._next_agent_expire = now + next_expire_delta
+- trace.debug("!!! next expire cycle = '%s'" % self._next_agent_expire)
+- finally:
+- self._lock.release()
+-
+-
+-
+- def _create_agent( self, name ):
+- """
+- Factory to create/retrieve an agent for this console
+- """
+- trace.debug("creating agent %s" % name)
+- self._lock.acquire()
+- try:
+- agent = self._agent_map.get(name)
+- if agent:
+- return agent
+-
+- agent = Agent(name, self)
+- try:
+- agent._sender = self._session.sender(str(agent._address) +
+- ";{create:always,"
+- " node:"
+- " {type:topic,"
+- " x-declare:"
+- " {type:direct}}}")
+- except:
+- log.warning("Unable to create sender for %s" % name)
+- return None
+- trace.debug("created agent sender %s" % agent._sender.target)
+-
+- self._agent_map[name] = agent
+- finally:
+- self._lock.release()
+-
+- # new agent - query for its schema database for
+- # seeding the schema cache (@todo)
+- # query = QmfQuery({QmfQuery.TARGET_SCHEMA_ID:None})
+- # agent._sendQuery( query )
+-
+- return agent
+-
+-
+-
+- def enable_agent_discovery(self, _query=None):
+- """
+- Called to enable the asynchronous Agent Discovery process.
+- Once enabled, AGENT_ADD work items can arrive on the WorkQueue.
+- """
+- # @todo: fix - take predicate only, not entire query!
+- if _query is not None:
+- if (not isinstance(_query, QmfQuery) or
+- _query.get_target() != QmfQuery.TARGET_AGENT):
+- raise TypeError("Type QmfQuery with target == TARGET_AGENT expected")
+- self._agent_discovery_filter = _query
+- else:
+- # create a match-all agent query (no predicate)
+- self._agent_discovery_filter = QmfQuery.create_wildcard(QmfQuery.TARGET_AGENT)
+-
+- def disable_agent_discovery(self):
+- """
+- Called to disable the async Agent Discovery process enabled by
+- calling enableAgentDiscovery()
+- """
+- self._agent_discovery_filter = None
+-
+-
+-
+- def get_workitem_count(self):
+- """
+- Returns the count of pending WorkItems that can be retrieved.
+- """
+- return self._work_q.qsize()
+-
+-
+-
+- def get_next_workitem(self, timeout=None):
+- """
+- Returns the next pending work item, or None if none available.
+- @todo: subclass and return an Empty event instead.
+- """
+- try:
+- wi = self._work_q.get(True, timeout)
+- except Queue.Empty:
+- return None
+- return wi
+-
+-
+- def release_workitem(self, wi):
+- """
+- Return a WorkItem to the Console when it is no longer needed.
+- @todo: call Queue.task_done() - only 2.5+
+-
+- @type wi: class qmfConsole.WorkItem
+- @param wi: work item object to return.
+- """
+- pass
+-
+- def _add_schema(self, schema):
+- """
+- @todo
+- """
+- if not isinstance(schema, SchemaClass):
+- raise TypeError("SchemaClass type expected")
+-
+- self._lock.acquire()
+- try:
+- sid = schema.get_class_id()
+- if not self._schema_cache.has_key(sid):
+- self._schema_cache[sid] = schema
+- if sid in self._pending_schema_req:
+- self._pending_schema_req.remove(sid)
+- finally:
+- self._lock.release()
+-
+- def _prefetch_schema(self, schema_id, agent):
+- """
+- Send an async request for the schema identified by schema_id if the
+- schema is not available in the cache.
+- """
+- need_fetch = False
+- self._lock.acquire()
+- try:
+- if ((not self._schema_cache.has_key(schema_id)) and
+- schema_id not in self._pending_schema_req):
+- self._pending_schema_req.append(schema_id)
+- need_fetch = True
+- finally:
+- self._lock.release()
+-
+- if need_fetch:
+- mbox = _SchemaPrefetchMailbox(self, schema_id)
+- query = QmfQuery.create_id(QmfQuery.TARGET_SCHEMA, schema_id)
+- trace.debug("Sending Schema Query to Agent (%s)" % time.time())
+- try:
+- agent._send_query(query, mbox.get_address())
+- except SendError, e:
+- log.error(str(e))
+- mbox.destroy()
+- self._lock.acquire()
+- try:
+- self._pending_schema_req.remove(schema_id)
+- finally:
+- self._lock.release()
+-
+-
+- def _fetch_schema(self, schema_id, _agent=None, _timeout=None):
+- """
+- Find the schema identified by schema_id. If not in the cache, ask the
+- agent for it.
+- """
+- if not isinstance(schema_id, SchemaClassId):
+- raise TypeError("SchemaClassId type expected")
+-
+- self._lock.acquire()
+- try:
+- schema = self._schema_cache.get(schema_id)
+- if schema:
+- return schema
+- finally:
+- self._lock.release()
+-
+- if _agent is None:
+- return None
+-
+- # note: do_query will add the new schema to the cache automatically.
+- slist = self.do_query(_agent,
+- QmfQuery.create_id(QmfQuery.TARGET_SCHEMA, schema_id),
+- _timeout=_timeout)
+- if slist:
+- return slist[0]
+- else:
+- return None
+-
+- def _add_mailbox(self, mbox):
+- """
+- Add a mailbox to the post office, and assign it a unique address.
+- """
+- self._lock.acquire()
+- try:
+- mbox.cid = self._correlation_id
+- self._correlation_id += 1
+- self._post_office[mbox.cid] = mbox
+- finally:
+- self._lock.release()
+-
+- def _get_mailbox(self, mid):
+- try:
+- mid = long(mid)
+- except TypeError:
+- log.error("Invalid mailbox id: %s" % str(mid))
+- return None
+-
+- self._lock.acquire()
+- try:
+- return self._post_office.get(mid)
+- finally:
+- self._lock.release()
+-
+-
+- def _remove_mailbox(self, mid):
+- """ Remove a mailbox and its address from the post office """
+- try:
+- mid = long(mid)
+- except TypeError:
+- log.error("Invalid mailbox id: %s" % str(mid))
+- return None
+-
+- self._lock.acquire()
+- try:
+- if mid in self._post_office:
+- del self._post_office[mid]
+- finally:
+- self._lock.release()
+-
+- def __repr__(self):
+- return str(self._address)
+-
+- # def get_packages(self):
+- # plist = []
+- # for i in range(self.impl.packageCount()):
+- # plist.append(self.impl.getPackageName(i))
+- # return plist
+-
+-
+- # def get_classes(self, package, kind=CLASS_OBJECT):
+- # clist = []
+- # for i in range(self.impl.classCount(package)):
+- # key = self.impl.getClass(package, i)
+- # class_kind = self.impl.getClassKind(key)
+- # if class_kind == kind:
+- # if kind == CLASS_OBJECT:
+- # clist.append(SchemaObjectClass(None, None, {"impl":self.impl.getObjectClass(key)}))
+- # elif kind == CLASS_EVENT:
+- # clist.append(SchemaEventClass(None, None, {"impl":self.impl.getEventClass(key)}))
+- # return clist
+-
+-
+- # def bind_package(self, package):
+- # return self.impl.bindPackage(package)
+-
+-
+- # def bind_class(self, kwargs = {}):
+- # if "key" in kwargs:
+- # self.impl.bindClass(kwargs["key"])
+- # elif "package" in kwargs:
+- # package = kwargs["package"]
+- # if "class" in kwargs:
+- # self.impl.bindClass(package, kwargs["class"])
+- # else:
+- # self.impl.bindClass(package)
+- # else:
+- # raise Exception("Argument error: invalid arguments, use 'key' or 'package'[,'class']")
+-
+-
+- # def get_agents(self, broker=None):
+- # blist = []
+- # if broker:
+- # blist.append(broker)
+- # else:
+- # self._cv.acquire()
+- # try:
+- # # copy while holding lock
+- # blist = self._broker_list[:]
+- # finally:
+- # self._cv.release()
+-
+- # agents = []
+- # for b in blist:
+- # for idx in range(b.impl.agentCount()):
+- # agents.append(AgentProxy(b.impl.getAgent(idx), b))
+-
+- # return agents
+-
+-
+- # def get_objects(self, query, kwargs = {}):
+- # timeout = 30
+- # agent = None
+- # temp_args = kwargs.copy()
+- # if type(query) == type({}):
+- # temp_args.update(query)
+-
+- # if "_timeout" in temp_args:
+- # timeout = temp_args["_timeout"]
+- # temp_args.pop("_timeout")
+-
+- # if "_agent" in temp_args:
+- # agent = temp_args["_agent"]
+- # temp_args.pop("_agent")
+-
+- # if type(query) == type({}):
+- # query = Query(temp_args)
+-
+- # self._select = {}
+- # for k in temp_args.iterkeys():
+- # if type(k) == str:
+- # self._select[k] = temp_args[k]
+-
+- # self._cv.acquire()
+- # try:
+- # self._sync_count = 1
+- # self._sync_result = []
+- # broker = self._broker_list[0]
+- # broker.send_query(query.impl, None, agent)
+- # self._cv.wait(timeout)
+- # if self._sync_count == 1:
+- # raise Exception("Timed out: waiting for query response")
+- # finally:
+- # self._cv.release()
+-
+- # return self._sync_result
+-
+-
+- # def get_object(self, query, kwargs = {}):
+- # '''
+- # Return one and only one object or None.
+- # '''
+- # objs = objects(query, kwargs)
+- # if len(objs) == 1:
+- # return objs[0]
+- # else:
+- # return None
+-
+-
+- # def first_object(self, query, kwargs = {}):
+- # '''
+- # Return the first of potentially many objects.
+- # '''
+- # objs = objects(query, kwargs)
+- # if objs:
+- # return objs[0]
+- # else:
+- # return None
+-
+-
+- # # Check the object against select to check for a match
+- # def _select_match(self, object):
+- # schema_props = object.properties()
+- # for key in self._select.iterkeys():
+- # for prop in schema_props:
+- # if key == p[0].name() and self._select[key] != p[1]:
+- # return False
+- # return True
+-
+-
+- # def _get_result(self, list, context):
+- # '''
+- # Called by Broker proxy to return the result of a query.
+- # '''
+- # self._cv.acquire()
+- # try:
+- # for item in list:
+- # if self._select_match(item):
+- # self._sync_result.append(item)
+- # self._sync_count -= 1
+- # self._cv.notify()
+- # finally:
+- # self._cv.release()
+-
+-
+- # def start_sync(self, query): pass
+-
+-
+- # def touch_sync(self, sync): pass
+-
+-
+- # def end_sync(self, sync): pass
+-
+-
+-
+-
+-# def start_console_events(self):
+-# self._cb_cond.acquire()
+-# try:
+-# self._cb_cond.notify()
+-# finally:
+-# self._cb_cond.release()
+-
+-
+-# def _do_console_events(self):
+-# '''
+-# Called by the Console thread to poll for events. Passes the events
+-# onto the ConsoleHandler associated with this Console. Is called
+-# periodically, but can also be kicked by Console.start_console_events().
+-# '''
+-# count = 0
+-# valid = self.impl.getEvent(self._event)
+-# while valid:
+-# count += 1
+-# try:
+-# if self._event.kind == qmfengine.ConsoleEvent.AGENT_ADDED:
+-# trace.debug("Console Event AGENT_ADDED received")
+-# if self._handler:
+-# self._handler.agent_added(AgentProxy(self._event.agent, None))
+-# elif self._event.kind == qmfengine.ConsoleEvent.AGENT_DELETED:
+-# trace.debug("Console Event AGENT_DELETED received")
+-# if self._handler:
+-# self._handler.agent_deleted(AgentProxy(self._event.agent, None))
+-# elif self._event.kind == qmfengine.ConsoleEvent.NEW_PACKAGE:
+-# trace.debug("Console Event NEW_PACKAGE received")
+-# if self._handler:
+-# self._handler.new_package(self._event.name)
+-# elif self._event.kind == qmfengine.ConsoleEvent.NEW_CLASS:
+-# trace.debug("Console Event NEW_CLASS received")
+-# if self._handler:
+-# self._handler.new_class(SchemaClassKey(self._event.classKey))
+-# elif self._event.kind == qmfengine.ConsoleEvent.OBJECT_UPDATE:
+-# trace.debug("Console Event OBJECT_UPDATE received")
+-# if self._handler:
+-# self._handler.object_update(ConsoleObject(None, {"impl":self._event.object}),
+-# self._event.hasProps, self._event.hasStats)
+-# elif self._event.kind == qmfengine.ConsoleEvent.EVENT_RECEIVED:
+-# trace.debug("Console Event EVENT_RECEIVED received")
+-# elif self._event.kind == qmfengine.ConsoleEvent.AGENT_HEARTBEAT:
+-# trace.debug("Console Event AGENT_HEARTBEAT received")
+-# if self._handler:
+-# self._handler.agent_heartbeat(AgentProxy(self._event.agent, None), self._event.timestamp)
+-# elif self._event.kind == qmfengine.ConsoleEvent.METHOD_RESPONSE:
+-# trace.debug("Console Event METHOD_RESPONSE received")
+-# else:
+-# trace.debug("Console thread received unknown event: '%s'" % str(self._event.kind))
+-# except e:
+-# print "Exception caught in callback thread:", e
+-# self.impl.popEvent()
+-# valid = self.impl.getEvent(self._event)
+-# return count
+-
+-
+-
+-
+-
+-# class Broker(ConnectionHandler):
+-# # attr_reader :impl :conn, :console, :broker_bank
+-# def __init__(self, console, conn):
+-# self.broker_bank = 1
+-# self.console = console
+-# self.conn = conn
+-# self._session = None
+-# self._cv = Condition()
+-# self._stable = None
+-# self._event = qmfengine.BrokerEvent()
+-# self._xmtMessage = qmfengine.Message()
+-# self.impl = qmfengine.BrokerProxy(self.console.impl)
+-# self.console.impl.addConnection(self.impl, self)
+-# self.conn.add_conn_handler(self)
+-# self._operational = True
+-
+-
+-# def shutdown(self):
+-# trace.debug("broker.shutdown() called.")
+-# self.console.impl.delConnection(self.impl)
+-# self.conn.del_conn_handler(self)
+-# if self._session:
+-# self.impl.sessionClosed()
+-# trace.debug("broker.shutdown() sessionClosed done.")
+-# self._session.destroy()
+-# trace.debug("broker.shutdown() session destroy done.")
+-# self._session = None
+-# self._operational = False
+-# trace.debug("broker.shutdown() done.")
+-
+-
+-# def wait_for_stable(self, timeout = None):
+-# self._cv.acquire()
+-# try:
+-# if self._stable:
+-# return
+-# if timeout:
+-# self._cv.wait(timeout)
+-# if not self._stable:
+-# raise Exception("Timed out: waiting for broker connection to become stable")
+-# else:
+-# while not self._stable:
+-# self._cv.wait()
+-# finally:
+-# self._cv.release()
+-
+-
+-# def send_query(self, query, ctx, agent):
+-# agent_impl = None
+-# if agent:
+-# agent_impl = agent.impl
+-# self.impl.sendQuery(query, ctx, agent_impl)
+-# self.conn.kick()
+-
+-
+-# def _do_broker_events(self):
+-# count = 0
+-# valid = self.impl.getEvent(self._event)
+-# while valid:
+-# count += 1
+-# if self._event.kind == qmfengine.BrokerEvent.BROKER_INFO:
+-# trace.debug("Broker Event BROKER_INFO received");
+-# elif self._event.kind == qmfengine.BrokerEvent.DECLARE_QUEUE:
+-# trace.debug("Broker Event DECLARE_QUEUE received");
+-# self.conn.impl.declareQueue(self._session.handle, self._event.name)
+-# elif self._event.kind == qmfengine.BrokerEvent.DELETE_QUEUE:
+-# trace.debug("Broker Event DELETE_QUEUE received");
+-# self.conn.impl.deleteQueue(self._session.handle, self._event.name)
+-# elif self._event.kind == qmfengine.BrokerEvent.BIND:
+-# trace.debug("Broker Event BIND received");
+-# self.conn.impl.bind(self._session.handle, self._event.exchange, self._event.name, self._event.bindingKey)
+-# elif self._event.kind == qmfengine.BrokerEvent.UNBIND:
+-# trace.debug("Broker Event UNBIND received");
+-# self.conn.impl.unbind(self._session.handle, self._event.exchange, self._event.name, self._event.bindingKey)
+-# elif self._event.kind == qmfengine.BrokerEvent.SETUP_COMPLETE:
+-# trace.debug("Broker Event SETUP_COMPLETE received");
+-# self.impl.startProtocol()
+-# elif self._event.kind == qmfengine.BrokerEvent.STABLE:
+-# trace.debug("Broker Event STABLE received");
+-# self._cv.acquire()
+-# try:
+-# self._stable = True
+-# self._cv.notify()
+-# finally:
+-# self._cv.release()
+-# elif self._event.kind == qmfengine.BrokerEvent.QUERY_COMPLETE:
+-# result = []
+-# for idx in range(self._event.queryResponse.getObjectCount()):
+-# result.append(ConsoleObject(None, {"impl":self._event.queryResponse.getObject(idx), "broker":self}))
+-# self.console._get_result(result, self._event.context)
+-# elif self._event.kind == qmfengine.BrokerEvent.METHOD_RESPONSE:
+-# obj = self._event.context
+-# obj._method_result(MethodResponse(self._event.methodResponse()))
+-
+-# self.impl.popEvent()
+-# valid = self.impl.getEvent(self._event)
+-
+-# return count
+-
+-
+-# def _do_broker_messages(self):
+-# count = 0
+-# valid = self.impl.getXmtMessage(self._xmtMessage)
+-# while valid:
+-# count += 1
+-# trace.debug("Broker: sending msg on connection")
+-# self.conn.impl.sendMessage(self._session.handle, self._xmtMessage)
+-# self.impl.popXmt()
+-# valid = self.impl.getXmtMessage(self._xmtMessage)
+-
+-# return count
+-
+-
+-# def _do_events(self):
+-# while True:
+-# self.console.start_console_events()
+-# bcnt = self._do_broker_events()
+-# mcnt = self._do_broker_messages()
+-# if bcnt == 0 and mcnt == 0:
+-# break;
+-
+-
+-# def conn_event_connected(self):
+-# trace.debug("Broker: Connection event CONNECTED")
+-# self._session = Session(self.conn, "qmfc-%s.%d" % (socket.gethostname(), os.getpid()), self)
+-# self.impl.sessionOpened(self._session.handle)
+-# self._do_events()
+-
+-
+-# def conn_event_disconnected(self, error):
+-# trace.debug("Broker: Connection event DISCONNECTED")
+-# pass
+-
+-
+-# def conn_event_visit(self):
+-# self._do_events()
+-
+-
+-# def sess_event_session_closed(self, context, error):
+-# trace.debug("Broker: Session event CLOSED")
+-# self.impl.sessionClosed()
+-
+-
+-# def sess_event_recv(self, context, message):
+-# trace.debug("Broker: Session event MSG_RECV")
+-# if not self._operational:
+-# log.warning("Unexpected session event message received by Broker proxy: context='%s'" % str(context))
+-# self.impl.handleRcvMessage(message)
+-# self._do_events()
+-
+-
+-
+-################################################################################
+-################################################################################
+-################################################################################
+-################################################################################
+-# TEMPORARY TEST CODE - TO BE DELETED
+-################################################################################
+-################################################################################
+-################################################################################
+-################################################################################
+-
+-if __name__ == '__main__':
+- # temp test code
+- import logging
+- from common import (qmfTypes, SchemaProperty)
+-
+- logging.getLogger().setLevel(logging.INFO)
+-
+- logging.info( "************* Creating Async Console **************" )
+-
+- class MyNotifier(Notifier):
+- def __init__(self, context):
+- self._myContext = context
+- self.WorkAvailable = False
+-
+- def indication(self):
+- print("Indication received! context=%d" % self._myContext)
+- self.WorkAvailable = True
+-
+- _noteMe = MyNotifier( 666 )
+-
+- _myConsole = Console(notifier=_noteMe)
+-
+- _myConsole.enable_agent_discovery()
+- logging.info("Waiting...")
+-
+-
+- logging.info( "Destroying console:" )
+- _myConsole.destroy( 10 )
+-
+- logging.info( "******** Messing around with Schema ********" )
+-
+- _sec = SchemaEventClass( _classId=SchemaClassId("myPackage", "myClass",
+- stype=SchemaClassId.TYPE_EVENT),
+- _desc="A typical event schema",
+- _props={"Argument-1": SchemaProperty(_type_code=qmfTypes.TYPE_UINT8,
+- kwargs = {"min":0,
+- "max":100,
+- "unit":"seconds",
+- "desc":"sleep value"}),
+- "Argument-2": SchemaProperty(_type_code=qmfTypes.TYPE_LSTR,
+- kwargs={"maxlen":100,
+- "desc":"a string argument"})})
+- print("_sec=%s" % _sec.get_class_id())
+- print("_sec.gePropertyCount()=%d" % _sec.get_property_count() )
+- print("_sec.getProperty('Argument-1`)=%s" % _sec.get_property('Argument-1') )
+- print("_sec.getProperty('Argument-2`)=%s" % _sec.get_property('Argument-2') )
+- try:
+- print("_sec.getProperty('not-found')=%s" % _sec.get_property('not-found') )
+- except:
+- pass
+- print("_sec.getProperties()='%s'" % _sec.get_properties())
+-
+- print("Adding another argument")
+- _arg3 = SchemaProperty( _type_code=qmfTypes.TYPE_BOOL,
+- kwargs={"dir":"IO",
+- "desc":"a boolean argument"})
+- _sec.add_property('Argument-3', _arg3)
+- print("_sec=%s" % _sec.get_class_id())
+- print("_sec.getPropertyCount()=%d" % _sec.get_property_count() )
+- print("_sec.getProperty('Argument-1')=%s" % _sec.get_property('Argument-1') )
+- print("_sec.getProperty('Argument-2')=%s" % _sec.get_property('Argument-2') )
+- print("_sec.getProperty('Argument-3')=%s" % _sec.get_property('Argument-3') )
+-
+- print("_arg3.mapEncode()='%s'" % _arg3.map_encode() )
+-
+- _secmap = _sec.map_encode()
+- print("_sec.mapEncode()='%s'" % _secmap )
+-
+- _sec2 = SchemaEventClass( _map=_secmap )
+-
+- print("_sec=%s" % _sec.get_class_id())
+- print("_sec2=%s" % _sec2.get_class_id())
+-
+- _soc = SchemaObjectClass( _map = {"_schema_id": {"_package_name": "myOtherPackage",
+- "_class_name": "myOtherClass",
+- "_type": "_data"},
+- "_desc": "A test data object",
+- "_values":
+- {"prop1": {"amqp_type": qmfTypes.TYPE_UINT8,
+- "access": "RO",
+- "index": True,
+- "unit": "degrees"},
+- "prop2": {"amqp_type": qmfTypes.TYPE_UINT8,
+- "access": "RW",
+- "index": True,
+- "desc": "The Second Property(tm)",
+- "unit": "radians"},
+- "statistics": { "amqp_type": qmfTypes.TYPE_DELTATIME,
+- "unit": "seconds",
+- "desc": "time until I retire"},
+- "meth1": {"_desc": "A test method",
+- "_arguments":
+- {"arg1": {"amqp_type": qmfTypes.TYPE_UINT32,
+- "desc": "an argument 1",
+- "dir": "I"},
+- "arg2": {"amqp_type": qmfTypes.TYPE_BOOL,
+- "dir": "IO",
+- "desc": "some weird boolean"}}},
+- "meth2": {"_desc": "A test method",
+- "_arguments":
+- {"m2arg1": {"amqp_type": qmfTypes.TYPE_UINT32,
+- "desc": "an 'nuther argument",
+- "dir":
+- "I"}}}},
+- "_subtypes":
+- {"prop1":"qmfProperty",
+- "prop2":"qmfProperty",
+- "statistics":"qmfProperty",
+- "meth1":"qmfMethod",
+- "meth2":"qmfMethod"},
+- "_primary_key_names": ["prop2", "prop1"]})
+-
+- print("_soc='%s'" % _soc)
+-
+- print("_soc.getPrimaryKeyList='%s'" % _soc.get_id_names())
+-
+- print("_soc.getPropertyCount='%d'" % _soc.get_property_count())
+- print("_soc.getProperties='%s'" % _soc.get_properties())
+- print("_soc.getProperty('prop2')='%s'" % _soc.get_property('prop2'))
+-
+- print("_soc.getMethodCount='%d'" % _soc.get_method_count())
+- print("_soc.getMethods='%s'" % _soc.get_methods())
+- print("_soc.getMethod('meth2')='%s'" % _soc.get_method('meth2'))
+-
+- _socmap = _soc.map_encode()
+- print("_socmap='%s'" % _socmap)
+- _soc2 = SchemaObjectClass( _map=_socmap )
+- print("_soc='%s'" % _soc)
+- print("_soc2='%s'" % _soc2)
+-
+- if _soc2.get_class_id() == _soc.get_class_id():
+- print("soc and soc2 are the same schema")
+-
+-
+- logging.info( "******** Messing around with ObjectIds ********" )
+-
+-
+- qd = QmfData( _values={"prop1":1, "prop2":True, "prop3": {"a":"map"}, "prop4": "astring"} )
+- print("qd='%s':" % qd)
+-
+- print("prop1=%d prop2=%s prop3=%s prop4=%s" % (qd.prop1, qd.prop2, qd.prop3, qd.prop4))
+-
+- print("qd map='%s'" % qd.map_encode())
+- print("qd getProperty('prop4')='%s'" % qd.get_value("prop4"))
+- qd.set_value("prop4", 4, "A test property called 4")
+- print("qd setProperty('prop4', 4)='%s'" % qd.get_value("prop4"))
+- qd.prop4 = 9
+- print("qd.prop4 = 9 ='%s'" % qd.prop4)
+- qd["prop4"] = 11
+- print("qd[prop4] = 11 ='%s'" % qd["prop4"])
+-
+- print("qd.mapEncode()='%s'" % qd.map_encode())
+- _qd2 = QmfData( _map = qd.map_encode() )
+- print("_qd2.mapEncode()='%s'" % _qd2.map_encode())
+-
+- _qmfDesc1 = QmfConsoleData( {"_values" : {"prop1": 1, "statistics": 666,
+- "prop2": 0}},
+- agent="some agent name?",
+- _schema = _soc)
+-
+- print("_qmfDesc1 map='%s'" % _qmfDesc1.map_encode())
+-
+- _qmfDesc1._set_schema( _soc )
+-
+- print("_qmfDesc1 prop2 = '%s'" % _qmfDesc1.get_value("prop2"))
+- print("_qmfDesc1 primarykey = '%s'" % _qmfDesc1.get_object_id())
+- print("_qmfDesc1 classid = '%s'" % _qmfDesc1.get_schema_class_id())
+-
+-
+- _qmfDescMap = _qmfDesc1.map_encode()
+- print("_qmfDescMap='%s'" % _qmfDescMap)
+-
+- _qmfDesc2 = QmfData( _map=_qmfDescMap, _schema=_soc )
+-
+- print("_qmfDesc2 map='%s'" % _qmfDesc2.map_encode())
+- print("_qmfDesc2 prop2 = '%s'" % _qmfDesc2.get_value("prop2"))
+- print("_qmfDesc2 primary key = '%s'" % _qmfDesc2.get_object_id())
+-
+-
+- logging.info( "******** Messing around with QmfEvents ********" )
+-
+-
+- _qmfevent1 = QmfEvent( _timestamp = 1111,
+- _schema = _sec,
+- _values = {"Argument-1": 77,
+- "Argument-3": True,
+- "Argument-2": "a string"})
+- print("_qmfevent1.mapEncode()='%s'" % _qmfevent1.map_encode())
+- print("_qmfevent1.getTimestamp()='%s'" % _qmfevent1.get_timestamp())
+-
+- _qmfevent1Map = _qmfevent1.map_encode()
+-
+- _qmfevent2 = QmfEvent(_map=_qmfevent1Map, _schema=_sec)
+- print("_qmfevent2.mapEncode()='%s'" % _qmfevent2.map_encode())
+-
+-
+- logging.info( "******** Messing around with Queries ********" )
+-
+- _q1 = QmfQuery.create_predicate(QmfQuery.TARGET_AGENT,
+- [QmfQuery.AND,
+- [QmfQuery.EQ, "vendor", [QmfQuery.QUOTE, "AVendor"]],
+- [QmfQuery.EQ, [QmfQuery.QUOTE, "SomeProduct"], "product"],
+- [QmfQuery.EQ, [QmfQuery.UNQUOTE, "name"], [QmfQuery.QUOTE, "Thingy"]],
+- [QmfQuery.OR,
+- [QmfQuery.LE, "temperature", -10],
+- [QmfQuery.FALSE],
+- [QmfQuery.EXISTS, "namey"]]])
+-
+- print("_q1.mapEncode() = [%s]" % _q1.map_encode())
+Index: extras/qmf/setup.py
+===================================================================
+--- extras/qmf/setup.py (revision 1056407)
++++ extras/qmf/setup.py (working copy)
+@@ -23,7 +23,7 @@
+ version="0.8",
+ author="Apache Qpid",
+ author_email="dev at qpid.apache.org",
+- packages=["qmf", "qmf2", "qmf2.tests"],
++ packages=["qmf"],
+ package_dir={"": "src/py"},
+ url="http://qpid.apache.org/",
+ license="Apache Software License",
More information about the scm-commits
mailing list