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 BUG_DIR = "/var/log/ovs-bugtool"
71 PLUGIN_DIR = "/etc/openvswitch/bugtool"
72 GRUB_CONFIG = '/boot/grub/menu.lst'
73 BOOT_KERNEL = '/boot/vmlinuz-' + OS_RELEASE
74 BOOT_INITRD = '/boot/initrd-' + OS_RELEASE + '.img'
75 PROC_PARTITIONS = '/proc/partitions'
77 PROC_MOUNTS = '/proc/mounts'
78 ISCSI_CONF = '/etc/iscsi/iscsid.conf'
79 ISCSI_INITIATOR = '/etc/iscsi/initiatorname.iscsi'
80 LVM_CACHE = '/etc/lvm/cache/.cache'
81 LVM_CONFIG = '/etc/lvm/lvm.conf'
82 PROC_CPUINFO = '/proc/cpuinfo'
83 PROC_MEMINFO = '/proc/meminfo'
84 PROC_IOPORTS = '/proc/ioports'
85 PROC_INTERRUPTS = '/proc/interrupts'
86 PROC_SCSI = '/proc/scsi/scsi'
87 PROC_VERSION = '/proc/version'
88 PROC_MODULES = '/proc/modules'
89 PROC_DEVICES = '/proc/devices'
90 PROC_FILESYSTEMS = '/proc/filesystems'
91 PROC_CMDLINE = '/proc/cmdline'
92 PROC_CONFIG = '/proc/config.gz'
93 PROC_USB_DEV = '/proc/bus/usb/devices'
94 PROC_XEN_BALLOON = '/proc/xen/balloon'
95 PROC_NET_BONDING_DIR = '/proc/net/bonding'
96 IFCFG_RE = re.compile(r'^.*/ifcfg-.*')
97 ROUTE_RE = re.compile(r'^.*/route-.*')
98 SYSCONFIG_HWCONF = '/etc/sysconfig/hwconf'
99 SYSCONFIG_NETWORK = '/etc/sysconfig/network'
100 SYSCONFIG_NETWORK_SCRIPTS = '/etc/sysconfig/network-scripts'
101 PROC_NET_VLAN_DIR = '/proc/net/vlan'
102 PROC_NET_SOFTNET_STAT = '/proc/net/softnet_stat'
103 MODPROBE_CONF = '/etc/modprobe.conf'
104 MODPROBE_DIR = '/etc/modprobe.d'
105 RESOLV_CONF = '/etc/resolv.conf'
106 MPP_CONF = '/etc/mpp.conf'
107 MULTIPATH_CONF = '/etc/multipath.conf'
108 NSSWITCH_CONF = '/etc/nsswitch.conf'
109 NTP_CONF = '/etc/ntp.conf'
110 IPTABLES_CONFIG = '/etc/sysconfig/iptables-config'
112 HOSTS_ALLOW = '/etc/hosts.allow'
113 HOSTS_DENY = '/etc/hosts.deny'
114 DHCP_LEASE_DIR = ['/var/lib/dhclient', '/var/lib/dhcp3']
115 OPENVSWITCH_LOG_DIR = '/var/log/openvswitch'
116 OPENVSWITCH_DEFAULT_SWITCH = '/etc/default/openvswitch-switch' # Debian
117 OPENVSWITCH_SYSCONFIG_SWITCH = '/etc/sysconfig/openvswitch' # RHEL
118 OPENVSWITCH_DEFAULT_CONTROLLER = '/etc/default/openvswitch-controller'
119 OPENVSWITCH_CONF_DB = '/etc/openvswitch/conf.db'
120 OPENVSWITCH_VSWITCHD_PID = '/var/run/openvswitch/ovs-vswitchd.pid'
121 COLLECTD_LOGS_DIR = '/var/lib/collectd/rrd'
122 VAR_LOG_DIR = '/var/log/'
123 VAR_LOG_CORE_DIR = '/var/log/core'
124 X11_LOGS_DIR = VAR_LOG_DIR
125 X11_LOGS_RE = re.compile(r'.*/Xorg\..*$')
126 X11_AUTH_DIR = '/root/'
127 X11_AUTH_RE = re.compile(r'.*/\.((Xauthority)|(serverauth\.[0-9]*))$')
128 YUM_LOG = '/var/log/yum.log'
129 YUM_REPOS_DIR = '/etc/yum.repos.d'
130 PAM_DIR = '/etc/pam.d'
131 KRB5_CONF = '/etc/krb5.conf'
137 os.environ['PATH'] = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
141 CHKCONFIG = 'chkconfig'
144 DMIDECODE = 'dmidecode'
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
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'])
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', '/var/run/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'])
642 # permit the user to filter out data
643 for k in sorted(data.keys()):
644 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % k):
647 # collect selected data now
648 output_ts('Running commands to collect data')
651 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
654 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
658 if output_file is None:
661 dirname = os.path.dirname(output_file)
662 if dirname and not os.path.exists(dirname):
669 output_ts('Creating output file')
671 if output_type.startswith('tar'):
672 make_tar(subdir, output_type, output_fd, output_file)
674 make_zip(subdir, output_file)
679 print >>sys.stderr, "Category sizes (max, actual):\n"
680 for c in caps.keys():
681 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
685 def find_tapdisk_logs():
686 return glob.glob('/var/log/blktap/*.log*')
688 def generate_tapdisk_logs():
689 for pid in pidof('tapdisk'):
691 os.kill(pid, SIGUSR1)
692 output_ts("Including logs for tapdisk process %d" % pid)
695 # give processes a second to write their logs
698 def clean_tapdisk_logs():
699 for filename in find_tapdisk_logs():
705 def filter_db_pii(str, state):
706 if 'in_secret_table' not in state:
707 state['in_secret_table'] = False
709 if str.startswith('<table ') and 'name="secret"' in str:
710 state['in_secret_table'] = True
711 elif str.startswith('</table>'):
712 state['in_secret_table'] = False
714 if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
715 str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
718 def dump_scsi_hosts(cap):
720 l = os.listdir('/sys/class/scsi_host')
726 f = open('/sys/class/scsi_host/%s/proc_name' % h)
727 procname = f.readline().strip("\n")
733 f = open('/sys/class/scsi_host/%s/model_name' % h)
734 modelname = f.readline().strip("\n")
740 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
744 def module_info(cap):
745 output = StringIO.StringIO()
746 modules = open(PROC_MODULES, 'r')
750 module = line.split()[0]
751 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
756 return output.getvalue()
759 def multipathd_topology(cap):
760 pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
761 stdout=PIPE, stderr=dev_null)
762 stdout, stderr = pipe.communicate('show topology')
767 output = StringIO.StringIO()
768 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
772 if not procs[0].timed_out:
773 return output.getvalue().splitlines()
777 output = StringIO.StringIO()
778 procs = [ProcOutput([OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
782 if not procs[0].timed_out:
783 bonds = output.getvalue().splitlines()[1:]
784 return [x.split('\t')[1] for x in bonds]
790 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
792 fh = open('/proc/'+d+'/cmdline')
794 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
796 if not num_fds in fd_dict:
797 fd_dict[num_fds] = []
798 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
801 keys = fd_dict.keys()
802 keys.sort(lambda a, b: int(b) - int(a))
804 output += "%s: %s\n" % (k, str(fd_dict[k]))
807 def dump_rdac_groups(cap):
808 output = StringIO.StringIO()
809 procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
813 if not procs[0].timed_out:
815 for line in output.getvalue().splitlines():
816 if line.startswith('ID'):
818 elif line.startswith('----'):
821 group, _ = line.split(None, 1)
822 cmd_output(cap, [MPPUTIL, '-g', group])
824 def load_plugins(just_capabilities = False):
825 def getText(nodelist):
827 for node in nodelist:
828 if node.nodeType == node.TEXT_NODE:
832 def getBoolAttr(el, attr, default = False):
834 val = el.getAttribute(attr).lower()
835 if val in ['true', 'false', 'yes', 'no']:
836 ret = val in ['true', 'yes']
839 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
840 if not caps.has_key(dir):
841 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
843 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
844 assert xmldoc.documentElement.tagName == "capability"
846 pii, min_size, max_size, min_time, max_time, mime = \
847 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
849 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
850 pii = xmldoc.documentElement.getAttribute("pii")
851 if xmldoc.documentElement.getAttribute("min_size") != '':
852 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
853 if xmldoc.documentElement.getAttribute("max_size") != '':
854 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
855 if xmldoc.documentElement.getAttribute("min_time") != '':
856 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
857 if xmldoc.documentElement.getAttribute("max_time") != '':
858 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
859 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
860 mime = xmldoc.documentElement.getAttribute("mime")
861 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
862 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
864 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
866 if just_capabilities:
869 plugdir = os.path.join(PLUGIN_DIR, dir)
870 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
871 xmldoc = parse(os.path.join(plugdir, file))
872 assert xmldoc.documentElement.tagName == "collect"
874 for el in xmldoc.documentElement.getElementsByTagName("*"):
875 if el.tagName == "files":
876 file_output(dir, getText(el.childNodes).split())
877 elif el.tagName == "directory":
878 pattern = el.getAttribute("pattern")
879 if pattern == '': pattern = None
880 negate = getBoolAttr(el, 'negate')
881 tree_output(dir, getText(el.childNodes), pattern and re.compile(pattern) or None, negate)
882 elif el.tagName == "command":
883 label = el.getAttribute("label")
884 if label == '': label = None
885 cmd_output(dir, getText(el.childNodes), label)
887 def make_tar(subdir, suffix, output_fd, output_file):
888 global SILENT_MODE, data
891 if suffix == 'tar.bz2':
893 elif suffix == 'tar.gz':
897 if output_file is None:
898 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
900 filename = output_file
901 old_umask = os.umask(0077)
902 tf = tarfile.open(filename, mode)
905 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
908 for (k, v) in data.items():
910 tar_filename = os.path.join(subdir, construct_filename(k, v))
911 ti = tarfile.TarInfo(tar_filename)
916 if v.has_key('output'):
917 ti.mtime = v['output'].mtime
918 ti.size = len(v['output'].getvalue())
920 tf.addfile(ti, v['output'])
921 elif v.has_key('filename'):
922 s = os.stat(v['filename'])
923 ti.mtime = s.st_mtime
925 tf.addfile(ti, file(v['filename']))
932 output ('Writing tarball %s successful.' % filename)
937 def make_zip(subdir, output_file):
938 global SILENT_MODE, data
940 if output_file is None:
941 filename = "%s/%s.zip" % (BUG_DIR, subdir)
943 filename = output_file
944 old_umask = os.umask(0077)
945 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
949 for (k, v) in data.items():
951 dest = os.path.join(subdir, construct_filename(k, v))
953 if v.has_key('output'):
954 zf.writestr(dest, v['output'].getvalue())
956 if os.stat(v['filename']).st_size < 50:
957 compress_type = zipfile.ZIP_STORED
959 compress_type = zipfile.ZIP_DEFLATED
960 zf.write(v['filename'], dest, compress_type)
966 output ('Writing archive %s successful.' % filename)
971 def make_inventory(inventory, subdir):
972 document = getDOMImplementation().createDocument(
973 None, INVENTORY_XML_ROOT, None)
975 # create summary entry
976 s = document.createElement(INVENTORY_XML_SUMMARY)
977 user = os.getenv('SUDO_USER', os.getenv('USER'))
979 s.setAttribute('user', user)
980 s.setAttribute('date', time.strftime('%c'))
981 s.setAttribute('hostname', platform.node())
982 s.setAttribute('uname', ' '.join(platform.uname()))
983 s.setAttribute('uptime', commands.getoutput(UPTIME))
984 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
986 map(lambda (k, v): inventory_entry(document, subdir, k, v),
988 return document.toprettyxml()
990 def inventory_entry(document, subdir, k, v):
992 el = document.createElement(INVENTORY_XML_ELEMENT)
993 el.setAttribute('capability', v['cap'])
994 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
995 el.setAttribute('md5sum', md5sum(v))
996 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
1003 if d.has_key('filename'):
1004 f = open(d['filename'])
1006 while len(data) > 0:
1010 elif d.has_key('output'):
1011 m.update(d['output'].getvalue())
1012 return m.hexdigest()
1015 def construct_filename(k, v):
1016 if v.has_key('filename'):
1017 if v['filename'][0] == '/':
1018 return v['filename'][1:]
1020 return v['filename']
1021 s = k.replace(' ', '-')
1022 s = s.replace('--', '-')
1023 s = s.replace('/', '%')
1024 if s.find('.') == -1:
1029 def update_capabilities():
1032 def update_cap_size(cap, size):
1033 update_cap(cap, MIN_SIZE, size)
1034 update_cap(cap, MAX_SIZE, size)
1035 update_cap(cap, CHECKED, size > 0)
1038 def update_cap(cap, k, v):
1042 caps[cap] = tuple(l)
1045 def size_of_dir(d, pattern = None, negate = False):
1046 if os.path.isdir(d):
1047 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1053 def size_of_all(files, pattern = None, negate = False):
1054 return sum([size_of(f, pattern, negate) for f in files])
1057 def matches(f, pattern, negate):
1059 return not matches(f, pattern, False)
1061 return pattern is None or pattern.match(f)
1064 def size_of(f, pattern, negate):
1065 if os.path.isfile(f) and matches(f, pattern, negate):
1066 return os.stat(f)[6]
1068 return size_of_dir(f, pattern, negate)
1071 def print_capabilities():
1072 document = getDOMImplementation().createDocument(
1073 "ns", CAP_XML_ROOT, None)
1074 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1075 print document.toprettyxml()
1077 def capability(document, key):
1079 el = document.createElement(CAP_XML_ELEMENT)
1080 el.setAttribute('key', c[KEY])
1081 el.setAttribute('pii', c[PII])
1082 el.setAttribute('min-size', str(c[MIN_SIZE]))
1083 el.setAttribute('max-size', str(c[MAX_SIZE]))
1084 el.setAttribute('min-time', str(c[MIN_TIME]))
1085 el.setAttribute('max-time', str(c[MAX_TIME]))
1086 el.setAttribute('content-type', c[MIME])
1087 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1088 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1092 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1093 return '\n'.join([format % i for i in d.items()]) + '\n'
1097 yn = raw_input(prompt)
1099 return len(yn) == 0 or yn.lower()[0] == 'y'
1102 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1107 f = open('/proc/partitions')
1110 for line in f.readlines():
1111 (major, minor, blocks, name) = line.split()
1112 if int(major) < 254 and not partition_re.match(name):
1123 def __init__(self, command, max_time, inst=None, filter=None):
1124 self.command = command
1125 self.max_time = max_time
1127 self.running = False
1129 self.timed_out = False
1131 self.timeout = int(time.time()) + self.max_time
1132 self.filter = filter
1133 self.filter_state = {}
1139 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1142 self.timed_out = False
1144 if ProcOutput.debug:
1145 output_ts("Starting '%s'" % self.cmdAsStr())
1146 self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1147 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1148 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1152 output_ts("'%s' failed" % self.cmdAsStr())
1153 self.running = False
1156 def terminate(self):
1159 os.kill(self.proc.pid, SIGTERM)
1163 self.running = False
1164 self.status = SIGTERM
1166 def read_line(self):
1168 line = self.proc.stdout.readline()
1171 self.status = self.proc.wait()
1173 self.running = False
1176 line = self.filter(line, self.filter_state)
1178 self.inst.write(line)
1180 def run_procs(procs):
1188 active_procs.append(p)
1189 pipes.append(p.proc.stdout)
1191 elif p.status == None and not p.failed and not p.timed_out:
1194 active_procs.append(p)
1195 pipes.append(p.proc.stdout)
1202 (i, o, x) = select(pipes, [], [], 1.0)
1203 now = int(time.time())
1205 # handle process output
1206 for p in active_procs:
1207 if p.proc.stdout in i:
1211 if p.running and now > p.timeout:
1212 output_ts("'%s' timed out" % p.cmdAsStr())
1214 p.inst.write("\n** timeout **\n")
1222 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1224 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1232 class StringIOmtime(StringIO.StringIO):
1233 def __init__(self, buf = ''):
1234 StringIO.StringIO.__init__(self, buf)
1235 self.mtime = time.time()
1238 StringIO.StringIO.write(self, s)
1239 self.mtime = time.time()
1242 if __name__ == "__main__":
1245 except KeyboardInterrupt:
1246 print "\nInterrupted."