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.
226 def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
227 max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
228 caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
233 cap(CAP_BOOT_LOADER, PII_NO, max_size=3*KB,
235 cap(CAP_DISK_INFO, PII_MAYBE, max_size=50*KB,
237 cap(CAP_HARDWARE_INFO, PII_MAYBE, max_size=2*MB,
239 cap(CAP_KERNEL_INFO, PII_MAYBE, max_size=120*KB,
241 cap(CAP_LOSETUP_A, PII_MAYBE, max_size=KB, max_time=5)
242 cap(CAP_MULTIPATH, PII_MAYBE, max_size=20*KB,
244 cap(CAP_NETWORK_CONFIG, PII_IF_CUSTOMIZED,
245 min_size=0, max_size=5*MB)
246 cap(CAP_NETWORK_INFO, PII_YES, max_size=50*MB,
248 cap(CAP_NETWORK_STATUS, PII_YES, max_size=-1,
250 cap(CAP_OPENVSWITCH_LOGS, PII_MAYBE, max_size=-1,
252 cap(CAP_PROCESS_LIST, PII_YES, max_size=30*KB,
254 cap(CAP_SYSTEM_LOGS, PII_MAYBE, max_size=200*MB,
256 cap(CAP_SYSTEM_SERVICES, PII_NO, max_size=5*KB,
258 cap(CAP_YUM, PII_IF_CUSTOMIZED, max_size=10*KB,
261 ANSWER_YES_TO_ALL = False
265 dev_null = open('/dev/null', 'r+')
273 output("[%s] %s" % (time.strftime("%x %X %Z"), x))
275 def cmd_output(cap, args, label=None, filter=None, binary=False):
278 if isinstance(args, list):
279 a = [aa for aa in args]
280 a[0] = os.path.basename(a[0])
284 data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter,
287 def file_output(cap, path_list, newest_first=False):
289 If newest_first is True, the list of files in path_list is sorted
290 by file modification time in descending order, else its sorted
295 for path in path_list:
300 path_entries.append((path, s))
302 mtime = lambda(path, stat): stat.st_mtime
303 path_entries.sort(key=mtime, reverse=newest_first)
304 for p in path_entries:
305 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
306 cap_sizes[cap] < caps[cap][MAX_SIZE]:
307 data[p] = {'cap': cap, 'filename': p[0]}
308 cap_sizes[cap] += p[1].st_size
310 output("Omitting %s, size constraint of %s exceeded" % (p[0], cap))
312 def tree_output(cap, path, pattern=None, negate=False, newest_first=False):
314 Walks the directory tree rooted at path. Files in current dir are processed
315 before files in sub-dirs.
318 if os.path.exists(path):
319 for root, dirs, files in os.walk(path):
320 fns = [fn for fn in [os.path.join(root, f) for f in files]
321 if os.path.isfile(fn) and matches(fn, pattern, negate)]
322 file_output(cap, fns, newest_first=newest_first)
324 def func_output(cap, label, func):
326 t = str(func).split()
327 data[label] = {'cap': cap, 'func': func}
329 def log_output(cap, logs, newest_first=False):
331 file_output(cap, logs)
333 ['%s.%d' % (f, n) for n in range(1, log_days+1) for f in logs], \
334 newest_first=newest_first)
336 ['%s.%d.gz' % (f, n) for n in range(1, log_days+1) for f in logs], \
337 newest_first=newest_first)
342 for (k, v) in data.items():
344 if v.has_key('cmd_args'):
345 v['output'] = StringIOmtime()
346 if not process_lists.has_key(cap):
347 process_lists[cap] = []
348 process_lists[cap].append(
349 ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'],
350 v['filter'], v['binary']))
351 elif v.has_key('filename') and v['filename'].startswith('/proc/'):
352 # proc files must be read into memory
354 f = open(v['filename'], 'r')
357 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
358 cap_sizes[cap] < caps[cap][MAX_SIZE]:
359 v['output'] = StringIOmtime(s)
360 cap_sizes[cap] += len(s)
362 output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
365 elif v.has_key('func'):
370 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
371 cap_sizes[cap] < caps[cap][MAX_SIZE]:
372 v['output'] = StringIOmtime(s)
373 cap_sizes[cap] += len(s)
375 output("Omitting %s, size constraint of %s exceeded" % (k, cap))
377 run_procs(process_lists.values())
381 global ANSWER_YES_TO_ALL, SILENT_MODE
382 global entries, data, dbg, unlimited_data, log_days
385 only_ovs_info = False
386 collect_all_info = True
388 # we need access to privileged files, exit if we are not running as root
390 print >>sys.stderr, "Error: ovs-bugtool must be run as root"
394 output_type = 'tar.gz'
401 (options, params) = getopt.gnu_getopt(
402 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
403 'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
404 'debug', 'ovs', 'log-days='])
405 except getopt.GetoptError, opterr:
406 print >>sys.stderr, opterr
414 entries = [e for e in caps.keys() if caps[e][CHECKED]]
416 for (k, v) in options:
417 if k == '--capabilities':
418 update_capabilities()
423 if v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
426 print >>sys.stderr, "Invalid output format '%s'" % v
429 # "-s" or "--silent" means suppress output (except for the final
430 # output filename at the end)
431 if k in ['-s', '--silent']:
434 if k == '--entries' and v != '':
435 entries = v.split(',')
437 # If the user runs the script with "-y" or "--yestoall" we don't ask
438 # all the really annoying questions.
439 if k in ['-y', '--yestoall']:
440 ANSWER_YES_TO_ALL = True
445 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
446 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
448 print >>sys.stderr, "Invalid output file descriptor", output_fd
455 entries = caps.keys()
456 elif k == '--unlimited':
457 unlimited_data = True
460 ProcOutput.debug = True
464 collect_all_info = False
466 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 ANSWER_YES_TO_ALL:
482 output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
485 This application will collate dmesg output, details of the
486 hardware configuration of your machine, information about the build of
487 openvswitch that you are using, plus, if you allow it, various logs.
489 The collated information will be saved as a .%s for archiving or
490 sending to a Technical Support Representative.
492 The logs may contain private information, and if you are at all
493 worried about that, you should exit now, or you should explicitly
494 exclude those logs from the archive.
498 # assemble potential data
500 file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
501 cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
502 cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
504 cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
505 file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
506 file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
507 cmd_output(CAP_DISK_INFO, [DF, '-alT'])
508 cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
509 if len(pidof('iscsid')) != 0:
510 cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
511 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
512 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
513 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
514 cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
515 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
517 file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
518 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
519 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
520 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
521 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
522 file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
523 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
526 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
527 PROC_FILESYSTEMS, PROC_CMDLINE])
528 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
529 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
530 file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
531 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
532 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
534 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
536 file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
537 cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
538 func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
539 cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
540 if CAP_MULTIPATH in entries and collect_all_info:
541 dump_rdac_groups(CAP_MULTIPATH)
543 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
544 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
545 file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
546 file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
547 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_DEFAULT_SWITCH,
548 OPENVSWITCH_SYSCONFIG_SWITCH, OPENVSWITCH_DEFAULT_CONTROLLER])
550 cmd_output(CAP_NETWORK_INFO, [IFCONFIG, '-a'])
551 cmd_output(CAP_NETWORK_INFO, [ROUTE, '-n'])
552 cmd_output(CAP_NETWORK_INFO, [ARP, '-n'])
553 cmd_output(CAP_NETWORK_INFO, [NETSTAT, '-an'])
554 for dir in DHCP_LEASE_DIR:
555 tree_output(CAP_NETWORK_INFO, dir)
556 for table in ['filter', 'nat', 'mangle', 'raw', 'security']:
557 cmd_output(CAP_NETWORK_INFO, [IPTABLES, '-t', table, '-nL'])
558 for p in os.listdir('/sys/class/net/'):
560 f = open('/sys/class/net/%s/type' % p, 'r')
563 if os.path.islink('/sys/class/net/%s/device' % p) and int(t) == 1:
565 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-S', p])
566 if not p.startswith('vif') and not p.startswith('tap'):
567 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, p])
568 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-k', p])
569 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-i', p])
570 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-c', p])
572 cmd_output(CAP_NETWORK_INFO,
573 [TC, '-s', '-d', 'class', 'show', 'dev', p])
576 tree_output(CAP_NETWORK_INFO, PROC_NET_BONDING_DIR)
577 tree_output(CAP_NETWORK_INFO, PROC_NET_VLAN_DIR)
578 cmd_output(CAP_NETWORK_INFO, [TC, '-s', 'qdisc'])
579 file_output(CAP_NETWORK_INFO, [PROC_NET_SOFTNET_STAT])
582 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
583 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
585 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
587 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
588 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
590 system_logs = ([ VAR_LOG_DIR + x for x in
591 ['crit.log', 'kern.log', 'daemon.log', 'user.log',
592 'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot']])
593 ovs_logs = ([ OPENVSWITCH_LOG_DIR + x for x in
594 ['ovs-vswitchd.log', 'ovsdb-server.log',
595 'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log', 'ovs-ctl.log']])
596 log_output(CAP_SYSTEM_LOGS, system_logs)
597 log_output(CAP_OPENVSWITCH_LOGS, ovs_logs)
599 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
600 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
602 cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
604 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
606 file_output(CAP_YUM, [YUM_LOG])
607 tree_output(CAP_YUM, YUM_REPOS_DIR)
608 cmd_output(CAP_YUM, [RPM, '-qa'])
609 file_output(CAP_YUM, [APT_SOURCES_LIST])
610 tree_output(CAP_YUM, APT_SOURCES_LIST_D)
611 cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
613 # Filter out ovs relevant information if --ovs option passed
614 # else collect all information
618 ovs_info_caps = [CAP_NETWORK_STATUS, CAP_SYSTEM_LOGS,
620 ovs_info_list = ['process-tree']
621 # We cannot use iteritems, since we modify 'data' as we pass through
622 for (k, v) in data.items():
628 if info not in ovs_info_list and cap not in ovs_info_caps:
632 filter = ",".join(filters)
637 load_plugins(filter=filter)
641 # permit the user to filter out data
642 # We cannot use iteritems, since we modify 'data' as we pass through
643 for (k, v) in sorted(data.items()):
649 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % key):
652 # collect selected data now
653 output_ts('Running commands to collect data')
656 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
659 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
663 if output_file is None:
666 dirname = os.path.dirname(output_file)
667 if dirname and not os.path.exists(dirname):
674 output_ts('Creating output file')
676 if output_type.startswith('tar'):
677 make_tar(subdir, output_type, output_fd, output_file)
679 make_zip(subdir, output_file)
682 print >>sys.stderr, "Category sizes (max, actual):\n"
683 for c in caps.keys():
684 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
690 def dump_scsi_hosts(cap):
692 l = os.listdir('/sys/class/scsi_host')
698 f = open('/sys/class/scsi_host/%s/proc_name' % h)
699 procname = f.readline().strip("\n")
705 f = open('/sys/class/scsi_host/%s/model_name' % h)
706 modelname = f.readline().strip("\n")
712 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
716 def module_info(cap):
717 output = StringIO.StringIO()
718 modules = open(PROC_MODULES, 'r')
722 module = line.split()[0]
723 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
728 return output.getvalue()
731 def multipathd_topology(cap):
732 pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
733 stdout=PIPE, stderr=dev_null)
734 stdout, stderr = pipe.communicate('show topology')
739 output = StringIO.StringIO()
740 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
744 if not procs[0].timed_out:
745 return output.getvalue().splitlines()
749 if not os.path.isfile(OPENVSWITCH_CONF_DB):
755 if os.path.getsize(OPENVSWITCH_CONF_DB) > max_size:
756 if os.path.isfile(OPENVSWITCH_COMPACT_DB):
757 os.unlink(OPENVSWITCH_COMPACT_DB)
759 output = StringIO.StringIO()
761 procs = [ProcOutput(['ovsdb-tool', 'compact',
762 OPENVSWITCH_CONF_DB, OPENVSWITCH_COMPACT_DB],
765 file_output(CAP_NETWORK_STATUS, [OPENVSWITCH_COMPACT_DB])
767 file_output(CAP_NETWORK_STATUS, [OPENVSWITCH_CONF_DB])
773 if os.path.isfile(OPENVSWITCH_COMPACT_DB):
774 os.unlink(OPENVSWITCH_COMPACT_DB)
781 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
783 fh = open('/proc/'+d+'/cmdline')
785 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
787 if not num_fds in fd_dict:
788 fd_dict[num_fds] = []
789 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
792 keys = fd_dict.keys()
793 keys.sort(lambda a, b: int(b) - int(a))
795 output += "%s: %s\n" % (k, str(fd_dict[k]))
798 def dump_rdac_groups(cap):
799 output = StringIO.StringIO()
800 procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
804 if not procs[0].timed_out:
806 for line in output.getvalue().splitlines():
807 if line.startswith('ID'):
809 elif line.startswith('----'):
812 group, _ = line.split(None, 1)
813 cmd_output(cap, [MPPUTIL, '-g', group])
815 def load_plugins(just_capabilities=False, filter=None):
816 def getText(nodelist):
818 for node in nodelist:
819 if node.nodeType == node.TEXT_NODE:
823 def getBoolAttr(el, attr, default=False):
825 val = el.getAttribute(attr).lower()
826 if val in ['true', 'false', 'yes', 'no']:
827 ret = val in ['true', 'yes']
830 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
831 if not caps.has_key(dir):
832 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
834 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
835 assert xmldoc.documentElement.tagName == "capability"
837 pii, min_size, max_size, min_time, max_time, mime = \
838 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
840 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
841 pii = xmldoc.documentElement.getAttribute("pii")
842 if xmldoc.documentElement.getAttribute("min_size") != '':
843 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
844 if xmldoc.documentElement.getAttribute("max_size") != '':
845 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
846 if xmldoc.documentElement.getAttribute("min_time") != '':
847 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
848 if xmldoc.documentElement.getAttribute("max_time") != '':
849 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
850 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
851 mime = xmldoc.documentElement.getAttribute("mime")
852 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
853 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
855 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
857 if just_capabilities:
860 plugdir = os.path.join(PLUGIN_DIR, dir)
861 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
862 xmldoc = parse(os.path.join(plugdir, file))
863 assert xmldoc.documentElement.tagName == "collect"
865 for el in xmldoc.documentElement.getElementsByTagName("*"):
866 filters_tmp = el.getAttribute("filters")
867 if filters_tmp == '':
870 filters = filters_tmp.split(',')
871 if not(filter is None or filter in filters):
873 if el.tagName == "files":
874 newest_first = getBoolAttr(el, 'newest_first')
875 if el.getAttribute("type") == "logs":
876 log_output(dir, getText(el.childNodes).split(),
877 newest_first=newest_first)
879 file_output(dir, getText(el.childNodes).split(),
880 newest_first=newest_first)
881 elif el.tagName == "directory":
882 pattern = el.getAttribute("pattern")
883 if pattern == '': pattern = None
884 negate = getBoolAttr(el, 'negate')
885 newest_first = getBoolAttr(el, 'newest_first')
886 tree_output(dir, getText(el.childNodes),
887 pattern and re.compile(pattern) or None,
888 negate=negate, newest_first=newest_first)
889 elif el.tagName == "command":
890 label = el.getAttribute("label")
891 if label == '': label = None
892 binary = getBoolAttr(el, 'binary')
893 cmd_output(dir, getText(el.childNodes), label, binary=binary)
895 def make_tar(subdir, suffix, output_fd, output_file):
896 global SILENT_MODE, data
899 if suffix == 'tar.bz2':
901 elif suffix == 'tar.gz':
905 if output_file is None:
906 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
908 filename = output_file
909 old_umask = os.umask(0077)
910 tf = tarfile.open(filename, mode)
913 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
916 for (k, v) in data.items():
918 tar_filename = os.path.join(subdir, construct_filename(k, v))
919 ti = tarfile.TarInfo(tar_filename)
924 if v.has_key('output'):
925 ti.mtime = v['output'].mtime
926 ti.size = len(v['output'].getvalue())
928 tf.addfile(ti, v['output'])
929 elif v.has_key('filename'):
930 s = os.stat(v['filename'])
931 ti.mtime = s.st_mtime
933 tf.addfile(ti, file(v['filename']))
940 output ('Writing tarball %s successful.' % filename)
945 def make_zip(subdir, output_file):
946 global SILENT_MODE, data
948 if output_file is None:
949 filename = "%s/%s.zip" % (BUG_DIR, subdir)
951 filename = output_file
952 old_umask = os.umask(0077)
953 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
957 for (k, v) in data.items():
959 dest = os.path.join(subdir, construct_filename(k, v))
961 if v.has_key('output'):
962 zf.writestr(dest, v['output'].getvalue())
964 if os.stat(v['filename']).st_size < 50:
965 compress_type = zipfile.ZIP_STORED
967 compress_type = zipfile.ZIP_DEFLATED
968 zf.write(v['filename'], dest, compress_type)
974 output ('Writing archive %s successful.' % filename)
979 def make_inventory(inventory, subdir):
980 document = getDOMImplementation().createDocument(
981 None, INVENTORY_XML_ROOT, None)
983 # create summary entry
984 s = document.createElement(INVENTORY_XML_SUMMARY)
985 user = os.getenv('SUDO_USER', os.getenv('USER'))
987 s.setAttribute('user', user)
988 s.setAttribute('date', time.strftime('%c'))
989 s.setAttribute('hostname', platform.node())
990 s.setAttribute('uname', ' '.join(platform.uname()))
991 s.setAttribute('uptime', commands.getoutput(UPTIME))
992 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
994 map(lambda (k, v): inventory_entry(document, subdir, k, v),
996 return document.toprettyxml()
998 def inventory_entry(document, subdir, k, v):
1000 el = document.createElement(INVENTORY_XML_ELEMENT)
1001 el.setAttribute('capability', v['cap'])
1002 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
1003 el.setAttribute('md5sum', md5sum(v))
1004 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
1011 if d.has_key('filename'):
1012 f = open(d['filename'])
1014 while len(data) > 0:
1018 elif d.has_key('output'):
1019 m.update(d['output'].getvalue())
1020 return m.hexdigest()
1023 def construct_filename(k, v):
1024 if v.has_key('filename'):
1025 if v['filename'][0] == '/':
1026 return v['filename'][1:]
1028 return v['filename']
1029 s = k.replace(' ', '-')
1030 s = s.replace('--', '-')
1031 s = s.replace('/', '%')
1032 if s.find('.') == -1:
1037 def update_capabilities():
1040 def update_cap_size(cap, size):
1041 update_cap(cap, MIN_SIZE, size)
1042 update_cap(cap, MAX_SIZE, size)
1043 update_cap(cap, CHECKED, size > 0)
1046 def update_cap(cap, k, v):
1050 caps[cap] = tuple(l)
1053 def size_of_dir(d, pattern=None, negate=False):
1054 if os.path.isdir(d):
1055 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1061 def size_of_all(files, pattern=None, negate=False):
1062 return sum([size_of(f, pattern, negate) for f in files])
1065 def matches(f, pattern, negate):
1067 return not matches(f, pattern, False)
1069 return pattern is None or pattern.match(f)
1072 def size_of(f, pattern, negate):
1073 if os.path.isfile(f) and matches(f, pattern, negate):
1074 return os.stat(f)[6]
1076 return size_of_dir(f, pattern, negate)
1079 def print_capabilities():
1080 document = getDOMImplementation().createDocument(
1081 "ns", CAP_XML_ROOT, None)
1082 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1083 print document.toprettyxml()
1085 def capability(document, key):
1087 el = document.createElement(CAP_XML_ELEMENT)
1088 el.setAttribute('key', c[KEY])
1089 el.setAttribute('pii', c[PII])
1090 el.setAttribute('min-size', str(c[MIN_SIZE]))
1091 el.setAttribute('max-size', str(c[MAX_SIZE]))
1092 el.setAttribute('min-time', str(c[MIN_TIME]))
1093 el.setAttribute('max-time', str(c[MAX_TIME]))
1094 el.setAttribute('content-type', c[MIME])
1095 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1096 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1100 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1101 return '\n'.join([format % i for i in d.items()]) + '\n'
1105 yn = raw_input(prompt)
1107 return len(yn) == 0 or yn.lower()[0] == 'y'
1110 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1115 f = open('/proc/partitions')
1118 for line in f.readlines():
1119 (major, minor, blocks, name) = line.split()
1120 if int(major) < 254 and not partition_re.match(name):
1131 def __init__(self, command, max_time, inst=None, filter=None, binary=False):
1132 self.command = command
1133 self.max_time = max_time
1135 self.running = False
1137 self.timed_out = False
1139 self.timeout = int(time.time()) + self.max_time
1140 self.filter = filter
1141 self.filter_state = {}
1143 self.bufsize = 1048576 # 1MB buffer
1145 self.bufsize = 1 # line buffered
1151 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1154 self.timed_out = False
1156 if ProcOutput.debug:
1157 output_ts("Starting '%s'" % self.cmdAsStr())
1158 self.proc = Popen(self.command, bufsize=self.bufsize,
1159 stdin=dev_null, stdout=PIPE, stderr=dev_null,
1160 shell=isinstance(self.command, str))
1161 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1162 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1166 output_ts("'%s' failed" % self.cmdAsStr())
1167 self.running = False
1170 def terminate(self):
1173 self.proc.stdout.close()
1174 os.kill(self.proc.pid, SIGTERM)
1178 self.running = False
1179 self.status = SIGTERM
1181 def read_line(self):
1183 if self.bufsize == 1:
1184 line = self.proc.stdout.readline()
1186 line = self.proc.stdout.read(self.bufsize)
1189 self.proc.stdout.close()
1190 self.status = self.proc.wait()
1192 self.running = False
1195 line = self.filter(line, self.filter_state)
1197 self.inst.write(line)
1199 def run_procs(procs):
1207 active_procs.append(p)
1208 pipes.append(p.proc.stdout)
1210 elif p.status == None and not p.failed and not p.timed_out:
1213 active_procs.append(p)
1214 pipes.append(p.proc.stdout)
1221 (i, o, x) = select(pipes, [], [], 1.0)
1222 now = int(time.time())
1224 # handle process output
1225 for p in active_procs:
1226 if p.proc.stdout in i:
1230 if p.running and now > p.timeout:
1231 output_ts("'%s' timed out" % p.cmdAsStr())
1233 p.inst.write("\n** timeout **\n")
1241 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1243 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1251 class StringIOmtime(StringIO.StringIO):
1252 def __init__(self, buf=''):
1253 StringIO.StringIO.__init__(self, buf)
1254 self.mtime = time.time()
1257 StringIO.StringIO.write(self, s)
1258 self.mtime = time.time()
1261 if __name__ == "__main__":
1264 except KeyboardInterrupt:
1265 print "\nInterrupted."