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