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_VSWITCHD_PID = '@RUNDIR@/ovs-vswitchd.pid'
117 VAR_LOG_DIR = '/var/log/'
118 VAR_LOG_CORE_DIR = '/var/log/core'
119 YUM_LOG = '/var/log/yum.log'
120 YUM_REPOS_DIR = '/etc/yum.repos.d'
126 os.environ['PATH'] = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:@pkgdatadir@/scripts'
129 CHKCONFIG = 'chkconfig'
132 DMIDECODE = 'dmidecode'
134 DPKG_QUERY = 'dpkg-query'
138 IFCONFIG = 'ifconfig'
139 IPTABLES = 'iptables'
140 ISCSIADM = 'iscsiadm'
147 MULTIPATHD = 'multipathd'
149 OVS_DPCTL = 'ovs-dpctl'
150 OVS_OFCTL = 'ovs-ofctl'
151 OVS_VSCTL = 'ovs-vsctl'
152 OVS_APPCTL = 'ovs-appctl'
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_STATUS = 'network-status'
209 CAP_OPENVSWITCH_LOGS = 'ovs-system-logs'
210 CAP_PROCESS_LIST = 'process-list'
211 CAP_SYSTEM_LOGS = 'system-logs'
212 CAP_SYSTEM_SERVICES = 'system-services'
220 unlimited_data = False
223 def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
224 max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
225 caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
230 cap(CAP_BOOT_LOADER, PII_NO, max_size=3*KB,
232 cap(CAP_DISK_INFO, PII_MAYBE, max_size=50*KB,
234 cap(CAP_HARDWARE_INFO, PII_MAYBE, max_size=30*KB,
236 cap(CAP_KERNEL_INFO, PII_MAYBE, max_size=120*KB,
238 cap(CAP_LOSETUP_A, PII_MAYBE, max_size=KB, max_time=5)
239 cap(CAP_MULTIPATH, PII_MAYBE, max_size=20*KB,
241 cap(CAP_NETWORK_CONFIG, PII_IF_CUSTOMIZED,
242 min_size=0, max_size=40*KB)
243 cap(CAP_NETWORK_STATUS, PII_YES, max_size=50*MB,
245 cap(CAP_OPENVSWITCH_LOGS, PII_MAYBE, max_size=-1,
247 cap(CAP_PROCESS_LIST, PII_YES, max_size=30*KB,
249 cap(CAP_SYSTEM_LOGS, PII_MAYBE, max_size=200*MB,
251 cap(CAP_SYSTEM_SERVICES, PII_NO, max_size=5*KB,
253 cap(CAP_YUM, PII_IF_CUSTOMIZED, max_size=10*KB,
256 ANSWER_YES_TO_ALL = False
260 dev_null = open('/dev/null', 'r+')
268 output("[%s] %s" % (time.strftime("%x %X %Z"), x))
270 def cmd_output(cap, args, label=None, filter=None, binary=False):
273 if isinstance(args, list):
274 a = [aa for aa in args]
275 a[0] = os.path.basename(a[0])
279 data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter,
282 def file_output(cap, path_list, newest_first=False):
284 If newest_first is True, the list of files in path_list is sorted
285 by file modification time in descending order, else its sorted
290 for path in path_list:
295 path_entries.append((path, s))
297 mtime = lambda(path, stat): stat.st_mtime
298 path_entries.sort(key=mtime, reverse=newest_first)
299 for p in path_entries:
300 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
301 cap_sizes[cap] < caps[cap][MAX_SIZE]:
302 data[p] = {'cap': cap, 'filename': p[0]}
303 cap_sizes[cap] += p[1].st_size
305 output("Omitting %s, size constraint of %s exceeded" % (p[0], cap))
307 def tree_output(cap, path, pattern=None, negate=False, newest_first=False):
309 Walks the directory tree rooted at path. Files in current dir are processed
310 before files in sub-dirs.
313 if os.path.exists(path):
314 for root, dirs, files in os.walk(path):
315 fns = [fn for fn in [os.path.join(root, f) for f in files]
316 if os.path.isfile(fn) and matches(fn, pattern, negate)]
317 file_output(cap, fns, newest_first=newest_first)
319 def func_output(cap, label, func):
321 t = str(func).split()
322 data[label] = {'cap': cap, 'func': func}
327 for (k, v) in data.items():
329 if v.has_key('cmd_args'):
330 v['output'] = StringIOmtime()
331 if not process_lists.has_key(cap):
332 process_lists[cap] = []
333 process_lists[cap].append(
334 ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'],
335 v['filter'], v['binary']))
336 elif v.has_key('filename') and v['filename'].startswith('/proc/'):
337 # proc files must be read into memory
339 f = open(v['filename'], 'r')
342 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
343 cap_sizes[cap] < caps[cap][MAX_SIZE]:
344 v['output'] = StringIOmtime(s)
345 cap_sizes[cap] += len(s)
347 output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
350 elif v.has_key('func'):
355 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
356 cap_sizes[cap] < caps[cap][MAX_SIZE]:
357 v['output'] = StringIOmtime(s)
358 cap_sizes[cap] += len(s)
360 output("Omitting %s, size constraint of %s exceeded" % (k, cap))
362 run_procs(process_lists.values())
366 global ANSWER_YES_TO_ALL, SILENT_MODE
367 global entries, data, dbg, unlimited_data
370 only_ovs_info = False
371 collect_all_info = True
373 # we need access to privileged files, exit if we are not running as root
375 print >>sys.stderr, "Error: ovs-bugtool must be run as root"
379 output_type = 'tar.gz'
382 # Default value for the number of rotated logs.
389 (options, params) = getopt.gnu_getopt(
390 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
391 'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
392 'debug', 'ovs', 'log-days='])
393 except getopt.GetoptError, opterr:
394 print >>sys.stderr, opterr
402 entries = [e for e in caps.keys() if caps[e][CHECKED]]
404 for (k, v) in options:
405 if k == '--capabilities':
406 update_capabilities()
411 if v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
414 print >>sys.stderr, "Invalid output format '%s'" % v
417 # "-s" or "--silent" means suppress output (except for the final
418 # output filename at the end)
419 if k in ['-s', '--silent']:
422 if k == '--entries' and v != '':
423 entries = v.split(',')
425 # If the user runs the script with "-y" or "--yestoall" we don't ask
426 # all the really annoying questions.
427 if k in ['-y', '--yestoall']:
428 ANSWER_YES_TO_ALL = True
433 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
434 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
436 print >>sys.stderr, "Invalid output file descriptor", output_fd
443 entries = caps.keys()
444 elif k == '--unlimited':
445 unlimited_data = True
448 ProcOutput.debug = True
452 collect_all_info = False
454 if k == '--log-days':
455 log_days = int(v) + 1
458 print >>sys.stderr, "Invalid additional arguments", str(params)
461 if output_fd != -1 and output_type != 'tar':
462 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
465 if output_fd != -1 and output_file is not None:
466 print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
469 if ANSWER_YES_TO_ALL:
470 output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
473 This application will collate dmesg output, details of the
474 hardware configuration of your machine, information about the build of
475 openvswitch that you are using, plus, if you allow it, various logs.
477 The collated information will be saved as a .%s for archiving or
478 sending to a Technical Support Representative.
480 The logs may contain private information, and if you are at all
481 worried about that, you should exit now, or you should explicitly
482 exclude those logs from the archive.
486 # assemble potential data
488 file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
489 cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
490 cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
492 cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
493 file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
494 file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
495 cmd_output(CAP_DISK_INFO, [DF, '-alT'])
496 cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
497 if len(pidof('iscsid')) != 0:
498 cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
499 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
500 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
501 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
502 cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
503 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
505 file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
506 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
507 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
508 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
509 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
510 file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
511 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
514 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
515 PROC_FILESYSTEMS, PROC_CMDLINE])
516 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
517 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
518 file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
519 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
520 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
522 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
524 file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
525 cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
526 func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
527 cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
528 if CAP_MULTIPATH in entries and collect_all_info:
529 dump_rdac_groups(CAP_MULTIPATH)
531 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
532 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
533 file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
534 file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
535 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_CONF_DB])
537 cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
538 cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
539 cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
540 cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
541 for dir in DHCP_LEASE_DIR:
542 tree_output(CAP_NETWORK_STATUS, dir)
543 cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
544 for p in os.listdir('/sys/class/net/'):
546 f = open('/sys/class/net/%s/type' % p, 'r')
549 if os.path.islink('/sys/class/net/%s/device' % p) and int(t) == 1:
551 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
552 if not p.startswith('vif') and not p.startswith('tap'):
553 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
554 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
555 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
556 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
558 cmd_output(CAP_NETWORK_STATUS,
559 [TC, '-s', '-d', 'class', 'show', 'dev', p])
562 tree_output(CAP_NETWORK_STATUS, PROC_NET_BONDING_DIR)
563 tree_output(CAP_NETWORK_STATUS, PROC_NET_VLAN_DIR)
564 cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
565 file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
566 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
567 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
569 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
570 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
571 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
573 vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
574 vspid = int(vspidfile.readline().strip())
576 for b in bond_list(vspid):
577 cmd_output(CAP_NETWORK_STATUS,
578 [OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
579 'ovs-appctl-bond-show-%s.out' % b)
583 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
584 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
586 system_logs = (CAP_SYSTEM_LOGS, [ VAR_LOG_DIR + x for x in
587 ['crit.log', 'kern.log', 'daemon.log', 'user.log',
588 'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot']])
589 ovs_logs = (CAP_OPENVSWITCH_LOGS, [ OPENVSWITCH_LOG_DIR + x for x in
590 ['ovs-vswitchd.log', 'ovsdb-server.log',
591 'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log']])
592 for cap, logs in [system_logs, ovs_logs]:
593 file_output(cap, logs)
595 ['%s.%d' % (f, n) for n in range(log_days) for f in logs])
597 ['%s.%d.gz' % (f, n) for n in range(log_days) for f in 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],
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 output = StringIO.StringIO()
748 procs = [ProcOutput([OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
752 if not procs[0].timed_out:
753 bonds = output.getvalue().splitlines()[1:]
754 return [x.split('\t')[1] for x in bonds]
760 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
762 fh = open('/proc/'+d+'/cmdline')
764 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
766 if not num_fds in fd_dict:
767 fd_dict[num_fds] = []
768 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
771 keys = fd_dict.keys()
772 keys.sort(lambda a, b: int(b) - int(a))
774 output += "%s: %s\n" % (k, str(fd_dict[k]))
777 def dump_rdac_groups(cap):
778 output = StringIO.StringIO()
779 procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
783 if not procs[0].timed_out:
785 for line in output.getvalue().splitlines():
786 if line.startswith('ID'):
788 elif line.startswith('----'):
791 group, _ = line.split(None, 1)
792 cmd_output(cap, [MPPUTIL, '-g', group])
794 def load_plugins(just_capabilities=False, filter=None):
795 def getText(nodelist):
797 for node in nodelist:
798 if node.nodeType == node.TEXT_NODE:
802 def getBoolAttr(el, attr, default=False):
804 val = el.getAttribute(attr).lower()
805 if val in ['true', 'false', 'yes', 'no']:
806 ret = val in ['true', 'yes']
809 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
810 if not caps.has_key(dir):
811 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
813 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
814 assert xmldoc.documentElement.tagName == "capability"
816 pii, min_size, max_size, min_time, max_time, mime = \
817 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
819 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
820 pii = xmldoc.documentElement.getAttribute("pii")
821 if xmldoc.documentElement.getAttribute("min_size") != '':
822 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
823 if xmldoc.documentElement.getAttribute("max_size") != '':
824 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
825 if xmldoc.documentElement.getAttribute("min_time") != '':
826 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
827 if xmldoc.documentElement.getAttribute("max_time") != '':
828 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
829 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
830 mime = xmldoc.documentElement.getAttribute("mime")
831 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
832 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
834 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
836 if just_capabilities:
839 plugdir = os.path.join(PLUGIN_DIR, dir)
840 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
841 xmldoc = parse(os.path.join(plugdir, file))
842 assert xmldoc.documentElement.tagName == "collect"
844 for el in xmldoc.documentElement.getElementsByTagName("*"):
845 filters_tmp = el.getAttribute("filters")
846 if filters_tmp == '':
849 filters = filters_tmp.split(',')
850 if not(filter is None or filter in filters):
852 if el.tagName == "files":
853 newest_first = getBoolAttr(el, 'newest_first')
854 file_output(dir, getText(el.childNodes).split(),
855 newest_first=newest_first)
856 elif el.tagName == "directory":
857 pattern = el.getAttribute("pattern")
858 if pattern == '': pattern = None
859 negate = getBoolAttr(el, 'negate')
860 newest_first = getBoolAttr(el, 'newest_first')
861 tree_output(dir, getText(el.childNodes),
862 pattern and re.compile(pattern) or None,
863 negate=negate, newest_first=newest_first)
864 elif el.tagName == "command":
865 label = el.getAttribute("label")
866 if label == '': label = None
867 binary = getBoolAttr(el, 'binary')
868 cmd_output(dir, getText(el.childNodes), label, binary=binary)
870 def make_tar(subdir, suffix, output_fd, output_file):
871 global SILENT_MODE, data
874 if suffix == 'tar.bz2':
876 elif suffix == 'tar.gz':
880 if output_file is None:
881 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
883 filename = output_file
884 old_umask = os.umask(0077)
885 tf = tarfile.open(filename, mode)
888 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
891 for (k, v) in data.items():
893 tar_filename = os.path.join(subdir, construct_filename(k, v))
894 ti = tarfile.TarInfo(tar_filename)
899 if v.has_key('output'):
900 ti.mtime = v['output'].mtime
901 ti.size = len(v['output'].getvalue())
903 tf.addfile(ti, v['output'])
904 elif v.has_key('filename'):
905 s = os.stat(v['filename'])
906 ti.mtime = s.st_mtime
908 tf.addfile(ti, file(v['filename']))
915 output ('Writing tarball %s successful.' % filename)
920 def make_zip(subdir, output_file):
921 global SILENT_MODE, data
923 if output_file is None:
924 filename = "%s/%s.zip" % (BUG_DIR, subdir)
926 filename = output_file
927 old_umask = os.umask(0077)
928 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
932 for (k, v) in data.items():
934 dest = os.path.join(subdir, construct_filename(k, v))
936 if v.has_key('output'):
937 zf.writestr(dest, v['output'].getvalue())
939 if os.stat(v['filename']).st_size < 50:
940 compress_type = zipfile.ZIP_STORED
942 compress_type = zipfile.ZIP_DEFLATED
943 zf.write(v['filename'], dest, compress_type)
949 output ('Writing archive %s successful.' % filename)
954 def make_inventory(inventory, subdir):
955 document = getDOMImplementation().createDocument(
956 None, INVENTORY_XML_ROOT, None)
958 # create summary entry
959 s = document.createElement(INVENTORY_XML_SUMMARY)
960 user = os.getenv('SUDO_USER', os.getenv('USER'))
962 s.setAttribute('user', user)
963 s.setAttribute('date', time.strftime('%c'))
964 s.setAttribute('hostname', platform.node())
965 s.setAttribute('uname', ' '.join(platform.uname()))
966 s.setAttribute('uptime', commands.getoutput(UPTIME))
967 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
969 map(lambda (k, v): inventory_entry(document, subdir, k, v),
971 return document.toprettyxml()
973 def inventory_entry(document, subdir, k, v):
975 el = document.createElement(INVENTORY_XML_ELEMENT)
976 el.setAttribute('capability', v['cap'])
977 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
978 el.setAttribute('md5sum', md5sum(v))
979 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
986 if d.has_key('filename'):
987 f = open(d['filename'])
993 elif d.has_key('output'):
994 m.update(d['output'].getvalue())
998 def construct_filename(k, v):
999 if v.has_key('filename'):
1000 if v['filename'][0] == '/':
1001 return v['filename'][1:]
1003 return v['filename']
1004 s = k.replace(' ', '-')
1005 s = s.replace('--', '-')
1006 s = s.replace('/', '%')
1007 if s.find('.') == -1:
1012 def update_capabilities():
1015 def update_cap_size(cap, size):
1016 update_cap(cap, MIN_SIZE, size)
1017 update_cap(cap, MAX_SIZE, size)
1018 update_cap(cap, CHECKED, size > 0)
1021 def update_cap(cap, k, v):
1025 caps[cap] = tuple(l)
1028 def size_of_dir(d, pattern=None, negate=False):
1029 if os.path.isdir(d):
1030 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1036 def size_of_all(files, pattern=None, negate=False):
1037 return sum([size_of(f, pattern, negate) for f in files])
1040 def matches(f, pattern, negate):
1042 return not matches(f, pattern, False)
1044 return pattern is None or pattern.match(f)
1047 def size_of(f, pattern, negate):
1048 if os.path.isfile(f) and matches(f, pattern, negate):
1049 return os.stat(f)[6]
1051 return size_of_dir(f, pattern, negate)
1054 def print_capabilities():
1055 document = getDOMImplementation().createDocument(
1056 "ns", CAP_XML_ROOT, None)
1057 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1058 print document.toprettyxml()
1060 def capability(document, key):
1062 el = document.createElement(CAP_XML_ELEMENT)
1063 el.setAttribute('key', c[KEY])
1064 el.setAttribute('pii', c[PII])
1065 el.setAttribute('min-size', str(c[MIN_SIZE]))
1066 el.setAttribute('max-size', str(c[MAX_SIZE]))
1067 el.setAttribute('min-time', str(c[MIN_TIME]))
1068 el.setAttribute('max-time', str(c[MAX_TIME]))
1069 el.setAttribute('content-type', c[MIME])
1070 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1071 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1075 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1076 return '\n'.join([format % i for i in d.items()]) + '\n'
1080 yn = raw_input(prompt)
1082 return len(yn) == 0 or yn.lower()[0] == 'y'
1085 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1090 f = open('/proc/partitions')
1093 for line in f.readlines():
1094 (major, minor, blocks, name) = line.split()
1095 if int(major) < 254 and not partition_re.match(name):
1106 def __init__(self, command, max_time, inst=None, filter=None, binary=False):
1107 self.command = command
1108 self.max_time = max_time
1110 self.running = False
1112 self.timed_out = False
1114 self.timeout = int(time.time()) + self.max_time
1115 self.filter = filter
1116 self.filter_state = {}
1118 self.bufsize = 1048576 # 1MB buffer
1120 self.bufsize = 1 # line buffered
1126 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1129 self.timed_out = False
1131 if ProcOutput.debug:
1132 output_ts("Starting '%s'" % self.cmdAsStr())
1133 self.proc = Popen(self.command, bufsize=self.bufsize,
1134 stdin=dev_null, stdout=PIPE, stderr=dev_null,
1135 shell=isinstance(self.command, str))
1136 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1137 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1141 output_ts("'%s' failed" % self.cmdAsStr())
1142 self.running = False
1145 def terminate(self):
1148 self.proc.stdout.close()
1149 os.kill(self.proc.pid, SIGTERM)
1153 self.running = False
1154 self.status = SIGTERM
1156 def read_line(self):
1158 if self.bufsize == 1:
1159 line = self.proc.stdout.readline()
1161 line = self.proc.stdout.read(self.bufsize)
1164 self.proc.stdout.close()
1165 self.status = self.proc.wait()
1167 self.running = False
1170 line = self.filter(line, self.filter_state)
1172 self.inst.write(line)
1174 def run_procs(procs):
1182 active_procs.append(p)
1183 pipes.append(p.proc.stdout)
1185 elif p.status == None and not p.failed and not p.timed_out:
1188 active_procs.append(p)
1189 pipes.append(p.proc.stdout)
1196 (i, o, x) = select(pipes, [], [], 1.0)
1197 now = int(time.time())
1199 # handle process output
1200 for p in active_procs:
1201 if p.proc.stdout in i:
1205 if p.running and now > p.timeout:
1206 output_ts("'%s' timed out" % p.cmdAsStr())
1208 p.inst.write("\n** timeout **\n")
1216 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1218 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1226 class StringIOmtime(StringIO.StringIO):
1227 def __init__(self, buf=''):
1228 StringIO.StringIO.__init__(self, buf)
1229 self.mtime = time.time()
1232 StringIO.StringIO.write(self, s)
1233 self.mtime = time.time()
1236 if __name__ == "__main__":
1239 except KeyboardInterrupt:
1240 print "\nInterrupted."