3 # This library is free software; you can redistribute it and/or
4 # modify it under the terms of version 2.1 of the GNU Lesser General Public
5 # License as published by the Free Software Foundation.
7 # This library is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 # Lesser General Public License for more details.
12 # You should have received a copy of the GNU Lesser General Public
13 # License along with this library; if not, write to the Free Software
14 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 # Copyright (c) 2005, 2007 XenSource Ltd.
17 # Copyright (c) 2010, 2011 Nicira Networks.
20 # To add new entries to the bugtool, you need to:
22 # Create a new capability. These declare the new entry to the GUI, including
23 # the expected size, time to collect, privacy implications, and whether the
24 # capability should be selected by default. One capability may refer to
25 # multiple files, assuming that they can be reasonably grouped together, and
26 # have the same privacy implications. You need:
28 # A new CAP_ constant.
29 # A cap() invocation to declare the capability.
31 # You then need to add calls to main() to collect the files. These will
32 # typically be calls to the helpers file_output(), tree_output(), cmd_output(),
37 warnings.filterwarnings(action="ignore", category=DeprecationWarning)
48 from xml.dom.minidom import parse, getDOMImplementation
50 from subprocess import Popen, PIPE
51 from select import select
52 from signal import SIGTERM, SIGUSR1
61 OS_RELEASE = platform.release()
67 APT_SOURCES_LIST = "/etc/apt/sources.list"
68 APT_SOURCES_LIST_D = "/etc/apt/sources.list.d"
69 BUG_DIR = "/var/log/ovs-bugtool"
70 PLUGIN_DIR = "@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')
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])
593 cmd_output(CAP_NETWORK_STATUS,
594 [TC, '-s', '-d', 'class', 'show', 'dev', p])
597 tree_output(CAP_NETWORK_STATUS, PROC_NET_BONDING_DIR)
598 tree_output(CAP_NETWORK_STATUS, PROC_NET_VLAN_DIR)
599 cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
600 file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
601 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
602 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
604 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
605 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
606 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
608 vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
609 vspid = int(vspidfile.readline().strip())
611 for b in bond_list(vspid):
612 cmd_output(CAP_NETWORK_STATUS,
613 [OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
614 'ovs-appctl-bond-show-%s.out' % b)
618 tree_output(CAP_PAM, PAM_DIR)
619 file_output(CAP_PAM, [KRB5_CONF])
621 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
622 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
624 logs = ([ VAR_LOG_DIR + x for x in
625 [ 'crit.log', 'kern.log', 'daemon.log', 'user.log',
626 'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot' ]]
627 + [ OPENVSWITCH_LOG_DIR + x for x in
628 [ 'ovs-vswitchd.log', 'ovs-brcompatd.log', 'ovsdb-server.log',
629 'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log' ]])
630 file_output(CAP_SYSTEM_LOGS, logs)
631 file_output(CAP_SYSTEM_LOGS,
632 [ '%s.%d' % (f, n) for n in range(20) for f in logs ])
633 file_output(CAP_SYSTEM_LOGS,
634 [ '%s.%d.gz' % (f, n) for n in range(20) for f in logs ])
636 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
637 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
639 cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
641 tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
642 tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
643 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
645 file_output(CAP_YUM, [YUM_LOG])
646 tree_output(CAP_YUM, YUM_REPOS_DIR)
647 cmd_output(CAP_YUM, [RPM, '-qa'])
648 file_output(CAP_YUM, [APT_SOURCES_LIST])
649 tree_output(CAP_YUM, APT_SOURCES_LIST_D)
650 cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
657 # permit the user to filter out data
658 for k in sorted(data.keys()):
659 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % k):
662 # collect selected data now
663 output_ts('Running commands to collect data')
666 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
669 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
673 if output_file is None:
676 dirname = os.path.dirname(output_file)
677 if dirname and not os.path.exists(dirname):
684 output_ts('Creating output file')
686 if output_type.startswith('tar'):
687 make_tar(subdir, output_type, output_fd, output_file)
689 make_zip(subdir, output_file)
694 print >>sys.stderr, "Category sizes (max, actual):\n"
695 for c in caps.keys():
696 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
700 def find_tapdisk_logs():
701 return glob.glob('/var/log/blktap/*.log*')
703 def generate_tapdisk_logs():
704 for pid in pidof('tapdisk'):
706 os.kill(pid, SIGUSR1)
707 output_ts("Including logs for tapdisk process %d" % pid)
710 # give processes a second to write their logs
713 def clean_tapdisk_logs():
714 for filename in find_tapdisk_logs():
720 def filter_db_pii(str, state):
721 if 'in_secret_table' not in state:
722 state['in_secret_table'] = False
724 if str.startswith('<table ') and 'name="secret"' in str:
725 state['in_secret_table'] = True
726 elif str.startswith('</table>'):
727 state['in_secret_table'] = False
729 if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
730 str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
733 def dump_scsi_hosts(cap):
735 l = os.listdir('/sys/class/scsi_host')
741 f = open('/sys/class/scsi_host/%s/proc_name' % h)
742 procname = f.readline().strip("\n")
748 f = open('/sys/class/scsi_host/%s/model_name' % h)
749 modelname = f.readline().strip("\n")
755 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
759 def module_info(cap):
760 output = StringIO.StringIO()
761 modules = open(PROC_MODULES, 'r')
765 module = line.split()[0]
766 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
771 return output.getvalue()
774 def multipathd_topology(cap):
775 pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
776 stdout=PIPE, stderr=dev_null)
777 stdout, stderr = pipe.communicate('show topology')
782 output = StringIO.StringIO()
783 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
787 if not procs[0].timed_out:
788 return output.getvalue().splitlines()
792 output = StringIO.StringIO()
793 procs = [ProcOutput([OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
797 if not procs[0].timed_out:
798 bonds = output.getvalue().splitlines()[1:]
799 return [x.split('\t')[1] for x in bonds]
805 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
807 fh = open('/proc/'+d+'/cmdline')
809 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
811 if not num_fds in fd_dict:
812 fd_dict[num_fds] = []
813 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
816 keys = fd_dict.keys()
817 keys.sort(lambda a, b: int(b) - int(a))
819 output += "%s: %s\n" % (k, str(fd_dict[k]))
822 def dump_rdac_groups(cap):
823 output = StringIO.StringIO()
824 procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
828 if not procs[0].timed_out:
830 for line in output.getvalue().splitlines():
831 if line.startswith('ID'):
833 elif line.startswith('----'):
836 group, _ = line.split(None, 1)
837 cmd_output(cap, [MPPUTIL, '-g', group])
839 def load_plugins(just_capabilities = False):
840 def getText(nodelist):
842 for node in nodelist:
843 if node.nodeType == node.TEXT_NODE:
847 def getBoolAttr(el, attr, default = False):
849 val = el.getAttribute(attr).lower()
850 if val in ['true', 'false', 'yes', 'no']:
851 ret = val in ['true', 'yes']
854 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
855 if not caps.has_key(dir):
856 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
858 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
859 assert xmldoc.documentElement.tagName == "capability"
861 pii, min_size, max_size, min_time, max_time, mime = \
862 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
864 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
865 pii = xmldoc.documentElement.getAttribute("pii")
866 if xmldoc.documentElement.getAttribute("min_size") != '':
867 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
868 if xmldoc.documentElement.getAttribute("max_size") != '':
869 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
870 if xmldoc.documentElement.getAttribute("min_time") != '':
871 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
872 if xmldoc.documentElement.getAttribute("max_time") != '':
873 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
874 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
875 mime = xmldoc.documentElement.getAttribute("mime")
876 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
877 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
879 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
881 if just_capabilities:
884 plugdir = os.path.join(PLUGIN_DIR, dir)
885 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
886 xmldoc = parse(os.path.join(plugdir, file))
887 assert xmldoc.documentElement.tagName == "collect"
889 for el in xmldoc.documentElement.getElementsByTagName("*"):
890 if el.tagName == "files":
891 newest_first = getBoolAttr(el, 'newest_first')
892 file_output(dir, getText(el.childNodes).split(),
893 newest_first=newest_first)
894 elif el.tagName == "directory":
895 pattern = el.getAttribute("pattern")
896 if pattern == '': pattern = None
897 negate = getBoolAttr(el, 'negate')
898 newest_first = getBoolAttr(el, 'newest_first')
899 tree_output(dir, getText(el.childNodes), pattern and re.compile(pattern) or None,
900 negate=negate, newest_first=newest_first)
901 elif el.tagName == "command":
902 label = el.getAttribute("label")
903 if label == '': label = None
904 cmd_output(dir, getText(el.childNodes), label)
906 def make_tar(subdir, suffix, output_fd, output_file):
907 global SILENT_MODE, data
910 if suffix == 'tar.bz2':
912 elif suffix == 'tar.gz':
916 if output_file is None:
917 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
919 filename = output_file
920 old_umask = os.umask(0077)
921 tf = tarfile.open(filename, mode)
924 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
927 for (k, v) in data.items():
929 tar_filename = os.path.join(subdir, construct_filename(k, v))
930 ti = tarfile.TarInfo(tar_filename)
935 if v.has_key('output'):
936 ti.mtime = v['output'].mtime
937 ti.size = len(v['output'].getvalue())
939 tf.addfile(ti, v['output'])
940 elif v.has_key('filename'):
941 s = os.stat(v['filename'])
942 ti.mtime = s.st_mtime
944 tf.addfile(ti, file(v['filename']))
951 output ('Writing tarball %s successful.' % filename)
956 def make_zip(subdir, output_file):
957 global SILENT_MODE, data
959 if output_file is None:
960 filename = "%s/%s.zip" % (BUG_DIR, subdir)
962 filename = output_file
963 old_umask = os.umask(0077)
964 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
968 for (k, v) in data.items():
970 dest = os.path.join(subdir, construct_filename(k, v))
972 if v.has_key('output'):
973 zf.writestr(dest, v['output'].getvalue())
975 if os.stat(v['filename']).st_size < 50:
976 compress_type = zipfile.ZIP_STORED
978 compress_type = zipfile.ZIP_DEFLATED
979 zf.write(v['filename'], dest, compress_type)
985 output ('Writing archive %s successful.' % filename)
990 def make_inventory(inventory, subdir):
991 document = getDOMImplementation().createDocument(
992 None, INVENTORY_XML_ROOT, None)
994 # create summary entry
995 s = document.createElement(INVENTORY_XML_SUMMARY)
996 user = os.getenv('SUDO_USER', os.getenv('USER'))
998 s.setAttribute('user', user)
999 s.setAttribute('date', time.strftime('%c'))
1000 s.setAttribute('hostname', platform.node())
1001 s.setAttribute('uname', ' '.join(platform.uname()))
1002 s.setAttribute('uptime', commands.getoutput(UPTIME))
1003 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
1005 map(lambda (k, v): inventory_entry(document, subdir, k, v),
1007 return document.toprettyxml()
1009 def inventory_entry(document, subdir, k, v):
1011 el = document.createElement(INVENTORY_XML_ELEMENT)
1012 el.setAttribute('capability', v['cap'])
1013 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
1014 el.setAttribute('md5sum', md5sum(v))
1015 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
1022 if d.has_key('filename'):
1023 f = open(d['filename'])
1025 while len(data) > 0:
1029 elif d.has_key('output'):
1030 m.update(d['output'].getvalue())
1031 return m.hexdigest()
1034 def construct_filename(k, v):
1035 if v.has_key('filename'):
1036 if v['filename'][0] == '/':
1037 return v['filename'][1:]
1039 return v['filename']
1040 s = k.replace(' ', '-')
1041 s = s.replace('--', '-')
1042 s = s.replace('/', '%')
1043 if s.find('.') == -1:
1048 def update_capabilities():
1051 def update_cap_size(cap, size):
1052 update_cap(cap, MIN_SIZE, size)
1053 update_cap(cap, MAX_SIZE, size)
1054 update_cap(cap, CHECKED, size > 0)
1057 def update_cap(cap, k, v):
1061 caps[cap] = tuple(l)
1064 def size_of_dir(d, pattern = None, negate = False):
1065 if os.path.isdir(d):
1066 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1072 def size_of_all(files, pattern = None, negate = False):
1073 return sum([size_of(f, pattern, negate) for f in files])
1076 def matches(f, pattern, negate):
1078 return not matches(f, pattern, False)
1080 return pattern is None or pattern.match(f)
1083 def size_of(f, pattern, negate):
1084 if os.path.isfile(f) and matches(f, pattern, negate):
1085 return os.stat(f)[6]
1087 return size_of_dir(f, pattern, negate)
1090 def print_capabilities():
1091 document = getDOMImplementation().createDocument(
1092 "ns", CAP_XML_ROOT, None)
1093 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1094 print document.toprettyxml()
1096 def capability(document, key):
1098 el = document.createElement(CAP_XML_ELEMENT)
1099 el.setAttribute('key', c[KEY])
1100 el.setAttribute('pii', c[PII])
1101 el.setAttribute('min-size', str(c[MIN_SIZE]))
1102 el.setAttribute('max-size', str(c[MAX_SIZE]))
1103 el.setAttribute('min-time', str(c[MIN_TIME]))
1104 el.setAttribute('max-time', str(c[MAX_TIME]))
1105 el.setAttribute('content-type', c[MIME])
1106 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1107 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1111 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1112 return '\n'.join([format % i for i in d.items()]) + '\n'
1116 yn = raw_input(prompt)
1118 return len(yn) == 0 or yn.lower()[0] == 'y'
1121 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1126 f = open('/proc/partitions')
1129 for line in f.readlines():
1130 (major, minor, blocks, name) = line.split()
1131 if int(major) < 254 and not partition_re.match(name):
1142 def __init__(self, command, max_time, inst=None, filter=None):
1143 self.command = command
1144 self.max_time = max_time
1146 self.running = False
1148 self.timed_out = False
1150 self.timeout = int(time.time()) + self.max_time
1151 self.filter = filter
1152 self.filter_state = {}
1158 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1161 self.timed_out = False
1163 if ProcOutput.debug:
1164 output_ts("Starting '%s'" % self.cmdAsStr())
1165 self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1166 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1167 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1171 output_ts("'%s' failed" % self.cmdAsStr())
1172 self.running = False
1175 def terminate(self):
1178 os.kill(self.proc.pid, SIGTERM)
1182 self.running = False
1183 self.status = SIGTERM
1185 def read_line(self):
1187 line = self.proc.stdout.readline()
1190 self.status = self.proc.wait()
1192 self.running = False
1195 line = self.filter(line, self.filter_state)
1197 self.inst.write(line)
1199 def run_procs(procs):
1207 active_procs.append(p)
1208 pipes.append(p.proc.stdout)
1210 elif p.status == None and not p.failed and not p.timed_out:
1213 active_procs.append(p)
1214 pipes.append(p.proc.stdout)
1221 (i, o, x) = select(pipes, [], [], 1.0)
1222 now = int(time.time())
1224 # handle process output
1225 for p in active_procs:
1226 if p.proc.stdout in i:
1230 if p.running and now > p.timeout:
1231 output_ts("'%s' timed out" % p.cmdAsStr())
1233 p.inst.write("\n** timeout **\n")
1241 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1243 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1251 class StringIOmtime(StringIO.StringIO):
1252 def __init__(self, buf = ''):
1253 StringIO.StringIO.__init__(self, buf)
1254 self.mtime = time.time()
1257 StringIO.StringIO.write(self, s)
1258 self.mtime = time.time()
1261 if __name__ == "__main__":
1264 except KeyboardInterrupt:
1265 print "\nInterrupted."