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, 2013, 2015 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 PROC_CPUINFO = '/proc/cpuinfo'
80 PROC_MEMINFO = '/proc/meminfo'
81 PROC_IOPORTS = '/proc/ioports'
82 PROC_INTERRUPTS = '/proc/interrupts'
83 PROC_SCSI = '/proc/scsi/scsi'
84 PROC_VERSION = '/proc/version'
85 PROC_MODULES = '/proc/modules'
86 PROC_DEVICES = '/proc/devices'
87 PROC_FILESYSTEMS = '/proc/filesystems'
88 PROC_CMDLINE = '/proc/cmdline'
89 PROC_CONFIG = '/proc/config.gz'
90 PROC_USB_DEV = '/proc/bus/usb/devices'
91 PROC_NET_BONDING_DIR = '/proc/net/bonding'
92 IFCFG_RE = re.compile(r'^.*/ifcfg-.*')
93 ROUTE_RE = re.compile(r'^.*/route-.*')
94 SYSCONFIG_HWCONF = '/etc/sysconfig/hwconf'
95 SYSCONFIG_NETWORK = '/etc/sysconfig/network'
96 SYSCONFIG_NETWORK_SCRIPTS = '/etc/sysconfig/network-scripts'
97 PROC_NET_VLAN_DIR = '/proc/net/vlan'
98 PROC_NET_SOFTNET_STAT = '/proc/net/softnet_stat'
99 MODPROBE_CONF = '/etc/modprobe.conf'
100 MODPROBE_DIR = '/etc/modprobe.d'
101 RESOLV_CONF = '/etc/resolv.conf'
102 MPP_CONF = '/etc/mpp.conf'
103 MULTIPATH_CONF = '/etc/multipath.conf'
104 NSSWITCH_CONF = '/etc/nsswitch.conf'
105 NTP_CONF = '/etc/ntp.conf'
106 IPTABLES_CONFIG = '/etc/sysconfig/iptables-config'
108 HOSTS_ALLOW = '/etc/hosts.allow'
109 HOSTS_DENY = '/etc/hosts.deny'
110 DHCP_LEASE_DIR = ['/var/lib/dhclient', '/var/lib/dhcp3']
111 OPENVSWITCH_LOG_DIR = '@LOGDIR@/'
112 OPENVSWITCH_DEFAULT_SWITCH = '/etc/default/openvswitch-switch' # Debian
113 OPENVSWITCH_SYSCONFIG_SWITCH = '/etc/sysconfig/openvswitch' # RHEL
114 OPENVSWITCH_CONF_DB = '@DBDIR@/conf.db'
115 OPENVSWITCH_COMPACT_DB = '@DBDIR@/bugtool-compact-conf.db'
116 OPENVSWITCH_VSWITCHD_PID = '@RUNDIR@/ovs-vswitchd.pid'
117 VAR_LOG_DIR = '/var/log/'
118 VAR_LOG_CORE_DIR = '/var/log/core'
119 YUM_LOG = '/var/log/yum.log'
120 YUM_REPOS_DIR = '/etc/yum.repos.d'
126 os.environ['PATH'] = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:@pkgdatadir@/scripts'
129 CHKCONFIG = 'chkconfig'
132 DMIDECODE = 'dmidecode'
134 DPKG_QUERY = 'dpkg-query'
138 IFCONFIG = 'ifconfig'
139 IPTABLES = 'iptables'
140 ISCSIADM = 'iscsiadm'
147 MULTIPATHD = 'multipathd'
149 OVS_DPCTL = 'ovs-dpctl'
150 OVS_OFCTL = 'ovs-ofctl'
151 OVS_VSCTL = 'ovs-vsctl'
162 # PII -- Personally identifiable information. Of particular concern are
163 # things that would identify customers, or their network topology.
164 # Passwords are never to be included in any bug report, regardless of any PII
167 # NO -- No PII will be in these entries.
168 # YES -- PII will likely or certainly be in these entries.
169 # MAYBE -- The user may wish to audit these entries for PII.
170 # IF_CUSTOMIZED -- If the files are unmodified, then they will contain no PII,
171 # but since we encourage customers to edit these files, PII may have been
172 # introduced by the customer. This is used in particular for the networking
179 PII_IF_CUSTOMIZED = 'if_customized'
190 MIME_DATA = 'application/data'
191 MIME_TEXT = 'text/plain'
193 INVENTORY_XML_ROOT = "system-status-inventory"
194 INVENTORY_XML_SUMMARY = 'system-summary'
195 INVENTORY_XML_ELEMENT = 'inventory-entry'
196 CAP_XML_ROOT = "system-status-capabilities"
197 CAP_XML_ELEMENT = 'capability'
200 CAP_BOOT_LOADER = 'boot-loader'
201 CAP_DISK_INFO = 'disk-info'
202 CAP_HARDWARE_INFO = 'hardware-info'
203 CAP_KERNEL_INFO = 'kernel-info'
204 CAP_LOSETUP_A = 'loopback-devices'
205 CAP_MULTIPATH = 'multipath'
206 CAP_NETWORK_CONFIG = 'network-config'
207 CAP_NETWORK_INFO = 'network-info'
208 CAP_NETWORK_STATUS = 'network-status'
209 CAP_OPENVSWITCH_LOGS = 'ovs-system-logs'
210 CAP_PROCESS_LIST = 'process-list'
211 CAP_SYSTEM_LOGS = 'system-logs'
212 CAP_SYSTEM_SERVICES = 'system-services'
220 unlimited_data = False
222 # Default value for the number of days to collect logs.
224 log_last_mod_time = None
225 free_disk_space = None
227 def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
228 max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
229 caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
234 cap(CAP_BOOT_LOADER, PII_NO, max_size=3*KB,
236 cap(CAP_DISK_INFO, PII_MAYBE, max_size=50*KB,
238 cap(CAP_HARDWARE_INFO, PII_MAYBE, max_size=2*MB,
240 cap(CAP_KERNEL_INFO, PII_MAYBE, max_size=120*KB,
242 cap(CAP_LOSETUP_A, PII_MAYBE, max_size=KB, max_time=5)
243 cap(CAP_MULTIPATH, PII_MAYBE, max_size=20*KB,
245 cap(CAP_NETWORK_CONFIG, PII_IF_CUSTOMIZED,
246 min_size=0, max_size=5*MB)
247 cap(CAP_NETWORK_INFO, PII_YES, max_size=50*MB,
249 cap(CAP_NETWORK_STATUS, PII_YES, max_size=-1,
251 cap(CAP_OPENVSWITCH_LOGS, PII_MAYBE, max_size=-1,
253 cap(CAP_PROCESS_LIST, PII_YES, max_size=30*KB,
255 cap(CAP_SYSTEM_LOGS, PII_MAYBE, max_size=200*MB,
257 cap(CAP_SYSTEM_SERVICES, PII_NO, max_size=5*KB,
259 cap(CAP_YUM, PII_IF_CUSTOMIZED, max_size=10*KB,
262 ANSWER_YES_TO_ALL = False
266 dev_null = open('/dev/null', 'r+')
274 output("[%s] %s" % (time.strftime("%x %X %Z"), x))
276 def cmd_output(cap, args, label=None, filter=None, binary=False):
279 if isinstance(args, list):
280 a = [aa for aa in args]
281 a[0] = os.path.basename(a[0])
285 data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter,
289 def file_output(cap, path_list, newest_first=False, last_mod_time=None):
291 If newest_first is True, the list of files in path_list is sorted
292 by file modification time in descending order, else its sorted
297 for path in path_list:
302 if last_mod_time is None or s.st_mtime >= last_mod_time:
303 path_entries.append((path, s))
305 mtime = lambda(path, stat): stat.st_mtime
306 path_entries.sort(key=mtime, reverse=newest_first)
307 for p in path_entries:
308 if check_space(cap, p[0], p[1].st_size):
309 data[p] = {'cap': cap, 'filename': p[0]}
312 def tree_output(cap, path, pattern=None, negate=False, newest_first=False,
315 Walks the directory tree rooted at path. Files in current dir are processed
316 before files in sub-dirs.
319 if os.path.exists(path):
320 for root, dirs, files in os.walk(path):
321 fns = [fn for fn in [os.path.join(root, f) for f in files]
322 if os.path.isfile(fn) and matches(fn, pattern, negate)]
323 file_output(cap, fns, newest_first=newest_first,
324 last_mod_time=last_mod_time)
327 def prefix_output(cap, prefix, newest_first=False, last_mod_time=None):
329 Output files with the same prefix.
332 for root, dirs, files in os.walk(os.path.dirname(prefix)):
333 fns += [fn for fn in [os.path.join(root, f) for f in files]
334 if fn.startswith(prefix)]
335 file_output(cap, fns, newest_first=newest_first,
336 last_mod_time=last_mod_time)
339 def func_output(cap, label, func):
341 t = str(func).split()
342 data[label] = {'cap': cap, 'func': func}
347 for (k, v) in data.items():
349 if v.has_key('cmd_args'):
350 v['output'] = StringIOmtime()
351 if not process_lists.has_key(cap):
352 process_lists[cap] = []
353 process_lists[cap].append(
354 ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'],
355 v['filter'], v['binary']))
356 elif v.has_key('filename') and v['filename'].startswith('/proc/'):
357 # proc files must be read into memory
359 f = open(v['filename'], 'r')
362 if check_space(cap, v['filename'], len(s)):
363 v['output'] = StringIOmtime(s)
366 elif v.has_key('func'):
371 if check_space(cap, k, len(s)):
372 v['output'] = StringIOmtime(s)
374 run_procs(process_lists.values())
378 global ANSWER_YES_TO_ALL, SILENT_MODE
379 global entries, data, dbg, unlimited_data, free_disk_space
380 global log_days, log_last_mod_time
383 only_ovs_info = False
384 collect_all_info = True
386 if '--help' in sys.argv:
388 %(argv0)s: create status report bundles to assist in problem diagnosis
389 usage: %(argv0)s OPTIONS
391 By default, %(argv0)s prompts for permission to collect each form of status
392 information and produces a .tar.gz file as output.
394 The following options are available.
395 --help display this help message, then exit
396 -s, --silent suppress most output to stdout
398 Options for categories of data to collect:
399 --entries=CAP_A,CAP_B,... set categories of data to collect
400 --all collect all categories
401 --ovs collect only directly OVS-related info
402 --log-days=DAYS collect DAYS worth of old logs
403 -y, --yestoall suppress prompts to confirm collection
404 --capabilities print categories as XML on stdout, then exit
407 --output=FORMAT set output format to one of tar tar.bz2 tar.gz zip
408 --outfile=FILE write output to FILE
409 --outfd=FD write output to FD (requires --output=tar)
410 --unlimited ignore default limits on sizes of data collected
411 --debug print ovs-bugtool debug info on stdout\
412 """ % {'argv0': sys.argv[0]}
415 # we need access to privileged files, exit if we are not running as root
417 print >>sys.stderr, "Error: ovs-bugtool must be run as root"
421 output_type = 'tar.gz'
428 (options, params) = getopt.gnu_getopt(
429 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
430 'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
431 'debug', 'ovs', 'log-days='])
432 except getopt.GetoptError, opterr:
433 print >>sys.stderr, opterr
441 entries = [e for e in caps.keys() if caps[e][CHECKED]]
443 for (k, v) in options:
444 if k == '--capabilities':
445 update_capabilities()
450 if v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
453 print >>sys.stderr, "Invalid output format '%s'" % v
456 # "-s" or "--silent" means suppress output (except for the final
457 # output filename at the end)
458 if k in ['-s', '--silent']:
461 if k == '--entries' and v != '':
462 entries = v.split(',')
464 # If the user runs the script with "-y" or "--yestoall" we don't ask
465 # all the really annoying questions.
466 if k in ['-y', '--yestoall']:
467 ANSWER_YES_TO_ALL = True
472 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
473 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
475 print >>sys.stderr, "Invalid output file descriptor", output_fd
482 entries = caps.keys()
483 elif k == '--unlimited':
484 unlimited_data = True
487 ProcOutput.debug = True
491 collect_all_info = False
493 if k == '--log-days':
498 print >>sys.stderr, "Invalid additional arguments", str(params)
501 if output_fd != -1 and output_type != 'tar':
502 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
505 if output_fd != -1 and output_file is not None:
506 print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
509 if output_file is not None and not unlimited_data:
510 free_disk_space = get_free_disk_space(output_file) * 90 / 100
512 log_last_mod_time = int(time.time()) - log_days * 86400
514 if ANSWER_YES_TO_ALL:
515 output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
518 This application will collate dmesg output, details of the
519 hardware configuration of your machine, information about the build of
520 openvswitch that you are using, plus, if you allow it, various logs.
522 The collated information will be saved as a .%s for archiving or
523 sending to a Technical Support Representative.
525 The logs may contain private information, and if you are at all
526 worried about that, you should exit now, or you should explicitly
527 exclude those logs from the archive.
531 # assemble potential data
533 file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
534 cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
535 cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
537 cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
538 file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
539 file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
540 cmd_output(CAP_DISK_INFO, [DF, '-alT'])
541 cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
542 if len(pidof('iscsid')) != 0:
543 cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
544 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
545 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
546 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
547 cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
548 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
550 file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
551 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
552 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
553 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
554 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
555 file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
556 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
559 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
560 PROC_FILESYSTEMS, PROC_CMDLINE])
561 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
562 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
563 file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
564 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
565 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
567 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
569 file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
570 cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
571 func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
572 cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
573 if CAP_MULTIPATH in entries and collect_all_info:
574 dump_rdac_groups(CAP_MULTIPATH)
576 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
577 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
578 file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
579 file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
580 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_DEFAULT_SWITCH,
581 OPENVSWITCH_SYSCONFIG_SWITCH])
583 cmd_output(CAP_NETWORK_INFO, [IFCONFIG, '-a'])
584 cmd_output(CAP_NETWORK_INFO, [ROUTE, '-n'])
585 cmd_output(CAP_NETWORK_INFO, [ARP, '-n'])
586 cmd_output(CAP_NETWORK_INFO, [NETSTAT, '-an'])
587 for dir in DHCP_LEASE_DIR:
588 tree_output(CAP_NETWORK_INFO, dir)
589 for table in ['filter', 'nat', 'mangle', 'raw', 'security']:
590 cmd_output(CAP_NETWORK_INFO, [IPTABLES, '-t', table, '-nL'])
591 for p in os.listdir('/sys/class/net/'):
593 f = open('/sys/class/net/%s/type' % p, 'r')
596 if os.path.islink('/sys/class/net/%s/device' % p) and int(t) == 1:
598 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-S', p])
599 if not p.startswith('vif') and not p.startswith('tap'):
600 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, p])
601 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-k', p])
602 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-i', p])
603 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-c', p])
605 cmd_output(CAP_NETWORK_INFO,
606 [TC, '-s', '-d', 'class', 'show', 'dev', p])
609 tree_output(CAP_NETWORK_INFO, PROC_NET_BONDING_DIR)
610 tree_output(CAP_NETWORK_INFO, PROC_NET_VLAN_DIR)
611 cmd_output(CAP_NETWORK_INFO, [TC, '-s', 'qdisc'])
612 file_output(CAP_NETWORK_INFO, [PROC_NET_SOFTNET_STAT])
615 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
616 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
618 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', '-m', d])
620 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
621 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
623 system_logs = ([ VAR_LOG_DIR + x for x in
624 ['crit.log', 'kern.log', 'daemon.log', 'user.log',
625 'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot']])
626 for log in system_logs:
627 prefix_output(CAP_SYSTEM_LOGS, log, last_mod_time=log_last_mod_time)
629 ovs_logs = ([ OPENVSWITCH_LOG_DIR + x for x in
630 ['ovs-vswitchd.log', 'ovsdb-server.log',
631 'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log', 'ovs-ctl.log']])
633 prefix_output(CAP_OPENVSWITCH_LOGS, log, last_mod_time=log_last_mod_time)
635 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
636 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
638 cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
640 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
642 file_output(CAP_YUM, [YUM_LOG])
643 tree_output(CAP_YUM, YUM_REPOS_DIR)
644 cmd_output(CAP_YUM, [RPM, '-qa'])
645 file_output(CAP_YUM, [APT_SOURCES_LIST])
646 tree_output(CAP_YUM, APT_SOURCES_LIST_D)
647 cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
649 # Filter out ovs relevant information if --ovs option passed
650 # else collect all information
654 ovs_info_caps = [CAP_NETWORK_STATUS, CAP_SYSTEM_LOGS,
656 ovs_info_list = ['process-tree']
657 # We cannot use iteritems, since we modify 'data' as we pass through
658 for (k, v) in data.items():
664 if info not in ovs_info_list and cap not in ovs_info_caps:
668 filter = ",".join(filters)
673 load_plugins(filter=filter)
677 # permit the user to filter out data
678 # We cannot use iteritems, since we modify 'data' as we pass through
679 for (k, v) in sorted(data.items()):
685 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % key):
688 # collect selected data now
689 output_ts('Running commands to collect data')
692 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
695 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
699 if output_file is None:
702 dirname = os.path.dirname(output_file)
703 if dirname and not os.path.exists(dirname):
710 output_ts('Creating output file')
712 if output_type.startswith('tar'):
713 make_tar(subdir, output_type, output_fd, output_file)
715 make_zip(subdir, output_file)
718 print >>sys.stderr, "Category sizes (max, actual):\n"
719 for c in caps.keys():
720 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
726 def dump_scsi_hosts(cap):
728 l = os.listdir('/sys/class/scsi_host')
734 f = open('/sys/class/scsi_host/%s/proc_name' % h)
735 procname = f.readline().strip("\n")
741 f = open('/sys/class/scsi_host/%s/model_name' % h)
742 modelname = f.readline().strip("\n")
748 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
752 def module_info(cap):
753 output = StringIO.StringIO()
754 modules = open(PROC_MODULES, 'r')
758 module = line.split()[0]
759 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
764 return output.getvalue()
767 def multipathd_topology(cap):
768 pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
769 stdout=PIPE, stderr=dev_null)
770 stdout, stderr = pipe.communicate('show topology')
775 output = StringIO.StringIO()
776 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
780 if not procs[0].timed_out:
781 return output.getvalue().splitlines()
785 if not os.path.isfile(OPENVSWITCH_CONF_DB):
791 if os.path.getsize(OPENVSWITCH_CONF_DB) > max_size:
792 if os.path.isfile(OPENVSWITCH_COMPACT_DB):
793 os.unlink(OPENVSWITCH_COMPACT_DB)
795 output = StringIO.StringIO()
797 procs = [ProcOutput(['ovsdb-tool', 'compact',
798 OPENVSWITCH_CONF_DB, OPENVSWITCH_COMPACT_DB],
801 file_output(CAP_NETWORK_STATUS, [OPENVSWITCH_COMPACT_DB])
803 file_output(CAP_NETWORK_STATUS, [OPENVSWITCH_CONF_DB])
809 if os.path.isfile(OPENVSWITCH_COMPACT_DB):
810 os.unlink(OPENVSWITCH_COMPACT_DB)
817 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
819 fh = open('/proc/'+d+'/cmdline')
821 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
823 if not num_fds in fd_dict:
824 fd_dict[num_fds] = []
825 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
828 keys = fd_dict.keys()
829 keys.sort(lambda a, b: int(b) - int(a))
831 output += "%s: %s\n" % (k, str(fd_dict[k]))
834 def dump_rdac_groups(cap):
835 output = StringIO.StringIO()
836 procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
840 if not procs[0].timed_out:
842 for line in output.getvalue().splitlines():
843 if line.startswith('ID'):
845 elif line.startswith('----'):
848 group, _ = line.split(None, 1)
849 cmd_output(cap, [MPPUTIL, '-g', group])
851 def load_plugins(just_capabilities=False, filter=None):
852 global log_last_mod_time
853 def getText(nodelist):
855 for node in nodelist:
856 if node.nodeType == node.TEXT_NODE:
860 def getBoolAttr(el, attr, default=False):
862 val = el.getAttribute(attr).lower()
863 if val in ['true', 'false', 'yes', 'no']:
864 ret = val in ['true', 'yes']
867 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
868 if not caps.has_key(dir):
869 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
871 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
872 assert xmldoc.documentElement.tagName == "capability"
874 pii, min_size, max_size, min_time, max_time, mime = \
875 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
877 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
878 pii = xmldoc.documentElement.getAttribute("pii")
879 if xmldoc.documentElement.getAttribute("min_size") != '':
880 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
881 if xmldoc.documentElement.getAttribute("max_size") != '':
882 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
883 if xmldoc.documentElement.getAttribute("min_time") != '':
884 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
885 if xmldoc.documentElement.getAttribute("max_time") != '':
886 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
887 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
888 mime = xmldoc.documentElement.getAttribute("mime")
889 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
890 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
892 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
894 if just_capabilities:
897 plugdir = os.path.join(PLUGIN_DIR, dir)
898 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
899 xmldoc = parse(os.path.join(plugdir, file))
900 assert xmldoc.documentElement.tagName == "collect"
902 for el in xmldoc.documentElement.getElementsByTagName("*"):
903 filters_tmp = el.getAttribute("filters")
904 if filters_tmp == '':
907 filters = filters_tmp.split(',')
908 if not(filter is None or filter in filters):
910 if el.tagName == "files":
911 newest_first = getBoolAttr(el, 'newest_first')
912 if el.getAttribute("type") == "logs":
913 for fn in getText(el.childNodes).split():
914 prefix_output(dir, fn, newest_first=newest_first,
915 last_mod_time=log_last_mod_time)
917 file_output(dir, getText(el.childNodes).split(),
918 newest_first=newest_first)
919 elif el.tagName == "directory":
920 pattern = el.getAttribute("pattern")
921 if pattern == '': pattern = None
922 negate = getBoolAttr(el, 'negate')
923 newest_first = getBoolAttr(el, 'newest_first')
924 if el.getAttribute("type") == "logs":
925 tree_output(dir, getText(el.childNodes),
926 pattern and re.compile(pattern) or None,
927 negate=negate, newest_first=newest_first,
928 last_mod_time=log_last_mod_time)
930 tree_output(dir, getText(el.childNodes),
931 pattern and re.compile(pattern) or None,
932 negate=negate, newest_first=newest_first)
933 elif el.tagName == "command":
934 label = el.getAttribute("label")
935 if label == '': label = None
936 binary = getBoolAttr(el, 'binary')
937 cmd_output(dir, getText(el.childNodes), label, binary=binary)
939 def make_tar(subdir, suffix, output_fd, output_file):
940 global SILENT_MODE, data
943 if suffix == 'tar.bz2':
945 elif suffix == 'tar.gz':
949 if output_file is None:
950 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
952 filename = output_file
953 old_umask = os.umask(0077)
954 tf = tarfile.open(filename, mode)
957 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
960 for (k, v) in data.items():
962 tar_filename = os.path.join(subdir, construct_filename(k, v))
963 ti = tarfile.TarInfo(tar_filename)
968 if v.has_key('output'):
969 ti.mtime = v['output'].mtime
970 ti.size = len(v['output'].getvalue())
972 tf.addfile(ti, v['output'])
973 elif v.has_key('filename'):
974 s = os.stat(v['filename'])
975 ti.mtime = s.st_mtime
977 tf.addfile(ti, file(v['filename']))
984 output ('Writing tarball %s successful.' % filename)
989 def make_zip(subdir, output_file):
990 global SILENT_MODE, data
992 if output_file is None:
993 filename = "%s/%s.zip" % (BUG_DIR, subdir)
995 filename = output_file
996 old_umask = os.umask(0077)
997 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
1001 for (k, v) in data.items():
1003 dest = os.path.join(subdir, construct_filename(k, v))
1005 if v.has_key('output'):
1006 zf.writestr(dest, v['output'].getvalue())
1008 if os.stat(v['filename']).st_size < 50:
1009 compress_type = zipfile.ZIP_STORED
1011 compress_type = zipfile.ZIP_DEFLATED
1012 zf.write(v['filename'], dest, compress_type)
1018 output ('Writing archive %s successful.' % filename)
1023 def make_inventory(inventory, subdir):
1024 document = getDOMImplementation().createDocument(
1025 None, INVENTORY_XML_ROOT, None)
1027 # create summary entry
1028 s = document.createElement(INVENTORY_XML_SUMMARY)
1029 user = os.getenv('SUDO_USER', os.getenv('USER'))
1031 s.setAttribute('user', user)
1032 s.setAttribute('date', time.strftime('%c'))
1033 s.setAttribute('hostname', platform.node())
1034 s.setAttribute('uname', ' '.join(platform.uname()))
1035 s.setAttribute('uptime', commands.getoutput(UPTIME))
1036 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
1038 map(lambda (k, v): inventory_entry(document, subdir, k, v),
1040 return document.toprettyxml()
1042 def inventory_entry(document, subdir, k, v):
1044 el = document.createElement(INVENTORY_XML_ELEMENT)
1045 el.setAttribute('capability', v['cap'])
1046 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
1047 el.setAttribute('md5sum', md5sum(v))
1048 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
1055 if d.has_key('filename'):
1056 f = open(d['filename'])
1058 while len(data) > 0:
1062 elif d.has_key('output'):
1063 m.update(d['output'].getvalue())
1064 return m.hexdigest()
1067 def construct_filename(k, v):
1068 if v.has_key('filename'):
1069 if v['filename'][0] == '/':
1070 return v['filename'][1:]
1072 return v['filename']
1073 s = k.replace(' ', '-')
1074 s = s.replace('--', '-')
1075 s = s.replace('/', '%')
1076 if s.find('.') == -1:
1081 def update_capabilities():
1084 def update_cap_size(cap, size):
1085 update_cap(cap, MIN_SIZE, size)
1086 update_cap(cap, MAX_SIZE, size)
1087 update_cap(cap, CHECKED, size > 0)
1090 def update_cap(cap, k, v):
1094 caps[cap] = tuple(l)
1097 def size_of_dir(d, pattern=None, negate=False):
1098 if os.path.isdir(d):
1099 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1105 def size_of_all(files, pattern=None, negate=False):
1106 return sum([size_of(f, pattern, negate) for f in files])
1109 def matches(f, pattern, negate):
1111 return not matches(f, pattern, False)
1113 return pattern is None or pattern.match(f)
1116 def size_of(f, pattern, negate):
1117 if os.path.isfile(f) and matches(f, pattern, negate):
1118 return os.stat(f)[6]
1120 return size_of_dir(f, pattern, negate)
1123 def print_capabilities():
1124 document = getDOMImplementation().createDocument(
1125 "ns", CAP_XML_ROOT, None)
1126 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1127 print document.toprettyxml()
1129 def capability(document, key):
1131 el = document.createElement(CAP_XML_ELEMENT)
1132 el.setAttribute('key', c[KEY])
1133 el.setAttribute('pii', c[PII])
1134 el.setAttribute('min-size', str(c[MIN_SIZE]))
1135 el.setAttribute('max-size', str(c[MAX_SIZE]))
1136 el.setAttribute('min-time', str(c[MIN_TIME]))
1137 el.setAttribute('max-time', str(c[MAX_TIME]))
1138 el.setAttribute('content-type', c[MIME])
1139 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1140 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1144 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1145 return '\n'.join([format % i for i in d.items()]) + '\n'
1149 yn = raw_input(prompt)
1151 return len(yn) == 0 or yn.lower()[0] == 'y'
1154 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1159 f = open('/proc/partitions')
1162 for line in f.readlines():
1163 (major, minor, blocks, name) = line.split()
1164 if int(major) < 254 and not partition_re.match(name):
1175 def __init__(self, command, max_time, inst=None, filter=None, binary=False):
1176 self.command = command
1177 self.max_time = max_time
1179 self.running = False
1181 self.timed_out = False
1183 self.timeout = int(time.time()) + self.max_time
1184 self.filter = filter
1185 self.filter_state = {}
1187 self.bufsize = 1048576 # 1MB buffer
1189 self.bufsize = 1 # line buffered
1195 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1198 self.timed_out = False
1200 if ProcOutput.debug:
1201 output_ts("Starting '%s'" % self.cmdAsStr())
1202 self.proc = Popen(self.command, bufsize=self.bufsize,
1203 stdin=dev_null, stdout=PIPE, stderr=dev_null,
1204 shell=isinstance(self.command, str))
1205 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1206 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1210 output_ts("'%s' failed" % self.cmdAsStr())
1211 self.running = False
1214 def terminate(self):
1217 self.proc.stdout.close()
1218 os.kill(self.proc.pid, SIGTERM)
1222 self.running = False
1223 self.status = SIGTERM
1225 def read_line(self):
1227 if self.bufsize == 1:
1228 line = self.proc.stdout.readline()
1230 line = self.proc.stdout.read(self.bufsize)
1233 self.proc.stdout.close()
1234 self.status = self.proc.wait()
1236 self.running = False
1239 line = self.filter(line, self.filter_state)
1241 self.inst.write(line)
1243 def run_procs(procs):
1251 active_procs.append(p)
1252 pipes.append(p.proc.stdout)
1254 elif p.status == None and not p.failed and not p.timed_out:
1257 active_procs.append(p)
1258 pipes.append(p.proc.stdout)
1265 (i, o, x) = select(pipes, [], [], 1.0)
1266 now = int(time.time())
1268 # handle process output
1269 for p in active_procs:
1270 if p.proc.stdout in i:
1274 if p.running and now > p.timeout:
1275 output_ts("'%s' timed out" % p.cmdAsStr())
1277 p.inst.write("\n** timeout **\n")
1285 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1287 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1295 def check_space(cap, name, size):
1296 global free_disk_space
1297 if free_disk_space is not None and size > free_disk_space:
1298 output("Omitting %s, out of disk space (requested: %u, allowed: %u)" %
1299 (name, size, free_disk_space))
1301 elif unlimited_data or caps[cap][MAX_SIZE] == -1 or \
1302 cap_sizes[cap] < caps[cap][MAX_SIZE]:
1303 cap_sizes[cap] += size
1304 if free_disk_space is not None:
1305 free_disk_space -= size
1308 output("Omitting %s, size constraint of %s exceeded" % (name, cap))
1312 def get_free_disk_space(path):
1313 path = os.path.abspath(path)
1314 while not os.path.exists(path):
1315 path = os.path.dirname(path)
1316 s = os.statvfs(path)
1317 return s.f_frsize * s.f_bfree
1320 class StringIOmtime(StringIO.StringIO):
1321 def __init__(self, buf=''):
1322 StringIO.StringIO.__init__(self, buf)
1323 self.mtime = time.time()
1326 StringIO.StringIO.write(self, s)
1327 self.mtime = time.time()
1330 if __name__ == "__main__":
1333 except KeyboardInterrupt:
1334 print "\nInterrupted."