3 # This library is free software; you can redistribute it and/or
4 # modify it under the terms of version 2.1 of the GNU Lesser General Public
5 # License as published by the Free Software Foundation.
7 # This library is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 # Lesser General Public License for more details.
12 # You should have received a copy of the GNU Lesser General Public
13 # License along with this library; if not, write to the Free Software
14 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 # Copyright (c) 2005, 2007 XenSource Ltd.
17 # Copyright (c) 2010, 2011, 2012 Nicira, Inc.
20 # To add new entries to the bugtool, you need to:
22 # Create a new capability. These declare the new entry to the GUI, including
23 # the expected size, time to collect, privacy implications, and whether the
24 # capability should be selected by default. One capability may refer to
25 # multiple files, assuming that they can be reasonably grouped together, and
26 # have the same privacy implications. You need:
28 # A new CAP_ constant.
29 # A cap() invocation to declare the capability.
31 # You then need to add calls to main() to collect the files. These will
32 # typically be calls to the helpers file_output(), tree_output(), cmd_output(),
37 warnings.filterwarnings(action="ignore", category=DeprecationWarning)
48 from xml.dom.minidom import parse, getDOMImplementation
50 from subprocess import Popen, PIPE
51 from select import select
52 from signal import SIGTERM, SIGUSR1
61 OS_RELEASE = platform.release()
67 APT_SOURCES_LIST = "/etc/apt/sources.list"
68 APT_SOURCES_LIST_D = "/etc/apt/sources.list.d"
69 BUG_DIR = "/var/log/ovs-bugtool"
70 PLUGIN_DIR = "@pkgdatadir@/bugtool-plugins"
71 GRUB_CONFIG = '/boot/grub/menu.lst'
72 BOOT_KERNEL = '/boot/vmlinuz-' + OS_RELEASE
73 BOOT_INITRD = '/boot/initrd-' + OS_RELEASE + '.img'
74 PROC_PARTITIONS = '/proc/partitions'
76 PROC_MOUNTS = '/proc/mounts'
77 ISCSI_CONF = '/etc/iscsi/iscsid.conf'
78 ISCSI_INITIATOR = '/etc/iscsi/initiatorname.iscsi'
79 LVM_CACHE = '/etc/lvm/cache/.cache'
80 LVM_CONFIG = '/etc/lvm/lvm.conf'
81 PROC_CPUINFO = '/proc/cpuinfo'
82 PROC_MEMINFO = '/proc/meminfo'
83 PROC_IOPORTS = '/proc/ioports'
84 PROC_INTERRUPTS = '/proc/interrupts'
85 PROC_SCSI = '/proc/scsi/scsi'
86 PROC_VERSION = '/proc/version'
87 PROC_MODULES = '/proc/modules'
88 PROC_DEVICES = '/proc/devices'
89 PROC_FILESYSTEMS = '/proc/filesystems'
90 PROC_CMDLINE = '/proc/cmdline'
91 PROC_CONFIG = '/proc/config.gz'
92 PROC_USB_DEV = '/proc/bus/usb/devices'
93 PROC_XEN_BALLOON = '/proc/xen/balloon'
94 PROC_NET_BONDING_DIR = '/proc/net/bonding'
95 IFCFG_RE = re.compile(r'^.*/ifcfg-.*')
96 ROUTE_RE = re.compile(r'^.*/route-.*')
97 SYSCONFIG_HWCONF = '/etc/sysconfig/hwconf'
98 SYSCONFIG_NETWORK = '/etc/sysconfig/network'
99 SYSCONFIG_NETWORK_SCRIPTS = '/etc/sysconfig/network-scripts'
100 PROC_NET_VLAN_DIR = '/proc/net/vlan'
101 PROC_NET_SOFTNET_STAT = '/proc/net/softnet_stat'
102 MODPROBE_CONF = '/etc/modprobe.conf'
103 MODPROBE_DIR = '/etc/modprobe.d'
104 RESOLV_CONF = '/etc/resolv.conf'
105 MPP_CONF = '/etc/mpp.conf'
106 MULTIPATH_CONF = '/etc/multipath.conf'
107 NSSWITCH_CONF = '/etc/nsswitch.conf'
108 NTP_CONF = '/etc/ntp.conf'
109 IPTABLES_CONFIG = '/etc/sysconfig/iptables-config'
111 HOSTS_ALLOW = '/etc/hosts.allow'
112 HOSTS_DENY = '/etc/hosts.deny'
113 DHCP_LEASE_DIR = ['/var/lib/dhclient', '/var/lib/dhcp3']
114 OPENVSWITCH_LOG_DIR = '@LOGDIR@/'
115 OPENVSWITCH_DEFAULT_SWITCH = '/etc/default/openvswitch-switch' # Debian
116 OPENVSWITCH_SYSCONFIG_SWITCH = '/etc/sysconfig/openvswitch' # RHEL
117 OPENVSWITCH_DEFAULT_CONTROLLER = '/etc/default/openvswitch-controller'
118 OPENVSWITCH_CONF_DB = '@DBDIR@/conf.db'
119 OPENVSWITCH_VSWITCHD_PID = '@RUNDIR@/ovs-vswitchd.pid'
120 COLLECTD_LOGS_DIR = '/var/lib/collectd/rrd'
121 VAR_LOG_DIR = '/var/log/'
122 VAR_LOG_CORE_DIR = '/var/log/core'
123 X11_LOGS_DIR = VAR_LOG_DIR
124 X11_LOGS_RE = re.compile(r'.*/Xorg\..*$')
125 X11_AUTH_DIR = '/root/'
126 X11_AUTH_RE = re.compile(r'.*/\.((Xauthority)|(serverauth\.[0-9]*))$')
127 YUM_LOG = '/var/log/yum.log'
128 YUM_REPOS_DIR = '/etc/yum.repos.d'
129 PAM_DIR = '/etc/pam.d'
130 KRB5_CONF = '/etc/krb5.conf'
136 os.environ['PATH'] = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:@pkgdatadir@/scripts'
139 CHKCONFIG = 'chkconfig'
142 DMIDECODE = 'dmidecode'
144 DPKG_QUERY = 'dpkg-query'
149 IFCONFIG = 'ifconfig'
150 IPTABLES = 'iptables'
151 ISCSIADM = 'iscsiadm'
155 LVDISPLAY = 'lvdisplay'
160 MULTIPATHD = 'multipathd'
162 OVS_DPCTL = 'ovs-dpctl'
163 OVS_OFCTL = 'ovs-ofctl'
164 OVS_VSCTL = 'ovs-vsctl'
165 OVS_APPCTL = 'ovs-appctl'
179 # PII -- Personally identifiable information. Of particular concern are
180 # things that would identify customers, or their network topology.
181 # Passwords are never to be included in any bug report, regardless of any PII
184 # NO -- No PII will be in these entries.
185 # YES -- PII will likely or certainly be in these entries.
186 # MAYBE -- The user may wish to audit these entries for PII.
187 # IF_CUSTOMIZED -- If the files are unmodified, then they will contain no PII,
188 # but since we encourage customers to edit these files, PII may have been
189 # introduced by the customer. This is used in particular for the networking
196 PII_IF_CUSTOMIZED = 'if_customized'
207 MIME_DATA = 'application/data'
208 MIME_TEXT = 'text/plain'
210 INVENTORY_XML_ROOT = "system-status-inventory"
211 INVENTORY_XML_SUMMARY = 'system-summary'
212 INVENTORY_XML_ELEMENT = 'inventory-entry'
213 CAP_XML_ROOT = "system-status-capabilities"
214 CAP_XML_ELEMENT = 'capability'
218 CAP_BOOT_LOADER = 'boot-loader'
219 CAP_COLLECTD_LOGS = 'collectd-logs'
220 CAP_DISK_INFO = 'disk-info'
221 CAP_FIRSTBOOT = 'firstboot'
222 CAP_HARDWARE_INFO = 'hardware-info'
223 CAP_HDPARM_T = 'hdparm-t'
224 CAP_HIGH_AVAILABILITY = 'high-availability'
225 CAP_KERNEL_INFO = 'kernel-info'
226 CAP_LOSETUP_A = 'loopback-devices'
227 CAP_MULTIPATH = 'multipath'
228 CAP_NETWORK_CONFIG = 'network-config'
229 CAP_NETWORK_STATUS = 'network-status'
232 CAP_PROCESS_LIST = 'process-list'
233 CAP_PERSISTENT_STATS = 'persistent-stats'
234 CAP_SYSTEM_LOGS = 'system-logs'
235 CAP_SYSTEM_SERVICES = 'system-services'
236 CAP_VNCTERM = 'vncterm'
239 CAP_X11_AUTH = 'X11-auth'
247 unlimited_data = False
250 def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
251 max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
252 caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
257 cap(CAP_BLOBS, PII_NO, max_size=5*MB)
258 cap(CAP_BOOT_LOADER, PII_NO, max_size=3*KB,
260 cap(CAP_COLLECTD_LOGS, PII_MAYBE, max_size=50*MB,
262 cap(CAP_DISK_INFO, PII_MAYBE, max_size=50*KB,
264 cap(CAP_FIRSTBOOT, PII_YES, min_size=60*KB, max_size=80*KB)
265 cap(CAP_HARDWARE_INFO, PII_MAYBE, max_size=30*KB,
267 cap(CAP_HDPARM_T, PII_NO, min_size=0, max_size=5*KB,
268 min_time=20, max_time=90, checked=False, hidden=True)
269 cap(CAP_HIGH_AVAILABILITY, PII_MAYBE, max_size=5*MB)
270 cap(CAP_KERNEL_INFO, PII_MAYBE, max_size=120*KB,
272 cap(CAP_LOSETUP_A, PII_MAYBE, max_size=KB, max_time=5)
273 cap(CAP_MULTIPATH, PII_MAYBE, max_size=20*KB,
275 cap(CAP_NETWORK_CONFIG, PII_IF_CUSTOMIZED,
276 min_size=0, max_size=40*KB)
277 cap(CAP_NETWORK_STATUS, PII_YES, max_size=50*KB,
279 cap(CAP_PAM, PII_NO, max_size=50*KB)
280 cap(CAP_PERSISTENT_STATS, PII_MAYBE, max_size=50*MB,
282 cap(CAP_PROCESS_LIST, PII_YES, max_size=30*KB,
284 cap(CAP_SYSTEM_LOGS, PII_MAYBE, max_size=50*MB,
286 cap(CAP_SYSTEM_SERVICES, PII_NO, max_size=5*KB,
288 cap(CAP_VNCTERM, PII_MAYBE, checked = False)
289 cap(CAP_WLB, PII_NO, max_size=3*MB,
291 cap(CAP_X11_LOGS, PII_NO, max_size=100*KB)
292 cap(CAP_X11_AUTH, PII_NO, max_size=100*KB)
293 cap(CAP_YUM, PII_IF_CUSTOMIZED, max_size=10*KB,
296 ANSWER_YES_TO_ALL = False
300 dev_null = open('/dev/null', 'r+')
308 output("[%s] %s" % (time.strftime("%x %X %Z"), x))
310 def cmd_output(cap, args, label=None, filter=None):
313 if isinstance(args, list):
314 a = [aa for aa in args]
315 a[0] = os.path.basename(a[0])
319 data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter}
321 def file_output(cap, path_list, newest_first=False):
323 If newest_first is True, the list of files in path_list is sorted
324 by file modification time in descending order, else its sorted
329 for path in path_list:
334 path_entries.append((path, s))
336 mtime = lambda(path, stat): stat.st_mtime
337 path_entries.sort(key=mtime, reverse=newest_first)
338 for p in path_entries:
339 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
340 cap_sizes[cap] < caps[cap][MAX_SIZE]:
341 data[p] = {'cap': cap, 'filename': p[0]}
342 cap_sizes[cap] += p[1].st_size
344 output("Omitting %s, size constraint of %s exceeded" % (p[0], cap))
346 def tree_output(cap, path, pattern=None, negate=False, newest_first=False):
348 Walks the directory tree rooted at path. Files in current dir are processed
349 before files in sub-dirs.
352 if os.path.exists(path):
353 for root, dirs, files in os.walk(path):
354 fns = [fn for fn in [os.path.join(root, f) for f in files]
355 if os.path.isfile(fn) and matches(fn, pattern, negate)]
356 file_output(cap, fns, newest_first=newest_first)
358 def func_output(cap, label, func):
360 t = str(func).split()
361 data[label] = {'cap': cap, 'func': func}
366 for (k, v) in data.items():
368 if v.has_key('cmd_args'):
369 v['output'] = StringIOmtime()
370 if not process_lists.has_key(cap):
371 process_lists[cap] = []
372 process_lists[cap].append(ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'], v['filter']))
373 elif v.has_key('filename') and v['filename'].startswith('/proc/'):
374 # proc files must be read into memory
376 f = open(v['filename'], 'r')
379 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
380 cap_sizes[cap] < caps[cap][MAX_SIZE]:
381 v['output'] = StringIOmtime(s)
382 cap_sizes[cap] += len(s)
384 output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
387 elif v.has_key('func'):
392 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
393 cap_sizes[cap] < caps[cap][MAX_SIZE]:
394 v['output'] = StringIOmtime(s)
395 cap_sizes[cap] += len(s)
397 output("Omitting %s, size constraint of %s exceeded" % (k, cap))
399 run_procs(process_lists.values())
403 global ANSWER_YES_TO_ALL, SILENT_MODE
404 global entries, data, dbg, unlimited_data
407 only_ovs_info = False
408 collect_all_info = True
410 # we need access to privileged files, exit if we are not running as root
412 print >>sys.stderr, "Error: ovs-bugtool must be run as root"
416 output_type = 'tar.bz2'
423 (options, params) = getopt.gnu_getopt(
424 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
425 'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
427 except getopt.GetoptError, opterr:
428 print >>sys.stderr, opterr
436 entries = [e for e in caps.keys() if caps[e][CHECKED]]
438 for (k, v) in options:
439 if k == '--capabilities':
440 update_capabilities()
445 if v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
448 print >>sys.stderr, "Invalid output format '%s'" % v
451 # "-s" or "--silent" means suppress output (except for the final
452 # output filename at the end)
453 if k in ['-s', '--silent']:
456 if k == '--entries' and v != '':
457 entries = v.split(',')
459 # If the user runs the script with "-y" or "--yestoall" we don't ask
460 # all the really annoying questions.
461 if k in ['-y', '--yestoall']:
462 ANSWER_YES_TO_ALL = True
467 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
468 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
470 print >>sys.stderr, "Invalid output file descriptor", output_fd
477 entries = caps.keys()
478 elif k == '--unlimited':
479 unlimited_data = True
482 ProcOutput.debug = True
486 collect_all_info = False
489 print >>sys.stderr, "Invalid additional arguments", str(params)
492 if output_fd != -1 and output_type != 'tar':
493 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
496 if output_fd != -1 and output_file is not None:
497 print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
500 if ANSWER_YES_TO_ALL:
501 output("Warning: '--yestoall' argument provided, will not prompt 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], label='vmlinuz-initrd.md5sum')
523 tree_output(CAP_COLLECTD_LOGS, COLLECTD_LOGS_DIR)
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 for d in disk_list():
530 cmd_output(CAP_DISK_INFO, [HDPARM, '-I', '/dev/%s' % d])
531 if len(pidof('iscsid')) != 0:
532 cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
533 cmd_output(CAP_DISK_INFO, [VGSCAN])
534 cmd_output(CAP_DISK_INFO, [PVS])
535 cmd_output(CAP_DISK_INFO, [VGS])
536 cmd_output(CAP_DISK_INFO, [LVS])
537 file_output(CAP_DISK_INFO, [LVM_CACHE, LVM_CONFIG])
538 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
539 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
540 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
541 cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
542 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
543 cmd_output(CAP_DISK_INFO, [LVDISPLAY, '--map'])
545 file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
546 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
547 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
548 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
549 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
550 file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
551 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
554 for d in disk_list():
555 cmd_output(CAP_HDPARM_T, [HDPARM, '-tT', '/dev/%s' % d])
557 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
558 PROC_FILESYSTEMS, PROC_CMDLINE])
559 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
560 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
561 file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
562 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
563 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
565 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
567 file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
568 cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
569 func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
570 cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
571 if CAP_MULTIPATH in entries and collect_all_info:
572 dump_rdac_groups(CAP_MULTIPATH)
574 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
575 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
576 file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
577 file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
578 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_CONF_DB])
580 cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
581 cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
582 cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
583 cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
584 for dir in DHCP_LEASE_DIR:
585 tree_output(CAP_NETWORK_STATUS, dir)
586 cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
587 for p in os.listdir('/sys/class/net/'):
589 f = open('/sys/class/net/%s/type' % p, 'r')
592 if os.path.islink('/sys/class/net/%s/device' % p) and int(t) == 1:
594 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
595 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
596 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
597 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
598 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
600 cmd_output(CAP_NETWORK_STATUS,
601 [TC, '-s', '-d', 'class', 'show', 'dev', p])
604 tree_output(CAP_NETWORK_STATUS, PROC_NET_BONDING_DIR)
605 tree_output(CAP_NETWORK_STATUS, PROC_NET_VLAN_DIR)
606 cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
607 file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
608 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
609 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
611 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
612 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
613 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
615 vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
616 vspid = int(vspidfile.readline().strip())
618 for b in bond_list(vspid):
619 cmd_output(CAP_NETWORK_STATUS,
620 [OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
621 'ovs-appctl-bond-show-%s.out' % b)
625 tree_output(CAP_PAM, PAM_DIR)
626 file_output(CAP_PAM, [KRB5_CONF])
628 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
629 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
631 logs = ([ VAR_LOG_DIR + x for x in
632 [ 'crit.log', 'kern.log', 'daemon.log', 'user.log',
633 'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot' ]]
634 + [ OPENVSWITCH_LOG_DIR + x for x in
635 [ 'ovs-vswitchd.log', 'ovsdb-server.log',
636 'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log' ]])
637 file_output(CAP_SYSTEM_LOGS, logs)
638 file_output(CAP_SYSTEM_LOGS,
639 [ '%s.%d' % (f, n) for n in range(20) for f in logs ])
640 file_output(CAP_SYSTEM_LOGS,
641 [ '%s.%d.gz' % (f, n) for n in range(20) for f in logs ])
643 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
644 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
646 cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
648 tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
649 tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
650 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
652 file_output(CAP_YUM, [YUM_LOG])
653 tree_output(CAP_YUM, YUM_REPOS_DIR)
654 cmd_output(CAP_YUM, [RPM, '-qa'])
655 file_output(CAP_YUM, [APT_SOURCES_LIST])
656 tree_output(CAP_YUM, APT_SOURCES_LIST_D)
657 cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
659 # Filter out ovs relevant information if --ovs option passed
660 # else collect all information
664 ovs_info_caps = [CAP_NETWORK_STATUS, CAP_SYSTEM_LOGS,
666 ovs_info_list = ['process-tree']
667 # We cannot use iteritems, since we modify 'data' as we pass through
668 for (k, v) in data.items():
674 if info not in ovs_info_list and cap not in ovs_info_caps:
678 filter = ",".join(filters)
683 load_plugins(filter=filter)
687 # permit the user to filter out data
688 # We cannot use iteritems, since we modify 'data' as we pass through
689 for (k, v) in sorted(data.items()):
695 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % key):
698 # collect selected data now
699 output_ts('Running commands to collect data')
702 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
705 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
709 if output_file is None:
712 dirname = os.path.dirname(output_file)
713 if dirname and not os.path.exists(dirname):
720 output_ts('Creating output file')
722 if output_type.startswith('tar'):
723 make_tar(subdir, output_type, output_fd, output_file)
725 make_zip(subdir, output_file)
730 print >>sys.stderr, "Category sizes (max, actual):\n"
731 for c in caps.keys():
732 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
736 def find_tapdisk_logs():
737 return glob.glob('/var/log/blktap/*.log*')
739 def generate_tapdisk_logs():
740 for pid in pidof('tapdisk'):
742 os.kill(pid, SIGUSR1)
743 output_ts("Including logs for tapdisk process %d" % pid)
746 # give processes a second to write their logs
749 def clean_tapdisk_logs():
750 for filename in find_tapdisk_logs():
756 def filter_db_pii(str, state):
757 if 'in_secret_table' not in state:
758 state['in_secret_table'] = False
760 if str.startswith('<table ') and 'name="secret"' in str:
761 state['in_secret_table'] = True
762 elif str.startswith('</table>'):
763 state['in_secret_table'] = False
765 if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
766 str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
769 def dump_scsi_hosts(cap):
771 l = os.listdir('/sys/class/scsi_host')
777 f = open('/sys/class/scsi_host/%s/proc_name' % h)
778 procname = f.readline().strip("\n")
784 f = open('/sys/class/scsi_host/%s/model_name' % h)
785 modelname = f.readline().strip("\n")
791 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
795 def module_info(cap):
796 output = StringIO.StringIO()
797 modules = open(PROC_MODULES, 'r')
801 module = line.split()[0]
802 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
807 return output.getvalue()
810 def multipathd_topology(cap):
811 pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
812 stdout=PIPE, stderr=dev_null)
813 stdout, stderr = pipe.communicate('show topology')
818 output = StringIO.StringIO()
819 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
823 if not procs[0].timed_out:
824 return output.getvalue().splitlines()
828 output = StringIO.StringIO()
829 procs = [ProcOutput([OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
833 if not procs[0].timed_out:
834 bonds = output.getvalue().splitlines()[1:]
835 return [x.split('\t')[1] for x in bonds]
841 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
843 fh = open('/proc/'+d+'/cmdline')
845 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
847 if not num_fds in fd_dict:
848 fd_dict[num_fds] = []
849 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
852 keys = fd_dict.keys()
853 keys.sort(lambda a, b: int(b) - int(a))
855 output += "%s: %s\n" % (k, str(fd_dict[k]))
858 def dump_rdac_groups(cap):
859 output = StringIO.StringIO()
860 procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
864 if not procs[0].timed_out:
866 for line in output.getvalue().splitlines():
867 if line.startswith('ID'):
869 elif line.startswith('----'):
872 group, _ = line.split(None, 1)
873 cmd_output(cap, [MPPUTIL, '-g', group])
875 def load_plugins(just_capabilities=False, filter=None):
876 def getText(nodelist):
878 for node in nodelist:
879 if node.nodeType == node.TEXT_NODE:
883 def getBoolAttr(el, attr, default=False):
885 val = el.getAttribute(attr).lower()
886 if val in ['true', 'false', 'yes', 'no']:
887 ret = val in ['true', 'yes']
890 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
891 if not caps.has_key(dir):
892 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
894 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
895 assert xmldoc.documentElement.tagName == "capability"
897 pii, min_size, max_size, min_time, max_time, mime = \
898 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
900 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
901 pii = xmldoc.documentElement.getAttribute("pii")
902 if xmldoc.documentElement.getAttribute("min_size") != '':
903 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
904 if xmldoc.documentElement.getAttribute("max_size") != '':
905 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
906 if xmldoc.documentElement.getAttribute("min_time") != '':
907 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
908 if xmldoc.documentElement.getAttribute("max_time") != '':
909 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
910 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
911 mime = xmldoc.documentElement.getAttribute("mime")
912 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
913 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
915 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
917 if just_capabilities:
920 plugdir = os.path.join(PLUGIN_DIR, dir)
921 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
922 xmldoc = parse(os.path.join(plugdir, file))
923 assert xmldoc.documentElement.tagName == "collect"
925 for el in xmldoc.documentElement.getElementsByTagName("*"):
926 filters_tmp = el.getAttribute("filters")
927 if filters_tmp == '':
930 filters = filters_tmp.split(',')
931 if not(filter is None or filter in filters):
933 if el.tagName == "files":
934 newest_first = getBoolAttr(el, 'newest_first')
935 file_output(dir, getText(el.childNodes).split(),
936 newest_first=newest_first)
937 elif el.tagName == "directory":
938 pattern = el.getAttribute("pattern")
939 if pattern == '': pattern = None
940 negate = getBoolAttr(el, 'negate')
941 newest_first = getBoolAttr(el, 'newest_first')
942 tree_output(dir, getText(el.childNodes),
943 pattern and re.compile(pattern) or None,
944 negate=negate, newest_first=newest_first)
945 elif el.tagName == "command":
946 label = el.getAttribute("label")
947 if label == '': label = None
948 cmd_output(dir, getText(el.childNodes), label)
950 def make_tar(subdir, suffix, output_fd, output_file):
951 global SILENT_MODE, data
954 if suffix == 'tar.bz2':
956 elif suffix == 'tar.gz':
960 if output_file is None:
961 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
963 filename = output_file
964 old_umask = os.umask(0077)
965 tf = tarfile.open(filename, mode)
968 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
971 for (k, v) in data.items():
973 tar_filename = os.path.join(subdir, construct_filename(k, v))
974 ti = tarfile.TarInfo(tar_filename)
979 if v.has_key('output'):
980 ti.mtime = v['output'].mtime
981 ti.size = len(v['output'].getvalue())
983 tf.addfile(ti, v['output'])
984 elif v.has_key('filename'):
985 s = os.stat(v['filename'])
986 ti.mtime = s.st_mtime
988 tf.addfile(ti, file(v['filename']))
995 output ('Writing tarball %s successful.' % filename)
1000 def make_zip(subdir, output_file):
1001 global SILENT_MODE, data
1003 if output_file is None:
1004 filename = "%s/%s.zip" % (BUG_DIR, subdir)
1006 filename = output_file
1007 old_umask = os.umask(0077)
1008 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
1012 for (k, v) in data.items():
1014 dest = os.path.join(subdir, construct_filename(k, v))
1016 if v.has_key('output'):
1017 zf.writestr(dest, v['output'].getvalue())
1019 if os.stat(v['filename']).st_size < 50:
1020 compress_type = zipfile.ZIP_STORED
1022 compress_type = zipfile.ZIP_DEFLATED
1023 zf.write(v['filename'], dest, compress_type)
1029 output ('Writing archive %s successful.' % filename)
1034 def make_inventory(inventory, subdir):
1035 document = getDOMImplementation().createDocument(
1036 None, INVENTORY_XML_ROOT, None)
1038 # create summary entry
1039 s = document.createElement(INVENTORY_XML_SUMMARY)
1040 user = os.getenv('SUDO_USER', os.getenv('USER'))
1042 s.setAttribute('user', user)
1043 s.setAttribute('date', time.strftime('%c'))
1044 s.setAttribute('hostname', platform.node())
1045 s.setAttribute('uname', ' '.join(platform.uname()))
1046 s.setAttribute('uptime', commands.getoutput(UPTIME))
1047 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
1049 map(lambda (k, v): inventory_entry(document, subdir, k, v),
1051 return document.toprettyxml()
1053 def inventory_entry(document, subdir, k, v):
1055 el = document.createElement(INVENTORY_XML_ELEMENT)
1056 el.setAttribute('capability', v['cap'])
1057 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
1058 el.setAttribute('md5sum', md5sum(v))
1059 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
1066 if d.has_key('filename'):
1067 f = open(d['filename'])
1069 while len(data) > 0:
1073 elif d.has_key('output'):
1074 m.update(d['output'].getvalue())
1075 return m.hexdigest()
1078 def construct_filename(k, v):
1079 if v.has_key('filename'):
1080 if v['filename'][0] == '/':
1081 return v['filename'][1:]
1083 return v['filename']
1084 s = k.replace(' ', '-')
1085 s = s.replace('--', '-')
1086 s = s.replace('/', '%')
1087 if s.find('.') == -1:
1092 def update_capabilities():
1095 def update_cap_size(cap, size):
1096 update_cap(cap, MIN_SIZE, size)
1097 update_cap(cap, MAX_SIZE, size)
1098 update_cap(cap, CHECKED, size > 0)
1101 def update_cap(cap, k, v):
1105 caps[cap] = tuple(l)
1108 def size_of_dir(d, pattern=None, negate=False):
1109 if os.path.isdir(d):
1110 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1116 def size_of_all(files, pattern=None, negate=False):
1117 return sum([size_of(f, pattern, negate) for f in files])
1120 def matches(f, pattern, negate):
1122 return not matches(f, pattern, False)
1124 return pattern is None or pattern.match(f)
1127 def size_of(f, pattern, negate):
1128 if os.path.isfile(f) and matches(f, pattern, negate):
1129 return os.stat(f)[6]
1131 return size_of_dir(f, pattern, negate)
1134 def print_capabilities():
1135 document = getDOMImplementation().createDocument(
1136 "ns", CAP_XML_ROOT, None)
1137 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1138 print document.toprettyxml()
1140 def capability(document, key):
1142 el = document.createElement(CAP_XML_ELEMENT)
1143 el.setAttribute('key', c[KEY])
1144 el.setAttribute('pii', c[PII])
1145 el.setAttribute('min-size', str(c[MIN_SIZE]))
1146 el.setAttribute('max-size', str(c[MAX_SIZE]))
1147 el.setAttribute('min-time', str(c[MIN_TIME]))
1148 el.setAttribute('max-time', str(c[MAX_TIME]))
1149 el.setAttribute('content-type', c[MIME])
1150 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1151 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1155 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1156 return '\n'.join([format % i for i in d.items()]) + '\n'
1160 yn = raw_input(prompt)
1162 return len(yn) == 0 or yn.lower()[0] == 'y'
1165 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1170 f = open('/proc/partitions')
1173 for line in f.readlines():
1174 (major, minor, blocks, name) = line.split()
1175 if int(major) < 254 and not partition_re.match(name):
1186 def __init__(self, command, max_time, inst=None, filter=None):
1187 self.command = command
1188 self.max_time = max_time
1190 self.running = False
1192 self.timed_out = False
1194 self.timeout = int(time.time()) + self.max_time
1195 self.filter = filter
1196 self.filter_state = {}
1202 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1205 self.timed_out = False
1207 if ProcOutput.debug:
1208 output_ts("Starting '%s'" % self.cmdAsStr())
1209 self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1210 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1211 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1215 output_ts("'%s' failed" % self.cmdAsStr())
1216 self.running = False
1219 def terminate(self):
1222 self.proc.stdout.close()
1223 os.kill(self.proc.pid, SIGTERM)
1227 self.running = False
1228 self.status = SIGTERM
1230 def read_line(self):
1232 line = self.proc.stdout.readline()
1235 self.proc.stdout.close()
1236 self.status = self.proc.wait()
1238 self.running = False
1241 line = self.filter(line, self.filter_state)
1243 self.inst.write(line)
1245 def run_procs(procs):
1253 active_procs.append(p)
1254 pipes.append(p.proc.stdout)
1256 elif p.status == None and not p.failed and not p.timed_out:
1259 active_procs.append(p)
1260 pipes.append(p.proc.stdout)
1267 (i, o, x) = select(pipes, [], [], 1.0)
1268 now = int(time.time())
1270 # handle process output
1271 for p in active_procs:
1272 if p.proc.stdout in i:
1276 if p.running and now > p.timeout:
1277 output_ts("'%s' timed out" % p.cmdAsStr())
1279 p.inst.write("\n** timeout **\n")
1287 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1289 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1297 class StringIOmtime(StringIO.StringIO):
1298 def __init__(self, buf=''):
1299 StringIO.StringIO.__init__(self, buf)
1300 self.mtime = time.time()
1303 StringIO.StringIO.write(self, s)
1304 self.mtime = time.time()
1307 if __name__ == "__main__":
1310 except KeyboardInterrupt:
1311 print "\nInterrupted."