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 rotated logs.
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):
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 path_entries.append((path, s))
304 mtime = lambda(path, stat): stat.st_mtime
305 path_entries.sort(key=mtime, reverse=newest_first)
306 for p in path_entries:
307 if check_space(cap, p[0], p[1].st_size):
308 data[p] = {'cap': cap, 'filename': p[0]}
311 def tree_output(cap, path, pattern=None, negate=False, newest_first=False):
313 Walks the directory tree rooted at path. Files in current dir are processed
314 before files in sub-dirs.
317 if os.path.exists(path):
318 for root, dirs, files in os.walk(path):
319 fns = [fn for fn in [os.path.join(root, f) for f in files]
320 if os.path.isfile(fn) and matches(fn, pattern, negate)]
321 file_output(cap, fns, newest_first=newest_first)
323 def func_output(cap, label, func):
325 t = str(func).split()
326 data[label] = {'cap': cap, 'func': func}
328 def log_output(cap, logs, newest_first=False):
330 file_output(cap, logs)
332 ['%s.%d' % (f, n) for n in range(1, log_days+1) for f in logs], \
333 newest_first=newest_first)
335 ['%s.%d.gz' % (f, n) for n in range(1, log_days+1) for f in logs], \
336 newest_first=newest_first)
341 for (k, v) in data.items():
343 if v.has_key('cmd_args'):
344 v['output'] = StringIOmtime()
345 if not process_lists.has_key(cap):
346 process_lists[cap] = []
347 process_lists[cap].append(
348 ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'],
349 v['filter'], v['binary']))
350 elif v.has_key('filename') and v['filename'].startswith('/proc/'):
351 # proc files must be read into memory
353 f = open(v['filename'], 'r')
356 if check_space(cap, v['filename'], len(s)):
357 v['output'] = StringIOmtime(s)
360 elif v.has_key('func'):
365 if check_space(cap, k, len(s)):
366 v['output'] = StringIOmtime(s)
368 run_procs(process_lists.values())
372 global ANSWER_YES_TO_ALL, SILENT_MODE
373 global entries, data, dbg, unlimited_data, log_days, free_disk_space
376 only_ovs_info = False
377 collect_all_info = True
379 # we need access to privileged files, exit if we are not running as root
381 print >>sys.stderr, "Error: ovs-bugtool must be run as root"
385 output_type = 'tar.gz'
392 (options, params) = getopt.gnu_getopt(
393 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
394 'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
395 'debug', 'ovs', 'log-days='])
396 except getopt.GetoptError, opterr:
397 print >>sys.stderr, opterr
405 entries = [e for e in caps.keys() if caps[e][CHECKED]]
407 for (k, v) in options:
408 if k == '--capabilities':
409 update_capabilities()
414 if v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
417 print >>sys.stderr, "Invalid output format '%s'" % v
420 # "-s" or "--silent" means suppress output (except for the final
421 # output filename at the end)
422 if k in ['-s', '--silent']:
425 if k == '--entries' and v != '':
426 entries = v.split(',')
428 # If the user runs the script with "-y" or "--yestoall" we don't ask
429 # all the really annoying questions.
430 if k in ['-y', '--yestoall']:
431 ANSWER_YES_TO_ALL = True
436 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
437 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
439 print >>sys.stderr, "Invalid output file descriptor", output_fd
446 entries = caps.keys()
447 elif k == '--unlimited':
448 unlimited_data = True
451 ProcOutput.debug = True
455 collect_all_info = False
457 if k == '--log-days':
462 print >>sys.stderr, "Invalid additional arguments", str(params)
465 if output_fd != -1 and output_type != 'tar':
466 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
469 if output_fd != -1 and output_file is not None:
470 print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
473 if output_file is not None and not unlimited_data:
474 free_disk_space = get_free_disk_space(output_file) * 90 / 100
476 if ANSWER_YES_TO_ALL:
477 output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
480 This application will collate dmesg output, details of the
481 hardware configuration of your machine, information about the build of
482 openvswitch that you are using, plus, if you allow it, various logs.
484 The collated information will be saved as a .%s for archiving or
485 sending to a Technical Support Representative.
487 The logs may contain private information, and if you are at all
488 worried about that, you should exit now, or you should explicitly
489 exclude those logs from the archive.
493 # assemble potential data
495 file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
496 cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
497 cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
499 cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
500 file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
501 file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
502 cmd_output(CAP_DISK_INFO, [DF, '-alT'])
503 cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
504 if len(pidof('iscsid')) != 0:
505 cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
506 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
507 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
508 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
509 cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
510 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
512 file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
513 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
514 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
515 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
516 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
517 file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
518 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
521 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
522 PROC_FILESYSTEMS, PROC_CMDLINE])
523 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
524 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
525 file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
526 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
527 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
529 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
531 file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
532 cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
533 func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
534 cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
535 if CAP_MULTIPATH in entries and collect_all_info:
536 dump_rdac_groups(CAP_MULTIPATH)
538 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
539 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
540 file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
541 file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
542 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_DEFAULT_SWITCH,
543 OPENVSWITCH_SYSCONFIG_SWITCH, OPENVSWITCH_DEFAULT_CONTROLLER])
545 cmd_output(CAP_NETWORK_INFO, [IFCONFIG, '-a'])
546 cmd_output(CAP_NETWORK_INFO, [ROUTE, '-n'])
547 cmd_output(CAP_NETWORK_INFO, [ARP, '-n'])
548 cmd_output(CAP_NETWORK_INFO, [NETSTAT, '-an'])
549 for dir in DHCP_LEASE_DIR:
550 tree_output(CAP_NETWORK_INFO, dir)
551 for table in ['filter', 'nat', 'mangle', 'raw', 'security']:
552 cmd_output(CAP_NETWORK_INFO, [IPTABLES, '-t', table, '-nL'])
553 for p in os.listdir('/sys/class/net/'):
555 f = open('/sys/class/net/%s/type' % p, 'r')
558 if os.path.islink('/sys/class/net/%s/device' % p) and int(t) == 1:
560 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-S', p])
561 if not p.startswith('vif') and not p.startswith('tap'):
562 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, p])
563 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-k', p])
564 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-i', p])
565 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-c', p])
567 cmd_output(CAP_NETWORK_INFO,
568 [TC, '-s', '-d', 'class', 'show', 'dev', p])
571 tree_output(CAP_NETWORK_INFO, PROC_NET_BONDING_DIR)
572 tree_output(CAP_NETWORK_INFO, PROC_NET_VLAN_DIR)
573 cmd_output(CAP_NETWORK_INFO, [TC, '-s', 'qdisc'])
574 file_output(CAP_NETWORK_INFO, [PROC_NET_SOFTNET_STAT])
577 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
578 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
580 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
582 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
583 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
585 system_logs = ([ VAR_LOG_DIR + x for x in
586 ['crit.log', 'kern.log', 'daemon.log', 'user.log',
587 'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot']])
588 ovs_logs = ([ OPENVSWITCH_LOG_DIR + x for x in
589 ['ovs-vswitchd.log', 'ovsdb-server.log',
590 'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log', 'ovs-ctl.log']])
591 log_output(CAP_SYSTEM_LOGS, system_logs)
592 log_output(CAP_OPENVSWITCH_LOGS, ovs_logs)
594 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
595 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
597 cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
599 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
601 file_output(CAP_YUM, [YUM_LOG])
602 tree_output(CAP_YUM, YUM_REPOS_DIR)
603 cmd_output(CAP_YUM, [RPM, '-qa'])
604 file_output(CAP_YUM, [APT_SOURCES_LIST])
605 tree_output(CAP_YUM, APT_SOURCES_LIST_D)
606 cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
608 # Filter out ovs relevant information if --ovs option passed
609 # else collect all information
613 ovs_info_caps = [CAP_NETWORK_STATUS, CAP_SYSTEM_LOGS,
615 ovs_info_list = ['process-tree']
616 # We cannot use iteritems, since we modify 'data' as we pass through
617 for (k, v) in data.items():
623 if info not in ovs_info_list and cap not in ovs_info_caps:
627 filter = ",".join(filters)
632 load_plugins(filter=filter)
636 # permit the user to filter out data
637 # We cannot use iteritems, since we modify 'data' as we pass through
638 for (k, v) in sorted(data.items()):
644 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % key):
647 # collect selected data now
648 output_ts('Running commands to collect data')
651 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
654 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
658 if output_file is None:
661 dirname = os.path.dirname(output_file)
662 if dirname and not os.path.exists(dirname):
669 output_ts('Creating output file')
671 if output_type.startswith('tar'):
672 make_tar(subdir, output_type, output_fd, output_file)
674 make_zip(subdir, output_file)
677 print >>sys.stderr, "Category sizes (max, actual):\n"
678 for c in caps.keys():
679 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
685 def dump_scsi_hosts(cap):
687 l = os.listdir('/sys/class/scsi_host')
693 f = open('/sys/class/scsi_host/%s/proc_name' % h)
694 procname = f.readline().strip("\n")
700 f = open('/sys/class/scsi_host/%s/model_name' % h)
701 modelname = f.readline().strip("\n")
707 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
711 def module_info(cap):
712 output = StringIO.StringIO()
713 modules = open(PROC_MODULES, 'r')
717 module = line.split()[0]
718 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
723 return output.getvalue()
726 def multipathd_topology(cap):
727 pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
728 stdout=PIPE, stderr=dev_null)
729 stdout, stderr = pipe.communicate('show topology')
734 output = StringIO.StringIO()
735 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
739 if not procs[0].timed_out:
740 return output.getvalue().splitlines()
744 if not os.path.isfile(OPENVSWITCH_CONF_DB):
750 if os.path.getsize(OPENVSWITCH_CONF_DB) > max_size:
751 if os.path.isfile(OPENVSWITCH_COMPACT_DB):
752 os.unlink(OPENVSWITCH_COMPACT_DB)
754 output = StringIO.StringIO()
756 procs = [ProcOutput(['ovsdb-tool', 'compact',
757 OPENVSWITCH_CONF_DB, OPENVSWITCH_COMPACT_DB],
760 file_output(CAP_NETWORK_STATUS, [OPENVSWITCH_COMPACT_DB])
762 file_output(CAP_NETWORK_STATUS, [OPENVSWITCH_CONF_DB])
768 if os.path.isfile(OPENVSWITCH_COMPACT_DB):
769 os.unlink(OPENVSWITCH_COMPACT_DB)
776 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
778 fh = open('/proc/'+d+'/cmdline')
780 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
782 if not num_fds in fd_dict:
783 fd_dict[num_fds] = []
784 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
787 keys = fd_dict.keys()
788 keys.sort(lambda a, b: int(b) - int(a))
790 output += "%s: %s\n" % (k, str(fd_dict[k]))
793 def dump_rdac_groups(cap):
794 output = StringIO.StringIO()
795 procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
799 if not procs[0].timed_out:
801 for line in output.getvalue().splitlines():
802 if line.startswith('ID'):
804 elif line.startswith('----'):
807 group, _ = line.split(None, 1)
808 cmd_output(cap, [MPPUTIL, '-g', group])
810 def load_plugins(just_capabilities=False, filter=None):
811 def getText(nodelist):
813 for node in nodelist:
814 if node.nodeType == node.TEXT_NODE:
818 def getBoolAttr(el, attr, default=False):
820 val = el.getAttribute(attr).lower()
821 if val in ['true', 'false', 'yes', 'no']:
822 ret = val in ['true', 'yes']
825 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
826 if not caps.has_key(dir):
827 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
829 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
830 assert xmldoc.documentElement.tagName == "capability"
832 pii, min_size, max_size, min_time, max_time, mime = \
833 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
835 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
836 pii = xmldoc.documentElement.getAttribute("pii")
837 if xmldoc.documentElement.getAttribute("min_size") != '':
838 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
839 if xmldoc.documentElement.getAttribute("max_size") != '':
840 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
841 if xmldoc.documentElement.getAttribute("min_time") != '':
842 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
843 if xmldoc.documentElement.getAttribute("max_time") != '':
844 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
845 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
846 mime = xmldoc.documentElement.getAttribute("mime")
847 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
848 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
850 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
852 if just_capabilities:
855 plugdir = os.path.join(PLUGIN_DIR, dir)
856 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
857 xmldoc = parse(os.path.join(plugdir, file))
858 assert xmldoc.documentElement.tagName == "collect"
860 for el in xmldoc.documentElement.getElementsByTagName("*"):
861 filters_tmp = el.getAttribute("filters")
862 if filters_tmp == '':
865 filters = filters_tmp.split(',')
866 if not(filter is None or filter in filters):
868 if el.tagName == "files":
869 newest_first = getBoolAttr(el, 'newest_first')
870 if el.getAttribute("type") == "logs":
871 log_output(dir, getText(el.childNodes).split(),
872 newest_first=newest_first)
874 file_output(dir, getText(el.childNodes).split(),
875 newest_first=newest_first)
876 elif el.tagName == "directory":
877 pattern = el.getAttribute("pattern")
878 if pattern == '': pattern = None
879 negate = getBoolAttr(el, 'negate')
880 newest_first = getBoolAttr(el, 'newest_first')
881 tree_output(dir, getText(el.childNodes),
882 pattern and re.compile(pattern) or None,
883 negate=negate, newest_first=newest_first)
884 elif el.tagName == "command":
885 label = el.getAttribute("label")
886 if label == '': label = None
887 binary = getBoolAttr(el, 'binary')
888 cmd_output(dir, getText(el.childNodes), label, binary=binary)
890 def make_tar(subdir, suffix, output_fd, output_file):
891 global SILENT_MODE, data
894 if suffix == 'tar.bz2':
896 elif suffix == 'tar.gz':
900 if output_file is None:
901 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
903 filename = output_file
904 old_umask = os.umask(0077)
905 tf = tarfile.open(filename, mode)
908 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
911 for (k, v) in data.items():
913 tar_filename = os.path.join(subdir, construct_filename(k, v))
914 ti = tarfile.TarInfo(tar_filename)
919 if v.has_key('output'):
920 ti.mtime = v['output'].mtime
921 ti.size = len(v['output'].getvalue())
923 tf.addfile(ti, v['output'])
924 elif v.has_key('filename'):
925 s = os.stat(v['filename'])
926 ti.mtime = s.st_mtime
928 tf.addfile(ti, file(v['filename']))
935 output ('Writing tarball %s successful.' % filename)
940 def make_zip(subdir, output_file):
941 global SILENT_MODE, data
943 if output_file is None:
944 filename = "%s/%s.zip" % (BUG_DIR, subdir)
946 filename = output_file
947 old_umask = os.umask(0077)
948 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
952 for (k, v) in data.items():
954 dest = os.path.join(subdir, construct_filename(k, v))
956 if v.has_key('output'):
957 zf.writestr(dest, v['output'].getvalue())
959 if os.stat(v['filename']).st_size < 50:
960 compress_type = zipfile.ZIP_STORED
962 compress_type = zipfile.ZIP_DEFLATED
963 zf.write(v['filename'], dest, compress_type)
969 output ('Writing archive %s successful.' % filename)
974 def make_inventory(inventory, subdir):
975 document = getDOMImplementation().createDocument(
976 None, INVENTORY_XML_ROOT, None)
978 # create summary entry
979 s = document.createElement(INVENTORY_XML_SUMMARY)
980 user = os.getenv('SUDO_USER', os.getenv('USER'))
982 s.setAttribute('user', user)
983 s.setAttribute('date', time.strftime('%c'))
984 s.setAttribute('hostname', platform.node())
985 s.setAttribute('uname', ' '.join(platform.uname()))
986 s.setAttribute('uptime', commands.getoutput(UPTIME))
987 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
989 map(lambda (k, v): inventory_entry(document, subdir, k, v),
991 return document.toprettyxml()
993 def inventory_entry(document, subdir, k, v):
995 el = document.createElement(INVENTORY_XML_ELEMENT)
996 el.setAttribute('capability', v['cap'])
997 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
998 el.setAttribute('md5sum', md5sum(v))
999 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
1006 if d.has_key('filename'):
1007 f = open(d['filename'])
1009 while len(data) > 0:
1013 elif d.has_key('output'):
1014 m.update(d['output'].getvalue())
1015 return m.hexdigest()
1018 def construct_filename(k, v):
1019 if v.has_key('filename'):
1020 if v['filename'][0] == '/':
1021 return v['filename'][1:]
1023 return v['filename']
1024 s = k.replace(' ', '-')
1025 s = s.replace('--', '-')
1026 s = s.replace('/', '%')
1027 if s.find('.') == -1:
1032 def update_capabilities():
1035 def update_cap_size(cap, size):
1036 update_cap(cap, MIN_SIZE, size)
1037 update_cap(cap, MAX_SIZE, size)
1038 update_cap(cap, CHECKED, size > 0)
1041 def update_cap(cap, k, v):
1045 caps[cap] = tuple(l)
1048 def size_of_dir(d, pattern=None, negate=False):
1049 if os.path.isdir(d):
1050 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1056 def size_of_all(files, pattern=None, negate=False):
1057 return sum([size_of(f, pattern, negate) for f in files])
1060 def matches(f, pattern, negate):
1062 return not matches(f, pattern, False)
1064 return pattern is None or pattern.match(f)
1067 def size_of(f, pattern, negate):
1068 if os.path.isfile(f) and matches(f, pattern, negate):
1069 return os.stat(f)[6]
1071 return size_of_dir(f, pattern, negate)
1074 def print_capabilities():
1075 document = getDOMImplementation().createDocument(
1076 "ns", CAP_XML_ROOT, None)
1077 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1078 print document.toprettyxml()
1080 def capability(document, key):
1082 el = document.createElement(CAP_XML_ELEMENT)
1083 el.setAttribute('key', c[KEY])
1084 el.setAttribute('pii', c[PII])
1085 el.setAttribute('min-size', str(c[MIN_SIZE]))
1086 el.setAttribute('max-size', str(c[MAX_SIZE]))
1087 el.setAttribute('min-time', str(c[MIN_TIME]))
1088 el.setAttribute('max-time', str(c[MAX_TIME]))
1089 el.setAttribute('content-type', c[MIME])
1090 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1091 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1095 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1096 return '\n'.join([format % i for i in d.items()]) + '\n'
1100 yn = raw_input(prompt)
1102 return len(yn) == 0 or yn.lower()[0] == 'y'
1105 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1110 f = open('/proc/partitions')
1113 for line in f.readlines():
1114 (major, minor, blocks, name) = line.split()
1115 if int(major) < 254 and not partition_re.match(name):
1126 def __init__(self, command, max_time, inst=None, filter=None, binary=False):
1127 self.command = command
1128 self.max_time = max_time
1130 self.running = False
1132 self.timed_out = False
1134 self.timeout = int(time.time()) + self.max_time
1135 self.filter = filter
1136 self.filter_state = {}
1138 self.bufsize = 1048576 # 1MB buffer
1140 self.bufsize = 1 # line buffered
1146 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1149 self.timed_out = False
1151 if ProcOutput.debug:
1152 output_ts("Starting '%s'" % self.cmdAsStr())
1153 self.proc = Popen(self.command, bufsize=self.bufsize,
1154 stdin=dev_null, stdout=PIPE, stderr=dev_null,
1155 shell=isinstance(self.command, str))
1156 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1157 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1161 output_ts("'%s' failed" % self.cmdAsStr())
1162 self.running = False
1165 def terminate(self):
1168 self.proc.stdout.close()
1169 os.kill(self.proc.pid, SIGTERM)
1173 self.running = False
1174 self.status = SIGTERM
1176 def read_line(self):
1178 if self.bufsize == 1:
1179 line = self.proc.stdout.readline()
1181 line = self.proc.stdout.read(self.bufsize)
1184 self.proc.stdout.close()
1185 self.status = self.proc.wait()
1187 self.running = False
1190 line = self.filter(line, self.filter_state)
1192 self.inst.write(line)
1194 def run_procs(procs):
1202 active_procs.append(p)
1203 pipes.append(p.proc.stdout)
1205 elif p.status == None and not p.failed and not p.timed_out:
1208 active_procs.append(p)
1209 pipes.append(p.proc.stdout)
1216 (i, o, x) = select(pipes, [], [], 1.0)
1217 now = int(time.time())
1219 # handle process output
1220 for p in active_procs:
1221 if p.proc.stdout in i:
1225 if p.running and now > p.timeout:
1226 output_ts("'%s' timed out" % p.cmdAsStr())
1228 p.inst.write("\n** timeout **\n")
1236 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1238 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1246 def check_space(cap, name, size):
1247 global free_disk_space
1248 if free_disk_space is not None and size > free_disk_space:
1249 output("Omitting %s, out of disk space (requested: %u, allowed: %u)" %
1250 (name, size, free_disk_space))
1252 elif unlimited_data or caps[cap][MAX_SIZE] == -1 or \
1253 cap_sizes[cap] < caps[cap][MAX_SIZE]:
1254 cap_sizes[cap] += size
1255 if free_disk_space is not None:
1256 free_disk_space -= size
1259 output("Omitting %s, size constraint of %s exceeded" % (name, cap))
1263 def get_free_disk_space(path):
1264 path = os.path.abspath(path)
1265 while not os.path.exists(path):
1266 path = os.path.dirname(path)
1267 s = os.statvfs(path)
1268 return s.f_frsize * s.f_bfree
1271 class StringIOmtime(StringIO.StringIO):
1272 def __init__(self, buf=''):
1273 StringIO.StringIO.__init__(self, buf)
1274 self.mtime = time.time()
1277 StringIO.StringIO.write(self, s)
1278 self.mtime = time.time()
1281 if __name__ == "__main__":
1284 except KeyboardInterrupt:
1285 print "\nInterrupted."