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, 2013 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_CONF_DB = '@DBDIR@/conf.db'
115 OPENVSWITCH_COMPACT_DB = '@DBDIR@/bugtool-compact-conf.db'
116 OPENVSWITCH_VSWITCHD_PID = '@RUNDIR@/ovs-vswitchd.pid'
117 VAR_LOG_DIR = '/var/log/'
118 VAR_LOG_CORE_DIR = '/var/log/core'
119 YUM_LOG = '/var/log/yum.log'
120 YUM_REPOS_DIR = '/etc/yum.repos.d'
126 os.environ['PATH'] = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:@pkgdatadir@/scripts'
129 CHKCONFIG = 'chkconfig'
132 DMIDECODE = 'dmidecode'
134 DPKG_QUERY = 'dpkg-query'
138 IFCONFIG = 'ifconfig'
139 IPTABLES = 'iptables'
140 ISCSIADM = 'iscsiadm'
147 MULTIPATHD = 'multipathd'
149 OVS_DPCTL = 'ovs-dpctl'
150 OVS_OFCTL = 'ovs-ofctl'
151 OVS_VSCTL = 'ovs-vsctl'
162 # PII -- Personally identifiable information. Of particular concern are
163 # things that would identify customers, or their network topology.
164 # Passwords are never to be included in any bug report, regardless of any PII
167 # NO -- No PII will be in these entries.
168 # YES -- PII will likely or certainly be in these entries.
169 # MAYBE -- The user may wish to audit these entries for PII.
170 # IF_CUSTOMIZED -- If the files are unmodified, then they will contain no PII,
171 # but since we encourage customers to edit these files, PII may have been
172 # introduced by the customer. This is used in particular for the networking
179 PII_IF_CUSTOMIZED = 'if_customized'
190 MIME_DATA = 'application/data'
191 MIME_TEXT = 'text/plain'
193 INVENTORY_XML_ROOT = "system-status-inventory"
194 INVENTORY_XML_SUMMARY = 'system-summary'
195 INVENTORY_XML_ELEMENT = 'inventory-entry'
196 CAP_XML_ROOT = "system-status-capabilities"
197 CAP_XML_ELEMENT = 'capability'
200 CAP_BOOT_LOADER = 'boot-loader'
201 CAP_DISK_INFO = 'disk-info'
202 CAP_HARDWARE_INFO = 'hardware-info'
203 CAP_KERNEL_INFO = 'kernel-info'
204 CAP_LOSETUP_A = 'loopback-devices'
205 CAP_MULTIPATH = 'multipath'
206 CAP_NETWORK_CONFIG = 'network-config'
207 CAP_NETWORK_INFO = 'network-info'
208 CAP_NETWORK_STATUS = 'network-status'
209 CAP_OPENVSWITCH_LOGS = 'ovs-system-logs'
210 CAP_PROCESS_LIST = 'process-list'
211 CAP_SYSTEM_LOGS = 'system-logs'
212 CAP_SYSTEM_SERVICES = 'system-services'
220 unlimited_data = False
222 # Default value for the number of days to collect logs.
224 log_last_mod_time = None
225 free_disk_space = None
227 def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
228 max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
229 caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
234 cap(CAP_BOOT_LOADER, PII_NO, max_size=3*KB,
236 cap(CAP_DISK_INFO, PII_MAYBE, max_size=50*KB,
238 cap(CAP_HARDWARE_INFO, PII_MAYBE, max_size=2*MB,
240 cap(CAP_KERNEL_INFO, PII_MAYBE, max_size=120*KB,
242 cap(CAP_LOSETUP_A, PII_MAYBE, max_size=KB, max_time=5)
243 cap(CAP_MULTIPATH, PII_MAYBE, max_size=20*KB,
245 cap(CAP_NETWORK_CONFIG, PII_IF_CUSTOMIZED,
246 min_size=0, max_size=5*MB)
247 cap(CAP_NETWORK_INFO, PII_YES, max_size=50*MB,
249 cap(CAP_NETWORK_STATUS, PII_YES, max_size=-1,
251 cap(CAP_OPENVSWITCH_LOGS, PII_MAYBE, max_size=-1,
253 cap(CAP_PROCESS_LIST, PII_YES, max_size=30*KB,
255 cap(CAP_SYSTEM_LOGS, PII_MAYBE, max_size=200*MB,
257 cap(CAP_SYSTEM_SERVICES, PII_NO, max_size=5*KB,
259 cap(CAP_YUM, PII_IF_CUSTOMIZED, max_size=10*KB,
262 ANSWER_YES_TO_ALL = False
266 dev_null = open('/dev/null', 'r+')
274 output("[%s] %s" % (time.strftime("%x %X %Z"), x))
276 def cmd_output(cap, args, label=None, filter=None, binary=False):
279 if isinstance(args, list):
280 a = [aa for aa in args]
281 a[0] = os.path.basename(a[0])
285 data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter,
289 def file_output(cap, path_list, newest_first=False, last_mod_time=None):
291 If newest_first is True, the list of files in path_list is sorted
292 by file modification time in descending order, else its sorted
297 for path in path_list:
302 if last_mod_time is None or s.st_mtime >= last_mod_time:
303 path_entries.append((path, s))
305 mtime = lambda(path, stat): stat.st_mtime
306 path_entries.sort(key=mtime, reverse=newest_first)
307 for p in path_entries:
308 if check_space(cap, p[0], p[1].st_size):
309 data[p] = {'cap': cap, 'filename': p[0]}
312 def tree_output(cap, path, pattern=None, negate=False, newest_first=False,
315 Walks the directory tree rooted at path. Files in current dir are processed
316 before files in sub-dirs.
319 if os.path.exists(path):
320 for root, dirs, files in os.walk(path):
321 fns = [fn for fn in [os.path.join(root, f) for f in files]
322 if os.path.isfile(fn) and matches(fn, pattern, negate)]
323 file_output(cap, fns, newest_first=newest_first,
324 last_mod_time=last_mod_time)
327 def prefix_output(cap, prefix, newest_first=False, last_mod_time=None):
329 Output files with the same prefix.
332 for root, dirs, files in os.walk(os.path.dirname(prefix)):
333 fns += [fn for fn in [os.path.join(root, f) for f in files]
334 if fn.startswith(prefix)]
335 file_output(cap, fns, newest_first=newest_first,
336 last_mod_time=last_mod_time)
339 def func_output(cap, label, func):
341 t = str(func).split()
342 data[label] = {'cap': cap, 'func': func}
347 for (k, v) in data.items():
349 if v.has_key('cmd_args'):
350 v['output'] = StringIOmtime()
351 if not process_lists.has_key(cap):
352 process_lists[cap] = []
353 process_lists[cap].append(
354 ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'],
355 v['filter'], v['binary']))
356 elif v.has_key('filename') and v['filename'].startswith('/proc/'):
357 # proc files must be read into memory
359 f = open(v['filename'], 'r')
362 if check_space(cap, v['filename'], len(s)):
363 v['output'] = StringIOmtime(s)
366 elif v.has_key('func'):
371 if check_space(cap, k, len(s)):
372 v['output'] = StringIOmtime(s)
374 run_procs(process_lists.values())
378 global ANSWER_YES_TO_ALL, SILENT_MODE
379 global entries, data, dbg, unlimited_data, free_disk_space
380 global log_days, log_last_mod_time
383 only_ovs_info = False
384 collect_all_info = True
386 # we need access to privileged files, exit if we are not running as root
388 print >>sys.stderr, "Error: ovs-bugtool must be run as root"
392 output_type = 'tar.gz'
399 (options, params) = getopt.gnu_getopt(
400 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
401 'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
402 'debug', 'ovs', 'log-days='])
403 except getopt.GetoptError, opterr:
404 print >>sys.stderr, opterr
412 entries = [e for e in caps.keys() if caps[e][CHECKED]]
414 for (k, v) in options:
415 if k == '--capabilities':
416 update_capabilities()
421 if v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
424 print >>sys.stderr, "Invalid output format '%s'" % v
427 # "-s" or "--silent" means suppress output (except for the final
428 # output filename at the end)
429 if k in ['-s', '--silent']:
432 if k == '--entries' and v != '':
433 entries = v.split(',')
435 # If the user runs the script with "-y" or "--yestoall" we don't ask
436 # all the really annoying questions.
437 if k in ['-y', '--yestoall']:
438 ANSWER_YES_TO_ALL = True
443 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
444 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
446 print >>sys.stderr, "Invalid output file descriptor", output_fd
453 entries = caps.keys()
454 elif k == '--unlimited':
455 unlimited_data = True
458 ProcOutput.debug = True
462 collect_all_info = False
464 if k == '--log-days':
469 print >>sys.stderr, "Invalid additional arguments", str(params)
472 if output_fd != -1 and output_type != 'tar':
473 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
476 if output_fd != -1 and output_file is not None:
477 print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
480 if output_file is not None and not unlimited_data:
481 free_disk_space = get_free_disk_space(output_file) * 90 / 100
483 log_last_mod_time = int(time.time()) - log_days * 86400
485 if ANSWER_YES_TO_ALL:
486 output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
489 This application will collate dmesg output, details of the
490 hardware configuration of your machine, information about the build of
491 openvswitch that you are using, plus, if you allow it, various logs.
493 The collated information will be saved as a .%s for archiving or
494 sending to a Technical Support Representative.
496 The logs may contain private information, and if you are at all
497 worried about that, you should exit now, or you should explicitly
498 exclude those logs from the archive.
502 # assemble potential data
504 file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
505 cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
506 cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
508 cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
509 file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
510 file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
511 cmd_output(CAP_DISK_INFO, [DF, '-alT'])
512 cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
513 if len(pidof('iscsid')) != 0:
514 cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
515 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
516 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
517 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
518 cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
519 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
521 file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
522 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
523 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
524 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
525 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
526 file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
527 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
530 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
531 PROC_FILESYSTEMS, PROC_CMDLINE])
532 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
533 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
534 file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
535 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
536 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
538 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
540 file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
541 cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
542 func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
543 cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
544 if CAP_MULTIPATH in entries and collect_all_info:
545 dump_rdac_groups(CAP_MULTIPATH)
547 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
548 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
549 file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
550 file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
551 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_DEFAULT_SWITCH,
552 OPENVSWITCH_SYSCONFIG_SWITCH])
554 cmd_output(CAP_NETWORK_INFO, [IFCONFIG, '-a'])
555 cmd_output(CAP_NETWORK_INFO, [ROUTE, '-n'])
556 cmd_output(CAP_NETWORK_INFO, [ARP, '-n'])
557 cmd_output(CAP_NETWORK_INFO, [NETSTAT, '-an'])
558 for dir in DHCP_LEASE_DIR:
559 tree_output(CAP_NETWORK_INFO, dir)
560 for table in ['filter', 'nat', 'mangle', 'raw', 'security']:
561 cmd_output(CAP_NETWORK_INFO, [IPTABLES, '-t', table, '-nL'])
562 for p in os.listdir('/sys/class/net/'):
564 f = open('/sys/class/net/%s/type' % p, 'r')
567 if os.path.islink('/sys/class/net/%s/device' % p) and int(t) == 1:
569 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-S', p])
570 if not p.startswith('vif') and not p.startswith('tap'):
571 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, p])
572 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-k', p])
573 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-i', p])
574 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-c', p])
576 cmd_output(CAP_NETWORK_INFO,
577 [TC, '-s', '-d', 'class', 'show', 'dev', p])
580 tree_output(CAP_NETWORK_INFO, PROC_NET_BONDING_DIR)
581 tree_output(CAP_NETWORK_INFO, PROC_NET_VLAN_DIR)
582 cmd_output(CAP_NETWORK_INFO, [TC, '-s', 'qdisc'])
583 file_output(CAP_NETWORK_INFO, [PROC_NET_SOFTNET_STAT])
586 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
587 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
589 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
591 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
592 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
594 system_logs = ([ VAR_LOG_DIR + x for x in
595 ['crit.log', 'kern.log', 'daemon.log', 'user.log',
596 'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot']])
597 for log in system_logs:
598 prefix_output(CAP_SYSTEM_LOGS, log, last_mod_time=log_last_mod_time)
600 ovs_logs = ([ OPENVSWITCH_LOG_DIR + x for x in
601 ['ovs-vswitchd.log', 'ovsdb-server.log',
602 'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log', 'ovs-ctl.log']])
604 prefix_output(CAP_OPENVSWITCH_LOGS, log, last_mod_time=log_last_mod_time)
606 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
607 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
609 cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
611 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
613 file_output(CAP_YUM, [YUM_LOG])
614 tree_output(CAP_YUM, YUM_REPOS_DIR)
615 cmd_output(CAP_YUM, [RPM, '-qa'])
616 file_output(CAP_YUM, [APT_SOURCES_LIST])
617 tree_output(CAP_YUM, APT_SOURCES_LIST_D)
618 cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
620 # Filter out ovs relevant information if --ovs option passed
621 # else collect all information
625 ovs_info_caps = [CAP_NETWORK_STATUS, CAP_SYSTEM_LOGS,
627 ovs_info_list = ['process-tree']
628 # We cannot use iteritems, since we modify 'data' as we pass through
629 for (k, v) in data.items():
635 if info not in ovs_info_list and cap not in ovs_info_caps:
639 filter = ",".join(filters)
644 load_plugins(filter=filter)
648 # permit the user to filter out data
649 # We cannot use iteritems, since we modify 'data' as we pass through
650 for (k, v) in sorted(data.items()):
656 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % key):
659 # collect selected data now
660 output_ts('Running commands to collect data')
663 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
666 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
670 if output_file is None:
673 dirname = os.path.dirname(output_file)
674 if dirname and not os.path.exists(dirname):
681 output_ts('Creating output file')
683 if output_type.startswith('tar'):
684 make_tar(subdir, output_type, output_fd, output_file)
686 make_zip(subdir, output_file)
689 print >>sys.stderr, "Category sizes (max, actual):\n"
690 for c in caps.keys():
691 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
697 def dump_scsi_hosts(cap):
699 l = os.listdir('/sys/class/scsi_host')
705 f = open('/sys/class/scsi_host/%s/proc_name' % h)
706 procname = f.readline().strip("\n")
712 f = open('/sys/class/scsi_host/%s/model_name' % h)
713 modelname = f.readline().strip("\n")
719 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
723 def module_info(cap):
724 output = StringIO.StringIO()
725 modules = open(PROC_MODULES, 'r')
729 module = line.split()[0]
730 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
735 return output.getvalue()
738 def multipathd_topology(cap):
739 pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
740 stdout=PIPE, stderr=dev_null)
741 stdout, stderr = pipe.communicate('show topology')
746 output = StringIO.StringIO()
747 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
751 if not procs[0].timed_out:
752 return output.getvalue().splitlines()
756 if not os.path.isfile(OPENVSWITCH_CONF_DB):
762 if os.path.getsize(OPENVSWITCH_CONF_DB) > max_size:
763 if os.path.isfile(OPENVSWITCH_COMPACT_DB):
764 os.unlink(OPENVSWITCH_COMPACT_DB)
766 output = StringIO.StringIO()
768 procs = [ProcOutput(['ovsdb-tool', 'compact',
769 OPENVSWITCH_CONF_DB, OPENVSWITCH_COMPACT_DB],
772 file_output(CAP_NETWORK_STATUS, [OPENVSWITCH_COMPACT_DB])
774 file_output(CAP_NETWORK_STATUS, [OPENVSWITCH_CONF_DB])
780 if os.path.isfile(OPENVSWITCH_COMPACT_DB):
781 os.unlink(OPENVSWITCH_COMPACT_DB)
788 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
790 fh = open('/proc/'+d+'/cmdline')
792 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
794 if not num_fds in fd_dict:
795 fd_dict[num_fds] = []
796 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
799 keys = fd_dict.keys()
800 keys.sort(lambda a, b: int(b) - int(a))
802 output += "%s: %s\n" % (k, str(fd_dict[k]))
805 def dump_rdac_groups(cap):
806 output = StringIO.StringIO()
807 procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
811 if not procs[0].timed_out:
813 for line in output.getvalue().splitlines():
814 if line.startswith('ID'):
816 elif line.startswith('----'):
819 group, _ = line.split(None, 1)
820 cmd_output(cap, [MPPUTIL, '-g', group])
822 def load_plugins(just_capabilities=False, filter=None):
823 global log_last_mod_time
824 def getText(nodelist):
826 for node in nodelist:
827 if node.nodeType == node.TEXT_NODE:
831 def getBoolAttr(el, attr, default=False):
833 val = el.getAttribute(attr).lower()
834 if val in ['true', 'false', 'yes', 'no']:
835 ret = val in ['true', 'yes']
838 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
839 if not caps.has_key(dir):
840 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
842 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
843 assert xmldoc.documentElement.tagName == "capability"
845 pii, min_size, max_size, min_time, max_time, mime = \
846 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
848 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
849 pii = xmldoc.documentElement.getAttribute("pii")
850 if xmldoc.documentElement.getAttribute("min_size") != '':
851 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
852 if xmldoc.documentElement.getAttribute("max_size") != '':
853 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
854 if xmldoc.documentElement.getAttribute("min_time") != '':
855 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
856 if xmldoc.documentElement.getAttribute("max_time") != '':
857 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
858 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
859 mime = xmldoc.documentElement.getAttribute("mime")
860 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
861 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
863 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
865 if just_capabilities:
868 plugdir = os.path.join(PLUGIN_DIR, dir)
869 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
870 xmldoc = parse(os.path.join(plugdir, file))
871 assert xmldoc.documentElement.tagName == "collect"
873 for el in xmldoc.documentElement.getElementsByTagName("*"):
874 filters_tmp = el.getAttribute("filters")
875 if filters_tmp == '':
878 filters = filters_tmp.split(',')
879 if not(filter is None or filter in filters):
881 if el.tagName == "files":
882 newest_first = getBoolAttr(el, 'newest_first')
883 if el.getAttribute("type") == "logs":
884 for fn in getText(el.childNodes).split():
885 prefix_output(dir, fn, newest_first=newest_first,
886 last_mod_time=log_last_mod_time)
888 file_output(dir, getText(el.childNodes).split(),
889 newest_first=newest_first)
890 elif el.tagName == "directory":
891 pattern = el.getAttribute("pattern")
892 if pattern == '': pattern = None
893 negate = getBoolAttr(el, 'negate')
894 newest_first = getBoolAttr(el, 'newest_first')
895 if el.getAttribute("type") == "logs":
896 tree_output(dir, getText(el.childNodes),
897 pattern and re.compile(pattern) or None,
898 negate=negate, newest_first=newest_first,
899 last_mod_time=log_last_mod_time)
901 tree_output(dir, getText(el.childNodes),
902 pattern and re.compile(pattern) or None,
903 negate=negate, newest_first=newest_first)
904 elif el.tagName == "command":
905 label = el.getAttribute("label")
906 if label == '': label = None
907 binary = getBoolAttr(el, 'binary')
908 cmd_output(dir, getText(el.childNodes), label, binary=binary)
910 def make_tar(subdir, suffix, output_fd, output_file):
911 global SILENT_MODE, data
914 if suffix == 'tar.bz2':
916 elif suffix == 'tar.gz':
920 if output_file is None:
921 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
923 filename = output_file
924 old_umask = os.umask(0077)
925 tf = tarfile.open(filename, mode)
928 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
931 for (k, v) in data.items():
933 tar_filename = os.path.join(subdir, construct_filename(k, v))
934 ti = tarfile.TarInfo(tar_filename)
939 if v.has_key('output'):
940 ti.mtime = v['output'].mtime
941 ti.size = len(v['output'].getvalue())
943 tf.addfile(ti, v['output'])
944 elif v.has_key('filename'):
945 s = os.stat(v['filename'])
946 ti.mtime = s.st_mtime
948 tf.addfile(ti, file(v['filename']))
955 output ('Writing tarball %s successful.' % filename)
960 def make_zip(subdir, output_file):
961 global SILENT_MODE, data
963 if output_file is None:
964 filename = "%s/%s.zip" % (BUG_DIR, subdir)
966 filename = output_file
967 old_umask = os.umask(0077)
968 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
972 for (k, v) in data.items():
974 dest = os.path.join(subdir, construct_filename(k, v))
976 if v.has_key('output'):
977 zf.writestr(dest, v['output'].getvalue())
979 if os.stat(v['filename']).st_size < 50:
980 compress_type = zipfile.ZIP_STORED
982 compress_type = zipfile.ZIP_DEFLATED
983 zf.write(v['filename'], dest, compress_type)
989 output ('Writing archive %s successful.' % filename)
994 def make_inventory(inventory, subdir):
995 document = getDOMImplementation().createDocument(
996 None, INVENTORY_XML_ROOT, None)
998 # create summary entry
999 s = document.createElement(INVENTORY_XML_SUMMARY)
1000 user = os.getenv('SUDO_USER', os.getenv('USER'))
1002 s.setAttribute('user', user)
1003 s.setAttribute('date', time.strftime('%c'))
1004 s.setAttribute('hostname', platform.node())
1005 s.setAttribute('uname', ' '.join(platform.uname()))
1006 s.setAttribute('uptime', commands.getoutput(UPTIME))
1007 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
1009 map(lambda (k, v): inventory_entry(document, subdir, k, v),
1011 return document.toprettyxml()
1013 def inventory_entry(document, subdir, k, v):
1015 el = document.createElement(INVENTORY_XML_ELEMENT)
1016 el.setAttribute('capability', v['cap'])
1017 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
1018 el.setAttribute('md5sum', md5sum(v))
1019 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
1026 if d.has_key('filename'):
1027 f = open(d['filename'])
1029 while len(data) > 0:
1033 elif d.has_key('output'):
1034 m.update(d['output'].getvalue())
1035 return m.hexdigest()
1038 def construct_filename(k, v):
1039 if v.has_key('filename'):
1040 if v['filename'][0] == '/':
1041 return v['filename'][1:]
1043 return v['filename']
1044 s = k.replace(' ', '-')
1045 s = s.replace('--', '-')
1046 s = s.replace('/', '%')
1047 if s.find('.') == -1:
1052 def update_capabilities():
1055 def update_cap_size(cap, size):
1056 update_cap(cap, MIN_SIZE, size)
1057 update_cap(cap, MAX_SIZE, size)
1058 update_cap(cap, CHECKED, size > 0)
1061 def update_cap(cap, k, v):
1065 caps[cap] = tuple(l)
1068 def size_of_dir(d, pattern=None, negate=False):
1069 if os.path.isdir(d):
1070 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1076 def size_of_all(files, pattern=None, negate=False):
1077 return sum([size_of(f, pattern, negate) for f in files])
1080 def matches(f, pattern, negate):
1082 return not matches(f, pattern, False)
1084 return pattern is None or pattern.match(f)
1087 def size_of(f, pattern, negate):
1088 if os.path.isfile(f) and matches(f, pattern, negate):
1089 return os.stat(f)[6]
1091 return size_of_dir(f, pattern, negate)
1094 def print_capabilities():
1095 document = getDOMImplementation().createDocument(
1096 "ns", CAP_XML_ROOT, None)
1097 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1098 print document.toprettyxml()
1100 def capability(document, key):
1102 el = document.createElement(CAP_XML_ELEMENT)
1103 el.setAttribute('key', c[KEY])
1104 el.setAttribute('pii', c[PII])
1105 el.setAttribute('min-size', str(c[MIN_SIZE]))
1106 el.setAttribute('max-size', str(c[MAX_SIZE]))
1107 el.setAttribute('min-time', str(c[MIN_TIME]))
1108 el.setAttribute('max-time', str(c[MAX_TIME]))
1109 el.setAttribute('content-type', c[MIME])
1110 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1111 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1115 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1116 return '\n'.join([format % i for i in d.items()]) + '\n'
1120 yn = raw_input(prompt)
1122 return len(yn) == 0 or yn.lower()[0] == 'y'
1125 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1130 f = open('/proc/partitions')
1133 for line in f.readlines():
1134 (major, minor, blocks, name) = line.split()
1135 if int(major) < 254 and not partition_re.match(name):
1146 def __init__(self, command, max_time, inst=None, filter=None, binary=False):
1147 self.command = command
1148 self.max_time = max_time
1150 self.running = False
1152 self.timed_out = False
1154 self.timeout = int(time.time()) + self.max_time
1155 self.filter = filter
1156 self.filter_state = {}
1158 self.bufsize = 1048576 # 1MB buffer
1160 self.bufsize = 1 # line buffered
1166 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1169 self.timed_out = False
1171 if ProcOutput.debug:
1172 output_ts("Starting '%s'" % self.cmdAsStr())
1173 self.proc = Popen(self.command, bufsize=self.bufsize,
1174 stdin=dev_null, stdout=PIPE, stderr=dev_null,
1175 shell=isinstance(self.command, str))
1176 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1177 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1181 output_ts("'%s' failed" % self.cmdAsStr())
1182 self.running = False
1185 def terminate(self):
1188 self.proc.stdout.close()
1189 os.kill(self.proc.pid, SIGTERM)
1193 self.running = False
1194 self.status = SIGTERM
1196 def read_line(self):
1198 if self.bufsize == 1:
1199 line = self.proc.stdout.readline()
1201 line = self.proc.stdout.read(self.bufsize)
1204 self.proc.stdout.close()
1205 self.status = self.proc.wait()
1207 self.running = False
1210 line = self.filter(line, self.filter_state)
1212 self.inst.write(line)
1214 def run_procs(procs):
1222 active_procs.append(p)
1223 pipes.append(p.proc.stdout)
1225 elif p.status == None and not p.failed and not p.timed_out:
1228 active_procs.append(p)
1229 pipes.append(p.proc.stdout)
1236 (i, o, x) = select(pipes, [], [], 1.0)
1237 now = int(time.time())
1239 # handle process output
1240 for p in active_procs:
1241 if p.proc.stdout in i:
1245 if p.running and now > p.timeout:
1246 output_ts("'%s' timed out" % p.cmdAsStr())
1248 p.inst.write("\n** timeout **\n")
1256 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1258 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1266 def check_space(cap, name, size):
1267 global free_disk_space
1268 if free_disk_space is not None and size > free_disk_space:
1269 output("Omitting %s, out of disk space (requested: %u, allowed: %u)" %
1270 (name, size, free_disk_space))
1272 elif unlimited_data or caps[cap][MAX_SIZE] == -1 or \
1273 cap_sizes[cap] < caps[cap][MAX_SIZE]:
1274 cap_sizes[cap] += size
1275 if free_disk_space is not None:
1276 free_disk_space -= size
1279 output("Omitting %s, size constraint of %s exceeded" % (name, cap))
1283 def get_free_disk_space(path):
1284 path = os.path.abspath(path)
1285 while not os.path.exists(path):
1286 path = os.path.dirname(path)
1287 s = os.statvfs(path)
1288 return s.f_frsize * s.f_bfree
1291 class StringIOmtime(StringIO.StringIO):
1292 def __init__(self, buf=''):
1293 StringIO.StringIO.__init__(self, buf)
1294 self.mtime = time.time()
1297 StringIO.StringIO.write(self, s)
1298 self.mtime = time.time()
1301 if __name__ == "__main__":
1304 except KeyboardInterrupt:
1305 print "\nInterrupted."