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
222 # Default value for the number of rotated logs.
225 def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
226 max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
227 caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
232 cap(CAP_BOOT_LOADER, PII_NO, max_size=3*KB,
234 cap(CAP_DISK_INFO, PII_MAYBE, max_size=50*KB,
236 cap(CAP_HARDWARE_INFO, PII_MAYBE, max_size=30*KB,
238 cap(CAP_KERNEL_INFO, PII_MAYBE, max_size=120*KB,
240 cap(CAP_LOSETUP_A, PII_MAYBE, max_size=KB, max_time=5)
241 cap(CAP_MULTIPATH, PII_MAYBE, max_size=20*KB,
243 cap(CAP_NETWORK_CONFIG, PII_IF_CUSTOMIZED,
244 min_size=0, max_size=40*KB)
245 cap(CAP_NETWORK_STATUS, PII_YES, max_size=50*MB,
247 cap(CAP_OPENVSWITCH_LOGS, PII_MAYBE, max_size=-1,
249 cap(CAP_PROCESS_LIST, PII_YES, max_size=30*KB,
251 cap(CAP_SYSTEM_LOGS, PII_MAYBE, max_size=200*MB,
253 cap(CAP_SYSTEM_SERVICES, PII_NO, max_size=5*KB,
255 cap(CAP_YUM, PII_IF_CUSTOMIZED, max_size=10*KB,
258 ANSWER_YES_TO_ALL = False
262 dev_null = open('/dev/null', 'r+')
270 output("[%s] %s" % (time.strftime("%x %X %Z"), x))
272 def cmd_output(cap, args, label=None, filter=None, binary=False):
275 if isinstance(args, list):
276 a = [aa for aa in args]
277 a[0] = os.path.basename(a[0])
281 data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter,
284 def file_output(cap, path_list, newest_first=False):
286 If newest_first is True, the list of files in path_list is sorted
287 by file modification time in descending order, else its sorted
292 for path in path_list:
297 path_entries.append((path, s))
299 mtime = lambda(path, stat): stat.st_mtime
300 path_entries.sort(key=mtime, reverse=newest_first)
301 for p in path_entries:
302 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
303 cap_sizes[cap] < caps[cap][MAX_SIZE]:
304 data[p] = {'cap': cap, 'filename': p[0]}
305 cap_sizes[cap] += p[1].st_size
307 output("Omitting %s, size constraint of %s exceeded" % (p[0], cap))
309 def tree_output(cap, path, pattern=None, negate=False, newest_first=False):
311 Walks the directory tree rooted at path. Files in current dir are processed
312 before files in sub-dirs.
315 if os.path.exists(path):
316 for root, dirs, files in os.walk(path):
317 fns = [fn for fn in [os.path.join(root, f) for f in files]
318 if os.path.isfile(fn) and matches(fn, pattern, negate)]
319 file_output(cap, fns, newest_first=newest_first)
321 def func_output(cap, label, func):
323 t = str(func).split()
324 data[label] = {'cap': cap, 'func': func}
326 def log_output(cap, logs, newest_first=False):
328 file_output(cap, logs)
330 ['%s.%d' % (f, n) for n in range(1, log_days+1) for f in logs], \
331 newest_first=newest_first)
333 ['%s.%d.gz' % (f, n) for n in range(1, log_days+1) for f in logs], \
334 newest_first=newest_first)
339 for (k, v) in data.items():
341 if v.has_key('cmd_args'):
342 v['output'] = StringIOmtime()
343 if not process_lists.has_key(cap):
344 process_lists[cap] = []
345 process_lists[cap].append(
346 ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'],
347 v['filter'], v['binary']))
348 elif v.has_key('filename') and v['filename'].startswith('/proc/'):
349 # proc files must be read into memory
351 f = open(v['filename'], 'r')
354 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
355 cap_sizes[cap] < caps[cap][MAX_SIZE]:
356 v['output'] = StringIOmtime(s)
357 cap_sizes[cap] += len(s)
359 output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
362 elif v.has_key('func'):
367 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
368 cap_sizes[cap] < caps[cap][MAX_SIZE]:
369 v['output'] = StringIOmtime(s)
370 cap_sizes[cap] += len(s)
372 output("Omitting %s, size constraint of %s exceeded" % (k, cap))
374 run_procs(process_lists.values())
378 global ANSWER_YES_TO_ALL, SILENT_MODE
379 global entries, data, dbg, unlimited_data, log_days
382 only_ovs_info = False
383 collect_all_info = True
385 # we need access to privileged files, exit if we are not running as root
387 print >>sys.stderr, "Error: ovs-bugtool must be run as root"
391 output_type = 'tar.gz'
398 (options, params) = getopt.gnu_getopt(
399 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
400 'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
401 'debug', 'ovs', 'log-days='])
402 except getopt.GetoptError, opterr:
403 print >>sys.stderr, opterr
411 entries = [e for e in caps.keys() if caps[e][CHECKED]]
413 for (k, v) in options:
414 if k == '--capabilities':
415 update_capabilities()
420 if v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
423 print >>sys.stderr, "Invalid output format '%s'" % v
426 # "-s" or "--silent" means suppress output (except for the final
427 # output filename at the end)
428 if k in ['-s', '--silent']:
431 if k == '--entries' and v != '':
432 entries = v.split(',')
434 # If the user runs the script with "-y" or "--yestoall" we don't ask
435 # all the really annoying questions.
436 if k in ['-y', '--yestoall']:
437 ANSWER_YES_TO_ALL = True
442 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
443 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
445 print >>sys.stderr, "Invalid output file descriptor", output_fd
452 entries = caps.keys()
453 elif k == '--unlimited':
454 unlimited_data = True
457 ProcOutput.debug = True
461 collect_all_info = False
463 if k == '--log-days':
467 print >>sys.stderr, "Invalid additional arguments", str(params)
470 if output_fd != -1 and output_type != 'tar':
471 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
474 if output_fd != -1 and output_file is not None:
475 print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
478 if ANSWER_YES_TO_ALL:
479 output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
482 This application will collate dmesg output, details of the
483 hardware configuration of your machine, information about the build of
484 openvswitch that you are using, plus, if you allow it, various logs.
486 The collated information will be saved as a .%s for archiving or
487 sending to a Technical Support Representative.
489 The logs may contain private information, and if you are at all
490 worried about that, you should exit now, or you should explicitly
491 exclude those logs from the archive.
495 # assemble potential data
497 file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
498 cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
499 cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
501 cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
502 file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
503 file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
504 cmd_output(CAP_DISK_INFO, [DF, '-alT'])
505 cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
506 if len(pidof('iscsid')) != 0:
507 cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
508 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
509 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
510 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
511 cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
512 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
514 file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
515 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
516 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
517 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
518 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
519 file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
520 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
523 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
524 PROC_FILESYSTEMS, PROC_CMDLINE])
525 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
526 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
527 file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
528 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
529 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
531 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
533 file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
534 cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
535 func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
536 cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
537 if CAP_MULTIPATH in entries and collect_all_info:
538 dump_rdac_groups(CAP_MULTIPATH)
540 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
541 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
542 file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
543 file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
544 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_CONF_DB])
546 cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
547 cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
548 cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
549 cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
550 for dir in DHCP_LEASE_DIR:
551 tree_output(CAP_NETWORK_STATUS, dir)
552 cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
553 for p in os.listdir('/sys/class/net/'):
555 f = open('/sys/class/net/%s/type' % p, 'r')
558 if os.path.islink('/sys/class/net/%s/device' % p) and int(t) == 1:
560 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
561 if not p.startswith('vif') and not p.startswith('tap'):
562 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
563 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
564 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
565 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
567 cmd_output(CAP_NETWORK_STATUS,
568 [TC, '-s', '-d', 'class', 'show', 'dev', p])
571 tree_output(CAP_NETWORK_STATUS, PROC_NET_BONDING_DIR)
572 tree_output(CAP_NETWORK_STATUS, PROC_NET_VLAN_DIR)
573 cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
574 file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
575 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
576 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
578 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
579 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
580 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
582 vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
583 vspid = int(vspidfile.readline().strip())
585 for b in bond_list(vspid):
586 cmd_output(CAP_NETWORK_STATUS,
587 [OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
588 'ovs-appctl-bond-show-%s.out' % b)
592 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
593 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
595 system_logs = ([ VAR_LOG_DIR + x for x in
596 ['crit.log', 'kern.log', 'daemon.log', 'user.log',
597 'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot']])
598 ovs_logs = ([ OPENVSWITCH_LOG_DIR + x for x in
599 ['ovs-vswitchd.log', 'ovsdb-server.log',
600 'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log']])
601 log_output(CAP_SYSTEM_LOGS, system_logs)
602 log_output(CAP_OPENVSWITCH_LOGS, ovs_logs)
604 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
605 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
607 cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
609 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
611 file_output(CAP_YUM, [YUM_LOG])
612 tree_output(CAP_YUM, YUM_REPOS_DIR)
613 cmd_output(CAP_YUM, [RPM, '-qa'])
614 file_output(CAP_YUM, [APT_SOURCES_LIST])
615 tree_output(CAP_YUM, APT_SOURCES_LIST_D)
616 cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
618 # Filter out ovs relevant information if --ovs option passed
619 # else collect all information
623 ovs_info_caps = [CAP_NETWORK_STATUS, CAP_SYSTEM_LOGS,
625 ovs_info_list = ['process-tree']
626 # We cannot use iteritems, since we modify 'data' as we pass through
627 for (k, v) in data.items():
633 if info not in ovs_info_list and cap not in ovs_info_caps:
637 filter = ",".join(filters)
642 load_plugins(filter=filter)
646 # permit the user to filter out data
647 # We cannot use iteritems, since we modify 'data' as we pass through
648 for (k, v) in sorted(data.items()):
654 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % key):
657 # collect selected data now
658 output_ts('Running commands to collect data')
661 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
664 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
668 if output_file is None:
671 dirname = os.path.dirname(output_file)
672 if dirname and not os.path.exists(dirname):
679 output_ts('Creating output file')
681 if output_type.startswith('tar'):
682 make_tar(subdir, output_type, output_fd, output_file)
684 make_zip(subdir, output_file)
687 print >>sys.stderr, "Category sizes (max, actual):\n"
688 for c in caps.keys():
689 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
693 def dump_scsi_hosts(cap):
695 l = os.listdir('/sys/class/scsi_host')
701 f = open('/sys/class/scsi_host/%s/proc_name' % h)
702 procname = f.readline().strip("\n")
708 f = open('/sys/class/scsi_host/%s/model_name' % h)
709 modelname = f.readline().strip("\n")
715 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
719 def module_info(cap):
720 output = StringIO.StringIO()
721 modules = open(PROC_MODULES, 'r')
725 module = line.split()[0]
726 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
731 return output.getvalue()
734 def multipathd_topology(cap):
735 pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
736 stdout=PIPE, stderr=dev_null)
737 stdout, stderr = pipe.communicate('show topology')
742 output = StringIO.StringIO()
743 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
747 if not procs[0].timed_out:
748 return output.getvalue().splitlines()
752 output = StringIO.StringIO()
753 procs = [ProcOutput([OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
757 if not procs[0].timed_out:
758 bonds = output.getvalue().splitlines()[1:]
759 return [x.split('\t')[1] for x in bonds]
765 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
767 fh = open('/proc/'+d+'/cmdline')
769 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
771 if not num_fds in fd_dict:
772 fd_dict[num_fds] = []
773 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
776 keys = fd_dict.keys()
777 keys.sort(lambda a, b: int(b) - int(a))
779 output += "%s: %s\n" % (k, str(fd_dict[k]))
782 def dump_rdac_groups(cap):
783 output = StringIO.StringIO()
784 procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
788 if not procs[0].timed_out:
790 for line in output.getvalue().splitlines():
791 if line.startswith('ID'):
793 elif line.startswith('----'):
796 group, _ = line.split(None, 1)
797 cmd_output(cap, [MPPUTIL, '-g', group])
799 def load_plugins(just_capabilities=False, filter=None):
800 def getText(nodelist):
802 for node in nodelist:
803 if node.nodeType == node.TEXT_NODE:
807 def getBoolAttr(el, attr, default=False):
809 val = el.getAttribute(attr).lower()
810 if val in ['true', 'false', 'yes', 'no']:
811 ret = val in ['true', 'yes']
814 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
815 if not caps.has_key(dir):
816 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
818 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
819 assert xmldoc.documentElement.tagName == "capability"
821 pii, min_size, max_size, min_time, max_time, mime = \
822 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
824 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
825 pii = xmldoc.documentElement.getAttribute("pii")
826 if xmldoc.documentElement.getAttribute("min_size") != '':
827 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
828 if xmldoc.documentElement.getAttribute("max_size") != '':
829 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
830 if xmldoc.documentElement.getAttribute("min_time") != '':
831 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
832 if xmldoc.documentElement.getAttribute("max_time") != '':
833 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
834 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
835 mime = xmldoc.documentElement.getAttribute("mime")
836 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
837 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
839 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
841 if just_capabilities:
844 plugdir = os.path.join(PLUGIN_DIR, dir)
845 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
846 xmldoc = parse(os.path.join(plugdir, file))
847 assert xmldoc.documentElement.tagName == "collect"
849 for el in xmldoc.documentElement.getElementsByTagName("*"):
850 filters_tmp = el.getAttribute("filters")
851 if filters_tmp == '':
854 filters = filters_tmp.split(',')
855 if not(filter is None or filter in filters):
857 if el.tagName == "files":
858 newest_first = getBoolAttr(el, 'newest_first')
859 if el.getAttribute("type") == "logs":
860 log_output(dir, getText(el.childNodes).split(),
861 newest_first=newest_first)
863 file_output(dir, getText(el.childNodes).split(),
864 newest_first=newest_first)
865 elif el.tagName == "directory":
866 pattern = el.getAttribute("pattern")
867 if pattern == '': pattern = None
868 negate = getBoolAttr(el, 'negate')
869 newest_first = getBoolAttr(el, 'newest_first')
870 tree_output(dir, getText(el.childNodes),
871 pattern and re.compile(pattern) or None,
872 negate=negate, newest_first=newest_first)
873 elif el.tagName == "command":
874 label = el.getAttribute("label")
875 if label == '': label = None
876 binary = getBoolAttr(el, 'binary')
877 cmd_output(dir, getText(el.childNodes), label, binary=binary)
879 def make_tar(subdir, suffix, output_fd, output_file):
880 global SILENT_MODE, data
883 if suffix == 'tar.bz2':
885 elif suffix == 'tar.gz':
889 if output_file is None:
890 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
892 filename = output_file
893 old_umask = os.umask(0077)
894 tf = tarfile.open(filename, mode)
897 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
900 for (k, v) in data.items():
902 tar_filename = os.path.join(subdir, construct_filename(k, v))
903 ti = tarfile.TarInfo(tar_filename)
908 if v.has_key('output'):
909 ti.mtime = v['output'].mtime
910 ti.size = len(v['output'].getvalue())
912 tf.addfile(ti, v['output'])
913 elif v.has_key('filename'):
914 s = os.stat(v['filename'])
915 ti.mtime = s.st_mtime
917 tf.addfile(ti, file(v['filename']))
924 output ('Writing tarball %s successful.' % filename)
929 def make_zip(subdir, output_file):
930 global SILENT_MODE, data
932 if output_file is None:
933 filename = "%s/%s.zip" % (BUG_DIR, subdir)
935 filename = output_file
936 old_umask = os.umask(0077)
937 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
941 for (k, v) in data.items():
943 dest = os.path.join(subdir, construct_filename(k, v))
945 if v.has_key('output'):
946 zf.writestr(dest, v['output'].getvalue())
948 if os.stat(v['filename']).st_size < 50:
949 compress_type = zipfile.ZIP_STORED
951 compress_type = zipfile.ZIP_DEFLATED
952 zf.write(v['filename'], dest, compress_type)
958 output ('Writing archive %s successful.' % filename)
963 def make_inventory(inventory, subdir):
964 document = getDOMImplementation().createDocument(
965 None, INVENTORY_XML_ROOT, None)
967 # create summary entry
968 s = document.createElement(INVENTORY_XML_SUMMARY)
969 user = os.getenv('SUDO_USER', os.getenv('USER'))
971 s.setAttribute('user', user)
972 s.setAttribute('date', time.strftime('%c'))
973 s.setAttribute('hostname', platform.node())
974 s.setAttribute('uname', ' '.join(platform.uname()))
975 s.setAttribute('uptime', commands.getoutput(UPTIME))
976 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
978 map(lambda (k, v): inventory_entry(document, subdir, k, v),
980 return document.toprettyxml()
982 def inventory_entry(document, subdir, k, v):
984 el = document.createElement(INVENTORY_XML_ELEMENT)
985 el.setAttribute('capability', v['cap'])
986 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
987 el.setAttribute('md5sum', md5sum(v))
988 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
995 if d.has_key('filename'):
996 f = open(d['filename'])
1002 elif d.has_key('output'):
1003 m.update(d['output'].getvalue())
1004 return m.hexdigest()
1007 def construct_filename(k, v):
1008 if v.has_key('filename'):
1009 if v['filename'][0] == '/':
1010 return v['filename'][1:]
1012 return v['filename']
1013 s = k.replace(' ', '-')
1014 s = s.replace('--', '-')
1015 s = s.replace('/', '%')
1016 if s.find('.') == -1:
1021 def update_capabilities():
1024 def update_cap_size(cap, size):
1025 update_cap(cap, MIN_SIZE, size)
1026 update_cap(cap, MAX_SIZE, size)
1027 update_cap(cap, CHECKED, size > 0)
1030 def update_cap(cap, k, v):
1034 caps[cap] = tuple(l)
1037 def size_of_dir(d, pattern=None, negate=False):
1038 if os.path.isdir(d):
1039 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1045 def size_of_all(files, pattern=None, negate=False):
1046 return sum([size_of(f, pattern, negate) for f in files])
1049 def matches(f, pattern, negate):
1051 return not matches(f, pattern, False)
1053 return pattern is None or pattern.match(f)
1056 def size_of(f, pattern, negate):
1057 if os.path.isfile(f) and matches(f, pattern, negate):
1058 return os.stat(f)[6]
1060 return size_of_dir(f, pattern, negate)
1063 def print_capabilities():
1064 document = getDOMImplementation().createDocument(
1065 "ns", CAP_XML_ROOT, None)
1066 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1067 print document.toprettyxml()
1069 def capability(document, key):
1071 el = document.createElement(CAP_XML_ELEMENT)
1072 el.setAttribute('key', c[KEY])
1073 el.setAttribute('pii', c[PII])
1074 el.setAttribute('min-size', str(c[MIN_SIZE]))
1075 el.setAttribute('max-size', str(c[MAX_SIZE]))
1076 el.setAttribute('min-time', str(c[MIN_TIME]))
1077 el.setAttribute('max-time', str(c[MAX_TIME]))
1078 el.setAttribute('content-type', c[MIME])
1079 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1080 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1084 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1085 return '\n'.join([format % i for i in d.items()]) + '\n'
1089 yn = raw_input(prompt)
1091 return len(yn) == 0 or yn.lower()[0] == 'y'
1094 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1099 f = open('/proc/partitions')
1102 for line in f.readlines():
1103 (major, minor, blocks, name) = line.split()
1104 if int(major) < 254 and not partition_re.match(name):
1115 def __init__(self, command, max_time, inst=None, filter=None, binary=False):
1116 self.command = command
1117 self.max_time = max_time
1119 self.running = False
1121 self.timed_out = False
1123 self.timeout = int(time.time()) + self.max_time
1124 self.filter = filter
1125 self.filter_state = {}
1127 self.bufsize = 1048576 # 1MB buffer
1129 self.bufsize = 1 # line buffered
1135 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1138 self.timed_out = False
1140 if ProcOutput.debug:
1141 output_ts("Starting '%s'" % self.cmdAsStr())
1142 self.proc = Popen(self.command, bufsize=self.bufsize,
1143 stdin=dev_null, stdout=PIPE, stderr=dev_null,
1144 shell=isinstance(self.command, str))
1145 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1146 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1150 output_ts("'%s' failed" % self.cmdAsStr())
1151 self.running = False
1154 def terminate(self):
1157 self.proc.stdout.close()
1158 os.kill(self.proc.pid, SIGTERM)
1162 self.running = False
1163 self.status = SIGTERM
1165 def read_line(self):
1167 if self.bufsize == 1:
1168 line = self.proc.stdout.readline()
1170 line = self.proc.stdout.read(self.bufsize)
1173 self.proc.stdout.close()
1174 self.status = self.proc.wait()
1176 self.running = False
1179 line = self.filter(line, self.filter_state)
1181 self.inst.write(line)
1183 def run_procs(procs):
1191 active_procs.append(p)
1192 pipes.append(p.proc.stdout)
1194 elif p.status == None and not p.failed and not p.timed_out:
1197 active_procs.append(p)
1198 pipes.append(p.proc.stdout)
1205 (i, o, x) = select(pipes, [], [], 1.0)
1206 now = int(time.time())
1208 # handle process output
1209 for p in active_procs:
1210 if p.proc.stdout in i:
1214 if p.running and now > p.timeout:
1215 output_ts("'%s' timed out" % p.cmdAsStr())
1217 p.inst.write("\n** timeout **\n")
1225 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1227 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1235 class StringIOmtime(StringIO.StringIO):
1236 def __init__(self, buf=''):
1237 StringIO.StringIO.__init__(self, buf)
1238 self.mtime = time.time()
1241 StringIO.StringIO.write(self, s)
1242 self.mtime = time.time()
1245 if __name__ == "__main__":
1248 except KeyboardInterrupt:
1249 print "\nInterrupted."