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'
231 CAP_OPENVSWITCH_LOGS = 'ovs-system-logs'
233 CAP_PROCESS_LIST = 'process-list'
234 CAP_PERSISTENT_STATS = 'persistent-stats'
235 CAP_SYSTEM_LOGS = 'system-logs'
236 CAP_SYSTEM_SERVICES = 'system-services'
237 CAP_VNCTERM = 'vncterm'
240 CAP_X11_AUTH = 'X11-auth'
248 unlimited_data = False
251 def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
252 max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
253 caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
258 cap(CAP_BLOBS, PII_NO, max_size=5*MB)
259 cap(CAP_BOOT_LOADER, PII_NO, max_size=3*KB,
261 cap(CAP_COLLECTD_LOGS, PII_MAYBE, max_size=50*MB,
263 cap(CAP_DISK_INFO, PII_MAYBE, max_size=50*KB,
265 cap(CAP_FIRSTBOOT, PII_YES, min_size=60*KB, max_size=80*KB)
266 cap(CAP_HARDWARE_INFO, PII_MAYBE, max_size=30*KB,
268 cap(CAP_HDPARM_T, PII_NO, min_size=0, max_size=5*KB,
269 min_time=20, max_time=90, checked=False, hidden=True)
270 cap(CAP_HIGH_AVAILABILITY, PII_MAYBE, max_size=5*MB)
271 cap(CAP_KERNEL_INFO, PII_MAYBE, max_size=120*KB,
273 cap(CAP_LOSETUP_A, PII_MAYBE, max_size=KB, max_time=5)
274 cap(CAP_MULTIPATH, PII_MAYBE, max_size=20*KB,
276 cap(CAP_NETWORK_CONFIG, PII_IF_CUSTOMIZED,
277 min_size=0, max_size=40*KB)
278 cap(CAP_NETWORK_STATUS, PII_YES, max_size=50*MB,
280 cap(CAP_OPENVSWITCH_LOGS, PII_MAYBE, max_size=-1,
282 cap(CAP_PAM, PII_NO, max_size=50*KB)
283 cap(CAP_PERSISTENT_STATS, PII_MAYBE, max_size=50*MB,
285 cap(CAP_PROCESS_LIST, PII_YES, max_size=30*KB,
287 cap(CAP_SYSTEM_LOGS, PII_MAYBE, max_size=200*MB,
289 cap(CAP_SYSTEM_SERVICES, PII_NO, max_size=5*KB,
291 cap(CAP_VNCTERM, PII_MAYBE, checked = False)
292 cap(CAP_WLB, PII_NO, max_size=3*MB,
294 cap(CAP_X11_LOGS, PII_NO, max_size=100*KB)
295 cap(CAP_X11_AUTH, PII_NO, max_size=100*KB)
296 cap(CAP_YUM, PII_IF_CUSTOMIZED, max_size=10*KB,
299 ANSWER_YES_TO_ALL = False
303 dev_null = open('/dev/null', 'r+')
311 output("[%s] %s" % (time.strftime("%x %X %Z"), x))
313 def cmd_output(cap, args, label=None, filter=None):
316 if isinstance(args, list):
317 a = [aa for aa in args]
318 a[0] = os.path.basename(a[0])
322 data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter}
324 def file_output(cap, path_list, newest_first=False):
326 If newest_first is True, the list of files in path_list is sorted
327 by file modification time in descending order, else its sorted
332 for path in path_list:
337 path_entries.append((path, s))
339 mtime = lambda(path, stat): stat.st_mtime
340 path_entries.sort(key=mtime, reverse=newest_first)
341 for p in path_entries:
342 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
343 cap_sizes[cap] < caps[cap][MAX_SIZE]:
344 data[p] = {'cap': cap, 'filename': p[0]}
345 cap_sizes[cap] += p[1].st_size
347 output("Omitting %s, size constraint of %s exceeded" % (p[0], cap))
349 def tree_output(cap, path, pattern=None, negate=False, newest_first=False):
351 Walks the directory tree rooted at path. Files in current dir are processed
352 before files in sub-dirs.
355 if os.path.exists(path):
356 for root, dirs, files in os.walk(path):
357 fns = [fn for fn in [os.path.join(root, f) for f in files]
358 if os.path.isfile(fn) and matches(fn, pattern, negate)]
359 file_output(cap, fns, newest_first=newest_first)
361 def func_output(cap, label, func):
363 t = str(func).split()
364 data[label] = {'cap': cap, 'func': func}
369 for (k, v) in data.items():
371 if v.has_key('cmd_args'):
372 v['output'] = StringIOmtime()
373 if not process_lists.has_key(cap):
374 process_lists[cap] = []
375 process_lists[cap].append(ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'], v['filter']))
376 elif v.has_key('filename') and v['filename'].startswith('/proc/'):
377 # proc files must be read into memory
379 f = open(v['filename'], 'r')
382 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
383 cap_sizes[cap] < caps[cap][MAX_SIZE]:
384 v['output'] = StringIOmtime(s)
385 cap_sizes[cap] += len(s)
387 output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
390 elif v.has_key('func'):
395 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
396 cap_sizes[cap] < caps[cap][MAX_SIZE]:
397 v['output'] = StringIOmtime(s)
398 cap_sizes[cap] += len(s)
400 output("Omitting %s, size constraint of %s exceeded" % (k, cap))
402 run_procs(process_lists.values())
406 global ANSWER_YES_TO_ALL, SILENT_MODE
407 global entries, data, dbg, unlimited_data
410 only_ovs_info = False
411 collect_all_info = True
413 # we need access to privileged files, exit if we are not running as root
415 print >>sys.stderr, "Error: ovs-bugtool must be run as root"
419 output_type = 'tar.gz'
422 # Default value for the number of rotated logs.
429 (options, params) = getopt.gnu_getopt(
430 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
431 'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
432 'debug', 'ovs', 'log-days='])
433 except getopt.GetoptError, opterr:
434 print >>sys.stderr, opterr
442 entries = [e for e in caps.keys() if caps[e][CHECKED]]
444 for (k, v) in options:
445 if k == '--capabilities':
446 update_capabilities()
451 if v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
454 print >>sys.stderr, "Invalid output format '%s'" % v
457 # "-s" or "--silent" means suppress output (except for the final
458 # output filename at the end)
459 if k in ['-s', '--silent']:
462 if k == '--entries' and v != '':
463 entries = v.split(',')
465 # If the user runs the script with "-y" or "--yestoall" we don't ask
466 # all the really annoying questions.
467 if k in ['-y', '--yestoall']:
468 ANSWER_YES_TO_ALL = True
473 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
474 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
476 print >>sys.stderr, "Invalid output file descriptor", output_fd
483 entries = caps.keys()
484 elif k == '--unlimited':
485 unlimited_data = True
488 ProcOutput.debug = True
492 collect_all_info = False
494 if k == '--log-days':
495 log_days = int(v) + 1
498 print >>sys.stderr, "Invalid additional arguments", str(params)
501 if output_fd != -1 and output_type != 'tar':
502 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
505 if output_fd != -1 and output_file is not None:
506 print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
509 if ANSWER_YES_TO_ALL:
510 output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
513 This application will collate dmesg output, details of the
514 hardware configuration of your machine, information about the build of
515 openvswitch that you are using, plus, if you allow it, various logs.
517 The collated information will be saved as a .%s for archiving or
518 sending to a Technical Support Representative.
520 The logs may contain private information, and if you are at all
521 worried about that, you should exit now, or you should explicitly
522 exclude those logs from the archive.
526 # assemble potential data
528 file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
529 cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
530 cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
532 tree_output(CAP_COLLECTD_LOGS, COLLECTD_LOGS_DIR)
533 cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
534 file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
535 file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
536 cmd_output(CAP_DISK_INFO, [DF, '-alT'])
537 cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
538 for d in disk_list():
539 cmd_output(CAP_DISK_INFO, [HDPARM, '-I', '/dev/%s' % d])
540 if len(pidof('iscsid')) != 0:
541 cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
542 cmd_output(CAP_DISK_INFO, [VGSCAN])
543 cmd_output(CAP_DISK_INFO, [PVS])
544 cmd_output(CAP_DISK_INFO, [VGS])
545 cmd_output(CAP_DISK_INFO, [LVS])
546 file_output(CAP_DISK_INFO, [LVM_CACHE, LVM_CONFIG])
547 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
548 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
549 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
550 cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
551 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
552 cmd_output(CAP_DISK_INFO, [LVDISPLAY, '--map'])
554 file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
555 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
556 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
557 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
558 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
559 file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
560 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
563 for d in disk_list():
564 cmd_output(CAP_HDPARM_T, [HDPARM, '-tT', '/dev/%s' % d])
566 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
567 PROC_FILESYSTEMS, PROC_CMDLINE])
568 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
569 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
570 file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
571 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
572 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
574 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
576 file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
577 cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
578 func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
579 cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
580 if CAP_MULTIPATH in entries and collect_all_info:
581 dump_rdac_groups(CAP_MULTIPATH)
583 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
584 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
585 file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
586 file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
587 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_CONF_DB])
589 cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
590 cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
591 cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
592 cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
593 for dir in DHCP_LEASE_DIR:
594 tree_output(CAP_NETWORK_STATUS, dir)
595 cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
596 for p in os.listdir('/sys/class/net/'):
598 f = open('/sys/class/net/%s/type' % p, 'r')
601 if os.path.islink('/sys/class/net/%s/device' % p) and int(t) == 1:
603 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
604 if not p.startswith('vif') and not p.startswith('tap'):
605 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
606 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
607 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
608 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
610 cmd_output(CAP_NETWORK_STATUS,
611 [TC, '-s', '-d', 'class', 'show', 'dev', p])
614 tree_output(CAP_NETWORK_STATUS, PROC_NET_BONDING_DIR)
615 tree_output(CAP_NETWORK_STATUS, PROC_NET_VLAN_DIR)
616 cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
617 file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
618 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
619 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
621 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
622 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
623 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
625 vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
626 vspid = int(vspidfile.readline().strip())
628 for b in bond_list(vspid):
629 cmd_output(CAP_NETWORK_STATUS,
630 [OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
631 'ovs-appctl-bond-show-%s.out' % b)
635 tree_output(CAP_PAM, PAM_DIR)
636 file_output(CAP_PAM, [KRB5_CONF])
638 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
639 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
641 system_logs = (CAP_SYSTEM_LOGS, [ VAR_LOG_DIR + x for x in
642 ['crit.log', 'kern.log', 'daemon.log', 'user.log',
643 'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot']])
644 ovs_logs = (CAP_OPENVSWITCH_LOGS, [ OPENVSWITCH_LOG_DIR + x for x in
645 ['ovs-vswitchd.log', 'ovsdb-server.log',
646 'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log']])
647 for cap, logs in [system_logs, ovs_logs]:
648 file_output(cap, logs)
650 ['%s.%d' % (f, n) for n in range(log_days) for f in logs])
652 ['%s.%d.gz' % (f, n) for n in range(log_days) for f in logs])
654 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
655 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
657 cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
659 tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
660 tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
661 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
663 file_output(CAP_YUM, [YUM_LOG])
664 tree_output(CAP_YUM, YUM_REPOS_DIR)
665 cmd_output(CAP_YUM, [RPM, '-qa'])
666 file_output(CAP_YUM, [APT_SOURCES_LIST])
667 tree_output(CAP_YUM, APT_SOURCES_LIST_D)
668 cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
670 # Filter out ovs relevant information if --ovs option passed
671 # else collect all information
675 ovs_info_caps = [CAP_NETWORK_STATUS, CAP_SYSTEM_LOGS,
677 ovs_info_list = ['process-tree']
678 # We cannot use iteritems, since we modify 'data' as we pass through
679 for (k, v) in data.items():
685 if info not in ovs_info_list and cap not in ovs_info_caps:
689 filter = ",".join(filters)
694 load_plugins(filter=filter)
698 # permit the user to filter out data
699 # We cannot use iteritems, since we modify 'data' as we pass through
700 for (k, v) in sorted(data.items()):
706 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % key):
709 # collect selected data now
710 output_ts('Running commands to collect data')
713 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
716 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
720 if output_file is None:
723 dirname = os.path.dirname(output_file)
724 if dirname and not os.path.exists(dirname):
731 output_ts('Creating output file')
733 if output_type.startswith('tar'):
734 make_tar(subdir, output_type, output_fd, output_file)
736 make_zip(subdir, output_file)
741 print >>sys.stderr, "Category sizes (max, actual):\n"
742 for c in caps.keys():
743 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
747 def find_tapdisk_logs():
748 return glob.glob('/var/log/blktap/*.log*')
750 def generate_tapdisk_logs():
751 for pid in pidof('tapdisk'):
753 os.kill(pid, SIGUSR1)
754 output_ts("Including logs for tapdisk process %d" % pid)
757 # give processes a second to write their logs
760 def clean_tapdisk_logs():
761 for filename in find_tapdisk_logs():
767 def filter_db_pii(str, state):
768 if 'in_secret_table' not in state:
769 state['in_secret_table'] = False
771 if str.startswith('<table ') and 'name="secret"' in str:
772 state['in_secret_table'] = True
773 elif str.startswith('</table>'):
774 state['in_secret_table'] = False
776 if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
777 str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
780 def dump_scsi_hosts(cap):
782 l = os.listdir('/sys/class/scsi_host')
788 f = open('/sys/class/scsi_host/%s/proc_name' % h)
789 procname = f.readline().strip("\n")
795 f = open('/sys/class/scsi_host/%s/model_name' % h)
796 modelname = f.readline().strip("\n")
802 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
806 def module_info(cap):
807 output = StringIO.StringIO()
808 modules = open(PROC_MODULES, 'r')
812 module = line.split()[0]
813 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
818 return output.getvalue()
821 def multipathd_topology(cap):
822 pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
823 stdout=PIPE, stderr=dev_null)
824 stdout, stderr = pipe.communicate('show topology')
829 output = StringIO.StringIO()
830 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
834 if not procs[0].timed_out:
835 return output.getvalue().splitlines()
839 output = StringIO.StringIO()
840 procs = [ProcOutput([OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
844 if not procs[0].timed_out:
845 bonds = output.getvalue().splitlines()[1:]
846 return [x.split('\t')[1] for x in bonds]
852 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
854 fh = open('/proc/'+d+'/cmdline')
856 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
858 if not num_fds in fd_dict:
859 fd_dict[num_fds] = []
860 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
863 keys = fd_dict.keys()
864 keys.sort(lambda a, b: int(b) - int(a))
866 output += "%s: %s\n" % (k, str(fd_dict[k]))
869 def dump_rdac_groups(cap):
870 output = StringIO.StringIO()
871 procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
875 if not procs[0].timed_out:
877 for line in output.getvalue().splitlines():
878 if line.startswith('ID'):
880 elif line.startswith('----'):
883 group, _ = line.split(None, 1)
884 cmd_output(cap, [MPPUTIL, '-g', group])
886 def load_plugins(just_capabilities=False, filter=None):
887 def getText(nodelist):
889 for node in nodelist:
890 if node.nodeType == node.TEXT_NODE:
894 def getBoolAttr(el, attr, default=False):
896 val = el.getAttribute(attr).lower()
897 if val in ['true', 'false', 'yes', 'no']:
898 ret = val in ['true', 'yes']
901 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
902 if not caps.has_key(dir):
903 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
905 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
906 assert xmldoc.documentElement.tagName == "capability"
908 pii, min_size, max_size, min_time, max_time, mime = \
909 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
911 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
912 pii = xmldoc.documentElement.getAttribute("pii")
913 if xmldoc.documentElement.getAttribute("min_size") != '':
914 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
915 if xmldoc.documentElement.getAttribute("max_size") != '':
916 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
917 if xmldoc.documentElement.getAttribute("min_time") != '':
918 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
919 if xmldoc.documentElement.getAttribute("max_time") != '':
920 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
921 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
922 mime = xmldoc.documentElement.getAttribute("mime")
923 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
924 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
926 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
928 if just_capabilities:
931 plugdir = os.path.join(PLUGIN_DIR, dir)
932 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
933 xmldoc = parse(os.path.join(plugdir, file))
934 assert xmldoc.documentElement.tagName == "collect"
936 for el in xmldoc.documentElement.getElementsByTagName("*"):
937 filters_tmp = el.getAttribute("filters")
938 if filters_tmp == '':
941 filters = filters_tmp.split(',')
942 if not(filter is None or filter in filters):
944 if el.tagName == "files":
945 newest_first = getBoolAttr(el, 'newest_first')
946 file_output(dir, getText(el.childNodes).split(),
947 newest_first=newest_first)
948 elif el.tagName == "directory":
949 pattern = el.getAttribute("pattern")
950 if pattern == '': pattern = None
951 negate = getBoolAttr(el, 'negate')
952 newest_first = getBoolAttr(el, 'newest_first')
953 tree_output(dir, getText(el.childNodes),
954 pattern and re.compile(pattern) or None,
955 negate=negate, newest_first=newest_first)
956 elif el.tagName == "command":
957 label = el.getAttribute("label")
958 if label == '': label = None
959 cmd_output(dir, getText(el.childNodes), label)
961 def make_tar(subdir, suffix, output_fd, output_file):
962 global SILENT_MODE, data
965 if suffix == 'tar.bz2':
967 elif suffix == 'tar.gz':
971 if output_file is None:
972 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
974 filename = output_file
975 old_umask = os.umask(0077)
976 tf = tarfile.open(filename, mode)
979 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
982 for (k, v) in data.items():
984 tar_filename = os.path.join(subdir, construct_filename(k, v))
985 ti = tarfile.TarInfo(tar_filename)
990 if v.has_key('output'):
991 ti.mtime = v['output'].mtime
992 ti.size = len(v['output'].getvalue())
994 tf.addfile(ti, v['output'])
995 elif v.has_key('filename'):
996 s = os.stat(v['filename'])
997 ti.mtime = s.st_mtime
999 tf.addfile(ti, file(v['filename']))
1006 output ('Writing tarball %s successful.' % filename)
1011 def make_zip(subdir, output_file):
1012 global SILENT_MODE, data
1014 if output_file is None:
1015 filename = "%s/%s.zip" % (BUG_DIR, subdir)
1017 filename = output_file
1018 old_umask = os.umask(0077)
1019 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
1023 for (k, v) in data.items():
1025 dest = os.path.join(subdir, construct_filename(k, v))
1027 if v.has_key('output'):
1028 zf.writestr(dest, v['output'].getvalue())
1030 if os.stat(v['filename']).st_size < 50:
1031 compress_type = zipfile.ZIP_STORED
1033 compress_type = zipfile.ZIP_DEFLATED
1034 zf.write(v['filename'], dest, compress_type)
1040 output ('Writing archive %s successful.' % filename)
1045 def make_inventory(inventory, subdir):
1046 document = getDOMImplementation().createDocument(
1047 None, INVENTORY_XML_ROOT, None)
1049 # create summary entry
1050 s = document.createElement(INVENTORY_XML_SUMMARY)
1051 user = os.getenv('SUDO_USER', os.getenv('USER'))
1053 s.setAttribute('user', user)
1054 s.setAttribute('date', time.strftime('%c'))
1055 s.setAttribute('hostname', platform.node())
1056 s.setAttribute('uname', ' '.join(platform.uname()))
1057 s.setAttribute('uptime', commands.getoutput(UPTIME))
1058 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
1060 map(lambda (k, v): inventory_entry(document, subdir, k, v),
1062 return document.toprettyxml()
1064 def inventory_entry(document, subdir, k, v):
1066 el = document.createElement(INVENTORY_XML_ELEMENT)
1067 el.setAttribute('capability', v['cap'])
1068 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
1069 el.setAttribute('md5sum', md5sum(v))
1070 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
1077 if d.has_key('filename'):
1078 f = open(d['filename'])
1080 while len(data) > 0:
1084 elif d.has_key('output'):
1085 m.update(d['output'].getvalue())
1086 return m.hexdigest()
1089 def construct_filename(k, v):
1090 if v.has_key('filename'):
1091 if v['filename'][0] == '/':
1092 return v['filename'][1:]
1094 return v['filename']
1095 s = k.replace(' ', '-')
1096 s = s.replace('--', '-')
1097 s = s.replace('/', '%')
1098 if s.find('.') == -1:
1103 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), [k for k in caps.keys() if not caps[k][HIDDEN]])
1149 print document.toprettyxml()
1151 def capability(document, key):
1153 el = document.createElement(CAP_XML_ELEMENT)
1154 el.setAttribute('key', c[KEY])
1155 el.setAttribute('pii', c[PII])
1156 el.setAttribute('min-size', str(c[MIN_SIZE]))
1157 el.setAttribute('max-size', str(c[MAX_SIZE]))
1158 el.setAttribute('min-time', str(c[MIN_TIME]))
1159 el.setAttribute('max-time', str(c[MAX_TIME]))
1160 el.setAttribute('content-type', c[MIME])
1161 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1162 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1166 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1167 return '\n'.join([format % i for i in d.items()]) + '\n'
1171 yn = raw_input(prompt)
1173 return len(yn) == 0 or yn.lower()[0] == 'y'
1176 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1181 f = open('/proc/partitions')
1184 for line in f.readlines():
1185 (major, minor, blocks, name) = line.split()
1186 if int(major) < 254 and not partition_re.match(name):
1197 def __init__(self, command, max_time, inst=None, filter=None):
1198 self.command = command
1199 self.max_time = max_time
1201 self.running = False
1203 self.timed_out = False
1205 self.timeout = int(time.time()) + self.max_time
1206 self.filter = filter
1207 self.filter_state = {}
1213 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1216 self.timed_out = False
1218 if ProcOutput.debug:
1219 output_ts("Starting '%s'" % self.cmdAsStr())
1220 self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1221 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1222 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1226 output_ts("'%s' failed" % self.cmdAsStr())
1227 self.running = False
1230 def terminate(self):
1233 self.proc.stdout.close()
1234 os.kill(self.proc.pid, SIGTERM)
1238 self.running = False
1239 self.status = SIGTERM
1241 def read_line(self):
1243 line = self.proc.stdout.readline()
1246 self.proc.stdout.close()
1247 self.status = self.proc.wait()
1249 self.running = False
1252 line = self.filter(line, self.filter_state)
1254 self.inst.write(line)
1256 def run_procs(procs):
1264 active_procs.append(p)
1265 pipes.append(p.proc.stdout)
1267 elif p.status == None and not p.failed and not p.timed_out:
1270 active_procs.append(p)
1271 pipes.append(p.proc.stdout)
1278 (i, o, x) = select(pipes, [], [], 1.0)
1279 now = int(time.time())
1281 # handle process output
1282 for p in active_procs:
1283 if p.proc.stdout in i:
1287 if p.running and now > p.timeout:
1288 output_ts("'%s' timed out" % p.cmdAsStr())
1290 p.inst.write("\n** timeout **\n")
1298 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1300 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1308 class StringIOmtime(StringIO.StringIO):
1309 def __init__(self, buf=''):
1310 StringIO.StringIO.__init__(self, buf)
1311 self.mtime = time.time()
1314 StringIO.StringIO.write(self, s)
1315 self.mtime = time.time()
1318 if __name__ == "__main__":
1321 except KeyboardInterrupt:
1322 print "\nInterrupted."