1 #! /usr/bin/python
2 #
3 # CDDL HEADER START
4 #
5 # The contents of this file are subject to the terms of the
6 # Common Development and Distribution License (the "License").
7 # You may not use this file except in compliance with the License.
8 #
9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 # or http://www.opensolaris.org/os/licensing.
11 # See the License for the specific language governing permissions
12 # and limitations under the License.
13 #
14 # When distributing Covered Code, include this CDDL HEADER in each
15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 # If applicable, add the following below this CDDL HEADER, with the
17 # fields enclosed by brackets "[]" replaced with your own identifying
18 # information: Portions Copyright [yyyy] [name of copyright owner]
19 #
20 # CDDL HEADER END
21 #
22
23 #
24 # Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
25 #
26
27 # Copyright 2010, Richard Lowe
28
29 #
30 # Various database lookup classes/methods, i.e.:
31 # * monaco
32 # * bugs.opensolaris.org (b.o.o.)
33 # * redmine (illumos.org)
34 #
35
36 import htmllib
37 import re
38 import urllib
39 import urllib2
40
41 try: # Python >= 2.5
42 from xml.etree import ElementTree
43 except ImportError:
44 from elementtree import ElementTree
45
46 class NonExistentBug(Exception):
47 def __str__(self):
48 return "Bug %s does not exist" % (Exception.__str__(self))
49
50 class BugDBException(Exception):
51 def __str__(self):
52 return "Unknown bug database: %s" % (Exception.__str__(self))
53
54 class BugDB(object):
55 """Lookup change requests.
56
57 Usage:
58 bdb = BugDB()
59 r = bdb.lookup("6455550")
60 print r["6455550"]["synopsis"]
61 r = bdb.lookup(["6455550", "6505625"])
62 print r["6505625"]["synopsis"]
63 """
64
65 VALID_DBS = ["bugster", "illumos"]
66
67 def __init__(self, priority = ("illumos", "bugster")):
68 """Create a BugDB object.
69
70 Keyword argument:
71 priority: use bug databases in this order
72 """
73 for database in priority:
74 if database not in self.VALID_DBS:
75 raise BugDBException, database
76 self.__priority = priority
77
78
79 def __illbug(self, cr):
80 url = "http://illumos.org/issues/%s.xml" % cr
81 req = urllib2.Request(url)
82
83 try:
84 data = urllib2.urlopen(req)
85 except urllib2.HTTPError, e:
86 if e.code == 404:
87 raise NonExistentBug(cr)
88 else:
89 raise
90
91 bug = ElementTree.parse(data)
92
93 return {'cr_number': bug.find('id').text,
94 'synopsis': bug.find('subject').text,
95 'status': bug.find('status').attrib['name']
96 }
97
98
99 def __boobug(self, cr):
100 cr = str(cr)
101 url = "http://bugs.opensolaris.org/view_bug.do"
102 req = urllib2.Request(url, urllib.urlencode({"bug_id": cr}))
103 results = {}
104 try:
105 data = urllib2.urlopen(req).readlines()
106 except urllib2.HTTPError, e:
107 if e.code != 404:
108 print "ERROR: HTTP error at " + \
109 req.get_full_url() + \
110 " got error: " + str(e.code)
111 raise e
112 else:
113 raise NonExistentBug(cr)
114 except urllib2.URLError, e:
115 print "ERROR: could not connect to " + \
116 req.get_full_url() + \
117 ' got error: "' + e.reason[1] + '"'
118 raise e
119 htmlParser = htmllib.HTMLParser(None)
120 metaHtmlRe = re.compile(r'^<meta name="([^"]+)" content="([^"]*)">$')
121 for line in data:
122 m = metaHtmlRe.search(line)
123 if not m:
124 continue
125 val = urllib.unquote(m.group(2))
126 htmlParser.save_bgn()
127 htmlParser.feed(val)
128 results[m.group(1)] = htmlParser.save_end()
129 htmlParser.close()
130
131 if "synopsis" not in results:
132 raise NonExistentBug(cr)
133
134 results["cr_number"] = cr
135 results["sub_category"] = results.pop("subcategory")
136 results["status"] = results.pop("state")
137 results["date_submitted"] = results.pop("submit_date")
138
139 return results
140
141 def lookup(self, crs):
142 """Return all info for requested change reports.
143
144 Argument:
145 crs: one change request id (may be integer, string, or list),
146 or multiple change request ids (must be a list)
147
148 Returns:
149 Dictionary, mapping CR=>dictionary, where the nested dictionary
150 is a mapping of field=>value
151 """
152 results = {}
153 if not isinstance(crs, list):
154 crs = [str(crs)]
155 for database in self.__priority:
156 if database == "bugster":
157 for cr in crs:
158 cr = str(cr)
159 try:
160 results[cr] = self.__boobug(cr)
161 except NonExistentBug:
162 continue
163 elif database == "illumos":
164 for cr in crs:
165 try:
166 results[str(cr)] = self.__illbug(cr)
167 except NonExistentBug:
168 continue
169
170 # the CR has already been found by one bug database
171 # so don't bother looking it up in the others
172 for cr in crs:
173 if cr in results:
174 crs.remove(cr)
175
176 return results