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(),
44 from xml.dom.minidom import parse, getDOMImplementation
46 from subprocess import Popen, PIPE
47 from select import select
48 from signal import SIGTERM
53 warnings.filterwarnings(action="ignore", category=DeprecationWarning)
55 OS_RELEASE = platform.release()
61 APT_SOURCES_LIST = "/etc/apt/sources.list"
62 APT_SOURCES_LIST_D = "/etc/apt/sources.list.d"
63 BUG_DIR = "/var/log/ovs-bugtool"
64 PLUGIN_DIR = "@pkgdatadir@/bugtool-plugins"
65 GRUB_CONFIG = '/boot/grub/menu.lst'
66 BOOT_KERNEL = '/boot/vmlinuz-' + OS_RELEASE
67 BOOT_INITRD = '/boot/initrd-' + OS_RELEASE + '.img'
68 PROC_PARTITIONS = '/proc/partitions'
70 PROC_MOUNTS = '/proc/mounts'
71 ISCSI_CONF = '/etc/iscsi/iscsid.conf'
72 ISCSI_INITIATOR = '/etc/iscsi/initiatorname.iscsi'
73 PROC_CPUINFO = '/proc/cpuinfo'
74 PROC_MEMINFO = '/proc/meminfo'
75 PROC_IOPORTS = '/proc/ioports'
76 PROC_INTERRUPTS = '/proc/interrupts'
77 PROC_SCSI = '/proc/scsi/scsi'
78 PROC_VERSION = '/proc/version'
79 PROC_MODULES = '/proc/modules'
80 PROC_DEVICES = '/proc/devices'
81 PROC_FILESYSTEMS = '/proc/filesystems'
82 PROC_CMDLINE = '/proc/cmdline'
83 PROC_CONFIG = '/proc/config.gz'
84 PROC_USB_DEV = '/proc/bus/usb/devices'
85 PROC_NET_BONDING_DIR = '/proc/net/bonding'
86 IFCFG_RE = re.compile(r'^.*/ifcfg-.*')
87 ROUTE_RE = re.compile(r'^.*/route-.*')
88 SYSCONFIG_HWCONF = '/etc/sysconfig/hwconf'
89 SYSCONFIG_NETWORK = '/etc/sysconfig/network'
90 SYSCONFIG_NETWORK_SCRIPTS = '/etc/sysconfig/network-scripts'
91 PROC_NET_VLAN_DIR = '/proc/net/vlan'
92 PROC_NET_SOFTNET_STAT = '/proc/net/softnet_stat'
93 MODPROBE_CONF = '/etc/modprobe.conf'
94 MODPROBE_DIR = '/etc/modprobe.d'
95 RESOLV_CONF = '/etc/resolv.conf'
96 MPP_CONF = '/etc/mpp.conf'
97 MULTIPATH_CONF = '/etc/multipath.conf'
98 NSSWITCH_CONF = '/etc/nsswitch.conf'
99 NTP_CONF = '/etc/ntp.conf'
100 IPTABLES_CONFIG = '/etc/sysconfig/iptables-config'
102 HOSTS_ALLOW = '/etc/hosts.allow'
103 HOSTS_DENY = '/etc/hosts.deny'
104 DHCP_LEASE_DIR = ['/var/lib/dhclient', '/var/lib/dhcp3']
105 OPENVSWITCH_LOG_DIR = '@LOGDIR@/'
106 OPENVSWITCH_DEFAULT_SWITCH = '/etc/default/openvswitch-switch' # Debian
107 OPENVSWITCH_SYSCONFIG_SWITCH = '/etc/sysconfig/openvswitch' # RHEL
108 OPENVSWITCH_CONF_DB = '@DBDIR@/conf.db'
109 OPENVSWITCH_COMPACT_DB = '@DBDIR@/bugtool-compact-conf.db'
110 OPENVSWITCH_VSWITCHD_PID = '@RUNDIR@/ovs-vswitchd.pid'
111 VAR_LOG_DIR = '/var/log/'
112 VAR_LOG_CORE_DIR = '/var/log/core'
113 YUM_LOG = '/var/log/yum.log'
114 YUM_REPOS_DIR = '/etc/yum.repos.d'
120 os.environ['PATH'] = '/usr/local/sbin:/usr/local/bin:' \
121 '/usr/sbin:/usr/bin:/sbin:/bin:@pkgdatadir@/scripts'
124 CHKCONFIG = 'chkconfig'
127 DMIDECODE = 'dmidecode'
129 DPKG_QUERY = 'dpkg-query'
133 IFCONFIG = 'ifconfig'
134 IPTABLES = 'iptables'
135 ISCSIADM = 'iscsiadm'
142 MULTIPATHD = 'multipathd'
144 OVS_DPCTL = 'ovs-dpctl'
145 OVS_OFCTL = 'ovs-ofctl'
146 OVS_VSCTL = 'ovs-vsctl'
157 # PII -- Personally identifiable information. Of particular concern are
158 # things that would identify customers, or their network topology.
159 # Passwords are never to be included in any bug report, regardless of any PII
162 # NO -- No PII will be in these entries.
163 # YES -- PII will likely or certainly be in these entries.
164 # MAYBE -- The user may wish to audit these entries for PII.
165 # IF_CUSTOMIZED -- If the files are unmodified, then they will contain no PII,
166 # but since we encourage customers to edit these files, PII may have been
167 # introduced by the customer. This is used in particular for the networking
174 PII_IF_CUSTOMIZED = 'if_customized'
185 MIME_DATA = 'application/data'
186 MIME_TEXT = 'text/plain'
188 INVENTORY_XML_ROOT = "system-status-inventory"
189 INVENTORY_XML_SUMMARY = 'system-summary'
190 INVENTORY_XML_ELEMENT = 'inventory-entry'
191 CAP_XML_ROOT = "system-status-capabilities"
192 CAP_XML_ELEMENT = 'capability'
195 CAP_BOOT_LOADER = 'boot-loader'
196 CAP_DISK_INFO = 'disk-info'
197 CAP_HARDWARE_INFO = 'hardware-info'
198 CAP_KERNEL_INFO = 'kernel-info'
199 CAP_LOSETUP_A = 'loopback-devices'
200 CAP_MULTIPATH = 'multipath'
201 CAP_NETWORK_CONFIG = 'network-config'
202 CAP_NETWORK_INFO = 'network-info'
203 CAP_NETWORK_STATUS = 'network-status'
204 CAP_OPENVSWITCH_LOGS = 'ovs-system-logs'
205 CAP_PROCESS_LIST = 'process-list'
206 CAP_SYSTEM_LOGS = 'system-logs'
207 CAP_SYSTEM_SERVICES = 'system-services'
215 unlimited_data = False
217 # Default value for the number of days to collect logs.
219 log_last_mod_time = None
220 free_disk_space = None
223 def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
224 max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
225 caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
230 cap(CAP_BOOT_LOADER, PII_NO, max_size=3 * KB, max_time=5)
231 cap(CAP_DISK_INFO, PII_MAYBE, max_size=50 * KB, max_time=20)
232 cap(CAP_HARDWARE_INFO, PII_MAYBE, max_size=2 * MB, max_time=20)
233 cap(CAP_KERNEL_INFO, PII_MAYBE, max_size=120 * KB, max_time=5)
234 cap(CAP_LOSETUP_A, PII_MAYBE, max_size=KB, max_time=5)
235 cap(CAP_MULTIPATH, PII_MAYBE, max_size=20 * KB, max_time=10)
236 cap(CAP_NETWORK_CONFIG, PII_IF_CUSTOMIZED, min_size=0, max_size=5 * MB)
237 cap(CAP_NETWORK_INFO, PII_YES, max_size=50 * MB, max_time=30)
238 cap(CAP_NETWORK_STATUS, PII_YES, max_size=-1, max_time=30)
239 cap(CAP_OPENVSWITCH_LOGS, PII_MAYBE, max_size=-1, max_time=5)
240 cap(CAP_PROCESS_LIST, PII_YES, max_size=30 * KB, max_time=20)
241 cap(CAP_SYSTEM_LOGS, PII_MAYBE, max_size=200 * MB, max_time=5)
242 cap(CAP_SYSTEM_SERVICES, PII_NO, max_size=5 * KB, max_time=20)
243 cap(CAP_YUM, PII_IF_CUSTOMIZED, max_size=10 * KB, max_time=30)
245 ANSWER_YES_TO_ALL = False
249 dev_null = open('/dev/null', 'r+')
259 output("[%s] %s" % (time.strftime("%x %X %Z"), x))
262 def cmd_output(cap, args, label=None, filter=None, binary=False):
265 if isinstance(args, list):
266 a = [aa for aa in args]
267 a[0] = os.path.basename(a[0])
271 data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter,
275 def file_output(cap, path_list, newest_first=False, last_mod_time=None):
277 If newest_first is True, the list of files in path_list is sorted
278 by file modification time in descending order, else its sorted
283 for path in path_list:
288 if last_mod_time is None or s.st_mtime >= last_mod_time:
289 path_entries.append((path, s))
291 mtime = lambda(path, stat): stat.st_mtime
292 path_entries.sort(key=mtime, reverse=newest_first)
293 for p in path_entries:
294 if check_space(cap, p[0], p[1].st_size):
295 data[p] = {'cap': cap, 'filename': p[0]}
298 def tree_output(cap, path, pattern=None, negate=False, newest_first=False,
301 Walks the directory tree rooted at path. Files in current dir are processed
302 before files in sub-dirs.
305 if os.path.exists(path):
306 for root, dirs, files in os.walk(path):
307 fns = [fn for fn in [os.path.join(root, f) for f in files]
308 if os.path.isfile(fn) and matches(fn, pattern, negate)]
309 file_output(cap, fns, newest_first=newest_first,
310 last_mod_time=last_mod_time)
313 def prefix_output(cap, prefix, newest_first=False, last_mod_time=None):
315 Output files with the same prefix.
318 for root, dirs, files in os.walk(os.path.dirname(prefix)):
319 fns += [fn for fn in [os.path.join(root, f) for f in files]
320 if fn.startswith(prefix)]
321 file_output(cap, fns, newest_first=newest_first,
322 last_mod_time=last_mod_time)
325 def func_output(cap, label, func):
327 t = str(func).split()
328 data[label] = {'cap': cap, 'func': func}
334 for (k, v) in data.items():
336 if v.has_key('cmd_args'):
337 v['output'] = StringIOmtime()
338 if not process_lists.has_key(cap):
339 process_lists[cap] = []
340 process_lists[cap].append(
341 ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'],
342 v['filter'], v['binary']))
343 elif v.has_key('filename') and v['filename'].startswith('/proc/'):
344 # proc files must be read into memory
346 f = open(v['filename'], 'r')
349 if check_space(cap, v['filename'], len(s)):
350 v['output'] = StringIOmtime(s)
353 elif v.has_key('func'):
358 if check_space(cap, k, len(s)):
359 v['output'] = StringIOmtime(s)
361 run_procs(process_lists.values())
365 global ANSWER_YES_TO_ALL, SILENT_MODE
366 global entries, data, dbg, unlimited_data, free_disk_space
367 global log_days, log_last_mod_time
370 only_ovs_info = False
371 collect_all_info = True
373 if '--help' in sys.argv:
375 %(argv0)s: create status report bundles to assist in problem diagnosis
376 usage: %(argv0)s OPTIONS
378 By default, %(argv0)s prompts for permission to collect each form of status
379 information and produces a .tar.gz file as output.
381 The following options are available.
382 --help display this help message, then exit
383 -s, --silent suppress most output to stdout
385 Options for categories of data to collect:
386 --entries=CAP_A,CAP_B,... set categories of data to collect
387 --all collect all categories
388 --ovs collect only directly OVS-related info
389 --log-days=DAYS collect DAYS worth of old logs
390 -y, --yestoall suppress prompts to confirm collection
391 --capabilities print categories as XML on stdout, then exit
394 --output=FORMAT set output format to one of tar tar.bz2 tar.gz zip
395 --outfile=FILE write output to FILE
396 --outfd=FD write output to FD (requires --output=tar)
397 --unlimited ignore default limits on sizes of data collected
398 --debug print ovs-bugtool debug info on stdout\
399 """ % {'argv0': sys.argv[0]}
402 # we need access to privileged files, exit if we are not running as root
404 print >>sys.stderr, "Error: ovs-bugtool must be run as root"
408 output_type = 'tar.gz'
415 (options, params) = getopt.gnu_getopt(
416 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
417 'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
418 'debug', 'ovs', 'log-days='])
419 except getopt.GetoptError, opterr:
420 print >>sys.stderr, opterr
428 entries = [e for e in caps.keys() if caps[e][CHECKED]]
430 for (k, v) in options:
431 if k == '--capabilities':
432 update_capabilities()
437 if v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
440 print >>sys.stderr, "Invalid output format '%s'" % v
443 # "-s" or "--silent" means suppress output (except for the final
444 # output filename at the end)
445 if k in ['-s', '--silent']:
448 if k == '--entries' and v != '':
449 entries = v.split(',')
451 # If the user runs the script with "-y" or "--yestoall" we don't ask
452 # all the really annoying questions.
453 if k in ['-y', '--yestoall']:
454 ANSWER_YES_TO_ALL = True
459 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
460 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
462 print >>sys.stderr, "Invalid output file descriptor", output_fd
469 entries = caps.keys()
470 elif k == '--unlimited':
471 unlimited_data = True
474 ProcOutput.debug = True
478 collect_all_info = False
480 if k == '--log-days':
484 print >>sys.stderr, "Invalid additional arguments", str(params)
487 if output_fd != -1 and output_type != 'tar':
488 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
491 if output_fd != -1 and output_file is not None:
492 print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
495 if output_file is not None and not unlimited_data:
496 free_disk_space = get_free_disk_space(output_file) * 90 / 100
498 log_last_mod_time = int(time.time()) - log_days * 86400
500 if ANSWER_YES_TO_ALL:
501 output("Warning: '--yestoall' argument provided, will not prompt "
502 "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],
523 label='vmlinuz-initrd.md5sum')
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 if len(pidof('iscsid')) != 0:
531 cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
532 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
533 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
534 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
535 cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
536 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
538 file_output(CAP_HARDWARE_INFO,
539 [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
540 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
541 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
542 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
543 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
544 file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
545 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
547 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES,
548 PROC_DEVICES, PROC_FILESYSTEMS, PROC_CMDLINE])
549 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
550 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
551 file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
552 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
553 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
555 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
557 file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
558 cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
559 func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
560 cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
561 if CAP_MULTIPATH in entries and collect_all_info:
562 dump_rdac_groups(CAP_MULTIPATH)
564 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
565 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
566 file_output(CAP_NETWORK_CONFIG,
567 [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
568 file_output(CAP_NETWORK_CONFIG,
569 [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
570 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_DEFAULT_SWITCH,
571 OPENVSWITCH_SYSCONFIG_SWITCH])
573 cmd_output(CAP_NETWORK_INFO, [IFCONFIG, '-a'])
574 cmd_output(CAP_NETWORK_INFO, [ROUTE, '-n'])
575 cmd_output(CAP_NETWORK_INFO, [ARP, '-n'])
576 cmd_output(CAP_NETWORK_INFO, [NETSTAT, '-an'])
577 for dir in DHCP_LEASE_DIR:
578 tree_output(CAP_NETWORK_INFO, dir)
579 for table in ['filter', 'nat', 'mangle', 'raw', 'security']:
580 cmd_output(CAP_NETWORK_INFO, [IPTABLES, '-t', table, '-nL'])
581 for p in os.listdir('/sys/class/net/'):
583 f = open('/sys/class/net/%s/type' % p, 'r')
586 if os.path.islink('/sys/class/net/%s/device' % p) and int(t) == 1:
588 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-S', p])
589 if not p.startswith('vif') and not p.startswith('tap'):
590 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, p])
591 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-k', p])
592 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-i', p])
593 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-c', p])
595 cmd_output(CAP_NETWORK_INFO,
596 [TC, '-s', '-d', 'class', 'show', 'dev', p])
599 tree_output(CAP_NETWORK_INFO, PROC_NET_BONDING_DIR)
600 tree_output(CAP_NETWORK_INFO, PROC_NET_VLAN_DIR)
601 cmd_output(CAP_NETWORK_INFO, [TC, '-s', 'qdisc'])
602 file_output(CAP_NETWORK_INFO, [PROC_NET_SOFTNET_STAT])
605 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
606 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
608 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', '-m', d])
610 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo',
611 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'],
612 label='process-tree')
613 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
615 system_logs = ([VAR_LOG_DIR + x for x in
616 ['crit.log', 'kern.log', 'daemon.log', 'user.log',
617 'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot']])
618 for log in system_logs:
619 prefix_output(CAP_SYSTEM_LOGS, log, last_mod_time=log_last_mod_time)
621 ovs_logs = ([OPENVSWITCH_LOG_DIR + x for x in
622 ['ovs-vswitchd.log', 'ovsdb-server.log',
623 'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log', 'ovs-ctl.log']])
625 prefix_output(CAP_OPENVSWITCH_LOGS, log,
626 last_mod_time=log_last_mod_time)
628 if not os.path.exists('/var/log/dmesg') and \
629 not os.path.exists('/var/log/boot'):
630 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
632 cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
634 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
636 file_output(CAP_YUM, [YUM_LOG])
637 tree_output(CAP_YUM, YUM_REPOS_DIR)
638 cmd_output(CAP_YUM, [RPM, '-qa'])
639 file_output(CAP_YUM, [APT_SOURCES_LIST])
640 tree_output(CAP_YUM, APT_SOURCES_LIST_D)
641 cmd_output(CAP_YUM, [DPKG_QUERY, '-W',
642 '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
644 # Filter out ovs relevant information if --ovs option passed
645 # else collect all information
649 ovs_info_caps = [CAP_NETWORK_STATUS, CAP_SYSTEM_LOGS,
651 ovs_info_list = ['process-tree']
652 # We cannot use iteritems, since we modify 'data' as we pass through
653 for (k, v) in data.items():
659 if info not in ovs_info_list and cap not in ovs_info_caps:
663 filter = ",".join(filters)
668 load_plugins(filter=filter)
672 # permit the user to filter out data
673 # We cannot use iteritems, since we modify 'data' as we pass through
674 for (k, v) in sorted(data.items()):
680 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % key):
683 # collect selected data now
684 output_ts('Running commands to collect data')
687 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
690 data['inventory.xml'] = {'cap': None,
691 'output': StringIOmtime(make_inventory(data, subdir))}
695 if output_file is None:
698 dirname = os.path.dirname(output_file)
699 if dirname and not os.path.exists(dirname):
706 output_ts('Creating output file')
708 if output_type.startswith('tar'):
709 make_tar(subdir, output_type, output_fd, output_file)
711 make_zip(subdir, output_file)
714 print >>sys.stderr, "Category sizes (max, actual):\n"
715 for c in caps.keys():
716 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
723 def dump_scsi_hosts(cap):
725 l = os.listdir('/sys/class/scsi_host')
731 f = open('/sys/class/scsi_host/%s/proc_name' % h)
732 procname = f.readline().strip("\n")
738 f = open('/sys/class/scsi_host/%s/model_name' % h)
739 modelname = f.readline().strip("\n")
744 output += "%s:\n" % h
745 output += " %s%s\n" \
746 % (procname, modelname and (" -> %s" % modelname) or '')
751 def module_info(cap):
752 output = StringIO.StringIO()
753 modules = open(PROC_MODULES, 'r')
757 module = line.split()[0]
758 procs.append(ProcOutput([MODINFO, module],
759 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')
776 output = StringIO.StringIO()
777 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'],
778 caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
782 if not procs[0].timed_out:
783 return output.getvalue().splitlines()
788 if not os.path.isfile(OPENVSWITCH_CONF_DB):
794 if os.path.getsize(OPENVSWITCH_CONF_DB) > max_size:
795 if os.path.isfile(OPENVSWITCH_COMPACT_DB):
796 os.unlink(OPENVSWITCH_COMPACT_DB)
798 output = StringIO.StringIO()
800 procs = [ProcOutput(['ovsdb-tool', 'compact',
801 OPENVSWITCH_CONF_DB, OPENVSWITCH_COMPACT_DB],
804 file_output(CAP_NETWORK_STATUS, [OPENVSWITCH_COMPACT_DB])
806 file_output(CAP_NETWORK_STATUS, [OPENVSWITCH_CONF_DB])
813 if os.path.isfile(OPENVSWITCH_COMPACT_DB):
814 os.unlink(OPENVSWITCH_COMPACT_DB)
822 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
824 fh = open('/proc/' + d + '/cmdline')
826 num_fds = len(os.listdir(os.path.join('/proc/' + d + '/fd')))
828 if not num_fds in fd_dict:
829 fd_dict[num_fds] = []
830 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
833 keys = fd_dict.keys()
834 keys.sort(lambda a, b: int(b) - int(a))
836 output += "%s: %s\n" % (k, str(fd_dict[k]))
840 def dump_rdac_groups(cap):
841 output = StringIO.StringIO()
842 procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
846 if not procs[0].timed_out:
848 for line in output.getvalue().splitlines():
849 if line.startswith('ID'):
851 elif line.startswith('----'):
854 group, _ = line.split(None, 1)
855 cmd_output(cap, [MPPUTIL, '-g', group])
858 def load_plugins(just_capabilities=False, filter=None):
859 global log_last_mod_time
861 def getText(nodelist):
863 for node in nodelist:
864 if node.nodeType == node.TEXT_NODE:
868 def getBoolAttr(el, attr, default=False):
870 val = el.getAttribute(attr).lower()
871 if val in ['true', 'false', 'yes', 'no']:
872 ret = val in ['true', 'yes']
875 for dir in [d for d in os.listdir(PLUGIN_DIR)
876 if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
877 if not caps.has_key(dir):
878 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
880 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
881 assert xmldoc.documentElement.tagName == "capability"
883 pii, min_size, max_size, min_time, max_time, mime = \
884 PII_MAYBE, -1, -1, -1, -1, MIME_TEXT
886 if xmldoc.documentElement.getAttribute("pii") in \
887 [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
888 pii = xmldoc.documentElement.getAttribute("pii")
889 if xmldoc.documentElement.getAttribute("min_size") != '':
891 xmldoc.documentElement.getAttribute("min_size"))
892 if xmldoc.documentElement.getAttribute("max_size") != '':
894 xmldoc.documentElement.getAttribute("max_size"))
895 if xmldoc.documentElement.getAttribute("min_time") != '':
896 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
897 if xmldoc.documentElement.getAttribute("max_time") != '':
898 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
899 if xmldoc.documentElement.getAttribute("mime") in \
900 [MIME_DATA, MIME_TEXT]:
901 mime = xmldoc.documentElement.getAttribute("mime")
902 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
903 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
905 cap(dir, pii, min_size, max_size, min_time, max_time, mime,
908 if just_capabilities:
911 plugdir = os.path.join(PLUGIN_DIR, dir)
912 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
913 xmldoc = parse(os.path.join(plugdir, file))
914 assert xmldoc.documentElement.tagName == "collect"
916 for el in xmldoc.documentElement.getElementsByTagName("*"):
917 filters_tmp = el.getAttribute("filters")
918 if filters_tmp == '':
921 filters = filters_tmp.split(',')
922 if not(filter is None or filter in filters):
924 if el.tagName == "files":
925 newest_first = getBoolAttr(el, 'newest_first')
926 if el.getAttribute("type") == "logs":
927 for fn in getText(el.childNodes).split():
928 prefix_output(dir, fn, newest_first=newest_first,
929 last_mod_time=log_last_mod_time)
931 file_output(dir, getText(el.childNodes).split(),
932 newest_first=newest_first)
933 elif el.tagName == "directory":
934 pattern = el.getAttribute("pattern")
937 negate = getBoolAttr(el, 'negate')
938 newest_first = getBoolAttr(el, 'newest_first')
939 if el.getAttribute("type") == "logs":
940 tree_output(dir, getText(el.childNodes),
941 pattern and re.compile(pattern) or None,
942 negate=negate, newest_first=newest_first,
943 last_mod_time=log_last_mod_time)
945 tree_output(dir, getText(el.childNodes),
946 pattern and re.compile(pattern) or None,
947 negate=negate, newest_first=newest_first)
948 elif el.tagName == "command":
949 label = el.getAttribute("label")
952 binary = getBoolAttr(el, 'binary')
954 getText(el.childNodes), label, binary=binary)
957 def make_tar(subdir, suffix, output_fd, output_file):
958 global SILENT_MODE, data
961 if suffix == 'tar.bz2':
963 elif suffix == 'tar.gz':
967 if output_file is None:
968 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
970 filename = output_file
971 old_umask = os.umask(0077)
972 tf = tarfile.open(filename, mode)
975 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
978 for (k, v) in data.items():
980 tar_filename = os.path.join(subdir, construct_filename(k, v))
981 ti = tarfile.TarInfo(tar_filename)
986 if v.has_key('output'):
987 ti.mtime = v['output'].mtime
988 ti.size = len(v['output'].getvalue())
990 tf.addfile(ti, v['output'])
991 elif v.has_key('filename'):
992 s = os.stat(v['filename'])
993 ti.mtime = s.st_mtime
995 tf.addfile(ti, file(v['filename']))
1002 output('Writing tarball %s successful.' % filename)
1007 def make_zip(subdir, output_file):
1008 global SILENT_MODE, data
1010 if output_file is None:
1011 filename = "%s/%s.zip" % (BUG_DIR, subdir)
1013 filename = output_file
1014 old_umask = os.umask(0077)
1015 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
1019 for (k, v) in data.items():
1021 dest = os.path.join(subdir, construct_filename(k, v))
1023 if v.has_key('output'):
1024 zf.writestr(dest, v['output'].getvalue())
1026 if os.stat(v['filename']).st_size < 50:
1027 compress_type = zipfile.ZIP_STORED
1029 compress_type = zipfile.ZIP_DEFLATED
1030 zf.write(v['filename'], dest, compress_type)
1036 output('Writing archive %s successful.' % filename)
1041 def make_inventory(inventory, subdir):
1042 document = getDOMImplementation().createDocument(
1043 None, INVENTORY_XML_ROOT, None)
1045 # create summary entry
1046 s = document.createElement(INVENTORY_XML_SUMMARY)
1047 user = os.getenv('SUDO_USER', os.getenv('USER'))
1049 s.setAttribute('user', user)
1050 s.setAttribute('date', time.strftime('%c'))
1051 s.setAttribute('hostname', platform.node())
1052 s.setAttribute('uname', ' '.join(platform.uname()))
1053 s.setAttribute('uptime', commands.getoutput(UPTIME))
1054 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
1056 map(lambda (k, v): inventory_entry(document, subdir, k, v),
1058 return document.toprettyxml()
1061 def inventory_entry(document, subdir, k, v):
1063 el = document.createElement(INVENTORY_XML_ELEMENT)
1064 el.setAttribute('capability', v['cap'])
1065 el.setAttribute('filename',
1066 os.path.join(subdir, construct_filename(k, v)))
1067 el.setAttribute('md5sum', md5sum(v))
1068 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
1075 if d.has_key('filename'):
1076 f = open(d['filename'])
1078 while len(data) > 0:
1082 elif d.has_key('output'):
1083 m.update(d['output'].getvalue())
1084 return m.hexdigest()
1087 def construct_filename(k, v):
1088 if v.has_key('filename'):
1089 if v['filename'][0] == '/':
1090 return v['filename'][1:]
1092 return v['filename']
1093 s = k.replace(' ', '-')
1094 s = s.replace('--', '-')
1095 s = s.replace('/', '%')
1096 if s.find('.') == -1:
1102 def update_capabilities():
1106 def update_cap_size(cap, size):
1107 update_cap(cap, MIN_SIZE, size)
1108 update_cap(cap, MAX_SIZE, size)
1109 update_cap(cap, CHECKED, size > 0)
1112 def update_cap(cap, k, v):
1116 caps[cap] = tuple(l)
1119 def size_of_dir(d, pattern=None, negate=False):
1120 if os.path.isdir(d):
1121 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1127 def size_of_all(files, pattern=None, negate=False):
1128 return sum([size_of(f, pattern, negate) for f in files])
1131 def matches(f, pattern, negate):
1133 return not matches(f, pattern, False)
1135 return pattern is None or pattern.match(f)
1138 def size_of(f, pattern, negate):
1139 if os.path.isfile(f) and matches(f, pattern, negate):
1140 return os.stat(f)[6]
1142 return size_of_dir(f, pattern, negate)
1145 def print_capabilities():
1146 document = getDOMImplementation().createDocument(
1147 "ns", CAP_XML_ROOT, None)
1148 map(lambda key: capability(document, key),
1149 [k for k in caps.keys() if not caps[k][HIDDEN]])
1150 print document.toprettyxml()
1153 def capability(document, key):
1155 el = document.createElement(CAP_XML_ELEMENT)
1156 el.setAttribute('key', c[KEY])
1157 el.setAttribute('pii', c[PII])
1158 el.setAttribute('min-size', str(c[MIN_SIZE]))
1159 el.setAttribute('max-size', str(c[MAX_SIZE]))
1160 el.setAttribute('min-time', str(c[MIN_TIME]))
1161 el.setAttribute('max-time', str(c[MAX_TIME]))
1162 el.setAttribute('content-type', c[MIME])
1163 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1164 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1168 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1169 return '\n'.join([format % i for i in d.items()]) + '\n'
1173 yn = raw_input(prompt)
1175 return len(yn) == 0 or yn.lower()[0] == 'y'
1178 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1184 f = open('/proc/partitions')
1187 for line in f.readlines():
1188 (major, minor, blocks, name) = line.split()
1189 if int(major) < 254 and not partition_re.match(name):
1200 def __init__(self, command, max_time, inst=None, filter=None,
1202 self.command = command
1203 self.max_time = max_time
1205 self.running = False
1207 self.timed_out = False
1209 self.timeout = int(time.time()) + self.max_time
1210 self.filter = filter
1211 self.filter_state = {}
1213 self.bufsize = 1048576 # 1MB buffer
1215 self.bufsize = 1 # line buffered
1221 return isinstance(self.command, list) \
1222 and ' '.join(self.command) or self.command
1225 self.timed_out = False
1227 if ProcOutput.debug:
1228 output_ts("Starting '%s'" % self.cmdAsStr())
1229 self.proc = Popen(self.command, bufsize=self.bufsize,
1230 stdin=dev_null, stdout=PIPE, stderr=dev_null,
1231 shell=isinstance(self.command, str))
1232 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1233 fcntl.fcntl(self.proc.stdout.fileno(),
1234 fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1238 output_ts("'%s' failed" % self.cmdAsStr())
1239 self.running = False
1242 def terminate(self):
1245 self.proc.stdout.close()
1246 os.kill(self.proc.pid, SIGTERM)
1250 self.running = False
1251 self.status = SIGTERM
1253 def read_line(self):
1255 if self.bufsize == 1:
1256 line = self.proc.stdout.readline()
1258 line = self.proc.stdout.read(self.bufsize)
1261 self.proc.stdout.close()
1262 self.status = self.proc.wait()
1264 self.running = False
1267 line = self.filter(line, self.filter_state)
1269 self.inst.write(line)
1272 def run_procs(procs):
1280 active_procs.append(p)
1281 pipes.append(p.proc.stdout)
1283 elif p.status == None and not p.failed and not p.timed_out:
1286 active_procs.append(p)
1287 pipes.append(p.proc.stdout)
1294 (i, o, x) = select(pipes, [], [], 1.0)
1295 now = int(time.time())
1297 # handle process output
1298 for p in active_procs:
1299 if p.proc.stdout in i:
1303 if p.running and now > p.timeout:
1304 output_ts("'%s' timed out" % p.cmdAsStr())
1306 p.inst.write("\n** timeout **\n")
1314 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1316 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1324 def check_space(cap, name, size):
1325 global free_disk_space
1326 if free_disk_space is not None and size > free_disk_space:
1327 output("Omitting %s, out of disk space (requested: %u, allowed: %u)" %
1328 (name, size, free_disk_space))
1330 elif unlimited_data or caps[cap][MAX_SIZE] == -1 or \
1331 cap_sizes[cap] < caps[cap][MAX_SIZE]:
1332 cap_sizes[cap] += size
1333 if free_disk_space is not None:
1334 free_disk_space -= size
1337 output("Omitting %s, size constraint of %s exceeded" % (name, cap))
1341 def get_free_disk_space(path):
1342 path = os.path.abspath(path)
1343 while not os.path.exists(path):
1344 path = os.path.dirname(path)
1345 s = os.statvfs(path)
1346 return s.f_frsize * s.f_bfree
1349 class StringIOmtime(StringIO.StringIO):
1350 def __init__(self, buf=''):
1351 StringIO.StringIO.__init__(self, buf)
1352 self.mtime = time.time()
1355 StringIO.StringIO.write(self, s)
1356 self.mtime = time.time()
1359 if __name__ == "__main__":
1362 except KeyboardInterrupt:
1363 print "\nInterrupted."