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 sys.path.append('/usr/lib/python')
62 sys.path.append('/usr/lib64/python')
64 OS_RELEASE = platform.release()
70 APT_SOURCES_LIST = "/etc/apt/sources.list"
71 APT_SOURCES_LIST_D = "/etc/apt/sources.list.d"
72 BUG_DIR = "/var/log/ovs-bugtool"
73 PLUGIN_DIR = "/etc/openvswitch/bugtool"
74 GRUB_CONFIG = '/boot/grub/menu.lst'
75 BOOT_KERNEL = '/boot/vmlinuz-' + OS_RELEASE
76 BOOT_INITRD = '/boot/initrd-' + OS_RELEASE + '.img'
77 PROC_PARTITIONS = '/proc/partitions'
79 PROC_MOUNTS = '/proc/mounts'
80 ISCSI_CONF = '/etc/iscsi/iscsid.conf'
81 ISCSI_INITIATOR = '/etc/iscsi/initiatorname.iscsi'
82 LVM_CACHE = '/etc/lvm/cache/.cache'
83 LVM_CONFIG = '/etc/lvm/lvm.conf'
84 PROC_CPUINFO = '/proc/cpuinfo'
85 PROC_MEMINFO = '/proc/meminfo'
86 PROC_IOPORTS = '/proc/ioports'
87 PROC_INTERRUPTS = '/proc/interrupts'
88 PROC_SCSI = '/proc/scsi/scsi'
89 PROC_VERSION = '/proc/version'
90 PROC_MODULES = '/proc/modules'
91 PROC_DEVICES = '/proc/devices'
92 PROC_FILESYSTEMS = '/proc/filesystems'
93 PROC_CMDLINE = '/proc/cmdline'
94 PROC_CONFIG = '/proc/config.gz'
95 PROC_USB_DEV = '/proc/bus/usb/devices'
96 PROC_XEN_BALLOON = '/proc/xen/balloon'
97 PROC_NET_BONDING_DIR = '/proc/net/bonding'
98 IFCFG_RE = re.compile(r'^.*/ifcfg-.*')
99 ROUTE_RE = re.compile(r'^.*/route-.*')
100 SYSCONFIG_HWCONF = '/etc/sysconfig/hwconf'
101 SYSCONFIG_NETWORK = '/etc/sysconfig/network'
102 SYSCONFIG_NETWORK_SCRIPTS = '/etc/sysconfig/network-scripts'
103 PROC_NET_VLAN_DIR = '/proc/net/vlan'
104 PROC_NET_SOFTNET_STAT = '/proc/net/softnet_stat'
105 MODPROBE_CONF = '/etc/modprobe.conf'
106 MODPROBE_DIR = '/etc/modprobe.d'
107 RESOLV_CONF = '/etc/resolv.conf'
108 MPP_CONF = '/etc/mpp.conf'
109 MULTIPATH_CONF = '/etc/multipath.conf'
110 NSSWITCH_CONF = '/etc/nsswitch.conf'
111 NTP_CONF = '/etc/ntp.conf'
112 IPTABLES_CONFIG = '/etc/sysconfig/iptables-config'
114 HOSTS_ALLOW = '/etc/hosts.allow'
115 HOSTS_DENY = '/etc/hosts.deny'
116 DHCP_LEASE_DIR = ['/var/lib/dhclient', '/var/lib/dhcp3']
117 OPENVSWITCH_LOG_DIR = '/var/log/openvswitch'
118 OPENVSWITCH_DEFAULT_SWITCH = '/etc/default/openvswitch-switch' # Debian
119 OPENVSWITCH_SYSCONFIG_SWITCH = '/etc/sysconfig/openvswitch' # RHEL
120 OPENVSWITCH_DEFAULT_CONTROLLER = '/etc/default/openvswitch-controller'
121 OPENVSWITCH_CONF_DB = '/etc/openvswitch/conf.db'
122 OPENVSWITCH_VSWITCHD_PID = '/var/run/openvswitch/ovs-vswitchd.pid'
123 COLLECTD_LOGS_DIR = '/var/lib/collectd/rrd'
124 VAR_LOG_DIR = '/var/log/'
125 VAR_LOG_CORE_DIR = '/var/log/core'
126 X11_LOGS_DIR = VAR_LOG_DIR
127 X11_LOGS_RE = re.compile(r'.*/Xorg\..*$')
128 X11_AUTH_DIR = '/root/'
129 X11_AUTH_RE = re.compile(r'.*/\.((Xauthority)|(serverauth\.[0-9]*))$')
130 YUM_LOG = '/var/log/yum.log'
131 YUM_REPOS_DIR = '/etc/yum.repos.d'
132 PAM_DIR = '/etc/pam.d'
133 KRB5_CONF = '/etc/krb5.conf'
139 os.environ['PATH'] = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
143 CHKCONFIG = 'chkconfig'
146 DMIDECODE = 'dmidecode'
148 DPKG_QUERY = 'dpkg-query'
153 IFCONFIG = 'ifconfig'
154 IPTABLES = 'iptables'
155 ISCSIADM = 'iscsiadm'
159 LVDISPLAY = 'lvdisplay'
164 MULTIPATHD = 'multipathd'
166 OVS_DPCTL = 'ovs-dpctl'
167 OVS_OFCTL = 'ovs-ofctl'
168 OVS_VSCTL = 'ovs-vsctl'
169 OVS_APPCTL = 'ovs-appctl'
183 # PII -- Personally identifiable information. Of particular concern are
184 # things that would identify customers, or their network topology.
185 # Passwords are never to be included in any bug report, regardless of any PII
188 # NO -- No PII will be in these entries.
189 # YES -- PII will likely or certainly be in these entries.
190 # MAYBE -- The user may wish to audit these entries for PII.
191 # IF_CUSTOMIZED -- If the files are unmodified, then they will contain no PII,
192 # but since we encourage customers to edit these files, PII may have been
193 # introduced by the customer. This is used in particular for the networking
200 PII_IF_CUSTOMIZED = 'if_customized'
211 MIME_DATA = 'application/data'
212 MIME_TEXT = 'text/plain'
214 INVENTORY_XML_ROOT = "system-status-inventory"
215 INVENTORY_XML_SUMMARY = 'system-summary'
216 INVENTORY_XML_ELEMENT = 'inventory-entry'
217 CAP_XML_ROOT = "system-status-capabilities"
218 CAP_XML_ELEMENT = 'capability'
222 CAP_BOOT_LOADER = 'boot-loader'
223 CAP_COLLECTD_LOGS = 'collectd-logs'
224 CAP_DISK_INFO = 'disk-info'
225 CAP_FIRSTBOOT = 'firstboot'
226 CAP_HARDWARE_INFO = 'hardware-info'
227 CAP_HDPARM_T = 'hdparm-t'
228 CAP_HIGH_AVAILABILITY = 'high-availability'
229 CAP_KERNEL_INFO = 'kernel-info'
230 CAP_LOSETUP_A = 'loopback-devices'
231 CAP_MULTIPATH = 'multipath'
232 CAP_NETWORK_CONFIG = 'network-config'
233 CAP_NETWORK_STATUS = 'network-status'
236 CAP_PROCESS_LIST = 'process-list'
237 CAP_PERSISTENT_STATS = 'persistent-stats'
238 CAP_SYSTEM_LOGS = 'system-logs'
239 CAP_SYSTEM_SERVICES = 'system-services'
240 CAP_VNCTERM = 'vncterm'
243 CAP_X11_AUTH = 'X11-auth'
251 unlimited_data = False
254 def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
255 max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
256 caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
261 cap(CAP_BLOBS, PII_NO, max_size=5*MB)
262 cap(CAP_BOOT_LOADER, PII_NO, max_size=3*KB,
264 cap(CAP_COLLECTD_LOGS, PII_MAYBE, max_size=50*MB,
266 cap(CAP_DISK_INFO, PII_MAYBE, max_size=50*KB,
268 cap(CAP_FIRSTBOOT, PII_YES, min_size=60*KB, max_size=80*KB)
269 cap(CAP_HARDWARE_INFO, PII_MAYBE, max_size=30*KB,
271 cap(CAP_HDPARM_T, PII_NO, min_size=0, max_size=5*KB,
272 min_time=20, max_time=90, checked=False, hidden=True)
273 cap(CAP_HIGH_AVAILABILITY, PII_MAYBE, max_size=5*MB)
274 cap(CAP_KERNEL_INFO, PII_MAYBE, max_size=120*KB,
276 cap(CAP_LOSETUP_A, PII_MAYBE, max_size=KB, max_time=5)
277 cap(CAP_MULTIPATH, PII_MAYBE, max_size=20*KB,
279 cap(CAP_NETWORK_CONFIG, PII_IF_CUSTOMIZED,
280 min_size=0, max_size=40*KB)
281 cap(CAP_NETWORK_STATUS, PII_YES, max_size=50*KB,
283 cap(CAP_PAM, PII_NO, max_size=50*KB)
284 cap(CAP_PERSISTENT_STATS, PII_MAYBE, max_size=50*MB,
286 cap(CAP_PROCESS_LIST, PII_YES, max_size=30*KB,
288 cap(CAP_SYSTEM_LOGS, PII_MAYBE, max_size=50*MB,
290 cap(CAP_SYSTEM_SERVICES, PII_NO, max_size=5*KB,
292 cap(CAP_VNCTERM, PII_MAYBE, checked = False)
293 cap(CAP_WLB, PII_NO, max_size=3*MB,
295 cap(CAP_X11_LOGS, PII_NO, max_size=100*KB)
296 cap(CAP_X11_AUTH, PII_NO, max_size=100*KB)
297 cap(CAP_YUM, PII_IF_CUSTOMIZED, max_size=10*KB,
300 ANSWER_YES_TO_ALL = False
304 dev_null = open('/dev/null', 'r+')
312 output("[%s] %s" % (time.strftime("%x %X %Z"), x))
314 def cmd_output(cap, args, label = None, filter = None):
317 if isinstance(args, list):
318 a = [aa for aa in args]
319 a[0] = os.path.basename(a[0])
323 data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter}
325 def file_output(cap, path_list):
328 if os.path.exists(p):
329 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
330 cap_sizes[cap] < caps[cap][MAX_SIZE]:
331 data[p] = {'cap': cap, 'filename': p}
334 cap_sizes[cap] += s.st_size
338 output("Omitting %s, size constraint of %s exceeded" % (p, cap))
340 def tree_output(cap, path, pattern = None, negate = False):
342 if os.path.exists(path):
343 for f in os.listdir(path):
344 fn = os.path.join(path, f)
345 if os.path.isfile(fn) and matches(fn, pattern, negate):
346 file_output(cap, [fn])
347 elif os.path.isdir(fn):
348 tree_output(cap, fn, pattern, negate)
350 def func_output(cap, label, func):
352 t = str(func).split()
353 data[label] = {'cap': cap, 'func': func}
358 for (k, v) in data.items():
360 if v.has_key('cmd_args'):
361 v['output'] = StringIOmtime()
362 if not process_lists.has_key(cap):
363 process_lists[cap] = []
364 process_lists[cap].append(ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'], v['filter']))
365 elif v.has_key('filename') and v['filename'].startswith('/proc/'):
366 # proc files must be read into memory
368 f = open(v['filename'], 'r')
371 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
372 cap_sizes[cap] < caps[cap][MAX_SIZE]:
373 v['output'] = StringIOmtime(s)
374 cap_sizes[cap] += len(s)
376 output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
379 elif v.has_key('func'):
384 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
385 cap_sizes[cap] < caps[cap][MAX_SIZE]:
386 v['output'] = StringIOmtime(s)
387 cap_sizes[cap] += len(s)
389 output("Omitting %s, size constraint of %s exceeded" % (k, cap))
391 run_procs(process_lists.values())
394 def main(argv = None):
395 global ANSWER_YES_TO_ALL, SILENT_MODE
396 global entries, data, dbg
398 # we need access to privileged files, exit if we are not running as root
400 print >>sys.stderr, "Error: ovs-bugtool must be run as root"
404 output_type = 'tar.bz2'
411 (options, params) = getopt.gnu_getopt(
412 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
413 'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
415 except getopt.GetoptError, opterr:
416 print >>sys.stderr, opterr
424 entries = [e for e in caps.keys() if caps[e][CHECKED]]
426 for (k, v) in options:
427 if k == '--capabilities':
428 update_capabilities()
433 if v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
436 print >>sys.stderr, "Invalid output format '%s'" % v
439 # "-s" or "--silent" means suppress output (except for the final
440 # output filename at the end)
441 if k in ['-s', '--silent']:
444 if k == '--entries' and v != '':
445 entries = v.split(',')
447 # If the user runs the script with "-y" or "--yestoall" we don't ask
448 # all the really annoying questions.
449 if k in ['-y', '--yestoall']:
450 ANSWER_YES_TO_ALL = True
455 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
456 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
458 print >>sys.stderr, "Invalid output file descriptor", output_fd
465 entries = caps.keys()
466 elif k == '--unlimited':
467 unlimited_data = True
470 ProcOutput.debug = True
473 print >>sys.stderr, "Invalid additional arguments", str(params)
476 if output_fd != -1 and output_type != 'tar':
477 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
480 if output_fd != -1 and output_file is not None:
481 print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
484 if ANSWER_YES_TO_ALL:
485 output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
488 This application will collate dmesg output, details of the
489 hardware configuration of your machine, information about the build of
490 openvswitch that you are using, plus, if you allow it, various logs.
492 The collated information will be saved as a .%s for archiving or
493 sending to a Technical Support Representative.
495 The logs may contain private information, and if you are at all
496 worried about that, you should exit now, or you should explicitly
497 exclude those logs from the archive.
501 # assemble potential data
503 file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
504 cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
505 cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
507 tree_output(CAP_COLLECTD_LOGS, COLLECTD_LOGS_DIR)
508 cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
509 file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
510 file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
511 cmd_output(CAP_DISK_INFO, [DF, '-alT'])
512 cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
513 for d in disk_list():
514 cmd_output(CAP_DISK_INFO, [HDPARM, '-I', '/dev/%s' % d])
515 if len(pidof('iscsid')) != 0:
516 cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
517 cmd_output(CAP_DISK_INFO, [VGSCAN])
518 cmd_output(CAP_DISK_INFO, [PVS])
519 cmd_output(CAP_DISK_INFO, [VGS])
520 cmd_output(CAP_DISK_INFO, [LVS])
521 file_output(CAP_DISK_INFO, [LVM_CACHE, LVM_CONFIG])
522 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
523 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
524 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
525 cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
526 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
527 cmd_output(CAP_DISK_INFO, [LVDISPLAY, '--map'])
529 file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
530 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
531 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
532 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
533 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
534 file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
535 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
538 for d in disk_list():
539 cmd_output(CAP_HDPARM_T, [HDPARM, '-tT', '/dev/%s' % d])
541 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
542 PROC_FILESYSTEMS, PROC_CMDLINE])
543 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
544 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
545 file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
546 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
547 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
549 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
551 file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
552 cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
553 func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
554 cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
555 if CAP_MULTIPATH in entries:
556 dump_rdac_groups(CAP_MULTIPATH)
558 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
559 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
560 file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
561 file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
562 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_CONF_DB])
564 cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
565 cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
566 cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
567 cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
568 for dir in DHCP_LEASE_DIR:
569 tree_output(CAP_NETWORK_STATUS, dir)
570 cmd_output(CAP_NETWORK_STATUS, [BRCTL, 'show'])
571 cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
572 for p in os.listdir('/sys/class/net/'):
574 f = open('/sys/class/net/%s/type' % p, 'r')
579 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
580 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
581 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
582 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
583 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
584 cmd_output(CAP_NETWORK_STATUS,
585 [TC, '-s', '-d', 'class', 'show', 'dev', p])
588 tree_output(CAP_NETWORK_STATUS, PROC_NET_BONDING_DIR)
589 tree_output(CAP_NETWORK_STATUS, PROC_NET_VLAN_DIR)
590 cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
591 file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
592 tree_output(CAP_NETWORK_STATUS, OPENVSWITCH_LOG_DIR)
593 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
594 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show'])
596 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
597 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
598 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
600 vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
601 vspid = int(vspidfile.readline().strip())
603 for b in bond_list(vspid):
604 cmd_output(CAP_NETWORK_STATUS,
605 [OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
606 'ovs-appctl-bond-show-%s.out' % b)
610 tree_output(CAP_PAM, PAM_DIR)
611 file_output(CAP_PAM, [KRB5_CONF])
613 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
614 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
616 file_output(CAP_SYSTEM_LOGS,
617 [ VAR_LOG_DIR + x for x in
618 [ 'crit.log', 'kern.log', 'daemon.log', 'user.log', 'syslog',
619 'messages', 'secure', 'debug', 'dmesg', 'boot'] +
620 [ f % n for n in range(1, 20) \
621 for f in ['crit.log.%d', 'crit.log.%d.gz',
622 'kern.log.%d', 'kern.log.%d.gz',
623 'daemon.log.%d', 'daemon.log.%d.gz',
624 'user.log.%d', 'user.log.%d.gz',
625 'messages.%d', 'messages.%d.gz',
626 'syslog.%d', 'syslog.%d.gz']]])
627 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
628 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
630 cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
632 tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
633 tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
634 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
636 file_output(CAP_YUM, [YUM_LOG])
637 tree_output(CAP_YUM, YUM_REPOS_DIR)
638 cmd_output(CAP_YUM, [RPM, '-qa'])
639 file_output(CAP_YUM, [APT_SOURCES_LIST])
640 tree_output(CAP_YUM, APT_SOURCES_LIST_D)
641 cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
648 # permit the user to filter out data
649 for k in sorted(data.keys()):
650 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % k):
653 # collect selected data now
654 output_ts('Running commands to collect data')
657 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
660 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
664 if output_file is None:
667 dirname = os.path.dirname(output_file)
668 if dirname and not os.path.exists(dirname):
675 output_ts('Creating output file')
677 if output_type.startswith('tar'):
678 make_tar(subdir, output_type, output_fd, output_file)
680 make_zip(subdir, output_file)
685 print >>sys.stderr, "Category sizes (max, actual):\n"
686 for c in caps.keys():
687 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
691 def find_tapdisk_logs():
692 return glob.glob('/var/log/blktap/*.log*')
694 def generate_tapdisk_logs():
695 for pid in pidof('tapdisk'):
697 os.kill(pid, SIGUSR1)
698 output_ts("Including logs for tapdisk process %d" % pid)
701 # give processes a second to write their logs
704 def clean_tapdisk_logs():
705 for filename in find_tapdisk_logs():
711 def filter_db_pii(str, state):
712 if 'in_secret_table' not in state:
713 state['in_secret_table'] = False
715 if str.startswith('<table ') and 'name="secret"' in str:
716 state['in_secret_table'] = True
717 elif str.startswith('</table>'):
718 state['in_secret_table'] = False
720 if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
721 str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
724 def dump_scsi_hosts(cap):
726 l = os.listdir('/sys/class/scsi_host')
732 f = open('/sys/class/scsi_host/%s/proc_name' % h)
733 procname = f.readline().strip("\n")
739 f = open('/sys/class/scsi_host/%s/model_name' % h)
740 modelname = f.readline().strip("\n")
746 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
750 def module_info(cap):
751 output = StringIO.StringIO()
752 modules = open(PROC_MODULES, 'r')
756 module = line.split()[0]
757 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
762 return output.getvalue()
765 def multipathd_topology(cap):
766 pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
767 stdout=PIPE, stderr=dev_null)
768 stdout, stderr = pipe.communicate('show topology')
773 output = StringIO.StringIO()
774 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
778 if not procs[0].timed_out:
779 return output.getvalue().splitlines()
783 output = StringIO.StringIO()
784 procs = [ProcOutput([OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
788 if not procs[0].timed_out:
789 bonds = output.getvalue().splitlines()[1:]
790 return [x.split('\t')[1] for x in bonds]
796 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
798 fh = open('/proc/'+d+'/cmdline')
800 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
802 if not num_fds in fd_dict:
803 fd_dict[num_fds] = []
804 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
807 keys = fd_dict.keys()
808 keys.sort(lambda a, b: int(b) - int(a))
810 output += "%s: %s\n" % (k, str(fd_dict[k]))
813 def dump_rdac_groups(cap):
814 output = StringIO.StringIO()
815 procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
819 if not procs[0].timed_out:
821 for line in output.getvalue().splitlines():
822 if line.startswith('ID'):
824 elif line.startswith('----'):
827 group, _ = line.split(None, 1)
828 cmd_output(cap, [MPPUTIL, '-g', group])
830 def load_plugins(just_capabilities = False):
831 def getText(nodelist):
833 for node in nodelist:
834 if node.nodeType == node.TEXT_NODE:
838 def getBoolAttr(el, attr, default = False):
840 val = el.getAttribute(attr).lower()
841 if val in ['true', 'false', 'yes', 'no']:
842 ret = val in ['true', 'yes']
845 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
846 if not caps.has_key(dir):
847 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
849 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
850 assert xmldoc.documentElement.tagName == "capability"
852 pii, min_size, max_size, min_time, max_time, mime = \
853 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
855 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
856 pii = xmldoc.documentElement.getAttribute("pii")
857 if xmldoc.documentElement.getAttribute("min_size") != '':
858 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
859 if xmldoc.documentElement.getAttribute("max_size") != '':
860 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
861 if xmldoc.documentElement.getAttribute("min_time") != '':
862 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
863 if xmldoc.documentElement.getAttribute("max_time") != '':
864 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
865 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
866 mime = xmldoc.documentElement.getAttribute("mime")
867 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
868 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
870 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
872 if just_capabilities:
875 plugdir = os.path.join(PLUGIN_DIR, dir)
876 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
877 xmldoc = parse(os.path.join(plugdir, file))
878 assert xmldoc.documentElement.tagName == "collect"
880 for el in xmldoc.documentElement.getElementsByTagName("*"):
881 if el.tagName == "files":
882 file_output(dir, getText(el.childNodes).split())
883 elif el.tagName == "directory":
884 pattern = el.getAttribute("pattern")
885 if pattern == '': pattern = None
886 negate = getBoolAttr(el, 'negate')
887 tree_output(dir, getText(el.childNodes), pattern and re.compile(pattern) or None, negate)
888 elif el.tagName == "command":
889 label = el.getAttribute("label")
890 if label == '': label = None
891 cmd_output(dir, getText(el.childNodes), label)
893 def make_tar(subdir, suffix, output_fd, output_file):
894 global SILENT_MODE, data
897 if suffix == 'tar.bz2':
899 elif suffix == 'tar.gz':
903 if output_file is None:
904 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
906 filename = output_file
907 old_umask = os.umask(0077)
908 tf = tarfile.open(filename, mode)
911 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
914 for (k, v) in data.items():
916 tar_filename = os.path.join(subdir, construct_filename(k, v))
917 ti = tarfile.TarInfo(tar_filename)
922 if v.has_key('output'):
923 ti.mtime = v['output'].mtime
924 ti.size = len(v['output'].getvalue())
926 tf.addfile(ti, v['output'])
927 elif v.has_key('filename'):
928 s = os.stat(v['filename'])
929 ti.mtime = s.st_mtime
931 tf.addfile(ti, file(v['filename']))
938 output ('Writing tarball %s successful.' % filename)
943 def make_zip(subdir, output_file):
944 global SILENT_MODE, data
946 if output_file is None:
947 filename = "%s/%s.zip" % (BUG_DIR, subdir)
949 filename = output_file
950 old_umask = os.umask(0077)
951 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
955 for (k, v) in data.items():
957 dest = os.path.join(subdir, construct_filename(k, v))
959 if v.has_key('output'):
960 zf.writestr(dest, v['output'].getvalue())
962 if os.stat(v['filename']).st_size < 50:
963 compress_type = zipfile.ZIP_STORED
965 compress_type = zipfile.ZIP_DEFLATED
966 zf.write(v['filename'], dest, compress_type)
972 output ('Writing archive %s successful.' % filename)
977 def make_inventory(inventory, subdir):
978 document = getDOMImplementation().createDocument(
979 None, INVENTORY_XML_ROOT, None)
981 # create summary entry
982 s = document.createElement(INVENTORY_XML_SUMMARY)
983 user = os.getenv('SUDO_USER', os.getenv('USER'))
985 s.setAttribute('user', user)
986 s.setAttribute('date', time.strftime('%c'))
987 s.setAttribute('hostname', platform.node())
988 s.setAttribute('uname', ' '.join(platform.uname()))
989 s.setAttribute('uptime', commands.getoutput(UPTIME))
990 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
992 map(lambda (k, v): inventory_entry(document, subdir, k, v),
994 return document.toprettyxml()
996 def inventory_entry(document, subdir, k, v):
998 el = document.createElement(INVENTORY_XML_ELEMENT)
999 el.setAttribute('capability', v['cap'])
1000 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
1001 el.setAttribute('md5sum', md5sum(v))
1002 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
1009 if d.has_key('filename'):
1010 f = open(d['filename'])
1012 while len(data) > 0:
1016 elif d.has_key('output'):
1017 m.update(d['output'].getvalue())
1018 return m.hexdigest()
1021 def construct_filename(k, v):
1022 if v.has_key('filename'):
1023 if v['filename'][0] == '/':
1024 return v['filename'][1:]
1026 return v['filename']
1027 s = k.replace(' ', '-')
1028 s = s.replace('--', '-')
1029 s = s.replace('/', '%')
1030 if s.find('.') == -1:
1035 def update_capabilities():
1038 def update_cap_size(cap, size):
1039 update_cap(cap, MIN_SIZE, size)
1040 update_cap(cap, MAX_SIZE, size)
1041 update_cap(cap, CHECKED, size > 0)
1044 def update_cap(cap, k, v):
1048 caps[cap] = tuple(l)
1051 def size_of_dir(d, pattern = None, negate = False):
1052 if os.path.isdir(d):
1053 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1059 def size_of_all(files, pattern = None, negate = False):
1060 return sum([size_of(f, pattern, negate) for f in files])
1063 def matches(f, pattern, negate):
1065 return not matches(f, pattern, False)
1067 return pattern is None or pattern.match(f)
1070 def size_of(f, pattern, negate):
1071 if os.path.isfile(f) and matches(f, pattern, negate):
1072 return os.stat(f)[6]
1074 return size_of_dir(f, pattern, negate)
1077 def print_capabilities():
1078 document = getDOMImplementation().createDocument(
1079 "ns", CAP_XML_ROOT, None)
1080 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1081 print document.toprettyxml()
1083 def capability(document, key):
1085 el = document.createElement(CAP_XML_ELEMENT)
1086 el.setAttribute('key', c[KEY])
1087 el.setAttribute('pii', c[PII])
1088 el.setAttribute('min-size', str(c[MIN_SIZE]))
1089 el.setAttribute('max-size', str(c[MAX_SIZE]))
1090 el.setAttribute('min-time', str(c[MIN_TIME]))
1091 el.setAttribute('max-time', str(c[MAX_TIME]))
1092 el.setAttribute('content-type', c[MIME])
1093 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1094 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1098 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1099 return '\n'.join([format % i for i in d.items()]) + '\n'
1103 yn = raw_input(prompt)
1105 return len(yn) == 0 or yn.lower()[0] == 'y'
1108 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1113 f = open('/proc/partitions')
1116 for line in f.readlines():
1117 (major, minor, blocks, name) = line.split()
1118 if int(major) < 254 and not partition_re.match(name):
1129 def __init__(self, command, max_time, inst=None, filter=None):
1130 self.command = command
1131 self.max_time = max_time
1133 self.running = False
1135 self.timed_out = False
1137 self.timeout = int(time.time()) + self.max_time
1138 self.filter = filter
1139 self.filter_state = {}
1145 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1148 self.timed_out = False
1150 if ProcOutput.debug:
1151 output_ts("Starting '%s'" % self.cmdAsStr())
1152 self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1153 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1154 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1158 output_ts("'%s' failed" % self.cmdAsStr())
1159 self.running = False
1162 def terminate(self):
1165 os.kill(self.proc.pid, SIGTERM)
1169 self.running = False
1170 self.status = SIGTERM
1172 def read_line(self):
1174 line = self.proc.stdout.readline()
1177 self.status = self.proc.wait()
1179 self.running = False
1182 line = self.filter(line, self.filter_state)
1184 self.inst.write(line)
1186 def run_procs(procs):
1194 active_procs.append(p)
1195 pipes.append(p.proc.stdout)
1197 elif p.status == None and not p.failed and not p.timed_out:
1200 active_procs.append(p)
1201 pipes.append(p.proc.stdout)
1208 (i, o, x) = select(pipes, [], [], 1.0)
1209 now = int(time.time())
1211 # handle process output
1212 for p in active_procs:
1213 if p.proc.stdout in i:
1217 if p.running and now > p.timeout:
1218 output_ts("'%s' timed out" % p.cmdAsStr())
1220 p.inst.write("\n** timeout **\n")
1228 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1230 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1238 class StringIOmtime(StringIO.StringIO):
1239 def __init__(self, buf = ''):
1240 StringIO.StringIO.__init__(self, buf)
1241 self.mtime = time.time()
1244 StringIO.StringIO.write(self, s)
1245 self.mtime = time.time()
1248 if __name__ == "__main__":
1251 except KeyboardInterrupt:
1252 print "\nInterrupted."