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 """ 977711Srichlowe@richlowe.net monacoFields = [ "cr_number", "category", "sub_category", 987078Smjnelson "area", "release", "build", "responsible_manager", 997078Smjnelson "responsible_engineer", "priority", "status", "sub_status", 1007711Srichlowe@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: 1127711Srichlowe@richlowe.net line = line.rstrip('\n') 1137711Srichlowe@richlowe.net 1147711Srichlowe@richlowe.net # 1157711Srichlowe@richlowe.net # We request synopsis last, and split on only 1167711Srichlowe@richlowe.net # the number of separators that we expect to 1177711Srichlowe@richlowe.net # see such that a | in the synopsis doesn't 1187711Srichlowe@richlowe.net # throw us out of whack. 1197711Srichlowe@richlowe.net # 1207711Srichlowe@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 286*7764SJohn.Sonnenschein@Sun.COMWEBRTICLI = '/net/onnv.sfbay.sun.com/export/onnv-gate/public/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 2977078Smjnelsonclass RtiCallFailed(RtiException): 2987078Smjnelson def __str__(self): 2997078Smjnelson return "Unable to call webrti: %s" % self.data 3007078Smjnelson 3017078Smjnelsonclass RtiSystemProblem(RtiException): 3027078Smjnelson def __str__(self): 3037078Smjnelson return "RTI status cannot be determined: %s" % self.data 3047078Smjnelson 3057078Smjnelsonclass RtiIncorrectCR(RtiException): 3067078Smjnelson def __str__(self): 3077078Smjnelson return "Incorrect CR number specified: %s" % self.data 3087078Smjnelson 3097078Smjnelsonclass RtiNotFound(RtiException): 3107078Smjnelson def __str__(self): 3117078Smjnelson return "RTI not found: %s" % self.data 3127078Smjnelson 3137078Smjnelsonclass RtiNeedConsolidation(RtiException): 3147078Smjnelson def __str__(self): 3157078Smjnelson return "More than one consolidation has this CR: %s" % self.data 3167078Smjnelson 3177078Smjnelsonclass RtiBadGate(RtiException): 3187078Smjnelson def __str__(self): 3197078Smjnelson return "Incorrect gate name specified: %s" % self.data 3207078Smjnelson 3217078Smjnelsonclass RtiOffSwan(RtiException): 3227078Smjnelson def __str__(self): 3237078Smjnelson return "RTI status checks need SWAN access: %s" % self.data 3247078Smjnelson 3257078SmjnelsonWEBRTI_ERRORS = { 3267078Smjnelson '1': RtiSystemProblem, 3277078Smjnelson '2': RtiIncorrectCR, 3287078Smjnelson '3': RtiNotFound, 3297078Smjnelson '4': RtiNeedConsolidation, 3307078Smjnelson '5': RtiBadGate, 3317078Smjnelson} 3327078Smjnelson 3337078Smjnelson# Our Rti object which we'll use to represent an Rti query 3347078Smjnelson# It's really just a wrapper around the Rti connection, and attempts 3357078Smjnelson# to establish a direct socket connection and query the webrti server 3367078Smjnelson# directly (thus avoiding a system/fork/exec call). If it fails, it 3377078Smjnelson# falls back to the webrticli command line client. 3387078Smjnelson 3397078SmjnelsonreturnCodeRe = re.compile(r'.*RETURN_CODE=(\d+)') 3407078Smjnelsonclass Rti: 3417078Smjnelson """Lookup an RTI. 3427078Smjnelson 3437078Smjnelson Usage: 3447078Smjnelson r = Rti("6640538") 3457078Smjnelson print r.rtiNumber(); 3467078Smjnelson """ 3477078Smjnelson 3487078Smjnelson def __init__(self, cr, gate=None, consolidation=None): 3497078Smjnelson """Create an Rti object for the specified change request. 3507078Smjnelson 3517078Smjnelson Argument: 3527078Smjnelson cr: change request id 3537078Smjnelson 3547078Smjnelson Keyword arguments, to limit scope of RTI search: 3557078Smjnelson gate: path to gate workspace (default=None) 3567078Smjnelson consolidation: consolidation name (default=None) 3577078Smjnelson """ 3587078Smjnelson 3597078Smjnelson bufSz = 1024 3607078Smjnelson addr = (WEBRTI_HOST, WEBRTI_PORT) 3617078Smjnelson # If the passed 'cr' was given as an int, then wrap it 3627078Smjnelson # into a string to make our life easier 3637078Smjnelson if isinstance(cr, int): 3647078Smjnelson cr = str(cr) 3657078Smjnelson self.__queryCr = cr 3667078Smjnelson self.__queryGate = gate 3677078Smjnelson self.__queryConsolidation = consolidation 3687078Smjnelson 369*7764SJohn.Sonnenschein@Sun.COM self.__webRtiOutput = [] 370*7764SJohn.Sonnenschein@Sun.COM self.__mainCR = [] 371*7764SJohn.Sonnenschein@Sun.COM self.__rtiNumber = [] 372*7764SJohn.Sonnenschein@Sun.COM self.__consolidation = [] 373*7764SJohn.Sonnenschein@Sun.COM self.__project = [] 374*7764SJohn.Sonnenschein@Sun.COM self.__status = [] 375*7764SJohn.Sonnenschein@Sun.COM self.__rtiType = [] 3767078Smjnelson try: 3777078Smjnelson # try to use a direct connection to the 3787078Smjnelson # webrti server first 3797078Smjnelson sock = socket(AF_INET, SOCK_STREAM) 3807078Smjnelson sock.connect(addr) 3817078Smjnelson command = "WEBRTICLI/1.0\nRTIstatus\n%s\n" % cr 3827078Smjnelson if consolidation: 3837078Smjnelson command += "-c\n%s\n" % consolidation 3847078Smjnelson if gate: 3857078Smjnelson command += "-g\n%s\n" % gate 3867078Smjnelson command += "\n" 3877078Smjnelson sock.send(command) 3887078Smjnelson dataList = [] 3897078Smjnelson # keep receiving data from the socket until the 3907078Smjnelson # server closes the connection 3917078Smjnelson stillReceiving = True 3927078Smjnelson while stillReceiving: 3937078Smjnelson dataPiece = sock.recv(bufSz) 3947078Smjnelson if dataPiece: 3957078Smjnelson dataList.append(dataPiece) 3967078Smjnelson else: 3977078Smjnelson stillReceiving = False 3987078Smjnelson # create the lines, skipping the first 3997078Smjnelson # ("WEBRTCLI/1.0\n") 4007078Smjnelson data = '\n'.join(''.join(dataList).split('\n')[1:]) 4017078Smjnelson except: 4027078Smjnelson if not onSWAN(): 4037078Smjnelson raise RtiOffSwan(cr) 4047078Smjnelson 4057078Smjnelson if not os.path.exists(WEBRTICLI): 4067078Smjnelson raise RtiCallFailed('not found') 4077078Smjnelson 4087078Smjnelson # fallback to the "supported" webrticli interface 4097078Smjnelson command = WEBRTICLI 4107078Smjnelson if consolidation: 4117078Smjnelson command += " -c " + consolidation 4127078Smjnelson if gate: 4137078Smjnelson command += " -g " + gate 4147078Smjnelson command += " RTIstatus " + cr 4157078Smjnelson 4167078Smjnelson try: 4177078Smjnelson cliPipe = os.popen(command) 4187078Smjnelson except: 4197078Smjnelson # we couldn't call the webrticli for some 4207078Smjnelson # reason, so return a failure 4217078Smjnelson raise RtiCallFailed('unknown') 4227078Smjnelson 4237078Smjnelson data = cliPipe.readline() 4247078Smjnelson 4257078Smjnelson # parse the data to see if we got a return code 4267078Smjnelson # if we did, then that's bad. if we didn't, 4277078Smjnelson # then our call was successfully 4287078Smjnelson m = returnCodeRe.search(data) 4297078Smjnelson if m: 4307078Smjnelson # we got a return code, set it in our 4317078Smjnelson # object, set the webRtiOutput for debugging 4327078Smjnelson # or logging, and return a failure 4337078Smjnelson if m.group(1) in WEBRTI_ERRORS: 4347078Smjnelson exc = WEBRTI_ERRORS[m.group(1)] 4357078Smjnelson else: 4367078Smjnelson exc = RtiException 4377078Smjnelson raise exc(data) 4387078Smjnelson 439*7764SJohn.Sonnenschein@Sun.COM data = data.splitlines() 4407078Smjnelson # At this point, we should have valid data 441*7764SJohn.Sonnenschein@Sun.COM for line in data: 442*7764SJohn.Sonnenschein@Sun.COM line = line.rstrip('\r\n') 443*7764SJohn.Sonnenschein@Sun.COM self.__webRtiOutput.append(line) 444*7764SJohn.Sonnenschein@Sun.COM fields = line.split(':') 445*7764SJohn.Sonnenschein@Sun.COM self.__mainCR.append(fields[0]) 446*7764SJohn.Sonnenschein@Sun.COM self.__rtiNumber.append(fields[1]) 447*7764SJohn.Sonnenschein@Sun.COM self.__consolidation.append(fields[2]) 448*7764SJohn.Sonnenschein@Sun.COM self.__project.append(fields[3]) 449*7764SJohn.Sonnenschein@Sun.COM self.__status.append(fields[4]) 450*7764SJohn.Sonnenschein@Sun.COM self.__rtiType.append(fields[5]) 4517078Smjnelson 4527078Smjnelson # accessors in case callers need the raw data 4537078Smjnelson def mainCR(self): 4547078Smjnelson return self.__mainCR 4557078Smjnelson def rtiNumber(self): 4567078Smjnelson return self.__rtiNumber 4577078Smjnelson def consolidation(self): 4587078Smjnelson return self.__consolidation 4597078Smjnelson def project(self): 4607078Smjnelson return self.__project 4617078Smjnelson def status(self): 4627078Smjnelson return self.__status 4637078Smjnelson def rtiType(self): 4647078Smjnelson return self.__rtiType 4657078Smjnelson def queryCr(self): 4667078Smjnelson return self.__queryCr 4677078Smjnelson def queryGate(self): 4687078Smjnelson return self.__queryGate 4697078Smjnelson def queryConsolidation(self): 4707078Smjnelson return self.__queryConsolidation 4717078Smjnelson 4727078Smjnelson # in practice, most callers only care about the following 4737078Smjnelson def accepted(self): 474*7764SJohn.Sonnenschein@Sun.COM for status in self.__status: 475*7764SJohn.Sonnenschein@Sun.COM if status != "S_ACCEPTED": 476*7764SJohn.Sonnenschein@Sun.COM return False 477*7764SJohn.Sonnenschein@Sun.COM return True 4787078Smjnelson 4797078Smjnelson # for logging/debugging in case the caller wants the raw webrti output 4807078Smjnelson def webRtiOutput(self): 4817078Smjnelson return self.__webRtiOutput 4827078Smjnelson 483