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=40*KB)
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])
548 cmd_output(CAP_NETWORK_INFO, [IFCONFIG, '-a'])
549 cmd_output(CAP_NETWORK_INFO, [ROUTE, '-n'])
550 cmd_output(CAP_NETWORK_INFO, [ARP, '-n'])
551 cmd_output(CAP_NETWORK_INFO, [NETSTAT, '-an'])
552 for dir in DHCP_LEASE_DIR:
553 tree_output(CAP_NETWORK_INFO, dir)
554 for table in ['filter', 'nat', 'mangle', 'raw', 'security']:
555 cmd_output(CAP_NETWORK_INFO, [IPTABLES, '-t', table, '-nL'])
556 for p in os.listdir('/sys/class/net/'):
558 f = open('/sys/class/net/%s/type' % p, 'r')
561 if os.path.islink('/sys/class/net/%s/device' % p) and int(t) == 1:
563 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-S', p])
564 if not p.startswith('vif') and not p.startswith('tap'):
565 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, p])
566 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-k', p])
567 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-i', p])
568 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-c', p])
570 cmd_output(CAP_NETWORK_INFO,
571 [TC, '-s', '-d', 'class', 'show', 'dev', p])
574 tree_output(CAP_NETWORK_INFO, PROC_NET_BONDING_DIR)
575 tree_output(CAP_NETWORK_INFO, PROC_NET_VLAN_DIR)
576 cmd_output(CAP_NETWORK_INFO, [TC, '-s', 'qdisc'])
577 file_output(CAP_NETWORK_INFO, [PROC_NET_SOFTNET_STAT])
580 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
581 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
583 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
585 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
586 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
588 system_logs = ([ VAR_LOG_DIR + x for x in
589 ['crit.log', 'kern.log', 'daemon.log', 'user.log',
590 'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot']])
591 ovs_logs = ([ OPENVSWITCH_LOG_DIR + x for x in
592 ['ovs-vswitchd.log', 'ovsdb-server.log',
593 'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log', 'ovs-ctl.log']])
594 log_output(CAP_SYSTEM_LOGS, system_logs)
595 log_output(CAP_OPENVSWITCH_LOGS, ovs_logs)
597 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
598 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
600 cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
602 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
604 file_output(CAP_YUM, [YUM_LOG])
605 tree_output(CAP_YUM, YUM_REPOS_DIR)
606 cmd_output(CAP_YUM, [RPM, '-qa'])
607 file_output(CAP_YUM, [APT_SOURCES_LIST])
608 tree_output(CAP_YUM, APT_SOURCES_LIST_D)
609 cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
611 # Filter out ovs relevant information if --ovs option passed
612 # else collect all information
616 ovs_info_caps = [CAP_NETWORK_STATUS, CAP_SYSTEM_LOGS,
618 ovs_info_list = ['process-tree']
619 # We cannot use iteritems, since we modify 'data' as we pass through
620 for (k, v) in data.items():
626 if info not in ovs_info_list and cap not in ovs_info_caps:
630 filter = ",".join(filters)
635 load_plugins(filter=filter)
639 # permit the user to filter out data
640 # We cannot use iteritems, since we modify 'data' as we pass through
641 for (k, v) in sorted(data.items()):
647 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % key):
650 # collect selected data now
651 output_ts('Running commands to collect data')
654 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
657 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
661 if output_file is None:
664 dirname = os.path.dirname(output_file)
665 if dirname and not os.path.exists(dirname):
672 output_ts('Creating output file')
674 if output_type.startswith('tar'):
675 make_tar(subdir, output_type, output_fd, output_file)
677 make_zip(subdir, output_file)
680 print >>sys.stderr, "Category sizes (max, actual):\n"
681 for c in caps.keys():
682 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
688 def dump_scsi_hosts(cap):
690 l = os.listdir('/sys/class/scsi_host')
696 f = open('/sys/class/scsi_host/%s/proc_name' % h)
697 procname = f.readline().strip("\n")
703 f = open('/sys/class/scsi_host/%s/model_name' % h)
704 modelname = f.readline().strip("\n")
710 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
714 def module_info(cap):
715 output = StringIO.StringIO()
716 modules = open(PROC_MODULES, 'r')
720 module = line.split()[0]
721 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
726 return output.getvalue()
729 def multipathd_topology(cap):
730 pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
731 stdout=PIPE, stderr=dev_null)
732 stdout, stderr = pipe.communicate('show topology')
737 output = StringIO.StringIO()
738 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
742 if not procs[0].timed_out:
743 return output.getvalue().splitlines()
747 if not os.path.isfile(OPENVSWITCH_CONF_DB):
753 if os.path.getsize(OPENVSWITCH_CONF_DB) > max_size:
754 if os.path.isfile(OPENVSWITCH_COMPACT_DB):
755 os.unlink(OPENVSWITCH_COMPACT_DB)
757 output = StringIO.StringIO()
759 procs = [ProcOutput(['ovsdb-tool', 'compact',
760 OPENVSWITCH_CONF_DB, OPENVSWITCH_COMPACT_DB],
763 file_output(CAP_NETWORK_STATUS, [OPENVSWITCH_COMPACT_DB])
765 file_output(CAP_NETWORK_STATUS, [OPENVSWITCH_CONF_DB])
771 if os.path.isfile(OPENVSWITCH_COMPACT_DB):
772 os.unlink(OPENVSWITCH_COMPACT_DB)
779 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
781 fh = open('/proc/'+d+'/cmdline')
783 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
785 if not num_fds in fd_dict:
786 fd_dict[num_fds] = []
787 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
790 keys = fd_dict.keys()
791 keys.sort(lambda a, b: int(b) - int(a))
793 output += "%s: %s\n" % (k, str(fd_dict[k]))
796 def dump_rdac_groups(cap):
797 output = StringIO.StringIO()
798 procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
802 if not procs[0].timed_out:
804 for line in output.getvalue().splitlines():
805 if line.startswith('ID'):
807 elif line.startswith('----'):
810 group, _ = line.split(None, 1)
811 cmd_output(cap, [MPPUTIL, '-g', group])
813 def load_plugins(just_capabilities=False, filter=None):
814 def getText(nodelist):
816 for node in nodelist:
817 if node.nodeType == node.TEXT_NODE:
821 def getBoolAttr(el, attr, default=False):
823 val = el.getAttribute(attr).lower()
824 if val in ['true', 'false', 'yes', 'no']:
825 ret = val in ['true', 'yes']
828 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
829 if not caps.has_key(dir):
830 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
832 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
833 assert xmldoc.documentElement.tagName == "capability"
835 pii, min_size, max_size, min_time, max_time, mime = \
836 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
838 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
839 pii = xmldoc.documentElement.getAttribute("pii")
840 if xmldoc.documentElement.getAttribute("min_size") != '':
841 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
842 if xmldoc.documentElement.getAttribute("max_size") != '':
843 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
844 if xmldoc.documentElement.getAttribute("min_time") != '':
845 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
846 if xmldoc.documentElement.getAttribute("max_time") != '':
847 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
848 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
849 mime = xmldoc.documentElement.getAttribute("mime")
850 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
851 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
853 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
855 if just_capabilities:
858 plugdir = os.path.join(PLUGIN_DIR, dir)
859 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
860 xmldoc = parse(os.path.join(plugdir, file))
861 assert xmldoc.documentElement.tagName == "collect"
863 for el in xmldoc.documentElement.getElementsByTagName("*"):
864 filters_tmp = el.getAttribute("filters")
865 if filters_tmp == '':
868 filters = filters_tmp.split(',')
869 if not(filter is None or filter in filters):
871 if el.tagName == "files":
872 newest_first = getBoolAttr(el, 'newest_first')
873 if el.getAttribute("type") == "logs":
874 log_output(dir, getText(el.childNodes).split(),
875 newest_first=newest_first)
877 file_output(dir, getText(el.childNodes).split(),
878 newest_first=newest_first)
879 elif el.tagName == "directory":
880 pattern = el.getAttribute("pattern")
881 if pattern == '': pattern = None
882 negate = getBoolAttr(el, 'negate')
883 newest_first = getBoolAttr(el, 'newest_first')
884 tree_output(dir, getText(el.childNodes),
885 pattern and re.compile(pattern) or None,
886 negate=negate, newest_first=newest_first)
887 elif el.tagName == "command":
888 label = el.getAttribute("label")
889 if label == '': label = None
890 binary = getBoolAttr(el, 'binary')
891 cmd_output(dir, getText(el.childNodes), label, binary=binary)
893 def make_tar(subdir, suffix, output_fd, output_file):
894 global SILENT_MODE, data
897 if suffix == 'tar.bz2':
899 elif suffix == 'tar.gz':
903 if output_file is None:
904 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
906 filename = output_file
907 old_umask = os.umask(0077)
908 tf = tarfile.open(filename, mode)
911 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
914 for (k, v) in data.items():
916 tar_filename = os.path.join(subdir, construct_filename(k, v))
917 ti = tarfile.TarInfo(tar_filename)
922 if v.has_key('output'):
923 ti.mtime = v['output'].mtime
924 ti.size = len(v['output'].getvalue())
926 tf.addfile(ti, v['output'])
927 elif v.has_key('filename'):
928 s = os.stat(v['filename'])
929 ti.mtime = s.st_mtime
931 tf.addfile(ti, file(v['filename']))
938 output ('Writing tarball %s successful.' % filename)
943 def make_zip(subdir, output_file):
944 global SILENT_MODE, data
946 if output_file is None:
947 filename = "%s/%s.zip" % (BUG_DIR, subdir)
949 filename = output_file
950 old_umask = os.umask(0077)
951 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
955 for (k, v) in data.items():
957 dest = os.path.join(subdir, construct_filename(k, v))
959 if v.has_key('output'):
960 zf.writestr(dest, v['output'].getvalue())
962 if os.stat(v['filename']).st_size < 50:
963 compress_type = zipfile.ZIP_STORED
965 compress_type = zipfile.ZIP_DEFLATED
966 zf.write(v['filename'], dest, compress_type)
972 output ('Writing archive %s successful.' % filename)
977 def make_inventory(inventory, subdir):
978 document = getDOMImplementation().createDocument(
979 None, INVENTORY_XML_ROOT, None)
981 # create summary entry
982 s = document.createElement(INVENTORY_XML_SUMMARY)
983 user = os.getenv('SUDO_USER', os.getenv('USER'))
985 s.setAttribute('user', user)
986 s.setAttribute('date', time.strftime('%c'))
987 s.setAttribute('hostname', platform.node())
988 s.setAttribute('uname', ' '.join(platform.uname()))
989 s.setAttribute('uptime', commands.getoutput(UPTIME))
990 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
992 map(lambda (k, v): inventory_entry(document, subdir, k, v),
994 return document.toprettyxml()
996 def inventory_entry(document, subdir, k, v):
998 el = document.createElement(INVENTORY_XML_ELEMENT)
999 el.setAttribute('capability', v['cap'])
1000 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
1001 el.setAttribute('md5sum', md5sum(v))
1002 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
1009 if d.has_key('filename'):
1010 f = open(d['filename'])
1012 while len(data) > 0:
1016 elif d.has_key('output'):
1017 m.update(d['output'].getvalue())
1018 return m.hexdigest()
1021 def construct_filename(k, v):
1022 if v.has_key('filename'):
1023 if v['filename'][0] == '/':
1024 return v['filename'][1:]
1026 return v['filename']
1027 s = k.replace(' ', '-')
1028 s = s.replace('--', '-')
1029 s = s.replace('/', '%')
1030 if s.find('.') == -1:
1035 def update_capabilities():
1038 def update_cap_size(cap, size):
1039 update_cap(cap, MIN_SIZE, size)
1040 update_cap(cap, MAX_SIZE, size)
1041 update_cap(cap, CHECKED, size > 0)
1044 def update_cap(cap, k, v):
1048 caps[cap] = tuple(l)
1051 def size_of_dir(d, pattern=None, negate=False):
1052 if os.path.isdir(d):
1053 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1059 def size_of_all(files, pattern=None, negate=False):
1060 return sum([size_of(f, pattern, negate) for f in files])
1063 def matches(f, pattern, negate):
1065 return not matches(f, pattern, False)
1067 return pattern is None or pattern.match(f)
1070 def size_of(f, pattern, negate):
1071 if os.path.isfile(f) and matches(f, pattern, negate):
1072 return os.stat(f)[6]
1074 return size_of_dir(f, pattern, negate)
1077 def print_capabilities():
1078 document = getDOMImplementation().createDocument(
1079 "ns", CAP_XML_ROOT, None)
1080 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1081 print document.toprettyxml()
1083 def capability(document, key):
1085 el = document.createElement(CAP_XML_ELEMENT)
1086 el.setAttribute('key', c[KEY])
1087 el.setAttribute('pii', c[PII])
1088 el.setAttribute('min-size', str(c[MIN_SIZE]))
1089 el.setAttribute('max-size', str(c[MAX_SIZE]))
1090 el.setAttribute('min-time', str(c[MIN_TIME]))
1091 el.setAttribute('max-time', str(c[MAX_TIME]))
1092 el.setAttribute('content-type', c[MIME])
1093 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1094 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1098 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1099 return '\n'.join([format % i for i in d.items()]) + '\n'
1103 yn = raw_input(prompt)
1105 return len(yn) == 0 or yn.lower()[0] == 'y'
1108 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1113 f = open('/proc/partitions')
1116 for line in f.readlines():
1117 (major, minor, blocks, name) = line.split()
1118 if int(major) < 254 and not partition_re.match(name):
1129 def __init__(self, command, max_time, inst=None, filter=None, binary=False):
1130 self.command = command
1131 self.max_time = max_time
1133 self.running = False
1135 self.timed_out = False
1137 self.timeout = int(time.time()) + self.max_time
1138 self.filter = filter
1139 self.filter_state = {}
1141 self.bufsize = 1048576 # 1MB buffer
1143 self.bufsize = 1 # line buffered
1149 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1152 self.timed_out = False
1154 if ProcOutput.debug:
1155 output_ts("Starting '%s'" % self.cmdAsStr())
1156 self.proc = Popen(self.command, bufsize=self.bufsize,
1157 stdin=dev_null, stdout=PIPE, stderr=dev_null,
1158 shell=isinstance(self.command, str))
1159 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1160 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1164 output_ts("'%s' failed" % self.cmdAsStr())
1165 self.running = False
1168 def terminate(self):
1171 self.proc.stdout.close()
1172 os.kill(self.proc.pid, SIGTERM)
1176 self.running = False
1177 self.status = SIGTERM
1179 def read_line(self):
1181 if self.bufsize == 1:
1182 line = self.proc.stdout.readline()
1184 line = self.proc.stdout.read(self.bufsize)
1187 self.proc.stdout.close()
1188 self.status = self.proc.wait()
1190 self.running = False
1193 line = self.filter(line, self.filter_state)
1195 self.inst.write(line)
1197 def run_procs(procs):
1205 active_procs.append(p)
1206 pipes.append(p.proc.stdout)
1208 elif p.status == None and not p.failed and not p.timed_out:
1211 active_procs.append(p)
1212 pipes.append(p.proc.stdout)
1219 (i, o, x) = select(pipes, [], [], 1.0)
1220 now = int(time.time())
1222 # handle process output
1223 for p in active_procs:
1224 if p.proc.stdout in i:
1228 if p.running and now > p.timeout:
1229 output_ts("'%s' timed out" % p.cmdAsStr())
1231 p.inst.write("\n** timeout **\n")
1239 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1241 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1249 class StringIOmtime(StringIO.StringIO):
1250 def __init__(self, buf=''):
1251 StringIO.StringIO.__init__(self, buf)
1252 self.mtime = time.time()
1255 StringIO.StringIO.write(self, s)
1256 self.mtime = time.time()
1259 if __name__ == "__main__":
1262 except KeyboardInterrupt:
1263 print "\nInterrupted."