1N/A#! /usr/bin/python
1N/A#
1N/A# CDDL HEADER START
1N/A#
1N/A# The contents of this file are subject to the terms of the
1N/A# Common Development and Distribution License (the "License").
1N/A# You may not use this file except in compliance with the License.
1N/A#
1N/A# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
1N/A# or http://www.opensolaris.org/os/licensing.
1N/A# See the License for the specific language governing permissions
1N/A# and limitations under the License.
1N/A#
1N/A# When distributing Covered Code, include this CDDL HEADER in each
1N/A# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
1N/A# If applicable, add the following below this CDDL HEADER, with the
1N/A# fields enclosed by brackets "[]" replaced with your own identifying
1N/A# information: Portions Copyright [yyyy] [name of copyright owner]
1N/A#
1N/A# CDDL HEADER END
1N/A#
1N/A
1N/A#
1N/A# Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
1N/A#
1N/A
1N/A#
1N/A# Various database lookup classes/methods, i.e.:
1N/A# * monaco
1N/A# * bugs.opensolaris.org (b.o.o.)
1N/A# * onnv.us.oracle.com/cgi-bin/arc.cgi (for ARC on SWAN)
1N/A#
1N/A
1N/Aimport csv
1N/Aimport re
1N/Aimport urllib
1N/Aimport urllib2
1N/Aimport htmllib
1N/Aimport os
1N/Afrom socket import socket, AF_INET, SOCK_STREAM
1N/A
1N/Afrom onbld.Checks import onSWAN
1N/A
1N/Aclass NonExistentBug(Exception):
1N/A def __str__(self):
1N/A return "Bug %s does not exist" % (Exception.__str__(self))
1N/A
1N/Aclass BugDBException(Exception):
1N/A def __str__(self):
1N/A return "Unknown bug database: %s" % (Exception.__str__(self))
1N/A
1N/Aclass BugDB(object):
1N/A """Lookup change requests.
1N/A
1N/A Object can be used on or off of SWAN, using either monaco or
1N/A bugs.opensolaris.org as a database.
1N/A
1N/A Usage:
1N/A bdb = BugDB()
1N/A r = bdb.lookup("6455550")
1N/A print r["6455550"]["synopsis"]
1N/A r = bdb.lookup(["6455550", "6505625"])
1N/A print r["6505625"]["synopsis"]
1N/A """
1N/A
1N/A def __init__(self, priority = ("bugster",), forceBoo=False):
1N/A """Create a BugDB object.
1N/A
1N/A Keyword argument:
1N/A forceBoo: use b.o.o even from SWAN (default=False)
1N/A priority: use bug databases in this order
1N/A """
1N/A self.__validBugDB = ["bugster"]
1N/A self.__onSWAN = not forceBoo and onSWAN()
1N/A for database in priority:
1N/A if database not in self.__validBugDB:
1N/A raise BugDBException, database
1N/A self.__priority = priority
1N/A
1N/A
1N/A def __boobug(self, cr):
1N/A cr = str(cr)
1N/A url = "http://bugs.opensolaris.org/view_bug.do"
1N/A req = urllib2.Request(url, urllib.urlencode({"bug_id": cr}))
1N/A results = {}
1N/A try:
1N/A data = urllib2.urlopen(req).readlines()
1N/A except urllib2.HTTPError, e:
1N/A if e.code != 404:
1N/A print "ERROR: HTTP error at " + \
1N/A req.get_full_url() + \
1N/A " got error: " + str(e.code)
1N/A raise e
1N/A else:
1N/A raise NonExistentBug
1N/A except urllib2.URLError, e:
1N/A print "ERROR: could not connect to " + \
1N/A req.get_full_url() + \
1N/A ' got error: "' + e.reason[1] + '"'
1N/A raise e
1N/A htmlParser = htmllib.HTMLParser(None)
1N/A metaHtmlRe = re.compile(r'^<meta name="([^"]+)" content="([^"]*)">$')
1N/A for line in data:
1N/A m = metaHtmlRe.search(line)
1N/A if not m:
1N/A continue
1N/A val = urllib.unquote(m.group(2))
1N/A htmlParser.save_bgn()
1N/A htmlParser.feed(val)
1N/A results[m.group(1)] = htmlParser.save_end()
1N/A htmlParser.close()
1N/A
1N/A if "synopsis" not in results:
1N/A raise NonExistentBug(cr)
1N/A
1N/A results["cr_number"] = cr
1N/A results["sub_category"] = results.pop("subcategory")
1N/A results["status"] = results.pop("state")
1N/A results["date_submitted"] = results.pop("submit_date")
1N/A
1N/A return results
1N/A
1N/A
1N/A def __monaco(self, crs):
1N/A """Return all info for requested change reports.
1N/A
1N/A Argument:
1N/A crs: list of change request ids
1N/A
1N/A Returns:
1N/A Dictionary, mapping CR=>dictionary, where the nested dictionary
1N/A is a mapping of field=>value
1N/A """
1N/A
1N/A #
1N/A # See if 'maxcrs' for maximal batch query size is defined
1N/A # if not, default to 200.
1N/A # This clears the 2499 chars query limit
1N/A #
1N/A try:
1N/A maxcrs
1N/A except NameError:
1N/A maxcrs = 200
1N/A
1N/A i = 0
1N/A results = {}
1N/A data = []
1N/A
1N/A while i < len(crs):
1N/A if len(crs) < ( i + maxcrs ):
1N/A j = len(crs)
1N/A else:
1N/A j = i + maxcrs
1N/A
1N/A crstmp=crs[i:j]
1N/A
1N/A #
1N/A # We request synopsis last, and split on only
1N/A # the number of separators that we expect to
1N/A # see such that a | in the synopsis doesn't
1N/A # throw us out of whack.
1N/A #
1N/A monacoFields = [ "cr_number", "category", "sub_category",
1N/A "area", "release", "build", "responsible_manager",
1N/A "responsible_engineer", "priority", "status", "sub_status",
1N/A "submitted_by", "date_submitted", "synopsis" ]
1N/A cmd = []
1N/A cmd.append("set What = cr." + ', cr.'.join(monacoFields))
1N/A cmd.append("")
1N/A cmd.append("set Which = cr.cr_number in (" + ','.join(crstmp) +")")
1N/A cmd.append("")
1N/A cmd.append("set FinalClauses = order by cr.cr_number")
1N/A cmd.append("")
1N/A cmd.append("doMeta genQuery cr")
1N/A url = "http://hestia-sca.us.oracle.com/cgi-bin/expert?format="
1N/A url += "Pipe-delimited+text;Go=2;no_header=on;cmds="
1N/A url += urllib.quote_plus("\n".join(cmd))
1N/A try:
1N/A data += urllib2.urlopen(url).readlines()
1N/A except urllib2.HTTPError, e:
1N/A print "ERROR: HTTP error at " + url + \
1N/A " got error: " + str(e.code)
1N/A raise e
1N/A
1N/A except urllib2.URLError, e:
1N/A print "ERROR: could not connect to " + url + \
1N/A ' got error: "' + e.reason[1] + '"'
1N/A raise e
1N/A
1N/A i += maxcrs
1N/A
1N/A for line in data:
1N/A line = line.rstrip('\n')
1N/A values = line.split('|', len(monacoFields) - 1)
1N/A v = 0
1N/A cr = values[0]
1N/A results[cr] = {}
1N/A for field in monacoFields:
1N/A results[cr][field] = values[v]
1N/A v += 1
1N/A
1N/A
1N/A return results
1N/A
1N/A def lookup(self, crs):
1N/A """Return all info for requested change reports.
1N/A
1N/A Argument:
1N/A crs: one change request id (may be integer, string, or list),
1N/A or multiple change request ids (must be a list)
1N/A
1N/A Returns:
1N/A Dictionary, mapping CR=>dictionary, where the nested dictionary
1N/A is a mapping of field=>value
1N/A """
1N/A results = {}
1N/A if not isinstance(crs, list):
1N/A crs = [str(crs)]
1N/A for database in self.__priority:
1N/A if database == "bugster":
1N/A if self.__onSWAN:
1N/A results.update(self.__monaco(crs))
1N/A # else we're off-swan and querying via boo, which we can
1N/A # only do one bug at a time
1N/A else:
1N/A for cr in crs:
1N/A cr = str(cr)
1N/A try:
1N/A results[cr] = self.__boobug(cr)
1N/A except NonExistentBug:
1N/A continue
1N/A
1N/A # the CR has already been found by one bug database
1N/A # so don't bother looking it up in the others
1N/A for cr in crs:
1N/A if cr in results:
1N/A crs.remove(cr)
1N/A
1N/A return results
1N/A####################################################################
1N/Aclass ARCException(Exception):
1N/A """This covers arc.cgi script failure."""
1N/A def __str__(self):
1N/A return "Error retrieving ARC data: %s" % (Exception.__str__(self))
1N/A
1N/Adef ARC(arclist, arcPath=None):
1N/A if not arcPath:
1N/A arcPath = "http://onnv.us.oracle.com/cgi-bin/arc.cgi"
1N/A
1N/A fields = ["present", "arc", "year", "case", "status", "title"]
1N/A opts = [("case", "%s/%s" % (a, c)) for a, c in arclist]
1N/A req = urllib2.Request(arcPath, urllib.urlencode(opts))
1N/A try:
1N/A data = urllib2.urlopen(req).readlines()
1N/A except urllib2.HTTPError, e:
1N/A print "ERROR: HTTP error at " + req.get_full_url() + \
1N/A " got error: " + str(e.code)
1N/A raise e
1N/A
1N/A except urllib2.URLError, e:
1N/A print "ERROR: could not connect to " + req.get_full_url() + \
1N/A ' got error: "' + e.reason[1] + '"'
1N/A raise e
1N/A ret = {}
1N/A for line in csv.DictReader(data, fields):
1N/A if line["present"] == "exists":
1N/A yc = "%s/%s" % (line["year"], line["case"])
1N/A ret[(line["arc"], yc)] = line["title"]
1N/A elif line["present"] == "fatal":
1N/A raise ARCException(line["arc"])
1N/A
1N/A return ret
1N/A
1N/A####################################################################
1N/A
1N/A# Pointers to the webrti server hostname & port to use
1N/A# Using it directly is probably not *officially* supported, so we'll
1N/A# have a pointer to the official `webrticli` command line interface
1N/A# if using a direct socket connection fails for some reason, so we
1N/A# have a fallback
1N/AWEBRTI_HOST = 'webrti.us.oracle.com'
1N/AWEBRTI_PORT = 9188
1N/AWEBRTICLI = '/net/onnv.us.oracle.com/export/onnv-gate/public/bin/webrticli'
1N/A
1N/A
1N/Aclass RtiException(Exception):
1N/A pass
1N/A
1N/Aclass RtiCallFailed(RtiException):
1N/A def __str__(self):
1N/A return "Unable to call webrti: %s" % (RtiException.__str__(self))
1N/A
1N/Aclass RtiSystemProblem(RtiException):
1N/A def __str__(self):
1N/A return "RTI status cannot be determined for CR: %s" % (RtiException.__str__(self))
1N/A
1N/Aclass RtiIncorrectCR(RtiException):
1N/A def __str__(self):
1N/A return "Incorrect CR number specified: %s" % (RtiException.__str__(self))
1N/A
1N/Aclass RtiNotFound(RtiException):
1N/A def __str__(self):
1N/A return "RTI not found for CR: %s" % (RtiException.__str__(self))
1N/A
1N/Aclass RtiNeedConsolidation(RtiException):
1N/A def __str__(self):
1N/A return "More than one consolidation has this CR: %s" % (RtiException.__str__(self))
1N/A
1N/Aclass RtiBadGate(RtiException):
1N/A def __str__(self):
1N/A return "Incorrect gate name specified: %s" % (RtiException.__str__(self))
1N/A
1N/Aclass RtiUnknownException(Exception):
1N/A def __str__(self):
1N/A return "Unknown webrti return code: %s" % (RtiException.__str__(self))
1N/A
1N/Aclass RtiOffSwan(RtiException):
1N/A def __str__(self):
1N/A return "RTI status checks need SWAN access: %s" % (RtiException.__str__(self))
1N/A
1N/AWEBRTI_ERRORS = {
1N/A '1': RtiSystemProblem,
1N/A '2': RtiIncorrectCR,
1N/A '3': RtiNotFound,
1N/A '4': RtiNeedConsolidation,
1N/A '5': RtiBadGate,
1N/A}
1N/A
1N/A# Our Rti object which we'll use to represent an Rti query
1N/A# It's really just a wrapper around the Rti connection, and attempts
1N/A# to establish a direct socket connection and query the webrti server
1N/A# directly (thus avoiding a system/fork/exec call). If it fails, it
1N/A# falls back to the webrticli command line client.
1N/A
1N/AreturnCodeRe = re.compile(r'.*RETURN_CODE=(\d+)')
1N/Aclass Rti:
1N/A """Lookup an RTI.
1N/A
1N/A Usage:
1N/A r = Rti("6640538")
1N/A print r.rtiNumber();
1N/A """
1N/A
1N/A def __init__(self, cr, gate=None, consolidation=None):
1N/A """Create an Rti object for the specified change request.
1N/A
1N/A Argument:
1N/A cr: change request id
1N/A
1N/A Keyword arguments, to limit scope of RTI search:
1N/A gate: path to gate workspace (default=None)
1N/A consolidation: consolidation name (default=None)
1N/A """
1N/A
1N/A bufSz = 1024
1N/A addr = (WEBRTI_HOST, WEBRTI_PORT)
1N/A # If the passed 'cr' was given as an int, then wrap it
1N/A # into a string to make our life easier
1N/A if isinstance(cr, int):
1N/A cr = str(cr)
1N/A self.__queryCr = cr
1N/A self.__queryGate = gate
1N/A self.__queryConsolidation = consolidation
1N/A
1N/A self.__webRtiOutput = []
1N/A self.__mainCR = []
1N/A self.__rtiNumber = []
1N/A self.__consolidation = []
1N/A self.__project = []
1N/A self.__status = []
1N/A self.__rtiType = []
1N/A try:
1N/A # try to use a direct connection to the
1N/A # webrti server first
1N/A sock = socket(AF_INET, SOCK_STREAM)
1N/A sock.connect(addr)
1N/A command = "WEBRTICLI/1.0\nRTIstatus\n%s\n" % cr
1N/A if consolidation:
1N/A command += "-c\n%s\n" % consolidation
1N/A if gate:
1N/A command += "-g\n%s\n" % gate
1N/A command += "\n"
1N/A sock.send(command)
1N/A dataList = []
1N/A # keep receiving data from the socket until the
1N/A # server closes the connection
1N/A stillReceiving = True
1N/A while stillReceiving:
1N/A dataPiece = sock.recv(bufSz)
1N/A if dataPiece:
1N/A dataList.append(dataPiece)
1N/A else:
1N/A stillReceiving = False
1N/A # create the lines, skipping the first
1N/A # ("WEBRTCLI/1.0\n")
1N/A data = '\n'.join(''.join(dataList).split('\n')[1:])
1N/A except:
1N/A if not onSWAN():
1N/A raise RtiOffSwan(cr)
1N/A
1N/A if not os.path.exists(WEBRTICLI):
1N/A raise RtiCallFailed('not found')
1N/A
1N/A # fallback to the "supported" webrticli interface
1N/A command = WEBRTICLI
1N/A if consolidation:
1N/A command += " -c " + consolidation
1N/A if gate:
1N/A command += " -g " + gate
1N/A command += " RTIstatus " + cr
1N/A
1N/A try:
1N/A cliPipe = os.popen(command)
1N/A except:
1N/A # we couldn't call the webrticli for some
1N/A # reason, so return a failure
1N/A raise RtiCallFailed('unknown')
1N/A
1N/A data = cliPipe.readline()
1N/A
1N/A # parse the data to see if we got a return code
1N/A # if we did, then that's bad. if we didn't,
1N/A # then our call was successful
1N/A m = returnCodeRe.search(data)
1N/A if m:
1N/A rc = m.group(1)
1N/A # we got a return code, set it in our
1N/A # object, set the webRtiOutput for debugging
1N/A # or logging, and return a failure
1N/A if rc in WEBRTI_ERRORS:
1N/A exc = WEBRTI_ERRORS[rc]
1N/A if exc == RtiBadGate:
1N/A edata = gate
1N/A else:
1N/A edata = cr
1N/A else:
1N/A exc = RtiUnknownException
1N/A edata = rc
1N/A raise exc(edata)
1N/A
1N/A data = data.splitlines()
1N/A # At this point, we should have valid data
1N/A for line in data:
1N/A line = line.rstrip('\r\n')
1N/A self.__webRtiOutput.append(line)
1N/A fields = line.split(':')
1N/A self.__mainCR.append(fields[0])
1N/A self.__rtiNumber.append(fields[1])
1N/A self.__consolidation.append(fields[2])
1N/A self.__project.append(fields[3])
1N/A self.__status.append(fields[4])
1N/A self.__rtiType.append(fields[5])
1N/A
1N/A # accessors in case callers need the raw data
1N/A def mainCR(self):
1N/A return self.__mainCR
1N/A def rtiNumber(self):
1N/A return self.__rtiNumber
1N/A def consolidation(self):
1N/A return self.__consolidation
1N/A def project(self):
1N/A return self.__project
1N/A def status(self):
1N/A return self.__status
1N/A def rtiType(self):
1N/A return self.__rtiType
1N/A def queryCr(self):
1N/A return self.__queryCr
1N/A def queryGate(self):
1N/A return self.__queryGate
1N/A def queryConsolidation(self):
1N/A return self.__queryConsolidation
1N/A
1N/A # in practice, most callers only care about the following
1N/A def accepted(self):
1N/A for status in self.__status:
1N/A if status != "S_ACCEPTED":
1N/A return False
1N/A return True
1N/A
1N/A # for logging/debugging in case the caller wants the raw webrti output
1N/A def webRtiOutput(self):
1N/A return self.__webRtiOutput
1N/A