xref: /onnv-gate/usr/src/tools/onbld/Checks/DbLookups.py (revision 7711:fe07301cae7d)
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