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 data[label] = {'cap': cap, 'func': func}
333 for (k, v) in data.items():
335 if v.has_key('cmd_args'):
336 v['output'] = StringIOmtime()
337 if not process_lists.has_key(cap):
338 process_lists[cap] = []
339 process_lists[cap].append(
340 ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'],
341 v['filter'], v['binary']))
342 elif v.has_key('filename') and v['filename'].startswith('/proc/'):
343 # proc files must be read into memory
345 f = open(v['filename'], 'r')
348 if check_space(cap, v['filename'], len(s)):
349 v['output'] = StringIOmtime(s)
352 elif v.has_key('func'):
357 if check_space(cap, k, len(s)):
358 v['output'] = StringIOmtime(s)
360 run_procs(process_lists.values())
364 global ANSWER_YES_TO_ALL, SILENT_MODE
365 global entries, data, dbg, unlimited_data, free_disk_space
366 global log_days, log_last_mod_time
369 only_ovs_info = False
370 collect_all_info = True
372 if '--help' in sys.argv:
374 %(argv0)s: create status report bundles to assist in problem diagnosis
375 usage: %(argv0)s OPTIONS
377 By default, %(argv0)s prompts for permission to collect each form of status
378 information and produces a .tar.gz file as output.
380 The following options are available.
381 --help display this help message, then exit
382 -s, --silent suppress most output to stdout
384 Options for categories of data to collect:
385 --entries=CAP_A,CAP_B,... set categories of data to collect
386 --all collect all categories
387 --ovs collect only directly OVS-related info
388 --log-days=DAYS collect DAYS worth of old logs
389 -y, --yestoall suppress prompts to confirm collection
390 --capabilities print categories as XML on stdout, then exit
393 --output=FORMAT set output format to one of tar tar.bz2 tar.gz zip
394 --outfile=FILE write output to FILE
395 --outfd=FD write output to FD (requires --output=tar)
396 --unlimited ignore default limits on sizes of data collected
397 --debug print ovs-bugtool debug info on stdout\
398 """ % {'argv0': sys.argv[0]}
401 # we need access to privileged files, exit if we are not running as root
403 print >>sys.stderr, "Error: ovs-bugtool must be run as root"
407 output_type = 'tar.gz'
414 (options, params) = getopt.gnu_getopt(
415 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
416 'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
417 'debug', 'ovs', 'log-days='])
418 except getopt.GetoptError, opterr:
419 print >>sys.stderr, opterr
427 entries = [e for e in caps.keys() if caps[e][CHECKED]]
429 for (k, v) in options:
430 if k == '--capabilities':
431 update_capabilities()
436 if v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
439 print >>sys.stderr, "Invalid output format '%s'" % v
442 # "-s" or "--silent" means suppress output (except for the final
443 # output filename at the end)
444 if k in ['-s', '--silent']:
447 if k == '--entries' and v != '':
448 entries = v.split(',')
450 # If the user runs the script with "-y" or "--yestoall" we don't ask
451 # all the really annoying questions.
452 if k in ['-y', '--yestoall']:
453 ANSWER_YES_TO_ALL = True
458 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
459 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
461 print >>sys.stderr, "Invalid output file descriptor", output_fd
468 entries = caps.keys()
469 elif k == '--unlimited':
470 unlimited_data = True
473 ProcOutput.debug = True
477 collect_all_info = False
479 if k == '--log-days':
483 print >>sys.stderr, "Invalid additional arguments", str(params)
486 if output_fd != -1 and output_type != 'tar':
487 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
490 if output_fd != -1 and output_file is not None:
491 print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
494 if output_file is not None and not unlimited_data:
495 free_disk_space = get_free_disk_space(output_file) * 90 / 100
497 log_last_mod_time = int(time.time()) - log_days * 86400
499 if ANSWER_YES_TO_ALL:
500 output("Warning: '--yestoall' argument provided, will not prompt "
501 "for individual files.")
504 This application will collate dmesg output, details of the
505 hardware configuration of your machine, information about the build of
506 openvswitch that you are using, plus, if you allow it, various logs.
508 The collated information will be saved as a .%s for archiving or
509 sending to a Technical Support Representative.
511 The logs may contain private information, and if you are at all
512 worried about that, you should exit now, or you should explicitly
513 exclude those logs from the archive.
517 # assemble potential data
519 file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
520 cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
521 cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD],
522 label='vmlinuz-initrd.md5sum')
524 cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
525 file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
526 file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
527 cmd_output(CAP_DISK_INFO, [DF, '-alT'])
528 cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
529 if len(pidof('iscsid')) != 0:
530 cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
531 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
532 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
533 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
534 cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
535 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
537 file_output(CAP_HARDWARE_INFO,
538 [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
539 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
540 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
541 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
542 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
543 file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
544 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
546 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES,
547 PROC_DEVICES, PROC_FILESYSTEMS, PROC_CMDLINE])
548 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
549 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
550 file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
551 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
552 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
554 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
556 file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
557 cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
558 func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
559 cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
560 if CAP_MULTIPATH in entries and collect_all_info:
561 dump_rdac_groups(CAP_MULTIPATH)
563 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
564 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
565 file_output(CAP_NETWORK_CONFIG,
566 [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
567 file_output(CAP_NETWORK_CONFIG,
568 [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
569 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_DEFAULT_SWITCH,
570 OPENVSWITCH_SYSCONFIG_SWITCH])
572 cmd_output(CAP_NETWORK_INFO, [IFCONFIG, '-a'])
573 cmd_output(CAP_NETWORK_INFO, [ROUTE, '-n'])
574 cmd_output(CAP_NETWORK_INFO, [ARP, '-n'])
575 cmd_output(CAP_NETWORK_INFO, [NETSTAT, '-an'])
576 for dir in DHCP_LEASE_DIR:
577 tree_output(CAP_NETWORK_INFO, dir)
578 for table in ['filter', 'nat', 'mangle', 'raw', 'security']:
579 cmd_output(CAP_NETWORK_INFO, [IPTABLES, '-t', table, '-nL'])
580 for p in os.listdir('/sys/class/net/'):
582 f = open('/sys/class/net/%s/type' % p, 'r')
585 if os.path.islink('/sys/class/net/%s/device' % p) and int(t) == 1:
587 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-S', p])
588 if not p.startswith('vif') and not p.startswith('tap'):
589 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, p])
590 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-k', p])
591 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-i', p])
592 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-c', p])
594 cmd_output(CAP_NETWORK_INFO,
595 [TC, '-s', '-d', 'class', 'show', 'dev', p])
598 tree_output(CAP_NETWORK_INFO, PROC_NET_BONDING_DIR)
599 tree_output(CAP_NETWORK_INFO, PROC_NET_VLAN_DIR)
600 cmd_output(CAP_NETWORK_INFO, [TC, '-s', 'qdisc'])
601 file_output(CAP_NETWORK_INFO, [PROC_NET_SOFTNET_STAT])
604 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
605 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
607 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', '-m', d])
609 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo',
610 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'],
611 label='process-tree')
612 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
614 system_logs = ([VAR_LOG_DIR + x for x in
615 ['crit.log', 'kern.log', 'daemon.log', 'user.log',
616 'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot']])
617 for log in system_logs:
618 prefix_output(CAP_SYSTEM_LOGS, log, last_mod_time=log_last_mod_time)
620 ovs_logs = ([OPENVSWITCH_LOG_DIR + x for x in
621 ['ovs-vswitchd.log', 'ovsdb-server.log',
622 'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log', 'ovs-ctl.log']])
624 prefix_output(CAP_OPENVSWITCH_LOGS, log,
625 last_mod_time=log_last_mod_time)
627 if not os.path.exists('/var/log/dmesg') and \
628 not os.path.exists('/var/log/boot'):
629 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
631 cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
633 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
635 file_output(CAP_YUM, [YUM_LOG])
636 tree_output(CAP_YUM, YUM_REPOS_DIR)
637 cmd_output(CAP_YUM, [RPM, '-qa'])
638 file_output(CAP_YUM, [APT_SOURCES_LIST])
639 tree_output(CAP_YUM, APT_SOURCES_LIST_D)
640 cmd_output(CAP_YUM, [DPKG_QUERY, '-W',
641 '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
643 # Filter out ovs relevant information if --ovs option passed
644 # else collect all information
648 ovs_info_caps = [CAP_NETWORK_STATUS, CAP_SYSTEM_LOGS,
650 ovs_info_list = ['process-tree']
651 # We cannot use iteritems, since we modify 'data' as we pass through
652 for (k, v) in data.items():
658 if info not in ovs_info_list and cap not in ovs_info_caps:
662 filter = ",".join(filters)
667 load_plugins(filter=filter)
671 # permit the user to filter out data
672 # We cannot use iteritems, since we modify 'data' as we pass through
673 for (k, v) in sorted(data.items()):
679 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % key):
682 # collect selected data now
683 output_ts('Running commands to collect data')
686 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
689 data['inventory.xml'] = {'cap': None,
690 'output': StringIOmtime(make_inventory(data, subdir))}
694 if output_file is None:
697 dirname = os.path.dirname(output_file)
698 if dirname and not os.path.exists(dirname):
705 output_ts('Creating output file')
707 if output_type.startswith('tar'):
708 make_tar(subdir, output_type, output_fd, output_file)
710 make_zip(subdir, output_file)
713 print >>sys.stderr, "Category sizes (max, actual):\n"
714 for c in caps.keys():
715 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
722 def dump_scsi_hosts(cap):
724 l = os.listdir('/sys/class/scsi_host')
730 f = open('/sys/class/scsi_host/%s/proc_name' % h)
731 procname = f.readline().strip("\n")
737 f = open('/sys/class/scsi_host/%s/model_name' % h)
738 modelname = f.readline().strip("\n")
743 output += "%s:\n" % h
744 output += " %s%s\n" \
745 % (procname, modelname and (" -> %s" % modelname) or '')
750 def module_info(cap):
751 output = StringIO.StringIO()
752 modules = open(PROC_MODULES, 'r')
756 module = line.split()[0]
757 procs.append(ProcOutput([MODINFO, module],
758 caps[cap][MAX_TIME], output))
763 return output.getvalue()
766 def multipathd_topology(cap):
767 pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
768 stdout=PIPE, stderr=dev_null)
769 stdout, stderr = pipe.communicate('show topology')
775 output = StringIO.StringIO()
776 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'],
777 caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
781 if not procs[0].timed_out:
782 return output.getvalue().splitlines()
787 if not os.path.isfile(OPENVSWITCH_CONF_DB):
793 if os.path.getsize(OPENVSWITCH_CONF_DB) > max_size:
794 if os.path.isfile(OPENVSWITCH_COMPACT_DB):
795 os.unlink(OPENVSWITCH_COMPACT_DB)
797 output = StringIO.StringIO()
799 procs = [ProcOutput(['ovsdb-tool', 'compact',
800 OPENVSWITCH_CONF_DB, OPENVSWITCH_COMPACT_DB],
803 file_output(CAP_NETWORK_STATUS, [OPENVSWITCH_COMPACT_DB])
805 file_output(CAP_NETWORK_STATUS, [OPENVSWITCH_CONF_DB])
812 if os.path.isfile(OPENVSWITCH_COMPACT_DB):
813 os.unlink(OPENVSWITCH_COMPACT_DB)
821 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
823 fh = open('/proc/' + d + '/cmdline')
825 num_fds = len(os.listdir(os.path.join('/proc/' + d + '/fd')))
827 if not num_fds in fd_dict:
828 fd_dict[num_fds] = []
829 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
832 keys = fd_dict.keys()
833 keys.sort(lambda a, b: int(b) - int(a))
835 output += "%s: %s\n" % (k, str(fd_dict[k]))
839 def dump_rdac_groups(cap):
840 output = StringIO.StringIO()
841 procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
845 if not procs[0].timed_out:
847 for line in output.getvalue().splitlines():
848 if line.startswith('ID'):
850 elif line.startswith('----'):
853 group, _ = line.split(None, 1)
854 cmd_output(cap, [MPPUTIL, '-g', group])
857 def load_plugins(just_capabilities=False, filter=None):
858 global log_last_mod_time
860 def getText(nodelist):
862 for node in nodelist:
863 if node.nodeType == node.TEXT_NODE:
867 def getBoolAttr(el, attr, default=False):
869 val = el.getAttribute(attr).lower()
870 if val in ['true', 'false', 'yes', 'no']:
871 ret = val in ['true', 'yes']
874 for dir in [d for d in os.listdir(PLUGIN_DIR)
875 if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
876 if not caps.has_key(dir):
877 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
879 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
880 assert xmldoc.documentElement.tagName == "capability"
882 pii, min_size, max_size, min_time, max_time, mime = \
883 PII_MAYBE, -1, -1, -1, -1, MIME_TEXT
885 if xmldoc.documentElement.getAttribute("pii") in \
886 [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
887 pii = xmldoc.documentElement.getAttribute("pii")
888 if xmldoc.documentElement.getAttribute("min_size") != '':
890 xmldoc.documentElement.getAttribute("min_size"))
891 if xmldoc.documentElement.getAttribute("max_size") != '':
893 xmldoc.documentElement.getAttribute("max_size"))
894 if xmldoc.documentElement.getAttribute("min_time") != '':
895 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
896 if xmldoc.documentElement.getAttribute("max_time") != '':
897 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
898 if xmldoc.documentElement.getAttribute("mime") in \
899 [MIME_DATA, MIME_TEXT]:
900 mime = xmldoc.documentElement.getAttribute("mime")
901 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
902 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
904 cap(dir, pii, min_size, max_size, min_time, max_time, mime,
907 if just_capabilities:
910 plugdir = os.path.join(PLUGIN_DIR, dir)
911 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
912 xmldoc = parse(os.path.join(plugdir, file))
913 assert xmldoc.documentElement.tagName == "collect"
915 for el in xmldoc.documentElement.getElementsByTagName("*"):
916 filters_tmp = el.getAttribute("filters")
917 if filters_tmp == '':
920 filters = filters_tmp.split(',')
921 if not(filter is None or filter in filters):
923 if el.tagName == "files":
924 newest_first = getBoolAttr(el, 'newest_first')
925 if el.getAttribute("type") == "logs":
926 for fn in getText(el.childNodes).split():
927 prefix_output(dir, fn, newest_first=newest_first,
928 last_mod_time=log_last_mod_time)
930 file_output(dir, getText(el.childNodes).split(),
931 newest_first=newest_first)
932 elif el.tagName == "directory":
933 pattern = el.getAttribute("pattern")
936 negate = getBoolAttr(el, 'negate')
937 newest_first = getBoolAttr(el, 'newest_first')
938 if el.getAttribute("type") == "logs":
939 tree_output(dir, getText(el.childNodes),
940 pattern and re.compile(pattern) or None,
941 negate=negate, newest_first=newest_first,
942 last_mod_time=log_last_mod_time)
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")
951 binary = getBoolAttr(el, 'binary')
953 getText(el.childNodes), label, binary=binary)
956 def make_tar(subdir, suffix, output_fd, output_file):
957 global SILENT_MODE, data
960 if suffix == 'tar.bz2':
962 elif suffix == 'tar.gz':
966 if output_file is None:
967 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
969 filename = output_file
970 old_umask = os.umask(0077)
971 tf = tarfile.open(filename, mode)
974 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
977 for (k, v) in data.items():
979 tar_filename = os.path.join(subdir, construct_filename(k, v))
980 ti = tarfile.TarInfo(tar_filename)
985 if v.has_key('output'):
986 ti.mtime = v['output'].mtime
987 ti.size = len(v['output'].getvalue())
989 tf.addfile(ti, v['output'])
990 elif v.has_key('filename'):
991 s = os.stat(v['filename'])
992 ti.mtime = s.st_mtime
994 tf.addfile(ti, file(v['filename']))
1001 output('Writing tarball %s successful.' % filename)
1006 def make_zip(subdir, output_file):
1007 global SILENT_MODE, data
1009 if output_file is None:
1010 filename = "%s/%s.zip" % (BUG_DIR, subdir)
1012 filename = output_file
1013 old_umask = os.umask(0077)
1014 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
1018 for (k, v) in data.items():
1020 dest = os.path.join(subdir, construct_filename(k, v))
1022 if v.has_key('output'):
1023 zf.writestr(dest, v['output'].getvalue())
1025 if os.stat(v['filename']).st_size < 50:
1026 compress_type = zipfile.ZIP_STORED
1028 compress_type = zipfile.ZIP_DEFLATED
1029 zf.write(v['filename'], dest, compress_type)
1035 output('Writing archive %s successful.' % filename)
1040 def make_inventory(inventory, subdir):
1041 document = getDOMImplementation().createDocument(
1042 None, INVENTORY_XML_ROOT, None)
1044 # create summary entry
1045 s = document.createElement(INVENTORY_XML_SUMMARY)
1046 user = os.getenv('SUDO_USER', os.getenv('USER'))
1048 s.setAttribute('user', user)
1049 s.setAttribute('date', time.strftime('%c'))
1050 s.setAttribute('hostname', platform.node())
1051 s.setAttribute('uname', ' '.join(platform.uname()))
1052 s.setAttribute('uptime', commands.getoutput(UPTIME))
1053 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
1055 map(lambda (k, v): inventory_entry(document, subdir, k, v),
1057 return document.toprettyxml()
1060 def inventory_entry(document, subdir, k, v):
1062 el = document.createElement(INVENTORY_XML_ELEMENT)
1063 el.setAttribute('capability', v['cap'])
1064 el.setAttribute('filename',
1065 os.path.join(subdir, construct_filename(k, v)))
1066 el.setAttribute('md5sum', md5sum(v))
1067 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
1074 if d.has_key('filename'):
1075 f = open(d['filename'])
1077 while len(data) > 0:
1081 elif d.has_key('output'):
1082 m.update(d['output'].getvalue())
1083 return m.hexdigest()
1086 def construct_filename(k, v):
1087 if v.has_key('filename'):
1088 if v['filename'][0] == '/':
1089 return v['filename'][1:]
1091 return v['filename']
1092 s = k.replace(' ', '-')
1093 s = s.replace('--', '-')
1094 s = s.replace('/', '%')
1095 if s.find('.') == -1:
1101 def update_capabilities():
1105 def update_cap_size(cap, size):
1106 update_cap(cap, MIN_SIZE, size)
1107 update_cap(cap, MAX_SIZE, size)
1108 update_cap(cap, CHECKED, size > 0)
1111 def update_cap(cap, k, v):
1115 caps[cap] = tuple(l)
1118 def size_of_dir(d, pattern=None, negate=False):
1119 if os.path.isdir(d):
1120 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1126 def size_of_all(files, pattern=None, negate=False):
1127 return sum([size_of(f, pattern, negate) for f in files])
1130 def matches(f, pattern, negate):
1132 return not matches(f, pattern, False)
1134 return pattern is None or pattern.match(f)
1137 def size_of(f, pattern, negate):
1138 if os.path.isfile(f) and matches(f, pattern, negate):
1139 return os.stat(f)[6]
1141 return size_of_dir(f, pattern, negate)
1144 def print_capabilities():
1145 document = getDOMImplementation().createDocument(
1146 "ns", CAP_XML_ROOT, None)
1147 map(lambda key: capability(document, key),
1148 [k for k in caps.keys() if not caps[k][HIDDEN]])
1149 print document.toprettyxml()
1152 def capability(document, key):
1154 el = document.createElement(CAP_XML_ELEMENT)
1155 el.setAttribute('key', c[KEY])
1156 el.setAttribute('pii', c[PII])
1157 el.setAttribute('min-size', str(c[MIN_SIZE]))
1158 el.setAttribute('max-size', str(c[MAX_SIZE]))
1159 el.setAttribute('min-time', str(c[MIN_TIME]))
1160 el.setAttribute('max-time', str(c[MAX_TIME]))
1161 el.setAttribute('content-type', c[MIME])
1162 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1163 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1167 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1168 return '\n'.join([format % i for i in d.items()]) + '\n'
1172 yn = raw_input(prompt)
1174 return len(yn) == 0 or yn.lower()[0] == 'y'
1177 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1183 f = open('/proc/partitions')
1186 for line in f.readlines():
1187 (major, minor, blocks, name) = line.split()
1188 if int(major) < 254 and not partition_re.match(name):
1199 def __init__(self, command, max_time, inst=None, filter=None,
1201 self.command = command
1202 self.max_time = max_time
1204 self.running = False
1206 self.timed_out = False
1208 self.timeout = int(time.time()) + self.max_time
1209 self.filter = filter
1210 self.filter_state = {}
1212 self.bufsize = 1048576 # 1MB buffer
1214 self.bufsize = 1 # line buffered
1220 return isinstance(self.command, list) \
1221 and ' '.join(self.command) or self.command
1224 self.timed_out = False
1226 if ProcOutput.debug:
1227 output_ts("Starting '%s'" % self.cmdAsStr())
1228 self.proc = Popen(self.command, bufsize=self.bufsize,
1229 stdin=dev_null, stdout=PIPE, stderr=dev_null,
1230 shell=isinstance(self.command, str))
1231 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1232 fcntl.fcntl(self.proc.stdout.fileno(),
1233 fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1237 output_ts("'%s' failed" % self.cmdAsStr())
1238 self.running = False
1241 def terminate(self):
1244 self.proc.stdout.close()
1245 os.kill(self.proc.pid, SIGTERM)
1249 self.running = False
1250 self.status = SIGTERM
1252 def read_line(self):
1254 if self.bufsize == 1:
1255 line = self.proc.stdout.readline()
1257 line = self.proc.stdout.read(self.bufsize)
1260 self.proc.stdout.close()
1261 self.status = self.proc.wait()
1263 self.running = False
1266 line = self.filter(line, self.filter_state)
1268 self.inst.write(line)
1271 def run_procs(procs):
1279 active_procs.append(p)
1280 pipes.append(p.proc.stdout)
1282 elif p.status == None and not p.failed and not p.timed_out:
1285 active_procs.append(p)
1286 pipes.append(p.proc.stdout)
1293 (i, o, x) = select(pipes, [], [], 1.0)
1294 now = int(time.time())
1296 # handle process output
1297 for p in active_procs:
1298 if p.proc.stdout in i:
1302 if p.running and now > p.timeout:
1303 output_ts("'%s' timed out" % p.cmdAsStr())
1305 p.inst.write("\n** timeout **\n")
1313 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1315 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1323 def check_space(cap, name, size):
1324 global free_disk_space
1325 if free_disk_space is not None and size > free_disk_space:
1326 output("Omitting %s, out of disk space (requested: %u, allowed: %u)" %
1327 (name, size, free_disk_space))
1329 elif unlimited_data or caps[cap][MAX_SIZE] == -1 or \
1330 cap_sizes[cap] < caps[cap][MAX_SIZE]:
1331 cap_sizes[cap] += size
1332 if free_disk_space is not None:
1333 free_disk_space -= size
1336 output("Omitting %s, size constraint of %s exceeded" % (name, cap))
1340 def get_free_disk_space(path):
1341 path = os.path.abspath(path)
1342 while not os.path.exists(path):
1343 path = os.path.dirname(path)
1344 s = os.statvfs(path)
1345 return s.f_frsize * s.f_bfree
1348 class StringIOmtime(StringIO.StringIO):
1349 def __init__(self, buf=''):
1350 StringIO.StringIO.__init__(self, buf)
1351 self.mtime = time.time()
1354 StringIO.StringIO.write(self, s)
1355 self.mtime = time.time()
1358 if __name__ == "__main__":
1361 except KeyboardInterrupt:
1362 print "\nInterrupted."