]> git.proxmox.com Git - ovs.git/blame - debian/ovs-bugtool
Debian: Make ovsdb-server use manager_options column to find managers.
[ovs.git] / debian / ovs-bugtool
CommitLineData
b828c2f5
EJ
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, 2007 XenSource Ltd.
17# Copyright (c) 2010, Nicira Networks.
18
19#
20# To add new entries to the bugtool, you need to:
21#
22# Create a new capability. These declare the new entry to the GUI, including
23# the expected size, time to collect, privacy implications, and whether the
24# capability should be selected by default. One capability may refer to
25# multiple files, assuming that they can be reasonably grouped together, and
26# have the same privacy implications. You need:
27#
28# A new CAP_ constant.
29# A cap() invocation to declare the capability.
30#
31# You then need to add calls to main() to collect the files. These will
32# typically be calls to the helpers file_output(), tree_output(), cmd_output(),
33# or func_output().
34#
35
36import getopt
37import re
38import os
39import StringIO
40import sys
41import tarfile
42import time
43import commands
44import pprint
45from xml.dom.minidom import parse, getDOMImplementation
46import zipfile
47from subprocess import Popen, PIPE
48from select import select
49from signal import SIGTERM, SIGUSR1
50import md5
51import platform
52import fcntl
53import glob
54import urllib
55import socket
56import base64
57
58sys.path.append('/usr/lib/python')
59sys.path.append('/usr/lib64/python')
60
61OS_RELEASE = platform.release()
62
63#
64# Files & directories
65#
66
36f0987f 67BUG_DIR = "/var/log/ovs-bugtool"
b828c2f5
EJ
68PLUGIN_DIR = "/etc/openvswitch/bugtool"
69GRUB_CONFIG = '/boot/grub/menu.lst'
70BOOT_KERNEL = '/boot/vmlinuz-' + OS_RELEASE
71BOOT_INITRD = '/boot/initrd-' + OS_RELEASE + '.img'
72PROC_PARTITIONS = '/proc/partitions'
73FSTAB = '/etc/fstab'
74PROC_MOUNTS = '/proc/mounts'
75PROC_CPUINFO = '/proc/cpuinfo'
76PROC_MEMINFO = '/proc/meminfo'
77PROC_IOPORTS = '/proc/ioports'
78PROC_INTERRUPTS = '/proc/interrupts'
79PROC_SCSI = '/proc/scsi/scsi'
80PROC_VERSION = '/proc/version'
81PROC_MODULES = '/proc/modules'
82PROC_DEVICES = '/proc/devices'
83PROC_FILESYSTEMS = '/proc/filesystems'
84PROC_CMDLINE = '/proc/cmdline'
85PROC_CONFIG = '/proc/config.gz'
86PROC_USB_DEV = '/proc/bus/usb/devices'
87PROC_NET_SOFTNET_STAT = '/proc/net/softnet_stat'
88MODPROBE_DIR = '/etc/modprobe.d'
89RESOLV_CONF = '/etc/resolv.conf'
90NSSWITCH_CONF = '/etc/nsswitch.conf'
91NTP_CONF = '/etc/ntp.conf'
92HOSTS = '/etc/hosts'
93HOSTS_ALLOW = '/etc/hosts.allow'
94HOSTS_DENY = '/etc/hosts.deny'
95DHCP_LEASE_DIR = '/var/lib/dhcp3'
36f0987f 96OPENVSWITCH_LOG_DIR = '/var/log/openvswitch'
b828c2f5
EJ
97OPENVSWITCH_DEFAULT_SWITCH = '/etc/default/openvswitch-switch'
98OPENVSWITCH_DEFAULT_CONTROLLER = '/etc/default/openvswitch-controller'
99OPENVSWITCH_CONF_DB = '/etc/openvswitch/conf.db'
100OPENVSWITCH_VSWITCHD_PID = '/var/run/openvswitch/ovs-vswitchd.pid'
e05924ba 101COLLECTD_LOGS_DIR = '/var/lib/collectd/rrd'
b828c2f5 102VAR_LOG_DIR = '/var/log/'
36f0987f 103VAR_LOG_CORE_DIR = '/var/log/core'
b828c2f5
EJ
104X11_LOGS_DIR = VAR_LOG_DIR
105X11_LOGS_RE = re.compile(r'.*/Xorg\..*$')
106X11_AUTH_DIR = '/root/'
107X11_AUTH_RE = re.compile(r'.*/\.((Xauthority)|(serverauth\.[0-9]*))$')
108PAM_DIR = '/etc/pam.d'
109
110#
111# External programs
112#
113
114ARP = '/usr/sbin/arp'
115CAT = '/bin/cat'
116DF = '/bin/df'
117DMESG = '/bin/dmesg'
118DMIDECODE = '/usr/sbin/dmidecode'
b828c2f5
EJ
119FDISK = '/sbin/fdisk'
120FIND = '/usr/bin/find'
121IFCONFIG = '/sbin/ifconfig'
122IPTABLES = '/sbin/iptables'
123LOSETUP = '/sbin/losetup'
124LS = '/bin/ls'
125LSPCI = '/usr/bin/lspci'
126MD5SUM = '/usr/bin/md5sum'
127MODINFO = '/sbin/modinfo'
128NETSTAT = '/bin/netstat'
129OVS_DPCTL = '/usr/sbin/ovs-dpctl'
130OVS_OFCTL = '/usr/sbin/ovs-ofctl'
131OVS_VSCTL = '/usr/sbin/ovs-vsctl'
132OVS_APPCTL = '/usr/sbin/ovs-appctl'
133PS = '/bin/ps'
134ROUTE = '/sbin/route'
135SYSCTL = '/sbin/sysctl'
136TC = '/sbin/tc'
137UPTIME = '/usr/bin/uptime'
138ZCAT = '/bin/zcat'
139
2b8e39ae
EJ
140ETHTOOL = '/sbin/ethtool'
141# ETHTOOL recently moved from /usr/sbin to /sbin in debian
142if not os.path.isfile(ETHTOOL):
143 ETHTOOL = '/usr/sbin/ethtool'
144
b828c2f5
EJ
145#
146# PII -- Personally identifiable information. Of particular concern are
147# things that would identify customers, or their network topology.
148# Passwords are never to be included in any bug report, regardless of any PII
149# declaration.
150#
151# NO -- No PII will be in these entries.
152# YES -- PII will likely or certainly be in these entries.
153# MAYBE -- The user may wish to audit these entries for PII.
154# IF_CUSTOMIZED -- If the files are unmodified, then they will contain no PII,
155# but since we encourage customers to edit these files, PII may have been
156# introduced by the customer. This is used in particular for the networking
157# scripts in dom0.
158#
159
160PII_NO = 'no'
161PII_YES = 'yes'
162PII_MAYBE = 'maybe'
163PII_IF_CUSTOMIZED = 'if_customized'
164KEY = 0
165PII = 1
166MIN_SIZE = 2
167MAX_SIZE = 3
168MIN_TIME = 4
169MAX_TIME = 5
170MIME = 6
171CHECKED = 7
172HIDDEN = 8
173
174MIME_DATA = 'application/data'
175MIME_TEXT = 'text/plain'
176
177INVENTORY_XML_ROOT = "system-status-inventory"
178INVENTORY_XML_SUMMARY = 'system-summary'
179INVENTORY_XML_ELEMENT = 'inventory-entry'
180CAP_XML_ROOT = "system-status-capabilities"
181CAP_XML_ELEMENT = 'capability'
182
183
184CAP_BLOBS = 'blobs'
185CAP_BOOT_LOADER = 'boot-loader'
e05924ba 186CAP_COLLECTD_LOGS = 'collectd-logs'
b828c2f5
EJ
187CAP_DISK_INFO = 'disk-info'
188CAP_FIRSTBOOT = 'firstboot'
189CAP_HARDWARE_INFO = 'hardware-info'
190CAP_HIGH_AVAILABILITY = 'high-availability'
191CAP_HOST_CRASHDUMP_DUMPS = 'host-crashdump-dumps'
192CAP_HOST_CRASHDUMP_LOGS = 'host-crashdump-logs'
193CAP_KERNEL_INFO = 'kernel-info'
194CAP_LOSETUP_A = 'loopback-devices'
195CAP_NETWORK_CONFIG = 'network-config'
196CAP_NETWORK_STATUS = 'network-status'
197CAP_OEM = 'oem'
198CAP_PAM = 'pam'
199CAP_PROCESS_LIST = 'process-list'
200CAP_PERSISTENT_STATS = 'persistent-stats'
201CAP_SYSTEM_LOGS = 'system-logs'
202CAP_SYSTEM_SERVICES = 'system-services'
203CAP_VNCTERM = 'vncterm'
204CAP_WLB = 'wlb'
205CAP_X11_LOGS = 'X11'
206CAP_X11_AUTH = 'X11-auth'
207
208KB = 1024
209MB = 1024 * 1024
210
211caps = {}
212cap_sizes = {}
213unlimited_data = False
214dbg = False
215
216def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
217 max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
218 caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
219 checked, hidden)
220 cap_sizes[key] = 0
221
222
223cap(CAP_BLOBS, PII_NO, max_size=5*MB)
224cap(CAP_BOOT_LOADER, PII_NO, max_size=3*KB,
225 max_time=5)
e05924ba
SL
226cap(CAP_COLLECTD_LOGS, PII_MAYBE, max_size=50*MB,
227 max_time=5)
b828c2f5
EJ
228cap(CAP_DISK_INFO, PII_MAYBE, max_size=25*KB,
229 max_time=20)
230cap(CAP_FIRSTBOOT, PII_YES, min_size=60*KB, max_size=80*KB)
231cap(CAP_HARDWARE_INFO, PII_MAYBE, max_size=30*KB,
232 max_time=20)
233cap(CAP_HIGH_AVAILABILITY, PII_MAYBE, max_size=5*MB)
234cap(CAP_HOST_CRASHDUMP_DUMPS,PII_YES, checked = False)
235cap(CAP_HOST_CRASHDUMP_LOGS, PII_NO)
236cap(CAP_KERNEL_INFO, PII_MAYBE, max_size=120*KB,
237 max_time=5)
238cap(CAP_LOSETUP_A, PII_MAYBE, max_size=KB, max_time=5)
239cap(CAP_NETWORK_CONFIG, PII_IF_CUSTOMIZED,
240 min_size=0, max_size=20*KB)
241cap(CAP_NETWORK_STATUS, PII_YES, max_size=19*KB,
242 max_time=30)
243cap(CAP_PAM, PII_NO, max_size=30*KB)
244cap(CAP_PERSISTENT_STATS, PII_MAYBE, max_size=50*MB,
245 max_time=60)
246cap(CAP_PROCESS_LIST, PII_YES, max_size=30*KB,
247 max_time=20)
248cap(CAP_SYSTEM_LOGS, PII_MAYBE, max_size=50*MB,
249 max_time=5)
250cap(CAP_SYSTEM_SERVICES, PII_NO, max_size=5*KB,
251 max_time=20)
252cap(CAP_VNCTERM, PII_MAYBE, checked = False)
253cap(CAP_WLB, PII_NO, max_size=3*MB,
254 max_time=20)
255cap(CAP_X11_LOGS, PII_NO, max_size=100*KB)
256cap(CAP_X11_AUTH, PII_NO, max_size=100*KB)
257
258ANSWER_YES_TO_ALL = False
259SILENT_MODE = False
260entries = None
261data = {}
262dev_null = open('/dev/null', 'r+')
263
264def output(x):
265 global SILENT_MODE
266 if not SILENT_MODE:
267 print x
268
269def output_ts(x):
270 output("[%s] %s" % (time.strftime("%x %X %Z"), x))
271
272def cmd_output(cap, args, label = None, filter = None):
273 if cap in entries:
274 if not label:
275 if isinstance(args, list):
276 a = [aa for aa in args]
277 a[0] = os.path.basename(a[0])
278 label = ' '.join(a)
279 else:
280 label = args
281 data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter}
282
283def file_output(cap, path_list):
284 if cap in entries:
285 for p in path_list:
286 if os.path.exists(p):
287 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
288 cap_sizes[cap] < caps[cap][MAX_SIZE]:
289 data[p] = {'cap': cap, 'filename': p}
290 try:
291 s = os.stat(p)
292 cap_sizes[cap] += s.st_size
293 except:
294 pass
295 else:
296 output("Omitting %s, size constraint of %s exceeded" % (p, cap))
297
298def tree_output(cap, path, pattern = None, negate = False):
299 if cap in entries:
300 if os.path.exists(path):
301 for f in os.listdir(path):
302 fn = os.path.join(path, f)
303 if os.path.isfile(fn) and matches(fn, pattern, negate):
304 file_output(cap, [fn])
305 elif os.path.isdir(fn):
306 tree_output(cap, fn, pattern, negate)
307
308def func_output(cap, label, func):
309 if cap in entries:
310 t = str(func).split()
311 data[label] = {'cap': cap, 'func': func}
312
313def collect_data():
314 process_lists = {}
315
316 for (k, v) in data.items():
317 cap = v['cap']
318 if v.has_key('cmd_args'):
319 v['output'] = StringIOmtime()
320 if not process_lists.has_key(cap):
321 process_lists[cap] = []
322 process_lists[cap].append(ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'], v['filter']))
323 elif v.has_key('filename') and v['filename'].startswith('/proc/'):
324 # proc files must be read into memory
325 try:
326 f = open(v['filename'], 'r')
327 s = f.read()
328 f.close()
329 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
330 cap_sizes[cap] < caps[cap][MAX_SIZE]:
331 v['output'] = StringIOmtime(s)
332 cap_sizes[cap] += len(s)
333 else:
334 output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
335 except:
336 pass
337 elif v.has_key('func'):
338 try:
339 s = v['func'](cap)
340 except Exception, e:
341 s = str(e)
342 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
343 cap_sizes[cap] < caps[cap][MAX_SIZE]:
344 v['output'] = StringIOmtime(s)
345 cap_sizes[cap] += len(s)
346 else:
347 output("Omitting %s, size constraint of %s exceeded" % (k, cap))
348
349 run_procs(process_lists.values())
350
351
352def main(argv = None):
353 global ANSWER_YES_TO_ALL, SILENT_MODE
354 global entries, data, dbg
355
356 # we need access to privileged files, exit if we are not running as root
357 if os.getuid() != 0:
358 print >>sys.stderr, "Error: ovs-bugtool must be run as root"
359 return 1
360
361 output_type = 'tar.bz2'
362 output_fd = -1
363
364 if argv is None:
365 argv = sys.argv
366
367 try:
368 (options, params) = getopt.gnu_getopt(
369 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
370 'output=', 'outfd=', 'all', 'unlimited', 'debug'])
371 except getopt.GetoptError, opterr:
372 print >>sys.stderr, opterr
373 return 2
374
375 try:
376 load_plugins(True)
377 except:
378 pass
379
380 entries = [e for e in caps.keys() if caps[e][CHECKED]]
381
382 for (k, v) in options:
383 if k == '--capabilities':
384 update_capabilities()
385 print_capabilities()
386 return 0
387
388 if k == '--output':
389 if v in ['tar', 'tar.bz2', 'zip']:
390 output_type = v
391 else:
392 print >>sys.stderr, "Invalid output format '%s'" % v
393 return 2
394
395 # "-s" or "--silent" means suppress output (except for the final
396 # output filename at the end)
397 if k in ['-s', '--silent']:
398 SILENT_MODE = True
399
400 if k == '--entries' and v != '':
401 entries = v.split(',')
402
403 # If the user runs the script with "-y" or "--yestoall" we don't ask
404 # all the really annoying questions.
405 if k in ['-y', '--yestoall']:
406 ANSWER_YES_TO_ALL = True
407
408 if k == '--outfd':
409 output_fd = int(v)
410 try:
411 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
412 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
413 except:
414 print >>sys.stderr, "Invalid output file descriptor", output_fd
415 return 2
416
417 elif k == '--all':
418 entries = caps.keys()
419 elif k == '--unlimited':
420 unlimited_data = True
421 elif k == '--debug':
422 dbg = True
423 ProcOutput.debug = True
424
425 if len(params) != 1:
426 print >>sys.stderr, "Invalid additional arguments", str(params)
427 return 2
428
429 if output_fd != -1 and output_type != 'tar':
430 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
431 return 2
432
433 if ANSWER_YES_TO_ALL:
434 output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
435
436 output('''
437This application will collate dmesg output, details of the
438hardware configuration of your machine, information about the build of
439openvswitch that you are using, plus, if you allow it, various logs.
440
441The collated information will be saved as a .%s for archiving or
442sending to a Technical Support Representative.
443
444The logs may contain private information, and if you are at all
445worried about that, you should exit now, or you should explicitly
446exclude those logs from the archive.
447
448''' % output_type)
449
450 # assemble potential data
451
452 file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
453 cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
454 cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
455
e05924ba 456 tree_output(CAP_COLLECTD_LOGS, COLLECTD_LOGS_DIR)
b828c2f5
EJ
457 cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
458 file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
459 file_output(CAP_DISK_INFO, [FSTAB])
460 cmd_output(CAP_DISK_INFO, [DF, '-alT'])
461 cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
462 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
463 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
464 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
465 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
466
467
468 file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
469 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
470 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
471 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
472 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
473 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
474
475 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
476 PROC_FILESYSTEMS, PROC_CMDLINE])
477 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
478 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
479 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
480 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
481
482 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
483
484 file_output(CAP_NETWORK_CONFIG, [RESOLV_CONF, NSSWITCH_CONF, HOSTS])
485 file_output(CAP_NETWORK_CONFIG, [NTP_CONF, HOSTS_ALLOW, HOSTS_DENY])
486 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_DEFAULT_SWITCH,
487 OPENVSWITCH_DEFAULT_CONTROLLER, OPENVSWITCH_CONF_DB])
488
489 cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
490 cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
491 cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
492 cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
493 tree_output(CAP_NETWORK_STATUS, DHCP_LEASE_DIR)
494 cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
495 for p in os.listdir('/sys/class/net/'):
496 try:
497 f = open('/sys/class/net/%s/type' % p, 'r')
498 t = f.readline()
499 f.close()
500 if int(t) == 1:
501 # ARPHRD_ETHER
502 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
503 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
504 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
505 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
506 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
237414f8
EJ
507 cmd_output(CAP_NETWORK_STATUS,
508 [TC, '-s', '-d', 'class', 'show', 'dev', p])
b828c2f5
EJ
509 except:
510 pass
511 cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
512 file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
36f0987f 513 tree_output(CAP_NETWORK_STATUS, OPENVSWITCH_LOG_DIR)
b828c2f5
EJ
514 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
515 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show'])
516 for d in dp_list():
517 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
518 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'status', d])
519 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
520 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
521 try:
522 vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
523 vspid = int(vspidfile.readline().strip())
524 vspidfile.close()
525 for b in bond_list(vspid):
526 cmd_output(CAP_NETWORK_STATUS,
527 [OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
528 'ovs-appctl-bond-show-%s.out' % b)
529 except e:
530 pass
531
532 tree_output(CAP_PAM, PAM_DIR)
533
534 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
535 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
536
537 file_output(CAP_SYSTEM_LOGS,
538 [ VAR_LOG_DIR + x for x in
539 [ 'kern.log', 'daemon.log', 'user.log', 'syslog', 'messages',
540 'debug', 'dmesg', 'boot'] +
541 [ f % n for n in range(1, 20) \
542 for f in ['kern.log.%d', 'kern.log.%d.gz',
543 'daemon.log.%d', 'daemon.log.%d.gz',
544 'user.log.%d', 'user.log.%d.gz',
545 'messages.%d', 'messages.%d.gz']]])
546 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
547 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
548
549
550 tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
551 tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
36f0987f 552 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
b828c2f5
EJ
553
554
555 try:
556 load_plugins()
557 except:
558 pass
559
560 # permit the user to filter out data
561 for k in sorted(data.keys()):
562 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % k):
563 del data[k]
564
565 # collect selected data now
566 output_ts('Running commands to collect data')
567 collect_data()
568
569 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
570
571 # include inventory
572 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
573
574 # create archive
575 if output_fd == -1 and not os.path.exists(BUG_DIR):
576 try:
577 os.makedirs(BUG_DIR)
578 except:
579 pass
580
581 if output_fd == -1:
582 output_ts('Creating output file')
583
584 if output_type.startswith('tar'):
585 make_tar(subdir, output_type, output_fd)
586 else:
587 make_zip(subdir)
588
589 clean_tapdisk_logs()
590
591 if dbg:
592 print >>sys.stderr, "Category sizes (max, actual):\n"
593 for c in caps.keys():
594 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
595 cap_sizes[c])
596 return 0
597
598def find_tapdisk_logs():
599 return glob.glob('/var/log/blktap/*.log*')
600
601def generate_tapdisk_logs():
602 for pid in pidof('tapdisk'):
603 try:
604 os.kill(pid, SIGUSR1)
605 output_ts("Including logs for tapdisk process %d" % pid)
606 except :
607 pass
608 # give processes a second to write their logs
609 time.sleep(1)
610
611def clean_tapdisk_logs():
612 for filename in find_tapdisk_logs():
613 try:
614 os.remove(filename)
615 except :
616 pass
617
618def filter_db_pii(str, state):
619 if 'in_secret_table' not in state:
620 state['in_secret_table'] = False
621
622 if str.startswith('<table ') and 'name="secret"' in str:
623 state['in_secret_table'] = True
624 elif str.startswith('</table>'):
625 state['in_secret_table'] = False
626
627 if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
628 str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
629 return str
630
631def dump_scsi_hosts(cap):
632 output = ''
633 l = os.listdir('/sys/class/scsi_host')
634 l.sort()
635
636 for h in l:
637 procname = ''
638 try:
639 f = open('/sys/class/scsi_host/%s/proc_name' % h)
640 procname = f.readline().strip("\n")
641 f.close()
642 except:
643 pass
644 modelname = None
645 try:
646 f = open('/sys/class/scsi_host/%s/model_name' % h)
647 modelname = f.readline().strip("\n")
648 f.close()
649 except:
650 pass
651
652 output += "%s:\n" %h
653 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
654
655 return output
656
657def module_info(cap):
658 output = StringIO.StringIO()
659 modules = open(PROC_MODULES, 'r')
660 procs = []
661
662 for line in modules:
663 module = line.split()[0]
664 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
665 modules.close()
666
667 run_procs([procs])
668
669 return output.getvalue()
670
671def dp_list():
672 output = StringIO.StringIO()
673 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
674
675 run_procs([procs])
676
677 if not procs[0].timed_out:
678 return output.getvalue().splitlines()
679 return []
680
681def bond_list(pid):
682 output = StringIO.StringIO()
683 procs = [ProcOutput([OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
684
685 run_procs([procs])
686
687 if not procs[0].timed_out:
688 bonds = output.getvalue().splitlines()[1:]
689 return [x.split('\t')[1] for x in bonds]
690 return []
691
692def fd_usage(cap):
693 output = ''
694 fd_dict = {}
695 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
696 try:
697 fh = open('/proc/'+d+'/cmdline')
698 name = fh.readline()
699 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
700 if num_fds > 0:
701 if not num_fds in fd_dict:
702 fd_dict[num_fds] = []
703 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
704 finally:
705 fh.close()
706 keys = fd_dict.keys()
707 keys.sort(lambda a, b: int(b) - int(a))
708 for k in keys:
709 output += "%s: %s\n" % (k, str(fd_dict[k]))
710 return output
711
712def load_plugins(just_capabilities = False):
713 def getText(nodelist):
714 rc = ""
715 for node in nodelist:
716 if node.nodeType == node.TEXT_NODE:
717 rc += node.data
718 return rc.encode()
719
720 def getBoolAttr(el, attr, default = False):
721 ret = default
722 val = el.getAttribute(attr).lower()
723 if val in ['true', 'false', 'yes', 'no']:
724 ret = val in ['true', 'yes']
725 return ret
726
727 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
728 if not caps.has_key(dir):
729 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
730 continue
731 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
732 assert xmldoc.documentElement.tagName == "capability"
733
734 pii, min_size, max_size, min_time, max_time, mime = \
735 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
736
737 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
738 pii = xmldoc.documentElement.getAttribute("pii")
739 if xmldoc.documentElement.getAttribute("min_size") != '':
740 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
741 if xmldoc.documentElement.getAttribute("max_size") != '':
742 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
743 if xmldoc.documentElement.getAttribute("min_time") != '':
744 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
745 if xmldoc.documentElement.getAttribute("max_time") != '':
746 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
747 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
748 mime = xmldoc.documentElement.getAttribute("mime")
749 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
750 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
751
752 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
753
754 if just_capabilities:
755 continue
756
757 plugdir = os.path.join(PLUGIN_DIR, dir)
758 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
759 xmldoc = parse(os.path.join(plugdir, file))
760 assert xmldoc.documentElement.tagName == "collect"
761
762 for el in xmldoc.documentElement.getElementsByTagName("*"):
763 if el.tagName == "files":
764 file_output(dir, getText(el.childNodes).split())
765 elif el.tagName == "directory":
766 pattern = el.getAttribute("pattern")
767 if pattern == '': pattern = None
768 negate = getBoolAttr(el, 'negate')
769 tree_output(dir, getText(el.childNodes), pattern and re.compile(pattern) or None, negate)
770 elif el.tagName == "command":
771 label = el.getAttribute("label")
772 if label == '': label = None
773 cmd_output(dir, getText(el.childNodes), label)
774
775def make_tar(subdir, suffix, output_fd):
776 global SILENT_MODE, data
777
778 mode = 'w'
779 if suffix == 'tar.bz2':
780 mode = 'w:bz2'
781 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
782
783 if output_fd == -1:
784 tf = tarfile.open(filename, mode)
785 else:
786 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
787
788 try:
789 for (k, v) in data.items():
790 try:
791 tar_filename = os.path.join(subdir, construct_filename(k, v))
792 ti = tarfile.TarInfo(tar_filename)
793
794 ti.uname = 'root'
795 ti.gname = 'root'
796
797 if v.has_key('output'):
798 ti.mtime = v['output'].mtime
799 ti.size = len(v['output'].getvalue())
800 v['output'].seek(0)
801 tf.addfile(ti, v['output'])
802 elif v.has_key('filename'):
803 s = os.stat(v['filename'])
804 ti.mtime = s.st_mtime
805 ti.size = s.st_size
806 tf.addfile(ti, file(v['filename']))
807 except:
808 pass
809 finally:
810 tf.close()
811
812 if output_fd == -1:
813 output ('Writing tarball %s successful.' % filename)
814 if SILENT_MODE:
815 print filename
816
817
818def make_zip(subdir):
819 global SILENT_MODE, data
820
821 filename = "%s/%s.zip" % (BUG_DIR, subdir)
822 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
823
824 try:
825 for (k, v) in data.items():
826 try:
827 dest = os.path.join(subdir, construct_filename(k, v))
828
829 if v.has_key('output'):
830 zf.writestr(dest, v['output'].getvalue())
831 else:
832 if os.stat(v['filename']).st_size < 50:
833 compress_type = zipfile.ZIP_STORED
834 else:
835 compress_type = zipfile.ZIP_DEFLATED
836 zf.write(v['filename'], dest, compress_type)
837 except:
838 pass
839 finally:
840 zf.close()
841
842 output ('Writing archive %s successful.' % filename)
843 if SILENT_MODE:
844 print filename
845
846
847def make_inventory(inventory, subdir):
848 document = getDOMImplementation().createDocument(
849 None, INVENTORY_XML_ROOT, None)
850
851 # create summary entry
852 s = document.createElement(INVENTORY_XML_SUMMARY)
853 user = os.getenv('SUDO_USER', os.getenv('USER'))
854 if user:
855 s.setAttribute('user', user)
856 s.setAttribute('date', time.strftime('%c'))
857 s.setAttribute('hostname', platform.node())
858 s.setAttribute('uname', ' '.join(platform.uname()))
859 s.setAttribute('uptime', commands.getoutput(UPTIME))
860 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
861
862 map(lambda (k, v): inventory_entry(document, subdir, k, v),
863 inventory.items())
864 return document.toprettyxml()
865
866def inventory_entry(document, subdir, k, v):
867 try:
868 el = document.createElement(INVENTORY_XML_ELEMENT)
869 el.setAttribute('capability', v['cap'])
870 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
871 el.setAttribute('md5sum', md5sum(v))
872 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
873 except:
874 pass
875
876
877def md5sum(d):
878 m = md5.new()
879 if d.has_key('filename'):
880 f = open(d['filename'])
881 data = f.read(1024)
882 while len(data) > 0:
883 m.update(data)
884 data = f.read(1024)
885 f.close()
886 elif d.has_key('output'):
887 m.update(d['output'].getvalue())
888 return m.hexdigest()
889
890
891def construct_filename(k, v):
892 if v.has_key('filename'):
893 if v['filename'][0] == '/':
894 return v['filename'][1:]
895 else:
896 return v['filename']
897 s = k.replace(' ', '-')
898 s = s.replace('--', '-')
899 s = s.replace('/', '%')
900 if s.find('.') == -1:
901 s += '.out'
902
903 return s
904
905def update_capabilities():
906 pass
907
908def update_cap_size(cap, size):
909 update_cap(cap, MIN_SIZE, size)
910 update_cap(cap, MAX_SIZE, size)
911 update_cap(cap, CHECKED, size > 0)
912
913
914def update_cap(cap, k, v):
915 global caps
916 l = list(caps[cap])
917 l[k] = v
918 caps[cap] = tuple(l)
919
920
921def size_of_dir(d, pattern = None, negate = False):
922 if os.path.isdir(d):
923 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
924 pattern, negate)
925 else:
926 return 0
927
928
929def size_of_all(files, pattern = None, negate = False):
930 return sum([size_of(f, pattern, negate) for f in files])
931
932
933def matches(f, pattern, negate):
934 if negate:
935 return not matches(f, pattern, False)
936 else:
937 return pattern is None or pattern.match(f)
938
939
940def size_of(f, pattern, negate):
941 if os.path.isfile(f) and matches(f, pattern, negate):
942 return os.stat(f)[6]
943 else:
944 return size_of_dir(f, pattern, negate)
945
946
947def print_capabilities():
948 document = getDOMImplementation().createDocument(
949 "ns", CAP_XML_ROOT, None)
950 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
951 print document.toprettyxml()
952
953def capability(document, key):
954 c = caps[key]
955 el = document.createElement(CAP_XML_ELEMENT)
956 el.setAttribute('key', c[KEY])
957 el.setAttribute('pii', c[PII])
958 el.setAttribute('min-size', str(c[MIN_SIZE]))
959 el.setAttribute('max-size', str(c[MAX_SIZE]))
960 el.setAttribute('min-time', str(c[MIN_TIME]))
961 el.setAttribute('max-time', str(c[MAX_TIME]))
962 el.setAttribute('content-type', c[MIME])
963 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
964 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
965
966
967def prettyDict(d):
968 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
969 return '\n'.join([format % i for i in d.items()]) + '\n'
970
971
972def yes(prompt):
973 yn = raw_input(prompt)
974
975 return len(yn) == 0 or yn.lower()[0] == 'y'
976
977
978partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
979
980def disk_list():
981 disks = []
982 try:
983 f = open('/proc/partitions')
984 f.readline()
985 f.readline()
986 for line in f.readlines():
987 (major, minor, blocks, name) = line.split()
988 if int(major) < 254 and not partition_re.match(name):
989 disks.append(name)
990 f.close()
991 except:
992 pass
993 return disks
994
995
996class ProcOutput:
997 debug = False
998
999 def __init__(self, command, max_time, inst=None, filter=None):
1000 self.command = command
1001 self.max_time = max_time
1002 self.inst = inst
1003 self.running = False
1004 self.status = None
1005 self.timed_out = False
1006 self.failed = False
1007 self.timeout = int(time.time()) + self.max_time
1008 self.filter = filter
1009 self.filter_state = {}
1010
1011 def __del__(self):
1012 self.terminate()
1013
1014 def cmdAsStr(self):
1015 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1016
1017 def run(self):
1018 self.timed_out = False
1019 try:
1020 if ProcOutput.debug:
1021 output_ts("Starting '%s'" % self.cmdAsStr())
1022 self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1023 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1024 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1025 self.running = True
1026 self.failed = False
1027 except:
1028 output_ts("'%s' failed" % self.cmdAsStr())
1029 self.running = False
1030 self.failed = True
1031
1032 def terminate(self):
1033 if self.running:
1034 try:
1035 os.kill(self.proc.pid, SIGTERM)
1036 except:
1037 pass
1038 self.proc = None
1039 self.running = False
1040 self.status = SIGTERM
1041
1042 def read_line(self):
1043 assert self.running
1044 line = self.proc.stdout.readline()
1045 if line == '':
1046 # process exited
1047 self.status = self.proc.wait()
1048 self.proc = None
1049 self.running = False
1050 else:
1051 if self.filter:
1052 line = self.filter(line, self.filter_state)
1053 if self.inst:
1054 self.inst.write(line)
1055
1056def run_procs(procs):
1057 while True:
1058 pipes = []
1059 active_procs = []
1060
1061 for pp in procs:
1062 for p in pp:
1063 if p.running:
1064 active_procs.append(p)
1065 pipes.append(p.proc.stdout)
1066 break
1067 elif p.status == None and not p.failed and not p.timed_out:
1068 p.run()
1069 if p.running:
1070 active_procs.append(p)
1071 pipes.append(p.proc.stdout)
1072 break
1073
1074 if len(pipes) == 0:
1075 # all finished
1076 break
1077
1078 (i, o, x) = select(pipes, [], [], 1.0)
1079 now = int(time.time())
1080
1081 # handle process output
1082 for p in active_procs:
1083 if p.proc.stdout in i:
1084 p.read_line()
1085
1086 # handle timeout
1087 if p.running and now > p.timeout:
1088 output_ts("'%s' timed out" % p.cmdAsStr())
1089 if p.inst:
1090 p.inst.write("\n** timeout **\n")
1091 p.timed_out = True
1092 p.terminate()
1093
1094
1095def pidof(name):
1096 pids = []
1097
1098 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1099 try:
1100 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1101 pids.append(int(d))
1102 except:
1103 pass
1104
1105 return pids
1106
1107
1108class StringIOmtime(StringIO.StringIO):
1109 def __init__(self, buf = ''):
1110 StringIO.StringIO.__init__(self, buf)
1111 self.mtime = time.time()
1112
1113 def write(self, s):
1114 StringIO.StringIO.write(self, s)
1115 self.mtime = time.time()
1116
1117
1118if __name__ == "__main__":
1119 try:
1120 sys.exit(main())
1121 except KeyboardInterrupt:
1122 print "\nInterrupted."
1123 sys.exit(3)