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, Inc.
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 = "@pkgdatadir@/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, newest_first=False):
324 If newest_first is True, the list of files in path_list is sorted
325 by file modification time in descending order, else its sorted
330 for path in path_list:
335 path_entries.append((path, s))
337 mtime = lambda(path, stat): stat.st_mtime
338 path_entries.sort(key=mtime, reverse=newest_first)
339 for p in path_entries:
340 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
341 cap_sizes[cap] < caps[cap][MAX_SIZE]:
342 data[p] = {'cap': cap, 'filename': p[0]}
343 cap_sizes[cap] += p[1].st_size
345 output("Omitting %s, size constraint of %s exceeded" % (p[0], cap))
347 def tree_output(cap, path, pattern=None, negate=False, newest_first=False):
349 Walks the directory tree rooted at path. Files in current dir are processed
350 before files in sub-dirs.
353 if os.path.exists(path):
354 for root, dirs, files in os.walk(path):
355 fns = [fn for fn in [os.path.join(root, f) for f in files]
356 if os.path.isfile(fn) and matches(fn, pattern, negate)]
357 file_output(cap, fns, newest_first=newest_first)
359 def func_output(cap, label, func):
361 t = str(func).split()
362 data[label] = {'cap': cap, 'func': func}
367 for (k, v) in data.items():
369 if v.has_key('cmd_args'):
370 v['output'] = StringIOmtime()
371 if not process_lists.has_key(cap):
372 process_lists[cap] = []
373 process_lists[cap].append(ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'], v['filter']))
374 elif v.has_key('filename') and v['filename'].startswith('/proc/'):
375 # proc files must be read into memory
377 f = open(v['filename'], 'r')
380 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
381 cap_sizes[cap] < caps[cap][MAX_SIZE]:
382 v['output'] = StringIOmtime(s)
383 cap_sizes[cap] += len(s)
385 output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
388 elif v.has_key('func'):
393 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
394 cap_sizes[cap] < caps[cap][MAX_SIZE]:
395 v['output'] = StringIOmtime(s)
396 cap_sizes[cap] += len(s)
398 output("Omitting %s, size constraint of %s exceeded" % (k, cap))
400 run_procs(process_lists.values())
403 def main(argv = None):
404 global ANSWER_YES_TO_ALL, SILENT_MODE
405 global entries, data, dbg, unlimited_data
407 # we need access to privileged files, exit if we are not running as root
409 print >>sys.stderr, "Error: ovs-bugtool must be run as root"
413 output_type = 'tar.bz2'
420 (options, params) = getopt.gnu_getopt(
421 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
422 'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
424 except getopt.GetoptError, opterr:
425 print >>sys.stderr, opterr
433 entries = [e for e in caps.keys() if caps[e][CHECKED]]
435 for (k, v) in options:
436 if k == '--capabilities':
437 update_capabilities()
442 if v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
445 print >>sys.stderr, "Invalid output format '%s'" % v
448 # "-s" or "--silent" means suppress output (except for the final
449 # output filename at the end)
450 if k in ['-s', '--silent']:
453 if k == '--entries' and v != '':
454 entries = v.split(',')
456 # If the user runs the script with "-y" or "--yestoall" we don't ask
457 # all the really annoying questions.
458 if k in ['-y', '--yestoall']:
459 ANSWER_YES_TO_ALL = True
464 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
465 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
467 print >>sys.stderr, "Invalid output file descriptor", output_fd
474 entries = caps.keys()
475 elif k == '--unlimited':
476 unlimited_data = True
479 ProcOutput.debug = True
482 print >>sys.stderr, "Invalid additional arguments", str(params)
485 if output_fd != -1 and output_type != 'tar':
486 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
489 if output_fd != -1 and output_file is not None:
490 print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
493 if ANSWER_YES_TO_ALL:
494 output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
497 This application will collate dmesg output, details of the
498 hardware configuration of your machine, information about the build of
499 openvswitch that you are using, plus, if you allow it, various logs.
501 The collated information will be saved as a .%s for archiving or
502 sending to a Technical Support Representative.
504 The logs may contain private information, and if you are at all
505 worried about that, you should exit now, or you should explicitly
506 exclude those logs from the archive.
510 # assemble potential data
512 file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
513 cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
514 cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
516 tree_output(CAP_COLLECTD_LOGS, COLLECTD_LOGS_DIR)
517 cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
518 file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
519 file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
520 cmd_output(CAP_DISK_INFO, [DF, '-alT'])
521 cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
522 for d in disk_list():
523 cmd_output(CAP_DISK_INFO, [HDPARM, '-I', '/dev/%s' % d])
524 if len(pidof('iscsid')) != 0:
525 cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
526 cmd_output(CAP_DISK_INFO, [VGSCAN])
527 cmd_output(CAP_DISK_INFO, [PVS])
528 cmd_output(CAP_DISK_INFO, [VGS])
529 cmd_output(CAP_DISK_INFO, [LVS])
530 file_output(CAP_DISK_INFO, [LVM_CACHE, LVM_CONFIG])
531 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
532 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
533 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
534 cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
535 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
536 cmd_output(CAP_DISK_INFO, [LVDISPLAY, '--map'])
538 file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
539 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
540 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
541 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
542 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
543 file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
544 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
547 for d in disk_list():
548 cmd_output(CAP_HDPARM_T, [HDPARM, '-tT', '/dev/%s' % d])
550 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
551 PROC_FILESYSTEMS, PROC_CMDLINE])
552 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
553 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
554 file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
555 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
556 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
558 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
560 file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
561 cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
562 func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
563 cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
564 if CAP_MULTIPATH in entries:
565 dump_rdac_groups(CAP_MULTIPATH)
567 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
568 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
569 file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
570 file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
571 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_CONF_DB])
573 cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
574 cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
575 cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
576 cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
577 for dir in DHCP_LEASE_DIR:
578 tree_output(CAP_NETWORK_STATUS, dir)
579 cmd_output(CAP_NETWORK_STATUS, [BRCTL, 'show'])
580 cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
581 for p in os.listdir('/sys/class/net/'):
583 f = open('/sys/class/net/%s/type' % p, 'r')
586 if os.path.islink('/sys/class/net/%s/device' % p) and int(t) == 1:
588 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
589 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
590 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
591 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
592 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
594 cmd_output(CAP_NETWORK_STATUS,
595 [TC, '-s', '-d', 'class', 'show', 'dev', p])
598 tree_output(CAP_NETWORK_STATUS, PROC_NET_BONDING_DIR)
599 tree_output(CAP_NETWORK_STATUS, PROC_NET_VLAN_DIR)
600 cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
601 file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
602 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
603 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
605 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
606 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
607 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
609 vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
610 vspid = int(vspidfile.readline().strip())
612 for b in bond_list(vspid):
613 cmd_output(CAP_NETWORK_STATUS,
614 [OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
615 'ovs-appctl-bond-show-%s.out' % b)
619 tree_output(CAP_PAM, PAM_DIR)
620 file_output(CAP_PAM, [KRB5_CONF])
622 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
623 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
625 logs = ([ VAR_LOG_DIR + x for x in
626 [ 'crit.log', 'kern.log', 'daemon.log', 'user.log',
627 'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot' ]]
628 + [ OPENVSWITCH_LOG_DIR + x for x in
629 [ 'ovs-vswitchd.log', 'ovs-brcompatd.log', 'ovsdb-server.log',
630 'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log' ]])
631 file_output(CAP_SYSTEM_LOGS, logs)
632 file_output(CAP_SYSTEM_LOGS,
633 [ '%s.%d' % (f, n) for n in range(20) for f in logs ])
634 file_output(CAP_SYSTEM_LOGS,
635 [ '%s.%d.gz' % (f, n) for n in range(20) for f in logs ])
637 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
638 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
640 cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
642 tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
643 tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
644 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
646 file_output(CAP_YUM, [YUM_LOG])
647 tree_output(CAP_YUM, YUM_REPOS_DIR)
648 cmd_output(CAP_YUM, [RPM, '-qa'])
649 file_output(CAP_YUM, [APT_SOURCES_LIST])
650 tree_output(CAP_YUM, APT_SOURCES_LIST_D)
651 cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
658 # permit the user to filter out data
659 for k in sorted(data.keys()):
660 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % k):
663 # collect selected data now
664 output_ts('Running commands to collect data')
667 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
670 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
674 if output_file is None:
677 dirname = os.path.dirname(output_file)
678 if dirname and not os.path.exists(dirname):
685 output_ts('Creating output file')
687 if output_type.startswith('tar'):
688 make_tar(subdir, output_type, output_fd, output_file)
690 make_zip(subdir, output_file)
695 print >>sys.stderr, "Category sizes (max, actual):\n"
696 for c in caps.keys():
697 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
701 def find_tapdisk_logs():
702 return glob.glob('/var/log/blktap/*.log*')
704 def generate_tapdisk_logs():
705 for pid in pidof('tapdisk'):
707 os.kill(pid, SIGUSR1)
708 output_ts("Including logs for tapdisk process %d" % pid)
711 # give processes a second to write their logs
714 def clean_tapdisk_logs():
715 for filename in find_tapdisk_logs():
721 def filter_db_pii(str, state):
722 if 'in_secret_table' not in state:
723 state['in_secret_table'] = False
725 if str.startswith('<table ') and 'name="secret"' in str:
726 state['in_secret_table'] = True
727 elif str.startswith('</table>'):
728 state['in_secret_table'] = False
730 if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
731 str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
734 def dump_scsi_hosts(cap):
736 l = os.listdir('/sys/class/scsi_host')
742 f = open('/sys/class/scsi_host/%s/proc_name' % h)
743 procname = f.readline().strip("\n")
749 f = open('/sys/class/scsi_host/%s/model_name' % h)
750 modelname = f.readline().strip("\n")
756 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
760 def module_info(cap):
761 output = StringIO.StringIO()
762 modules = open(PROC_MODULES, 'r')
766 module = line.split()[0]
767 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
772 return output.getvalue()
775 def multipathd_topology(cap):
776 pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
777 stdout=PIPE, stderr=dev_null)
778 stdout, stderr = pipe.communicate('show topology')
783 output = StringIO.StringIO()
784 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
788 if not procs[0].timed_out:
789 return output.getvalue().splitlines()
793 output = StringIO.StringIO()
794 procs = [ProcOutput([OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
798 if not procs[0].timed_out:
799 bonds = output.getvalue().splitlines()[1:]
800 return [x.split('\t')[1] for x in bonds]
806 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
808 fh = open('/proc/'+d+'/cmdline')
810 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
812 if not num_fds in fd_dict:
813 fd_dict[num_fds] = []
814 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
817 keys = fd_dict.keys()
818 keys.sort(lambda a, b: int(b) - int(a))
820 output += "%s: %s\n" % (k, str(fd_dict[k]))
823 def dump_rdac_groups(cap):
824 output = StringIO.StringIO()
825 procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
829 if not procs[0].timed_out:
831 for line in output.getvalue().splitlines():
832 if line.startswith('ID'):
834 elif line.startswith('----'):
837 group, _ = line.split(None, 1)
838 cmd_output(cap, [MPPUTIL, '-g', group])
840 def load_plugins(just_capabilities = False):
841 def getText(nodelist):
843 for node in nodelist:
844 if node.nodeType == node.TEXT_NODE:
848 def getBoolAttr(el, attr, default = False):
850 val = el.getAttribute(attr).lower()
851 if val in ['true', 'false', 'yes', 'no']:
852 ret = val in ['true', 'yes']
855 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
856 if not caps.has_key(dir):
857 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
859 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
860 assert xmldoc.documentElement.tagName == "capability"
862 pii, min_size, max_size, min_time, max_time, mime = \
863 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
865 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
866 pii = xmldoc.documentElement.getAttribute("pii")
867 if xmldoc.documentElement.getAttribute("min_size") != '':
868 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
869 if xmldoc.documentElement.getAttribute("max_size") != '':
870 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
871 if xmldoc.documentElement.getAttribute("min_time") != '':
872 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
873 if xmldoc.documentElement.getAttribute("max_time") != '':
874 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
875 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
876 mime = xmldoc.documentElement.getAttribute("mime")
877 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
878 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
880 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
882 if just_capabilities:
885 plugdir = os.path.join(PLUGIN_DIR, dir)
886 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
887 xmldoc = parse(os.path.join(plugdir, file))
888 assert xmldoc.documentElement.tagName == "collect"
890 for el in xmldoc.documentElement.getElementsByTagName("*"):
891 if el.tagName == "files":
892 newest_first = getBoolAttr(el, 'newest_first')
893 file_output(dir, getText(el.childNodes).split(),
894 newest_first=newest_first)
895 elif el.tagName == "directory":
896 pattern = el.getAttribute("pattern")
897 if pattern == '': pattern = None
898 negate = getBoolAttr(el, 'negate')
899 newest_first = getBoolAttr(el, 'newest_first')
900 tree_output(dir, getText(el.childNodes), pattern and re.compile(pattern) or None,
901 negate=negate, newest_first=newest_first)
902 elif el.tagName == "command":
903 label = el.getAttribute("label")
904 if label == '': label = None
905 cmd_output(dir, getText(el.childNodes), label)
907 def make_tar(subdir, suffix, output_fd, output_file):
908 global SILENT_MODE, data
911 if suffix == 'tar.bz2':
913 elif suffix == 'tar.gz':
917 if output_file is None:
918 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
920 filename = output_file
921 old_umask = os.umask(0077)
922 tf = tarfile.open(filename, mode)
925 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
928 for (k, v) in data.items():
930 tar_filename = os.path.join(subdir, construct_filename(k, v))
931 ti = tarfile.TarInfo(tar_filename)
936 if v.has_key('output'):
937 ti.mtime = v['output'].mtime
938 ti.size = len(v['output'].getvalue())
940 tf.addfile(ti, v['output'])
941 elif v.has_key('filename'):
942 s = os.stat(v['filename'])
943 ti.mtime = s.st_mtime
945 tf.addfile(ti, file(v['filename']))
952 output ('Writing tarball %s successful.' % filename)
957 def make_zip(subdir, output_file):
958 global SILENT_MODE, data
960 if output_file is None:
961 filename = "%s/%s.zip" % (BUG_DIR, subdir)
963 filename = output_file
964 old_umask = os.umask(0077)
965 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
969 for (k, v) in data.items():
971 dest = os.path.join(subdir, construct_filename(k, v))
973 if v.has_key('output'):
974 zf.writestr(dest, v['output'].getvalue())
976 if os.stat(v['filename']).st_size < 50:
977 compress_type = zipfile.ZIP_STORED
979 compress_type = zipfile.ZIP_DEFLATED
980 zf.write(v['filename'], dest, compress_type)
986 output ('Writing archive %s successful.' % filename)
991 def make_inventory(inventory, subdir):
992 document = getDOMImplementation().createDocument(
993 None, INVENTORY_XML_ROOT, None)
995 # create summary entry
996 s = document.createElement(INVENTORY_XML_SUMMARY)
997 user = os.getenv('SUDO_USER', os.getenv('USER'))
999 s.setAttribute('user', user)
1000 s.setAttribute('date', time.strftime('%c'))
1001 s.setAttribute('hostname', platform.node())
1002 s.setAttribute('uname', ' '.join(platform.uname()))
1003 s.setAttribute('uptime', commands.getoutput(UPTIME))
1004 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
1006 map(lambda (k, v): inventory_entry(document, subdir, k, v),
1008 return document.toprettyxml()
1010 def inventory_entry(document, subdir, k, v):
1012 el = document.createElement(INVENTORY_XML_ELEMENT)
1013 el.setAttribute('capability', v['cap'])
1014 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
1015 el.setAttribute('md5sum', md5sum(v))
1016 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
1023 if d.has_key('filename'):
1024 f = open(d['filename'])
1026 while len(data) > 0:
1030 elif d.has_key('output'):
1031 m.update(d['output'].getvalue())
1032 return m.hexdigest()
1035 def construct_filename(k, v):
1036 if v.has_key('filename'):
1037 if v['filename'][0] == '/':
1038 return v['filename'][1:]
1040 return v['filename']
1041 s = k.replace(' ', '-')
1042 s = s.replace('--', '-')
1043 s = s.replace('/', '%')
1044 if s.find('.') == -1:
1049 def update_capabilities():
1052 def update_cap_size(cap, size):
1053 update_cap(cap, MIN_SIZE, size)
1054 update_cap(cap, MAX_SIZE, size)
1055 update_cap(cap, CHECKED, size > 0)
1058 def update_cap(cap, k, v):
1062 caps[cap] = tuple(l)
1065 def size_of_dir(d, pattern = None, negate = False):
1066 if os.path.isdir(d):
1067 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1073 def size_of_all(files, pattern = None, negate = False):
1074 return sum([size_of(f, pattern, negate) for f in files])
1077 def matches(f, pattern, negate):
1079 return not matches(f, pattern, False)
1081 return pattern is None or pattern.match(f)
1084 def size_of(f, pattern, negate):
1085 if os.path.isfile(f) and matches(f, pattern, negate):
1086 return os.stat(f)[6]
1088 return size_of_dir(f, pattern, negate)
1091 def print_capabilities():
1092 document = getDOMImplementation().createDocument(
1093 "ns", CAP_XML_ROOT, None)
1094 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1095 print document.toprettyxml()
1097 def capability(document, key):
1099 el = document.createElement(CAP_XML_ELEMENT)
1100 el.setAttribute('key', c[KEY])
1101 el.setAttribute('pii', c[PII])
1102 el.setAttribute('min-size', str(c[MIN_SIZE]))
1103 el.setAttribute('max-size', str(c[MAX_SIZE]))
1104 el.setAttribute('min-time', str(c[MIN_TIME]))
1105 el.setAttribute('max-time', str(c[MAX_TIME]))
1106 el.setAttribute('content-type', c[MIME])
1107 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1108 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1112 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1113 return '\n'.join([format % i for i in d.items()]) + '\n'
1117 yn = raw_input(prompt)
1119 return len(yn) == 0 or yn.lower()[0] == 'y'
1122 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1127 f = open('/proc/partitions')
1130 for line in f.readlines():
1131 (major, minor, blocks, name) = line.split()
1132 if int(major) < 254 and not partition_re.match(name):
1143 def __init__(self, command, max_time, inst=None, filter=None):
1144 self.command = command
1145 self.max_time = max_time
1147 self.running = False
1149 self.timed_out = False
1151 self.timeout = int(time.time()) + self.max_time
1152 self.filter = filter
1153 self.filter_state = {}
1159 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1162 self.timed_out = False
1164 if ProcOutput.debug:
1165 output_ts("Starting '%s'" % self.cmdAsStr())
1166 self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1167 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1168 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1172 output_ts("'%s' failed" % self.cmdAsStr())
1173 self.running = False
1176 def terminate(self):
1179 self.proc.stdout.close()
1180 os.kill(self.proc.pid, SIGTERM)
1184 self.running = False
1185 self.status = SIGTERM
1187 def read_line(self):
1189 line = self.proc.stdout.readline()
1192 self.proc.stdout.close()
1193 self.status = self.proc.wait()
1195 self.running = False
1198 line = self.filter(line, self.filter_state)
1200 self.inst.write(line)
1202 def run_procs(procs):
1210 active_procs.append(p)
1211 pipes.append(p.proc.stdout)
1213 elif p.status == None and not p.failed and not p.timed_out:
1216 active_procs.append(p)
1217 pipes.append(p.proc.stdout)
1224 (i, o, x) = select(pipes, [], [], 1.0)
1225 now = int(time.time())
1227 # handle process output
1228 for p in active_procs:
1229 if p.proc.stdout in i:
1233 if p.running and now > p.timeout:
1234 output_ts("'%s' timed out" % p.cmdAsStr())
1236 p.inst.write("\n** timeout **\n")
1244 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1246 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1254 class StringIOmtime(StringIO.StringIO):
1255 def __init__(self, buf = ''):
1256 StringIO.StringIO.__init__(self, buf)
1257 self.mtime = time.time()
1260 StringIO.StringIO.write(self, s)
1261 self.mtime = time.time()
1264 if __name__ == "__main__":
1267 except KeyboardInterrupt:
1268 print "\nInterrupted."