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*KB,
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.bz2'
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, p])
604 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
605 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
606 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
607 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
609 cmd_output(CAP_NETWORK_STATUS,
610 [TC, '-s', '-d', 'class', 'show', 'dev', p])
613 tree_output(CAP_NETWORK_STATUS, PROC_NET_BONDING_DIR)
614 tree_output(CAP_NETWORK_STATUS, PROC_NET_VLAN_DIR)
615 cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
616 file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
617 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
618 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
620 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
621 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
622 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
624 vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
625 vspid = int(vspidfile.readline().strip())
627 for b in bond_list(vspid):
628 cmd_output(CAP_NETWORK_STATUS,
629 [OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
630 'ovs-appctl-bond-show-%s.out' % b)
634 tree_output(CAP_PAM, PAM_DIR)
635 file_output(CAP_PAM, [KRB5_CONF])
637 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
638 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
640 system_logs = (CAP_SYSTEM_LOGS, [ VAR_LOG_DIR + x for x in
641 ['crit.log', 'kern.log', 'daemon.log', 'user.log',
642 'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot']])
643 ovs_logs = (CAP_OPENVSWITCH_LOGS, [ OPENVSWITCH_LOG_DIR + x for x in
644 ['ovs-vswitchd.log', 'ovsdb-server.log',
645 'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log']])
646 for cap, logs in [system_logs, ovs_logs]:
647 file_output(cap, logs)
649 ['%s.%d' % (f, n) for n in range(log_days) for f in logs])
651 ['%s.%d.gz' % (f, n) for n in range(log_days) for f in logs])
653 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
654 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
656 cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
658 tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
659 tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
660 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
662 file_output(CAP_YUM, [YUM_LOG])
663 tree_output(CAP_YUM, YUM_REPOS_DIR)
664 cmd_output(CAP_YUM, [RPM, '-qa'])
665 file_output(CAP_YUM, [APT_SOURCES_LIST])
666 tree_output(CAP_YUM, APT_SOURCES_LIST_D)
667 cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
669 # Filter out ovs relevant information if --ovs option passed
670 # else collect all information
674 ovs_info_caps = [CAP_NETWORK_STATUS, CAP_SYSTEM_LOGS,
676 ovs_info_list = ['process-tree']
677 # We cannot use iteritems, since we modify 'data' as we pass through
678 for (k, v) in data.items():
684 if info not in ovs_info_list and cap not in ovs_info_caps:
688 filter = ",".join(filters)
693 load_plugins(filter=filter)
697 # permit the user to filter out data
698 # We cannot use iteritems, since we modify 'data' as we pass through
699 for (k, v) in sorted(data.items()):
705 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % key):
708 # collect selected data now
709 output_ts('Running commands to collect data')
712 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
715 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
719 if output_file is None:
722 dirname = os.path.dirname(output_file)
723 if dirname and not os.path.exists(dirname):
730 output_ts('Creating output file')
732 if output_type.startswith('tar'):
733 make_tar(subdir, output_type, output_fd, output_file)
735 make_zip(subdir, output_file)
740 print >>sys.stderr, "Category sizes (max, actual):\n"
741 for c in caps.keys():
742 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
746 def find_tapdisk_logs():
747 return glob.glob('/var/log/blktap/*.log*')
749 def generate_tapdisk_logs():
750 for pid in pidof('tapdisk'):
752 os.kill(pid, SIGUSR1)
753 output_ts("Including logs for tapdisk process %d" % pid)
756 # give processes a second to write their logs
759 def clean_tapdisk_logs():
760 for filename in find_tapdisk_logs():
766 def filter_db_pii(str, state):
767 if 'in_secret_table' not in state:
768 state['in_secret_table'] = False
770 if str.startswith('<table ') and 'name="secret"' in str:
771 state['in_secret_table'] = True
772 elif str.startswith('</table>'):
773 state['in_secret_table'] = False
775 if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
776 str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
779 def dump_scsi_hosts(cap):
781 l = os.listdir('/sys/class/scsi_host')
787 f = open('/sys/class/scsi_host/%s/proc_name' % h)
788 procname = f.readline().strip("\n")
794 f = open('/sys/class/scsi_host/%s/model_name' % h)
795 modelname = f.readline().strip("\n")
801 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
805 def module_info(cap):
806 output = StringIO.StringIO()
807 modules = open(PROC_MODULES, 'r')
811 module = line.split()[0]
812 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
817 return output.getvalue()
820 def multipathd_topology(cap):
821 pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
822 stdout=PIPE, stderr=dev_null)
823 stdout, stderr = pipe.communicate('show topology')
828 output = StringIO.StringIO()
829 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
833 if not procs[0].timed_out:
834 return output.getvalue().splitlines()
838 output = StringIO.StringIO()
839 procs = [ProcOutput([OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
843 if not procs[0].timed_out:
844 bonds = output.getvalue().splitlines()[1:]
845 return [x.split('\t')[1] for x in bonds]
851 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
853 fh = open('/proc/'+d+'/cmdline')
855 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
857 if not num_fds in fd_dict:
858 fd_dict[num_fds] = []
859 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
862 keys = fd_dict.keys()
863 keys.sort(lambda a, b: int(b) - int(a))
865 output += "%s: %s\n" % (k, str(fd_dict[k]))
868 def dump_rdac_groups(cap):
869 output = StringIO.StringIO()
870 procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
874 if not procs[0].timed_out:
876 for line in output.getvalue().splitlines():
877 if line.startswith('ID'):
879 elif line.startswith('----'):
882 group, _ = line.split(None, 1)
883 cmd_output(cap, [MPPUTIL, '-g', group])
885 def load_plugins(just_capabilities=False, filter=None):
886 def getText(nodelist):
888 for node in nodelist:
889 if node.nodeType == node.TEXT_NODE:
893 def getBoolAttr(el, attr, default=False):
895 val = el.getAttribute(attr).lower()
896 if val in ['true', 'false', 'yes', 'no']:
897 ret = val in ['true', 'yes']
900 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
901 if not caps.has_key(dir):
902 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
904 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
905 assert xmldoc.documentElement.tagName == "capability"
907 pii, min_size, max_size, min_time, max_time, mime = \
908 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
910 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
911 pii = xmldoc.documentElement.getAttribute("pii")
912 if xmldoc.documentElement.getAttribute("min_size") != '':
913 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
914 if xmldoc.documentElement.getAttribute("max_size") != '':
915 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
916 if xmldoc.documentElement.getAttribute("min_time") != '':
917 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
918 if xmldoc.documentElement.getAttribute("max_time") != '':
919 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
920 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
921 mime = xmldoc.documentElement.getAttribute("mime")
922 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
923 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
925 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
927 if just_capabilities:
930 plugdir = os.path.join(PLUGIN_DIR, dir)
931 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
932 xmldoc = parse(os.path.join(plugdir, file))
933 assert xmldoc.documentElement.tagName == "collect"
935 for el in xmldoc.documentElement.getElementsByTagName("*"):
936 filters_tmp = el.getAttribute("filters")
937 if filters_tmp == '':
940 filters = filters_tmp.split(',')
941 if not(filter is None or filter in filters):
943 if el.tagName == "files":
944 newest_first = getBoolAttr(el, 'newest_first')
945 file_output(dir, getText(el.childNodes).split(),
946 newest_first=newest_first)
947 elif el.tagName == "directory":
948 pattern = el.getAttribute("pattern")
949 if pattern == '': pattern = None
950 negate = getBoolAttr(el, 'negate')
951 newest_first = getBoolAttr(el, 'newest_first')
952 tree_output(dir, getText(el.childNodes),
953 pattern and re.compile(pattern) or None,
954 negate=negate, newest_first=newest_first)
955 elif el.tagName == "command":
956 label = el.getAttribute("label")
957 if label == '': label = None
958 cmd_output(dir, getText(el.childNodes), label)
960 def make_tar(subdir, suffix, output_fd, output_file):
961 global SILENT_MODE, data
964 if suffix == 'tar.bz2':
966 elif suffix == 'tar.gz':
970 if output_file is None:
971 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
973 filename = output_file
974 old_umask = os.umask(0077)
975 tf = tarfile.open(filename, mode)
978 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
981 for (k, v) in data.items():
983 tar_filename = os.path.join(subdir, construct_filename(k, v))
984 ti = tarfile.TarInfo(tar_filename)
989 if v.has_key('output'):
990 ti.mtime = v['output'].mtime
991 ti.size = len(v['output'].getvalue())
993 tf.addfile(ti, v['output'])
994 elif v.has_key('filename'):
995 s = os.stat(v['filename'])
996 ti.mtime = s.st_mtime
998 tf.addfile(ti, file(v['filename']))
1005 output ('Writing tarball %s successful.' % filename)
1010 def make_zip(subdir, output_file):
1011 global SILENT_MODE, data
1013 if output_file is None:
1014 filename = "%s/%s.zip" % (BUG_DIR, subdir)
1016 filename = output_file
1017 old_umask = os.umask(0077)
1018 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
1022 for (k, v) in data.items():
1024 dest = os.path.join(subdir, construct_filename(k, v))
1026 if v.has_key('output'):
1027 zf.writestr(dest, v['output'].getvalue())
1029 if os.stat(v['filename']).st_size < 50:
1030 compress_type = zipfile.ZIP_STORED
1032 compress_type = zipfile.ZIP_DEFLATED
1033 zf.write(v['filename'], dest, compress_type)
1039 output ('Writing archive %s successful.' % filename)
1044 def make_inventory(inventory, subdir):
1045 document = getDOMImplementation().createDocument(
1046 None, INVENTORY_XML_ROOT, None)
1048 # create summary entry
1049 s = document.createElement(INVENTORY_XML_SUMMARY)
1050 user = os.getenv('SUDO_USER', os.getenv('USER'))
1052 s.setAttribute('user', user)
1053 s.setAttribute('date', time.strftime('%c'))
1054 s.setAttribute('hostname', platform.node())
1055 s.setAttribute('uname', ' '.join(platform.uname()))
1056 s.setAttribute('uptime', commands.getoutput(UPTIME))
1057 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
1059 map(lambda (k, v): inventory_entry(document, subdir, k, v),
1061 return document.toprettyxml()
1063 def inventory_entry(document, subdir, k, v):
1065 el = document.createElement(INVENTORY_XML_ELEMENT)
1066 el.setAttribute('capability', v['cap'])
1067 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
1068 el.setAttribute('md5sum', md5sum(v))
1069 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
1076 if d.has_key('filename'):
1077 f = open(d['filename'])
1079 while len(data) > 0:
1083 elif d.has_key('output'):
1084 m.update(d['output'].getvalue())
1085 return m.hexdigest()
1088 def construct_filename(k, v):
1089 if v.has_key('filename'):
1090 if v['filename'][0] == '/':
1091 return v['filename'][1:]
1093 return v['filename']
1094 s = k.replace(' ', '-')
1095 s = s.replace('--', '-')
1096 s = s.replace('/', '%')
1097 if s.find('.') == -1:
1102 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), [k for k in caps.keys() if not caps[k][HIDDEN]])
1148 print document.toprettyxml()
1150 def capability(document, key):
1152 el = document.createElement(CAP_XML_ELEMENT)
1153 el.setAttribute('key', c[KEY])
1154 el.setAttribute('pii', c[PII])
1155 el.setAttribute('min-size', str(c[MIN_SIZE]))
1156 el.setAttribute('max-size', str(c[MAX_SIZE]))
1157 el.setAttribute('min-time', str(c[MIN_TIME]))
1158 el.setAttribute('max-time', str(c[MAX_TIME]))
1159 el.setAttribute('content-type', c[MIME])
1160 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1161 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1165 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1166 return '\n'.join([format % i for i in d.items()]) + '\n'
1170 yn = raw_input(prompt)
1172 return len(yn) == 0 or yn.lower()[0] == 'y'
1175 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1180 f = open('/proc/partitions')
1183 for line in f.readlines():
1184 (major, minor, blocks, name) = line.split()
1185 if int(major) < 254 and not partition_re.match(name):
1196 def __init__(self, command, max_time, inst=None, filter=None):
1197 self.command = command
1198 self.max_time = max_time
1200 self.running = False
1202 self.timed_out = False
1204 self.timeout = int(time.time()) + self.max_time
1205 self.filter = filter
1206 self.filter_state = {}
1212 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1215 self.timed_out = False
1217 if ProcOutput.debug:
1218 output_ts("Starting '%s'" % self.cmdAsStr())
1219 self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1220 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1221 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1225 output_ts("'%s' failed" % self.cmdAsStr())
1226 self.running = False
1229 def terminate(self):
1232 self.proc.stdout.close()
1233 os.kill(self.proc.pid, SIGTERM)
1237 self.running = False
1238 self.status = SIGTERM
1240 def read_line(self):
1242 line = self.proc.stdout.readline()
1245 self.proc.stdout.close()
1246 self.status = self.proc.wait()
1248 self.running = False
1251 line = self.filter(line, self.filter_state)
1253 self.inst.write(line)
1255 def run_procs(procs):
1263 active_procs.append(p)
1264 pipes.append(p.proc.stdout)
1266 elif p.status == None and not p.failed and not p.timed_out:
1269 active_procs.append(p)
1270 pipes.append(p.proc.stdout)
1277 (i, o, x) = select(pipes, [], [], 1.0)
1278 now = int(time.time())
1280 # handle process output
1281 for p in active_procs:
1282 if p.proc.stdout in i:
1286 if p.running and now > p.timeout:
1287 output_ts("'%s' timed out" % p.cmdAsStr())
1289 p.inst.write("\n** timeout **\n")
1297 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1299 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1307 class StringIOmtime(StringIO.StringIO):
1308 def __init__(self, buf=''):
1309 StringIO.StringIO.__init__(self, buf)
1310 self.mtime = time.time()
1313 StringIO.StringIO.write(self, s)
1314 self.mtime = time.time()
1317 if __name__ == "__main__":
1320 except KeyboardInterrupt:
1321 print "\nInterrupted."