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 PROC_CPUINFO = '/proc/cpuinfo'
80 PROC_MEMINFO = '/proc/meminfo'
81 PROC_IOPORTS = '/proc/ioports'
82 PROC_INTERRUPTS = '/proc/interrupts'
83 PROC_SCSI = '/proc/scsi/scsi'
84 PROC_VERSION = '/proc/version'
85 PROC_MODULES = '/proc/modules'
86 PROC_DEVICES = '/proc/devices'
87 PROC_FILESYSTEMS = '/proc/filesystems'
88 PROC_CMDLINE = '/proc/cmdline'
89 PROC_CONFIG = '/proc/config.gz'
90 PROC_USB_DEV = '/proc/bus/usb/devices'
91 PROC_NET_BONDING_DIR = '/proc/net/bonding'
92 IFCFG_RE = re.compile(r'^.*/ifcfg-.*')
93 ROUTE_RE = re.compile(r'^.*/route-.*')
94 SYSCONFIG_HWCONF = '/etc/sysconfig/hwconf'
95 SYSCONFIG_NETWORK = '/etc/sysconfig/network'
96 SYSCONFIG_NETWORK_SCRIPTS = '/etc/sysconfig/network-scripts'
97 PROC_NET_VLAN_DIR = '/proc/net/vlan'
98 PROC_NET_SOFTNET_STAT = '/proc/net/softnet_stat'
99 MODPROBE_CONF = '/etc/modprobe.conf'
100 MODPROBE_DIR = '/etc/modprobe.d'
101 RESOLV_CONF = '/etc/resolv.conf'
102 MPP_CONF = '/etc/mpp.conf'
103 MULTIPATH_CONF = '/etc/multipath.conf'
104 NSSWITCH_CONF = '/etc/nsswitch.conf'
105 NTP_CONF = '/etc/ntp.conf'
106 IPTABLES_CONFIG = '/etc/sysconfig/iptables-config'
108 HOSTS_ALLOW = '/etc/hosts.allow'
109 HOSTS_DENY = '/etc/hosts.deny'
110 DHCP_LEASE_DIR = ['/var/lib/dhclient', '/var/lib/dhcp3']
111 OPENVSWITCH_LOG_DIR = '@LOGDIR@/'
112 OPENVSWITCH_DEFAULT_SWITCH = '/etc/default/openvswitch-switch' # Debian
113 OPENVSWITCH_SYSCONFIG_SWITCH = '/etc/sysconfig/openvswitch' # RHEL
114 OPENVSWITCH_DEFAULT_CONTROLLER = '/etc/default/openvswitch-controller'
115 OPENVSWITCH_CONF_DB = '@DBDIR@/conf.db'
116 OPENVSWITCH_COMPACT_DB = '@DBDIR@/bugtool-compact-conf.db'
117 OPENVSWITCH_VSWITCHD_PID = '@RUNDIR@/ovs-vswitchd.pid'
118 VAR_LOG_DIR = '/var/log/'
119 VAR_LOG_CORE_DIR = '/var/log/core'
120 YUM_LOG = '/var/log/yum.log'
121 YUM_REPOS_DIR = '/etc/yum.repos.d'
127 os.environ['PATH'] = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:@pkgdatadir@/scripts'
130 CHKCONFIG = 'chkconfig'
133 DMIDECODE = 'dmidecode'
135 DPKG_QUERY = 'dpkg-query'
139 IFCONFIG = 'ifconfig'
140 IPTABLES = 'iptables'
141 ISCSIADM = 'iscsiadm'
148 MULTIPATHD = 'multipathd'
150 OVS_DPCTL = 'ovs-dpctl'
151 OVS_OFCTL = 'ovs-ofctl'
152 OVS_VSCTL = 'ovs-vsctl'
163 # PII -- Personally identifiable information. Of particular concern are
164 # things that would identify customers, or their network topology.
165 # Passwords are never to be included in any bug report, regardless of any PII
168 # NO -- No PII will be in these entries.
169 # YES -- PII will likely or certainly be in these entries.
170 # MAYBE -- The user may wish to audit these entries for PII.
171 # IF_CUSTOMIZED -- If the files are unmodified, then they will contain no PII,
172 # but since we encourage customers to edit these files, PII may have been
173 # introduced by the customer. This is used in particular for the networking
180 PII_IF_CUSTOMIZED = 'if_customized'
191 MIME_DATA = 'application/data'
192 MIME_TEXT = 'text/plain'
194 INVENTORY_XML_ROOT = "system-status-inventory"
195 INVENTORY_XML_SUMMARY = 'system-summary'
196 INVENTORY_XML_ELEMENT = 'inventory-entry'
197 CAP_XML_ROOT = "system-status-capabilities"
198 CAP_XML_ELEMENT = 'capability'
201 CAP_BOOT_LOADER = 'boot-loader'
202 CAP_DISK_INFO = 'disk-info'
203 CAP_HARDWARE_INFO = 'hardware-info'
204 CAP_KERNEL_INFO = 'kernel-info'
205 CAP_LOSETUP_A = 'loopback-devices'
206 CAP_MULTIPATH = 'multipath'
207 CAP_NETWORK_CONFIG = 'network-config'
208 CAP_NETWORK_INFO = 'network-info'
209 CAP_NETWORK_STATUS = 'network-status'
210 CAP_OPENVSWITCH_LOGS = 'ovs-system-logs'
211 CAP_PROCESS_LIST = 'process-list'
212 CAP_SYSTEM_LOGS = 'system-logs'
213 CAP_SYSTEM_SERVICES = 'system-services'
221 unlimited_data = False
223 # Default value for the number of days to collect logs.
225 log_last_mod_time = None
226 free_disk_space = None
228 def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
229 max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
230 caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
235 cap(CAP_BOOT_LOADER, PII_NO, max_size=3*KB,
237 cap(CAP_DISK_INFO, PII_MAYBE, max_size=50*KB,
239 cap(CAP_HARDWARE_INFO, PII_MAYBE, max_size=2*MB,
241 cap(CAP_KERNEL_INFO, PII_MAYBE, max_size=120*KB,
243 cap(CAP_LOSETUP_A, PII_MAYBE, max_size=KB, max_time=5)
244 cap(CAP_MULTIPATH, PII_MAYBE, max_size=20*KB,
246 cap(CAP_NETWORK_CONFIG, PII_IF_CUSTOMIZED,
247 min_size=0, max_size=5*MB)
248 cap(CAP_NETWORK_INFO, PII_YES, max_size=50*MB,
250 cap(CAP_NETWORK_STATUS, PII_YES, max_size=-1,
252 cap(CAP_OPENVSWITCH_LOGS, PII_MAYBE, max_size=-1,
254 cap(CAP_PROCESS_LIST, PII_YES, max_size=30*KB,
256 cap(CAP_SYSTEM_LOGS, PII_MAYBE, max_size=200*MB,
258 cap(CAP_SYSTEM_SERVICES, PII_NO, max_size=5*KB,
260 cap(CAP_YUM, PII_IF_CUSTOMIZED, max_size=10*KB,
263 ANSWER_YES_TO_ALL = False
267 dev_null = open('/dev/null', 'r+')
275 output("[%s] %s" % (time.strftime("%x %X %Z"), x))
277 def cmd_output(cap, args, label=None, filter=None, binary=False):
280 if isinstance(args, list):
281 a = [aa for aa in args]
282 a[0] = os.path.basename(a[0])
286 data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter,
290 def file_output(cap, path_list, newest_first=False, last_mod_time=None):
292 If newest_first is True, the list of files in path_list is sorted
293 by file modification time in descending order, else its sorted
298 for path in path_list:
303 if last_mod_time is None or s.st_mtime >= last_mod_time:
304 path_entries.append((path, s))
306 mtime = lambda(path, stat): stat.st_mtime
307 path_entries.sort(key=mtime, reverse=newest_first)
308 for p in path_entries:
309 if check_space(cap, p[0], p[1].st_size):
310 data[p] = {'cap': cap, 'filename': p[0]}
313 def tree_output(cap, path, pattern=None, negate=False, newest_first=False,
316 Walks the directory tree rooted at path. Files in current dir are processed
317 before files in sub-dirs.
320 if os.path.exists(path):
321 for root, dirs, files in os.walk(path):
322 fns = [fn for fn in [os.path.join(root, f) for f in files]
323 if os.path.isfile(fn) and matches(fn, pattern, negate)]
324 file_output(cap, fns, newest_first=newest_first,
325 last_mod_time=last_mod_time)
328 def prefix_output(cap, prefix, newest_first=False, last_mod_time=None):
330 Output files with the same prefix.
333 for root, dirs, files in os.walk(os.path.dirname(prefix)):
334 fns += [fn for fn in [os.path.join(root, f) for f in files]
335 if fn.startswith(prefix)]
336 file_output(cap, fns, newest_first=newest_first,
337 last_mod_time=last_mod_time)
340 def func_output(cap, label, func):
342 t = str(func).split()
343 data[label] = {'cap': cap, 'func': func}
348 for (k, v) in data.items():
350 if v.has_key('cmd_args'):
351 v['output'] = StringIOmtime()
352 if not process_lists.has_key(cap):
353 process_lists[cap] = []
354 process_lists[cap].append(
355 ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'],
356 v['filter'], v['binary']))
357 elif v.has_key('filename') and v['filename'].startswith('/proc/'):
358 # proc files must be read into memory
360 f = open(v['filename'], 'r')
363 if check_space(cap, v['filename'], len(s)):
364 v['output'] = StringIOmtime(s)
367 elif v.has_key('func'):
372 if check_space(cap, k, len(s)):
373 v['output'] = StringIOmtime(s)
375 run_procs(process_lists.values())
379 global ANSWER_YES_TO_ALL, SILENT_MODE
380 global entries, data, dbg, unlimited_data, free_disk_space
381 global log_days, log_last_mod_time
384 only_ovs_info = False
385 collect_all_info = True
387 # we need access to privileged files, exit if we are not running as root
389 print >>sys.stderr, "Error: ovs-bugtool must be run as root"
393 output_type = 'tar.gz'
400 (options, params) = getopt.gnu_getopt(
401 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
402 'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
403 'debug', 'ovs', 'log-days='])
404 except getopt.GetoptError, opterr:
405 print >>sys.stderr, opterr
413 entries = [e for e in caps.keys() if caps[e][CHECKED]]
415 for (k, v) in options:
416 if k == '--capabilities':
417 update_capabilities()
422 if v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
425 print >>sys.stderr, "Invalid output format '%s'" % v
428 # "-s" or "--silent" means suppress output (except for the final
429 # output filename at the end)
430 if k in ['-s', '--silent']:
433 if k == '--entries' and v != '':
434 entries = v.split(',')
436 # If the user runs the script with "-y" or "--yestoall" we don't ask
437 # all the really annoying questions.
438 if k in ['-y', '--yestoall']:
439 ANSWER_YES_TO_ALL = True
444 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
445 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
447 print >>sys.stderr, "Invalid output file descriptor", output_fd
454 entries = caps.keys()
455 elif k == '--unlimited':
456 unlimited_data = True
459 ProcOutput.debug = True
463 collect_all_info = False
465 if k == '--log-days':
470 print >>sys.stderr, "Invalid additional arguments", str(params)
473 if output_fd != -1 and output_type != 'tar':
474 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
477 if output_fd != -1 and output_file is not None:
478 print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
481 if output_file is not None and not unlimited_data:
482 free_disk_space = get_free_disk_space(output_file) * 90 / 100
484 log_last_mod_time = int(time.time()) - log_days * 86400
486 if ANSWER_YES_TO_ALL:
487 output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
490 This application will collate dmesg output, details of the
491 hardware configuration of your machine, information about the build of
492 openvswitch that you are using, plus, if you allow it, various logs.
494 The collated information will be saved as a .%s for archiving or
495 sending to a Technical Support Representative.
497 The logs may contain private information, and if you are at all
498 worried about that, you should exit now, or you should explicitly
499 exclude those logs from the archive.
503 # assemble potential data
505 file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
506 cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
507 cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
509 cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
510 file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
511 file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
512 cmd_output(CAP_DISK_INFO, [DF, '-alT'])
513 cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
514 if len(pidof('iscsid')) != 0:
515 cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
516 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
517 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
518 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
519 cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
520 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
522 file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
523 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
524 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
525 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
526 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
527 file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
528 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
531 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
532 PROC_FILESYSTEMS, PROC_CMDLINE])
533 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
534 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
535 file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
536 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
537 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
539 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
541 file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
542 cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
543 func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
544 cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
545 if CAP_MULTIPATH in entries and collect_all_info:
546 dump_rdac_groups(CAP_MULTIPATH)
548 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
549 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
550 file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
551 file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
552 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_DEFAULT_SWITCH,
553 OPENVSWITCH_SYSCONFIG_SWITCH, OPENVSWITCH_DEFAULT_CONTROLLER])
555 cmd_output(CAP_NETWORK_INFO, [IFCONFIG, '-a'])
556 cmd_output(CAP_NETWORK_INFO, [ROUTE, '-n'])
557 cmd_output(CAP_NETWORK_INFO, [ARP, '-n'])
558 cmd_output(CAP_NETWORK_INFO, [NETSTAT, '-an'])
559 for dir in DHCP_LEASE_DIR:
560 tree_output(CAP_NETWORK_INFO, dir)
561 for table in ['filter', 'nat', 'mangle', 'raw', 'security']:
562 cmd_output(CAP_NETWORK_INFO, [IPTABLES, '-t', table, '-nL'])
563 for p in os.listdir('/sys/class/net/'):
565 f = open('/sys/class/net/%s/type' % p, 'r')
568 if os.path.islink('/sys/class/net/%s/device' % p) and int(t) == 1:
570 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-S', p])
571 if not p.startswith('vif') and not p.startswith('tap'):
572 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, p])
573 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-k', p])
574 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-i', p])
575 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-c', p])
577 cmd_output(CAP_NETWORK_INFO,
578 [TC, '-s', '-d', 'class', 'show', 'dev', p])
581 tree_output(CAP_NETWORK_INFO, PROC_NET_BONDING_DIR)
582 tree_output(CAP_NETWORK_INFO, PROC_NET_VLAN_DIR)
583 cmd_output(CAP_NETWORK_INFO, [TC, '-s', 'qdisc'])
584 file_output(CAP_NETWORK_INFO, [PROC_NET_SOFTNET_STAT])
587 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
588 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
590 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
592 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
593 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
595 system_logs = ([ VAR_LOG_DIR + x for x in
596 ['crit.log', 'kern.log', 'daemon.log', 'user.log',
597 'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot']])
598 for log in system_logs:
599 prefix_output(CAP_SYSTEM_LOGS, log, last_mod_time=log_last_mod_time)
601 ovs_logs = ([ OPENVSWITCH_LOG_DIR + x for x in
602 ['ovs-vswitchd.log', 'ovsdb-server.log',
603 'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log', 'ovs-ctl.log']])
605 prefix_output(CAP_OPENVSWITCH_LOGS, log, last_mod_time=log_last_mod_time)
607 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
608 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
610 cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
612 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
614 file_output(CAP_YUM, [YUM_LOG])
615 tree_output(CAP_YUM, YUM_REPOS_DIR)
616 cmd_output(CAP_YUM, [RPM, '-qa'])
617 file_output(CAP_YUM, [APT_SOURCES_LIST])
618 tree_output(CAP_YUM, APT_SOURCES_LIST_D)
619 cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
621 # Filter out ovs relevant information if --ovs option passed
622 # else collect all information
626 ovs_info_caps = [CAP_NETWORK_STATUS, CAP_SYSTEM_LOGS,
628 ovs_info_list = ['process-tree']
629 # We cannot use iteritems, since we modify 'data' as we pass through
630 for (k, v) in data.items():
636 if info not in ovs_info_list and cap not in ovs_info_caps:
640 filter = ",".join(filters)
645 load_plugins(filter=filter)
649 # permit the user to filter out data
650 # We cannot use iteritems, since we modify 'data' as we pass through
651 for (k, v) in sorted(data.items()):
657 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % key):
660 # collect selected data now
661 output_ts('Running commands to collect data')
664 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
667 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
671 if output_file is None:
674 dirname = os.path.dirname(output_file)
675 if dirname and not os.path.exists(dirname):
682 output_ts('Creating output file')
684 if output_type.startswith('tar'):
685 make_tar(subdir, output_type, output_fd, output_file)
687 make_zip(subdir, output_file)
690 print >>sys.stderr, "Category sizes (max, actual):\n"
691 for c in caps.keys():
692 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
698 def dump_scsi_hosts(cap):
700 l = os.listdir('/sys/class/scsi_host')
706 f = open('/sys/class/scsi_host/%s/proc_name' % h)
707 procname = f.readline().strip("\n")
713 f = open('/sys/class/scsi_host/%s/model_name' % h)
714 modelname = f.readline().strip("\n")
720 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
724 def module_info(cap):
725 output = StringIO.StringIO()
726 modules = open(PROC_MODULES, 'r')
730 module = line.split()[0]
731 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
736 return output.getvalue()
739 def multipathd_topology(cap):
740 pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
741 stdout=PIPE, stderr=dev_null)
742 stdout, stderr = pipe.communicate('show topology')
747 output = StringIO.StringIO()
748 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
752 if not procs[0].timed_out:
753 return output.getvalue().splitlines()
757 if not os.path.isfile(OPENVSWITCH_CONF_DB):
763 if os.path.getsize(OPENVSWITCH_CONF_DB) > max_size:
764 if os.path.isfile(OPENVSWITCH_COMPACT_DB):
765 os.unlink(OPENVSWITCH_COMPACT_DB)
767 output = StringIO.StringIO()
769 procs = [ProcOutput(['ovsdb-tool', 'compact',
770 OPENVSWITCH_CONF_DB, OPENVSWITCH_COMPACT_DB],
773 file_output(CAP_NETWORK_STATUS, [OPENVSWITCH_COMPACT_DB])
775 file_output(CAP_NETWORK_STATUS, [OPENVSWITCH_CONF_DB])
781 if os.path.isfile(OPENVSWITCH_COMPACT_DB):
782 os.unlink(OPENVSWITCH_COMPACT_DB)
789 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
791 fh = open('/proc/'+d+'/cmdline')
793 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
795 if not num_fds in fd_dict:
796 fd_dict[num_fds] = []
797 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
800 keys = fd_dict.keys()
801 keys.sort(lambda a, b: int(b) - int(a))
803 output += "%s: %s\n" % (k, str(fd_dict[k]))
806 def dump_rdac_groups(cap):
807 output = StringIO.StringIO()
808 procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
812 if not procs[0].timed_out:
814 for line in output.getvalue().splitlines():
815 if line.startswith('ID'):
817 elif line.startswith('----'):
820 group, _ = line.split(None, 1)
821 cmd_output(cap, [MPPUTIL, '-g', group])
823 def load_plugins(just_capabilities=False, filter=None):
824 global log_last_mod_time
825 def getText(nodelist):
827 for node in nodelist:
828 if node.nodeType == node.TEXT_NODE:
832 def getBoolAttr(el, attr, default=False):
834 val = el.getAttribute(attr).lower()
835 if val in ['true', 'false', 'yes', 'no']:
836 ret = val in ['true', 'yes']
839 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
840 if not caps.has_key(dir):
841 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
843 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
844 assert xmldoc.documentElement.tagName == "capability"
846 pii, min_size, max_size, min_time, max_time, mime = \
847 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
849 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
850 pii = xmldoc.documentElement.getAttribute("pii")
851 if xmldoc.documentElement.getAttribute("min_size") != '':
852 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
853 if xmldoc.documentElement.getAttribute("max_size") != '':
854 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
855 if xmldoc.documentElement.getAttribute("min_time") != '':
856 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
857 if xmldoc.documentElement.getAttribute("max_time") != '':
858 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
859 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
860 mime = xmldoc.documentElement.getAttribute("mime")
861 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
862 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
864 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
866 if just_capabilities:
869 plugdir = os.path.join(PLUGIN_DIR, dir)
870 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
871 xmldoc = parse(os.path.join(plugdir, file))
872 assert xmldoc.documentElement.tagName == "collect"
874 for el in xmldoc.documentElement.getElementsByTagName("*"):
875 filters_tmp = el.getAttribute("filters")
876 if filters_tmp == '':
879 filters = filters_tmp.split(',')
880 if not(filter is None or filter in filters):
882 if el.tagName == "files":
883 newest_first = getBoolAttr(el, 'newest_first')
884 if el.getAttribute("type") == "logs":
885 for fn in getText(el.childNodes).split():
886 prefix_output(dir, fn, newest_first=newest_first,
887 last_mod_time=log_last_mod_time)
889 file_output(dir, getText(el.childNodes).split(),
890 newest_first=newest_first)
891 elif el.tagName == "directory":
892 pattern = el.getAttribute("pattern")
893 if pattern == '': pattern = None
894 negate = getBoolAttr(el, 'negate')
895 newest_first = getBoolAttr(el, 'newest_first')
896 if el.getAttribute("type") == "logs":
897 tree_output(dir, getText(el.childNodes),
898 pattern and re.compile(pattern) or None,
899 negate=negate, newest_first=newest_first,
900 last_mod_time=log_last_mod_time)
902 tree_output(dir, getText(el.childNodes),
903 pattern and re.compile(pattern) or None,
904 negate=negate, newest_first=newest_first)
905 elif el.tagName == "command":
906 label = el.getAttribute("label")
907 if label == '': label = None
908 binary = getBoolAttr(el, 'binary')
909 cmd_output(dir, getText(el.childNodes), label, binary=binary)
911 def make_tar(subdir, suffix, output_fd, output_file):
912 global SILENT_MODE, data
915 if suffix == 'tar.bz2':
917 elif suffix == 'tar.gz':
921 if output_file is None:
922 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
924 filename = output_file
925 old_umask = os.umask(0077)
926 tf = tarfile.open(filename, mode)
929 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
932 for (k, v) in data.items():
934 tar_filename = os.path.join(subdir, construct_filename(k, v))
935 ti = tarfile.TarInfo(tar_filename)
940 if v.has_key('output'):
941 ti.mtime = v['output'].mtime
942 ti.size = len(v['output'].getvalue())
944 tf.addfile(ti, v['output'])
945 elif v.has_key('filename'):
946 s = os.stat(v['filename'])
947 ti.mtime = s.st_mtime
949 tf.addfile(ti, file(v['filename']))
956 output ('Writing tarball %s successful.' % filename)
961 def make_zip(subdir, output_file):
962 global SILENT_MODE, data
964 if output_file is None:
965 filename = "%s/%s.zip" % (BUG_DIR, subdir)
967 filename = output_file
968 old_umask = os.umask(0077)
969 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
973 for (k, v) in data.items():
975 dest = os.path.join(subdir, construct_filename(k, v))
977 if v.has_key('output'):
978 zf.writestr(dest, v['output'].getvalue())
980 if os.stat(v['filename']).st_size < 50:
981 compress_type = zipfile.ZIP_STORED
983 compress_type = zipfile.ZIP_DEFLATED
984 zf.write(v['filename'], dest, compress_type)
990 output ('Writing archive %s successful.' % filename)
995 def make_inventory(inventory, subdir):
996 document = getDOMImplementation().createDocument(
997 None, INVENTORY_XML_ROOT, None)
999 # create summary entry
1000 s = document.createElement(INVENTORY_XML_SUMMARY)
1001 user = os.getenv('SUDO_USER', os.getenv('USER'))
1003 s.setAttribute('user', user)
1004 s.setAttribute('date', time.strftime('%c'))
1005 s.setAttribute('hostname', platform.node())
1006 s.setAttribute('uname', ' '.join(platform.uname()))
1007 s.setAttribute('uptime', commands.getoutput(UPTIME))
1008 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
1010 map(lambda (k, v): inventory_entry(document, subdir, k, v),
1012 return document.toprettyxml()
1014 def inventory_entry(document, subdir, k, v):
1016 el = document.createElement(INVENTORY_XML_ELEMENT)
1017 el.setAttribute('capability', v['cap'])
1018 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
1019 el.setAttribute('md5sum', md5sum(v))
1020 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
1027 if d.has_key('filename'):
1028 f = open(d['filename'])
1030 while len(data) > 0:
1034 elif d.has_key('output'):
1035 m.update(d['output'].getvalue())
1036 return m.hexdigest()
1039 def construct_filename(k, v):
1040 if v.has_key('filename'):
1041 if v['filename'][0] == '/':
1042 return v['filename'][1:]
1044 return v['filename']
1045 s = k.replace(' ', '-')
1046 s = s.replace('--', '-')
1047 s = s.replace('/', '%')
1048 if s.find('.') == -1:
1053 def update_capabilities():
1056 def update_cap_size(cap, size):
1057 update_cap(cap, MIN_SIZE, size)
1058 update_cap(cap, MAX_SIZE, size)
1059 update_cap(cap, CHECKED, size > 0)
1062 def update_cap(cap, k, v):
1066 caps[cap] = tuple(l)
1069 def size_of_dir(d, pattern=None, negate=False):
1070 if os.path.isdir(d):
1071 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1077 def size_of_all(files, pattern=None, negate=False):
1078 return sum([size_of(f, pattern, negate) for f in files])
1081 def matches(f, pattern, negate):
1083 return not matches(f, pattern, False)
1085 return pattern is None or pattern.match(f)
1088 def size_of(f, pattern, negate):
1089 if os.path.isfile(f) and matches(f, pattern, negate):
1090 return os.stat(f)[6]
1092 return size_of_dir(f, pattern, negate)
1095 def print_capabilities():
1096 document = getDOMImplementation().createDocument(
1097 "ns", CAP_XML_ROOT, None)
1098 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1099 print document.toprettyxml()
1101 def capability(document, key):
1103 el = document.createElement(CAP_XML_ELEMENT)
1104 el.setAttribute('key', c[KEY])
1105 el.setAttribute('pii', c[PII])
1106 el.setAttribute('min-size', str(c[MIN_SIZE]))
1107 el.setAttribute('max-size', str(c[MAX_SIZE]))
1108 el.setAttribute('min-time', str(c[MIN_TIME]))
1109 el.setAttribute('max-time', str(c[MAX_TIME]))
1110 el.setAttribute('content-type', c[MIME])
1111 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1112 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1116 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1117 return '\n'.join([format % i for i in d.items()]) + '\n'
1121 yn = raw_input(prompt)
1123 return len(yn) == 0 or yn.lower()[0] == 'y'
1126 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1131 f = open('/proc/partitions')
1134 for line in f.readlines():
1135 (major, minor, blocks, name) = line.split()
1136 if int(major) < 254 and not partition_re.match(name):
1147 def __init__(self, command, max_time, inst=None, filter=None, binary=False):
1148 self.command = command
1149 self.max_time = max_time
1151 self.running = False
1153 self.timed_out = False
1155 self.timeout = int(time.time()) + self.max_time
1156 self.filter = filter
1157 self.filter_state = {}
1159 self.bufsize = 1048576 # 1MB buffer
1161 self.bufsize = 1 # line buffered
1167 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1170 self.timed_out = False
1172 if ProcOutput.debug:
1173 output_ts("Starting '%s'" % self.cmdAsStr())
1174 self.proc = Popen(self.command, bufsize=self.bufsize,
1175 stdin=dev_null, stdout=PIPE, stderr=dev_null,
1176 shell=isinstance(self.command, str))
1177 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1178 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1182 output_ts("'%s' failed" % self.cmdAsStr())
1183 self.running = False
1186 def terminate(self):
1189 self.proc.stdout.close()
1190 os.kill(self.proc.pid, SIGTERM)
1194 self.running = False
1195 self.status = SIGTERM
1197 def read_line(self):
1199 if self.bufsize == 1:
1200 line = self.proc.stdout.readline()
1202 line = self.proc.stdout.read(self.bufsize)
1205 self.proc.stdout.close()
1206 self.status = self.proc.wait()
1208 self.running = False
1211 line = self.filter(line, self.filter_state)
1213 self.inst.write(line)
1215 def run_procs(procs):
1223 active_procs.append(p)
1224 pipes.append(p.proc.stdout)
1226 elif p.status == None and not p.failed and not p.timed_out:
1229 active_procs.append(p)
1230 pipes.append(p.proc.stdout)
1237 (i, o, x) = select(pipes, [], [], 1.0)
1238 now = int(time.time())
1240 # handle process output
1241 for p in active_procs:
1242 if p.proc.stdout in i:
1246 if p.running and now > p.timeout:
1247 output_ts("'%s' timed out" % p.cmdAsStr())
1249 p.inst.write("\n** timeout **\n")
1257 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1259 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1267 def check_space(cap, name, size):
1268 global free_disk_space
1269 if free_disk_space is not None and size > free_disk_space:
1270 output("Omitting %s, out of disk space (requested: %u, allowed: %u)" %
1271 (name, size, free_disk_space))
1273 elif unlimited_data or caps[cap][MAX_SIZE] == -1 or \
1274 cap_sizes[cap] < caps[cap][MAX_SIZE]:
1275 cap_sizes[cap] += size
1276 if free_disk_space is not None:
1277 free_disk_space -= size
1280 output("Omitting %s, size constraint of %s exceeded" % (name, cap))
1284 def get_free_disk_space(path):
1285 path = os.path.abspath(path)
1286 while not os.path.exists(path):
1287 path = os.path.dirname(path)
1288 s = os.statvfs(path)
1289 return s.f_frsize * s.f_bfree
1292 class StringIOmtime(StringIO.StringIO):
1293 def __init__(self, buf=''):
1294 StringIO.StringIO.__init__(self, buf)
1295 self.mtime = time.time()
1298 StringIO.StringIO.write(self, s)
1299 self.mtime = time.time()
1302 if __name__ == "__main__":
1305 except KeyboardInterrupt:
1306 print "\nInterrupted."