17078Smjnelson#! /usr/bin/python 27078Smjnelson# 37078Smjnelson# CDDL HEADER START 47078Smjnelson# 57078Smjnelson# The contents of this file are subject to the terms of the 67078Smjnelson# Common Development and Distribution License (the "License"). 77078Smjnelson# You may not use this file except in compliance with the License. 87078Smjnelson# 97078Smjnelson# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 107078Smjnelson# or http://www.opensolaris.org/os/licensing. 117078Smjnelson# See the License for the specific language governing permissions 127078Smjnelson# and limitations under the License. 137078Smjnelson# 147078Smjnelson# When distributing Covered Code, include this CDDL HEADER in each 157078Smjnelson# file and include the License file at usr/src/OPENSOLARIS.LICENSE. 167078Smjnelson# If applicable, add the following below this CDDL HEADER, with the 177078Smjnelson# fields enclosed by brackets "[]" replaced with your own identifying 187078Smjnelson# information: Portions Copyright [yyyy] [name of copyright owner] 197078Smjnelson# 207078Smjnelson# CDDL HEADER END 217078Smjnelson# 227078Smjnelson 237078Smjnelson# 247078Smjnelson# Copyright 2008 Sun Microsystems, Inc. All rights reserved. 257078Smjnelson# Use is subject to license terms. 267078Smjnelson# 277078Smjnelson 287078Smjnelson# 297078Smjnelson# Various database lookup classes/methods, i.e.: 307078Smjnelson# * monaco 317078Smjnelson# * bugs.opensolaris.org (b.o.o.) 327078Smjnelson# * opensolaris.org/cgi/arc.py (for ARC) 337078Smjnelson# 347078Smjnelson 357078Smjnelsonimport re 367078Smjnelsonimport urllib 377078Smjnelsonimport htmllib 387078Smjnelsonimport os 397078Smjnelsonfrom socket import socket, AF_INET, SOCK_STREAM 407078Smjnelson 417078Smjnelsonfrom onbld.Checks import onSWAN 427078Smjnelson 437078Smjnelsonclass BugException(Exception): 447078Smjnelson def __init__(self, data=''): 457078Smjnelson self.data = data 467078Smjnelson Exception.__init__(self, data) 477078Smjnelson 487078Smjnelson def __str__(self): 497078Smjnelson return "Unknown error: %s" % self.data 507078Smjnelson 517078Smjnelsonclass NonExistentBug(BugException): 527078Smjnelson def __str__(self): 537078Smjnelson return "Bug %s does not exist" % self.data 547078Smjnelson 557078Smjnelsonclass Monaco(object): 567078Smjnelson """ 577078Smjnelson Query bug database. 587078Smjnelson 597078Smjnelson Methods: 607078Smjnelson queryBugs() 617078Smjnelson expertQuery() 627078Smjnelson """ 637078Smjnelson 647078Smjnelson def __init__(self): 657078Smjnelson self.__baseURL = "http://hestia.sfbay.sun.com/cgi-bin/expert?" 667078Smjnelson 677078Smjnelson def expertQuery(self, cmd, format="Normal+text", header=False): 687078Smjnelson """Return results of user-supplied bug query. 697078Smjnelson 707078Smjnelson Argument: 717078Smjnelson cmd: query to run 727078Smjnelson 737078Smjnelson Keyword arguments: 747078Smjnelson format: desired output format (default="Normal+text") 757078Smjnelson header: include headers in output? (default=False) 767078Smjnelson 777078Smjnelson Returns: 787078Smjnelson List of lines representing the output from Monaco 797078Smjnelson """ 807078Smjnelson 817078Smjnelson url = self.__baseURL + "format=" + format + ";Go=2;" 827078Smjnelson if not header: url += "no_header=on;" 837078Smjnelson url += "cmds=" + urllib.quote_plus("\n".join(cmd)) 847078Smjnelson myMonaco = urllib.urlopen(url) 857078Smjnelson return myMonaco.readlines() 867078Smjnelson 877078Smjnelson def queryBugs(self, crs): 887078Smjnelson """Return all info for requested change reports. 897078Smjnelson 907078Smjnelson Argument: 917078Smjnelson crs: list of change request ids 927078Smjnelson 937078Smjnelson Returns: 947078Smjnelson Dictionary, mapping CR=>dictionary, where the nested dictionary 957078Smjnelson is a mapping of field=>value 967078Smjnelson """ 97*7711Srichlowe@richlowe.net monacoFields = [ "cr_number", "category", "sub_category", 987078Smjnelson "area", "release", "build", "responsible_manager", 997078Smjnelson "responsible_engineer", "priority", "status", "sub_status", 100*7711Srichlowe@richlowe.net "submitted_by", "date_submitted", "synopsis" ] 1017078Smjnelson cmd = [] 1027078Smjnelson cmd.append("set What = cr." + ', cr.'.join(monacoFields)) 1037078Smjnelson cmd.append("") 1047078Smjnelson cmd.append("set Which = cr.cr_number in (" + ','.join(crs) +")") 1057078Smjnelson cmd.append("") 1067078Smjnelson cmd.append("set FinalClauses = order by cr.cr_number") 1077078Smjnelson cmd.append("") 1087078Smjnelson cmd.append("doMeta genQuery cr") 1097078Smjnelson output = self.expertQuery(cmd, "Pipe-delimited+text") 1107078Smjnelson results = {} 1117078Smjnelson for line in output: 112*7711Srichlowe@richlowe.net line = line.rstrip('\n') 113*7711Srichlowe@richlowe.net 114*7711Srichlowe@richlowe.net # 115*7711Srichlowe@richlowe.net # We request synopsis last, and split on only 116*7711Srichlowe@richlowe.net # the number of separators that we expect to 117*7711Srichlowe@richlowe.net # see such that a | in the synopsis doesn't 118*7711Srichlowe@richlowe.net # throw us out of whack. 119*7711Srichlowe@richlowe.net # 120*7711Srichlowe@richlowe.net values = line.split('|', len(monacoFields) - 1) 1217078Smjnelson v = 0 1227078Smjnelson cr = values[0] 1237078Smjnelson results[cr] = {} 1247078Smjnelson for field in monacoFields: 1257078Smjnelson results[cr][field] = values[v] 1267078Smjnelson v += 1 1277078Smjnelson return results 1287078Smjnelson 1297078Smjnelsonclass BooBug(object): 1307078Smjnelson """Look up a single bug on bugs.opensolaris.org.""" 1317078Smjnelson def __init__(self, cr): 1327078Smjnelson cr = str(cr) 1337078Smjnelson url = "http://bugs.opensolaris.org/view_bug.do?bug_id="+cr 1347078Smjnelson data = urllib.urlopen(url).readlines() 1357078Smjnelson self.__fields = {} 1367078Smjnelson self.__fields["cr_number"] = cr 1377078Smjnelson htmlParser = htmllib.HTMLParser(None) 1387078Smjnelson metaHtmlRe = re.compile(r'^<meta name="([^"]+)" content="([^"]*)">$') 1397078Smjnelson for line in data: 1407078Smjnelson m = metaHtmlRe.search(line) 1417078Smjnelson if not m: 1427078Smjnelson continue 1437078Smjnelson val = urllib.unquote(m.group(2)) 1447078Smjnelson htmlParser.save_bgn() 1457078Smjnelson htmlParser.feed(val) 1467078Smjnelson self.__fields[m.group(1)] = htmlParser.save_end() 1477078Smjnelson htmlParser.close() 1487078Smjnelson if "synopsis" not in self.__fields: 1497078Smjnelson raise NonExistentBug(cr) 1507078Smjnelson 1517078Smjnelson def synopsis(self): 1527078Smjnelson return self.__fields["synopsis"] 1537078Smjnelson def product(self): 1547078Smjnelson return self.__fields["product"] 1557078Smjnelson def cat(self): 1567078Smjnelson return self.__fields["category"] 1577078Smjnelson def subcat(self): 1587078Smjnelson return self.__fields["subcategory"] 1597078Smjnelson def keywords(self): 1607078Smjnelson return self.__fields["keywords"] 1617078Smjnelson def state(self): 1627078Smjnelson return self.__fields["state"] 1637078Smjnelson def submit_date(self): 1647078Smjnelson return self.__fields["submit_date"] 1657078Smjnelson def type(self): 1667078Smjnelson return self.__fields["type"] 1677078Smjnelson def date(self): 1687078Smjnelson return self.__fields["date"] 1697078Smjnelson def number(self): 1707078Smjnelson return self.__fields["cr_number"] 1717078Smjnelson 1727078Smjnelsonclass BugDB(object): 1737078Smjnelson """Lookup change requests. 1747078Smjnelson 1757078Smjnelson Object can be used on or off of SWAN, using either monaco or 1767078Smjnelson bugs.opensolaris.org as a database. 1777078Smjnelson 1787078Smjnelson Usage: 1797078Smjnelson bdb = BugDB() 1807078Smjnelson r = bdb.lookup("6455550") 1817078Smjnelson print r["6455550"]["synopsis"] 1827078Smjnelson r = bdb.lookup(["6455550", "6505625"]) 1837078Smjnelson print r["6505625"]["synopsis"] 1847078Smjnelson """ 1857078Smjnelson 1867078Smjnelson def __init__(self, forceBoo = False): 1877078Smjnelson """Create a BugDB object. 1887078Smjnelson 1897078Smjnelson Keyword argument: 1907078Smjnelson forceBoo: use b.o.o even from SWAN (default=False) 1917078Smjnelson """ 1927078Smjnelson if forceBoo: 1937078Smjnelson self.__onSWAN = False 1947078Smjnelson else: 1957078Smjnelson self.__onSWAN = onSWAN() 1967078Smjnelson if self.__onSWAN: 1977078Smjnelson self.__m = Monaco() 1987078Smjnelson 1997078Smjnelson def lookup(self, crs): 2007078Smjnelson """Return all info for requested change reports. 2017078Smjnelson 2027078Smjnelson Argument: 2037078Smjnelson crs: one change request id (may be integer, string, or list), 2047078Smjnelson or multiple change request ids (must be a list) 2057078Smjnelson 2067078Smjnelson Returns: 2077078Smjnelson Dictionary, mapping CR=>dictionary, where the nested dictionary 2087078Smjnelson is a mapping of field=>value 2097078Smjnelson """ 2107078Smjnelson if not isinstance(crs, list): 2117078Smjnelson crs = [str(crs)] 2127078Smjnelson if self.__onSWAN: 2137078Smjnelson results = self.__m.queryBugs(crs) 2147078Smjnelson return self.__m.queryBugs(crs) 2157078Smjnelson # else we're off-swan and querying via boo, which we can 2167078Smjnelson # only do one bug at a time 2177078Smjnelson results = {} 2187078Smjnelson for cr in crs: 2197078Smjnelson cr = str(cr) 2207078Smjnelson try: 2217078Smjnelson b = BooBug(cr) 2227078Smjnelson except NonExistentBug: 2237078Smjnelson continue 2247078Smjnelson 2257078Smjnelson results[cr] = {} 2267078Smjnelson results[cr]["cr_number"] = cr 2277078Smjnelson results[cr]["product"] = b.product() 2287078Smjnelson results[cr]["synopsis"] = b.synopsis() 2297078Smjnelson results[cr]["category"] = b.cat() 2307078Smjnelson results[cr]["sub_category"] = b.subcat() 2317078Smjnelson results[cr]["keywords"] = b.keywords() 2327078Smjnelson results[cr]["status"] = b.state() 2337078Smjnelson results[cr]["date_submitted"] = b.submit_date() 2347078Smjnelson results[cr]["type"] = b.type() 2357078Smjnelson results[cr]["date"] = b.date() 2367078Smjnelson 2377078Smjnelson return results 2387078Smjnelson 2397078Smjnelson#################################################################### 2407078Smjnelson 2417078Smjnelsonclass ARC(object): 2427078Smjnelson """Lookup an ARC case on opensolaris.org. 2437078Smjnelson 2447078Smjnelson Usage: 2457078Smjnelson a = ARC("PSARC", "2008/002") 2467078Smjnelson if a.valid(): 2477078Smjnelson print a.name() 2487078Smjnelson """ 2497078Smjnelson def __init__(self, arc, case): 2507078Smjnelson self.__valid = False 2517078Smjnelson q = "http://opensolaris.org/cgi/arc.py?n=1" 2527078Smjnelson q += "&arc0=" + arc 2537078Smjnelson q += "&case0=" + case 2547078Smjnelson data = urllib.urlopen(q).readlines() 2557078Smjnelson self.__fields = {} 2567078Smjnelson for line in data: 2577078Smjnelson line = line.rstrip('\n') 2587078Smjnelson fields = line.split('|') 2597078Smjnelson validity = fields[0] 2607078Smjnelson 2617078Smjnelson if validity != "0": 2627078Smjnelson return 2637078Smjnelson else: 2647078Smjnelson self.__fields["Name"] = fields[2] 2657078Smjnelson 2667078Smjnelson self.__valid = True 2677078Smjnelson 2687078Smjnelson def valid(self): 2697078Smjnelson return self.__valid 2707078Smjnelson def name(self): 2717078Smjnelson return self.__fields["Name"] 2727078Smjnelson def status(self): 2737078Smjnelson return self.__fields["Status"] 2747078Smjnelson def type(self): 2757078Smjnelson return self.__fields["Type"] 2767078Smjnelson 2777078Smjnelson#################################################################### 2787078Smjnelson 2797078Smjnelson# Pointers to the webrti server hostname & port to use 2807078Smjnelson# Using it directly is probably not *officially* supported, so we'll 2817078Smjnelson# have a pointer to the official `webrticli` command line interface 2827078Smjnelson# if using a direct socket connection fails for some reason, so we 2837078Smjnelson# have a fallback 2847078SmjnelsonWEBRTI_HOST = 'webrti.sfbay.sun.com' 2857078SmjnelsonWEBRTI_PORT = 9188 2867078SmjnelsonWEBRTICLI = '/net/webrti.sfbay.sun.com/export/home/bin/webrticli' 2877078Smjnelson 2887078Smjnelson 2897078Smjnelsonclass RtiException(Exception): 2907078Smjnelson def __init__(self, data=''): 2917078Smjnelson self.data = data 2927078Smjnelson Exception.__init__(self, data) 2937078Smjnelson 2947078Smjnelson def __str__(self): 2957078Smjnelson return "Unknown error: %s" % self.data 2967078Smjnelson 2977078Smjnelson# RtiInvalidOutput & RtiCallFailed are our "own" failures 2987078Smjnelson# The other exceptions are triggered from WebRTI itself 2997078Smjnelsonclass RtiInvalidOutput(RtiException): 3007078Smjnelson def __str__(self): 3017078Smjnelson return "Invalid output from WebRTI: %s" % self.data 3027078Smjnelson 3037078Smjnelsonclass RtiCallFailed(RtiException): 3047078Smjnelson def __str__(self): 3057078Smjnelson return "Unable to call webrti: %s" % self.data 3067078Smjnelson 3077078Smjnelsonclass RtiSystemProblem(RtiException): 3087078Smjnelson def __str__(self): 3097078Smjnelson return "RTI status cannot be determined: %s" % self.data 3107078Smjnelson 3117078Smjnelsonclass RtiIncorrectCR(RtiException): 3127078Smjnelson def __str__(self): 3137078Smjnelson return "Incorrect CR number specified: %s" % self.data 3147078Smjnelson 3157078Smjnelsonclass RtiNotFound(RtiException): 3167078Smjnelson def __str__(self): 3177078Smjnelson return "RTI not found: %s" % self.data 3187078Smjnelson 3197078Smjnelsonclass RtiNeedConsolidation(RtiException): 3207078Smjnelson def __str__(self): 3217078Smjnelson return "More than one consolidation has this CR: %s" % self.data 3227078Smjnelson 3237078Smjnelsonclass RtiBadGate(RtiException): 3247078Smjnelson def __str__(self): 3257078Smjnelson return "Incorrect gate name specified: %s" % self.data 3267078Smjnelson 3277078Smjnelsonclass RtiOffSwan(RtiException): 3287078Smjnelson def __str__(self): 3297078Smjnelson return "RTI status checks need SWAN access: %s" % self.data 3307078Smjnelson 3317078SmjnelsonWEBRTI_ERRORS = { 3327078Smjnelson '1': RtiSystemProblem, 3337078Smjnelson '2': RtiIncorrectCR, 3347078Smjnelson '3': RtiNotFound, 3357078Smjnelson '4': RtiNeedConsolidation, 3367078Smjnelson '5': RtiBadGate, 3377078Smjnelson} 3387078Smjnelson 3397078Smjnelson# Our Rti object which we'll use to represent an Rti query 3407078Smjnelson# It's really just a wrapper around the Rti connection, and attempts 3417078Smjnelson# to establish a direct socket connection and query the webrti server 3427078Smjnelson# directly (thus avoiding a system/fork/exec call). If it fails, it 3437078Smjnelson# falls back to the webrticli command line client. 3447078Smjnelson 3457078SmjnelsonreturnCodeRe = re.compile(r'.*RETURN_CODE=(\d+)') 3467078Smjnelsonclass Rti: 3477078Smjnelson """Lookup an RTI. 3487078Smjnelson 3497078Smjnelson Usage: 3507078Smjnelson r = Rti("6640538") 3517078Smjnelson print r.rtiNumber(); 3527078Smjnelson """ 3537078Smjnelson 3547078Smjnelson def __init__(self, cr, gate=None, consolidation=None): 3557078Smjnelson """Create an Rti object for the specified change request. 3567078Smjnelson 3577078Smjnelson Argument: 3587078Smjnelson cr: change request id 3597078Smjnelson 3607078Smjnelson Keyword arguments, to limit scope of RTI search: 3617078Smjnelson gate: path to gate workspace (default=None) 3627078Smjnelson consolidation: consolidation name (default=None) 3637078Smjnelson """ 3647078Smjnelson 3657078Smjnelson bufSz = 1024 3667078Smjnelson addr = (WEBRTI_HOST, WEBRTI_PORT) 3677078Smjnelson # If the passed 'cr' was given as an int, then wrap it 3687078Smjnelson # into a string to make our life easier 3697078Smjnelson if isinstance(cr, int): 3707078Smjnelson cr = str(cr) 3717078Smjnelson self.__queryCr = cr 3727078Smjnelson self.__queryGate = gate 3737078Smjnelson self.__queryConsolidation = consolidation 3747078Smjnelson 3757078Smjnelson try: 3767078Smjnelson # try to use a direct connection to the 3777078Smjnelson # webrti server first 3787078Smjnelson sock = socket(AF_INET, SOCK_STREAM) 3797078Smjnelson sock.connect(addr) 3807078Smjnelson command = "WEBRTICLI/1.0\nRTIstatus\n%s\n" % cr 3817078Smjnelson if consolidation: 3827078Smjnelson command += "-c\n%s\n" % consolidation 3837078Smjnelson if gate: 3847078Smjnelson command += "-g\n%s\n" % gate 3857078Smjnelson command += "\n" 3867078Smjnelson sock.send(command) 3877078Smjnelson dataList = [] 3887078Smjnelson # keep receiving data from the socket until the 3897078Smjnelson # server closes the connection 3907078Smjnelson stillReceiving = True 3917078Smjnelson while stillReceiving: 3927078Smjnelson dataPiece = sock.recv(bufSz) 3937078Smjnelson if dataPiece: 3947078Smjnelson dataList.append(dataPiece) 3957078Smjnelson else: 3967078Smjnelson stillReceiving = False 3977078Smjnelson # create the lines, skipping the first 3987078Smjnelson # ("WEBRTCLI/1.0\n") 3997078Smjnelson data = '\n'.join(''.join(dataList).split('\n')[1:]) 4007078Smjnelson except: 4017078Smjnelson if not onSWAN(): 4027078Smjnelson raise RtiOffSwan(cr) 4037078Smjnelson 4047078Smjnelson if not os.path.exists(WEBRTICLI): 4057078Smjnelson raise RtiCallFailed('not found') 4067078Smjnelson 4077078Smjnelson # fallback to the "supported" webrticli interface 4087078Smjnelson command = WEBRTICLI 4097078Smjnelson if consolidation: 4107078Smjnelson command += " -c " + consolidation 4117078Smjnelson if gate: 4127078Smjnelson command += " -g " + gate 4137078Smjnelson command += " RTIstatus " + cr 4147078Smjnelson 4157078Smjnelson try: 4167078Smjnelson cliPipe = os.popen(command) 4177078Smjnelson except: 4187078Smjnelson # we couldn't call the webrticli for some 4197078Smjnelson # reason, so return a failure 4207078Smjnelson raise RtiCallFailed('unknown') 4217078Smjnelson 4227078Smjnelson data = cliPipe.readline() 4237078Smjnelson 4247078Smjnelson # parse the data to see if we got a return code 4257078Smjnelson # if we did, then that's bad. if we didn't, 4267078Smjnelson # then our call was successfully 4277078Smjnelson m = returnCodeRe.search(data) 4287078Smjnelson if m: 4297078Smjnelson # we got a return code, set it in our 4307078Smjnelson # object, set the webRtiOutput for debugging 4317078Smjnelson # or logging, and return a failure 4327078Smjnelson if m.group(1) in WEBRTI_ERRORS: 4337078Smjnelson exc = WEBRTI_ERRORS[m.group(1)] 4347078Smjnelson else: 4357078Smjnelson exc = RtiException 4367078Smjnelson raise exc(data) 4377078Smjnelson 4387078Smjnelson if data.count('\n') != 1: 4397078Smjnelson # there shouldn't be more than one line in 4407078Smjnelson # the output. if we got more than one line, 4417078Smjnelson # then let's be paranoid, and abort. 4427078Smjnelson raise RtiInvalidOutput(data) 4437078Smjnelson 4447078Smjnelson # At this point, we should have valid data 4457078Smjnelson data = data.rstrip('\r\n') 4467078Smjnelson self.__webRtiOutput = data 4477078Smjnelson self.__fields = data.split(':') 4487078Smjnelson self.__mainCR = self.__fields[0] 4497078Smjnelson self.__rtiNumber = self.__fields[1] 4507078Smjnelson self.__consolidation = self.__fields[2] 4517078Smjnelson self.__project = self.__fields[3] 4527078Smjnelson self.__status = self.__fields[4] 4537078Smjnelson self.__rtiType = self.__fields[5] 4547078Smjnelson 4557078Smjnelson # accessors in case callers need the raw data 4567078Smjnelson def mainCR(self): 4577078Smjnelson return self.__mainCR 4587078Smjnelson def rtiNumber(self): 4597078Smjnelson return self.__rtiNumber 4607078Smjnelson def consolidation(self): 4617078Smjnelson return self.__consolidation 4627078Smjnelson def project(self): 4637078Smjnelson return self.__project 4647078Smjnelson def status(self): 4657078Smjnelson return self.__status 4667078Smjnelson def rtiType(self): 4677078Smjnelson return self.__rtiType 4687078Smjnelson def queryCr(self): 4697078Smjnelson return self.__queryCr 4707078Smjnelson def queryGate(self): 4717078Smjnelson return self.__queryGate 4727078Smjnelson def queryConsolidation(self): 4737078Smjnelson return self.__queryConsolidation 4747078Smjnelson 4757078Smjnelson # in practice, most callers only care about the following 4767078Smjnelson def accepted(self): 4777078Smjnelson return (self.__status == "S_ACCEPTED") 4787078Smjnelson 4797078Smjnelson # for logging/debugging in case the caller wants the raw webrti output 4807078Smjnelson def webRtiOutput(self): 4817078Smjnelson return self.__webRtiOutput 4827078Smjnelson 4837078Smjnelson 484