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'
426 (options, params) = getopt.gnu_getopt(
427 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
428 'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
430 except getopt.GetoptError, opterr:
431 print >>sys.stderr, opterr
439 entries = [e for e in caps.keys() if caps[e][CHECKED]]
441 for (k, v) in options:
442 if k == '--capabilities':
443 update_capabilities()
448 if v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
451 print >>sys.stderr, "Invalid output format '%s'" % v
454 # "-s" or "--silent" means suppress output (except for the final
455 # output filename at the end)
456 if k in ['-s', '--silent']:
459 if k == '--entries' and v != '':
460 entries = v.split(',')
462 # If the user runs the script with "-y" or "--yestoall" we don't ask
463 # all the really annoying questions.
464 if k in ['-y', '--yestoall']:
465 ANSWER_YES_TO_ALL = True
470 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
471 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
473 print >>sys.stderr, "Invalid output file descriptor", output_fd
480 entries = caps.keys()
481 elif k == '--unlimited':
482 unlimited_data = True
485 ProcOutput.debug = True
489 collect_all_info = False
492 print >>sys.stderr, "Invalid additional arguments", str(params)
495 if output_fd != -1 and output_type != 'tar':
496 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
499 if output_fd != -1 and output_file is not None:
500 print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
503 if ANSWER_YES_TO_ALL:
504 output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
507 This application will collate dmesg output, details of the
508 hardware configuration of your machine, information about the build of
509 openvswitch that you are using, plus, if you allow it, various logs.
511 The collated information will be saved as a .%s for archiving or
512 sending to a Technical Support Representative.
514 The logs may contain private information, and if you are at all
515 worried about that, you should exit now, or you should explicitly
516 exclude those logs from the archive.
520 # assemble potential data
522 file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
523 cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
524 cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
526 tree_output(CAP_COLLECTD_LOGS, COLLECTD_LOGS_DIR)
527 cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
528 file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
529 file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
530 cmd_output(CAP_DISK_INFO, [DF, '-alT'])
531 cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
532 for d in disk_list():
533 cmd_output(CAP_DISK_INFO, [HDPARM, '-I', '/dev/%s' % d])
534 if len(pidof('iscsid')) != 0:
535 cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
536 cmd_output(CAP_DISK_INFO, [VGSCAN])
537 cmd_output(CAP_DISK_INFO, [PVS])
538 cmd_output(CAP_DISK_INFO, [VGS])
539 cmd_output(CAP_DISK_INFO, [LVS])
540 file_output(CAP_DISK_INFO, [LVM_CACHE, LVM_CONFIG])
541 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
542 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
543 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
544 cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
545 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
546 cmd_output(CAP_DISK_INFO, [LVDISPLAY, '--map'])
548 file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
549 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
550 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
551 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
552 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
553 file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
554 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
557 for d in disk_list():
558 cmd_output(CAP_HDPARM_T, [HDPARM, '-tT', '/dev/%s' % d])
560 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
561 PROC_FILESYSTEMS, PROC_CMDLINE])
562 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
563 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
564 file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
565 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
566 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
568 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
570 file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
571 cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
572 func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
573 cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
574 if CAP_MULTIPATH in entries and collect_all_info:
575 dump_rdac_groups(CAP_MULTIPATH)
577 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
578 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
579 file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
580 file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
581 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_CONF_DB])
583 cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
584 cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
585 cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
586 cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
587 for dir in DHCP_LEASE_DIR:
588 tree_output(CAP_NETWORK_STATUS, dir)
589 cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
590 for p in os.listdir('/sys/class/net/'):
592 f = open('/sys/class/net/%s/type' % p, 'r')
595 if os.path.islink('/sys/class/net/%s/device' % p) and int(t) == 1:
597 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
598 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
599 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
600 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
601 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
603 cmd_output(CAP_NETWORK_STATUS,
604 [TC, '-s', '-d', 'class', 'show', 'dev', p])
607 tree_output(CAP_NETWORK_STATUS, PROC_NET_BONDING_DIR)
608 tree_output(CAP_NETWORK_STATUS, PROC_NET_VLAN_DIR)
609 cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
610 file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
611 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
612 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
614 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
615 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
616 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
618 vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
619 vspid = int(vspidfile.readline().strip())
621 for b in bond_list(vspid):
622 cmd_output(CAP_NETWORK_STATUS,
623 [OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
624 'ovs-appctl-bond-show-%s.out' % b)
628 tree_output(CAP_PAM, PAM_DIR)
629 file_output(CAP_PAM, [KRB5_CONF])
631 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
632 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
634 system_logs = (CAP_SYSTEM_LOGS, [ VAR_LOG_DIR + x for x in
635 ['crit.log', 'kern.log', 'daemon.log', 'user.log',
636 'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot']])
637 ovs_logs = (CAP_OPENVSWITCH_LOGS, [ OPENVSWITCH_LOG_DIR + x for x in
638 ['ovs-vswitchd.log', 'ovsdb-server.log',
639 'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log']])
640 for cap, logs in [system_logs, ovs_logs]:
641 file_output(cap, logs)
642 file_output(cap, ['%s.%d' % (f, n) for n in range(20) for f in logs])
643 file_output(cap, ['%s.%d.gz' % (f, n) for n in range(20) for f in logs])
645 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
646 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
648 cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
650 tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
651 tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
652 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
654 file_output(CAP_YUM, [YUM_LOG])
655 tree_output(CAP_YUM, YUM_REPOS_DIR)
656 cmd_output(CAP_YUM, [RPM, '-qa'])
657 file_output(CAP_YUM, [APT_SOURCES_LIST])
658 tree_output(CAP_YUM, APT_SOURCES_LIST_D)
659 cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
661 # Filter out ovs relevant information if --ovs option passed
662 # else collect all information
666 ovs_info_caps = [CAP_NETWORK_STATUS, CAP_SYSTEM_LOGS,
668 ovs_info_list = ['process-tree']
669 # We cannot use iteritems, since we modify 'data' as we pass through
670 for (k, v) in data.items():
676 if info not in ovs_info_list and cap not in ovs_info_caps:
680 filter = ",".join(filters)
685 load_plugins(filter=filter)
689 # permit the user to filter out data
690 # We cannot use iteritems, since we modify 'data' as we pass through
691 for (k, v) in sorted(data.items()):
697 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % key):
700 # collect selected data now
701 output_ts('Running commands to collect data')
704 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
707 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
711 if output_file is None:
714 dirname = os.path.dirname(output_file)
715 if dirname and not os.path.exists(dirname):
722 output_ts('Creating output file')
724 if output_type.startswith('tar'):
725 make_tar(subdir, output_type, output_fd, output_file)
727 make_zip(subdir, output_file)
732 print >>sys.stderr, "Category sizes (max, actual):\n"
733 for c in caps.keys():
734 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
738 def find_tapdisk_logs():
739 return glob.glob('/var/log/blktap/*.log*')
741 def generate_tapdisk_logs():
742 for pid in pidof('tapdisk'):
744 os.kill(pid, SIGUSR1)
745 output_ts("Including logs for tapdisk process %d" % pid)
748 # give processes a second to write their logs
751 def clean_tapdisk_logs():
752 for filename in find_tapdisk_logs():
758 def filter_db_pii(str, state):
759 if 'in_secret_table' not in state:
760 state['in_secret_table'] = False
762 if str.startswith('<table ') and 'name="secret"' in str:
763 state['in_secret_table'] = True
764 elif str.startswith('</table>'):
765 state['in_secret_table'] = False
767 if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
768 str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
771 def dump_scsi_hosts(cap):
773 l = os.listdir('/sys/class/scsi_host')
779 f = open('/sys/class/scsi_host/%s/proc_name' % h)
780 procname = f.readline().strip("\n")
786 f = open('/sys/class/scsi_host/%s/model_name' % h)
787 modelname = f.readline().strip("\n")
793 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
797 def module_info(cap):
798 output = StringIO.StringIO()
799 modules = open(PROC_MODULES, 'r')
803 module = line.split()[0]
804 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
809 return output.getvalue()
812 def multipathd_topology(cap):
813 pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
814 stdout=PIPE, stderr=dev_null)
815 stdout, stderr = pipe.communicate('show topology')
820 output = StringIO.StringIO()
821 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
825 if not procs[0].timed_out:
826 return output.getvalue().splitlines()
830 output = StringIO.StringIO()
831 procs = [ProcOutput([OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
835 if not procs[0].timed_out:
836 bonds = output.getvalue().splitlines()[1:]
837 return [x.split('\t')[1] for x in bonds]
843 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
845 fh = open('/proc/'+d+'/cmdline')
847 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
849 if not num_fds in fd_dict:
850 fd_dict[num_fds] = []
851 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
854 keys = fd_dict.keys()
855 keys.sort(lambda a, b: int(b) - int(a))
857 output += "%s: %s\n" % (k, str(fd_dict[k]))
860 def dump_rdac_groups(cap):
861 output = StringIO.StringIO()
862 procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
866 if not procs[0].timed_out:
868 for line in output.getvalue().splitlines():
869 if line.startswith('ID'):
871 elif line.startswith('----'):
874 group, _ = line.split(None, 1)
875 cmd_output(cap, [MPPUTIL, '-g', group])
877 def load_plugins(just_capabilities=False, filter=None):
878 def getText(nodelist):
880 for node in nodelist:
881 if node.nodeType == node.TEXT_NODE:
885 def getBoolAttr(el, attr, default=False):
887 val = el.getAttribute(attr).lower()
888 if val in ['true', 'false', 'yes', 'no']:
889 ret = val in ['true', 'yes']
892 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
893 if not caps.has_key(dir):
894 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
896 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
897 assert xmldoc.documentElement.tagName == "capability"
899 pii, min_size, max_size, min_time, max_time, mime = \
900 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
902 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
903 pii = xmldoc.documentElement.getAttribute("pii")
904 if xmldoc.documentElement.getAttribute("min_size") != '':
905 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
906 if xmldoc.documentElement.getAttribute("max_size") != '':
907 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
908 if xmldoc.documentElement.getAttribute("min_time") != '':
909 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
910 if xmldoc.documentElement.getAttribute("max_time") != '':
911 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
912 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
913 mime = xmldoc.documentElement.getAttribute("mime")
914 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
915 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
917 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
919 if just_capabilities:
922 plugdir = os.path.join(PLUGIN_DIR, dir)
923 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
924 xmldoc = parse(os.path.join(plugdir, file))
925 assert xmldoc.documentElement.tagName == "collect"
927 for el in xmldoc.documentElement.getElementsByTagName("*"):
928 filters_tmp = el.getAttribute("filters")
929 if filters_tmp == '':
932 filters = filters_tmp.split(',')
933 if not(filter is None or filter in filters):
935 if el.tagName == "files":
936 newest_first = getBoolAttr(el, 'newest_first')
937 file_output(dir, getText(el.childNodes).split(),
938 newest_first=newest_first)
939 elif el.tagName == "directory":
940 pattern = el.getAttribute("pattern")
941 if pattern == '': pattern = None
942 negate = getBoolAttr(el, 'negate')
943 newest_first = getBoolAttr(el, 'newest_first')
944 tree_output(dir, getText(el.childNodes),
945 pattern and re.compile(pattern) or None,
946 negate=negate, newest_first=newest_first)
947 elif el.tagName == "command":
948 label = el.getAttribute("label")
949 if label == '': label = None
950 cmd_output(dir, getText(el.childNodes), label)
952 def make_tar(subdir, suffix, output_fd, output_file):
953 global SILENT_MODE, data
956 if suffix == 'tar.bz2':
958 elif suffix == 'tar.gz':
962 if output_file is None:
963 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
965 filename = output_file
966 old_umask = os.umask(0077)
967 tf = tarfile.open(filename, mode)
970 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
973 for (k, v) in data.items():
975 tar_filename = os.path.join(subdir, construct_filename(k, v))
976 ti = tarfile.TarInfo(tar_filename)
981 if v.has_key('output'):
982 ti.mtime = v['output'].mtime
983 ti.size = len(v['output'].getvalue())
985 tf.addfile(ti, v['output'])
986 elif v.has_key('filename'):
987 s = os.stat(v['filename'])
988 ti.mtime = s.st_mtime
990 tf.addfile(ti, file(v['filename']))
997 output ('Writing tarball %s successful.' % filename)
1002 def make_zip(subdir, output_file):
1003 global SILENT_MODE, data
1005 if output_file is None:
1006 filename = "%s/%s.zip" % (BUG_DIR, subdir)
1008 filename = output_file
1009 old_umask = os.umask(0077)
1010 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
1014 for (k, v) in data.items():
1016 dest = os.path.join(subdir, construct_filename(k, v))
1018 if v.has_key('output'):
1019 zf.writestr(dest, v['output'].getvalue())
1021 if os.stat(v['filename']).st_size < 50:
1022 compress_type = zipfile.ZIP_STORED
1024 compress_type = zipfile.ZIP_DEFLATED
1025 zf.write(v['filename'], dest, compress_type)
1031 output ('Writing archive %s successful.' % filename)
1036 def make_inventory(inventory, subdir):
1037 document = getDOMImplementation().createDocument(
1038 None, INVENTORY_XML_ROOT, None)
1040 # create summary entry
1041 s = document.createElement(INVENTORY_XML_SUMMARY)
1042 user = os.getenv('SUDO_USER', os.getenv('USER'))
1044 s.setAttribute('user', user)
1045 s.setAttribute('date', time.strftime('%c'))
1046 s.setAttribute('hostname', platform.node())
1047 s.setAttribute('uname', ' '.join(platform.uname()))
1048 s.setAttribute('uptime', commands.getoutput(UPTIME))
1049 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
1051 map(lambda (k, v): inventory_entry(document, subdir, k, v),
1053 return document.toprettyxml()
1055 def inventory_entry(document, subdir, k, v):
1057 el = document.createElement(INVENTORY_XML_ELEMENT)
1058 el.setAttribute('capability', v['cap'])
1059 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
1060 el.setAttribute('md5sum', md5sum(v))
1061 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
1068 if d.has_key('filename'):
1069 f = open(d['filename'])
1071 while len(data) > 0:
1075 elif d.has_key('output'):
1076 m.update(d['output'].getvalue())
1077 return m.hexdigest()
1080 def construct_filename(k, v):
1081 if v.has_key('filename'):
1082 if v['filename'][0] == '/':
1083 return v['filename'][1:]
1085 return v['filename']
1086 s = k.replace(' ', '-')
1087 s = s.replace('--', '-')
1088 s = s.replace('/', '%')
1089 if s.find('.') == -1:
1094 def update_capabilities():
1097 def update_cap_size(cap, size):
1098 update_cap(cap, MIN_SIZE, size)
1099 update_cap(cap, MAX_SIZE, size)
1100 update_cap(cap, CHECKED, size > 0)
1103 def update_cap(cap, k, v):
1107 caps[cap] = tuple(l)
1110 def size_of_dir(d, pattern=None, negate=False):
1111 if os.path.isdir(d):
1112 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1118 def size_of_all(files, pattern=None, negate=False):
1119 return sum([size_of(f, pattern, negate) for f in files])
1122 def matches(f, pattern, negate):
1124 return not matches(f, pattern, False)
1126 return pattern is None or pattern.match(f)
1129 def size_of(f, pattern, negate):
1130 if os.path.isfile(f) and matches(f, pattern, negate):
1131 return os.stat(f)[6]
1133 return size_of_dir(f, pattern, negate)
1136 def print_capabilities():
1137 document = getDOMImplementation().createDocument(
1138 "ns", CAP_XML_ROOT, None)
1139 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1140 print document.toprettyxml()
1142 def capability(document, key):
1144 el = document.createElement(CAP_XML_ELEMENT)
1145 el.setAttribute('key', c[KEY])
1146 el.setAttribute('pii', c[PII])
1147 el.setAttribute('min-size', str(c[MIN_SIZE]))
1148 el.setAttribute('max-size', str(c[MAX_SIZE]))
1149 el.setAttribute('min-time', str(c[MIN_TIME]))
1150 el.setAttribute('max-time', str(c[MAX_TIME]))
1151 el.setAttribute('content-type', c[MIME])
1152 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1153 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1157 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1158 return '\n'.join([format % i for i in d.items()]) + '\n'
1162 yn = raw_input(prompt)
1164 return len(yn) == 0 or yn.lower()[0] == 'y'
1167 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1172 f = open('/proc/partitions')
1175 for line in f.readlines():
1176 (major, minor, blocks, name) = line.split()
1177 if int(major) < 254 and not partition_re.match(name):
1188 def __init__(self, command, max_time, inst=None, filter=None):
1189 self.command = command
1190 self.max_time = max_time
1192 self.running = False
1194 self.timed_out = False
1196 self.timeout = int(time.time()) + self.max_time
1197 self.filter = filter
1198 self.filter_state = {}
1204 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1207 self.timed_out = False
1209 if ProcOutput.debug:
1210 output_ts("Starting '%s'" % self.cmdAsStr())
1211 self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1212 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1213 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1217 output_ts("'%s' failed" % self.cmdAsStr())
1218 self.running = False
1221 def terminate(self):
1224 self.proc.stdout.close()
1225 os.kill(self.proc.pid, SIGTERM)
1229 self.running = False
1230 self.status = SIGTERM
1232 def read_line(self):
1234 line = self.proc.stdout.readline()
1237 self.proc.stdout.close()
1238 self.status = self.proc.wait()
1240 self.running = False
1243 line = self.filter(line, self.filter_state)
1245 self.inst.write(line)
1247 def run_procs(procs):
1255 active_procs.append(p)
1256 pipes.append(p.proc.stdout)
1258 elif p.status == None and not p.failed and not p.timed_out:
1261 active_procs.append(p)
1262 pipes.append(p.proc.stdout)
1269 (i, o, x) = select(pipes, [], [], 1.0)
1270 now = int(time.time())
1272 # handle process output
1273 for p in active_procs:
1274 if p.proc.stdout in i:
1278 if p.running and now > p.timeout:
1279 output_ts("'%s' timed out" % p.cmdAsStr())
1281 p.inst.write("\n** timeout **\n")
1289 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1291 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1299 class StringIOmtime(StringIO.StringIO):
1300 def __init__(self, buf=''):
1301 StringIO.StringIO.__init__(self, buf)
1302 self.mtime = time.time()
1305 StringIO.StringIO.write(self, s)
1306 self.mtime = time.time()
1309 if __name__ == "__main__":
1312 except KeyboardInterrupt:
1313 print "\nInterrupted."