| #!/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() |