qpid-qmf-0.32/0000775000175000017500000000000012500276324013501 5ustar jrossjross00000000000000qpid-qmf-0.32/LICENSE.txt0000664000175000017500000002613711354426146015342 0ustar jrossjross00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. qpid-qmf-0.32/src/0000775000175000017500000000000012500276324014270 5ustar jrossjross00000000000000qpid-qmf-0.32/src/py/0000775000175000017500000000000012500276324014720 5ustar jrossjross00000000000000qpid-qmf-0.32/src/py/qmf/0000775000175000017500000000000012500276324015503 5ustar jrossjross00000000000000qpid-qmf-0.32/src/py/qmf/__init__.py0000664000175000017500000000142711111344141017606 0ustar jrossjross00000000000000# # 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. # qpid-qmf-0.32/src/py/qmf/console.py0000664000175000017500000041352412365716410017534 0ustar jrossjross00000000000000# # 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. # """ Console API for Qpid Management Framework """ import os import platform import qpid import struct import socket import re import sys from qpid.datatypes import UUID from qpid.datatypes import timestamp from qpid.datatypes import datetime from qpid.exceptions import Closed from qpid.session import SessionDetached from qpid.connection import Connection, ConnectionFailed, Timeout from qpid.datatypes import Message, RangedSet, UUID from qpid.util import connect, ssl, URL from qpid.codec010 import StringCodec as Codec from threading import Lock, Condition, Thread, Semaphore from Queue import Queue, Empty from time import time, strftime, gmtime, sleep from cStringIO import StringIO #import qpid.log #qpid.log.enable(name="qpid.io.cmd", level=qpid.log.DEBUG) #=================================================================================================== # CONSOLE #=================================================================================================== class Console: """ To access the asynchronous operations, a class must be derived from Console with overrides of any combination of the available methods. """ def brokerConnected(self, broker): """ 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 def newPackage(self, name): """ Invoked when a QMF package is discovered. """ pass def newClass(self, kind, classKey): """ Invoked when a new class is discovered. Session.getSchema can be used to obtain details about the class.""" pass def newAgent(self, agent): """ Invoked when a QMF agent is discovered. """ pass def delAgent(self, agent): """ Invoked when a QMF agent disconects. """ pass def objectProps(self, broker, record): """ Invoked when an object is updated. """ pass def objectStats(self, broker, record): """ Invoked when an object is updated. """ pass def event(self, broker, event): """ Invoked when an event is raised. """ pass def heartbeat(self, agent, timestamp): """ Invoked when an agent heartbeat is received. """ pass def brokerInfo(self, broker): """ Invoked when the connection sequence reaches the point where broker information is available. """ pass def methodResponse(self, broker, seq, response): """ Invoked when a method response from an asynchronous method call is received. """ pass #=================================================================================================== # BrokerURL #=================================================================================================== class BrokerURL(URL): def __init__(self, *args, **kwargs): URL.__init__(self, *args, **kwargs) if self.port is None: if self.scheme == URL.AMQPS: self.port = 5671 else: self.port = 5672 self.authName = None self.authPass = None if self.user: self.authName = str(self.user) if self.password: self.authPass = str(self.password) def name(self): return str(self) def match(self, host, port): return socket.getaddrinfo(self.host, self.port)[0][4] == socket.getaddrinfo(host, port)[0][4] #=================================================================================================== # Object #=================================================================================================== class Object(object): """ This class defines a 'proxy' object representing a real managed object on an agent. Actions taken on this proxy are remotely affected on the real managed object. """ def __init__(self, agent, schema, codec=None, prop=None, stat=None, v2Map=None, agentName=None, kwargs={}): self._agent = agent self._session = None self._broker = None if agent: self._session = agent.session self._broker = agent.broker self._schema = schema self._properties = [] self._statistics = [] self._currentTime = None self._createTime = None self._deleteTime = 0 self._objectId = None if v2Map: self.v2Init(v2Map, agentName) return if self._agent: self._currentTime = codec.read_uint64() self._createTime = codec.read_uint64() self._deleteTime = codec.read_uint64() self._objectId = ObjectId(codec) if codec: if prop: notPresent = self._parsePresenceMasks(codec, schema) for property in schema.getProperties(): if property.name in notPresent: self._properties.append((property, None)) else: self._properties.append((property, self._session._decodeValue(codec, property.type, self._broker))) if stat: for statistic in schema.getStatistics(): self._statistics.append((statistic, self._session._decodeValue(codec, statistic.type, self._broker))) else: for property in schema.getProperties(): if property.optional: self._properties.append((property, None)) else: self._properties.append((property, self._session._defaultValue(property, self._broker, kwargs))) for statistic in schema.getStatistics(): self._statistics.append((statistic, self._session._defaultValue(statistic, self._broker, kwargs))) def v2Init(self, omap, agentName): if omap.__class__ != dict: raise Exception("QMFv2 object data must be a map/dict") if '_values' not in omap: raise Exception("QMFv2 object must have '_values' element") values = omap['_values'] for prop in self._schema.getProperties(): if prop.name in values: if prop.type == 10: # Reference self._properties.append((prop, ObjectId(values[prop.name], agentName=agentName))) else: self._properties.append((prop, values[prop.name])) for stat in self._schema.getStatistics(): if stat.name in values: self._statistics.append((stat, values[stat.name])) if '_subtypes' in omap: self._subtypes = omap['_subtypes'] if '_object_id' in omap: self._objectId = ObjectId(omap['_object_id'], agentName=agentName) else: self._objectId = None self._currentTime = omap.get("_update_ts", 0) self._createTime = omap.get("_create_ts", 0) self._deleteTime = omap.get("_delete_ts", 0) def getAgent(self): """ Return the agent from which this object was sent """ return self._agent def getBroker(self): """ Return the broker from which this object was sent """ return self._broker def getV2RoutingKey(self): """ Get the QMFv2 routing key to address this object """ return self._agent.getV2RoutingKey() def getObjectId(self): """ Return the object identifier for this object """ return self._objectId def getClassKey(self): """ Return the class-key that references the schema describing this object. """ return self._schema.getKey() def getSchema(self): """ Return the schema that describes this object. """ return self._schema def getMethods(self): """ Return a list of methods available for this object. """ return self._schema.getMethods() def getTimestamps(self): """ Return the current, creation, and deletion times for this object. """ return self._currentTime, self._createTime, self._deleteTime def isDeleted(self): """ Return True iff this object has been deleted. """ return self._deleteTime != 0 def isManaged(self): """ Return True iff this object is a proxy for a managed object on an agent. """ return self._objectId and self._agent def getIndex(self): """ Return a string describing this object's primary key. """ if self._objectId.isV2: return self._objectId.getObject() result = u"" for prop, value in self._properties: if prop.index: if result != u"": result += u":" try: valstr = unicode(self._session._displayValue(value, prop.type)) except Exception, e: valstr = u"" result += valstr return result def getProperties(self): """ Return a list of object properties """ return self._properties def getStatistics(self): """ Return a list of object statistics """ return self._statistics def mergeUpdate(self, newer): """ Replace properties and/or statistics with a newly received update """ if not self.isManaged(): raise Exception("Object is not managed") if self._objectId != newer._objectId: raise Exception("Objects with different object-ids") if len(newer.getProperties()) > 0: self._properties = newer.getProperties() if len(newer.getStatistics()) > 0: self._statistics = newer.getStatistics() self._currentTime = newer._currentTime self._deleteTime = newer._deleteTime def update(self): """ Contact the agent and retrieve the lastest property and statistic values for this object. """ if not self.isManaged(): raise Exception("Object is not managed") obj = self._agent.getObjects(_objectId=self._objectId) if obj: self.mergeUpdate(obj[0]) else: raise Exception("Underlying object no longer exists") def __repr__(self): if self.isManaged(): id = self.getObjectId().__repr__() else: id = "unmanaged" key = self.getClassKey() return key.getPackageName() + ":" + key.getClassName() +\ "[" + id + "] " + self.getIndex().encode("utf8") def __getattr__(self, name): for method in self._schema.getMethods(): if name == method.name: return lambda *args, **kwargs : self._invoke(name, args, kwargs) for prop, value in self._properties: if name == prop.name: return value if name == "_" + prop.name + "_" and prop.type == 10: # Dereference references deref = self._agent.getObjects(_objectId=value) if len(deref) != 1: return None else: return deref[0] for stat, value in self._statistics: if name == stat.name: return value # # Check to see if the name is in the schema. If so, return None (i.e. this is a not-present attribute) # for prop in self._schema.getProperties(): if name == prop.name: return None for stat in self._schema.getStatistics(): if name == stat.name: return None raise Exception("Type Object has no attribute '%s'" % name) def __setattr__(self, name, value): if name[0] == '_': super.__setattr__(self, name, value) return for prop, unusedValue in self._properties: if name == prop.name: newprop = (prop, value) newlist = [] for old, val in self._properties: if name == old.name: newlist.append(newprop) else: newlist.append((old, val)) self._properties = newlist return super.__setattr__(self, name, value) def _parseDefault(self, typ, val): try: if typ in (2, 3, 4): # 16, 32, 64 bit numbers val = int(val, 0) elif typ == 11: # bool val = val.lower() in ("t", "true", "1", "yes", "y") elif typ == 15: # map val = eval(val) except: pass return val def _handleDefaultArguments(self, method, args, kwargs): count = len([x for x in method.arguments if x.dir.find("I") != -1]) for kwarg in kwargs.keys(): if not [x for x in method.arguments if x.dir.find("I") != -1 and \ x.name == kwarg]: del kwargs[kwarg] # If there were not enough args supplied, add any defaulted arguments # from the schema (starting at the end) until we either get enough # arguments or run out of defaults while count > len(args) + len(kwargs): for arg in reversed(method.arguments): if arg.dir.find("I") != -1 and getattr(arg, "default") is not None and \ arg.name not in kwargs: # add missing defaulted value to the kwargs dict kwargs[arg.name] = self._parseDefault(arg.type, arg.default) break else: # no suitable defaulted args found, end the while loop break return count def _sendMethodRequest(self, name, args, kwargs, synchronous=False, timeWait=None): for method in self._schema.getMethods(): if name == method.name: aIdx = 0 sendCodec = Codec() seq = self._session.seqMgr._reserve((method, synchronous)) count = self._handleDefaultArguments(method, args, kwargs) if count != len(args) + len(kwargs): raise Exception("Incorrect number of arguments: expected %d, got %d" % (count, len(args) + len(kwargs))) if self._agent.isV2: # # Compose and send a QMFv2 method request # call = {} call['_object_id'] = self._objectId.asMap() call['_method_name'] = name argMap = {} for arg in method.arguments: if arg.dir.find("I") != -1: # If any kwargs match this schema arg, insert them in the proper place if arg.name in kwargs: argMap[arg.name] = kwargs[arg.name] elif aIdx < len(args): argMap[arg.name] = args[aIdx] aIdx += 1 call['_arguments'] = argMap dp = self._broker.amqpSession.delivery_properties() dp.routing_key = self.getV2RoutingKey() mp = self._broker.amqpSession.message_properties() mp.content_type = "amqp/map" 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) mp.application_headers = {'qmf.opcode':'_method_request'} sendCodec.write_map(call) smsg = Message(dp, mp, sendCodec.encoded) exchange = "qmf.default.direct" else: # # Associate this sequence with the agent hosting the object so we can correctly # route the method-response # agent = self._broker.getAgent(self._broker.getBrokerBank(), self._objectId.getAgentBank()) self._broker._setSequence(seq, agent) # # Compose and send a QMFv1 method request # self._broker._setHeader(sendCodec, 'M', seq) self._objectId.encode(sendCodec) self._schema.getKey().encode(sendCodec) sendCodec.write_str8(name) for arg in method.arguments: if arg.dir.find("I") != -1: self._session._encodeValue(sendCodec, args[aIdx], arg.type) aIdx += 1 smsg = self._broker._message(sendCodec.encoded, "agent.%d.%s" % (self._objectId.getBrokerBank(), self._objectId.getAgentBank())) exchange = "qpid.management" if synchronous: try: self._broker.cv.acquire() self._broker.syncInFlight = True finally: self._broker.cv.release() self._broker._send(smsg, exchange) return seq return None def _invoke(self, name, args, kwargs): if not self.isManaged(): raise Exception("Object is not managed") if "_timeout" in kwargs: timeout = kwargs["_timeout"] else: timeout = self._broker.SYNC_TIME if "_async" in kwargs and kwargs["_async"]: sync = False if "_timeout" not in kwargs: timeout = None else: sync = True # Remove special "meta" kwargs before handing to _sendMethodRequest() to process if "_timeout" in kwargs: del kwargs["_timeout"] if "_async" in kwargs: del kwargs["_async"] seq = self._sendMethodRequest(name, args, kwargs, sync, timeout) if seq: if not sync: return seq self._broker.cv.acquire() try: starttime = time() while self._broker.syncInFlight and self._broker.error == None: self._broker.cv.wait(timeout) if time() - starttime > timeout: raise RuntimeError("Timed out waiting for method to respond") finally: self._session.seqMgr._release(seq) self._broker.cv.release() if self._broker.error != None: errorText = self._broker.error self._broker.error = None raise Exception(errorText) return self._broker.syncResult raise Exception("Invalid Method (software defect) [%s]" % name) def _encodeUnmanaged(self, codec): codec.write_uint8(20) codec.write_str8(self._schema.getKey().getPackageName()) codec.write_str8(self._schema.getKey().getClassName()) codec.write_bin128(self._schema.getKey().getHash()) # emit presence masks for optional properties mask = 0 bit = 0 for prop, value in self._properties: if prop.optional: if bit == 0: bit = 1 if value: mask |= bit bit = bit << 1 if bit == 256: bit = 0 codec.write_uint8(mask) mask = 0 if bit != 0: codec.write_uint8(mask) # encode properties for prop, value in self._properties: if value != None: self._session._encodeValue(codec, value, prop.type) # encode statistics for stat, value in self._statistics: self._session._encodeValue(codec, value, stat.type) def _parsePresenceMasks(self, codec, schema): excludeList = [] bit = 0 for property in schema.getProperties(): if property.optional: if bit == 0: mask = codec.read_uint8() bit = 1 if (mask & bit) == 0: excludeList.append(property.name) bit *= 2 if bit == 256: bit = 0 return excludeList #=================================================================================================== # Session #=================================================================================================== class Session: """ An instance of the Session class represents a console session running against one or more QMF brokers. A single instance of Session is needed to interact with the management framework as a console. """ _CONTEXT_SYNC = 1 _CONTEXT_STARTUP = 2 _CONTEXT_MULTIGET = 3 DEFAULT_GET_WAIT_TIME = 60 ENCODINGS = { str: 7, timestamp: 8, datetime: 8, int: 9, long: 9, float: 13, UUID: 14, Object: 20, list: 21 } def __init__(self, console=None, rcvObjects=True, rcvEvents=True, rcvHeartbeats=True, manageConnections=False, userBindings=False): """ Initialize a session. If the console argument is provided, the more advanced asynchronous features are available. If console is defaulted, the session will operate in a simpler, synchronous manner. The rcvObjects, rcvEvents, and rcvHeartbeats arguments are meaningful only if 'console' is provided. They control whether object updates, events, and agent-heartbeats are subscribed to. If the console is not interested in receiving one or more of the above, setting the argument to False will reduce tha bandwidth used by the API. If manageConnections is set to True, the Session object will manage connections to the brokers. This means that if a broker is unreachable, it will retry until a connection can be established. If a connection is lost, the Session will attempt to reconnect. If manageConnections is set to False, the user is responsible for handing failures. In this case, an unreachable broker will cause addBroker to raise an exception. If userBindings is set to False (the default) and rcvObjects is True, the console will receive data for all object classes. If userBindings is set to True, the user must select which classes the console shall receive by invoking the bindPackage or bindClass methods. This allows the console to be configured to receive only information that is relavant to a particular application. If rcvObjects id False, userBindings has no meaning. """ self.console = console self.brokers = [] self.schemaCache = SchemaCache() self.seqMgr = SequenceManager() self.cv = Condition() self.syncSequenceList = [] self.getResult = [] self.getSelect = [] self.error = None self.rcvObjects = rcvObjects self.rcvEvents = rcvEvents self.rcvHeartbeats = rcvHeartbeats self.userBindings = userBindings if self.console == None: self.rcvObjects = False self.rcvEvents = False self.rcvHeartbeats = False self.v1BindingKeyList, self.v2BindingKeyList = self._bindingKeys() self.manageConnections = manageConnections # callback filters: self.agent_filter = [] # (vendor, product, instance) || v1-agent-label-str self.class_filter = [] # (pkg, class) self.event_filter = [] # (pkg, event) self.agent_heartbeat_min = 10 # minimum agent heartbeat timeout interval self.agent_heartbeat_miss = 3 # # of heartbeats to miss before deleting agent if self.userBindings and not self.console: raise Exception("userBindings can't be set unless a console is provided.") def close(self): """ Releases all resources held by the session. Must be called by the application when it is done with the Session object. """ self.cv.acquire() try: while len(self.brokers): b = self.brokers.pop() try: b._shutdown() except: pass finally: self.cv.release() def _getBrokerForAgentAddr(self, agent_addr): try: self.cv.acquire() key = (1, agent_addr) for b in self.brokers: if key in b.agents: return b finally: self.cv.release() return None def _getAgentForAgentAddr(self, agent_addr): try: self.cv.acquire() key = agent_addr for b in self.brokers: if key in b.agents: return b.agents[key] finally: self.cv.release() return None def __repr__(self): return "QMF Console Session Manager (brokers: %d)" % len(self.brokers) def addBroker(self, target="localhost", timeout=None, mechanisms=None, sessTimeout=None, **connectArgs): """ Connect to a Qpid broker. Returns an object of type Broker. Will raise an exception if the session is not managing the connection and the connection setup to the broker fails. """ if isinstance(target, BrokerURL): url = target else: url = BrokerURL(target) broker = Broker(self, url.host, url.port, mechanisms, url.authName, url.authPass, ssl = url.scheme == URL.AMQPS, connTimeout=timeout, sessTimeout=sessTimeout, **connectArgs) self.brokers.append(broker) return broker def delBroker(self, broker): """ Disconnect from a broker, and deallocate the broker proxy object. The 'broker' argument is the object returned from the addBroker call. Errors are ignored. """ broker._shutdown() self.brokers.remove(broker) del broker def getPackages(self): """ Get the list of known QMF packages """ for broker in self.brokers: broker._waitForStable() return self.schemaCache.getPackages() def getClasses(self, packageName): """ Get the list of known classes within a QMF package """ for broker in self.brokers: broker._waitForStable() return self.schemaCache.getClasses(packageName) def getSchema(self, classKey): """ Get the schema for a QMF class """ for broker in self.brokers: broker._waitForStable() return self.schemaCache.getSchema(classKey) def bindPackage(self, packageName): """ Filter object and event callbacks to only those elements of the specified package. Also filters newPackage and newClass callbacks to the given package. Only valid if userBindings is True. """ if not self.userBindings: raise Exception("userBindings option must be set for this Session.") if not self.rcvObjects and not self.rcvEvents: raise Exception("Session needs to be configured to receive events or objects.") v1keys = ["console.obj.*.*.%s.#" % packageName, "console.event.*.*.%s.#" % packageName] v2keys = ["agent.ind.data.%s.#" % packageName.replace(".", "_"), "agent.ind.event.%s.#" % packageName.replace(".", "_"),] if (packageName, None) not in self.class_filter: self.class_filter.append((packageName, None)) if (packageName, None) not in self.event_filter: self.event_filter.append((packageName, None)) self.v1BindingKeyList.extend(v1keys) self.v2BindingKeyList.extend(v2keys) for broker in self.brokers: if broker.isConnected(): for v1key in v1keys: broker.amqpSession.exchange_bind(exchange="qpid.management", queue=broker.topicName, binding_key=v1key) if broker.brokerSupportsV2: for v2key in v2keys: # data indications should arrive on the unsolicited indication queue broker.amqpSession.exchange_bind(exchange="qmf.default.topic", queue=broker.v2_topic_queue_ui, binding_key=v2key) def bindClass(self, pname, cname=None): """ Filter object callbacks to only those objects of the specified package and optional class. Will also filter newPackage/newClass callbacks to the specified package and class. Only valid if userBindings is True and rcvObjects is True. """ if not self.userBindings: raise Exception("userBindings option must be set for this Session.") if not self.rcvObjects: raise Exception("Session needs to be configured with rcvObjects=True.") if cname is not None: v1key = "console.obj.*.*.%s.%s.#" % (pname, cname) v2key = "agent.ind.data.%s.%s.#" % (pname.replace(".", "_"), cname.replace(".", "_")) else: v1key = "console.obj.*.*.%s.#" % pname v2key = "agent.ind.data.%s.#" % pname.replace(".", "_") self.v1BindingKeyList.append(v1key) self.v2BindingKeyList.append(v2key) if (pname, cname) not in self.class_filter: self.class_filter.append((pname, cname)) for broker in self.brokers: if broker.isConnected(): broker.amqpSession.exchange_bind(exchange="qpid.management", queue=broker.topicName, binding_key=v1key) if broker.brokerSupportsV2: # data indications should arrive on the unsolicited indication queue broker.amqpSession.exchange_bind(exchange="qmf.default.topic", queue=broker.v2_topic_queue_ui, binding_key=v2key) def bindClassKey(self, classKey): """ Filter object callbacks to only those objects of the specified class. Will also filter newPackage/newClass callbacks to the specified package and class. Only valid if userBindings is True and rcvObjects is True. """ pname = classKey.getPackageName() cname = classKey.getClassName() self.bindClass(pname, cname) def bindEvent(self, pname, ename=None): """ Filter event callbacks only from a particular class by package and event name, or all events in a package if ename=None. Will also filter newPackage/newClass callbacks to the specified package and class. Only valid if userBindings is True and rcvEvents is True. """ if not self.userBindings: raise Exception("userBindings option must be set for this Session.") if not self.rcvEvents: raise Exception("Session needs to be configured with rcvEvents=True.") if ename is not None: v1key = "console.event.*.*.%s.%s.#" % (pname, ename) v2key = "agent.ind.event.%s.%s.#" % (pname.replace(".", "_"), ename.replace(".", "_")) else: v1key = "console.event.*.*.%s.#" % pname v2key = "agent.ind.event.%s.#" % pname.replace(".", "_") self.v1BindingKeyList.append(v1key) self.v2BindingKeyList.append(v2key) if (pname, ename) not in self.event_filter: self.event_filter.append((pname, ename)) for broker in self.brokers: if broker.isConnected(): broker.amqpSession.exchange_bind(exchange="qpid.management", queue=broker.topicName, binding_key=v1key) if broker.brokerSupportsV2: # event indications should arrive on the unsolicited indication queue broker.amqpSession.exchange_bind(exchange="qmf.default.topic", queue=broker.v2_topic_queue_ui, binding_key=v2key) def bindEventKey(self, eventKey): """ Filter event callbacks only from a particular class key. Will also filter newPackage/newClass callbacks to the specified package and class. Only valid if userBindings is True and rcvEvents is True. """ pname = eventKey.getPackageName() ename = eventKey.getClassName() self.bindEvent(pname, ename) def bindAgent(self, vendor=None, product=None, instance=None, label=None): """ Receive heartbeats, newAgent and delAgent callbacks only for those agent(s) that match the passed identification criteria: V2 agents: vendor, optionally product and instance strings V1 agents: the label string. Only valid if userBindings is True. """ if not self.userBindings: raise Exception("Session not configured for binding specific agents.") if vendor is None and label is None: raise Exception("Must specify at least a vendor (V2 agents)" " or label (V1 agents).") if vendor: # V2 agent identification if product is not None: v2key = "agent.ind.heartbeat.%s.%s.#" % (vendor.replace(".", "_"), product.replace(".", "_")) else: v2key = "agent.ind.heartbeat.%s.#" % vendor.replace(".", "_") self.v2BindingKeyList.append(v2key) # allow wildcards - only add filter if a non-wildcarded component is given if vendor == "*": vendor = None if product == "*": product = None if instance == "*": instance = None if vendor or product or instance: if (vendor, product, instance) not in self.agent_filter: self.agent_filter.append((vendor, product, instance)) for broker in self.brokers: if broker.isConnected(): if broker.brokerSupportsV2: # heartbeats should arrive on the heartbeat queue broker.amqpSession.exchange_bind(exchange="qmf.default.topic", queue=broker.v2_topic_queue_hb, binding_key=v2key) elif label != "*": # non-wildcard V1 agent label # V1 format heartbeats do not have any agent identifier in the routing # key, so we cannot filter them by bindings. if label not in self.agent_filter: self.agent_filter.append(label) def getAgents(self, broker=None): """ Get a list of currently known agents """ brokerList = [] if broker == None: for b in self.brokers: brokerList.append(b) else: brokerList.append(broker) for b in brokerList: b._waitForStable() agentList = [] for b in brokerList: for a in b.getAgents(): agentList.append(a) return agentList def makeObject(self, classKey, **kwargs): """ Create a new, unmanaged object of the schema indicated by classKey """ schema = self.getSchema(classKey) if schema == None: raise Exception("Schema not found for classKey") return Object(None, schema, None, True, True, kwargs) def getObjects(self, **kwargs): """ Get a list of objects from QMF agents. All arguments are passed by name(keyword). The class for queried objects may be specified in one of the following ways: _schema = - supply a schema object returned from getSchema. _key = - supply a classKey from the list returned by getClasses. _class = - supply a class name as a string. If the class name exists in multiple packages, a _package argument may also be supplied. _objectId = - get the object referenced by the object-id If objects should be obtained from only one agent, use the following argument. Otherwise, the query will go to all agents. _agent = - supply an agent from the list returned by getAgents. If the get query is to be restricted to one broker (as opposed to all connected brokers), add the following argument: _broker = - supply a broker as returned by addBroker. The default timeout for this synchronous operation is 60 seconds. To change the timeout, use the following argument: _timeout =