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, 2012 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 = '@DBDIR@/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())
404 global ANSWER_YES_TO_ALL, SILENT_MODE
405 global entries, data, dbg, unlimited_data
408 only_ovs_info = False
409 collect_all_info = True
411 # we need access to privileged files, exit if we are not running as root
413 print >>sys.stderr, "Error: ovs-bugtool must be run as root"
417 output_type = 'tar.bz2'
424 (options, params) = getopt.gnu_getopt(
425 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
426 'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
428 except getopt.GetoptError, opterr:
429 print >>sys.stderr, opterr
437 entries = [e for e in caps.keys() if caps[e][CHECKED]]
439 for (k, v) in options:
440 if k == '--capabilities':
441 update_capabilities()
446 if v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
449 print >>sys.stderr, "Invalid output format '%s'" % v
452 # "-s" or "--silent" means suppress output (except for the final
453 # output filename at the end)
454 if k in ['-s', '--silent']:
457 if k == '--entries' and v != '':
458 entries = v.split(',')
460 # If the user runs the script with "-y" or "--yestoall" we don't ask
461 # all the really annoying questions.
462 if k in ['-y', '--yestoall']:
463 ANSWER_YES_TO_ALL = True
468 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
469 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
471 print >>sys.stderr, "Invalid output file descriptor", output_fd
478 entries = caps.keys()
479 elif k == '--unlimited':
480 unlimited_data = True
483 ProcOutput.debug = True
487 collect_all_info = False
490 print >>sys.stderr, "Invalid additional arguments", str(params)
493 if output_fd != -1 and output_type != 'tar':
494 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
497 if output_fd != -1 and output_file is not None:
498 print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
501 if ANSWER_YES_TO_ALL:
502 output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
505 This application will collate dmesg output, details of the
506 hardware configuration of your machine, information about the build of
507 openvswitch that you are using, plus, if you allow it, various logs.
509 The collated information will be saved as a .%s for archiving or
510 sending to a Technical Support Representative.
512 The logs may contain private information, and if you are at all
513 worried about that, you should exit now, or you should explicitly
514 exclude those logs from the archive.
518 # assemble potential data
520 file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
521 cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
522 cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
524 tree_output(CAP_COLLECTD_LOGS, COLLECTD_LOGS_DIR)
525 cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
526 file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
527 file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
528 cmd_output(CAP_DISK_INFO, [DF, '-alT'])
529 cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
530 for d in disk_list():
531 cmd_output(CAP_DISK_INFO, [HDPARM, '-I', '/dev/%s' % d])
532 if len(pidof('iscsid')) != 0:
533 cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
534 cmd_output(CAP_DISK_INFO, [VGSCAN])
535 cmd_output(CAP_DISK_INFO, [PVS])
536 cmd_output(CAP_DISK_INFO, [VGS])
537 cmd_output(CAP_DISK_INFO, [LVS])
538 file_output(CAP_DISK_INFO, [LVM_CACHE, LVM_CONFIG])
539 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
540 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
541 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
542 cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
543 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
544 cmd_output(CAP_DISK_INFO, [LVDISPLAY, '--map'])
546 file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
547 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
548 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
549 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
550 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
551 file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
552 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
555 for d in disk_list():
556 cmd_output(CAP_HDPARM_T, [HDPARM, '-tT', '/dev/%s' % d])
558 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
559 PROC_FILESYSTEMS, PROC_CMDLINE])
560 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
561 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
562 file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
563 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
564 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
566 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
568 file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
569 cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
570 func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
571 cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
572 if CAP_MULTIPATH in entries and collect_all_info:
573 dump_rdac_groups(CAP_MULTIPATH)
575 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
576 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
577 file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
578 file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
579 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_CONF_DB])
581 cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
582 cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
583 cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
584 cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
585 for dir in DHCP_LEASE_DIR:
586 tree_output(CAP_NETWORK_STATUS, dir)
587 cmd_output(CAP_NETWORK_STATUS, [BRCTL, 'show'])
588 cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
589 for p in os.listdir('/sys/class/net/'):
591 f = open('/sys/class/net/%s/type' % p, 'r')
594 if os.path.islink('/sys/class/net/%s/device' % p) and int(t) == 1:
596 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
597 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
598 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
599 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
600 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
602 cmd_output(CAP_NETWORK_STATUS,
603 [TC, '-s', '-d', 'class', 'show', 'dev', p])
606 tree_output(CAP_NETWORK_STATUS, PROC_NET_BONDING_DIR)
607 tree_output(CAP_NETWORK_STATUS, PROC_NET_VLAN_DIR)
608 cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
609 file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
610 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
611 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
613 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
614 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
615 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
617 vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
618 vspid = int(vspidfile.readline().strip())
620 for b in bond_list(vspid):
621 cmd_output(CAP_NETWORK_STATUS,
622 [OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
623 'ovs-appctl-bond-show-%s.out' % b)
627 tree_output(CAP_PAM, PAM_DIR)
628 file_output(CAP_PAM, [KRB5_CONF])
630 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
631 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
633 logs = ([ VAR_LOG_DIR + x for x in
634 [ 'crit.log', 'kern.log', 'daemon.log', 'user.log',
635 'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot' ]]
636 + [ OPENVSWITCH_LOG_DIR + x for x in
637 [ 'ovs-vswitchd.log', 'ovs-brcompatd.log', 'ovsdb-server.log',
638 'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log' ]])
639 file_output(CAP_SYSTEM_LOGS, logs)
640 file_output(CAP_SYSTEM_LOGS,
641 [ '%s.%d' % (f, n) for n in range(20) for f in logs ])
642 file_output(CAP_SYSTEM_LOGS,
643 [ '%s.%d.gz' % (f, n) for n in range(20) for f in logs ])
645 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
646 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
648 cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
650 tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
651 tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
652 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
654 file_output(CAP_YUM, [YUM_LOG])
655 tree_output(CAP_YUM, YUM_REPOS_DIR)
656 cmd_output(CAP_YUM, [RPM, '-qa'])
657 file_output(CAP_YUM, [APT_SOURCES_LIST])
658 tree_output(CAP_YUM, APT_SOURCES_LIST_D)
659 cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
661 # Filter out ovs relevant information if --ovs option passed
662 # else collect all information
666 ovs_info_caps = [CAP_NETWORK_STATUS, CAP_SYSTEM_LOGS,
668 ovs_info_list = ['process-tree']
669 # We cannot use iteritems, since we modify 'data' as we pass through
670 for (k, v) in data.items():
676 if info not in ovs_info_list and cap not in ovs_info_caps:
680 filter = ",".join(filters)
685 load_plugins(filter=filter)
689 # permit the user to filter out data
690 # We cannot use iteritems, since we modify 'data' as we pass through
691 for (k, v) in sorted(data.items()):
697 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % key):
700 # collect selected data now
701 output_ts('Running commands to collect data')
704 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
707 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
711 if output_file is None:
714 dirname = os.path.dirname(output_file)
715 if dirname and not os.path.exists(dirname):
722 output_ts('Creating output file')
724 if output_type.startswith('tar'):
725 make_tar(subdir, output_type, output_fd, output_file)
727 make_zip(subdir, output_file)
732 print >>sys.stderr, "Category sizes (max, actual):\n"
733 for c in caps.keys():
734 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
738 def find_tapdisk_logs():
739 return glob.glob('/var/log/blktap/*.log*')
741 def generate_tapdisk_logs():
742 for pid in pidof('tapdisk'):
744 os.kill(pid, SIGUSR1)
745 output_ts("Including logs for tapdisk process %d" % pid)
748 # give processes a second to write their logs
751 def clean_tapdisk_logs():
752 for filename in find_tapdisk_logs():
758 def filter_db_pii(str, state):
759 if 'in_secret_table' not in state:
760 state['in_secret_table'] = False
762 if str.startswith('<table ') and 'name="secret"' in str:
763 state['in_secret_table'] = True
764 elif str.startswith('</table>'):
765 state['in_secret_table'] = False
767 if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
768 str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
771 def dump_scsi_hosts(cap):
773 l = os.listdir('/sys/class/scsi_host')
779 f = open('/sys/class/scsi_host/%s/proc_name' % h)
780 procname = f.readline().strip("\n")
786 f = open('/sys/class/scsi_host/%s/model_name' % h)
787 modelname = f.readline().strip("\n")
793 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
797 def module_info(cap):
798 output = StringIO.StringIO()
799 modules = open(PROC_MODULES, 'r')
803 module = line.split()[0]
804 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
809 return output.getvalue()
812 def multipathd_topology(cap):
813 pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
814 stdout=PIPE, stderr=dev_null)
815 stdout, stderr = pipe.communicate('show topology')
820 output = StringIO.StringIO()
821 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
825 if not procs[0].timed_out:
826 return output.getvalue().splitlines()
830 output = StringIO.StringIO()
831 procs = [ProcOutput([OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
835 if not procs[0].timed_out:
836 bonds = output.getvalue().splitlines()[1:]
837 return [x.split('\t')[1] for x in bonds]
843 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
845 fh = open('/proc/'+d+'/cmdline')
847 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
849 if not num_fds in fd_dict:
850 fd_dict[num_fds] = []
851 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
854 keys = fd_dict.keys()
855 keys.sort(lambda a, b: int(b) - int(a))
857 output += "%s: %s\n" % (k, str(fd_dict[k]))
860 def dump_rdac_groups(cap):
861 output = StringIO.StringIO()
862 procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
866 if not procs[0].timed_out:
868 for line in output.getvalue().splitlines():
869 if line.startswith('ID'):
871 elif line.startswith('----'):
874 group, _ = line.split(None, 1)
875 cmd_output(cap, [MPPUTIL, '-g', group])
877 def load_plugins(just_capabilities=False, filter=None):
878 def getText(nodelist):
880 for node in nodelist:
881 if node.nodeType == node.TEXT_NODE:
885 def getBoolAttr(el, attr, default=False):
887 val = el.getAttribute(attr).lower()
888 if val in ['true', 'false', 'yes', 'no']:
889 ret = val in ['true', 'yes']
892 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
893 if not caps.has_key(dir):
894 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
896 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
897 assert xmldoc.documentElement.tagName == "capability"
899 pii, min_size, max_size, min_time, max_time, mime = \
900 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
902 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
903 pii = xmldoc.documentElement.getAttribute("pii")
904 if xmldoc.documentElement.getAttribute("min_size") != '':
905 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
906 if xmldoc.documentElement.getAttribute("max_size") != '':
907 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
908 if xmldoc.documentElement.getAttribute("min_time") != '':
909 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
910 if xmldoc.documentElement.getAttribute("max_time") != '':
911 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
912 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
913 mime = xmldoc.documentElement.getAttribute("mime")
914 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
915 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
917 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
919 if just_capabilities:
922 plugdir = os.path.join(PLUGIN_DIR, dir)
923 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
924 xmldoc = parse(os.path.join(plugdir, file))
925 assert xmldoc.documentElement.tagName == "collect"
927 for el in xmldoc.documentElement.getElementsByTagName("*"):
928 filters_tmp = el.getAttribute("filters")
929 if filters_tmp == '':
932 filters = filters_tmp.split(',')
933 if not(filter is None or filter in filters):
935 if el.tagName == "files":
936 newest_first = getBoolAttr(el, 'newest_first')
937 file_output(dir, getText(el.childNodes).split(),
938 newest_first=newest_first)
939 elif el.tagName == "directory":
940 pattern = el.getAttribute("pattern")
941 if pattern == '': pattern = None
942 negate = getBoolAttr(el, 'negate')
943 newest_first = getBoolAttr(el, 'newest_first')
944 tree_output(dir, getText(el.childNodes),
945 pattern and re.compile(pattern) or None,
946 negate=negate, newest_first=newest_first)
947 elif el.tagName == "command":
948 label = el.getAttribute("label")
949 if label == '': label = None
950 cmd_output(dir, getText(el.childNodes), label)
952 def make_tar(subdir, suffix, output_fd, output_file):
953 global SILENT_MODE, data
956 if suffix == 'tar.bz2':
958 elif suffix == 'tar.gz':
962 if output_file is None:
963 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
965 filename = output_file
966 old_umask = os.umask(0077)
967 tf = tarfile.open(filename, mode)
970 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
973 for (k, v) in data.items():
975 tar_filename = os.path.join(subdir, construct_filename(k, v))
976 ti = tarfile.TarInfo(tar_filename)
981 if v.has_key('output'):
982 ti.mtime = v['output'].mtime
983 ti.size = len(v['output'].getvalue())
985 tf.addfile(ti, v['output'])
986 elif v.has_key('filename'):
987 s = os.stat(v['filename'])
988 ti.mtime = s.st_mtime
990 tf.addfile(ti, file(v['filename']))
997 output ('Writing tarball %s successful.' % filename)
1002 def make_zip(subdir, output_file):
1003 global SILENT_MODE, data
1005 if output_file is None:
1006 filename = "%s/%s.zip" % (BUG_DIR, subdir)
1008 filename = output_file
1009 old_umask = os.umask(0077)
1010 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
1014 for (k, v) in data.items():
1016 dest = os.path.join(subdir, construct_filename(k, v))
1018 if v.has_key('output'):
1019 zf.writestr(dest, v['output'].getvalue())
1021 if os.stat(v['filename']).st_size < 50:
1022 compress_type = zipfile.ZIP_STORED
1024 compress_type = zipfile.ZIP_DEFLATED
1025 zf.write(v['filename'], dest, compress_type)
1031 output ('Writing archive %s successful.' % filename)
1036 def make_inventory(inventory, subdir):
1037 document = getDOMImplementation().createDocument(
1038 None, INVENTORY_XML_ROOT, None)
1040 # create summary entry
1041 s = document.createElement(INVENTORY_XML_SUMMARY)
1042 user = os.getenv('SUDO_USER', os.getenv('USER'))
1044 s.setAttribute('user', user)
1045 s.setAttribute('date', time.strftime('%c'))
1046 s.setAttribute('hostname', platform.node())
1047 s.setAttribute('uname', ' '.join(platform.uname()))
1048 s.setAttribute('uptime', commands.getoutput(UPTIME))
1049 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
1051 map(lambda (k, v): inventory_entry(document, subdir, k, v),
1053 return document.toprettyxml()
1055 def inventory_entry(document, subdir, k, v):
1057 el = document.createElement(INVENTORY_XML_ELEMENT)
1058 el.setAttribute('capability', v['cap'])
1059 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
1060 el.setAttribute('md5sum', md5sum(v))
1061 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
1068 if d.has_key('filename'):
1069 f = open(d['filename'])
1071 while len(data) > 0:
1075 elif d.has_key('output'):
1076 m.update(d['output'].getvalue())
1077 return m.hexdigest()
1080 def construct_filename(k, v):
1081 if v.has_key('filename'):
1082 if v['filename'][0] == '/':
1083 return v['filename'][1:]
1085 return v['filename']
1086 s = k.replace(' ', '-')
1087 s = s.replace('--', '-')
1088 s = s.replace('/', '%')
1089 if s.find('.') == -1:
1094 def update_capabilities():
1097 def update_cap_size(cap, size):
1098 update_cap(cap, MIN_SIZE, size)
1099 update_cap(cap, MAX_SIZE, size)
1100 update_cap(cap, CHECKED, size > 0)
1103 def update_cap(cap, k, v):
1107 caps[cap] = tuple(l)
1110 def size_of_dir(d, pattern=None, negate=False):
1111 if os.path.isdir(d):
1112 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1118 def size_of_all(files, pattern=None, negate=False):
1119 return sum([size_of(f, pattern, negate) for f in files])
1122 def matches(f, pattern, negate):
1124 return not matches(f, pattern, False)
1126 return pattern is None or pattern.match(f)
1129 def size_of(f, pattern, negate):
1130 if os.path.isfile(f) and matches(f, pattern, negate):
1131 return os.stat(f)[6]
1133 return size_of_dir(f, pattern, negate)
1136 def print_capabilities():
1137 document = getDOMImplementation().createDocument(
1138 "ns", CAP_XML_ROOT, None)
1139 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1140 print document.toprettyxml()
1142 def capability(document, key):
1144 el = document.createElement(CAP_XML_ELEMENT)
1145 el.setAttribute('key', c[KEY])
1146 el.setAttribute('pii', c[PII])
1147 el.setAttribute('min-size', str(c[MIN_SIZE]))
1148 el.setAttribute('max-size', str(c[MAX_SIZE]))
1149 el.setAttribute('min-time', str(c[MIN_TIME]))
1150 el.setAttribute('max-time', str(c[MAX_TIME]))
1151 el.setAttribute('content-type', c[MIME])
1152 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1153 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1157 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1158 return '\n'.join([format % i for i in d.items()]) + '\n'
1162 yn = raw_input(prompt)
1164 return len(yn) == 0 or yn.lower()[0] == 'y'
1167 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1172 f = open('/proc/partitions')
1175 for line in f.readlines():
1176 (major, minor, blocks, name) = line.split()
1177 if int(major) < 254 and not partition_re.match(name):
1188 def __init__(self, command, max_time, inst=None, filter=None):
1189 self.command = command
1190 self.max_time = max_time
1192 self.running = False
1194 self.timed_out = False
1196 self.timeout = int(time.time()) + self.max_time
1197 self.filter = filter
1198 self.filter_state = {}
1204 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1207 self.timed_out = False
1209 if ProcOutput.debug:
1210 output_ts("Starting '%s'" % self.cmdAsStr())
1211 self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1212 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1213 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1217 output_ts("'%s' failed" % self.cmdAsStr())
1218 self.running = False
1221 def terminate(self):
1224 self.proc.stdout.close()
1225 os.kill(self.proc.pid, SIGTERM)
1229 self.running = False
1230 self.status = SIGTERM
1232 def read_line(self):
1234 line = self.proc.stdout.readline()
1237 self.proc.stdout.close()
1238 self.status = self.proc.wait()
1240 self.running = False
1243 line = self.filter(line, self.filter_state)
1245 self.inst.write(line)
1247 def run_procs(procs):
1255 active_procs.append(p)
1256 pipes.append(p.proc.stdout)
1258 elif p.status == None and not p.failed and not p.timed_out:
1261 active_procs.append(p)
1262 pipes.append(p.proc.stdout)
1269 (i, o, x) = select(pipes, [], [], 1.0)
1270 now = int(time.time())
1272 # handle process output
1273 for p in active_procs:
1274 if p.proc.stdout in i:
1278 if p.running and now > p.timeout:
1279 output_ts("'%s' timed out" % p.cmdAsStr())
1281 p.inst.write("\n** timeout **\n")
1289 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1291 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1299 class StringIOmtime(StringIO.StringIO):
1300 def __init__(self, buf=''):
1301 StringIO.StringIO.__init__(self, buf)
1302 self.mtime = time.time()
1305 StringIO.StringIO.write(self, s)
1306 self.mtime = time.time()
1309 if __name__ == "__main__":
1312 except KeyboardInterrupt:
1313 print "\nInterrupted."