#!/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 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 - Print detailed information about an Agent" print " set default - 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 [] - Print all classes supported byt the default agent" print " show class [] - Show details of a class" print print "Data Commands:" print " query [] [] - Query for data from the agent" print " list - List accumulated query results" print " clear - Clear accumulated query results" print " show - Show details from a data object" print " call [] - 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 [[/@][:]]" 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()