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.
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.
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
16 # Copyright (c) 2005, 2007 XenSource Ltd.
17 # Copyright (c) 2010, 2011 Nicira Networks.
20 # To add new entries to the bugtool, you need to:
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:
28 # A new CAP_ constant.
29 # A cap() invocation to declare the capability.
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(),
37 warnings.filterwarnings(action="ignore", category=DeprecationWarning)
48 from xml.dom.minidom import parse, getDOMImplementation
50 from subprocess import Popen, PIPE
51 from select import select
52 from signal import SIGTERM, SIGUSR1
61 OS_RELEASE = platform.release()
67 APT_SOURCES_LIST = "/etc/apt/sources.list"
68 APT_SOURCES_LIST_D = "/etc/apt/sources.list.d"
69 BUG_DIR = "/var/log/ovs-bugtool"
70 PLUGIN_DIR = "@sysconfdir@/openvswitch/bugtool-plugins"
71 GRUB_CONFIG = '/boot/grub/menu.lst'
72 BOOT_KERNEL = '/boot/vmlinuz-' + OS_RELEASE
73 BOOT_INITRD = '/boot/initrd-' + OS_RELEASE + '.img'
74 PROC_PARTITIONS = '/proc/partitions'
76 PROC_MOUNTS = '/proc/mounts'
77 ISCSI_CONF = '/etc/iscsi/iscsid.conf'
78 ISCSI_INITIATOR = '/etc/iscsi/initiatorname.iscsi'
79 LVM_CACHE = '/etc/lvm/cache/.cache'
80 LVM_CONFIG = '/etc/lvm/lvm.conf'
81 PROC_CPUINFO = '/proc/cpuinfo'
82 PROC_MEMINFO = '/proc/meminfo'
83 PROC_IOPORTS = '/proc/ioports'
84 PROC_INTERRUPTS = '/proc/interrupts'
85 PROC_SCSI = '/proc/scsi/scsi'
86 PROC_VERSION = '/proc/version'
87 PROC_MODULES = '/proc/modules'
88 PROC_DEVICES = '/proc/devices'
89 PROC_FILESYSTEMS = '/proc/filesystems'
90 PROC_CMDLINE = '/proc/cmdline'
91 PROC_CONFIG = '/proc/config.gz'
92 PROC_USB_DEV = '/proc/bus/usb/devices'
93 PROC_XEN_BALLOON = '/proc/xen/balloon'
94 PROC_NET_BONDING_DIR = '/proc/net/bonding'
95 IFCFG_RE = re.compile(r'^.*/ifcfg-.*')
96 ROUTE_RE = re.compile(r'^.*/route-.*')
97 SYSCONFIG_HWCONF = '/etc/sysconfig/hwconf'
98 SYSCONFIG_NETWORK = '/etc/sysconfig/network'
99 SYSCONFIG_NETWORK_SCRIPTS = '/etc/sysconfig/network-scripts'
100 PROC_NET_VLAN_DIR = '/proc/net/vlan'
101 PROC_NET_SOFTNET_STAT = '/proc/net/softnet_stat'
102 MODPROBE_CONF = '/etc/modprobe.conf'
103 MODPROBE_DIR = '/etc/modprobe.d'
104 RESOLV_CONF = '/etc/resolv.conf'
105 MPP_CONF = '/etc/mpp.conf'
106 MULTIPATH_CONF = '/etc/multipath.conf'
107 NSSWITCH_CONF = '/etc/nsswitch.conf'
108 NTP_CONF = '/etc/ntp.conf'
109 IPTABLES_CONFIG = '/etc/sysconfig/iptables-config'
111 HOSTS_ALLOW = '/etc/hosts.allow'
112 HOSTS_DENY = '/etc/hosts.deny'
113 DHCP_LEASE_DIR = ['/var/lib/dhclient', '/var/lib/dhcp3']
114 OPENVSWITCH_LOG_DIR = '@LOGDIR@'
115 OPENVSWITCH_DEFAULT_SWITCH = '/etc/default/openvswitch-switch' # Debian
116 OPENVSWITCH_SYSCONFIG_SWITCH = '/etc/sysconfig/openvswitch' # RHEL
117 OPENVSWITCH_DEFAULT_CONTROLLER = '/etc/default/openvswitch-controller'
118 OPENVSWITCH_CONF_DB = '@sysconfdir@/openvswitch/conf.db'
119 OPENVSWITCH_VSWITCHD_PID = '@RUNDIR@/ovs-vswitchd.pid'
120 COLLECTD_LOGS_DIR = '/var/lib/collectd/rrd'
121 VAR_LOG_DIR = '/var/log/'
122 VAR_LOG_CORE_DIR = '/var/log/core'
123 X11_LOGS_DIR = VAR_LOG_DIR
124 X11_LOGS_RE = re.compile(r'.*/Xorg\..*$')
125 X11_AUTH_DIR = '/root/'
126 X11_AUTH_RE = re.compile(r'.*/\.((Xauthority)|(serverauth\.[0-9]*))$')
127 YUM_LOG = '/var/log/yum.log'
128 YUM_REPOS_DIR = '/etc/yum.repos.d'
129 PAM_DIR = '/etc/pam.d'
130 KRB5_CONF = '/etc/krb5.conf'
136 os.environ['PATH'] = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:@pkgdatadir@/scripts'
140 CHKCONFIG = 'chkconfig'
143 DMIDECODE = 'dmidecode'
145 DPKG_QUERY = 'dpkg-query'
150 IFCONFIG = 'ifconfig'
151 IPTABLES = 'iptables'
152 ISCSIADM = 'iscsiadm'
156 LVDISPLAY = 'lvdisplay'
161 MULTIPATHD = 'multipathd'
163 OVS_DPCTL = 'ovs-dpctl'
164 OVS_OFCTL = 'ovs-ofctl'
165 OVS_VSCTL = 'ovs-vsctl'
166 OVS_APPCTL = 'ovs-appctl'
180 # PII -- Personally identifiable information. Of particular concern are
181 # things that would identify customers, or their network topology.
182 # Passwords are never to be included in any bug report, regardless of any PII
185 # NO -- No PII will be in these entries.
186 # YES -- PII will likely or certainly be in these entries.
187 # MAYBE -- The user may wish to audit these entries for PII.
188 # IF_CUSTOMIZED -- If the files are unmodified, then they will contain no PII,
189 # but since we encourage customers to edit these files, PII may have been
190 # introduced by the customer. This is used in particular for the networking
197 PII_IF_CUSTOMIZED = 'if_customized'
208 MIME_DATA = 'application/data'
209 MIME_TEXT = 'text/plain'
211 INVENTORY_XML_ROOT = "system-status-inventory"
212 INVENTORY_XML_SUMMARY = 'system-summary'
213 INVENTORY_XML_ELEMENT = 'inventory-entry'
214 CAP_XML_ROOT = "system-status-capabilities"
215 CAP_XML_ELEMENT = 'capability'
219 CAP_BOOT_LOADER = 'boot-loader'
220 CAP_COLLECTD_LOGS = 'collectd-logs'
221 CAP_DISK_INFO = 'disk-info'
222 CAP_FIRSTBOOT = 'firstboot'
223 CAP_HARDWARE_INFO = 'hardware-info'
224 CAP_HDPARM_T = 'hdparm-t'
225 CAP_HIGH_AVAILABILITY = 'high-availability'
226 CAP_KERNEL_INFO = 'kernel-info'
227 CAP_LOSETUP_A = 'loopback-devices'
228 CAP_MULTIPATH = 'multipath'
229 CAP_NETWORK_CONFIG = 'network-config'
230 CAP_NETWORK_STATUS = 'network-status'
233 CAP_PROCESS_LIST = 'process-list'
234 CAP_PERSISTENT_STATS = 'persistent-stats'
235 CAP_SYSTEM_LOGS = 'system-logs'
236 CAP_SYSTEM_SERVICES = 'system-services'
237 CAP_VNCTERM = 'vncterm'
240 CAP_X11_AUTH = 'X11-auth'
248 unlimited_data = False
251 def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
252 max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
253 caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
258 cap(CAP_BLOBS, PII_NO, max_size=5*MB)
259 cap(CAP_BOOT_LOADER, PII_NO, max_size=3*KB,
261 cap(CAP_COLLECTD_LOGS, PII_MAYBE, max_size=50*MB,
263 cap(CAP_DISK_INFO, PII_MAYBE, max_size=50*KB,
265 cap(CAP_FIRSTBOOT, PII_YES, min_size=60*KB, max_size=80*KB)
266 cap(CAP_HARDWARE_INFO, PII_MAYBE, max_size=30*KB,
268 cap(CAP_HDPARM_T, PII_NO, min_size=0, max_size=5*KB,
269 min_time=20, max_time=90, checked=False, hidden=True)
270 cap(CAP_HIGH_AVAILABILITY, PII_MAYBE, max_size=5*MB)
271 cap(CAP_KERNEL_INFO, PII_MAYBE, max_size=120*KB,
273 cap(CAP_LOSETUP_A, PII_MAYBE, max_size=KB, max_time=5)
274 cap(CAP_MULTIPATH, PII_MAYBE, max_size=20*KB,
276 cap(CAP_NETWORK_CONFIG, PII_IF_CUSTOMIZED,
277 min_size=0, max_size=40*KB)
278 cap(CAP_NETWORK_STATUS, PII_YES, max_size=50*KB,
280 cap(CAP_PAM, PII_NO, max_size=50*KB)
281 cap(CAP_PERSISTENT_STATS, PII_MAYBE, max_size=50*MB,
283 cap(CAP_PROCESS_LIST, PII_YES, max_size=30*KB,
285 cap(CAP_SYSTEM_LOGS, PII_MAYBE, max_size=50*MB,
287 cap(CAP_SYSTEM_SERVICES, PII_NO, max_size=5*KB,
289 cap(CAP_VNCTERM, PII_MAYBE, checked = False)
290 cap(CAP_WLB, PII_NO, max_size=3*MB,
292 cap(CAP_X11_LOGS, PII_NO, max_size=100*KB)
293 cap(CAP_X11_AUTH, PII_NO, max_size=100*KB)
294 cap(CAP_YUM, PII_IF_CUSTOMIZED, max_size=10*KB,
297 ANSWER_YES_TO_ALL = False
301 dev_null = open('/dev/null', 'r+')
309 output("[%s] %s" % (time.strftime("%x %X %Z"), x))
311 def cmd_output(cap, args, label = None, filter = None):
314 if isinstance(args, list):
315 a = [aa for aa in args]
316 a[0] = os.path.basename(a[0])
320 data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter}
322 def file_output(cap, path_list):
325 if os.path.exists(p):
326 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
327 cap_sizes[cap] < caps[cap][MAX_SIZE]:
328 data[p] = {'cap': cap, 'filename': p}
331 cap_sizes[cap] += s.st_size
335 output("Omitting %s, size constraint of %s exceeded" % (p, cap))
337 def tree_output(cap, path, pattern = None, negate = False):
339 if os.path.exists(path):
340 for f in os.listdir(path):
341 fn = os.path.join(path, f)
342 if os.path.isfile(fn) and matches(fn, pattern, negate):
343 file_output(cap, [fn])
344 elif os.path.isdir(fn):
345 tree_output(cap, fn, pattern, negate)
347 def func_output(cap, label, func):
349 t = str(func).split()
350 data[label] = {'cap': cap, 'func': func}
355 for (k, v) in data.items():
357 if v.has_key('cmd_args'):
358 v['output'] = StringIOmtime()
359 if not process_lists.has_key(cap):
360 process_lists[cap] = []
361 process_lists[cap].append(ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'], v['filter']))
362 elif v.has_key('filename') and v['filename'].startswith('/proc/'):
363 # proc files must be read into memory
365 f = open(v['filename'], 'r')
368 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
369 cap_sizes[cap] < caps[cap][MAX_SIZE]:
370 v['output'] = StringIOmtime(s)
371 cap_sizes[cap] += len(s)
373 output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
376 elif v.has_key('func'):
381 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
382 cap_sizes[cap] < caps[cap][MAX_SIZE]:
383 v['output'] = StringIOmtime(s)
384 cap_sizes[cap] += len(s)
386 output("Omitting %s, size constraint of %s exceeded" % (k, cap))
388 run_procs(process_lists.values())
391 def main(argv = None):
392 global ANSWER_YES_TO_ALL, SILENT_MODE
393 global entries, data, dbg, unlimited_data
395 # we need access to privileged files, exit if we are not running as root
397 print >>sys.stderr, "Error: ovs-bugtool must be run as root"
401 output_type = 'tar.bz2'
408 (options, params) = getopt.gnu_getopt(
409 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
410 'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
412 except getopt.GetoptError, opterr:
413 print >>sys.stderr, opterr
421 entries = [e for e in caps.keys() if caps[e][CHECKED]]
423 for (k, v) in options:
424 if k == '--capabilities':
425 update_capabilities()
430 if v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
433 print >>sys.stderr, "Invalid output format '%s'" % v
436 # "-s" or "--silent" means suppress output (except for the final
437 # output filename at the end)
438 if k in ['-s', '--silent']:
441 if k == '--entries' and v != '':
442 entries = v.split(',')
444 # If the user runs the script with "-y" or "--yestoall" we don't ask
445 # all the really annoying questions.
446 if k in ['-y', '--yestoall']:
447 ANSWER_YES_TO_ALL = True
452 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
453 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
455 print >>sys.stderr, "Invalid output file descriptor", output_fd
462 entries = caps.keys()
463 elif k == '--unlimited':
464 unlimited_data = True
467 ProcOutput.debug = True
470 print >>sys.stderr, "Invalid additional arguments", str(params)
473 if output_fd != -1 and output_type != 'tar':
474 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
477 if output_fd != -1 and output_file is not None:
478 print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
481 if ANSWER_YES_TO_ALL:
482 output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
485 This application will collate dmesg output, details of the
486 hardware configuration of your machine, information about the build of
487 openvswitch that you are using, plus, if you allow it, various logs.
489 The collated information will be saved as a .%s for archiving or
490 sending to a Technical Support Representative.
492 The logs may contain private information, and if you are at all
493 worried about that, you should exit now, or you should explicitly
494 exclude those logs from the archive.
498 # assemble potential data
500 file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
501 cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
502 cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
504 tree_output(CAP_COLLECTD_LOGS, COLLECTD_LOGS_DIR)
505 cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
506 file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
507 file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
508 cmd_output(CAP_DISK_INFO, [DF, '-alT'])
509 cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
510 for d in disk_list():
511 cmd_output(CAP_DISK_INFO, [HDPARM, '-I', '/dev/%s' % d])
512 if len(pidof('iscsid')) != 0:
513 cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
514 cmd_output(CAP_DISK_INFO, [VGSCAN])
515 cmd_output(CAP_DISK_INFO, [PVS])
516 cmd_output(CAP_DISK_INFO, [VGS])
517 cmd_output(CAP_DISK_INFO, [LVS])
518 file_output(CAP_DISK_INFO, [LVM_CACHE, LVM_CONFIG])
519 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
520 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
521 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
522 cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
523 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
524 cmd_output(CAP_DISK_INFO, [LVDISPLAY, '--map'])
526 file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
527 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
528 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
529 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
530 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
531 file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
532 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
535 for d in disk_list():
536 cmd_output(CAP_HDPARM_T, [HDPARM, '-tT', '/dev/%s' % d])
538 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
539 PROC_FILESYSTEMS, PROC_CMDLINE])
540 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
541 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
542 file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
543 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
544 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
546 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
548 file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
549 cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
550 func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
551 cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
552 if CAP_MULTIPATH in entries:
553 dump_rdac_groups(CAP_MULTIPATH)
555 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
556 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
557 file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
558 file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
559 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_CONF_DB])
561 cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
562 cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
563 cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
564 cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
565 for dir in DHCP_LEASE_DIR:
566 tree_output(CAP_NETWORK_STATUS, dir)
567 cmd_output(CAP_NETWORK_STATUS, [BRCTL, 'show'])
568 cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
569 for p in os.listdir('/sys/class/net/'):
571 f = open('/sys/class/net/%s/type' % p, 'r')
576 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
577 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
578 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
579 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
580 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
581 cmd_output(CAP_NETWORK_STATUS,
582 [TC, '-s', '-d', 'class', 'show', 'dev', p])
585 tree_output(CAP_NETWORK_STATUS, PROC_NET_BONDING_DIR)
586 tree_output(CAP_NETWORK_STATUS, PROC_NET_VLAN_DIR)
587 cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
588 file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
589 tree_output(CAP_NETWORK_STATUS, OPENVSWITCH_LOG_DIR)
590 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
591 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
593 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
594 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
595 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
597 vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
598 vspid = int(vspidfile.readline().strip())
600 for b in bond_list(vspid):
601 cmd_output(CAP_NETWORK_STATUS,
602 [OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
603 'ovs-appctl-bond-show-%s.out' % b)
607 tree_output(CAP_PAM, PAM_DIR)
608 file_output(CAP_PAM, [KRB5_CONF])
610 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
611 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
613 file_output(CAP_SYSTEM_LOGS,
614 [ VAR_LOG_DIR + x for x in
615 [ 'crit.log', 'kern.log', 'daemon.log', 'user.log', 'syslog',
616 'messages', 'secure', 'debug', 'dmesg', 'boot'] +
617 [ f % n for n in range(1, 20) \
618 for f in ['crit.log.%d', 'crit.log.%d.gz',
619 'kern.log.%d', 'kern.log.%d.gz',
620 'daemon.log.%d', 'daemon.log.%d.gz',
621 'user.log.%d', 'user.log.%d.gz',
622 'messages.%d', 'messages.%d.gz',
623 'syslog.%d', 'syslog.%d.gz']]])
624 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
625 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
627 cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
629 tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
630 tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
631 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
633 file_output(CAP_YUM, [YUM_LOG])
634 tree_output(CAP_YUM, YUM_REPOS_DIR)
635 cmd_output(CAP_YUM, [RPM, '-qa'])
636 file_output(CAP_YUM, [APT_SOURCES_LIST])
637 tree_output(CAP_YUM, APT_SOURCES_LIST_D)
638 cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
645 # permit the user to filter out data
646 for k in sorted(data.keys()):
647 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % k):
650 # collect selected data now
651 output_ts('Running commands to collect data')
654 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
657 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
661 if output_file is None:
664 dirname = os.path.dirname(output_file)
665 if dirname and not os.path.exists(dirname):
672 output_ts('Creating output file')
674 if output_type.startswith('tar'):
675 make_tar(subdir, output_type, output_fd, output_file)
677 make_zip(subdir, output_file)
682 print >>sys.stderr, "Category sizes (max, actual):\n"
683 for c in caps.keys():
684 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
688 def find_tapdisk_logs():
689 return glob.glob('/var/log/blktap/*.log*')
691 def generate_tapdisk_logs():
692 for pid in pidof('tapdisk'):
694 os.kill(pid, SIGUSR1)
695 output_ts("Including logs for tapdisk process %d" % pid)
698 # give processes a second to write their logs
701 def clean_tapdisk_logs():
702 for filename in find_tapdisk_logs():
708 def filter_db_pii(str, state):
709 if 'in_secret_table' not in state:
710 state['in_secret_table'] = False
712 if str.startswith('<table ') and 'name="secret"' in str:
713 state['in_secret_table'] = True
714 elif str.startswith('</table>'):
715 state['in_secret_table'] = False
717 if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
718 str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
721 def dump_scsi_hosts(cap):
723 l = os.listdir('/sys/class/scsi_host')
729 f = open('/sys/class/scsi_host/%s/proc_name' % h)
730 procname = f.readline().strip("\n")
736 f = open('/sys/class/scsi_host/%s/model_name' % h)
737 modelname = f.readline().strip("\n")
743 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
747 def module_info(cap):
748 output = StringIO.StringIO()
749 modules = open(PROC_MODULES, 'r')
753 module = line.split()[0]
754 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
759 return output.getvalue()
762 def multipathd_topology(cap):
763 pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
764 stdout=PIPE, stderr=dev_null)
765 stdout, stderr = pipe.communicate('show topology')
770 output = StringIO.StringIO()
771 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
775 if not procs[0].timed_out:
776 return output.getvalue().splitlines()
780 output = StringIO.StringIO()
781 procs = [ProcOutput([OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
785 if not procs[0].timed_out:
786 bonds = output.getvalue().splitlines()[1:]
787 return [x.split('\t')[1] for x in bonds]
793 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
795 fh = open('/proc/'+d+'/cmdline')
797 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
799 if not num_fds in fd_dict:
800 fd_dict[num_fds] = []
801 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
804 keys = fd_dict.keys()
805 keys.sort(lambda a, b: int(b) - int(a))
807 output += "%s: %s\n" % (k, str(fd_dict[k]))
810 def dump_rdac_groups(cap):
811 output = StringIO.StringIO()
812 procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
816 if not procs[0].timed_out:
818 for line in output.getvalue().splitlines():
819 if line.startswith('ID'):
821 elif line.startswith('----'):
824 group, _ = line.split(None, 1)
825 cmd_output(cap, [MPPUTIL, '-g', group])
827 def load_plugins(just_capabilities = False):
828 def getText(nodelist):
830 for node in nodelist:
831 if node.nodeType == node.TEXT_NODE:
835 def getBoolAttr(el, attr, default = False):
837 val = el.getAttribute(attr).lower()
838 if val in ['true', 'false', 'yes', 'no']:
839 ret = val in ['true', 'yes']
842 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
843 if not caps.has_key(dir):
844 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
846 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
847 assert xmldoc.documentElement.tagName == "capability"
849 pii, min_size, max_size, min_time, max_time, mime = \
850 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
852 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
853 pii = xmldoc.documentElement.getAttribute("pii")
854 if xmldoc.documentElement.getAttribute("min_size") != '':
855 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
856 if xmldoc.documentElement.getAttribute("max_size") != '':
857 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
858 if xmldoc.documentElement.getAttribute("min_time") != '':
859 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
860 if xmldoc.documentElement.getAttribute("max_time") != '':
861 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
862 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
863 mime = xmldoc.documentElement.getAttribute("mime")
864 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
865 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
867 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
869 if just_capabilities:
872 plugdir = os.path.join(PLUGIN_DIR, dir)
873 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
874 xmldoc = parse(os.path.join(plugdir, file))
875 assert xmldoc.documentElement.tagName == "collect"
877 for el in xmldoc.documentElement.getElementsByTagName("*"):
878 if el.tagName == "files":
879 file_output(dir, getText(el.childNodes).split())
880 elif el.tagName == "directory":
881 pattern = el.getAttribute("pattern")
882 if pattern == '': pattern = None
883 negate = getBoolAttr(el, 'negate')
884 tree_output(dir, getText(el.childNodes), pattern and re.compile(pattern) or None, negate)
885 elif el.tagName == "command":
886 label = el.getAttribute("label")
887 if label == '': label = None
888 cmd_output(dir, getText(el.childNodes), label)
890 def make_tar(subdir, suffix, output_fd, output_file):
891 global SILENT_MODE, data
894 if suffix == 'tar.bz2':
896 elif suffix == 'tar.gz':
900 if output_file is None:
901 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
903 filename = output_file
904 old_umask = os.umask(0077)
905 tf = tarfile.open(filename, mode)
908 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
911 for (k, v) in data.items():
913 tar_filename = os.path.join(subdir, construct_filename(k, v))
914 ti = tarfile.TarInfo(tar_filename)
919 if v.has_key('output'):
920 ti.mtime = v['output'].mtime
921 ti.size = len(v['output'].getvalue())
923 tf.addfile(ti, v['output'])
924 elif v.has_key('filename'):
925 s = os.stat(v['filename'])
926 ti.mtime = s.st_mtime
928 tf.addfile(ti, file(v['filename']))
935 output ('Writing tarball %s successful.' % filename)
940 def make_zip(subdir, output_file):
941 global SILENT_MODE, data
943 if output_file is None:
944 filename = "%s/%s.zip" % (BUG_DIR, subdir)
946 filename = output_file
947 old_umask = os.umask(0077)
948 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
952 for (k, v) in data.items():
954 dest = os.path.join(subdir, construct_filename(k, v))
956 if v.has_key('output'):
957 zf.writestr(dest, v['output'].getvalue())
959 if os.stat(v['filename']).st_size < 50:
960 compress_type = zipfile.ZIP_STORED
962 compress_type = zipfile.ZIP_DEFLATED
963 zf.write(v['filename'], dest, compress_type)
969 output ('Writing archive %s successful.' % filename)
974 def make_inventory(inventory, subdir):
975 document = getDOMImplementation().createDocument(
976 None, INVENTORY_XML_ROOT, None)
978 # create summary entry
979 s = document.createElement(INVENTORY_XML_SUMMARY)
980 user = os.getenv('SUDO_USER', os.getenv('USER'))
982 s.setAttribute('user', user)
983 s.setAttribute('date', time.strftime('%c'))
984 s.setAttribute('hostname', platform.node())
985 s.setAttribute('uname', ' '.join(platform.uname()))
986 s.setAttribute('uptime', commands.getoutput(UPTIME))
987 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
989 map(lambda (k, v): inventory_entry(document, subdir, k, v),
991 return document.toprettyxml()
993 def inventory_entry(document, subdir, k, v):
995 el = document.createElement(INVENTORY_XML_ELEMENT)
996 el.setAttribute('capability', v['cap'])
997 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
998 el.setAttribute('md5sum', md5sum(v))
999 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
1006 if d.has_key('filename'):
1007 f = open(d['filename'])
1009 while len(data) > 0:
1013 elif d.has_key('output'):
1014 m.update(d['output'].getvalue())
1015 return m.hexdigest()
1018 def construct_filename(k, v):
1019 if v.has_key('filename'):
1020 if v['filename'][0] == '/':
1021 return v['filename'][1:]
1023 return v['filename']
1024 s = k.replace(' ', '-')
1025 s = s.replace('--', '-')
1026 s = s.replace('/', '%')
1027 if s.find('.') == -1:
1032 def update_capabilities():
1035 def update_cap_size(cap, size):
1036 update_cap(cap, MIN_SIZE, size)
1037 update_cap(cap, MAX_SIZE, size)
1038 update_cap(cap, CHECKED, size > 0)
1041 def update_cap(cap, k, v):
1045 caps[cap] = tuple(l)
1048 def size_of_dir(d, pattern = None, negate = False):
1049 if os.path.isdir(d):
1050 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1056 def size_of_all(files, pattern = None, negate = False):
1057 return sum([size_of(f, pattern, negate) for f in files])
1060 def matches(f, pattern, negate):
1062 return not matches(f, pattern, False)
1064 return pattern is None or pattern.match(f)
1067 def size_of(f, pattern, negate):
1068 if os.path.isfile(f) and matches(f, pattern, negate):
1069 return os.stat(f)[6]
1071 return size_of_dir(f, pattern, negate)
1074 def print_capabilities():
1075 document = getDOMImplementation().createDocument(
1076 "ns", CAP_XML_ROOT, None)
1077 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1078 print document.toprettyxml()
1080 def capability(document, key):
1082 el = document.createElement(CAP_XML_ELEMENT)
1083 el.setAttribute('key', c[KEY])
1084 el.setAttribute('pii', c[PII])
1085 el.setAttribute('min-size', str(c[MIN_SIZE]))
1086 el.setAttribute('max-size', str(c[MAX_SIZE]))
1087 el.setAttribute('min-time', str(c[MIN_TIME]))
1088 el.setAttribute('max-time', str(c[MAX_TIME]))
1089 el.setAttribute('content-type', c[MIME])
1090 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1091 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1095 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1096 return '\n'.join([format % i for i in d.items()]) + '\n'
1100 yn = raw_input(prompt)
1102 return len(yn) == 0 or yn.lower()[0] == 'y'
1105 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1110 f = open('/proc/partitions')
1113 for line in f.readlines():
1114 (major, minor, blocks, name) = line.split()
1115 if int(major) < 254 and not partition_re.match(name):
1126 def __init__(self, command, max_time, inst=None, filter=None):
1127 self.command = command
1128 self.max_time = max_time
1130 self.running = False
1132 self.timed_out = False
1134 self.timeout = int(time.time()) + self.max_time
1135 self.filter = filter
1136 self.filter_state = {}
1142 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1145 self.timed_out = False
1147 if ProcOutput.debug:
1148 output_ts("Starting '%s'" % self.cmdAsStr())
1149 self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1150 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1151 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1155 output_ts("'%s' failed" % self.cmdAsStr())
1156 self.running = False
1159 def terminate(self):
1162 os.kill(self.proc.pid, SIGTERM)
1166 self.running = False
1167 self.status = SIGTERM
1169 def read_line(self):
1171 line = self.proc.stdout.readline()
1174 self.status = self.proc.wait()
1176 self.running = False
1179 line = self.filter(line, self.filter_state)
1181 self.inst.write(line)
1183 def run_procs(procs):
1191 active_procs.append(p)
1192 pipes.append(p.proc.stdout)
1194 elif p.status == None and not p.failed and not p.timed_out:
1197 active_procs.append(p)
1198 pipes.append(p.proc.stdout)
1205 (i, o, x) = select(pipes, [], [], 1.0)
1206 now = int(time.time())
1208 # handle process output
1209 for p in active_procs:
1210 if p.proc.stdout in i:
1214 if p.running and now > p.timeout:
1215 output_ts("'%s' timed out" % p.cmdAsStr())
1217 p.inst.write("\n** timeout **\n")
1225 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1227 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1235 class StringIOmtime(StringIO.StringIO):
1236 def __init__(self, buf = ''):
1237 StringIO.StringIO.__init__(self, buf)
1238 self.mtime = time.time()
1241 StringIO.StringIO.write(self, s)
1242 self.mtime = time.time()
1245 if __name__ == "__main__":
1248 except KeyboardInterrupt:
1249 print "\nInterrupted."