| 1 | #!/usr/bin/env python |
|---|
| 2 | |
|---|
| 3 | # This library is free software; you can redistribute it and/or |
|---|
| 4 | # modify it under the terms of version 2.1 of the GNU Lesser General Public |
|---|
| 5 | # License as published by the Free Software Foundation. |
|---|
| 6 | # |
|---|
| 7 | # This library is distributed in the hope that it will be useful, |
|---|
| 8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|---|
| 10 | # Lesser General Public License for more details. |
|---|
| 11 | # |
|---|
| 12 | # You should have received a copy of the GNU Lesser General Public |
|---|
| 13 | # License along with this library; if not, write to the Free Software |
|---|
| 14 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|---|
| 15 | # |
|---|
| 16 | # Copyright (c) 2005, XenSource Ltd. |
|---|
| 17 | |
|---|
| 18 | |
|---|
| 19 | import errno |
|---|
| 20 | import getpass |
|---|
| 21 | import httplib |
|---|
| 22 | import re |
|---|
| 23 | import os |
|---|
| 24 | import os.path |
|---|
| 25 | import StringIO |
|---|
| 26 | import sys |
|---|
| 27 | import tarfile |
|---|
| 28 | import tempfile |
|---|
| 29 | import time |
|---|
| 30 | import urllib |
|---|
| 31 | |
|---|
| 32 | import xen.lowlevel.xc |
|---|
| 33 | |
|---|
| 34 | from xen.xend import encode |
|---|
| 35 | |
|---|
| 36 | |
|---|
| 37 | SERVER = 'bugzilla.xensource.com' |
|---|
| 38 | SHOW_BUG_PATTERN = 'http://%s/bugzilla/show_bug.cgi?id=%%d' % SERVER |
|---|
| 39 | ATTACH_PATTERN = \ |
|---|
| 40 | 'http://%s/bugzilla/attachment.cgi?bugid=%%d&action=enter' % SERVER |
|---|
| 41 | |
|---|
| 42 | TITLE_RE = re.compile(r'<title>(.*)</title>') |
|---|
| 43 | |
|---|
| 44 | FILES_TO_SEND = [ '/var/log/' + x for x in |
|---|
| 45 | [ 'syslog', 'messages', 'debug', |
|---|
| 46 | 'xen/xend-debug.log', 'xen/xenstored-trace.log', |
|---|
| 47 | 'xen/xen-hotplug.log', 'xen/xend.log' ] + |
|---|
| 48 | [ 'xen/xend.log.%d' % z for z in range(1,6) ] ] |
|---|
| 49 | #FILES_TO_SEND = [ ] |
|---|
| 50 | |
|---|
| 51 | |
|---|
| 52 | def main(argv = None): |
|---|
| 53 | if argv is None: |
|---|
| 54 | argv = sys.argv |
|---|
| 55 | |
|---|
| 56 | print ''' |
|---|
| 57 | This application will collate the Xen dmesg output, details of the hardware |
|---|
| 58 | configuration of your machine, information about the build of Xen that you are |
|---|
| 59 | using, plus, if you allow it, various logs. |
|---|
| 60 | |
|---|
| 61 | The information collated can either be posted to a Xen Bugzilla bug (this bug |
|---|
| 62 | must already exist in the system, and you must be a registered user there), or |
|---|
| 63 | it can be saved as a .tar.bz2 for sending or archiving. |
|---|
| 64 | |
|---|
| 65 | The collated logs may contain private information, and if you are at all |
|---|
| 66 | worried about that, you should exit now, or you should explicitly exclude |
|---|
| 67 | those logs from the archive. |
|---|
| 68 | |
|---|
| 69 | ''' |
|---|
| 70 | |
|---|
| 71 | bugball = [] |
|---|
| 72 | |
|---|
| 73 | xc = xen.lowlevel.xc.xc() |
|---|
| 74 | |
|---|
| 75 | def do(n, f): |
|---|
| 76 | try: |
|---|
| 77 | s = f() |
|---|
| 78 | except Exception, exn: |
|---|
| 79 | s = str(exn) |
|---|
| 80 | bugball.append(string_iterator(n, s)) |
|---|
| 81 | |
|---|
| 82 | do('xen-dmesg', lambda: xc.readconsolering()) |
|---|
| 83 | do('physinfo', lambda: prettyDict(xc.physinfo())) |
|---|
| 84 | do('xeninfo', lambda: prettyDict(xc.xeninfo())) |
|---|
| 85 | |
|---|
| 86 | for filename in FILES_TO_SEND: |
|---|
| 87 | if not os.path.exists(filename): |
|---|
| 88 | continue |
|---|
| 89 | |
|---|
| 90 | if yes('Include %s? [Y/n] ' % filename): |
|---|
| 91 | bugball.append(file(filename)) |
|---|
| 92 | |
|---|
| 93 | maybeAttach(bugball) |
|---|
| 94 | |
|---|
| 95 | if (yes(''' |
|---|
| 96 | Do you wish to save these details as a tarball (.tar.bz2)? [Y/n] ''')): |
|---|
| 97 | tar(bugball) |
|---|
| 98 | |
|---|
| 99 | return 0 |
|---|
| 100 | |
|---|
| 101 | |
|---|
| 102 | def maybeAttach(bugball): |
|---|
| 103 | if not yes(''' |
|---|
| 104 | Do you wish to attach these details to a Bugzilla bug? [Y/n] '''): |
|---|
| 105 | return |
|---|
| 106 | |
|---|
| 107 | bug = int(raw_input('Bug number? ')) |
|---|
| 108 | |
|---|
| 109 | bug_title = getBugTitle(bug) |
|---|
| 110 | |
|---|
| 111 | if bug_title == 'Search by bug number' or bug_title == 'Invalid Bug ID': |
|---|
| 112 | print >>sys.stderr, 'Bug %d does not exist!' % bug |
|---|
| 113 | maybeAttach(bugball) |
|---|
| 114 | elif yes('Are you sure that you want to attach to %s? [Y/n] ' % |
|---|
| 115 | bug_title): |
|---|
| 116 | attach(bug, bugball) |
|---|
| 117 | else: |
|---|
| 118 | maybeAttach(bugball) |
|---|
| 119 | |
|---|
| 120 | |
|---|
| 121 | def attach(bug, bugball): |
|---|
| 122 | username = raw_input('Bugzilla username: ') |
|---|
| 123 | password = getpass.getpass('Bugzilla password: ') |
|---|
| 124 | |
|---|
| 125 | conn = httplib.HTTPConnection(SERVER) |
|---|
| 126 | try: |
|---|
| 127 | for f in bugball: |
|---|
| 128 | send(bug, conn, f, f.name, username, password) |
|---|
| 129 | finally: |
|---|
| 130 | conn.close() |
|---|
| 131 | |
|---|
| 132 | |
|---|
| 133 | def getBugTitle(bug): |
|---|
| 134 | f = urllib.urlopen(SHOW_BUG_PATTERN % bug) |
|---|
| 135 | |
|---|
| 136 | try: |
|---|
| 137 | for line in f: |
|---|
| 138 | m = TITLE_RE.search(line) |
|---|
| 139 | if m: |
|---|
| 140 | return m.group(1) |
|---|
| 141 | finally: |
|---|
| 142 | f.close() |
|---|
| 143 | |
|---|
| 144 | raise "Could not find title of bug %d!" % bug |
|---|
| 145 | |
|---|
| 146 | |
|---|
| 147 | def send(bug, conn, fd, filename, username, password): |
|---|
| 148 | |
|---|
| 149 | print "Attaching %s to bug %d." % (filename, bug) |
|---|
| 150 | |
|---|
| 151 | headers, data = encode.encode_data( |
|---|
| 152 | { 'bugid' : str(bug), |
|---|
| 153 | 'action' : 'insert', |
|---|
| 154 | 'data' : fd, |
|---|
| 155 | 'description' : '%s from %s' % (filename, username), |
|---|
| 156 | 'contenttypeselection' : 'text/plain', |
|---|
| 157 | 'contenttypemethod' : 'list', |
|---|
| 158 | 'ispatch' : '0', |
|---|
| 159 | 'GoAheadAndLogIn' : '1', |
|---|
| 160 | 'Bugzilla_login' : username, |
|---|
| 161 | 'Bugzilla_password' : password, |
|---|
| 162 | }) |
|---|
| 163 | |
|---|
| 164 | conn.request('POST',ATTACH_PATTERN % bug, data, headers) |
|---|
| 165 | response = conn.getresponse() |
|---|
| 166 | try: |
|---|
| 167 | body = response.read() |
|---|
| 168 | m = TITLE_RE.search(body) |
|---|
| 169 | |
|---|
| 170 | if response.status != 200: |
|---|
| 171 | print >>sys.stderr, ( |
|---|
| 172 | 'Attach failed: %s %s.' % (response.status, response.reason)) |
|---|
| 173 | elif not m or m.group(1) != 'Changes Submitted': |
|---|
| 174 | print >>sys.stderr, ( |
|---|
| 175 | 'Attach failed: got a page titled %s.' % m.group(1)) |
|---|
| 176 | else: |
|---|
| 177 | print "Attaching %s to bug %d succeeded." % (filename, bug) |
|---|
| 178 | finally: |
|---|
| 179 | response.close() |
|---|
| 180 | |
|---|
| 181 | |
|---|
| 182 | def tar(bugball): |
|---|
| 183 | filename = raw_input('Tarball destination filename? ') |
|---|
| 184 | |
|---|
| 185 | now = time.time() |
|---|
| 186 | |
|---|
| 187 | tf = tarfile.open(filename, 'w:bz2') |
|---|
| 188 | |
|---|
| 189 | try: |
|---|
| 190 | for f in bugball: |
|---|
| 191 | ti = tarfile.TarInfo(f.name.split('/')[-1]) |
|---|
| 192 | if hasattr(f, 'size'): |
|---|
| 193 | ti.size = f.size() |
|---|
| 194 | else: |
|---|
| 195 | ti.size = os.stat(f.name).st_size |
|---|
| 196 | |
|---|
| 197 | ti.mtime = now |
|---|
| 198 | ti.type = tarfile.REGTYPE |
|---|
| 199 | ti.uid = 0 |
|---|
| 200 | ti.gid = 0 |
|---|
| 201 | ti.uname = 'root' |
|---|
| 202 | ti.gname = 'root' |
|---|
| 203 | |
|---|
| 204 | f.seek(0) # If we've added this file to a bug, it will have been |
|---|
| 205 | # read once already, so reset it. |
|---|
| 206 | tf.addfile(ti, f) |
|---|
| 207 | finally: |
|---|
| 208 | tf.close() |
|---|
| 209 | |
|---|
| 210 | print 'Writing tarball %s successful.' % filename |
|---|
| 211 | |
|---|
| 212 | |
|---|
| 213 | def prettyDict(d): |
|---|
| 214 | format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()])) |
|---|
| 215 | return '\n'.join([format % i for i in d.items()]) + '\n' |
|---|
| 216 | |
|---|
| 217 | |
|---|
| 218 | class string_iterator(StringIO.StringIO): |
|---|
| 219 | def __init__(self, name, val): |
|---|
| 220 | StringIO.StringIO.__init__(self, val) |
|---|
| 221 | self.name = name |
|---|
| 222 | |
|---|
| 223 | def size(self): |
|---|
| 224 | return len(self.getvalue()) |
|---|
| 225 | |
|---|
| 226 | |
|---|
| 227 | def yes(prompt): |
|---|
| 228 | yn = raw_input(prompt) |
|---|
| 229 | |
|---|
| 230 | return len(yn) == 0 or yn.lower()[0] == 'y' |
|---|
| 231 | |
|---|
| 232 | |
|---|
| 233 | if __name__ == "__main__": |
|---|
| 234 | sys.exit(main()) |
|---|